Skip to main content

iris_core/conntrack/conn/
conn_actions.rs

1use super::conn_state::{StateTransition, NUM_STATE_TRANSITIONS};
2use bitmask_enum::bitmask;
3
4/// Possible actions to be taken on a connection
5#[bitmask(u8)]
6#[bitmask_config(vec_debug)]
7pub enum Actions {
8    /// Invoke Tracked datatype "Update" API at this Layer to pass new frames
9    /// to users' subscribed datatype(s).
10    Update,
11    /// Indicates that some Layer-specific stateful parsing is required.
12    /// For L4, this means that some subscription requires reassembled updates
13    /// (separate from protocol parsing, which is handled by PassThrough).
14    /// For L6/L7, this indicates a stateful application-layer protocol
15    /// parser should be invoked.
16    Parse,
17    /// Indicates that some child layer(s) require actions.
18    /// For TCP connections, this will trigger reassembly.
19    PassThrough,
20    /// Track the connection, updating with state transitions.
21    Track,
22}
23
24/// Basic representation of Actions
25/// NICE-TO-HAVE: change to single bitmask in the future
26#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd)]
27pub struct TrackedActions {
28    // Currently-active actions (as bitmask)
29    pub active: Actions,
30    // Bitmask of actions that should be refreshed at each stage
31    pub refresh_at: [Actions; NUM_STATE_TRANSITIONS],
32}
33
34fn fmt_actions(actions: Actions) -> String {
35    if actions.is_none() {
36        return "-".to_string();
37    }
38    let mut parts: Vec<&'static str> = Vec::new();
39    if actions.intersects(Actions::Update) {
40        parts.push("Update");
41    }
42    if actions.intersects(Actions::Parse) {
43        parts.push("Parse");
44    }
45    if actions.intersects(Actions::PassThrough) {
46        parts.push("PassThrough");
47    }
48    if actions.intersects(Actions::Track) {
49        parts.push("Track");
50    }
51    parts.join(",")
52}
53
54// Clippy #new_without_default warning for pub types
55impl Default for TrackedActions {
56    fn default() -> Self {
57        Self::new()
58    }
59}
60
61impl TrackedActions {
62    /// Initialize empty
63    pub fn new() -> Self {
64        Self {
65            active: Actions { bits: 0 },
66            refresh_at: [Actions { bits: 0 }; NUM_STATE_TRANSITIONS],
67        }
68    }
69
70    /// Set up actions for executing a state transition
71    /// Clear out actions that will need to be re-checked
72    /// Also clear `PassThrough`, which will be reset after the
73    /// state TX if child layer(s) have actions set.
74    #[inline]
75    pub(crate) fn start_state_tx(&mut self, state: StateTransition) {
76        self.active &= self.refresh_at[state.as_usize()].not();
77        self.active &= (Actions::PassThrough).not();
78    }
79
80    /// Clear an action
81    #[inline]
82    pub fn clear(&mut self, actions: &Actions) {
83        self.active &= actions.not();
84    }
85
86    /// Clear intersection of actions with `peer`, including `update_at`
87    #[inline]
88    pub(crate) fn clear_intersection(&mut self, peer: &TrackedActions) {
89        self.clear(&peer.active);
90        for i in 0..NUM_STATE_TRANSITIONS {
91            self.refresh_at[i] &= peer.refresh_at[i].not();
92        }
93    }
94
95    /// All actions are empty; nothing to do for future packets in connection.
96    #[inline]
97    pub fn drop(&self) -> bool {
98        self.active.is_none()
99    }
100
101    /// `PassThrough` indicates that there is another session/protocol encapsulated
102    /// in this one. Since we currently just support one layer within TCP/UDP, this is
103    /// currently unused.
104    #[allow(dead_code)]
105    #[inline]
106    pub(crate) fn has_next_layer(&self) -> bool {
107        self.active.intersects(Actions::PassThrough)
108    }
109
110    /// See has_next_layer
111    #[inline]
112    pub(crate) fn set_next_layer(&mut self) {
113        self.active |= Actions::PassThrough;
114    }
115
116    #[inline]
117    pub(crate) fn needs_parse(&self) -> bool {
118        self.active.intersects(Actions::Parse)
119    }
120
121    #[inline]
122    pub(crate) fn needs_update(&self) -> bool {
123        self.active.intersects(Actions::Update)
124    }
125
126    /// Append TrackedActions. Used at compile-time and
127    /// when building up actions in runtime filters.
128    #[inline]
129    pub fn extend(&mut self, other: &TrackedActions) {
130        self.active |= other.active;
131        for i in 0..NUM_STATE_TRANSITIONS {
132            self.refresh_at[i] |= other.refresh_at[i];
133        }
134    }
135
136    /// When a filter has definitively matched AND it will be required
137    /// for the rest of the connection (i.e., connection-level subscription),
138    /// remove it from all future state transition "refresh" slots.
139    pub fn set_terminal_action(&mut self, action: &Actions) {
140        for i in 0..NUM_STATE_TRANSITIONS {
141            self.refresh_at[i] &= action.not();
142        }
143    }
144
145    /// Returns `true` if this state TX can be safely skipped
146    /// (i.e., nothing needs to be delivered and no actions need refresh here)
147    pub(crate) fn skip_tx(&self, tx: &StateTransition) -> bool {
148        self.refresh_at[tx.as_usize()].is_none()
149    }
150}
151
152impl std::fmt::Display for TrackedActions {
153    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
154        if self.active.is_none() {
155            return write!(f, "-");
156        }
157
158        let active_str = fmt_actions(self.active);
159        let mut state_txs: Vec<(String, Actions)> = Vec::new();
160        for i in 0..NUM_STATE_TRANSITIONS {
161            let a = self.refresh_at[i];
162            if !a.is_none() {
163                state_txs.push((StateTransition::from_usize(i).to_string(), a));
164            }
165        }
166
167        assert!(
168            !state_txs.is_empty(),
169            "Active actions but no refresh points?"
170        );
171
172        // All actions have same refresh point
173        if !self.active.is_none() && state_txs.iter().all(|(_, a)| *a == self.active) {
174            let state_tx_list = state_txs
175                .into_iter()
176                .map(|(u, _)| u)
177                .collect::<Vec<_>>()
178                .join(",");
179            return write!(f, "{}->({})", active_str, state_tx_list);
180        }
181
182        // Map actions to refresh point
183        let mut state_tx_list: Vec<Actions> = Vec::new();
184        for (_, a) in &state_txs {
185            if !state_tx_list.contains(a) {
186                state_tx_list.push(*a);
187            }
188        }
189        let action_set = state_tx_list
190            .into_iter()
191            .map(fmt_actions)
192            .collect::<Vec<_>>()
193            .join(",");
194        let state_tx_list = state_txs
195            .into_iter()
196            .map(|(u, a)| format!("{}={}", u, fmt_actions(a)))
197            .collect::<Vec<_>>()
198            .join(",");
199        write!(f, "{}->({})", action_set, state_tx_list)
200    }
201}