#[cfg(feature = "pcap")]
use pcap::{Capture, Device};
use serde::Serialize;
use std::path::PathBuf;
use crate::error::{Error, Result};
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};
use std::time::Duration;
#[cfg(feature = "pcap")]
use std::time::Instant;
#[derive(Debug, Serialize)]
pub struct PcapConfig {
pub interface: String,
pub filter: Option<String>,
pub output_file: PathBuf,
pub duration: Option<Duration>,
pub max_packets: Option<usize>,
}
#[derive(Debug, Serialize)]
pub struct PcapResult {
pub packets_captured: usize,
pub duration: Duration,
pub file_path: PathBuf,
}
#[derive(Debug, Clone, Default)]
pub struct PcapCancelToken {
cancelled: Arc<AtomicBool>,
}
impl PcapCancelToken {
pub fn new() -> Self {
Self {
cancelled: Arc::new(AtomicBool::new(false)),
}
}
pub fn cancel(&self) {
self.cancelled.store(true, Ordering::SeqCst);
}
pub fn is_cancelled(&self) -> bool {
self.cancelled.load(Ordering::SeqCst)
}
}
pub struct PcapEngine;
#[cfg(feature = "pcap")]
impl PcapEngine {
pub fn check_support() -> Result<Vec<String>> {
let devices = Device::list()?;
if devices.is_empty() {
return Err(Error::unsupported(
"pcap available but no capture interfaces detected",
));
}
Ok(devices.into_iter().map(|d| d.name).collect())
}
pub fn capture(config: PcapConfig) -> Result<PcapResult> {
Self::capture_with_cancel(config, None)
}
pub fn capture_with_cancel(
config: PcapConfig,
cancel: Option<PcapCancelToken>,
) -> Result<PcapResult> {
let device = Device::list()?
.into_iter()
.find(|d| d.name == config.interface)
.ok_or_else(|| {
Error::invalid_input(format!("Interface not found: {}", config.interface))
})?;
let mut cap = Capture::from_device(device)?
.promisc(true)
.snaplen(65535)
.timeout(250)
.open()?;
if let Some(filter) = &config.filter {
cap.filter(filter, true)?;
}
let mut savefile = cap.savefile(&config.output_file)?;
let start = Instant::now();
let mut packets_captured = 0;
loop {
if cancel.as_ref().is_some_and(|c| c.is_cancelled()) {
break;
}
if let Some(duration) = config.duration {
if start.elapsed() >= duration {
break;
}
}
if let Some(max) = config.max_packets {
if packets_captured >= max {
break;
}
}
match cap.next_packet() {
Ok(packet) => {
savefile.write(&packet);
packets_captured += 1;
if packets_captured % 128 == 0 {
savefile
.flush()
.map_err(|e| Error::Other(format!("failed to flush savefile: {e}")))?;
}
}
Err(pcap::Error::TimeoutExpired) => continue,
Err(e) => return Err(e.into()),
}
}
savefile
.flush()
.map_err(|e| Error::Other(format!("failed to flush savefile: {e}")))?;
Ok(PcapResult {
packets_captured,
duration: start.elapsed(),
file_path: config.output_file,
})
}
}
#[cfg(not(feature = "pcap"))]
impl PcapEngine {
pub fn check_support() -> Result<Vec<String>> {
Err(Error::unsupported(
"pcap support disabled at compile time (built without feature 'pcap')",
))
}
pub fn capture(_config: PcapConfig) -> Result<PcapResult> {
Err(Error::unsupported(
"pcap support disabled at compile time (built without feature 'pcap')",
))
}
pub fn capture_with_cancel(
_config: PcapConfig,
_cancel: Option<PcapCancelToken>,
) -> Result<PcapResult> {
Err(Error::unsupported(
"pcap support disabled at compile time (built without feature 'pcap')",
))
}
}