use crate::Timestamp;
use crate::extractor::L4Proto;
use crate::history::HistoryString;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum FlowSide {
Initiator,
Responder,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum EndReason {
Fin,
Rst,
IdleTimeout,
Evicted,
BufferOverflow,
ParseError,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum OverflowPolicy {
#[default]
SlidingWindow,
DropFlow,
}
#[derive(Debug, Clone, Default)]
#[non_exhaustive]
pub struct FlowStats {
pub packets_initiator: u64,
pub packets_responder: u64,
pub bytes_initiator: u64,
pub bytes_responder: u64,
pub started: Timestamp,
pub last_seen: Timestamp,
pub reassembly_dropped_ooo_initiator: u64,
pub reassembly_dropped_ooo_responder: u64,
pub reassembly_bytes_dropped_oversize_initiator: u64,
pub reassembly_bytes_dropped_oversize_responder: u64,
pub reassembler_high_watermark_initiator: u64,
pub reassembler_high_watermark_responder: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum FlowState {
SynSent,
SynReceived,
Established,
FinWait,
ClosingTcp,
Active,
Closed,
Reset,
Aborted,
}
impl FlowState {
pub fn is_terminal(self) -> bool {
matches!(
self,
FlowState::Closed | FlowState::Reset | FlowState::Aborted
)
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum AnomalyKind {
BufferOverflow {
side: FlowSide,
bytes: u64,
policy: OverflowPolicy,
},
OutOfOrderSegment { side: FlowSide, count: u64 },
FlowTableEvictionPressure {
evicted_in_tick: u64,
evicted_total: u64,
},
SessionParseError {
side: FlowSide,
reason: Option<String>,
},
}
#[derive(Debug, Clone)]
pub enum FlowEvent<K> {
Started {
key: K,
side: FlowSide,
ts: Timestamp,
l4: Option<L4Proto>,
},
Packet {
key: K,
side: FlowSide,
len: usize,
ts: Timestamp,
},
Established { key: K, ts: Timestamp },
StateChange {
key: K,
from: FlowState,
to: FlowState,
ts: Timestamp,
},
Ended {
key: K,
reason: EndReason,
stats: FlowStats,
history: HistoryString,
},
Anomaly {
key: Option<K>,
kind: AnomalyKind,
ts: Timestamp,
},
}
impl<K> FlowEvent<K> {
pub fn key(&self) -> Option<&K> {
match self {
FlowEvent::Started { key, .. }
| FlowEvent::Packet { key, .. }
| FlowEvent::Established { key, .. }
| FlowEvent::StateChange { key, .. }
| FlowEvent::Ended { key, .. } => Some(key),
FlowEvent::Anomaly { key, .. } => key.as_ref(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn flow_state_terminal() {
assert!(FlowState::Closed.is_terminal());
assert!(FlowState::Reset.is_terminal());
assert!(FlowState::Aborted.is_terminal());
assert!(!FlowState::Active.is_terminal());
assert!(!FlowState::Established.is_terminal());
assert!(!FlowState::SynSent.is_terminal());
}
#[test]
fn flow_event_key_borrow() {
let evt: FlowEvent<u32> = FlowEvent::Packet {
key: 7,
side: FlowSide::Initiator,
len: 100,
ts: Timestamp::default(),
};
assert_eq!(evt.key().copied(), Some(7));
}
#[test]
fn flow_event_key_returns_none_for_global_anomaly() {
let evt: FlowEvent<u32> = FlowEvent::Anomaly {
key: None,
kind: AnomalyKind::FlowTableEvictionPressure {
evicted_in_tick: 1,
evicted_total: 42,
},
ts: Timestamp::default(),
};
assert!(evt.key().is_none());
}
#[test]
fn flow_event_key_returns_some_for_per_flow_anomaly() {
let evt: FlowEvent<u32> = FlowEvent::Anomaly {
key: Some(7),
kind: AnomalyKind::OutOfOrderSegment {
side: FlowSide::Initiator,
count: 3,
},
ts: Timestamp::default(),
};
assert_eq!(evt.key().copied(), Some(7));
}
}