Skip to main content

retina_core/subscription/
quic_stream.rs

1//! QUIC streams.
2//!
3//! This is a session-level subscription that delivers parsed QUIC stream records and associated
4//! connection metadata.
5//!
6
7use crate::conntrack::conn_id::FiveTuple;
8use crate::conntrack::pdu::{L4Context, L4Pdu};
9use crate::conntrack::ConnTracker;
10use crate::filter::FilterResult;
11use crate::memory::mbuf::Mbuf;
12use crate::protocols::stream::quic::parser::QuicParser;
13use crate::protocols::stream::quic::QuicConn;
14use crate::protocols::stream::{ConnParser, Session, SessionData};
15use crate::subscription::{Level, Subscribable, Subscription, Trackable};
16use std::collections::HashSet;
17
18use serde::Serialize;
19
20use std::net::SocketAddr;
21
22/// A parsed QUIC stream and connection metadata.
23#[derive(Debug, Serialize)]
24pub struct QuicStream {
25    pub five_tuple: FiveTuple,
26    pub data: QuicConn,
27}
28
29impl QuicStream {
30    /// Returns the QUIC client's socket address.
31    #[inline]
32    pub fn client(&self) -> SocketAddr {
33        self.five_tuple.orig
34    }
35
36    /// Returns the QUIC server's socket address.
37    #[inline]
38    pub fn server(&self) -> SocketAddr {
39        self.five_tuple.resp
40    }
41}
42
43impl Subscribable for QuicStream {
44    type Tracked = TrackedQuic;
45
46    fn level() -> Level {
47        Level::Session
48    }
49
50    fn parsers() -> Vec<ConnParser> {
51        vec![ConnParser::Quic(QuicParser::default())]
52    }
53
54    fn process_packet(
55        mbuf: Mbuf,
56        subscription: &Subscription<Self>,
57        conn_tracker: &mut ConnTracker<Self::Tracked>,
58    ) {
59        match subscription.filter_packet(&mbuf) {
60            FilterResult::MatchTerminal(idx) | FilterResult::MatchNonTerminal(idx) => {
61                if let Ok(ctxt) = L4Context::new(&mbuf, idx) {
62                    conn_tracker.process(mbuf, ctxt, subscription);
63                }
64            }
65            FilterResult::NoMatch => drop(mbuf),
66        }
67    }
68}
69
70/// Represents a QUIC connection during the connection lifetime.
71///
72/// ## Remarks
73/// Retina uses an internal parser to track and filter application-layer protocols, and transfers
74/// session ownership to the subscription to invoke the callback on a filter match. This is an
75/// optimization to avoid double-parsing: once for the filter and once for the subscription data.
76/// This is why most `Trackable` trait methods for this type are unimplemented.
77///
78/// ## Note
79/// Internal connection state is an associated type of a `pub` trait, and therefore must also be
80/// public. Documentation is hidden by default to avoid confusing users.
81#[doc(hidden)]
82pub struct TrackedQuic {
83    five_tuple: FiveTuple,
84    connection_id: HashSet<String>,
85}
86
87impl Trackable for TrackedQuic {
88    type Subscribed = QuicStream;
89
90    fn new(five_tuple: FiveTuple) -> Self {
91        TrackedQuic {
92            five_tuple,
93            connection_id: HashSet::new(),
94        }
95    }
96
97    fn pre_match(&mut self, _pdu: L4Pdu, _session_id: Option<usize>) {}
98
99    fn on_match(&mut self, session: Session, subscription: &Subscription<Self::Subscribed>) {
100        if let SessionData::Quic(quic) = session.data {
101            let quic_clone = *quic;
102            for cid in &quic_clone.cids {
103                self.connection_id.insert(cid.to_string());
104            }
105
106            subscription.invoke(QuicStream {
107                five_tuple: self.five_tuple,
108                data: quic_clone,
109            });
110        }
111    }
112
113    fn post_match(&mut self, _pdu: L4Pdu, _subscription: &Subscription<Self::Subscribed>) {}
114
115    fn on_terminate(&mut self, _subscription: &Subscription<Self::Subscribed>) {
116        self.connection_id.clear();
117    }
118}