Skip to main content

retina_core/protocols/stream/
mod.rs

1//! Types for parsing and manipulating stream-level network protocols.
2//!
3//! Any protocol that requires parsing over multiple packets within a single connection or flow is
4//! considered a "stream-level" protocol, even if it is a datagram-based protocol in the
5//! traditional-sense.
6
7pub mod dns;
8pub mod http;
9pub mod quic;
10pub mod tls;
11
12use self::dns::{parser::DnsParser, Dns};
13use self::http::{parser::HttpParser, Http};
14use self::quic::parser::QuicParser;
15use self::tls::{parser::TlsParser, Tls};
16use crate::conntrack::conn::conn_info::ConnState;
17use crate::conntrack::conn_id::FiveTuple;
18use crate::conntrack::pdu::L4Pdu;
19use crate::filter::Filter;
20use crate::subscription::*;
21
22use std::str::FromStr;
23
24use anyhow::{bail, Result};
25use quic::QuicConn;
26use strum_macros::EnumString;
27
28/// Represents the result of parsing one packet as a protocol message.
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub(crate) enum ParseResult {
31    /// Session parsing done, check session filter. Returns the most-recently-updated session ID.
32    Done(usize),
33    /// Successfully extracted data, continue processing more packets. Returns most recently updated
34    /// session ID.
35    Continue(usize),
36    /// Parsing skipped, no data extracted.
37    Skipped,
38}
39
40/// Represents the result of a probing one packet as a protocol message type.
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub(crate) enum ProbeResult {
43    /// Segment matches the parser with great probability.
44    Certain,
45    /// Unsure if the segment matches the parser.
46    Unsure,
47    /// Segment does not match the parser.
48    NotForUs,
49    /// Error occurred during the probe. Functionally equivalent to Unsure.
50    Error,
51}
52
53/// Represents the result of probing one packet with all registered protocol parsers.
54#[derive(Debug)]
55pub(crate) enum ProbeRegistryResult {
56    /// A parser in the registry was definitively matched.
57    Some(ConnParser),
58    /// All parsers in the registry were definitively not matched.
59    None,
60    /// Unsure, continue sending more data.
61    Unsure,
62}
63
64/// The set of application-layer protocol parsers required to fulfill the subscription.
65#[derive(Debug)]
66pub(crate) struct ParserRegistry(Vec<ConnParser>);
67
68impl ParserRegistry {
69    /// Builds a new `ParserRegistry` from the `filter` and tracked subscribable type `T`.
70    pub(crate) fn build<T: Subscribable>(filter: &Filter) -> Result<ParserRegistry> {
71        let parsers = T::parsers();
72        if !parsers.is_empty() {
73            return Ok(ParserRegistry(parsers));
74        }
75
76        let mut stream_protocols = hashset! {};
77        for pattern in filter.get_patterns_flat().iter() {
78            for predicate in pattern.predicates.iter() {
79                if predicate.on_connection() {
80                    stream_protocols.insert(predicate.get_protocol().to_owned());
81                }
82            }
83        }
84
85        let mut parsers = vec![];
86        for stream_protocol in stream_protocols.iter() {
87            if let Ok(parser) = ConnParser::from_str(stream_protocol.name()) {
88                parsers.push(parser);
89            } else {
90                bail!("Unknown application-layer protocol");
91            }
92        }
93        Ok(ParserRegistry(parsers))
94    }
95
96    /// Probe the packet `pdu` with all registered protocol parsers.
97    pub(crate) fn probe_all(&self, pdu: &L4Pdu) -> ProbeRegistryResult {
98        if self.0.is_empty() {
99            return ProbeRegistryResult::None;
100        }
101        if pdu.length() == 0 {
102            return ProbeRegistryResult::Unsure;
103        }
104
105        let mut num_notmatched = 0;
106        for parser in self.0.iter() {
107            match parser.probe(pdu) {
108                ProbeResult::Certain => {
109                    return ProbeRegistryResult::Some(parser.reset_new());
110                }
111                ProbeResult::NotForUs => {
112                    num_notmatched += 1;
113                }
114                _ => (), // Unsure, Error, Reverse
115            }
116        }
117        if num_notmatched == self.0.len() {
118            ProbeRegistryResult::None
119        } else {
120            ProbeRegistryResult::Unsure
121        }
122    }
123}
124
125/// A trait all application-layer protocol parsers must implement.
126pub(crate) trait ConnParsable {
127    /// Parse the L4 protocol data unit as the parser's protocol.
128    fn parse(&mut self, pdu: &L4Pdu) -> ParseResult;
129
130    /// Probe if the L4 protocol data unit matches the parser's protocol.
131    fn probe(&self, pdu: &L4Pdu) -> ProbeResult;
132
133    /// Removes session with ID `session_id` and returns it.
134    fn remove_session(&mut self, session_id: usize) -> Option<Session>;
135
136    /// Removes all sessions in the connection parser and returns them.
137    fn drain_sessions(&mut self) -> Vec<Session>;
138
139    /// Default state to set the tracked connection to on a matched session.
140    fn session_match_state(&self) -> ConnState;
141
142    /// Default state to set the tracked connection to on a non-matched session.
143    fn session_nomatch_state(&self) -> ConnState;
144}
145
146/// Data required to filter on connections.
147///
148/// ## Note
149/// This must have `pub` visibility because it needs to be accessible by the
150/// [retina_filtergen](fixlink) crate. At time of this writing, procedural macros must be defined in
151/// a separate crate, so items that ought to be crate-private have their documentation hidden to
152/// avoid confusing users.
153#[doc(hidden)]
154#[derive(Debug)]
155pub struct ConnData {
156    /// The connection 5-tuple.
157    pub five_tuple: FiveTuple,
158    /// The protocol parser associated with the connection.
159    pub conn_parser: ConnParser,
160    /// Packet terminal node ID matched by first packet of connection.
161    pub pkt_term_node: usize,
162    /// Connection terminal node ID matched by connection after successful probe. If packet terminal
163    /// node is terminal, this is the same as the packet terminal node.
164    pub conn_term_node: usize,
165}
166
167impl ConnData {
168    /// Create a new `ConnData` from the connection `five_tuple` and the ID of the last matched node
169    /// in the filter predicate trie.
170    pub(crate) fn new(five_tuple: FiveTuple, pkt_term_node: usize) -> Self {
171        ConnData {
172            five_tuple,
173            conn_parser: ConnParser::Unknown,
174            pkt_term_node,
175            conn_term_node: pkt_term_node,
176        }
177    }
178
179    /// Returns the application-layer protocol parser associated with the connection.
180    pub fn service(&self) -> &ConnParser {
181        &self.conn_parser
182    }
183}
184
185/// Data required to filter on application-layer protocol sessions.
186///
187/// ## Note
188/// This must have `pub` visibility because it needs to be accessible by the
189/// [retina_filtergen](fixlink) crate. At time of this writing, procedural macros must be defined in
190/// a separate crate, so items that ought to be crate-private have their documentation hidden to
191/// avoid confusing users.
192#[doc(hidden)]
193#[derive(Debug)]
194pub enum SessionData {
195    // TODO: refactor to use trait objects.
196    Tls(Box<Tls>),
197    Dns(Box<Dns>),
198    Http(Box<Http>),
199    Quic(Box<QuicConn>),
200    Null,
201}
202
203/// An application-layer protocol session.
204///
205/// ## Note
206/// This must have `pub` visibility because it needs to be accessible by the
207/// [retina_filtergen](fixlink) crate. At time of this writing, procedural macros must be defined in
208/// a separate crate, so items that ought to be crate-private have their documentation hidden to
209/// avoid confusing users.
210#[doc(hidden)]
211pub struct Session {
212    /// Application-layer session data.
213    pub data: SessionData,
214    /// A unique identifier that represents the arrival order of the first packet of the session.
215    pub id: usize,
216}
217
218impl Default for Session {
219    fn default() -> Self {
220        Session {
221            data: SessionData::Null,
222            id: 0,
223        }
224    }
225}
226
227/// A connection protocol parser.
228///
229/// ## Note
230/// This must have `pub` visibility because it needs to be accessible by the
231/// [retina_filtergen](fixlink) crate. At time of this writing, procedural macros must be defined in
232/// a separate crate, so items that ought to be crate-private have their documentation hidden to
233/// avoid confusing users.
234#[doc(hidden)]
235#[derive(Debug, EnumString)]
236#[strum(serialize_all = "snake_case")]
237pub enum ConnParser {
238    // TODO: refactor to use trait objects.
239    Tls(TlsParser),
240    Dns(DnsParser),
241    Http(HttpParser),
242    Quic(QuicParser),
243    Unknown,
244}
245
246impl ConnParser {
247    /// Returns a new connection protocol parser of the same type, but with state reset.
248    pub(crate) fn reset_new(&self) -> ConnParser {
249        match self {
250            ConnParser::Tls(_) => ConnParser::Tls(TlsParser::default()),
251            ConnParser::Dns(_) => ConnParser::Dns(DnsParser::default()),
252            ConnParser::Http(_) => ConnParser::Http(HttpParser::default()),
253            ConnParser::Quic(_) => ConnParser::Quic(QuicParser::default()),
254            ConnParser::Unknown => ConnParser::Unknown,
255        }
256    }
257
258    /// Returns the result of parsing `pdu` as a protocol message.
259    pub(crate) fn parse(&mut self, pdu: &L4Pdu) -> ParseResult {
260        match self {
261            ConnParser::Tls(parser) => parser.parse(pdu),
262            ConnParser::Dns(parser) => parser.parse(pdu),
263            ConnParser::Http(parser) => parser.parse(pdu),
264            ConnParser::Quic(parser) => parser.parse(pdu),
265            ConnParser::Unknown => ParseResult::Skipped,
266        }
267    }
268
269    /// Returns the result of probing whether `pdu` is a protocol message.
270    pub(crate) fn probe(&self, pdu: &L4Pdu) -> ProbeResult {
271        match self {
272            ConnParser::Tls(parser) => parser.probe(pdu),
273            ConnParser::Dns(parser) => parser.probe(pdu),
274            ConnParser::Http(parser) => parser.probe(pdu),
275            ConnParser::Quic(parser) => parser.probe(pdu),
276            ConnParser::Unknown => ProbeResult::Error,
277        }
278    }
279
280    /// Removes the session with ID `session_id` from any protocol state managed by the parser, and
281    /// returns it.
282    pub(crate) fn remove_session(&mut self, session_id: usize) -> Option<Session> {
283        match self {
284            ConnParser::Tls(parser) => parser.remove_session(session_id),
285            ConnParser::Dns(parser) => parser.remove_session(session_id),
286            ConnParser::Http(parser) => parser.remove_session(session_id),
287            ConnParser::Quic(parser) => parser.remove_session(session_id),
288            ConnParser::Unknown => None,
289        }
290    }
291
292    /// Removes all remaining sessions managed by the parser and returns them.
293    pub(crate) fn drain_sessions(&mut self) -> Vec<Session> {
294        match self {
295            ConnParser::Tls(parser) => parser.drain_sessions(),
296            ConnParser::Dns(parser) => parser.drain_sessions(),
297            ConnParser::Http(parser) => parser.drain_sessions(),
298            ConnParser::Quic(parser) => parser.drain_sessions(),
299            ConnParser::Unknown => vec![],
300        }
301    }
302
303    /// Returns the state that a connection should transition to on a session filter match.
304    pub(crate) fn session_match_state(&self) -> ConnState {
305        match self {
306            ConnParser::Tls(parser) => parser.session_match_state(),
307            ConnParser::Dns(parser) => parser.session_match_state(),
308            ConnParser::Http(parser) => parser.session_match_state(),
309            ConnParser::Quic(parser) => parser.session_match_state(),
310            ConnParser::Unknown => ConnState::Remove,
311        }
312    }
313
314    /// Returns the state that a connection should transition to on a failed session filter match.
315    pub(crate) fn session_nomatch_state(&self) -> ConnState {
316        match self {
317            ConnParser::Tls(parser) => parser.session_nomatch_state(),
318            ConnParser::Dns(parser) => parser.session_nomatch_state(),
319            ConnParser::Http(parser) => parser.session_nomatch_state(),
320            ConnParser::Quic(parser) => parser.session_nomatch_state(),
321            ConnParser::Unknown => ConnState::Remove,
322        }
323    }
324}