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
27impl DiagnosticsReport {
28 pub(crate) fn into_proto(self) -> iroh_services_proto::net_diagnostics::DiagnosticsReport {
29 iroh_services_proto::net_diagnostics::DiagnosticsReport {
30 endpoint_id: self.endpoint_id,
31 net_report: self.net_report,
32 direct_addrs: self.direct_addrs,
33 portmap_probe: self.portmap_probe.map(PortMapProbe::into_proto),
34 iroh_version: self.iroh_version,
35 iroh_services_version: self.iroh_services_version,
36 }
37 }
38}
39
40#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
42pub struct PortMapProbe {
43 pub upnp: bool,
44 pub pcp: bool,
45 pub nat_pmp: bool,
46}
47
48impl PortMapProbe {
49 fn into_proto(self) -> iroh_services_proto::net_diagnostics::PortMapProbe {
50 iroh_services_proto::net_diagnostics::PortMapProbe {
51 upnp: self.upnp,
52 pcp: self.pcp,
53 nat_pmp: self.nat_pmp,
54 }
55 }
56}
57
58pub mod checks {
59 use std::net::SocketAddr;
60
61 use anyhow::Result;
62 use iroh::{Endpoint, Watcher};
63 use n0_future::time::Duration;
64
65 use super::*;
66
67 pub async fn run_diagnostics(endpoint: &Endpoint) -> Result<DiagnosticsReport> {
69 run_diagnostics_with_timeout(endpoint, Duration::from_secs(10)).await
70 }
71
72 async fn run_diagnostics_with_timeout(
74 endpoint: &Endpoint,
75 timeout: Duration,
76 ) -> Result<DiagnosticsReport> {
77 let endpoint_id = endpoint.id();
78
79 if n0_future::time::timeout(timeout, endpoint.online())
81 .await
82 .is_err()
83 {
84 tracing::warn!("waiting for relay connection timed out after {timeout:?}");
85 }
86
87 let mut watcher = endpoint.net_report();
89 let net_report = match n0_future::time::timeout(timeout, watcher.initialized()).await {
90 Ok(report) => Some(report),
91 Err(_) => {
92 tracing::warn!("net report timed out after {timeout:?}, using partial data");
93 watcher.get()
94 }
95 };
96
97 let addr = endpoint.addr();
99 let direct_addrs: Vec<SocketAddr> = addr.ip_addrs().copied().collect();
100
101 #[cfg(not(wasm_browser))]
103 let portmap_probe =
104 match n0_future::time::timeout(Duration::from_secs(5), probe_port_mapping()).await {
105 Ok(Ok(p)) => Some(p),
106 Ok(Err(e)) => {
107 tracing::warn!("portmap probe failed: {e}");
108 None
109 }
110 Err(_) => {
111 tracing::warn!("portmap probe timed out");
112 None
113 }
114 };
115
116 #[cfg(wasm_browser)]
119 let portmap_probe = Some(PortMapProbe {
120 upnp: false,
121 pcp: false,
122 nat_pmp: false,
123 });
124
125 Ok(DiagnosticsReport {
126 endpoint_id,
127 net_report,
128 direct_addrs,
129 portmap_probe,
130 iroh_version: crate::IROH_VERSION.to_string(),
131 iroh_services_version: crate::IROH_SERVICES_VERSION.to_string(),
132 })
133 }
134
135 #[cfg(not(wasm_browser))]
136 async fn probe_port_mapping() -> Result<PortMapProbe> {
137 let config = portmapper::Config {
138 enable_upnp: true,
139 enable_pcp: true,
140 enable_nat_pmp: true,
141 protocol: portmapper::Protocol::Udp,
142 };
143 let client = portmapper::Client::new(config);
144 let probe_rx = client.probe();
145 let probe = probe_rx.await?.map_err(|e| anyhow::anyhow!(e))?;
146 Ok(PortMapProbe {
147 upnp: probe.upnp,
148 pcp: probe.pcp,
149 nat_pmp: probe.nat_pmp,
150 })
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use iroh::endpoint::presets;
157
158 use crate::run_diagnostics;
159
160 #[tokio::test]
161 async fn test_run_diagnostics() {
162 let endpoint = iroh::Endpoint::builder(presets::Minimal)
163 .bind()
164 .await
165 .unwrap();
166 run_diagnostics(&endpoint).await.unwrap();
167 endpoint.close().await;
168 }
169}