Skip to main content

pcapforge_core/
capture.rs

1use anyhow::{Result, Context, bail};
2use pcap::Capture;
3use pcap_file::{pcap::PcapReader, pcapng::PcapNgReader};
4use std::fs::File;
5use std::path::Path;
6use std::io::Read;
7
8pub enum CaptureSource {
9    Pcap(Capture<pcap::Offline>),
10    PcapFile(Vec<u8>),
11    PcapNg(Vec<u8>),
12}
13
14pub struct PacketCapture {
15    source: CaptureSource,
16    filter: Option<String>,
17}
18
19impl PacketCapture {
20    /// Open a capture file (supports both pcap and pcapng)
21    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
22        let path = path.as_ref();
23        let extension = path.extension()
24            .and_then(|s| s.to_str())
25            .unwrap_or("");
26
27        match extension.to_lowercase().as_str() {
28            "pcap" | "cap" => Self::from_pcap_file(path),
29            "pcapng" | "ntar" => Self::from_pcapng_file(path),
30            _ => {
31                // Try to detect format by reading magic bytes
32                Self::auto_detect(path)
33            }
34        }
35    }
36
37    fn from_pcap_file(path: &Path) -> Result<Self> {
38        let capture = Capture::from_file(path)
39            .context("Failed to open pcap file")?;
40
41        Ok(Self {
42            source: CaptureSource::Pcap(capture),
43            filter: None,
44        })
45    }
46
47    fn from_pcapng_file(path: &Path) -> Result<Self> {
48        let mut file = File::open(path)?;
49        let mut buffer = Vec::new();
50        file.read_to_end(&mut buffer)?;
51
52        Ok(Self {
53            source: CaptureSource::PcapNg(buffer),
54            filter: None,
55        })
56    }
57
58    fn auto_detect(path: &Path) -> Result<Self> {
59        let mut file = File::open(path)?;
60        let mut magic = [0u8; 4];
61        file.read_exact(&mut magic)?;
62
63        // Check magic numbers
64        match &magic {
65            // pcap magic numbers
66            [0xa1, 0xb2, 0xc3, 0xd4] | [0xd4, 0xc3, 0xb2, 0xa1] |
67            [0xa1, 0xb2, 0x3c, 0x4d] | [0x4d, 0x3c, 0xb2, 0xa1] => {
68                drop(file);
69                Self::from_pcap_file(path)
70            }
71            // pcapng magic number
72            [0x0a, 0x0d, 0x0d, 0x0a] => {
73                drop(file);
74                Self::from_pcapng_file(path)
75            }
76            _ => bail!("Unknown file format. Expected pcap or pcapng")
77        }
78    }
79
80    /// Apply a BPF filter (only works with pcap backend currently)
81    pub fn set_filter(&mut self, filter: &str) -> Result<()> {
82        match &mut self.source {
83            CaptureSource::Pcap(capture) => {
84                capture.filter(filter, true)
85                    .context("Failed to apply BPF filter")?;
86                self.filter = Some(filter.to_string());
87                Ok(())
88            }
89            _ => {
90                // For pcapng, we'll need to filter manually during iteration
91                self.filter = Some(filter.to_string());
92                Ok(())
93            }
94        }
95    }
96
97    /// Get statistics about the capture file
98    pub fn stats(&mut self) -> Result<CaptureStats> {
99        let mut stats = CaptureStats::default();
100
101        match &mut self.source {
102            CaptureSource::Pcap(capture) => {
103                while let Ok(packet) = capture.next_packet() {
104                    stats.update(&packet.data, packet.header.len);
105                }
106            }
107            CaptureSource::PcapFile(data) => {
108                let mut reader = PcapReader::new(&data[..])?;
109                while let Some(pkt) = reader.next_packet() {
110                    let pkt = pkt?;
111                    stats.update(&pkt.data, pkt.data.len() as u32);
112                }
113            }
114            CaptureSource::PcapNg(data) => {
115                let mut reader = PcapNgReader::new(&data[..])?;
116                while let Some(block) = reader.next_block() {
117                    match block {
118                        Ok(pcap_file::pcapng::Block::EnhancedPacket(pkt)) => {
119                            stats.update(&pkt.data, pkt.data.len() as u32);
120                        }
121                        Ok(pcap_file::pcapng::Block::SimplePacket(pkt)) => {
122                            stats.update(&pkt.data, pkt.data.len() as u32);
123                        }
124                        _ => {}
125                    }
126                }
127            }
128        }
129
130        Ok(stats)
131    }
132
133    /// Process packets with a callback function
134    pub fn process_packets<F>(&mut self, mut callback: F) -> Result<()>
135    where
136        F: FnMut(ProcessedPacket) -> Result<()>,
137    {
138        match &mut self.source {
139            CaptureSource::Pcap(capture) => {
140                while let Ok(packet) = capture.next_packet() {
141                    let processed = ProcessedPacket {
142                        timestamp: packet.header.ts.tv_sec as u64 * 1_000_000
143                            + packet.header.ts.tv_usec as u64,
144                        data: packet.data.to_vec(),
145                        len: packet.header.len,
146                        caplen: packet.header.caplen,
147                    };
148                    callback(processed)?;
149                }
150            }
151            CaptureSource::PcapFile(data) => {
152                let mut reader = PcapReader::new(&data[..])?;
153                while let Some(pkt) = reader.next_packet() {
154                    let pkt = pkt?;
155                    let processed = ProcessedPacket {
156                        timestamp: pkt.timestamp.as_micros() as u64,
157                        data: pkt.data.to_vec(),
158                        len: pkt.orig_len,
159                        caplen: pkt.data.len() as u32,
160                    };
161                    callback(processed)?;
162                }
163            }
164            CaptureSource::PcapNg(data) => {
165                let mut reader = PcapNgReader::new(&data[..])?;
166                while let Some(block) = reader.next_block() {
167                    match block {
168                        Ok(pcap_file::pcapng::Block::EnhancedPacket(pkt)) => {
169                            let processed = ProcessedPacket {
170                                timestamp: pkt.timestamp.as_micros() as u64,
171                                data: pkt.data.to_vec(),
172                                len: pkt.original_len,
173                                caplen: pkt.data.len() as u32,
174                            };
175                            callback(processed)?;
176                        }
177                        Ok(pcap_file::pcapng::Block::SimplePacket(pkt)) => {
178                            let processed = ProcessedPacket {
179                                timestamp: 0, // Simple packets don't have timestamps
180                                data: pkt.data.to_vec(),
181                                len: pkt.original_len,
182                                caplen: pkt.data.len() as u32,
183                            };
184                            callback(processed)?;
185                        }
186                        _ => {}
187                    }
188                }
189            }
190        }
191        Ok(())
192    }
193}
194
195pub struct ProcessedPacket {
196    pub timestamp: u64, // microseconds since epoch
197    pub data: Vec<u8>,
198    pub len: u32,
199    pub caplen: u32,
200}
201
202#[derive(Default, Debug)]
203pub struct CaptureStats {
204    pub total_packets: u64,
205    pub total_bytes: u64,
206    pub avg_packet_size: u64,
207    pub max_packet_size: u32,
208    pub min_packet_size: u32,
209}
210
211impl CaptureStats {
212    fn update(&mut self, _data: &[u8], len: u32) {
213        self.total_packets += 1;
214        self.total_bytes += len as u64;
215
216        if len > self.max_packet_size {
217            self.max_packet_size = len;
218        }
219        if len < self.min_packet_size || self.min_packet_size == 0 {
220            self.min_packet_size = len;
221        }
222
223        if self.total_packets > 0 {
224            self.avg_packet_size = self.total_bytes / self.total_packets;
225        }
226    }
227}