From b3172f19e1d72eccece93a77f9e2ac3341000f56 Mon Sep 17 00:00:00 2001 From: shellrow <81893184+shellrow@users.noreply.github.com> Date: Sun, 16 Jan 2022 02:13:15 +0900 Subject: [PATCH] Impl receiver --- src/bpf/binding.rs | 111 ++++++++ src/bpf/mod.rs | 6 + src/bpf/unix.rs | 416 +++++++++++++++++++++++++++-- src/{gateway.rs => gateway/mod.rs} | 11 +- src/gateway/unix.rs | 0 src/interface1.rs | 107 -------- src/lib.rs | 2 + src/socket/mod.rs | 77 ++++++ src/socket/packet.rs | 115 ++++++++ src/socket/unix.rs | 85 ++++++ src/sys/unix.rs | 2 +- 11 files changed, 807 insertions(+), 125 deletions(-) create mode 100644 src/bpf/binding.rs rename src/{gateway.rs => gateway/mod.rs} (86%) create mode 100644 src/gateway/unix.rs delete mode 100644 src/interface1.rs create mode 100644 src/socket/mod.rs create mode 100644 src/socket/packet.rs create mode 100644 src/socket/unix.rs diff --git a/src/bpf/binding.rs b/src/bpf/binding.rs new file mode 100644 index 0000000..afe65ac --- /dev/null +++ b/src/bpf/binding.rs @@ -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; +} diff --git a/src/bpf/mod.rs b/src/bpf/mod.rs index 1aa60bf..29456a1 100644 --- a/src/bpf/mod.rs +++ b/src/bpf/mod.rs @@ -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"))] mod unix; #[cfg(not(target_os="windows"))] pub use self::unix::*; + diff --git a/src/bpf/unix.rs b/src/bpf/unix.rs index a2809a8..80a093e 100644 --- a/src/bpf/unix.rs +++ b/src/bpf/unix.rs @@ -1,17 +1,401 @@ -#![allow(non_camel_case_types)] -#![allow(non_snake_case)] -#![allow(dead_code)] - -pub const AF_LINK: libc::c_int = 18; - -#[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], +use super::binding; +use crate::socket::{DataLinkReceiver, DataLinkSender}; +use crate::interface::Interface; + +use std::collections::VecDeque; +use std::ffi::CString; +use std::io; +use std::mem; +use std::ptr; +use std::sync::Arc; +use std::time::Duration; + +static ETHERNET_HEADER_SIZE: usize = 14; + +pub type CSocket = libc::c_int; +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, + pub write_timeout: Option, + 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 { + #[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, + fd_set: libc::fd_set, + write_buffer: Vec, + loopback: bool, + timeout: Option, +} + +impl DataLinkSender for DataLinkSenderImpl { + #[inline] + fn build_and_send( + &mut self, + num_packets: usize, + packet_size: usize, + func: &mut dyn FnMut(&mut [u8]), + ) -> Option> { + 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) -> Option> { + 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, + fd_set: libc::fd_set, + read_buffer: Vec, + loopback: bool, + timeout: Option, + 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]) + } } diff --git a/src/gateway.rs b/src/gateway/mod.rs similarity index 86% rename from src/gateway.rs rename to src/gateway/mod.rs index 1ff5eb6..77a6f5d 100644 --- a/src/gateway.rs +++ b/src/gateway/mod.rs @@ -1,4 +1,4 @@ -use std::net::IpAddr; +use std::net::{IpAddr, Ipv4Addr}; use crate::interface::{MacAddr, Interface}; use crate::os; @@ -9,6 +9,15 @@ pub struct Gateway { pub ip_addr: IpAddr, } +impl Gateway { + pub fn new() -> Gateway { + Gateway { + mac_addr: MacAddr::zero(), + ip_addr: IpAddr::V4(Ipv4Addr::LOCALHOST), + } + } +} + /// Get default Gateway pub fn get_default_gateway() -> Result { let local_ip: IpAddr = match os::get_local_ipaddr(){ diff --git a/src/gateway/unix.rs b/src/gateway/unix.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/interface1.rs b/src/interface1.rs deleted file mode 100644 index 9725dc3..0000000 --- a/src/interface1.rs +++ /dev/null @@ -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, - pub mac_addr: Option, - pub ipv4: Vec, - pub ipv6: Vec, - pub gateway: Option, -} - -/// Get default Network Interface -pub fn get_default_interface() -> Result { - 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 = 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 { - os::default_interface_index() -} - -/// Get default Network Interface name -pub fn get_default_interface_name() -> Option { - os::default_interface_name() -} - -/// Get a list of available Network Interfaces -pub fn get_interfaces() -> Vec { - 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()); - } -} diff --git a/src/lib.rs b/src/lib.rs index 1edd47c..4cba196 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,8 @@ mod os; mod sys; mod bpf; +mod socket; + pub mod interface; pub mod gateway; diff --git a/src/socket/mod.rs b/src/socket/mod.rs new file mode 100644 index 0000000..180b8ef --- /dev/null +++ b/src/socket/mod.rs @@ -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); + } + } + } + } +} + diff --git a/src/socket/packet.rs b/src/socket/packet.rs new file mode 100644 index 0000000..ee82fda --- /dev/null +++ b/src/socket/packet.rs @@ -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 { + 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(()) +} diff --git a/src/socket/unix.rs b/src/socket/unix.rs new file mode 100644 index 0000000..2b3e69c --- /dev/null +++ b/src/socket/unix.rs @@ -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, Box), +} + +#[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, + pub write_timeout: Option, + pub channel_type: ChannelType, + pub bpf_fd_attempts: usize, + pub linux_fanout: Option, + 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 { + 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>; + + fn send_to(&mut self, packet: &[u8], dst: Option) -> Option>; +} + +pub trait DataLinkReceiver: Send { + fn next(&mut self) -> io::Result<&[u8]>; +} diff --git a/src/sys/unix.rs b/src/sys/unix.rs index 74021b0..f5ab24c 100644 --- a/src/sys/unix.rs +++ b/src/sys/unix.rs @@ -51,7 +51,7 @@ pub fn sockaddr_to_addr(storage: &SockAddrStorage, len: usize) -> io::Result Err(io::Error::new(io::ErrorKind::InvalidData, "expected IPv4 or IPv6 socket",)), + _ => Err(io::Error::new(io::ErrorKind::InvalidData, "Not supported",)), } }