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}