use std::{
ffi::{self, CString, c_char, c_void},
net::SocketAddr,
};
use crate::{TOKIO_RUNTIME, device, ffi_guard, net_types::sockaddr};
#[repr(C)]
pub struct status_node {
pub stable_id: *const c_char,
pub display_name: *const c_char,
pub ipv4: sockaddr,
pub ipv6: sockaddr,
pub online: ffi::c_int,
pub allowed_routes: *const *const c_char,
pub allowed_routes_len: usize,
pub is_exit_node: ffi::c_int,
}
pub type status_visitor = Option<unsafe extern "C" fn(node: *const status_node, user: *mut c_void)>;
fn visit_node(
stable_id: &str,
display_name: &str,
ipv4: std::net::IpAddr,
ipv6: std::net::IpAddr,
online: Option<bool>,
routes: &[String],
is_exit_node: bool,
visit: unsafe extern "C" fn(*const status_node, *mut c_void),
user: *mut c_void,
) {
let stable_id = CString::new(stable_id).unwrap_or_default();
let display_name = CString::new(display_name).unwrap_or_default();
let routes: Vec<CString> = routes
.iter()
.filter_map(|r| CString::new(r.as_str()).ok())
.collect();
let mut route_ptrs: Vec<*const c_char> = routes.iter().map(|c| c.as_ptr()).collect();
route_ptrs.push(std::ptr::null());
let node = status_node {
stable_id: stable_id.as_ptr(),
display_name: display_name.as_ptr(),
ipv4: SocketAddr::from((ipv4, 0)).into(),
ipv6: SocketAddr::from((ipv6, 0)).into(),
online: match online {
Some(true) => 1,
Some(false) => 0,
None => -1,
},
allowed_routes: route_ptrs.as_ptr(),
allowed_routes_len: routes.len(),
is_exit_node: is_exit_node as ffi::c_int,
};
unsafe { visit(&node, user) };
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ts_status(
dev: &device,
visit: status_visitor,
user: *mut c_void,
) -> ffi::c_int {
ffi_guard(move || {
let Some(visit) = visit else {
return 0;
};
let status = match TOKIO_RUNTIME.block_on(dev.0.status()) {
Ok(status) => status,
Err(e) => {
tracing::error!(err = %e, "status");
return -1;
}
};
let mut count = 0;
if let Some(n) = &status.self_node {
let routes: Vec<String> = n.allowed_routes.iter().map(ToString::to_string).collect();
visit_node(
&n.stable_id.0,
&n.display_name,
n.ipv4,
n.ipv6,
n.online,
&routes,
n.is_exit_node,
visit,
user,
);
count += 1;
}
for n in &status.peers {
let routes: Vec<String> = n.allowed_routes.iter().map(ToString::to_string).collect();
visit_node(
&n.stable_id.0,
&n.display_name,
n.ipv4,
n.ipv6,
n.online,
&routes,
n.is_exit_node,
visit,
user,
);
count += 1;
}
count
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ts_whois(
dev: &device,
addr: &sockaddr,
visit: status_visitor,
user: *mut c_void,
) -> ffi::c_int {
ffi_guard(move || {
let Ok(addr): Result<SocketAddr, _> = addr.try_into() else {
tracing::error!("whois: invalid sockaddr");
return -1;
};
match TOKIO_RUNTIME.block_on(dev.0.whois(addr)) {
Ok(Some(whois)) => {
if let Some(visit) = visit {
let node = tailscale::StatusNode::from_node(&whois.node);
let routes = node
.allowed_routes
.iter()
.map(ToString::to_string)
.collect::<Vec<_>>();
visit_node(
&node.stable_id.0,
&node.display_name,
node.ipv4,
node.ipv6,
node.online,
&routes,
node.is_exit_node,
visit,
user,
);
}
1
}
Ok(None) => 0,
Err(e) => {
tracing::error!(err = %e, "whois");
-1
}
}
})
}