Skip to main content

retina_core/subscription/
frame.rs

1//! Ethernet frames.
2//!
3//! This is a packet-level subscription that delivers raw Ethernet frames in the order of arrival.
4//!
5//! ## Example
6//! Prints IPv4 packets with a TTL greater than 64:
7//! ```
8//! #[filter("ipv4.time_to_live > 64")]
9//! fn main() {
10//!     let config = default_config();
11//!     let cb = |frame: Frame| {
12//!         println!("{:?}", frame.data);
13//!     };
14//!     let mut runtime = Runtime::new(config, filter, cb).unwrap();
15//!     runtime.run();
16//! }
17//! ```
18//!
19//! ## Remarks
20//! The `Frame` type is most suited for packet-specific analysis with filters that do not require
21//! connection tracking or stream-level protocol parsing. While all types of filters are technically
22//! allowed, some may introduce subtle behaviors.
23//!
24//! For example, take the filter `tcp.port = 80 or http`. Packet-level filters take precedence in
25//! Retina, meaning that if a packet satisfies the filter, the callback will immediately be invoked.
26//! In this example, Retina will deliver all TCP packets where the source or destination port is 80,
27//! as well as packets associated with HTTP request/response messages (not including control
28//! packets) in connections not on port 80. For HTTP connections on port 80, Retina will deliver all
29//! packets in the connection (including control packets) by virtue of satisfying the `tcp.port =
30//! 80` predicate.
31//!
32//! To subscribe to all packets in the connection by default (with connection-level semantics), use
33//! [`ConnectionFrame`](crate::subscription::connection_frame::ConnectionFrame) instead.
34
35use crate::conntrack::conn_id::FiveTuple;
36use crate::conntrack::pdu::{L4Context, L4Pdu};
37use crate::conntrack::ConnTracker;
38use crate::filter::FilterResult;
39use crate::memory::mbuf::Mbuf;
40use crate::protocols::stream::{ConnParser, Session};
41use crate::subscription::{Level, Subscribable, Subscription, Trackable};
42
43use std::collections::HashMap;
44
45/// An Ethernet Frame.
46///
47/// ## Remarks
48/// This subscribable type is equivalent to an owned packet buffer. Internally, packet data remains
49/// in memory pool-allocated DPDK message buffers for as long as possible, before it is copied into
50/// a heap buffer to transfer ownership to the callback on a filter match. The DPDK message buffers
51/// are then freed back to the memory pool.
52#[derive(Debug, Clone)]
53pub struct Frame {
54    pub data: Vec<u8>,
55}
56
57impl Frame {
58    pub(crate) fn from_mbuf(mbuf: &Mbuf) -> Self {
59        Frame {
60            data: mbuf.data().to_vec(),
61        }
62    }
63}
64
65impl Subscribable for Frame {
66    type Tracked = TrackedFrame;
67
68    fn level() -> Level {
69        Level::Packet
70    }
71
72    fn parsers() -> Vec<ConnParser> {
73        vec![]
74    }
75
76    fn process_packet(
77        mbuf: Mbuf,
78        subscription: &Subscription<Self>,
79        conn_tracker: &mut ConnTracker<Self::Tracked>,
80    ) {
81        match subscription.filter_packet(&mbuf) {
82            FilterResult::MatchTerminal(_idx) => {
83                let frame = Frame::from_mbuf(&mbuf);
84                subscription.invoke(frame);
85            }
86            FilterResult::MatchNonTerminal(idx) => {
87                if let Ok(ctxt) = L4Context::new(&mbuf, idx) {
88                    conn_tracker.process(mbuf, ctxt, subscription);
89                }
90            }
91            FilterResult::NoMatch => drop(mbuf),
92        }
93    }
94}
95
96/// Buffers packets associated with parsed sessions throughout the duration of the connection.
97///
98/// ## Note
99/// Internal connection state is an associated type of a `pub` trait, and therefore must also be
100/// public. Documentation is hidden by default to avoid confusing users.
101#[doc(hidden)]
102pub struct TrackedFrame {
103    session_buf: HashMap<usize, Vec<Mbuf>>,
104    // Buffers packets not associated with parsed sessions. (e.g., control packets, malformed,
105    // etc.). misc_buf: Vec<Mbuf>,
106}
107
108impl Trackable for TrackedFrame {
109    type Subscribed = Frame;
110
111    fn new(_five_tuple: FiveTuple) -> Self {
112        TrackedFrame {
113            session_buf: HashMap::new(),
114            // misc_buf: vec![],
115        }
116    }
117
118    fn pre_match(&mut self, pdu: L4Pdu, session_id: Option<usize>) {
119        if let Some(session_id) = session_id {
120            self.session_buf
121                .entry(session_id)
122                .or_default()
123                .push(pdu.mbuf_own());
124        } else {
125            drop(pdu);
126            // self.misc_buf.push(pdu.mbuf_own());
127        }
128    }
129
130    fn on_match(&mut self, session: Session, subscription: &Subscription<Self::Subscribed>) {
131        if let Some(session) = self.session_buf.remove(&session.id) {
132            session.into_iter().for_each(|mbuf| {
133                let frame = Frame::from_mbuf(&mbuf);
134                subscription.invoke(frame);
135            });
136        }
137    }
138
139    fn post_match(&mut self, pdu: L4Pdu, subscription: &Subscription<Self::Subscribed>) {
140        let frame = Frame::from_mbuf(&pdu.mbuf_own());
141        subscription.invoke(frame);
142    }
143
144    fn on_terminate(&mut self, _subscription: &Subscription<Self::Subscribed>) {}
145}