From: Thomas Karpiniec Date: Fri, 31 May 2024 02:16:54 +0000 (+1000) Subject: Basic listing of interfaces on Windows X-Git-Tag: v0.1.0~28 X-Git-Url: https://code.octet-stream.net/netwatcher/commitdiff_plain/0ecc1ee8ddfdf50d813fe18794eb27ac4ec0cf56?ds=sidebyside;hp=f9610b2cf9cf1c3f79fa906cf4c33a8e09eb3e1a Basic listing of interfaces on Windows --- diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..b7d6ccc --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,162 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "netwatcher" +version = "0.1.0" +dependencies = [ + "windows", +] + +[[package]] +name = "proc-macro2" +version = "1.0.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "windows" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1de69df01bdf1ead2f4ac895dc77c9351aefff65b2f3db429a343f9cbf05e132" +dependencies = [ + "windows-core", + "windows-targets", +] + +[[package]] +name = "windows-core" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4698e52ed2d08f8658ab0c39512a7c00ee5fe2688c65f8c0a4f06750d729f2a6" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-targets", +] + +[[package]] +name = "windows-implement" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.56.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-result" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "749f0da9cc72d82e600d8d2e44cadd0b9eedb9038f71a1c58556ac1c5791813b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..04dea3f --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "netwatcher" +version = "0.1.0" +edition = "2021" + +[dependencies] + +[dependencies.windows] +version = "0.56.0" +features = [ + "Win32_NetworkManagement_IpHelper", + "Win32_NetworkManagement_Ndis", + "Win32_Networking_WinSock", +] diff --git a/src/imp_win.rs b/src/imp_win.rs new file mode 100644 index 0000000..15b115c --- /dev/null +++ b/src/imp_win.rs @@ -0,0 +1,111 @@ +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, 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( + 0, /* AF_UNSPEC */ + 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 new file mode 100644 index 0000000..af5b7af --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,71 @@ +use std::{ + collections::HashMap, + net::{IpAddr, Ipv4Addr, Ipv6Addr}, +}; + +#[cfg_attr(windows, path = "imp_win.rs")] +mod imp; + +type IfIndex = u32; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Interface { + pub index: u32, + pub name: String, + pub hw_addr: String, + pub ips: Vec, +} + +impl Interface { + pub fn ipv4_ips(&self) -> impl Iterator { + self.ips.iter().filter_map(|ip| match ip { + IpAddr::V4(v4) => Some(v4), + IpAddr::V6(_) => None, + }) + } + + pub fn ipv6_ips(&self) -> impl Iterator { + self.ips.iter().filter_map(|ip| match ip { + IpAddr::V4(_) => None, + IpAddr::V6(v6) => Some(v6), + }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Update { + pub interfaces: HashMap, + pub diff: UpdateDiff, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct UpdateDiff { + pub added: Vec, + pub removed: Vec, + pub modified: HashMap, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct InterfaceDiff { + pub hw_addr_changed: bool, + pub addrs_added: Vec, + pub addrs_removed: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Error { + Internal, +} + +pub fn list_interfaces() -> Result, Error> { + imp::list_interfaces() +} + +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 +}