Skip to main content

iris_core/conntrack/conn/
mod.rs

1//! Per-connection state management.
2//!
3//! Tracks a TCP or UDP connection, performs stream reassembly, and (via ConnInfo)
4//! manages protocol parser state throughout the duration of the connection.
5//!
6//! Developers should not have to directly interact with anything in this module, but
7//! it must be public for generated code.
8
9pub mod conn_actions;
10pub mod conn_info;
11pub mod conn_layers;
12pub mod conn_state;
13pub mod tcp_conn;
14pub mod udp_conn;
15
16pub use conn_info::ConnInfo;
17
18use self::tcp_conn::TcpConn;
19use crate::conntrack::conn::udp_conn::UdpConn;
20use crate::conntrack::pdu::{L4Context, L4Pdu};
21use crate::lcore::CoreId;
22use crate::protocols::packet::tcp::{ACK, RST, SYN};
23use crate::protocols::stream::ParserRegistry;
24use crate::stats::{
25    StatExt, DROPPED_MIDDLE_OF_CONNECTION_TCP_BYTE, DROPPED_MIDDLE_OF_CONNECTION_TCP_PKT,
26};
27use crate::subscription::{Subscription, Trackable};
28
29use anyhow::{bail, Result};
30use std::time::Instant;
31
32/// Tracks either a TCP or a UDP connection.
33///
34/// Performs zero-copy stream reassembly for TCP connections and tracks UDP connections.
35pub(crate) enum L4Conn {
36    Tcp(TcpConn),
37    Udp(UdpConn),
38}
39
40/// Connection state.
41pub(crate) struct Conn<T>
42where
43    T: Trackable,
44{
45    /// Timestamp of the last observed packet in the connection.
46    pub(crate) last_seen_ts: Instant,
47    /// Amount of time (in milliseconds) before the connection should be expired for inactivity.
48    pub(crate) inactivity_window: usize,
49    /// Layer-4 connection tracking.
50    pub(crate) l4conn: L4Conn,
51    /// Connection tracking for filtering and parsing.
52    pub(crate) info: ConnInfo<T>,
53}
54
55impl<T> Conn<T>
56where
57    T: Trackable,
58{
59    /// Creates a new TCP connection from `ctxt` with an initial inactivity window of
60    /// `initial_timeout` and a maximum out-or-order tolerance of `max_ooo`. This means that there
61    /// can be at most `max_ooo` packets buffered out of sequence before Iris chooses to discard
62    /// the connection.
63    pub(super) fn new_tcp(
64        initial_timeout: usize,
65        max_ooo: usize,
66        pdu: &L4Pdu,
67        core_id: CoreId,
68    ) -> Result<Self> {
69        let tcp_conn = if pdu.ctxt.flags & SYN != 0
70            && pdu.ctxt.flags & ACK == 0
71            && pdu.ctxt.flags & RST == 0
72        {
73            TcpConn::new_on_syn(pdu.ctxt, max_ooo)
74        } else {
75            DROPPED_MIDDLE_OF_CONNECTION_TCP_PKT.inc();
76            DROPPED_MIDDLE_OF_CONNECTION_TCP_BYTE.inc_by(pdu.mbuf.data_len() as u64);
77            bail!("Not SYN")
78        };
79        Ok(Conn {
80            last_seen_ts: pdu.ts,
81            inactivity_window: initial_timeout,
82            l4conn: L4Conn::Tcp(tcp_conn),
83            info: ConnInfo::new(pdu, core_id),
84        })
85    }
86
87    /// Creates a new UDP connection from `ctxt` with an initial inactivity window of
88    /// `initial_timeout`.
89    #[allow(clippy::unnecessary_wraps)]
90    pub(super) fn new_udp(initial_timeout: usize, pdu: &L4Pdu, core_id: CoreId) -> Result<Self> {
91        let udp_conn = UdpConn;
92        Ok(Conn {
93            last_seen_ts: pdu.ts,
94            inactivity_window: initial_timeout,
95            l4conn: L4Conn::Udp(udp_conn),
96            info: ConnInfo::new(pdu, core_id),
97        })
98    }
99
100    #[allow(dead_code)]
101    pub(super) fn flow_len(&self, dir: bool) -> Option<usize> {
102        match &self.l4conn {
103            L4Conn::Tcp(tcp_conn) => Some(tcp_conn.flow_len(dir)),
104            L4Conn::Udp(_) => None,
105        }
106    }
107
108    #[allow(dead_code)]
109    pub(super) fn total_len(&self) -> Option<usize> {
110        match &self.l4conn {
111            L4Conn::Tcp(tcp_conn) => Some(tcp_conn.total_len()),
112            L4Conn::Udp(_) => None,
113        }
114    }
115
116    /// Updates a connection on the arrival of a new packet.
117    pub(super) fn update(
118        &mut self,
119        mut pdu: L4Pdu,
120        subscription: &Subscription<T::Subscribed>,
121        registry: &ParserRegistry,
122    ) {
123        // Pre-reassembly update
124        if self.info.linfo.actions.needs_update() {
125            self.info.new_packet(&pdu, subscription);
126        }
127
128        // Case 1: no need to pass through parsing/reassembly infrastructure,
129        // but still may need to track for termination.
130        if !self.info.needs_reassembly() {
131            self.update_tcp_flags(pdu.flags(), pdu.dir);
132            return;
133        }
134
135        // Case 2: reassembly/parsing needed
136        match &mut self.l4conn {
137            L4Conn::Tcp(tcp_conn) => {
138                tcp_conn.reassemble(pdu, &mut self.info, subscription, registry);
139                // Check if, after actions update, the framework/subscriptions
140                // no longer require receiving reassembled traffic.
141                if !self.info.needs_reassembly() {
142                    // Safe to discard out-of-order buffers
143                    if !tcp_conn.ctos.ooo_buf.is_empty() {
144                        tcp_conn.ctos.ooo_buf.buf.clear();
145                    }
146                    if !tcp_conn.stoc.ooo_buf.is_empty() {
147                        tcp_conn.stoc.ooo_buf.buf.clear();
148                    }
149                }
150            }
151            L4Conn::Udp(_) => self.info.consume_stream(&mut pdu, subscription, registry),
152        }
153    }
154
155    /// Updates flags
156    #[inline]
157    pub(super) fn update_tcp_flags(&mut self, flags: u8, dir: bool) {
158        if let L4Conn::Tcp(tcp_conn) = &mut self.l4conn {
159            tcp_conn.update_flags(flags, dir);
160        }
161    }
162
163    /// Returns `true` if the connection should be removed from the conn. table.
164    /// Note UDP connections are kept for a buffer period. UDP packets
165    /// that pass the packet filter stage are assumed to represent an
166    /// existing or new connection and are inserted into the connection
167    /// table. Keeping UDP connections in "drop" state for a buffer
168    /// period prevents dropped connections from being re-inserted.
169    pub(super) fn remove_from_table(&self) -> bool {
170        match &self.l4conn {
171            L4Conn::Udp(_) => false,
172            _ => self.info.drop(),
173        }
174    }
175
176    /// Returns `true` if PDUs for this connection should be dropped.
177    /// This happens for UDP connections that no longer require tracking,
178    /// but we keep it around (with no assoc. data) to avoid re-insertion.
179    /// Note - consider in future ways to track removed UDP connections
180    /// in more efficient way.
181    pub(super) fn drop_pdu(&self) -> bool {
182        self.info.drop()
183    }
184
185    /// Returns `true` if the connection has been naturally terminated.
186    pub(super) fn terminated(&self) -> bool {
187        match &self.l4conn {
188            L4Conn::Tcp(tcp_conn) => tcp_conn.is_terminated(),
189            L4Conn::Udp(_udp_conn) => false,
190        }
191    }
192
193    /// Returns the `true` if the packet represented by `ctxt` is in the direction of originator ->
194    /// responder.
195    pub(super) fn packet_dir(&self, ctxt: &L4Context) -> bool {
196        self.info.cdata.five_tuple.orig == ctxt.src
197    }
198
199    /// Invokes connection termination tasks that are triggered when any of the following conditions
200    /// occur:
201    /// - the connection naturally terminates (e.g., FIN/RST)
202    /// - the connection expires due to inactivity
203    /// - the connection is drained at the end of the run
204    pub(crate) fn terminate(&mut self, subscription: &Subscription<T::Subscribed>) {
205        self.info.handle_terminate(subscription);
206    }
207}