parent
3afc519414
commit
b3172f19e1
@ -0,0 +1,111 @@ |
|||||||
|
#![allow(non_camel_case_types)] |
||||||
|
#![allow(non_snake_case)] |
||||||
|
#![allow(dead_code)] |
||||||
|
|
||||||
|
extern crate libc; |
||||||
|
|
||||||
|
pub type SockAddr = libc::sockaddr; |
||||||
|
|
||||||
|
pub const AF_LINK: libc::c_int = 18; |
||||||
|
|
||||||
|
const IF_NAMESIZE: usize = 16; |
||||||
|
const IFNAMSIZ: usize = IF_NAMESIZE; |
||||||
|
const IOC_IN: libc::c_ulong = 0x80000000; |
||||||
|
const IOC_OUT: libc::c_ulong = 0x40000000; |
||||||
|
const IOC_INOUT: libc::c_ulong = IOC_IN | IOC_OUT; |
||||||
|
const IOCPARM_SHIFT: libc::c_ulong = 13; |
||||||
|
const IOCPARM_MASK: libc::c_ulong = (1 << (IOCPARM_SHIFT as usize)) - 1; |
||||||
|
|
||||||
|
const SIZEOF_TIMEVAL: libc::c_ulong = 16; |
||||||
|
const SIZEOF_IFREQ: libc::c_ulong = 32; |
||||||
|
const SIZEOF_C_UINT: libc::c_ulong = 4; |
||||||
|
#[cfg(any(target_os = "freebsd", target_os = "netbsd"))] |
||||||
|
const SIZEOF_C_LONG: libc::c_int = 8; |
||||||
|
|
||||||
|
pub const BIOCSETIF: libc::c_ulong = |
||||||
|
IOC_IN | ((SIZEOF_IFREQ & IOCPARM_MASK) << 16usize) | (('B' as libc::c_ulong) << 8usize) | 108; |
||||||
|
pub const BIOCIMMEDIATE: libc::c_ulong = |
||||||
|
IOC_IN | ((SIZEOF_C_UINT & IOCPARM_MASK) << 16) | (('B' as libc::c_ulong) << 8) | 112; |
||||||
|
pub const BIOCGBLEN: libc::c_ulong = |
||||||
|
IOC_OUT | ((SIZEOF_C_UINT & IOCPARM_MASK) << 16) | (('B' as libc::c_ulong) << 8) | 102; |
||||||
|
pub const BIOCGDLT: libc::c_ulong = |
||||||
|
IOC_OUT | ((SIZEOF_C_UINT & IOCPARM_MASK) << 16) | (('B' as libc::c_ulong) << 8) | 106; |
||||||
|
|
||||||
|
pub const BIOCSBLEN: libc::c_ulong = |
||||||
|
IOC_INOUT | ((SIZEOF_C_UINT & IOCPARM_MASK) << 16) | (('B' as libc::c_ulong) << 8) | 102; |
||||||
|
pub const BIOCSHDRCMPLT: libc::c_ulong = |
||||||
|
IOC_IN | ((SIZEOF_C_UINT & IOCPARM_MASK) << 16) | (('B' as libc::c_ulong) << 8) | 117; |
||||||
|
pub const BIOCSRTIMEOUT: libc::c_ulong = |
||||||
|
IOC_IN | ((SIZEOF_TIMEVAL & IOCPARM_MASK) << 16) | (('B' as libc::c_ulong) << 8) | 109; |
||||||
|
|
||||||
|
#[cfg(target_os = "freebsd")] |
||||||
|
pub const BIOCFEEDBACK: libc::c_ulong = |
||||||
|
IOC_IN | ((SIZEOF_C_UINT & IOCPARM_MASK) << 16) | (('B' as libc::c_ulong) << 8) | 124; |
||||||
|
#[cfg(target_os = "netbsd")] |
||||||
|
pub const BIOCFEEDBACK: libc::c_ulong = |
||||||
|
IOC_IN | ((SIZEOF_C_UINT & IOCPARM_MASK) << 16) | (('B' as libc::c_ulong) << 8) | 125; |
||||||
|
|
||||||
|
pub const DLT_NULL: libc::c_uint = 0; |
||||||
|
|
||||||
|
#[cfg(any(target_os = "freebsd", target_os = "netbsd"))] |
||||||
|
const BPF_ALIGNMENT: libc::c_int = SIZEOF_C_LONG; |
||||||
|
#[cfg(any(target_os = "openbsd", target_os = "macos", target_os = "ios", windows))] |
||||||
|
const BPF_ALIGNMENT: libc::c_int = 4; |
||||||
|
|
||||||
|
pub fn BPF_WORDALIGN(x: isize) -> isize { |
||||||
|
let bpf_alignment = BPF_ALIGNMENT as isize; |
||||||
|
(x + (bpf_alignment - 1)) & !(bpf_alignment - 1) |
||||||
|
} |
||||||
|
|
||||||
|
pub struct ifreq { |
||||||
|
pub ifr_name: [libc::c_char; IFNAMSIZ], |
||||||
|
pub ifru_addr: SockAddr,
|
||||||
|
} |
||||||
|
|
||||||
|
#[cfg(any(target_os = "openbsd", target_os = "freebsd", target_os = "netbsd", target_os = "macos", target_os = "ios"))] |
||||||
|
pub struct sockaddr_dl { |
||||||
|
pub sdl_len: libc::c_uchar, |
||||||
|
pub sdl_family: libc::c_uchar, |
||||||
|
pub sdl_index: libc::c_ushort, |
||||||
|
pub sdl_type: libc::c_uchar, |
||||||
|
pub sdl_nlen: libc::c_uchar, |
||||||
|
pub sdl_alen: libc::c_uchar, |
||||||
|
pub sdl_slen: libc::c_uchar, |
||||||
|
pub sdl_data: [libc::c_char; 46], |
||||||
|
} |
||||||
|
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "freebsd", |
||||||
|
target_os = "netbsd", |
||||||
|
all(any(target_os = "macos", target_os = "ios"), target_pointer_width = "32"), |
||||||
|
windows |
||||||
|
))] |
||||||
|
#[repr(C)] |
||||||
|
pub struct bpf_hdr { |
||||||
|
pub bh_tstamp: libc::timeval, |
||||||
|
pub bh_caplen: u32, |
||||||
|
pub bh_datalen: u32, |
||||||
|
pub bh_hdrlen: libc::c_ushort, |
||||||
|
} |
||||||
|
|
||||||
|
pub struct timeval32 { |
||||||
|
pub tv_sec: i32, |
||||||
|
pub tv_usec: i32, |
||||||
|
} |
||||||
|
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "openbsd", |
||||||
|
all(any(target_os = "macos", target_os = "ios"), target_pointer_width = "64") |
||||||
|
))] |
||||||
|
#[repr(C)] |
||||||
|
pub struct bpf_hdr { |
||||||
|
pub bh_tstamp: timeval32, |
||||||
|
pub bh_caplen: u32, |
||||||
|
pub bh_datalen: u32, |
||||||
|
pub bh_hdrlen: libc::c_ushort, |
||||||
|
} |
||||||
|
|
||||||
|
#[cfg(not(windows))] |
||||||
|
extern "C" { |
||||||
|
pub fn ioctl(d: libc::c_int, request: libc::c_ulong, ...) -> libc::c_int; |
||||||
|
} |
@ -1,4 +1,10 @@ |
|||||||
|
#[cfg(not(target_os="windows"))] |
||||||
|
mod binding; |
||||||
|
#[cfg(not(target_os="windows"))] |
||||||
|
pub use self::binding::*; |
||||||
|
|
||||||
#[cfg(not(target_os="windows"))] |
#[cfg(not(target_os="windows"))] |
||||||
mod unix; |
mod unix; |
||||||
#[cfg(not(target_os="windows"))] |
#[cfg(not(target_os="windows"))] |
||||||
pub use self::unix::*; |
pub use self::unix::*; |
||||||
|
|
||||||
|
@ -1,17 +1,401 @@ |
|||||||
#![allow(non_camel_case_types)] |
use super::binding; |
||||||
#![allow(non_snake_case)] |
use crate::socket::{DataLinkReceiver, DataLinkSender}; |
||||||
#![allow(dead_code)] |
use crate::interface::Interface; |
||||||
|
|
||||||
pub const AF_LINK: libc::c_int = 18; |
use std::collections::VecDeque; |
||||||
|
use std::ffi::CString; |
||||||
#[cfg(any(target_os = "openbsd", target_os = "freebsd", target_os = "netbsd", target_os = "macos", target_os = "ios"))] |
use std::io; |
||||||
pub struct sockaddr_dl { |
use std::mem; |
||||||
pub sdl_len: libc::c_uchar, |
use std::ptr; |
||||||
pub sdl_family: libc::c_uchar, |
use std::sync::Arc; |
||||||
pub sdl_index: libc::c_ushort, |
use std::time::Duration; |
||||||
pub sdl_type: libc::c_uchar, |
|
||||||
pub sdl_nlen: libc::c_uchar, |
static ETHERNET_HEADER_SIZE: usize = 14; |
||||||
pub sdl_alen: libc::c_uchar, |
|
||||||
pub sdl_slen: libc::c_uchar, |
pub type CSocket = libc::c_int; |
||||||
pub sdl_data: [libc::c_char; 46], |
pub type TvUsecType = libc::c_int; |
||||||
|
|
||||||
|
unsafe fn close(sock: CSocket) { |
||||||
|
let _ = libc::close(sock); |
||||||
|
} |
||||||
|
|
||||||
|
fn duration_to_timespec(dur: Duration) -> libc::timespec { |
||||||
|
libc::timespec { |
||||||
|
tv_sec: dur.as_secs() as libc::time_t, |
||||||
|
tv_nsec: (dur.subsec_nanos() as TvUsecType).into(), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub struct FileDesc { |
||||||
|
pub fd: CSocket, |
||||||
|
} |
||||||
|
|
||||||
|
impl Drop for FileDesc { |
||||||
|
fn drop(&mut self) { |
||||||
|
unsafe { |
||||||
|
close(self.fd); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] |
||||||
|
pub struct Config { |
||||||
|
pub write_buffer_size: usize, |
||||||
|
pub read_buffer_size: usize, |
||||||
|
pub read_timeout: Option<Duration>, |
||||||
|
pub write_timeout: Option<Duration>, |
||||||
|
pub bpf_fd_attempts: usize, |
||||||
|
} |
||||||
|
|
||||||
|
impl<'a> From<&'a crate::socket::Config> for Config { |
||||||
|
fn from(config: &crate::socket::Config) -> Config { |
||||||
|
Config { |
||||||
|
write_buffer_size: config.write_buffer_size, |
||||||
|
read_buffer_size: config.read_buffer_size, |
||||||
|
bpf_fd_attempts: config.bpf_fd_attempts, |
||||||
|
read_timeout: config.read_timeout, |
||||||
|
write_timeout: config.write_timeout, |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Default for Config { |
||||||
|
fn default() -> Config { |
||||||
|
Config { |
||||||
|
write_buffer_size: 4096, |
||||||
|
read_buffer_size: 4096, |
||||||
|
bpf_fd_attempts: 1000, |
||||||
|
read_timeout: None, |
||||||
|
write_timeout: None, |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[inline] |
||||||
|
pub fn channel(interface_name: String, config: Config) -> io::Result<crate::socket::Channel> { |
||||||
|
#[cfg(any(target_os = "freebsd", target_os = "netbsd"))] |
||||||
|
fn get_fd(_attempts: usize) -> libc::c_int { |
||||||
|
unsafe { |
||||||
|
libc::open( |
||||||
|
CString::new(&b"/dev/bpf"[..]).unwrap().as_ptr(), |
||||||
|
libc::O_RDWR, |
||||||
|
0, |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[cfg(any(target_os = "openbsd", target_os = "macos", target_os = "ios"))] |
||||||
|
fn get_fd(attempts: usize) -> libc::c_int { |
||||||
|
for i in 0..attempts { |
||||||
|
let fd = unsafe { |
||||||
|
let file_name = format!("/dev/bpf{}", i); |
||||||
|
libc::open( |
||||||
|
CString::new(file_name.as_bytes()).unwrap().as_ptr(), |
||||||
|
libc::O_RDWR, |
||||||
|
0, |
||||||
|
) |
||||||
|
}; |
||||||
|
if fd != -1 { |
||||||
|
return fd; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
-1 |
||||||
|
} |
||||||
|
|
||||||
|
#[cfg(any(target_os = "freebsd", target_os = "netbsd"))] |
||||||
|
fn set_feedback(fd: libc::c_int) -> io::Result<()> { |
||||||
|
if unsafe { binding::ioctl(fd, binding::BIOCFEEDBACK, &1) } == -1 { |
||||||
|
let err = io::Error::last_os_error(); |
||||||
|
unsafe { |
||||||
|
libc::close(fd); |
||||||
|
} |
||||||
|
return Err(err); |
||||||
|
} |
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
|
||||||
|
#[cfg(any(target_os = "macos", target_os = "openbsd", target_os = "ios"))] |
||||||
|
fn set_feedback(_fd: libc::c_int) -> io::Result<()> { |
||||||
|
Ok(()) |
||||||
|
} |
||||||
|
|
||||||
|
let fd = get_fd(config.bpf_fd_attempts); |
||||||
|
if fd == -1 { |
||||||
|
return Err(io::Error::last_os_error()); |
||||||
|
} |
||||||
|
let mut iface: binding::ifreq = unsafe { mem::zeroed() }; |
||||||
|
for (i, c) in interface_name.bytes().enumerate() { |
||||||
|
iface.ifr_name[i] = c as i8; |
||||||
|
} |
||||||
|
|
||||||
|
let buflen = config.read_buffer_size as libc::c_uint; |
||||||
|
|
||||||
|
if unsafe { binding::ioctl(fd, binding::BIOCSBLEN, &buflen) } == -1 { |
||||||
|
let err = io::Error::last_os_error(); |
||||||
|
unsafe { |
||||||
|
libc::close(fd); |
||||||
|
} |
||||||
|
return Err(err); |
||||||
|
} |
||||||
|
|
||||||
|
if unsafe { binding::ioctl(fd, binding::BIOCSETIF, &iface) } == -1 { |
||||||
|
let err = io::Error::last_os_error(); |
||||||
|
unsafe { |
||||||
|
libc::close(fd); |
||||||
|
} |
||||||
|
return Err(err); |
||||||
|
} |
||||||
|
|
||||||
|
if unsafe { binding::ioctl(fd, binding::BIOCIMMEDIATE, &1) } == -1 { |
||||||
|
let err = io::Error::last_os_error(); |
||||||
|
unsafe { |
||||||
|
libc::close(fd); |
||||||
|
} |
||||||
|
return Err(err); |
||||||
|
} |
||||||
|
|
||||||
|
let mut dlt: libc::c_uint = 0; |
||||||
|
if unsafe { binding::ioctl(fd, binding::BIOCGDLT, &mut dlt) } == -1 { |
||||||
|
let err = io::Error::last_os_error(); |
||||||
|
unsafe { |
||||||
|
libc::close(fd); |
||||||
|
} |
||||||
|
return Err(err); |
||||||
|
} |
||||||
|
|
||||||
|
let mut loopback = false; |
||||||
|
let mut allocated_read_buffer_size = config.read_buffer_size; |
||||||
|
|
||||||
|
if dlt == binding::DLT_NULL { |
||||||
|
loopback = true; |
||||||
|
|
||||||
|
allocated_read_buffer_size += ETHERNET_HEADER_SIZE; |
||||||
|
|
||||||
|
if let Err(e) = set_feedback(fd) { |
||||||
|
return Err(e); |
||||||
|
} |
||||||
|
} else { |
||||||
|
if unsafe { binding::ioctl(fd, binding::BIOCSHDRCMPLT, &1) } == -1 { |
||||||
|
let err = io::Error::last_os_error(); |
||||||
|
unsafe { |
||||||
|
libc::close(fd); |
||||||
|
} |
||||||
|
return Err(err); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if unsafe { libc::fcntl(fd, libc::F_SETFL, libc::O_NONBLOCK) } == -1 { |
||||||
|
let err = io::Error::last_os_error(); |
||||||
|
unsafe { |
||||||
|
close(fd); |
||||||
|
} |
||||||
|
return Err(err); |
||||||
|
} |
||||||
|
|
||||||
|
let fd = Arc::new(FileDesc { fd: fd }); |
||||||
|
let mut sender = Box::new(DataLinkSenderImpl { |
||||||
|
fd: fd.clone(), |
||||||
|
fd_set: unsafe { mem::zeroed() }, |
||||||
|
write_buffer: vec![0; config.write_buffer_size], |
||||||
|
loopback: loopback, |
||||||
|
timeout: config |
||||||
|
.write_timeout |
||||||
|
.map(|to| duration_to_timespec(to)), |
||||||
|
}); |
||||||
|
unsafe { |
||||||
|
libc::FD_ZERO(&mut sender.fd_set as *mut libc::fd_set); |
||||||
|
libc::FD_SET(fd.fd, &mut sender.fd_set as *mut libc::fd_set); |
||||||
|
} |
||||||
|
let mut receiver = Box::new(DataLinkReceiverImpl { |
||||||
|
fd: fd.clone(), |
||||||
|
fd_set: unsafe { mem::zeroed() }, |
||||||
|
read_buffer: vec![0; allocated_read_buffer_size], |
||||||
|
loopback: loopback, |
||||||
|
timeout: config |
||||||
|
.read_timeout |
||||||
|
.map(|to| duration_to_timespec(to)), |
||||||
|
packets: VecDeque::with_capacity(allocated_read_buffer_size / 64), |
||||||
|
}); |
||||||
|
unsafe { |
||||||
|
libc::FD_ZERO(&mut receiver.fd_set as *mut libc::fd_set); |
||||||
|
libc::FD_SET(fd.fd, &mut receiver.fd_set as *mut libc::fd_set); |
||||||
|
} |
||||||
|
Ok(crate::socket::Channel::Ethernet(sender, receiver)) |
||||||
|
} |
||||||
|
|
||||||
|
struct DataLinkSenderImpl { |
||||||
|
fd: Arc<FileDesc>, |
||||||
|
fd_set: libc::fd_set, |
||||||
|
write_buffer: Vec<u8>, |
||||||
|
loopback: bool, |
||||||
|
timeout: Option<libc::timespec>, |
||||||
|
} |
||||||
|
|
||||||
|
impl DataLinkSender for DataLinkSenderImpl { |
||||||
|
#[inline] |
||||||
|
fn build_and_send( |
||||||
|
&mut self, |
||||||
|
num_packets: usize, |
||||||
|
packet_size: usize, |
||||||
|
func: &mut dyn FnMut(&mut [u8]), |
||||||
|
) -> Option<io::Result<()>> { |
||||||
|
let len = num_packets * packet_size; |
||||||
|
if len >= self.write_buffer.len() { |
||||||
|
None |
||||||
|
} else { |
||||||
|
|
||||||
|
let offset = if self.loopback { |
||||||
|
ETHERNET_HEADER_SIZE |
||||||
|
} else { |
||||||
|
0 |
||||||
|
}; |
||||||
|
for chunk in self.write_buffer[..len].chunks_mut(packet_size) { |
||||||
|
func(chunk); |
||||||
|
let ret = unsafe { |
||||||
|
libc::FD_SET(self.fd.fd, &mut self.fd_set as *mut libc::fd_set); |
||||||
|
libc::pselect( |
||||||
|
self.fd.fd + 1, |
||||||
|
ptr::null_mut(), |
||||||
|
&mut self.fd_set as *mut libc::fd_set, |
||||||
|
ptr::null_mut(), |
||||||
|
self.timeout |
||||||
|
.as_ref() |
||||||
|
.map(|to| to as *const libc::timespec) |
||||||
|
.unwrap_or(ptr::null()), |
||||||
|
ptr::null(), |
||||||
|
) |
||||||
|
}; |
||||||
|
if ret == -1 { |
||||||
|
return Some(Err(io::Error::last_os_error())); |
||||||
|
} else if ret == 0 { |
||||||
|
return Some(Err(io::Error::new(io::ErrorKind::TimedOut, "Timed out"))); |
||||||
|
} else { |
||||||
|
match unsafe { |
||||||
|
libc::write( |
||||||
|
self.fd.fd, |
||||||
|
chunk.as_ptr().offset(offset as isize) as *const libc::c_void, |
||||||
|
(chunk.len() - offset) as libc::size_t, |
||||||
|
) |
||||||
|
} { |
||||||
|
len if len == -1 => return Some(Err(io::Error::last_os_error())), |
||||||
|
_ => (), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
Some(Ok(())) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[inline] |
||||||
|
fn send_to(&mut self, packet: &[u8], _dst: Option<Interface>) -> Option<io::Result<()>> { |
||||||
|
let offset = if self.loopback { |
||||||
|
ETHERNET_HEADER_SIZE |
||||||
|
} else { |
||||||
|
0 |
||||||
|
}; |
||||||
|
let ret = unsafe { |
||||||
|
libc::FD_SET(self.fd.fd, &mut self.fd_set as *mut libc::fd_set); |
||||||
|
libc::pselect( |
||||||
|
self.fd.fd + 1, |
||||||
|
ptr::null_mut(), |
||||||
|
&mut self.fd_set as *mut libc::fd_set, |
||||||
|
ptr::null_mut(), |
||||||
|
self.timeout |
||||||
|
.as_ref() |
||||||
|
.map(|to| to as *const libc::timespec) |
||||||
|
.unwrap_or(ptr::null()), |
||||||
|
ptr::null(), |
||||||
|
) |
||||||
|
}; |
||||||
|
if ret == -1 { |
||||||
|
return Some(Err(io::Error::last_os_error())); |
||||||
|
} else if ret == 0 { |
||||||
|
return Some(Err(io::Error::new(io::ErrorKind::TimedOut, "Timed out"))); |
||||||
|
} else { |
||||||
|
match unsafe { |
||||||
|
libc::write( |
||||||
|
self.fd.fd, |
||||||
|
packet.as_ptr().offset(offset as isize) as *const libc::c_void, |
||||||
|
(packet.len() - offset) as libc::size_t, |
||||||
|
) |
||||||
|
} { |
||||||
|
len if len == -1 => Some(Err(io::Error::last_os_error())), |
||||||
|
_ => Some(Ok(())), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
struct DataLinkReceiverImpl { |
||||||
|
fd: Arc<FileDesc>, |
||||||
|
fd_set: libc::fd_set, |
||||||
|
read_buffer: Vec<u8>, |
||||||
|
loopback: bool, |
||||||
|
timeout: Option<libc::timespec>, |
||||||
|
packets: VecDeque<(usize, usize)>, |
||||||
|
} |
||||||
|
|
||||||
|
impl DataLinkReceiver for DataLinkReceiverImpl { |
||||||
|
fn next(&mut self) -> io::Result<&[u8]> { |
||||||
|
let (header_size, buffer_offset) = if self.loopback { |
||||||
|
(4, ETHERNET_HEADER_SIZE) |
||||||
|
} else { |
||||||
|
(0, 0) |
||||||
|
}; |
||||||
|
if self.packets.is_empty() { |
||||||
|
let buffer = &mut self.read_buffer[buffer_offset..]; |
||||||
|
let ret = unsafe { |
||||||
|
libc::FD_SET(self.fd.fd, &mut self.fd_set as *mut libc::fd_set); |
||||||
|
libc::pselect( |
||||||
|
self.fd.fd + 1, |
||||||
|
&mut self.fd_set as *mut libc::fd_set, |
||||||
|
ptr::null_mut(), |
||||||
|
ptr::null_mut(), |
||||||
|
self.timeout |
||||||
|
.as_ref() |
||||||
|
.map(|to| to as *const libc::timespec) |
||||||
|
.unwrap_or(ptr::null()), |
||||||
|
ptr::null(), |
||||||
|
) |
||||||
|
}; |
||||||
|
if ret == -1 { |
||||||
|
return Err(io::Error::last_os_error()); |
||||||
|
} else if ret == 0 { |
||||||
|
return Err(io::Error::new(io::ErrorKind::TimedOut, "Timed out")); |
||||||
|
} else { |
||||||
|
let buflen = match unsafe { |
||||||
|
libc::read( |
||||||
|
self.fd.fd, |
||||||
|
buffer.as_ptr() as *mut libc::c_void, |
||||||
|
buffer.len() as libc::size_t, |
||||||
|
) |
||||||
|
} { |
||||||
|
len if len > 0 => len, |
||||||
|
_ => return Err(io::Error::last_os_error()), |
||||||
|
}; |
||||||
|
let mut ptr = buffer.as_mut_ptr(); |
||||||
|
let end = unsafe { buffer.as_ptr().offset(buflen as isize) }; |
||||||
|
while (ptr as *const u8) < end { |
||||||
|
unsafe { |
||||||
|
let packet: *const binding::bpf_hdr = mem::transmute(ptr); |
||||||
|
let start = |
||||||
|
ptr as isize + (*packet).bh_hdrlen as isize - buffer.as_ptr() as isize; |
||||||
|
self.packets.push_back(( |
||||||
|
start as usize + header_size, |
||||||
|
(*packet).bh_caplen as usize - header_size, |
||||||
|
)); |
||||||
|
let offset = (*packet).bh_hdrlen as isize + (*packet).bh_caplen as isize; |
||||||
|
ptr = ptr.offset(binding::BPF_WORDALIGN(offset)); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
let (start, mut len) = self.packets.pop_front().unwrap(); |
||||||
|
len += buffer_offset; |
||||||
|
for i in (&mut self.read_buffer[start..start + buffer_offset]).iter_mut() { |
||||||
|
*i = 0; |
||||||
|
} |
||||||
|
Ok(&self.read_buffer[start..start + len]) |
||||||
|
} |
||||||
} |
} |
||||||
|
@ -1,107 +0,0 @@ |
|||||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; |
|
||||||
use crate::gateway::{Gateway}; |
|
||||||
use crate::os; |
|
||||||
|
|
||||||
/// Structure of MAC address
|
|
||||||
#[derive(Clone, Debug)] |
|
||||||
pub struct MacAddr(u8, u8, u8, u8, u8, u8); |
|
||||||
|
|
||||||
impl MacAddr { |
|
||||||
/// Construct a new MacAddr struct from the given octets
|
|
||||||
pub fn new(octets: [u8; 6]) -> MacAddr { |
|
||||||
MacAddr(octets[0], octets[1], octets[2], octets[3], octets[4], octets[5]) |
|
||||||
} |
|
||||||
/// Returns an array of MAC address octets
|
|
||||||
pub fn octets(&self) -> [u8; 6] { |
|
||||||
[self.0,self.1,self.2,self.3,self.4,self.5] |
|
||||||
} |
|
||||||
/// Return a formatted string of MAC address
|
|
||||||
pub fn address(&self) -> String { |
|
||||||
format!("{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", self.0,self.1,self.2,self.3,self.4,self.5) |
|
||||||
} |
|
||||||
pub fn zero() -> MacAddr { |
|
||||||
MacAddr(0,0,0,0,0,0) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
impl std::fmt::Display for MacAddr { |
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { |
|
||||||
let _ = write!(f,"{:<02x}:{:<02x}:{:<02x}:{:<02x}:{:<02x}:{:<02x}",self.0,self.1,self.2,self.3,self.4,self.5); |
|
||||||
Ok(())
|
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// Structure of Network Interface information
|
|
||||||
#[derive(Clone, Debug)] |
|
||||||
pub struct Interface { |
|
||||||
pub index: u32, |
|
||||||
pub name: String, |
|
||||||
pub description: Option<String>, |
|
||||||
pub mac_addr: Option<MacAddr>, |
|
||||||
pub ipv4: Vec<Ipv4Addr>, |
|
||||||
pub ipv6: Vec<Ipv6Addr>, |
|
||||||
pub gateway: Option<Gateway>, |
|
||||||
} |
|
||||||
|
|
||||||
/// Get default Network Interface
|
|
||||||
pub fn get_default_interface() -> Result<Interface, String> { |
|
||||||
let local_ip: IpAddr = match os::get_local_ipaddr(){ |
|
||||||
Some(local_ip) => local_ip, |
|
||||||
None => return Err(String::from("Local IP address not found")), |
|
||||||
}; |
|
||||||
let interfaces: Vec<Interface> = os::interfaces(); |
|
||||||
for iface in interfaces { |
|
||||||
match local_ip { |
|
||||||
IpAddr::V4(local_ipv4) => { |
|
||||||
if iface.ipv4.contains(&local_ipv4) { |
|
||||||
return Ok(iface); |
|
||||||
} |
|
||||||
}, |
|
||||||
IpAddr::V6(local_ipv6) => { |
|
||||||
if iface.ipv6.contains(&local_ipv6) { |
|
||||||
return Ok(iface); |
|
||||||
} |
|
||||||
}, |
|
||||||
} |
|
||||||
} |
|
||||||
Err(String::from("Default Interface not found")) |
|
||||||
} |
|
||||||
|
|
||||||
/// Get default Network Interface index
|
|
||||||
pub fn get_default_interface_index() -> Option<u32> { |
|
||||||
os::default_interface_index() |
|
||||||
} |
|
||||||
|
|
||||||
/// Get default Network Interface name
|
|
||||||
pub fn get_default_interface_name() -> Option<String> { |
|
||||||
os::default_interface_name() |
|
||||||
} |
|
||||||
|
|
||||||
/// Get a list of available Network Interfaces
|
|
||||||
pub fn get_interfaces() -> Vec<Interface> { |
|
||||||
os::interfaces() |
|
||||||
} |
|
||||||
|
|
||||||
#[cfg(test)] |
|
||||||
mod tests { |
|
||||||
use super::*; |
|
||||||
#[test] |
|
||||||
fn test_interfaces() { |
|
||||||
let interfaces = get_interfaces(); |
|
||||||
for interface in interfaces { |
|
||||||
println!("{:#?}", interface); |
|
||||||
} |
|
||||||
} |
|
||||||
#[test] |
|
||||||
fn test_default_interface() { |
|
||||||
println!("{:#?}", get_default_interface()); |
|
||||||
} |
|
||||||
#[test] |
|
||||||
fn test_default_interface_index() { |
|
||||||
println!("{:?}", get_default_interface_index()); |
|
||||||
} |
|
||||||
#[test] |
|
||||||
fn test_default_interface_name() { |
|
||||||
println!("{:?}", get_default_interface_name()); |
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,77 @@ |
|||||||
|
pub mod packet; |
||||||
|
|
||||||
|
#[cfg(not(target_os="windows"))] |
||||||
|
mod unix; |
||||||
|
#[cfg(not(target_os="windows"))] |
||||||
|
pub use self::unix::*; |
||||||
|
|
||||||
|
#[cfg(test)] |
||||||
|
mod tests { |
||||||
|
use super::*; |
||||||
|
use std::net::UdpSocket; |
||||||
|
#[test] |
||||||
|
fn test_packet_capture() { |
||||||
|
let interface_name: String = String::from("en0"); |
||||||
|
let config = Config { |
||||||
|
write_buffer_size: 4096, |
||||||
|
read_buffer_size: 4096, |
||||||
|
read_timeout: None, |
||||||
|
write_timeout: None, |
||||||
|
channel_type: ChannelType::Layer2, |
||||||
|
bpf_fd_attempts: 1000, |
||||||
|
linux_fanout: None, |
||||||
|
promiscuous: false, |
||||||
|
}; |
||||||
|
let (mut _tx, mut rx) = match channel(interface_name, config) { |
||||||
|
Ok(Channel::Ethernet(tx, rx)) => (tx, rx), |
||||||
|
//Ok(_) => panic!("Unknown channel type"),
|
||||||
|
Err(e) => panic!("Error happened {}", e), |
||||||
|
}; |
||||||
|
|
||||||
|
let buf = [0u8; 0]; |
||||||
|
let socket = match UdpSocket::bind("0.0.0.0:0") { |
||||||
|
Ok(s) => s, |
||||||
|
Err(e) => { |
||||||
|
println!("Failed to create UDP socket {}", e); |
||||||
|
return; |
||||||
|
}, |
||||||
|
}; |
||||||
|
let dst: &str = "1.1.1.1:80"; |
||||||
|
match socket.set_ttl(1) { |
||||||
|
Ok(_) => (), |
||||||
|
Err(e) => { |
||||||
|
println!("Failed to set TTL {}", e); |
||||||
|
return; |
||||||
|
}, |
||||||
|
} |
||||||
|
match socket.send_to(&buf, dst) { |
||||||
|
Ok(_) => (), |
||||||
|
Err(e) => { |
||||||
|
println!("Failed to send data {}", e); |
||||||
|
return; |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
loop { |
||||||
|
match rx.next() { |
||||||
|
Ok(frame) => { |
||||||
|
match packet::parse_frame(frame){ |
||||||
|
Ok(gateway) => { |
||||||
|
println!("Default Gateway:"); |
||||||
|
println!("{}", gateway.mac_addr); |
||||||
|
println!("{}", gateway.ip_addr); |
||||||
|
return; |
||||||
|
}, |
||||||
|
Err(_) => { |
||||||
|
println!("Parse Error"); |
||||||
|
}, |
||||||
|
} |
||||||
|
}, |
||||||
|
Err(e) => { |
||||||
|
println!("{}", e); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
@ -0,0 +1,115 @@ |
|||||||
|
use std::convert::TryInto; |
||||||
|
use crate::gateway::Gateway; |
||||||
|
use crate::interface::MacAddr; |
||||||
|
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; |
||||||
|
use std::u16; |
||||||
|
|
||||||
|
pub const ETHER_TYPE_IPV4: [u8; 2] = [8, 0]; |
||||||
|
pub const ETHER_TYPE_IPV6: [u8; 2] = [134, 221]; |
||||||
|
pub const NEXT_HEADER_ICMP: u8 = 1; |
||||||
|
pub const NEXT_HEADER_ICMPV6: u8 = 58; |
||||||
|
pub const ICMP_TYPE_TIME_EXCEEDED: u8 = 11; |
||||||
|
pub const ICMPV6_TYPE_TIME_EXCEEDED: u8 = 3; |
||||||
|
|
||||||
|
pub enum Frame { |
||||||
|
SrcMacAddr, |
||||||
|
DstMacAddr, |
||||||
|
EtherType, |
||||||
|
SrcIpv4Addr, |
||||||
|
DstIpv4Addr, |
||||||
|
SrcIpv6Addr, |
||||||
|
DstIpv6Addr, |
||||||
|
NextHeaderProtocolIpv4, |
||||||
|
NextHeaderProtocolIpv6, |
||||||
|
IcmpType, |
||||||
|
Icmpv6Type, |
||||||
|
} |
||||||
|
|
||||||
|
impl Frame { |
||||||
|
fn start_index(&self) -> usize { |
||||||
|
match *self { |
||||||
|
Frame::SrcMacAddr => 6, |
||||||
|
Frame::DstMacAddr => 0, |
||||||
|
Frame::EtherType => 12, |
||||||
|
Frame::SrcIpv4Addr => 26, |
||||||
|
Frame::DstIpv4Addr => 30, |
||||||
|
Frame::SrcIpv6Addr => 22, |
||||||
|
Frame::DstIpv6Addr => 38, |
||||||
|
Frame::NextHeaderProtocolIpv4 => 23, |
||||||
|
Frame::NextHeaderProtocolIpv6 => 20, |
||||||
|
Frame::IcmpType => 34, |
||||||
|
Frame::Icmpv6Type => 54, |
||||||
|
} |
||||||
|
} |
||||||
|
fn end_index(&self) -> usize { |
||||||
|
match *self { |
||||||
|
Frame::SrcMacAddr => 12, |
||||||
|
Frame::DstMacAddr => 6, |
||||||
|
Frame::EtherType => 14, |
||||||
|
Frame::SrcIpv4Addr => 30, |
||||||
|
Frame::DstIpv4Addr => 34, |
||||||
|
Frame::SrcIpv6Addr => 38, |
||||||
|
Frame::DstIpv6Addr => 54, |
||||||
|
Frame::NextHeaderProtocolIpv4 => 24, |
||||||
|
Frame::NextHeaderProtocolIpv6 => 21, |
||||||
|
Frame::IcmpType => 35, |
||||||
|
Frame::Icmpv6Type => 55, |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fn convert_ipv4_bytes(bytes: [u8; 4]) -> Ipv4Addr { |
||||||
|
Ipv4Addr::new(bytes[0], bytes[1], bytes[2], bytes[3]) |
||||||
|
} |
||||||
|
|
||||||
|
fn convert_ipv6_bytes(bytes: [u8; 16]) -> Ipv6Addr { |
||||||
|
let h1: u16 = ((bytes[0] as u16) << 8) | bytes[1] as u16; |
||||||
|
let h2: u16 = ((bytes[2] as u16) << 8) | bytes[3] as u16; |
||||||
|
let h3: u16 = ((bytes[4] as u16) << 8) | bytes[5] as u16; |
||||||
|
let h4: u16 = ((bytes[6] as u16) << 8) | bytes[7] as u16; |
||||||
|
let h5: u16 = ((bytes[8] as u16) << 8) | bytes[9] as u16; |
||||||
|
let h6: u16 = ((bytes[10] as u16) << 8) | bytes[11] as u16; |
||||||
|
let h7: u16 = ((bytes[12] as u16) << 8) | bytes[13] as u16; |
||||||
|
let h8: u16 = ((bytes[14] as u16) << 8) | bytes[15] as u16; |
||||||
|
Ipv6Addr::new(h1, h2, h3, h4, h5, h6, h7, h8) |
||||||
|
} |
||||||
|
|
||||||
|
pub fn parse_frame(frame: &[u8]) -> Result<Gateway, ()> { |
||||||
|
let src_mac: [u8; 6] = frame[Frame::SrcMacAddr.start_index()..Frame::SrcMacAddr.end_index()].try_into().unwrap(); |
||||||
|
let ether_type: [u8; 2] = frame[Frame::EtherType.start_index()..Frame::EtherType.end_index()].try_into().unwrap(); |
||||||
|
match ether_type { |
||||||
|
ETHER_TYPE_IPV4 => { |
||||||
|
let src_ip: [u8; 4] = frame[Frame::SrcIpv4Addr.start_index()..Frame::SrcIpv4Addr.end_index()].try_into().unwrap(); |
||||||
|
let next_header_protocol: u8 = frame[Frame::NextHeaderProtocolIpv4.start_index()]; |
||||||
|
if next_header_protocol == NEXT_HEADER_ICMP { |
||||||
|
let icmp_type: u8 = frame[Frame::IcmpType.start_index()]; |
||||||
|
if icmp_type == ICMP_TYPE_TIME_EXCEEDED { |
||||||
|
let gateway = Gateway { |
||||||
|
mac_addr: MacAddr::new(src_mac), |
||||||
|
ip_addr: IpAddr::V4(convert_ipv4_bytes(src_ip)), |
||||||
|
}; |
||||||
|
return Ok(gateway); |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
ETHER_TYPE_IPV6 => { |
||||||
|
let src_ip: [u8; 16] = frame[Frame::SrcIpv6Addr.start_index()..Frame::SrcIpv6Addr.end_index()].try_into().unwrap(); |
||||||
|
let next_header_protocol: u8 = frame[Frame::NextHeaderProtocolIpv6.start_index()]; |
||||||
|
if next_header_protocol == NEXT_HEADER_ICMPV6 { |
||||||
|
let icmp_type: u8 = frame[Frame::Icmpv6Type.start_index()]; |
||||||
|
if icmp_type == ICMPV6_TYPE_TIME_EXCEEDED { |
||||||
|
let icmp_type: u8 = frame[Frame::Icmpv6Type.start_index()]; |
||||||
|
if icmp_type == ICMPV6_TYPE_TIME_EXCEEDED { |
||||||
|
let gateway = Gateway { |
||||||
|
mac_addr: MacAddr::new(src_mac), |
||||||
|
ip_addr: IpAddr::V6(convert_ipv6_bytes(src_ip)), |
||||||
|
}; |
||||||
|
return Ok(gateway); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
_ => {}, |
||||||
|
} |
||||||
|
Err(()) |
||||||
|
} |
@ -0,0 +1,85 @@ |
|||||||
|
use std::io; |
||||||
|
use std::time::Duration; |
||||||
|
|
||||||
|
use crate::bpf; |
||||||
|
use crate::interface::Interface; |
||||||
|
|
||||||
|
pub type EtherType = u16; |
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] |
||||||
|
pub enum ChannelType { |
||||||
|
Layer2, |
||||||
|
Layer3(EtherType), |
||||||
|
} |
||||||
|
|
||||||
|
#[non_exhaustive] |
||||||
|
pub enum Channel { |
||||||
|
Ethernet(Box<dyn DataLinkSender>, Box<dyn DataLinkReceiver>), |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] |
||||||
|
pub enum FanoutType { |
||||||
|
HASH, |
||||||
|
LB, |
||||||
|
CPU, |
||||||
|
ROLLOVER, |
||||||
|
RND, |
||||||
|
QM, |
||||||
|
CBPF, |
||||||
|
EBPF, |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] |
||||||
|
pub struct FanoutOption { |
||||||
|
pub group_id: u16, |
||||||
|
pub fanout_type: FanoutType, |
||||||
|
pub defrag: bool, |
||||||
|
pub rollover: bool, |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] |
||||||
|
pub struct Config { |
||||||
|
pub write_buffer_size: usize, |
||||||
|
pub read_buffer_size: usize, |
||||||
|
pub read_timeout: Option<Duration>, |
||||||
|
pub write_timeout: Option<Duration>, |
||||||
|
pub channel_type: ChannelType, |
||||||
|
pub bpf_fd_attempts: usize, |
||||||
|
pub linux_fanout: Option<FanoutOption>, |
||||||
|
pub promiscuous: bool, |
||||||
|
} |
||||||
|
|
||||||
|
impl Default for Config { |
||||||
|
fn default() -> Config { |
||||||
|
Config { |
||||||
|
write_buffer_size: 4096, |
||||||
|
read_buffer_size: 4096, |
||||||
|
read_timeout: None, |
||||||
|
write_timeout: None, |
||||||
|
channel_type: ChannelType::Layer2, |
||||||
|
bpf_fd_attempts: 1000, |
||||||
|
linux_fanout: None, |
||||||
|
promiscuous: true, |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[inline] |
||||||
|
pub fn channel(interface_name: String, configuration: Config) -> io::Result<Channel> { |
||||||
|
bpf::channel(interface_name, (&configuration).into()) |
||||||
|
} |
||||||
|
|
||||||
|
pub trait DataLinkSender: Send { |
||||||
|
fn build_and_send( |
||||||
|
&mut self, |
||||||
|
num_packets: usize, |
||||||
|
packet_size: usize, |
||||||
|
func: &mut dyn FnMut(&mut [u8]), |
||||||
|
) -> Option<io::Result<()>>; |
||||||
|
|
||||||
|
fn send_to(&mut self, packet: &[u8], dst: Option<Interface>) -> Option<io::Result<()>>; |
||||||
|
} |
||||||
|
|
||||||
|
pub trait DataLinkReceiver: Send { |
||||||
|
fn next(&mut self) -> io::Result<&[u8]>; |
||||||
|
} |
Loading…
Reference in new issue