Skip to main content

clawft_kernel/workload_runtime/infer/
exposure.rs

1//! Is a managed server reachable beyond loopback? The adapter passes
2//! `--host 127.0.0.1`, but the launcher and the server are not ours, so
3//! the binding is checked, not assumed: connect to every non-loopback
4//! address of this machine on the server's port.
5
6use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
7use std::time::Duration;
8
9use tokio::net::TcpStream;
10use tokio::task::JoinSet;
11
12const CONNECT_TIMEOUT: Duration = Duration::from_millis(300);
13
14/// Non-loopback unicast addresses of this machine's interfaces (IPv6
15/// link-local addresses are skipped: they need a scope to connect to).
16pub fn local_addrs() -> Vec<IpAddr> {
17    let mut out: Vec<IpAddr> = Vec::new();
18    let mut head: *mut libc::ifaddrs = std::ptr::null_mut();
19    // SAFETY: getifaddrs fills `head` with a list we only read and then
20    // release with freeifaddrs; every pointer is checked for null first.
21    unsafe {
22        if libc::getifaddrs(&mut head) != 0 {
23            return out;
24        }
25        let mut cur = head;
26        while !cur.is_null() {
27            let a = &*cur;
28            if !a.ifa_addr.is_null() {
29                match i32::from((*a.ifa_addr).sa_family) {
30                    libc::AF_INET => {
31                        let s = &*(a.ifa_addr as *const libc::sockaddr_in);
32                        out.push(IpAddr::V4(Ipv4Addr::from(u32::from_be(s.sin_addr.s_addr))));
33                    }
34                    libc::AF_INET6 => {
35                        let s = &*(a.ifa_addr as *const libc::sockaddr_in6);
36                        out.push(IpAddr::V6(Ipv6Addr::from(s.sin6_addr.s6_addr)));
37                    }
38                    _ => {}
39                }
40            }
41            cur = a.ifa_next;
42        }
43        libc::freeifaddrs(head);
44    }
45    out.retain(|ip| {
46        !ip.is_loopback()
47            && !ip.is_unspecified()
48            && !matches!(ip, IpAddr::V6(v) if v.segments()[0] & 0xffc0 == 0xfe80)
49    });
50    out.sort();
51    out.dedup();
52    out
53}
54
55/// Whether something accepts connections on `ip:port`.
56pub async fn port_in_use(ip: IpAddr, port: u16) -> bool {
57    matches!(
58        tokio::time::timeout(
59            CONNECT_TIMEOUT,
60            TcpStream::connect(SocketAddr::new(ip, port))
61        )
62        .await,
63        Ok(Ok(_))
64    )
65}
66
67/// The local non-loopback addresses `port` answers on (empty: loopback only).
68pub async fn reachable_beyond_loopback(port: u16) -> Vec<IpAddr> {
69    let mut set = JoinSet::new();
70    for ip in local_addrs() {
71        set.spawn(async move { port_in_use(ip, port).await.then_some(ip) });
72    }
73    let mut out = Vec::new();
74    while let Some(r) = set.join_next().await {
75        if let Ok(Some(ip)) = r {
76            out.push(ip);
77        }
78    }
79    out.sort();
80    out
81}