Skip to main content

iris_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
7#[doc(hidden)]
8pub mod conn;
9pub mod dns;
10pub mod http;
11pub mod quic;
12pub mod ssh;
13pub mod tls;
14
15use self::conn::ConnField;
16use self::conn::{Ipv4CData, Ipv6CData, TcpCData, UdpCData};
17use self::dns::{parser::DnsParser, Dns};
18use self::http::{parser::HttpParser, Http};
19use self::quic::parser::QuicParser;
20use self::ssh::{parser::SshParser, Ssh};
21use self::tls::{parser::TlsParser, Tls};
22use crate::conntrack::conn_id::FiveTuple;
23use crate::conntrack::pdu::L4Pdu;
24
25use std::collections::HashSet;
26use std::str::FromStr;
27
28use anyhow::Result;
29use quic::QuicConn;
30use strum_macros::EnumString;
31
32pub const IMPLEMENTED_PROTOCOLS: [&str; 5] = ["tls", "dns", "http", "quic", "ssh"];
33
34/// Represents the result of parsing one packet as a protocol message.
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub(crate) enum ParseResult {
37    /// Session headers done, ready to be filtered on.
38    /// Returns the most-recently-updated session ID.
39    HeadersDone(usize),
40    /// Session parsing, including body, done. Returns the most-recently-updated session ID.
41    Done(usize),
42    /// Successfully extracted data, continue processing more packets. Returns most recently updated
43    /// session ID.
44    Continue(usize),
45    /// Parsing skipped, no data extracted.
46    Skipped,
47    /// For Unknown parser
48    None,
49}
50
51/// Represents the result of a probing one packet as a protocol message type.
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub(crate) enum ProbeResult {
54    /// Segment matches the parser with great probability.
55    Certain,
56    /// Unsure if the segment matches the parser.
57    Unsure,
58    /// Segment does not match the parser.
59    NotForUs,
60    /// Error occurred during the probe. Functionally equivalent to Unsure.
61    Error,
62}
63
64/// Represents the result of probing one packet with all registered protocol parsers.
65#[derive(Debug)]
66pub(crate) enum ProbeRegistryResult {
67    /// A parser in the registry was definitively matched.
68    Some(ConnParser),
69    /// All parsers in the registry were definitively not matched.
70    None,
71    /// Unsure, continue sending more data.
72    Unsure,
73}
74
75/// The set of application-layer protocol parsers required to fulfill the subscription.
76#[derive(Debug)]
77pub struct ParserRegistry(Vec<ConnParser>);
78
79impl ParserRegistry {
80    // Assumes that `input` is deduplicated
81    pub fn from_strings(input: Vec<&'static str>) -> ParserRegistry {
82        // Deduplicate
83        let stream_protocols: HashSet<&'static str> = input.into_iter().collect();
84        let mut parsers = vec![];
85        for stream_protocol in stream_protocols {
86            let parser = ConnParser::from_str(stream_protocol)
87                .unwrap_or_else(|_| panic!("Invalid stream protocol: {}", stream_protocol));
88            parsers.push(parser);
89        }
90        ParserRegistry(parsers)
91    }
92
93    /// Probe the packet `pdu` with all registered protocol parsers.
94    pub(crate) fn probe_all(&self, pdu: &L4Pdu) -> ProbeRegistryResult {
95        if self.0.is_empty() {
96            return ProbeRegistryResult::None;
97        }
98        if pdu.length() == 0 {
99            return ProbeRegistryResult::Unsure;
100        }
101
102        let mut num_notmatched = 0;
103        for parser in self.0.iter() {
104            match parser.probe(pdu) {
105                ProbeResult::Certain => {
106                    return ProbeRegistryResult::Some(parser.reset_new());
107                }
108                ProbeResult::NotForUs => {
109                    num_notmatched += 1;
110                }
111                _ => (), // Unsure, Error, Reverse
112            }
113        }
114        if num_notmatched == self.0.len() {
115            ProbeRegistryResult::None
116        } else {
117            ProbeRegistryResult::Unsure
118        }
119    }
120}
121
122/// A trait all application-layer protocol parsers must implement.
123pub(crate) trait ConnParsable {
124    /// Parse the L4 protocol data unit as the parser's protocol.
125    fn parse(&mut self, pdu: &L4Pdu) -> ParseResult;
126
127    /// Probe if the L4 protocol data unit matches the parser's protocol.
128    fn probe(&self, pdu: &L4Pdu) -> ProbeResult;
129
130    /// Removes session with ID `session_id` and returns it.
131    fn remove_session(&mut self, session_id: usize) -> Option<Session>;
132
133    /// Removes all sessions in the connection parser and returns them.
134    fn drain_sessions(&mut self) -> Vec<Session>;
135
136    /// Indicates whether we expect to see >1 sessions per connection
137    fn session_parsed_state(&self) -> ParsingState;
138
139    /// If applicable, returns the offset into the most recently processed payload
140    /// where application-layer body begins. Some and non-zero if a payload contains
141    /// both header and body data. Clears the offset after access.
142    fn body_offset(&mut self) -> Option<usize>;
143}
144
145/// Data required to filter on Five-Tuple fields after the first packet.
146///
147/// ## Note
148/// This must have `pub` visibility because it needs to be accessible by the
149/// compiler crates. At time of this writing, procedural macros must be defined in
150/// a separate crate, so items that ought to be crate-private have their documentation hidden to
151/// avoid confusing users.
152#[doc(hidden)]
153#[derive(Debug)]
154pub struct ConnData {
155    /// The connection 5-tuple.
156    pub five_tuple: FiveTuple,
157}
158
159// REFACTOR: get rid of ConnData - likely no longer needed
160impl ConnData {
161    pub(crate) fn supported_fields() -> Vec<&'static str> {
162        let mut v: Vec<_> = TcpCData::supported_fields()
163            .into_iter()
164            .chain(UdpCData::supported_fields())
165            .chain(Ipv4CData::supported_fields())
166            .chain(Ipv6CData::supported_fields())
167            .collect();
168        v.dedup();
169        v
170    }
171
172    pub(crate) fn supported_protocols() -> Vec<&'static str> {
173        vec!["ipv4", "ipv6", "tcp", "udp"]
174    }
175
176    /// Create a new `ConnData` from the connection `five_tuple` and the ID of the last matched node
177    /// in the filter predicate trie.
178    pub(crate) fn new(five_tuple: FiveTuple) -> Self {
179        ConnData { five_tuple }
180    }
181
182    /// Parses the `ConnData`'s FiveTuple into sub-protocol metadata
183    pub fn parse_to<T: ConnField>(&self) -> Result<T>
184    where
185        Self: Sized,
186    {
187        T::parse_from(self)
188    }
189}
190
191/// Data required to filter on application-layer protocol sessions.
192///
193/// ## Note
194/// This must have `pub` visibility because it needs to be accessible by the
195/// compiler crates. At time of this writing, procedural macros must be defined in
196/// a separate crate, so items that ought to be crate-private have their documentation hidden to
197/// avoid confusing users.
198#[doc(hidden)]
199#[derive(Debug)]
200pub enum SessionData {
201    // REFACTOR: use trait objects?
202    Tls(Box<Tls>),
203    Dns(Box<Dns>),
204    Http(Box<Http>),
205    Quic(Box<QuicConn>),
206    Ssh(Box<Ssh>),
207    Null,
208}
209
210/// Supported session (encapsulated in L4 connection)
211/// Includes possibility for nested protocols
212#[derive(Debug, Clone)]
213pub enum SessionProto {
214    Tls,
215    Dns,
216    Http,
217    Quic,
218    Ssh,
219    Ipv4,
220    Ipv6,
221    Tcp,
222    Udp,
223    Null,    // All protocol identification has failed
224    Probing, // Protocol identification ongoing
225}
226
227/// An application-layer protocol session.
228///
229/// ## Note
230/// This must have `pub` visibility because it needs to be accessible by the
231/// compiler 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)]
236pub struct Session {
237    /// Application-layer session data.
238    pub data: SessionData,
239    /// A unique identifier that represents the arrival order of the first packet of the session.
240    pub id: usize,
241}
242
243impl Default for Session {
244    fn default() -> Self {
245        Session {
246            data: SessionData::Null,
247            id: 0,
248        }
249    }
250}
251
252/// A connection protocol parser.
253///
254/// ## Note
255/// This must have `pub` visibility because it needs to be accessible by the
256/// compiler crate. At time of this writing, procedural macros must be defined in
257/// a separate crate, so items that ought to be crate-private have their documentation hidden to
258/// avoid confusing users.
259#[doc(hidden)]
260#[derive(Debug, EnumString)]
261#[strum(serialize_all = "snake_case")]
262pub enum ConnParser {
263    // REFACTOR: use trait objects?
264    Tls(TlsParser),
265    Dns(DnsParser),
266    Http(HttpParser),
267    Quic(QuicParser),
268    Ssh(SshParser),
269    Unknown,
270}
271
272impl ConnParser {
273    /// Returns a new connection protocol parser of the same type, but with state reset.
274    pub(crate) fn reset_new(&self) -> ConnParser {
275        match self {
276            ConnParser::Tls(_) => ConnParser::Tls(TlsParser::default()),
277            ConnParser::Dns(_) => ConnParser::Dns(DnsParser::default()),
278            ConnParser::Http(_) => ConnParser::Http(HttpParser::default()),
279            ConnParser::Quic(_) => ConnParser::Quic(QuicParser::default()),
280            ConnParser::Ssh(_) => ConnParser::Ssh(SshParser::default()),
281            ConnParser::Unknown => ConnParser::Unknown,
282        }
283    }
284
285    /// Returns the result of parsing `pdu` as a protocol message.
286    pub(crate) fn parse(&mut self, pdu: &L4Pdu) -> ParseResult {
287        match self {
288            ConnParser::Tls(parser) => parser.parse(pdu),
289            ConnParser::Dns(parser) => parser.parse(pdu),
290            ConnParser::Http(parser) => parser.parse(pdu),
291            ConnParser::Quic(parser) => parser.parse(pdu),
292            ConnParser::Ssh(parser) => parser.parse(pdu),
293            ConnParser::Unknown => ParseResult::None,
294        }
295    }
296
297    /// Returns the result of probing whether `pdu` is a protocol message.
298    pub(crate) fn probe(&self, pdu: &L4Pdu) -> ProbeResult {
299        match self {
300            ConnParser::Tls(parser) => parser.probe(pdu),
301            ConnParser::Dns(parser) => parser.probe(pdu),
302            ConnParser::Http(parser) => parser.probe(pdu),
303            ConnParser::Quic(parser) => parser.probe(pdu),
304            ConnParser::Ssh(parser) => parser.probe(pdu),
305            ConnParser::Unknown => ProbeResult::Error,
306        }
307    }
308
309    /// Removes the session with ID `session_id` from any protocol state managed by the parser, and
310    /// returns it.
311    pub(crate) fn remove_session(&mut self, session_id: usize) -> Option<Session> {
312        match self {
313            ConnParser::Tls(parser) => parser.remove_session(session_id),
314            ConnParser::Dns(parser) => parser.remove_session(session_id),
315            ConnParser::Http(parser) => parser.remove_session(session_id),
316            ConnParser::Quic(parser) => parser.remove_session(session_id),
317            ConnParser::Ssh(parser) => parser.remove_session(session_id),
318            ConnParser::Unknown => None,
319        }
320    }
321
322    /// Removes all remaining sessions managed by the parser and returns them.
323    pub(crate) fn drain_sessions(&mut self) -> Vec<Session> {
324        match self {
325            ConnParser::Tls(parser) => parser.drain_sessions(),
326            ConnParser::Dns(parser) => parser.drain_sessions(),
327            ConnParser::Http(parser) => parser.drain_sessions(),
328            ConnParser::Quic(parser) => parser.drain_sessions(),
329            ConnParser::Ssh(parser) => parser.drain_sessions(),
330            ConnParser::Unknown => vec![],
331        }
332    }
333
334    pub(crate) fn session_parsed_state(&self) -> ParsingState {
335        match self {
336            ConnParser::Tls(parser) => parser.session_parsed_state(),
337            ConnParser::Dns(parser) => parser.session_parsed_state(),
338            ConnParser::Http(parser) => parser.session_parsed_state(),
339            ConnParser::Quic(parser) => parser.session_parsed_state(),
340            ConnParser::Ssh(parser) => parser.session_parsed_state(),
341            ConnParser::Unknown => ParsingState::Stop,
342        }
343    }
344
345    pub(crate) fn body_offset(&mut self) -> Option<usize> {
346        match self {
347            ConnParser::Tls(parser) => parser.body_offset(),
348            ConnParser::Dns(parser) => parser.body_offset(),
349            ConnParser::Http(parser) => parser.body_offset(),
350            ConnParser::Quic(parser) => parser.body_offset(),
351            ConnParser::Ssh(parser) => parser.body_offset(),
352            ConnParser::Unknown => None,
353        }
354    }
355
356    // \note This should match the name of the protocol used
357    // in the filter syntax (see filter/ast.rs::LAYERS)
358    pub fn protocol_name(&self) -> Option<String> {
359        match self {
360            ConnParser::Tls(_parser) => Some("tls".into()),
361            ConnParser::Dns(_parser) => Some("dns".into()),
362            ConnParser::Http(_parser) => Some("http".into()),
363            ConnParser::Quic(_parser) => Some("quic".into()),
364            ConnParser::Ssh(_parser) => Some("ssh".into()),
365            ConnParser::Unknown => None,
366        }
367    }
368
369    pub fn protocol(&self) -> SessionProto {
370        match self {
371            ConnParser::Tls(_) => SessionProto::Tls,
372            ConnParser::Dns(_) => SessionProto::Dns,
373            ConnParser::Http(_) => SessionProto::Http,
374            ConnParser::Quic(_) => SessionProto::Quic,
375            ConnParser::Ssh(_) => SessionProto::Ssh,
376            ConnParser::Unknown => SessionProto::Null,
377        }
378    }
379
380    pub fn requires_parsing(filter_str: &str) -> HashSet<&'static str> {
381        let mut out = hashset! {};
382
383        for s in IMPLEMENTED_PROTOCOLS {
384            if filter_str.contains(s) {
385                out.insert(s);
386            }
387        }
388        out
389    }
390}
391
392#[derive(Debug)]
393pub enum ParsingState {
394    /// Unknown application-layer protocol, needs probing.
395    Probing,
396    /// Known application-layer protocol, needs parsing.
397    Parsing,
398    /// No more sessions expected in connection.
399    Stop,
400}