Skip to main content

retina_core/subscription/
http_transaction.rs

1//! HTTP transactions.
2//!
3//! This is a session-level subscription that delivers parsed HTTP transaction records and
4//! associated connection metadata.
5//!
6//! ## Example
7//! Counts the number of HTTP `GET` requests with a user agent containing `Safari`:
8//! ```
9//! #[filter("http.method = 'GET' and http.user_agent ~ 'Safari'")]
10//! fn main() {
11//!     let config = default_config();
12//!     let cnt = AtomicUsize::new(0);
13//!     let cb = |_http: HttpTransaction| {
14//!         cnt.fetch_add(1, Ordering::Relaxed);
15//!     };
16//!     let mut runtime = Runtime::new(config, filter, cb).unwrap();
17//!     runtime.run();
18//!     println!("Count: {:?}", cnt);
19//! }
20
21use crate::conntrack::conn_id::FiveTuple;
22use crate::conntrack::pdu::{L4Context, L4Pdu};
23use crate::conntrack::ConnTracker;
24use crate::filter::FilterResult;
25use crate::memory::mbuf::Mbuf;
26use crate::protocols::stream::http::{parser::HttpParser, Http};
27use crate::protocols::stream::{ConnParser, Session, SessionData};
28use crate::subscription::{Level, Subscribable, Subscription, Trackable};
29
30use serde::Serialize;
31
32use std::net::SocketAddr;
33
34/// A parsed HTTP transaction and connection metadata.
35#[derive(Debug, Serialize)]
36pub struct HttpTransaction {
37    pub five_tuple: FiveTuple,
38    pub data: Http,
39}
40
41impl HttpTransaction {
42    /// Returns the client's socket address.
43    #[inline]
44    pub fn client(&self) -> SocketAddr {
45        self.five_tuple.orig
46    }
47
48    /// Returns the server's socket address.
49    #[inline]
50    pub fn server(&self) -> SocketAddr {
51        self.five_tuple.resp
52    }
53}
54
55impl Subscribable for HttpTransaction {
56    type Tracked = TrackedHttp;
57
58    fn level() -> Level {
59        Level::Session
60    }
61
62    fn parsers() -> Vec<ConnParser> {
63        vec![ConnParser::Http(HttpParser::default())]
64    }
65
66    fn process_packet(
67        mbuf: Mbuf,
68        subscription: &Subscription<Self>,
69        conn_tracker: &mut ConnTracker<Self::Tracked>,
70    ) {
71        match subscription.filter_packet(&mbuf) {
72            FilterResult::MatchTerminal(idx) | FilterResult::MatchNonTerminal(idx) => {
73                if let Ok(ctxt) = L4Context::new(&mbuf, idx) {
74                    conn_tracker.process(mbuf, ctxt, subscription);
75                }
76            }
77            FilterResult::NoMatch => drop(mbuf),
78        }
79    }
80}
81
82/// Represents an HTTP connection's state during the connection lifetime.
83///
84/// ## Remarks
85/// Retina uses an internal parser to track and filter application-layer protocols, and transfers
86/// session ownership to the subscription to invoke the callback on a filter match. This is an
87/// optimization to avoid double-parsing: once for the filter and once for the subscription data.
88/// This is why most `Trackable` trait methods for this type are unimplemented.
89///
90/// ## Note
91/// Internal connection state is an associated type of a `pub` trait, and therefore must also be
92/// public. Currently, we hide its documentation to avoid confusing users. TODO: A future workaround
93/// could be to split the trait into a public and private part.
94#[doc(hidden)]
95pub struct TrackedHttp {
96    five_tuple: FiveTuple,
97}
98
99impl Trackable for TrackedHttp {
100    type Subscribed = HttpTransaction;
101
102    fn new(five_tuple: FiveTuple) -> Self {
103        TrackedHttp { five_tuple }
104    }
105
106    fn pre_match(&mut self, _pdu: L4Pdu, _session_id: Option<usize>) {}
107
108    fn on_match(&mut self, session: Session, subscription: &Subscription<Self::Subscribed>) {
109        if let SessionData::Http(http) = session.data {
110            subscription.invoke(HttpTransaction {
111                five_tuple: self.five_tuple,
112                data: *http,
113            });
114        }
115    }
116
117    fn post_match(&mut self, _pdu: L4Pdu, _subscription: &Subscription<Self::Subscribed>) {}
118
119    fn on_terminate(&mut self, _subscription: &Subscription<Self::Subscribed>) {}
120}