1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210
//! Helper to receive the IP of the local machine.
//
// Copyright (C) 2021 Zhang Maiyun <myzhang1029@hotmail.com>
//
// This file is part of DNS updater.
//
// DNS updater is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// DNS updater is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with DNS updater. If not, see <https://www.gnu.org/licenses/>.
//
use crate::libc_getips::get_iface_addrs;
use crate::{Error, IpType, Result};
use log::{debug, info};
use std::net::IpAddr;
use std::process::{Output, Stdio};
use std::str::FromStr;
use tokio::process::Command;
/// Run a chain of ip/ifconfig commands and returns the output of first succeeded one
async fn chain_ip_cmd_until_succeed(nic: &str) -> Result<Output> {
let commands = [
// First try to use `ip`
(
"ip",
vec![
"address", "show", "dev", nic,
// This scope filters out unique-local addresses
"scope", "global",
],
),
// If that failed, try (BSD) ifconfig
("ifconfig", vec!["-L", nic, "inet6"]),
// Linux ifconfig cannot distinguish between RFC 3041 temporary/permanent addresses
("ifconfig", vec![nic]),
];
// Record only the last failure
let mut last_error: Option<Error> = None;
for (cmd, args) in commands {
let mut command = Command::new(cmd);
command.stdout(Stdio::piped());
debug!("Running command {:?} with arguments {:?}", cmd, args);
let output = command.args(&args).output().await;
match output {
Ok(output) => {
if output.status.success() {
return Ok(output);
}
// Since a chain of commands are executed, these are not really errors
debug!(
"Command {:?} failed with status: {}",
command, output.status
);
last_error = Some(Error::NonZeroExit(output.status));
}
Err(exec_error) => {
debug!(
"Command {:?} failed to be executed: {}",
command, exec_error
);
last_error = Some(Error::IoError(exec_error));
}
}
}
info!("None of the commands to extract the IPv6 address succeeded.");
// Not failable
Err(last_error.unwrap())
}
/// Try to get a IPv6 address from a interface with `ifconfig` or `ip` if possible.
/// Two Results are used to indicate a "determining error":
/// if the outer Result is an Err variant, it indicates an error with the current IP backend.
/// But if the inner Result is an Err variant, no other backends will be tried.
async fn get_ipv6_ifconfig_ip(nic: &str, permanent: bool) -> Result<Result<IpAddr>> {
let out = chain_ip_cmd_until_succeed(nic).await?;
// Extract output
let out_br = out.stdout.split(|c| *c == b'\n');
// Some systems use temporary addresses and one permanent address.
// The second fields indicates whether the "secured"/permanent flag is present.
let mut addrs: Vec<(IpAddr, bool)> = Vec::with_capacity(4);
// Extract addresses line by line
for line in out_br {
let line = String::from_utf8(line.to_vec())?;
let fields: Vec<String> = line
.split_whitespace()
.map(std::string::ToString::to_string)
.collect();
// A shorter one is certainly not an entry
// Check if the label is "inet6"
if fields.len() > 1 && fields[0] == "inet6" {
let address_stripped = match fields[1].split_once("/") {
// `ip` includes the prefix length in the address
Some((addr, _prefixlen)) => addr,
// but `ifconfig` doesn't
None => &fields[1],
};
if let Ok(addr6) = IpAddr::from_str(address_stripped) {
// If "secured" (for RFC 3041, ifconfig) or "mngtmpaddr" (RFC 3041, ip) is in the flags, it is permanent
let is_perm = fields.iter().any(|f| f == "secured" || f == "mngtmpaddr")
// But any "temporary" tells us it is not.
&& fields.iter().all(|f| f != "temporary");
// Treating non-RFC-3041 interface's addresses all the same
if !addr6.is_loopback() {
addrs.push((addr6, is_perm));
}
}
}
}
if addrs.is_empty() {
debug!("Short-circuting NoAddress because an ip command succeeded without addresses");
Ok(Err(crate::Error::NoAddress))
} else {
Ok(Ok(addrs
.iter()
// If is_perm == permanent, it is the one we are looking for
.filter(|(_, is_perm)| *is_perm == permanent)
.map(|(addr, _)| *addr)
.next()
.unwrap_or(addrs[0].0)))
}
}
/// Force-cast $id: IpAddr to Ipv4Addr
macro_rules! cast_ipv4 {
($id: expr) => {
if let IpAddr::V4(ip) = $id {
ip
} else {
unreachable!()
}
};
}
/// Force-cast $id: IpAddr to Ipv6Addr
macro_rules! cast_ipv6 {
($id: expr) => {
if let IpAddr::V6(ip) = $id {
ip
} else {
unreachable!()
}
};
}
/// Get a local IPv4 address on the specified interface.
///
/// # Errors
///
/// This function propagates the error from `libc_getips::get_iface_addrs`.
pub fn get_local_ipv4(nic: Option<&str>) -> Result<IpAddr> {
let ipv4_addrs = get_iface_addrs(Some(IpType::Ipv4), nic)?;
let ipv4_addrs: Vec<&IpAddr> = ipv4_addrs
.iter()
// Remove loopback, link local, and unspecified
.filter(|addr| {
let remove =
!addr.is_loopback() && !cast_ipv4!(addr).is_link_local() && !addr.is_unspecified();
if remove {
debug!(
"Removing address {:?} because it is loopback/link local/unspecified",
addr
);
}
remove
})
.collect();
// Prefer the ones that are likely global
// XXX: `IpAddr::is_global` is probably a better choice but it's currently unstable.
let first_non_local_addr: Option<&&IpAddr> = ipv4_addrs
.iter()
.find(|addr| !cast_ipv4!(addr).is_private());
first_non_local_addr.map_or_else(|| Ok(*ipv4_addrs[0]), |addr| Ok(**addr))
}
/// Get a local IPv6 address on the specified interface.
///
/// # Errors
///
/// This function returns `NoAddress` if the IP commands, or in case that none
/// of those commands are available, it propagates the error from `libc_getips::get_iface_addrs`.
pub async fn get_local_ipv6(nic: Option<&str>) -> Result<IpAddr> {
if let Some(nic) = nic {
if let Ok(command_result) = get_ipv6_ifconfig_ip(nic, true).await {
// TODO: ipconfig.exe IPv6 backend
return command_result;
}
};
let ipv6_addrs = get_iface_addrs(Some(IpType::Ipv6), nic)?;
let ipv6_addrs: Vec<&IpAddr> = ipv6_addrs
.iter()
// Remove loopback, link local, and unspecified
.filter(|addr| {
!addr.is_loopback()
&& !addr.is_unspecified()
&& !(cast_ipv6!(addr).segments()[0] & 0xffc0) == 0xfe80
})
.collect();
// TODO: Prefer the ones that are likely global
// `IpAddr::is_global` is currently unstable.
Ok(*ipv6_addrs[0])
}