Skip to main content

retina_core/subscription/
dns_transaction.rs

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