diff --git a/rust/gui-client/src-tauri/src/bin/firezone-gui-client.rs b/rust/gui-client/src-tauri/src/bin/firezone-gui-client.rs index 8a1d6c8c7..e0ae0c08d 100644 --- a/rust/gui-client/src-tauri/src/bin/firezone-gui-client.rs +++ b/rust/gui-client/src-tauri/src/bin/firezone-gui-client.rs @@ -163,6 +163,13 @@ fn run_gui(config: RunConfig) -> Result<()> { return Err(anyhow); } + if anyhow.root_cause().is::() { + show_error_dialog( + "The Firezone Tunnel service is not responding. If the issue persists, contact your administrator.", + )?; + return Err(anyhow); + } + show_error_dialog( "An unexpected error occurred. Please try restarting Firezone. If the issue persists, contact your administrator.", )?; diff --git a/rust/gui-client/src-tauri/src/controller.rs b/rust/gui-client/src-tauri/src/controller.rs index 14272175c..3ea3e0e40 100644 --- a/rust/gui-client/src-tauri/src/controller.rs +++ b/rust/gui-client/src-tauri/src/controller.rs @@ -6,7 +6,7 @@ use crate::{ settings::{self, AdvancedSettings}, updates, uptime, }; -use anyhow::{Context, Result, anyhow}; +use anyhow::{Context, Result, anyhow, bail}; use connlib_model::ResourceView; use firezone_bin_shared::DnsControlMethod; use firezone_logging::FilterReloadHandle; @@ -16,7 +16,13 @@ use futures::{ stream::{self, BoxStream}, }; use secrecy::{ExposeSecret as _, SecretString}; -use std::{collections::BTreeSet, ops::ControlFlow, path::PathBuf, task::Poll, time::Instant}; +use std::{ + collections::BTreeSet, + ops::ControlFlow, + path::PathBuf, + task::Poll, + time::{Duration, Instant}, +}; use tokio::sync::{mpsc, oneshot}; use tokio_stream::wrappers::ReceiverStream; use url::Url; @@ -194,6 +200,10 @@ enum EventloopTick { ), } +#[derive(Debug, thiserror::Error)] +#[error("Failed to receive hello: {0:#}")] +pub struct FailedToReceiveHello(anyhow::Error); + impl Controller { pub(crate) async fn start( ctlr_tx: CtlrTx, @@ -206,9 +216,13 @@ impl Controller { ) -> Result<()> { tracing::debug!("Starting new instance of `Controller`"); - let (ipc_rx, ipc_client) = + let (mut ipc_rx, ipc_client) = ipc::connect(SocketId::Tunnel, ipc::ConnectOptions::default()).await?; + receive_hello(&mut ipc_rx) + .await + .map_err(FailedToReceiveHello)?; + let dns_notifier = new_dns_notifier().await?.boxed(); let network_notifier = new_network_notifier().await?.boxed(); @@ -664,6 +678,7 @@ impl Controller { )?; self.refresh_system_tray_menu(); } + service::ServerMsg::Hello => {} } Ok(ControlFlow::Continue(())) } @@ -914,3 +929,21 @@ async fn new_network_notifier() -> Result>> { Ok(Some(((), worker))) })) } + +async fn receive_hello(ipc_rx: &mut ipc::ClientRead) -> Result<()> { + const TIMEOUT: Duration = Duration::from_secs(5); + + let server_msg = tokio::time::timeout(TIMEOUT, ipc_rx.next()) + .await + .with_context(|| { + format!("Timeout while waiting for message from tunnel service for {TIMEOUT:?}") + })? + .context("No message received from tunnel service")? + .context("Failed to receive message from tunnel service")?; + + if !matches!(server_msg, service::ServerMsg::Hello) { + bail!("Expected `Hello` from tunnel service but got `{server_msg}`") + } + + Ok(()) +} diff --git a/rust/gui-client/src-tauri/src/service.rs b/rust/gui-client/src-tauri/src/service.rs index d886e9f5c..3ba690990 100644 --- a/rust/gui-client/src-tauri/src/service.rs +++ b/rust/gui-client/src-tauri/src/service.rs @@ -67,6 +67,7 @@ pub enum ClientMsg { /// Messages that end up in the GUI, either forwarded from connlib or from the IPC service. #[derive(Debug, serde::Deserialize, serde::Serialize, strum::Display)] pub enum ServerMsg { + Hello, /// The IPC service finished clearing its log dir. ClearedLogs(Result<(), String>), ConnectResult(Result<(), ConnectError>), @@ -150,7 +151,13 @@ async fn ipc_listen( tracing::info!("Caught SIGINT / SIGTERM / Ctrl+C while waiting on the next client."); break; }; - let mut handler = handler?; + let mut handler = match handler { + Ok(handler) => handler, + Err(e) => { + tracing::warn!("Failed to initialise IPC handler: {e:#}"); + continue; + } + }; if let HandlerOk::ServiceTerminating = handler.run(signals).await { break; } @@ -206,12 +213,17 @@ impl<'a> Handler<'a> { "Listening for GUI to connect over IPC..." ); - let (ipc_rx, ipc_tx) = server + let (ipc_rx, mut ipc_tx) = server .next_client_split() .await .context("Failed to wait for incoming IPC connection from a GUI")?; let tun_device = TunDeviceManager::new(ip_packet::MAX_IP_SIZE, 1)?; + ipc_tx + .send(&ServerMsg::Hello) + .await + .context("Failed to greet to new GUI process")?; // Greet the GUI process. If the GUI process doesn't receive this after connecting, it knows that the tunnel service isn't responding. + Ok(Self { dns_controller, ipc_rx,