diff --git a/deltachat-jsonrpc/src/api.rs b/deltachat-jsonrpc/src/api.rs index 68a4906e1..94b401362 100644 --- a/deltachat-jsonrpc/src/api.rs +++ b/deltachat-jsonrpc/src/api.rs @@ -546,7 +546,7 @@ impl CommandApi { ctx.add_transport_from_qr(&qr).await } - /// Adds an initial transport on a randomly chosen chatmail relay + /// Adds an initial transport on the chatmail relay that answers fastest /// and lets the profile add further ones in the background. /// /// A `DCACCOUNT:` or `DCLOGIN:` `qr` code adds a single transport diff --git a/deltachat-rpc-client/src/deltachat_rpc_client/account.py b/deltachat-rpc-client/src/deltachat_rpc_client/account.py index 42c3f28ea..db0ef9ece 100644 --- a/deltachat-rpc-client/src/deltachat_rpc_client/account.py +++ b/deltachat-rpc-client/src/deltachat_rpc_client/account.py @@ -141,7 +141,7 @@ class Account: @futuremethod def init_transports(self, qr: Optional[str] = None): - """Add an initial transport on a randomly chosen chatmail relay. + """Add an initial transport on the chatmail relay that answers fastest. The profile then adds further ones in the background. A ``DCACCOUNT:`` or ``DCLOGIN:`` ``qr`` code adds a single transport diff --git a/src/autorelay.rs b/src/autorelay.rs index ddd24826f..d93233449 100644 --- a/src/autorelay.rs +++ b/src/autorelay.rs @@ -11,11 +11,13 @@ use deltachat_contact_tools::addr_normalize; use rand::distr::{Alphanumeric, SampleString}; use rand::seq::{IndexedRandom, SliceRandom}; use rusqlite::Transaction; +use tokio::task::JoinSet; use crate::config::{self, Config}; use crate::configure::{EnteredLoginParam, configure}; use crate::log::{LogExt, warn}; use crate::login_param::{EnteredCertificateChecks, EnteredImapLoginParam}; +use crate::net::{connect_tcp, proxy::ProxyConfig}; use crate::{context::Context, tools::time}; /// The target number of transports. @@ -57,19 +59,41 @@ pub(crate) async fn add_relay_candidates(context: &Context, addrs: &[String]) -> /// All candidates are probed at once with a TCP connection to their HTTPS port /// and configured in the order in which the connections complete, /// stopping at the first success. Candidates that fail the probe are skipped. +/// Answering TCP fastest is used as a network proximity measure, +/// which keeps latency low for initial onboarding, +/// and it avoids relays that are down or black-holing traffic. pub(crate) async fn add_transport_from_candidates( context: &Context, skip_network: bool, ) -> Result<()> { let mut candidates = triable_relay_candidates(context, time()).await?; candidates.shuffle(&mut rand::rng()); - let mut last_err = format_err!("No relay candidates"); + let mut probes = JoinSet::new(); + let proxy_config = ProxyConfig::load(context).await?; + let load_cache = false; + for host in candidates { + let ctx = context.clone(); + let proxy_config = proxy_config.clone(); + probes.spawn(async move { + let res = match proxy_config { + _ if skip_network => Ok(()), + Some(proxy) => proxy.connect(&ctx, &host, 443, load_cache).await.map(drop), + None => connect_tcp(&ctx, &host, 443, load_cache).await.map(drop), + }; + (host, res) + }); + } - // We patiently try each candidate in turn which might take a while - // if many hosts are unreachable but eventually succeeds if one candidate works. + let mut last_err = format_err!("No relay candidates"); let mark_as_autorelay = true; - for host in &candidates { - let param = login_param_from_host(host, mark_as_autorelay); + while let Some(res) = probes.join_next().await { + let (host, res) = res?; + if let Err(err) = res { + warn!(context, "Failed to connect to relay {host}: {err:#}."); + last_err = err; + continue; + } + let param = login_param_from_host(&host, mark_as_autorelay); match configure(context, ¶m, skip_network).await { Ok(()) => { info!(context, "Added a transport on relay {host}."); diff --git a/src/autorelay/autorelay_tests.rs b/src/autorelay/autorelay_tests.rs index 069e6ba95..2e08de1ff 100644 --- a/src/autorelay/autorelay_tests.rs +++ b/src/autorelay/autorelay_tests.rs @@ -56,7 +56,7 @@ async fn test_add_transport_from_candidates_failure() -> Result<()> { mark_defaults_tried(t, time()).await?; save_relay_candidates(t, &["bad host", "worse host"], 0).await?; - let skip_network = false; + let skip_network = true; let err = add_transport_from_candidates(t, skip_network) .await .unwrap_err(); diff --git a/src/configure.rs b/src/configure.rs index a686a3402..d77d83dc7 100644 --- a/src/configure.rs +++ b/src/configure.rs @@ -189,7 +189,7 @@ impl Context { Ok(()) } - /// Adds an initial transport on a randomly chosen chatmail relay + /// Adds an initial transport on the chatmail relay that answers fastest /// and lets the profile add further ones in the background. /// /// A `DCACCOUNT:` or `DCLOGIN:` `qr` code adds a single transport