1#[cfg(feature = "pcap")]
2use pcap::{Capture, Device};
3use serde::Serialize;
4use std::path::PathBuf;
5
6use crate::error::{Error, Result};
7use std::sync::{
8 atomic::{AtomicBool, Ordering},
9 Arc,
10};
11use std::time::Duration;
12
13#[cfg(feature = "pcap")]
14use std::time::Instant;
15
16#[derive(Debug, Serialize)]
17pub struct PcapConfig {
18 pub interface: String,
19 pub filter: Option<String>,
20 pub output_file: PathBuf,
21 pub duration: Option<Duration>,
22 pub max_packets: Option<usize>,
23}
24
25#[derive(Debug, Serialize)]
26pub struct PcapResult {
27 pub packets_captured: usize,
28 pub duration: Duration,
29 pub file_path: PathBuf,
30}
31
32#[derive(Debug, Clone, Default)]
37pub struct PcapCancelToken {
38 cancelled: Arc<AtomicBool>,
39}
40
41impl PcapCancelToken {
42 pub fn new() -> Self {
43 Self {
44 cancelled: Arc::new(AtomicBool::new(false)),
45 }
46 }
47
48 pub fn cancel(&self) {
49 self.cancelled.store(true, Ordering::SeqCst);
50 }
51
52 pub fn is_cancelled(&self) -> bool {
53 self.cancelled.load(Ordering::SeqCst)
54 }
55}
56
57pub struct PcapEngine;
58
59#[cfg(feature = "pcap")]
60impl PcapEngine {
61 pub fn check_support() -> Result<Vec<String>> {
63 let devices = Device::list()?;
64 if devices.is_empty() {
65 return Err(Error::unsupported(
66 "pcap available but no capture interfaces detected",
67 ));
68 }
69 Ok(devices.into_iter().map(|d| d.name).collect())
70 }
71
72 pub fn capture(config: PcapConfig) -> Result<PcapResult> {
73 Self::capture_with_cancel(config, None)
74 }
75
76 pub fn capture_with_cancel(
77 config: PcapConfig,
78 cancel: Option<PcapCancelToken>,
79 ) -> Result<PcapResult> {
80 let device = Device::list()?
81 .into_iter()
82 .find(|d| d.name == config.interface)
83 .ok_or_else(|| {
84 Error::invalid_input(format!("Interface not found: {}", config.interface))
85 })?;
86
87 let mut cap = Capture::from_device(device)?
88 .promisc(true)
89 .snaplen(65535)
90 .timeout(250)
93 .open()?;
94
95 if let Some(filter) = &config.filter {
96 cap.filter(filter, true)?;
97 }
98
99 let mut savefile = cap.savefile(&config.output_file)?;
100 let start = Instant::now();
101 let mut packets_captured = 0;
102
103 loop {
104 if cancel.as_ref().is_some_and(|c| c.is_cancelled()) {
105 break;
106 }
107
108 if let Some(duration) = config.duration {
110 if start.elapsed() >= duration {
111 break;
112 }
113 }
114
115 if let Some(max) = config.max_packets {
117 if packets_captured >= max {
118 break;
119 }
120 }
121
122 match cap.next_packet() {
123 Ok(packet) => {
124 savefile.write(&packet);
125 packets_captured += 1;
126 if packets_captured % 128 == 0 {
129 savefile
130 .flush()
131 .map_err(|e| Error::Other(format!("failed to flush savefile: {e}")))?;
132 }
133 }
134 Err(pcap::Error::TimeoutExpired) => continue,
135 Err(e) => return Err(e.into()),
136 }
137 }
138
139 savefile
140 .flush()
141 .map_err(|e| Error::Other(format!("failed to flush savefile: {e}")))?;
142
143 Ok(PcapResult {
144 packets_captured,
145 duration: start.elapsed(),
146 file_path: config.output_file,
147 })
148 }
149}
150
151#[cfg(not(feature = "pcap"))]
152impl PcapEngine {
153 pub fn check_support() -> Result<Vec<String>> {
155 Err(Error::unsupported(
156 "pcap support disabled at compile time (built without feature 'pcap')",
157 ))
158 }
159
160 pub fn capture(_config: PcapConfig) -> Result<PcapResult> {
161 Err(Error::unsupported(
162 "pcap support disabled at compile time (built without feature 'pcap')",
163 ))
164 }
165
166 pub fn capture_with_cancel(
167 _config: PcapConfig,
168 _cancel: Option<PcapCancelToken>,
169 ) -> Result<PcapResult> {
170 Err(Error::unsupported(
171 "pcap support disabled at compile time (built without feature 'pcap')",
172 ))
173 }
174}