Skip to content

Commit f2d60f4

Browse files
committed
Allow applications to abort a capture loop (pcap_breakloop)
Expose a safe `BreakLoop` handle that forwards to libpcap’s `pcap_breakloop`. The handle stores a copy of the internal `Arc<RwLock<bool>>` validity flag so it can be used from any thread as long as the underlying `Capture` is still open. * Implements @FeldrinH’s suggestion from issue #312. * Adds `Capture::breakloop_handle(&self) -> BreakLoop`. Fixes #312
1 parent 538cd95 commit f2d60f4

File tree

3 files changed

+76
-1
lines changed

3 files changed

+76
-1
lines changed

src/capture/activated/mod.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use std::{
1212
path::Path,
1313
ptr::{self, NonNull},
1414
slice,
15+
sync::{Arc, RwLock},
1516
};
1617

1718
#[cfg(not(windows))]
@@ -235,6 +236,39 @@ impl<T: Activated + ?Sized> Capture<T> {
235236
self.check_err(return_code == 0)
236237
}
237238

239+
/// Returns a thread-safe `BreakLoop` handle for calling pcap_breakloop() on an active capture.
240+
///
241+
/// # Example
242+
///
243+
/// ```no_run
244+
/// // Using an active capture
245+
/// use pcap::Device;
246+
///
247+
/// let mut cap = Device::lookup().unwrap().unwrap().open().unwrap();
248+
///
249+
/// let break_handle = cap.breakloop_handle();
250+
///
251+
/// let capture_thread = std::thread::spawn(move || {
252+
/// while let Ok(packet) = cap.next_packet() {
253+
/// println!("received packet! {:?}", packet);
254+
/// }
255+
/// });
256+
///
257+
/// // Send break_handle to a seperate thread (i.e. could be signal handler)
258+
/// std::thread::spawn(move || {
259+
/// std::thread::sleep(std::time::Duration::from_secs(1));
260+
/// break_handle.breakloop();
261+
/// });
262+
///
263+
/// capture_thread.join().unwrap();
264+
/// ```
265+
pub fn breakloop_handle(&mut self) -> BreakLoop {
266+
BreakLoop {
267+
handle: self.handle,
268+
handle_valid: Arc::clone(&self.handle_valid),
269+
}
270+
}
271+
238272
/// Compiles the string into a filter program using `pcap_compile`.
239273
pub fn compile(&self, program: &str, optimize: bool) -> Result<BpfProgram, Error> {
240274
let program = CString::new(program)?;
@@ -321,6 +355,39 @@ impl<T: Activated> From<Capture<T>> for Capture<dyn Activated> {
321355
unsafe { mem::transmute(cap) }
322356
}
323357
}
358+
/// Breakloop can safely be sent to other threads such as signal handlers to abort
359+
/// blocking capture loops such as `Capture::next_packet` and `Capture::for_each`.
360+
///
361+
/// See <https://www.tcpdump.org/manpages/pcap_breakloop.3pcap.html> for per-platform caveats about
362+
/// how breakloop can wake up blocked threads.
363+
pub struct BreakLoop {
364+
handle: NonNull<raw::pcap_t>,
365+
handle_valid: Arc<RwLock<bool>>,
366+
}
367+
368+
// The `handle_valid` ensures that a valid handle is always referenced
369+
unsafe impl Send for BreakLoop {}
370+
// `pcap_breakloop()` is thread-safe
371+
unsafe impl Sync for BreakLoop {}
372+
373+
impl BreakLoop {
374+
/// Calls `pcap_breakloop` to make the blocking loop of a pcap capture return.
375+
/// The call is a no-op if the handle is invalid.
376+
pub fn breakloop(&self) {
377+
if let Ok(handle_valid) = self.handle_valid.try_read() {
378+
if *handle_valid {
379+
//
380+
// # Safety
381+
//
382+
// `handle_valid` is true, so the handle is valid before `pcap_breakloop`
383+
// `handle_valid` lock is held for the duration of `pcap_breakloop` so the handle
384+
// can't be deallocated while `pcap_breakloop` is running
385+
unsafe { raw::pcap_breakloop(self.handle.as_ptr()) };
386+
}
387+
}
388+
}
389+
}
390+
324391

325392
/// Abstraction for writing pcap savefiles, which can be read afterwards via `Capture::from_file()`.
326393
pub struct Savefile {

src/capture/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use std::{
88
ffi::CString,
99
marker::PhantomData,
1010
ptr::{self, NonNull},
11+
sync::{Arc, RwLock},
1112
};
1213

1314
#[cfg(windows)]
@@ -91,6 +92,7 @@ impl State for Dead {}
9192
pub struct Capture<T: State + ?Sized> {
9293
nonblock: bool,
9394
handle: NonNull<raw::pcap_t>,
95+
handle_valid: Arc<RwLock<bool>>,
9496
_marker: PhantomData<T>,
9597
}
9698

@@ -104,6 +106,7 @@ impl<T: State + ?Sized> From<NonNull<raw::pcap_t>> for Capture<T> {
104106
Capture {
105107
nonblock: false,
106108
handle,
109+
handle_valid: Arc::new(RwLock::new(true)),
107110
_marker: PhantomData,
108111
}
109112
}
@@ -180,6 +183,11 @@ impl<T: State + ?Sized> Capture<T> {
180183

181184
impl<T: State + ?Sized> Drop for Capture<T> {
182185
fn drop(&mut self) {
186+
let mut handle_valid = match self.handle_valid.write() {
187+
Ok(g) => g,
188+
Err(poison) => poison.into_inner()
189+
};
190+
*handle_valid = false;
183191
unsafe { raw::pcap_close(self.handle.as_ptr()) }
184192
}
185193
}

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ mod packet;
7474
#[cfg(not(windows))]
7575
pub use capture::activated::open_raw_fd;
7676
pub use capture::{
77-
activated::{iterator::PacketIter, BpfInstruction, BpfProgram, Direction, Savefile, Stat},
77+
activated::{iterator::PacketIter, BpfInstruction, BpfProgram, BreakLoop, Direction, Savefile, Stat},
7878
inactive::TimestampType,
7979
{Activated, Active, Capture, Dead, Inactive, Offline, Precision, State},
8080
};

0 commit comments

Comments
 (0)