use std::net::IpAddr;
use miette::{IntoDiagnostic, Result, WrapErr};
use serde::Deserialize;
use tracing::trace;
pub async fn whois(ip: &IpAddr) -> Result<Option<String>> {
let ip = *ip;
let output = tokio::task::spawn_blocking(move || {
duct::cmd!("tailscale", "whois", "--json", ip.to_string())
.stdout_capture()
.stderr_capture()
.unchecked()
.run()
})
.await
.into_diagnostic()?
.into_diagnostic()
.wrap_err("running tailscale whois")?;
if !output.status.success() {
trace!(
status = ?output.status,
stderr = %String::from_utf8_lossy(&output.stderr),
"tailscale whois returned non-zero"
);
return Ok(None);
}
let parsed: WhoisJson = match serde_json::from_slice(&output.stdout) {
Ok(p) => p,
Err(err) => {
trace!(?err, "failed to parse tailscale whois JSON");
return Ok(None);
}
};
Ok(parsed
.user_profile
.and_then(|u| u.login_name.or(u.display_name)))
}
#[derive(Debug, Deserialize)]
struct WhoisJson {
#[serde(rename = "UserProfile")]
user_profile: Option<UserProfile>,
}
#[derive(Debug, Deserialize)]
struct UserProfile {
#[serde(rename = "LoginName")]
login_name: Option<String>,
#[serde(rename = "DisplayName")]
display_name: Option<String>,
}