diff --git a/Cargo.toml b/Cargo.toml index 10afe76..465aac7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,10 @@ objc2-core-foundation = "0.3" objc2-system-configuration = { version = "0.3", features = ["SCNetworkConfiguration"] } plist = "1.8" +[target.'cfg(target_os = "ios")'.dependencies] +dispatch2 = "0.3" +block2 = "0.6" + [dev-dependencies] serde_json = "1.0" diff --git a/src/os/ios/interface.rs b/src/os/ios/interface.rs index 72ca9e1..352f614 100644 --- a/src/os/ios/interface.rs +++ b/src/os/ios/interface.rs @@ -5,6 +5,8 @@ use crate::{interface::interface::Interface, os::unix::interface::unix_interface pub fn interfaces() -> Vec { let mut ifaces: Vec = unix_interfaces(); + let nw_iface_map = super::network::nw_interface_map(); + #[cfg(feature = "gateway")] let gateway_map = crate::os::darwin::route::get_gateway_map(); @@ -21,6 +23,10 @@ pub fn interfaces() -> Vec { iface.if_type = name_type; } + if let Some(nw_iface) = nw_iface_map.get(&iface.name) { + iface.if_type = nw_iface.if_type; + } + #[cfg(feature = "gateway")] { if let Some(gateway) = gateway_map.get(&iface.index) { diff --git a/src/os/ios/mod.rs b/src/os/ios/mod.rs index 8d3d626..c419d79 100644 --- a/src/os/ios/mod.rs +++ b/src/os/ios/mod.rs @@ -1 +1,2 @@ pub mod interface; +pub mod network; diff --git a/src/os/ios/network.rs b/src/os/ios/network.rs new file mode 100644 index 0000000..56fe08e --- /dev/null +++ b/src/os/ios/network.rs @@ -0,0 +1,170 @@ +//! Minimal Network.framework interface enumeration + +#![allow(non_camel_case_types)] + +use std::collections::HashMap; +use std::ffi::CStr; +use std::os::raw::{c_char, c_void}; +use std::sync::{Arc, Condvar, Mutex}; +use std::time::Duration; + +use block2::RcBlock; +use dispatch2::{ + DispatchQueue, DispatchQueueGlobalPriority, DispatchRetained, GlobalQueueIdentifier, +}; + +use crate::interface::types::InterfaceType; + +#[derive(Debug, Clone)] +pub(crate) struct NWInterface { + pub bsd_name: String, + pub if_type: InterfaceType, + #[allow(dead_code)] + pub index: u32, +} + +// Link to Network.framework +#[link(name = "Network", kind = "framework")] +unsafe extern "C" {} + +// Network.framework (minimal) FFI types +type nw_path_monitor_t = *mut c_void; +type nw_path_t = *mut c_void; +type nw_interface_t = *mut c_void; + +#[allow(dead_code)] +#[repr(C)] +#[derive(Clone, Copy, Debug)] +enum nw_interface_type_t { + other = 0, + wifi = 1, + cellular = 2, + wired = 3, + loopback = 4, +} + +unsafe extern "C" { + fn nw_path_monitor_create() -> nw_path_monitor_t; + fn nw_path_monitor_set_queue(monitor: nw_path_monitor_t, queue: *mut c_void); + fn nw_path_monitor_set_update_handler(monitor: nw_path_monitor_t, handler: *mut c_void); + fn nw_path_monitor_start(monitor: nw_path_monitor_t); + fn nw_path_monitor_cancel(monitor: nw_path_monitor_t); + + fn nw_path_enumerate_interfaces(path: nw_path_t, enumerate_block: *mut c_void); + + fn nw_interface_get_name(interface: nw_interface_t) -> *const c_char; + fn nw_interface_get_type(interface: nw_interface_t) -> nw_interface_type_t; + fn nw_interface_get_index(interface: nw_interface_t) -> u32; + + fn nw_release(obj: *mut c_void); +} + +fn map_type(t: nw_interface_type_t) -> InterfaceType { + match t { + nw_interface_type_t::wifi => InterfaceType::Wireless80211, + nw_interface_type_t::cellular => InterfaceType::Wwanpp, + nw_interface_type_t::wired => InterfaceType::Ethernet, + nw_interface_type_t::loopback => InterfaceType::Loopback, + nw_interface_type_t::other => InterfaceType::Unknown, + } +} + +type EnumBlock = dyn Fn(*mut c_void) -> u8 + 'static; + +/// Enumerate interfaces from Network.framework (NWPath/NWInterface) +pub fn nfw_interfaces() -> Vec { + let shared: Arc<(Mutex>>, Condvar)> = + Arc::new((Mutex::new(None), Condvar::new())); + let shared2 = Arc::clone(&shared); + + let queue = DispatchQueue::global_queue(GlobalQueueIdentifier::Priority( + DispatchQueueGlobalPriority::Default, + )); + + type UpdateHandler = dyn Fn(*mut c_void) + 'static; + + let blk: RcBlock = RcBlock::new(move |path_ptr: *mut c_void| { + let path = path_ptr as nw_path_t; + + let acc: Arc>> = Arc::new(Mutex::new(Vec::new())); + let acc2 = Arc::clone(&acc); + + let enum_blk: RcBlock = RcBlock::new(move |iface_ptr: *mut c_void| -> u8 { + let iface = iface_ptr as nw_interface_t; + if iface.is_null() { + return 1; + } + + let name_ptr = unsafe { nw_interface_get_name(iface) }; + if name_ptr.is_null() { + return 1; + } + + let name = unsafe { CStr::from_ptr(name_ptr) } + .to_string_lossy() + .into_owned(); + + let ty = unsafe { nw_interface_get_type(iface) }; + let idx = unsafe { nw_interface_get_index(iface) }; + + acc2.lock().unwrap().push(NWInterface { + bsd_name: name, + if_type: map_type(ty), + index: idx, + }); + + 1 + }); + + let enum_ptr: *mut c_void = RcBlock::::as_ptr(&enum_blk) as *mut c_void; + unsafe { nw_path_enumerate_interfaces(path, enum_ptr) }; + + // Take only the first callback result and notify + let v = std::mem::take(&mut *acc.lock().unwrap()); + let (lock, cv) = &*shared2; + let mut g = lock.lock().unwrap(); + if g.is_none() { + *g = Some(v); + cv.notify_one(); + } + + // note: enum_blk lives until the end of this closure + }); + + unsafe { + let monitor = nw_path_monitor_create(); + if monitor.is_null() { + return Vec::new(); + } + + let q_nn = DispatchRetained::::as_ptr(&queue); + let q_ptr: *mut c_void = q_nn.as_ptr() as *mut c_void; + + let blk_ptr: *mut c_void = RcBlock::::as_ptr(&blk) as *mut c_void; + + nw_path_monitor_set_queue(monitor, q_ptr); + nw_path_monitor_set_update_handler(monitor, blk_ptr); + nw_path_monitor_start(monitor); + + let (lock, cv) = &*shared; + + let guard = lock.lock().unwrap(); + if guard.is_none() { + let _ = cv.wait_timeout(guard, Duration::from_millis(500)).unwrap(); + } + + nw_path_monitor_cancel(monitor); + nw_release(monitor as *mut c_void); + } + + shared.0.lock().unwrap().take().unwrap_or_default() +} + +pub fn nw_interface_map() -> HashMap { + let mut map = HashMap::new(); + let ifaces = nfw_interfaces(); + for iface in ifaces { + map.insert(iface.bsd_name.clone(), iface); + } + map +}