#![cfg(all(feature = "tokio", feature = "flow", feature = "integration-tests"))]
use std::net::SocketAddr;
use std::sync::Arc;
use std::sync::atomic::{AtomicU32, Ordering};
use std::time::Duration;
use netring::prelude::*;
async fn generate_lo_traffic(target: SocketAddr, dur: Duration) {
let deadline = tokio::time::Instant::now() + dur;
while tokio::time::Instant::now() < deadline {
let _ = tokio::time::timeout(
Duration::from_millis(20),
tokio::net::TcpStream::connect(target),
)
.await;
tokio::task::yield_now().await;
}
}
#[tokio::test(flavor = "current_thread")]
async fn monitor_lo_fires_on_synthetic_traffic() {
let listener = tokio::net::TcpListener::bind("127.0.0.1:0")
.await
.expect("bind ephemeral lo socket");
let target = listener.local_addr().expect("local_addr");
let accept_task = tokio::spawn(async move { while listener.accept().await.is_ok() {} });
let starts = Arc::new(AtomicU32::new(0));
let s = Arc::clone(&starts);
let monitor_result = Monitor::builder()
.interface("lo")
.protocol::<Tcp>()
.on::<FlowStarted<Tcp>>(move |_evt: &FlowStarted<Tcp>| {
s.fetch_add(1, Ordering::Relaxed);
Ok(())
})
.build();
let monitor = match monitor_result {
Ok(m) => m,
Err(e) => {
eprintln!("Monitor::build failed (likely needs CAP_NET_RAW): {e}");
accept_task.abort();
return;
}
};
let dur = Duration::from_millis(500);
let gen_task = tokio::spawn(generate_lo_traffic(target, dur));
let run_res = tokio::time::timeout(Duration::from_secs(5), monitor.run_for(dur)).await;
gen_task.abort();
accept_task.abort();
match run_res {
Ok(Ok(())) => {}
Ok(Err(e)) => {
eprintln!("monitor.run_for failed (likely CAP_NET_RAW missing): {e}");
return;
}
Err(_) => panic!("monitor.run_for didn't honour its 500ms deadline within 5s"),
}
let fired = starts.load(Ordering::Relaxed);
assert!(
fired >= 1,
"expected at least one FlowStarted<Tcp> from the lo generator, got {fired}"
);
eprintln!("monitor_lo_dispatch: handler fired {fired} times");
}
#[tokio::test(flavor = "current_thread")]
async fn monitor_lo_run_until_signal_can_be_aborted() {
let monitor_result = Monitor::builder().interface("lo").protocol::<Tcp>().build();
let monitor = match monitor_result {
Ok(m) => m,
Err(e) => {
eprintln!("Monitor::build failed: {e}");
return;
}
};
let res = tokio::time::timeout(Duration::from_millis(200), monitor.run_until_signal()).await;
let _ = res;
}
#[tokio::test(flavor = "current_thread")]
async fn monitor_lo_tick_handler_fires_at_period() {
let tick_count = Arc::new(AtomicU32::new(0));
let c = Arc::clone(&tick_count);
let monitor_result = Monitor::builder()
.interface("lo")
.tick(Duration::from_millis(50), move |_t: &Tick| {
c.fetch_add(1, Ordering::Relaxed);
Ok(())
})
.build();
let monitor = match monitor_result {
Ok(m) => m,
Err(e) => {
eprintln!("Monitor::build failed: {e}");
return;
}
};
let res = tokio::time::timeout(
Duration::from_secs(2),
monitor.run_for(Duration::from_millis(300)),
)
.await;
match res {
Ok(Ok(())) => {
let fired = tick_count.load(Ordering::Relaxed);
eprintln!("tick handler fired {fired} times in 300ms (period 50ms)");
assert!(
fired >= 2,
"expected tick handler to fire ≥ 2 times across 300ms / 50ms period; got {fired}"
);
}
Ok(Err(e)) => eprintln!("run_for failed (likely no CAP_NET_RAW): {e}"),
Err(_) => panic!("run_for didn't honour its 300ms deadline within 2s"),
}
}
#[tokio::test(flavor = "current_thread")]
async fn monitor_lo_with_two_interfaces_tags_source() {
let starts_per_source: Arc<std::sync::Mutex<[u32; 2]>> =
Arc::new(std::sync::Mutex::new([0; 2]));
let s = Arc::clone(&starts_per_source);
let monitor_result = Monitor::builder()
.interfaces(["lo", "lo"])
.protocol::<Tcp>()
.on_ctx::<FlowStarted<Tcp>>(move |_evt: &FlowStarted<Tcp>, ctx: &mut Ctx<'_>| {
let idx = ctx.source.0 as usize;
if idx < 2 {
s.lock().unwrap()[idx] += 1;
}
Ok(())
})
.build();
let monitor = match monitor_result {
Ok(m) => m,
Err(e) => {
eprintln!("Monitor::build failed: {e}");
return;
}
};
let _ = tokio::time::timeout(
Duration::from_millis(300),
monitor.run_for(Duration::from_millis(100)),
)
.await;
let counts = *starts_per_source.lock().unwrap();
eprintln!(
"monitor_lo_with_two_interfaces: source 0 = {}, source 1 = {}",
counts[0], counts[1]
);
}
#[tokio::test(flavor = "current_thread")]
async fn monitor_lo_with_layers_and_sink_chain() {
let monitor_result = Monitor::builder()
.interface("lo")
.protocol::<Tcp>()
.on_ctx::<FlowStarted<Tcp>>(|evt: &FlowStarted<Tcp>, ctx: &mut Ctx<'_>| {
let now = ctx.ts;
let key = evt.key;
ctx.sink_mut()
.begin("LoFlow", Severity::Info, now)
.with_key(&key)
.emit();
Ok(())
})
.layer(MinSeverity::at_least(Severity::Warning))
.sink(StdoutSink::with_capacity(1024))
.build();
let monitor = match monitor_result {
Ok(m) => m,
Err(e) => {
eprintln!("Monitor::build failed: {e}");
return;
}
};
let _ = tokio::time::timeout(
Duration::from_millis(300),
monitor.run_for(Duration::from_millis(100)),
)
.await;
}