Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src-tauri/src/appstate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,13 @@ pub struct AppState {

impl AppState {
#[must_use]
pub fn new(app_handle: &AppHandle) -> Self {
pub fn new(config: AppConfig) -> Self {
AppState {
db: init_db().expect("Failed to initalize database"),
active_connections: Mutex::new(Vec::new()),
client: setup_client().expect("Failed to setup gRPC client"),
log_watchers: std::sync::Mutex::new(HashMap::new()),
app_config: std::sync::Mutex::new(AppConfig::new(app_handle)),
app_config: std::sync::Mutex::new(config),
stat_threads: std::sync::Mutex::new(HashMap::new()),
}
}
Expand Down
20 changes: 10 additions & 10 deletions src-tauri/src/bin/defguard-client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use std::{env, str::FromStr, sync::LazyLock};
#[cfg(target_os = "windows")]
use defguard_client::utils::sync_connections;
use defguard_client::{
app_config::AppConfig,
appstate::AppState,
commands::*,
database::models::{location_stats::LocationStats, tunnel::TunnelStats},
Expand Down Expand Up @@ -102,15 +103,12 @@ async fn main() {
let _ = app.emit_all(SINGLE_INSTANCE, Payload { args: argv, cwd });
}))
.setup(|app| {
{
let state = AppState::new(&app.app_handle());
app.manage(state);
}
let app_state: State<AppState> = app.state();
// prepare config
let config = AppConfig::new(&app.app_handle());

// setup logging
// use config default if deriving from env value fails so that env can override config file
let config_log_level = app_state.app_config.lock().unwrap().log_level;

let config_log_level = config.log_level;
let log_level = match &env::var("DEFGUARD_CLIENT_LOG_LEVEL") {
Ok(env_value) => LevelFilter::from_str(env_value).unwrap_or(config_log_level),
Err(_) => config_log_level,
Expand Down Expand Up @@ -173,6 +171,11 @@ async fn main() {
)
.unwrap();

{
let state = AppState::new(config);
app.manage(state);
}

info!("App setup completed, log level: {log_level}");

Ok(())
Expand Down Expand Up @@ -254,9 +257,6 @@ async fn main() {
}

// run periodic tasks
debug!(
"Starting periodic tasks (config, version polling and active connection verification)..."
);
let periodic_tasks_handle = app_handle.clone();
tauri::async_runtime::spawn(async move {
run_periodic_tasks(&periodic_tasks_handle).await;
Expand Down
21 changes: 14 additions & 7 deletions src-tauri/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -804,14 +804,15 @@ pub async fn delete_instance(instance_id: Id, handle: AppHandle) -> Result<(), E
debug!("Deleting instance with ID {instance_id}");
let app_state = handle.state::<AppState>();
let mut client = app_state.client.clone();
let pool = &app_state.db;
let Some(instance) = Instance::find_by_id(pool, instance_id).await? else {
let mut transaction = app_state.db.begin().await?;

let Some(instance) = Instance::find_by_id(&mut *transaction, instance_id).await? else {
error!("Couldn't delete instance: instance with ID {instance_id} could not be found.");
return Err(Error::NotFound);
};
debug!("The instance that is being deleted has been identified as {instance}");

let instance_locations = Location::find_by_instance_id(pool, instance_id).await?;
let instance_locations = Location::find_by_instance_id(&mut *transaction, instance_id).await?;
if !instance_locations.is_empty() {
debug!(
"Found locations associated with the instance {instance}, closing their connections..."
Expand All @@ -836,7 +837,10 @@ pub async fn delete_instance(instance_id: Id, handle: AppHandle) -> Result<(), E
info!("The connection to location {location} has been closed, as it was associated with the instance {instance} that is being deleted.");
}
}
instance.delete(pool).await?;
instance.delete(&mut *transaction).await?;

transaction.commit().await?;

reload_tray_menu(&handle).await;

handle.emit_all(INSTANCE_UPDATE, ())?;
Expand Down Expand Up @@ -948,8 +952,9 @@ pub async fn delete_tunnel(tunnel_id: Id, handle: AppHandle) -> Result<(), Error
debug!("Deleting tunnel with ID {tunnel_id}");
let app_state = handle.state::<AppState>();
let mut client = app_state.client.clone();
let pool = &app_state.db;
let Some(tunnel) = Tunnel::find_by_id(pool, tunnel_id).await? else {
let mut transaction = app_state.db.begin().await?;

let Some(tunnel) = Tunnel::find_by_id(&mut *transaction, tunnel_id).await? else {
error!("The tunnel to delete with ID {tunnel_id} could not be found, cannot delete.");
return Err(Error::NotFound);
};
Expand Down Expand Up @@ -1015,7 +1020,9 @@ pub async fn delete_tunnel(tunnel_id: Id, handle: AppHandle) -> Result<(), Error
);
}
}
tunnel.delete(pool).await?;
tunnel.delete(&mut *transaction).await?;

transaction.commit().await?;

info!("Successfully deleted tunnel {tunnel}");
Ok(())
Expand Down
10 changes: 8 additions & 2 deletions src-tauri/src/database/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ pub mod models;
use std::{
env,
fs::{create_dir_all, File},
str::FromStr,
};

use sqlx::sqlite::SqliteConnectOptions;

use crate::{app_data_dir, error::Error};

const DB_NAME: &str = "defguard.db";
Expand All @@ -14,8 +17,11 @@ pub(crate) type DbPool = sqlx::SqlitePool;
/// Initializes the database
pub fn init_db() -> Result<DbPool, Error> {
let db_url = prepare_db_url()?;
debug!("Connecting to database: {db_url}");
let pool = DbPool::connect_lazy(&db_url)?;
let opts = SqliteConnectOptions::from_str(&db_url)?
.auto_vacuum(sqlx::sqlite::SqliteAutoVacuum::Incremental)
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal);
debug!("Connecting to database: {db_url} with options: {opts:?}");
let pool = DbPool::connect_lazy_with(opts);

Ok(pool)
}
Expand Down
12 changes: 10 additions & 2 deletions src-tauri/src/periodic/mod.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
use connection::verify_active_connections;
use self::{
connection::verify_active_connections, purge_stats::purge_stats, version::poll_version,
};
use tauri::AppHandle;
use tokio::select;
use version::poll_version;

use crate::enterprise::periodic::config::poll_config;

pub mod connection;
pub mod purge_stats;
pub mod version;

/// Runs all the client periodic tasks, finishing when any of them returns.
pub async fn run_periodic_tasks(app_handle: &AppHandle) {
debug!(
"Starting periodic tasks (config, version polling, stats purging and active connection verification)..."
);
select! {
() = poll_version(app_handle.clone()) => {
error!("Version polling task has stopped unexpectedly");
Expand All @@ -20,5 +25,8 @@ pub async fn run_periodic_tasks(app_handle: &AppHandle) {
() = verify_active_connections(app_handle.clone()) => {
error!("Active connection verification task has stopped unexpectedly");
}
() = purge_stats(app_handle.clone()) => {
error!("Stats purging task has stopped unexpectedly");
}
};
}
58 changes: 58 additions & 0 deletions src-tauri/src/periodic/purge_stats.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use std::time::Duration;

use tauri::{AppHandle, Manager, State};
use tokio::time::interval;

use crate::{
appstate::AppState,
database::models::{location_stats::LocationStats, tunnel::TunnelStats},
};

// 12 hours
const PURGE_INTERVAL: Duration = Duration::from_secs(12 * 60 * 60);

/// Periodically purges location and tunnel stats.
///
/// By design this happens infrequently to not overload the DB connection.
/// There is a separate purge done at client startup.
pub async fn purge_stats(handle: AppHandle) {
debug!("Starting the stats purging loop...");
let app_state: State<AppState> = handle.state();
let pool = &app_state.db;

let mut interval = interval(PURGE_INTERVAL);

loop {
// wait for next iteration
interval.tick().await;

// begin transaction
let Ok(mut transaction) = pool.begin().await else {
error!(
"Failed to begin database transaction for stats purging, retrying in {}h",
PURGE_INTERVAL.as_secs() / 3600
);
continue;
};

debug!("Purging old stats from the database...");
if let Err(err) = LocationStats::purge(&mut *transaction).await {
error!("Failed to purge location stats: {err}");
} else {
debug!("Old location stats have been purged successfully.");
}
if let Err(err) = TunnelStats::purge(&mut *transaction).await {
error!("Failed to purge tunnel stats: {err}");
} else {
debug!("Old tunnel stats have been purged successfully.");
}

// commit transaction
if let Err(err) = transaction.commit().await {
error!(
"Failed to commit database transaction for stats purging: {err}. Retrying in {}h",
PURGE_INTERVAL.as_secs() / 3600
)
}
}
}
84 changes: 51 additions & 33 deletions src-tauri/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ use crate::{
models::{
connection::{ActiveConnection, Connection},
location::Location,
location_stats::{peer_to_location_stats, LocationStats},
tunnel::{peer_to_tunnel_stats, Tunnel, TunnelConnection, TunnelStats},
location_stats::peer_to_location_stats,
tunnel::{peer_to_tunnel_stats, Tunnel, TunnelConnection},
wireguard_keys::WireguardKeys,
Id,
},
Expand Down Expand Up @@ -210,29 +210,37 @@ pub(crate) async fn stats_handler(
match stream.message().await {
Ok(Some(interface_data)) => {
debug!("Received new network usage statistics for interface {interface_name}.");
if let Err(err) = LocationStats::purge(&pool).await {
error!("Failed to purge location stats: {err}");
}
if let Err(err) = TunnelStats::purge(&pool).await {
error!("Failed to purge tunnel stats: {err}");
}

trace!("Received interface data: {interface_data:?}");

// begin transaction
let mut transaction = match pool.begin().await {
Ok(transactions) => transactions,
Err(err) => {
error!(
"Failed to begin database transaction for saving location/tunnel stats: {err}",
);
continue;
}
};

let peers: Vec<Peer> = interface_data.peers.into_iter().map(Into::into).collect();
for peer in peers {
if connection_type.eq(&ConnectionType::Location) {
let location_stats =
match peer_to_location_stats(&peer, interface_data.listen_port, &pool)
.await
{
Ok(stats) => stats,
Err(err) => {
error!("Failed to convert peer data to location stats: {err}");
continue;
}
};
let location_stats = match peer_to_location_stats(
&peer,
interface_data.listen_port,
&mut *transaction,
)
.await
{
Ok(stats) => stats,
Err(err) => {
error!("Failed to convert peer data to location stats: {err}");
continue;
}
};
let location_name = location_stats
.get_name(&pool)
.get_name(&mut *transaction)
.await
.unwrap_or("UNKNOWN".to_string());

Expand All @@ -241,7 +249,7 @@ pub(crate) async fn stats_handler(
(interface {interface_name})."
);
trace!("Stats: {location_stats:?}");
match location_stats.save(&pool).await {
match location_stats.save(&mut *transaction).await {
Ok(_) => {
debug!("Saved network usage stats for location {location_name}");
}
Expand All @@ -253,25 +261,28 @@ pub(crate) async fn stats_handler(
}
}
} else {
let tunnel_stats =
match peer_to_tunnel_stats(&peer, interface_data.listen_port, &pool)
.await
{
Ok(stats) => stats,
Err(err) => {
error!("Failed to convert peer data to tunnel stats: {err}");
continue;
}
};
let tunnel_stats = match peer_to_tunnel_stats(
&peer,
interface_data.listen_port,
&mut *transaction,
)
.await
{
Ok(stats) => stats,
Err(err) => {
error!("Failed to convert peer data to tunnel stats: {err}");
continue;
}
};
let tunnel_name = tunnel_stats
.get_name(&pool)
.get_name(&mut *transaction)
.await
.unwrap_or("UNKNOWN".to_string());
debug!(
"Saving network usage stats related to tunnel {tunnel_name} \
(interface {interface_name}): {tunnel_stats:?}"
);
match tunnel_stats.save(&pool).await {
match tunnel_stats.save(&mut *transaction).await {
Ok(_) => {
debug!("Saved stats for tunnel {tunnel_name}");
}
Expand All @@ -281,6 +292,13 @@ pub(crate) async fn stats_handler(
}
}
}

// commit transaction
if let Err(err) = transaction.commit().await {
error!(
"Failed to commit database transaction for saving location/tunnel stats: {err}",
)
}
}
Ok(None) => {
debug!("gRPC stream to the defguard-service managing connections has been closed");
Expand Down