Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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();
}
105 changes: 97 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, PcapHandle},
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::<PcapHandle>::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<PcapHandle>,
}

impl<F> Handler<F>
impl<F> HandlerFn<F>
where
F: FnMut(Packet),
{
Expand Down Expand Up @@ -322,6 +355,37 @@ 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<PcapHandle>,
}

unsafe impl Send for BreakLoop {}
unsafe impl Sync for BreakLoop {}

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.
///
/// # Safety
///
/// Can be called from any thread, but **must not** be used inside a
/// signal handler unless the owning `Capture` is guaranteed to still
/// be alive.
///
/// The signal handler should defer the execution of `BreakLoop::breakloop()`
/// to a thread instead for safety.
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 +677,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
38 changes: 27 additions & 11 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,20 +91,41 @@ impl State for Dead {}
/// ```
pub struct Capture<T: State + ?Sized> {
nonblock: bool,
handle: NonNull<raw::pcap_t>,
handle: Arc<PcapHandle>,
_marker: PhantomData<T>,
}

// 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.
struct PcapHandle {
handle: NonNull<raw::pcap_t>,
}

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

// `PcapHandle` is safe to Send as it encapsulates the entire lifetime of `raw::pcap_t *`
// `PcapHandle` is only Sync under special circumstances when used in thread-safe functions such as
// the `pcap_breakloop` function. The Sync correctness is left to the wrapping structure to provide.
unsafe impl Send for PcapHandle {}

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

unsafe impl<T: State + ?Sized> Send for Capture<T> {}

// `Capture` is not safe to implement Sync as the libpcap functions it uses are not promised to have
// thread-safe access to the same `raw::pcap_t *` from multiple threads.
#[allow(clippy::arc_with_non_send_sync)]
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(PcapHandle { handle }),
_marker: PhantomData,
}
}
Expand Down Expand Up @@ -178,12 +200,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