Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,10 @@ features = [
"Win32_NetworkManagement_Ndis",
]

[target.'cfg(target_os = "macos")'.dependencies]
system-configuration = "0.6"
[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies]
objc2-core-foundation = "0.3"
objc2-system-configuration = { version = "0.3", features = ["SCNetworkConfiguration"] }
plist = "1.8"

[dev-dependencies]
serde_json = "1.0"
Expand Down
3 changes: 3 additions & 0 deletions src/interface/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ pub enum InterfaceType {
Bridge,
/// Controller Area Network
Can,
/// Peer-to-Peer Wireless (Wi-Fi Direct / AWDL)
PeerToPeerWireless,
/// Unknown interface type with a specific value
UnknownWithValue(u32),
}
Expand Down Expand Up @@ -198,6 +200,7 @@ impl InterfaceType {
InterfaceType::Wwanpp => String::from("WWANPP"),
InterfaceType::Wwanpp2 => String::from("WWANPP2"),
InterfaceType::Can => String::from("CAN"),
InterfaceType::PeerToPeerWireless => String::from("Peer-to-Peer Wireless"),
InterfaceType::UnknownWithValue(v) => format!("Unknown ({})", v),
}
}
Expand Down
100 changes: 100 additions & 0 deletions src/os/darwin/types.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::interface::types::InterfaceType;
use std::{ffi::CString, mem};

/// Get the interface type from ifaddrs if_data.ifi_type field.
pub fn get_interface_type(addr_ref: &libc::ifaddrs) -> InterfaceType {
if !addr_ref.ifa_data.is_null() {
let if_data = unsafe { &*(addr_ref.ifa_data as *const libc::if_data) };
Expand All @@ -8,3 +10,101 @@ pub fn get_interface_type(addr_ref: &libc::ifaddrs) -> InterfaceType {
InterfaceType::Unknown
}
}

// BSD ioctl encoding (ioccom.h)
const IOC_INOUT: u32 = 0xC000_0000;
const IOCPARM_MASK: u32 = 0x1fff;

const fn ioc(inout: u32, group: u8, num: u8, len: u32) -> u32 {
inout | ((len & IOCPARM_MASK) << 16) | ((group as u32) << 8) | (num as u32)
}

// #define SIOCGIFFUNCTIONALTYPE _IOWR('i', 173, struct ifreq)
fn siocgiffunctionaltype() -> u64 {
let len = mem::size_of::<libc::ifreq>() as u32;
ioc(IOC_INOUT, b'i', 173, len) as u64
}

// Values from <net/if.h> (IFRTYPE_FUNCTIONAL_*)
const IFRTYPE_FUNCTIONAL_UNKNOWN: u32 = 0;
const IFRTYPE_FUNCTIONAL_LOOPBACK: u32 = 1;
const IFRTYPE_FUNCTIONAL_WIRED: u32 = 2;
const IFRTYPE_FUNCTIONAL_WIFI_INFRA: u32 = 3;
const IFRTYPE_FUNCTIONAL_WIFI_AWDL: u32 = 4;
const IFRTYPE_FUNCTIONAL_CELLULAR: u32 = 5;
const IFRTYPE_FUNCTIONAL_INTCOPROC: u32 = 6;
const IFRTYPE_FUNCTIONAL_COMPANIONLINK: u32 = 7;

fn map_to_interface_type(ft: u32) -> InterfaceType {
match ft {
IFRTYPE_FUNCTIONAL_UNKNOWN => InterfaceType::Unknown,
IFRTYPE_FUNCTIONAL_LOOPBACK => InterfaceType::Loopback,
IFRTYPE_FUNCTIONAL_WIRED => InterfaceType::Ethernet,
IFRTYPE_FUNCTIONAL_WIFI_INFRA => InterfaceType::Wireless80211,
IFRTYPE_FUNCTIONAL_WIFI_AWDL => InterfaceType::PeerToPeerWireless,
IFRTYPE_FUNCTIONAL_CELLULAR => InterfaceType::Wwanpp,
IFRTYPE_FUNCTIONAL_INTCOPROC => InterfaceType::Unknown,
IFRTYPE_FUNCTIONAL_COMPANIONLINK => InterfaceType::Unknown,
_ => InterfaceType::Unknown,
}
}

/// Get the functional interface type using SIOCGIFFUNCTIONALTYPE ioctl.
pub(crate) fn get_functional_type(name: &str) -> InterfaceType {
// socket(AF_INET, SOCK_DGRAM, 0) + ioctl(SIOCGIFFUNCTIONALTYPE)
let fd = unsafe { libc::socket(libc::AF_INET, libc::SOCK_DGRAM, 0) };
if fd < 0 {
return InterfaceType::Unknown;
}

let c_name = match CString::new(name) {
Ok(v) => v,
Err(_) => {
unsafe { libc::close(fd) };
return InterfaceType::Unknown;
}
};

// ifreq layout is platform-specific; libc::ifreq exists on macOS/iOS targets.
let mut ifr: libc::ifreq = unsafe { mem::zeroed() };

// ifr_name is a fixed-size [c_char; IFNAMSIZ]
// Copy with truncation; ensure NUL terminated
unsafe {
let dst = ifr.ifr_name.as_mut_ptr() as *mut u8;
let src = c_name.as_bytes_with_nul();
let copy_len = std::cmp::min(ifr.ifr_name.len() - 1, src.len() - 1);

std::ptr::copy_nonoverlapping(src.as_ptr(), dst, copy_len);
*dst.add(copy_len) = 0;
}

let req = siocgiffunctionaltype();
let ok = unsafe { libc::ioctl(fd, req, &mut ifr) } >= 0;
unsafe { libc::close(fd) };

if !ok {
return InterfaceType::Unknown;
}

#[allow(clippy::unnecessary_cast)]
let type_id = unsafe { ifr.ifr_ifru.ifru_functional_type as u32 };

map_to_interface_type(type_id)
}

pub(crate) fn interface_type_by_name(name: &str) -> Option<InterfaceType> {
let n = name.as_bytes();

if n.starts_with(b"awdl") {
return Some(InterfaceType::PeerToPeerWireless);
}
if n.starts_with(b"utun") || n.starts_with(b"gif") || n.starts_with(b"stf") {
return Some(InterfaceType::Tunnel);
}
if n.starts_with(b"bridge") {
return Some(InterfaceType::Bridge);
}

None
}
38 changes: 27 additions & 11 deletions src/os/ios/interface.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,45 @@
use crate::interface::types::InterfaceType;
use crate::os::darwin::types::{get_functional_type, interface_type_by_name};
use crate::{interface::interface::Interface, os::unix::interface::unix_interfaces};

pub fn interfaces() -> Vec<Interface> {
#[cfg(not(feature = "gateway"))]
{
unix_interfaces()
}
let mut ifaces: Vec<Interface> = unix_interfaces();

#[cfg(feature = "gateway")]
{
use crate::os::unix::dns::get_system_dns_conf;
let gateway_map = crate::os::darwin::route::get_gateway_map();

for iface in &mut ifaces {
// If interface type is Ethernet, try to get a more accurate type
if iface.if_type == InterfaceType::Ethernet {
let ft: InterfaceType = get_functional_type(&iface.name);
if ft != InterfaceType::Unknown {
iface.if_type = ft;
}
}

let mut ifaces: Vec<Interface> = unix_interfaces();
let gateway_map = crate::os::darwin::route::get_gateway_map();
for iface in &mut ifaces {
if let Some(name_type) = interface_type_by_name(&iface.name) {
iface.if_type = name_type;
}

#[cfg(feature = "gateway")]
{
if let Some(gateway) = gateway_map.get(&iface.index) {
iface.gateway = Some(gateway.clone());
}
}
}

#[cfg(feature = "gateway")]
{
if let Some(local_ip) = crate::net::ip::get_local_ipaddr() {
if let Some(idx) = crate::interface::pick_default_iface_index(&ifaces, local_ip) {
if let Some(iface) = ifaces.iter_mut().find(|it| it.index == idx) {
iface.default = true;
iface.dns_servers = get_system_dns_conf();
iface.dns_servers = crate::os::unix::dns::get_system_dns_conf();
}
}
}
ifaces
}

ifaces
}
47 changes: 31 additions & 16 deletions src/os/macos/interface.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,43 @@
use crate::os::darwin::types::{get_functional_type, interface_type_by_name};
use crate::os::macos::sc::{get_sc_interface_map, read_sc_interfaces_plist_map};
use crate::{
interface::{interface::Interface, types::InterfaceType},
os::unix::interface::unix_interfaces,
interface::interface::Interface,
os::{macos::sc::SCInterface, unix::interface::unix_interfaces},
prelude::InterfaceType,
};

#[derive(Debug)]
pub struct SCInterface {
#[allow(dead_code)]
pub name: String,
pub friendly_name: Option<String>,
pub interface_type: InterfaceType,
}
use std::collections::HashMap;

pub fn interfaces() -> Vec<Interface> {
let type_map = super::types::get_if_type_map();
let mut ifaces: Vec<Interface> = unix_interfaces();

let if_extra_map: HashMap<String, SCInterface> = match read_sc_interfaces_plist_map() {
Ok(m) => m,
Err(_) => {
// Fallback to SCNetworkInterfaceCopyAll ...
get_sc_interface_map()
}
};

#[cfg(feature = "gateway")]
let gateway_map = crate::os::darwin::route::get_gateway_map();

for iface in &mut ifaces {
if let Some(sc_interface) = type_map.get(&iface.name) {
iface.if_type = sc_interface.interface_type;
iface.friendly_name = sc_interface.friendly_name.clone();
// If interface type is Ethernet, try to get a more accurate type
if iface.if_type == InterfaceType::Ethernet {
let ft: InterfaceType = get_functional_type(&iface.name);
if ft != InterfaceType::Unknown {
iface.if_type = ft;
}
}
if let Some(name_type) = interface_type_by_name(&iface.name) {
iface.if_type = name_type;
}

if let Some(sc_inface) = if_extra_map.get(&iface.name) {
if let Some(sc_type) = sc_inface.if_type() {
iface.if_type = sc_type;
}
iface.friendly_name = sc_inface.friendly_name.clone();
}

#[cfg(feature = "gateway")]
Expand All @@ -34,12 +50,11 @@ pub fn interfaces() -> Vec<Interface> {

#[cfg(feature = "gateway")]
{
use crate::os::unix::dns::get_system_dns_conf;
if let Some(local_ip) = crate::net::ip::get_local_ipaddr() {
if let Some(idx) = crate::interface::pick_default_iface_index(&ifaces, local_ip) {
if let Some(iface) = ifaces.iter_mut().find(|it| it.index == idx) {
iface.default = true;
iface.dns_servers = get_system_dns_conf();
iface.dns_servers = crate::os::unix::dns::get_system_dns_conf();
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/os/macos/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
pub mod interface;
pub mod types;
pub mod sc;
Loading