mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 10:18:54 +00:00
chore(gui-client): greet GUI instance upon connect (#9151)
The tunnel service of the GUI client can only handle one process at a time. The OS however will happily connect multiple clients to the socket / pipe. They will simply idle until the previous process disconnects. To avoid this situation, we introduce a `Hello` message from the tunnel service to the GUI client. If the GUI client doesn't receive this message within 5s, it considers the tunnel service be not responsive. If our duplicate instance detection works as intended, users are not expected to hit this.
This commit is contained in:
@@ -163,6 +163,13 @@ fn run_gui(config: RunConfig) -> Result<()> {
|
||||
return Err(anyhow);
|
||||
}
|
||||
|
||||
if anyhow.root_cause().is::<controller::FailedToReceiveHello>() {
|
||||
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.",
|
||||
)?;
|
||||
|
||||
@@ -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<I: GuiIntegration> Controller<I> {
|
||||
pub(crate) async fn start(
|
||||
ctlr_tx: CtlrTx,
|
||||
@@ -206,9 +216,13 @@ impl<I: GuiIntegration> Controller<I> {
|
||||
) -> 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<I: GuiIntegration> Controller<I> {
|
||||
)?;
|
||||
self.refresh_system_tray_menu();
|
||||
}
|
||||
service::ServerMsg::Hello => {}
|
||||
}
|
||||
Ok(ControlFlow::Continue(()))
|
||||
}
|
||||
@@ -914,3 +929,21 @@ async fn new_network_notifier() -> Result<impl Stream<Item = Result<()>>> {
|
||||
Ok(Some(((), worker)))
|
||||
}))
|
||||
}
|
||||
|
||||
async fn receive_hello(ipc_rx: &mut ipc::ClientRead<service::ServerMsg>) -> 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(())
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user