use std::task::{Context, Poll};
use std::time::{Duration, Instant};
use flowscope::L4Proto;
use flowscope::driver::Event as FsEvent;
use flowscope::extract::FiveTuple;
use crate::AsyncCapture;
use crate::anomaly::sink::AnomalySink;
use crate::ctx::{CounterRegistry, Ctx, SourceIdx, StateMap};
use crate::error::Result;
use crate::monitor::backend::AnyBackend;
use crate::monitor::dispatcher::Dispatcher;
use crate::monitor::{BackendErrorPolicy, HandlerErrorPolicy, Monitor};
use crate::protocol::FlowKey;
#[cfg(feature = "icmp")]
use crate::protocol::builtin::Icmp;
use crate::protocol::builtin::{Tcp, Udp};
use crate::protocol::event_typed::{
AnyFlowAnomaly, FlowEnded, FlowEstablished, FlowPacket, FlowStarted, FlowTick, ParserClosed,
TcpRst, Tick,
};
use std::time::SystemTime;
pub(crate) enum StopCondition {
Deadline(Instant),
Signal,
Idle(Duration),
}
pub(crate) async fn run_loop(monitor: Monitor, stop: StopCondition) -> Result<()> {
let Monitor {
interfaces,
#[cfg(feature = "af-xdp")]
xdp_interfaces,
mut driver,
mut dispatcher,
mut protocol_slots,
mut state_map,
mut counters,
mut sink,
mut tick_handlers,
detector_names: _,
monitor_name,
drain_timeout,
broadcast_handles: _,
#[cfg(all(feature = "pcap", feature = "tokio"))]
pcap_source_path: _,
#[cfg(all(feature = "pcap", feature = "tokio"))]
pcap_speed_factor: _,
mut flow_states,
fanout,
label_table,
mut merge_rx,
handler_error_policy,
backend_error_policy,
mut capture_stats,
health,
mut flow_exporters,
} = monitor;
let monitor_name_borrow: Option<&str> = monitor_name.as_deref();
let cap_count = {
#[cfg(feature = "af-xdp")]
{
interfaces.len() + xdp_interfaces.len()
}
#[cfg(not(feature = "af-xdp"))]
{
interfaces.len()
}
};
let mut caps: Vec<AnyBackend> = Vec::with_capacity(cap_count);
for iface in &interfaces {
let cap = match fanout {
Some((mode, group_id)) => {
let rx = crate::Capture::builder()
.interface(iface)
.fanout(mode, group_id)
.build()?;
AsyncCapture::new(rx)?
}
None => AsyncCapture::open(iface)?,
};
caps.push(AnyBackend::AfPacket(cap));
}
#[cfg(feature = "af-xdp")]
for iface in &xdp_interfaces {
let xdp = crate::AsyncXdpSocket::open(iface)?;
caps.push(AnyBackend::Xdp(xdp));
}
health.mark_started();
health.mark_sockets_open();
let mut events: Vec<FsEvent<FlowKey>> = Vec::with_capacity(64);
let mut shutdown = ShutdownSignal::new(stop);
let mut rr_anchor: usize = 0;
let mut backend_errors: u64 = 0;
let mut last_event_at = Instant::now();
let mut tick_intervals: Vec<tokio::time::Interval> = tick_handlers
.iter()
.map(|t| {
let mut int =
tokio::time::interval_at(tokio::time::Instant::now() + t.period, t.period);
int.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
int
})
.collect();
let mut telemetry_interval = capture_stats.as_ref().map(|reg| {
let mut int =
tokio::time::interval_at(tokio::time::Instant::now() + reg.period, reg.period);
int.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
int
});
let mut telemetry_sampler =
crate::monitor::telemetry::TelemetrySampler::new(if capture_stats.is_some() {
caps.len()
} else {
0
});
loop {
let ready = tokio::select! {
biased;
_ = shutdown.recv(last_event_at) => break,
idx = ready_capture(&mut caps, &mut rr_anchor) => idx,
tick_idx = next_tick(&mut tick_intervals), if !tick_intervals.is_empty() => {
last_event_at = Instant::now();
fire_tick(
tick_idx,
&mut tick_handlers,
&mut dispatcher,
sink.as_mut(),
&mut state_map,
&mut counters,
monitor_name_borrow,
&mut flow_states,
&label_table,
)
.await?;
health.record_event(driver.tracker().flow_count());
continue;
}
req = recv_merge(&mut merge_rx), if merge_rx.is_some() => {
if let Some(req) = req {
let taken = state_map.take_dyn(req.type_id);
let _ = req.reply.send(taken);
}
continue;
}
_ = next_telemetry_sample(&mut telemetry_interval),
if telemetry_interval.is_some() =>
{
if let Some(reg) = capture_stats.as_mut() {
sample_and_fire_capture_stats(
&caps,
&mut telemetry_sampler,
reg,
sink.as_mut(),
&mut state_map,
&mut counters,
monitor_name_borrow,
&mut flow_states,
&label_table,
&health,
)?;
}
continue;
}
};
let i = match ready {
Some(Ok(i)) => i,
Some(Err(e)) => match backend_error_policy {
BackendErrorPolicy::FailFast => return Err(e),
BackendErrorPolicy::SkipSource => {
backend_errors += 1;
health.record_backend_error();
tracing::warn!(error = %e, count = backend_errors, "capture backend error (SkipSource)");
if backend_errors > 64 {
return Err(e);
}
tokio::time::sleep(Duration::from_millis(50)).await;
continue;
}
},
None => break, };
backend_errors = 0; let source = SourceIdx(i as u8);
last_event_at = Instant::now();
events.clear();
let last_ts = caps[i]
.drain_batch(|view| driver.track_into(view, &mut events))
.await?;
let Some(ts) = last_ts else { continue };
dispatch_tracked_events(
&mut dispatcher,
sink.as_mut(),
&mut state_map,
&mut counters,
&mut events,
source,
monitor_name_borrow,
&mut flow_states,
&label_table,
handler_error_policy,
&mut flow_exporters,
&health,
)
.await?;
drain_protocol_slots(
&mut dispatcher,
&mut protocol_slots,
&driver,
sink.as_mut(),
&mut state_map,
&mut counters,
&mut flow_states,
ts,
source,
monitor_name_borrow,
&label_table,
handler_error_policy,
&health,
)?;
health.record_event(driver.tracker().flow_count());
}
if !drain_timeout.is_zero() {
let deadline = Instant::now() + drain_timeout;
drain_phase(
&mut driver,
&mut dispatcher,
sink.as_mut(),
&mut state_map,
&mut counters,
&mut protocol_slots,
monitor_name_borrow,
deadline,
&mut flow_states,
&label_table,
handler_error_policy,
&mut flow_exporters,
&health,
)
.await?;
}
for exporter in flow_exporters.iter_mut() {
let _ = exporter.flush();
}
Ok(())
}
#[cfg(all(feature = "pcap", feature = "tokio"))]
pub(crate) async fn replay_loop(
monitor: Monitor,
path: std::path::PathBuf,
config: crate::pcap_source::AsyncPcapConfig,
) -> Result<()> {
use std::pin::Pin;
use futures_core::Stream;
let Monitor {
interfaces: _,
#[cfg(feature = "af-xdp")]
xdp_interfaces: _,
mut driver,
mut dispatcher,
mut protocol_slots,
mut state_map,
mut counters,
mut sink,
tick_handlers: _,
detector_names: _,
monitor_name,
drain_timeout,
broadcast_handles: _,
pcap_source_path: _,
pcap_speed_factor: _,
mut flow_states,
fanout: _,
label_table,
merge_rx: _, handler_error_policy,
backend_error_policy: _, capture_stats: _, health,
mut flow_exporters,
} = monitor;
let monitor_name_borrow: Option<&str> = monitor_name.as_deref();
let mut source = crate::pcap_source::AsyncPcapSource::open_with_config(&path, config).await?;
let mut events: Vec<FsEvent<FlowKey>> = Vec::with_capacity(64);
health.mark_started();
health.mark_sockets_open();
loop {
let next = std::future::poll_fn(|cx| Pin::new(&mut source).poll_next(cx)).await;
let pkt = match next {
Some(Ok(p)) => p,
Some(Err(e)) => return Err(e),
None => break,
};
let view = flowscope::PacketView::new(&pkt.data, pkt.timestamp);
events.clear();
driver.track_into(view, &mut events);
dispatch_tracked_events(
&mut dispatcher,
sink.as_mut(),
&mut state_map,
&mut counters,
&mut events,
SourceIdx(0),
monitor_name_borrow,
&mut flow_states,
&label_table,
handler_error_policy,
&mut flow_exporters,
&health,
)
.await?;
drain_protocol_slots(
&mut dispatcher,
&mut protocol_slots,
&driver,
sink.as_mut(),
&mut state_map,
&mut counters,
&mut flow_states,
pkt.timestamp,
SourceIdx(0),
monitor_name_borrow,
&label_table,
handler_error_policy,
&health,
)?;
health.record_event(driver.tracker().flow_count());
}
if !drain_timeout.is_zero() {
let deadline = Instant::now() + drain_timeout;
drain_phase(
&mut driver,
&mut dispatcher,
sink.as_mut(),
&mut state_map,
&mut counters,
&mut protocol_slots,
monitor_name_borrow,
deadline,
&mut flow_states,
&label_table,
handler_error_policy,
&mut flow_exporters,
&health,
)
.await?;
}
for exporter in flow_exporters.iter_mut() {
let _ = exporter.flush();
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
async fn drain_phase(
driver: &mut flowscope::driver::Driver<FiveTuple>,
dispatcher: &mut Dispatcher,
sink: &mut dyn AnomalySink,
state_map: &mut StateMap,
counters: &mut CounterRegistry,
protocol_slots: &mut [Box<dyn crate::monitor::ProtocolSlot>],
monitor_name: Option<&str>,
deadline: Instant,
flow_states: &mut crate::ctx::FlowStateRegistry,
label_table: &flowscope::well_known::LabelTable,
policy: HandlerErrorPolicy,
flow_exporters: &mut [Box<dyn crate::export::FlowExporter>],
health: &crate::monitor::health::HealthState,
) -> Result<()> {
let mut leftover: Vec<FsEvent<FlowKey>> = Vec::new();
driver.finish_into(&mut leftover);
for evt in leftover.drain(..) {
if Instant::now() >= deadline {
return Ok(());
}
if !flow_exporters.is_empty()
&& let FsEvent::FlowEnded {
key, stats, reason, ..
} = &evt
{
let record = crate::export::FlowRecord::from_ended(key, stats, *reason);
for exporter in flow_exporters.iter_mut() {
exporter.export(&record);
}
}
let res = match dispatch_lifecycle(
dispatcher,
sink,
state_map,
counters,
evt.clone(),
SourceIdx(0),
monitor_name,
flow_states,
label_table,
) {
Ok(()) => dispatch_lifecycle_async(dispatcher, evt).await,
Err(e) => Err(e),
};
if let Err(e) = res {
match policy {
HandlerErrorPolicy::Propagate => return Err(e),
HandlerErrorPolicy::Isolate => {
health.record_handler_error();
tracing::warn!(error = %e, "handler error isolated (drain)")
}
}
}
}
if Instant::now() >= deadline {
return Ok(());
}
let ts = flowscope::Timestamp::from_system_time(SystemTime::now());
for slot in protocol_slots.iter_mut() {
if Instant::now() >= deadline {
return Ok(());
}
let mut ctx = Ctx::new(
None,
ts,
SourceIdx(0),
state_map,
sink,
counters,
flow_states,
);
ctx.monitor_name = monitor_name;
ctx.label_table = label_table;
ctx.tracker = Some(driver.tracker());
if let Err(e) = slot.drain_and_dispatch(dispatcher, &mut ctx) {
match policy {
HandlerErrorPolicy::Propagate => return Err(e),
HandlerErrorPolicy::Isolate => {
health.record_handler_error();
tracing::warn!(error = %e, "handler error isolated (drain slot)")
}
}
}
}
if Instant::now() >= deadline {
return Ok(());
}
sink.flush().map_err(|e| {
crate::error::Error::Io(std::io::Error::new(e.kind(), format!("sink flush: {e}")))
})?;
Ok(())
}
async fn ready_capture(caps: &mut [AnyBackend], anchor: &mut usize) -> Option<Result<usize>> {
std::future::poll_fn(|cx: &mut Context<'_>| -> Poll<Option<Result<usize>>> {
let n = caps.len();
if n == 0 {
return Poll::Ready(None);
}
let start = *anchor % n;
for offset in 0..n {
let i = (start + offset) % n;
match caps[i].poll_read_ready(cx) {
Poll::Ready(Ok(())) => {
*anchor = (i + 1) % n;
return Poll::Ready(Some(Ok(i)));
}
Poll::Ready(Err(e)) => return Poll::Ready(Some(Err(e))),
Poll::Pending => {}
}
}
Poll::Pending
})
.await
}
async fn next_tick(intervals: &mut [tokio::time::Interval]) -> usize {
std::future::poll_fn(|cx: &mut Context<'_>| -> Poll<usize> {
for (i, interval) in intervals.iter_mut().enumerate() {
if interval.poll_tick(cx).is_ready() {
return Poll::Ready(i);
}
}
Poll::Pending
})
.await
}
async fn recv_merge(
rx: &mut Option<tokio::sync::mpsc::UnboundedReceiver<crate::monitor::merge::MergeRequest>>,
) -> Option<crate::monitor::merge::MergeRequest> {
match rx {
Some(r) => r.recv().await,
None => std::future::pending().await,
}
}
async fn next_telemetry_sample(interval: &mut Option<tokio::time::Interval>) {
match interval {
Some(int) => {
int.tick().await;
}
None => std::future::pending().await,
}
}
#[allow(clippy::too_many_arguments)]
fn sample_and_fire_capture_stats(
caps: &[AnyBackend],
sampler: &mut crate::monitor::telemetry::TelemetrySampler,
reg: &mut crate::monitor::telemetry::CaptureStatsRegistration,
sink: &mut dyn AnomalySink,
state_map: &mut StateMap,
counters: &mut CounterRegistry,
monitor_name: Option<&str>,
flow_states: &mut crate::ctx::FlowStateRegistry,
label_table: &flowscope::well_known::LabelTable,
health: &crate::monitor::health::HealthState,
) -> Result<()> {
let now = flowscope::Timestamp::from_system_time(SystemTime::now());
let mut total_packets: u64 = 0;
let mut total_drops: u64 = 0;
for (i, cap) in caps.iter().enumerate() {
let cum = match cap.cumulative_stats() {
Ok(s) => s,
Err(e) => {
tracing::warn!(
source = i,
error = %e,
"capture stats read failed; skipping telemetry sample for this source"
);
continue;
}
};
let telemetry = sampler.sample(i, cum);
total_packets += telemetry.packets;
total_drops += telemetry.drops;
let mut ctx = Ctx::new(
None,
now,
SourceIdx(i as u8),
state_map,
sink,
counters,
flow_states,
);
ctx.monitor_name = monitor_name;
ctx.label_table = label_table;
(reg.handler)(&telemetry, &mut ctx)?;
}
health.record_totals(total_packets, total_drops);
Ok(())
}
#[allow(clippy::too_many_arguments)]
async fn dispatch_tracked_events(
dispatcher: &mut Dispatcher,
sink: &mut dyn AnomalySink,
state_map: &mut StateMap,
counters: &mut CounterRegistry,
events: &mut Vec<FsEvent<FlowKey>>,
source: SourceIdx,
monitor_name: Option<&str>,
flow_states: &mut crate::ctx::FlowStateRegistry,
label_table: &flowscope::well_known::LabelTable,
policy: HandlerErrorPolicy,
flow_exporters: &mut [Box<dyn crate::export::FlowExporter>],
health: &crate::monitor::health::HealthState,
) -> Result<()> {
for evt in events.drain(..) {
if !flow_exporters.is_empty()
&& let FsEvent::FlowEnded {
key, stats, reason, ..
} = &evt
{
let record = crate::export::FlowRecord::from_ended(key, stats, *reason);
for exporter in flow_exporters.iter_mut() {
exporter.export(&record);
}
}
let res = match dispatch_lifecycle(
dispatcher,
sink,
state_map,
counters,
evt.clone(),
source,
monitor_name,
flow_states,
label_table,
) {
Ok(()) => dispatch_lifecycle_async(dispatcher, evt).await,
Err(e) => Err(e),
};
if let Err(e) = res {
match policy {
HandlerErrorPolicy::Propagate => return Err(e),
HandlerErrorPolicy::Isolate => {
health.record_handler_error();
tracing::warn!(error = %e, "handler error isolated (per-event)")
}
}
}
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn drain_protocol_slots(
dispatcher: &mut Dispatcher,
protocol_slots: &mut [Box<dyn crate::monitor::ProtocolSlot>],
driver: &flowscope::driver::Driver<FiveTuple>,
sink: &mut dyn AnomalySink,
state_map: &mut StateMap,
counters: &mut CounterRegistry,
flow_states: &mut crate::ctx::FlowStateRegistry,
ts: flowscope::Timestamp,
source: SourceIdx,
monitor_name: Option<&str>,
label_table: &flowscope::well_known::LabelTable,
policy: HandlerErrorPolicy,
health: &crate::monitor::health::HealthState,
) -> Result<()> {
for slot in protocol_slots.iter_mut() {
let mut ctx = Ctx::new(None, ts, source, state_map, sink, counters, flow_states);
ctx.monitor_name = monitor_name;
ctx.label_table = label_table;
ctx.tracker = Some(driver.tracker());
if let Err(e) = slot.drain_and_dispatch(dispatcher, &mut ctx) {
match policy {
HandlerErrorPolicy::Propagate => return Err(e),
HandlerErrorPolicy::Isolate => {
health.record_handler_error();
tracing::warn!(error = %e, "handler error isolated (per-slot)")
}
}
}
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
async fn fire_tick(
tick_idx: usize,
tick_handlers: &mut [crate::monitor::tick::TickRegistration],
dispatcher: &mut Dispatcher,
sink: &mut dyn AnomalySink,
state_map: &mut StateMap,
counters: &mut CounterRegistry,
monitor_name: Option<&str>,
flow_states: &mut crate::ctx::FlowStateRegistry,
label_table: &flowscope::well_known::LabelTable,
) -> Result<()> {
let reg = &mut tick_handlers[tick_idx];
let tick = Tick {
now: flowscope::Timestamp::from_system_time(SystemTime::now()),
period: reg.period,
};
{
let mut ctx = Ctx::new(
None,
tick.now,
SourceIdx(0),
state_map,
sink,
counters,
flow_states,
);
ctx.monitor_name = monitor_name;
ctx.label_table = label_table;
(reg.handler)(&tick, &mut ctx)?;
}
{
let mut ctx = Ctx::new(
None,
tick.now,
SourceIdx(0),
state_map,
sink,
counters,
flow_states,
);
ctx.monitor_name = monitor_name;
ctx.label_table = label_table;
dispatcher.dispatch::<Tick>(&tick, &mut ctx)?;
}
dispatcher.dispatch_async::<Tick>(&tick).await?;
Ok(())
}
struct ShutdownSignal {
stop: StopCondition,
sig_int: Option<tokio::signal::unix::Signal>,
sig_term: Option<tokio::signal::unix::Signal>,
}
impl ShutdownSignal {
fn new(stop: StopCondition) -> Self {
let (sig_int, sig_term) = match &stop {
StopCondition::Signal => {
let sigint =
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::interrupt()).ok();
let sigterm =
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()).ok();
(sigint, sigterm)
}
StopCondition::Deadline(_) | StopCondition::Idle(_) => (None, None),
};
Self {
stop,
sig_int,
sig_term,
}
}
async fn recv(&mut self, last_event_at: Instant) {
match &mut self.stop {
StopCondition::Deadline(t) => {
tokio::time::sleep_until((*t).into()).await;
}
StopCondition::Idle(window) => {
tokio::time::sleep_until((last_event_at + *window).into()).await;
}
StopCondition::Signal => match (self.sig_int.as_mut(), self.sig_term.as_mut()) {
(Some(i), Some(t)) => {
tokio::select! {
_ = i.recv() => {},
_ = t.recv() => {},
}
}
(Some(i), None) => {
let _ = i.recv().await;
}
(None, Some(t)) => {
let _ = t.recv().await;
}
(None, None) => std::future::pending::<()>().await,
},
}
}
}
async fn dispatch_lifecycle_async(
dispatcher: &mut Dispatcher,
evt: FsEvent<FlowKey>,
) -> Result<()> {
match evt {
FsEvent::FlowStarted { key, ts, l4 } => match l4 {
Some(L4Proto::Tcp) => {
dispatcher
.dispatch_async(&FlowStarted::<Tcp>::new(key, l4, ts))
.await?;
}
Some(L4Proto::Udp) => {
dispatcher
.dispatch_async(&FlowStarted::<Udp>::new(key, l4, ts))
.await?;
}
#[cfg(feature = "icmp")]
Some(L4Proto::Icmp) | Some(L4Proto::IcmpV6) => {
dispatcher
.dispatch_async(&FlowStarted::<Icmp>::new(key, l4, ts))
.await?;
}
_ => {}
},
FsEvent::FlowEnded {
key,
reason,
stats,
ts,
l4,
..
} => match l4 {
Some(L4Proto::Tcp) => {
let is_rst = reason == flowscope::EndReason::Rst;
dispatcher
.dispatch_async(&FlowEnded::<Tcp>::new(key, reason, stats.clone(), l4, ts))
.await?;
if is_rst {
dispatcher
.dispatch_async(&TcpRst::new(key, stats, ts))
.await?;
}
}
Some(L4Proto::Udp) => {
dispatcher
.dispatch_async(&FlowEnded::<Udp>::new(key, reason, stats, l4, ts))
.await?;
}
#[cfg(feature = "icmp")]
Some(L4Proto::Icmp) | Some(L4Proto::IcmpV6) => {
dispatcher
.dispatch_async(&FlowEnded::<Icmp>::new(key, reason, stats, l4, ts))
.await?;
}
_ => {}
},
FsEvent::FlowEstablished { key, ts, l4 } => {
if matches!(l4, Some(L4Proto::Tcp)) {
dispatcher
.dispatch_async(&FlowEstablished::<Tcp>::new(key, ts))
.await?;
}
}
FsEvent::FlowAnomaly { key, kind, ts } => {
dispatcher
.dispatch_async(&AnyFlowAnomaly {
key: Some(key),
kind,
ts,
})
.await?;
}
FsEvent::TrackerAnomaly { kind, ts } => {
dispatcher
.dispatch_async(&AnyFlowAnomaly {
key: None,
kind,
ts,
})
.await?;
}
FsEvent::FlowPacket {
key,
side,
len,
ts,
tcp,
} => {
dispatcher
.dispatch_async(&FlowPacket::new(key.proto, key, side, len, tcp, ts))
.await?;
}
FsEvent::FlowTick { key, stats, ts } => match key.proto {
L4Proto::Tcp => {
dispatcher
.dispatch_async(&FlowTick::<Tcp>::new(key, stats, ts))
.await?;
}
L4Proto::Udp => {
dispatcher
.dispatch_async(&FlowTick::<Udp>::new(key, stats, ts))
.await?;
}
#[cfg(feature = "icmp")]
L4Proto::Icmp | L4Proto::IcmpV6 => {
dispatcher
.dispatch_async(&FlowTick::<Icmp>::new(key, stats, ts))
.await?;
}
_ => {}
},
FsEvent::ParserClosed {
key,
parser_kind,
reason,
ts,
} => match key.proto {
L4Proto::Tcp => {
dispatcher
.dispatch_async(&ParserClosed::<Tcp>::new(key, parser_kind, reason, ts))
.await?;
}
L4Proto::Udp => {
dispatcher
.dispatch_async(&ParserClosed::<Udp>::new(key, parser_kind, reason, ts))
.await?;
}
#[cfg(feature = "icmp")]
L4Proto::Icmp | L4Proto::IcmpV6 => {
dispatcher
.dispatch_async(&ParserClosed::<Icmp>::new(key, parser_kind, reason, ts))
.await?;
}
_ => {}
},
_ => {}
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn dispatch_lifecycle(
dispatcher: &mut Dispatcher,
sink: &mut dyn AnomalySink,
state_map: &mut StateMap,
counters: &mut CounterRegistry,
evt: FsEvent<FlowKey>,
source: SourceIdx,
monitor_name: Option<&str>,
flow_states: &mut crate::ctx::FlowStateRegistry,
label_table: &flowscope::well_known::LabelTable,
) -> Result<()> {
macro_rules! dispatch_one {
($ty:ty, $payload:expr, $flow:expr, $ts:expr) => {{
let mut ctx = Ctx {
flow: $flow,
ts: $ts,
source,
monitor_name,
state_map: &mut *state_map,
sink: &mut *sink,
counters: &mut *counters,
flow_states: &mut *flow_states,
label_table,
tracker: None,
};
dispatcher.dispatch::<$ty>(&$payload, &mut ctx)?;
}};
}
match evt {
FsEvent::FlowStarted { key, ts, l4 } => match l4 {
Some(L4Proto::Tcp) => {
dispatch_one!(
FlowStarted<Tcp>,
FlowStarted::<Tcp>::new(key, l4, ts),
Some(key),
ts
);
}
Some(L4Proto::Udp) => {
dispatch_one!(
FlowStarted<Udp>,
FlowStarted::<Udp>::new(key, l4, ts),
Some(key),
ts
);
}
#[cfg(feature = "icmp")]
Some(L4Proto::Icmp) | Some(L4Proto::IcmpV6) => {
dispatch_one!(
FlowStarted<Icmp>,
FlowStarted::<Icmp>::new(key, l4, ts),
Some(key),
ts
);
}
_ => {}
},
FsEvent::FlowEnded {
key,
reason,
stats,
ts,
l4,
..
} => match l4 {
Some(L4Proto::Tcp) => {
let is_rst = reason == flowscope::EndReason::Rst;
dispatch_one!(
FlowEnded<Tcp>,
FlowEnded::<Tcp>::new(key, reason, stats.clone(), l4, ts),
Some(key),
ts
);
if is_rst {
dispatch_one!(TcpRst, TcpRst::new(key, stats, ts), Some(key), ts);
}
}
Some(L4Proto::Udp) => {
dispatch_one!(
FlowEnded<Udp>,
FlowEnded::<Udp>::new(key, reason, stats, l4, ts),
Some(key),
ts
);
}
#[cfg(feature = "icmp")]
Some(L4Proto::Icmp) | Some(L4Proto::IcmpV6) => {
dispatch_one!(
FlowEnded<Icmp>,
FlowEnded::<Icmp>::new(key, reason, stats, l4, ts),
Some(key),
ts
);
}
_ => {}
},
FsEvent::FlowEstablished { key, ts, l4 } => {
if matches!(l4, Some(L4Proto::Tcp)) {
dispatch_one!(
FlowEstablished<Tcp>,
FlowEstablished::<Tcp>::new(key, ts),
Some(key),
ts
);
}
}
FsEvent::FlowAnomaly { key, kind, ts } => {
dispatch_one!(
AnyFlowAnomaly,
AnyFlowAnomaly {
key: Some(key),
kind,
ts,
},
Some(key),
ts
);
}
FsEvent::TrackerAnomaly { kind, ts } => {
dispatch_one!(
AnyFlowAnomaly,
AnyFlowAnomaly {
key: None,
kind,
ts,
},
None,
ts
);
}
FsEvent::FlowPacket {
key,
side,
len,
ts,
tcp,
} => {
dispatch_one!(
FlowPacket,
FlowPacket::new(key.proto, key, side, len, tcp, ts),
Some(key),
ts
);
}
FsEvent::FlowTick { key, stats, ts } => match key.proto {
L4Proto::Tcp => {
dispatch_one!(
FlowTick<Tcp>,
FlowTick::<Tcp>::new(key, stats, ts),
Some(key),
ts
);
}
L4Proto::Udp => {
dispatch_one!(
FlowTick<Udp>,
FlowTick::<Udp>::new(key, stats, ts),
Some(key),
ts
);
}
#[cfg(feature = "icmp")]
L4Proto::Icmp | L4Proto::IcmpV6 => {
dispatch_one!(
FlowTick<Icmp>,
FlowTick::<Icmp>::new(key, stats, ts),
Some(key),
ts
);
}
_ => {}
},
FsEvent::ParserClosed {
key,
parser_kind,
reason,
ts,
} => match key.proto {
L4Proto::Tcp => {
dispatch_one!(
ParserClosed<Tcp>,
ParserClosed::<Tcp>::new(key, parser_kind, reason, ts),
Some(key),
ts
);
}
L4Proto::Udp => {
dispatch_one!(
ParserClosed<Udp>,
ParserClosed::<Udp>::new(key, parser_kind, reason, ts),
Some(key),
ts
);
}
#[cfg(feature = "icmp")]
L4Proto::Icmp | L4Proto::IcmpV6 => {
dispatch_one!(
ParserClosed<Icmp>,
ParserClosed::<Icmp>::new(key, parser_kind, reason, ts),
Some(key),
ts
);
}
_ => {}
},
_ => {}
}
Ok(())
}