├── examples └── demo.rs ├── src ├── test.rs ├── lib.rs ├── target │ ├── unix │ │ ├── ffi │ │ │ ├── mod.rs │ │ │ └── lladdr.c │ │ └── mod.rs │ ├── mod.rs │ ├── getifaddrs.rs │ ├── linux.rs │ └── windows.rs ├── utils │ ├── hex.rs │ ├── ffialloc.rs │ ├── mod.rs │ └── unix.rs ├── error.rs └── interface.rs ├── .github ├── workflows │ ├── build.yml │ ├── test.yml │ ├── clippy.yml │ ├── fmt.yml │ └── release.yml └── PULL_REQUEST_TEMPLATE.md ├── CHANGELOG.md ├── .gitignore ├── LICENSE-MIT ├── Cargo.toml ├── CONTRIBUTING.md ├── CODE_OF_CONDUCT.md ├── README.md ├── .cirrus.yml ├── rustfmt.toml └── LICENSE-APACHE /examples/demo.rs: -------------------------------------------------------------------------------- 1 | use network_interface::{NetworkInterface, NetworkInterfaceConfig}; 2 | 3 | fn main() { 4 | let interfaces = NetworkInterface::show().unwrap(); 5 | println!("{interfaces:#?}"); 6 | } 7 | -------------------------------------------------------------------------------- /src/test.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused_imports)] 2 | use crate::{NetworkInterface, NetworkInterfaceConfig}; 3 | 4 | #[test] 5 | fn show_network_interfaces() { 6 | let network_interfaces = NetworkInterface::show().unwrap(); 7 | 8 | println!("{network_interfaces:#?}"); 9 | assert!(network_interfaces.len() > 1); 10 | } 11 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | mod error; 2 | mod interface; 3 | mod target; 4 | mod test; 5 | 6 | mod utils; 7 | 8 | pub use error::*; 9 | pub use interface::*; 10 | 11 | pub type Result = std::result::Result; 12 | 13 | pub trait NetworkInterfaceConfig { 14 | /// List system's network interfaces configuration 15 | fn show() -> Result>; 16 | } 17 | -------------------------------------------------------------------------------- /src/target/unix/ffi/mod.rs: -------------------------------------------------------------------------------- 1 | use libc::ifaddrs; 2 | 3 | #[cfg(any( 4 | target_os = "macos", 5 | target_os = "ios", 6 | target_os = "tvos", 7 | target_os = "freebsd", 8 | target_os = "openbsd", 9 | target_os = "netbsd", 10 | target_os = "dragonfly", 11 | target_os = "illumos", 12 | ))] 13 | extern "C" { 14 | pub fn lladdr(ptr: *mut ifaddrs) -> *const u8; 15 | } 16 | -------------------------------------------------------------------------------- /src/target/unix/ffi/lladdr.c: -------------------------------------------------------------------------------- 1 | #if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__)) 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | uint8_t* lladdr(struct ifaddrs* ifap) { 12 | return (uint8_t *)LLADDR((struct sockaddr_dl *)(ifap)->ifa_addr); 13 | } 14 | #endif 15 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | 9 | jobs: 10 | build: 11 | name: Builds on ${{ matrix.os }} 12 | runs-on: ${{ matrix.os }} 13 | strategy: 14 | matrix: 15 | os: [ 16 | ubuntu-latest, 17 | windows-latest, 18 | macOS-latest 19 | ] 20 | 21 | steps: 22 | - uses: actions/checkout@v1 23 | 24 | - uses: actions-rs/toolchain@v1 25 | with: 26 | profile: minimal 27 | toolchain: stable 28 | 29 | - name: Build 30 | run: cargo build --release 31 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [main] 8 | ### Changed 9 | - Changed type of `NetworkInterface::addr` to `Vec` 10 | 11 | ## [0.1.2-beta] - 2021-10-04 12 | ### Fixed 13 | - Fixed issue where `NetworkInterface` is not being exported 14 | 15 | ## [0.1.1-beta] - 2021-10-03 16 | ### Fixed 17 | - Fixed issue where Linked List doesn't advance on Windows 18 | 19 | ## [0.1.0-beta] - 2021-09-24 20 | ### Added 21 | - First public beta release 22 | -------------------------------------------------------------------------------- /src/utils/hex.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | pub(crate) struct HexSlice<'a>(&'a [u8]); 4 | 5 | impl<'a> HexSlice<'a> { 6 | pub(crate) fn new(data: &'a T) -> HexSlice<'a> 7 | where 8 | T: ?Sized + AsRef<[u8]> + 'a, 9 | { 10 | HexSlice(data.as_ref()) 11 | } 12 | } 13 | 14 | impl Display for HexSlice<'_> { 15 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 16 | for (index, byte) in self.0.iter().enumerate() { 17 | if index > 0 { 18 | write!(f, ":{byte:02X}")?; 19 | } else { 20 | write!(f, "{byte:02X}")?; 21 | } 22 | } 23 | Ok(()) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/utils/ffialloc.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::c_void; 2 | 3 | pub(crate) struct FFIAlloc { 4 | ptr: *mut T, 5 | } 6 | 7 | impl FFIAlloc { 8 | pub fn alloc(buffer_size: usize) -> Option { 9 | let ptr = unsafe { libc::malloc(buffer_size) as *mut T }; 10 | if ptr.is_null() { 11 | None 12 | } else { 13 | Some(Self { ptr }) 14 | } 15 | } 16 | 17 | pub const fn as_ptr(&self) -> *const T { 18 | self.ptr 19 | } 20 | 21 | pub const fn as_mut_ptr(&self) -> *mut T { 22 | self.ptr 23 | } 24 | } 25 | 26 | impl Drop for FFIAlloc { 27 | fn drop(&mut self) { 28 | unsafe { libc::free(self.ptr as *mut c_void) } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(windows)] 2 | pub mod hex; 3 | 4 | #[cfg(any( 5 | target_os = "android", 6 | target_os = "linux", 7 | target_os = "ios", 8 | target_os = "macos", 9 | target_os = "tvos", 10 | target_os = "freebsd", 11 | target_os = "openbsd", 12 | target_os = "netbsd", 13 | target_os = "dragonfly", 14 | target_os = "illumos", 15 | ))] 16 | mod unix; 17 | 18 | #[cfg(windows)] 19 | pub(crate) mod ffialloc; 20 | 21 | #[cfg(any( 22 | target_os = "android", 23 | target_os = "linux", 24 | target_os = "ios", 25 | target_os = "macos", 26 | target_os = "tvos", 27 | target_os = "freebsd", 28 | target_os = "openbsd", 29 | target_os = "netbsd", 30 | target_os = "dragonfly", 31 | target_os = "illumos", 32 | ))] 33 | pub use unix::*; 34 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::string::{FromUtf16Error, FromUtf8Error}; 2 | 3 | #[derive(thiserror::Error, Debug)] 4 | pub enum Error { 5 | #[error("Failed to execute `{0}`. Received error code `{1}`")] 6 | GetIfAddrsError(String, i32), 7 | #[error("Failed to execute `{0}`. Received error code `{1}`")] 8 | GetIfNameError(String, u32), 9 | #[error("Failed to parse bytes into UTF-8 characters. `{0}`")] 10 | ParseUtf8Error(FromUtf8Error), 11 | #[error("Failed to parse bytes into UTF-16 characters. `{0}`")] 12 | ParseUtf16Error(FromUtf16Error), 13 | } 14 | 15 | impl From for Error { 16 | fn from(error: FromUtf8Error) -> Self { 17 | Error::ParseUtf8Error(error) 18 | } 19 | } 20 | 21 | impl From for Error { 22 | fn from(error: FromUtf16Error) -> Self { 23 | Error::ParseUtf16Error(error) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Application Related # 2 | ####################### 3 | .vscode/ 4 | .cargo/ 5 | /target 6 | 7 | # Compiled source # 8 | ################### 9 | *.com 10 | *.class 11 | *.dll 12 | *.exe 13 | *.o 14 | *.so 15 | bundle 16 | 17 | # Packages # 18 | ############ 19 | # it's better to unpack these files and commit the raw source 20 | # git has its own built in compression methods 21 | *.7z 22 | *.dmg 23 | *.gz 24 | *.iso 25 | *.jar 26 | *.rar 27 | *.tar 28 | *.zip 29 | 30 | # Logs and databases # 31 | ###################### 32 | *.log 33 | *.sqlite 34 | 35 | # OS generated files # 36 | ###################### 37 | .DS_Store 38 | .DS_Store? 39 | ._* 40 | .Spotlight-V100 41 | .Trashes 42 | ehthumbs.db 43 | Thumbs.db 44 | 45 | # Rust # 46 | ######## 47 | # Ignores `Cargo.lock` as recommended here: 48 | # https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html#cargotoml-vs-cargolock 49 | Cargo.lock 50 | -------------------------------------------------------------------------------- /src/target/mod.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused_imports)] 2 | #[cfg(any(target_os = "android", target_os = "linux"))] 3 | mod linux; 4 | 5 | #[allow(unused_imports)] 6 | #[cfg(any(target_os = "android", target_os = "linux"))] 7 | pub use linux::*; 8 | 9 | #[cfg(any( 10 | target_os = "macos", 11 | target_os = "ios", 12 | target_os = "tvos", 13 | target_os = "freebsd", 14 | target_os = "openbsd", 15 | target_os = "netbsd", 16 | target_os = "dragonfly", 17 | target_os = "illumos", 18 | ))] 19 | mod unix; 20 | 21 | #[cfg(any( 22 | target_os = "macos", 23 | target_os = "ios", 24 | target_os = "tvos", 25 | target_os = "freebsd", 26 | target_os = "openbsd", 27 | target_os = "netbsd", 28 | target_os = "dragonfly", 29 | target_os = "illumos", 30 | ))] 31 | pub use unix::*; 32 | 33 | #[allow(unused_imports)] 34 | #[cfg(target_os = "windows")] 35 | mod windows; 36 | 37 | #[allow(unused_imports)] 38 | #[cfg(target_os = "windows")] 39 | pub use self::windows::*; 40 | 41 | #[cfg(not(target_os = "windows"))] 42 | mod getifaddrs; 43 | 44 | #[cfg(not(target_os = "windows"))] 45 | pub use getifaddrs::*; 46 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 Esteban Borai and Contributors 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | test: 10 | name: Runs "cargo test" on ${{ matrix.os }} 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | os: [ 15 | ubuntu-latest, 16 | windows-latest, 17 | macOS-latest 18 | ] 19 | 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v4 23 | 24 | - uses: actions-rs/toolchain@v1 25 | with: 26 | profile: minimal 27 | toolchain: stable 28 | override: true 29 | 30 | - name: Cache .cargo and target 31 | uses: actions/cache@v4 32 | with: 33 | path: | 34 | ~/.cargo 35 | ./target 36 | key: ${{ runner.os }}-cargo-fmt-${{ hashFiles('**/Cargo.lock') }} 37 | restore-keys: | 38 | ${{ runner.os }}-cargo-fmt-${{ hashFiles('**/Cargo.lock') }} 39 | ${{ runner.os }}-cargo-fmt 40 | 41 | - name: Run tests 42 | uses: actions-rs/cargo@v1 43 | with: 44 | command: test 45 | args: -- --nocapture 46 | -------------------------------------------------------------------------------- /.github/workflows/clippy.yml: -------------------------------------------------------------------------------- 1 | name: clippy 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | clippy: 10 | name: Runs "cargo clippy" on ${{ matrix.os }} 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | os: [ 15 | ubuntu-latest, 16 | windows-latest, 17 | macOS-latest 18 | ] 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | 23 | - uses: actions-rs/toolchain@v1 24 | with: 25 | profile: minimal 26 | toolchain: stable 27 | override: true 28 | components: clippy 29 | 30 | - name: Cache .cargo and target 31 | uses: actions/cache@v4 32 | with: 33 | path: | 34 | ~/.cargo 35 | ./target 36 | key: ${{ runner.os }}-cargo-clippy-${{ hashFiles('**/Cargo.lock') }} 37 | restore-keys: | 38 | ${{ runner.os }}-cargo-clippy-${{ hashFiles('**/Cargo.lock') }} 39 | ${{ runner.os }}-cargo-clippy 40 | 41 | - name: cargo clippy 42 | uses: actions-rs/cargo@v1 43 | with: 44 | command: clippy 45 | args: -- -D warnings 46 | -------------------------------------------------------------------------------- /.github/workflows/fmt.yml: -------------------------------------------------------------------------------- 1 | name: fmt 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | fmt: 10 | name: Runs "cargo fmt" on ${{ matrix.os }} 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | os: [ 15 | ubuntu-latest, 16 | windows-latest, 17 | macOS-latest 18 | ] 19 | 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v4 23 | 24 | - uses: actions-rs/toolchain@v1 25 | with: 26 | profile: minimal 27 | toolchain: stable 28 | override: true 29 | components: rustfmt 30 | 31 | - name: Cache .cargo and target 32 | uses: actions/cache@v4 33 | with: 34 | path: | 35 | ~/.cargo 36 | ./target 37 | key: ${{ runner.os }}-cargo-fmt-${{ hashFiles('**/Cargo.lock') }} 38 | restore-keys: | 39 | ${{ runner.os }}-cargo-fmt-${{ hashFiles('**/Cargo.lock') }} 40 | ${{ runner.os }}-cargo-fmt 41 | 42 | - name: Run fmt 43 | uses: actions-rs/cargo@v1 44 | with: 45 | command: fmt 46 | args: --all -- --check 47 | -------------------------------------------------------------------------------- /src/target/getifaddrs.rs: -------------------------------------------------------------------------------- 1 | use std::mem; 2 | use crate::{Error, Result}; 3 | 4 | pub struct IfAddrIterator { 5 | base: *mut libc::ifaddrs, 6 | next: *mut libc::ifaddrs, 7 | } 8 | 9 | impl Iterator for IfAddrIterator { 10 | type Item = libc::ifaddrs; 11 | 12 | fn next(&mut self) -> Option { 13 | match unsafe { self.next.as_ref() } { 14 | Some(ifaddrs) => { 15 | self.next = ifaddrs.ifa_next; 16 | Some(ifaddrs.to_owned()) 17 | } 18 | None => None, 19 | } 20 | } 21 | } 22 | 23 | impl Drop for IfAddrIterator { 24 | fn drop(&mut self) { 25 | unsafe { libc::freeifaddrs(self.base) } 26 | } 27 | } 28 | 29 | pub fn getifaddrs() -> Result { 30 | let mut addr = mem::MaybeUninit::<*mut libc::ifaddrs>::uninit(); 31 | match unsafe { libc::getifaddrs(addr.as_mut_ptr()) } { 32 | 0 => Ok(IfAddrIterator { 33 | base: unsafe { addr.assume_init() }, 34 | next: unsafe { addr.assume_init() }, 35 | }), 36 | getifaddrs_result => Err(Error::GetIfAddrsError( 37 | String::from("getifaddrs"), 38 | getifaddrs_result, 39 | )), 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "network-interface" 3 | description = "Retrieve system's Network Interfaces on Linux, FreeBSD, macOS and Windows on a standarized manner" 4 | version = "2.0.4" 5 | repository = "https://github.com/EstebanBorai/network-interface" 6 | categories = ["web-programming", "network-programming"] 7 | homepage = "https://github.com/EstebanBorai/network-interface" 8 | keywords = ["network", "interfaces", "ip", "web", "network"] 9 | license = "MIT OR Apache-2.0" 10 | authors = ["Esteban Borai "] 11 | edition = "2018" 12 | readme = "README.md" 13 | 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 15 | [dependencies] 16 | serde = { version = "1.0.183", features = ["derive"], optional = true} 17 | thiserror = "2.0" 18 | 19 | [target.'cfg(target_family = "unix")'.build-dependencies] 20 | cc = "1.0.73" 21 | 22 | [target.'cfg(any(target_os = "android", target_os = "linux"))'.dependencies] 23 | libc = "0.2.101" 24 | 25 | [target.'cfg(any(target_os = "ios", target_os = "macos", target_os = "tvos", target_os = "freebsd", target_os = "openbsd", target_os = "netbsd", target_os = "dragonfly", target_os = "illumos"))'.dependencies] 26 | libc = "0.2.101" 27 | 28 | [target.'cfg(target_os = "windows")'.dependencies] 29 | libc = "0.2.101" 30 | winapi = {version = "0.3", features = ["ws2def", "ws2ipdef", "netioapi", "iphlpapi", "iptypes", "ntdef", "winerror"] } 31 | 32 | 33 | [features] 34 | serde = ["dep:serde"] 35 | -------------------------------------------------------------------------------- /src/utils/unix.rs: -------------------------------------------------------------------------------- 1 | use std::net::{Ipv4Addr, Ipv6Addr}; 2 | use libc::{in6_addr, in_addr, sockaddr_in, sockaddr_in6}; 3 | 4 | use crate::Result; 5 | use crate::interface::Netmask; 6 | 7 | /// Creates a `Ipv4Addr` from a (Unix) `in_addr` taking in account 8 | /// the CPU endianess to avoid having twisted IP addresses. 9 | /// 10 | /// refer: https://github.com/rust-lang/rust/issues/48819 11 | pub fn ipv4_from_in_addr(internet_address: &in_addr) -> Result { 12 | let mut ip_addr = Ipv4Addr::from(internet_address.s_addr); 13 | 14 | if cfg!(target_endian = "little") { 15 | // due to a difference on how bytes are arranged on a 16 | // single word of memory by the CPU, swap bytes based 17 | // on CPU endianess to avoid having twisted IP addresses 18 | ip_addr = Ipv4Addr::from(internet_address.s_addr.swap_bytes()); 19 | } 20 | 21 | Ok(ip_addr) 22 | } 23 | 24 | /// Creates a `Ipv6Addr` from a (Unix) `in6_addr` 25 | pub fn ipv6_from_in6_addr(internet_address: &in6_addr) -> Result { 26 | let ip_addr = Ipv6Addr::from(internet_address.s6_addr); 27 | 28 | Ok(ip_addr) 29 | } 30 | 31 | /// Retrieves the Netmask from a `ifaddrs` instance for a network interface 32 | /// from the AF_INET (IPv4) family. 33 | pub fn make_ipv4_netmask(netifa: &libc::ifaddrs) -> Netmask { 34 | let sockaddr = netifa.ifa_netmask; 35 | 36 | if sockaddr.is_null() { 37 | return None; 38 | } 39 | 40 | let socket_addr = sockaddr as *mut sockaddr_in; 41 | let internet_address = unsafe { (*socket_addr).sin_addr }; 42 | 43 | ipv4_from_in_addr(&internet_address).ok() 44 | } 45 | 46 | /// Retrieves the Netmask from a `ifaddrs` instance for a network interface 47 | /// from the AF_INET6 (IPv6) family. 48 | pub fn make_ipv6_netmask(netifa: &libc::ifaddrs) -> Netmask { 49 | let sockaddr = netifa.ifa_netmask; 50 | 51 | if sockaddr.is_null() { 52 | return None; 53 | } 54 | 55 | let socket_addr = sockaddr as *mut sockaddr_in6; 56 | let internet_address = unsafe { (*socket_addr).sin6_addr }; 57 | 58 | // Ignore local addresses 59 | if internet_address.s6_addr[0] == 0xfe && internet_address.s6_addr[1] == 0x80 { 60 | return None; 61 | } 62 | 63 | ipv6_from_in6_addr(&internet_address).ok() 64 | } 65 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | * [Code of Conduct](#code-of-conduct) 4 | * [Pull Requests](#pull-requests) 5 | * [Developer's Certificate of Origin 1.1](#developers-certificate-of-origin) 6 | 7 | ## [Code of Conduct](./CODE_OF_CONDUCT.md) 8 | 9 | All individuals participating will be expected to abide by the Code of Conduct. 10 | 11 | Violating the Code of Conduct will result in action ranging from a conversation 12 | about behavior to being permanently banned from the 13 | project. 14 | 15 | ### The Spirit of the law 16 | 17 | Not all interactions that require remediation are clear violations 18 | of the Code of Conduct. Project maintainers will take appropriate 19 | action, when neccessary, to ensure the zwnbsp community is a space 20 | where individuals can comfortably collaborate and bring their 21 | entire selves. Unfortunately, if bringing your entire self is 22 | infringing on others from doing the same, you may be asked to leave. 23 | 24 | ## Pull Requests 25 | 26 | If you would like to make a change open a Pull Request. 27 | 28 | Project maintainers will do their best to review and land code 29 | in a reasonable time frame. 30 | 31 | All Pull Requests require CI to be green to land. 32 | 33 | It is possible that some Pull Requests may be rejected. The project 34 | maintainers will make best effort to explain why a Pull Request is 35 | rejected in a timely manner. 36 | 37 | ## Developer's Certificate of Origin 1.1 38 | 39 | By making a contribution to this project, I certify that: 40 | 41 | * (a) The contribution was created in whole or in part by me and I 42 | have the right to submit it under the open source license 43 | indicated in the file; or 44 | 45 | * (b) The contribution is based upon previous work that, to the best 46 | of my knowledge, is covered under an appropriate open source 47 | license and I have the right under that license to submit that 48 | work with modifications, whether created in whole or in part 49 | by me, under the same open source license (unless I am 50 | permitted to submit under a different license), as indicated 51 | in the file; or 52 | 53 | * (c) The contribution was provided directly to me by some other 54 | person who certified (a), (b) or (c) and I have not modified 55 | it. 56 | 57 | * (d) I understand and agree that this project and the contribution 58 | are public and that a record of the contribution (including all 59 | personal information I submit with it, including my sign-off) is 60 | maintained indefinitely and may be redistributed consistent with 61 | this project or the open source license(s) involved. 62 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | type: choice 8 | required: true 9 | description: 'Version number to bump' 10 | options: 11 | - patch 12 | - minor 13 | - major 14 | 15 | permissions: 16 | contents: write 17 | issues: write 18 | pull-requests: write 19 | 20 | jobs: 21 | publish-dry-run: 22 | name: "Runs cargo publish --dry-run" 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v1 26 | 27 | - uses: actions-rs/toolchain@v1 28 | with: 29 | profile: minimal 30 | toolchain: stable 31 | 32 | - name: publish crate 33 | run: cargo publish --dry-run 34 | 35 | release: 36 | name: Create Release 37 | needs: publish-dry-run 38 | runs-on: ubuntu-latest 39 | steps: 40 | - name: Checkout code 41 | uses: actions/checkout@v4 42 | 43 | - name: Setup Rust 44 | uses: actions-rs/toolchain@v1 45 | with: 46 | profile: minimal 47 | toolchain: stable 48 | override: true 49 | 50 | - name: Install `cargo-edit` 51 | run: cargo install cargo-edit 52 | 53 | - id: cargo-set-version 54 | name: Set Version 55 | run: cargo set-version --bump ${{ inputs.version }} 56 | 57 | - name: Set Crate Version as Environment Variable 58 | run: | 59 | CARGO_TOML_VERSION=$(awk -F ' = ' '$1 ~ /version/ { gsub(/[\"]/, "", $2); printf("%s",$2) }' Cargo.toml) 60 | echo "CRATE_VERSION=$CARGO_TOML_VERSION" >> $GITHUB_ENV 61 | 62 | - name: Create Commit 63 | run: | 64 | git config --global user.name 'github-actions[bot]' 65 | git config --global user.email 'github-actions[bot]@users.noreply.github.com' 66 | git add . 67 | git commit -m "chore: bump version to v$CRATE_VERSION" 68 | git push origin main --follow-tags 69 | 70 | - name: Login to Crates.io 71 | run: cargo login ${CRATES_IO_TOKEN} 72 | env: 73 | CRATES_IO_TOKEN: ${{ secrets.CRATES_IO_TOKEN }} 74 | 75 | - name: Publish crate 76 | run: cargo publish 77 | 78 | - name: Create Release 79 | id: create_release 80 | uses: actions/github-script@v5 81 | with: 82 | script: | 83 | await github.request(`POST /repos/${{ github.repository }}/releases`, { 84 | tag_name: "v${{ env.CRATE_VERSION }}", 85 | generate_release_notes: true 86 | }); 87 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting estebanborai@gmail.com. 58 | 59 | All complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

network-interface

3 |

4 | Retrieve system's Network Interfaces/Adapters on Android, FreeBSD, Linux, macOS, iOS and Windows 5 | on a standarized manner 6 |

7 |
8 | 9 |
10 | 11 | [![Crates.io](https://img.shields.io/crates/v/network-interface.svg)](https://crates.io/crates/network-interface) 12 | [![Documentation](https://docs.rs/network-interface/badge.svg)](https://docs.rs/network-interface) 13 | ![Build](https://github.com/EstebanBorai/network-interface/workflows/build/badge.svg) 14 | ![Clippy](https://github.com/EstebanBorai/network-interface/workflows/clippy/badge.svg) 15 | ![Formatter](https://github.com/EstebanBorai/network-interface/workflows/fmt/badge.svg) 16 | 17 |
18 | 19 | > This crate is under development, feel free to contribute on [GitHub](https://github.com/EstebanBorai/network-interface). API and implementation is subject to change. 20 | 21 | The main goal of `network-interface` crate is to retrieve system's Network 22 | Interfaces in a standardized manner. 23 | 24 | _standardized manner_ means that every supported platform must expose the same 25 | API and no further changes to the implementation are required to support such 26 | platform. 27 | 28 | ## Usage 29 | ```rust 30 | use network_interface::NetworkInterface; 31 | use network_interface::NetworkInterfaceConfig; 32 | 33 | fn main() { 34 | let network_interfaces = NetworkInterface::show().unwrap(); 35 | 36 | for itf in network_interfaces.iter() { 37 | println!("{:?}", itf); 38 | } 39 | } 40 | ``` 41 | 42 |
43 | Output 44 | 45 | ``` 46 | NetworkInterface { name: "lo", addr: Some(V4(V4IfAddr { ip: 127.0.0.1, broadcast: Some(127.0.0.1), netmask: Some(255.0.0.0) })) } 47 | NetworkInterface { name: "wlp1s0", addr: Some(V4(V4IfAddr { ip: 192.168.0.16, broadcast: Some(192.168.0.255), netmask: Some(255.255.255.0) })) } 48 | NetworkInterface { name: "wg0", addr: Some(V4(V4IfAddr { ip: 10.8.0.4, broadcast: Some(10.8.0.4), netmask: Some(255.255.255.0) })) } 49 | NetworkInterface { name: "docker0", addr: Some(V4(V4IfAddr { ip: 172.17.0.1, broadcast: Some(172.17.255.255), netmask: Some(255.255.0.0) })) } 50 | NetworkInterface { name: "lo", addr: Some(V6(V6IfAddr { ip: ::1, broadcast: None, netmask: Some(ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff) })) } 51 | NetworkInterface { name: "wlp1s0", addr: Some(V6(V6IfAddr { ip: , broadcast: None, netmask: Some(ffff:ffff:ffff:ffff::) })) } 52 | NetworkInterface { name: "docker0", addr: Some(V6(V6IfAddr { ip: , broadcast: None, netmask: Some(ffff:ffff:ffff:ffff::) })) } 53 | NetworkInterface { name: "veth9d2904f", addr: Some(V6(V6IfAddr { ip: , broadcast: None, netmask: Some(ffff:ffff:ffff:ffff::) })) } 54 | NetworkInterface { name: "vethcdd79af", addr: Some(V6(V6IfAddr { ip: , broadcast: None, netmask: Some(ffff:ffff:ffff:ffff::) })) } 55 | ``` 56 |
57 | 58 | ## Release 59 | 60 | In order to create a release you must push a Git tag as follows 61 | 62 | ```sh 63 | git tag -a -m 64 | ``` 65 | 66 | **Example** 67 | 68 | ```sh 69 | git tag -a v0.1.0 -m "First release" 70 | ``` 71 | 72 | > Tags must follow semver conventions 73 | > Tags must be prefixed with a lowercase `v` letter. 74 | 75 | Then push tags as follows: 76 | 77 | ```sh 78 | git push origin main --follow-tags 79 | ``` 80 | 81 | ## Contributing 82 | 83 | Every contribution to this project is welcome. Feel free to open a pull request, 84 | an issue or just by starting this project. 85 | 86 | ## License 87 | 88 | Distributed under the terms of both the MIT license and the Apache License (Version 2.0) 89 | -------------------------------------------------------------------------------- /.cirrus.yml: -------------------------------------------------------------------------------- 1 | ############################################################################ 2 | # Cirrus workflow for testing on (Free)BSD. 3 | # 4 | # References: 5 | # - https://cirrus-ci.org/guide/writing-tasks/ 6 | # - https://github.com/tokio-rs/tokio/blob/master/.cirrus.yml 7 | # - https://github.com/nix-rust/nix/blob/master/.cirrus.yml 8 | # - https://github.com/jakewilliami/HiddenFiles.jl/blob/master/.cirrus.yml 9 | # 10 | # TODO: 11 | # - Implement tests for other BSD OS's (will need to handle setup 12 | # differently for Tier 3 support OS's) 13 | ############################################################################ 14 | 15 | # Specify container (FreeBSD) 16 | # 17 | # References: 18 | # - https://cirrus-ci.org/guide/writing-tasks/#execution-environment 19 | # - https://cirrus-ci.org/guide/FreeBSD/ 20 | freebsd_instance: 21 | image: freebsd-13-1-release-amd64 22 | 23 | # Set up environment variables 24 | env: 25 | # The minimum supported Rust version (MSRV) 26 | # https://github.com/foresterre/cargo-msrv 27 | TOOLCHAIN: 1.56.1 28 | 29 | # Define set up procedure by downloading Rustup (to consume later) 30 | setup_common: &SETUP 31 | setup_script: 32 | - kldload mqueuefs 33 | - fetch https://sh.rustup.rs -o rustup.sh 34 | - sh rustup.sh -y --profile=minimal --default-toolchain $TOOLCHAIN 35 | - rm rustup.sh 36 | - . $HOME/.cargo/env || true 37 | - rustup --version 38 | - cargo -Vv 39 | - rustc -Vv 40 | - ifconfig 41 | 42 | # Cache the Cargo directory between runs 43 | cargo_cache: 44 | folder: $CARGO_HOME/registry 45 | fingerprint_script: cat Cargo.lock || echo "" 46 | 47 | # Test Cargo Build 48 | task: 49 | name: "Builds on FreeBSD 13" 50 | <<: *SETUP 51 | env: 52 | RUSTFLAGS: "-D warnings" 53 | test_script: 54 | - . $HOME/.cargo/env || true 55 | - cargo +$TOOLCHAIN build --release --all-targets 56 | before_cache_script: rm -rf $CARGO_HOME/registry/index 57 | 58 | # Run Unit Tests 59 | task: 60 | name: "Runs \"cargo test\" on FreeBSD 13" 61 | <<: *SETUP 62 | env: 63 | RUSTFLAGS: "-D warnings" 64 | RUSTDOCFLAGS: -D warnings 65 | test_script: 66 | - . $HOME/.cargo/env || true 67 | - cargo +$TOOLCHAIN test --all --all-features -- --nocapture 68 | - cargo +$TOOLCHAIN doc --no-deps 69 | before_cache_script: rm -rf $CARGO_HOME/registry/index 70 | 71 | # Run example 72 | task: 73 | name: "Runs \"cargo run --example demo\" on FreeBSD 13" 74 | <<: *SETUP 75 | env: 76 | RUSTFLAGS: "-D warnings" 77 | RUSTDOCFLAGS: -D warnings 78 | test_script: 79 | - . $HOME/.cargo/env || true 80 | - cargo +$TOOLCHAIN run --example demo --all-features 81 | before_cache_script: rm -rf $CARGO_HOME/registry/index 82 | 83 | # Test Cargo Clippy 84 | task: 85 | name: "Runs \"cargo clippy\" on FreeBSD 13" 86 | <<: *SETUP 87 | install_script: 88 | - . $HOME/.cargo/env || true 89 | - rustup component add --toolchain $TOOLCHAIN clippy 90 | test_script: 91 | - . $HOME/.cargo/env || true 92 | - cargo +$TOOLCHAIN clippy --all-targets 93 | before_cache_script: rm -rf $CARGO_HOME/registry/index 94 | 95 | # Test Cargo Fmt 96 | task: 97 | name: "Runs \"cargo fmt\" on FreeBSD 13" 98 | <<: *SETUP 99 | install_script: 100 | - . $HOME/.cargo/env || true 101 | - rustup +$TOOLCHAIN component add rustfmt 102 | test_script: 103 | - . $HOME/.cargo/env || true 104 | - cargo +$TOOLCHAIN fmt --all -- --check 105 | before_cache_script: rm -rf $CARGO_HOME/registry/index 106 | 107 | # Test Cargo Publish 108 | task: 109 | name: "Runs \"cargo publish --dry-run\" on FreeBSD 13" 110 | <<: *SETUP 111 | test_script: 112 | - . $HOME/.cargo/env || true 113 | - cargo +$TOOLCHAIN publish --dry-run 114 | before_cache_script: rm -rf $CARGO_HOME/registry/index 115 | -------------------------------------------------------------------------------- /src/interface.rs: -------------------------------------------------------------------------------- 1 | //! Network Interface abstraction from commonly used fields for nodes from the 2 | //! linked list provided by system functions like `getifaddrs` and 3 | //! `GetAdaptersAddresses`. 4 | use std::fmt::Debug; 5 | #[cfg(feature = "serde")] 6 | use serde::{Deserialize, Serialize}; 7 | use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; 8 | 9 | /// An alias for an `Option` that wraps either a `Ipv4Addr` or a `Ipv6Addr` 10 | /// representing the IP for a Network Interface netmask 11 | pub type Netmask = Option; 12 | 13 | /// A system's network interface 14 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 15 | #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] 16 | pub struct NetworkInterface { 17 | /// Interface's name 18 | pub name: String, 19 | /// Interface's address 20 | pub addr: Vec, 21 | /// MAC Address 22 | pub mac_addr: Option, 23 | /// Interface's index 24 | pub index: u32, 25 | } 26 | 27 | /// Network interface address 28 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 29 | #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] 30 | pub enum Addr { 31 | /// IPV4 Interface from the AFINET network interface family 32 | V4(V4IfAddr), 33 | /// IPV6 Interface from the AFINET6 network interface family 34 | V6(V6IfAddr), 35 | } 36 | 37 | /// IPV4 Interface from the AFINET network interface family 38 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 39 | #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] 40 | pub struct V4IfAddr { 41 | /// The IP address for this network interface 42 | pub ip: Ipv4Addr, 43 | /// The broadcast address for this interface 44 | pub broadcast: Option, 45 | /// The netmask for this interface 46 | pub netmask: Netmask, 47 | } 48 | 49 | /// IPV6 Interface from the AFINET6 network interface family 50 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 51 | #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] 52 | pub struct V6IfAddr { 53 | /// The IP address for this network interface 54 | pub ip: Ipv6Addr, 55 | /// The broadcast address for this interface 56 | pub broadcast: Option, 57 | /// The netmask for this interface 58 | pub netmask: Netmask, 59 | } 60 | 61 | impl NetworkInterface { 62 | pub fn new_afinet( 63 | name: &str, 64 | addr: Ipv4Addr, 65 | netmask: Netmask, 66 | broadcast: Option, 67 | index: u32, 68 | ) -> NetworkInterface { 69 | let ifaddr_v4 = V4IfAddr { 70 | ip: addr, 71 | broadcast, 72 | netmask, 73 | }; 74 | 75 | NetworkInterface { 76 | name: name.to_string(), 77 | addr: vec![Addr::V4(ifaddr_v4)], 78 | mac_addr: None, 79 | index, 80 | } 81 | } 82 | 83 | pub fn new_afinet6( 84 | name: &str, 85 | addr: Ipv6Addr, 86 | netmask: Netmask, 87 | broadcast: Option, 88 | index: u32, 89 | ) -> NetworkInterface { 90 | let ifaddr_v6 = V6IfAddr { 91 | ip: addr, 92 | broadcast, 93 | netmask, 94 | }; 95 | 96 | NetworkInterface { 97 | name: name.to_string(), 98 | addr: vec![Addr::V6(ifaddr_v6)], 99 | mac_addr: None, 100 | index, 101 | } 102 | } 103 | 104 | pub fn with_mac_addr(self, mac_addr: Option) -> Self { 105 | Self { mac_addr, ..self } 106 | } 107 | } 108 | 109 | impl Addr { 110 | pub fn ip(self) -> IpAddr { 111 | match self { 112 | Addr::V4(ifaddr_v4) => ifaddr_v4.ip.into(), 113 | Addr::V6(ifaddr_v6) => ifaddr_v6.ip.into(), 114 | } 115 | } 116 | 117 | pub fn broadcast(self) -> Option { 118 | match self { 119 | Addr::V4(ifaddr_v4) => ifaddr_v4.broadcast.map(Into::into), 120 | Addr::V6(ifaddr_v6) => ifaddr_v6.broadcast.map(Into::into), 121 | } 122 | } 123 | 124 | pub fn netmask(self) -> Netmask { 125 | match self { 126 | Addr::V4(ifaddr_v4) => ifaddr_v4.netmask.map(Into::into), 127 | Addr::V6(ifaddr_v6) => ifaddr_v6.netmask.map(Into::into), 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/target/unix/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod ffi; 2 | 3 | use std::collections::HashMap; 4 | 5 | use std::net::{Ipv4Addr, Ipv6Addr}; 6 | use std::slice::from_raw_parts; 7 | 8 | use libc::{AF_INET, AF_INET6, sockaddr_in, sockaddr_in6, strlen, AF_LINK, if_nametoindex}; 9 | 10 | use crate::target::ffi::lladdr; 11 | use crate::target::getifaddrs; 12 | use crate::{Error, NetworkInterface, NetworkInterfaceConfig, Result}; 13 | use crate::utils::{ipv4_from_in_addr, ipv6_from_in6_addr, make_ipv4_netmask, make_ipv6_netmask}; 14 | 15 | impl NetworkInterfaceConfig for NetworkInterface { 16 | fn show() -> Result> { 17 | let mut network_interfaces: HashMap = HashMap::new(); 18 | 19 | for netifa in getifaddrs()? { 20 | let netifa_addr = netifa.ifa_addr; 21 | let netifa_family = if netifa_addr.is_null() { 22 | continue; 23 | } else { 24 | unsafe { (*netifa_addr).sa_family as i32 } 25 | }; 26 | 27 | let mut network_interface = match netifa_family { 28 | AF_LINK => { 29 | let name = make_netifa_name(&netifa)?; 30 | let mac = make_mac_addrs(&netifa); 31 | let index = netifa_index(&netifa); 32 | NetworkInterface { 33 | name, 34 | mac_addr: Some(mac), 35 | addr: Vec::new(), 36 | index, 37 | } 38 | } 39 | AF_INET => { 40 | let socket_addr = netifa_addr as *mut sockaddr_in; 41 | let internet_address = unsafe { (*socket_addr).sin_addr }; 42 | let name = make_netifa_name(&netifa)?; 43 | let index = netifa_index(&netifa); 44 | let netmask = make_ipv4_netmask(&netifa); 45 | let addr = ipv4_from_in_addr(&internet_address)?; 46 | let broadcast = make_ipv4_broadcast_addr(&netifa)?; 47 | NetworkInterface::new_afinet(name.as_str(), addr, netmask, broadcast, index) 48 | } 49 | AF_INET6 => { 50 | let socket_addr = netifa_addr as *mut sockaddr_in6; 51 | let internet_address = unsafe { (*socket_addr).sin6_addr }; 52 | let name = make_netifa_name(&netifa)?; 53 | let index = netifa_index(&netifa); 54 | let netmask = make_ipv6_netmask(&netifa); 55 | let addr = ipv6_from_in6_addr(&internet_address)?; 56 | let broadcast = make_ipv6_broadcast_addr(&netifa)?; 57 | NetworkInterface::new_afinet6(name.as_str(), addr, netmask, broadcast, index) 58 | } 59 | _ => continue, 60 | }; 61 | 62 | network_interfaces 63 | .entry(network_interface.name.clone()) 64 | .and_modify(|old| old.addr.append(&mut network_interface.addr)) 65 | .or_insert(network_interface); 66 | } 67 | 68 | Ok(network_interfaces.into_values().collect()) 69 | } 70 | } 71 | 72 | /// Retrieves the network interface name 73 | fn make_netifa_name(netifa: &libc::ifaddrs) -> Result { 74 | let data = netifa.ifa_name as *mut u8; 75 | let len = unsafe { strlen(data as *const _) }; 76 | let bytes_slice = unsafe { from_raw_parts(data, len) }; 77 | let string = String::from_utf8(bytes_slice.to_vec()).map_err(Error::from)?; 78 | 79 | Ok(string) 80 | } 81 | 82 | /// Retrieves the broadcast address for the network interface provided of the 83 | /// AF_INET family. 84 | /// 85 | /// ## References 86 | /// 87 | /// https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/getifaddrs.3.html 88 | fn make_ipv4_broadcast_addr(netifa: &libc::ifaddrs) -> Result> { 89 | let ifa_dstaddr = netifa.ifa_dstaddr; 90 | 91 | if ifa_dstaddr.is_null() { 92 | return Ok(None); 93 | } 94 | 95 | let socket_addr = ifa_dstaddr as *mut sockaddr_in; 96 | let internet_address = unsafe { (*socket_addr).sin_addr }; 97 | let addr = ipv4_from_in_addr(&internet_address)?; 98 | 99 | Ok(Some(addr)) 100 | } 101 | 102 | /// Retrieves the broadcast address for the network interface provided of the 103 | /// AF_INET6 family. 104 | /// 105 | /// ## References 106 | /// 107 | /// https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/getifaddrs.3.html 108 | fn make_ipv6_broadcast_addr(netifa: &libc::ifaddrs) -> Result> { 109 | let ifa_dstaddr = netifa.ifa_dstaddr; 110 | 111 | if ifa_dstaddr.is_null() { 112 | return Ok(None); 113 | } 114 | 115 | let socket_addr = ifa_dstaddr as *mut sockaddr_in6; 116 | let internet_address = unsafe { (*socket_addr).sin6_addr }; 117 | let addr = ipv6_from_in6_addr(&internet_address)?; 118 | 119 | Ok(Some(addr)) 120 | } 121 | 122 | fn make_mac_addrs(netifa: &libc::ifaddrs) -> String { 123 | let mut mac = [0; 6]; 124 | let mut ptr = unsafe { lladdr(netifa as *const libc::ifaddrs as *mut _) }; 125 | 126 | for el in &mut mac { 127 | *el = unsafe { *ptr }; 128 | ptr = ((ptr as usize) + 1) as *const u8; 129 | } 130 | 131 | format!( 132 | "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", 133 | mac[0], mac[1], mac[2], mac[3], mac[4], mac[5] 134 | ) 135 | } 136 | 137 | /// Retreives the name for the the network interface provided 138 | /// 139 | /// ## References 140 | /// 141 | /// https://man7.org/linux/man-pages/man3/if_nametoindex.3.html 142 | fn netifa_index(netifa: &libc::ifaddrs) -> u32 { 143 | let name = netifa.ifa_name as *const libc::c_char; 144 | 145 | unsafe { if_nametoindex(name) } 146 | } 147 | -------------------------------------------------------------------------------- /src/target/linux.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::net::{Ipv4Addr, Ipv6Addr}; 3 | use std::slice::from_raw_parts; 4 | 5 | use libc::{ 6 | sockaddr_in, sockaddr_in6, strlen, AF_INET, AF_INET6, if_nametoindex, sockaddr_ll, AF_PACKET, 7 | }; 8 | 9 | use crate::target::getifaddrs; 10 | use crate::{Error, NetworkInterface, NetworkInterfaceConfig, Result}; 11 | use crate::utils::{ipv4_from_in_addr, ipv6_from_in6_addr, make_ipv4_netmask, make_ipv6_netmask}; 12 | 13 | impl NetworkInterfaceConfig for NetworkInterface { 14 | fn show() -> Result> { 15 | let mut network_interfaces: HashMap = HashMap::new(); 16 | 17 | for netifa in getifaddrs()? { 18 | let netifa_addr = netifa.ifa_addr; 19 | let netifa_family = if netifa_addr.is_null() { 20 | continue; 21 | } else { 22 | unsafe { (*netifa_addr).sa_family as i32 } 23 | }; 24 | 25 | let mut network_interface = match netifa_family { 26 | AF_PACKET => { 27 | let name = make_netifa_name(&netifa)?; 28 | let mac = make_mac_addrs(&netifa); 29 | let index = netifa_index(&netifa); 30 | NetworkInterface { 31 | name, 32 | addr: Vec::new(), 33 | mac_addr: Some(mac), 34 | index, 35 | } 36 | } 37 | AF_INET => { 38 | let socket_addr = netifa_addr as *mut sockaddr_in; 39 | let internet_address = unsafe { (*socket_addr).sin_addr }; 40 | let name = make_netifa_name(&netifa)?; 41 | let index = netifa_index(&netifa); 42 | let netmask = make_ipv4_netmask(&netifa); 43 | let addr = ipv4_from_in_addr(&internet_address)?; 44 | let broadcast = make_ipv4_broadcast_addr(&netifa)?; 45 | NetworkInterface::new_afinet(name.as_str(), addr, netmask, broadcast, index) 46 | } 47 | AF_INET6 => { 48 | let socket_addr = netifa_addr as *mut sockaddr_in6; 49 | let internet_address = unsafe { (*socket_addr).sin6_addr }; 50 | let name = make_netifa_name(&netifa)?; 51 | let index = netifa_index(&netifa); 52 | let netmask = make_ipv6_netmask(&netifa); 53 | let addr = ipv6_from_in6_addr(&internet_address)?; 54 | let broadcast = make_ipv6_broadcast_addr(&netifa)?; 55 | NetworkInterface::new_afinet6(name.as_str(), addr, netmask, broadcast, index) 56 | } 57 | _ => continue, 58 | }; 59 | 60 | network_interfaces 61 | .entry(network_interface.name.clone()) 62 | .and_modify(|old| old.addr.append(&mut network_interface.addr)) 63 | .or_insert(network_interface); 64 | } 65 | 66 | Ok(network_interfaces.into_values().collect()) 67 | } 68 | } 69 | 70 | /// Retrieves the network interface name 71 | fn make_netifa_name(netifa: &libc::ifaddrs) -> Result { 72 | let data = netifa.ifa_name as *const libc::c_char; 73 | let len = unsafe { strlen(data) }; 74 | let bytes_slice = unsafe { from_raw_parts(data as *const u8, len) }; 75 | 76 | match String::from_utf8(bytes_slice.to_vec()) { 77 | Ok(s) => Ok(s), 78 | Err(e) => Err(Error::ParseUtf8Error(e)), 79 | } 80 | } 81 | 82 | /// Retrieves the broadcast address for the network interface provided of the 83 | /// AF_INET family. 84 | /// 85 | /// ## References 86 | /// 87 | /// https://man7.org/linux/man-pages/man3/getifaddrs.3.html 88 | fn make_ipv4_broadcast_addr(netifa: &libc::ifaddrs) -> Result> { 89 | let ifa_dstaddr = netifa.ifa_ifu; 90 | 91 | if ifa_dstaddr.is_null() { 92 | return Ok(None); 93 | } 94 | 95 | let socket_addr = ifa_dstaddr as *mut sockaddr_in; 96 | let internet_address = unsafe { (*socket_addr).sin_addr }; 97 | let addr = ipv4_from_in_addr(&internet_address)?; 98 | 99 | Ok(Some(addr)) 100 | } 101 | 102 | /// Retrieves the broadcast address for the network interface provided of the 103 | /// AF_INET6 family. 104 | /// 105 | /// ## References 106 | /// 107 | /// https://man7.org/linux/man-pages/man3/getifaddrs.3.html 108 | fn make_ipv6_broadcast_addr(netifa: &libc::ifaddrs) -> Result> { 109 | let ifa_dstaddr = netifa.ifa_ifu; 110 | 111 | if ifa_dstaddr.is_null() { 112 | return Ok(None); 113 | } 114 | 115 | let socket_addr = ifa_dstaddr as *mut sockaddr_in6; 116 | let internet_address = unsafe { (*socket_addr).sin6_addr }; 117 | let addr = ipv6_from_in6_addr(&internet_address)?; 118 | 119 | Ok(Some(addr)) 120 | } 121 | 122 | fn make_mac_addrs(netifa: &libc::ifaddrs) -> String { 123 | let netifa_addr = netifa.ifa_addr; 124 | let socket_addr = netifa_addr as *mut sockaddr_ll; 125 | let mac_array = unsafe { (*socket_addr).sll_addr }; 126 | let addr_len = unsafe { (*socket_addr).sll_halen }; 127 | let real_addr_len = std::cmp::min(addr_len as usize, mac_array.len()); 128 | let mac_slice = unsafe { std::slice::from_raw_parts(mac_array.as_ptr(), real_addr_len) }; 129 | 130 | mac_slice 131 | .iter() 132 | .map(|x| format!("{x:02x}")) 133 | .collect::>() 134 | .join(":") 135 | } 136 | 137 | /// Retreives the name for the the network interface provided 138 | /// 139 | /// ## References 140 | /// 141 | /// https://man7.org/linux/man-pages/man3/if_nametoindex.3.html 142 | fn netifa_index(netifa: &libc::ifaddrs) -> u32 { 143 | let name = netifa.ifa_name as *const libc::c_char; 144 | 145 | unsafe { if_nametoindex(name) } 146 | } 147 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | # Use verbose output. 2 | # Default: false 3 | # verbose = 4 | 5 | # Do not reformat out of line modules. 6 | # Default: false 7 | # skip_children = 8 | 9 | # Lines to format; this is not supported in rustfmt.toml, 10 | # and can only be specified via the --file-lines option. 11 | # file_lines = 12 | 13 | # Maximum width of each line. 14 | # Default: 100 15 | # max_width = 16 | 17 | # Ideal width of each line. 18 | # Default: 80 19 | # max_width = 20 | 21 | # Number of spaces per tab. 22 | # Default: 4 23 | # tab_spaces = 4 24 | 25 | # Maximum width of the args of a function call before 26 | # falling back to vertical formatting. 27 | # Default: 60 28 | # fn_call_width = 29 | 30 | # Maximum width in the body of a struct lit before falling back to vertical formatting. 31 | # Default: 16 32 | # struct_lit_width = 33 | 34 | # Maximum width in the body of a struct variant before falling back to vertical formatting. 35 | # Default: 35 36 | # struct_variant_width = 37 | 38 | # Always print the abi for extern items. 39 | # Default: true 40 | # force_explicit_abi = 41 | 42 | # Unix or Windows line endings. 43 | # Values: Windows | Unix | Native 44 | # Default: Unix 45 | # newline_style = 46 | 47 | # Brace style for functions. 48 | # Values: AlwaysNextLine | PreferSameLine | SameLineWhere 49 | # Default: SameLineWhere 50 | # fn_brace_style = 51 | 52 | # Brace style for structs and enums. 53 | # Values: AlwaysNextLine | PreferSameLine | SameLineWhere 54 | # Default: SameLineWhere 55 | # item_brace_style = 56 | 57 | # Brace style for control flow construct. 58 | # Values: AlwaysSameLine | ClosingNextLine | AlwaysNextLine 59 | # Default: AlwaysSameLine 60 | # control_brace_style = 61 | 62 | # Put empty-body implementations on a single line. 63 | # Default: true 64 | # impl_empty_single_line = 65 | 66 | # Put empty-body functions on a single line. 67 | # Default: true 68 | # fn fn_empty_single_line = 69 | 70 | # Put single-expression functions on a single line. 71 | # Default: false 72 | # fn_single_line = 73 | 74 | # Location of return type in function declaration. 75 | # Values: WithArgs | WithWhereClause 76 | # Default: WithArgs 77 | # fn_return_indent = 78 | 79 | # If function argument parenthesis goes on a newline. 80 | # Default: true 81 | # fn_args_paren_newline = 82 | 83 | # Argument density in functions. 84 | # Values: Compressed | Tall | CompressedIfEmpty | Vertical 85 | # Default: Tall 86 | # fn_args_density = 87 | 88 | # Layout of function arguments. 89 | # Values: Visual | Block | BlockAlways 90 | # Default: Visual 91 | # fn_args_layout = 92 | 93 | # Indent on function arguments. 94 | # Values: Inherit | Tabbed | Visual 95 | # Default: Visual 96 | # fn_arg_indent = 97 | 98 | # Determines if '+' or '=' are wrapped in spaces in the punctuation of types. 99 | # Values: Compressed | Wide 100 | # Default: Wide 101 | # type_punctuation_density = 102 | 103 | # Density of a where clause. 104 | # Values: Compressed | Tall | CompressedIfEmpty | Vertical 105 | # Default: CompressedIfEmpty 106 | # where_density = 107 | 108 | # Indentation of a where clause. 109 | # Values: Inherit | Tabbed | Visual 110 | # Default: Tabbed 111 | # where_indent = 112 | 113 | # Element layout inside a where clause. 114 | # Values: Vertical | Horizontal | HorizontalVertical | Mixed 115 | # Default: Vertical 116 | # where_layout = 117 | 118 | # Indentation style of a where predicate. 119 | # Values: Inherit | Tabbed | Visual 120 | # Default: Visual 121 | # where_pred_indent = 122 | 123 | # Put a trailing comma on where clauses. 124 | # Default: false 125 | # where_trailing_comma = 126 | 127 | # Indentation of generics. 128 | # Values: Inherit | Tabbed | Visual 129 | # Default: Visual 130 | # generics_indent = 131 | 132 | # If there is a trailing comma on structs. 133 | # Values: Always | Never | Vertical 134 | # Default: Vertical 135 | # struct_trailing_comma = 136 | 137 | # If there is a trailing comma on literal structs. 138 | # Values: Always | Never | Vertical 139 | # Default: Vertical 140 | # struct_lit_trailing_comma = 141 | 142 | # Style of struct definition. 143 | # Values: Visual | Block 144 | # Default: Block 145 | # struct_lit_style = 146 | 147 | # Multiline style on literal structs. 148 | # Values: PreferSingle | ForceMulti 149 | # Default: PreferSingle 150 | # struct_lit_multiline_style = 151 | 152 | # Put a trailing comma on enum declarations. 153 | # Default: true 154 | # enum_trailing_comma = 155 | 156 | # Report all, none or unnumbered occurrences of TODO in source file comments. 157 | # Values: Always | Unnumbered | Never 158 | # Default: Never 159 | # report_todo = 160 | 161 | # Report all, none or unnumbered occurrences of FIXME in source file comments. 162 | # Values: Always | Unnumbered | Never 163 | # Default: Never 164 | # report_fixme = 165 | 166 | # Indent on chain base. 167 | # Values: Inherit | Tabbed | Visual 168 | # Default: Tabbed 169 | # chain_base_indent = 170 | 171 | # Indentation of chain. 172 | # Values: Inherit | Tabbed | Visual 173 | # Default: Tabbed 174 | # chain_indent = 175 | 176 | # Allow last call in method chain to break the line. 177 | # Default: true 178 | # chains_overflow_last = 179 | 180 | # Reorder import statements alphabetically. 181 | # Default: false 182 | reorder_imports = false 183 | 184 | # Reorder lists of names in import statements alphabetically. 185 | # Default: false 186 | # reorder_imported_names = 187 | 188 | # Maximum line length for single line if-else expressions. 189 | # A value of zero means always break if-else expressions. 190 | # Default: 50 191 | # single_line_if_else_max_width = 192 | 193 | # Format string literals where necessary. 194 | # Default: true 195 | # format_strings = 196 | 197 | # Always format string literals. 198 | # Default: false 199 | # force_format_strings = 200 | 201 | # Retain some formatting characteristics from the source code. 202 | # Default: true 203 | # take_source_hints = 204 | 205 | # Use tab characters for indentation, spaces for alignment. 206 | # Default: false 207 | # hard_tabs = 208 | 209 | # Break comments to fit on the line. 210 | # Default: false 211 | # wrap_comments = 212 | 213 | # Convert /* */ comments to // comments where possible. 214 | # Default: false 215 | # normalize_comments = 216 | 217 | # Wrap multiline match arms in blocks. 218 | # Default: true 219 | # wrap_match_arms = 220 | 221 | # Put a trailing comma after a block based match arm (non-block arms are not affected). 222 | # Default: false 223 | # match_block_trailing_comma = 224 | 225 | # Put a trailing comma after a wildcard arm. 226 | # Default: true 227 | # match_wildcard_trailing_comma = 228 | 229 | # How many lines a closure must have before it is block indented. 230 | # -1 means never use block indent. 231 | # Type: 232 | # Default: 5 233 | # closure_block_indent_threshold = 234 | 235 | # Leave a space before the colon in a type annotation. 236 | # Default: false 237 | # space_before_type_annotation = 238 | 239 | # Leave a space after the colon in a type annotation. 240 | # Default: true 241 | # space_after_type_annotation_colon = 242 | 243 | # Leave a space before the colon in a trait or lifetime bound. 244 | # Default: false 245 | # space_before_bound = 246 | 247 | # Leave a space after the colon in a trait or lifetime bound. 248 | # Default: true 249 | # space_after_bound_colon = 250 | 251 | # Put spaces around the .. and ... range operators. 252 | # Default: false 253 | # spaces_around_ranges = 254 | 255 | # Put spaces within non-empty generic arguments. 256 | # Default: false 257 | # spaces_within_angle_brackets = 258 | 259 | # Put spaces within non-empty square brackets. 260 | # Default: false 261 | # spaces_within_square_brackets = 262 | 263 | # Put spaces within non-empty parentheses. 264 | # Default: false 265 | # spaces_within_parens = 266 | 267 | # Replace uses of the try! macro by the ? shorthand. 268 | # Default: false 269 | # use_try_shorthand = 270 | 271 | # What Write Mode to use when none is supplied: Replace, Overwrite, Display, Diff, Coverage. 272 | # Values: Replace | Overwrite | Display | Diff | Coverage | Plain | Checkstyle 273 | # Default: Replace 274 | # write_mode = 275 | 276 | # Replace strings of _ wildcards by a single .. in tuple patterns. 277 | # Default: false 278 | # condense_wildcard_suffices = 279 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2021 Esteban Borai and Contributors 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/target/windows.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::c_void; 2 | use std::fmt::Pointer; 3 | use std::mem::size_of; 4 | use std::net::{Ipv4Addr, Ipv6Addr}; 5 | use std::ptr::null_mut; 6 | use std::slice::from_raw_parts; 7 | use std::iter::Iterator; 8 | use std::marker::PhantomData; 9 | 10 | use libc::{free, malloc, wchar_t, wcslen}; 11 | use winapi::{ 12 | ctypes::c_ulong, 13 | shared::{ 14 | ws2def::{AF_UNSPEC, SOCKADDR_IN}, 15 | ws2ipdef::SOCKADDR_IN6, 16 | netioapi::{ConvertLengthToIpv4Mask, ConvertInterfaceLuidToIndex}, 17 | ntdef::ULONG, 18 | ifdef::IF_LUID, 19 | winerror, 20 | }, 21 | um::{ 22 | iptypes::{IP_ADAPTER_ADDRESSES, IP_ADAPTER_UNICAST_ADDRESS, IP_ADAPTER_PREFIX}, 23 | iphlpapi::GetAdaptersAddresses, 24 | }, 25 | }; 26 | 27 | use crate::utils::hex::HexSlice; 28 | use crate::utils::ffialloc::FFIAlloc; 29 | use crate::{Addr, Error, NetworkInterface, NetworkInterfaceConfig, Result, V4IfAddr, V6IfAddr}; 30 | use crate::interface::Netmask; 31 | 32 | /// An alias for `IP_ADAPTER_ADDRESSES` 33 | type AdapterAddress = IP_ADAPTER_ADDRESSES; 34 | 35 | /// A constant to store `winapi::shared::ws2def::AF_INET` casted as `u16` 36 | const AF_INET: u16 = winapi::shared::ws2def::AF_INET as u16; 37 | 38 | /// A constant to store ` winapi::shared::ws2def::AF_INET6` casted as `u16` 39 | const AF_INET6: u16 = winapi::shared::ws2def::AF_INET6 as u16; 40 | 41 | /// The address family of the addresses to retrieve. This parameter must be one of the following values. 42 | /// The default address family is `AF_UNSPECT` in order to gather both IPv4 and IPv6 network interfaces. 43 | /// 44 | /// Source: https://docs.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getadaptersaddresses#parameters 45 | const GET_ADAPTERS_ADDRESSES_FAMILY: u32 = AF_UNSPEC as u32; 46 | 47 | /// A constant to store `winapi::um::iptypes::GAA_FLAG_INCLUDE_PREFIX` 48 | const GET_ADAPTERS_ADDRESSES_FLAGS: ULONG = winapi::um::iptypes::GAA_FLAG_INCLUDE_PREFIX; 49 | 50 | type MacAddress = Option; 51 | 52 | macro_rules! iterable_raw_pointer { 53 | ($t: ty, $n: ident) => { 54 | impl IterableRawPointer for $t { 55 | type Pointer = *const $t; 56 | type Value = $t; 57 | 58 | fn next(&self) -> Self::Pointer { 59 | self.$n 60 | } 61 | } 62 | }; 63 | } 64 | 65 | iterable_raw_pointer!(IP_ADAPTER_ADDRESSES, Next); 66 | iterable_raw_pointer!(IP_ADAPTER_UNICAST_ADDRESS, Next); 67 | iterable_raw_pointer!(IP_ADAPTER_PREFIX, Next); 68 | 69 | impl NetworkInterfaceConfig for NetworkInterface { 70 | fn show() -> Result> { 71 | // Allocate a 15 KB buffer to start with. 72 | let mut buffer_size: u32 = 15000; 73 | // Limit retries 74 | const MAX_TRIES: i32 = 10; 75 | let mut try_no = 1; 76 | 77 | let adapter_address = loop { 78 | let adapter_address = FFIAlloc::alloc(buffer_size as usize).ok_or_else(|| { 79 | // Memory allocation failed for IP_ADAPTER_ADDRESSES struct 80 | Error::GetIfAddrsError(String::from("GetAdaptersAddresses"), 1) 81 | })?; 82 | 83 | let res = unsafe { 84 | GetAdaptersAddresses( 85 | GET_ADAPTERS_ADDRESSES_FAMILY, 86 | GET_ADAPTERS_ADDRESSES_FLAGS, 87 | null_mut(), 88 | adapter_address.as_mut_ptr(), 89 | &mut buffer_size, 90 | ) 91 | }; 92 | match res { 93 | winerror::ERROR_SUCCESS => { 94 | break Ok(adapter_address); 95 | } 96 | winerror::ERROR_BUFFER_OVERFLOW => { 97 | // The buffer size indicated by the `SizePointer` parameter is too small to hold the 98 | // adapter information or the `AdapterAddresses` parameter is `NULL`. The `SizePointer` 99 | // parameter returned points to the required size of the buffer to hold the adapter 100 | // information. 101 | // 102 | // Source: https://docs.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getadaptersaddresses#return-value 103 | if try_no == MAX_TRIES { 104 | break Err(Error::GetIfAddrsError( 105 | "GetAdapterAddresses: alloc error".to_string(), 106 | res as i32, 107 | )); 108 | } 109 | try_no += 1; 110 | } 111 | _ => { 112 | break Err(Error::GetIfAddrsError( 113 | "GetAdapterAddresses".to_string(), 114 | res as i32, 115 | )); 116 | } 117 | } 118 | }?; 119 | 120 | // iterate over the contained structs 121 | let mut network_interfaces = Vec::::new(); 122 | 123 | for adapter_address in RawPointerWrapper::new(adapter_address.as_ptr()) { 124 | let name = make_adapter_address_name(adapter_address)?; 125 | let index = get_adapter_address_index(adapter_address)?; 126 | let mac_addr = make_mac_address(adapter_address); 127 | let mut network_interface = NetworkInterface { 128 | name, 129 | addr: Vec::new(), 130 | mac_addr, 131 | index, 132 | }; 133 | 134 | for current_unicast_address in 135 | RawPointerWrapper::new(adapter_address.FirstUnicastAddress) 136 | { 137 | let address = current_unicast_address.Address; 138 | 139 | network_interface 140 | .addr 141 | .push(match unsafe { (*address.lpSockaddr).sa_family } { 142 | AF_INET => { 143 | let sockaddr = &unsafe { *(address.lpSockaddr as *const SOCKADDR_IN) }; 144 | Addr::V4(V4IfAddr { 145 | ip: make_ipv4_addr(sockaddr), 146 | broadcast: lookup_ipv4_broadcast_addr(adapter_address, sockaddr), 147 | netmask: make_ipv4_netmask(current_unicast_address), 148 | }) 149 | } 150 | AF_INET6 => { 151 | let sockaddr = &unsafe { *(address.lpSockaddr as *const SOCKADDR_IN6) }; 152 | Addr::V6(V6IfAddr { 153 | ip: make_ipv6_addr(sockaddr)?, 154 | broadcast: None, 155 | netmask: make_ipv6_netmask(sockaddr), 156 | }) 157 | } 158 | _ => continue, 159 | }); 160 | } 161 | 162 | network_interfaces.push(network_interface); 163 | } 164 | 165 | Ok(network_interfaces) 166 | } 167 | } 168 | 169 | // Find broadcast address 170 | // 171 | // see https://docs.microsoft.com/en-us/windows/win32/api/iptypes/ns-iptypes-ip_adapter_addresses_lh 172 | // 173 | // On Windows Vista and later, the linked IP_ADAPTER_PREFIX structures pointed 174 | // to by the FirstPrefix member include three IP adapter prefixes for each IPv4 175 | // address assigned to the adapter. These include 176 | // 0. the host IP address prefix 177 | // 1. the subnet IP address prefix 178 | // 2. and the subnet broadcast IP address prefix. << we want these 179 | // In addition, for each adapter with n IP adresses there are (not used) 180 | // 3*n + 0. multicast address prefix 181 | // 3*n + 1. and a broadcast address prefix.sb 182 | // 183 | // The order of addresses in prefix list and unicast list is not guaranteed to 184 | // be the same, so we search for the unicast address in the prefix list, and 185 | // then the broadcast address is next in list. 186 | fn lookup_ipv4_broadcast_addr( 187 | adapter_address: &IP_ADAPTER_ADDRESSES, 188 | unicast_ip: &SOCKADDR_IN, 189 | ) -> Option { 190 | let mut prefix_index_v4 = 0; 191 | let mut broadcast_index: Option = None; 192 | 193 | // Find adapter 194 | for prefix_address in RawPointerWrapper::new(adapter_address.FirstPrefix) { 195 | let address = prefix_address.Address; 196 | 197 | if unsafe { (*address.lpSockaddr).sa_family } == AF_INET { 198 | let sockaddr = &unsafe { *(address.lpSockaddr as *const SOCKADDR_IN) }; 199 | 200 | if let Some(broadcast_index) = broadcast_index { 201 | if prefix_index_v4 == broadcast_index { 202 | return Some(make_ipv4_addr(sockaddr)); 203 | } 204 | } else if prefix_index_v4 % 3 == 1 && ipv4_addr_equal(sockaddr, unicast_ip) { 205 | broadcast_index = Some(prefix_index_v4 + 1); 206 | } 207 | prefix_index_v4 += 1; 208 | } 209 | } 210 | None 211 | } 212 | 213 | /// Retrieves the network interface name 214 | fn make_adapter_address_name(adapter_address: &AdapterAddress) -> Result { 215 | let address_name = adapter_address.FriendlyName; 216 | let address_name_length = unsafe { wcslen(address_name as *const wchar_t) }; 217 | let byte_slice = unsafe { from_raw_parts(address_name, address_name_length) }; 218 | let string = String::from_utf16(byte_slice).map_err(Error::from)?; 219 | 220 | Ok(string) 221 | } 222 | 223 | /// Creates a `Ipv6Addr` from a `SOCKADDR_IN6` 224 | fn make_ipv6_addr(sockaddr: &SOCKADDR_IN6) -> Result { 225 | let address_bytes = unsafe { sockaddr.sin6_addr.u.Byte() }; 226 | let ip = Ipv6Addr::from(*address_bytes); 227 | 228 | Ok(ip) 229 | } 230 | 231 | /// Creates a `Ipv4Addr` from a `SOCKADDR_IN` 232 | fn make_ipv4_addr(sockaddr: &SOCKADDR_IN) -> Ipv4Addr { 233 | let address = unsafe { sockaddr.sin_addr.S_un.S_addr() }; 234 | 235 | if cfg!(target_endian = "little") { 236 | // due to a difference on how bytes are arranged on a 237 | // single word of memory by the CPU, swap bytes based 238 | // on CPU endianess to avoid having twisted IP addresses 239 | // 240 | // refer: https://github.com/rust-lang/rust/issues/48819 241 | return Ipv4Addr::from(address.swap_bytes()); 242 | } 243 | 244 | Ipv4Addr::from(*address) 245 | } 246 | 247 | /// Compare 2 ipv4 addresses. 248 | fn ipv4_addr_equal(sockaddr1: &SOCKADDR_IN, sockaddr2: &SOCKADDR_IN) -> bool { 249 | let address1 = unsafe { sockaddr1.sin_addr.S_un.S_addr() }; 250 | let address2 = unsafe { sockaddr2.sin_addr.S_un.S_addr() }; 251 | address1 == address2 252 | } 253 | 254 | /// This function relies on the `GetAdapterAddresses` API which is available only on Windows Vista 255 | /// and later versions. 256 | /// 257 | /// An implementation of `GetIpAddrTable` to get all available network interfaces would be required 258 | /// in order to support previous versions of Windows. 259 | fn make_ipv4_netmask(unicast_address: &IP_ADAPTER_UNICAST_ADDRESS) -> Netmask { 260 | let mut mask: c_ulong = 0; 261 | let on_link_prefix_length = unicast_address.OnLinkPrefixLength; 262 | unsafe { 263 | ConvertLengthToIpv4Mask(on_link_prefix_length as u32, &mut mask as *mut c_ulong); 264 | } 265 | 266 | if cfg!(target_endian = "little") { 267 | // due to a difference on how bytes are arranged on a 268 | // single word of memory by the CPU, swap bytes based 269 | // on CPU endianess to avoid having twisted IP addresses 270 | // 271 | // refer: https://github.com/rust-lang/rust/issues/48819 272 | return Some(Ipv4Addr::from(mask.swap_bytes())); 273 | } 274 | 275 | Some(Ipv4Addr::from(mask)) 276 | } 277 | 278 | fn make_ipv6_netmask(_sockaddr: &SOCKADDR_IN6) -> Netmask { 279 | None 280 | } 281 | 282 | /// Creates MacAddress from AdapterAddress 283 | fn make_mac_address(adapter_address: &AdapterAddress) -> MacAddress { 284 | // see https://docs.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getadaptersaddresses#examples 285 | let mac_addr_len = adapter_address.PhysicalAddressLength as usize; 286 | match mac_addr_len { 287 | 0 => None, 288 | len => Some(format!( 289 | "{}", 290 | HexSlice::new(&adapter_address.PhysicalAddress[..len]) 291 | )), 292 | } 293 | } 294 | 295 | fn get_adapter_address_index(adapter_address: &AdapterAddress) -> Result { 296 | let adapter_luid = &adapter_address.Luid as *const IF_LUID; 297 | 298 | let index = &mut 0u32 as *mut u32; 299 | 300 | match unsafe { ConvertInterfaceLuidToIndex(adapter_luid, index) } { 301 | 0 => Ok(unsafe { *index }), 302 | e => Err(crate::error::Error::GetIfNameError( 303 | "ConvertInterfaceLuidToIndex".to_string(), 304 | e, 305 | )), 306 | } 307 | } 308 | 309 | /// Trait for linked lists in Windows API structures iteration 310 | trait IterableRawPointer { 311 | type Pointer; 312 | type Value; 313 | 314 | /// Returns: pointer to the next element in the linked list 315 | /// null at the end 316 | fn next(&self) -> Self::Pointer; 317 | } 318 | 319 | /// Raw pointer container 320 | struct RawPointerWrapper<'a, T>(*const T, PhantomData<&'a T>) 321 | where 322 | T: IterableRawPointer; 323 | 324 | impl<'a, T> RawPointerWrapper<'a, T> 325 | where 326 | T: IterableRawPointer, 327 | { 328 | fn new(ptr: *const T) -> RawPointerWrapper<'a, T> { 329 | Self(ptr, PhantomData) 330 | } 331 | } 332 | 333 | /// Iterator implementation for RawPointer 334 | impl<'a, T> Iterator for RawPointerWrapper<'a, T> 335 | where 336 | T: IterableRawPointer, 337 | { 338 | type Item = &'a T::Value; 339 | 340 | fn next(&mut self) -> Option { 341 | let ret = unsafe { self.0.as_ref() }; 342 | if let Some(v) = ret { 343 | self.0 = v.next(); 344 | } 345 | ret 346 | } 347 | } 348 | 349 | #[cfg(test)] 350 | mod tests { 351 | use std::{process::Command, cmp::min}; 352 | 353 | use crate::{NetworkInterface, NetworkInterfaceConfig, Addr}; 354 | 355 | #[test] 356 | fn test_mac_addr() { 357 | const MAC_ADDR_LEN: usize = "00:22:48:03:ED:76".len(); 358 | 359 | let output = Command::new("getmac").arg("/nh").output().unwrap().stdout; 360 | let output_string = String::from_utf8(output).unwrap(); 361 | let mac_addr_list: Vec<_> = output_string 362 | .lines() 363 | .filter_map(|line| { 364 | let line = line.trim(); 365 | let line = &line[..min(MAC_ADDR_LEN, line.len())]; 366 | match line.split('-').count() { 367 | 6 => Some(line.replace('-', ":")), 368 | _ => None, 369 | } 370 | }) 371 | .collect(); 372 | assert!(!mac_addr_list.is_empty()); 373 | 374 | let interfaces = NetworkInterface::show().unwrap(); 375 | for mac_addr in mac_addr_list { 376 | assert!(interfaces 377 | .iter() 378 | .any(|int| int.mac_addr.as_ref() == Some(&mac_addr))); 379 | } 380 | } 381 | 382 | #[test] 383 | // Check IP address consistency. 384 | fn test_ipv4_broadcast() { 385 | let interfaces = NetworkInterface::show().unwrap(); 386 | for ipv4 in interfaces.iter().flat_map(|i| &i.addr).filter_map(|addr| { 387 | if let Addr::V4(ipv4) = addr { 388 | Some(ipv4) 389 | } else { 390 | None 391 | } 392 | }) { 393 | let Some(bc_addr) = ipv4.broadcast else { 394 | continue; 395 | }; 396 | let ip_bytes = ipv4.ip.octets(); 397 | let mask_bytes = ipv4.netmask.unwrap().octets(); 398 | let bc_bytes = bc_addr.octets(); 399 | for i in 0..4 { 400 | assert_eq!(ip_bytes[i] & mask_bytes[i], bc_bytes[i] & mask_bytes[i]); 401 | assert_eq!(bc_bytes[i] | mask_bytes[i], 255); 402 | } 403 | } 404 | } 405 | } 406 | --------------------------------------------------------------------------------