iroh_services/
net_diagnostics.rs1use std::net::SocketAddr;
11
12use iroh::NetReport;
13use serde::{Deserialize, Serialize};
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
16pub struct DiagnosticsReport {
17 pub endpoint_id: iroh::EndpointId,
18 pub net_report: Option<NetReport>,
19 pub direct_addrs: Vec<SocketAddr>,
20 pub portmap_probe: Option<PortMapProbe>,
21 #[serde(default)]
22 pub iroh_version: String,
23 #[serde(default)]
24 pub iroh_services_version: String,
25}
26
27#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
29pub struct PortMapProbe {
30 pub upnp: bool,
31 pub pcp: bool,
32 pub nat_pmp: bool,
33}
34
35pub mod checks {
36 use std::{net::SocketAddr, time::Duration};
37
38 use anyhow::Result;
39 use iroh::{Endpoint, Watcher};
40
41 use super::*;
42
43 pub async fn run_diagnostics(endpoint: &Endpoint) -> Result<DiagnosticsReport> {
45 run_diagnostics_with_timeout(endpoint, Duration::from_secs(10)).await
46 }
47
48 async fn run_diagnostics_with_timeout(
50 endpoint: &Endpoint,
51 timeout: Duration,
52 ) -> Result<DiagnosticsReport> {
53 let endpoint_id = endpoint.id();
54
55 if tokio::time::timeout(timeout, endpoint.online())
57 .await
58 .is_err()
59 {
60 tracing::warn!("waiting for relay connection timed out after {timeout:?}");
61 }
62
63 let mut watcher = endpoint.net_report();
65 let net_report = match tokio::time::timeout(timeout, watcher.initialized()).await {
66 Ok(report) => Some(report),
67 Err(_) => {
68 tracing::warn!("net report timed out after {timeout:?}, using partial data");
69 watcher.get()
70 }
71 };
72
73 let addr = endpoint.addr();
75 let direct_addrs: Vec<SocketAddr> = addr.ip_addrs().copied().collect();
76
77 #[cfg(not(target_arch = "wasm32"))]
79 let portmap_probe =
80 match tokio::time::timeout(Duration::from_secs(5), probe_port_mapping()).await {
81 Ok(Ok(p)) => Some(p),
82 Ok(Err(e)) => {
83 tracing::warn!("portmap probe failed: {e}");
84 None
85 }
86 Err(_) => {
87 tracing::warn!("portmap probe timed out");
88 None
89 }
90 };
91 #[cfg(target_arch = "wasm32")]
92 let portmap_probe = None;
93
94 Ok(DiagnosticsReport {
95 endpoint_id,
96 net_report,
97 direct_addrs,
98 portmap_probe,
99 iroh_version: crate::IROH_VERSION.to_string(),
100 iroh_services_version: crate::IROH_SERVICES_VERSION.to_string(),
101 })
102 }
103
104 #[cfg(not(target_arch = "wasm32"))]
105 async fn probe_port_mapping() -> Result<PortMapProbe> {
106 let config = portmapper::Config {
107 enable_upnp: true,
108 enable_pcp: true,
109 enable_nat_pmp: true,
110 protocol: portmapper::Protocol::Udp,
111 };
112 let client = portmapper::Client::new(config);
113 let probe_rx = client.probe();
114 let probe = probe_rx.await?.map_err(|e| anyhow::anyhow!(e))?;
115 Ok(PortMapProbe {
116 upnp: probe.upnp,
117 pcp: probe.pcp,
118 nat_pmp: probe.nat_pmp,
119 })
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use iroh::endpoint::presets;
126
127 use crate::run_diagnostics;
128
129 #[tokio::test]
130 async fn test_run_diagnostics() {
131 let endpoint = iroh::Endpoint::builder(presets::Minimal)
132 .bind()
133 .await
134 .unwrap();
135 run_diagnostics(&endpoint).await.unwrap();
136 endpoint.close().await;
137 }
138}