├── .github └── workflows │ ├── CI.yml │ └── Cross.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE-BSD ├── LICENSE-MIT ├── README.md ├── examples ├── detect_interface_changes.rs └── list_interfaces.rs └── src ├── lib.rs ├── posix.rs ├── posix_not_apple.rs ├── sockaddr.rs └── windows.rs /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: CI 4 | 5 | jobs: 6 | check: 7 | name: Check 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v4 11 | - uses: dtolnay/rust-toolchain@stable 12 | with: 13 | toolchain: stable 14 | - name: Run check 15 | run: cargo check 16 | 17 | test: 18 | name: Test Suite 19 | runs-on: ${{ matrix.os }} 20 | strategy: 21 | matrix: 22 | os: [ubuntu-latest, macos-latest, windows-latest] 23 | rust: ["1.64.0", stable] 24 | steps: 25 | - uses: actions/checkout@v4 26 | - uses: maxim-lobanov/setup-xcode@v1 27 | if: ${{ matrix.os == 'macos-latest' && matrix.rust == '1.64.0' }} 28 | with: 29 | xcode-version: latest-stable 30 | - uses: dtolnay/rust-toolchain@stable 31 | if: ${{ matrix.rust == 'stable' }} 32 | - uses: dtolnay/rust-toolchain@1.64.0 33 | if: ${{ matrix.rust == '1.64.0' }} 34 | - name: Build 35 | run: cargo build 36 | - name: Test 37 | run: cargo test -- --show-output 38 | 39 | fmt: 40 | name: Rustfmt 41 | runs-on: ubuntu-latest 42 | steps: 43 | - uses: actions/checkout@v4 44 | - uses: dtolnay/rust-toolchain@stable 45 | with: 46 | components: rustfmt 47 | - name: Run fmt 48 | run: cargo fmt --all -- --check 49 | 50 | clippy_check: 51 | name: Clippy Check 52 | runs-on: ubuntu-latest 53 | steps: 54 | - uses: actions/checkout@v4 55 | - uses: dtolnay/rust-toolchain@stable 56 | with: 57 | components: clippy 58 | - uses: r7kamura/rust-problem-matchers@9fe7ca9f6550e5d6358e179d451cc25ea6b54f98 #v1.5.0 59 | - name: Run clippy 60 | run: cargo clippy --all-features 61 | -------------------------------------------------------------------------------- /.github/workflows/Cross.yml: -------------------------------------------------------------------------------- 1 | # We could use `@actions-rs/cargo` Action ability to automatically install `cross` tool 2 | # in order to compile our application for some unusual targets. 3 | 4 | on: [push, pull_request] 5 | 6 | name: Cross-compile 7 | 8 | jobs: 9 | build: 10 | name: Build 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | target: 15 | - aarch64-linux-android 16 | - x86_64-unknown-freebsd 17 | - x86_64-unknown-netbsd 18 | - x86_64-unknown-illumos 19 | steps: 20 | - uses: actions/checkout@v4 21 | - uses: dtolnay/rust-toolchain@stable 22 | with: 23 | toolchain: stable 24 | target: ${{ matrix.target }} 25 | - name: Build 26 | run: cargo build --target=${{ matrix.target }} 27 | 28 | apple: 29 | name: Apple Build on ${{ matrix.target }} 30 | runs-on: macos-latest 31 | strategy: 32 | matrix: 33 | include: 34 | - target: aarch64-apple-ios 35 | tier3: false 36 | - target: aarch64-apple-watchos 37 | tier3: true 38 | - target: aarch64-apple-tvos 39 | tier3: true 40 | - target: aarch64-apple-visionos 41 | tier3: true 42 | steps: 43 | - uses: actions/checkout@v4 44 | - uses: dtolnay/rust-toolchain@stable 45 | if: ${{ !matrix.tier3 }} 46 | with: 47 | toolchain: 'stable' 48 | targets: ${{ matrix.target }} 49 | components: rust-src 50 | - uses: dtolnay/rust-toolchain@master 51 | if: ${{ matrix.tier3 }} 52 | with: 53 | toolchain: 'nightly' 54 | components: rust-src 55 | 56 | - name: Build 57 | run: cargo build --target ${{ matrix.target }} ${{ matrix.tier3 && '-Zbuild-std' || '' }} 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | *.lock 3 | *.rsproj 4 | tags* 5 | build/ 6 | build-tests/ 7 | target/ 8 | /.idea 9 | tests/tmp* 10 | src/tmp* 11 | /.project 12 | *.bootstrap.cache 13 | /bin/ 14 | 15 | # Generated by Editors 16 | *~ 17 | *.bk 18 | *.sublime-* 19 | *.swp 20 | 21 | # Misc 22 | .DS_Store 23 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # if-addrs - Change Log 2 | 3 | ## Unreleased 4 | - Use Rust 1.56 stable and edition 2021 5 | 6 | ## [0.7.0] 7 | - Fix support for Android 11 8 | - Drop support for Android `<` 7 9 | 10 | ## [0.6.7] 11 | - Add support for haiku 12 | 13 | ## [0.6.6] 14 | - Add support for illumos 15 | 16 | ## [0.6.5] 17 | - Drop `unwrap` dev dependency 18 | 19 | ## [0.6.4] 20 | - Support Rust 1.40.0 21 | 22 | ## [0.6.3] 23 | - Fix Android build with Rust 2018 edition 24 | 25 | ## [0.6.2] 26 | - Fix Android build and add CI check 27 | 28 | ## [0.6.1] 29 | - Fixed Windows build issue after `winapi 0.3` upgrade 30 | 31 | ## [0.6.0] - forked from get_if_addrs 32 | - Rename to if-addrs 33 | - Replace `gcc` crate with `cc` crate 34 | - Upgrade `winapi` crate to 0.3 35 | 36 | ## [0.5.3] 37 | - Update dependency version of get_if_addrs-sys to 0.1.1 38 | - Update dependency version of c_linked_list to 1.1.1 39 | 40 | ## [0.5.2] 41 | - Fix incorrect parsing of IPv6 addresses 42 | 43 | ## [0.5.1] 44 | - Fixed nullptr deref in unsafe code 45 | - Use Rust 1.24.0 stable / 2018-02-05 nightly 46 | - Use Clippy 0.0.186 47 | 48 | ## [0.5.0] 49 | - Use rust 1.22.1 stable / 2017-12-02 nightly 50 | - rustfmt 0.9.0 and clippy-0.0.175 51 | 52 | ## [0.4.1] 53 | - Fix build for android 54 | 55 | ## [0.4.0] 56 | - Replaced ip::IpAddr with std::IpAddr 57 | - Changed to support BSD 58 | - Updated lints 59 | - Documentation fixes 60 | 61 | ## [0.3.1] 62 | - Fix build on ARM 63 | 64 | ## [0.3.0] 65 | - Added a method on the interface object to get the ip addresses 66 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["MaidSafe Developers ", "Messense Lv "] 3 | description = "Return interface IP addresses on Posix and windows systems" 4 | license = "MIT OR BSD-3-Clause" 5 | name = "if-addrs" 6 | readme = "README.md" 7 | repository = "https://github.com/messense/if-addrs" 8 | version = "0.13.4" 9 | edition = "2021" 10 | 11 | [target.'cfg(not(target_os = "windows"))'.dependencies] 12 | libc = "0.2" 13 | 14 | [target.'cfg(target_os = "windows")'.dependencies.windows-sys] 15 | version = "0.59.0" 16 | features = [ 17 | "Win32_Foundation", 18 | "Win32_System_IO", 19 | "Win32_System_Memory", 20 | "Win32_System_Threading", 21 | "Win32_Networking_WinSock", 22 | "Win32_NetworkManagement_IpHelper", 23 | "Win32_NetworkManagement_Ndis", 24 | ] 25 | 26 | [features] 27 | link-local = [] 28 | 29 | [package.metadata.docs.rs] 30 | all-features = true 31 | rustdoc-args = ["--cfg", "docsrs"] 32 | targets = ["x86_64-unknown-linux-gnu", "x86_64-apple-darwin", "x86_64-pc-windows-msvc", "aarch64-apple-ios", "aarch64-apple-watchos", "aarch64-apple-tvos", "aarch64-apple-visionos"] 33 | cargo-args = ["-Z", "build-std"] 34 | -------------------------------------------------------------------------------- /LICENSE-BSD: -------------------------------------------------------------------------------- 1 | Copyright 2018 MaidSafe.net limited. 2 | Copyright 2020 messense 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 13 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright 2018 MaidSafe.net limited. 2 | Copyright 2020 messense 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # if-addrs 2 | https://crates.io/crates/if-addrs 3 | 4 | ## Overview 5 | 6 | Retrieve network interface info for all interfaces on the system: 7 | 8 | ```rust 9 | // List all of the machine's network interfaces 10 | for iface in if_addrs::get_if_addrs().unwrap() { 11 | println!("{:#?}", iface); 12 | } 13 | ``` 14 | 15 | Get notifications for changes in network interfaces: 16 | 17 | ```rust 18 | let mut notifier = if_addrs::IfChangeNotifier::new().unwrap(); 19 | loop { 20 | if let Ok(details) = notifier.wait(None) { 21 | println!("{:#?}", details); 22 | } 23 | } 24 | ``` 25 | 26 | ## License 27 | 28 | This SAFE Network library is dual-licensed under the Modified BSD ([LICENSE-BSD](LICENSE-BSD) https://opensource.org/licenses/BSD-3-Clause) or the MIT license ([LICENSE-MIT](LICENSE-MIT) http://opensource.org/licenses/MIT) at your option. 29 | 30 | ## Contribution 31 | 32 | Copyrights in the SAFE Network are retained by their contributors. No copyright assignment is required to contribute to this project. 33 | -------------------------------------------------------------------------------- /examples/detect_interface_changes.rs: -------------------------------------------------------------------------------- 1 | //! Interface change notifier example. 2 | 3 | #[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "illumos")))] 4 | fn main() { 5 | let mut if_change_notifier = if_addrs::IfChangeNotifier::new().unwrap(); 6 | println!("Waiting for interface changes..."); 7 | loop { 8 | if let Ok(details) = if_change_notifier.wait(None) { 9 | println!("Network interfaces changed: {:#?}", details); 10 | } 11 | } 12 | } 13 | 14 | #[cfg(any(target_os = "macos", target_os = "ios", target_os = "illumos"))] 15 | fn main() { 16 | panic!("Interface change API is not implemented for macOS or iOS"); 17 | } 18 | -------------------------------------------------------------------------------- /examples/list_interfaces.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 MaidSafe.net limited. 2 | // 3 | // This SAFE Network Software is licensed to you under the MIT license or the Modified BSD license , at your option. This file may not be copied, 6 | // modified, or distributed except according to those terms. Please review the Licences for the 7 | // specific language governing permissions and limitations relating to use of the SAFE Network 8 | // Software. 9 | 10 | //! List interface example. 11 | 12 | fn main() { 13 | let ifaces = if_addrs::get_if_addrs().unwrap(); 14 | println!("Got list of interfaces"); 15 | println!("{:#?}", ifaces); 16 | } 17 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 MaidSafe.net limited. 2 | // 3 | // This SAFE Network Software is licensed to you under the MIT license or the Modified BSD license , at your option. This file may not be copied, 6 | // modified, or distributed except according to those terms. Please review the Licences for the 7 | // specific language governing permissions and limitations relating to use of the SAFE Network 8 | // Software. 9 | #![cfg_attr(docsrs, feature(doc_auto_cfg))] 10 | #![cfg_attr(docsrs, feature(doc_cfg))] 11 | 12 | #[cfg(not(windows))] 13 | mod posix; 14 | #[cfg(all( 15 | not(windows), 16 | not(all( 17 | target_vendor = "apple", 18 | any( 19 | target_os = "macos", 20 | target_os = "ios", 21 | target_os = "tvos", 22 | target_os = "watchos", 23 | target_os = "visionos" 24 | ) 25 | )), 26 | not(target_os = "freebsd"), 27 | not(target_os = "netbsd"), 28 | not(target_os = "openbsd"), 29 | not(target_os = "illumos") 30 | ))] 31 | mod posix_not_apple; 32 | mod sockaddr; 33 | #[cfg(windows)] 34 | mod windows; 35 | 36 | use std::io; 37 | use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; 38 | 39 | /// Details about an interface on this host. 40 | #[derive(Debug, PartialEq, Eq, Hash, Clone)] 41 | pub struct Interface { 42 | /// The name of the interface. 43 | pub name: String, 44 | /// The address details of the interface. 45 | pub addr: IfAddr, 46 | /// The index of the interface. 47 | pub index: Option, 48 | /// (Windows only) A permanent and unique identifier for the interface. It 49 | /// cannot be modified by the user. It is typically a GUID string of the 50 | /// form: "{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}", but this is not 51 | /// guaranteed by the Windows API. 52 | #[cfg(windows)] 53 | pub adapter_name: String, 54 | } 55 | 56 | impl Interface { 57 | /// Check whether this is a loopback interface. 58 | #[must_use] 59 | pub const fn is_loopback(&self) -> bool { 60 | self.addr.is_loopback() 61 | } 62 | 63 | /// Check whether this is a link local interface. 64 | #[must_use] 65 | pub const fn is_link_local(&self) -> bool { 66 | self.addr.is_link_local() 67 | } 68 | 69 | /// Get the IP address of this interface. 70 | #[must_use] 71 | pub const fn ip(&self) -> IpAddr { 72 | self.addr.ip() 73 | } 74 | } 75 | 76 | /// Details about the address of an interface on this host. 77 | #[derive(Debug, PartialEq, Eq, Hash, Clone)] 78 | pub enum IfAddr { 79 | /// This is an Ipv4 interface. 80 | V4(Ifv4Addr), 81 | /// This is an Ipv6 interface. 82 | V6(Ifv6Addr), 83 | } 84 | 85 | impl IfAddr { 86 | /// Check whether this is a loopback address. 87 | #[must_use] 88 | pub const fn is_loopback(&self) -> bool { 89 | match *self { 90 | IfAddr::V4(ref ifv4_addr) => ifv4_addr.is_loopback(), 91 | IfAddr::V6(ref ifv6_addr) => ifv6_addr.is_loopback(), 92 | } 93 | } 94 | 95 | /// Check whether this is a link local interface. 96 | #[must_use] 97 | pub const fn is_link_local(&self) -> bool { 98 | match *self { 99 | IfAddr::V4(ref ifv4_addr) => ifv4_addr.is_link_local(), 100 | IfAddr::V6(ref ifv6_addr) => ifv6_addr.is_link_local(), 101 | } 102 | } 103 | 104 | /// Get the IP address of this interface address. 105 | #[must_use] 106 | pub const fn ip(&self) -> IpAddr { 107 | match *self { 108 | IfAddr::V4(ref ifv4_addr) => IpAddr::V4(ifv4_addr.ip), 109 | IfAddr::V6(ref ifv6_addr) => IpAddr::V6(ifv6_addr.ip), 110 | } 111 | } 112 | } 113 | 114 | /// Details about the ipv4 address of an interface on this host. 115 | #[derive(Debug, PartialEq, Eq, Hash, Clone)] 116 | pub struct Ifv4Addr { 117 | /// The IP address of the interface. 118 | pub ip: Ipv4Addr, 119 | /// The netmask of the interface. 120 | pub netmask: Ipv4Addr, 121 | /// The CIDR prefix of the interface. 122 | pub prefixlen: u8, 123 | /// The broadcast address of the interface. 124 | pub broadcast: Option, 125 | } 126 | 127 | impl Ifv4Addr { 128 | /// Check whether this is a loopback address. 129 | #[must_use] 130 | pub const fn is_loopback(&self) -> bool { 131 | self.ip.is_loopback() 132 | } 133 | 134 | /// Check whether this is a link local address. 135 | #[must_use] 136 | pub const fn is_link_local(&self) -> bool { 137 | self.ip.is_link_local() 138 | } 139 | } 140 | 141 | /// Details about the ipv6 address of an interface on this host. 142 | #[derive(Debug, PartialEq, Eq, Hash, Clone)] 143 | pub struct Ifv6Addr { 144 | /// The IP address of the interface. 145 | pub ip: Ipv6Addr, 146 | /// The netmask of the interface. 147 | pub netmask: Ipv6Addr, 148 | /// The CIDR prefix of the interface. 149 | pub prefixlen: u8, 150 | /// The broadcast address of the interface. 151 | pub broadcast: Option, 152 | } 153 | 154 | impl Ifv6Addr { 155 | /// Check whether this is a loopback address. 156 | #[must_use] 157 | pub const fn is_loopback(&self) -> bool { 158 | self.ip.is_loopback() 159 | } 160 | 161 | /// Check whether this is a link local address. 162 | #[must_use] 163 | pub const fn is_link_local(&self) -> bool { 164 | let bytes = self.ip.octets(); 165 | 166 | bytes[0] == 0xfe && bytes[1] == 0x80 167 | } 168 | } 169 | 170 | #[cfg(not(windows))] 171 | mod getifaddrs_posix { 172 | use libc::if_nametoindex; 173 | 174 | use super::{IfAddr, Ifv4Addr, Ifv6Addr, Interface}; 175 | use crate::posix::{self as ifaddrs, IfAddrs}; 176 | use crate::sockaddr; 177 | use std::ffi::CStr; 178 | use std::io; 179 | use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; 180 | 181 | /// Return a vector of IP details for all the valid interfaces on this host. 182 | #[allow(unsafe_code)] 183 | pub fn get_if_addrs() -> io::Result> { 184 | let mut ret = Vec::::new(); 185 | let ifaddrs = IfAddrs::new()?; 186 | 187 | for ifaddr in ifaddrs.iter() { 188 | let addr = match sockaddr::to_ipaddr(ifaddr.ifa_addr) { 189 | None => continue, 190 | Some(IpAddr::V4(ipv4_addr)) => { 191 | let netmask = match sockaddr::to_ipaddr(ifaddr.ifa_netmask) { 192 | Some(IpAddr::V4(netmask)) => netmask, 193 | _ => Ipv4Addr::new(0, 0, 0, 0), 194 | }; 195 | let broadcast = if (ifaddr.ifa_flags & 2) != 0 { 196 | match ifaddrs::do_broadcast(&ifaddr) { 197 | Some(IpAddr::V4(broadcast)) => Some(broadcast), 198 | _ => None, 199 | } 200 | } else { 201 | None 202 | }; 203 | let prefixlen = if cfg!(target_endian = "little") { 204 | u32::from_le_bytes(netmask.octets()).count_ones() as u8 205 | } else { 206 | u32::from_be_bytes(netmask.octets()).count_ones() as u8 207 | }; 208 | IfAddr::V4(Ifv4Addr { 209 | ip: ipv4_addr, 210 | netmask, 211 | prefixlen, 212 | broadcast, 213 | }) 214 | } 215 | Some(IpAddr::V6(ipv6_addr)) => { 216 | let netmask = match sockaddr::to_ipaddr(ifaddr.ifa_netmask) { 217 | Some(IpAddr::V6(netmask)) => netmask, 218 | _ => Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0), 219 | }; 220 | let broadcast = if (ifaddr.ifa_flags & 2) != 0 { 221 | match ifaddrs::do_broadcast(&ifaddr) { 222 | Some(IpAddr::V6(broadcast)) => Some(broadcast), 223 | _ => None, 224 | } 225 | } else { 226 | None 227 | }; 228 | let prefixlen = if cfg!(target_endian = "little") { 229 | u128::from_le_bytes(netmask.octets()).count_ones() as u8 230 | } else { 231 | u128::from_be_bytes(netmask.octets()).count_ones() as u8 232 | }; 233 | IfAddr::V6(Ifv6Addr { 234 | ip: ipv6_addr, 235 | netmask, 236 | prefixlen, 237 | broadcast, 238 | }) 239 | } 240 | }; 241 | 242 | let name = unsafe { CStr::from_ptr(ifaddr.ifa_name) } 243 | .to_string_lossy() 244 | .into_owned(); 245 | let index = { 246 | let index = unsafe { if_nametoindex(ifaddr.ifa_name) }; 247 | 248 | // From `man if_nametoindex 3`: 249 | // The if_nametoindex() function maps the interface name specified in ifname to its 250 | // corresponding index. If the specified interface does not exist, it returns 0. 251 | if index == 0 { 252 | None 253 | } else { 254 | Some(index) 255 | } 256 | }; 257 | ret.push(Interface { name, addr, index }); 258 | } 259 | 260 | Ok(ret) 261 | } 262 | } 263 | 264 | /// Get a list of all the network interfaces on this machine along with their IP info. 265 | #[cfg(not(windows))] 266 | pub fn get_if_addrs() -> io::Result> { 267 | getifaddrs_posix::get_if_addrs() 268 | } 269 | 270 | #[cfg(windows)] 271 | mod getifaddrs_windows { 272 | use super::{IfAddr, Ifv4Addr, Ifv6Addr, Interface}; 273 | use crate::sockaddr; 274 | use crate::windows::IfAddrs; 275 | use std::io; 276 | use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; 277 | use windows_sys::Win32::Networking::WinSock::IpDadStatePreferred; 278 | 279 | /// Return a vector of IP details for all the valid interfaces on this host. 280 | pub fn get_if_addrs() -> io::Result> { 281 | let mut ret = Vec::::new(); 282 | let ifaddrs = IfAddrs::new()?; 283 | 284 | for ifaddr in ifaddrs.iter() { 285 | for addr in ifaddr.unicast_addresses() { 286 | if addr.DadState != IpDadStatePreferred { 287 | continue; 288 | } 289 | let addr = match sockaddr::to_ipaddr(addr.Address.lpSockaddr) { 290 | None => continue, 291 | Some(IpAddr::V4(ipv4_addr)) => { 292 | let mut item_netmask = Ipv4Addr::new(0, 0, 0, 0); 293 | let mut item_broadcast = None; 294 | let item_prefix = addr.OnLinkPrefixLength; 295 | 296 | // Search prefixes for a prefix matching addr 297 | 'prefixloopv4: for prefix in ifaddr.prefixes() { 298 | let ipprefix = sockaddr::to_ipaddr(prefix.Address.lpSockaddr); 299 | match ipprefix { 300 | Some(IpAddr::V4(ref a)) => { 301 | let mut netmask: [u8; 4] = [0; 4]; 302 | for (n, netmask_elt) in netmask 303 | .iter_mut() 304 | .enumerate() 305 | .take((prefix.PrefixLength as usize + 7) / 8) 306 | { 307 | let x_byte = ipv4_addr.octets()[n]; 308 | let y_byte = a.octets()[n]; 309 | for m in 0..8 { 310 | if (n * 8) + m >= prefix.PrefixLength as usize { 311 | break; 312 | } 313 | let bit = 1 << (7 - m); 314 | if (x_byte & bit) == (y_byte & bit) { 315 | *netmask_elt |= bit; 316 | } else { 317 | continue 'prefixloopv4; 318 | } 319 | } 320 | } 321 | item_netmask = Ipv4Addr::new( 322 | netmask[0], netmask[1], netmask[2], netmask[3], 323 | ); 324 | let mut broadcast: [u8; 4] = ipv4_addr.octets(); 325 | for n in 0..4 { 326 | broadcast[n] |= !netmask[n]; 327 | } 328 | item_broadcast = Some(Ipv4Addr::new( 329 | broadcast[0], 330 | broadcast[1], 331 | broadcast[2], 332 | broadcast[3], 333 | )); 334 | break 'prefixloopv4; 335 | } 336 | _ => continue, 337 | }; 338 | } 339 | IfAddr::V4(Ifv4Addr { 340 | ip: ipv4_addr, 341 | netmask: item_netmask, 342 | prefixlen: item_prefix, 343 | broadcast: item_broadcast, 344 | }) 345 | } 346 | Some(IpAddr::V6(ipv6_addr)) => { 347 | let mut item_netmask = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0); 348 | let item_prefix = addr.OnLinkPrefixLength; 349 | // Search prefixes for a prefix matching addr 350 | 'prefixloopv6: for prefix in ifaddr.prefixes() { 351 | let ipprefix = sockaddr::to_ipaddr(prefix.Address.lpSockaddr); 352 | match ipprefix { 353 | Some(IpAddr::V6(ref a)) => { 354 | // Iterate the bits in the prefix, if they all match this prefix 355 | // is the right one, else try the next prefix 356 | let mut netmask: [u16; 8] = [0; 8]; 357 | for (n, netmask_elt) in netmask 358 | .iter_mut() 359 | .enumerate() 360 | .take((prefix.PrefixLength as usize + 15) / 16) 361 | { 362 | let x_word = ipv6_addr.segments()[n]; 363 | let y_word = a.segments()[n]; 364 | for m in 0..16 { 365 | if (n * 16) + m >= prefix.PrefixLength as usize { 366 | break; 367 | } 368 | let bit = 1 << (15 - m); 369 | if (x_word & bit) == (y_word & bit) { 370 | *netmask_elt |= bit; 371 | } else { 372 | continue 'prefixloopv6; 373 | } 374 | } 375 | } 376 | item_netmask = Ipv6Addr::new( 377 | netmask[0], netmask[1], netmask[2], netmask[3], netmask[4], 378 | netmask[5], netmask[6], netmask[7], 379 | ); 380 | break 'prefixloopv6; 381 | } 382 | _ => continue, 383 | }; 384 | } 385 | IfAddr::V6(Ifv6Addr { 386 | ip: ipv6_addr, 387 | netmask: item_netmask, 388 | prefixlen: item_prefix, 389 | broadcast: None, 390 | }) 391 | } 392 | }; 393 | 394 | let index = match addr { 395 | IfAddr::V4(_) => ifaddr.ipv4_index(), 396 | IfAddr::V6(_) => ifaddr.ipv6_index(), 397 | }; 398 | ret.push(Interface { 399 | name: ifaddr.name(), 400 | addr, 401 | index, 402 | adapter_name: ifaddr.adapter_name(), 403 | }); 404 | } 405 | } 406 | 407 | Ok(ret) 408 | } 409 | } 410 | 411 | /// Get a list of all the network interfaces on this machine along with their IP info. 412 | #[cfg(windows)] 413 | pub fn get_if_addrs() -> io::Result> { 414 | getifaddrs_windows::get_if_addrs() 415 | } 416 | 417 | #[cfg(not(any( 418 | all( 419 | target_vendor = "apple", 420 | any( 421 | target_os = "macos", 422 | target_os = "ios", 423 | target_os = "tvos", 424 | target_os = "watchos", 425 | target_os = "visionos" 426 | ) 427 | ), 428 | target_os = "freebsd", 429 | target_os = "netbsd", 430 | target_os = "openbsd", 431 | target_os = "illumos" 432 | )))] 433 | #[cfg_attr( 434 | docsrs, 435 | doc(cfg(any( 436 | not(target_vendor = "apple"), 437 | not(target_os = "freebsd"), 438 | not(target_os = "netbsd"), 439 | not(target_os = "openbsd"), 440 | not(target_os = "illumos") 441 | ))) 442 | )] 443 | mod if_change_notifier { 444 | use super::Interface; 445 | use std::collections::HashSet; 446 | use std::io; 447 | use std::time::{Duration, Instant}; 448 | 449 | #[derive(Debug, PartialEq, Eq, Hash, Clone)] 450 | pub enum IfChangeType { 451 | Added(Interface), 452 | Removed(Interface), 453 | } 454 | 455 | #[cfg(windows)] 456 | type InternalIfChangeNotifier = crate::windows::WindowsIfChangeNotifier; 457 | #[cfg(not(windows))] 458 | type InternalIfChangeNotifier = crate::posix_not_apple::PosixIfChangeNotifier; 459 | 460 | /// (Not available on iOS/macOS) A utility to monitor for interface changes 461 | /// and report them, so you can handle events such as WiFi 462 | /// disconnection/flight mode/route changes 463 | pub struct IfChangeNotifier { 464 | inner: InternalIfChangeNotifier, 465 | last_ifs: HashSet, 466 | } 467 | 468 | impl IfChangeNotifier { 469 | /// Create a new interface change notifier. Returns an OS specific error 470 | /// if the network notifier could not be set up. 471 | pub fn new() -> io::Result { 472 | Ok(Self { 473 | inner: InternalIfChangeNotifier::new()?, 474 | last_ifs: HashSet::from_iter(super::get_if_addrs()?), 475 | }) 476 | } 477 | 478 | /// (Not available on iOS/macOS) Block until the OS reports that the 479 | /// network interface list has changed, or until an optional timeout. 480 | /// 481 | /// For example, if an ethernet connector is plugged/unplugged, or a 482 | /// WiFi network is connected to. 483 | /// 484 | /// The changed interfaces are returned. If an interface has both IPv4 485 | /// and IPv6 addresses, you can expect both of them to be returned from 486 | /// a single call to `wait`. 487 | /// 488 | /// Returns an [`io::ErrorKind::WouldBlock`] error on timeout, or 489 | /// another error if the network notifier could not be read from. 490 | pub fn wait(&mut self, timeout: Option) -> io::Result> { 491 | let start = Instant::now(); 492 | loop { 493 | self.inner 494 | .wait(timeout.map(|t| t.saturating_sub(start.elapsed())))?; 495 | 496 | // something has changed - now we find out what (or whether it was spurious) 497 | let new_ifs = HashSet::from_iter(super::get_if_addrs()?); 498 | let mut changes: Vec = new_ifs 499 | .difference(&self.last_ifs) 500 | .cloned() 501 | .map(IfChangeType::Added) 502 | .collect(); 503 | changes.extend( 504 | self.last_ifs 505 | .difference(&new_ifs) 506 | .cloned() 507 | .map(IfChangeType::Removed), 508 | ); 509 | self.last_ifs = new_ifs; 510 | 511 | if !changes.is_empty() { 512 | return Ok(changes); 513 | } 514 | } 515 | } 516 | } 517 | } 518 | 519 | #[cfg(not(any( 520 | all( 521 | target_vendor = "apple", 522 | any( 523 | target_os = "macos", 524 | target_os = "ios", 525 | target_os = "tvos", 526 | target_os = "watchos", 527 | target_os = "visionos" 528 | ) 529 | ), 530 | target_os = "freebsd", 531 | target_os = "netbsd", 532 | target_os = "openbsd", 533 | target_os = "illumos" 534 | )))] 535 | #[cfg_attr( 536 | docsrs, 537 | doc(cfg(any( 538 | not(target_vendor = "apple"), 539 | not(target_os = "freebsd"), 540 | not(target_os = "netbsd"), 541 | not(target_os = "openbsd"), 542 | not(target_os = "illumos") 543 | ))) 544 | )] 545 | pub use if_change_notifier::{IfChangeNotifier, IfChangeType}; 546 | 547 | #[cfg(test)] 548 | mod tests { 549 | use super::{get_if_addrs, Interface}; 550 | use std::io::Read; 551 | use std::net::{IpAddr, Ipv4Addr}; 552 | use std::process::{Command, Stdio}; 553 | use std::str::FromStr; 554 | use std::thread; 555 | use std::time::Duration; 556 | 557 | fn list_system_interfaces(cmd: &str, arg: &str) -> String { 558 | let start_cmd = if arg.is_empty() { 559 | Command::new(cmd).stdout(Stdio::piped()).spawn() 560 | } else { 561 | Command::new(cmd).arg(arg).stdout(Stdio::piped()).spawn() 562 | }; 563 | let mut process = match start_cmd { 564 | Err(why) => { 565 | println!("couldn't start cmd {} : {}", cmd, why); 566 | return String::new(); 567 | } 568 | Ok(process) => process, 569 | }; 570 | thread::sleep(Duration::from_millis(1000)); 571 | let _ = process.kill(); 572 | let result: Vec = process 573 | .stdout 574 | .unwrap() 575 | .bytes() 576 | .map(|x| x.unwrap()) 577 | .collect(); 578 | String::from_utf8(result).unwrap() 579 | } 580 | 581 | #[cfg(windows)] 582 | fn list_system_addrs() -> Vec { 583 | use std::net::Ipv6Addr; 584 | list_system_interfaces("ipconfig", "") 585 | .lines() 586 | .filter_map(|line| { 587 | println!("{}", line); 588 | if line.contains("Address") && !line.contains("Link-local") { 589 | let addr_s: Vec<&str> = line.split(" : ").collect(); 590 | if line.contains("IPv6") { 591 | return Some(IpAddr::V6(Ipv6Addr::from_str(addr_s[1]).unwrap())); 592 | } else if line.contains("IPv4") { 593 | return Some(IpAddr::V4(Ipv4Addr::from_str(addr_s[1]).unwrap())); 594 | } 595 | } 596 | None 597 | }) 598 | .collect() 599 | } 600 | 601 | #[cfg(any(target_os = "linux", target_os = "android"))] 602 | fn list_system_addrs() -> Vec { 603 | list_system_interfaces("ip", "addr") 604 | .lines() 605 | .filter_map(|line| { 606 | println!("{}", line); 607 | if line.contains("inet ") { 608 | let addr_s: Vec<&str> = line.split_whitespace().collect(); 609 | let addr: Vec<&str> = addr_s[1].split('/').collect(); 610 | return Some(IpAddr::V4(Ipv4Addr::from_str(addr[0]).unwrap())); 611 | } 612 | None 613 | }) 614 | .collect() 615 | } 616 | 617 | #[cfg(any( 618 | target_os = "freebsd", 619 | target_os = "netbsd", 620 | target_os = "openbsd", 621 | target_os = "illumos", 622 | all( 623 | target_vendor = "apple", 624 | any( 625 | target_os = "macos", 626 | target_os = "ios", 627 | target_os = "tvos", 628 | target_os = "watchos", 629 | target_os = "visionos" 630 | ) 631 | ) 632 | ))] 633 | fn list_system_addrs() -> Vec { 634 | list_system_interfaces("ifconfig", "") 635 | .lines() 636 | .filter_map(|line| { 637 | println!("{}", line); 638 | if line.contains("inet ") { 639 | let addr_s: Vec<&str> = line.split_whitespace().collect(); 640 | return Some(IpAddr::V4(Ipv4Addr::from_str(addr_s[1]).unwrap())); 641 | } 642 | None 643 | }) 644 | .collect() 645 | } 646 | 647 | #[test] 648 | fn test_get_if_addrs() { 649 | let ifaces = get_if_addrs().unwrap(); 650 | println!("Local interfaces:"); 651 | println!("{:#?}", ifaces); 652 | // at least one loop back address 653 | assert!( 654 | 1 <= ifaces 655 | .iter() 656 | .filter(|interface| interface.is_loopback()) 657 | .count() 658 | ); 659 | // if index is set, it is non-zero 660 | for interface in &ifaces { 661 | if let Some(idx) = interface.index { 662 | assert!(idx > 0); 663 | } 664 | } 665 | 666 | // one address of IpV4(127.0.0.1) 667 | let is_loopback = 668 | |interface: &&Interface| interface.addr.ip() == IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); 669 | assert_eq!(1, ifaces.iter().filter(is_loopback).count()); 670 | 671 | // each system address shall be listed 672 | let system_addrs = list_system_addrs(); 673 | assert!(!system_addrs.is_empty()); 674 | for addr in system_addrs { 675 | let mut listed = false; 676 | println!("\n checking whether {:?} has been properly listed \n", addr); 677 | for interface in &ifaces { 678 | if interface.addr.ip() == addr { 679 | listed = true; 680 | } 681 | 682 | assert!(interface.index.is_some()); 683 | } 684 | assert!(listed); 685 | } 686 | } 687 | 688 | #[cfg(not(any( 689 | all( 690 | target_vendor = "apple", 691 | any( 692 | target_os = "macos", 693 | target_os = "ios", 694 | target_os = "tvos", 695 | target_os = "watchos", 696 | target_os = "visionos" 697 | ) 698 | ), 699 | target_os = "freebsd", 700 | target_os = "netbsd", 701 | target_os = "openbsd", 702 | target_os = "illumos" 703 | )))] 704 | #[test] 705 | fn test_if_notifier() { 706 | // Check that the interface notifier can start up and time out. No easy 707 | // way to programmatically add/remove interfaces, so set a timeout of 0. 708 | // Will cover a potential case of inadequate setup leading to an 709 | // immediate change notification. 710 | // 711 | // There is a small race condition from creation -> check that an 712 | // interface change *actually* occurs, so this test may spuriously fail 713 | // extremely rarely. 714 | 715 | let notifier = crate::IfChangeNotifier::new(); 716 | assert!(notifier.is_ok()); 717 | let mut notifier = notifier.unwrap(); 718 | 719 | assert!(notifier.wait(Some(Duration::ZERO)).is_err()); 720 | } 721 | } 722 | -------------------------------------------------------------------------------- /src/posix.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 MaidSafe.net limited. 2 | // 3 | // This SAFE Network Software is licensed to you under the MIT license or the Modified BSD license , at your option. This file may not be copied, 6 | // modified, or distributed except according to those terms. Please review the Licences for the 7 | // specific language governing permissions and limitations relating to use of the SAFE Network 8 | // Software. 9 | 10 | use crate::sockaddr; 11 | use libc::{freeifaddrs, getifaddrs, ifaddrs}; 12 | use std::net::IpAddr; 13 | use std::{io, mem}; 14 | 15 | pub fn do_broadcast(ifaddr: &ifaddrs) -> Option { 16 | // On Linux-like systems, `ifa_ifu` is a union of `*ifa_dstaddr` and `*ifa_broadaddr`. 17 | #[cfg(any( 18 | target_os = "linux", 19 | target_os = "android", 20 | target_os = "l4re", 21 | target_os = "emscripten", 22 | target_os = "fuchsia", 23 | target_os = "hurd", 24 | ))] 25 | let sockaddr = ifaddr.ifa_ifu; 26 | 27 | // On BSD-like and embedded systems, only `ifa_dstaddr` is available. 28 | #[cfg(not(any( 29 | target_os = "linux", 30 | target_os = "android", 31 | target_os = "l4re", 32 | target_os = "emscripten", 33 | target_os = "fuchsia", 34 | target_os = "hurd", 35 | )))] 36 | let sockaddr = ifaddr.ifa_dstaddr; 37 | 38 | sockaddr::to_ipaddr(sockaddr) 39 | } 40 | 41 | pub struct IfAddrs { 42 | inner: *mut ifaddrs, 43 | } 44 | 45 | impl IfAddrs { 46 | #[allow(unsafe_code, clippy::new_ret_no_self)] 47 | pub fn new() -> io::Result { 48 | let mut ifaddrs = mem::MaybeUninit::uninit(); 49 | 50 | unsafe { 51 | if -1 == getifaddrs(ifaddrs.as_mut_ptr()) { 52 | return Err(io::Error::last_os_error()); 53 | } 54 | Ok(Self { 55 | inner: ifaddrs.assume_init(), 56 | }) 57 | } 58 | } 59 | 60 | pub fn iter(&self) -> IfAddrsIterator { 61 | IfAddrsIterator { next: self.inner } 62 | } 63 | } 64 | 65 | impl Drop for IfAddrs { 66 | #[allow(unsafe_code)] 67 | fn drop(&mut self) { 68 | unsafe { 69 | freeifaddrs(self.inner); 70 | } 71 | } 72 | } 73 | 74 | pub struct IfAddrsIterator { 75 | next: *mut ifaddrs, 76 | } 77 | 78 | impl Iterator for IfAddrsIterator { 79 | type Item = ifaddrs; 80 | 81 | #[allow(unsafe_code)] 82 | fn next(&mut self) -> Option { 83 | if self.next.is_null() { 84 | return None; 85 | }; 86 | 87 | Some(unsafe { 88 | let result = *self.next; 89 | self.next = (*self.next).ifa_next; 90 | 91 | result 92 | }) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/posix_not_apple.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::mem; 3 | use std::time::Duration; 4 | 5 | use libc::{ 6 | bind, c_int, c_void, close, recv, setsockopt, sockaddr_nl, socket, socklen_t, ssize_t, timeval, 7 | AF_NETLINK, NETLINK_ROUTE, SOCK_RAW, SOL_SOCKET, SO_RCVTIMEO, 8 | }; 9 | 10 | #[repr(transparent)] 11 | struct NetlinkSocket(c_int); 12 | 13 | impl NetlinkSocket { 14 | fn new() -> io::Result { 15 | Ok(NetlinkSocket(check_io(unsafe { 16 | socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE) 17 | })?)) 18 | } 19 | } 20 | 21 | impl Drop for NetlinkSocket { 22 | fn drop(&mut self) { 23 | unsafe { close(self.0) }; 24 | } 25 | } 26 | 27 | fn check_io(res: c_int) -> io::Result { 28 | if res < 0 { 29 | Err(io::Error::last_os_error()) 30 | } else { 31 | Ok(res) 32 | } 33 | } 34 | 35 | fn check_recv(res: ssize_t) -> io::Result { 36 | if res < 0 { 37 | Err(io::Error::last_os_error()) 38 | } else { 39 | Ok(res) 40 | } 41 | } 42 | 43 | pub struct PosixIfChangeNotifier { 44 | socket: NetlinkSocket, 45 | } 46 | 47 | impl PosixIfChangeNotifier { 48 | pub fn new() -> io::Result { 49 | let socket = NetlinkSocket::new()?; 50 | 51 | let mut sockaddr: sockaddr_nl = unsafe { mem::zeroed() }; 52 | sockaddr.nl_family = AF_NETLINK as u16; 53 | sockaddr.nl_groups = 1; // RTNLGRP_LINK 54 | 55 | check_io(unsafe { 56 | bind( 57 | socket.0, 58 | &sockaddr as *const _ as *const libc::sockaddr, 59 | mem::size_of::() as libc::socklen_t, 60 | ) 61 | })?; 62 | 63 | Ok(Self { socket }) 64 | } 65 | 66 | pub fn wait(&self, timeout: Option) -> io::Result<()> { 67 | // TODO: When MSRV moves beyond Rust 1.66, this can be cleaner as 68 | // let mut socket = UdpSocket::from_raw_fd(socket); 69 | // socket.set_read_timeout(timeout)?; 70 | // socket.recv(&mut buf)?; 71 | 72 | let timeout = if let Some(timeout) = timeout { 73 | let mut t = timeval { 74 | tv_sec: timeout.as_secs().try_into().expect("timeout overflow"), 75 | tv_usec: timeout 76 | .subsec_micros() 77 | .try_into() 78 | .expect("timeout overflow"), 79 | }; 80 | // a timeout of 0 is infinity, so if the requested duration is too 81 | // small, make it nonzero 82 | if t.tv_sec == 0 && t.tv_usec == 0 { 83 | t.tv_usec = 1; 84 | } 85 | t 86 | } else { 87 | timeval { 88 | tv_sec: 0, 89 | tv_usec: 0, 90 | } 91 | }; 92 | 93 | check_io(unsafe { 94 | setsockopt( 95 | self.socket.0, 96 | SOL_SOCKET, 97 | SO_RCVTIMEO, 98 | core::ptr::addr_of!(timeout) as *const _, 99 | mem::size_of::() as socklen_t, 100 | ) 101 | })?; 102 | let mut buf = [0u8; 65536]; 103 | check_recv(unsafe { recv(self.socket.0, buf.as_mut_ptr() as *mut c_void, buf.len(), 0) })?; 104 | 105 | Ok(()) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/sockaddr.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 MaidSafe.net limited. 2 | // 3 | // This SAFE Network Software is licensed to you under the MIT license or the Modified BSD license , at your option. This file may not be copied, 6 | // modified, or distributed except according to those terms. Please review the Licences for the 7 | // specific language governing permissions and limitations relating to use of the SAFE Network 8 | // Software. 9 | 10 | #[cfg(not(windows))] 11 | use libc::{sockaddr, sockaddr_in, sockaddr_in6, AF_INET, AF_INET6}; 12 | use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; 13 | use std::ptr::NonNull; 14 | #[cfg(windows)] 15 | use windows_sys::Win32::Networking::WinSock::{ 16 | AF_INET, AF_INET6, SOCKADDR as sockaddr, SOCKADDR_IN as sockaddr_in, 17 | SOCKADDR_IN6 as sockaddr_in6, 18 | }; 19 | 20 | pub fn to_ipaddr(sockaddr: *const sockaddr) -> Option { 21 | if sockaddr.is_null() { 22 | return None; 23 | } 24 | SockAddr::new(sockaddr)?.as_ipaddr() 25 | } 26 | 27 | // Wrapper around a sockaddr pointer. Guaranteed to not be null. 28 | struct SockAddr { 29 | inner: NonNull, 30 | } 31 | 32 | impl SockAddr { 33 | #[allow(clippy::new_ret_no_self)] 34 | fn new(sockaddr: *const sockaddr) -> Option { 35 | NonNull::new(sockaddr as *mut _).map(|inner| Self { inner }) 36 | } 37 | 38 | #[cfg(not(windows))] 39 | fn as_ipaddr(&self) -> Option { 40 | match self.sockaddr_in() { 41 | Some(SockAddrIn::In(sa)) => { 42 | let b = sa.sin_addr.s_addr.to_ne_bytes(); 43 | Some(IpAddr::V4(Ipv4Addr::new(b[0], b[1], b[2], b[3]))) 44 | } 45 | Some(SockAddrIn::In6(sa)) => { 46 | // Ignore all fe80:: addresses as these are link locals 47 | #[cfg(not(feature = "link-local"))] 48 | if sa.sin6_addr.s6_addr[0] == 0xfe && sa.sin6_addr.s6_addr[1] == 0x80 { 49 | return None; 50 | } 51 | Some(IpAddr::V6(Ipv6Addr::from(sa.sin6_addr.s6_addr))) 52 | } 53 | None => None, 54 | } 55 | } 56 | 57 | #[cfg(windows)] 58 | fn as_ipaddr(&self) -> Option { 59 | match self.sockaddr_in() { 60 | Some(SockAddrIn::In(sa)) => { 61 | let s_addr = unsafe { sa.sin_addr.S_un.S_addr }; 62 | // Ignore all 169.254.x.x addresses as these are not active interfaces 63 | #[cfg(not(feature = "link-local"))] 64 | if s_addr & 65535 == 0xfea9 { 65 | return None; 66 | } 67 | let b = s_addr.to_ne_bytes(); 68 | Some(IpAddr::V4(Ipv4Addr::new(b[0], b[1], b[2], b[3]))) 69 | } 70 | Some(SockAddrIn::In6(sa)) => { 71 | let s6_addr = unsafe { sa.sin6_addr.u.Byte }; 72 | // Ignore all fe80:: addresses as these are link locals 73 | #[cfg(not(feature = "link-local"))] 74 | if s6_addr[0] == 0xfe && s6_addr[1] == 0x80 { 75 | return None; 76 | } 77 | Some(IpAddr::V6(Ipv6Addr::from(s6_addr.clone()))) 78 | } 79 | None => None, 80 | } 81 | } 82 | 83 | fn sockaddr_in(&self) -> Option { 84 | const AF_INET_U32: u32 = AF_INET as u32; 85 | const AF_INET6_U32: u32 = AF_INET6 as u32; 86 | 87 | match self.sa_family() { 88 | AF_INET_U32 => Some(SockAddrIn::In(self.sa_in())), 89 | AF_INET6_U32 => Some(SockAddrIn::In6(self.sa_in6())), 90 | _ => None, 91 | } 92 | } 93 | 94 | #[allow(unsafe_code)] 95 | fn sa_family(&self) -> u32 { 96 | unsafe { u32::from(self.inner.as_ref().sa_family) } 97 | } 98 | 99 | #[allow(unsafe_code)] 100 | #[allow(clippy::cast_ptr_alignment)] 101 | fn sa_in(&self) -> sockaddr_in { 102 | unsafe { *(self.inner.as_ptr() as *const sockaddr_in) } 103 | } 104 | 105 | #[allow(unsafe_code)] 106 | #[allow(clippy::cast_ptr_alignment)] 107 | fn sa_in6(&self) -> sockaddr_in6 { 108 | unsafe { *(self.inner.as_ptr() as *const sockaddr_in6) } 109 | } 110 | } 111 | 112 | enum SockAddrIn { 113 | In(sockaddr_in), 114 | In6(sockaddr_in6), 115 | } 116 | -------------------------------------------------------------------------------- /src/windows.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2018 MaidSafe.net limited. 2 | // 3 | // This SAFE Network Software is licensed to you under the MIT license or the Modified BSD license , at your option. This file may not be copied, 6 | // modified, or distributed except according to those terms. Please review the Licences for the 7 | // specific language governing permissions and limitations relating to use of the SAFE Network 8 | // Software. 9 | 10 | use std::ffi::{c_void, CStr}; 11 | 12 | use std::sync::mpsc::{channel, Receiver, RecvTimeoutError, Sender}; 13 | use std::time::Duration; 14 | use std::{io, ptr}; 15 | use windows_sys::Win32::Foundation::{ERROR_BUFFER_OVERFLOW, ERROR_SUCCESS, HANDLE}; 16 | use windows_sys::Win32::NetworkManagement::IpHelper::{ 17 | CancelMibChangeNotify2, GetAdaptersAddresses, NotifyIpInterfaceChange, GAA_FLAG_INCLUDE_PREFIX, 18 | GAA_FLAG_SKIP_ANYCAST, GAA_FLAG_SKIP_DNS_SERVER, GAA_FLAG_SKIP_MULTICAST, 19 | IP_ADAPTER_ADDRESSES_LH, IP_ADAPTER_PREFIX_XP, IP_ADAPTER_UNICAST_ADDRESS_LH, 20 | MIB_IPINTERFACE_ROW, MIB_NOTIFICATION_TYPE, 21 | }; 22 | use windows_sys::Win32::Networking::WinSock::AF_UNSPEC; 23 | use windows_sys::Win32::System::Memory::{ 24 | GetProcessHeap, HeapAlloc, HeapFree, HEAP_NONE, HEAP_ZERO_MEMORY, 25 | }; 26 | 27 | #[repr(transparent)] 28 | pub struct IpAdapterAddresses(*const IP_ADAPTER_ADDRESSES_LH); 29 | 30 | impl IpAdapterAddresses { 31 | #[allow(unsafe_code)] 32 | pub fn name(&self) -> String { 33 | let len = (0..) 34 | .take_while(|&i| unsafe { *(*self.0).FriendlyName.offset(i) } != 0) 35 | .count(); 36 | let slice = unsafe { std::slice::from_raw_parts((*self.0).FriendlyName, len) }; 37 | String::from_utf16_lossy(slice) 38 | } 39 | 40 | #[allow(unsafe_code)] 41 | pub fn adapter_name(&self) -> String { 42 | unsafe { CStr::from_ptr((*self.0).AdapterName as _) } 43 | .to_string_lossy() 44 | .into_owned() 45 | } 46 | 47 | pub fn ipv4_index(&self) -> Option { 48 | let if_index = unsafe { (*self.0).Anonymous1.Anonymous.IfIndex }; 49 | if if_index == 0 { 50 | None 51 | } else { 52 | Some(if_index) 53 | } 54 | } 55 | 56 | pub fn ipv6_index(&self) -> Option { 57 | let if_index = unsafe { (*self.0).Ipv6IfIndex }; 58 | if if_index == 0 { 59 | None 60 | } else { 61 | Some(if_index) 62 | } 63 | } 64 | 65 | pub fn prefixes(&self) -> PrefixesIterator { 66 | PrefixesIterator { 67 | _head: unsafe { &*self.0 }, 68 | next: unsafe { (*self.0).FirstPrefix }, 69 | } 70 | } 71 | 72 | pub fn unicast_addresses(&self) -> UnicastAddressesIterator { 73 | UnicastAddressesIterator { 74 | _head: unsafe { &*self.0 }, 75 | next: unsafe { (*self.0).FirstUnicastAddress }, 76 | } 77 | } 78 | } 79 | 80 | pub struct IfAddrs { 81 | inner: IpAdapterAddresses, 82 | } 83 | 84 | impl IfAddrs { 85 | #[allow(unsafe_code)] 86 | pub fn new() -> io::Result { 87 | let mut buffersize = 15000; 88 | let mut ifaddrs: *mut IP_ADAPTER_ADDRESSES_LH; 89 | 90 | loop { 91 | unsafe { 92 | ifaddrs = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, buffersize as _) 93 | as *mut IP_ADAPTER_ADDRESSES_LH; 94 | if ifaddrs.is_null() { 95 | panic!("Failed to allocate buffer in get_if_addrs()"); 96 | } 97 | 98 | let retcode = GetAdaptersAddresses( 99 | 0, 100 | GAA_FLAG_SKIP_ANYCAST 101 | | GAA_FLAG_SKIP_MULTICAST 102 | | GAA_FLAG_SKIP_DNS_SERVER 103 | | GAA_FLAG_INCLUDE_PREFIX, 104 | ptr::null_mut(), 105 | ifaddrs, 106 | &mut buffersize, 107 | ); 108 | 109 | match retcode { 110 | ERROR_SUCCESS => break, 111 | ERROR_BUFFER_OVERFLOW => { 112 | HeapFree(GetProcessHeap(), HEAP_NONE, ifaddrs as _); 113 | buffersize *= 2; 114 | continue; 115 | } 116 | _ => { 117 | HeapFree(GetProcessHeap(), HEAP_NONE, ifaddrs as _); 118 | return Err(io::Error::last_os_error()); 119 | } 120 | } 121 | } 122 | } 123 | 124 | Ok(Self { 125 | inner: IpAdapterAddresses(ifaddrs), 126 | }) 127 | } 128 | 129 | pub fn iter(&self) -> IfAddrsIterator { 130 | IfAddrsIterator { 131 | _head: self, 132 | next: self.inner.0, 133 | } 134 | } 135 | } 136 | 137 | impl Drop for IfAddrs { 138 | #[allow(unsafe_code)] 139 | fn drop(&mut self) { 140 | unsafe { 141 | HeapFree(GetProcessHeap(), HEAP_NONE, self.inner.0 as _); 142 | } 143 | } 144 | } 145 | 146 | pub struct IfAddrsIterator<'a> { 147 | _head: &'a IfAddrs, 148 | next: *const IP_ADAPTER_ADDRESSES_LH, 149 | } 150 | 151 | impl<'a> Iterator for IfAddrsIterator<'a> { 152 | type Item = IpAdapterAddresses; 153 | 154 | #[allow(unsafe_code)] 155 | fn next(&mut self) -> Option { 156 | if self.next.is_null() { 157 | return None; 158 | }; 159 | 160 | Some(unsafe { 161 | let result = &*self.next; 162 | self.next = (*self.next).Next; 163 | 164 | IpAdapterAddresses(result) 165 | }) 166 | } 167 | } 168 | 169 | pub struct PrefixesIterator<'a> { 170 | _head: &'a IP_ADAPTER_ADDRESSES_LH, 171 | next: *const IP_ADAPTER_PREFIX_XP, 172 | } 173 | 174 | impl<'a> Iterator for PrefixesIterator<'a> { 175 | type Item = &'a IP_ADAPTER_PREFIX_XP; 176 | 177 | #[allow(unsafe_code)] 178 | fn next(&mut self) -> Option { 179 | if self.next.is_null() { 180 | return None; 181 | }; 182 | 183 | Some(unsafe { 184 | let result = &*self.next; 185 | self.next = (*self.next).Next; 186 | 187 | result 188 | }) 189 | } 190 | } 191 | 192 | pub struct UnicastAddressesIterator<'a> { 193 | _head: &'a IP_ADAPTER_ADDRESSES_LH, 194 | next: *const IP_ADAPTER_UNICAST_ADDRESS_LH, 195 | } 196 | 197 | impl<'a> Iterator for UnicastAddressesIterator<'a> { 198 | type Item = &'a IP_ADAPTER_UNICAST_ADDRESS_LH; 199 | 200 | #[allow(unsafe_code)] 201 | fn next(&mut self) -> Option { 202 | if self.next.is_null() { 203 | return None; 204 | }; 205 | 206 | Some(unsafe { 207 | let result = &*self.next; 208 | self.next = (*self.next).Next; 209 | 210 | result 211 | }) 212 | } 213 | } 214 | 215 | pub struct WindowsIfChangeNotifier { 216 | handle: HANDLE, 217 | // maintain constant memory address for callback fn 218 | tx: *mut Sender<()>, 219 | rx: Receiver<()>, 220 | } 221 | 222 | impl WindowsIfChangeNotifier { 223 | pub fn new() -> io::Result { 224 | let (tx, rx) = channel(); 225 | let mut ret = Self { 226 | handle: std::ptr::null_mut(), 227 | tx: Box::into_raw(Box::new(tx)), 228 | rx, 229 | }; 230 | let stat = unsafe { 231 | // Notes on the function used here and alternatives that were 232 | // considered: 233 | // 234 | // NotifyAddrChange works pretty well, but only for IPv4, and the 235 | // API itself is a little awkward, requiring overlapped IO 236 | // 237 | // NotifyRouteChange2 doesn't catch interfaces going away (e.g. 238 | // unplugging ethernet, going into airplane mode) 239 | // 240 | // WlanRegisterNotification is only for WiFi 241 | // 242 | // Monitoring changes to MSFT_NetAdapter is possible, but requires 243 | // WMI/COM, which is undesirable 244 | // 245 | // NotifyIpInterfaceChange produces several spurious messages 246 | // (mostly related to WiFi speed, it seems), but they aren't too 247 | // frequent (at most, I've seen a batch of 3 every 10 seconds), so 248 | // don't pose a performance concern 249 | NotifyIpInterfaceChange( 250 | AF_UNSPEC, 251 | Some(if_change_callback), 252 | ret.tx as *const c_void, 253 | 0, 254 | &mut ret.handle, 255 | ) 256 | }; 257 | if stat != 0 { 258 | Err(io::Error::from_raw_os_error(stat as i32)) 259 | } else { 260 | Ok(ret) 261 | } 262 | } 263 | 264 | pub fn wait(&self, timeout: Option) -> io::Result<()> { 265 | if let Some(timeout) = timeout { 266 | self.rx.recv_timeout(timeout) 267 | } else { 268 | self.rx.recv().map_err(RecvTimeoutError::from) 269 | } 270 | .map_err(|_| io::Error::new(io::ErrorKind::WouldBlock, "Timed out")) 271 | } 272 | } 273 | 274 | impl Drop for WindowsIfChangeNotifier { 275 | fn drop(&mut self) { 276 | if !self.handle.is_null() { 277 | unsafe { CancelMibChangeNotify2(self.handle) }; 278 | } 279 | unsafe { drop(Box::from_raw(self.tx)) }; 280 | } 281 | } 282 | 283 | unsafe extern "system" fn if_change_callback( 284 | ctx: *const c_void, 285 | _row: *const MIB_IPINTERFACE_ROW, 286 | _notificationtype: MIB_NOTIFICATION_TYPE, 287 | ) { 288 | if let Some(tx) = (ctx as *const Sender<()>).as_ref() { 289 | tx.send(()).ok(); 290 | }; 291 | 292 | // note: `row` not used, as for all changes that we care for (interface 293 | // add/remove), all the member values are 0, so it's useless. 294 | } 295 | --------------------------------------------------------------------------------