1#[cfg(not(wasm_browser))]
4use std::net::IpAddr;
5use std::net::Ipv6Addr;
6
7#[cfg(not(wasm_browser))]
8const IFF_UP: u32 = 0x1;
9#[cfg(not(wasm_browser))]
10const IFF_LOOPBACK: u32 = 0x8;
11
12#[cfg(not(wasm_browser))]
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct LocalAddresses {
16 pub loopback: Vec<IpAddr>,
18 pub regular: Vec<IpAddr>,
20}
21
22#[cfg(not(wasm_browser))]
23impl Default for LocalAddresses {
24 fn default() -> Self {
25 Self::new()
26 }
27}
28
29#[cfg(not(wasm_browser))]
30impl LocalAddresses {
31 pub fn new() -> Self {
35 let ifaces = netdev::interface::get_interfaces();
36 Self::from_raw_interfaces(&ifaces)
37 }
38
39 pub(crate) fn from_raw_interfaces(ifaces: &[netdev::Interface]) -> Self {
40 let mut loopback = Vec::new();
41 let mut regular4 = Vec::new();
42 let mut regular6 = Vec::new();
43 let mut linklocal4 = Vec::new();
44 let mut ula6 = Vec::new();
45
46 for iface in ifaces {
47 if !is_up(iface) {
48 continue;
50 }
51 let ifc_is_loopback = is_loopback(iface);
52 let addrs = iface
53 .ipv4
54 .iter()
55 .map(|a| IpAddr::V4(a.addr()))
56 .chain(iface.ipv6.iter().map(|a| IpAddr::V6(a.addr())));
57
58 for ip in addrs {
59 let ip = ip.to_canonical();
60
61 if ip.is_loopback() || ifc_is_loopback {
62 loopback.push(ip);
63 } else if is_link_local(ip) {
64 if ip.is_ipv4() {
65 linklocal4.push(ip);
66 }
67
68 } else if ip.is_ipv6() && is_private(&ip) {
75 ula6.push(ip);
78 } else if ip.is_ipv4() {
79 regular4.push(ip);
80 } else {
81 regular6.push(ip);
82 }
83 }
84 }
85
86 if regular4.is_empty() && regular6.is_empty() {
87 regular4 = linklocal4;
92 regular6 = ula6;
93 }
94 let mut regular = regular4;
95 regular.extend(regular6);
96
97 regular.sort();
98 loopback.sort();
99
100 LocalAddresses { loopback, regular }
101 }
102}
103
104#[cfg(not(wasm_browser))]
105pub(crate) const fn is_up(interface: &netdev::Interface) -> bool {
106 interface.flags & IFF_UP != 0
107}
108
109#[cfg(not(wasm_browser))]
110pub(crate) const fn is_loopback(interface: &netdev::Interface) -> bool {
111 interface.flags & IFF_LOOPBACK != 0
112}
113
114#[cfg(not(wasm_browser))]
118pub(crate) fn is_private(ip: &IpAddr) -> bool {
119 match ip {
120 IpAddr::V4(ip) => {
121 let octets = ip.octets();
124 octets[0] == 10
125 || (octets[0] == 172 && octets[1] & 0xf0 == 16)
126 || (octets[0] == 192 && octets[1] == 168)
127 }
128 IpAddr::V6(ip) => is_private_v6(ip),
129 }
130}
131
132#[cfg(not(wasm_browser))]
133pub(crate) fn is_private_v6(ip: &Ipv6Addr) -> bool {
134 ip.octets()[0] & 0xfe == 0xfc
136}
137
138#[cfg(not(wasm_browser))]
139pub(super) fn is_link_local(ip: IpAddr) -> bool {
140 match ip {
141 IpAddr::V4(ip) => ip.is_link_local(),
142 IpAddr::V6(ip) => is_unicast_link_local(ip),
143 }
144}
145
146pub const fn is_unicast_link_local(addr: Ipv6Addr) -> bool {
149 (addr.segments()[0] & 0xffc0) == 0xfe80
150}
151
152#[cfg(test)]
153mod tests {
154 #[cfg(not(wasm_browser))]
155 #[test]
156 fn test_local_addresses() {
157 let addrs = super::LocalAddresses::new();
158 dbg!(&addrs);
159 assert!(!addrs.loopback.is_empty());
160 assert!(!addrs.regular.is_empty());
161 }
162}