diff --git a/iroh/src/endpoint.rs b/iroh/src/endpoint.rs index 77da031d79..d4637c322c 100644 --- a/iroh/src/endpoint.rs +++ b/iroh/src/endpoint.rs @@ -96,10 +96,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::NetReportConfig, portmapper::PortmapperConfig}; /// Builder for [`Endpoint`]. /// @@ -127,6 +127,7 @@ pub struct Builder { hooks: EndpointHooksList, transport_bias: socket::transports::TransportBiasMap, portmapper_config: PortmapperConfig, + net_report_config: NetReportConfig, crypto_provider: Option>, } @@ -193,6 +194,7 @@ impl Builder { hooks: Default::default(), transport_bias: Default::default(), portmapper_config: Default::default(), + net_report_config: Default::default(), crypto_provider: None, } } @@ -255,6 +257,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, }; @@ -742,6 +745,18 @@ impl Builder { self } + /// Configures the net report. + /// + /// 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 + } + /// 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 0a051389c7..cfd5948357 100644 --- a/iroh/src/lib.rs +++ b/iroh/src/lib.rs @@ -288,7 +288,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::{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 729d679f0d..f8698e949c 100644 --- a/iroh/src/net_report.rs +++ b/iroh/src/net_report.rs @@ -85,6 +85,56 @@ pub use self::{ }; pub(crate) use self::{options::Options, reportgen::QuicConfig}; +/// 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 NetReportConfig { + /// Run HTTPS latency probes against relay servers. + /// + /// 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. + /// + /// 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 NetReportConfig { + /// 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 NetReportConfig { + 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 +150,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 +291,7 @@ impl Client { qad_conns: QadConns::default(), #[cfg(not(wasm_browser))] tls_config: opts.tls_config, + captive_portal_check: opts.user_config.captive_portal_check, } } @@ -294,6 +347,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..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::{QuicConfig, probes::Probe}; + use crate::net_report::{NetReportConfig, 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) user_config: NetReportConfig, } impl Options { @@ -28,6 +30,7 @@ mod imp { Self { quic_config: None, tls_config, + user_config: NetReportConfig::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: NetReportConfig) -> Self { + 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(); @@ -47,7 +56,9 @@ mod imp { protocols.insert(Probe::QadIpv6); } } - protocols.insert(Probe::Https); + if self.user_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::{NetReportConfig, Probe}; /// Options for running probes (in browsers). /// @@ -65,34 +76,29 @@ 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) user_config: NetReportConfig, } impl Default for Options { fn default() -> Self { - Self { https: true } + Self { + user_config: NetReportConfig::default(), + } } } impl Options { - /// Create an [`Options`] that disables all probes - pub(crate) fn disabled() -> Self { - Self { https: 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: NetReportConfig) -> Self { + 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.https { + if self.user_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 4dc66f3b83..a66df1d1be 100644 --- a/iroh/src/net_report/reportgen.rs +++ b/iroh/src/net_report/reportgen.rs @@ -115,10 +115,12 @@ 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, protocols: BTreeSet, + captive_portal_check: bool, if_state: IfStateDetails, shutdown_token: CancellationToken, #[cfg(not(wasm_browser))] socket_state: SocketState, @@ -130,6 +132,7 @@ impl Client { last_report, relay_map, protocols, + captive_portal_check, #[cfg(not(wasm_browser))] socket_state, #[cfg(not(wasm_browser))] @@ -168,6 +171,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 +282,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 5e307ccd1c..a6c5ad5530 100644 --- a/iroh/src/socket.rs +++ b/iroh/src/socket.rs @@ -159,6 +159,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::NetReportConfig, /// Static configuration for the endpoint. pub(crate) static_config: StaticConfig, @@ -812,6 +813,7 @@ impl EndpointInner { hooks, transport_bias, portmapper_config, + net_report_config, static_config, } = opts; @@ -967,11 +969,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))] @@ -1935,6 +1939,7 @@ mod tests { hooks: Default::default(), transport_bias: Default::default(), portmapper_config: Default::default(), + net_report_config: Default::default(), static_config, } } @@ -2343,6 +2348,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?;