netwatch/
ip.rs

1//! IP address related utilities.
2
3#[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/// List of machine's IP addresses.
13#[cfg(not(wasm_browser))]
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct LocalAddresses {
16    /// Loopback addresses.
17    pub loopback: Vec<IpAddr>,
18    /// Regular addresses.
19    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    /// Returns the machine's IP addresses.
32    /// If there are no regular addresses it will return any IPv4 linklocal or IPv6 unique local
33    /// addresses because we know of environments where these are used with NAT to provide connectivity.
34    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                // Skip down interfaces
49                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                    // We know of no cases where the IPv6 fe80:: addresses
69                    // are used to provide WAN connectivity. It is also very
70                    // common for users to have no IPv6 WAN connectivity,
71                    // but their OS supports IPv6 so they have an fe80::
72                    // address. We don't want to report all of those
73                    // IPv6 LL to Control.
74                } else if ip.is_ipv6() && is_private(&ip) {
75                    // Google Cloud Run uses NAT with IPv6 Unique
76                    // Local Addresses to provide IPv6 connectivity.
77                    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            // if we have no usable IP addresses then be willing to accept
88            // addresses we otherwise wouldn't, like:
89            //   + 169.254.x.x (AWS Lambda uses NAT with these)
90            //   + IPv6 ULA (Google Cloud Run uses these with address translation)
91            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/// Reports whether ip is a private address, according to RFC 1918
115/// (IPv4 addresses) and RFC 4193 (IPv6 addresses). That is, it reports whether
116/// ip is in 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, or fc00::/7.
117#[cfg(not(wasm_browser))]
118pub(crate) fn is_private(ip: &IpAddr) -> bool {
119    match ip {
120        IpAddr::V4(ip) => {
121            // RFC 1918 allocates 10.0.0.0/8, 172.16.0.0/12, and 192.168.0.0/16 as
122            // private IPv4 address subnets.
123            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    // RFC 4193 allocates fc00::/7 as the unique local unicast IPv6 address subnet.
135    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
146/// Returns true if the address is a unicast address with link-local scope, as defined in RFC 4291.
147// Copied from std lib, not stable yet
148pub 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}