|
| 1 | +//! Fallback interfaces implementation for platforms without `netdev`. |
| 2 | +//! Provides stub types — no network interface enumeration available. |
| 3 | +
|
| 4 | +use std::{collections::HashMap, fmt, net::IpAddr}; |
| 5 | + |
| 6 | +pub(crate) use ipnet::{Ipv4Net, Ipv6Net}; |
| 7 | +use n0_future::time::Instant; |
| 8 | + |
| 9 | +use crate::ip::LocalAddresses; |
| 10 | + |
| 11 | +/// Represents a network interface (stub). |
| 12 | +#[derive(Clone, Debug, PartialEq, Eq)] |
| 13 | +pub struct Interface; |
| 14 | + |
| 15 | +impl fmt::Display for Interface { |
| 16 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 17 | + write!(f, "unknown") |
| 18 | + } |
| 19 | +} |
| 20 | + |
| 21 | +impl Interface { |
| 22 | + /// A list of all ip addresses of this interface. |
| 23 | + pub fn addrs(&self) -> impl Iterator<Item = IpNet> + '_ { |
| 24 | + std::iter::empty() |
| 25 | + } |
| 26 | +} |
| 27 | + |
| 28 | +/// State flags for a single IPv6 address. |
| 29 | +/// |
| 30 | +/// Hand-kept mirror of netdev's `Ipv6AddrFlags`, so the `interfaces` API is |
| 31 | +/// identical on platforms built without `netdev` (e.g. esp-idf). All fields |
| 32 | +/// default to `false` when the platform does not provide the information. |
| 33 | +#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] |
| 34 | +pub struct Ipv6AddrFlags { |
| 35 | + /// Preferred lifetime expired; should not be used for new connections. |
| 36 | + pub deprecated: bool, |
| 37 | + /// Privacy address ([RFC 4941](https://datatracker.ietf.org/doc/html/rfc4941)). |
| 38 | + pub temporary: bool, |
| 39 | + /// Undergoing duplicate address detection. |
| 40 | + pub tentative: bool, |
| 41 | + /// Duplicate address detection failed. |
| 42 | + pub duplicated: bool, |
| 43 | + /// Manually configured, not from SLAAC. |
| 44 | + pub permanent: bool, |
| 45 | +} |
| 46 | + |
| 47 | +/// Structure of an IP network, either IPv4 or IPv6. |
| 48 | +/// |
| 49 | +/// The shape mirrors the `netdev`-based `IpNet` so downstream code is |
| 50 | +/// platform-agnostic. |
| 51 | +#[derive(Clone, Debug)] |
| 52 | +pub enum IpNet { |
| 53 | + /// Structure of IPv4 Network. |
| 54 | + V4(Ipv4Net), |
| 55 | + /// Structure of IPv6 Network. |
| 56 | + V6 { |
| 57 | + /// The actual network address. |
| 58 | + net: Ipv6Net, |
| 59 | + /// IPv6 scope ID |
| 60 | + scope_id: u32, |
| 61 | + /// IPv6 address flags. |
| 62 | + flags: Ipv6AddrFlags, |
| 63 | + }, |
| 64 | +} |
| 65 | + |
| 66 | +impl PartialEq for IpNet { |
| 67 | + fn eq(&self, other: &Self) -> bool { |
| 68 | + match (self, other) { |
| 69 | + (IpNet::V4(a), IpNet::V4(b)) => { |
| 70 | + a.addr() == b.addr() |
| 71 | + && a.prefix_len() == b.prefix_len() |
| 72 | + && a.netmask() == b.netmask() |
| 73 | + } |
| 74 | + ( |
| 75 | + IpNet::V6 { |
| 76 | + net: net_a, |
| 77 | + scope_id: scope_id_a, |
| 78 | + flags: flags_a, |
| 79 | + }, |
| 80 | + IpNet::V6 { |
| 81 | + net: net_b, |
| 82 | + scope_id: scope_id_b, |
| 83 | + flags: flags_b, |
| 84 | + }, |
| 85 | + ) => { |
| 86 | + net_a.addr() == net_b.addr() |
| 87 | + && net_a.prefix_len() == net_b.prefix_len() |
| 88 | + && net_a.netmask() == net_b.netmask() |
| 89 | + && scope_id_a == scope_id_b |
| 90 | + && flags_a == flags_b |
| 91 | + } |
| 92 | + _ => false, |
| 93 | + } |
| 94 | + } |
| 95 | +} |
| 96 | +impl Eq for IpNet {} |
| 97 | + |
| 98 | +impl IpNet { |
| 99 | + /// The IP address of this structure. |
| 100 | + pub fn addr(&self) -> IpAddr { |
| 101 | + match self { |
| 102 | + IpNet::V4(a) => IpAddr::V4(a.addr()), |
| 103 | + IpNet::V6 { net, .. } => IpAddr::V6(net.addr()), |
| 104 | + } |
| 105 | + } |
| 106 | +} |
| 107 | + |
| 108 | +/// The router/gateway of the local network (stub — always returns `None`). |
| 109 | +#[derive(Debug, Clone)] |
| 110 | +pub struct HomeRouter { |
| 111 | + /// Ip of the router. |
| 112 | + pub gateway: IpAddr, |
| 113 | + /// Our local Ip if known. |
| 114 | + pub my_ip: Option<IpAddr>, |
| 115 | +} |
| 116 | + |
| 117 | +impl HomeRouter { |
| 118 | + /// Returns `None` — no gateway discovery available on this platform. |
| 119 | + pub fn new() -> Option<Self> { |
| 120 | + None |
| 121 | + } |
| 122 | +} |
| 123 | + |
| 124 | +/// Intended to store the state of the machine's network interfaces. |
| 125 | +#[derive(Debug, PartialEq, Eq, Clone)] |
| 126 | +pub struct State { |
| 127 | + /// Maps from an interface name to interface. |
| 128 | + pub interfaces: HashMap<String, Interface>, |
| 129 | + /// List of machine's local IP addresses. |
| 130 | + pub local_addresses: LocalAddresses, |
| 131 | + /// Whether this machine has IPv6 connectivity. |
| 132 | + pub have_v6: bool, |
| 133 | + /// Whether the machine has IPv4 connectivity. |
| 134 | + pub have_v4: bool, |
| 135 | + /// Whether the current network interface is considered "expensive". |
| 136 | + pub(crate) is_expensive: bool, |
| 137 | + /// The interface name for the machine's default route. |
| 138 | + pub default_route_interface: Option<String>, |
| 139 | + /// Monotonic timestamp, when an unsuspend was detected. |
| 140 | + pub last_unsuspend: Option<Instant>, |
| 141 | +} |
| 142 | + |
| 143 | +impl fmt::Display for State { |
| 144 | + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 145 | + write!(f, "fallback(no interfaces)") |
| 146 | + } |
| 147 | +} |
| 148 | + |
| 149 | +impl State { |
| 150 | + /// Returns a default empty state (no interface enumeration on this platform). |
| 151 | + pub async fn new() -> Self { |
| 152 | + State { |
| 153 | + interfaces: HashMap::new(), |
| 154 | + local_addresses: LocalAddresses::default(), |
| 155 | + have_v6: false, |
| 156 | + have_v4: true, |
| 157 | + is_expensive: false, |
| 158 | + default_route_interface: None, |
| 159 | + last_unsuspend: None, |
| 160 | + } |
| 161 | + } |
| 162 | + |
| 163 | + /// Is this a major change compared to the `old` one? |
| 164 | + pub fn is_major_change(&self, old: &State) -> bool { |
| 165 | + self != old |
| 166 | + } |
| 167 | +} |
0 commit comments