use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
use std::time::Duration;
use tokio::net::TcpStream;
use tokio::task::JoinSet;
const CONNECT_TIMEOUT: Duration = Duration::from_millis(300);
pub fn local_addrs() -> Vec<IpAddr> {
let mut out: Vec<IpAddr> = Vec::new();
let mut head: *mut libc::ifaddrs = std::ptr::null_mut();
unsafe {
if libc::getifaddrs(&mut head) != 0 {
return out;
}
let mut cur = head;
while !cur.is_null() {
let a = &*cur;
if !a.ifa_addr.is_null() {
match i32::from((*a.ifa_addr).sa_family) {
libc::AF_INET => {
let s = &*(a.ifa_addr as *const libc::sockaddr_in);
out.push(IpAddr::V4(Ipv4Addr::from(u32::from_be(s.sin_addr.s_addr))));
}
libc::AF_INET6 => {
let s = &*(a.ifa_addr as *const libc::sockaddr_in6);
out.push(IpAddr::V6(Ipv6Addr::from(s.sin6_addr.s6_addr)));
}
_ => {}
}
}
cur = a.ifa_next;
}
libc::freeifaddrs(head);
}
out.retain(|ip| {
!ip.is_loopback()
&& !ip.is_unspecified()
&& !matches!(ip, IpAddr::V6(v) if v.segments()[0] & 0xffc0 == 0xfe80)
});
out.sort();
out.dedup();
out
}
pub async fn port_in_use(ip: IpAddr, port: u16) -> bool {
matches!(
tokio::time::timeout(
CONNECT_TIMEOUT,
TcpStream::connect(SocketAddr::new(ip, port))
)
.await,
Ok(Ok(_))
)
}
pub async fn reachable_beyond_loopback(port: u16) -> Vec<IpAddr> {
let mut set = JoinSet::new();
for ip in local_addrs() {
set.spawn(async move { port_in_use(ip, port).await.then_some(ip) });
}
let mut out = Vec::new();
while let Some(r) = set.join_next().await {
if let Ok(Some(ip)) = r {
out.push(ip);
}
}
out.sort();
out
}