Skip to main content

stackforge_core/sniffer/
capture.rs

1use bytes::Bytes;
2use pcap::{Capture, Device};
3
4use super::config::SnifferConfig;
5use super::error::SnifferError;
6
7/// Information about a network interface.
8#[derive(Debug, Clone)]
9pub struct InterfaceInfo {
10    pub name: String,
11    pub description: Option<String>,
12    pub addresses: Vec<String>,
13    pub is_loopback: bool,
14    pub is_up: bool,
15}
16
17/// A raw captured packet with timestamp.
18#[derive(Debug, Clone)]
19pub struct RawPacket {
20    /// Packet data as a zero-copy `Bytes` buffer.
21    pub data: Bytes,
22    /// Capture timestamp in microseconds since epoch.
23    pub timestamp_us: i64,
24}
25
26/// Opens a live capture on the given interface with the specified config.
27pub(crate) fn open_capture(config: &SnifferConfig) -> Result<Capture<pcap::Active>, SnifferError> {
28    // Find the device
29    let device = Device::list()
30        .map_err(SnifferError::Pcap)?
31        .into_iter()
32        .find(|d| d.name == config.iface)
33        .ok_or_else(|| SnifferError::InterfaceNotFound(config.iface.clone()))?;
34
35    // Open capture
36    let mut cap = Capture::from_device(device)
37        .map_err(SnifferError::Pcap)?
38        .snaplen(config.snaplen)
39        .promisc(config.promisc)
40        // Use a short read timeout so the capture thread can check the stop flag
41        .timeout(100)
42        .open()
43        .map_err(|e| {
44            let msg = e.to_string();
45            if msg.contains("ermission") || msg.contains("Operation not permitted") {
46                SnifferError::PermissionDenied(msg)
47            } else {
48                SnifferError::Pcap(e)
49            }
50        })?;
51
52    // Apply BPF filter if specified
53    if let Some(ref filter) = config.filter {
54        cap.filter(filter, true)
55            .map_err(|e| SnifferError::InvalidFilter(format!("{filter}: {e}")))?;
56    }
57
58    Ok(cap)
59}
60
61/// List all available network interfaces.
62pub fn list_interfaces() -> Result<Vec<InterfaceInfo>, SnifferError> {
63    let devices = Device::list().map_err(SnifferError::Pcap)?;
64    Ok(devices
65        .into_iter()
66        .map(|d| {
67            let addresses: Vec<String> = d.addresses.iter().map(|a| a.addr.to_string()).collect();
68
69            InterfaceInfo {
70                name: d.name,
71                description: d.desc,
72                addresses,
73                is_loopback: false, // pcap crate doesn't expose flags directly
74                is_up: true,
75            }
76        })
77        .collect())
78}
79
80/// Validate a BPF filter string without starting a capture.
81pub fn validate_filter(filter: &str) -> Result<(), SnifferError> {
82    // Open a dead capture and compile (not set) the filter to check validity
83    let cap = Capture::dead(pcap::Linktype::ETHERNET).map_err(SnifferError::Pcap)?;
84    cap.compile(filter, true)
85        .map_err(|e| SnifferError::InvalidFilter(format!("{filter}: {e}")))?;
86    Ok(())
87}