Skip to main content

netwatcher/
lib.rs

1//! # netwatcher
2//!
3//! `netwatcher` is a cross-platform library for enumerating network interfaces and their
4//! IP addresses, featuring the ability to watch for changes to those interfaces
5//! _efficiently_. It uses platform-specific methods to detect when interface changes
6//! have occurred instead of polling, which means that you find out about changes more
7//! quickly and there is no CPU or wakeup overhead when nothing is happening.
8//!
9//! ## List example
10//!
11//! ```
12//! // Returns a HashMap from ifindex (a `u32`) to an `Interface` struct.
13//! let interfaces = netwatcher::list_interfaces().unwrap();
14//! for i in interfaces.values() {
15//!     println!("interface {}", i.name);
16//!     for ip_record in &i.ips {
17//!         println!("IP: {}/{}", ip_record.ip, ip_record.prefix_len);
18//!     }
19//! }
20//! ```
21//!
22//! ## Watch options
23//!
24//! - `watch_interfaces_with_callback`: simplest callback-based API. On Linux and Apple
25//!   platforms this creates a background thread.
26//! - `watch_interfaces_blocking`: waits in the current thread until there is an interface
27//!   change.
28//! - `watch_interfaces_async::<T>`: allows you to `.await` interface changes by integrating
29//     with an async runtime adapter such as `Tokio` or `AsyncIo`.
30//!
31//! ### Callback watch example
32//!
33//! ```no_run
34//! let handle = netwatcher::watch_interfaces_with_callback(|update| {
35//!     println!("Initial update: {}", update.is_initial);
36//!     println!("Current interface map: {:#?}", update.interfaces);
37//!
38//!     // Interfaces may appear or disappear entirely.
39//!     for ifindex in &update.diff.added {
40//!         println!("ifindex {} was added", ifindex);
41//!     }
42//!     for ifindex in &update.diff.removed {
43//!         println!("ifindex {} was removed", ifindex);
44//!     }
45//!
46//!     // Existing interfaces may gain or lose IPs.
47//!     for (ifindex, diff) in &update.diff.modified {
48//!         let interface = &update.interfaces[ifindex];
49//!         for addr in &diff.addrs_added {
50//!             println!("{} gained {}/{}", interface.name, addr.ip, addr.prefix_len);
51//!         }
52//!         for addr in &diff.addrs_removed {
53//!             println!("{} lost {}/{}", interface.name, addr.ip, addr.prefix_len);
54//!         }
55//!     }
56//! })
57//! .unwrap();
58//!
59//! // Keep `handle` alive as long as you want callbacks.
60//! // ...
61//! drop(handle);
62//! ```
63//!
64//! ### Blocking watch example
65//!
66//! `changed()` waits forever if nothing changes, so it is intended for a thread or program
67//! that has no other work to do until an interface change arrives.
68//!
69//! ```no_run
70//! let mut watch = netwatcher::watch_interfaces_blocking().unwrap();
71//!
72//! loop {
73//!     let update = watch.changed();
74//!     println!("Initial update: {}", update.is_initial);
75//!     println!("Current interface map: {:#?}", update.interfaces);
76//! }
77//! ```
78//!
79//! ### Async watch example
80//!
81//! You will probably want to enable a crate feature such as `tokio` or `async-io` in order
82//! to use the adapter appropriate for your async runtime.
83//!
84//! ```no_run
85//! # #[cfg(all(target_os = "linux", feature = "tokio"))]
86//! # {
87//! use netwatcher::async_adapter::Tokio;
88//!
89//! let runtime = tokio::runtime::Builder::new_current_thread()
90//!     .enable_all()
91//!     .build()
92//!     .unwrap();
93//! runtime.block_on(async {
94//!     let mut watch = netwatcher::watch_interfaces_async::<Tokio>().unwrap();
95//!     loop {
96//!         let update = watch.changed().await;
97//!         println!("Initial update: {}", update.is_initial);
98//!         println!("Current interface map: {:#?}", update.interfaces);
99//!     }
100//! });
101//! # }
102//! ```
103
104use std::{
105    collections::{HashMap, HashSet},
106    net::{IpAddr, Ipv4Addr, Ipv6Addr},
107    ops::Sub,
108};
109
110mod error;
111
112#[cfg(any(windows, target_os = "android"))]
113mod async_callback;
114
115#[cfg(any(target_os = "linux", target_vendor = "apple"))]
116mod watch_fd;
117
118#[cfg_attr(windows, path = "list_win.rs")]
119#[cfg_attr(unix, path = "list_unix.rs")]
120mod list;
121
122#[cfg(target_os = "android")]
123mod android;
124
125#[cfg_attr(windows, path = "watch_win.rs")]
126#[cfg_attr(target_vendor = "apple", path = "watch_mac.rs")]
127#[cfg_attr(target_os = "linux", path = "watch_linux.rs")]
128#[cfg_attr(target_os = "android", path = "watch_android.rs")]
129mod watch;
130
131pub mod async_adapter;
132
133type IfIndex = u32;
134
135pub use error::Error;
136
137#[cfg(target_os = "android")]
138pub use android::set_android_context;
139
140/// An IP address paired with its prefix length (network mask).
141#[derive(Debug, Clone, PartialEq, Eq, Hash)]
142pub struct IpRecord {
143    pub ip: IpAddr,
144    pub prefix_len: u8,
145}
146
147/// Information about one network interface at a point in time.
148#[derive(Debug, Clone, PartialEq, Eq)]
149pub struct Interface {
150    /// Internal index identifying this interface.
151    pub index: u32,
152    /// Interface name.
153    pub name: String,
154    /// Hardware address. Android may have a placeholder due to privacy restrictions.
155    pub hw_addr: String,
156    /// List of associated IPs and prefix length (netmask).
157    pub ips: Vec<IpRecord>,
158}
159
160impl Interface {
161    /// Helper to iterate over only the IPv4 addresses on this interface.
162    pub fn ipv4_ips(&self) -> impl Iterator<Item = &Ipv4Addr> {
163        self.ips.iter().filter_map(|ip_record| match ip_record.ip {
164            IpAddr::V4(ref v4) => Some(v4),
165            IpAddr::V6(_) => None,
166        })
167    }
168
169    /// Helper to iterate over only the IPv6 addresses on this interface.
170    pub fn ipv6_ips(&self) -> impl Iterator<Item = &Ipv6Addr> {
171        self.ips.iter().filter_map(|ip_record| match ip_record.ip {
172            IpAddr::V4(_) => None,
173            IpAddr::V6(ref v6) => Some(v6),
174        })
175    }
176}
177
178/// Information delivered when a network interface snapshot changes.
179///
180/// This contains up-to-date information about all interfaces, plus a diff which
181/// details which interfaces and IP addresses have changed since the previous update.
182#[derive(Debug, Clone, PartialEq, Eq)]
183pub struct Update {
184    /// Whether this update represents the initial existing interface state.
185    pub is_initial: bool,
186    pub interfaces: HashMap<IfIndex, Interface>,
187    pub diff: UpdateDiff,
188}
189
190/// What changed between one `Update` and the next.
191#[derive(Debug, Clone, PartialEq, Eq, Default)]
192pub struct UpdateDiff {
193    pub added: Vec<IfIndex>,
194    pub removed: Vec<IfIndex>,
195    pub modified: HashMap<IfIndex, InterfaceDiff>,
196}
197
198/// What changed within a single interface between updates, if it was present in both.
199#[derive(Debug, Clone, PartialEq, Eq, Default)]
200pub struct InterfaceDiff {
201    pub hw_addr_changed: bool,
202    pub addrs_added: Vec<IpRecord>,
203    pub addrs_removed: Vec<IpRecord>,
204}
205
206#[derive(Default, PartialEq, Eq, Clone)]
207struct List(HashMap<IfIndex, Interface>);
208
209impl List {
210    fn initial_update(&self) -> Update {
211        self.update_from_with_flag(&List::default(), true)
212    }
213
214    fn update_from(&self, prev: &List) -> Update {
215        self.update_from_with_flag(prev, false)
216    }
217
218    fn update_from_with_flag(&self, prev: &List, is_initial: bool) -> Update {
219        let prev_index_set: HashSet<IfIndex> = prev.0.keys().cloned().collect();
220        let curr_index_set: HashSet<IfIndex> = self.0.keys().cloned().collect();
221        let added = curr_index_set.sub(&prev_index_set).into_iter().collect();
222        let removed = prev_index_set.sub(&curr_index_set).into_iter().collect();
223        let mut modified = HashMap::new();
224        for index in curr_index_set.intersection(&prev_index_set) {
225            if prev.0[index] == self.0[index] {
226                continue;
227            }
228            let prev_addr_set: HashSet<&IpRecord> = prev.0[index].ips.iter().collect();
229            let curr_addr_set: HashSet<&IpRecord> = self.0[index].ips.iter().collect();
230            let addrs_added: Vec<IpRecord> = curr_addr_set
231                .sub(&prev_addr_set)
232                .iter()
233                .cloned()
234                .cloned()
235                .collect();
236            let addrs_removed: Vec<IpRecord> = prev_addr_set
237                .sub(&curr_addr_set)
238                .iter()
239                .cloned()
240                .cloned()
241                .collect();
242            let hw_addr_changed = prev.0[index].hw_addr != self.0[index].hw_addr;
243            modified.insert(
244                *index,
245                InterfaceDiff {
246                    hw_addr_changed,
247                    addrs_added,
248                    addrs_removed,
249                },
250            );
251        }
252        Update {
253            is_initial,
254            interfaces: self.0.clone(),
255            diff: UpdateDiff {
256                added,
257                removed,
258                modified,
259            },
260        }
261    }
262}
263
264struct UpdateCursor {
265    prev_list: List,
266    initial_pending: bool,
267}
268
269impl Default for UpdateCursor {
270    fn default() -> Self {
271        Self {
272            prev_list: List::default(),
273            initial_pending: true,
274        }
275    }
276}
277
278impl UpdateCursor {
279    fn advance(&mut self, new_list: List) -> Option<Update> {
280        if self.initial_pending {
281            self.initial_pending = false;
282            self.prev_list = new_list.clone();
283            return Some(new_list.initial_update());
284        }
285
286        if new_list == self.prev_list {
287            return None;
288        }
289
290        let update = new_list.update_from(&self.prev_list);
291        self.prev_list = new_list;
292        Some(update)
293    }
294}
295
296/// A handle to keep alive as long as you wish to receive callbacks.
297///
298/// If the callback is executing at the time the handle is dropped, drop will block until
299/// the callback is finished and it's guaranteed that it will not be called again.
300///
301/// Do not drop the handle from within the callback itself. It will probably deadlock.
302pub struct WatchHandle {
303    _inner: watch::WatchHandle,
304}
305
306/// A handle that yields `Update`s asynchronously when network interfaces change.
307pub struct AsyncWatch {
308    _inner: watch::AsyncWatch,
309}
310
311/// A handle that yields `Update`s synchronously when network interfaces change.
312pub struct BlockingWatch {
313    _inner: watch::BlockingWatch,
314}
315
316impl AsyncWatch {
317    /// Wait for the next interface snapshot that differs from the last snapshot yielded.
318    ///
319    /// The first call returns the current interface snapshot immediately. Subsequent calls wait
320    /// until there is a change.
321    ///
322    /// This method is infallible. Once a watch has been created successfully, later failures to
323    /// read platform notifications or re-list interfaces are swallowed and no update is emitted
324    /// for that event.
325    pub async fn changed(&mut self) -> Update {
326        self._inner.changed().await
327    }
328}
329
330impl BlockingWatch {
331    /// Wait for the next interface snapshot that differs from the last snapshot yielded.
332    ///
333    /// The first call returns the current interface snapshot immediately. Subsequent calls wait
334    /// until there is a change.
335    ///
336    /// This method is infallible. Once a watch has been created successfully, later failures to
337    /// read platform notifications or re-list interfaces are swallowed and no update is emitted
338    /// for that event.
339    pub fn changed(&mut self) -> Update {
340        self._inner.changed()
341    }
342}
343
344/// Retrieve information about all enabled network interfaces and their IP addresses.
345///
346/// This is a once-off operation. If you want to detect changes over time, see
347/// `watch_interfaces_with_callback`, `watch_interfaces_blocking`, or `watch_interfaces_async`.
348pub fn list_interfaces() -> Result<HashMap<IfIndex, Interface>, Error> {
349    list::list_interfaces().map(|list| list.0)
350}
351
352/// Retrieve interface information and watch for changes, which will be delivered via callback.
353///
354/// If setting up the watch is successful, this returns a `WatchHandle` which must be kept for
355/// as long as the provided callback should operate.
356///
357/// The callback will fire once immediately with an initial interface list, and a diff as if
358/// there were originally no interfaces present.
359///
360/// This function will return an error if there is a problem configuring the watcher, or if there
361/// is an error retrieving the initial interface list.
362///
363/// We assume that if listing the interfaces worked the first time, then it will continue to work
364/// for as long as the watcher is running. If listing interfaces begins to fail later, those
365/// failures will be swallowed and the callback will not be called for that change event.
366pub fn watch_interfaces_with_callback<F: FnMut(Update) + Send + 'static>(
367    callback: F,
368) -> Result<WatchHandle, Error> {
369    watch::watch_interfaces_with_callback(callback).map(|handle| WatchHandle { _inner: handle })
370}
371
372/// Retrieve interface information and watch for changes synchronously.
373///
374/// The first call to `changed()` returns the current interface snapshot immediately.
375pub fn watch_interfaces_blocking() -> Result<BlockingWatch, Error> {
376    watch::watch_interfaces_blocking().map(|handle| BlockingWatch { _inner: handle })
377}
378
379/// Retrieve interface information and watch for changes asynchronously using the given runtime adapter.
380///
381/// The first call to `changed()` returns the current interface snapshot immediately.
382pub fn watch_interfaces_async<A: async_adapter::AsyncFdAdapter>() -> Result<AsyncWatch, Error> {
383    watch::watch_interfaces_async::<A>().map(|handle| AsyncWatch { _inner: handle })
384}