use std::net::SocketAddr;
use iroh::NetReport;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiagnosticsReport {
pub endpoint_id: iroh::EndpointId,
pub net_report: Option<NetReport>,
pub direct_addrs: Vec<SocketAddr>,
pub portmap_probe: Option<PortMapProbe>,
#[serde(default)]
pub iroh_version: String,
#[serde(default)]
pub iroh_services_version: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PortMapProbe {
pub upnp: bool,
pub pcp: bool,
pub nat_pmp: bool,
}
pub mod checks {
use std::{net::SocketAddr, time::Duration};
use anyhow::Result;
use iroh::{Endpoint, Watcher};
use super::*;
pub async fn run_diagnostics(endpoint: &Endpoint) -> Result<DiagnosticsReport> {
run_diagnostics_with_timeout(endpoint, Duration::from_secs(10)).await
}
async fn run_diagnostics_with_timeout(
endpoint: &Endpoint,
timeout: Duration,
) -> Result<DiagnosticsReport> {
let endpoint_id = endpoint.id();
if tokio::time::timeout(timeout, endpoint.online())
.await
.is_err()
{
tracing::warn!("waiting for relay connection timed out after {timeout:?}");
}
let mut watcher = endpoint.net_report();
let net_report = match tokio::time::timeout(timeout, watcher.initialized()).await {
Ok(report) => Some(report),
Err(_) => {
tracing::warn!("net report timed out after {timeout:?}, using partial data");
watcher.get()
}
};
let addr = endpoint.addr();
let direct_addrs: Vec<SocketAddr> = addr.ip_addrs().copied().collect();
#[cfg(not(target_arch = "wasm32"))]
let portmap_probe =
match tokio::time::timeout(Duration::from_secs(5), probe_port_mapping()).await {
Ok(Ok(p)) => Some(p),
Ok(Err(e)) => {
tracing::warn!("portmap probe failed: {e}");
None
}
Err(_) => {
tracing::warn!("portmap probe timed out");
None
}
};
#[cfg(target_arch = "wasm32")]
let portmap_probe = None;
Ok(DiagnosticsReport {
endpoint_id,
net_report,
direct_addrs,
portmap_probe,
iroh_version: crate::IROH_VERSION.to_string(),
iroh_services_version: crate::IROH_SERVICES_VERSION.to_string(),
})
}
#[cfg(not(target_arch = "wasm32"))]
async fn probe_port_mapping() -> Result<PortMapProbe> {
let config = portmapper::Config {
enable_upnp: true,
enable_pcp: true,
enable_nat_pmp: true,
protocol: portmapper::Protocol::Udp,
};
let client = portmapper::Client::new(config);
let probe_rx = client.probe();
let probe = probe_rx.await?.map_err(|e| anyhow::anyhow!(e))?;
Ok(PortMapProbe {
upnp: probe.upnp,
pcp: probe.pcp,
nat_pmp: probe.nat_pmp,
})
}
}
#[cfg(test)]
mod tests {
use iroh::endpoint::presets;
use crate::run_diagnostics;
#[tokio::test]
async fn test_run_diagnostics() {
let endpoint = iroh::Endpoint::builder(presets::Minimal)
.bind()
.await
.unwrap();
run_diagnostics(&endpoint).await.unwrap();
endpoint.close().await;
}
}