if-watch

Cross-platform async stream of network interface address changes for Rust, backed by Tokio or smol.

Library
Cargo
v3.2.2
42stars
MIT OR Apache-2.0

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
31/100Needs Attention
Development Activity4
Maintenance20
Community28
Maturity60
Momentum12

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
65/100Good
Architecture72
Code Quality68
Innovation55
Learning Curve65

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 IfWatcher type per async runtime (if_watch::tokio::IfWatcher, if_watch::smol::IfWatcher) exposing the same Stream<Item = Result<IfEvent>> interface everywhere.
  • Linux support via rtnetlink/netlink-packet-route, subscribing to RTMGRP_IPV4_IFADDR/RTMGRP_IPV6_IFADDR for real-time address change notifications.
  • macOS and iOS support via Apple’s SystemConfiguration framework through the system-configuration and core-foundation crates.
  • Windows support via the Win32_NetworkManagement_IpHelper bindings in the windows crate.
  • 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/Ipv6Net re-export from the ipnet crate 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.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search