Files
firezone/rust/connlib/shared/src/lib.rs
Thomas Eizinger 2a46fce574 refactor(connlib): remove Result return values from callbacks (#4158)
Currently, an error returned by `Tunnel::poll_next_event` is only
logged. In other words, they are never fatal. This creates a tricky to
understand relationship on what kind of errors should be returned from
callbacks. Because connlib is used on multiple operating systems, it has
no idea how fatal a particular error is.

This PR removes all of these `Result` return values with the following
consequences:

- For Android, we now panic when a callback fails. This is a slight
change in behaviour. I believe that previously, any exception thrown by
a callback into Android was caught and returned as an error. Now, we
panic because in the FFI layer, we don't have any information on how
fatal the error is. For non-fatal errors, the Android app should simply
not throw an exception. The panics will cause the connlib task to be
shut down which triggers an `on_disconnect`.
- For Swift, there is no behaviour change. The FFI layer already did not
support `Result`s for those callbacks. I don't know how exceptions from
Swift are translated across the FFI layer but there is no change to what
we had before.
- For the Tauri client:
- I chose to log errors on ERROR level and continue gracefully for the
DNS resolvers.
- We panic in case the controller channel is full / closed. That should
really never happen in practice though unless we are currently shutting
down the app.

Resolves: #4064.
2024-03-20 02:09:20 +00:00

133 lines
4.3 KiB
Rust

//! This crates contains shared types and behavior between all the other libraries.
//!
//! This includes types provided by external crates, i.e. [boringtun] to make sure that
//! we are using the same version across our own crates.
mod callbacks;
pub mod error;
pub mod messages;
/// Module to generate and store a persistent device ID on disk
///
/// Only properly implemented on Linux and Windows (platforms with Tauri and headless client)
pub mod device_id;
// Must be compiled on Mac so the Mac runner can do `version-check` on `linux-client`
pub mod linux;
#[cfg(target_os = "windows")]
pub mod windows;
pub use boringtun::x25519::PublicKey;
pub use boringtun::x25519::StaticSecret;
pub use callbacks::{Callbacks, Cidrv4, Cidrv6};
pub use error::ConnlibError as Error;
pub use error::Result;
pub use phoenix_channel::{LoginUrl, LoginUrlError};
use rand_core::OsRng;
pub type Dname = domain::base::Dname<Vec<u8>>;
/// Bundle ID / App ID that the client uses to distinguish itself from other programs on the system
///
/// e.g. In ProgramData and AppData we use this to name our subdirectories for configs and data,
/// and Windows may use it to track things like the MSI installer, notification titles,
/// deep link registration, etc.
///
/// This should be identical to the `tauri.bundle.identifier` over in `tauri.conf.json`,
/// but sometimes I need to use this before Tauri has booted up, or in a place where
/// getting the Tauri app handle would be awkward.
///
/// Luckily this is also the AppUserModelId that Windows uses to label notifications,
/// so if your dev system has Firezone installed by MSI, the notifications will look right.
/// <https://learn.microsoft.com/en-us/windows/configuration/find-the-application-user-model-id-of-an-installed-app>
pub const BUNDLE_ID: &str = "dev.firezone.client";
const VERSION: &str = env!("CARGO_PKG_VERSION");
const LIB_NAME: &str = "connlib";
pub fn keypair() -> (StaticSecret, PublicKey) {
let private_key = StaticSecret::random_from_rng(OsRng);
let public_key = PublicKey::from(&private_key);
(private_key, public_key)
}
pub fn get_user_agent(os_version_override: Option<String>) -> String {
// Note: we could switch to sys-info and get the hostname
// but we lose the arch
// and neither of the libraries provide the kernel version.
// so I rather keep os_info which seems like the most popular
// and keep implementing things that we are missing on top
let info = os_info::get();
// iOS returns "Unknown", but we already know we're on iOS here
#[cfg(target_os = "ios")]
let os_type = "iOS";
#[cfg(not(target_os = "ios"))]
let os_type = info.os_type();
let os_version = os_version_override.unwrap_or(info.version().to_string());
let additional_info = additional_info();
let lib_version = VERSION;
let lib_name = LIB_NAME;
format!("{os_type}/{os_version}{additional_info}{lib_name}/{lib_version}")
}
fn additional_info() -> String {
let info = os_info::get();
match (info.architecture(), kernel_version()) {
(None, None) => " ".to_string(),
(None, Some(k)) => format!(" {k} "),
(Some(a), None) => format!(" {a} "),
(Some(a), Some(k)) => format!(" ({a};{k};) "),
}
}
#[cfg(not(target_family = "unix"))]
fn kernel_version() -> Option<String> {
None
}
#[cfg(target_family = "unix")]
fn kernel_version() -> Option<String> {
#[cfg(any(target_os = "android", target_os = "linux"))]
let mut utsname = libc::utsname {
sysname: [0; 65],
nodename: [0; 65],
release: [0; 65],
version: [0; 65],
machine: [0; 65],
domainname: [0; 65],
};
#[cfg(any(target_os = "macos", target_os = "ios"))]
let mut utsname = libc::utsname {
sysname: [0; 256],
nodename: [0; 256],
release: [0; 256],
version: [0; 256],
machine: [0; 256],
};
// SAFETY: we just allocated the pointer
if unsafe { libc::uname(&mut utsname as *mut _) } != 0 {
return None;
}
#[cfg_attr(
all(target_os = "linux", target_arch = "aarch64"),
allow(clippy::unnecessary_cast)
)]
let version: Vec<u8> = utsname
.release
.split(|c| *c == 0)
.next()?
.iter()
.map(|x| *x as u8)
.collect();
String::from_utf8(version).ok()
}