Skip to main content

stackforge_core/pcap/
reader.rs

1//! PCAP and PcapNG file reader with streaming support and auto-detection.
2
3use std::fs::File;
4use std::io::{BufReader, Cursor, Read, Seek, SeekFrom};
5
6use bytes::Bytes;
7use pcap_file::pcap::PcapReader as PcapFileReader;
8use pcap_file::pcapng::Block;
9use pcap_file::pcapng::PcapNgReader as PcapNgFileReader;
10
11use crate::error::{PacketError, Result};
12use crate::packet::Packet;
13
14use super::{CaptureFormat, CapturedPacket, LinkType, PcapMetadata};
15
16/// Detect capture format from the first 4 bytes (magic number).
17fn detect_format(magic: &[u8; 4]) -> Result<CaptureFormat> {
18    let le = u32::from_le_bytes(*magic);
19    let be = u32::from_be_bytes(*magic);
20
21    // PcapNG Section Header Block type is 0x0A0D0D0A in both endiannesses
22    if le == 0x0A0D_0D0A || be == 0x0A0D_0D0A {
23        return Ok(CaptureFormat::PcapNg);
24    }
25
26    // PCAP magic: microsecond or nanosecond, either endian
27    if le == 0xA1B2_C3D4 || be == 0xA1B2_C3D4 || le == 0xA1B2_3C4D || be == 0xA1B2_3C4D {
28        return Ok(CaptureFormat::Pcap);
29    }
30
31    Err(PacketError::Io(
32        "unknown capture file format (not PCAP or PcapNG)".into(),
33    ))
34}
35
36/// Read all packets from a PCAP or PcapNG file into memory.
37///
38/// Auto-detects the file format from magic bytes.
39/// This is the simple Scapy-like API. For large files, use [`CaptureIterator`] instead.
40pub fn rdpcap(path: impl AsRef<Path>) -> Result<Vec<CapturedPacket>> {
41    let iter = CaptureIterator::open(&path)?;
42    iter.collect()
43}
44
45use std::path::Path;
46
47// ---------------------------------------------------------------------------
48// Classic PCAP iterator
49// ---------------------------------------------------------------------------
50
51/// Streaming iterator over packets in a classic PCAP file.
52///
53/// Reads packets one at a time, suitable for gigabyte-sized captures.
54pub struct PcapIterator<R: Read> {
55    inner: PcapFileReader<R>,
56    link_type: LinkType,
57}
58
59impl PcapIterator<BufReader<File>> {
60    /// Open a classic PCAP file for streaming iteration.
61    pub fn open(path: impl AsRef<Path>) -> Result<Self> {
62        let file = File::open(path.as_ref()).map_err(|e| {
63            PacketError::Io(format!("failed to open {}: {}", path.as_ref().display(), e))
64        })?;
65        let reader = BufReader::new(file);
66        Self::from_reader(reader)
67    }
68}
69
70impl<R: Read> PcapIterator<R> {
71    /// Create a `PcapIterator` from any reader.
72    pub fn from_reader(reader: R) -> Result<Self> {
73        let pcap_reader = PcapFileReader::new(reader)
74            .map_err(|e| PacketError::Io(format!("invalid PCAP: {e}")))?;
75        let link_type = LinkType(u32::from(pcap_reader.header().datalink));
76        Ok(Self {
77            inner: pcap_reader,
78            link_type,
79        })
80    }
81
82    /// Returns the link-layer type from the PCAP global header.
83    pub fn link_type(&self) -> LinkType {
84        self.link_type
85    }
86}
87
88impl<R: Read> Iterator for PcapIterator<R> {
89    type Item = Result<CapturedPacket>;
90
91    fn next(&mut self) -> Option<Self::Item> {
92        match self.inner.next_packet() {
93            Some(Ok(pcap_pkt)) => {
94                let ts = pcap_pkt.timestamp;
95                let data = Bytes::copy_from_slice(&pcap_pkt.data);
96                let mut pkt = Packet::from_bytes(data);
97                let _ = pkt.parse();
98                Some(Ok(CapturedPacket {
99                    packet: pkt,
100                    metadata: PcapMetadata {
101                        timestamp: ts,
102                        orig_len: pcap_pkt.orig_len,
103                        interface_id: None,
104                        comment: None,
105                    },
106                }))
107            },
108            Some(Err(e)) => Some(Err(PacketError::Io(format!("PCAP read error: {e}")))),
109            None => None,
110        }
111    }
112}
113
114// ---------------------------------------------------------------------------
115// PcapNG iterator
116// ---------------------------------------------------------------------------
117
118/// Streaming iterator over packets in a PcapNG file.
119///
120/// Reads packets one at a time, skipping non-packet blocks (SHB, IDB, etc.).
121pub struct PcapNgIterator<R: Read> {
122    inner: PcapNgFileReader<R>,
123}
124
125impl PcapNgIterator<BufReader<File>> {
126    /// Open a PcapNG file for streaming iteration.
127    pub fn open(path: impl AsRef<Path>) -> Result<Self> {
128        let file = File::open(path.as_ref()).map_err(|e| {
129            PacketError::Io(format!("failed to open {}: {}", path.as_ref().display(), e))
130        })?;
131        let reader = BufReader::new(file);
132        Self::from_reader(reader)
133    }
134}
135
136impl<R: Read> PcapNgIterator<R> {
137    /// Create a `PcapNgIterator` from any reader.
138    pub fn from_reader(reader: R) -> Result<Self> {
139        let ng_reader = PcapNgFileReader::new(reader)
140            .map_err(|e| PacketError::Io(format!("invalid PcapNG: {e}")))?;
141        Ok(Self { inner: ng_reader })
142    }
143
144    /// Returns the link type of the first interface (most common case).
145    pub fn link_type(&self) -> LinkType {
146        self.inner
147            .interfaces()
148            .first()
149            .map(|idb| LinkType(u32::from(idb.linktype)))
150            .unwrap_or(LinkType::ETHERNET)
151    }
152}
153
154impl<R: Read> Iterator for PcapNgIterator<R> {
155    type Item = Result<CapturedPacket>;
156
157    fn next(&mut self) -> Option<Self::Item> {
158        loop {
159            match self.inner.next_block() {
160                Some(Ok(block)) => {
161                    match block {
162                        Block::EnhancedPacket(epb) => {
163                            let data = Bytes::copy_from_slice(&epb.data);
164                            let mut pkt = Packet::from_bytes(data);
165                            let _ = pkt.parse();
166                            return Some(Ok(CapturedPacket {
167                                packet: pkt,
168                                metadata: PcapMetadata {
169                                    timestamp: epb.timestamp,
170                                    orig_len: epb.original_len,
171                                    interface_id: Some(epb.interface_id),
172                                    comment: None,
173                                },
174                            }));
175                        },
176                        Block::SimplePacket(spb) => {
177                            let data = Bytes::copy_from_slice(&spb.data);
178                            let mut pkt = Packet::from_bytes(data);
179                            let _ = pkt.parse();
180                            return Some(Ok(CapturedPacket {
181                                packet: pkt,
182                                metadata: PcapMetadata {
183                                    timestamp: std::time::Duration::ZERO,
184                                    orig_len: spb.original_len,
185                                    interface_id: Some(0),
186                                    comment: None,
187                                },
188                            }));
189                        },
190                        // Skip non-packet blocks (SHB, IDB, NRB, ISB, etc.)
191                        _ => continue,
192                    }
193                },
194                Some(Err(e)) => {
195                    return Some(Err(PacketError::Io(format!("PcapNG read error: {e}"))));
196                },
197                None => return None,
198            }
199        }
200    }
201}
202
203// ---------------------------------------------------------------------------
204// Unified auto-detecting iterator
205// ---------------------------------------------------------------------------
206
207/// Auto-detecting iterator over packets from either PCAP or PcapNG files.
208///
209/// Detects the format from magic bytes and delegates to the appropriate reader.
210pub enum CaptureIterator<R: Read> {
211    /// Classic PCAP format.
212    Pcap(PcapIterator<R>),
213    /// PcapNG format.
214    PcapNg(PcapNgIterator<R>),
215}
216
217impl CaptureIterator<BufReader<File>> {
218    /// Open any capture file, auto-detecting format from magic bytes.
219    pub fn open(path: impl AsRef<Path>) -> Result<Self> {
220        let mut file = File::open(path.as_ref()).map_err(|e| {
221            PacketError::Io(format!("failed to open {}: {}", path.as_ref().display(), e))
222        })?;
223
224        let mut magic = [0u8; 4];
225        file.read_exact(&mut magic).map_err(|e| {
226            PacketError::Io(format!(
227                "failed to read magic bytes from {}: {e}",
228                path.as_ref().display()
229            ))
230        })?;
231
232        let format = detect_format(&magic)?;
233
234        // Seek back to start so the reader sees the full header
235        file.seek(SeekFrom::Start(0))
236            .map_err(|e| PacketError::Io(format!("failed to seek: {e}")))?;
237
238        let reader = BufReader::new(file);
239        match format {
240            CaptureFormat::Pcap => Ok(Self::Pcap(PcapIterator::from_reader(reader)?)),
241            CaptureFormat::PcapNg => Ok(Self::PcapNg(PcapNgIterator::from_reader(reader)?)),
242        }
243    }
244}
245
246impl<R: Read> CaptureIterator<R> {
247    /// Create from a reader by reading 4 magic bytes, then chaining them back.
248    ///
249    /// For readers that don't support `Seek`, this reads the magic bytes and
250    /// chains them back using `Cursor::chain`.
251    pub fn from_reader(
252        mut reader: R,
253    ) -> Result<CaptureIterator<std::io::Chain<Cursor<[u8; 4]>, R>>> {
254        let mut magic = [0u8; 4];
255        reader
256            .read_exact(&mut magic)
257            .map_err(|e| PacketError::Io(format!("failed to read magic bytes: {e}")))?;
258
259        let format = detect_format(&magic)?;
260        let chain = Cursor::new(magic).chain(reader);
261
262        match format {
263            CaptureFormat::Pcap => Ok(CaptureIterator::Pcap(PcapIterator::from_reader(chain)?)),
264            CaptureFormat::PcapNg => {
265                Ok(CaptureIterator::PcapNg(PcapNgIterator::from_reader(chain)?))
266            },
267        }
268    }
269
270    /// Returns the link-layer type of the capture.
271    pub fn link_type(&self) -> LinkType {
272        match self {
273            Self::Pcap(p) => p.link_type(),
274            Self::PcapNg(p) => p.link_type(),
275        }
276    }
277
278    /// Returns the detected capture format.
279    pub fn format(&self) -> CaptureFormat {
280        match self {
281            Self::Pcap(_) => CaptureFormat::Pcap,
282            Self::PcapNg(_) => CaptureFormat::PcapNg,
283        }
284    }
285}
286
287impl<R: Read> Iterator for CaptureIterator<R> {
288    type Item = Result<CapturedPacket>;
289
290    fn next(&mut self) -> Option<Self::Item> {
291        match self {
292            Self::Pcap(iter) => iter.next(),
293            Self::PcapNg(iter) => iter.next(),
294        }
295    }
296}
297
298#[cfg(test)]
299mod tests {
300    use super::*;
301    use std::time::Duration;
302
303    use pcap_file::pcap::{PcapHeader, PcapPacket, PcapWriter as PcapFileWriter};
304
305    fn sample_ethernet_packet() -> Vec<u8> {
306        // Minimal Ethernet frame: dst(6) + src(6) + type(2) + payload(4)
307        vec![
308            0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // dst: broadcast
309            0x00, 0x11, 0x22, 0x33, 0x44, 0x55, // src
310            0x08, 0x00, // EtherType: IPv4
311            0x00, 0x00, 0x00, 0x00, // dummy payload
312        ]
313    }
314
315    fn create_test_pcap(packets: &[(Duration, &[u8])]) -> Vec<u8> {
316        let mut buf = Vec::new();
317        let header = PcapHeader::default();
318        let mut writer = PcapFileWriter::with_header(Cursor::new(&mut buf), header).unwrap();
319        for (ts, data) in packets {
320            let pkt = PcapPacket::new(*ts, data.len() as u32, data);
321            writer.write_packet(&pkt).unwrap();
322        }
323        drop(writer);
324        buf
325    }
326
327    fn create_test_pcapng(packets: &[(Duration, &[u8])]) -> Vec<u8> {
328        use pcap_file::pcapng::PcapNgWriter;
329        use pcap_file::pcapng::blocks::enhanced_packet::EnhancedPacketBlock;
330        use pcap_file::pcapng::blocks::interface_description::InterfaceDescriptionBlock;
331        use std::borrow::Cow;
332
333        let mut buf = Vec::new();
334        let mut writer = PcapNgWriter::new(Cursor::new(&mut buf)).unwrap();
335
336        // Write interface description block
337        let idb = InterfaceDescriptionBlock {
338            linktype: pcap_file::DataLink::ETHERNET,
339            snaplen: 0xFFFF,
340            options: vec![],
341        };
342        writer.write_pcapng_block(idb).unwrap();
343
344        for (ts, data) in packets {
345            let epb = EnhancedPacketBlock {
346                interface_id: 0,
347                timestamp: *ts,
348                original_len: data.len() as u32,
349                data: Cow::Borrowed(data),
350                options: vec![],
351            };
352            writer.write_pcapng_block(epb).unwrap();
353        }
354        drop(writer);
355        buf
356    }
357
358    #[test]
359    fn test_detect_format_pcap() {
360        // Little-endian PCAP magic
361        let magic = [0xD4, 0xC3, 0xB2, 0xA1];
362        assert_eq!(detect_format(&magic).unwrap(), CaptureFormat::Pcap);
363    }
364
365    #[test]
366    fn test_detect_format_pcapng() {
367        // PcapNG SHB magic
368        let magic = [0x0A, 0x0D, 0x0D, 0x0A];
369        assert_eq!(detect_format(&magic).unwrap(), CaptureFormat::PcapNg);
370    }
371
372    #[test]
373    fn test_detect_format_unknown() {
374        let magic = [0x00, 0x00, 0x00, 0x00];
375        assert!(detect_format(&magic).is_err());
376    }
377
378    #[test]
379    fn test_pcap_iterator_from_reader() {
380        let eth = sample_ethernet_packet();
381        let pcap_data = create_test_pcap(&[
382            (Duration::from_secs(1), &eth),
383            (Duration::from_secs(2), &eth),
384        ]);
385        let iter = PcapIterator::from_reader(Cursor::new(pcap_data)).unwrap();
386        let packets: Vec<_> = iter.collect::<std::result::Result<Vec<_>, _>>().unwrap();
387        assert_eq!(packets.len(), 2);
388        assert_eq!(packets[0].metadata.timestamp, Duration::from_secs(1));
389        assert_eq!(packets[1].metadata.timestamp, Duration::from_secs(2));
390        // Classic PCAP should have no interface_id
391        assert!(packets[0].metadata.interface_id.is_none());
392    }
393
394    #[test]
395    fn test_pcap_iterator_link_type() {
396        let pcap_data = create_test_pcap(&[]);
397        let iter = PcapIterator::from_reader(Cursor::new(pcap_data)).unwrap();
398        assert_eq!(iter.link_type(), LinkType::ETHERNET);
399    }
400
401    #[test]
402    fn test_pcap_iterator_empty() {
403        let pcap_data = create_test_pcap(&[]);
404        let iter = PcapIterator::from_reader(Cursor::new(pcap_data)).unwrap();
405        let packets: Vec<_> = iter.collect::<std::result::Result<Vec<_>, _>>().unwrap();
406        assert!(packets.is_empty());
407    }
408
409    #[test]
410    fn test_pcap_iterator_metadata() {
411        let eth = sample_ethernet_packet();
412        let pcap_data = create_test_pcap(&[(Duration::from_millis(1500), &eth)]);
413        let iter = PcapIterator::from_reader(Cursor::new(pcap_data)).unwrap();
414        let packets: Vec<_> = iter.collect::<std::result::Result<Vec<_>, _>>().unwrap();
415        assert_eq!(packets.len(), 1);
416        assert_eq!(packets[0].metadata.orig_len, eth.len() as u32);
417        assert_eq!(packets[0].packet.len(), eth.len());
418    }
419
420    #[test]
421    fn test_pcap_iterator_is_lazy() {
422        let eth = sample_ethernet_packet();
423        let pcap_data = create_test_pcap(&[
424            (Duration::from_secs(1), &eth),
425            (Duration::from_secs(2), &eth),
426            (Duration::from_secs(3), &eth),
427        ]);
428        let mut iter = PcapIterator::from_reader(Cursor::new(pcap_data)).unwrap();
429        let first = iter.next().unwrap().unwrap();
430        assert_eq!(first.metadata.timestamp, Duration::from_secs(1));
431        let second = iter.next().unwrap().unwrap();
432        assert_eq!(second.metadata.timestamp, Duration::from_secs(2));
433    }
434
435    #[test]
436    fn test_pcapng_iterator_from_reader() {
437        let eth = sample_ethernet_packet();
438        let pcapng_data = create_test_pcapng(&[
439            (Duration::from_secs(10), &eth),
440            (Duration::from_secs(20), &eth),
441        ]);
442        let iter = PcapNgIterator::from_reader(Cursor::new(pcapng_data)).unwrap();
443        let packets: Vec<_> = iter.collect::<std::result::Result<Vec<_>, _>>().unwrap();
444        assert_eq!(packets.len(), 2);
445        assert_eq!(packets[0].metadata.timestamp, Duration::from_secs(10));
446        assert_eq!(packets[1].metadata.timestamp, Duration::from_secs(20));
447        assert_eq!(packets[0].metadata.interface_id, Some(0));
448    }
449
450    #[test]
451    fn test_pcapng_iterator_empty() {
452        let pcapng_data = create_test_pcapng(&[]);
453        let iter = PcapNgIterator::from_reader(Cursor::new(pcapng_data)).unwrap();
454        let packets: Vec<_> = iter.collect::<std::result::Result<Vec<_>, _>>().unwrap();
455        assert!(packets.is_empty());
456    }
457
458    #[test]
459    fn test_capture_iterator_auto_detect_pcap() {
460        let eth = sample_ethernet_packet();
461        let pcap_data = create_test_pcap(&[(Duration::from_secs(1), &eth)]);
462        let iter = CaptureIterator::from_reader(Cursor::new(pcap_data)).unwrap();
463        assert_eq!(iter.format(), CaptureFormat::Pcap);
464        let packets: Vec<_> = iter.collect::<std::result::Result<Vec<_>, _>>().unwrap();
465        assert_eq!(packets.len(), 1);
466    }
467
468    #[test]
469    fn test_capture_iterator_auto_detect_pcapng() {
470        let eth = sample_ethernet_packet();
471        let pcapng_data = create_test_pcapng(&[(Duration::from_secs(5), &eth)]);
472        let iter = CaptureIterator::from_reader(Cursor::new(pcapng_data)).unwrap();
473        assert_eq!(iter.format(), CaptureFormat::PcapNg);
474        let packets: Vec<_> = iter.collect::<std::result::Result<Vec<_>, _>>().unwrap();
475        assert_eq!(packets.len(), 1);
476        assert_eq!(packets[0].metadata.timestamp, Duration::from_secs(5));
477    }
478
479    #[test]
480    fn test_rdpcap_pcapng_roundtrip() {
481        let eth = sample_ethernet_packet();
482        let pcapng_data = create_test_pcapng(&[
483            (Duration::from_secs(1), &eth),
484            (Duration::from_secs(2), &eth),
485            (Duration::from_secs(3), &eth),
486        ]);
487        // Write to temp file and read back with rdpcap
488        let tmpdir = tempfile::tempdir().unwrap();
489        let path = tmpdir.path().join("test.pcapng");
490        std::fs::write(&path, &pcapng_data).unwrap();
491        let packets = rdpcap(&path).unwrap();
492        assert_eq!(packets.len(), 3);
493        assert_eq!(packets[0].metadata.timestamp, Duration::from_secs(1));
494        assert_eq!(packets[2].metadata.timestamp, Duration::from_secs(3));
495    }
496}