Cross-platform enumeration of network interfaces and their MTU, gateway, multicast, and local/private/public IP addresses — with a libc-free netlink backend on Linux and no getifaddrs dependency.
[dependencies]
getifs = "0.7"- Zero libc dependency on Linux (uses netlink directly)
- MTU information - Get interface MTU values
- Multicast addresses - Fetch multicast group memberships
- Gateway discovery - Find IPv4 and IPv6 gateway addresses
- Routing table - Enumerate kernel routing-table entries
- RFC-based filtering - Filter addresses by RFC classification
- High performance - Up to 72x faster than alternatives on macOS (see benchmarks)
- Cross-platform - Linux, macOS, BSD, Windows, and Android support
use getifs::{interfaces, local_addrs, gateway_addrs};
// Get all network interfaces
let interfaces = interfaces().unwrap();
for interface in interfaces {
println!("Interface: {} (index: {})", interface.name(), interface.index());
println!(" MTU: {}", interface.mtu());
println!(" Flags: {:?}", interface.flags());
}
// Get local IP addresses
let local_ips = local_addrs().unwrap();
for ip in local_ips {
println!("Local IP: {}", ip);
}
// Get gateway addresses
let gateways = gateway_addrs().unwrap();
for gateway in gateways {
println!("Gateway: {}", gateway);
}- Fetching all interfaces: examples/interfaces.rs
- Fetching all interface addresses (excluding multicast addrs): examples/addrs.rs
- Fetching all interface multicast addresses: examples/multicast_addrs.rs
- Fetching gateway addresses: examples/gateway.rs
- Fetching the routing table: examples/route.rs
- Fetching local ip addresses: examples/local_ip_addrs.rs
- Fetching ip addresses by RFC: examples/filter_by_rfc.rs
| Platform | Interfaces, unicast addresses, and local MTU | Gateways and routes | Multicast memberships |
|---|---|---|---|
| Linux | Yes | Yes | Yes |
| Android | Yes* | Yes | Unsupported |
| Apple platforms | Yes | Yes | Yes |
| FreeBSD | Yes | Yes | Yes |
| NetBSD / OpenBSD | Yes | Yes | Unsupported |
| DragonFly | Yes | Yes | Unsupported |
| Windows | Yes | Yes | Yes |
* Android's untrusted-app fallback can enumerate only interfaces with at least one current address; see Android.
| OS | Approach |
|---|---|
Linux (no libc) |
socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE) |
Android (no libc) |
netlink with kernel auto-bind + SIOCGIF* ioctl fallback — see Android |
| BSD-like | sysctl |
| Windows | GetAdaptersAddresses |
public_*means not in the RFC 6890 special-purpose registries.private_*is the longstanding local-use classification: RFC 6890 ranges that are not in its forwarding blacklist. It includes RFC 1918, CGNAT, and IPv6 ULA, and excludes documentation, loopback, and link-local ranges; it is not a synonym for RFC 1918 alone.get_interface_mtuandget_ifaddr_mtureturn a local link MTU, not a remote path MTU. IP-only MTU lookup returnsNotFoundfor no local match andInvalidInputfor an address assigned to distinct interfaces. IPv6 link-local addresses should be queried with theirIfAddrscope.interface_by_indexandinterface_by_namereturnOk(None)when the index or name is absent from the interface enumeration (including an interface skipped for an unrepresentable name) or the OS reports a known missing-interface status. Permission, parsing, and other system errors remain errors. The olderifname_to_v6_ifaceandifname_to_ifaceretain their historicalOptionmeanings; a missing name remains an error there.- Kernel reads are weak snapshots, not transactions. An interface, address, name, or index can change between calls (or during a multi-query operation), so callers must handle ordinary TOCTOU races.
- On Linux and Android, a netlink dump that the kernel reports as interrupted
is attempted up to three times, and persistent interruption returns
ErrorKind::Interrupted. A filter sees each entry of a successful dump once and never sees the entries of an attempt that is retried, so a dump interrupted after a large number of entries (on the order of ten thousand) have reached the filter returnsErrorKind::Interruptedinstead of being retried. - Result ordering is unspecified. There is no general deduplication guarantee; deduplication is promised only where a specific API documents it.
IfAddrandIfNetpreserve the local interface index. Their derived ordering is enum family followed by storedindexand address/network;IpRouteordering is family followed byindex, destination, and gateway. These are value orderings, not routing or address-preference orderings.Interfaceequality and hashing cover the complete captured snapshot (index, MTU, name, MAC address, and flags), not stable interface identity.Flags::bits()is target-specific raw operating-system data; do not persist or compare those bits across platforms as a portable wire format. OnlyUP,BROADCAST,LOOPBACK,POINTOPOINT,MULTICAST, andRUNNINGare common semantic flags; additional constants are target-dependent.- Routes intentionally model only same-family destination/gateway pairs.
Cross-family Linux
RTA_VIAroutes are omitted rather than misrepresented. - Interface names are UTF-8 only. A platform can skip an unrepresentable name; getifs does not promise lossless non-UTF-8 name access.
ipnet,rfc,probe,SmolStr,SmallVec, andTinyVecare public re-exports and part of the compatibility contract. Serde is not currently exposed; it may be added later as an additive feature.
The detached fuzz workspace contains cfg-only parser
drivers for Linux/Android netlink and Apple/BSD sockaddr parsing. These hooks
are not normal public API. Generate its deterministic corpus before running
cargo fuzz; pull requests use fixed runs and scheduled/manual CI bounds each
target to 60 seconds.
The MSRV is Rust 1.85. smol_str 0.3.4 and later require Rust 1.89, so on
Rust 1.85–1.88 enable Cargo's MSRV-aware resolver
(resolver.incompatible-rust-versions = "fallback" in Cargo configuration, or
package.resolver = "3") or run cargo update -p smol_str --precise 0.3.2.
The direct macro dependency is the maintained pastey
crate, aliased as paste so existing macro invocations remain compatible.
RUSTSEC-2024-0436 identifies the older paste crate as unmaintained, not as a
known vulnerability. Transitive uses can still be selected by
smallvec-wrapper; this crate does not modify that upstream dependency graph.
Android runs the same libc-free netlink backend as Linux, but apps in the
untrusted_app SELinux domain hit two extra restrictions, both handled
transparently by getifs:
bind()onnetlink_route_socketis denied (Android bug b/155595000).getifsnever binds explicitly; the kernel auto-binds a unique port id on the first send — the same pathgetifaddrs()and Go'snetpackage rely on.RTM_GETLINKis denied for apps targeting API level 30+ (it needs thenlmsg_readprivpermission). Interface metadata therefore falls back to discovering indices viaRTM_GETADDR(which stays permitted) and reading the name / MTU / flags withSIOCGIF*ioctls on a datagram socket.
Working inside the untrusted_app sandbox: interfaces(),
interface_by_index(), interface_by_name(), MTU, every address query
(interface_addrs(), local_addrs(), private_addrs(), public_addrs()),
gateways, and the routing table.
Caveats on Android 11+ (API level 30+):
interfaces()lists only interfaces that currently have an address — there is no app-permitted way to enumerate address-less ones withoutgetifaddrs. (interface_by_index()/interface_by_name()are unaffected.)- The hardware (MAC) address is reported as
None(Android restricts it for apps). - Interfaces with a non-UTF-8 name are skipped.
- The ioctl socket requires the
android.permission.INTERNETpermission (which any networking app already holds).
On every Android version, multicast group enumeration returns
io::ErrorKind::Unsupported. The Linux backend reads group memberships from
/proc/net/igmp*, which apps cannot read on Android 10+, so the Android build
does not attempt it.
Existing network interface crates have limitations:
- Missing features: Most don't support MTU or multicast addresses
- Performance overhead: Nearly all use
libc::getifaddrs, which is slower - Unnecessary allocations: Heavy use of heap allocations for simple queries
getifs addresses these by:
- Using platform-native APIs directly (netlink, sysctl, GetAdaptersAddresses)
- Minimizing allocations with
SmallVecandSmolStr - Providing comprehensive interface information including MTU and multicast support
- Achieving significantly better performance than alternatives:
- Up to 72x faster on macOS (
interface_by_index) - Up to 22x faster on macOS (list interfaces)
- 2.4–2.8x faster on Linux (interface enumeration)
- ~6.8x faster local-IP lookup on Linux
- Comparable performance on Windows (
GetAdaptersAddressesoverhead dominates)
- Up to 72x faster on macOS (
All benchmarks are run with Criterion.rs on GitHub Actions. Lower is better.
Note: Automated benchmarks run on Linux, macOS, and Windows via GitHub Actions. View the latest results in the Actions tab or see benchmark documentation for more details.
Numbers below compare getifs against network-interface 2 and
local-ip-address 0.6. Each row reports median cargo bench time
(Criterion, 100 samples), measured on GitHub Actions
macos-latest (ARM64) / ubuntu-latest (x64) / windows-latest
(x64) runners on 2026-05-06.
| Platform | Best Operation | getifs |
Alternative | Speedup |
|---|---|---|---|---|
| macOS (ARM64) | Get interface by index | 2.6 μs | 187.4 μs | 72x faster |
| macOS (ARM64) | List all interfaces | 8.5 μs | 187.7 μs | 22x faster |
| Linux (x64) | Local IPv4 lookup | 13.7 μs | 93.3 μs | 6.8x faster |
| Linux (x64) | List all interfaces | 42.7 μs | 113.7 μs | 2.7x faster |
| Windows (x64) | Gateway IPv4 | 35.5 μs | N/A | Unique feature |
macOS (GitHub Actions ARM64)
| Operation | getifs |
Alternative | Speedup |
|---|---|---|---|
| List all interfaces | 8.5 μs | 187.7 μs (network-interface) |
22x faster |
| Get interface by index | 2.6 μs | 187.4 μs (network-interface) |
72x faster |
| Get interface by name | 11.4 μs | 187.8 μs (network-interface) |
17x faster |
| Get interface addresses | 8.5 μs | - | - |
| Get multicast addresses | 4.6 μs | - | - |
Linux (GitHub Actions x64)
| Operation | getifs |
Alternative | Speedup |
|---|---|---|---|
| List all interfaces | 42.7 μs | 113.7 μs (network-interface) |
2.7x faster |
| Get interface by index | 40.7 μs | 113.4 μs (network-interface) |
2.8x faster |
| Get interface by name | 46.4 μs | 113.5 μs (network-interface) |
2.4x faster |
| Get interface addresses | 17.2 μs | - | - |
| Get multicast addresses | 31.6 μs | - | - |
Windows (GitHub Actions x64)
| Operation | getifs |
Alternative | Notes |
|---|---|---|---|
| List all interfaces | 1119 μs | 1108 μs (network-interface) |
Within noise |
| Get interface by index | 1101 μs | 1110 μs (network-interface) |
Within noise |
| Get interface by name | 1167 μs | 1109 μs (network-interface) |
Within noise |
| Get interface addresses | 1094 μs | - | - |
| Get multicast addresses | 1104 μs | - | - |
Note: the Win32 GetAdaptersAddresses API has an inherent ~1 ms floor
that dominates every implementation — getifs and network-interface
end up within measurement noise of each other on Windows.
macOS (GitHub Actions ARM64)
| Operation | getifs |
Alternative | Speedup |
|---|---|---|---|
| Get local IPv4 address | 6.2 μs | 9.9 μs (local-ip-address) |
1.6x faster |
| Get local IPv6 address | 7.8 μs | 9.5 μs (local-ip-address) |
1.2x faster |
Linux (GitHub Actions x64)
| Operation | getifs |
Alternative | Speedup |
|---|---|---|---|
| Get local IPv4 address | 13.7 μs | 93.3 μs (local-ip-address) |
6.8x faster |
| Get local IPv6 address | 11.3 μs | - | No IPv6 result from alternative |
Windows (GitHub Actions x64)
| Operation | getifs |
Alternative | Notes |
|---|---|---|---|
| Get local IPv4 address | 1110 μs | 1062 μs (local-ip-address) |
Win32 ~1 ms floor |
| Get local IPv6 address | 1104 μs | 1120 μs (local-ip-address) |
Win32 ~1 ms floor |
| Platform | IPv4 Gateways | IPv6 Gateways | All Gateways |
|---|---|---|---|
| macOS (ARM64, CI) | 24.3 μs | 3.4 μs | 26.8 μs |
| Linux (x64, CI) | 19.9 μs | 16.1 μs | 24.1 μs |
| Windows (x64, CI) | 35.5 μs | 21.8 μs | 58.4 μs |
Note: No direct alternatives available for gateway discovery, so these are reported as absolute times rather than as speedups.
| Platform | IPv4 routes | IPv6 routes | All routes | Default-only filter |
|---|---|---|---|---|
| macOS (ARM64, CI) | 28.1 μs | 12.2 μs | 41.0 μs | 40.8 μs |
| Linux (x64, CI) | 30.1 μs | 26.6 μs | 34.8 μs | 34.6 μs |
| Windows (x64, CI) | 65.1 μs | 22.5 μs | 81.7 μs | 80.8 μs |
Note: The default_only filter result is essentially identical to the
unfiltered route_table because the closure runs after each row is
built — the dominant cost is the kernel dump itself, not the per-row
filter. No direct alternatives expose a routing-table API for
comparison.
Why is getifs faster?
- Direct system calls: Uses platform-native APIs (netlink on Linux, sysctl on BSD/macOS, GetAdaptersAddresses on Windows)
- Zero-copy parsing: Minimal allocations and efficient buffer reuse
- No libc dependency on Linux: Direct netlink socket communication
- Optimized data structures: Uses
SmallVecandSmolStrto avoid heap allocations for common cases
Platform Performance Notes:
- macOS: Shows the largest speedups (17–72x on the ARM64 CI
runners) due to the efficient sysctl-based implementation avoiding
getifaddrs's per-call overhead. - Linux: 2.4–2.8x faster interface enumeration via direct netlink,
and ~6.8x faster local-IP lookup from avoiding the test-socket round
trip that
local-ip-addressperforms. - Windows: Similar performance to alternatives —
GetAdaptersAddresseshas an inherent ~1 ms floor that dominates every implementation. Gateway and route-table queries skip that path entirely (~36 μs gateway / ~80 μs full table);getifsonly — the alternatives don't expose them.
iprobe: Probe if the host system supports IPv4, IPv6 and IPv4-mapped-IPv6.iprfc: Known RFCs for IP addresses.
- The code in this crate is inspired by Golang's
interface.goand HashiCorp's go-sockaddr.
getifs is under the terms of both the MIT license and the
Apache License (Version 2.0).
See LICENSE-APACHE, LICENSE-MIT for details.
Copyright (c) 2026 Al Liu.