diff --git a/common/client-core/src/cli_helpers/client_add_gateway.rs b/common/client-core/src/cli_helpers/client_add_gateway.rs index dc4280ba296..024c88e7b48 100644 --- a/common/client-core/src/cli_helpers/client_add_gateway.rs +++ b/common/client-core/src/cli_helpers/client_add_gateway.rs @@ -140,6 +140,7 @@ where available_gateways, #[cfg(unix)] connection_fd_callback: None, + connect_timeout: None, }; let init_details = diff --git a/common/client-core/src/cli_helpers/client_init.rs b/common/client-core/src/cli_helpers/client_init.rs index feda3ab8d11..ffc577330b9 100644 --- a/common/client-core/src/cli_helpers/client_init.rs +++ b/common/client-core/src/cli_helpers/client_init.rs @@ -188,6 +188,7 @@ where available_gateways, #[cfg(unix)] connection_fd_callback: None, + connect_timeout: None, }; let init_details = diff --git a/common/client-core/src/client/base_client/mod.rs b/common/client-core/src/client/base_client/mod.rs index a663aadd516..173cd7bd8d2 100644 --- a/common/client-core/src/client/base_client/mod.rs +++ b/common/client-core/src/client/base_client/mod.rs @@ -65,6 +65,7 @@ use std::fmt::Debug; use std::os::raw::c_int as RawFd; use std::path::Path; use std::sync::Arc; +use std::time::Duration; use time::OffsetDateTime; use tokio::sync::mpsc::Sender; use url::Url; @@ -230,6 +231,7 @@ pub struct BaseClientBuilder { #[cfg(unix)] connection_fd_callback: Option>, + connect_timeout: Option, derivation_material: Option, } @@ -258,6 +260,7 @@ where setup_method: GatewaySetup::MustLoad { gateway_id: None }, #[cfg(unix)] connection_fd_callback: None, + connect_timeout: None, derivation_material: None, } } @@ -356,6 +359,11 @@ where self } + pub fn with_connect_timeout(mut self, timeout: Duration) -> Self { + self.connect_timeout = Some(timeout); + self + } + // note: do **NOT** make this method public as its only valid usage is from within `start_base` // because it relies on the crypto keys being already loaded fn mix_address(details: &InitialisationResult) -> Recipient { @@ -533,6 +541,7 @@ where packet_router: PacketRouter, stats_reporter: ClientStatsSender, #[cfg(unix)] connection_fd_callback: Option>, + connect_timeout: Option, shutdown_tracker: &ShutdownTracker, ) -> Result, ClientCoreError> where @@ -577,6 +586,7 @@ where stats_reporter, #[cfg(unix)] connection_fd_callback, + connect_timeout, shutdown_tracker.clone_shutdown_token(), ) }; @@ -640,6 +650,7 @@ where packet_router: PacketRouter, stats_reporter: ClientStatsSender, #[cfg(unix)] connection_fd_callback: Option>, + connect_timeout: Option, shutdown_tracker: &ShutdownTracker, ) -> Result, ClientCoreError> where @@ -672,6 +683,7 @@ where stats_reporter, #[cfg(unix)] connection_fd_callback, + connect_timeout, shutdown_tracker, ) .await?; @@ -1074,6 +1086,7 @@ where stats_reporter.clone(), #[cfg(unix)] self.connection_fd_callback, + self.connect_timeout, &shutdown_tracker.child_tracker(), ) .await?; diff --git a/common/client-core/src/init/helpers.rs b/common/client-core/src/init/helpers.rs index 76dd94deb78..6df9cb6eda2 100644 --- a/common/client-core/src/init/helpers.rs +++ b/common/client-core/src/init/helpers.rs @@ -382,6 +382,7 @@ pub(super) async fn register_with_gateway( gateway_listener: Url, our_identity: Arc, #[cfg(unix)] connection_fd_callback: Option>, + connect_timeout: Option, ) -> Result { let mut gateway_client = GatewayClient::new_init( gateway_listener, @@ -389,6 +390,7 @@ pub(super) async fn register_with_gateway( our_identity.clone(), #[cfg(unix)] connection_fd_callback, + connect_timeout, ); gateway_client.establish_connection().await.map_err(|err| { diff --git a/common/client-core/src/init/mod.rs b/common/client-core/src/init/mod.rs index 5bece573fdc..81a2466a125 100644 --- a/common/client-core/src/init/mod.rs +++ b/common/client-core/src/init/mod.rs @@ -23,6 +23,7 @@ use nym_topology::node::RoutingNode; use rand::rngs::OsRng; use rand::{CryptoRng, RngCore}; use serde::Serialize; +use std::time::Duration; #[cfg(unix)] use std::{os::fd::RawFd, sync::Arc}; @@ -56,6 +57,7 @@ async fn setup_new_gateway( selection_specification: GatewaySelectionSpecification, available_gateways: Vec, #[cfg(unix)] connection_fd_callback: Option>, + connect_timeout: Option, ) -> Result where K: KeyStore, @@ -117,6 +119,7 @@ where our_identity, #[cfg(unix)] connection_fd_callback, + connect_timeout, ) .await?; ( @@ -213,6 +216,7 @@ where available_gateways, #[cfg(unix)] connection_fd_callback, + connect_timeout, } => { tracing::debug!("GatewaySetup::New with spec: {specification:?}"); setup_new_gateway( @@ -222,6 +226,7 @@ where available_gateways, #[cfg(unix)] connection_fd_callback, + connect_timeout, ) .await } diff --git a/common/client-core/src/init/types.rs b/common/client-core/src/init/types.rs index 8f09980a43e..2f2e89edc03 100644 --- a/common/client-core/src/init/types.rs +++ b/common/client-core/src/init/types.rs @@ -21,6 +21,7 @@ use std::fmt::{Debug, Display}; #[cfg(unix)] use std::os::fd::RawFd; use std::sync::Arc; +use std::time::Duration; use time::OffsetDateTime; use url::Url; @@ -214,6 +215,9 @@ pub enum GatewaySetup { /// Callback useful for allowing initial connection to gateway #[cfg(unix)] connection_fd_callback: Option>, + + /// Timeout for establishing connection + connect_timeout: Option, }, ReuseConnection { @@ -239,6 +243,7 @@ impl Debug for GatewaySetup { available_gateways, #[cfg(unix)] connection_fd_callback: _, + connect_timeout: _, } => f .debug_struct("GatewaySetup::New") .field("specification", specification) @@ -280,6 +285,7 @@ impl GatewaySetup { available_gateways: vec![], #[cfg(unix)] connection_fd_callback: None, + connect_timeout: None, } } diff --git a/common/client-libs/gateway-client/src/client/mod.rs b/common/client-libs/gateway-client/src/client/mod.rs index 52e5833eb29..21fa712c723 100644 --- a/common/client-libs/gateway-client/src/client/mod.rs +++ b/common/client-libs/gateway-client/src/client/mod.rs @@ -38,6 +38,7 @@ use url::Url; #[cfg(unix)] use std::os::fd::RawFd; +use std::time::Duration; #[cfg(not(target_arch = "wasm32"))] use tokio::time::sleep; @@ -104,10 +105,13 @@ pub struct GatewayClient { // currently unused (but populated) negotiated_protocol: Option, - // Callback on the fd as soon as the connection has been established + /// Callback on the fd as soon as the connection has been established #[cfg(unix)] connection_fd_callback: Option>, + /// Maximum duration to wait for a connection to be established when set + connect_timeout: Option, + /// Listen to shutdown messages and send notifications back to the task manager shutdown_token: ShutdownToken, } @@ -124,6 +128,7 @@ impl GatewayClient { bandwidth_controller: Option>, stats_reporter: ClientStatsSender, #[cfg(unix)] connection_fd_callback: Option>, + connect_timeout: Option, shutdown_token: ShutdownToken, ) -> Self { GatewayClient { @@ -141,6 +146,7 @@ impl GatewayClient { negotiated_protocol: None, #[cfg(unix)] connection_fd_callback, + connect_timeout, shutdown_token, } } @@ -208,6 +214,7 @@ impl GatewayClient { &self.gateway_address, #[cfg(unix)] self.connection_fd_callback.clone(), + self.connect_timeout, ) .await?; @@ -1132,6 +1139,7 @@ impl GatewayClient { gateway_identity: ed25519::PublicKey, local_identity: Arc, #[cfg(unix)] connection_fd_callback: Option>, + connect_timeout: Option, ) -> Self { log::trace!("Initialising gateway client"); use futures::channel::mpsc; @@ -1158,6 +1166,7 @@ impl GatewayClient { negotiated_protocol: None, #[cfg(unix)] connection_fd_callback, + connect_timeout, shutdown_token, } } @@ -1190,6 +1199,7 @@ impl GatewayClient { negotiated_protocol: self.negotiated_protocol, #[cfg(unix)] connection_fd_callback: self.connection_fd_callback, + connect_timeout: self.connect_timeout, shutdown_token, } } diff --git a/common/client-libs/gateway-client/src/client/websockets.rs b/common/client-libs/gateway-client/src/client/websockets.rs index 9b04c9e0285..b8b6a54f421 100644 --- a/common/client-libs/gateway-client/src/client/websockets.rs +++ b/common/client-libs/gateway-client/src/client/websockets.rs @@ -1,6 +1,7 @@ use crate::error::GatewayClientError; use nym_http_api_client::HickoryDnsResolver; +use std::time::Duration; #[cfg(unix)] use std::{ os::fd::{AsRawFd, RawFd}, @@ -17,6 +18,7 @@ use std::net::SocketAddr; pub(crate) async fn connect_async( endpoint: &str, #[cfg(unix)] connection_fd_callback: Option>, + connect_timeout: Option, ) -> Result<(WebSocketStream>, Response), GatewayClientError> { use tokio::net::TcpSocket; @@ -64,7 +66,22 @@ pub(crate) async fn connect_async( callback.as_ref()(socket.as_raw_fd()); } - match socket.connect(sock_addr).await { + let connect_res = if let Some(connect_timeout) = connect_timeout { + match tokio::time::timeout(connect_timeout, socket.connect(sock_addr)).await { + Ok(res) => res, + Err(_elapsed) => { + stream = Err(GatewayClientError::NetworkConnectionTimeout { + address: endpoint.to_owned(), + timeout: connect_timeout, + }); + continue; + } + } + } else { + socket.connect(sock_addr).await + }; + + match connect_res { Ok(s) => { stream = Ok(s); break; diff --git a/common/client-libs/gateway-client/src/error.rs b/common/client-libs/gateway-client/src/error.rs index 8766f9f5a67..1a0925096f4 100644 --- a/common/client-libs/gateway-client/src/error.rs +++ b/common/client-libs/gateway-client/src/error.rs @@ -4,6 +4,7 @@ use nym_gateway_requests::registration::handshake::error::HandshakeError; use nym_gateway_requests::{GatewayRequestsError, SimpleGatewayRequestsError}; use std::io; +use std::time::Duration; use thiserror::Error; use tungstenite::Error as WsError; @@ -46,6 +47,9 @@ pub enum GatewayClientError { source: Box, }, + #[error("timeout when establishing connection: {address}, timeout: {timeout:?}")] + NetworkConnectionTimeout { address: String, timeout: Duration }, + #[error("no socket address for endpoint: {address}")] NoEndpointForConnection { address: String }, diff --git a/common/wasm/client-core/src/helpers.rs b/common/wasm/client-core/src/helpers.rs index de5cd6883a2..ff356ae5c24 100644 --- a/common/wasm/client-core/src/helpers.rs +++ b/common/wasm/client-core/src/helpers.rs @@ -23,6 +23,7 @@ use nym_topology::{EpochRewardedSet, NymTopology, RoutingNode}; use nym_validator_client::client::IdentityKey; use nym_validator_client::{nym_api::NymApiClientExt, UserAgent}; use rand::thread_rng; +use std::time::Duration; use url::Url; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen_futures::future_to_promise; @@ -127,6 +128,7 @@ pub async fn setup_gateway_wasm( force_tls: bool, chosen_gateway: Option, gateways: Vec, + connect_timeout: Option, ) -> Result { // TODO: so much optimization and extra features could be added here, but that's for the future @@ -144,6 +146,7 @@ pub async fn setup_gateway_wasm( GatewaySetup::New { specification: selection_spec, available_gateways: gateways, + connect_timeout, } }; @@ -159,6 +162,7 @@ pub async fn setup_gateway_from_api( nym_apis: &[Url], minimum_performance: u8, ignore_epoch_roles: bool, + connect_timeout: Option, ) -> Result { let gateways = gateways_for_init( nym_apis, @@ -168,7 +172,14 @@ pub async fn setup_gateway_from_api( None, ) .await?; - setup_gateway_wasm(client_store, force_tls, chosen_gateway, gateways).await + setup_gateway_wasm( + client_store, + force_tls, + chosen_gateway, + gateways, + connect_timeout, + ) + .await } pub async fn current_gateways_wasm( @@ -192,9 +203,17 @@ pub async fn setup_from_topology( force_tls: bool, topology: &NymTopology, client_store: &ClientStorage, + connect_timeout: Option, ) -> Result { let gateways = topology.entry_capable_nodes().cloned().collect::>(); - setup_gateway_wasm(client_store, force_tls, explicit_gateway, gateways).await + setup_gateway_wasm( + client_store, + force_tls, + explicit_gateway, + gateways, + connect_timeout, + ) + .await } pub async fn generate_new_client_keys(store: &ClientStorage) -> Result<(), WasmCoreError> { @@ -213,6 +232,7 @@ pub async fn add_gateway( min_performance: u8, ignore_epoch_roles: bool, storage: &ClientStorage, + connect_timeout: Option, ) -> Result<(), WasmCoreError> { let selection_spec = GatewaySelectionSpecification::new( preferred_gateway.clone(), @@ -267,6 +287,7 @@ pub async fn add_gateway( let gateway_setup = GatewaySetup::New { specification: selection_spec, available_gateways, + connect_timeout, }; let init_details = setup_gateway(gateway_setup, storage, storage).await?; diff --git a/nym-api/src/network_monitor/monitor/sender.rs b/nym-api/src/network_monitor/monitor/sender.rs index 358fb8e3167..0ecb6735973 100644 --- a/nym-api/src/network_monitor/monitor/sender.rs +++ b/nym-api/src/network_monitor/monitor/sender.rs @@ -190,6 +190,7 @@ impl PacketSender { ), #[cfg(unix)] None, + None, fresh_gateway_client_data.shutdown_token.clone(), ); diff --git a/nym-registration-client/src/builder/config.rs b/nym-registration-client/src/builder/config.rs index d1a342faa98..9f36ebf6b1e 100644 --- a/nym-registration-client/src/builder/config.rs +++ b/nym-registration-client/src/builder/config.rs @@ -38,6 +38,7 @@ pub struct BuilderConfig { pub cancel_token: CancellationToken, #[cfg(unix)] pub connection_fd_callback: Arc, + pub connect_timeout: Option, } #[derive(Clone, Default, Debug, Eq, PartialEq)] @@ -71,6 +72,7 @@ impl BuilderConfig { network_env: NymNetworkDetails, cancel_token: CancellationToken, #[cfg(unix)] connection_fd_callback: Arc, + connect_timeout: Option, ) -> Self { Self { entry_node, @@ -84,6 +86,7 @@ impl BuilderConfig { cancel_token, #[cfg(unix)] connection_fd_callback, + connect_timeout, } } @@ -294,6 +297,7 @@ pub struct BuilderConfigBuilder { cancel_token: Option, #[cfg(unix)] connection_fd_callback: Option>, + connect_timeout: Option, } impl BuilderConfigBuilder { @@ -358,6 +362,11 @@ impl BuilderConfigBuilder { self } + pub fn with_connect_timeout(mut self, connect_timeout: Duration) -> Self { + self.connect_timeout = Some(connect_timeout); + self + } + /// Builds the `BuilderConfig`. /// /// Returns an error if any required field is missing. @@ -388,6 +397,7 @@ impl BuilderConfigBuilder { connection_fd_callback: self .connection_fd_callback .ok_or(BuilderConfigError::MissingConnectionFdCallback)?, + connect_timeout: self.connect_timeout, }) } } diff --git a/sdk/rust/nym-sdk/src/mixnet/client.rs b/sdk/rust/nym-sdk/src/mixnet/client.rs index a9e12b0fe5f..c2d26b16ca6 100644 --- a/sdk/rust/nym-sdk/src/mixnet/client.rs +++ b/sdk/rust/nym-sdk/src/mixnet/client.rs @@ -38,6 +38,7 @@ use std::path::Path; use std::path::PathBuf; #[cfg(unix)] use std::sync::Arc; +use std::time::Duration; use url::Url; use zeroize::Zeroizing; @@ -405,6 +406,9 @@ where #[cfg(unix)] connection_fd_callback: Option>, + /// Timeout for establishing a connection + connect_timeout: Option, + forget_me: ForgetMe, remember_me: RememberMe, @@ -466,6 +470,7 @@ where user_agent: None, #[cfg(unix)] connection_fd_callback: None, + connect_timeout: None, forget_me, remember_me, derivation_material: None, @@ -589,6 +594,7 @@ where available_gateways, #[cfg(unix)] connection_fd_callback: self.connection_fd_callback.clone(), + connect_timeout: self.connect_timeout, }) } @@ -758,6 +764,10 @@ where base_builder = base_builder.with_connection_fd_callback(connection_fd_callback); } + if let Some(connect_timeout) = self.connect_timeout { + base_builder = base_builder.with_connect_timeout(connect_timeout); + } + let started_client = base_builder.start_base().await?; self.state = BuilderState::Registered {}; let nym_address = started_client.address; diff --git a/wasm/client/src/client.rs b/wasm/client/src/client.rs index 8c006b7f09f..45fe6396011 100644 --- a/wasm/client/src/client.rs +++ b/wasm/client/src/client.rs @@ -222,6 +222,7 @@ impl NymClientBuilder { self.config.base.debug.topology.minimum_gateway_performance, self.config.base.debug.topology.ignore_ingress_epoch_role, &client_store, + None, ) .await?; } diff --git a/wasm/mix-fetch/src/client.rs b/wasm/mix-fetch/src/client.rs index 736c82c4139..7fb409d6fe8 100644 --- a/wasm/mix-fetch/src/client.rs +++ b/wasm/mix-fetch/src/client.rs @@ -158,6 +158,7 @@ impl MixFetchClientBuilder { self.config.base.debug.topology.minimum_gateway_performance, self.config.base.debug.topology.ignore_ingress_epoch_role, &client_store, + None, ) .await?; } diff --git a/wasm/node-tester/src/tester.rs b/wasm/node-tester/src/tester.rs index 9a48951ea7b..d26722de3b7 100644 --- a/wasm/node-tester/src/tester.rs +++ b/wasm/node-tester/src/tester.rs @@ -153,6 +153,7 @@ impl NymNodeTesterBuilder { false, &self.base_topology, client_store, + None, ) .await?) } @@ -211,6 +212,7 @@ impl NymNodeTesterBuilder { packet_router, self.bandwidth_controller.take(), ClientStatsSender::new(None, stats_sender_task), + None, gateway_task, ) };