Skip to content

Commit

Permalink
Change client disconnect/reconnect mechanic and notifications (#367)
Browse files Browse the repository at this point in the history
* change client disconnect/reconnect mechanic and notifications

* Update src-tauri/src/periodic/connection.rs

Co-authored-by: Adam <[email protected]>

* fix frontend

* change message

* change some logs

* fix frontend

* Update GlobalSettingsTab.tsx

---------

Co-authored-by: Adam <[email protected]>
  • Loading branch information
t-aleksander and moubctez authored Jan 8, 2025
1 parent 278d3d8 commit 41a619b
Show file tree
Hide file tree
Showing 11 changed files with 161 additions and 113 deletions.
26 changes: 26 additions & 0 deletions src-tauri/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ pub static LOCATION_UPDATE: &str = "location-update";
pub static APP_VERSION_FETCH: &str = "app-version-fetch";
pub static CONFIG_CHANGED: &str = "config-changed";
pub static DEAD_CONNECTION_DROPPED: &str = "dead-connection-dropped";
pub static DEAD_CONNECTION_RECONNECTED: &str = "dead-connection-reconnected";
pub static APPLICATION_CONFIG_CHANGED: &str = "application-config-changed";

/// Used as payload for [`DEAD_CONNECTION_DROPPED`] event
#[derive(Serialize, Clone, Debug)]
pub struct DeadConnDroppedOut {
pub(crate) name: String,
pub(crate) con_type: ConnectionType,
pub(crate) peer_alive_period: i64,
}

impl DeadConnDroppedOut {
Expand All @@ -35,3 +37,27 @@ impl DeadConnDroppedOut {
}
}
}

/// Used as payload for [`DEAD_CONNECTION_RECONNECTED`] event
#[derive(Serialize, Clone, Debug)]
pub struct DeadConnReconnected {
pub(crate) name: String,
pub(crate) con_type: ConnectionType,
pub(crate) peer_alive_period: i64,
}

impl DeadConnReconnected {
/// Emits [`DEAD_CONNECTION_RECONNECTED`] event with corresponding side effects.
pub(crate) fn emit(self, app_handle: &AppHandle) {
if let Err(err) = Notification::new(&app_handle.config().tauri.bundle.identifier)
.title(format!("{} {} reconnected", self.con_type, self.name))
.body("Connection activity timeout")
.show()
{
warn!("Dead connection reconnected notification not shown. Reason: {err}");
}
if let Err(err) = app_handle.emit_all(DEAD_CONNECTION_RECONNECTED, self) {
error!("Event Dead Connection Reconnected was not emitted. Reason: {err}");
}
}
}
99 changes: 82 additions & 17 deletions src-tauri/src/periodic/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use crate::{
Id,
},
error::Error,
events::DeadConnDroppedOut,
events::{DeadConnDroppedOut, DeadConnReconnected},
ConnectionType,
};

Expand All @@ -34,22 +34,24 @@ async fn reconnect(
con_interface_name: &str,
app_handle: &AppHandle,
con_type: ConnectionType,
peer_alive_period: &TimeDelta,
) {
debug!("Starting attempt to reconnect {con_interface_name} {con_type}({con_id})...");
match disconnect(con_id, con_type, app_handle.clone()).await {
Ok(()) => {
debug!("Connection for {con_type} {con_interface_name}({con_id}) disconnected successfully in path of reconnection.");
let payload = DeadConnReconnected {
name: con_interface_name.to_string(),
con_type,
peer_alive_period: peer_alive_period.num_seconds(),
};
payload.emit(app_handle);
match connect(con_id, con_type, None, app_handle.clone()).await {
Ok(()) => {
info!("Reconnect for {con_type} {con_interface_name} ({con_id}) succeeded.",);
}
Err(err) => {
error!("Reconnect attempt failed, disconnect succeeded but connect failed. Error: {err}");
let payload = DeadConnDroppedOut {
name: con_interface_name.to_string(),
con_type,
};
payload.emit(app_handle);
}
}
}
Expand All @@ -66,6 +68,7 @@ async fn disconnect_dead_connection(
con_interface_name: &str,
app_handle: AppHandle,
con_type: ConnectionType,
peer_alive_period: &TimeDelta,
) {
debug!(
"Attempting to disconnect dead connection for interface {con_interface_name}, {con_type}: {con_id}");
Expand All @@ -75,6 +78,7 @@ async fn disconnect_dead_connection(
let event_payload = DeadConnDroppedOut {
con_type,
name: con_interface_name.to_string(),
peer_alive_period: peer_alive_period.num_seconds(),
};
event_payload.emit(&app_handle);
}
Expand All @@ -91,7 +95,9 @@ pub async fn verify_active_connections(app_handle: AppHandle) -> Result<(), Erro
let pool = &app_state.db;
debug!("Active connections verification started.");

// Both vectors contain IDs.
// Both vectors contain (ID, allow_reconnect) tuples.
// If allow_reconnect is false, the connection will always be dropped without a reconnect attempt.
// Otherwise, the connection will be reconnected if nothing else prevents it (e.g. MFA).
let mut locations_to_disconnect = Vec::new();
let mut tunnels_to_disconnect = Vec::new();

Expand All @@ -101,6 +107,8 @@ pub async fn verify_active_connections(app_handle: AppHandle) -> Result<(), Erro
let connection_count = connections.len();
if connection_count == 0 {
debug!("Connections verification skipped, no active connections found, task will wait for next {CHECK_INTERVAL:?}");
} else {
debug!("Verifying state of {connection_count} active connections. Inactive connections will be disconnected and reconnected if possible.");
}
let peer_alive_period = TimeDelta::seconds(i64::from(
app_state.app_config.lock().unwrap().peer_alive_period,
Expand All @@ -117,15 +125,27 @@ pub async fn verify_active_connections(app_handle: AppHandle) -> Result<(), Erro
latest_stat.collected_at,
peer_alive_period,
) {
debug!("There wasn't any activity for Location {}; considering it being dead.", con.location_id);
locations_to_disconnect.push(con.location_id);
// Check if there was any traffic since the connection was established.
// If not, consider the location dead and disconnect it later without reconnecting.
if latest_stat.collected_at < con.start {
debug!("There wasn't any activity for Location {} since its connection at {}; considering it being dead and possibly broken. \
It will be disconnected without a further automatic reconnect.", con.location_id, con.start);
locations_to_disconnect.push((con.location_id, false));
} else {
debug!("There wasn't any activity for Location {} for the last {}s; considering it being dead.", con.location_id, peer_alive_period.num_seconds());
locations_to_disconnect.push((con.location_id, true));
}
}
}
Ok(None) => {
error!(
debug!(
"LocationStats not found in database for active connection {} {}({})",
con.connection_type, con.interface_name, con.location_id
);
if Utc::now() - con.start.and_utc() > peer_alive_period {
debug!("There wasn't any activity for Location {} since its connection at {}; considering it being dead.", con.location_id, con.start);
locations_to_disconnect.push((con.location_id, false));
}
}
Err(err) => {
warn!("Verification for location {}({}) skipped due to db error. Error: {err}", con.interface_name, con.location_id);
Expand All @@ -140,17 +160,28 @@ pub async fn verify_active_connections(app_handle: AppHandle) -> Result<(), Erro
latest_stat.collected_at,
peer_alive_period,
) {
debug!("There wasn't any activity for Tunnel {}; considering it being dead.", con.location_id);
tunnels_to_disconnect.push(con.location_id);
// Check if there was any traffic since the connection was established.
// If not, consider the location dead and disconnect it later without reconnecting.
if latest_stat.collected_at - con.start < TimeDelta::zero() {
debug!("There wasn't any activity for Tunnel {} since its connection at {}; considering it being dead and possibly broken. \
It will be disconnected without a further automatic reconnect.", con.location_id, con.start);
tunnels_to_disconnect.push((con.location_id, false));
} else {
debug!("There wasn't any activity for Tunnel {} for the last {}s; considering it being dead.", con.location_id, peer_alive_period.num_seconds());
tunnels_to_disconnect.push((con.location_id, true));
}
}
}
Ok(None) => {
warn!(
"TunnelStats not found in database for active connection Tunnel {}({})",
con.interface_name, con.location_id
);
if Utc::now() - con.start.and_utc() > peer_alive_period {
debug!("There wasn't any activity for Location {} since its connection at {}; considering it being dead.", con.location_id, con.start);
tunnels_to_disconnect.push((con.location_id, false));
}
}

Err(err) => {
warn!(
"Verification for tunnel {}({}) skipped due to db error. Error: {err}",
Expand All @@ -166,17 +197,29 @@ pub async fn verify_active_connections(app_handle: AppHandle) -> Result<(), Erro
drop(connections);

// Process locations
for location_id in locations_to_disconnect.drain(..) {
for (location_id, allow_reconnect) in locations_to_disconnect.drain(..) {
match Location::find_by_id(pool, location_id).await {
Ok(Some(location)) => {
if !allow_reconnect {
warn!("Automatic reconnect for location {}({}) is not possible due to lack of activity. Interface will be disconnected.", location.name, location.id);
disconnect_dead_connection(
location_id,
&location.name,
app_handle.clone(),
ConnectionType::Location,
&peer_alive_period,
)
.await;
} else if
// only try to reconnect when location is not protected behind MFA
if location.mfa_enabled {
location.mfa_enabled {
warn!("Automatic reconnect for location {}({}) is not possible due to enabled MFA. Interface will be disconnected.", location.name, location.id);
disconnect_dead_connection(
location_id,
&location.name,
app_handle.clone(),
ConnectionType::Location,
&peer_alive_period,
)
.await;
} else {
Expand All @@ -185,6 +228,7 @@ pub async fn verify_active_connections(app_handle: AppHandle) -> Result<(), Erro
&location.name,
&app_handle,
ConnectionType::Location,
&peer_alive_period,
)
.await;
}
Expand All @@ -200,17 +244,37 @@ pub async fn verify_active_connections(app_handle: AppHandle) -> Result<(), Erro
"DEAD LOCATION",
app_handle.clone(),
ConnectionType::Location,
&peer_alive_period,
)
.await;
}
}
}

// Process tunnels
for tunnel_id in tunnels_to_disconnect.drain(..) {
for (tunnel_id, allow_reconnect) in tunnels_to_disconnect.drain(..) {
match Tunnel::find_by_id(pool, tunnel_id).await {
Ok(Some(tunnel)) => {
reconnect(tunnel.id, &tunnel.name, &app_handle, ConnectionType::Tunnel).await;
if allow_reconnect {
reconnect(
tunnel.id,
&tunnel.name,
&app_handle,
ConnectionType::Tunnel,
&peer_alive_period,
)
.await;
} else {
debug!("Automatic reconnect for location {}({}) is not possible due to lack of activity since the connection start. Interface will be disconnected.", tunnel.name, tunnel.id);
disconnect_dead_connection(
tunnel_id,
"DEAD TUNNEL",
app_handle.clone(),
ConnectionType::Tunnel,
&peer_alive_period,
)
.await;
}
}
Ok(None) => {
// Unlikely due to ON DELETE CASCADE.
Expand All @@ -223,6 +287,7 @@ pub async fn verify_active_connections(app_handle: AppHandle) -> Result<(), Erro
"DEAD TUNNEL",
app_handle.clone(),
ConnectionType::Tunnel,
&peer_alive_period,
)
.await;
}
Expand Down
12 changes: 5 additions & 7 deletions src/i18n/en/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ const en = {
networkError: "There was a network error. Can't reach proxy.",
configChanged:
'Configuration for instance {instance: string} has changed. Disconnect from all locations to apply changes.',
deadConDropped: '{con_type: string} {interface_name: string} disconnected.',
deadConDropped:
'Detected that the {con_type: string} {interface_name: string} has disconnected, trying to reconnect...',
},
},
components: {
Expand All @@ -64,14 +65,11 @@ const en = {
client: {
modals: {
deadConDropped: {
title: '{conType: string} disconnected',
title: '{conType: string} {name: string} disconnected',
tunnel: 'Tunnel',
location: 'Location',
body: {
periodic:
'{conType: string} {instanceName: string} was automatically disconnected because it exceeded the expected time for staying active without receiving confirmation from the server.',
connection: `{conType: string} {name: string} connection was automatically disconnected because it didn't complete the necessary setup in time. This can happen if the connection wasn't fully established`,
},
message:
'The {conType: string} {name: string} has been disconnected, since we have detected that the server is not responding with any traffic for {time: number}s. If this message keeps occurring, please contact your administrator and inform them about this fact.',
controls: {
close: 'Close',
},
Expand Down
Loading

0 comments on commit 41a619b

Please sign in to comment.