getip/
hostip.rs

1//! Implementations of `Provider` that receive local IP addresses of the machine
2//! from `libc` APIs and command-line helper programs (if available).
3//
4//  Copyright (C) 2021 Zhang Maiyun <myzhang1029@hotmail.com>
5//
6//  This file is part of DNS updater.
7//
8//  DNS updater is free software: you can redistribute it and/or modify
9//  it under the terms of the GNU Affero General Public License as published by
10//  the Free Software Foundation, either version 3 of the License, or
11//  (at your option) any later version.
12//
13//  DNS updater is distributed in the hope that it will be useful,
14//  but WITHOUT ANY WARRANTY; without even the implied warranty of
15//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16//  GNU Affero General Public License for more details.
17//
18//  You should have received a copy of the GNU Affero General Public License
19//  along with DNS updater.  If not, see <https://www.gnu.org/licenses/>.
20//
21
22use crate::libc_getips::get_iface_addrs;
23use crate::Provider;
24use crate::{Error, IpType, Result};
25use async_trait::async_trait;
26use log::{debug, info};
27use std::net::IpAddr;
28use std::process::{Output, Stdio};
29use std::str::FromStr;
30use tokio::process::Command;
31
32/// IPv6 Provider that queries information from `ip` or `ifconfig`.
33#[derive(Debug, Clone)]
34pub struct LocalIpv6CommandProvider {
35    nic: String,
36    // XXX: follow address selection algorithm for non-permanent
37    prefer_permanent: bool,
38}
39
40impl LocalIpv6CommandProvider {
41    /// Create a new `LocalIpv6CommandProvider`.
42    #[must_use]
43    pub fn new(nic: &str, permanent: bool) -> Self {
44        Self {
45            nic: nic.to_string(),
46            prefer_permanent: permanent,
47        }
48    }
49}
50
51#[async_trait]
52impl Provider for LocalIpv6CommandProvider {
53    /// Get a local IPv6 address on the specified interface with `ip` or `ifconfig`.
54    ///
55    /// # Errors
56    ///
57    /// This function returns `NoAddress` if the IP commands return no addresses.
58    /// In case that none of those commands succeed, it returns the last process execution error.
59    async fn get_addr(&self) -> Result<IpAddr> {
60        let out = chain_ip_cmd_until_succeed(&self.nic).await?;
61        // Extract output
62        let out_br = out.stdout.split(|c| *c == b'\n');
63        // Some systems use temporary addresses and one permanent address.
64        // The second fields indicates whether the "secured"/permanent flag is present.
65        let mut addrs: Vec<(IpAddr, bool)> = Vec::with_capacity(4);
66        // Extract addresses line by line
67        for line in out_br {
68            let line = String::from_utf8(line.to_vec())?;
69            let fields: Vec<String> = line
70                .split_whitespace()
71                .map(std::string::ToString::to_string)
72                .collect();
73            // A shorter one is certainly not an entry
74            // Check if the label is "inet6"
75            if fields.len() > 1 && fields[0] == "inet6" {
76                let address_stripped = match fields[1].split_once('/') {
77                    // `ip` includes the prefix length in the address
78                    Some((addr, _prefixlen)) => addr,
79                    // but `ifconfig` doesn't
80                    None => &fields[1],
81                };
82                if let Ok(addr6) = IpAddr::from_str(address_stripped) {
83                    // If "secured" (for RFC 3041, ifconfig) or "mngtmpaddr" (RFC 3041, ip) is in the flags, it is permanent
84                    let is_perm = fields.iter().any(|f| f == "secured" || f == "mngtmpaddr")
85                    // But any "temporary" tells us it is not.
86                        && fields.iter().all(|f| f != "temporary");
87                    // Treating non-RFC-3041 interface's addresses all the same
88                    if !addr6.is_loopback() {
89                        addrs.push((addr6, is_perm));
90                    }
91                }
92            }
93        }
94        if addrs.is_empty() {
95            debug!("Short-circuting NoAddress because an ip command succeeded without addresses");
96            Err(crate::Error::NoAddress)
97        } else {
98            Ok(addrs
99                .iter()
100                // If is_perm == permanent, it is the one we are looking for
101                .filter(|(_, is_perm)| *is_perm == self.prefer_permanent)
102                .map(|(addr, _)| *addr)
103                .next()
104                .unwrap_or(addrs[0].0))
105        }
106    }
107
108    fn get_type(&self) -> IpType {
109        // This Provider only has IPv6 capabilities
110        IpType::Ipv6
111    }
112}
113
114/// Run a chain of ip/ifconfig commands and returns the output of first succeeded one
115async fn chain_ip_cmd_until_succeed(nic: &str) -> Result<Output> {
116    // TODO: netsh.exe IPv6 backend
117    // netsh interface ipv6 show addresses interface="Ethernet" level=normal
118    // TODO: Enable IPv4 to be queried like this
119    let commands = [
120        // First try to use `ip`
121        (
122            "ip",
123            vec![
124                "address", "show", "dev", nic,
125                // This scope filters out unique-local addresses
126                "scope", "global",
127            ],
128        ),
129        // If that failed, try (BSD) ifconfig
130        ("ifconfig", vec!["-L", nic, "inet6"]),
131        // Linux ifconfig cannot distinguish between RFC 3041 temporary/permanent addresses
132        ("ifconfig", vec![nic]),
133    ];
134    // Record only the last failure
135    let mut last_error: Option<Error> = None;
136    for (cmd, args) in commands {
137        let mut command = Command::new(cmd);
138        command.stdout(Stdio::piped());
139        debug!("Running command {:?} with arguments {:?}", cmd, args);
140        let output = command.args(&args).output().await;
141        match output {
142            Ok(output) => {
143                if output.status.success() {
144                    return Ok(output);
145                }
146                // Since a chain of commands are executed, these are not really errors
147                debug!(
148                    "Command {:?} failed with status: {}",
149                    command, output.status
150                );
151                last_error = Some(Error::NonZeroExit(output.status));
152            }
153            Err(exec_error) => {
154                debug!(
155                    "Command {:?} failed to be executed: {}",
156                    command, exec_error
157                );
158                last_error = Some(Error::IoError(exec_error));
159            }
160        }
161    }
162    info!("None of the commands to extract the IPv6 address succeeded.");
163    // Not failable
164    Err(last_error.unwrap())
165}
166
167/// Force-cast $id: `IpAddr` to `Ipv4Addr`
168macro_rules! cast_ipv4 {
169    ($id: expr) => {
170        if let IpAddr::V4(ip) = $id {
171            ip
172        } else {
173            unreachable!()
174        }
175    };
176}
177
178/// Force-cast $id: `IpAddr` to `Ipv6Addr`
179macro_rules! cast_ipv6 {
180    ($id: expr) => {
181        if let IpAddr::V6(ip) = $id {
182            ip
183        } else {
184            unreachable!()
185        }
186    };
187}
188
189/// Filter function that removes loopback/link local/unspecified IPv4 addresses
190#[allow(clippy::trivially_copy_pass_by_ref)]
191fn filter_nonroute_ipv4(addr: &&IpAddr) -> bool {
192    let remove = !addr.is_loopback() && !cast_ipv4!(addr).is_link_local() && !addr.is_unspecified();
193    if remove {
194        debug!(
195            "Removing address {:?} because it is loopback/link local/unspecified",
196            addr
197        );
198    }
199    remove
200}
201
202/// Filter function that removes loopback/link local/unspecified IPv6 addresses
203#[allow(clippy::trivially_copy_pass_by_ref)]
204fn filter_nonroute_ipv6(addr: &&IpAddr) -> bool {
205    !addr.is_loopback()
206        && !addr.is_unspecified()
207        && !(cast_ipv6!(addr).segments()[0] & 0xffc0) == 0xfe80
208}
209
210/// Filter function that prefers global IPv4 addresses
211#[allow(clippy::trivially_copy_pass_by_ref)]
212fn filter_nonlocal_ipv4(addr: &&&IpAddr) -> bool {
213    !cast_ipv4!(addr).is_private()
214}
215
216/// Filter function that prefers global IPv6 addresses
217#[allow(clippy::trivially_copy_pass_by_ref)]
218fn filter_nonlocal_ipv6(_addr: &&&IpAddr) -> bool {
219    // XXX: `IpAddr::is_global` is probably a better choice but it's currently unstable.
220    false
221}
222
223/// Provider that queries information from libc interface.
224#[derive(Debug, Clone)]
225pub struct LocalLibcProvider {
226    nic: Option<String>,
227    ip_type: IpType,
228}
229
230impl<'a> LocalLibcProvider {
231    #[must_use]
232    /// Create a new `LocalLibcProvider`.
233    pub fn new(nic: Option<&'a str>, ip_type: IpType) -> Self {
234        Self {
235            nic: nic.map(std::string::ToString::to_string),
236            ip_type,
237        }
238    }
239}
240
241#[async_trait]
242impl Provider for LocalLibcProvider {
243    /// Get a local address on the specified interface.
244    ///
245    /// # Errors
246    ///
247    /// This function propagates the error from `libc_getips::get_iface_addrs`.
248    async fn get_addr(&self) -> Result<IpAddr> {
249        let addrs = get_iface_addrs(Some(self.ip_type), self.nic.as_deref())?;
250        let addrs: Vec<&IpAddr> = addrs
251            .iter()
252            // Remove loopback, link local, and unspecified
253            .filter(if self.ip_type == IpType::Ipv4 {
254                filter_nonroute_ipv4
255            } else {
256                filter_nonroute_ipv6
257            })
258            .collect();
259        // Prefer the ones that are likely global
260        let first_non_local_addr: Option<&&IpAddr> =
261            addrs.iter().find(if self.ip_type == IpType::Ipv4 {
262                filter_nonlocal_ipv4
263            } else {
264                filter_nonlocal_ipv6
265            });
266        first_non_local_addr.map_or_else(|| Ok(*addrs[0]), |addr| Ok(**addr))
267    }
268
269    fn get_type(&self) -> IpType {
270        self.ip_type
271    }
272}