if-watch
Cross-platform async stream of network interface address changes for Rust, backed by Tokio or smol.
Repository Health
Technical Analysis
if-watch is a small Rust crate that turns OS-level network interface changes into an async stream of IfEvent::Up/IfEvent::Down items. Instead of polling ifconfig/ip addr output or hand-rolling platform-specific netlink/SystemConfiguration/IP Helper code, applications construct an IfWatcher and await events as local IP addresses come and go.
Each supported platform gets a dedicated backend behind the same public API: Linux uses rtnetlink sockets, macOS/iOS use the system-configuration framework via core-foundation, Windows uses the IpHelper APIs through the windows crate, and every other target falls back to polling if-addrs every 10 seconds. The crate is a foundational dependency inside libp2p, where knowing which addresses a node can currently be reached on is essential for NAT traversal, address advertisement, and connectivity management.
What You Get
- A unified
IfWatchertype per async runtime (if_watch::tokio::IfWatcher,if_watch::smol::IfWatcher) exposing the sameStream<Item = Result<IfEvent>>interface everywhere. - Linux support via
rtnetlink/netlink-packet-route, subscribing toRTMGRP_IPV4_IFADDR/RTMGRP_IPV6_IFADDRfor real-time address change notifications. - macOS and iOS support via Apple’s
SystemConfigurationframework through thesystem-configurationandcore-foundationcrates. - Windows support via the
Win32_NetworkManagement_IpHelperbindings in thewindowscrate. - A 10-second polling fallback (using
if-addrs) for any other target so the crate still compiles and works everywhere, just without push notifications. - An
IpNet/Ipv4Net/Ipv6Netre-export from theipnetcrate so callers don’t need a separate dependency to work with the reported addresses.
Common Use Cases
- libp2p-style peer-to-peer networking stacks that need to know their current reachable addresses to advertise to peers or perform NAT traversal.
- Long-running network daemons or agents that must react when a machine gains or loses connectivity on an interface (e.g. Wi-Fi reconnect, VPN up/down, DHCP renewal).
- Service-discovery or mDNS-style tooling that needs to rebind sockets whenever the set of local addresses changes.
- Diagnostic and monitoring tools that log or expose interface address changes over time on multi-platform Rust binaries.
Under The Hood
Architecture
The crate is organized as one thin, platform-selected module (linux.rs, apple.rs, win.rs, fallback.rs) behind src/lib.rs, chosen entirely via #[cfg(target_os = ...)] and re-exported under tokio/smol submodules gated by feature flags. Each backend implements the same IfWatcher::new() constructor and Stream impl that yields deduplicated IfEvent::Up/Down values from an internal FnvHashSet<IpNet> plus a VecDeque<IfEvent> queue; on Linux this is driven by polling a netlink_proto::Connection and translating RouteNetlinkMessage::NewAddress/DelAddress payloads, while the fallback backend simply diffs if_addrs::get_if_addrs() snapshots on a timer. There is no runtime dispatch — the correct backend is resolved at compile time, so the crate that ships for a given target only pulls in that platform’s dependency tree.
Tech Stack
Written in Rust (edition 2021) with a cdylib/lib crate type. Core dependencies are futures (for the Stream/FusedStream traits), fnv (fast hashing for the address set), ipnet (CIDR-aware address types re-exported to consumers), and log. Platform-specific dependency trees are pulled in via [target.'cfg(...)'.dependencies]: rtnetlink/netlink-packet-route/netlink-proto/netlink-sys on Linux, core-foundation/system-configuration/if-addrs on macOS/iOS, windows with the Win32_NetworkManagement_IpHelper feature on Windows, and async-io/if-addrs for the generic fallback. The tokio and smol cargo features are additive and select which async-runtime-specific socket wrapper (TokioSocket/SmolSocket from netlink-sys, or runtime-specific timer types elsewhere) each backend uses.
Code Quality
The crate enables #![deny(missing_docs)] and #![deny(warnings)] at the crate root, so all public items are documented and the build fails on any warning. Tests live inline in src/lib.rs under #[cfg(test)] mod tests and exercise both the smol and tokio backends end to end (constructing a real IfWatcher and awaiting the first event), plus explicit is_send assertions confirming the watcher types are Send. There’s no separate integration-test suite or mocking layer — correctness is verified against the real OS networking stack, which is appropriate for a crate this thin but means CI coverage depends on the runner’s OS/network configuration. A GitHub Actions workflow under .github/ runs the build.
API Design
The public surface is deliberately minimal: one struct (IfWatcher<T>), one enum (IfEvent), and two feature-gated module paths (if_watch::tokio / if_watch::smol) that both expose the identical type alias, so switching async runtimes is a one-line import change with no other code affected. Getting started is a single IfWatcher::new() call plus a while let Some(event) = watcher.next().await loop, as shown directly in the crate’s own examples/if_watch.rs. The IfEvent enum’s Up/Down variants carry the affected IpNet directly, avoiding an extra lookup step to find out what changed.