#![cfg(all(feature = "tokio", feature = "flow"))]
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::sync::Arc;
use std::sync::atomic::{AtomicU32, Ordering};
use flowscope::Timestamp;
use netring::anomaly::Severity;
use netring::anomaly::sink::NoopSink;
use netring::ctx::{CounterRegistry, Ctx, SourceIdx, StateMap};
use netring::monitor::HandlerRegistry;
use netring::prelude::*;
fn dummy_flow_started() -> FlowStarted<Tcp> {
let key = flowscope::extract::FiveTupleKey::new(
flowscope::L4Proto::Tcp,
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)), 22),
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 2)), 53456),
);
FlowStarted::<Tcp>::new(key, Some(flowscope::L4Proto::Tcp), Timestamp::new(0, 0))
}
fn fresh_ctx<'a>(
state: &'a mut StateMap,
sink: &'a mut NoopSink,
counters: &'a mut CounterRegistry,
flow_states: &'a mut netring::ctx::FlowStateRegistry,
) -> Ctx<'a> {
Ctx::new(
None,
Timestamp::new(0, 0),
SourceIdx(0),
state,
sink,
counters,
flow_states,
)
}
#[test]
fn macro_returns_typed_detector_inferred_by_detect() {
let _m = Monitor::builder()
.interface("lo")
.protocol::<Tcp>()
.detect(netring::detector! {
name: "InferenceCheck",
severity: Info,
event: FlowStarted<Tcp>,
emit: |_evt, _ctx| {
},
})
.build();
}
#[test]
fn detector_macro_drives_dispatch_path() {
let count = Arc::new(AtomicU32::new(0));
let c = Arc::clone(&count);
let det = netring::detector! {
name: "Counter",
severity: Info,
event: FlowStarted<Tcp>,
emit: |_evt, _ctx| {
c.fetch_add(1, Ordering::Relaxed);
},
};
let mut reg = HandlerRegistry::default();
reg.register::<FlowStarted<Tcp>, _, _>(det);
let mut disp = reg.into_dispatcher().unwrap();
let mut state = StateMap::default();
let mut sink = NoopSink;
let mut counters = CounterRegistry::default();
let mut flow_states = netring::ctx::FlowStateRegistry::default();
let mut ctx = fresh_ctx(&mut state, &mut sink, &mut counters, &mut flow_states);
let evt = dummy_flow_started();
for _ in 0..6 {
disp.dispatch::<FlowStarted<Tcp>>(&evt, &mut ctx).unwrap();
}
assert_eq!(count.load(Ordering::Relaxed), 6);
}
#[test]
fn detector_macro_guard_short_circuits_emit() {
let fired = Arc::new(AtomicU32::new(0));
let f = Arc::clone(&fired);
let det = netring::detector! {
name: "SshGuarded",
severity: Info,
event: FlowStarted<Tcp>,
matches: |evt| evt.key.either_port(22),
emit: |_evt, _ctx| {
f.fetch_add(1, Ordering::Relaxed);
},
};
let mut reg = HandlerRegistry::default();
reg.register::<FlowStarted<Tcp>, _, _>(det);
let mut disp = reg.into_dispatcher().unwrap();
let mut state = StateMap::default();
let mut sink = NoopSink;
let mut counters = CounterRegistry::default();
let mut flow_states = netring::ctx::FlowStateRegistry::default();
let mut ctx = fresh_ctx(&mut state, &mut sink, &mut counters, &mut flow_states);
disp.dispatch::<FlowStarted<Tcp>>(&dummy_flow_started(), &mut ctx)
.unwrap();
assert_eq!(fired.load(Ordering::Relaxed), 1);
let key = flowscope::extract::FiveTupleKey::new(
flowscope::L4Proto::Tcp,
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 1)), 12345),
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(10, 0, 0, 2)), 80),
);
let non_ssh = FlowStarted::<Tcp>::new(key, Some(flowscope::L4Proto::Tcp), Timestamp::new(0, 0));
disp.dispatch::<FlowStarted<Tcp>>(&non_ssh, &mut ctx)
.unwrap();
assert_eq!(fired.load(Ordering::Relaxed), 1, "guard short-circuited");
}
#[test]
fn detector_macro_emit_body_can_early_return() {
let det = netring::detector! {
name: "EarlyReturn",
severity: Info,
event: FlowStarted<Tcp>,
emit: |evt, ctx| {
let Some(flowscope::L4Proto::Tcp) = evt.l4 else { return };
let now = ctx.ts;
ctx.sink_mut()
.begin("EarlyReturn", Severity::Info, now)
.emit();
},
};
let mut reg = HandlerRegistry::default();
reg.register::<FlowStarted<Tcp>, _, _>(det);
let _ = reg.into_dispatcher().unwrap();
}