--- /dev/null
+# 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"
--- /dev/null
+[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",
+]
--- /dev/null
+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);
+ }
+}
--- /dev/null
+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
+}