use std::ffi::OsStr;
use std::fs;
use std::io::BufWriter;
use std::path::{Path, PathBuf};
use std::thread::sleep;
use std::time::Duration;
use flowscope::emit::EveJsonWriter;
use flowscope::extract::FiveTuple;
use flowscope::pcap::PcapFlowSource;
use flowscope::reassembler::BufferedReassemblerFactory;
use flowscope::{FlowDriver, Timestamp};
const POLL_INTERVAL: Duration = Duration::from_secs(3);
const PROCESSED_SUBDIR: &str = "processed";
fn main() -> Result<(), Box<dyn std::error::Error>> {
let dir = std::env::args()
.nth(1)
.ok_or("usage: pcap_dir_tail <watch-directory>")?;
let dir = PathBuf::from(dir);
if !dir.is_dir() {
return Err(format!("{} is not a directory", dir.display()).into());
}
let processed_dir = dir.join(PROCESSED_SUBDIR);
fs::create_dir_all(&processed_dir)?;
eprintln!(
"[tail] watching {} every {:?}",
dir.display(),
POLL_INTERVAL
);
let mut eve = EveJsonWriter::new(BufWriter::new(std::io::stdout().lock()));
loop {
match scan_and_process(&dir, &processed_dir, &mut eve) {
Ok(0) => {}
Ok(n) => eprintln!("[tail] processed {n} file(s)"),
Err(e) => eprintln!("[tail] error: {e}"),
}
sleep(POLL_INTERVAL);
}
}
fn scan_and_process<W: std::io::Write>(
dir: &Path,
processed_dir: &Path,
eve: &mut EveJsonWriter<W>,
) -> Result<usize, Box<dyn std::error::Error>> {
let mut processed = 0;
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.extension() != Some(OsStr::new("pcap")) {
continue;
}
let (flows, anomalies) = process_one(&path, eve)?;
eprintln!(
"[tail] processed: {:<32} [{flows} flows, {anomalies} anomalies]",
path.file_name().unwrap_or_default().to_string_lossy()
);
let dest = processed_dir.join(path.file_name().unwrap_or_default());
fs::rename(&path, &dest)?;
processed += 1;
}
Ok(processed)
}
fn process_one<W: std::io::Write>(
path: &Path,
eve: &mut EveJsonWriter<W>,
) -> Result<(u64, u64), Box<dyn std::error::Error>> {
let mut driver = FlowDriver::new(
FiveTuple::bidirectional(),
BufferedReassemblerFactory::default(),
)
.with_emit_anomalies(true);
let mut flows = 0u64;
let mut anomalies = 0u64;
let mut last_ts = Timestamp { sec: 0, nsec: 0 };
for view in PcapFlowSource::open(path)?.views() {
let view = view?;
last_ts = view.timestamp;
for ev in driver.track(&view) {
use flowscope::FlowEvent::*;
match ev {
Ended { .. } => flows += 1,
FlowAnomaly { .. } | TrackerAnomaly { .. } => anomalies += 1,
_ => {}
}
eve.write_event(&ev)?;
}
}
for ev in driver.sweep(last_ts) {
if let flowscope::FlowEvent::Ended { .. } = ev {
flows += 1;
}
eve.write_event(&ev)?;
}
Ok((flows, anomalies))
}