From 19d7a3b950911491d08ff4719e91e6aafc4c5807 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Thu, 12 Mar 2026 17:37:37 +0200 Subject: [PATCH 1/5] Allow configuring NetReport This allows us to disable captive portal checks and https probes on esp32 --- iroh/src/endpoint.rs | 14 ++++++++++- iroh/src/lib.rs | 4 ++- iroh/src/net_report.rs | 32 ++++++++++++++++++++++++ iroh/src/net_report/options.rs | 42 ++++++++++++++++++++++---------- iroh/src/net_report/reportgen.rs | 7 +++++- iroh/src/socket.rs | 10 ++++++-- 6 files changed, 91 insertions(+), 18 deletions(-) diff --git a/iroh/src/endpoint.rs b/iroh/src/endpoint.rs index a417713950..370d2d5408 100644 --- a/iroh/src/endpoint.rs +++ b/iroh/src/endpoint.rs @@ -98,10 +98,10 @@ pub use self::{ VarIntBoundsExceeded, WriteError, Written, }, }; -pub use crate::portmapper::PortmapperConfig; #[cfg(not(wasm_browser))] use crate::socket::transports::IpConfig; use crate::socket::transports::TransportConfig; +pub use crate::{net_report::Config as NetReportConfig, portmapper::PortmapperConfig}; /// Builder for [`Endpoint`]. /// @@ -129,6 +129,7 @@ pub struct Builder { hooks: EndpointHooksList, transport_bias: socket::transports::TransportBiasMap, portmapper_config: PortmapperConfig, + net_report_config: NetReportConfig, } impl From for Option { @@ -196,6 +197,7 @@ impl Builder { hooks: Default::default(), transport_bias: Default::default(), portmapper_config: Default::default(), + net_report_config: Default::default(), } } @@ -242,6 +244,7 @@ impl Builder { hooks: self.hooks, transport_bias: self.transport_bias, portmapper_config: self.portmapper_config, + net_report_config: self.net_report_config, static_config, }; @@ -705,6 +708,15 @@ impl Builder { self } + /// Configures the net report service. + /// + /// Controls which probes (HTTPS latency, captive portal detection) are run. + /// Defaults to all probes enabled. + pub fn net_report_config(mut self, config: NetReportConfig) -> Self { + self.net_report_config = config; + self + } + /// Adds a custom transport #[cfg(feature = "unstable-custom-transports")] pub fn add_custom_transport(mut self, factory: Arc) -> Self { diff --git a/iroh/src/lib.rs b/iroh/src/lib.rs index 9eaf5e0aa7..7831a4e082 100644 --- a/iroh/src/lib.rs +++ b/iroh/src/lib.rs @@ -276,7 +276,9 @@ pub use iroh_base::{ pub use iroh_relay::dns; pub use iroh_relay::{RelayConfig, RelayMap, endpoint_info}; pub use n0_watcher::Watcher; -pub use net_report::{Report as NetReport, TIMEOUT as NET_REPORT_TIMEOUT}; +pub use net_report::{ + Config as NetReportConfig, Report as NetReport, TIMEOUT as NET_REPORT_TIMEOUT, +}; #[cfg(any(test, feature = "test-utils"))] pub mod test_utils; diff --git a/iroh/src/net_report.rs b/iroh/src/net_report.rs index f46c3dfbfd..1b8bdb2e37 100644 --- a/iroh/src/net_report.rs +++ b/iroh/src/net_report.rs @@ -85,6 +85,34 @@ pub use self::{ }; pub(crate) use self::{options::Options, reportgen::QuicConfig}; +/// Configuration for the net report service. +/// +/// Controls which probes and checks are performed when generating network reports. +/// All options default to `true`. +#[derive(Debug, Clone)] +#[non_exhaustive] +pub struct Config { + /// Run HTTPS latency probes against relay servers. + /// + /// Disable this on constrained devices where the extra TCP connections are + /// too expensive — QAD (UDP) probes already measure relay latency. + pub https_probes: bool, + /// Check for captive portals when generating the first report. + /// + /// Disable this on embedded devices or environments where captive portal + /// detection is not meaningful. + pub captive_portal_check: bool, +} + +impl Default for Config { + fn default() -> Self { + Self { + https_probes: true, + captive_portal_check: true, + } + } +} + const FULL_REPORT_INTERVAL: Duration = Duration::from_secs(5 * 60); const ENOUGH_ENDPOINTS: usize = 3; @@ -100,6 +128,8 @@ pub(crate) struct Client { qad_conns: QadConns, #[cfg(not(wasm_browser))] tls_config: rustls::ClientConfig, + /// Whether to check for captive portals. + captive_portal_check: bool, /// A collection of previously generated reports. /// /// Sometimes it is useful to look at past reports to decide what to do. @@ -239,6 +269,7 @@ impl Client { qad_conns: QadConns::default(), #[cfg(not(wasm_browser))] tls_config: opts.tls_config, + captive_portal_check: opts.config.captive_portal_check, } } @@ -294,6 +325,7 @@ impl Client { self.reports.last.clone(), self.relay_map.clone(), self.probes.clone(), + self.captive_portal_check, if_state.clone(), shutdown_token.child_token(), #[cfg(not(wasm_browser))] diff --git a/iroh/src/net_report/options.rs b/iroh/src/net_report/options.rs index e30d5710fe..07749c97fb 100644 --- a/iroh/src/net_report/options.rs +++ b/iroh/src/net_report/options.rs @@ -6,7 +6,7 @@ pub(crate) use imp::Options; mod imp { use std::collections::BTreeSet; - use crate::net_report::{QuicConfig, probes::Probe}; + use crate::net_report::{Config, QuicConfig, probes::Probe}; /// Options for running probes /// @@ -21,6 +21,8 @@ mod imp { pub(crate) quic_config: Option, /// TLS config for HTTPS probes. pub(crate) tls_config: rustls::ClientConfig, + /// User-facing configuration. + pub(crate) config: Config, } impl Options { @@ -28,6 +30,7 @@ mod imp { Self { quic_config: None, tls_config, + config: Config::default(), } } /// Enable quic probes @@ -36,6 +39,12 @@ mod imp { self } + /// Set the net report configuration. + pub(crate) fn net_report_config(mut self, config: Config) -> Self { + self.config = config; + self + } + /// Turn the options into set of valid protocols pub(crate) fn as_protocols(&self) -> BTreeSet { let mut protocols = BTreeSet::new(); @@ -47,7 +56,9 @@ mod imp { protocols.insert(Probe::QadIpv6); } } - protocols.insert(Probe::Https); + if self.config.https_probes { + protocols.insert(Probe::Https); + } protocols } } @@ -57,7 +68,7 @@ mod imp { mod imp { use std::collections::BTreeSet; - use crate::net_report::Probe; + use crate::net_report::{Config, Probe}; /// Options for running probes (in browsers). /// @@ -65,34 +76,39 @@ mod imp { /// These are run by default. #[derive(Debug, Clone)] pub(crate) struct Options { - /// Enable https probes - /// - /// On by default - pub(crate) https: bool, + /// User-facing configuration. + pub(crate) config: Config, } impl Default for Options { fn default() -> Self { - Self { https: true } + Self { + config: Config::default(), + } } } impl Options { /// Create an [`Options`] that disables all probes pub(crate) fn disabled() -> Self { - Self { https: false } + Self { + config: Config { + https_probes: false, + captive_portal_check: false, + }, + } } - /// Enable or disable https probe - pub(crate) fn https(mut self, enable: bool) -> Self { - self.https = enable; + /// Set the net report configuration. + pub(crate) fn net_report_config(mut self, config: Config) -> Self { + self.config = config; self } /// Turn the options into set of valid protocols pub(crate) fn as_protocols(&self) -> BTreeSet { let mut protocols = BTreeSet::new(); - if self.https { + if self.config.https_probes { protocols.insert(Probe::Https); } protocols diff --git a/iroh/src/net_report/reportgen.rs b/iroh/src/net_report/reportgen.rs index 9c8b016488..cb381fdf77 100644 --- a/iroh/src/net_report/reportgen.rs +++ b/iroh/src/net_report/reportgen.rs @@ -119,6 +119,7 @@ impl Client { last_report: Option, relay_map: RelayMap, protocols: BTreeSet, + captive_portal_check: bool, if_state: IfStateDetails, shutdown_token: CancellationToken, #[cfg(not(wasm_browser))] socket_state: SocketState, @@ -130,6 +131,7 @@ impl Client { last_report, relay_map, protocols, + captive_portal_check, #[cfg(not(wasm_browser))] socket_state, #[cfg(not(wasm_browser))] @@ -168,6 +170,9 @@ struct Actor { /// configuration for that protocol. protocols: BTreeSet, + /// Whether to check for captive portals. + captive_portal_check: bool, + /// Any socket-related state that doesn't exist/work in browsers #[cfg(not(wasm_browser))] socket_state: SocketState, @@ -276,7 +281,7 @@ impl Actor { // delay by a bit to wait for UDP QAD to finish, to avoid the probe if // it's unnecessary. #[cfg(not(wasm_browser))] - if self.last_report.is_none() { + if self.captive_portal_check && self.last_report.is_none() { // Even if we're doing a non-incremental update, we may want to try our // preferred relay for captive portal detection. let preferred_relay = self diff --git a/iroh/src/socket.rs b/iroh/src/socket.rs index becf33a7fb..88c3963e88 100644 --- a/iroh/src/socket.rs +++ b/iroh/src/socket.rs @@ -150,6 +150,7 @@ pub(crate) struct Options { pub(crate) hooks: EndpointHooksList, pub(crate) transport_bias: TransportBiasMap, pub(crate) portmapper_config: portmapper::PortmapperConfig, + pub(crate) net_report_config: crate::net_report::Config, /// Static configuration for the endpoint. pub(crate) static_config: StaticConfig, @@ -777,6 +778,7 @@ impl EndpointInner { hooks, transport_bias, portmapper_config, + net_report_config, static_config, } = opts; @@ -931,11 +933,13 @@ impl EndpointInner { ipv4: true, ipv6: has_ipv6_transport, }); - net_report::Options::new(tls_config.clone()).quic_config(qad_config) + net_report::Options::new(tls_config.clone()) + .quic_config(qad_config) + .net_report_config(net_report_config) }; #[cfg(wasm_browser)] - let net_report_config = net_report::Options::default(); + let net_report_config = net_report::Options::default().net_report_config(net_report_config); let net_reporter = net_report::Client::new( #[cfg(not(wasm_browser))] @@ -1779,6 +1783,7 @@ mod tests { hooks: Default::default(), transport_bias: Default::default(), portmapper_config: Default::default(), + net_report_config: Default::default(), static_config, } } @@ -2178,6 +2183,7 @@ mod tests { hooks: Default::default(), transport_bias: Default::default(), portmapper_config: Default::default(), + net_report_config: Default::default(), static_config, }; let sock = EndpointInner::bind(opts).await?; From 1c65cd4593c952508a901b0cdb75df34d9b7abf9 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Thu, 12 Mar 2026 17:48:43 +0200 Subject: [PATCH 2/5] shut up clippy! --- iroh/src/net_report/reportgen.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/iroh/src/net_report/reportgen.rs b/iroh/src/net_report/reportgen.rs index cb381fdf77..886d7c9543 100644 --- a/iroh/src/net_report/reportgen.rs +++ b/iroh/src/net_report/reportgen.rs @@ -115,6 +115,7 @@ impl Client { /// /// The actor starts running immediately and only generates a single report, after which /// it shuts down. Dropping this handle will abort the actor. + #[allow(clippy::too_many_arguments)] pub(super) fn new( last_report: Option, relay_map: RelayMap, From 35e351f550428866e3b107a7f0d501d73a33390f Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Mon, 16 Mar 2026 11:05:37 +0200 Subject: [PATCH 3/5] Add a built in minimal setting for NetReportConfig --- iroh/src/net_report.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/iroh/src/net_report.rs b/iroh/src/net_report.rs index 1b8bdb2e37..c221c521f8 100644 --- a/iroh/src/net_report.rs +++ b/iroh/src/net_report.rs @@ -104,6 +104,17 @@ pub struct Config { pub captive_portal_check: bool, } +impl Config { + + /// Creates a minimal configuration that disables all optional probes and checks. + pub fn minimal() -> Self { + Self { + https_probes: false, + captive_portal_check: false, + } + } +} + impl Default for Config { fn default() -> Self { Self { From 37e3e2e1e47345a1e5aa65f210aa9f05ccb61e71 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Thu, 19 Mar 2026 12:00:21 +0200 Subject: [PATCH 4/5] Rename config to user_config To avoid the weird config in options thing in the wasm branch --- iroh/src/net_report.rs | 3 +-- iroh/src/net_report/options.rs | 26 ++++++++------------------ 2 files changed, 9 insertions(+), 20 deletions(-) diff --git a/iroh/src/net_report.rs b/iroh/src/net_report.rs index c221c521f8..6c39a23d01 100644 --- a/iroh/src/net_report.rs +++ b/iroh/src/net_report.rs @@ -105,7 +105,6 @@ pub struct Config { } impl Config { - /// Creates a minimal configuration that disables all optional probes and checks. pub fn minimal() -> Self { Self { @@ -280,7 +279,7 @@ impl Client { qad_conns: QadConns::default(), #[cfg(not(wasm_browser))] tls_config: opts.tls_config, - captive_portal_check: opts.config.captive_portal_check, + captive_portal_check: opts.user_config.captive_portal_check, } } diff --git a/iroh/src/net_report/options.rs b/iroh/src/net_report/options.rs index 07749c97fb..2bbb71be3a 100644 --- a/iroh/src/net_report/options.rs +++ b/iroh/src/net_report/options.rs @@ -22,7 +22,7 @@ mod imp { /// TLS config for HTTPS probes. pub(crate) tls_config: rustls::ClientConfig, /// User-facing configuration. - pub(crate) config: Config, + pub(crate) user_config: Config, } impl Options { @@ -30,7 +30,7 @@ mod imp { Self { quic_config: None, tls_config, - config: Config::default(), + user_config: Config::default(), } } /// Enable quic probes @@ -41,7 +41,7 @@ mod imp { /// Set the net report configuration. pub(crate) fn net_report_config(mut self, config: Config) -> Self { - self.config = config; + self.user_config = config; self } @@ -56,7 +56,7 @@ mod imp { protocols.insert(Probe::QadIpv6); } } - if self.config.https_probes { + if self.user_config.https_probes { protocols.insert(Probe::Https); } protocols @@ -77,38 +77,28 @@ mod imp { #[derive(Debug, Clone)] pub(crate) struct Options { /// User-facing configuration. - pub(crate) config: Config, + pub(crate) user_config: Config, } impl Default for Options { fn default() -> Self { Self { - config: Config::default(), + user_config: Config::default(), } } } impl Options { - /// Create an [`Options`] that disables all probes - pub(crate) fn disabled() -> Self { - Self { - config: Config { - https_probes: false, - captive_portal_check: false, - }, - } - } - /// Set the net report configuration. pub(crate) fn net_report_config(mut self, config: Config) -> Self { - self.config = config; + self.user_config = config; self } /// Turn the options into set of valid protocols pub(crate) fn as_protocols(&self) -> BTreeSet { let mut protocols = BTreeSet::new(); - if self.config.https_probes { + if self.user_config.https_probes { protocols.insert(Probe::Https); } protocols From fa5360025a608fcfb9a5e64bc560bc8459357bd0 Mon Sep 17 00:00:00 2001 From: Ruediger Klaehn Date: Thu, 19 Mar 2026 18:17:02 +0200 Subject: [PATCH 5/5] PR review feedback: - rename :net_report::Config to Nnet_report::NetReportConfig - better documentation of net report config options --- iroh/src/endpoint.rs | 11 +++++++---- iroh/src/lib.rs | 4 +--- iroh/src/net_report.rs | 28 ++++++++++++++++++++-------- iroh/src/net_report/options.rs | 16 ++++++++-------- iroh/src/socket.rs | 2 +- 5 files changed, 37 insertions(+), 24 deletions(-) diff --git a/iroh/src/endpoint.rs b/iroh/src/endpoint.rs index 8f17b96a6e..6efe30f3be 100644 --- a/iroh/src/endpoint.rs +++ b/iroh/src/endpoint.rs @@ -101,7 +101,7 @@ pub use self::{ #[cfg(not(wasm_browser))] use crate::socket::transports::IpConfig; use crate::socket::transports::TransportConfig; -pub use crate::{net_report::Config as NetReportConfig, portmapper::PortmapperConfig}; +pub use crate::{net_report::NetReportConfig, portmapper::PortmapperConfig}; /// Builder for [`Endpoint`]. /// @@ -706,10 +706,13 @@ impl Builder { self } - /// Configures the net report service. + /// Configures the net report. /// - /// Controls which probes (HTTPS latency, captive portal detection) are run. - /// Defaults to all probes enabled. + /// The net report component is responsible for figuring out if and how the endpoint is connected to the internet. + /// It does this by doing various probes to the configured relay servers to get public addresses, NAT behaviour, and + /// relay latencies. In addition it tries to detect captive portals. + /// + /// Some non-essential features of the net report component can be disabled via this configuration. pub fn net_report_config(mut self, config: NetReportConfig) -> Self { self.net_report_config = config; self diff --git a/iroh/src/lib.rs b/iroh/src/lib.rs index e27239ce52..2bc8e9fb9e 100644 --- a/iroh/src/lib.rs +++ b/iroh/src/lib.rs @@ -276,9 +276,7 @@ pub use iroh_base::{ pub use iroh_relay::dns; pub use iroh_relay::{RelayConfig, RelayMap, endpoint_info}; pub use n0_watcher::Watcher; -pub use net_report::{ - Config as NetReportConfig, Report as NetReport, TIMEOUT as NET_REPORT_TIMEOUT, -}; +pub use net_report::{NetReportConfig, Report as NetReport, TIMEOUT as NET_REPORT_TIMEOUT}; #[cfg(any(test, feature = "test-utils"))] pub mod test_utils; diff --git a/iroh/src/net_report.rs b/iroh/src/net_report.rs index 6c39a23d01..546289c881 100644 --- a/iroh/src/net_report.rs +++ b/iroh/src/net_report.rs @@ -85,26 +85,38 @@ pub use self::{ }; pub(crate) use self::{options::Options, reportgen::QuicConfig}; -/// Configuration for the net report service. +/// Configuration for the net report component. /// /// Controls which probes and checks are performed when generating network reports. /// All options default to `true`. #[derive(Debug, Clone)] #[non_exhaustive] -pub struct Config { +pub struct NetReportConfig { /// Run HTTPS latency probes against relay servers. /// - /// Disable this on constrained devices where the extra TCP connections are - /// too expensive — QAD (UDP) probes already measure relay latency. + /// HTTPS latency probes perform an empty HTTPS GET request to each configured + /// relay server and measure latency. + /// + /// They are performed in addition to the QUIC address discovery (QAD) probes. + /// They are the only way to detect relay latencies and thus the preferred relay + /// in networks that do not allow QUIC traffic. + /// + /// Disabling them is harmless on networks that do allow QUIC traffic, but will + /// completely prevent finding the home relay on networks that do block QUIC. pub https_probes: bool, + /// Check for captive portals when generating the first report. /// - /// Disable this on embedded devices or environments where captive portal - /// detection is not meaningful. + /// This is done by accessing a well-known URL that is available on each relay + /// server, `/generate_204`. If a GET request to this URL returns anything else + /// but a 204 No Content response, we assume we are behind a captive portal. + /// + /// When we have detected that we are behind a captive portal, we try to contact + /// the relay servers more frequently in case the captive portal status changes. pub captive_portal_check: bool, } -impl Config { +impl NetReportConfig { /// Creates a minimal configuration that disables all optional probes and checks. pub fn minimal() -> Self { Self { @@ -114,7 +126,7 @@ impl Config { } } -impl Default for Config { +impl Default for NetReportConfig { fn default() -> Self { Self { https_probes: true, diff --git a/iroh/src/net_report/options.rs b/iroh/src/net_report/options.rs index 2bbb71be3a..8046958d79 100644 --- a/iroh/src/net_report/options.rs +++ b/iroh/src/net_report/options.rs @@ -6,7 +6,7 @@ pub(crate) use imp::Options; mod imp { use std::collections::BTreeSet; - use crate::net_report::{Config, QuicConfig, probes::Probe}; + use crate::net_report::{NetReportConfig, QuicConfig, probes::Probe}; /// Options for running probes /// @@ -22,7 +22,7 @@ mod imp { /// TLS config for HTTPS probes. pub(crate) tls_config: rustls::ClientConfig, /// User-facing configuration. - pub(crate) user_config: Config, + pub(crate) user_config: NetReportConfig, } impl Options { @@ -30,7 +30,7 @@ mod imp { Self { quic_config: None, tls_config, - user_config: Config::default(), + user_config: NetReportConfig::default(), } } /// Enable quic probes @@ -40,7 +40,7 @@ mod imp { } /// Set the net report configuration. - pub(crate) fn net_report_config(mut self, config: Config) -> Self { + pub(crate) fn net_report_config(mut self, config: NetReportConfig) -> Self { self.user_config = config; self } @@ -68,7 +68,7 @@ mod imp { mod imp { use std::collections::BTreeSet; - use crate::net_report::{Config, Probe}; + use crate::net_report::{NetReportConfig, Probe}; /// Options for running probes (in browsers). /// @@ -77,20 +77,20 @@ mod imp { #[derive(Debug, Clone)] pub(crate) struct Options { /// User-facing configuration. - pub(crate) user_config: Config, + pub(crate) user_config: NetReportConfig, } impl Default for Options { fn default() -> Self { Self { - user_config: Config::default(), + user_config: NetReportConfig::default(), } } } impl Options { /// Set the net report configuration. - pub(crate) fn net_report_config(mut self, config: Config) -> Self { + pub(crate) fn net_report_config(mut self, config: NetReportConfig) -> Self { self.user_config = config; self } diff --git a/iroh/src/socket.rs b/iroh/src/socket.rs index 9a477dfec7..d703c20f8b 100644 --- a/iroh/src/socket.rs +++ b/iroh/src/socket.rs @@ -150,7 +150,7 @@ pub(crate) struct Options { pub(crate) hooks: EndpointHooksList, pub(crate) transport_bias: TransportBiasMap, pub(crate) portmapper_config: portmapper::PortmapperConfig, - pub(crate) net_report_config: crate::net_report::Config, + pub(crate) net_report_config: crate::net_report::NetReportConfig, /// Static configuration for the endpoint. pub(crate) static_config: StaticConfig,