diff options
| -rw-r--r-- | mullvad-api/src/bin/relay_list.rs | 1 | ||||
| -rw-r--r-- | mullvad-cli/src/main.rs | 372 | ||||
| -rw-r--r-- | mullvad-daemon/src/device/mod.rs | 5 | ||||
| -rw-r--r-- | mullvad-daemon/src/main.rs | 428 | ||||
| -rw-r--r-- | mullvad-daemon/src/migrations/v11.rs | 2 | ||||
| -rw-r--r-- | mullvad-daemon/src/migrations/v9.rs | 19 | ||||
| -rw-r--r-- | mullvad-daemon/src/settings/mod.rs | 4 | ||||
| -rw-r--r-- | mullvad-problem-report/src/main.rs | 220 | ||||
| -rw-r--r-- | mullvad-setup/src/main.rs | 394 | ||||
| -rw-r--r-- | mullvad-types/src/features.rs | 7 |
10 files changed, 768 insertions, 684 deletions
diff --git a/mullvad-api/src/bin/relay_list.rs b/mullvad-api/src/bin/relay_list.rs index aea9e61bd7..413bc6b871 100644 --- a/mullvad-api/src/bin/relay_list.rs +++ b/mullvad-api/src/bin/relay_list.rs @@ -49,5 +49,6 @@ async fn main() { #[cfg(target_os = "android")] mod imp { + #[expect(clippy::unused_async)] pub async fn main() {} } diff --git a/mullvad-cli/src/main.rs b/mullvad-cli/src/main.rs index 1b03012453..de7ef306a5 100644 --- a/mullvad-cli/src/main.rs +++ b/mullvad-cli/src/main.rs @@ -1,216 +1,228 @@ -use anyhow::Result; -use clap::Parser; +#[cfg(not(target_os = "android"))] +pub(crate) mod cmds; +#[cfg(not(target_os = "android"))] +pub(crate) mod format; -mod cmds; -mod format; -use cmds::*; +fn main() -> anyhow::Result<()> { + #[cfg(not(target_os = "android"))] + imp::main()?; + Ok(()) +} -pub const BIN_NAME: &str = env!("CARGO_BIN_NAME"); +#[cfg(not(target_os = "android"))] +mod imp { + use anyhow::Result; + use clap::Parser; -#[derive(Debug, Parser)] -#[command(version = mullvad_version::VERSION, about, long_about = None)] -#[command(propagate_version = true)] -enum Cli { - /// Control and display information about your Mullvad account - #[clap(subcommand)] - Account(account::Account), + use super::cmds::*; - /// Control the daemon auto-connect setting - #[clap(subcommand)] - AutoConnect(auto_connect::AutoConnect), + pub const BIN_NAME: &str = env!("CARGO_BIN_NAME"); - /// Receive notifications about beta updates - #[clap(subcommand)] - BetaProgram(beta_program::BetaProgram), + #[derive(Debug, Parser)] + #[command(version = mullvad_version::VERSION, about, long_about = None)] + #[command(propagate_version = true)] + enum Cli { + /// Control and display information about your Mullvad account + #[clap(subcommand)] + Account(account::Account), - /// Control whether to block network access when disconnected from VPN - #[clap(subcommand)] - LockdownMode(lockdown::LockdownMode), + /// Control the daemon auto-connect setting + #[clap(subcommand)] + AutoConnect(auto_connect::AutoConnect), - /// Debug commands used for internal testing of the app. - /// - /// These commands will likely set the app in an invalid state, which is - /// used to test security under various edge cases. - #[clap(subcommand, hide = true)] - Debug(debug::DebugCommands), + /// Receive notifications about beta updates + #[clap(subcommand)] + BetaProgram(beta_program::BetaProgram), - /// Configure DNS servers to use when connected - #[clap(subcommand)] - Dns(dns::Dns), + /// Control whether to block network access when disconnected from VPN + #[clap(subcommand)] + LockdownMode(lockdown::LockdownMode), - /// Control the allow local network sharing setting - #[clap(subcommand)] - Lan(lan::Lan), + /// Debug commands used for internal testing of the app. + /// + /// These commands will likely set the app in an invalid state, which is + /// used to test security under various edge cases. + #[clap(subcommand, hide = true)] + Debug(debug::DebugCommands), - /// Connect to a VPN relay - Connect { - /// Wait until connected before exiting - #[arg(long, short = 'w')] - wait: bool, - }, + /// Configure DNS servers to use when connected + #[clap(subcommand)] + Dns(dns::Dns), - /// Disconnect from the VPN - Disconnect { - /// Wait until disconnected before exiting - #[arg(long, short = 'w')] - wait: bool, - }, + /// Control the allow local network sharing setting + #[clap(subcommand)] + Lan(lan::Lan), - /// Reconnect to any matching VPN relay - Reconnect { - /// Wait until connected before exiting - #[arg(long, short = 'w')] - wait: bool, - }, + /// Connect to a VPN relay + Connect { + /// Wait until connected before exiting + #[arg(long, short = 'w')] + wait: bool, + }, - /// Manage relay and tunnel constraints - #[clap(subcommand)] - Relay(relay::Relay), - /// Manage Mullvad API access methods. - /// - /// Access methods are used to connect to the Mullvad API via one of - /// Mullvad's bridge servers or a custom proxy (SOCKS5 & Shadowsocks) when - /// and where establishing a direct connection does not work. - /// - /// If the Mullvad daemon is unable to connect to the Mullvad API, it will - /// automatically try to use any other configured access method and re-try - /// the API call. If it succeeds, all subsequent API calls are made using - /// the new access method. Otherwise it will re-try using yet another access - /// method. - /// - /// The Mullvad API is used for logging in, accessing the relay list, - /// rotating Wireguard keys and more. - #[clap(subcommand)] - ApiAccess(api_access::ApiAccess), + /// Disconnect from the VPN + Disconnect { + /// Wait until disconnected before exiting + #[arg(long, short = 'w')] + wait: bool, + }, - /// Manage use of anti censorship methods for WireGuard. - /// Can make WireGuard traffic look like something else on the network. - /// Helps circumvent censorship and to establish a tunnel when on restricted networks - #[clap(subcommand)] - AntiCensorship(anti_censorship::AntiCensorship), + /// Reconnect to any matching VPN relay + Reconnect { + /// Wait until connected before exiting + #[arg(long, short = 'w')] + wait: bool, + }, + + /// Manage relay and tunnel constraints + #[clap(subcommand)] + Relay(relay::Relay), + /// Manage Mullvad API access methods. + /// + /// Access methods are used to connect to the Mullvad API via one of + /// Mullvad's bridge servers or a custom proxy (SOCKS5 & Shadowsocks) when + /// and where establishing a direct connection does not work. + /// + /// If the Mullvad daemon is unable to connect to the Mullvad API, it will + /// automatically try to use any other configured access method and re-try + /// the API call. If it succeeds, all subsequent API calls are made using + /// the new access method. Otherwise it will re-try using yet another access + /// method. + /// + /// The Mullvad API is used for logging in, accessing the relay list, + /// rotating Wireguard keys and more. + #[clap(subcommand)] + ApiAccess(api_access::ApiAccess), - #[clap(subcommand)] - SplitTunnel(split_tunnel::SplitTunnel), + /// Manage use of anti censorship methods for WireGuard. + /// Can make WireGuard traffic look like something else on the network. + /// Helps circumvent censorship and to establish a tunnel when on restricted networks + #[clap(subcommand)] + AntiCensorship(anti_censorship::AntiCensorship), - /// Return the state of the VPN tunnel - Status { #[clap(subcommand)] - cmd: Option<status::Status>, + SplitTunnel(split_tunnel::SplitTunnel), - #[clap(flatten)] - args: status::StatusArgs, - }, + /// Return the state of the VPN tunnel + Status { + #[clap(subcommand)] + cmd: Option<status::Status>, - /// Manage tunnel options - #[clap(subcommand)] - Tunnel(tunnel::Tunnel), + #[clap(flatten)] + args: status::StatusArgs, + }, - /// Show information about the current Mullvad version - /// and available versions - Version, + /// Manage tunnel options + #[clap(subcommand)] + Tunnel(tunnel::Tunnel), - /// Generate completion scripts for the specified shell - #[cfg(all(unix, not(target_os = "android")))] - #[command(hide = true)] - ShellCompletions { - /// The shell to generate the script for - shell: clap_complete::Shell, + /// Show information about the current Mullvad version + /// and available versions + Version, - /// Output directory where the shell completions are written - #[arg(default_value = "./")] - dir: std::path::PathBuf, - }, + /// Generate completion scripts for the specified shell + #[cfg(all(unix, not(target_os = "android")))] + #[command(hide = true)] + ShellCompletions { + /// The shell to generate the script for + shell: clap_complete::Shell, - /// Reset settings, caches, and logs - FactoryReset { - #[clap(long, short = 'y', default_value_t = false)] - assume_yes: bool, - }, + /// Output directory where the shell completions are written + #[arg(default_value = "./")] + dir: std::path::PathBuf, + }, - /// Reset settings only, but remain logged in and keep logs and caches - ResetSettings { - #[clap(long, short = 'y', default_value_t = false)] - assume_yes: bool, - }, + /// Reset settings, caches, and logs + FactoryReset { + #[clap(long, short = 'y', default_value_t = false)] + assume_yes: bool, + }, - /// Manage custom lists - #[clap(subcommand)] - CustomList(custom_list::CustomList), + /// Reset settings only, but remain logged in and keep logs and caches + ResetSettings { + #[clap(long, short = 'y', default_value_t = false)] + assume_yes: bool, + }, - /// Apply a JSON patch generated by 'export-settings' - #[clap(arg_required_else_help = true)] - ImportSettings { - /// File to read from. If this is "-", read from standard input - file: String, - }, + /// Manage custom lists + #[clap(subcommand)] + CustomList(custom_list::CustomList), - /// Export a JSON patch based on the current settings - #[clap(arg_required_else_help = true)] - ExportSettings { - /// File to write to. If this is "-", write to standard output - file: String, - }, + /// Apply a JSON patch generated by 'export-settings' + #[clap(arg_required_else_help = true)] + ImportSettings { + /// File to read from. If this is "-", read from standard input + file: String, + }, - /// Manage logs and tracing - #[clap(subcommand)] - Log(log::Log), -} + /// Export a JSON patch based on the current settings + #[clap(arg_required_else_help = true)] + ExportSettings { + /// File to write to. If this is "-", write to standard output + file: String, + }, -#[tokio::main] -async fn main() -> Result<()> { - // Handle SIGPIPE - // https://stackoverflow.com/questions/65755853/simple-word-count-rust-program-outputs-valid-stdout-but-panicks-when-piped-to-he/65760807 - // https://github.com/typst/typst/pull/5444 - #[cfg(unix)] - handle_sigpipe().unwrap(); + /// Manage logs and tracing + #[clap(subcommand)] + Log(log::Log), + } - match Cli::parse() { - Cli::Account(cmd) => cmd.handle().await, - Cli::Connect { wait } => tunnel_state::connect(wait).await, - Cli::Reconnect { wait } => tunnel_state::reconnect(wait).await, - Cli::Debug(cmd) => cmd.handle().await, - Cli::Disconnect { wait } => tunnel_state::disconnect(wait).await, - Cli::AutoConnect(cmd) => cmd.handle().await, - Cli::BetaProgram(cmd) => cmd.handle().await, - Cli::LockdownMode(cmd) => cmd.handle().await, - Cli::Dns(cmd) => cmd.handle().await, - Cli::Lan(cmd) => cmd.handle().await, - Cli::AntiCensorship(cmd) => cmd.handle().await, - Cli::ApiAccess(cmd) => cmd.handle().await, - Cli::Version => version::print().await, - Cli::FactoryReset { assume_yes } => reset::handle_factory_reset(assume_yes).await, - Cli::ResetSettings { assume_yes } => reset::handle_settings_reset(assume_yes).await, - Cli::Relay(cmd) => cmd.handle().await, - Cli::Tunnel(cmd) => cmd.handle().await, - Cli::SplitTunnel(cmd) => cmd.handle().await, - Cli::Status { cmd, args } => status::handle(cmd, args).await, - Cli::CustomList(cmd) => cmd.handle().await, - Cli::ImportSettings { file } => patch::import(file).await, - Cli::ExportSettings { file } => patch::export(file).await, - Cli::Log(cmd) => cmd.handle().await, + #[tokio::main] + pub(crate) async fn main() -> Result<()> { + // Handle SIGPIPE + // https://stackoverflow.com/questions/65755853/simple-word-count-rust-program-outputs-valid-stdout-but-panicks-when-piped-to-he/65760807 + // https://github.com/typst/typst/pull/5444 + #[cfg(unix)] + handle_sigpipe().unwrap(); - #[cfg(all(unix, not(target_os = "android")))] - Cli::ShellCompletions { shell, dir } => { - use anyhow::Context; - use clap::CommandFactory; + match Cli::parse() { + Cli::Account(cmd) => cmd.handle().await, + Cli::Connect { wait } => tunnel_state::connect(wait).await, + Cli::Reconnect { wait } => tunnel_state::reconnect(wait).await, + Cli::Debug(cmd) => cmd.handle().await, + Cli::Disconnect { wait } => tunnel_state::disconnect(wait).await, + Cli::AutoConnect(cmd) => cmd.handle().await, + Cli::BetaProgram(cmd) => cmd.handle().await, + Cli::LockdownMode(cmd) => cmd.handle().await, + Cli::Dns(cmd) => cmd.handle().await, + Cli::Lan(cmd) => cmd.handle().await, + Cli::AntiCensorship(cmd) => cmd.handle().await, + Cli::ApiAccess(cmd) => cmd.handle().await, + Cli::Version => version::print().await, + Cli::FactoryReset { assume_yes } => reset::handle_factory_reset(assume_yes).await, + Cli::ResetSettings { assume_yes } => reset::handle_settings_reset(assume_yes).await, + Cli::Relay(cmd) => cmd.handle().await, + Cli::Tunnel(cmd) => cmd.handle().await, + Cli::SplitTunnel(cmd) => cmd.handle().await, + Cli::Status { cmd, args } => status::handle(cmd, args).await, + Cli::CustomList(cmd) => cmd.handle().await, + Cli::ImportSettings { file } => patch::import(file).await, + Cli::ExportSettings { file } => patch::export(file).await, + Cli::Log(cmd) => cmd.handle().await, + + #[cfg(all(unix, not(target_os = "android")))] + Cli::ShellCompletions { shell, dir } => { + use anyhow::Context; + use clap::CommandFactory; - // FIXME: The shell completions include hidden commands (including "shell-completions") - println!("Generating shell completions to {}", dir.display()); - clap_complete::generate_to(shell, &mut Cli::command(), BIN_NAME, dir) - .context("Failed to generate shell completions")?; - Ok(()) + // FIXME: The shell completions include hidden commands (including "shell-completions") + println!("Generating shell completions to {}", dir.display()); + clap_complete::generate_to(shell, &mut Cli::command(), BIN_NAME, dir) + .context("Failed to generate shell completions")?; + Ok(()) + } } } -} -/// Install the default signal handler for `SIGPIPE`. -/// -/// By default, Rust replaces it with an empty handler because reasons: https://github.com/rust-lang/rust/issues/119980 -#[cfg(unix)] -fn handle_sigpipe() -> Result<(), nix::errno::Errno> { - use nix::sys::signal::{SigHandler, Signal, signal}; - // SAFETY: We do not use the previous signal handler, which could cause UB if done carelessly: - // https://pubs.opengroup.org/onlinepubs/9699919799/functions/signal.html - unsafe { signal(Signal::SIGPIPE, SigHandler::SigDfl) }?; - Ok(()) + /// Install the default signal handler for `SIGPIPE`. + /// + /// By default, Rust replaces it with an empty handler because reasons: https://github.com/rust-lang/rust/issues/119980 + #[cfg(unix)] + pub(crate) fn handle_sigpipe() -> Result<(), nix::errno::Errno> { + use nix::sys::signal::{SigHandler, Signal, signal}; + // SAFETY: We do not use the previous signal handler, which could cause UB if done carelessly: + // https://pubs.opengroup.org/onlinepubs/9699919799/functions/signal.html + unsafe { signal(Signal::SIGPIPE, SigHandler::SigDfl) }?; + Ok(()) + } } diff --git a/mullvad-daemon/src/device/mod.rs b/mullvad-daemon/src/device/mod.rs index cc6ce4c50a..a57a22daa3 100644 --- a/mullvad-daemon/src/device/mod.rs +++ b/mullvad-daemon/src/device/mod.rs @@ -1402,7 +1402,10 @@ mod test { fn test_device_check_reset() { let can_retry = Arc::new(AtomicBool::new(false)); // Transitioning to the 'Disconnected' state counts as breaking the 'connection loop' - let new_tunnel_state = TunnelStateTransition::Disconnected { locked_down: false }; + let new_tunnel_state = TunnelStateTransition::Disconnected { + #[cfg(not(target_os = "android"))] + locked_down: false, + }; TunnelStateChangeHandler::update_retry_bool(&new_tunnel_state, can_retry.clone()); assert!( diff --git a/mullvad-daemon/src/main.rs b/mullvad-daemon/src/main.rs index bbc99ca7ee..8dd267f9fe 100644 --- a/mullvad-daemon/src/main.rs +++ b/mullvad-daemon/src/main.rs @@ -1,254 +1,276 @@ -use std::{path::PathBuf, thread, time::Duration}; +#[cfg(not(target_os = "android"))] +pub(crate) mod cli; -#[cfg(not(windows))] -use mullvad_daemon::cleanup_old_rpc_socket; -use mullvad_daemon::{ - Daemon, DaemonCommandChannel, DaemonConfig, exception_logging, - logging::{self, LogLocation}, - rpc_uniqueness_check, runtime, version, -}; -use talpid_types::ErrorExt; - -mod cli; #[cfg(target_os = "linux")] -mod early_boot_firewall; +pub(crate) mod early_boot_firewall; + #[cfg(target_os = "macos")] -mod macos_launch_daemon; -#[cfg(windows)] -mod system_service; +pub(crate) mod macos_launch_daemon; -#[cfg(target_os = "linux")] -const EARLY_BOOT_LOG_FILENAME: &str = "early-boot-fw.log"; +#[cfg(windows)] +pub(crate) mod system_service; fn main() { - let runtime = new_runtime(); - let exit_code = match runtime.block_on(run()) { - Ok(_) => 0, - Err(error) => { - if logging::is_enabled() { - log::error!("{error}"); - } else { - eprintln!("{error}") - } + #[cfg(not(target_os = "android"))] + imp::main() +} - 1 - } - }; +#[cfg(not(target_os = "android"))] +mod imp { + use std::{path::PathBuf, thread, time::Duration}; - log::debug!("Process exiting with code {}", exit_code); - std::process::exit(exit_code); -} + #[cfg(not(windows))] + use mullvad_daemon::cleanup_old_rpc_socket; -fn new_runtime() -> tokio::runtime::Runtime { - let mut builder = match cli::get_config().command { - #[cfg(target_os = "windows")] - cli::Command::RunAsService | cli::Command::RegisterService => runtime::new_current_thread(), - _ => runtime::new_multi_thread(), + use mullvad_daemon::{ + Daemon, DaemonCommandChannel, DaemonConfig, exception_logging, + logging::{self, LogLocation}, + rpc_uniqueness_check, runtime, version, }; - builder.build().unwrap_or_else(|e| { - eprintln!("{}", e.display_chain()); - std::process::exit(1); - }) -} + use talpid_types::ErrorExt; -async fn run() -> Result<(), String> { - let config = cli::get_config(); + use crate::cli; - match config.command { - cli::Command::Daemon => { - // uniqueness check must happen before logging initializaton, - // as initializing logs will rotate any existing log file. - assert_unique().await?; - let (log_location, reload_handle) = init_daemon_logging(config)?; - log::trace!("Using configuration: {:?}", config); + #[cfg(target_os = "linux")] + pub(crate) const EARLY_BOOT_LOG_FILENAME: &str = "early-boot-fw.log"; - let log_dir = log_location.map(|l| l.directory); - run_standalone(log_dir, reload_handle).await - } + pub(crate) fn main() { + let runtime = new_runtime(); + let exit_code = match runtime.block_on(run()) { + Ok(_) => 0, + Err(error) => { + if logging::is_enabled() { + log::error!("{error}"); + } else { + eprintln!("{error}") + } - #[cfg(target_os = "linux")] - cli::Command::InitializeEarlyBootFirewall => { - init_early_boot_logging(config); + 1 + } + }; - crate::early_boot_firewall::initialize_firewall() - .await - .map_err(|err| format!("{err}")) - } + log::debug!("Process exiting with code {}", exit_code); + std::process::exit(exit_code); + } - #[cfg(target_os = "windows")] - cli::Command::RunAsService => { - assert_unique().await?; - system_service::run() - } + pub(crate) fn new_runtime() -> tokio::runtime::Runtime { + let mut builder = match cli::get_config().command { + #[cfg(target_os = "windows")] + cli::Command::RunAsService | cli::Command::RegisterService => { + runtime::new_current_thread() + } + _ => runtime::new_multi_thread(), + }; - #[cfg(target_os = "windows")] - cli::Command::RegisterService => { - init_logger(config, None)?; - system_service::install_service() - .inspect(|_| println!("Installed the service.")) - .map_err(|e| e.display_chain()) - } + builder.build().unwrap_or_else(|e| { + eprintln!("{}", e.display_chain()); + std::process::exit(1); + }) + } + + pub(crate) async fn run() -> Result<(), String> { + let config = cli::get_config(); + + match config.command { + cli::Command::Daemon => { + // uniqueness check must happen before logging initializaton, + // as initializing logs will rotate any existing log file. + assert_unique().await?; + let (log_location, reload_handle) = init_daemon_logging(config)?; + log::trace!("Using configuration: {:?}", config); + + let log_dir = log_location.map(|l| l.directory); + run_standalone(log_dir, reload_handle).await + } - #[cfg(target_os = "macos")] - cli::Command::LaunchDaemonStatus => { - if version::is_dev_version() { - eprintln!("Note: This command may not work on non-notarized builds."); + #[cfg(target_os = "linux")] + cli::Command::InitializeEarlyBootFirewall => { + init_early_boot_logging(config); + + crate::early_boot_firewall::initialize_firewall() + .await + .map_err(|err| format!("{err}")) + } + + #[cfg(target_os = "windows")] + cli::Command::RunAsService => { + assert_unique().await?; + system_service::run() + } + + #[cfg(target_os = "windows")] + cli::Command::RegisterService => { + init_logger(config, None)?; + system_service::install_service() + .inspect(|_| println!("Installed the service.")) + .map_err(|e| e.display_chain()) } - std::process::exit(macos_launch_daemon::get_status() as i32); + #[cfg(target_os = "macos")] + cli::Command::LaunchDaemonStatus => { + if version::is_dev_version() { + eprintln!("Note: This command may not work on non-notarized builds."); + } + + std::process::exit(crate::macos_launch_daemon::get_status() as i32); + } } } -} -/// Check that there's not another daemon currently running. -async fn assert_unique() -> Result<(), &'static str> { - if rpc_uniqueness_check::is_another_instance_running().await { - return Err("Another instance of the daemon is already running"); + /// Check that there's not another daemon currently running. + pub(crate) async fn assert_unique() -> Result<(), &'static str> { + if rpc_uniqueness_check::is_another_instance_running().await { + return Err("Another instance of the daemon is already running"); + } + Ok(()) } - Ok(()) -} -/// Initialize logging to stderr and to file (if configured). -fn init_daemon_logging( - config: &cli::Config, -) -> Result<(Option<LogLocation>, logging::LogHandle), String> { - let log_location = get_log_dir(config)?.map(|directory| LogLocation { - directory, - filename: PathBuf::from("daemon.log"), - }); + /// Initialize logging to stderr and to file (if configured). + pub(crate) fn init_daemon_logging( + config: &cli::Config, + ) -> Result<(Option<LogLocation>, logging::LogHandle), String> { + let log_location = get_log_dir(config)?.map(|directory| LogLocation { + directory, + filename: PathBuf::from("daemon.log"), + }); - let reload_handle = init_logger(config, log_location.clone())?; + let reload_handle = init_logger(config, log_location.clone())?; - if let Some(log_location) = log_location.as_ref() { - log::info!("Logging to {}", log_location.log_path().display()); + if let Some(log_location) = log_location.as_ref() { + log::info!("Logging to {}", log_location.log_path().display()); + } + Ok((log_location, reload_handle)) } - Ok((log_location, reload_handle)) -} -/// Initialize logging to stder and to the [`EARLY_BOOT_LOG_FILENAME`] -#[cfg(target_os = "linux")] -fn init_early_boot_logging(config: &cli::Config) { - let logging = get_log_dir(config) - .ok() - .flatten() - .map(|log_dir| LogLocation { - directory: log_dir, - filename: PathBuf::from(EARLY_BOOT_LOG_FILENAME), - }); + /// Initialize logging to stder and to the [`EARLY_BOOT_LOG_FILENAME`] + #[cfg(target_os = "linux")] + pub(crate) fn init_early_boot_logging(config: &cli::Config) { + let logging = get_log_dir(config) + .ok() + .flatten() + .map(|log_dir| LogLocation { + directory: log_dir, + filename: PathBuf::from(EARLY_BOOT_LOG_FILENAME), + }); - // If it's possible to log to the filesystem - attempt to do so, but failing that mustn't stop - // the daemon from starting here. - if init_logger(config, logging).is_err() { - let _ = init_logger(config, None); - }; -} + // If it's possible to log to the filesystem - attempt to do so, but failing that mustn't stop + // the daemon from starting here. + if init_logger(config, logging).is_err() { + let _ = init_logger(config, None); + }; + } -/// Initialize logging to stderr and to file (if provided). -/// -/// Also install the [exception_logging] signal handler to log faults. -fn init_logger( - config: &cli::Config, - log_location: Option<LogLocation>, -) -> Result<logging::LogHandle, String> { - #[cfg(unix)] - if let Some(log_location) = log_location.as_ref() { - use std::os::unix::ffi::OsStrExt; + /// Initialize logging to stderr and to file (if provided). + /// + /// Also install the [exception_logging] signal handler to log faults. + pub(crate) fn init_logger( + config: &cli::Config, + log_location: Option<LogLocation>, + ) -> Result<logging::LogHandle, String> { + #[cfg(unix)] + if let Some(log_location) = log_location.as_ref() { + use std::os::unix::ffi::OsStrExt; - exception_logging::set_log_file( - std::ffi::CString::new(log_location.log_path().as_os_str().as_bytes()) - .map_err(|_| "Log file path contains null-bytes".to_string())?, - ); - } + exception_logging::set_log_file( + std::ffi::CString::new(log_location.log_path().as_os_str().as_bytes()) + .map_err(|_| "Log file path contains null-bytes".to_string())?, + ); + } - exception_logging::enable(); + exception_logging::enable(); - let reload_handle = - logging::init_logger(config.log_level, log_location, config.log_stdout_timestamps) - .map_err(|e| e.display_chain_with_msg("Unable to initialize logger"))?; - log_panics::init(); - version::log_version(); - Ok(reload_handle) -} + let reload_handle = + logging::init_logger(config.log_level, log_location, config.log_stdout_timestamps) + .map_err(|e| e.display_chain_with_msg("Unable to initialize logger"))?; + log_panics::init(); + version::log_version(); + Ok(reload_handle) + } -fn get_log_dir(config: &cli::Config) -> Result<Option<PathBuf>, String> { - if config.log_to_file { - Ok(Some(mullvad_paths::log_dir().map_err(|e| { - e.display_chain_with_msg("Unable to get log directory") - })?)) - } else { - Ok(None) + pub(crate) fn get_log_dir(config: &cli::Config) -> Result<Option<PathBuf>, String> { + if config.log_to_file { + Ok(Some(mullvad_paths::log_dir().map_err(|e| { + e.display_chain_with_msg("Unable to get log directory") + })?)) + } else { + Ok(None) + } } -} -async fn run_standalone( - log_dir: Option<PathBuf>, - log_handle: logging::LogHandle, -) -> Result<(), String> { - #[cfg(not(windows))] - cleanup_old_rpc_socket(mullvad_paths::get_rpc_socket_path()).await; + pub(crate) async fn run_standalone( + log_dir: Option<PathBuf>, + log_handle: logging::LogHandle, + ) -> Result<(), String> { + #[cfg(not(windows))] + cleanup_old_rpc_socket(mullvad_paths::get_rpc_socket_path()).await; - if !running_as_admin() { - log::warn!("Running daemon as a non-administrator user, clients might refuse to connect"); - } + if !running_as_admin() { + log::warn!( + "Running daemon as a non-administrator user, clients might refuse to connect" + ); + } - let daemon = create_daemon(log_dir, log_handle).await?; + let daemon = create_daemon(log_dir, log_handle).await?; - let shutdown_handle = daemon.shutdown_handle(); - #[cfg(any(target_os = "linux", target_os = "macos"))] - mullvad_daemon::shutdown::set_shutdown_signal_handler(move || { - shutdown_handle.shutdown(!mullvad_daemon::shutdown::is_shutdown_user_initiated()) - }) - .map_err(|e| e.display_chain())?; + let shutdown_handle = daemon.shutdown_handle(); + #[cfg(any(target_os = "linux", target_os = "macos"))] + mullvad_daemon::shutdown::set_shutdown_signal_handler(move || { + shutdown_handle.shutdown(!mullvad_daemon::shutdown::is_shutdown_user_initiated()) + }) + .map_err(|e| e.display_chain())?; - #[cfg(any(windows, target_os = "android"))] - mullvad_daemon::shutdown::set_shutdown_signal_handler(move || shutdown_handle.shutdown(true)) + #[cfg(any(windows, target_os = "android"))] + mullvad_daemon::shutdown::set_shutdown_signal_handler(move || { + shutdown_handle.shutdown(true) + }) .map_err(|e| e.display_chain())?; - daemon.run().await.map_err(|e| e.display_chain())?; + daemon.run().await.map_err(|e| e.display_chain())?; - #[cfg(not(windows))] - cleanup_old_rpc_socket(mullvad_paths::get_rpc_socket_path()).await; + #[cfg(not(windows))] + cleanup_old_rpc_socket(mullvad_paths::get_rpc_socket_path()).await; - log::info!("Mullvad daemon is quitting"); - thread::sleep(Duration::from_millis(500)); - Ok(()) -} + log::info!("Mullvad daemon is quitting"); + thread::sleep(Duration::from_millis(500)); + Ok(()) + } -async fn create_daemon( - log_dir: Option<PathBuf>, - log_handle: logging::LogHandle, -) -> Result<Daemon, String> { - let rpc_socket_path = mullvad_paths::get_rpc_socket_path(); - let resource_dir = mullvad_paths::get_resource_dir(); - let settings_dir = mullvad_paths::settings_dir() - .map_err(|e| e.display_chain_with_msg("Unable to get settings dir"))?; - let cache_dir = mullvad_paths::cache_dir() - .map_err(|e| e.display_chain_with_msg("Unable to get cache dir"))?; + pub(crate) async fn create_daemon( + log_dir: Option<PathBuf>, + log_handle: logging::LogHandle, + ) -> Result<Daemon, String> { + let rpc_socket_path = mullvad_paths::get_rpc_socket_path(); + let resource_dir = mullvad_paths::get_resource_dir(); + let settings_dir = mullvad_paths::settings_dir() + .map_err(|e| e.display_chain_with_msg("Unable to get settings dir"))?; + let cache_dir = mullvad_paths::cache_dir() + .map_err(|e| e.display_chain_with_msg("Unable to get cache dir"))?; - Daemon::start( - DaemonConfig { - log_dir, - resource_dir, - settings_dir, - cache_dir, - rpc_socket_path, - endpoint: mullvad_api::ApiEndpoint::from_env_vars(), - log_handle, - }, - DaemonCommandChannel::new(), - ) - .await - .map_err(|e| e.display_chain_with_msg("Unable to initialize daemon")) -} + Daemon::start( + DaemonConfig { + log_dir, + resource_dir, + settings_dir, + cache_dir, + rpc_socket_path, + endpoint: mullvad_api::ApiEndpoint::from_env_vars(), + log_handle, + }, + DaemonCommandChannel::new(), + ) + .await + .map_err(|e| e.display_chain_with_msg("Unable to initialize daemon")) + } -#[cfg(unix)] -fn running_as_admin() -> bool { - nix::unistd::Uid::current().is_root() -} + #[cfg(unix)] + pub(crate) fn running_as_admin() -> bool { + nix::unistd::Uid::current().is_root() + } -#[cfg(windows)] -fn running_as_admin() -> bool { - // TODO: Check if user is administrator correctly on Windows. - true + #[cfg(windows)] + pub(crate) fn running_as_admin() -> bool { + // TODO: Check if user is administrator correctly on Windows. + true + } } diff --git a/mullvad-daemon/src/migrations/v11.rs b/mullvad-daemon/src/migrations/v11.rs index da33bb05ae..f78ea73ad6 100644 --- a/mullvad-daemon/src/migrations/v11.rs +++ b/mullvad-daemon/src/migrations/v11.rs @@ -163,6 +163,7 @@ mod test { /// "block_when_disconnected" is renamed to "lockdown_mode" #[test] + #[cfg(not(target_os = "android"))] fn test_v11_to_v12_migration_block_when_disconnected_disabled() { let mut old_settings = json!({ "block_when_disconnected": false, @@ -175,6 +176,7 @@ mod test { } #[test] + #[cfg(not(target_os = "android"))] fn test_v11_to_v12_migration_block_when_disconnected_enabled() { let mut old_settings = json!({ "block_when_disconnected": true, diff --git a/mullvad-daemon/src/migrations/v9.rs b/mullvad-daemon/src/migrations/v9.rs index f2fc86e472..9b3fe13f8d 100644 --- a/mullvad-daemon/src/migrations/v9.rs +++ b/mullvad-daemon/src/migrations/v9.rs @@ -197,16 +197,20 @@ mod test { #[cfg(target_os = "android")] mod android { + use crate::migrations::v9::android::add_split_tunneling_settings; + /// Assert that split-tunneling settings has been added to the android settings post-migration. #[test] fn test_v9_to_v10_migration() { - use crate::migrations::v9::{ - add_split_tunneling_settings, - test::android::constants::{V9_ANDROID_SETTINGS, V10_ANDROID_SETTINGS}, + use crate::migrations::v9::test::android::constants::{ + V9_ANDROID_SETTINGS, V10_ANDROID_SETTINGS, }; let enabled = true; - let apps = ["com.android.chrome", "net.mullvad.mullvadvpn"]; + let apps = vec![ + "com.android.chrome".to_string(), + "net.mullvad.mullvadvpn".to_string(), + ]; let mut settings = serde_json::from_str(V9_ANDROID_SETTINGS).unwrap(); // Perform the actual settings migration while skipping the I/O performed in @@ -442,7 +446,12 @@ mod test { let mut old_settings = serde_json::from_str(V9_SETTINGS).unwrap(); assert!(version_matches(&old_settings)); - migrate(&mut old_settings).unwrap(); + migrate( + &mut old_settings, + #[cfg(target_os = "android")] + None, + ) + .unwrap(); let new_settings: serde_json::Value = serde_json::from_str(V10_SETTINGS).unwrap(); eprintln!( diff --git a/mullvad-daemon/src/settings/mod.rs b/mullvad-daemon/src/settings/mod.rs index 0966a95590..00990ad412 100644 --- a/mullvad-daemon/src/settings/mod.rs +++ b/mullvad-daemon/src/settings/mod.rs @@ -677,6 +677,7 @@ mod test { async fn test_deserialize_missing_settings() { let LoadSettingsResult { should_save, + #[cfg_attr(target_os = "android", expect(unused_variables))] settings, } = SettingsPersister::load_inner(|| async { Err(Error::ReadError( @@ -691,6 +692,7 @@ mod test { "Settings should be saved to disk if they didn't exist previously" ); + #[cfg(not(target_os = "android"))] assert!( !settings.lockdown_mode, "The daemon should not block the internet if settings are missing" @@ -710,6 +712,7 @@ mod test { async fn test_deserialize_invalid_settings() { let LoadSettingsResult { should_save, + #[cfg_attr(target_os = "android", expect(unused_variables))] settings, } = SettingsPersister::load_inner(|| async { SettingsPersister::load_from_bytes(b"Not a valid settings file") @@ -721,6 +724,7 @@ mod test { "Settings should be saved to disk if they have become corrupt" ); + #[cfg(not(target_os = "android"))] assert!( settings.lockdown_mode, "The daemon should block the internet if settings are corrupt" diff --git a/mullvad-problem-report/src/main.rs b/mullvad-problem-report/src/main.rs index d0cee15ed1..ed75a399d4 100644 --- a/mullvad-problem-report/src/main.rs +++ b/mullvad-problem-report/src/main.rs @@ -1,118 +1,130 @@ -use clap::Parser; -use mullvad_api::ApiEndpoint; -use mullvad_problem_report::{Error, ProblemReportCollector, WriteSource}; -use std::{ - env, io, - path::{Path, PathBuf}, - process, -}; -use talpid_types::ErrorExt; - fn main() { - process::exit(match run() { - Ok(()) => 0, - Err(error) => { - eprintln!("{}", error.display_chain()); - 1 - } - }) + #[cfg(not(target_os = "android"))] + imp::main(); } -#[derive(Debug, Parser)] -#[command(author, version = mullvad_version::VERSION, about, long_about = None)] -#[command( - arg_required_else_help = true, - disable_help_subcommand = true, - disable_version_flag = true -)] -enum Cli { - /// Collect problem report to a single file - Collect { - /// The destination path for saving the collected report - #[arg(required = true, long, short = 'o')] - output: String, - /// Paths to additional log files to be included - extra_logs: Vec<PathBuf>, - /// List of strings to remove from the report - #[arg(long)] - redact: Vec<String>, - }, +#[cfg(not(target_os = "android"))] +mod imp { + use clap::Parser; - /// Send collected problem report - Send { - /// Path to a previously collected report file - #[arg(required = true, long, short = 'r')] - report: PathBuf, - /// Email to attach to the problem report - #[arg(long, short = 'e')] - email: Option<String>, - /// Message to include in the problem report - #[arg(long, short = 'm')] - message: Option<String>, - }, -} + use mullvad_api::ApiEndpoint; + + use mullvad_problem_report::{Error, ProblemReportCollector, WriteSource}; + + use std::{ + env, io, + path::{Path, PathBuf}, + process, + }; + + use talpid_types::ErrorExt; + + pub(crate) fn main() { + process::exit(match run() { + Ok(()) => 0, + Err(error) => { + eprintln!("{}", error.display_chain()); + 1 + } + }) + } -fn run() -> Result<(), Error> { - tracing_subscriber::fmt::init(); + #[derive(Debug, Parser)] + #[command(author, version = mullvad_version::VERSION, about, long_about = None)] + #[command( + arg_required_else_help = true, + disable_help_subcommand = true, + disable_version_flag = true + )] + pub(crate) enum Cli { + /// Collect problem report to a single file + Collect { + /// The destination path for saving the collected report + #[arg(required = true, long, short = 'o')] + output: String, + /// Paths to additional log files to be included + extra_logs: Vec<PathBuf>, + /// List of strings to remove from the report + #[arg(long)] + redact: Vec<String>, + }, - match Cli::parse() { - Cli::Collect { - output, - extra_logs, - redact, - } => { - let collector = ProblemReportCollector { + /// Send collected problem report + Send { + /// Path to a previously collected report file + #[arg(required = true, long, short = 'r')] + report: PathBuf, + /// Email to attach to the problem report + #[arg(long, short = 'e')] + email: Option<String>, + /// Message to include in the problem report + #[arg(long, short = 'm')] + message: Option<String>, + }, + } + + pub(crate) fn run() -> Result<(), Error> { + tracing_subscriber::fmt::init(); + + match Cli::parse() { + Cli::Collect { + output, extra_logs, - redact_custom_strings: redact, - }; - if output != "-" { - collector.write_to_path(&output)?; + redact, + } => { + let collector = ProblemReportCollector { + extra_logs, + redact_custom_strings: redact, + }; + if output != "-" { + collector.write_to_path(&output)?; - println!("Problem report written to {output}"); - println!(); - println!("Send the problem report to support via the send subcommand. See:"); - println!(" $ {} send --help", env::args().next().unwrap()); - } else { - // Write logs to stdout - collector.write(WriteSource::from((io::stdout(), "stdout".to_owned())))?; + println!("Problem report written to {output}"); + println!(); + println!("Send the problem report to support via the send subcommand. See:"); + println!(" $ {} send --help", env::args().next().unwrap()); + } else { + // Write logs to stdout + collector.write(WriteSource::from((io::stdout(), "stdout".to_owned())))?; + } + } + Cli::Send { + report, + email, + message, + } => { + send_problem_report( + &email.unwrap_or_default(), + &message.unwrap_or_default(), + None, + &report, + )?; } } - Cli::Send { - report, - email, - message, - } => { - send_problem_report( - &email.unwrap_or_default(), - &message.unwrap_or_default(), - None, - &report, - )?; - } - } - Ok(()) -} + Ok(()) + } -fn send_problem_report( - user_email: &str, - user_message: &str, - account_token: Option<&str>, - report_path: &Path, -) -> Result<(), Error> { - let cache_dir = mullvad_paths::get_cache_dir().map_err(Error::ObtainCacheDirectory)?; - mullvad_problem_report::send_problem_report( - user_email, - user_message, - account_token, - report_path, - &cache_dir, - ApiEndpoint::from_env_vars(), - ) - .inspect_err(|error| { - eprintln!("{}", error.display_chain()); - })?; + pub(crate) fn send_problem_report( + user_email: &str, + user_message: &str, + account_token: Option<&str>, + report_path: &Path, + ) -> Result<(), Error> { + let cache_dir = mullvad_paths::get_cache_dir().map_err(Error::ObtainCacheDirectory)?; + mullvad_problem_report::send_problem_report( + user_email, + user_message, + account_token, + report_path, + &cache_dir, + ApiEndpoint::from_env_vars(), + ) + .inspect_err(|error| { + eprintln!("{}", error.display_chain()); + })?; - println!("Problem report sent"); - Ok(()) + println!("Problem report sent"); + Ok(()) + } } diff --git a/mullvad-setup/src/main.rs b/mullvad-setup/src/main.rs index 8bb83777ba..23ccbfe9d4 100644 --- a/mullvad-setup/src/main.rs +++ b/mullvad-setup/src/main.rs @@ -1,232 +1,248 @@ -use clap::Parser; -use mullvad_api::{ApiEndpoint, DEVICE_NOT_FOUND, proxy::ApiConnectionMode}; -use mullvad_management_interface::MullvadProxyClient; -use mullvad_version::Version; -use std::{path::PathBuf, process, str::FromStr, sync::LazyLock, time::Duration}; -use talpid_core::firewall::{self, Firewall}; -use talpid_future::retry::{ConstantInterval, retry_future}; -use talpid_types::ErrorExt; -use tracing_subscriber::{EnvFilter, filter::LevelFilter}; - #[cfg(target_os = "windows")] -mod service; +pub(crate) mod service; -static APP_VERSION: LazyLock<Version> = - LazyLock::new(|| Version::from_str(mullvad_version::VERSION).unwrap()); +fn main() { + #[cfg(not(target_os = "android"))] + imp::main() +} -const DEVICE_REMOVAL_STRATEGY: ConstantInterval = ConstantInterval::new(Duration::ZERO, Some(5)); +#[cfg(not(target_os = "android"))] +mod imp { + use clap::Parser; -#[repr(i32)] -enum ExitStatus { - Ok = 0, - Error = 1, - VersionNotOlder = 2, - DaemonNotRunning = 3, -} + use mullvad_api::{ApiEndpoint, DEVICE_NOT_FOUND, proxy::ApiConnectionMode}; + + use mullvad_management_interface::MullvadProxyClient; + + use mullvad_version::Version; + + use std::{path::PathBuf, process, str::FromStr, sync::LazyLock, time::Duration}; + + use talpid_core::firewall::{self, Firewall}; + + use talpid_future::retry::{ConstantInterval, retry_future}; + + use talpid_types::ErrorExt; + + use tracing_subscriber::{EnvFilter, filter::LevelFilter}; + + pub(crate) static APP_VERSION: LazyLock<Version> = + LazyLock::new(|| Version::from_str(mullvad_version::VERSION).unwrap()); + + pub(crate) const DEVICE_REMOVAL_STRATEGY: ConstantInterval = + ConstantInterval::new(Duration::ZERO, Some(5)); -impl From<Error> for ExitStatus { - fn from(error: Error) -> ExitStatus { - match error { - Error::RpcConnectionError(_) => ExitStatus::DaemonNotRunning, - _ => ExitStatus::Error, + #[repr(i32)] + pub(crate) enum ExitStatus { + Ok = 0, + Error = 1, + VersionNotOlder = 2, + DaemonNotRunning = 3, + } + + impl From<Error> for ExitStatus { + fn from(error: Error) -> ExitStatus { + match error { + Error::RpcConnection(_) => ExitStatus::DaemonNotRunning, + _ => ExitStatus::Error, + } } } -} -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error("Failed to connect to RPC client")] - RpcConnectionError(#[source] mullvad_management_interface::Error), + #[derive(thiserror::Error, Debug)] + pub enum Error { + #[error("Failed to connect to RPC client")] + RpcConnection(#[source] mullvad_management_interface::Error), - #[error("RPC call failed")] - DaemonRpcError(#[source] mullvad_management_interface::Error), + #[error("RPC call failed")] + DaemonRpc(#[source] mullvad_management_interface::Error), - #[error("This command cannot be run if the daemon is active")] - DaemonIsRunning, + #[error("This command cannot be run if the daemon is active")] + DaemonIsRunning, - #[error("Firewall error")] - FirewallError(#[source] firewall::Error), + #[error("Firewall error")] + Firewall(#[source] firewall::Error), - #[error("Failed to initialize mullvad RPC runtime")] - RpcInitializationError(#[source] mullvad_api::Error), + #[error("Failed to initialize mullvad RPC runtime")] + RpcInitialization(#[source] mullvad_api::Error), - #[error("Failed to remove device from account")] - RemoveDeviceError(#[source] mullvad_api::rest::Error), + #[error("Failed to remove device from account")] + RemoveDevice(#[source] mullvad_api::rest::Error), - #[error("Failed to obtain settings directory path")] - SettingsPathError(#[source] mullvad_paths::Error), + #[error("Failed to obtain settings directory path")] + SettingsPath(#[source] mullvad_paths::Error), - #[error("Failed to obtain cache directory path")] - CachePathError(#[source] mullvad_paths::Error), + #[error("Failed to obtain cache directory path")] + CachePath(#[source] mullvad_paths::Error), - #[error("Failed to read the device cache")] - ReadDeviceCacheError(#[source] mullvad_daemon::device::Error), + #[error("Failed to read the device cache")] + ReadDeviceCache(#[source] mullvad_daemon::device::Error), - #[error("Failed to write the device cache")] - WriteDeviceCacheError(#[source] mullvad_daemon::device::Error), + #[error("Failed to write the device cache")] + WriteDeviceCache(#[source] mullvad_daemon::device::Error), - #[error("Cannot parse the version string")] - ParseVersionStringError, + #[error("Cannot parse the version string")] + ParseVersionString, - #[cfg(target_os = "windows")] - #[error("Failed to start system service")] - StartService(#[source] windows_service::Error), + #[cfg(target_os = "windows")] + #[error("Failed to start system service")] + StartService(#[source] windows_service::Error), - #[cfg(target_os = "windows")] - #[error("Failed to query system service")] - QueryServiceStatus(#[source] windows_service::Error), + #[cfg(target_os = "windows")] + #[error("Failed to query system service")] + QueryServiceStatus(#[source] windows_service::Error), - #[cfg(target_os = "windows")] - #[error("Starting system service timed out")] - StartServiceTimeout, + #[cfg(target_os = "windows")] + #[error("Starting system service timed out")] + StartServiceTimeout, - #[cfg(target_os = "windows")] - #[error("Failed to open service")] - OpenService(#[source] windows_service::Error), + #[cfg(target_os = "windows")] + #[error("Failed to open service")] + OpenService(#[source] windows_service::Error), - #[cfg(target_os = "windows")] - #[error("Failed to open service control manager")] - OpenServiceControlManager(#[source] windows_service::Error), -} + #[cfg(target_os = "windows")] + #[error("Failed to open service control manager")] + OpenServiceControlManager(#[source] windows_service::Error), + } -#[derive(Debug, Parser)] -#[command(author, version = mullvad_version::VERSION, about, long_about = None)] -#[command(propagate_version = true)] -#[command( - arg_required_else_help = true, - disable_help_subcommand = true, - disable_version_flag = true -)] -enum Cli { - /// Move a running daemon into a blocking state and save its target state - PrepareRestart, - /// Remove any firewall rules introduced by the daemon - ResetFirewall, - /// Remove the current device from the active account - RemoveDevice, - /// Checks whether the given version is older than the current version - IsOlderVersion { - /// Version string to compare the current version - #[arg(required = true)] - old_version: String, - }, - /// Start the Mullvad daemon service - #[cfg(target_os = "windows")] - StartService, -} + #[derive(Debug, Parser)] + #[command(author, version = mullvad_version::VERSION, about, long_about = None)] + #[command(propagate_version = true)] + #[command( + arg_required_else_help = true, + disable_help_subcommand = true, + disable_version_flag = true + )] + pub(crate) enum Cli { + /// Move a running daemon into a blocking state and save its target state + PrepareRestart, + /// Remove any firewall rules introduced by the daemon + ResetFirewall, + /// Remove the current device from the active account + RemoveDevice, + /// Checks whether the given version is older than the current version + IsOlderVersion { + /// Version string to compare the current version + #[arg(required = true)] + old_version: String, + }, + /// Start the Mullvad daemon service + #[cfg(target_os = "windows")] + StartService, + } -#[tokio::main] -async fn main() { - tracing_subscriber::fmt() - .with_env_filter(EnvFilter::from_default_env().add_directive(LevelFilter::INFO.into())) - .init(); + #[tokio::main] + pub(crate) async fn main() { + tracing_subscriber::fmt() + .with_env_filter(EnvFilter::from_default_env().add_directive(LevelFilter::INFO.into())) + .init(); - let result = match Cli::parse() { - Cli::PrepareRestart => prepare_restart().await, - Cli::ResetFirewall => reset_firewall().await, - Cli::RemoveDevice => remove_device().await, - Cli::IsOlderVersion { old_version } => { - match is_older_version(&old_version) { - // Returning exit status - Ok(status) => process::exit(status as i32), - Err(error) => Err(error), + let result = match Cli::parse() { + Cli::PrepareRestart => prepare_restart().await, + Cli::ResetFirewall => reset_firewall().await, + Cli::RemoveDevice => remove_device().await, + Cli::IsOlderVersion { old_version } => { + match is_older_version(&old_version) { + // Returning exit status + Ok(status) => process::exit(status as i32), + Err(error) => Err(error), + } } - } - #[cfg(target_os = "windows")] - Cli::StartService => service::start().await, - }; + #[cfg(target_os = "windows")] + Cli::StartService => crate::service::start().await, + }; - if let Err(e) = result { - eprintln!("{}", e.display_chain()); - process::exit(ExitStatus::from(e) as i32); + if let Err(e) = result { + eprintln!("{}", e.display_chain()); + process::exit(ExitStatus::from(e) as i32); + } } -} -fn is_older_version(old_version: &str) -> Result<ExitStatus, Error> { - let parsed_version = - Version::from_str(old_version).map_err(|_| Error::ParseVersionStringError)?; + pub(crate) fn is_older_version(old_version: &str) -> Result<ExitStatus, Error> { + let parsed_version = + Version::from_str(old_version).map_err(|_| Error::ParseVersionString)?; - Ok(if *APP_VERSION > parsed_version { - ExitStatus::Ok - } else { - ExitStatus::VersionNotOlder - }) -} + Ok(if *APP_VERSION > parsed_version { + ExitStatus::Ok + } else { + ExitStatus::VersionNotOlder + }) + } -async fn prepare_restart() -> Result<(), Error> { - let mut rpc = MullvadProxyClient::new() - .await - .map_err(Error::RpcConnectionError)?; - rpc.prepare_restart().await.map_err(Error::DaemonRpcError)?; - Ok(()) -} + pub(crate) async fn prepare_restart() -> Result<(), Error> { + let mut rpc = MullvadProxyClient::new() + .await + .map_err(Error::RpcConnection)?; + rpc.prepare_restart().await.map_err(Error::DaemonRpc)?; + Ok(()) + } -async fn reset_firewall() -> Result<(), Error> { - // Ensure that the daemon isn't running - if MullvadProxyClient::new().await.is_ok() { - return Err(Error::DaemonIsRunning); + pub(crate) async fn reset_firewall() -> Result<(), Error> { + // Ensure that the daemon isn't running + if MullvadProxyClient::new().await.is_ok() { + return Err(Error::DaemonIsRunning); + } + + Firewall::new( + #[cfg(target_os = "linux")] + mullvad_types::TUNNEL_FWMARK, + #[cfg(target_os = "linux")] + None, + // TODO split-tunneling? + #[cfg(target_os = "linux")] + None, + ) + .map_err(Error::Firewall)? + .reset_policy() + .map_err(Error::Firewall) } - Firewall::new( - #[cfg(target_os = "linux")] - mullvad_types::TUNNEL_FWMARK, - #[cfg(target_os = "linux")] - None, - // TODO split-tunneling? - #[cfg(target_os = "linux")] - None, - ) - .map_err(Error::FirewallError)? - .reset_policy() - .map_err(Error::FirewallError) -} + pub(crate) async fn remove_device() -> Result<(), Error> { + let (cache_path, settings_path) = get_paths()?; + let (cacher, state) = mullvad_daemon::device::DeviceCacher::new(&settings_path) + .await + .map_err(Error::ReadDeviceCache)?; + if let Some(device) = state.into_device() { + let api_runtime = + mullvad_api::Runtime::with_cache(&ApiEndpoint::from_env_vars(), &cache_path, false) + .await + .map_err(Error::RpcInitialization)?; -async fn remove_device() -> Result<(), Error> { - let (cache_path, settings_path) = get_paths()?; - let (cacher, state) = mullvad_daemon::device::DeviceCacher::new(&settings_path) - .await - .map_err(Error::ReadDeviceCacheError)?; - if let Some(device) = state.into_device() { - let api_runtime = - mullvad_api::Runtime::with_cache(&ApiEndpoint::from_env_vars(), &cache_path, false) - .await - .map_err(Error::RpcInitializationError)?; + let connection_mode = ApiConnectionMode::try_from_cache(&cache_path).await; + let proxy = mullvad_api::DevicesProxy::new( + api_runtime.mullvad_rest_handle(connection_mode.into_provider()), + ); - let connection_mode = ApiConnectionMode::try_from_cache(&cache_path).await; - let proxy = mullvad_api::DevicesProxy::new( - api_runtime.mullvad_rest_handle(connection_mode.into_provider()), - ); + let device_removal = retry_future( + move || proxy.remove(device.account_number.clone(), device.device.id.clone()), + move |result| match result { + Err(error) => error.is_network_error(), + _ => false, + }, + DEVICE_REMOVAL_STRATEGY, + ) + .await; - let device_removal = retry_future( - move || proxy.remove(device.account_number.clone(), device.device.id.clone()), - move |result| match result { - Err(error) => error.is_network_error(), - _ => false, - }, - DEVICE_REMOVAL_STRATEGY, - ) - .await; + // `DEVICE_NOT_FOUND` is not considered to be an error in this context. + match device_removal { + Ok(_) => Ok(()), + Err(mullvad_api::rest::Error::ApiError(_status, code)) + if code == DEVICE_NOT_FOUND => + { + Ok(()) + } + Err(e) => Err(Error::RemoveDevice(e)), + }?; - // `DEVICE_NOT_FOUND` is not considered to be an error in this context. - match device_removal { - Ok(_) => Ok(()), - Err(mullvad_api::rest::Error::ApiError(_status, code)) if code == DEVICE_NOT_FOUND => { - Ok(()) - } - Err(e) => Err(Error::RemoveDeviceError(e)), - }?; + cacher.remove().await.map_err(Error::WriteDeviceCache)?; + } - cacher - .remove() - .await - .map_err(Error::WriteDeviceCacheError)?; + Ok(()) } - Ok(()) -} - -fn get_paths() -> Result<(PathBuf, PathBuf), Error> { - let cache_path = mullvad_paths::cache_dir().map_err(Error::CachePathError)?; - let settings_path = mullvad_paths::settings_dir().map_err(Error::SettingsPathError)?; - Ok((cache_path, settings_path)) + pub(crate) fn get_paths() -> Result<(PathBuf, PathBuf), Error> { + let cache_path = mullvad_paths::cache_dir().map_err(Error::CachePath)?; + let settings_path = mullvad_paths::settings_dir().map_err(Error::SettingsPath)?; + Ok((cache_path, settings_path)) + } } diff --git a/mullvad-types/src/features.rs b/mullvad-types/src/features.rs index 45c3cc2bfc..25d8b1d290 100644 --- a/mullvad-types/src/features.rs +++ b/mullvad-types/src/features.rs @@ -248,8 +248,11 @@ mod tests { If this is not true anymore, please update this test." ); - settings.lockdown_mode = true; - expected_indicators.0.insert(FeatureIndicator::LockdownMode); + #[cfg(not(target_os = "android"))] + { + settings.lockdown_mode = true; + expected_indicators.0.insert(FeatureIndicator::LockdownMode); + } assert_eq!( compute_feature_indicators(&settings, &endpoint, false), |
