]> code.octet-stream.net Git - netwatcher/commitdiff
Basic listing of interfaces on Windows
authorThomas Karpiniec <tom.karpiniec@outlook.com>
Fri, 31 May 2024 02:16:54 +0000 (12:16 +1000)
committerThomas Karpiniec <tom.karpiniec@outlook.com>
Fri, 31 May 2024 02:16:54 +0000 (12:16 +1000)
.gitignore [new file with mode: 0644]
Cargo.lock [new file with mode: 0644]
Cargo.toml [new file with mode: 0644]
src/imp_win.rs [new file with mode: 0644]
src/lib.rs [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..ea8c4bf
--- /dev/null
@@ -0,0 +1 @@
+/target
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644 (file)
index 0000000..b7d6ccc
--- /dev/null
@@ -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 (file)
index 0000000..04dea3f
--- /dev/null
@@ -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 (file)
index 0000000..15b115c
--- /dev/null
@@ -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<HashMap<IfIndex, Interface>, Error> {
+    let mut ifs = HashMap::new();
+    // Microsoft recommends a 15 KB initial buffer
+    let start_size = 15 * 1024;
+    let mut buf: Vec<u8> = 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 (file)
index 0000000..af5b7af
--- /dev/null
@@ -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<IpAddr>,
+}
+
+impl Interface {
+    pub fn ipv4_ips(&self) -> impl Iterator<Item = &Ipv4Addr> {
+        self.ips.iter().filter_map(|ip| match ip {
+            IpAddr::V4(v4) => Some(v4),
+            IpAddr::V6(_) => None,
+        })
+    }
+
+    pub fn ipv6_ips(&self) -> impl Iterator<Item = &Ipv6Addr> {
+        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<IfIndex, Interface>,
+    pub diff: UpdateDiff,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct UpdateDiff {
+    pub added: Vec<IfIndex>,
+    pub removed: Vec<IfIndex>,
+    pub modified: HashMap<IfIndex, InterfaceDiff>,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct InterfaceDiff {
+    pub hw_addr_changed: bool,
+    pub addrs_added: Vec<IpAddr>,
+    pub addrs_removed: Vec<IpAddr>,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum Error {
+    Internal,
+}
+
+pub fn list_interfaces() -> Result<HashMap<IfIndex, Interface>, Error> {
+    imp::list_interfaces()
+}
+
+pub struct WatchHandle;
+
+pub fn watch_interfaces<F: FnMut(Update)>(callback: F) -> WatchHandle {
+    // stop current worker thread
+    // post this into a thread that will use it
+    drop(callback);
+    WatchHandle
+}