summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--mullvad-api/src/bin/relay_list.rs1
-rw-r--r--mullvad-cli/src/main.rs372
-rw-r--r--mullvad-daemon/src/device/mod.rs5
-rw-r--r--mullvad-daemon/src/main.rs428
-rw-r--r--mullvad-daemon/src/migrations/v11.rs2
-rw-r--r--mullvad-daemon/src/migrations/v9.rs19
-rw-r--r--mullvad-daemon/src/settings/mod.rs4
-rw-r--r--mullvad-problem-report/src/main.rs220
-rw-r--r--mullvad-setup/src/main.rs394
-rw-r--r--mullvad-types/src/features.rs7
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),