Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Changed

- MSRV is now `1.64`.
- Binding for `pcap_breakloop` added. It can be accessed via the `breakloop_handle` call on Activated captures.

## [2.3.0] - 2025-07-19

Expand Down
36 changes: 36 additions & 0 deletions examples/breakloop.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use std::{thread, time::Duration};

fn main() {
// Get the default device
let device = pcap::Device::lookup()
.expect("device lookup failed")
.expect("no device available");
println!("Using device {}", device.name);

// Setup capture
let mut cap = pcap::Capture::from_device(device)
.unwrap()
.immediate_mode(true)
.open()
.unwrap();
println!("Using device");

let break_handle = cap.breakloop_handle();

// Start capture in a separate thread
let capture_thread = thread::spawn(move || {
while cap.next_packet().is_ok() {
println!("got packet!");
}
println!("capture loop exited");
});

// Send break_handle to a separate thread (e.g. user input, signal handler, etc.)
thread::spawn(move || {
thread::sleep(Duration::from_secs(1));
println!("break loop called!");
break_handle.breakloop();
});

capture_thread.join().unwrap();
}
93 changes: 85 additions & 8 deletions src/capture/activated/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ use std::{
path::Path,
ptr::{self, NonNull},
slice,
sync::{Arc, Weak},
};

#[cfg(not(windows))]
use std::os::unix::io::RawFd;

use crate::{
capture::{Activated, Capture},
capture::{Activated, Capture, Handle},
codec::PacketCodec,
linktype::Linktype,
packet::{Packet, PacketHeader},
Expand Down Expand Up @@ -216,17 +217,17 @@ impl<T: Activated + ?Sized> Capture<T> {
None => -1,
};

let mut handler = Handler {
let mut handler = HandlerFn {
func: AssertUnwindSafe(handler),
panic_payload: None,
handle: self.handle,
handle: self.handle.clone(),
};
let return_code = unsafe {
raw::pcap_loop(
self.handle.as_ptr(),
cnt,
Handler::<F>::callback,
&mut handler as *mut Handler<AssertUnwindSafe<F>> as *mut u8,
HandlerFn::<F>::callback,
&mut handler as *mut HandlerFn<AssertUnwindSafe<F>> as *mut u8,
)
};
if let Some(e) = handler.panic_payload {
Expand All @@ -235,6 +236,38 @@ impl<T: Activated + ?Sized> Capture<T> {
self.check_err(return_code == 0)
}

/// Returns a thread-safe `BreakLoop` handle for calling pcap_breakloop() on an active capture.
///
/// # Example
///
/// ```no_run
/// // Using an active capture
/// use pcap::Device;
///
/// let mut cap = Device::lookup().unwrap().unwrap().open().unwrap();
///
/// let break_handle = cap.breakloop_handle();
///
/// let capture_thread = std::thread::spawn(move || {
/// while let Ok(packet) = cap.next_packet() {
/// println!("received packet! {:?}", packet);
/// }
/// });
///
/// // Send break_handle to a separate thread (e.g. user input, signal handler, etc.)
/// std::thread::spawn(move || {
/// std::thread::sleep(std::time::Duration::from_secs(1));
/// break_handle.breakloop();
/// });
///
/// capture_thread.join().unwrap();
/// ```
pub fn breakloop_handle(&mut self) -> BreakLoop {
BreakLoop {
handle: Arc::<Handle>::downgrade(&self.handle),
}
}

/// Compiles the string into a filter program using `pcap_compile`.
pub fn compile(&self, program: &str, optimize: bool) -> Result<BpfProgram, Error> {
let program = CString::new(program)?;
Expand Down Expand Up @@ -281,13 +314,13 @@ impl<T: Activated + ?Sized> Capture<T> {
// Rust FnMut, which may be a closure with a captured environment. The *only* purpose of this
// generic parameter is to ensure that in Capture::pcap_loop that we pass the right function
// pointer and the right data pointer to pcap_loop.
struct Handler<F> {
struct HandlerFn<F> {
func: F,
panic_payload: Option<Box<dyn Any + Send>>,
handle: NonNull<raw::pcap_t>,
handle: Arc<Handle>,
}

impl<F> Handler<F>
impl<F> HandlerFn<F>
where
F: FnMut(Packet),
{
Expand Down Expand Up @@ -322,6 +355,25 @@ impl<T: Activated> From<Capture<T>> for Capture<dyn Activated> {
}
}

/// BreakLoop can safely be sent to other threads such as signal handlers to abort
/// blocking capture loops such as `Capture::next_packet` and `Capture::for_each`.
///
/// See <https://www.tcpdump.org/manpages/pcap_breakloop.3pcap.html> for per-platform caveats about
/// how breakloop can wake up blocked threads.
pub struct BreakLoop {
handle: Weak<Handle>,
}

impl BreakLoop {
/// Calls `pcap_breakloop` to make the blocking loop of a pcap capture return.
/// The call is a no-op if the handle is invalid.
pub fn breakloop(&self) {
if let Some(handle) = self.handle.upgrade() {
unsafe { raw::pcap_breakloop(handle.as_ptr()) };
}
}
}

/// Abstraction for writing pcap savefiles, which can be read afterwards via `Capture::from_file()`.
pub struct Savefile {
handle: NonNull<raw::pcap_dumper_t>,
Expand Down Expand Up @@ -613,6 +665,31 @@ mod tests {
}
}

#[test]
fn test_breakloop_capture_dropped() {
let _m = RAWMTX.lock();

let mut value: isize = 1234;
let pcap = as_pcap_t(&mut value);

let test_capture = test_capture::<Active>(pcap);
let mut capture: Capture<dyn Activated> = test_capture.capture.into();

let ctx = raw::pcap_breakloop_context();
ctx.expect()
.withf_st(move |h| *h == pcap)
.return_const(())
.times(1);

let break_handle = capture.breakloop_handle();

break_handle.breakloop();

drop(capture);

break_handle.breakloop(); // this call does not trigger mock after drop
}

#[test]
fn test_savefile() {
let _m = RAWMTX.lock();
Expand Down
30 changes: 22 additions & 8 deletions src/capture/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use std::{
ffi::CString,
marker::PhantomData,
ptr::{self, NonNull},
sync::Arc,
};

#[cfg(windows)]
Expand Down Expand Up @@ -90,10 +91,29 @@ impl State for Dead {}
/// ```
pub struct Capture<T: State + ?Sized> {
nonblock: bool,
handle: NonNull<raw::pcap_t>,
handle: Arc<Handle>,
_marker: PhantomData<T>,
}

struct Handle {
handle: NonNull<raw::pcap_t>,
}

impl Handle {
fn as_ptr(&self) -> *mut raw::pcap_t {
self.handle.as_ptr()
}
}

unsafe impl Send for Handle {}
unsafe impl Sync for Handle {}

impl Drop for Handle {
fn drop(&mut self) {
unsafe { raw::pcap_close(self.handle.as_ptr()) }
}
}

// A Capture is safe to Send as it encapsulates the entire lifetime of `raw::pcap_t *`, but it is
// not safe to Sync as libpcap does not promise thread-safe access to the same `raw::pcap_t *` from
// multiple threads.
Expand All @@ -103,7 +123,7 @@ impl<T: State + ?Sized> From<NonNull<raw::pcap_t>> for Capture<T> {
fn from(handle: NonNull<raw::pcap_t>) -> Self {
Capture {
nonblock: false,
handle,
handle: Arc::new(Handle { handle }),
_marker: PhantomData,
}
}
Expand Down Expand Up @@ -178,12 +198,6 @@ impl<T: State + ?Sized> Capture<T> {
}
}

impl<T: State + ?Sized> Drop for Capture<T> {
fn drop(&mut self) {
unsafe { raw::pcap_close(self.handle.as_ptr()) }
}
}

#[repr(u32)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
/// Timestamp resolution types
Expand Down
4 changes: 3 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ mod packet;
#[cfg(not(windows))]
pub use capture::activated::open_raw_fd;
pub use capture::{
activated::{iterator::PacketIter, BpfInstruction, BpfProgram, Direction, Savefile, Stat},
activated::{
iterator::PacketIter, BpfInstruction, BpfProgram, BreakLoop, Direction, Savefile, Stat,
},
inactive::TimestampType,
{Activated, Active, Capture, Dead, Inactive, Offline, Precision, State},
};
Expand Down