Skip to content

Commit d1494d0

Browse files
authored
Add a binding for pcap_loop (#335)
* Add a binding for pcap_loop * Add a test that covers panic in pcap_loop * Change to mock tests and add a count argument * Check return code from pcap_loop
1 parent 587b263 commit d1494d0

3 files changed

Lines changed: 239 additions & 4 deletions

File tree

examples/loop.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
fn main() {
2+
// get the default Device
3+
let device = pcap::Device::lookup()
4+
.expect("device lookup failed")
5+
.expect("no device available");
6+
println!("Using device {}", device.name);
7+
8+
// Setup Capture
9+
let mut cap = pcap::Capture::from_device(device)
10+
.unwrap()
11+
.immediate_mode(true)
12+
.open()
13+
.unwrap();
14+
15+
let mut count = 0;
16+
cap.for_each(None, |packet| {
17+
println!("Got {:?}", packet.header);
18+
count += 1;
19+
if count > 100 {
20+
panic!("ow");
21+
}
22+
})
23+
.unwrap();
24+
}

src/capture/activated/mod.rs

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@ pub mod iterator;
44
pub mod offline;
55

66
use std::{
7+
any::Any,
8+
convert::TryInto,
79
ffi::CString,
810
fmt, mem,
11+
panic::{catch_unwind, resume_unwind, AssertUnwindSafe},
912
path::Path,
1013
ptr::{self, NonNull},
1114
slice,
@@ -199,6 +202,39 @@ impl<T: Activated + ?Sized> Capture<T> {
199202
PacketIter::new(self, codec)
200203
}
201204

205+
pub fn for_each<F>(&mut self, count: Option<usize>, handler: F) -> Result<(), Error>
206+
where
207+
F: FnMut(Packet),
208+
{
209+
let cnt = match count {
210+
// Actually passing 0 down to pcap_loop would mean read forever.
211+
// We interpret it as "read nothing", so we just succeed immediately.
212+
Some(0) => return Ok(()),
213+
Some(cnt) => cnt
214+
.try_into()
215+
.expect("count of packets to read cannot exceed c_int::MAX"),
216+
None => -1,
217+
};
218+
219+
let mut handler = Handler {
220+
func: AssertUnwindSafe(handler),
221+
panic_payload: None,
222+
handle: self.handle,
223+
};
224+
let return_code = unsafe {
225+
raw::pcap_loop(
226+
self.handle.as_ptr(),
227+
cnt,
228+
Handler::<F>::callback,
229+
&mut handler as *mut Handler<AssertUnwindSafe<F>> as *mut u8,
230+
)
231+
};
232+
if let Some(e) = handler.panic_payload {
233+
resume_unwind(e);
234+
}
235+
self.check_err(return_code == 0)
236+
}
237+
202238
/// Compiles the string into a filter program using `pcap_compile`.
203239
pub fn compile(&self, program: &str, optimize: bool) -> Result<BpfProgram, Error> {
204240
let program = CString::new(program)?;
@@ -241,6 +277,45 @@ impl<T: Activated + ?Sized> Capture<T> {
241277
}
242278
}
243279

280+
// Handler and its associated function let us create an extern "C" fn which dispatches to a normal
281+
// Rust FnMut, which may be a closure with a captured environment. The *only* purpose of this
282+
// generic parameter is to ensure that in Capture::pcap_loop that we pass the right function
283+
// pointer and the right data pointer to pcap_loop.
284+
struct Handler<F> {
285+
func: F,
286+
panic_payload: Option<Box<dyn Any + Send>>,
287+
handle: NonNull<raw::pcap_t>,
288+
}
289+
290+
impl<F> Handler<F>
291+
where
292+
F: FnMut(Packet),
293+
{
294+
extern "C" fn callback(
295+
slf: *mut libc::c_uchar,
296+
header: *const raw::pcap_pkthdr,
297+
packet: *const libc::c_uchar,
298+
) {
299+
unsafe {
300+
let packet = Packet::new(
301+
&*(header as *const PacketHeader),
302+
slice::from_raw_parts(packet, (*header).caplen as _),
303+
);
304+
305+
let slf = slf as *mut Self;
306+
let func = &mut (*slf).func;
307+
let mut func = AssertUnwindSafe(func);
308+
// If our handler function panics, we need to prevent it from unwinding across the
309+
// FFI boundary. If the handler panics we catch the unwind here, break out of
310+
// pcap_loop, and resume the unwind outside.
311+
if let Err(e) = catch_unwind(move || func(packet)) {
312+
(*slf).panic_payload = Some(e);
313+
raw::pcap_breakloop((*slf).handle.as_ptr());
314+
}
315+
}
316+
}
317+
}
318+
244319
impl<T: Activated> From<Capture<T>> for Capture<dyn Activated> {
245320
fn from(cap: Capture<T>) -> Capture<dyn Activated> {
246321
unsafe { mem::transmute(cap) }
@@ -920,4 +995,134 @@ mod tests {
920995
});
921996
assert_eq!(format!("{}", instr), "1 2 3 4");
922997
}
998+
999+
#[test]
1000+
fn read_packet_via_pcap_loop() {
1001+
let _m = RAWMTX.lock();
1002+
1003+
let mut value: isize = 777;
1004+
let pcap = as_pcap_t(&mut value);
1005+
1006+
let test_capture = test_capture::<Active>(pcap);
1007+
let mut capture: Capture<dyn Activated> = test_capture.capture.into();
1008+
1009+
let ctx = raw::pcap_loop_context();
1010+
ctx.expect()
1011+
.withf_st(move |arg1, cnt, _, _| *arg1 == pcap && *cnt == -1)
1012+
.return_once_st(move |_, _, func, data| {
1013+
let header = raw::pcap_pkthdr {
1014+
ts: libc::timeval {
1015+
tv_sec: 0,
1016+
tv_usec: 0,
1017+
},
1018+
caplen: 0,
1019+
len: 0,
1020+
};
1021+
let packet_data = &[];
1022+
func(data, &header, packet_data.as_ptr());
1023+
0
1024+
});
1025+
1026+
let mut packets = 0;
1027+
capture
1028+
.for_each(None, |_| {
1029+
packets += 1;
1030+
})
1031+
.unwrap();
1032+
assert_eq!(packets, 1);
1033+
}
1034+
1035+
#[test]
1036+
#[should_panic = "panic in callback"]
1037+
fn panic_in_pcap_loop() {
1038+
let _m = RAWMTX.lock();
1039+
1040+
let mut value: isize = 777;
1041+
let pcap = as_pcap_t(&mut value);
1042+
1043+
let test_capture = test_capture::<Active>(pcap);
1044+
let mut capture: Capture<dyn Activated> = test_capture.capture.into();
1045+
1046+
let ctx = raw::pcap_loop_context();
1047+
ctx.expect()
1048+
.withf_st(move |arg1, cnt, _, _| *arg1 == pcap && *cnt == -1)
1049+
.return_once_st(move |_, _, func, data| {
1050+
let header = raw::pcap_pkthdr {
1051+
ts: libc::timeval {
1052+
tv_sec: 0,
1053+
tv_usec: 0,
1054+
},
1055+
caplen: 0,
1056+
len: 0,
1057+
};
1058+
let packet_data = &[];
1059+
func(data, &header, packet_data.as_ptr());
1060+
0
1061+
});
1062+
1063+
let ctx = raw::pcap_breakloop_context();
1064+
ctx.expect()
1065+
.withf_st(move |arg1| *arg1 == pcap)
1066+
.return_once_st(move |_| {});
1067+
1068+
capture
1069+
.for_each(None, |_| panic!("panic in callback"))
1070+
.unwrap();
1071+
}
1072+
1073+
#[test]
1074+
fn for_each_with_count() {
1075+
let _m = RAWMTX.lock();
1076+
1077+
let mut value: isize = 777;
1078+
let pcap = as_pcap_t(&mut value);
1079+
1080+
let test_capture = test_capture::<Active>(pcap);
1081+
let mut capture: Capture<dyn Activated> = test_capture.capture.into();
1082+
1083+
let ctx = raw::pcap_loop_context();
1084+
ctx.expect()
1085+
.withf_st(move |arg1, cnt, _, _| *arg1 == pcap && *cnt == 2)
1086+
.return_once_st(move |_, _, func, data| {
1087+
let header = raw::pcap_pkthdr {
1088+
ts: libc::timeval {
1089+
tv_sec: 0,
1090+
tv_usec: 0,
1091+
},
1092+
caplen: 0,
1093+
len: 0,
1094+
};
1095+
let packet_data = &[];
1096+
func(data, &header, packet_data.as_ptr());
1097+
func(data, &header, packet_data.as_ptr());
1098+
0
1099+
});
1100+
1101+
let mut packets = 0;
1102+
capture
1103+
.for_each(Some(2), |_| {
1104+
packets += 1;
1105+
})
1106+
.unwrap();
1107+
assert_eq!(packets, 2);
1108+
}
1109+
1110+
#[test]
1111+
fn for_each_with_count_0() {
1112+
let _m = RAWMTX.lock();
1113+
1114+
let mut value: isize = 777;
1115+
let pcap = as_pcap_t(&mut value);
1116+
1117+
let test_capture = test_capture::<Active>(pcap);
1118+
let mut capture: Capture<dyn Activated> = test_capture.capture.into();
1119+
1120+
let mut packets = 0;
1121+
capture
1122+
.for_each(Some(0), |_| {
1123+
packets += 1;
1124+
})
1125+
.unwrap();
1126+
assert_eq!(packets, 0);
1127+
}
9231128
}

src/raw.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,10 @@ pub struct pcap_send_queue {
100100
pub buffer: *mut c_char,
101101
}
102102

103+
// This is not Option<fn>, pcap functions do not check if the handler is null so it is wrong to
104+
// pass them Option::<fn>::None.
103105
pub type pcap_handler =
104-
Option<extern "C" fn(arg1: *mut c_uchar, arg2: *const pcap_pkthdr, arg3: *const c_uchar) -> ()>;
106+
extern "C" fn(arg1: *mut c_uchar, arg2: *const pcap_pkthdr, arg3: *const c_uchar) -> ();
105107

106108
#[cfg_attr(test, automock)]
107109
pub mod ffi {
@@ -124,8 +126,12 @@ pub mod ffi {
124126
pub fn pcap_open_offline(arg1: *const c_char, arg2: *mut c_char) -> *mut pcap_t;
125127
pub fn pcap_fopen_offline(arg1: *mut FILE, arg2: *mut c_char) -> *mut pcap_t;
126128
pub fn pcap_close(arg1: *mut pcap_t);
127-
// pub fn pcap_loop(arg1: *mut pcap_t, arg2: c_int,
128-
// arg3: pcap_handler, arg4: *mut c_uchar) -> c_int;
129+
pub fn pcap_loop(
130+
arg1: *mut pcap_t,
131+
arg2: c_int,
132+
arg3: pcap_handler,
133+
arg4: *mut c_uchar,
134+
) -> c_int;
129135
// pub fn pcap_dispatch(arg1: *mut pcap_t, arg2: c_int, arg3: pcap_handler,
130136
// arg4: *mut c_uchar)-> c_int;
131137
// pub fn pcap_next(arg1: *mut pcap_t, arg2: *mut pcap_pkthdr) -> *const c_uchar;
@@ -134,7 +140,7 @@ pub mod ffi {
134140
arg2: *mut *mut pcap_pkthdr,
135141
arg3: *mut *const c_uchar,
136142
) -> c_int;
137-
// pub fn pcap_breakloop(arg1: *mut pcap_t);
143+
pub fn pcap_breakloop(arg1: *mut pcap_t);
138144
pub fn pcap_stats(arg1: *mut pcap_t, arg2: *mut pcap_stat) -> c_int;
139145
pub fn pcap_setfilter(arg1: *mut pcap_t, arg2: *mut bpf_program) -> c_int;
140146
pub fn pcap_setdirection(arg1: *mut pcap_t, arg2: pcap_direction_t) -> c_int;

0 commit comments

Comments
 (0)