From: Thomas Karpiniec Date: Sat, 8 Jun 2024 03:25:04 +0000 (+1000) Subject: Split out list and watch implementations X-Git-Tag: v0.1.0~24 X-Git-Url: https://code.octet-stream.net/netwatcher/commitdiff_plain/1c34fe3947aaf8af2d773d59bdebf19e17d78527 Split out list and watch implementations --- diff --git a/src/imp_mac.rs b/src/imp_mac.rs deleted file mode 100644 index 4ae0fc7..0000000 --- a/src/imp_mac.rs +++ /dev/null @@ -1,99 +0,0 @@ -use std::{collections::HashMap, net::IpAddr}; - -use block2::Block; -use nix::libc::c_long; -use nix::{ifaddrs::getifaddrs, net::if_::if_nametoindex}; - -use crate::util::format_mac; -use crate::{Error, IfIndex, Interface}; - -struct CandidateInterface { - name: String, - index: u32, - hw_addr: Option, - ips: Vec, -} - -pub(crate) fn list_interfaces() -> Result, Error> { - let addrs = getifaddrs().map_err(|_| Error::Internal)?; - let mut candidates = HashMap::new(); - - for addr in addrs { - let index = if_nametoindex(addr.interface_name.as_str()).map_err(|_| Error::Internal)?; - let candidate = candidates - .entry(addr.interface_name.clone()) - .or_insert_with(|| CandidateInterface { - name: addr.interface_name.clone(), - index, - hw_addr: None, - ips: vec![], - }); - if let Some(a) = addr.address { - if let Some(a) = a.as_link_addr() { - if let Some(raw_addr) = a.addr() { - candidate.hw_addr = Some(format_mac(&raw_addr)?); - } - } - if let Some(a) = a.as_sockaddr_in() { - candidate.ips.push(IpAddr::V4(a.ip())); - } - if let Some(a) = a.as_sockaddr_in6() { - candidate.ips.push(IpAddr::V6(a.ip())); - } - } - } - - let ifs = candidates - .drain() - .flat_map(|(_, c)| { - c.hw_addr.map(|hw_addr| { - ( - c.index, - Interface { - index: c.index, - hw_addr, - name: c.name, - ips: c.ips, - }, - ) - }) - }) - .collect(); - Ok(ifs) -} - -// The "objc2" project aims to provide bindings for all frameworks but Network.framework -// isn't ready yet so let's kick it old-school - -struct nw_path_monitor; -type nw_path_monitor_t = *mut nw_path_monitor; -struct nw_path; -type nw_path_t = *mut nw_path; -struct dispatch_queue; -type dispatch_queue_t = *mut dispatch_queue; -const QOS_CLASS_BACKGROUND: usize = 0x09; - -#[link(name = "Network", kind = "framework")] -extern "C" { - fn nw_path_monitor_create() -> nw_path_monitor_t; - fn nw_path_monitor_set_update_handler( - monitor: nw_path_monitor_t, - update_handler: &Block, - ); - fn nw_path_monitor_set_queue(monitor: nw_path_monitor_t, queue: dispatch_queue_t); - fn nw_path_monitor_start(monitor: nw_path_monitor_t); - fn nw_path_monitor_cancel(monitor: nw_path_monitor_t); - - fn dispatch_get_global_queue(identifier: usize, flag: usize) -> dispatch_queue_t; -} - -#[cfg(test)] -mod test { - use super::list_interfaces; - - #[test] - fn list() { - let ifaces = list_interfaces().unwrap(); - println!("{:?}", ifaces); - } -} diff --git a/src/imp_win.rs b/src/imp_win.rs deleted file mode 100644 index f34b207..0000000 --- a/src/imp_win.rs +++ /dev/null @@ -1,113 +0,0 @@ -use std::collections::HashMap; -use std::fmt::Write; -use std::net::IpAddr; - -use windows::Win32::Foundation::{ - ERROR_ADDRESS_NOT_ASSOCIATED, ERROR_BUFFER_OVERFLOW, ERROR_INVALID_PARAMETER, - ERROR_NOT_ENOUGH_MEMORY, ERROR_NO_DATA, ERROR_SUCCESS, WIN32_ERROR, -}; -use windows::Win32::NetworkManagement::IpHelper::{ - GetAdaptersAddresses, IP_ADAPTER_UNICAST_ADDRESS_LH, -}; -use windows::Win32::NetworkManagement::IpHelper::{ - GAA_FLAG_SKIP_ANYCAST, GAA_FLAG_SKIP_MULTICAST, IP_ADAPTER_ADDRESSES_LH, -}; -use windows::Win32::Networking::WinSock::{ - AF_INET, AF_INET6, AF_UNSPEC, SOCKADDR, SOCKADDR_IN, SOCKADDR_IN6, -}; - -use crate::{Error, IfIndex, Interface}; - -pub(crate) fn list_interfaces() -> Result, Error> { - let mut ifs = HashMap::new(); - // Microsoft recommends a 15 KB initial buffer - let start_size = 15 * 1024; - let mut buf: Vec = vec![0; start_size]; - let mut sizepointer: u32 = start_size as u32; - - unsafe { - loop { - let bufptr = &mut buf[0] as *mut _ as *mut IP_ADAPTER_ADDRESSES_LH; - let res = GetAdaptersAddresses( - AF_UNSPEC.0.into(), - GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST, - None, - Some(bufptr), - &mut sizepointer, - ); - match WIN32_ERROR(res) { - ERROR_SUCCESS => break, - ERROR_ADDRESS_NOT_ASSOCIATED => return Err(Error::Internal), - ERROR_BUFFER_OVERFLOW => { - buf.resize(sizepointer as usize, 0); - continue; - } - ERROR_INVALID_PARAMETER => return Err(Error::Internal), - ERROR_NOT_ENOUGH_MEMORY => return Err(Error::Internal), - ERROR_NO_DATA => return Ok(HashMap::new()), // there aren't any - _ => return Err(Error::Internal), // TODO: Use FormatMessage to get a string - } - } - - // We have at least one - let mut adapter_ptr = &buf[0] as *const _ as *const IP_ADAPTER_ADDRESSES_LH; - while !adapter_ptr.is_null() { - let adapter = &*adapter_ptr as &IP_ADAPTER_ADDRESSES_LH; - let mut hw_addr = String::with_capacity(adapter.PhysicalAddressLength as usize * 3); - for i in 0..adapter.PhysicalAddressLength as usize { - if i != 0 { - write!(hw_addr, ":").map_err(|_| Error::Internal)?; - } - write!(hw_addr, "{:02X}", adapter.PhysicalAddress[i]) - .map_err(|_| Error::Internal)?; - } - let mut ips = vec![]; - let mut unicast_ptr = adapter.FirstUnicastAddress; - while !unicast_ptr.is_null() { - let unicast = &*unicast_ptr as &IP_ADAPTER_UNICAST_ADDRESS_LH; - let sockaddr = &*unicast.Address.lpSockaddr as &SOCKADDR; - let ip = match sockaddr.sa_family { - AF_INET => { - let sockaddr_in = - &*(unicast.Address.lpSockaddr as *const SOCKADDR_IN) as &SOCKADDR_IN; - IpAddr::V4(sockaddr_in.sin_addr.into()) - } - AF_INET6 => { - let sockaddr_in6 = - &*(unicast.Address.lpSockaddr as *const SOCKADDR_IN6) as &SOCKADDR_IN6; - IpAddr::V6(sockaddr_in6.sin6_addr.into()) - } - _ => continue, - }; - ips.push(ip); - unicast_ptr = unicast.Next; - } - let ifindex = adapter.Ipv6IfIndex; - let name = adapter - .FriendlyName - .to_string() - .unwrap_or_else(|_| "".to_owned()); - let iface = Interface { - index: ifindex, - name, - hw_addr, - ips, - }; - ifs.insert(ifindex, iface); - adapter_ptr = adapter.Next; - } - } - - Ok(ifs) -} - -#[cfg(test)] -mod test { - use super::list_interfaces; - - #[test] - fn list() { - let ifaces = list_interfaces().unwrap(); - println!("{:?}", ifaces); - } -} diff --git a/src/lib.rs b/src/lib.rs index 9f82092..61e4940 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,9 +3,14 @@ use std::{ net::{IpAddr, Ipv4Addr, Ipv6Addr}, }; -#[cfg_attr(windows, path = "imp_win.rs")] -#[cfg_attr(target_vendor = "apple", path = "imp_mac.rs")] -mod imp; +#[cfg_attr(windows, path = "list_win.rs")] +#[cfg_attr(target_vendor = "apple", path = "list_mac.rs")] +mod list; + +#[cfg_attr(windows, path = "watch_win.rs")] +#[cfg_attr(target_vendor = "apple", path = "watch_mac.rs")] +mod watch; + mod util; type IfIndex = u32; @@ -40,14 +45,21 @@ pub struct Update { pub diff: UpdateDiff, } -#[derive(Debug, Clone, PartialEq, Eq)] +impl Update { + pub fn diff_from_previous(_prev: &Update) -> UpdateDiff { + // TODO: real calculation + UpdateDiff::default() + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct UpdateDiff { pub added: Vec, pub removed: Vec, pub modified: HashMap, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Default)] pub struct InterfaceDiff { pub hw_addr_changed: bool, pub addrs_added: Vec, @@ -56,18 +68,10 @@ pub struct InterfaceDiff { #[derive(Debug, Clone, PartialEq, Eq)] pub enum Error { + // TODO: handle all cases with proper sources Internal, } -pub fn list_interfaces() -> Result, Error> { - imp::list_interfaces() -} +pub use list::list_interfaces; +pub use watch::{watch_interfaces, WatchHandle}; -pub struct WatchHandle; - -pub fn watch_interfaces(callback: F) -> WatchHandle { - // stop current worker thread - // post this into a thread that will use it - drop(callback); - WatchHandle -} diff --git a/src/list_mac.rs b/src/list_mac.rs new file mode 100644 index 0000000..4ae0fc7 --- /dev/null +++ b/src/list_mac.rs @@ -0,0 +1,99 @@ +use std::{collections::HashMap, net::IpAddr}; + +use block2::Block; +use nix::libc::c_long; +use nix::{ifaddrs::getifaddrs, net::if_::if_nametoindex}; + +use crate::util::format_mac; +use crate::{Error, IfIndex, Interface}; + +struct CandidateInterface { + name: String, + index: u32, + hw_addr: Option, + ips: Vec, +} + +pub(crate) fn list_interfaces() -> Result, Error> { + let addrs = getifaddrs().map_err(|_| Error::Internal)?; + let mut candidates = HashMap::new(); + + for addr in addrs { + let index = if_nametoindex(addr.interface_name.as_str()).map_err(|_| Error::Internal)?; + let candidate = candidates + .entry(addr.interface_name.clone()) + .or_insert_with(|| CandidateInterface { + name: addr.interface_name.clone(), + index, + hw_addr: None, + ips: vec![], + }); + if let Some(a) = addr.address { + if let Some(a) = a.as_link_addr() { + if let Some(raw_addr) = a.addr() { + candidate.hw_addr = Some(format_mac(&raw_addr)?); + } + } + if let Some(a) = a.as_sockaddr_in() { + candidate.ips.push(IpAddr::V4(a.ip())); + } + if let Some(a) = a.as_sockaddr_in6() { + candidate.ips.push(IpAddr::V6(a.ip())); + } + } + } + + let ifs = candidates + .drain() + .flat_map(|(_, c)| { + c.hw_addr.map(|hw_addr| { + ( + c.index, + Interface { + index: c.index, + hw_addr, + name: c.name, + ips: c.ips, + }, + ) + }) + }) + .collect(); + Ok(ifs) +} + +// The "objc2" project aims to provide bindings for all frameworks but Network.framework +// isn't ready yet so let's kick it old-school + +struct nw_path_monitor; +type nw_path_monitor_t = *mut nw_path_monitor; +struct nw_path; +type nw_path_t = *mut nw_path; +struct dispatch_queue; +type dispatch_queue_t = *mut dispatch_queue; +const QOS_CLASS_BACKGROUND: usize = 0x09; + +#[link(name = "Network", kind = "framework")] +extern "C" { + fn nw_path_monitor_create() -> nw_path_monitor_t; + fn nw_path_monitor_set_update_handler( + monitor: nw_path_monitor_t, + update_handler: &Block, + ); + fn nw_path_monitor_set_queue(monitor: nw_path_monitor_t, queue: dispatch_queue_t); + fn nw_path_monitor_start(monitor: nw_path_monitor_t); + fn nw_path_monitor_cancel(monitor: nw_path_monitor_t); + + fn dispatch_get_global_queue(identifier: usize, flag: usize) -> dispatch_queue_t; +} + +#[cfg(test)] +mod test { + use super::list_interfaces; + + #[test] + fn list() { + let ifaces = list_interfaces().unwrap(); + println!("{:?}", ifaces); + } +} diff --git a/src/list_win.rs b/src/list_win.rs new file mode 100644 index 0000000..e842244 --- /dev/null +++ b/src/list_win.rs @@ -0,0 +1,113 @@ +use std::collections::HashMap; +use std::fmt::Write; +use std::net::IpAddr; + +use windows::Win32::Foundation::{ + ERROR_ADDRESS_NOT_ASSOCIATED, ERROR_BUFFER_OVERFLOW, ERROR_INVALID_PARAMETER, + ERROR_NOT_ENOUGH_MEMORY, ERROR_NO_DATA, ERROR_SUCCESS, WIN32_ERROR, +}; +use windows::Win32::NetworkManagement::IpHelper::{ + GetAdaptersAddresses, IP_ADAPTER_UNICAST_ADDRESS_LH, +}; +use windows::Win32::NetworkManagement::IpHelper::{ + GAA_FLAG_SKIP_ANYCAST, GAA_FLAG_SKIP_MULTICAST, IP_ADAPTER_ADDRESSES_LH, +}; +use windows::Win32::Networking::WinSock::{ + AF_INET, AF_INET6, AF_UNSPEC, SOCKADDR, SOCKADDR_IN, SOCKADDR_IN6, +}; + +use crate::{Error, IfIndex, Interface}; + +pub fn list_interfaces() -> Result, Error> { + let mut ifs = HashMap::new(); + // Microsoft recommends a 15 KB initial buffer + let start_size = 15 * 1024; + let mut buf: Vec = vec![0; start_size]; + let mut sizepointer: u32 = start_size as u32; + + unsafe { + loop { + let bufptr = &mut buf[0] as *mut _ as *mut IP_ADAPTER_ADDRESSES_LH; + let res = GetAdaptersAddresses( + AF_UNSPEC.0.into(), + GAA_FLAG_SKIP_ANYCAST | GAA_FLAG_SKIP_MULTICAST, + None, + Some(bufptr), + &mut sizepointer, + ); + match WIN32_ERROR(res) { + ERROR_SUCCESS => break, + ERROR_ADDRESS_NOT_ASSOCIATED => return Err(Error::Internal), + ERROR_BUFFER_OVERFLOW => { + buf.resize(sizepointer as usize, 0); + continue; + } + ERROR_INVALID_PARAMETER => return Err(Error::Internal), + ERROR_NOT_ENOUGH_MEMORY => return Err(Error::Internal), + ERROR_NO_DATA => return Ok(HashMap::new()), // there aren't any + _ => return Err(Error::Internal), // TODO: Use FormatMessage to get a string + } + } + + // We have at least one + let mut adapter_ptr = &buf[0] as *const _ as *const IP_ADAPTER_ADDRESSES_LH; + while !adapter_ptr.is_null() { + let adapter = &*adapter_ptr as &IP_ADAPTER_ADDRESSES_LH; + let mut hw_addr = String::with_capacity(adapter.PhysicalAddressLength as usize * 3); + for i in 0..adapter.PhysicalAddressLength as usize { + if i != 0 { + write!(hw_addr, ":").map_err(|_| Error::Internal)?; + } + write!(hw_addr, "{:02X}", adapter.PhysicalAddress[i]) + .map_err(|_| Error::Internal)?; + } + let mut ips = vec![]; + let mut unicast_ptr = adapter.FirstUnicastAddress; + while !unicast_ptr.is_null() { + let unicast = &*unicast_ptr as &IP_ADAPTER_UNICAST_ADDRESS_LH; + let sockaddr = &*unicast.Address.lpSockaddr as &SOCKADDR; + let ip = match sockaddr.sa_family { + AF_INET => { + let sockaddr_in = + &*(unicast.Address.lpSockaddr as *const SOCKADDR_IN) as &SOCKADDR_IN; + IpAddr::V4(sockaddr_in.sin_addr.into()) + } + AF_INET6 => { + let sockaddr_in6 = + &*(unicast.Address.lpSockaddr as *const SOCKADDR_IN6) as &SOCKADDR_IN6; + IpAddr::V6(sockaddr_in6.sin6_addr.into()) + } + _ => continue, + }; + ips.push(ip); + unicast_ptr = unicast.Next; + } + let ifindex = adapter.Ipv6IfIndex; + let name = adapter + .FriendlyName + .to_string() + .unwrap_or_else(|_| "".to_owned()); + let iface = Interface { + index: ifindex, + name, + hw_addr, + ips, + }; + ifs.insert(ifindex, iface); + adapter_ptr = adapter.Next; + } + } + + Ok(ifs) +} + +#[cfg(test)] +mod test { + use super::list_interfaces; + + #[test] + fn list() { + let ifaces = list_interfaces().unwrap(); + println!("{:?}", ifaces); + } +} diff --git a/src/watch_mac.rs b/src/watch_mac.rs new file mode 100644 index 0000000..547db48 --- /dev/null +++ b/src/watch_mac.rs @@ -0,0 +1,10 @@ +use crate::Update; + +pub struct WatchHandle; + +pub fn watch_interfaces(callback: F) -> WatchHandle { + // stop current worker thread + // post this into a thread that will use it + drop(callback); + WatchHandle +} diff --git a/src/watch_win.rs b/src/watch_win.rs new file mode 100644 index 0000000..e495e9a --- /dev/null +++ b/src/watch_win.rs @@ -0,0 +1,8 @@ +use crate::Update; + +pub struct WatchHandle; + +pub fn watch_interfaces(callback: F) -> WatchHandle { + drop(callback); + WatchHandle +}