use std::net::IpAddr;
use std::process::Command;
pub fn preferred_wireguard_endpoint_ip(endpoints: &[String]) -> Option<String> {
preferred_wireguard_endpoint_ip_with_ipv6_support(endpoints, system_supports_public_ipv6())
}
pub fn wireguard_endpoint_ipv4(endpoints: &[String]) -> Option<String> {
endpoints.iter().find_map(|endpoint| {
endpoint
.parse::<IpAddr>()
.ok()
.filter(IpAddr::is_ipv4)
.map(|_| endpoint.clone())
})
}
pub fn system_supports_public_ipv6() -> bool {
let Ok(output) = Command::new("ip")
.args(["-6", "route", "show", "default"])
.output()
else {
return false;
};
output.status.success() && output.stdout.iter().any(|byte| !byte.is_ascii_whitespace())
}
pub fn preferred_wireguard_endpoint_ip_with_ipv6_support(
endpoints: &[String],
ipv6_supported: bool,
) -> Option<String> {
let mut ipv4 = None;
let mut ipv6 = None;
for endpoint in endpoints {
match endpoint.parse::<IpAddr>().ok()? {
IpAddr::V4(_) if ipv4.is_none() => ipv4 = Some(endpoint.clone()),
IpAddr::V6(_) if ipv6.is_none() => ipv6 = Some(endpoint.clone()),
_ => {}
}
}
if ipv6_supported {
ipv6.or(ipv4)
} else {
ipv4.or(ipv6)
}
}
#[cfg(test)]
mod tests {
use super::{preferred_wireguard_endpoint_ip_with_ipv6_support, wireguard_endpoint_ipv4};
#[test]
fn ipv4_only_selection_never_falls_back_to_ipv6() {
let dual_stack = vec!["2600:1900:4000:fec::1".to_string(), "34.1.2.3".to_string()];
assert_eq!(
Some("34.1.2.3".to_string()),
wireguard_endpoint_ipv4(&dual_stack)
);
assert_eq!(
None,
wireguard_endpoint_ipv4(&["2600:1900:4000:fec::1".to_string()])
);
}
#[test]
fn prefers_ipv6_when_supported() {
let endpoints = vec!["34.1.2.3".to_string(), "2600:1900:4000:fec::1".to_string()];
assert_eq!(
Some("2600:1900:4000:fec::1".to_string()),
preferred_wireguard_endpoint_ip_with_ipv6_support(&endpoints, true)
);
}
#[test]
fn falls_back_to_ipv4_when_ipv6_is_not_supported() {
let endpoints = vec!["2600:1900:4000:fec::1".to_string(), "34.1.2.3".to_string()];
assert_eq!(
Some("34.1.2.3".to_string()),
preferred_wireguard_endpoint_ip_with_ipv6_support(&endpoints, false)
);
}
#[test]
fn still_returns_ipv6_when_it_is_the_only_option() {
let endpoints = vec!["2600:1900:4000:fec::1".to_string()];
assert_eq!(
Some("2600:1900:4000:fec::1".to_string()),
preferred_wireguard_endpoint_ip_with_ipv6_support(&endpoints, false)
);
}
}