use anyhow::{Result, Context, bail};
use pcap::Capture;
use pcap_file::{pcap::PcapReader, pcapng::PcapNgReader};
use std::fs::File;
use std::path::Path;
use std::io::Read;
pub enum CaptureSource {
Pcap(Capture<pcap::Offline>),
PcapFile(Vec<u8>),
PcapNg(Vec<u8>),
}
pub struct PacketCapture {
source: CaptureSource,
filter: Option<String>,
}
impl PacketCapture {
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
let path = path.as_ref();
let extension = path.extension()
.and_then(|s| s.to_str())
.unwrap_or("");
match extension.to_lowercase().as_str() {
"pcap" | "cap" => Self::from_pcap_file(path),
"pcapng" | "ntar" => Self::from_pcapng_file(path),
_ => {
Self::auto_detect(path)
}
}
}
fn from_pcap_file(path: &Path) -> Result<Self> {
let capture = Capture::from_file(path)
.context("Failed to open pcap file")?;
Ok(Self {
source: CaptureSource::Pcap(capture),
filter: None,
})
}
fn from_pcapng_file(path: &Path) -> Result<Self> {
let mut file = File::open(path)?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer)?;
Ok(Self {
source: CaptureSource::PcapNg(buffer),
filter: None,
})
}
fn auto_detect(path: &Path) -> Result<Self> {
let mut file = File::open(path)?;
let mut magic = [0u8; 4];
file.read_exact(&mut magic)?;
match &magic {
[0xa1, 0xb2, 0xc3, 0xd4] | [0xd4, 0xc3, 0xb2, 0xa1] |
[0xa1, 0xb2, 0x3c, 0x4d] | [0x4d, 0x3c, 0xb2, 0xa1] => {
drop(file);
Self::from_pcap_file(path)
}
[0x0a, 0x0d, 0x0d, 0x0a] => {
drop(file);
Self::from_pcapng_file(path)
}
_ => bail!("Unknown file format. Expected pcap or pcapng")
}
}
pub fn set_filter(&mut self, filter: &str) -> Result<()> {
match &mut self.source {
CaptureSource::Pcap(capture) => {
capture.filter(filter, true)
.context("Failed to apply BPF filter")?;
self.filter = Some(filter.to_string());
Ok(())
}
_ => {
self.filter = Some(filter.to_string());
Ok(())
}
}
}
pub fn stats(&mut self) -> Result<CaptureStats> {
let mut stats = CaptureStats::default();
match &mut self.source {
CaptureSource::Pcap(capture) => {
while let Ok(packet) = capture.next_packet() {
stats.update(&packet.data, packet.header.len);
}
}
CaptureSource::PcapFile(data) => {
let mut reader = PcapReader::new(&data[..])?;
while let Some(pkt) = reader.next_packet() {
let pkt = pkt?;
stats.update(&pkt.data, pkt.data.len() as u32);
}
}
CaptureSource::PcapNg(data) => {
let mut reader = PcapNgReader::new(&data[..])?;
while let Some(block) = reader.next_block() {
match block {
Ok(pcap_file::pcapng::Block::EnhancedPacket(pkt)) => {
stats.update(&pkt.data, pkt.data.len() as u32);
}
Ok(pcap_file::pcapng::Block::SimplePacket(pkt)) => {
stats.update(&pkt.data, pkt.data.len() as u32);
}
_ => {}
}
}
}
}
Ok(stats)
}
pub fn process_packets<F>(&mut self, mut callback: F) -> Result<()>
where
F: FnMut(ProcessedPacket) -> Result<()>,
{
match &mut self.source {
CaptureSource::Pcap(capture) => {
while let Ok(packet) = capture.next_packet() {
let processed = ProcessedPacket {
timestamp: packet.header.ts.tv_sec as u64 * 1_000_000
+ packet.header.ts.tv_usec as u64,
data: packet.data.to_vec(),
len: packet.header.len,
caplen: packet.header.caplen,
};
callback(processed)?;
}
}
CaptureSource::PcapFile(data) => {
let mut reader = PcapReader::new(&data[..])?;
while let Some(pkt) = reader.next_packet() {
let pkt = pkt?;
let processed = ProcessedPacket {
timestamp: pkt.timestamp.as_micros() as u64,
data: pkt.data.to_vec(),
len: pkt.orig_len,
caplen: pkt.data.len() as u32,
};
callback(processed)?;
}
}
CaptureSource::PcapNg(data) => {
let mut reader = PcapNgReader::new(&data[..])?;
while let Some(block) = reader.next_block() {
match block {
Ok(pcap_file::pcapng::Block::EnhancedPacket(pkt)) => {
let processed = ProcessedPacket {
timestamp: pkt.timestamp.as_micros() as u64,
data: pkt.data.to_vec(),
len: pkt.original_len,
caplen: pkt.data.len() as u32,
};
callback(processed)?;
}
Ok(pcap_file::pcapng::Block::SimplePacket(pkt)) => {
let processed = ProcessedPacket {
timestamp: 0, data: pkt.data.to_vec(),
len: pkt.original_len,
caplen: pkt.data.len() as u32,
};
callback(processed)?;
}
_ => {}
}
}
}
}
Ok(())
}
}
pub struct ProcessedPacket {
pub timestamp: u64, pub data: Vec<u8>,
pub len: u32,
pub caplen: u32,
}
#[derive(Default, Debug)]
pub struct CaptureStats {
pub total_packets: u64,
pub total_bytes: u64,
pub avg_packet_size: u64,
pub max_packet_size: u32,
pub min_packet_size: u32,
}
impl CaptureStats {
fn update(&mut self, _data: &[u8], len: u32) {
self.total_packets += 1;
self.total_bytes += len as u64;
if len > self.max_packet_size {
self.max_packet_size = len;
}
if len < self.min_packet_size || self.min_packet_size == 0 {
self.min_packet_size = len;
}
if self.total_packets > 0 {
self.avg_packet_size = self.total_bytes / self.total_packets;
}
}
}