#[cfg(feature = "tokio")]
pub mod tokio;
mod unique;
use self::{unique::Unique, Error::*};
use pcap_sys as raw;
pub use pcap_sys::linktypes;
#[cfg(feature = "tokio")]
use std::io;
#[cfg(not(windows))]
use std::os::unix::io::{AsRawFd, RawFd};
#[cfg(windows)]
use std::os::windows::io::RawHandle;
use std::{
borrow::Borrow,
convert::TryInto,
ffi::{self, CStr, CString},
fmt,
marker::PhantomData,
mem,
ops::Deref,
path::Path,
ptr, slice,
};
#[derive(Debug, PartialEq)]
pub enum Error {
MalformedError(std::str::Utf8Error),
InvalidString,
PcapError(String),
InvalidLinktype,
TimeoutExpired,
NoMorePackets,
NonNonBlock,
InsufficientMemory,
InvalidInputString,
IoError(std::io::ErrorKind),
InvalidRawFd,
InvalidStdHandle,
PcapDumpFlushFailure,
}
impl Error {
fn new(ptr: *const libc::c_char) -> Error {
match cstr_to_string(ptr) {
Err(e) => e as Error,
Ok(string) => PcapError(string.unwrap_or_default()),
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
MalformedError(ref e) => write!(f, "libpcap returned invalid UTF-8: {}", e),
InvalidString => write!(f, "libpcap returned a null string"),
PcapError(ref e) => write!(f, "libpcap error: {}", e),
InvalidLinktype => write!(f, "invalid or unknown linktype"),
TimeoutExpired => write!(f, "timeout expired while reading from a live capture"),
NonNonBlock => write!(f, "must be in non-blocking mode to function"),
NoMorePackets => write!(f, "no more packets to read from the file"),
InsufficientMemory => write!(f, "insufficient memory"),
InvalidInputString => write!(f, "invalid input string (internal null)"),
IoError(ref e) => write!(f, "io error occurred: {:?}", e),
InvalidRawFd => write!(f, "invalid raw file descriptor provided"),
InvalidStdHandle => write!(f, "invalid std handle provided"),
PcapDumpFlushFailure => write!(f, "pcap_dump_flush failed"),
}
}
}
impl std::error::Error for Error {
fn description(&self) -> &str {
match *self {
MalformedError(..) => "libpcap returned invalid UTF-8",
PcapError(..) => "libpcap FFI error",
InvalidString => "libpcap returned a null string",
InvalidLinktype => "invalid or unknown linktype",
TimeoutExpired => "timeout expired while reading from a live capture",
NonNonBlock => "must be in non-blocking mode to function",
NoMorePackets => "no more packets to read from the file",
InsufficientMemory => "insufficient memory",
InvalidInputString => "invalid input string (internal null)",
IoError(..) => "io error occurred",
InvalidRawFd => "invalid raw file descriptor provided",
InvalidStdHandle => "invalid raw file descriptor provided",
PcapDumpFlushFailure => "pcap_dump_flush failed",
}
}
fn cause(&self) -> Option<&dyn std::error::Error> {
match *self {
MalformedError(ref e) => Some(e),
_ => None,
}
}
}
impl From<ffi::NulError> for Error {
fn from(_: ffi::NulError) -> Error {
InvalidInputString
}
}
impl From<std::str::Utf8Error> for Error {
fn from(obj: std::str::Utf8Error) -> Error {
MalformedError(obj)
}
}
impl From<std::io::Error> for Error {
fn from(obj: std::io::Error) -> Error {
IoError(obj.kind())
}
}
impl From<std::io::ErrorKind> for Error {
fn from(obj: std::io::ErrorKind) -> Error {
IoError(obj)
}
}
#[derive(Debug)]
pub struct Device {
pub name: String,
pub desc: Option<String>,
}
impl Device {
fn new(name: String, desc: Option<String>) -> Device {
Device { name, desc }
}
pub fn open(self) -> Result<Capture<Active>, Error> {
Capture::from_device(self)?.open()
}
pub fn lookup() -> Result<Device, Error> {
with_errbuf(|err| unsafe {
cstr_to_string(raw::pcap_lookupdev(err))?
.map(|name| Device::new(name, None))
.ok_or_else(|| Error::new(err))
})
}
pub fn list() -> Result<Vec<Device>, Error> {
with_errbuf(|err| unsafe {
let mut dev_buf: *mut raw::pcap_if_t = ptr::null_mut();
if raw::pcap_findalldevs(&mut dev_buf, err) != 0 {
return Err(Error::new(err));
}
let result = (|| {
let mut devices = vec![];
let mut cur = dev_buf;
while !cur.is_null() {
let dev = &*cur;
devices.push(Device::new(
cstr_to_string(dev.name)?.ok_or(InvalidString)?,
cstr_to_string(dev.description)?,
));
cur = dev.next;
}
Ok(devices)
})();
raw::pcap_freealldevs(dev_buf);
result
})
}
}
impl<'a> Into<Device> for &'a str {
fn into(self) -> Device {
Device::new(self.into(), None)
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct Linktype(pub i32);
impl Linktype {
pub fn get_name(self) -> Result<String, Error> {
cstr_to_string(unsafe { raw::pcap_datalink_val_to_name(self.0) })?.ok_or(InvalidLinktype)
}
pub fn get_description(self) -> Result<String, Error> {
cstr_to_string(unsafe { raw::pcap_datalink_val_to_description(self.0) })?
.ok_or(InvalidLinktype)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Packet<'a> {
pub header: &'a PacketHeader,
pub data: &'a [u8],
}
impl<'a> Packet<'a> {
#[doc(hidden)]
pub fn new(header: &'a PacketHeader, data: &'a [u8]) -> Packet<'a> {
Packet { header, data }
}
}
impl<'b> Deref for Packet<'b> {
type Target = [u8];
fn deref(&self) -> &[u8] {
self.data
}
}
#[repr(C)]
#[derive(Copy, Clone)]
pub struct PacketHeader {
pub ts: libc::timeval,
pub caplen: u32,
pub len: u32,
}
impl fmt::Debug for PacketHeader {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"PacketHeader {{ ts: {}.{:06}, caplen: {}, len: {} }}",
self.ts.tv_sec, self.ts.tv_usec, self.caplen, self.len
)
}
}
impl PartialEq for PacketHeader {
fn eq(&self, rhs: &PacketHeader) -> bool {
self.ts.tv_sec == rhs.ts.tv_sec
&& self.ts.tv_usec == rhs.ts.tv_usec
&& self.caplen == rhs.caplen
&& self.len == rhs.len
}
}
impl Eq for PacketHeader {}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Stat {
pub received: u32,
pub dropped: u32,
pub if_dropped: u32,
}
impl Stat {
fn new(received: u32, dropped: u32, if_dropped: u32) -> Stat {
Stat {
received,
dropped,
if_dropped,
}
}
}
#[repr(u32)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Precision {
Micro = raw::PCAP_TSTAMP_PRECISION_NANO,
Nano = raw::PCAP_TSTAMP_PRECISION_MICRO,
}
pub enum Inactive {}
pub enum Active {}
pub enum Offline {}
pub enum Dead {}
pub unsafe trait Activated: State {}
unsafe impl Activated for Active {}
unsafe impl Activated for Offline {}
unsafe impl Activated for Dead {}
pub unsafe trait State: Send + Sync {}
unsafe impl State for Inactive {}
unsafe impl State for Active {}
unsafe impl State for Offline {}
unsafe impl State for Dead {}
pub struct Capture<T: State + ?Sized> {
nonblock: bool,
handle: Unique<raw::pcap_t>,
_marker: PhantomData<T>,
}
impl<T: State + ?Sized> Capture<T> {
fn new(handle: *mut raw::pcap_t) -> Capture<T> {
unsafe {
Capture {
nonblock: false,
handle: Unique::new(handle),
_marker: PhantomData,
}
}
}
fn new_raw<F>(path: Option<&str>, func: F) -> Result<Capture<T>, Error>
where
F: FnOnce(*const libc::c_char, *mut libc::c_char) -> *mut raw::pcap_t,
{
with_errbuf(|err| {
let handle = match path {
None => func(ptr::null(), err),
Some(path) => {
let path = CString::new(path)?;
func(path.as_ptr(), err)
}
};
unsafe { handle.as_mut() }
.map(|h| Capture::new(h))
.ok_or_else(|| Error::new(err))
})
}
#[inline]
fn check_err(&self, success: bool) -> Result<(), Error> {
if success {
Ok(())
} else {
Err(Error::new(unsafe { raw::pcap_geterr(*self.handle) }))
}
}
}
impl Capture<Offline> {
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Capture<Offline>, Error> {
Capture::new_raw(path.as_ref().to_str(), |path, err| unsafe {
raw::pcap_open_offline(path, err)
})
}
pub fn from_file_with_precision<P: AsRef<Path>>(
path: P,
precision: Precision,
) -> Result<Capture<Offline>, Error> {
Capture::new_raw(path.as_ref().to_str(), |path, err| unsafe {
raw::pcap_open_offline_with_tstamp_precision(path, precision as libc::c_uint, err)
})
}
#[cfg(not(windows))]
pub fn from_raw_fd(fd: RawFd) -> Result<Capture<Offline>, Error> {
open_raw_fd(fd, "r").and_then(|file| {
Capture::new_raw(None, |_, err| unsafe {
raw::pcap_fopen_offline(file as _, err)
})
})
}
#[cfg(all(not(windows), feature = "pcap-fopen-offline-precision"))]
pub fn from_raw_fd_with_precision(
fd: RawFd,
precision: Precision,
) -> Result<Capture<Offline>, Error> {
open_raw_fd(fd, "r").and_then(|file| {
Capture::new_raw(None, |_, err| unsafe {
raw::pcap_fopen_offline_with_tstamp_precision(file, precision as _, err)
})
})
}
#[cfg(windows)]
pub fn from_raw_handle(handle: RawHandle) -> Result<Capture<Offline>, Error> {
Capture::new_raw(None, |_, err| unsafe {
raw::pcap_hopen_offline(handle as isize, err)
})
}
#[cfg(all(windows, feature = "pcap-fopen-offline-precision"))]
pub fn from_raw_handle_with_precision(
handle: RawHandle,
precision: Precision,
) -> Result<Capture<Offline>, Error> {
Capture::new_raw(None, |_, err| unsafe {
raw::pcap_hopen_offline_with_tstamp_precision(handle as isize, precision as _, err)
})
}
}
#[repr(i32)]
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum TimestampType {
Host = 0,
HostLowPrec = 1,
HostHighPrec = 2,
Adapter = 3,
AdapterUnsynced = 4,
}
#[deprecated(note = "Renamed to TimestampType")]
pub type TstampType = TimestampType;
pub enum Direction {
InOut = raw::pcap_direction_t_PCAP_D_INOUT as _,
In = raw::pcap_direction_t_PCAP_D_IN as _,
Out = raw::pcap_direction_t_PCAP_D_OUT as _,
}
impl Capture<Inactive> {
pub fn from_device<D: Into<Device>>(device: D) -> Result<Capture<Inactive>, Error> {
let device: Device = device.into();
Capture::new_raw(Some(&device.name), |name, err| unsafe {
raw::pcap_create(name, err)
})
}
pub fn open(self) -> Result<Capture<Active>, Error> {
unsafe {
self.check_err(raw::pcap_activate(*self.handle) == 0)?;
Ok(mem::transmute(self))
}
}
pub fn timeout(self, ms: i32) -> Capture<Inactive> {
unsafe { raw::pcap_set_timeout(*self.handle, ms) };
self
}
pub fn tstamp_type(self, tstamp_type: TimestampType) -> Result<Capture<Inactive>, Error> {
self.check_err(unsafe {
raw::pcap_set_tstamp_type(*self.handle, tstamp_type as libc::c_int) == 0
})?;
Ok(self)
}
pub fn promisc(self, to: bool) -> Capture<Inactive> {
unsafe { raw::pcap_set_promisc(*self.handle, if to { 1 } else { 0 }) };
self
}
pub fn rfmon(self, to: bool) -> Capture<Inactive> {
unsafe { raw::pcap_set_rfmon(*self.handle, if to { 1 } else { 0 }) };
self
}
pub fn buffer_size(self, to: i32) -> Capture<Inactive> {
unsafe { raw::pcap_set_buffer_size(*self.handle, to) };
self
}
pub fn precision(self, precision: Precision) -> Result<Capture<Inactive>, Error> {
self.check_err(unsafe {
raw::pcap_set_tstamp_precision(*self.handle, precision as libc::c_int) == 0
})?;
Ok(self)
}
pub fn snaplen(self, to: i32) -> Capture<Inactive> {
unsafe { raw::pcap_set_snaplen(*self.handle, to) };
self
}
pub fn immediate_mode(self, to: bool) -> Capture<Inactive> {
unsafe { raw::pcap_set_immediate_mode(*self.handle, if to { 1 } else { 0 }) };
self
}
}
impl<T: Activated + ?Sized> Capture<T> {
pub fn list_datalinks(&self) -> Result<Vec<Linktype>, Error> {
unsafe {
let mut links: *mut i32 = ptr::null_mut();
let num = raw::pcap_list_datalinks(*self.handle, &mut links);
let mut vec = vec![];
if num > 0 {
vec.extend(
slice::from_raw_parts(links, num.try_into().unwrap())
.iter()
.cloned()
.map(Linktype),
)
}
raw::pcap_free_datalinks(links);
self.check_err(num > 0).and(Ok(vec))
}
}
pub fn get_datalink(&self) -> Linktype {
unsafe { Linktype(raw::pcap_datalink(*self.handle)) }
}
pub fn set_datalink(&mut self, linktype: Linktype) -> Result<(), Error> {
self.check_err(unsafe { raw::pcap_set_datalink(*self.handle, linktype.0) == 0 })
}
pub fn savefile<P: AsRef<Path>>(&self, path: P) -> Result<Savefile, Error> {
let name = CString::new(path.as_ref().to_str().unwrap())?;
let handle = unsafe { raw::pcap_dump_open(*self.handle, name.as_ptr()) };
self.check_err(!handle.is_null())
.map(|_| Savefile::new(handle))
}
#[cfg(not(windows))]
pub fn savefile_raw_fd(&self, fd: libc::c_int) -> Result<Savefile, Error> {
open_raw_fd(fd, "w").and_then(|file| {
let handle = unsafe { raw::pcap_dump_fopen(*self.handle, file as _) };
self.check_err(!handle.is_null())
.map(|_| Savefile::new(handle))
})
}
#[cfg(feature = "pcap-savefile-append")]
pub fn savefile_append<P: AsRef<Path>>(&self, path: P) -> Result<Savefile, Error> {
let name = CString::new(path.as_ref().to_str().unwrap())?;
let handle = unsafe { raw::pcap_dump_open_append(*self.handle, name.as_ptr()) };
self.check_err(!handle.is_null())
.map(|_| Savefile::new(handle))
}
pub fn direction(&self, direction: Direction) -> Result<(), Error> {
self.check_err(unsafe {
raw::pcap_setdirection(*self.handle, direction as raw::pcap_direction_t) == 0
})
}
#[allow(clippy::should_implement_trait)]
pub fn next(&mut self) -> Result<Packet, Error> {
unsafe {
let mut header: *mut raw::pcap_pkthdr = ptr::null_mut();
let mut packet: *const libc::c_uchar = ptr::null();
let retcode = raw::pcap_next_ex(*self.handle, &mut header, &mut packet);
self.check_err(retcode != -1)?;
match retcode {
i if i >= 1 => {
Ok(Packet::new(
&*(&*header as *const raw::pcap_pkthdr as *const PacketHeader),
slice::from_raw_parts(packet, (*header).caplen.try_into().unwrap()),
))
}
0 => {
Err(TimeoutExpired)
}
-2 => {
Err(NoMorePackets)
}
_ => {
unreachable!()
}
}
}
}
#[cfg(feature = "tokio")]
fn next_noblock<'a>(
&'a mut self,
fd: &mut tokio_core::reactor::PollEvented<tokio::SelectableFd>,
) -> Result<Packet<'a>, Error> {
if let futures::Async::NotReady = fd.poll_read() {
return Err(IoError(io::ErrorKind::WouldBlock));
} else {
return match self.next() {
Ok(p) => Ok(p),
Err(TimeoutExpired) => {
fd.need_read();
Err(IoError(io::ErrorKind::WouldBlock))
}
Err(e) => Err(e),
};
}
}
#[cfg(feature = "tokio")]
pub fn stream<C: tokio::PacketCodec>(
self,
handle: &tokio_core::reactor::Handle,
codec: C,
) -> Result<tokio::PacketStream<T, C>, Error> {
if !self.nonblock {
return Err(NonNonBlock);
}
unsafe {
let fd = raw::pcap_get_selectable_fd(*self.handle);
tokio::PacketStream::new(self, fd, handle, codec)
}
}
pub fn filter(&mut self, program: &str) -> Result<(), Error> {
let program = CString::new(program)?;
unsafe {
let mut bpf_program: raw::bpf_program = mem::zeroed();
let ret = raw::pcap_compile(*self.handle, &mut bpf_program, program.as_ptr(), 0, 0);
self.check_err(ret != -1)?;
let ret = raw::pcap_setfilter(*self.handle, &mut bpf_program);
raw::pcap_freecode(&mut bpf_program);
self.check_err(ret != -1)
}
}
pub fn stats(&mut self) -> Result<Stat, Error> {
unsafe {
let mut stats: raw::pcap_stat = mem::zeroed();
self.check_err(raw::pcap_stats(*self.handle, &mut stats) != -1)
.map(|_| Stat::new(stats.ps_recv, stats.ps_drop, stats.ps_ifdrop))
}
}
}
impl Capture<Active> {
pub fn sendpacket<B: Borrow<[u8]>>(&mut self, buf: B) -> Result<(), Error> {
let buf = buf.borrow();
self.check_err(unsafe {
raw::pcap_sendpacket(*self.handle, buf.as_ptr(), buf.len().try_into().unwrap()) == 0
})
}
pub fn setnonblock(mut self) -> Result<Capture<Active>, Error> {
with_errbuf(|err| unsafe {
if raw::pcap_setnonblock(*self.handle, 1, err) != 0 {
return Err(Error::new(err));
}
self.nonblock = true;
Ok(self)
})
}
#[cfg(windows)]
pub fn as_raw_fd(&self) -> libc::c_int {
unsafe {
let fd = raw::pcap_fileno(*self.handle);
match fd {
-1 => {
panic!("Unable to get file descriptor for live capture");
}
fd => fd,
}
}
}
}
impl Capture<Dead> {
pub fn dead(linktype: Linktype) -> Result<Capture<Dead>, Error> {
unsafe { raw::pcap_open_dead(linktype.0, 65535).as_mut() }
.map(|h| Capture::new(h))
.ok_or(InsufficientMemory)
}
}
#[cfg(not(windows))]
impl AsRawFd for Capture<Active> {
fn as_raw_fd(&self) -> RawFd {
unsafe {
let fd = raw::pcap_fileno(*self.handle);
match fd {
-1 => {
panic!("Unable to get file descriptor for live capture");
}
fd => fd,
}
}
}
}
impl<T: State + ?Sized> Drop for Capture<T> {
fn drop(&mut self) {
unsafe { raw::pcap_close(*self.handle) }
}
}
impl<T: Activated> From<Capture<T>> for Capture<dyn Activated> {
fn from(cap: Capture<T>) -> Capture<dyn Activated> {
unsafe { mem::transmute(cap) }
}
}
pub struct Savefile {
handle: Unique<raw::pcap_dumper_t>,
}
impl Savefile {
pub fn write(&mut self, packet: &Packet) {
unsafe {
raw::pcap_dump(
*self.handle as *mut libc::c_uchar,
&*(packet.header as *const PacketHeader as *const raw::pcap_pkthdr),
packet.data.as_ptr(),
);
}
}
pub fn flush(&mut self) -> Result<(), Error> {
unsafe {
if raw::pcap_dump_flush(*self.handle) == 0 {
Ok(())
} else {
Err(PcapDumpFlushFailure)
}
}
}
}
impl Savefile {
fn new(handle: *mut raw::pcap_dumper_t) -> Savefile {
unsafe {
Savefile {
handle: Unique::new(handle),
}
}
}
}
impl Drop for Savefile {
fn drop(&mut self) {
unsafe { raw::pcap_dump_close(*self.handle) }
}
}
#[cfg(not(windows))]
fn open_raw_fd(fd: libc::c_int, mode: &str) -> Result<*mut libc::FILE, Error> {
let mode = CString::new(mode)?;
unsafe { libc::fdopen(fd, mode.as_ptr() as *const i8).as_mut() }
.map(|f| f as _)
.ok_or(InvalidRawFd)
}
#[inline]
fn cstr_to_string(ptr: *const libc::c_char) -> Result<Option<String>, Error> {
Ok(if ptr.is_null() {
None
} else {
Some(unsafe { CStr::from_ptr(ptr) }.to_str()?.to_owned())
})
}
#[inline]
fn with_errbuf<T, F>(func: F) -> Result<T, Error>
where
F: FnOnce(*mut libc::c_char) -> Result<T, Error>,
{
let mut errbuf = [0i8; 256];
func(errbuf.as_mut_ptr())
}
#[test]
fn test_struct_size() {
use std::mem::size_of;
assert_eq!(size_of::<PacketHeader>(), size_of::<raw::pcap_pkthdr>());
}