Skip to content

Commit

Permalink
Merge pull request #58 from sippejw/main
Browse files Browse the repository at this point in the history
Rebase on PR #37
  • Loading branch information
thearossman authored Sep 10, 2024
2 parents c3e1885 + 80aca43 commit 71a754d
Show file tree
Hide file tree
Showing 9 changed files with 572 additions and 138 deletions.
5 changes: 3 additions & 2 deletions core/src/protocols/stream/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub mod tls;

use self::dns::{parser::DnsParser, Dns};
use self::http::{parser::HttpParser, Http};
use self::quic::{parser::QuicParser, QuicPacket};
use self::quic::parser::QuicParser;
use self::tls::{parser::TlsParser, Tls};
use crate::conntrack::conn::conn_info::ConnState;
use crate::conntrack::conn_id::FiveTuple;
Expand All @@ -22,6 +22,7 @@ use crate::subscription::*;
use std::str::FromStr;

use anyhow::{bail, Result};
use quic::QuicConn;
use strum_macros::EnumString;

/// Represents the result of parsing one packet as a protocol message.
Expand Down Expand Up @@ -195,7 +196,7 @@ pub enum SessionData {
Tls(Box<Tls>),
Dns(Box<Dns>),
Http(Box<Http>),
Quic(Box<QuicPacket>),
Quic(Box<QuicConn>),
Null,
}

Expand Down
241 changes: 241 additions & 0 deletions core/src/protocols/stream/quic/frame.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
// QUIC Frame types and parsing
// Implemented per RFC 9000: https://datatracker.ietf.org/doc/html/rfc9000#name-frame-types-and-formats

use serde::Serialize;
use std::collections::BTreeMap;

use crate::protocols::stream::quic::QuicError;
use crate::protocols::stream::quic::QuicPacket;

// Types of supported QUIC frames
// Currently only includes those seen in the Init and Handshake packets
#[derive(Debug, Serialize, Clone)]
pub enum QuicFrame {
Padding {
length: usize,
},
Ping,
Ack {
largest_acknowledged: u64,
ack_delay: u64,
first_ack_range: u64,
ack_ranges: Vec<AckRange>,
ecn_counts: Option<EcnCounts>,
},
Crypto {
offset: u64,
},
}

// ACK Range field, part of ACK frame
// https://datatracker.ietf.org/doc/html/rfc9000#ack-range-format
#[derive(Debug, Serialize, Clone)]
pub struct AckRange {
gap: u64,
ack_range_len: u64,
}

// ECN Counts field, part of some ACK frames
// https://datatracker.ietf.org/doc/html/rfc9000#ecn-count-format
#[derive(Debug, Serialize, Clone)]
pub struct EcnCounts {
ect0_count: u64,
ect1_count: u64,
ecn_ce_count: u64,
}

impl QuicFrame {
// parse_frames takes the plaintext QUIC packet payload and parses the frame list
// it also returns the reassembled CRYPTO frame bytes as a Vec<u8>
pub fn parse_frames(
data: &[u8],
mut expected_offset: usize,
) -> Result<(Vec<QuicFrame>, Vec<u8>), QuicError> {
let mut frames: Vec<QuicFrame> = Vec::new();
let mut crypto_map: BTreeMap<usize, Vec<u8>> = BTreeMap::new();
let mut offset = 0;
// Iterate over plaintext payload bytes, this is a list of frames
while offset < data.len() {
// Parse frame type
let frame_type_len =
QuicPacket::get_var_len(QuicPacket::access_data(data, offset, offset + 1)?[0])?;
let frame_type = QuicPacket::slice_to_u64(QuicPacket::access_data(
data,
offset,
offset + frame_type_len,
)?)?;
offset += frame_type_len;
match frame_type {
0x00 => {
// Handle PADDING
let mut length = 0;
while offset + length + 1 < data.len()
&& QuicPacket::access_data(data, offset + length, offset + length + 1)?[0]
== 0
{
length += 1;
}
offset += length;
length += frame_type_len; // Add the original frame type bytes to length. Wireshark also does this
frames.push(QuicFrame::Padding { length });
}
0x01 => {
// Handle PING
frames.push(QuicFrame::Ping);
}
0x02 | 0x03 => {
// Handle ACK
// Parse Largest Acknowledged
let largest_acknowledged_len = QuicPacket::get_var_len(
QuicPacket::access_data(data, offset, offset + 1)?[0],
)?;
let largest_acknowledged = QuicPacket::slice_to_u64(QuicPacket::access_data(
data,
offset,
offset + largest_acknowledged_len,
)?)?;
offset += largest_acknowledged_len;
// Parse ACK Delay
let ack_delay_len = QuicPacket::get_var_len(
QuicPacket::access_data(data, offset, offset + 1)?[0],
)?;
let ack_delay = QuicPacket::slice_to_u64(QuicPacket::access_data(
data,
offset,
offset + ack_delay_len,
)?)?;
offset += ack_delay_len;
// Parse ACK Range Count
let ack_range_count_len = QuicPacket::get_var_len(
QuicPacket::access_data(data, offset, offset + 1)?[0],
)?;
let ack_range_count = QuicPacket::slice_to_u64(QuicPacket::access_data(
data,
offset,
offset + ack_range_count_len,
)?)?;
offset += ack_range_count_len;
// Parse First ACK Range
let first_ack_range_len = QuicPacket::get_var_len(
QuicPacket::access_data(data, offset, offset + 1)?[0],
)?;
let first_ack_range = QuicPacket::slice_to_u64(QuicPacket::access_data(
data,
offset,
offset + first_ack_range_len,
)?)?;
offset += first_ack_range_len;
// Parse ACK Range list field
let mut ack_ranges = Vec::new();
for _ in 0..ack_range_count {
let gap_len = QuicPacket::get_var_len(
QuicPacket::access_data(data, offset, offset + 1)?[0],
)?;
let gap = QuicPacket::slice_to_u64(QuicPacket::access_data(
data,
offset,
offset + gap_len,
)?)?;
offset += gap_len;
let ack_range_len_len = QuicPacket::get_var_len(
QuicPacket::access_data(data, offset, offset + 1)?[0],
)?;
let ack_range_len = QuicPacket::slice_to_u64(QuicPacket::access_data(
data,
offset,
offset + ack_range_len_len,
)?)?;
offset += ack_range_len_len;
ack_ranges.push(AckRange { gap, ack_range_len })
}
// Parse ECN Counts, if the ACK frame contains them
let ecn_counts: Option<EcnCounts> = if frame_type == 0x03 {
let ect0_count_len = QuicPacket::get_var_len(
QuicPacket::access_data(data, offset, offset + 1)?[0],
)?;
let ect0_count = QuicPacket::slice_to_u64(QuicPacket::access_data(
data,
offset,
offset + ect0_count_len,
)?)?;
offset += ect0_count_len;
let ect1_count_len = QuicPacket::get_var_len(
QuicPacket::access_data(data, offset, offset + 1)?[0],
)?;
let ect1_count = QuicPacket::slice_to_u64(QuicPacket::access_data(
data,
offset,
offset + ect1_count_len,
)?)?;
offset += ect1_count_len;
let ecn_ce_count_len = QuicPacket::get_var_len(
QuicPacket::access_data(data, offset, offset + 1)?[0],
)?;
let ecn_ce_count = QuicPacket::slice_to_u64(QuicPacket::access_data(
data,
offset,
offset + ecn_ce_count_len,
)?)?;
Some(EcnCounts {
ect0_count,
ect1_count,
ecn_ce_count,
})
} else {
None
};
frames.push(QuicFrame::Ack {
largest_acknowledged,
ack_delay,
first_ack_range,
ack_ranges,
ecn_counts,
})
}
0x06 => {
// Handle CRYPTO frame
// Parse offset
let crypto_offset_len = QuicPacket::get_var_len(
QuicPacket::access_data(data, offset, offset + 1)?[0],
)?;
let crypto_offset = QuicPacket::slice_to_u64(QuicPacket::access_data(
data,
offset,
offset + crypto_offset_len,
)?)?;
offset += crypto_offset_len;
// Parse length
let crypto_len_len = QuicPacket::get_var_len(
QuicPacket::access_data(data, offset, offset + 1)?[0],
)?;
let crypto_len = QuicPacket::slice_to_u64(QuicPacket::access_data(
data,
offset,
offset + crypto_len_len,
)?)? as usize;
offset += crypto_len_len;
// Parse data
let crypto_data =
QuicPacket::access_data(data, offset, offset + crypto_len)?.to_vec();
crypto_map
.entry(crypto_offset as usize)
.or_insert(crypto_data);
frames.push(QuicFrame::Crypto {
offset: crypto_offset,
});
offset += crypto_len;
}
_ => return Err(QuicError::UnknownFrameType),
}
}
let mut reassembled_crypto: Vec<u8> = Vec::new();
for (crypto_offset, crypto_data) in crypto_map {
if crypto_offset != expected_offset {
return Err(QuicError::MissingCryptoFrames);
}
expected_offset += crypto_data.len();
reassembled_crypto.extend(crypto_data);
}
Ok((frames, reassembled_crypto))
}
}
5 changes: 1 addition & 4 deletions core/src/protocols/stream/quic/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
use serde::Serialize;

use crate::protocols::stream::quic::parser::QuicError;
use crate::protocols::stream::quic::QuicError;

/// Quic Long Header
#[derive(Debug, Serialize, Clone)]
Expand All @@ -23,9 +23,6 @@ pub struct QuicLongHeader {
#[derive(Debug, Serialize, Clone)]
pub struct QuicShortHeader {
pub dcid: Option<String>, // optional. If not pre-existing cid then none.

#[serde(skip)]
pub dcid_bytes: Vec<u8>,
}

// Long Header Packet Types from RFC 9000 Table 5
Expand Down
58 changes: 56 additions & 2 deletions core/src/protocols/stream/quic/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,66 @@ TODO: support HTTP/3
*/
pub(crate) mod parser;

use std::collections::HashSet;

pub use self::header::{QuicLongHeader, QuicShortHeader};
use crypto::Open;
use frame::QuicFrame;
use header::LongHeaderPacketType;
use parser::QuicError;
use serde::Serialize;

use super::tls::Tls;
pub(crate) mod crypto;
pub(crate) mod frame;
pub(crate) mod header;

/// Errors Thrown throughout QUIC parsing. These are handled by retina and used to skip packets.
#[derive(Debug)]
pub enum QuicError {
FixedBitNotSet,
PacketTooShort,
UnknownVersion,
ShortHeader,
UnknowLongHeaderPacketType,
NoLongHeader,
UnsupportedVarLen,
InvalidDataIndices,
CryptoFail,
FailedHeaderProtection,
UnknownFrameType,
TlsParseFail,
MissingCryptoFrames,
}

/// Parsed Quic connections
#[derive(Debug, Serialize)]
pub struct QuicConn {
// All packets associated with the connection
pub packets: Vec<QuicPacket>,

// All cids, both src and destination, seen in Long Header packets
pub cids: HashSet<String>,

// Parsed TLS messsages
pub tls: Tls,

// Crypto needed to decrypt initial packets sent by client
pub client_opener: Option<Open>,

// Crypto needed to decrypt initial packets sent by server
pub server_opener: Option<Open>,

// Client buffer for multi-packet TLS messages
#[serde(skip_serializing)]
pub client_buffer: Vec<u8>,

// Server buffer for multi-packet TLS messages
#[serde(skip_serializing)]
pub server_buffer: Vec<u8>,
}

/// Parsed Quic Packet contents
#[derive(Debug, Serialize, Clone)]
#[derive(Debug, Serialize)]
pub struct QuicPacket {
/// Quic Short header
pub short_header: Option<QuicShortHeader>,
Expand All @@ -39,6 +91,8 @@ pub struct QuicPacket {

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

pub frames: Option<Vec<QuicFrame>>,
}

impl QuicPacket {
Expand Down
Loading

0 comments on commit 71a754d

Please sign in to comment.