Skip to main content

veilid_tools/network_interfaces/
mod.rs

1mod tools;
2
3use crate::*;
4use serde::*;
5
6cfg_if::cfg_if! {
7    if #[cfg(any(target_os = "linux", target_os = "android"))] {
8        mod netlink;
9        use self::netlink::PlatformSupportNetlink as PlatformSupport;
10    } else if #[cfg(target_os = "windows")] {
11        mod windows;
12        mod sockaddr_tools;
13        use self::windows::PlatformSupportWindows as PlatformSupport;
14    } else if #[cfg(any(target_os = "macos", target_os = "ios"))] {
15        mod apple;
16        mod sockaddr_tools;
17        use self::apple::PlatformSupportApple as PlatformSupport;
18    } else if #[cfg(target_os = "openbsd")] {
19        mod openbsd;
20        mod sockaddr_tools;
21        use self::openbsd::PlatformSupportOpenBSD as PlatformSupport;
22    } else if #[cfg(all(target_arch = "wasm32", target_os = "unknown"))] {
23        mod wasm;
24        use self::wasm::PlatformSupportWasm as PlatformSupport;
25    } else {
26        compile_error!("No network interfaces support for this platform!");
27    }
28}
29
30/// An interface address that is either IPv4 or IPv6, with its netmask and broadcast address.
31#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Serialize, Deserialize)]
32pub enum IfAddr {
33    /// An IPv4 interface address.
34    V4(Ifv4Addr),
35    /// An IPv6 interface address.
36    V6(Ifv6Addr),
37}
38
39impl IfAddr {
40    /// The IP address.
41    #[must_use]
42    pub fn ip(&self) -> IpAddr {
43        match *self {
44            IfAddr::V4(ref ifv4_addr) => IpAddr::V4(ifv4_addr.ip),
45            IfAddr::V6(ref ifv6_addr) => IpAddr::V6(ifv6_addr.ip),
46        }
47    }
48    /// The netmask.
49    #[must_use]
50    pub fn netmask(&self) -> IpAddr {
51        match *self {
52            IfAddr::V4(ref ifv4_addr) => IpAddr::V4(ifv4_addr.netmask),
53            IfAddr::V6(ref ifv6_addr) => IpAddr::V6(ifv6_addr.netmask),
54        }
55    }
56    /// The broadcast address, if one is set.
57    pub fn broadcast(&self) -> Option<IpAddr> {
58        match *self {
59            IfAddr::V4(ref ifv4_addr) => ifv4_addr.broadcast.map(IpAddr::V4),
60            IfAddr::V6(ref ifv6_addr) => ifv6_addr.broadcast.map(IpAddr::V6),
61        }
62    }
63
64    /// The network address, with the host bits masked off by the netmask.
65    #[must_use]
66    pub fn network(&self) -> IfAddr {
67        match *self {
68            IfAddr::V4(ref ifv4_addr) => IfAddr::V4(ifv4_addr.network()),
69            IfAddr::V6(ref ifv6_addr) => IfAddr::V6(ifv6_addr.network()),
70        }
71    }
72
73    /// Whether `addr` is in the same subnet as this address. False across address families.
74    #[must_use]
75    pub fn contains_address(&self, addr: IpAddr) -> bool {
76        match *self {
77            IfAddr::V4(ref ifv4_addr) => match addr {
78                IpAddr::V4(ipv4_addr) => ifv4_addr.contains_address(ipv4_addr),
79                IpAddr::V6(_consensus_count) => false,
80            },
81            IfAddr::V6(ref ifv6_addr) => match addr {
82                IpAddr::V4(_) => false,
83                IpAddr::V6(ipv6_addr) => ifv6_addr.contains_address(ipv6_addr),
84            },
85        }
86    }
87}
88
89impl fmt::Display for IfAddr {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        match *self {
92            IfAddr::V4(ref ifv4_addr) => write!(f, "{}", f.to_string(ifv4_addr)),
93            IfAddr::V6(ref ifv6_addr) => write!(f, "{}", f.to_string(ifv6_addr)),
94        }
95    }
96}
97
98/// Details about the ipv4 address of an interface on this host.
99#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Serialize, Deserialize)]
100pub struct Ifv4Addr {
101    /// The IP address of the interface.
102    pub ip: Ipv4Addr,
103    /// The netmask of the interface.
104    pub netmask: Ipv4Addr,
105    /// The broadcast address of the interface.
106    pub broadcast: Option<Ipv4Addr>,
107}
108
109impl fmt::Display for Ifv4Addr {
110    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111        write!(
112            f,
113            "{}/{}",
114            self.ip,
115            if let Some(prefix_len) = self.prefix_len() {
116                format!("{}", prefix_len)
117            } else {
118                format!("(invalid netmask={})", self.netmask)
119            },
120        )
121    }
122}
123
124fn ipv4_netmask_to_prefix_len(netmask: Ipv4Addr) -> Option<u8> {
125    let netmask_u32 = netmask.to_bits();
126    let prefix_len = netmask_u32.leading_ones() as u8;
127    let suffix_len = netmask_u32.trailing_zeros() as u8;
128
129    if suffix_len != 32 - prefix_len {
130        None
131    } else {
132        Some(prefix_len)
133    }
134}
135
136impl Ifv4Addr {
137    /// Convert ip address to the address of the network itself by applying the netmask
138    #[must_use]
139    pub fn network(&self) -> Ifv4Addr {
140        let v4 = self.ip.octets();
141        let v4mask = self.netmask.octets();
142        let ip = Ipv4Addr::new(
143            v4[0] & v4mask[0],
144            v4[1] & v4mask[1],
145            v4[2] & v4mask[2],
146            v4[3] & v4mask[3],
147        );
148        Ifv4Addr {
149            ip,
150            netmask: self.netmask,
151            broadcast: self.broadcast,
152        }
153    }
154
155    /// Whether `addr` is in this address's subnet.
156    #[must_use]
157    pub fn contains_address(&self, addr: Ipv4Addr) -> bool {
158        let v4mask = self.netmask.octets();
159
160        let self_v4 = self.ip.octets();
161        let self_ip = Ipv4Addr::new(
162            self_v4[0] & v4mask[0],
163            self_v4[1] & v4mask[1],
164            self_v4[2] & v4mask[2],
165            self_v4[3] & v4mask[3],
166        );
167
168        let addr_v4 = addr.octets();
169        let addr_ip = Ipv4Addr::new(
170            addr_v4[0] & v4mask[0],
171            addr_v4[1] & v4mask[1],
172            addr_v4[2] & v4mask[2],
173            addr_v4[3] & v4mask[3],
174        );
175
176        self_ip == addr_ip
177    }
178
179    /// CIDR prefix length of the netmask, or `None` if the netmask is not contiguous.
180    #[must_use]
181    pub fn prefix_len(&self) -> Option<u8> {
182        ipv4_netmask_to_prefix_len(self.netmask)
183    }
184}
185
186/// Details about the ipv6 address of an interface on this host.
187#[derive(Debug, PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Serialize, Deserialize)]
188pub struct Ifv6Addr {
189    /// The IP address of the interface.
190    pub ip: Ipv6Addr,
191    /// The netmask of the interface.
192    pub netmask: Ipv6Addr,
193    /// The broadcast address of the interface.
194    pub broadcast: Option<Ipv6Addr>,
195}
196
197impl fmt::Display for Ifv6Addr {
198    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199        write!(
200            f,
201            "{}/{}",
202            self.ip,
203            if let Some(prefix_len) = self.prefix_len() {
204                format!("{}", prefix_len)
205            } else {
206                format!("(invalid netmask={})", self.netmask)
207            },
208        )
209    }
210}
211
212impl Ifv6Addr {
213    /// Convert ip address to the address of the network itself by applying the netmask
214    #[must_use]
215    pub fn network(&self) -> Ifv6Addr {
216        let v6 = self.ip.segments();
217        let v6mask = self.netmask.segments();
218        let ip = Ipv6Addr::new(
219            v6[0] & v6mask[0],
220            v6[1] & v6mask[1],
221            v6[2] & v6mask[2],
222            v6[3] & v6mask[3],
223            v6[4] & v6mask[4],
224            v6[5] & v6mask[5],
225            v6[6] & v6mask[6],
226            v6[7] & v6mask[7],
227        );
228        Ifv6Addr {
229            ip,
230            netmask: self.netmask,
231            broadcast: self.broadcast,
232        }
233    }
234
235    /// Whether `addr` is in this address's subnet.
236    #[must_use]
237    pub fn contains_address(&self, addr: Ipv6Addr) -> bool {
238        let v6mask = self.netmask.segments();
239
240        let self_v6 = self.ip.segments();
241        let self_ip = Ipv6Addr::new(
242            self_v6[0] & v6mask[0],
243            self_v6[1] & v6mask[1],
244            self_v6[2] & v6mask[2],
245            self_v6[3] & v6mask[3],
246            self_v6[4] & v6mask[4],
247            self_v6[5] & v6mask[5],
248            self_v6[6] & v6mask[6],
249            self_v6[7] & v6mask[7],
250        );
251
252        let addr_v6 = addr.segments();
253        let addr_ip = Ipv6Addr::new(
254            addr_v6[0] & v6mask[0],
255            addr_v6[1] & v6mask[1],
256            addr_v6[2] & v6mask[2],
257            addr_v6[3] & v6mask[3],
258            addr_v6[4] & v6mask[4],
259            addr_v6[5] & v6mask[5],
260            addr_v6[6] & v6mask[6],
261            addr_v6[7] & v6mask[7],
262        );
263
264        self_ip == addr_ip
265    }
266
267    /// CIDR prefix length of the netmask, or `None` if the netmask is not contiguous.
268    #[must_use]
269    pub fn prefix_len(&self) -> Option<u8> {
270        ipv6_netmask_to_prefix_len(self.netmask)
271    }
272}
273
274fn ipv6_netmask_to_prefix_len(netmask: Ipv6Addr) -> Option<u8> {
275    let netmask_u128 = netmask.to_bits();
276    let prefix_len = netmask_u128.leading_ones() as u8;
277    let suffix_len = netmask_u128.trailing_zeros() as u8;
278
279    if suffix_len != 128 - prefix_len {
280        None
281    } else {
282        Some(prefix_len)
283    }
284}
285
286/// Some of the flags associated with an interface
287#[derive(
288    Debug, Default, PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Copy, Serialize, Deserialize,
289)]
290pub struct InterfaceFlags {
291    /// Loopback interface (lo/lo0)
292    pub is_loopback: bool,
293    /// Interface is up and operational
294    pub is_running: bool,
295    /// Point-to-point link (PPP, tun, etc.)
296    pub is_point_to_point: bool,
297    /// CLAT (RFC 6877) virtual interface (e.g. Android `clat4*` / `v4-*`); local-only
298    pub is_clat: bool,
299}
300
301/// Some of the flags associated with an address
302#[derive(
303    Debug, Default, PartialEq, Eq, Ord, PartialOrd, Hash, Clone, Copy, Serialize, Deserialize,
304)]
305pub struct AddressFlags {
306    /// Auto-configured (DHCP/SLAAC) rather than statically configured
307    pub is_dynamic: bool,
308    /// RFC 4941 privacy/temporary IPv6 address (rotates periodically)
309    pub is_temporary: bool,
310    /// Address has finished DAD and is not deprecated/tentative/duplicated
311    pub is_preferred: bool,
312    /// CLAT46 synthesized IPv6 (RFC 6877); local-only on macOS/iOS
313    pub is_clat: bool,
314    /// Absolute expiration in seconds since UNIX epoch; `None` if non-expiring (static)
315    pub expiration_secs: Option<u64>,
316}
317
318/// An address bound to an interface, with its flags. Ordered by reachability preference.
319#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)]
320pub struct InterfaceAddress {
321    /// The IPv4 or IPv6 address with its netmask and broadcast.
322    pub if_addr: IfAddr,
323    /// Scope and lifetime flags for the address.
324    pub flags: AddressFlags,
325}
326
327use core::cmp::Ordering;
328
329// less is less preferable, greater is more preferable
330impl Ord for InterfaceAddress {
331    fn cmp(&self, other: &Self) -> Ordering {
332        match (&self.if_addr, &other.if_addr) {
333            (IfAddr::V4(a), IfAddr::V4(b)) => {
334                // global scope addresses are better
335                let ret = ipv4addr_is_global(&a.ip).cmp(&ipv4addr_is_global(&b.ip));
336                if ret != Ordering::Equal {
337                    return ret;
338                }
339                // local scope addresses are better
340                let ret = ipv4addr_is_private(&a.ip).cmp(&ipv4addr_is_private(&b.ip));
341                if ret != Ordering::Equal {
342                    return ret;
343                }
344                // non-dynamic addresses are better
345                let ret = (!self.flags.is_dynamic).cmp(&!other.flags.is_dynamic);
346                if ret != Ordering::Equal {
347                    return ret;
348                }
349            }
350            (IfAddr::V6(a), IfAddr::V6(b)) => {
351                // preferred addresses are better
352                let ret = self.flags.is_preferred.cmp(&other.flags.is_preferred);
353                if ret != Ordering::Equal {
354                    return ret;
355                }
356                // non-temporary address are better
357                let ret = (!self.flags.is_temporary).cmp(&!other.flags.is_temporary);
358                if ret != Ordering::Equal {
359                    return ret;
360                }
361                // global scope addresses are better
362                let ret = ipv6addr_is_global(&a.ip).cmp(&ipv6addr_is_global(&b.ip));
363                if ret != Ordering::Equal {
364                    return ret;
365                }
366                // unique local unicast addresses are better
367                let ret = ipv6addr_is_unique_local(&a.ip).cmp(&ipv6addr_is_unique_local(&b.ip));
368                if ret != Ordering::Equal {
369                    return ret;
370                }
371                // unicast site local addresses are better
372                let ret = ipv6addr_is_unicast_site_local(&a.ip)
373                    .cmp(&ipv6addr_is_unicast_site_local(&b.ip));
374                if ret != Ordering::Equal {
375                    return ret;
376                }
377                // unicast link local addresses are better
378                let ret = ipv6addr_is_unicast_link_local(&a.ip)
379                    .cmp(&ipv6addr_is_unicast_link_local(&b.ip));
380                if ret != Ordering::Equal {
381                    return ret;
382                }
383                // non-dynamic addresses are better
384                let ret = (!self.flags.is_dynamic).cmp(&!other.flags.is_dynamic);
385                if ret != Ordering::Equal {
386                    return ret;
387                }
388            }
389            (IfAddr::V4(a), IfAddr::V6(b)) => {
390                // If the IPv6 address is preferred and not temporary, compare if it is global scope
391                if other.flags.is_preferred && !other.flags.is_temporary {
392                    let ret = ipv4addr_is_global(&a.ip).cmp(&ipv6addr_is_global(&b.ip));
393                    if ret != Ordering::Equal {
394                        return ret;
395                    }
396                }
397
398                // Default, prefer IPv4 because many IPv6 addresses are not actually routed
399                return Ordering::Greater;
400            }
401            (IfAddr::V6(a), IfAddr::V4(b)) => {
402                // If the IPv6 address is preferred and not temporary, compare if it is global scope
403                if self.flags.is_preferred && !self.flags.is_temporary {
404                    let ret = ipv6addr_is_global(&a.ip).cmp(&ipv4addr_is_global(&b.ip));
405                    if ret != Ordering::Equal {
406                        return ret;
407                    }
408                }
409
410                // Default, prefer IPv4 because many IPv6 addresses are not actually routed
411                return Ordering::Less;
412            }
413        }
414        // stable sort
415        let ret = self.if_addr.cmp(&other.if_addr);
416        if ret != Ordering::Equal {
417            return ret;
418        }
419        self.flags.cmp(&other.flags)
420    }
421}
422impl PartialOrd for InterfaceAddress {
423    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
424        Some(self.cmp(other))
425    }
426}
427
428impl InterfaceAddress {
429    /// Make an `InterfaceAddress` from an address and its flags.
430    #[must_use]
431    pub fn new(if_addr: IfAddr, flags: AddressFlags) -> Self {
432        Self { if_addr, flags }
433    }
434
435    /// Underlying IPv4 or IPv6 address with netmask/broadcast.
436    #[must_use]
437    pub fn if_addr(&self) -> &IfAddr {
438        &self.if_addr
439    }
440
441    /// RFC 4941 privacy/temporary IPv6 (rotates)
442    #[must_use]
443    pub fn is_temporary(&self) -> bool {
444        self.flags.is_temporary
445    }
446
447    /// Auto-configured (DHCP/SLAAC) rather than statically configured
448    #[must_use]
449    pub fn is_dynamic(&self) -> bool {
450        self.flags.is_dynamic
451    }
452
453    /// Address has finished DAD and is not deprecated/tentative/duplicated
454    #[must_use]
455    pub fn is_preferred(&self) -> bool {
456        self.flags.is_preferred
457    }
458
459    /// CLAT46 synthesized IPv6 (RFC 6877) — local-only on macOS/iOS
460    #[must_use]
461    pub fn is_clat(&self) -> bool {
462        self.flags.is_clat
463    }
464
465    /// IPv4 address in CGNAT (RFC 6598 100.64.0.0/10) or DS-Lite (RFC 6333 192.0.0.0/24);
466    /// always false for IPv6
467    #[must_use]
468    pub fn is_cgnat_or_ds_lite(&self) -> bool {
469        match &self.if_addr {
470            IfAddr::V4(v4) => {
471                ipv4addr_is_shared(&v4.ip) || ipv4addr_is_ietf_protocol_assignment(&v4.ip)
472            }
473            IfAddr::V6(_) => false,
474        }
475    }
476
477    /// IPv4 link-local / APIPA (169.254.0.0/16) — DHCP-failed fallback, transient
478    /// and not reachable by peers; always false for IPv6 (link-local IPv6 is
479    /// handled by `ipv6addr_is_global`)
480    #[must_use]
481    pub fn is_ipv4_link_local(&self) -> bool {
482        match &self.if_addr {
483            IfAddr::V4(v4) => ipv4addr_is_link_local(&v4.ip),
484            IfAddr::V6(_) => false,
485        }
486    }
487}
488
489// #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
490// enum NetworkInterfaceType {
491//     Mobile,     // Least preferable, usually metered and slow
492//     Unknown,    // Everything else if we can't detect the type
493//     Wireless,   // Wifi is usually free or cheap and medium speed
494//     Wired,      // Wired is usually free or cheap and high speed
495// }
496
497/// A single network interface on this host, with its flags, gateways, and addresses.
498#[derive(PartialEq, Eq, Clone, Serialize, Deserialize)]
499pub struct NetworkInterface {
500    /// OS interface name (e.g. `en0`, `eth0`).
501    pub name: String,
502    /// Interface-level flags.
503    pub flags: InterfaceFlags,
504    /// Default-route gateways reachable through this interface, sorted and deduplicated.
505    pub gateways: Vec<IpAddr>,
506    /// Addresses bound to this interface, sorted by preference and deduplicated.
507    pub addrs: Vec<InterfaceAddress>,
508}
509
510impl fmt::Debug for NetworkInterface {
511    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
512        let alt = f.alternate();
513        let mut ds = f.debug_struct("NetworkInterface");
514        ds.field("name", &self.name)
515            .field("flags", &self.flags)
516            .field("gateways", &self.gateways)
517            .field("addrs", &self.addrs);
518        if alt {
519            ds.field("// primary_ipv4", &self.primary_ipv4());
520            ds.field("// primary_ipv6", &self.primary_ipv6());
521        }
522        ds.finish()
523    }
524}
525impl NetworkInterface {
526    /// Make an empty `NetworkInterface` with the given name and flags.
527    #[must_use]
528    pub fn new(name: String, flags: InterfaceFlags) -> Self {
529        Self {
530            name,
531            flags,
532            gateways: Vec::new(),
533            addrs: Vec::new(),
534        }
535    }
536
537    /// OS interface name (e.g. `en0`, `eth0`, `clat4-rmnet_data0`)
538    #[must_use]
539    pub fn name(&self) -> String {
540        self.name.clone()
541    }
542
543    /// Loopback interface (lo/lo0)
544    #[must_use]
545    pub fn is_loopback(&self) -> bool {
546        self.flags.is_loopback
547    }
548
549    /// Point-to-point link (PPP, tun, etc.)
550    #[must_use]
551    pub fn is_point_to_point(&self) -> bool {
552        self.flags.is_point_to_point
553    }
554
555    /// Interface is up and operational
556    #[must_use]
557    pub fn is_running(&self) -> bool {
558        self.flags.is_running
559    }
560
561    /// CLAT (RFC 6877) virtual interface (e.g. Android `clat4*` / `v4-*`); local-only
562    #[must_use]
563    pub fn is_clat(&self) -> bool {
564        self.flags.is_clat
565    }
566
567    /// Append an address to this interface; addresses are kept sorted and deduplicated
568    pub fn add_address(&mut self, addr: InterfaceAddress) {
569        self.addrs.push(addr);
570        self.addrs.sort();
571        self.addrs.dedup();
572    }
573
574    /// All addresses bound to this interface.
575    #[must_use]
576    pub fn addresses(&self) -> &[InterfaceAddress] {
577        &self.addrs
578    }
579
580    /// Append a gateway to this interface; gateways are kept sorted and deduplicated.
581    pub fn add_gateway(&mut self, gateway: IpAddr) {
582        self.gateways.push(gateway);
583        self.gateways.sort();
584        self.gateways.dedup();
585    }
586
587    /// Default-route gateways reachable through this interface.
588    #[must_use]
589    pub fn gateways(&self) -> &[IpAddr] {
590        &self.gateways
591    }
592
593    /// Most-preferred IPv4 address on this interface (per `InterfaceAddress::cmp`).
594    #[must_use]
595    pub fn primary_ipv4(&self) -> Option<InterfaceAddress> {
596        let mut ipv4addrs: Vec<&InterfaceAddress> = self
597            .addrs
598            .iter()
599            .filter(|a| matches!(a.if_addr(), IfAddr::V4(_)))
600            .collect();
601        ipv4addrs.sort();
602        ipv4addrs.last().cloned().cloned()
603    }
604
605    /// Most-preferred IPv6 address on this interface (per `InterfaceAddress::cmp`).
606    #[must_use]
607    pub fn primary_ipv6(&self) -> Option<InterfaceAddress> {
608        let mut ipv6addrs: Vec<&InterfaceAddress> = self
609            .addrs
610            .iter()
611            .filter(|a| matches!(a.if_addr(), IfAddr::V6(_)))
612            .collect();
613        ipv6addrs.sort();
614        ipv6addrs.last().cloned().cloned()
615    }
616}
617
618/// The best routable gateway and interface addresses distilled from all interfaces.
619#[derive(Clone, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
620pub struct NetworkInterfaceAddressState {
621    /// Gateway addresses across all interfaces, sorted and deduplicated.
622    pub gateway_addresses: Vec<IpAddr>,
623    /// Best routable interface addresses, sorted by preference and deduplicated.
624    pub interface_addresses: Vec<IfAddr>,
625}
626
627/// Which address families are present across the running non-loopback interfaces.
628#[derive(Clone, Copy, Default, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
629pub struct RawAddressSummary {
630    /// At least one non-loopback IPv4 address is present.
631    pub has_non_loopback_ipv4: bool,
632    /// At least one non-loopback IPv6 address is present.
633    pub has_non_loopback_ipv6: bool,
634    /// At least one globally-routable unicast IPv6 address is present.
635    pub has_unicast_global_ipv6: bool,
636}
637
638/// Mutex-guarded interior state of `NetworkInterfaces`.
639pub struct NetworkInterfacesInner {
640    valid: bool,
641    interfaces: BTreeMap<String, NetworkInterface>,
642    interface_address_state_cache: Arc<NetworkInterfaceAddressState>,
643}
644
645/// Cross-platform enumeration of this host's network interfaces and their addresses.
646#[derive(Clone)]
647pub struct NetworkInterfaces {
648    inner: Arc<Mutex<NetworkInterfacesInner>>,
649}
650
651impl fmt::Debug for NetworkInterfaces {
652    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
653        let inner = self.inner.lock();
654        f.debug_struct("NetworkInterfaces")
655            .field("valid", &inner.valid)
656            .field("interfaces", &inner.interfaces)
657            .finish()?;
658        if f.alternate() {
659            writeln!(f)?;
660            writeln!(
661                f,
662                "// interface_address_state_cache: {:?}",
663                inner.interface_address_state_cache
664            )?;
665        }
666        Ok(())
667    }
668}
669
670impl Default for NetworkInterfaces {
671    fn default() -> Self {
672        Self::new()
673    }
674}
675
676impl NetworkInterfaces {
677    /// Make an empty `NetworkInterfaces`; call `refresh` to populate it.
678    #[must_use]
679    pub fn new() -> Self {
680        Self {
681            inner: Arc::new(Mutex::new(NetworkInterfacesInner {
682                valid: false,
683                interfaces: BTreeMap::new(),
684                interface_address_state_cache: Arc::new(NetworkInterfaceAddressState::default()),
685            })),
686        }
687    }
688
689    /// Whether a successful `refresh` has populated the interface set.
690    #[must_use]
691    pub fn is_valid(&self) -> bool {
692        let inner = self.inner.lock();
693        inner.valid
694    }
695    /// Drop all interfaces and the cached address state, marking the set invalid.
696    pub fn clear(&self) {
697        let mut inner = self.inner.lock();
698
699        inner.interfaces.clear();
700        inner.interface_address_state_cache = Arc::new(NetworkInterfaceAddressState::default());
701        inner.valid = false;
702    }
703    /// Re-enumerate the platform's interfaces. Returns `true` if the best-address cache changed.
704    ///
705    /// Errors with an `io::Error` if the platform's interface enumeration fails; the interface set is
706    /// left unchanged in that case.
707    // returns false if refresh had no changes, true if changes were present
708    pub async fn refresh(&self) -> std::io::Result<bool> {
709        let mut last_interfaces = {
710            let mut last_interfaces = BTreeMap::<String, NetworkInterface>::new();
711            let mut platform_support = PlatformSupport::new();
712            platform_support
713                .get_interfaces(&mut last_interfaces)
714                .await?;
715            last_interfaces
716        };
717
718        // Drop globally-routable addresses from interfaces that aren't the kernel's
719        // primary outbound source for that family (e.g. iOS cellular IPv6 while wifi
720        // is primary). Private/local addresses are kept so the LocalNetwork routing
721        // domain still works. Don't do this on wasm32-unknown-unknown because we don't have
722        // access to the network stack.
723        #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
724        Self::filter_non_primary_global_addresses(&mut last_interfaces);
725
726        let mut inner = self.inner.lock();
727        core::mem::swap(&mut inner.interfaces, &mut last_interfaces);
728        inner.valid = true;
729
730        if last_interfaces != inner.interfaces {
731            // get last address cache
732            let old_interface_address_state = inner.interface_address_state_cache.clone();
733
734            // redo the address cache
735            Self::cache_interface_address_state(&mut inner);
736
737            // See if our best addresses have changed
738            if old_interface_address_state != inner.interface_address_state_cache {
739                return Ok(true);
740            }
741        }
742        Ok(false)
743    }
744    /// Call `f` with the interface map held under the lock, returning its result.
745    pub fn with_interfaces<F, R>(&self, f: F) -> R
746    where
747        F: FnOnce(&BTreeMap<String, NetworkInterface>) -> R,
748    {
749        let inner = self.inner.lock();
750        f(&inner.interfaces)
751    }
752
753    /// The cached best-address state from the last `refresh`.
754    #[must_use]
755    pub fn interface_address_state(&self) -> Arc<NetworkInterfaceAddressState> {
756        let inner = self.inner.lock();
757        inner.interface_address_state_cache.clone()
758    }
759
760    /// Address presence across running, non-loopback interfaces, NOT filtered by
761    /// `is_temporary()`, since RFC 7217 secured SLAAC globals get marked temporary on macOS.
762    #[must_use]
763    pub fn raw_address_summary(&self) -> RawAddressSummary {
764        let inner = self.inner.lock();
765        let mut summary = RawAddressSummary::default();
766        for intf in inner.interfaces.values() {
767            if !intf.is_running() || intf.is_loopback() {
768                continue;
769            }
770            for addr in intf.addresses() {
771                match addr.if_addr() {
772                    IfAddr::V4(v4) => {
773                        if !v4.ip.is_loopback() && !v4.ip.is_unspecified() {
774                            summary.has_non_loopback_ipv4 = true;
775                        }
776                    }
777                    IfAddr::V6(v6) => {
778                        if !v6.ip.is_loopback() && !v6.ip.is_unspecified() {
779                            summary.has_non_loopback_ipv6 = true;
780                            if ipv6addr_is_unicast_global(&v6.ip) {
781                                summary.has_unicast_global_ipv6 = true;
782                            }
783                        }
784                    }
785                }
786            }
787        }
788        summary
789    }
790
791    /////////////////////////////////////////////
792
793    // For each interface, keep only addresses that are either non-global (private/
794    // link-local/ULA, needed by LocalNetwork) or match the kernel's primary outbound
795    // source for that family. A global address on a non-primary interface is
796    // unreachable from peers — publishing it as our dial info just causes timeouts.
797    // Interfaces that end up with no addresses are dropped entirely.
798
799    #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
800    fn filter_non_primary_global_addresses(interfaces: &mut BTreeMap<String, NetworkInterface>) {
801        let primary_v4 = primary_source_ipv4();
802        let primary_v6 = primary_source_ipv6();
803
804        interfaces.retain(|_, intf| {
805            intf.addrs.retain(|addr| match addr.if_addr() {
806                IfAddr::V4(v4) => {
807                    !ipv4addr_is_global(&v4.ip) || primary_v4.is_some_and(|p| p == v4.ip)
808                }
809                IfAddr::V6(v6) => {
810                    !ipv6addr_is_unicast_global(&v6.ip) || primary_v6.is_some_and(|p| p == v6.ip)
811                }
812            });
813            !intf.addrs.is_empty()
814        });
815    }
816
817    fn cache_interface_address_state(inner: &mut NetworkInterfacesInner) {
818        // Reduce interfaces to their best routable ip addresses
819        let mut intf_addrs = Vec::new();
820        let mut gateway_addresses = Vec::new();
821        for intf in inner.interfaces.values() {
822            // Skip down, loopback, and CLAT virtual interfaces (Android: clat4*/v4-*)
823            if !intf.is_running() || intf.is_loopback() || intf.is_clat() {
824                continue;
825            }
826
827            for addr in intf.addresses() {
828                // Skip RFC 4941 privacy addresses; they rotate and churn published peer info
829                if addr.is_temporary() {
830                    continue;
831                }
832                // Skip macOS/iOS CLAT46 synthesized IPv6 (local-only translator address)
833                if addr.is_clat() {
834                    continue;
835                }
836                // Skip RFC 6598 CGNAT and RFC 6333 DS-Lite IPv4; peers can't reach them
837                if addr.is_cgnat_or_ds_lite() {
838                    continue;
839                }
840                // Skip RFC 3927 IPv4 link-local (169.254.0.0/16); transient DHCP-fallback
841                // address, never reachable by peers
842                if addr.is_ipv4_link_local() {
843                    continue;
844                }
845                intf_addrs.push(addr.clone());
846            }
847
848            for gateway in intf.gateways() {
849                gateway_addresses.push(*gateway);
850            }
851        }
852
853        // Sort one more time to get the best interface addresses overall
854        let mut interface_addresses = intf_addrs
855            .iter()
856            .map(|x| x.if_addr().clone())
857            .collect::<Vec<_>>();
858
859        interface_addresses.sort();
860        interface_addresses.dedup();
861
862        gateway_addresses.sort();
863        gateway_addresses.dedup();
864
865        // Now export just the addresses
866        inner.interface_address_state_cache = Arc::new(NetworkInterfaceAddressState {
867            gateway_addresses,
868            interface_addresses,
869        });
870    }
871}