Skip to main content

iris_core/protocols/stream/quic/
parser.rs

1//! Quic Header parser
2//! Custom Quic Parser with many design choices borrowed from
3//! [Wireshark Quic Disector](https://gitlab.com/wireshark/wireshark/-/blob/master/epan/dissectors/packet-quic.c)
4//!
5use crate::protocols::stream::quic::crypto::calc_init_keys;
6use crate::protocols::stream::quic::frame::QuicFrame;
7use crate::protocols::stream::quic::header::{
8    LongHeaderPacketType, QuicLongHeader, QuicShortHeader,
9};
10use crate::protocols::stream::quic::{QuicError, QuicPacket};
11use crate::protocols::stream::tls::Tls;
12use crate::protocols::stream::{
13    ConnParsable, L4Pdu, ParseResult, ParsingState, ProbeResult, Session, SessionData,
14};
15use byteorder::{BigEndian, ByteOrder};
16use std::collections::HashSet;
17use tls_parser::parse_tls_message_handshake;
18
19use super::QuicConn;
20
21#[derive(Debug)]
22pub struct QuicParser {
23    // /// Maps session ID to Quic transaction
24    // sessions: HashMap<usize, QuicPacket>,
25    // /// Total sessions ever seen (Running session ID)
26    // cnt: usize,
27    sessions: Vec<QuicConn>,
28}
29
30impl Default for QuicParser {
31    fn default() -> Self {
32        QuicParser {
33            sessions: vec![QuicConn::new()],
34        }
35    }
36}
37
38impl ConnParsable for QuicParser {
39    fn parse(&mut self, pdu: &L4Pdu) -> ParseResult {
40        let offset = pdu.offset();
41        let length = pdu.length();
42        if length == 0 {
43            return ParseResult::Skipped;
44        }
45
46        if let Ok(data) = (pdu.mbuf_ref()).get_data_slice(offset, length) {
47            if !self.sessions.is_empty() {
48                return self.sessions[0].parse_packet(data, pdu.dir);
49            }
50            ParseResult::Skipped
51        } else {
52            log::warn!("Malformed packet on parse");
53            ParseResult::Skipped
54        }
55    }
56
57    fn probe(&self, pdu: &L4Pdu) -> ProbeResult {
58        if pdu.length() < 5 {
59            return ProbeResult::Unsure;
60        }
61
62        let offset = pdu.offset();
63        let length = pdu.length();
64
65        if let Ok(data) = (pdu.mbuf).get_data_slice(offset, length) {
66            // Check if Fixed Bit is set
67            if (data[0] & 0x40) == 0 {
68                return ProbeResult::NotForUs;
69            }
70
71            if (data[0] & 0x80) != 0 {
72                // Potential Long Header
73                if data.len() < 6 {
74                    return ProbeResult::Unsure;
75                }
76
77                // Check if version is known
78                let version = ((data[1] as u32) << 24)
79                    | ((data[2] as u32) << 16)
80                    | ((data[3] as u32) << 8)
81                    | (data[4] as u32);
82                match QuicVersion::from_u32(version) {
83                    QuicVersion::Unknown => ProbeResult::NotForUs,
84                    _ => ProbeResult::Certain,
85                }
86            } else {
87                ProbeResult::Unsure
88            }
89        } else {
90            log::warn!("Malformed packet");
91            ProbeResult::Error
92        }
93    }
94
95    fn remove_session(&mut self, session_id: usize) -> Option<Session> {
96        self.sessions.pop().map(|quic| Session {
97            data: SessionData::Quic(Box::new(quic)),
98            id: session_id,
99        })
100    }
101
102    fn drain_sessions(&mut self) -> Vec<Session> {
103        self.sessions
104            .drain(..)
105            .map(|quic| Session {
106                data: SessionData::Quic(Box::new(quic)),
107                id: 0,
108            })
109            .collect()
110    }
111
112    fn session_parsed_state(&self) -> ParsingState {
113        ParsingState::Parsing
114    }
115
116    // Temporary - not supported for QUIC parser.
117    fn body_offset(&mut self) -> Option<usize> {
118        None
119    }
120}
121
122/// Supported Quic Versions
123#[derive(Debug, PartialEq, Eq, Hash)]
124#[repr(u32)]
125pub enum QuicVersion {
126    ReservedNegotiation = 0x00000000,
127    Rfc9000 = 0x00000001, // Quic V1
128    Rfc9369 = 0x6b3343cf, // Quic V2
129    Draft27 = 0xff00001b, // Quic draft 27
130    Draft28 = 0xff00001c, // Quic draft 28
131    Draft29 = 0xff00001d, // Quic draft 29
132    Mvfst27 = 0xfaceb002, // Facebook Implementation of draft 27
133    Unknown,
134}
135
136impl QuicVersion {
137    pub fn from_u32(version: u32) -> Self {
138        match version {
139            0x00000000 => QuicVersion::ReservedNegotiation,
140            0x00000001 => QuicVersion::Rfc9000,
141            0x6b3343cf => QuicVersion::Rfc9369,
142            0xff00001b => QuicVersion::Draft27,
143            0xff00001c => QuicVersion::Draft28,
144            0xff00001d => QuicVersion::Draft29,
145            0xfaceb002 => QuicVersion::Mvfst27,
146            _ => QuicVersion::Unknown,
147        }
148    }
149}
150
151impl QuicPacket {
152    /// Processes the connection ID bytes array to a hex string
153    pub fn vec_u8_to_hex_string(vec: &[u8]) -> String {
154        vec.iter()
155            .map(|&byte| format!("{:02x}", byte))
156            .collect::<Vec<String>>()
157            .join("")
158    }
159
160    // Calculate the length of a variable length encoding
161    // See RFC 9000 Section 16 for details
162    pub fn get_var_len(a: u8) -> Result<usize, QuicError> {
163        let two_msb = a >> 6;
164        match two_msb {
165            0b00 => Ok(1),
166            0b01 => Ok(2),
167            0b10 => Ok(4),
168            0b11 => Ok(8),
169            _ => Err(QuicError::UnsupportedVarLen),
170        }
171    }
172
173    // Masks variable length encoding and returns u64 value for remainder of field
174    pub fn slice_to_u64(data: &[u8]) -> Result<u64, QuicError> {
175        if data.len() > 8 {
176            return Err(QuicError::UnsupportedVarLen);
177        }
178
179        let mut result: u64 = 0;
180        for &byte in data {
181            result = (result << 8) | u64::from(byte);
182        }
183        result &= !(0b11 << ((data.len() * 8) - 2)); // Var length encoding mask
184        Ok(result)
185    }
186
187    pub fn access_data(data: &[u8], start: usize, end: usize) -> Result<&[u8], QuicError> {
188        if end < start {
189            return Err(QuicError::InvalidDataIndices);
190        }
191        if data.len() < end {
192            return Err(QuicError::PacketTooShort);
193        }
194        Ok(&data[start..end])
195    }
196
197    /// Parses Quic packet from bytes
198    pub fn parse_from(
199        conn: &mut QuicConn,
200        data: &[u8],
201        mut offset: usize,
202        dir: bool,
203    ) -> Result<(QuicPacket, usize), QuicError> {
204        let packet_header_byte = QuicPacket::access_data(data, offset, offset + 1)?[0];
205        offset += 1;
206        // Check the fixed bit
207        if (packet_header_byte & 0x40) == 0 {
208            return Err(QuicError::FixedBitNotSet);
209        }
210        // Check the Header form
211        if (packet_header_byte & 0x80) != 0 {
212            // Long Header
213            // Parse packet type
214            let packet_type = LongHeaderPacketType::from_u8((packet_header_byte & 0x30) >> 4)?;
215            let type_specific = packet_header_byte & 0x0F; // Remainder of information from header byte, Reserved and protected packet number length
216                                                           // Parse version
217            let version_bytes = QuicPacket::access_data(data, offset, offset + 4)?;
218            let version = ((version_bytes[0] as u32) << 24)
219                | ((version_bytes[1] as u32) << 16)
220                | ((version_bytes[2] as u32) << 8)
221                | (version_bytes[3] as u32);
222            if QuicVersion::from_u32(version) == QuicVersion::Unknown {
223                return Err(QuicError::UnknownVersion);
224            }
225            offset += 4;
226            // Parse DCID
227            let dcid_len = QuicPacket::access_data(data, offset, offset + 1)?[0];
228            offset += 1;
229            let dcid_bytes = QuicPacket::access_data(data, offset, offset + dcid_len as usize)?;
230            let dcid = QuicPacket::vec_u8_to_hex_string(dcid_bytes);
231            if dcid_len > 0 && !conn.cids.contains(&dcid) {
232                conn.cids.insert(dcid.clone());
233            }
234            offset += dcid_len as usize;
235            // Parse SCID
236            let scid_len = QuicPacket::access_data(data, offset, offset + 1)?[0];
237            offset += 1;
238            let scid_bytes = QuicPacket::access_data(data, offset, offset + scid_len as usize)?;
239            let scid = QuicPacket::vec_u8_to_hex_string(scid_bytes);
240            if scid_len > 0 && !conn.cids.contains(&scid) {
241                conn.cids.insert(scid.clone());
242            }
243            offset += scid_len as usize;
244
245            let token_len;
246            let token;
247            let packet_len;
248            let retry_tag;
249            let decrypted_payload;
250            // Parse packet type specific fields
251            match packet_type {
252                LongHeaderPacketType::Initial => {
253                    retry_tag = None;
254                    // Parse token
255                    let token_len_len = QuicPacket::get_var_len(
256                        QuicPacket::access_data(data, offset, offset + 1)?[0],
257                    )?;
258                    let token_len_bytes =
259                        QuicPacket::access_data(data, offset, offset + token_len_len)?;
260                    token_len = Some(QuicPacket::slice_to_u64(token_len_bytes)?);
261                    offset += token_len_len;
262                    let token_bytes = QuicPacket::access_data(
263                        data,
264                        offset,
265                        offset + token_len.unwrap() as usize,
266                    )?;
267                    token = Some(QuicPacket::vec_u8_to_hex_string(token_bytes));
268                    offset += token_len.unwrap() as usize;
269                    // Parse payload length
270                    let packet_len_len = QuicPacket::get_var_len(
271                        QuicPacket::access_data(data, offset, offset + 1)?[0],
272                    )?;
273                    let packet_len_bytes =
274                        QuicPacket::access_data(data, offset, offset + packet_len_len)?;
275                    packet_len = Some(QuicPacket::slice_to_u64(packet_len_bytes)?);
276                    offset += packet_len_len;
277                    if conn.client_opener.is_none() {
278                        // Derive initial keys
279                        let [client_opener, server_opener] = calc_init_keys(dcid_bytes, version)?;
280                        conn.client_opener = Some(client_opener);
281                        conn.server_opener = Some(server_opener);
282                    }
283                    // Calculate HP
284                    let sample_len = conn.client_opener.as_ref().unwrap().sample_len();
285                    let hp_sample =
286                        QuicPacket::access_data(data, offset + 4, offset + 4 + sample_len)?;
287                    let mask = if dir {
288                        conn.client_opener.as_ref().unwrap().new_mask(hp_sample)?
289                    } else {
290                        conn.server_opener.as_ref().unwrap().new_mask(hp_sample)?
291                    };
292                    // Remove HP from packet header byte
293                    let unprotected_header = packet_header_byte ^ (mask[0] & 0b00001111);
294                    if (unprotected_header >> 2) & 0b00000011 != 0 {
295                        return Err(QuicError::FailedHeaderProtection);
296                    }
297                    // Parse packet number
298                    let packet_num_len = ((unprotected_header & 0b00000011) + 1) as usize;
299                    let packet_number_bytes =
300                        QuicPacket::access_data(data, offset, offset + packet_num_len)?;
301                    let mut packet_number = vec![0; 4 - packet_num_len];
302                    for i in 0..packet_num_len {
303                        packet_number.push(packet_number_bytes[i] ^ mask[i + 1]);
304                    }
305
306                    let initial_packet_number_bytes = &packet_number[4 - packet_num_len..];
307                    let packet_number_int = BigEndian::read_i32(&packet_number);
308                    offset += packet_num_len;
309                    // Parse the encrypted payload
310                    let tag_len = conn.client_opener.as_ref().unwrap().alg().tag_len();
311                    if (packet_len.unwrap() as usize) < (tag_len + packet_num_len) {
312                        return Err(QuicError::PacketTooShort);
313                    }
314                    let cipher_text_len = packet_len.unwrap() as usize - tag_len - packet_num_len;
315                    let mut encrypted_payload =
316                        QuicPacket::access_data(data, offset, offset + cipher_text_len)?.to_vec();
317                    offset += cipher_text_len;
318                    // Parse auth tag
319                    let tag = QuicPacket::access_data(data, offset, offset + tag_len)?;
320                    offset += tag_len;
321                    // Reconstruct authenticated data
322                    let mut ad = Vec::new();
323                    ad.append(&mut [unprotected_header].to_vec());
324                    ad.append(&mut version_bytes.to_vec());
325                    ad.append(&mut [dcid_len].to_vec());
326                    ad.append(&mut dcid_bytes.to_vec());
327                    ad.append(&mut [scid_len].to_vec());
328                    ad.append(&mut scid_bytes.to_vec());
329                    ad.append(&mut token_len_bytes.to_vec());
330                    ad.append(&mut token_bytes.to_vec());
331                    ad.append(&mut packet_len_bytes.to_vec());
332                    ad.append(&mut initial_packet_number_bytes.to_vec());
333                    // Decrypt payload with proper keys based on traffic direction
334                    if dir {
335                        decrypted_payload =
336                            Some(conn.client_opener.as_ref().unwrap().open_with_u64_counter(
337                                packet_number_int as u64,
338                                &ad,
339                                &mut encrypted_payload,
340                                tag,
341                            )?);
342                    } else {
343                        decrypted_payload =
344                            Some(conn.server_opener.as_ref().unwrap().open_with_u64_counter(
345                                packet_number_int as u64,
346                                &ad,
347                                &mut encrypted_payload,
348                                tag,
349                            )?);
350                    }
351                }
352                LongHeaderPacketType::ZeroRTT | LongHeaderPacketType::Handshake => {
353                    token_len = None;
354                    token = None;
355                    retry_tag = None;
356                    decrypted_payload = None;
357                    // Parse payload length
358                    let packet_len_len = QuicPacket::get_var_len(
359                        QuicPacket::access_data(data, offset, offset + 1)?[0],
360                    )?;
361                    packet_len = Some(QuicPacket::slice_to_u64(QuicPacket::access_data(
362                        data,
363                        offset,
364                        offset + packet_len_len,
365                    )?)?);
366                    offset += packet_len_len;
367                    offset += packet_len.unwrap() as usize;
368                }
369                LongHeaderPacketType::Retry => {
370                    packet_len = None;
371                    decrypted_payload = None;
372                    if data.len() > (offset + 16) {
373                        token_len = Some((data.len() - offset - 16) as u64);
374                    } else {
375                        return Err(QuicError::PacketTooShort);
376                    }
377                    // Parse retry token
378                    let token_bytes = QuicPacket::access_data(
379                        data,
380                        offset,
381                        offset + token_len.unwrap() as usize,
382                    )?;
383                    token = Some(QuicPacket::vec_u8_to_hex_string(token_bytes));
384                    offset += token_len.unwrap() as usize;
385                    // Parse retry tag
386                    let retry_tag_bytes = QuicPacket::access_data(data, offset, offset + 16)?;
387                    retry_tag = Some(QuicPacket::vec_u8_to_hex_string(retry_tag_bytes));
388                    offset += 16;
389                }
390            }
391
392            let mut frames: Option<Vec<QuicFrame>> = None;
393            // Grab the proper buffer for CRYPTO frame data
394            let crypto_buffer: &mut Vec<u8> = if dir {
395                conn.client_buffer.as_mut()
396            } else {
397                conn.server_buffer.as_mut()
398            };
399            // If decrypted payload is not None, parse the frames
400            if let Some(frame_bytes) = decrypted_payload {
401                // Get frames and reassembled CRYPTO data
402                // Pass the buffer's current length as starting offset for CRYPTO frames
403                let (q_frames, mut crypto_bytes) =
404                    QuicFrame::parse_frames(&frame_bytes, crypto_buffer.len())?;
405                frames = Some(q_frames);
406                if !crypto_bytes.is_empty() {
407                    crypto_buffer.append(&mut crypto_bytes);
408                    // Attempt to parse CRYPTO buffer
409                    // clear on success
410                    // NICE-TO-HAVE: This naive buffer will not work for out of order frames
411                    // across packets or multiple messages in the same buffer
412                    match parse_tls_message_handshake(crypto_buffer) {
413                        Ok((_, msg)) => {
414                            conn.tls.parse_message_level(&msg, dir);
415                            crypto_buffer.clear();
416                        }
417                        Err(_) => return Err(QuicError::TlsParseFail),
418                    }
419                }
420            }
421
422            Ok((
423                QuicPacket {
424                    payload_bytes_count: packet_len,
425                    short_header: None,
426                    long_header: Some(QuicLongHeader {
427                        packet_type,
428                        type_specific,
429                        version,
430                        dcid_len,
431                        dcid,
432                        scid_len,
433                        scid,
434                        token_len,
435                        token,
436                        retry_tag,
437                    }),
438                    frames,
439                },
440                offset,
441            ))
442        } else {
443            // Short Header
444            let mut dcid_len = 20;
445            if data.len() < 1 + dcid_len {
446                dcid_len = data.len() - 1;
447            }
448            // Parse DCID
449            let dcid_hex = QuicPacket::vec_u8_to_hex_string(QuicPacket::access_data(
450                data,
451                offset,
452                offset + dcid_len,
453            )?);
454            let mut dcid = None;
455            for cid in &conn.cids {
456                if dcid_hex.starts_with(cid) {
457                    dcid_len = cid.chars().count() / 2;
458                    dcid = Some(cid.clone());
459                }
460            }
461            offset += dcid_len;
462            // Counts all bytes remaining
463            let payload_bytes_count = (data.len() - offset) as u64;
464            offset += payload_bytes_count as usize;
465            Ok((
466                QuicPacket {
467                    short_header: Some(QuicShortHeader { dcid }),
468                    long_header: None,
469                    payload_bytes_count: Some(payload_bytes_count),
470                    frames: None,
471                },
472                offset,
473            ))
474        }
475    }
476}
477
478impl QuicConn {
479    pub(crate) fn new() -> QuicConn {
480        QuicConn {
481            packets: Vec::new(),
482            cids: HashSet::new(),
483            tls: Tls::new(),
484            client_opener: None,
485            server_opener: None,
486            client_buffer: Vec::new(),
487            server_buffer: Vec::new(),
488        }
489    }
490
491    fn parse_packet(&mut self, data: &[u8], direction: bool) -> ParseResult {
492        let mut offset = 0;
493        // Iterate over all of the data in the datagram
494        // Parse as many QUIC packets as possible
495        // NICE-TO-HAVE: identify padding appended to datagram
496        while data.len() > offset {
497            if let Ok((quic, off)) = QuicPacket::parse_from(self, data, offset, direction) {
498                self.packets.push(quic);
499                offset = off;
500            } else {
501                return ParseResult::Skipped;
502            }
503        }
504        if self
505            .packets
506            .last()
507            .is_some_and(|p| p.short_header.is_some())
508        {
509            return ParseResult::HeadersDone(0);
510        }
511        ParseResult::Continue(0)
512    }
513}