Skip to main content

retina_core/subscription/
zc_frame.rs

1//! Zero-copy Ethernet frames.
2//!
3//! This is a packet-level subscription that delivers raw Ethernet frames in the order of arrival.
4//! It has identical behavior to the [Frame](crate::subscription::frame::Frame) type, except is
5//! zero-copy, meaning that callbacks are invoked on raw DPDK memory buffers instead of a
6//! heap-allocated buffer. This is useful for performance sensitive applications that do not need to
7//! store packet data. If ownership of the packet data is required, it is recommended to use
8//! [Frame](crate::subscription::frame::Frame) instead.
9//!
10//! ## Warning
11//! All `ZcFrame`s must be dropped (freed and returned to the memory pool) before the Retina runtime
12//! is dropped.
13//!
14//! ## Example
15//! Prints IPv4 packets with a TTL greater than 64:
16//! ```
17//! #[filter("ipv4.time_to_live > 64")]
18//! fn main() {
19//!     let config = default_config();
20//!     let cb = |pkt: ZcFrame| {
21//!         println!("{:?}", pkt.data());
22//!         // implicit drop at end of scope
23//!     };
24//!     let mut runtime = Runtime::new(config, filter, cb).unwrap();
25//!     runtime.run();
26//!     // runtime dropped at end of scope
27//! }
28//! ```
29
30use crate::conntrack::conn_id::FiveTuple;
31use crate::conntrack::pdu::{L4Context, L4Pdu};
32use crate::conntrack::ConnTracker;
33use crate::filter::FilterResult;
34use crate::memory::mbuf::Mbuf;
35use crate::protocols::stream::{ConnParser, Session};
36use crate::subscription::{Level, Subscribable, Subscription, Trackable};
37
38use std::collections::HashMap;
39
40/// A zero-copy Ethernet frame.
41///
42/// ## Remarks
43/// This is a type alias of a DPDK message buffer. Retina allows subscriptions on raw DPDK memory
44/// buffers with zero-copy (i.e., without copying into a heap-allocated buffer). This is useful for
45/// performance sensitive applications that do not need to store packet data.
46///
47/// However, the callback does not obtain ownership of the packet. Therefore, all `ZcFrame`s must be
48/// dropped before the runtime is dropped, or a segmentation fault may occur when the memory pools
49/// are de-allocated. Storing `ZcFrame`s also reduces the number of available packet buffers for
50/// incoming packets and can cause memory pool exhaustion.
51///
52/// It is recommended that `ZcFrame` be used for stateless packet analysis, and to use
53/// [Frame](crate::subscription::Frame) instead if ownership of the packet is needed.
54pub type ZcFrame = Mbuf;
55
56impl Subscribable for ZcFrame {
57    type Tracked = TrackedZcFrame;
58
59    fn level() -> Level {
60        Level::Packet
61    }
62
63    fn parsers() -> Vec<ConnParser> {
64        vec![]
65    }
66
67    fn process_packet(
68        mbuf: Mbuf,
69        subscription: &Subscription<Self>,
70        conn_tracker: &mut ConnTracker<Self::Tracked>,
71    ) {
72        match subscription.filter_packet(&mbuf) {
73            FilterResult::MatchTerminal(_idx) => {
74                subscription.invoke(mbuf);
75            }
76            FilterResult::MatchNonTerminal(idx) => {
77                if let Ok(ctxt) = L4Context::new(&mbuf, idx) {
78                    conn_tracker.process(mbuf, ctxt, subscription);
79                }
80            }
81            FilterResult::NoMatch => drop(mbuf),
82        }
83    }
84}
85
86/// Buffers packets associated with parsed sessions throughout the duration of the connection.
87/// ## Note
88/// Internal connection state is an associated type of a `pub` trait, and therefore must also be
89/// public. Documentation is hidden by default to avoid confusing users.
90#[doc(hidden)]
91pub struct TrackedZcFrame {
92    session_buf: HashMap<usize, Vec<Mbuf>>,
93}
94
95impl Trackable for TrackedZcFrame {
96    type Subscribed = ZcFrame;
97
98    fn new(_five_tuple: FiveTuple) -> Self {
99        TrackedZcFrame {
100            session_buf: HashMap::new(),
101            // misc_buf: vec![],
102        }
103    }
104
105    fn pre_match(&mut self, pdu: L4Pdu, session_id: Option<usize>) {
106        if let Some(session_id) = session_id {
107            self.session_buf
108                .entry(session_id)
109                .or_default()
110                .push(pdu.mbuf_own());
111        } else {
112            drop(pdu);
113            // self.misc_buf.push(pdu.mbuf_own());
114        }
115    }
116
117    fn on_match(&mut self, session: Session, subscription: &Subscription<Self::Subscribed>) {
118        if let Some(session) = self.session_buf.remove(&session.id) {
119            session.into_iter().for_each(|mbuf| {
120                subscription.invoke(mbuf);
121            });
122        }
123    }
124
125    fn post_match(&mut self, pdu: L4Pdu, subscription: &Subscription<Self::Subscribed>) {
126        subscription.invoke(pdu.mbuf_own());
127    }
128
129    fn on_terminate(&mut self, _subscription: &Subscription<Self::Subscribed>) {}
130}