mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 10:18:54 +00:00
fix(gui-client/windows): delete IPC service logs when user clicks "clear logs" (#6280)
Closes #5453 Tested once on the Windows aarch64 VM. Should always leave 4 files behind, a `.log` and a `.jsonl` for the GUI and for the IPC service. The "log directory" is a bit of a lie since it's consistently 2 directories on both platforms now. ```[tasklist] - [x] Update changelog - [x] Make a note to remove the known issue from the website when the next release is cut after this PR merges ```
This commit is contained in:
6
rust/Cargo.lock
generated
6
rust/Cargo.lock
generated
@@ -5973,14 +5973,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tauri-build"
|
||||
version = "1.5.3"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0c6ec7a5c3296330c7818478948b422967ce4649094696c985f61d50076d29c"
|
||||
checksum = "e9914a4715e0b75d9f387a285c7e26b5bbfeb1249ad9f842675a82481565c532"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"cargo_toml",
|
||||
"dirs-next",
|
||||
"heck 0.5.0",
|
||||
"heck 0.4.1",
|
||||
"json-patch",
|
||||
"semver",
|
||||
"serde",
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::client::{
|
||||
};
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use firezone_bin_shared::{new_dns_notifier, new_network_notifier};
|
||||
use firezone_headless_client::IpcServerMsg;
|
||||
use firezone_headless_client::{IpcClientMsg, IpcServerMsg};
|
||||
use secrecy::{ExposeSecret, SecretString};
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
@@ -301,11 +301,16 @@ async fn smoke_test(ctlr_tx: CtlrTx) -> Result<()> {
|
||||
stem,
|
||||
})
|
||||
.await
|
||||
.context("Failed to send ExportLogs request")?;
|
||||
.context("Failed to send `ExportLogs` request")?;
|
||||
let (tx, rx) = oneshot::channel();
|
||||
ctlr_tx
|
||||
.send(ControllerRequest::ClearLogs)
|
||||
.send(ControllerRequest::ClearLogs(tx))
|
||||
.await
|
||||
.context("Failed to send ClearLogs request")?;
|
||||
.context("Failed to send `ClearLogs` request")?;
|
||||
rx.await
|
||||
.context("Failed to await `ClearLogs` result")?
|
||||
.map_err(|s| anyhow!(s))
|
||||
.context("`ClearLogs` failed")?;
|
||||
|
||||
// Tray icon stress test
|
||||
let num_icon_cycles = 100;
|
||||
@@ -420,8 +425,8 @@ fn handle_system_tray_event(app: &tauri::AppHandle, event: TrayMenuEvent) -> Res
|
||||
pub(crate) enum ControllerRequest {
|
||||
/// The GUI wants us to use these settings in-memory, they've already been saved to disk
|
||||
ApplySettings(AdvancedSettings),
|
||||
/// Only used for smoke tests
|
||||
ClearLogs,
|
||||
/// Clear the GUI's logs and await the IPC service to clear its logs
|
||||
ClearLogs(oneshot::Sender<Result<(), String>>),
|
||||
/// The same as the arguments to `client::logging::export_logs_to`
|
||||
ExportLogs {
|
||||
path: PathBuf,
|
||||
@@ -473,6 +478,7 @@ struct Controller {
|
||||
app: tauri::AppHandle,
|
||||
// Sign-in state with the portal / deep links
|
||||
auth: client::auth::Auth,
|
||||
clear_logs_callback: Option<oneshot::Sender<Result<(), String>>>,
|
||||
ctlr_tx: CtlrTx,
|
||||
ipc_client: ipc::Client,
|
||||
log_filter_reloader: logging::Reloader,
|
||||
@@ -534,9 +540,16 @@ impl Controller {
|
||||
// Refresh the menu in case the favorites were reset.
|
||||
self.refresh_system_tray_menu()?;
|
||||
}
|
||||
Req::ClearLogs => logging::clear_logs_inner()
|
||||
.await
|
||||
.context("Failed to clear logs")?,
|
||||
Req::ClearLogs(completion_tx) => {
|
||||
if self.clear_logs_callback.is_some() {
|
||||
tracing::error!("Can't clear logs, we're already waiting on another log-clearing operation");
|
||||
}
|
||||
if let Err(error) = logging::clear_gui_logs().await {
|
||||
tracing::error!(?error, "Failed to clear GUI logs");
|
||||
}
|
||||
self.ipc_client.send_msg(&IpcClientMsg::ClearLogs).await?;
|
||||
self.clear_logs_callback = Some(completion_tx);
|
||||
}
|
||||
Req::ExportLogs { path, stem } => logging::export_logs_to(path, stem)
|
||||
.await
|
||||
.context("Failed to export logs to zip")?,
|
||||
@@ -651,6 +664,15 @@ impl Controller {
|
||||
|
||||
async fn handle_ipc(&mut self, msg: IpcServerMsg) -> Result<(), Error> {
|
||||
match msg {
|
||||
IpcServerMsg::ClearedLogs(result) => {
|
||||
let Some(tx) = self.clear_logs_callback.take() else {
|
||||
return Err(Error::Other(anyhow!("Can't handle `IpcClearedLogs` when there's no callback waiting for a `ClearLogs` result")));
|
||||
};
|
||||
tx.send(result).map_err(|_| {
|
||||
Error::Other(anyhow!("Couldn't send `ClearLogs` result to Tauri task"))
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
IpcServerMsg::OnDisconnect {
|
||||
error_msg,
|
||||
is_authentication_error,
|
||||
@@ -793,6 +815,7 @@ async fn run_controller(
|
||||
advanced_settings,
|
||||
app: app.clone(),
|
||||
auth: client::auth::Auth::new()?,
|
||||
clear_logs_callback: None,
|
||||
ctlr_tx,
|
||||
ipc_client,
|
||||
log_filter_reloader,
|
||||
|
||||
@@ -5,13 +5,11 @@ use anyhow::{bail, Context, Result};
|
||||
use firezone_headless_client::known_dirs;
|
||||
use serde::Serialize;
|
||||
use std::{
|
||||
ffi::OsStr,
|
||||
fs,
|
||||
io::{self, ErrorKind::NotFound},
|
||||
path::{Path, PathBuf},
|
||||
result::Result as StdResult,
|
||||
};
|
||||
use tokio::task::spawn_blocking;
|
||||
use tokio::{sync::oneshot, task::spawn_blocking};
|
||||
use tracing::subscriber::set_global_default;
|
||||
use tracing_log::LogTracer;
|
||||
use tracing_subscriber::{fmt, layer::SubscriberExt, reload, EnvFilter, Layer, Registry};
|
||||
@@ -75,18 +73,22 @@ pub(crate) fn setup(directives: &str) -> Result<Handles> {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) async fn clear_logs() -> StdResult<(), String> {
|
||||
if let Err(error) = clear_logs_inner().await {
|
||||
// Log the error ourselves since Tauri will only log it to the JS console
|
||||
tracing::error!(?error, "Error while clearing logs");
|
||||
Err(error.to_string())
|
||||
} else {
|
||||
Ok(())
|
||||
pub(crate) async fn clear_logs(managed: tauri::State<'_, Managed>) -> Result<(), String> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
if let Err(error) = managed.ctlr_tx.send(ControllerRequest::ClearLogs(tx)).await {
|
||||
// Tauri will only log errors to the JS console for us, so log this ourselves.
|
||||
tracing::error!(?error, "Error while asking `Controller` to clear logs");
|
||||
return Err(error.to_string());
|
||||
}
|
||||
if let Err(error) = rx.await {
|
||||
tracing::error!(?error, "Error while awaiting log-clearing operation");
|
||||
return Err(error.to_string());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) async fn export_logs(managed: tauri::State<'_, Managed>) -> StdResult<(), String> {
|
||||
pub(crate) async fn export_logs(managed: tauri::State<'_, Managed>) -> Result<(), String> {
|
||||
show_export_dialog(managed.ctlr_tx.clone()).map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
@@ -97,7 +99,7 @@ pub(crate) struct FileCount {
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub(crate) async fn count_logs() -> StdResult<FileCount, String> {
|
||||
pub(crate) async fn count_logs() -> Result<FileCount, String> {
|
||||
count_logs_inner().await.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
@@ -108,75 +110,9 @@ pub(crate) async fn count_logs() -> StdResult<FileCount, String> {
|
||||
///
|
||||
/// If we get an error while removing a file, we still try to remove all other
|
||||
/// files, then we return the most recent error.
|
||||
pub(crate) async fn clear_logs_inner() -> Result<()> {
|
||||
let mut result = Ok(());
|
||||
|
||||
for log_path in log_paths()?.into_iter().map(|x| x.src) {
|
||||
let mut dir = match tokio::fs::read_dir(log_path).await {
|
||||
Ok(x) => x,
|
||||
Err(error) => {
|
||||
if matches!(error.kind(), NotFound) {
|
||||
// In smoke tests, the IPC service runs in debug mode, so it won't write any logs to disk. If the IPC service's log dir doesn't exist, we shouldn't crash, it's correct to simply not delete the non-existent files
|
||||
return Ok(());
|
||||
}
|
||||
// But any other error like permissions errors, should bubble.
|
||||
return Err(error.into());
|
||||
}
|
||||
};
|
||||
let mut paths = vec![];
|
||||
while let Some(entry) = dir.next_entry().await? {
|
||||
paths.push(entry.path());
|
||||
}
|
||||
|
||||
let to_delete = choose_logs_to_delete(&paths);
|
||||
for path in &to_delete {
|
||||
if let Err(e) = tokio::fs::remove_file(path).await {
|
||||
result = Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result?)
|
||||
}
|
||||
|
||||
fn choose_logs_to_delete(paths: &[PathBuf]) -> Vec<&Path> {
|
||||
let mut most_recent_stem = None;
|
||||
for path in paths {
|
||||
if path.extension() != Some(OsStr::new("log")) {
|
||||
continue;
|
||||
}
|
||||
let Some(stem) = path.file_stem() else {
|
||||
continue;
|
||||
};
|
||||
match most_recent_stem {
|
||||
None => most_recent_stem = Some(stem),
|
||||
Some(most_recent) if stem > most_recent => most_recent_stem = Some(stem),
|
||||
Some(_) => {}
|
||||
}
|
||||
}
|
||||
let Some(most_recent_stem) = most_recent_stem else {
|
||||
tracing::warn!(
|
||||
"Nothing to delete, should be impossible since both processes always write logs"
|
||||
);
|
||||
return vec![];
|
||||
};
|
||||
let Some(most_recent_stem) = most_recent_stem.to_str() else {
|
||||
tracing::warn!("Most recent log file does not have a UTF-8 path");
|
||||
return vec![];
|
||||
};
|
||||
|
||||
paths
|
||||
.iter()
|
||||
.filter_map(|path| {
|
||||
// Don't delete files if we can't parse their stems as UTF-8.
|
||||
let stem = path.file_stem()?.to_str()?;
|
||||
if !stem.starts_with("connlib.") {
|
||||
// Delete any non-log files like crash dumps.
|
||||
return Some(path.as_path());
|
||||
}
|
||||
(stem < most_recent_stem).then_some(path.as_path())
|
||||
})
|
||||
.collect()
|
||||
pub(crate) async fn clear_gui_logs() -> Result<()> {
|
||||
firezone_headless_client::clear_logs(&known_dirs::logs().context("Can't compute GUI log dir")?)
|
||||
.await
|
||||
}
|
||||
|
||||
/// Pops up the "Save File" dialog
|
||||
@@ -297,55 +233,12 @@ async fn count_one_dir(path: &Path) -> Result<FileCount> {
|
||||
fn log_paths() -> Result<Vec<LogPath>> {
|
||||
Ok(vec![
|
||||
LogPath {
|
||||
src: firezone_headless_client::known_dirs::ipc_service_logs()
|
||||
.context("Can't compute IPC service logs dir")?,
|
||||
src: known_dirs::ipc_service_logs().context("Can't compute IPC service logs dir")?,
|
||||
dst: PathBuf::from("connlib"),
|
||||
},
|
||||
LogPath {
|
||||
src: known_dirs::logs().context("Can't compute app log dir")?,
|
||||
src: known_dirs::logs().context("Can't compute GUI log dir")?,
|
||||
dst: PathBuf::from("app"),
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
fn clear_logs_logic() {
|
||||
// These are out of order just to make sure it works anyway
|
||||
let paths: Vec<_> = [
|
||||
"connlib.2024-08-05-19-41-46.jsonl",
|
||||
"connlib.2024-08-05-19-41-46.log",
|
||||
"connlib.2024-08-07-14-17-56.jsonl",
|
||||
"connlib.2024-08-07-14-17-56.log",
|
||||
"connlib.2024-08-06-14-21-13.jsonl",
|
||||
"connlib.2024-08-06-14-21-13.log",
|
||||
"connlib.2024-08-06-14-51-19.jsonl",
|
||||
"connlib.2024-08-06-14-51-19.log",
|
||||
"crash.2024-07-22-21-16-20.dmp",
|
||||
"last_crash.dmp",
|
||||
]
|
||||
.into_iter()
|
||||
.map(|x| Path::new("/bogus").join(x))
|
||||
.collect();
|
||||
let to_delete = super::choose_logs_to_delete(&paths);
|
||||
assert_eq!(
|
||||
to_delete,
|
||||
[
|
||||
"/bogus/connlib.2024-08-05-19-41-46.jsonl",
|
||||
"/bogus/connlib.2024-08-05-19-41-46.log",
|
||||
"/bogus/connlib.2024-08-06-14-21-13.jsonl",
|
||||
"/bogus/connlib.2024-08-06-14-21-13.log",
|
||||
"/bogus/connlib.2024-08-06-14-51-19.jsonl",
|
||||
"/bogus/connlib.2024-08-06-14-51-19.log",
|
||||
"/bogus/crash.2024-07-22-21-16-20.dmp",
|
||||
"/bogus/last_crash.dmp",
|
||||
]
|
||||
.into_iter()
|
||||
.map(Path::new)
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
118
rust/headless-client/src/clear_logs.rs
Normal file
118
rust/headless-client/src/clear_logs.rs
Normal file
@@ -0,0 +1,118 @@
|
||||
use anyhow::{Context, Result};
|
||||
use std::{
|
||||
ffi::OsStr,
|
||||
io::ErrorKind::NotFound,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
/// Deletes all `.log` and `.jsonl` files in `path` except the most recent
|
||||
pub async fn clear_logs(path: &Path) -> Result<()> {
|
||||
let mut dir = match tokio::fs::read_dir(path).await {
|
||||
Ok(x) => x,
|
||||
Err(error) => {
|
||||
if matches!(error.kind(), NotFound) {
|
||||
// In smoke tests, the IPC service runs in debug mode, so it won't write any logs to disk. If the IPC service's log dir doesn't exist, we shouldn't crash, it's correct to simply not delete the non-existent files
|
||||
return Ok(());
|
||||
}
|
||||
// But any other error like permissions errors, should bubble.
|
||||
return Err(error.into());
|
||||
}
|
||||
};
|
||||
let mut paths = vec![];
|
||||
while let Some(entry) = dir.next_entry().await? {
|
||||
paths.push(entry.path());
|
||||
}
|
||||
|
||||
// If we can't delete some files due to permission errors, just keep going
|
||||
// and delete as much as we can, then return the most recent error
|
||||
let mut result = Ok(());
|
||||
let to_delete = choose_logs_to_delete(&paths);
|
||||
for path in &to_delete {
|
||||
if let Err(e) = tokio::fs::remove_file(path).await {
|
||||
result = Err(e);
|
||||
}
|
||||
}
|
||||
result.context("Failed to delete at least one file")
|
||||
}
|
||||
|
||||
fn choose_logs_to_delete(paths: &[PathBuf]) -> Vec<&Path> {
|
||||
let mut most_recent_stem = None;
|
||||
for path in paths {
|
||||
if path.extension() != Some(OsStr::new("log")) {
|
||||
continue;
|
||||
}
|
||||
let Some(stem) = path.file_stem() else {
|
||||
continue;
|
||||
};
|
||||
match most_recent_stem {
|
||||
None => most_recent_stem = Some(stem),
|
||||
Some(most_recent) if stem > most_recent => most_recent_stem = Some(stem),
|
||||
Some(_) => {}
|
||||
}
|
||||
}
|
||||
let Some(most_recent_stem) = most_recent_stem else {
|
||||
tracing::warn!(
|
||||
"Nothing to delete, should be impossible since both processes always write logs"
|
||||
);
|
||||
return vec![];
|
||||
};
|
||||
let Some(most_recent_stem) = most_recent_stem.to_str() else {
|
||||
tracing::warn!("Most recent log file does not have a UTF-8 path");
|
||||
return vec![];
|
||||
};
|
||||
|
||||
paths
|
||||
.iter()
|
||||
.filter_map(|path| {
|
||||
// Don't delete files if we can't parse their stems as UTF-8.
|
||||
let stem = path.file_stem()?.to_str()?;
|
||||
if !stem.starts_with("connlib.") {
|
||||
// Delete any non-log files like crash dumps.
|
||||
return Some(path.as_path());
|
||||
}
|
||||
(stem < most_recent_stem).then_some(path.as_path())
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
fn clear_logs_logic() {
|
||||
// These are out of order just to make sure it works anyway
|
||||
let paths: Vec<_> = [
|
||||
"connlib.2024-08-05-19-41-46.jsonl",
|
||||
"connlib.2024-08-05-19-41-46.log",
|
||||
"connlib.2024-08-07-14-17-56.jsonl",
|
||||
"connlib.2024-08-07-14-17-56.log",
|
||||
"connlib.2024-08-06-14-21-13.jsonl",
|
||||
"connlib.2024-08-06-14-21-13.log",
|
||||
"connlib.2024-08-06-14-51-19.jsonl",
|
||||
"connlib.2024-08-06-14-51-19.log",
|
||||
"crash.2024-07-22-21-16-20.dmp",
|
||||
"last_crash.dmp",
|
||||
]
|
||||
.into_iter()
|
||||
.map(|x| Path::new("/bogus").join(x))
|
||||
.collect();
|
||||
let to_delete = super::choose_logs_to_delete(&paths);
|
||||
assert_eq!(
|
||||
to_delete,
|
||||
[
|
||||
"/bogus/connlib.2024-08-05-19-41-46.jsonl",
|
||||
"/bogus/connlib.2024-08-05-19-41-46.log",
|
||||
"/bogus/connlib.2024-08-06-14-21-13.jsonl",
|
||||
"/bogus/connlib.2024-08-06-14-21-13.log",
|
||||
"/bogus/connlib.2024-08-06-14-51-19.jsonl",
|
||||
"/bogus/connlib.2024-08-06-14-51-19.log",
|
||||
"/bogus/crash.2024-07-22-21-16-20.dmp",
|
||||
"/bogus/last_crash.dmp",
|
||||
]
|
||||
.into_iter()
|
||||
.map(Path::new)
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
device_id, dns_control::DnsController, known_dirs, signals, CallbackHandler, CliCommon,
|
||||
InternalServerMsg, IpcServerMsg,
|
||||
ConnlibMsg, IpcServerMsg,
|
||||
};
|
||||
use anyhow::{Context as _, Result};
|
||||
use clap::Parser;
|
||||
@@ -71,6 +71,7 @@ impl Default for Cmd {
|
||||
|
||||
#[derive(Debug, PartialEq, serde::Deserialize, serde::Serialize)]
|
||||
pub enum ClientMsg {
|
||||
ClearLogs,
|
||||
Connect { api_url: String, token: String },
|
||||
Disconnect,
|
||||
Reset,
|
||||
@@ -185,7 +186,7 @@ async fn ipc_listen(
|
||||
/// Handles one IPC client
|
||||
struct Handler<'a> {
|
||||
callback_handler: CallbackHandler,
|
||||
cb_rx: mpsc::Receiver<InternalServerMsg>,
|
||||
cb_rx: mpsc::Receiver<ConnlibMsg>,
|
||||
connlib: Option<connlib_client_shared::Session>,
|
||||
dns_controller: &'a mut DnsController,
|
||||
ipc_rx: ipc::ServerRead,
|
||||
@@ -195,7 +196,7 @@ struct Handler<'a> {
|
||||
}
|
||||
|
||||
enum Event {
|
||||
Callback(InternalServerMsg),
|
||||
Callback(ConnlibMsg),
|
||||
CallbackChannelClosed,
|
||||
Ipc(ClientMsg),
|
||||
IpcDisconnected,
|
||||
@@ -253,7 +254,7 @@ impl<'a> Handler<'a> {
|
||||
break HandlerOk::Err;
|
||||
}
|
||||
Event::Ipc(msg) => {
|
||||
if let Err(error) = self.handle_ipc_msg(msg) {
|
||||
if let Err(error) = self.handle_ipc_msg(msg).await {
|
||||
tracing::error!(?error, "Error while handling IPC message from client");
|
||||
continue;
|
||||
}
|
||||
@@ -307,36 +308,55 @@ impl<'a> Handler<'a> {
|
||||
Poll::Pending
|
||||
}
|
||||
|
||||
async fn handle_connlib_cb(&mut self, msg: InternalServerMsg) -> Result<()> {
|
||||
async fn handle_connlib_cb(&mut self, msg: ConnlibMsg) -> Result<()> {
|
||||
match msg {
|
||||
InternalServerMsg::Ipc(msg) => {
|
||||
// The first `OnUpdateResources` marks when connlib is fully initialized
|
||||
if let IpcServerMsg::OnUpdateResources(_) = &msg {
|
||||
if let Some(instant) = self.last_connlib_start_instant.take() {
|
||||
tracing::info!(elapsed = ?instant.elapsed(), "Tunnel ready");
|
||||
}
|
||||
|
||||
// On every resources update, flush DNS to mitigate <https://github.com/firezone/firezone/issues/5052>
|
||||
self.dns_controller.flush()?;
|
||||
}
|
||||
self.ipc_tx
|
||||
.send(&msg)
|
||||
.await
|
||||
.context("Error while sending IPC message")?
|
||||
}
|
||||
InternalServerMsg::OnSetInterfaceConfig { ipv4, ipv6, dns } => {
|
||||
ConnlibMsg::OnDisconnect {
|
||||
error_msg,
|
||||
is_authentication_error,
|
||||
} => self
|
||||
.ipc_tx
|
||||
.send(&IpcServerMsg::OnDisconnect {
|
||||
error_msg,
|
||||
is_authentication_error,
|
||||
})
|
||||
.await
|
||||
.context("Error while sending IPC message `OnDisconnect`")?,
|
||||
ConnlibMsg::OnSetInterfaceConfig { ipv4, ipv6, dns } => {
|
||||
self.tun_device.set_ips(ipv4, ipv6).await?;
|
||||
self.dns_controller.set_dns(dns).await?;
|
||||
if let Some(instant) = self.last_connlib_start_instant.take() {
|
||||
tracing::info!(elapsed = ?instant.elapsed(), "Tunnel ready");
|
||||
}
|
||||
}
|
||||
InternalServerMsg::OnUpdateRoutes { ipv4, ipv6 } => {
|
||||
ConnlibMsg::OnUpdateResources(resources) => {
|
||||
// On every resources update, flush DNS to mitigate <https://github.com/firezone/firezone/issues/5052>
|
||||
self.dns_controller.flush()?;
|
||||
self.ipc_tx
|
||||
.send(&IpcServerMsg::OnUpdateResources(resources))
|
||||
.await
|
||||
.context("Error while sending IPC message `OnUpdateResources`")?;
|
||||
}
|
||||
ConnlibMsg::OnUpdateRoutes { ipv4, ipv6 } => {
|
||||
self.tun_device.set_routes(ipv4, ipv6).await?
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_ipc_msg(&mut self, msg: ClientMsg) -> Result<()> {
|
||||
async fn handle_ipc_msg(&mut self, msg: ClientMsg) -> Result<()> {
|
||||
match msg {
|
||||
ClientMsg::ClearLogs => {
|
||||
let result = crate::clear_logs(
|
||||
&crate::known_dirs::ipc_service_logs().context("Can't compute logs dir")?,
|
||||
)
|
||||
.await;
|
||||
self.ipc_tx
|
||||
.send(&IpcServerMsg::ClearedLogs(
|
||||
result.map_err(|e| e.to_string()),
|
||||
))
|
||||
.await
|
||||
.context("Error while sending IPC message")?
|
||||
}
|
||||
ClientMsg::Connect { api_url, token } => {
|
||||
let token = secrecy::SecretString::from(token);
|
||||
// There isn't an airtight way to implement a "disconnect and reconnect"
|
||||
|
||||
@@ -169,9 +169,8 @@ impl platform::Server {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{platform::Server, ServiceId};
|
||||
use crate::{IpcClientMsg, IpcServerMsg};
|
||||
use anyhow::{bail, ensure, Context as _, Result};
|
||||
use super::{platform::Server, *};
|
||||
use anyhow::{bail, ensure, Result};
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use std::time::Duration;
|
||||
use tokio::{task::JoinHandle, time::timeout};
|
||||
|
||||
@@ -20,6 +20,7 @@ use tokio::sync::mpsc;
|
||||
use tracing::subscriber::set_global_default;
|
||||
use tracing_subscriber::{fmt, layer::SubscriberExt as _, EnvFilter, Layer as _, Registry};
|
||||
|
||||
mod clear_logs;
|
||||
/// Generate a persistent device ID, stores it to disk, and reads it back.
|
||||
pub mod device_id;
|
||||
// Pub because the GUI reads the system resolvers
|
||||
@@ -30,9 +31,10 @@ pub mod known_dirs;
|
||||
pub mod signals;
|
||||
pub mod uptime;
|
||||
|
||||
pub use clear_logs::clear_logs;
|
||||
pub use dns_control::DnsController;
|
||||
pub use ipc_service::{ipc, run_only_ipc_service, ClientMsg as IpcClientMsg};
|
||||
|
||||
pub use dns_control::DnsController;
|
||||
use ip_network::{Ipv4Network, Ipv6Network};
|
||||
|
||||
/// Only used on Linux
|
||||
@@ -59,23 +61,34 @@ pub struct CliCommon {
|
||||
pub max_partition_time: Option<humantime::Duration>,
|
||||
}
|
||||
|
||||
/// Messages we get from connlib, including ones that aren't sent to IPC clients
|
||||
pub enum InternalServerMsg {
|
||||
Ipc(IpcServerMsg),
|
||||
/// Messages that connlib can produce and send to the headless Client, IPC service, or GUI process.
|
||||
///
|
||||
/// i.e. callbacks
|
||||
// The names are CamelCase versions of the connlib callbacks.
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
pub enum ConnlibMsg {
|
||||
OnDisconnect {
|
||||
error_msg: String,
|
||||
is_authentication_error: bool,
|
||||
},
|
||||
/// Use this as `TunnelReady`, per `callbacks.rs`
|
||||
OnSetInterfaceConfig {
|
||||
ipv4: Ipv4Addr,
|
||||
ipv6: Ipv6Addr,
|
||||
dns: Vec<IpAddr>,
|
||||
},
|
||||
OnUpdateResources(Vec<callbacks::ResourceDescription>),
|
||||
OnUpdateRoutes {
|
||||
ipv4: Vec<Ipv4Network>,
|
||||
ipv6: Vec<Ipv6Network>,
|
||||
},
|
||||
}
|
||||
|
||||
/// Messages that we can send to IPC clients
|
||||
/// Messages that end up in the GUI, either from connlib or from the IPC service.
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub enum IpcServerMsg {
|
||||
/// The IPC service finished clearing its log dir.
|
||||
ClearedLogs(Result<(), String>),
|
||||
OnDisconnect {
|
||||
error_msg: String,
|
||||
is_authentication_error: bool,
|
||||
@@ -91,7 +104,7 @@ pub enum IpcServerMsg {
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CallbackHandler {
|
||||
pub cb_tx: mpsc::Sender<InternalServerMsg>,
|
||||
pub cb_tx: mpsc::Sender<ConnlibMsg>,
|
||||
}
|
||||
|
||||
impl Callbacks for CallbackHandler {
|
||||
@@ -104,31 +117,29 @@ impl Callbacks for CallbackHandler {
|
||||
false
|
||||
};
|
||||
self.cb_tx
|
||||
.try_send(InternalServerMsg::Ipc(IpcServerMsg::OnDisconnect {
|
||||
.try_send(ConnlibMsg::OnDisconnect {
|
||||
error_msg: error.to_string(),
|
||||
is_authentication_error,
|
||||
}))
|
||||
})
|
||||
.expect("should be able to send OnDisconnect");
|
||||
}
|
||||
|
||||
fn on_set_interface_config(&self, ipv4: Ipv4Addr, ipv6: Ipv6Addr, dns: Vec<IpAddr>) {
|
||||
self.cb_tx
|
||||
.try_send(InternalServerMsg::OnSetInterfaceConfig { ipv4, ipv6, dns })
|
||||
.try_send(ConnlibMsg::OnSetInterfaceConfig { ipv4, ipv6, dns })
|
||||
.expect("Should be able to send OnSetInterfaceConfig");
|
||||
}
|
||||
|
||||
fn on_update_resources(&self, resources: Vec<callbacks::ResourceDescription>) {
|
||||
tracing::debug!(len = resources.len(), "New resource list");
|
||||
self.cb_tx
|
||||
.try_send(InternalServerMsg::Ipc(IpcServerMsg::OnUpdateResources(
|
||||
resources,
|
||||
)))
|
||||
.try_send(ConnlibMsg::OnUpdateResources(resources))
|
||||
.expect("Should be able to send OnUpdateResources");
|
||||
}
|
||||
|
||||
fn on_update_routes(&self, ipv4: Vec<Ipv4Network>, ipv6: Vec<Ipv6Network>) {
|
||||
self.cb_tx
|
||||
.try_send(InternalServerMsg::OnUpdateRoutes { ipv4, ipv6 })
|
||||
.try_send(ConnlibMsg::OnUpdateRoutes { ipv4, ipv6 })
|
||||
.expect("Should be able to send messages");
|
||||
}
|
||||
}
|
||||
@@ -152,7 +163,7 @@ mod tests {
|
||||
// Make sure it's okay to store a bunch of these to mitigate #5880
|
||||
#[test]
|
||||
fn callback_msg_size() {
|
||||
assert_eq!(std::mem::size_of::<InternalServerMsg>(), 56)
|
||||
assert_eq!(std::mem::size_of::<ConnlibMsg>(), 56)
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -11,7 +11,7 @@ use firezone_bin_shared::{
|
||||
TunDeviceManager, TOKEN_ENV_KEY,
|
||||
};
|
||||
use firezone_headless_client::{
|
||||
device_id, signals, CallbackHandler, CliCommon, DnsController, InternalServerMsg, IpcServerMsg,
|
||||
device_id, signals, CallbackHandler, CliCommon, ConnlibMsg, DnsController,
|
||||
};
|
||||
use futures::{FutureExt as _, StreamExt as _};
|
||||
use phoenix_channel::PhoenixChannel;
|
||||
@@ -252,18 +252,15 @@ fn main() -> Result<()> {
|
||||
|
||||
match cb {
|
||||
// TODO: Headless Client shouldn't be using messages labelled `Ipc`
|
||||
InternalServerMsg::Ipc(IpcServerMsg::OnDisconnect {
|
||||
ConnlibMsg::OnDisconnect {
|
||||
error_msg,
|
||||
is_authentication_error: _,
|
||||
}) => break Err(anyhow!(error_msg).context("Firezone disconnected")),
|
||||
InternalServerMsg::Ipc(IpcServerMsg::OnUpdateResources(_)) => {
|
||||
} => break Err(anyhow!(error_msg).context("Firezone disconnected")),
|
||||
ConnlibMsg::OnUpdateResources(_) => {
|
||||
// On every Resources update, flush DNS to mitigate <https://github.com/firezone/firezone/issues/5052>
|
||||
dns_controller.flush()?;
|
||||
}
|
||||
InternalServerMsg::Ipc(IpcServerMsg::TerminatingGracefully) => unimplemented!(
|
||||
"The standalone Client does not send `TerminatingGracefully` messages"
|
||||
),
|
||||
InternalServerMsg::OnSetInterfaceConfig { ipv4, ipv6, dns } => {
|
||||
ConnlibMsg::OnSetInterfaceConfig { ipv4, ipv6, dns } => {
|
||||
tun_device.set_ips(ipv4, ipv6).await?;
|
||||
dns_controller.set_dns(dns).await?;
|
||||
// `on_set_interface_config` is guaranteed to be called when the tunnel is completely ready
|
||||
@@ -278,7 +275,7 @@ fn main() -> Result<()> {
|
||||
break Ok(());
|
||||
}
|
||||
}
|
||||
InternalServerMsg::OnUpdateRoutes { ipv4, ipv6 } => {
|
||||
ConnlibMsg::OnUpdateRoutes { ipv4, ipv6 } => {
|
||||
tun_device.set_routes(ipv4, ipv6).await?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ export default function GUI({ title }: { title: string }) {
|
||||
|
||||
return (
|
||||
<Entries href={href} arches={arches} title={title}>
|
||||
{/* When you cut a release, remove any solved issues from the "known issues" lists over in `client-apps`. This cannot be done when the issue's PR merges. */}
|
||||
{/*
|
||||
<Entry version="1.1.12" date={new Date("Invalid date")}>
|
||||
<ul className="list-disc space-y-2 pl-4 mb-4">
|
||||
@@ -24,6 +25,9 @@ export default function GUI({ title }: { title: string }) {
|
||||
<ChangeItem pull="6277">
|
||||
Fixes a bug where restrictive NATs caused connectivity problems.
|
||||
</ChangeItem>
|
||||
<ChangeItem enable={title === "Windows"} pull="6280">
|
||||
Fixes a bug where the "Clear Logs" button did not clear the IPC service logs.
|
||||
</ChangeItem>
|
||||
</ul>
|
||||
</Entry>
|
||||
*/}
|
||||
|
||||
Reference in New Issue
Block a user