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

Rebase on PR #37 #58

Merged
merged 21 commits into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
c149bb9
Add token and QUIC packet type support
sippejw Jul 11, 2024
61eeff9
Improve length validation, add offset to short header branch
sippejw Jul 11, 2024
2984ea3
Create data access function
sippejw Jul 12, 2024
a940f0e
BUG: Add further validation to token_len calculation on retry packets
sippejw Jul 12, 2024
377b651
First attempt at QUIC payload decryption
sippejw Jul 15, 2024
9a83103
Frame parsing and refactor of QuicError
sippejw Jul 16, 2024
2307b1b
Formatting and clippy
sippejw Jul 16, 2024
67f73b1
Add ClientHello parsing and CRYPTO frame reassembly
sippejw Jul 19, 2024
b17d9f0
First take on multi packet connection tracking
sippejw Jul 24, 2024
5a0d1c8
Parse ServerHello, move all TLS parsing to QuicConn Tls object
sippejw Jul 24, 2024
7adf47b
Parse multiple QUIC packets from single UDP datagram
sippejw Jul 24, 2024
30c891c
Add CRYPTO buffers allowing reassembly of long TLS messages
sippejw Jul 25, 2024
d4958d3
Comments
sippejw Jul 25, 2024
dc28ffd
Stop parsing connection after first short header packet
sippejw Jul 26, 2024
eb53926
Clippy
sippejw Jul 26, 2024
334057a
update parser dependency to stanford-esrg owned versions
thearossman Sep 5, 2024
5c339af
Assign RSS offload type with NIC supported values
sippejw Sep 5, 2024
952ff87
Add additional state for UDP connections aging out
thearossman Sep 5, 2024
0c04859
Move mlx5 feature dependency to example files
thearossman Sep 7, 2024
bfbe3d7
Remove rust-crypto, replace with aes-gcm
thearossman Sep 7, 2024
80aca43
Merge fix
sippejw Sep 9, 2024
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
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
Loading