From 8a77d9726eeaff65c788b08c9cf0018e8cfd64e6 Mon Sep 17 00:00:00 2001 From: shellrow <81893184+shellrow@users.noreply.github.com> Date: Tue, 11 Jul 2023 00:42:24 +0900 Subject: [PATCH] Fix macOS Impl routing table fn for macOS --- Cargo.toml | 2 +- src/bpf/binding.rs | 31 +-- src/bpf/mod.rs | 41 +++- src/bpf/shared.rs | 16 ++ src/gateway/macos.rs | 464 ++++++++++++++++++++++++++++++++++++++++++ src/gateway/mod.rs | 19 +- src/interface/unix.rs | 6 +- src/lib.rs | 6 +- src/socket/mod.rs | 24 ++- 9 files changed, 560 insertions(+), 49 deletions(-) create mode 100644 src/bpf/shared.rs create mode 100644 src/gateway/macos.rs diff --git a/Cargo.toml b/Cargo.toml index 94b6418..05e2c5a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,4 +30,4 @@ version = "0.48.0" features = ["Win32_Foundation","Win32_NetworkManagement_IpHelper", "Win32_Networking_WinSock", "Win32_NetworkManagement_Ndis"] [target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] -system-configuration = "0.5.0" +system-configuration = "0.5.1" \ No newline at end of file diff --git a/src/bpf/binding.rs b/src/bpf/binding.rs index 4d7653a..5d1ee88 100644 --- a/src/bpf/binding.rs +++ b/src/bpf/binding.rs @@ -4,7 +4,6 @@ use libc; pub type SockAddr = libc::sockaddr; -pub const AF_LINK: libc::c_int = 18; const IF_NAMESIZE: usize = 16; const IFNAMSIZ: usize = IF_NAMESIZE; @@ -44,7 +43,7 @@ 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))] +#[cfg(any(target_os = "openbsd"))] const BPF_ALIGNMENT: libc::c_int = 4; pub fn BPF_WORDALIGN(x: isize) -> isize { @@ -57,24 +56,6 @@ pub struct ifreq { 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", @@ -97,13 +78,6 @@ pub struct timeval32 { 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, @@ -112,7 +86,6 @@ pub struct bpf_hdr { 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; -} +} \ No newline at end of file diff --git a/src/bpf/mod.rs b/src/bpf/mod.rs index aa6c39e..14bc9e4 100644 --- a/src/bpf/mod.rs +++ b/src/bpf/mod.rs @@ -1,9 +1,42 @@ -#[cfg(not(target_os = "windows"))] +#[cfg(any( + target_os = "openbsd", + target_os = "freebsd", + target_os = "netbsd" +))] mod binding; -#[cfg(not(target_os = "windows"))] +#[cfg(any( + target_os = "openbsd", + target_os = "freebsd", + target_os = "netbsd" +))] pub use self::binding::*; -#[cfg(not(target_os = "windows"))] +#[cfg(any( + target_os = "openbsd", + target_os = "freebsd", + target_os = "netbsd" +))] mod unix; -#[cfg(not(target_os = "windows"))] +#[cfg(any( + target_os = "openbsd", + target_os = "freebsd", + target_os = "netbsd" +))] pub use self::unix::*; + +#[cfg(any( + target_os = "macos", + target_os = "openbsd", + target_os = "freebsd", + target_os = "netbsd", + target_os = "ios" +))] +mod shared; +#[cfg(any( + target_os = "macos", + target_os = "openbsd", + target_os = "freebsd", + target_os = "netbsd", + target_os = "ios" +))] +pub use self::shared::*; \ No newline at end of file diff --git a/src/bpf/shared.rs b/src/bpf/shared.rs new file mode 100644 index 0000000..2c106d3 --- /dev/null +++ b/src/bpf/shared.rs @@ -0,0 +1,16 @@ +#![allow(non_camel_case_types)] + +use libc; + +pub const AF_LINK: libc::c_int = 18; + +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], +} \ No newline at end of file diff --git a/src/gateway/macos.rs b/src/gateway/macos.rs new file mode 100644 index 0000000..45b12c6 --- /dev/null +++ b/src/gateway/macos.rs @@ -0,0 +1,464 @@ +#![allow(non_camel_case_types)] + +use super::Gateway; +use crate::interface::MacAddr; + +use std::{ + io, + net::{IpAddr, Ipv4Addr, Ipv6Addr}, collections::HashMap, +}; + +const CTL_NET: u32 = 4; +const AF_INET: u32 = 2; +const AF_ROUTE: u32 = 17; +const AF_LINK: u32 = 18; +const AF_INET6: u32 = 30; +const PF_ROUTE: u32 = 17; +const NET_RT_DUMP: u32 = 1; +const NET_RT_FLAGS: u32 = 2; +const RTM_VERSION: u32 = 5; +const RTF_LLINFO: u32 = 1024; +const RTF_WASCLONED: u32 = 131072; +const RTAX_DST: u32 = 0; +const RTAX_GATEWAY: u32 = 1; +const RTAX_NETMASK: u32 = 2; + +type __int32_t = ::std::os::raw::c_int; +type __uint8_t = ::std::os::raw::c_uchar; +type __uint16_t = ::std::os::raw::c_ushort; +type __uint32_t = ::std::os::raw::c_uint; +type __darwin_size_t = ::std::os::raw::c_ulong; +type __darwin_pid_t = __int32_t; +type sa_family_t = __uint8_t; +type in_addr_t = __uint32_t; +type in_port_t = __uint16_t; +type u_int = ::std::os::raw::c_uint; +type u_short = ::std::os::raw::c_ushort; +type u_char = ::std::os::raw::c_uchar; +type u_int32_t = ::std::os::raw::c_uint; +type size_t = __darwin_size_t; +type pid_t = __darwin_pid_t; + +extern "C" { + fn sysctl( + arg1: *mut ::std::os::raw::c_int, + arg2: u_int, + arg3: *mut ::std::os::raw::c_void, + arg4: *mut size_t, + arg5: *mut ::std::os::raw::c_void, + arg6: size_t, + ) -> ::std::os::raw::c_int; +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +struct rt_msghdr { + pub rtm_msglen: u_short, + pub rtm_version: u_char, + pub rtm_type: u_char, + pub rtm_index: u_short, + pub rtm_flags: ::std::os::raw::c_int, + pub rtm_addrs: ::std::os::raw::c_int, + pub rtm_pid: pid_t, + pub rtm_seq: ::std::os::raw::c_int, + pub rtm_errno: ::std::os::raw::c_int, + pub rtm_use: ::std::os::raw::c_int, + pub rtm_inits: u_int32_t, + pub rtm_rmx: rt_metrics, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +struct rt_metrics { + pub rmx_locks: u_int32_t, + pub rmx_mtu: u_int32_t, + pub rmx_hopcount: u_int32_t, + pub rmx_expire: i32, + pub rmx_recvpipe: u_int32_t, + pub rmx_sendpipe: u_int32_t, + pub rmx_ssthresh: u_int32_t, + pub rmx_rtt: u_int32_t, + pub rmx_rttvar: u_int32_t, + pub rmx_pksent: u_int32_t, + pub rmx_state: u_int32_t, + pub rmx_filler: [u_int32_t; 3usize], +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +struct sockaddr { + pub sa_len: __uint8_t, + pub sa_family: sa_family_t, + pub sa_data: [::std::os::raw::c_char; 14usize], +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +struct sockaddr_dl { + pub sdl_len: u_char, + pub sdl_family: u_char, + pub sdl_index: u_short, + pub sdl_type: u_char, + pub sdl_nlen: u_char, + pub sdl_alen: u_char, + pub sdl_slen: u_char, + pub sdl_data: [::std::os::raw::c_char; 12usize], +} + +#[repr(C)] +#[derive(Copy, Clone)] +union in6_addr_bind { + pub __u6_addr8: [__uint8_t; 16usize], + pub __u6_addr16: [__uint16_t; 8usize], + pub __u6_addr32: [__uint32_t; 4usize], +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct in_addr { + pub s_addr: in_addr_t, +} + +#[repr(C)] +#[derive(Copy, Clone)] +struct in6_addr { + pub __u6_addr: in6_addr_bind, +} + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +struct sockaddr_in { + pub sin_len: __uint8_t, + pub sin_family: sa_family_t, + pub sin_port: in_port_t, + pub sin_addr: in_addr, + pub sin_zero: [::std::os::raw::c_char; 8usize], +} + +#[repr(C)] +#[derive(Copy, Clone)] +struct sockaddr_in6 { + pub sin6_len: __uint8_t, + pub sin6_family: sa_family_t, + pub sin6_port: in_port_t, + pub sin6_flowinfo: __uint32_t, + pub sin6_addr: in6_addr, + pub sin6_scope_id: __uint32_t, +} + +fn code_to_error(err: i32) -> io::Error { + let kind = match err { + 17 => io::ErrorKind::AlreadyExists, // EEXIST + 3 => io::ErrorKind::NotFound, // ESRCH + 3436 => io::ErrorKind::OutOfMemory, // ENOBUFS + _ => io::ErrorKind::Other, + }; + + io::Error::new(kind, format!("rtm_errno {}", err)) +} + +unsafe fn sa_to_ip(sa: &sockaddr) -> Option { + match sa.sa_family as u32 { + AF_INET=> { + let inet: &sockaddr_in = std::mem::transmute(sa); + let octets: [u8; 4] = inet.sin_addr.s_addr.to_ne_bytes(); + Some(IpAddr::from(octets)) + } + AF_INET6=> { + let inet6: &sockaddr_in6 = std::mem::transmute(sa); + let octets: [u8; 16] = inet6.sin6_addr.__u6_addr.__u6_addr8; + Some(IpAddr::from(octets)) + } + AF_LINK => None, + _ => None, + } +} + +unsafe fn sa_to_link(sa: &sockaddr) -> Option<(Option<[u8; 6]>, u16)> { + match sa.sa_family as u32 { + AF_LINK => { + let sa_dl = sa as *const _ as *const sockaddr_dl; + let ifindex = (*sa_dl).sdl_index; + let mac; + if (*sa_dl).sdl_alen >= 6 { + let i = (*sa_dl).sdl_nlen as usize; + + let a = (*sa_dl).sdl_data[i + 0] as u8; + let b = (*sa_dl).sdl_data[i + 1] as u8; + let c = (*sa_dl).sdl_data[i + 2] as u8; + let d = (*sa_dl).sdl_data[i + 3] as u8; + let e = (*sa_dl).sdl_data[i + 4] as u8; + let f = (*sa_dl).sdl_data[i + 5] as u8; + mac = Some([a, b, c, d, e, f]); + } else { + mac = None; + } + Some((mac, ifindex)) + } + _ => None, + } +} + +fn message_to_route(hdr: &rt_msghdr, msg: *mut u8) -> Option { + let destination; + let mut gateway = None; + let mut ifindex = None; + + if hdr.rtm_addrs & (1 << RTAX_DST) == 0 { + return None; + } + + unsafe { + let dst_sa: &sockaddr = std::mem::transmute((msg as *mut sockaddr).add(RTAX_DST as usize)); + destination = sa_to_ip(dst_sa)?; + } + + let mut prefix = match destination { + IpAddr::V4(_) => 32, + IpAddr::V6(_) => 128, + }; + + if hdr.rtm_addrs & (1 << RTAX_GATEWAY) != 0 { + unsafe { + let gw_sa: &sockaddr = + std::mem::transmute((msg as *mut sockaddr).add(RTAX_GATEWAY as usize)); + + gateway = sa_to_ip(gw_sa); + + if gateway.is_none() { + if let Some((_mac, ifidx)) = sa_to_link(gw_sa) { + ifindex = Some(ifidx as u32); + } + } + } + } + + if hdr.rtm_addrs & (1 << RTAX_NETMASK) != 0 { + unsafe { + match destination { + IpAddr::V4(_) => { + let mask_sa: &sockaddr_in = + std::mem::transmute((msg as *mut sockaddr).add(RTAX_NETMASK as usize)); + let octets: [u8; 4] = mask_sa.sin_addr.s_addr.to_ne_bytes(); + prefix = u32::from_be_bytes(octets).leading_ones() as u8; + } + IpAddr::V6(_) => { + let mask_sa: &sockaddr_in6 = + std::mem::transmute((msg as *mut sockaddr).add(RTAX_NETMASK as usize)); + let octets: [u8; 16] = mask_sa.sin6_addr.__u6_addr.__u6_addr8; + prefix = u128::from_be_bytes(octets).leading_ones() as u8; + } + } + } + } + + Some(Route { + destination, + prefix, + gateway, + ifindex, + }) +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Route { + pub destination: IpAddr, + pub prefix: u8, + pub gateway: Option, + pub ifindex: Option, +} + +fn list_routes() -> io::Result> { + let mut mib: [u32; 6] = [0; 6]; + let mut len = 0; + + mib[0] = CTL_NET; + mib[1] = AF_ROUTE; + mib[2] = 0; + mib[3] = 0; // AddressFamily: IPv4 & IPv6 + mib[4] = NET_RT_DUMP; + // mib[5] flags: 0 + + if unsafe { + sysctl( + &mut mib as *mut _ as *mut _, + 6, + std::ptr::null_mut(), + &mut len, + std::ptr::null_mut(), + 0, + ) + } < 0 + { + return Err(io::Error::last_os_error()); + } + + let mut msgs_buf: Vec = vec![0; len as usize]; + + if unsafe { + sysctl( + &mut mib as *mut _ as *mut _, + 6, + msgs_buf.as_mut_ptr() as _, + &mut len, + std::ptr::null_mut(), + 0, + ) + } < 0 + { + return Err(io::Error::last_os_error()); + } + + let mut routes = vec![]; + let mut offset = 0; + + loop { + let buf = &mut msgs_buf[offset..]; + + if buf.len() < std::mem::size_of::() { + break; + } + + let rt_hdr = unsafe { std::mem::transmute::<_, &rt_msghdr>(buf.as_ptr()) }; + assert_eq!(rt_hdr.rtm_version as u32, RTM_VERSION); + if rt_hdr.rtm_errno != 0 { + return Err(code_to_error(rt_hdr.rtm_errno)); + } + + let msg_len = rt_hdr.rtm_msglen as usize; + offset += msg_len; + + if rt_hdr.rtm_flags as u32 & RTF_WASCLONED != 0 { + continue; + } + let rt_msg = &mut buf[std::mem::size_of::()..msg_len]; + + if let Some(route) = message_to_route(rt_hdr, rt_msg.as_mut_ptr()) { + routes.push(route); + } + } + + Ok(routes) +} + +fn message_to_arppair(msg_bytes: *mut u8) -> (IpAddr, MacAddr) { + const IP_START_INDEX: usize = 4; + const IP_END_INDEX: usize = 7; + const MAC_START_INDEX: usize = 24; + const MAC_END_INDEX: usize = 29; + let ip_bytes = unsafe { std::slice::from_raw_parts(msg_bytes.add(IP_START_INDEX), IP_END_INDEX + 1 - IP_START_INDEX) }; + let mac_bytes = unsafe { std::slice::from_raw_parts(msg_bytes.add(MAC_START_INDEX), MAC_END_INDEX + 1 - MAC_START_INDEX) }; + let ip_addr = IpAddr::V4(Ipv4Addr::new(ip_bytes[0], ip_bytes[1], ip_bytes[2], ip_bytes[3])); + let mac_addr = MacAddr::new([mac_bytes[0], mac_bytes[1], mac_bytes[2], mac_bytes[3], mac_bytes[4], mac_bytes[5]]); + (ip_addr, mac_addr) +} + +fn get_arp_table() -> io::Result> { + let mut arp_map: HashMap = HashMap::new(); + let mut mib: [u32; 6] = [0; 6]; + let mut len = 0; + mib[0] = CTL_NET; + mib[1] = PF_ROUTE; + mib[2] = 0; + mib[3] = AF_INET; + mib[4] = NET_RT_FLAGS; + mib[5] = RTF_LLINFO; + if unsafe { + sysctl( + &mut mib as *mut _ as *mut _, + 6, + std::ptr::null_mut(), + &mut len, + std::ptr::null_mut(), + 0, + ) + } < 0 + { + return Err(io::Error::last_os_error()); + } + + let mut msgs_buf: Vec = vec![0; len as usize]; + + if unsafe { + sysctl( + &mut mib as *mut _ as *mut _, + 6, + msgs_buf.as_mut_ptr() as _, + &mut len, + std::ptr::null_mut(), + 0, + ) + } < 0 + { + return Err(io::Error::last_os_error()); + } + let mut offset = 0; + loop { + let buf = &mut msgs_buf[offset..]; + + if buf.len() < std::mem::size_of::() { + break; + } + + let rt_hdr = unsafe { std::mem::transmute::<_, &rt_msghdr>(buf.as_ptr()) }; + assert_eq!(rt_hdr.rtm_version as u32, RTM_VERSION); + if rt_hdr.rtm_errno != 0 { + return Err(code_to_error(rt_hdr.rtm_errno)); + } + + let msg_len = rt_hdr.rtm_msglen as usize; + offset += msg_len; + + let rt_msg: &mut [u8] = &mut buf[std::mem::size_of::()..msg_len]; + let (ip, mac) = message_to_arppair(rt_msg.as_mut_ptr()); + arp_map.insert(ip, mac); + } + Ok(arp_map) +} + +fn get_default_route() -> Option { + match list_routes() { + Ok(routes) => { + for route in routes { + if (route.destination == Ipv4Addr::UNSPECIFIED + || route.destination == Ipv6Addr::UNSPECIFIED) + && route.prefix == 0 + && route.gateway != Some(IpAddr::V4(Ipv4Addr::UNSPECIFIED)) + && route.gateway != Some(IpAddr::V6(Ipv6Addr::UNSPECIFIED)) + { + return Some(route); + } + } + } + Err(_) => {}, + } + None +} + +pub fn get_default_gateway(_interface_name: String) -> Result { + if let Some(route) = get_default_route(){ + if let Some(gw_ip) = route.gateway { + match get_arp_table() { + Ok(arp_map) => { + if let Some(mac_addr) = arp_map.get(&gw_ip) { + let gateway = Gateway { + mac_addr: mac_addr.clone(), + ip_addr: gw_ip, + }; + return Ok(gateway); + } + } + Err(_) => {} + } + let gateway = Gateway { + mac_addr: MacAddr::zero(), + ip_addr: gw_ip, + }; + return Ok(gateway); + }else { + return Err(format!("Failed to get gateway IP address")); + } + }else{ + return Err(format!("Failed to get default route")); + } +} \ No newline at end of file diff --git a/src/gateway/mod.rs b/src/gateway/mod.rs index 8421a5f..f41bf7c 100644 --- a/src/gateway/mod.rs +++ b/src/gateway/mod.rs @@ -1,12 +1,16 @@ #[cfg(any( - target_os = "macos", target_os = "openbsd", target_os = "freebsd", - target_os = "netbsd", - target_os = "ios" + target_os = "netbsd" ))] pub(crate) mod unix; +#[cfg(any( + target_os = "macos", + target_os = "ios" +))] +pub(crate) mod macos; + #[cfg(any(target_os = "linux", target_os = "android"))] pub(crate) mod linux; @@ -60,7 +64,12 @@ pub fn get_default_gateway() -> Result { Err(String::from("Default Gateway not found")) } -#[cfg(not(target_os = "windows"))] +#[cfg(any( + target_os = "linux", + target_os = "openbsd", + target_os = "freebsd", + target_os = "netbsd" +))] fn send_udp_packet() -> Result<(), String> { use std::net::UdpSocket; let buf = [0u8; 0]; @@ -87,4 +96,4 @@ mod tests { fn test_default_gateway() { println!("{:?}", get_default_gateway()); } -} +} \ No newline at end of file diff --git a/src/interface/unix.rs b/src/interface/unix.rs index aa5a01b..65091e1 100644 --- a/src/interface/unix.rs +++ b/src/interface/unix.rs @@ -60,7 +60,7 @@ pub fn interfaces() -> Vec { match local_ip { IpAddr::V4(local_ipv4) => { if iface.ipv4.iter().any(|x| x.addr == local_ipv4) { - match gateway::unix::get_default_gateway(iface.name.clone()) { + match gateway::macos::get_default_gateway(iface.name.clone()) { Ok(gateway) => { iface.gateway = Some(gateway); } @@ -70,7 +70,7 @@ pub fn interfaces() -> Vec { } IpAddr::V6(local_ipv6) => { if iface.ipv6.iter().any(|x| x.addr == local_ipv6) { - match gateway::unix::get_default_gateway(iface.name.clone()) { + match gateway::macos::get_default_gateway(iface.name.clone()) { Ok(gateway) => { iface.gateway = Some(gateway); } @@ -335,4 +335,4 @@ mod tests { println!("{:#?}", interface); } } -} +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index cab9e24..2cd77c0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,11 +7,9 @@ ))] mod bpf; #[cfg(any( - target_os = "macos", target_os = "openbsd", target_os = "freebsd", - target_os = "netbsd", - target_os = "ios" + target_os = "netbsd" ))] mod socket; #[cfg(not(target_os = "windows"))] @@ -25,4 +23,4 @@ pub use gateway::get_default_gateway; pub use gateway::Gateway; pub use interface::get_default_interface; pub use interface::get_interfaces; -pub use interface::Interface; +pub use interface::Interface; \ No newline at end of file diff --git a/src/socket/mod.rs b/src/socket/mod.rs index a0ecbd8..c96984e 100644 --- a/src/socket/mod.rs +++ b/src/socket/mod.rs @@ -1,10 +1,28 @@ +#[cfg(any( + target_os = "openbsd", + target_os = "freebsd", + target_os = "netbsd" +))] pub mod packet; -#[cfg(not(target_os = "windows"))] +#[cfg(any( + target_os = "openbsd", + target_os = "freebsd", + target_os = "netbsd" +))] mod unix; -#[cfg(not(target_os = "windows"))] +#[cfg(any( + target_os = "openbsd", + target_os = "freebsd", + target_os = "netbsd" +))] pub use self::unix::*; +#[cfg(any( + target_os = "openbsd", + target_os = "freebsd", + target_os = "netbsd" +))] #[cfg(test)] mod tests { use super::*; @@ -73,4 +91,4 @@ mod tests { send_udp_packet(); } } -} +} \ No newline at end of file