iroh_services/
net_diagnostics.rs1use std::net::SocketAddr;
11
12use iroh::unstable_net_report::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;
37
38 use anyhow::Result;
39 use iroh::{Endpoint, Watcher};
40 use n0_future::time::Duration;
41
42 use super::*;
43
44 pub async fn run_diagnostics(endpoint: &Endpoint) -> Result<DiagnosticsReport> {
46 run_diagnostics_with_timeout(endpoint, Duration::from_secs(10)).await
47 }
48
49 async fn run_diagnostics_with_timeout(
51 endpoint: &Endpoint,
52 timeout: Duration,
53 ) -> Result<DiagnosticsReport> {
54 let endpoint_id = endpoint.id();
55
56 if n0_future::time::timeout(timeout, endpoint.online())
58 .await
59 .is_err()
60 {
61 tracing::warn!("waiting for relay connection timed out after {timeout:?}");
62 }
63
64 let mut watcher = endpoint.net_report();
66 let net_report = match n0_future::time::timeout(timeout, watcher.initialized()).await {
67 Ok(report) => Some(report),
68 Err(_) => {
69 tracing::warn!("net report timed out after {timeout:?}, using partial data");
70 watcher.get()
71 }
72 };
73
74 let addr = endpoint.addr();
76 let direct_addrs: Vec<SocketAddr> = addr.ip_addrs().copied().collect();
77
78 #[cfg(not(wasm_browser))]
80 let portmap_probe =
81 match n0_future::time::timeout(Duration::from_secs(5), probe_port_mapping()).await {
82 Ok(Ok(p)) => Some(p),
83 Ok(Err(e)) => {
84 tracing::warn!("portmap probe failed: {e}");
85 None
86 }
87 Err(_) => {
88 tracing::warn!("portmap probe timed out");
89 None
90 }
91 };
92
93 #[cfg(wasm_browser)]
96 let portmap_probe = Some(PortMapProbe {
97 upnp: false,
98 pcp: false,
99 nat_pmp: false,
100 });
101
102 Ok(DiagnosticsReport {
103 endpoint_id,
104 net_report,
105 direct_addrs,
106 portmap_probe,
107 iroh_version: crate::IROH_VERSION.to_string(),
108 iroh_services_version: crate::IROH_SERVICES_VERSION.to_string(),
109 })
110 }
111
112 #[cfg(not(wasm_browser))]
113 async fn probe_port_mapping() -> Result<PortMapProbe> {
114 let config = portmapper::Config {
115 enable_upnp: true,
116 enable_pcp: true,
117 enable_nat_pmp: true,
118 protocol: portmapper::Protocol::Udp,
119 };
120 let client = portmapper::Client::new(config);
121 let probe_rx = client.probe();
122 let probe = probe_rx.await?.map_err(|e| anyhow::anyhow!(e))?;
123 Ok(PortMapProbe {
124 upnp: probe.upnp,
125 pcp: probe.pcp,
126 nat_pmp: probe.nat_pmp,
127 })
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use iroh::endpoint::presets;
134
135 use crate::run_diagnostics;
136
137 #[tokio::test]
138 async fn test_run_diagnostics() {
139 let endpoint = iroh::Endpoint::builder(presets::Minimal)
140 .bind()
141 .await
142 .unwrap();
143 run_diagnostics(&endpoint).await.unwrap();
144 endpoint.close().await;
145 }
146}