From 2207e185cc29194173f420c757d65d09023da73e Mon Sep 17 00:00:00 2001 From: Ben Kimock Date: Sun, 25 Feb 2024 21:32:02 -0500 Subject: [PATCH 1/4] Add a binding for pcap_loop --- examples/loop.rs | 23 +++++++++++++ src/capture/activated/mod.rs | 63 ++++++++++++++++++++++++++++++++++ src/raw.rs | 14 +++++--- tests/capture/activated/mod.rs | 10 ++++++ 4 files changed, 106 insertions(+), 4 deletions(-) create mode 100644 examples/loop.rs diff --git a/examples/loop.rs b/examples/loop.rs new file mode 100644 index 000000000..f338c76db --- /dev/null +++ b/examples/loop.rs @@ -0,0 +1,23 @@ +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(); + + let mut count = 0; + cap.for_each(|packet| { + println!("Got {:?}", packet.header); + count += 1; + if count > 100 { + panic!("ow"); + } + }); +} diff --git a/src/capture/activated/mod.rs b/src/capture/activated/mod.rs index 0b08bf88b..27306da9f 100644 --- a/src/capture/activated/mod.rs +++ b/src/capture/activated/mod.rs @@ -4,8 +4,10 @@ pub mod iterator; pub mod offline; use std::{ + any::Any, ffi::CString, fmt, mem, + panic::{catch_unwind, resume_unwind, AssertUnwindSafe}, path::Path, ptr::{self, NonNull}, slice, @@ -199,6 +201,28 @@ impl Capture { PacketIter::new(self, codec) } + pub fn for_each(&mut self, handler: F) + where + F: FnMut(Packet), + { + let mut handler = Handler { + func: AssertUnwindSafe(handler), + panic_payload: None, + handle: self.handle, + }; + unsafe { + raw::pcap_loop( + self.handle.as_ptr(), + -1, + Handler::::callback, + &mut handler as *mut Handler> as *mut u8, + ) + }; + if let Some(e) = handler.panic_payload { + resume_unwind(e); + } + } + /// Compiles the string into a filter program using `pcap_compile`. pub fn compile(&self, program: &str, optimize: bool) -> Result { let program = CString::new(program)?; @@ -241,6 +265,45 @@ impl Capture { } } +// Handler and its associated function let us create an extern "C" fn which dispatches to a normal +// 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 { + func: F, + panic_payload: Option>, + handle: NonNull, +} + +impl Handler +where + F: FnMut(Packet), +{ + extern "C" fn callback( + slf: *mut libc::c_uchar, + header: *const raw::pcap_pkthdr, + packet: *const libc::c_uchar, + ) { + unsafe { + let packet = Packet::new( + &*(header as *const PacketHeader), + slice::from_raw_parts(packet, (*header).caplen as _), + ); + + let slf = slf as *mut Self; + let func = &mut (*slf).func; + let mut func = AssertUnwindSafe(func); + // If our handler function panics, we need to prevent it from unwinding across the + // FFI boundary. If the handler panics we catch the unwind here, break out of + // pcap_loop, and resume the unwind outside. + if let Err(e) = catch_unwind(move || func(packet)) { + (*slf).panic_payload = Some(e); + raw::pcap_breakloop((*slf).handle.as_ptr()); + } + } + } +} + impl From> for Capture { fn from(cap: Capture) -> Capture { unsafe { mem::transmute(cap) } diff --git a/src/raw.rs b/src/raw.rs index 0961ee0d3..ce8dca867 100644 --- a/src/raw.rs +++ b/src/raw.rs @@ -100,8 +100,10 @@ pub struct pcap_send_queue { pub buffer: *mut c_char, } +// This is not Option, pcap functions do not check if the handler is null so it is wrong to +// pass them Option::::None. pub type pcap_handler = - Option ()>; + extern "C" fn(arg1: *mut c_uchar, arg2: *const pcap_pkthdr, arg3: *const c_uchar) -> (); #[cfg_attr(test, automock)] pub mod ffi { @@ -124,8 +126,12 @@ pub mod ffi { pub fn pcap_open_offline(arg1: *const c_char, arg2: *mut c_char) -> *mut pcap_t; pub fn pcap_fopen_offline(arg1: *mut FILE, arg2: *mut c_char) -> *mut pcap_t; pub fn pcap_close(arg1: *mut pcap_t); - // pub fn pcap_loop(arg1: *mut pcap_t, arg2: c_int, - // arg3: pcap_handler, arg4: *mut c_uchar) -> c_int; + pub fn pcap_loop( + arg1: *mut pcap_t, + arg2: c_int, + arg3: pcap_handler, + arg4: *mut c_uchar, + ) -> c_int; // pub fn pcap_dispatch(arg1: *mut pcap_t, arg2: c_int, arg3: pcap_handler, // arg4: *mut c_uchar)-> c_int; // pub fn pcap_next(arg1: *mut pcap_t, arg2: *mut pcap_pkthdr) -> *const c_uchar; @@ -134,7 +140,7 @@ pub mod ffi { arg2: *mut *mut pcap_pkthdr, arg3: *mut *const c_uchar, ) -> c_int; - // pub fn pcap_breakloop(arg1: *mut pcap_t); + pub fn pcap_breakloop(arg1: *mut pcap_t); pub fn pcap_stats(arg1: *mut pcap_t, arg2: *mut pcap_stat) -> c_int; pub fn pcap_setfilter(arg1: *mut pcap_t, arg2: *mut bpf_program) -> c_int; pub fn pcap_setdirection(arg1: *mut pcap_t, arg2: pcap_direction_t) -> c_int; diff --git a/tests/capture/activated/mod.rs b/tests/capture/activated/mod.rs index 277795d68..948dca991 100644 --- a/tests/capture/activated/mod.rs +++ b/tests/capture/activated/mod.rs @@ -136,3 +136,13 @@ fn test_filter() { let result = capture.next_packet(); assert!(result.is_ok()); } + +#[test] +fn read_packet_via_pcap_loop() { + let mut packets = 0; + let mut capture = capture_from_test_file("packet_snaplen_65535.pcap"); + capture.for_each(|_| { + packets += 1; + }); + assert_eq!(packets, 1); +} From 4b09f61298a62bab82abdbda12d88b9b3ed3d77c Mon Sep 17 00:00:00 2001 From: Ben Kimock Date: Sun, 25 Feb 2024 21:48:00 -0500 Subject: [PATCH 2/4] Add a test that covers panic in pcap_loop --- tests/capture/activated/mod.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/capture/activated/mod.rs b/tests/capture/activated/mod.rs index 948dca991..d98f2b5a6 100644 --- a/tests/capture/activated/mod.rs +++ b/tests/capture/activated/mod.rs @@ -146,3 +146,10 @@ fn read_packet_via_pcap_loop() { }); assert_eq!(packets, 1); } + +#[test] +#[should_panic] +fn panic_in_pcap_loop() { + let mut capture = capture_from_test_file("packet_snaplen_65535.pcap"); + capture.for_each(|_| panic!()); +} From bbfd0f1c15c296f8856234dd80925fb8d4dadac3 Mon Sep 17 00:00:00 2001 From: Ben Kimock Date: Mon, 26 Feb 2024 18:20:26 -0500 Subject: [PATCH 3/4] Change to mock tests and add a count argument --- examples/loop.rs | 2 +- src/capture/activated/mod.rs | 136 ++++++++++++++++++++++++++++++++- tests/capture/activated/mod.rs | 17 ----- 3 files changed, 135 insertions(+), 20 deletions(-) diff --git a/examples/loop.rs b/examples/loop.rs index f338c76db..1a127f5d7 100644 --- a/examples/loop.rs +++ b/examples/loop.rs @@ -13,7 +13,7 @@ fn main() { .unwrap(); let mut count = 0; - cap.for_each(|packet| { + cap.for_each(None, |packet| { println!("Got {:?}", packet.header); count += 1; if count > 100 { diff --git a/src/capture/activated/mod.rs b/src/capture/activated/mod.rs index 27306da9f..076391516 100644 --- a/src/capture/activated/mod.rs +++ b/src/capture/activated/mod.rs @@ -5,6 +5,7 @@ pub mod offline; use std::{ any::Any, + convert::TryInto, ffi::CString, fmt, mem, panic::{catch_unwind, resume_unwind, AssertUnwindSafe}, @@ -201,10 +202,19 @@ impl Capture { PacketIter::new(self, codec) } - pub fn for_each(&mut self, handler: F) + pub fn for_each(&mut self, count: Option, handler: F) where F: FnMut(Packet), { + let cnt = match count { + // Actually passing 0 down to pcap_loop would mean read forever. + Some(0) => return, + Some(cnt) => cnt + .try_into() + .expect("count of packets to read cannot exceed c_int::MAX"), + None => -1, + }; + let mut handler = Handler { func: AssertUnwindSafe(handler), panic_payload: None, @@ -213,7 +223,7 @@ impl Capture { unsafe { raw::pcap_loop( self.handle.as_ptr(), - -1, + cnt, Handler::::callback, &mut handler as *mut Handler> as *mut u8, ) @@ -983,4 +993,126 @@ mod tests { }); assert_eq!(format!("{}", instr), "1 2 3 4"); } + + #[test] + fn read_packet_via_pcap_loop() { + let _m = RAWMTX.lock(); + + let mut value: isize = 777; + let pcap = as_pcap_t(&mut value); + + let test_capture = test_capture::(pcap); + let mut capture: Capture = test_capture.capture.into(); + + let ctx = raw::pcap_loop_context(); + ctx.expect() + .withf_st(move |arg1, cnt, _, _| *arg1 == pcap && *cnt == -1) + .return_once_st(move |_, _, func, data| { + let header = raw::pcap_pkthdr { + ts: libc::timeval { + tv_sec: 0, + tv_usec: 0, + }, + caplen: 0, + len: 0, + }; + let packet_data = &[]; + func(data, &header, packet_data.as_ptr()); + 0 + }); + + let mut packets = 0; + capture.for_each(None, |_| { + packets += 1; + }); + assert_eq!(packets, 1); + } + + #[test] + #[should_panic = "panic in callback"] + fn panic_in_pcap_loop() { + let _m = RAWMTX.lock(); + + let mut value: isize = 777; + let pcap = as_pcap_t(&mut value); + + let test_capture = test_capture::(pcap); + let mut capture: Capture = test_capture.capture.into(); + + let ctx = raw::pcap_loop_context(); + ctx.expect() + .withf_st(move |arg1, cnt, _, _| *arg1 == pcap && *cnt == -1) + .return_once_st(move |_, _, func, data| { + let header = raw::pcap_pkthdr { + ts: libc::timeval { + tv_sec: 0, + tv_usec: 0, + }, + caplen: 0, + len: 0, + }; + let packet_data = &[]; + func(data, &header, packet_data.as_ptr()); + 0 + }); + + let ctx = raw::pcap_breakloop_context(); + ctx.expect() + .withf_st(move |arg1| *arg1 == pcap) + .return_once_st(move |_| {}); + + capture.for_each(None, |_| panic!("panic in callback")); + } + + #[test] + fn for_each_with_count() { + let _m = RAWMTX.lock(); + + let mut value: isize = 777; + let pcap = as_pcap_t(&mut value); + + let test_capture = test_capture::(pcap); + let mut capture: Capture = test_capture.capture.into(); + + let ctx = raw::pcap_loop_context(); + ctx.expect() + .withf_st(move |arg1, cnt, _, _| *arg1 == pcap && *cnt == 2) + .return_once_st(move |_, _, func, data| { + let header = raw::pcap_pkthdr { + ts: libc::timeval { + tv_sec: 0, + tv_usec: 0, + }, + caplen: 0, + len: 0, + }; + let packet_data = &[]; + func(data, &header, packet_data.as_ptr()); + func(data, &header, packet_data.as_ptr()); + 0 + }); + + let mut packets = 0; + capture.for_each(Some(2), |_| { + packets += 1; + }); + assert_eq!(packets, 2); + } + + #[test] + fn for_each_with_count_0() { + let _m = RAWMTX.lock(); + + let mut value: isize = 777; + let pcap = as_pcap_t(&mut value); + + let test_capture = test_capture::(pcap); + let mut capture: Capture = test_capture.capture.into(); + + let mut packets = 0; + capture.for_each(Some(0), |_| { + packets += 1; + }); + assert_eq!(packets, 0); + } } diff --git a/tests/capture/activated/mod.rs b/tests/capture/activated/mod.rs index d98f2b5a6..277795d68 100644 --- a/tests/capture/activated/mod.rs +++ b/tests/capture/activated/mod.rs @@ -136,20 +136,3 @@ fn test_filter() { let result = capture.next_packet(); assert!(result.is_ok()); } - -#[test] -fn read_packet_via_pcap_loop() { - let mut packets = 0; - let mut capture = capture_from_test_file("packet_snaplen_65535.pcap"); - capture.for_each(|_| { - packets += 1; - }); - assert_eq!(packets, 1); -} - -#[test] -#[should_panic] -fn panic_in_pcap_loop() { - let mut capture = capture_from_test_file("packet_snaplen_65535.pcap"); - capture.for_each(|_| panic!()); -} From e02d7935576f590250923d038a0734e23be41ef7 Mon Sep 17 00:00:00 2001 From: Ben Kimock Date: Wed, 13 Mar 2024 19:23:53 -0400 Subject: [PATCH 4/4] Check return code from pcap_loop --- examples/loop.rs | 3 ++- src/capture/activated/mod.rs | 36 +++++++++++++++++++++++------------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/examples/loop.rs b/examples/loop.rs index 1a127f5d7..9344b29e3 100644 --- a/examples/loop.rs +++ b/examples/loop.rs @@ -19,5 +19,6 @@ fn main() { if count > 100 { panic!("ow"); } - }); + }) + .unwrap(); } diff --git a/src/capture/activated/mod.rs b/src/capture/activated/mod.rs index 076391516..5a4287c3e 100644 --- a/src/capture/activated/mod.rs +++ b/src/capture/activated/mod.rs @@ -202,13 +202,14 @@ impl Capture { PacketIter::new(self, codec) } - pub fn for_each(&mut self, count: Option, handler: F) + pub fn for_each(&mut self, count: Option, handler: F) -> Result<(), Error> where F: FnMut(Packet), { let cnt = match count { // Actually passing 0 down to pcap_loop would mean read forever. - Some(0) => return, + // We interpret it as "read nothing", so we just succeed immediately. + Some(0) => return Ok(()), Some(cnt) => cnt .try_into() .expect("count of packets to read cannot exceed c_int::MAX"), @@ -220,7 +221,7 @@ impl Capture { panic_payload: None, handle: self.handle, }; - unsafe { + let return_code = unsafe { raw::pcap_loop( self.handle.as_ptr(), cnt, @@ -231,6 +232,7 @@ impl Capture { if let Some(e) = handler.panic_payload { resume_unwind(e); } + self.check_err(return_code == 0) } /// Compiles the string into a filter program using `pcap_compile`. @@ -1022,9 +1024,11 @@ mod tests { }); let mut packets = 0; - capture.for_each(None, |_| { - packets += 1; - }); + capture + .for_each(None, |_| { + packets += 1; + }) + .unwrap(); assert_eq!(packets, 1); } @@ -1061,7 +1065,9 @@ mod tests { .withf_st(move |arg1| *arg1 == pcap) .return_once_st(move |_| {}); - capture.for_each(None, |_| panic!("panic in callback")); + capture + .for_each(None, |_| panic!("panic in callback")) + .unwrap(); } #[test] @@ -1093,9 +1099,11 @@ mod tests { }); let mut packets = 0; - capture.for_each(Some(2), |_| { - packets += 1; - }); + capture + .for_each(Some(2), |_| { + packets += 1; + }) + .unwrap(); assert_eq!(packets, 2); } @@ -1110,9 +1118,11 @@ mod tests { let mut capture: Capture = test_capture.capture.into(); let mut packets = 0; - capture.for_each(Some(0), |_| { - packets += 1; - }); + capture + .for_each(Some(0), |_| { + packets += 1; + }) + .unwrap(); assert_eq!(packets, 0); } }