Skip to main content

netscli_core/
pcap.rs

1mod cancel;
2mod types;
3
4pub use cancel::PcapCancelToken;
5pub use types::{
6    PcapConfig, PcapPacketSummary, PcapParseResult, PcapResult, DEFAULT_PCAP_CAPTURE_SECONDS,
7    MAX_PCAP_CAPTURE_PACKETS, MAX_PCAP_CAPTURE_SECONDS,
8};
9
10use std::path::PathBuf;
11
12#[cfg(not(feature = "pcap"))]
13use crate::error::Error;
14use crate::error::Result;
15
16#[cfg(feature = "pcap")]
17mod capture;
18#[cfg(feature = "pcap")]
19mod device;
20#[cfg(feature = "pcap")]
21mod parse;
22#[cfg(feature = "pcap")]
23mod protocols;
24#[cfg(all(test, feature = "pcap"))]
25mod tests;
26
27pub struct PcapEngine;
28
29#[cfg(feature = "pcap")]
30impl PcapEngine {
31    /// Check whether libpcap/npf is available and list interfaces.
32    pub fn check_support() -> Result<Vec<String>> {
33        capture::check_support()
34    }
35
36    pub fn capture(config: PcapConfig) -> Result<PcapResult> {
37        Self::capture_with_cancel(config, None)
38    }
39
40    /// Capture packets to `config.output_file`.
41    ///
42    /// Refuses a config with no stopping condition. Every field of
43    /// `PcapConfig` is public, and the helper that guarantees at least one
44    /// bound lives in the `ops` layer -- so a caller constructing the config
45    /// directly with `duration: None, max_packets: None` got a loop that
46    /// breaks only on cancel, and `capture` does not even accept a cancel
47    /// token. It wrote packets until the filesystem filled.
48    pub fn capture_with_cancel(
49        config: PcapConfig,
50        cancel: Option<PcapCancelToken>,
51    ) -> Result<PcapResult> {
52        if config.duration.is_none() && config.max_packets.is_none() && cancel.is_none() {
53            return Err(crate::error::Error::invalid_input(
54                "packet capture needs a duration, a max packet count, or a cancel token",
55            ));
56        }
57        capture::capture_with_cancel(config, cancel)
58    }
59
60    /// Summarise packets from a capture file.
61    ///
62    /// `max_packets` is bounded here rather than trusted. `None` meant
63    /// "retain every packet", and `Ops::parse_pcap_file` forwarded the
64    /// caller's value untouched while the capture paths normalised theirs --
65    /// so a ~500 MB file of 10M small frames built 10M summaries, about 5 GB,
66    /// each carrying an unconditional 191-char hex preview.
67    pub fn parse_file(path: PathBuf, max_packets: Option<usize>) -> Result<PcapParseResult> {
68        let bounded = max_packets
69            .unwrap_or(crate::MAX_PCAP_CAPTURE_PACKETS)
70            .min(crate::MAX_PCAP_CAPTURE_PACKETS);
71        parse::parse_file(path, Some(bounded))
72    }
73}
74
75#[cfg(not(feature = "pcap"))]
76impl PcapEngine {
77    /// Check whether libpcap/npf is available and list interfaces.
78    pub fn check_support() -> Result<Vec<String>> {
79        Err(Error::unsupported(
80            "pcap support disabled at compile time (built without feature 'pcap')",
81        ))
82    }
83
84    pub fn capture(_config: PcapConfig) -> Result<PcapResult> {
85        Err(Error::unsupported(
86            "pcap support disabled at compile time (built without feature 'pcap')",
87        ))
88    }
89
90    pub fn capture_with_cancel(
91        _config: PcapConfig,
92        _cancel: Option<PcapCancelToken>,
93    ) -> Result<PcapResult> {
94        Err(Error::unsupported(
95            "pcap support disabled at compile time (built without feature 'pcap')",
96        ))
97    }
98
99    pub fn parse_file(_path: PathBuf, _max_packets: Option<usize>) -> Result<PcapParseResult> {
100        Err(Error::unsupported(
101            "pcap support disabled at compile time (built without feature 'pcap')",
102        ))
103    }
104}