Skip to main content

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