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, 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 n0_future::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 n0_future::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 n0_future::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
92 #[cfg(target_arch = "wasm32")]
95 let portmap_probe = Some(PortMapProbe {
96 upnp: false,
97 pcp: false,
98 nat_pmp: false,
99 });
100
101 Ok(DiagnosticsReport {
102 endpoint_id,
103 net_report,
104 direct_addrs,
105 portmap_probe,
106 iroh_version: crate::IROH_VERSION.to_string(),
107 iroh_services_version: crate::IROH_SERVICES_VERSION.to_string(),
108 })
109 }
110
111 #[cfg(not(target_arch = "wasm32"))]
112 async fn probe_port_mapping() -> Result<PortMapProbe> {
113 let config = portmapper::Config {
114 enable_upnp: true,
115 enable_pcp: true,
116 enable_nat_pmp: true,
117 protocol: portmapper::Protocol::Udp,
118 };
119 let client = portmapper::Client::new(config);
120 let probe_rx = client.probe();
121 let probe = probe_rx.await?.map_err(|e| anyhow::anyhow!(e))?;
122 Ok(PortMapProbe {
123 upnp: probe.upnp,
124 pcp: probe.pcp,
125 nat_pmp: probe.nat_pmp,
126 })
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use iroh::endpoint::presets;
133
134 use crate::run_diagnostics;
135
136 #[tokio::test]
137 async fn test_run_diagnostics() {
138 let endpoint = iroh::Endpoint::builder(presets::Minimal)
139 .bind()
140 .await
141 .unwrap();
142 run_diagnostics(&endpoint).await.unwrap();
143 endpoint.close().await;
144 }
145}