use std::net::IpAddr;
use anyhow::{Context, Result};
use pcap::{Capture, Device};
use tokio::sync::mpsc;
use crate::capture::parser::{self, ParsedPacket};
pub struct PacketEvent {
pub parsed: ParsedPacket,
}
pub fn start_capture(
interface: Option<String>,
filter: Option<String>,
promiscuous: bool,
local_net: Option<(IpAddr, u8)>,
tx: mpsc::UnboundedSender<PacketEvent>,
) -> Result<CaptureHandle> {
let device = if let Some(ref name) = interface {
Device::list()
.context("Failed to list devices")?
.into_iter()
.find(|d| d.name == *name)
.with_context(|| format!("Interface '{}' not found", name))?
} else {
Device::lookup()
.context("Failed to lookup default device")?
.context("No default device found")?
};
let mut cap = Capture::from_device(device)
.context("Failed to open device")?
.promisc(promiscuous)
.snaplen(256)
.timeout(100)
.open()
.context("Failed to activate capture")?;
if let Some(ref f) = filter {
cap.filter(f, true)
.with_context(|| format!("Failed to set BPF filter: {}", f))?;
}
let datalink = cap.get_datalink();
let handle = std::thread::Builder::new()
.name("packet-capture".into())
.spawn(move || {
loop {
match cap.next_packet() {
Ok(packet) => {
let parsed = match datalink {
pcap::Linktype::ETHERNET => {
parser::parse_ethernet(packet.data, local_net)
}
pcap::Linktype(0) => {
parser::parse_loopback(packet.data, local_net)
}
pcap::Linktype(113) => {
parser::parse_sll(packet.data, local_net)
}
pcap::Linktype(101) => {
parser::parse_raw(packet.data, local_net)
}
_ => {
parser::parse_raw(packet.data, local_net)
}
};
if let Some(p) = parsed
&& tx.send(PacketEvent { parsed: p }).is_err() {
break; }
}
Err(pcap::Error::TimeoutExpired) => continue,
Err(_) => break,
}
}
})
.context("Failed to spawn capture thread")?;
Ok(CaptureHandle {
_thread: handle,
})
}
pub struct CaptureHandle {
_thread: std::thread::JoinHandle<()>,
}
pub fn list_interfaces() -> Result<Vec<String>> {
Ok(Device::list()
.context("Failed to list devices")?
.into_iter()
.map(|d| d.name)
.collect())
}