From 3afc51941434ee6c37a691b0d19ccab38d49bacc Mon Sep 17 00:00:00 2001 From: shellrow <81893184+shellrow@users.noreply.github.com> Date: Sat, 15 Jan 2022 16:51:30 +0900 Subject: [PATCH] Add modules --- Cargo.toml | 1 + src/bpf/mod.rs | 4 + src/bpf/unix.rs | 17 +++ src/interface/mod.rs | 110 +++++++++++++++++++ src/interface/unix.rs | 163 ++++++++++++++++++++++++++++ src/interface/windows.rs | 0 src/{interface.rs => interface1.rs} | 0 src/lib.rs | 2 + src/sys/mod.rs | 4 + src/sys/unix.rs | 61 +++++++++++ src/sys/windows.rs | 0 11 files changed, 362 insertions(+) create mode 100644 src/bpf/mod.rs create mode 100644 src/bpf/unix.rs create mode 100644 src/interface/mod.rs create mode 100644 src/interface/unix.rs create mode 100644 src/interface/windows.rs rename src/{interface.rs => interface1.rs} (100%) create mode 100644 src/sys/mod.rs create mode 100644 src/sys/unix.rs create mode 100644 src/sys/windows.rs diff --git a/Cargo.toml b/Cargo.toml index cc58ee8..03b7b95 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ license = "MIT" [target.'cfg(not(windows))'.dependencies] pnet_packet = "0.28" pnet_datalink = "0.28" +libc = "0.2" [target.'cfg(windows)'.dependencies.windows] version = "0.29.0" diff --git a/src/bpf/mod.rs b/src/bpf/mod.rs new file mode 100644 index 0000000..1aa60bf --- /dev/null +++ b/src/bpf/mod.rs @@ -0,0 +1,4 @@ +#[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 new file mode 100644 index 0000000..a2809a8 --- /dev/null +++ b/src/bpf/unix.rs @@ -0,0 +1,17 @@ +#![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], +} diff --git a/src/interface/mod.rs b/src/interface/mod.rs new file mode 100644 index 0000000..f53a144 --- /dev/null +++ b/src/interface/mod.rs @@ -0,0 +1,110 @@ +#[cfg(not(target_os="windows"))] +mod unix; + +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/interface/unix.rs b/src/interface/unix.rs new file mode 100644 index 0000000..5cfc817 --- /dev/null +++ b/src/interface/unix.rs @@ -0,0 +1,163 @@ +use super::Interface; +use super::MacAddr; +use crate::sys; + +use libc; +use std::ffi::{CStr, CString}; +use std::mem::{self, MaybeUninit}; +use std::os::raw::c_char; +use std::str::from_utf8_unchecked; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; + +pub fn interfaces() -> Vec { + unix_interfaces() +} + +pub fn unix_interfaces() -> Vec { + let mut ifaces: Vec = vec![]; + let mut addrs: MaybeUninit<*mut libc::ifaddrs> = MaybeUninit::uninit(); + if unsafe { libc::getifaddrs(addrs.as_mut_ptr()) } != 0 { + return ifaces; + } + let addrs = unsafe { addrs.assume_init() }; + let mut addr = addrs; + while !addr.is_null() { + let addr_ref: &libc::ifaddrs = unsafe {&*addr}; + let c_str = addr_ref.ifa_name as *const c_char; + let bytes = unsafe { CStr::from_ptr(c_str).to_bytes() }; + let name = unsafe {from_utf8_unchecked(bytes).to_owned() }; + let (mac, ip) = sockaddr_to_network_addr(addr_ref.ifa_addr as *const libc::sockaddr); + let mut ini_ipv4: Vec = vec![]; + let mut ini_ipv6: Vec = vec![]; + if let Some(ip) = ip { + match ip { + IpAddr::V4(ipv4) => { + ini_ipv4.push(ipv4); + }, + IpAddr::V6(ipv6) => { + ini_ipv6.push(ipv6); + }, + } + } + let interface: Interface = Interface{ + index: 0, + name: name.clone(), + description: None, + mac_addr: mac.clone(), + ipv4: ini_ipv4, + ipv6: ini_ipv6, + gateway: None, + }; + let mut found: bool = false; + for iface in &mut ifaces { + if name == iface.name { + if let Some(mac) = mac.clone() { + iface.mac_addr = Some(mac); + } + if let Some(ip) = ip { + match ip { + IpAddr::V4(ipv4) => { + iface.ipv4.push(ipv4); + }, + IpAddr::V6(ipv6) => { + iface.ipv6.push(ipv6); + }, + } + } + found = true; + } + } + if !found { + ifaces.push(interface); + } + addr = addr_ref.ifa_next; + } + unsafe{ libc::freeifaddrs(addrs); } + for iface in &mut ifaces { + let name = CString::new(iface.name.as_bytes()).unwrap(); + unsafe { iface.index = libc::if_nametoindex(name.as_ptr()); } + } + ifaces +} + +#[cfg(any(target_os = "linux", target_os = "android"))] +fn sockaddr_to_network_addr(sa: *const libc::sockaddr) -> (Option, Option) { + use std::net::SocketAddr; + + unsafe { + if sa.is_null() { + (None, None) + } else if (*sa).sa_family as libc::c_int == libc::AF_PACKET { + let sll: *const libc::sockaddr_ll = mem::transmute(sa); + let mac = MacAddr( + (*sll).sll_addr[0], + (*sll).sll_addr[1], + (*sll).sll_addr[2], + (*sll).sll_addr[3], + (*sll).sll_addr[4], + (*sll).sll_addr[5], + ); + + (Some(mac), None) + } else { + let addr = sys::sockaddr_to_addr( + mem::transmute(sa), + mem::size_of::(), + ); + + match addr { + Ok(SocketAddr::V4(sa)) => (None, Some(IpAddr::V4(*sa.ip()))), + Ok(SocketAddr::V6(sa)) => (None, Some(IpAddr::V6(*sa.ip()))), + Err(_) => (None, None), + } + } + } +} + +#[cfg(any(target_os = "openbsd", target_os = "freebsd", target_os = "netbsd", target_os = "macos", target_os = "ios"))] +fn sockaddr_to_network_addr(sa: *const libc::sockaddr) -> (Option, Option) { + use crate::bpf; + use std::net::SocketAddr; + + unsafe { + if sa.is_null() { + (None, None) + } else if (*sa).sa_family as libc::c_int == bpf::AF_LINK { + let sdl: *const bpf::sockaddr_dl = mem::transmute(sa); + let nlen = (*sdl).sdl_nlen as usize; + let mac = MacAddr( + (*sdl).sdl_data[nlen] as u8, + (*sdl).sdl_data[nlen + 1] as u8, + (*sdl).sdl_data[nlen + 2] as u8, + (*sdl).sdl_data[nlen + 3] as u8, + (*sdl).sdl_data[nlen + 4] as u8, + (*sdl).sdl_data[nlen + 5] as u8, + ); + + (Some(mac), None) + } else { + let addr = sys::sockaddr_to_addr( + mem::transmute(sa), + mem::size_of::(), + ); + + match addr { + Ok(SocketAddr::V4(sa)) => (None, Some(IpAddr::V4(*sa.ip()))), + Ok(SocketAddr::V6(sa)) => (None, Some(IpAddr::V6(*sa.ip()))), + Err(_) => (None, None), + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_unix_interfaces() { + let interfaces = interfaces(); + for interface in interfaces { + println!("{:#?}", interface); + } + } +} diff --git a/src/interface/windows.rs b/src/interface/windows.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/interface.rs b/src/interface1.rs similarity index 100% rename from src/interface.rs rename to src/interface1.rs diff --git a/src/lib.rs b/src/lib.rs index cec9340..1edd47c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,6 @@ mod os; +mod sys; +mod bpf; pub mod interface; pub mod gateway; diff --git a/src/sys/mod.rs b/src/sys/mod.rs new file mode 100644 index 0000000..1aa60bf --- /dev/null +++ b/src/sys/mod.rs @@ -0,0 +1,4 @@ +#[cfg(not(target_os="windows"))] +mod unix; +#[cfg(not(target_os="windows"))] +pub use self::unix::*; diff --git a/src/sys/unix.rs b/src/sys/unix.rs new file mode 100644 index 0000000..74021b0 --- /dev/null +++ b/src/sys/unix.rs @@ -0,0 +1,61 @@ +use std::io; +use std::mem; +use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; + +pub type SockAddrIn = libc::sockaddr_in; +pub type SockAddrIn6 = libc::sockaddr_in6; +pub type SockAddrStorage = libc::sockaddr_storage; +pub type InAddr = libc::in_addr; + +pub const AF_INET: libc::c_int = libc::AF_INET; +pub const AF_INET6: libc::c_int = libc::AF_INET6; + +pub use libc::{IFF_BROADCAST, IFF_LOOPBACK, IFF_MULTICAST, IFF_POINTOPOINT, IFF_UP}; + +fn ntohs(u: u16) -> u16 { + u16::from_be(u) +} + +pub fn sockaddr_to_addr(storage: &SockAddrStorage, len: usize) -> io::Result { + match storage.ss_family as libc::c_int { + AF_INET => { + assert!(len as usize >= mem::size_of::()); + let storage: &SockAddrIn = unsafe { mem::transmute(storage) }; + let ip = ipv4_addr_int(storage.sin_addr); + // octets + let o1 = (ip >> 24) as u8; + let o2 = (ip >> 16) as u8; + let o3 = (ip >> 8) as u8; + let o4 = ip as u8; + let sockaddrv4 = SocketAddrV4::new(Ipv4Addr::new(o1, o2, o3, o4), ntohs(storage.sin_port)); + Ok(SocketAddr::V4(sockaddrv4)) + } + AF_INET6 => { + assert!(len as usize >= mem::size_of::()); + let storage: &SockAddrIn6 = unsafe { mem::transmute(storage) }; + let arr: [u16; 8] = unsafe { mem::transmute(storage.sin6_addr.s6_addr) }; + // hextets + let h1 = ntohs(arr[0]); + let h2 = ntohs(arr[1]); + let h3 = ntohs(arr[2]); + let h4 = ntohs(arr[3]); + let h5 = ntohs(arr[4]); + let h6 = ntohs(arr[5]); + let h7 = ntohs(arr[6]); + let h8 = ntohs(arr[7]); + let ip = Ipv6Addr::new(h1, h2, h3, h4, h5, h6, h7, h8); + Ok(SocketAddr::V6(SocketAddrV6::new( + ip, + ntohs(storage.sin6_port), + u32::from_be(storage.sin6_flowinfo), + storage.sin6_scope_id, + ))) + } + _ => Err(io::Error::new(io::ErrorKind::InvalidData, "expected IPv4 or IPv6 socket",)), + } +} + +#[inline(always)] +pub fn ipv4_addr_int(addr: InAddr) -> u32 { + (addr.s_addr as u32).to_be() +} diff --git a/src/sys/windows.rs b/src/sys/windows.rs new file mode 100644 index 0000000..e69de29