use std::{
ffi::CStr,
net::{IpAddr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs},
ptr,
};
use libc::{
c_char, getnameinfo, socklen_t, AF_INET, AF_INET6, AF_UNSPEC, EAI_AGAIN, EAI_BADFLAGS,
EAI_FAIL, EAI_FAMILY, EAI_MEMORY, EAI_NONAME, EAI_SERVICE, EAI_SOCKTYPE, EAI_SYSTEM,
NI_MAXHOST, NI_NAMEREQD, NI_NUMERICSERV,
};
use nix::{
errno::Errno,
sys::socket::{SockaddrLike, SockaddrStorage},
};
use crate::{err::err2no, hash::SydIndexSet, rng::fillrandom};
pub fn resolve_rand(name: &str, family: Option<i32>) -> Result<IpAddr, Errno> {
let mut buf = [0u8; 4];
if fillrandom(&mut buf).is_err() {
return Err(Errno::EIO); }
let cookie = usize::try_from(u32::from_ne_bytes(buf)).unwrap_or(usize::MAX);
let addrs = resolve_host(name, family)?;
#[expect(clippy::arithmetic_side_effects)]
Ok(addrs[cookie.wrapping_rem(addrs.len())])
}
pub fn resolve_host(name: &str, family: Option<i32>) -> Result<Vec<IpAddr>, Errno> {
let ai_family = match family {
Some(AF_INET) => AF_INET,
Some(AF_INET6) => AF_INET6,
Some(_) => return Err(Errno::EINVAL),
None => AF_UNSPEC, };
let addrs: SydIndexSet<IpAddr> = SydIndexSet::from_iter(
(name, 22)
.to_socket_addrs()
.map_err(|err| err2no(&err))?
.filter(|addr| {
matches!(
(ai_family, addr),
(AF_UNSPEC, _) | (AF_INET, SocketAddr::V4(_)) | (AF_INET6, SocketAddr::V6(_))
)
})
.map(|addr| addr.ip()),
);
if addrs.is_empty() {
Err(Errno::ENOENT)
} else {
Ok(addrs.iter().copied().collect())
}
}
#[expect(clippy::cast_possible_truncation)]
pub fn lookup_addr(addr: IpAddr) -> Result<String, Errno> {
let addr = match addr {
IpAddr::V4(v4) => SockaddrStorage::from(SocketAddrV4::new(v4, 0)),
IpAddr::V6(v6) => SockaddrStorage::from(SocketAddrV6::new(v6, 0, 0, 0)),
};
let mut host_buf = [0 as c_char; NI_MAXHOST as usize];
let ret = unsafe {
getnameinfo(
addr.as_ptr(),
addr.len(),
host_buf.as_mut_ptr(),
host_buf.len() as socklen_t,
ptr::null_mut(),
0,
NI_NAMEREQD | NI_NUMERICSERV,
)
};
if ret != 0 {
if ret == EAI_SYSTEM {
return Err(Errno::last());
} else {
let e = match ret {
EAI_AGAIN => Errno::EAGAIN,
EAI_BADFLAGS => Errno::EINVAL,
EAI_FAIL => Errno::EIO,
EAI_FAMILY => Errno::EAFNOSUPPORT,
EAI_MEMORY => Errno::ENOMEM,
EAI_NONAME => Errno::ENOENT,
EAI_SERVICE => Errno::EPROTONOSUPPORT,
EAI_SOCKTYPE => Errno::ESOCKTNOSUPPORT,
_ => Errno::EIO,
};
return Err(e);
}
}
let cstr = unsafe { CStr::from_ptr(host_buf.as_ptr()) };
let name = cstr.to_string_lossy().into_owned();
Ok(name)
}