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}