use std::net::{IpAddr, SocketAddr};
pub(crate) fn resolve(addr: Option<&str>, default: &str) -> std::io::Result<SocketAddr> {
use std::net::ToSocketAddrs;
addr.unwrap_or(default)
.to_socket_addrs()?
.next()
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::NotFound, "no addresses resolved"))
}
pub(crate) fn pick_addr(
addrs: impl IntoIterator<Item = SocketAddr>,
local: SocketAddr,
dual_stack: bool,
) -> Option<SocketAddr> {
let mut fallback = None;
for addr in addrs {
let addr = normalize_family(addr, local);
if addressable(addr, local, dual_stack) {
return Some(addr);
}
fallback.get_or_insert(addr);
}
fallback
}
fn addressable(dest: SocketAddr, local: SocketAddr, dual_stack: bool) -> bool {
let (SocketAddr::V6(dest), SocketAddr::V6(local)) = (dest, local) else {
return dest.is_ipv4() == local.is_ipv4();
};
match (dest.ip().to_ipv4_mapped(), local.ip().to_ipv4_mapped()) {
(Some(_), None) => dual_stack && local.ip().is_unspecified(),
(None, Some(_)) => false,
_ => true,
}
}
fn normalize_family(addr: SocketAddr, local: SocketAddr) -> SocketAddr {
match (addr, local.is_ipv4()) {
(SocketAddr::V6(v6), true) => match v6.ip().to_ipv4_mapped() {
Some(v4) => SocketAddr::new(IpAddr::V4(v4), v6.port()),
None => addr,
},
(SocketAddr::V4(v4), false) => SocketAddr::new(IpAddr::V6(v4.ip().to_ipv6_mapped()), v4.port()),
_ => addr,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn resolves_socket_literal() {
let addr = resolve(Some("[::]:0"), "[::]:443").unwrap();
assert!(addr.ip().is_unspecified());
assert_eq!(addr.port(), 0);
}
#[test]
fn resolves_dns_hostname() {
let addr = resolve(Some("localhost:0"), "[::]:443").unwrap();
assert!(addr.ip().is_loopback());
assert_eq!(addr.port(), 0);
}
#[test]
fn falls_back_to_default() {
let addr = resolve(None, "127.0.0.1:1234").unwrap();
assert_eq!(addr.ip().to_string(), "127.0.0.1");
assert_eq!(addr.port(), 1234);
}
const V4: &str = "192.0.2.1:443";
const V4_MAPPED: &str = "[::ffff:192.0.2.1]:443";
const V6: &str = "[2001:db8::1]:443";
const LOCAL_V4: &str = "0.0.0.0:0";
const LOCAL_V6: &str = "[::]:0";
const BOUND_V6: &str = "[2001:db8::5]:0";
const BOUND_V4_MAPPED: &str = "[::ffff:192.0.2.5]:0";
fn addr(s: &str) -> SocketAddr {
s.parse().unwrap()
}
fn dual_stack(addrs: impl IntoIterator<Item = SocketAddr>, local: SocketAddr) -> Option<SocketAddr> {
pick_addr(addrs, local, true)
}
#[test]
fn pick_addr_keeps_the_resolver_ranking() {
assert_eq!(dual_stack([addr(V4), addr(V6)], addr(LOCAL_V6)), Some(addr(V4_MAPPED)));
assert_eq!(dual_stack([addr(V6), addr(V4)], addr(LOCAL_V6)), Some(addr(V6)));
}
#[test]
fn pick_addr_skips_a_family_the_socket_cant_send_to() {
assert_eq!(dual_stack([addr(V6), addr(V4)], addr(LOCAL_V4)), Some(addr(V4)));
assert_eq!(dual_stack([addr(V4), addr(V6)], addr(BOUND_V6)), Some(addr(V6)));
assert_eq!(
dual_stack([addr(V6), addr(V4)], addr(BOUND_V4_MAPPED)),
Some(addr(V4_MAPPED))
);
}
#[test]
fn pick_addr_skips_mapped_ipv4_on_a_v6_only_socket() {
assert_eq!(pick_addr([addr(V4), addr(V6)], addr(LOCAL_V6), false), Some(addr(V6)));
assert_eq!(
pick_addr([addr(V4), addr(V6)], addr(LOCAL_V6), true),
Some(addr(V4_MAPPED))
);
}
#[test]
fn pick_addr_still_returns_an_unusable_only_entry() {
assert_eq!(dual_stack([addr(V4)], addr(BOUND_V6)), Some(addr(V4_MAPPED)));
assert_eq!(pick_addr([addr(V4)], addr(LOCAL_V6), false), Some(addr(V4_MAPPED)));
}
#[test]
fn pick_addr_wraps_v4_for_v6_socket() {
assert_eq!(dual_stack([addr(V4)], addr(LOCAL_V6)), Some(addr(V4_MAPPED)));
}
#[test]
fn pick_addr_unwraps_v4_mapped_for_v4_socket() {
assert_eq!(dual_stack([addr(V4_MAPPED)], addr(LOCAL_V4)), Some(addr(V4)));
}
#[test]
fn pick_addr_falls_back_for_unmappable_v6() {
assert_eq!(dual_stack([addr(V6)], addr(LOCAL_V4)), Some(addr(V6)));
}
#[test]
fn pick_addr_empty() {
assert_eq!(dual_stack(std::iter::empty(), addr(LOCAL_V4)), None);
}
}