extern crate libc;
use unique::Unique;
use std::marker::PhantomData;
use std::ptr;
use std::ffi::{CStr,CString};
use std::path::Path;
use std::slice;
use std::ops::Deref;
use std::mem::transmute;
use std::str;
use std::fmt;
use self::Error::*;
#[cfg(not(windows))]
use std::os::unix::io::{RawFd, AsRawFd};
pub use raw::PacketHeader;
mod raw;
mod unique;
const PCAP_ERROR_NOT_ACTIVATED: i32 = -3;
const PCAP_ERRBUF_SIZE: usize = 256;
#[derive(Debug)]
pub enum Error {
MalformedError(str::Utf8Error),
InvalidString,
PcapError(String),
InvalidLinktype,
TimeoutExpired,
NoMorePackets,
InsufficientMemory,
}
impl Error {
fn new<T>(ptr: *const libc::c_char) -> Result<T, Error> {
Err(PcapError(try!(cstr_to_string(ptr))))
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
MalformedError(e) => {
write!(f, "pcap returned a string that was not encoded properly: {}", e)
},
InvalidString => {
write!(f, "pcap returned an invalid (null) string")
},
PcapError(ref e) => {
write!(f, "pcap error: {}", e)
},
InvalidLinktype => {
write!(f, "invalid or unknown linktype")
},
TimeoutExpired => {
write!(f, "timeout expired")
},
NoMorePackets => {
write!(f, "no more packets to read from the file")
},
InsufficientMemory => {
write!(f, "insufficient memory")
},
}
}
}
impl std::error::Error for Error {
fn description(&self) -> &str {
match *self {
MalformedError(..) => "message from pcap is not encoded properly",
PcapError(..) => "pcap FFI error",
InvalidString => "pcap returned an invalid (null) string",
InvalidLinktype => "invalid or unknown linktype",
TimeoutExpired => "pcap was reading from a live capture and the timeout expired",
NoMorePackets => "pcap was reading from a file and there were no more packets to read",
InsufficientMemory => "insufficient memory",
}
}
fn cause(&self) -> Option<&std::error::Error> {
match *self {
MalformedError(ref e) => Some(e),
_ => None
}
}
}
impl From<str::Utf8Error> for Error {
fn from(obj: str::Utf8Error) -> Error {
MalformedError(obj)
}
}
#[derive(Debug)]
pub struct Device {
pub name: String,
pub desc: Option<String>
}
impl Device {
pub fn open(self) -> Result<Capture<Active>, Error> {
Ok(try!(try!(Capture::from_device(self)).open()))
}
pub fn lookup() -> Result<Device, Error> {
let mut errbuf = [0i8; PCAP_ERRBUF_SIZE];
unsafe {
let default_name = raw::pcap_lookupdev(errbuf.as_mut_ptr() as *mut _);
println!("{:?}",cstr_to_string(default_name));
if default_name.is_null() {
return Error::new(errbuf.as_ptr() as *const _);
}
Ok(Device {
name: try!(cstr_to_string(default_name)),
desc: None
})
}
}
pub fn list() -> Result<Vec<Device>, Error> {
unsafe {
let mut errbuf = [0i8; PCAP_ERRBUF_SIZE];
let mut dev_buf: *mut raw::Struct_pcap_if = ptr::null_mut();
let mut ret = vec![];
match raw::pcap_findalldevs(&mut dev_buf, errbuf.as_mut_ptr() as *mut _) {
0 => {
let mut cur = dev_buf;
while !cur.is_null() {
ret.push(Device {
name: cstr_to_string((&*cur).name).unwrap(),
desc: {
if !(&*cur).description.is_null() {
Some(cstr_to_string((&*cur).description).unwrap())
} else {
None
}
}
});
cur = (&*cur).next;
}
raw::pcap_freealldevs(dev_buf);
Ok(ret)
},
_ => {
Error::new(errbuf.as_ptr() as *mut _)
}
}
}
}
}
impl<'a> Into<Device> for &'a str {
fn into(self) -> Device {
Device {
name: self.into(),
desc: None
}
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct Linktype(pub i32);
impl Linktype {
pub fn get_name(&self) -> Result<String, Error> {
unsafe {
let name = raw::pcap_datalink_val_to_name(self.0);
if name.is_null() {
return Err(InvalidLinktype)
} else {
Ok(try!(cstr_to_string(name)))
}
}
}
pub fn get_description(&self) -> Result<String, Error> {
unsafe {
let description = raw::pcap_datalink_val_to_description(self.0);
if description.is_null() {
return Err(InvalidLinktype)
} else {
Ok(try!(cstr_to_string(description)))
}
}
}
}
#[derive(Debug)]
pub struct Packet<'a> {
pub header: &'a PacketHeader,
pub data: &'a [u8]
}
impl<'b> Deref for Packet<'b> {
type Target = [u8];
fn deref(&self) -> &[u8] {
self.data
}
}
#[derive(Debug, Clone, Copy)]
pub struct Stat {
pub received: u32,
pub dropped: u32,
pub if_dropped: u32
}
pub enum Precision {
Micro,
Nano,
}
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 {}
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> {
handle: Unique<raw::pcap_t>,
_marker: PhantomData<T>
}
impl Capture<Offline> {
pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Capture<Offline>, Error> {
let name = CString::new(path.as_ref().to_str().unwrap()).unwrap();
let mut errbuf = [0i8; PCAP_ERRBUF_SIZE];
unsafe {
let handle = raw::pcap_open_offline(name.as_ptr(), errbuf.as_mut_ptr() as *mut _);
if handle.is_null() {
return Error::new(errbuf.as_ptr() as *mut _);
}
Ok(Capture {
handle: Unique::new(handle),
_marker: PhantomData
})
}
}
pub fn from_file_with_precision<P: AsRef<Path>>(path: P, precision: Precision) -> Result<Capture<Offline>, Error> {
let name = CString::new(path.as_ref().to_str().unwrap()).unwrap();
let mut errbuf = [0i8; PCAP_ERRBUF_SIZE];
unsafe {
let handle = raw::pcap_open_offline_with_tstamp_precision(name.as_ptr(), match precision {
Precision::Micro => 0,
Precision::Nano => 1,
}, errbuf.as_mut_ptr() as *mut _);
if handle.is_null() {
return Error::new(errbuf.as_ptr() as *const _);
}
Ok(Capture {
handle: Unique::new(handle),
_marker: PhantomData
})
}
}
}
pub enum TstampType {
Host,
HostLowPrec,
HostHighPrec,
Adapter,
AdapterUnsynced,
}
pub enum Direction {
InOut,
In,
Out,
}
impl Capture<Inactive> {
pub fn from_device<D: Into<Device>>(device: D) -> Result<Capture<Inactive>, Error> {
let device: Device = device.into();
let name = CString::new(device.name).unwrap();
let mut errbuf = [0i8; PCAP_ERRBUF_SIZE];
unsafe {
let handle = raw::pcap_create(name.as_ptr(), errbuf.as_mut_ptr() as *mut _);
if handle.is_null() {
return Error::new(errbuf.as_ptr() as *const _);
}
Ok(Capture {
handle: Unique::new(handle),
_marker: PhantomData
})
}
}
pub fn open(self) -> Result<Capture<Active>, Error> {
unsafe {
let cap = transmute::<Capture<Inactive>, Capture<Active>>(self);
if 0 != raw::pcap_activate(*cap.handle) {
return Error::new(raw::pcap_geterr(*cap.handle));
}
Ok(cap)
}
}
pub fn timeout(self, ms: i32) -> Capture<Inactive> {
unsafe {
raw::pcap_set_timeout(*self.handle, ms);
self
}
}
#[cfg(not(windows))]
pub fn tstamp_type(self, t: TstampType) -> Capture<Inactive> {
unsafe {
raw::pcap_set_tstamp_type(*self.handle, match t {
TstampType::Host => 0,
TstampType::HostLowPrec => 1,
TstampType::HostHighPrec => 2,
TstampType::Adapter => 3,
TstampType::AdapterUnsynced => 4,
});
self
}
}
pub fn promisc(self, to: bool) -> Capture<Inactive> {
unsafe {
raw::pcap_set_promisc(*self.handle, if to {1} else {0});
self
}
}
#[cfg(not(target_os = "windows"))]
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
}
}
#[cfg(not(windows))]
pub fn precision(self, precision: Precision) -> Capture<Inactive> {
unsafe {
raw::pcap_set_tstamp_precision(*self.handle, match precision {
Precision::Micro => 0,
Precision::Nano => 1,
});
self
}
}
pub fn snaplen(self, to: i32) -> Capture<Inactive> {
unsafe {
raw::pcap_set_snaplen(*self.handle, to);
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);
if num == PCAP_ERROR_NOT_ACTIVATED {
raw::pcap_free_datalinks(links);
panic!("It should not be possible to run list_datalinks on a Capture that is not activated, please report this bug!")
} else if num < 0 {
raw::pcap_free_datalinks(links);
Error::new(raw::pcap_geterr(*self.handle))
} else {
let slice = slice::from_raw_parts(links, num as usize).iter().map(|&a| Linktype(a)).collect();
raw::pcap_free_datalinks(links);
Ok(slice)
}
}
}
pub fn set_datalink(&mut self, linktype: Linktype) -> Result<(), Error> {
unsafe {
match raw::pcap_set_datalink(*self.handle, linktype.0) {
0 => {
Ok(())
},
_ => {
Error::new(raw::pcap_geterr(*self.handle))
}
}
}
}
pub fn get_datalink(&self) -> Linktype {
unsafe {
match raw::pcap_datalink(*self.handle) {
PCAP_ERROR_NOT_ACTIVATED => {
panic!("It should not be possible to run get_datalink on a Capture that is not activated, please report this bug!");
},
lt => {
Linktype(lt)
}
}
}
}
pub fn savefile<P: AsRef<Path>>(&self, path: P) -> Result<Savefile, Error> {
let name = CString::new(path.as_ref().to_str().unwrap()).unwrap();
unsafe {
let handle = raw::pcap_dump_open(*self.handle, name.as_ptr());
if handle.is_null() {
Error::new(raw::pcap_geterr(*self.handle))
} else {
Ok(Savefile {
handle: Unique::new(handle)
})
}
}
}
pub fn direction(&self, direction: Direction) -> Result<(), Error> {
let result = unsafe {
raw::pcap_setdirection(*self.handle, match direction {
Direction::InOut => raw::PCAP_D_INOUT,
Direction::In => raw::PCAP_D_IN,
Direction::Out => raw::PCAP_D_OUT,
})
};
if result == 0 {
Ok(())
} else {
Error::new( unsafe { raw::pcap_geterr(*self.handle) })
}
}
pub fn next<'a>(&'a mut self) -> Result<Packet<'a>, Error> {
unsafe {
let mut header: *mut raw::Struct_pcap_pkthdr = ptr::null_mut();
let mut packet: *const libc::c_uchar = ptr::null();
match raw::pcap_next_ex(*self.handle, &mut header, &mut packet) {
i if i >= 1 => {
Ok(Packet {
header: transmute(&*header),
data: slice::from_raw_parts(packet, (&*header).caplen as usize)
})
},
0 => {
Err(TimeoutExpired)
},
-1 => {
Error::new(raw::pcap_geterr(*self.handle))
},
-2 => {
Err(NoMorePackets)
},
_ => {
unreachable!()
}
}
}
}
pub fn filter(&mut self, program: &str) -> Result<(), Error> {
let program = CString::new(program).unwrap();
unsafe {
let mut bpf_program: raw::Struct_bpf_program = Default::default();
if -1 == raw::pcap_compile(*self.handle, &mut bpf_program, program.as_ptr(), 0, 0) {
return Error::new(raw::pcap_geterr(*self.handle));
}
if -1 == raw::pcap_setfilter(*self.handle, &mut bpf_program) {
raw::pcap_freecode(&mut bpf_program);
return Error::new(raw::pcap_geterr(*self.handle));
}
raw::pcap_freecode(&mut bpf_program);
Ok(())
}
}
pub fn stats(&mut self) -> Result<Stat, Error> {
unsafe {
let mut stats: raw::Struct_pcap_stat =
raw::Struct_pcap_stat {ps_recv: 0, ps_drop: 0, ps_ifdrop: 0};
if -1 == raw::pcap_stats(*self.handle, &mut stats) {
return Error::new(raw::pcap_geterr(*self.handle));
}
Ok(Stat {
received: stats.ps_recv,
dropped: stats.ps_drop,
if_dropped: stats.ps_ifdrop
})
}
}
}
impl Capture<Active> {
pub fn sendpacket<'a>(&mut self, buf: &'a [u8]) -> Result<(), Error> {
unsafe {
let result = raw::pcap_sendpacket(*self.handle, buf.as_ptr() as *const _, buf.len() as i32);
match result {
-1 => {
return Error::new(raw::pcap_geterr(*self.handle));
},
_ => {
Ok(())
}
}
}
}
}
impl Capture<Dead> {
pub fn dead(linktype: Linktype) -> Result<Capture<Dead>, Error> {
unsafe {
let handle = raw::pcap_open_dead(linktype.0, 65535);
if handle.is_null() {
return Err(Error::InsufficientMemory);
}
Ok(Capture {
handle: Unique::new(handle),
_marker: PhantomData
})
}
}
}
#[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<Activated> {
fn from(cap: Capture<T>) -> Capture<Activated> {
unsafe { transmute(cap) }
}
}
pub struct Savefile {
handle: Unique<raw::pcap_dumper_t>
}
impl Savefile {
pub fn write<'a>(&mut self, packet: &'a Packet<'a>) {
unsafe {
raw::pcap_dump(*self.handle as *mut u8, transmute::<_, &raw::Struct_pcap_pkthdr>(packet.header), packet.data.as_ptr());
}
}
}
impl Drop for Savefile {
fn drop(&mut self) {
unsafe {
raw::pcap_dump_close(*self.handle);
}
}
}
#[inline]
fn cstr_to_string(ptr: *const libc::c_char) -> Result<String, Error> {
if ptr.is_null() {
Err(InvalidString)
} else {
Ok(try!(str::from_utf8(unsafe{CStr::from_ptr(ptr)}.to_bytes())).into())
}
}