diff --git a/rust/Cargo.lock b/rust/Cargo.lock index d6e173319..010f55309 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -4661,6 +4661,7 @@ dependencies = [ "futures", "hex", "hostname", + "itertools 0.13.0", "libc", "os_info", "rand_core 0.6.4", diff --git a/rust/phoenix-channel/Cargo.toml b/rust/phoenix-channel/Cargo.toml index 66397cec9..24cf49bc9 100644 --- a/rust/phoenix-channel/Cargo.toml +++ b/rust/phoenix-channel/Cargo.toml @@ -11,6 +11,7 @@ base64 = { workspace = true } firezone-logging = { workspace = true } futures = { workspace = true } hex = { workspace = true } +itertools = { workspace = true } libc = { workspace = true } os_info = { workspace = true } rand_core = { workspace = true } diff --git a/rust/phoenix-channel/src/lib.rs b/rust/phoenix-channel/src/lib.rs index 7adda5e2c..64c5810ac 100644 --- a/rust/phoenix-channel/src/lib.rs +++ b/rust/phoenix-channel/src/lib.rs @@ -19,6 +19,7 @@ use firezone_logging::{err_with_src, telemetry_span}; use futures::future::BoxFuture; use futures::{FutureExt, SinkExt, StreamExt}; use heartbeat::{Heartbeat, MissedLastHeartbeat}; +use itertools::Itertools as _; use rand_core::{OsRng, RngCore}; use secrecy::{ExposeSecret, Secret}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; @@ -109,7 +110,8 @@ async fn connect( addresses: Vec, socket_factory: &dyn SocketFactory, ) -> Result { - let mut last_error = None; + let mut errors = Vec::with_capacity(addresses.len()); + for addr in addresses { let Ok(socket) = socket_factory(&addr) else { continue; @@ -118,16 +120,16 @@ async fn connect( match socket.connect(addr).await { Ok(socket) => return Ok(socket), Err(e) => { - last_error = Some(e); + errors.push((addr, e)); } } } - let Some(err) = last_error else { + if errors.is_empty() { return Err(InternalError::InvalidUrl); - }; + } - Err(InternalError::SocketConnection(err)) + Err(InternalError::SocketConnection(errors)) } #[derive(Debug, thiserror::Error)] @@ -162,7 +164,7 @@ enum InternalError { StreamClosed, InvalidUrl, FailedToBuildRequest(tokio_tungstenite::tungstenite::http::Error), - SocketConnection(std::io::Error), + SocketConnection(Vec<(SocketAddr, std::io::Error)>), Timeout { duration: Duration }, } @@ -185,7 +187,16 @@ impl fmt::Display for InternalError { InternalError::CloseMessage => write!(f, "portal closed the websocket connection"), InternalError::StreamClosed => write!(f, "websocket stream was closed"), InternalError::InvalidUrl => write!(f, "failed to resolve url"), - InternalError::SocketConnection(_) => write!(f, "failed to connect socket"), + InternalError::SocketConnection(errors) => { + write!( + f, + "failed to connect socket: [{}]", + errors + .iter() + .map(|(addr, e)| format!("{addr}: {e}")) + .join(", ") + ) + } InternalError::Timeout { duration, .. } => { write!(f, "operation timed out after {duration:?}") } @@ -202,8 +213,8 @@ impl std::error::Error for InternalError { InternalError::WebSocket(tokio_tungstenite::tungstenite::Error::Http(_)) => None, InternalError::WebSocket(e) => Some(e), InternalError::Serde(e) => Some(e), - InternalError::SocketConnection(e) => Some(e), InternalError::FailedToBuildRequest(e) => Some(e), + InternalError::SocketConnection(_) => None, InternalError::MissedHeartbeat => None, InternalError::CloseMessage => None, InternalError::StreamClosed => None,