Skip to main content

retina_core/conntrack/conn/
mod.rs

1//! State management for connections.
2//!
3//! Tracks a TCP or UDP connection, performs stream reassembly, and manages protocol parser state
4//! throughout the duration of the connection.
5
6pub(crate) mod conn_info;
7pub(crate) mod tcp_conn;
8pub(crate) mod udp_conn;
9
10use self::conn_info::{ConnInfo, ConnState};
11use self::tcp_conn::TcpConn;
12use self::udp_conn::UdpConn;
13use crate::conntrack::conn_id::FiveTuple;
14use crate::conntrack::pdu::{L4Context, L4Pdu};
15use crate::filter::FilterResult;
16use crate::protocols::packet::tcp::{ACK, RST, SYN};
17use crate::protocols::stream::ParserRegistry;
18use crate::subscription::{Subscription, Trackable};
19
20use anyhow::{bail, Result};
21use std::time::Instant;
22
23/// Tracks either a TCP or a UDP connection.
24///
25/// Performs light-weight stream reassembly for TCP connections and tracks UDP connections.
26pub(crate) enum L4Conn {
27    Tcp(TcpConn),
28    Udp(UdpConn),
29}
30
31/// Connection state.
32pub(crate) struct Conn<T>
33where
34    T: Trackable,
35{
36    /// Timestamp of the last observed packet in the connection.
37    pub(crate) last_seen_ts: Instant,
38    /// Amount of time (in milliseconds) before the connection should be expired for inactivity.
39    pub(crate) inactivity_window: usize,
40    /// Layer-4 connection tracking.
41    pub(crate) l4conn: L4Conn,
42    /// Connection information for filtering and parsing.
43    pub(crate) info: ConnInfo<T>,
44}
45
46impl<T> Conn<T>
47where
48    T: Trackable,
49{
50    /// Creates a new TCP connection from `ctxt` with an initial inactivity window of
51    /// `initial_timeout` and a maximum out-or-order tolerance of `max_ooo`. This means that there
52    /// can be at most `max_ooo` packets buffered out of sequence before Retina chooses to discard
53    /// the connection.
54    pub(super) fn new_tcp(ctxt: L4Context, initial_timeout: usize, max_ooo: usize) -> Result<Self> {
55        let five_tuple = FiveTuple::from_ctxt(ctxt);
56        let tcp_conn = if ctxt.flags & SYN != 0 && ctxt.flags & ACK == 0 && ctxt.flags & RST == 0 {
57            TcpConn::new_on_syn(ctxt, max_ooo)
58        } else {
59            bail!("Not SYN")
60        };
61        Ok(Conn {
62            last_seen_ts: Instant::now(),
63            inactivity_window: initial_timeout,
64            l4conn: L4Conn::Tcp(tcp_conn),
65            info: ConnInfo::new(five_tuple, ctxt.idx),
66        })
67    }
68
69    /// Creates a new UDP connection from `ctxt` with an initial inactivity window of
70    /// `initial_timeout`.
71    #[allow(clippy::unnecessary_wraps)]
72    pub(super) fn new_udp(ctxt: L4Context, initial_timeout: usize) -> Result<Self> {
73        let five_tuple = FiveTuple::from_ctxt(ctxt);
74        let udp_conn = UdpConn;
75        Ok(Conn {
76            last_seen_ts: Instant::now(),
77            inactivity_window: initial_timeout,
78            l4conn: L4Conn::Udp(udp_conn),
79            info: ConnInfo::new(five_tuple, ctxt.idx),
80        })
81    }
82
83    /// Updates a connection on the arrival of a new packet.
84    pub(super) fn update(
85        &mut self,
86        pdu: L4Pdu,
87        subscription: &Subscription<T::Subscribed>,
88        registry: &ParserRegistry,
89    ) {
90        match &mut self.l4conn {
91            L4Conn::Tcp(tcp_conn) => {
92                if self.info.state == ConnState::Tracking {
93                    if tcp_conn.ctos.ooo_buf.len() != 0 {
94                        tcp_conn.ctos.ooo_buf.buf.clear();
95                    }
96                    if tcp_conn.stoc.ooo_buf.len() != 0 {
97                        tcp_conn.stoc.ooo_buf.buf.clear();
98                    }
99                    tcp_conn.update_term_condition(pdu.flags(), pdu.dir);
100                    self.info.sdata.post_match(pdu, subscription);
101                } else {
102                    tcp_conn.reassemble(pdu, &mut self.info, subscription, registry);
103                }
104            }
105            L4Conn::Udp(_udp_conn) => self.info.consume_pdu(pdu, subscription, registry),
106        }
107    }
108
109    /// Returns the connection 5-tuple.
110    pub(super) fn five_tuple(&self) -> FiveTuple {
111        self.info.cdata.five_tuple
112    }
113
114    /// Returns the connection state.
115    pub(super) fn state(&self) -> ConnState {
116        self.info.state
117    }
118
119    /// Returns `true` if the connection has been naturally terminated.
120    pub(super) fn terminated(&self) -> bool {
121        match &self.l4conn {
122            L4Conn::Tcp(tcp_conn) => tcp_conn.is_terminated(),
123            L4Conn::Udp(_udp_conn) => false,
124        }
125    }
126
127    /// Returns the `true` if the packet represented by `ctxt` is in the direction of originator ->
128    /// responder.
129    pub(super) fn packet_dir(&self, ctxt: &L4Context) -> bool {
130        self.five_tuple().orig == ctxt.src
131    }
132
133    /// Invokes connection termination tasks that are triggered when any of the following conditions
134    /// occur:
135    /// - the connection naturally terminates (e.g., FIN/RST)
136    /// - the connection expires due to inactivity
137    /// - the connection is drained at the end of the run
138    pub(crate) fn terminate(&mut self, subscription: &Subscription<T::Subscribed>) {
139        match self.info.state {
140            ConnState::Probing => {
141                if let FilterResult::MatchTerminal(_) = subscription.filter_conn(&self.info.cdata) {
142                    self.info.sdata.on_terminate(subscription);
143                }
144            }
145            ConnState::Parsing => {
146                // only call on_terminate() if the first session in the connection was matched
147                let mut first_session_matched = false;
148                for session in self.info.cdata.conn_parser.drain_sessions() {
149                    if subscription.filter_session(&session, self.info.cdata.conn_term_node) {
150                        if session.id == 0 {
151                            first_session_matched = true;
152                        }
153                        self.info.sdata.on_match(session, subscription);
154                    }
155                }
156                if first_session_matched {
157                    self.info.sdata.on_terminate(subscription);
158                }
159            }
160            ConnState::Tracking => {
161                self.info.sdata.on_terminate(subscription);
162            }
163            ConnState::Remove | ConnState::Dropped => {
164                // do nothing
165            }
166        }
167    }
168}