use std::{hash::Hash, time::Duration};
use super::{
BroadcastSlotHandle,
slot::SlotHandle,
typed_slot::{ErasedSlot, TypedConcreteDatagramSlot, TypedConcreteSlot},
typed_slot_heuristic::{TypedHeuristicDatagramSlot, TypedHeuristicSessionSlot},
};
use crate::{
PacketView, Timestamp,
dedup::Dedup,
detect::signatures::SignatureFn,
event::{AnomalyKind, EndReason, FlowEvent, FlowSide, FlowState, FlowStats},
extractor::{FlowExtractor, L4Proto, Orientation, TcpInfo},
flow_driver::FlowDriver,
history::HistoryString,
parser_kind::ParserKind,
reassembler::NoopReassemblerFactory,
session::{DatagramParser, SessionParser},
tracker::{FlowTracker, FlowTrackerConfig},
};
type IdleTimeoutFn<K> =
Box<dyn Fn(&K, Option<L4Proto>) -> Option<Duration> + Send + Sync + 'static>;
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[cfg_attr(feature = "serde", serde(tag = "type", rename_all = "snake_case"))]
#[cfg_attr(feature = "serde", serde(bound(serialize = "K: serde::Serialize")))]
#[non_exhaustive]
pub enum Event<K> {
Started {
key: K,
orientation: Orientation,
ts: Timestamp,
l4: Option<L4Proto>,
},
Established {
key: K,
ts: Timestamp,
l4: Option<L4Proto>,
},
StateChange {
key: K,
from: FlowState,
to: FlowState,
ts: Timestamp,
},
Packet {
key: K,
side: FlowSide,
orientation: Orientation,
len: usize,
ts: Timestamp,
tcp: Option<TcpInfo>,
},
Ended {
key: K,
reason: EndReason,
stats: FlowStats,
history: HistoryString,
l4: Option<L4Proto>,
ts: Timestamp,
},
Tick {
key: K,
stats: FlowStats,
ts: Timestamp,
},
ParserClosed {
key: K,
parser_kind: ParserKind,
reason: EndReason,
ts: Timestamp,
},
FlowAnomaly {
key: K,
kind: AnomalyKind,
ts: Timestamp,
},
TrackerAnomaly { kind: AnomalyKind, ts: Timestamp },
}
impl<K> Event<K> {
pub fn key(&self) -> Option<&K> {
match self {
Event::Started { key, .. }
| Event::Established { key, .. }
| Event::StateChange { key, .. }
| Event::Packet { key, .. }
| Event::Ended { key, .. }
| Event::Tick { key, .. }
| Event::ParserClosed { key, .. }
| Event::FlowAnomaly { key, .. } => Some(key),
Event::TrackerAnomaly { .. } => None,
}
}
pub fn tcp(&self) -> Option<&TcpInfo> {
match self {
Event::Packet { tcp, .. } => tcp.as_ref(),
_ => None,
}
}
pub fn timestamp(&self) -> Timestamp {
match self {
Event::Started { ts, .. }
| Event::Established { ts, .. }
| Event::StateChange { ts, .. }
| Event::Packet { ts, .. }
| Event::Ended { ts, .. }
| Event::Tick { ts, .. }
| Event::ParserClosed { ts, .. }
| Event::FlowAnomaly { ts, .. }
| Event::TrackerAnomaly { ts, .. } => *ts,
}
}
pub fn into_flow_event(self) -> Option<FlowEvent<K>> {
Some(match self {
Event::Started {
key,
orientation,
ts,
l4,
} => FlowEvent::Started {
key,
side: FlowSide::Initiator,
orientation,
ts,
l4,
},
Event::Established { key, ts, l4 } => FlowEvent::Established { key, ts, l4 },
Event::StateChange { key, from, to, ts } => {
FlowEvent::StateChange { key, from, to, ts }
}
Event::Packet {
key,
side,
orientation,
len,
ts,
tcp: _,
} => FlowEvent::Packet {
key,
side,
orientation,
len,
ts,
},
Event::Ended {
key,
reason,
stats,
history,
l4,
ts: _,
} => FlowEvent::Ended {
key,
reason,
stats,
history,
l4,
},
Event::Tick { key, stats, ts } => FlowEvent::Tick { key, stats, ts },
Event::FlowAnomaly { key, kind, ts } => FlowEvent::FlowAnomaly { key, kind, ts },
Event::TrackerAnomaly { kind, ts } => FlowEvent::TrackerAnomaly { kind, ts },
Event::ParserClosed { .. } => return None,
})
}
pub fn to_flow_event(&self) -> Option<FlowEvent<K>>
where
K: Clone,
{
self.clone().into_flow_event()
}
}
impl<K> From<FlowEvent<K>> for Event<K> {
fn from(ev: FlowEvent<K>) -> Self {
match ev {
FlowEvent::Started {
key,
orientation,
ts,
l4,
..
} => Event::Started {
key,
orientation,
ts,
l4,
},
FlowEvent::Established { key, ts, l4 } => Event::Established { key, ts, l4 },
FlowEvent::StateChange { key, from, to, ts } => {
Event::StateChange { key, from, to, ts }
}
FlowEvent::Packet {
key,
side,
orientation,
len,
ts,
} => Event::Packet {
key,
side,
orientation,
len,
ts,
tcp: None,
},
FlowEvent::Ended {
key,
reason,
stats,
history,
l4,
} => {
let ts = stats.last_seen;
Event::Ended {
key,
reason,
stats,
history,
l4,
ts,
}
}
FlowEvent::Tick { key, stats, ts } => Event::Tick { key, stats, ts },
FlowEvent::FlowAnomaly { key, kind, ts } => Event::FlowAnomaly { key, kind, ts },
FlowEvent::TrackerAnomaly { kind, ts } => Event::TrackerAnomaly { kind, ts },
}
}
}
pub struct Driver<E>
where
E: FlowExtractor,
E::Key: Hash + Eq + Clone + Send + Sync + 'static,
{
central: FlowDriver<E, NoopReassemblerFactory, ()>,
extractor: E,
emit_packet_details: bool,
slots: Vec<Box<dyn ErasedSlot<E::Key> + Send + Sync>>,
}
impl<E> Driver<E>
where
E: FlowExtractor + Clone + Send + 'static,
E::Key: Hash + Eq + Clone + Send + Sync + 'static,
{
pub fn builder(extractor: E) -> DriverBuilder<E> {
DriverBuilder {
extractor,
config: FlowTrackerConfig::default(),
monotonic_timestamps: false,
emit_anomalies: false,
emit_packet_details: false,
dedup: None,
idle_timeout_fn: None,
slots: Vec::new(),
}
}
pub fn track<'v>(&mut self, view: impl Into<PacketView<'v>>) -> Vec<Event<E::Key>> {
let mut out = Vec::new();
self.track_into(view, &mut out);
out
}
pub fn track_into<'v>(
&mut self,
view: impl Into<PacketView<'v>>,
out: &mut Vec<Event<E::Key>>,
) {
let view: PacketView<'v> = view.into();
let ts = view.timestamp;
let tcp_for_packet: Option<TcpInfo> = if self.emit_packet_details {
self.extractor.extract(view).and_then(|e| e.tcp)
} else {
None
};
let mut tcp_slot = tcp_for_packet;
for flow_ev in self.central.track(view).into_iter() {
let this_tcp = if matches!(flow_ev, FlowEvent::Packet { .. }) {
let t = tcp_slot;
tcp_slot = None;
t
} else {
None
};
if let Some(ev) = map_flow_event(flow_ev, this_tcp) {
out.push(ev);
}
}
for slot in &mut self.slots {
slot.track_into(view, ts, out);
}
}
pub fn sweep(&mut self, now: Timestamp) -> Vec<Event<E::Key>> {
let mut out = Vec::new();
self.sweep_into(now, &mut out);
out
}
pub fn sweep_into(&mut self, now: Timestamp, out: &mut Vec<Event<E::Key>>) {
for flow_ev in self.central.sweep(now) {
if let Some(ev) = map_flow_event(flow_ev, None) {
out.push(ev);
}
}
for slot in &mut self.slots {
slot.sweep_into(now, out);
}
}
pub fn finish(&mut self) -> Vec<Event<E::Key>> {
let mut out = Vec::new();
self.finish_into(&mut out);
out
}
pub fn finish_into(&mut self, out: &mut Vec<Event<E::Key>>) {
for flow_ev in self.central.finish() {
if let Some(ev) = map_flow_event(flow_ev, None) {
out.push(ev);
}
}
for slot in &mut self.slots {
slot.finish_into(out);
}
}
#[cfg(feature = "pcap")]
pub fn run_pcap<P: AsRef<std::path::Path>>(self, path: P) -> crate::Result<RunPcap<E>> {
let source = crate::pcap::PcapFlowSource::open(path)?;
Ok(RunPcap {
driver: self,
views: source.views(),
buf: Vec::with_capacity(32),
cursor: 0,
finished: false,
})
}
pub fn force_close(&mut self, key: &E::Key, now: Timestamp) -> Vec<Event<E::Key>> {
let mut out = Vec::new();
self.force_close_into(key, now, &mut out);
out
}
pub fn force_close_into(&mut self, key: &E::Key, now: Timestamp, out: &mut Vec<Event<E::Key>>) {
for slot in &mut self.slots {
slot.force_close_into(key, now, out);
}
for flow_ev in self.central.force_close(key, now) {
if let Some(ev) = map_flow_event(flow_ev, None) {
out.push(ev);
}
}
}
pub fn tracker(&self) -> &FlowTracker<E, ()> {
self.central.tracker()
}
pub fn tracker_mut(&mut self) -> &mut FlowTracker<E, ()> {
self.central.tracker_mut()
}
}
#[must_use = "a DriverBuilder does nothing until you register parsers and call `.build()`"]
pub struct DriverBuilder<E>
where
E: FlowExtractor,
E::Key: Hash + Eq + Clone + Send + Sync + 'static,
{
extractor: E,
config: FlowTrackerConfig,
monotonic_timestamps: bool,
emit_anomalies: bool,
emit_packet_details: bool,
dedup: Option<Dedup>,
idle_timeout_fn: Option<IdleTimeoutFn<E::Key>>,
slots: Vec<Box<dyn ErasedSlot<E::Key> + Send + Sync>>,
}
impl<E> DriverBuilder<E>
where
E: FlowExtractor + Clone + Send + 'static,
E::Key: Hash + Eq + Clone + Send + Sync + 'static,
{
pub fn config(&mut self, c: FlowTrackerConfig) -> &mut Self {
self.config = c;
self
}
pub fn monotonic_timestamps(&mut self, on: bool) -> &mut Self {
self.monotonic_timestamps = on;
self
}
pub fn emit_packet_details(&mut self, on: bool) -> &mut Self {
self.emit_packet_details = on;
self
}
pub fn emit_anomalies(&mut self, on: bool) -> &mut Self {
self.emit_anomalies = on;
self
}
pub fn dedup(&mut self, dedup: Dedup) -> &mut Self {
self.dedup = Some(dedup);
self
}
pub fn idle_timeout_fn<F>(&mut self, f: F) -> &mut Self
where
F: Fn(&E::Key, Option<L4Proto>) -> Option<Duration> + Send + Sync + 'static,
{
self.idle_timeout_fn = Some(Box::new(f));
self
}
pub fn session_on_ports<P, I>(&mut self, parser: P, ports: I) -> SlotHandle<P::Message, E::Key>
where
P: SessionParser + Clone + Send + Sync + 'static,
P::Message: Send + Sync + 'static,
I: IntoIterator<Item = u16>,
{
let port_set: smallvec::SmallVec<[u16; 4]> = ports.into_iter().collect();
let (slot, handle) = TypedConcreteSlot::new(
self.extractor.clone(),
parser,
self.config.clone(),
Some(port_set),
self.monotonic_timestamps,
);
self.slots.push(Box::new(slot));
handle
}
pub fn session_on_ports_broadcast_each<P, I>(
&mut self,
parser: P,
ports: I,
) -> BroadcastSlotHandle<P::Message, E::Key>
where
P: SessionParser + Clone + Send + Sync + 'static,
P::Message: Send + Sync + Clone + 'static,
E::Key: Send + Sync + Clone + 'static,
I: IntoIterator<Item = u16>,
{
let port_set: smallvec::SmallVec<[u16; 4]> = ports.into_iter().collect();
let (slot, handle) = super::typed_slot::TypedBroadcastSlot::new(
self.extractor.clone(),
parser,
self.config.clone(),
Some(port_set),
self.monotonic_timestamps,
);
self.slots.push(Box::new(slot));
handle
}
pub fn session_broadcast<P>(&mut self, parser: P) -> SlotHandle<P::Message, E::Key>
where
P: SessionParser + Clone + Send + Sync + 'static,
P::Message: Send + Sync + 'static,
{
let (slot, handle) = TypedConcreteSlot::new(
self.extractor.clone(),
parser,
self.config.clone(),
None,
self.monotonic_timestamps,
);
self.slots.push(Box::new(slot));
handle
}
pub fn session_heuristic<P>(
&mut self,
parser: P,
signature: SignatureFn,
) -> SlotHandle<P::Message, E::Key>
where
P: SessionParser + Clone + Send + Sync + 'static,
P::Message: Send + Sync + 'static,
{
self.session_heuristic_with_budget(
parser,
signature,
super::typed_slot_heuristic::DEFAULT_PROBE_PACKETS,
)
}
pub fn session_heuristic_with_budget<P>(
&mut self,
parser: P,
signature: SignatureFn,
max_probe_packets: u8,
) -> SlotHandle<P::Message, E::Key>
where
P: SessionParser + Clone + Send + Sync + 'static,
P::Message: Send + Sync + 'static,
{
let (slot, handle) = TypedHeuristicSessionSlot::new(
self.extractor.clone(),
parser,
self.config.clone(),
signature,
max_probe_packets,
self.monotonic_timestamps,
);
self.slots.push(Box::new(slot));
handle
}
pub fn datagram_on_ports<D, I>(&mut self, parser: D, ports: I) -> SlotHandle<D::Message, E::Key>
where
D: DatagramParser + Clone + Send + Sync + 'static,
D::Message: Send + Sync + 'static,
I: IntoIterator<Item = u16>,
{
let port_set: smallvec::SmallVec<[u16; 4]> = ports.into_iter().collect();
let (slot, handle) = TypedConcreteDatagramSlot::new(
self.extractor.clone(),
parser,
self.config.clone(),
Some(port_set),
self.monotonic_timestamps,
);
self.slots.push(Box::new(slot));
handle
}
pub fn datagram_broadcast<D>(&mut self, parser: D) -> SlotHandle<D::Message, E::Key>
where
D: DatagramParser + Clone + Send + Sync + 'static,
D::Message: Send + Sync + 'static,
{
let (slot, handle) = TypedConcreteDatagramSlot::new(
self.extractor.clone(),
parser,
self.config.clone(),
None,
self.monotonic_timestamps,
);
self.slots.push(Box::new(slot));
handle
}
pub fn datagram_heuristic<D>(
&mut self,
parser: D,
signature: SignatureFn,
) -> SlotHandle<D::Message, E::Key>
where
D: DatagramParser + Clone + Send + Sync + 'static,
D::Message: Send + Sync + 'static,
{
self.datagram_heuristic_with_budget(
parser,
signature,
super::typed_slot_heuristic::DEFAULT_PROBE_PACKETS,
)
}
pub fn datagram_heuristic_with_budget<D>(
&mut self,
parser: D,
signature: SignatureFn,
max_probe_packets: u8,
) -> SlotHandle<D::Message, E::Key>
where
D: DatagramParser + Clone + Send + Sync + 'static,
D::Message: Send + Sync + 'static,
{
let (slot, handle) = TypedHeuristicDatagramSlot::new(
self.extractor.clone(),
parser,
self.config.clone(),
signature,
max_probe_packets,
self.monotonic_timestamps,
);
self.slots.push(Box::new(slot));
handle
}
pub fn build(self) -> Driver<E> {
let mut central =
FlowDriver::with_config(self.extractor.clone(), NoopReassemblerFactory, self.config)
.with_emit_anomalies(self.emit_anomalies)
.with_monotonic_timestamps(self.monotonic_timestamps);
if let Some(d) = self.dedup {
central = central.with_dedup(d);
}
if let Some(f) = self.idle_timeout_fn {
central
.tracker_mut()
.set_idle_timeout_fn(move |k, l4| f(k, l4));
}
Driver {
central,
extractor: self.extractor,
emit_packet_details: self.emit_packet_details,
slots: self.slots,
}
}
}
fn map_flow_event<K>(ev: FlowEvent<K>, tcp: Option<TcpInfo>) -> Option<Event<K>> {
if matches!(ev, FlowEvent::StateChange { .. }) {
return None;
}
let mut event = Event::from(ev);
if let Event::Packet { tcp: slot, .. } = &mut event {
*slot = tcp;
}
Some(event)
}
#[cfg(feature = "pcap")]
#[must_use = "RunPcap is a lazy iterator — it replays no packets until consumed (e.g. in a `for` loop)"]
pub struct RunPcap<E>
where
E: FlowExtractor + Clone + Send + 'static,
E::Key: Hash + Eq + Clone + Send + Sync + 'static,
{
driver: Driver<E>,
views: crate::pcap::ViewIter<std::io::BufReader<std::fs::File>>,
buf: Vec<Event<E::Key>>,
cursor: usize,
finished: bool,
}
#[cfg(feature = "pcap")]
impl<E> Iterator for RunPcap<E>
where
E: FlowExtractor + Clone + Send + 'static,
E::Key: Hash + Eq + Clone + Send + Sync + 'static,
{
type Item = crate::Result<Event<E::Key>>;
fn next(&mut self) -> Option<Self::Item> {
loop {
if self.cursor < self.buf.len() {
let ev = self.buf[self.cursor].clone();
self.cursor += 1;
return Some(Ok(ev));
}
self.buf.clear();
self.cursor = 0;
match self.views.next() {
Some(Ok(view)) => {
self.driver.track_into(&view, &mut self.buf);
}
Some(Err(e)) => return Some(Err(e)),
None => {
if self.finished {
return None;
}
self.finished = true;
self.driver.finish_into(&mut self.buf);
}
}
}
}
}