summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJanito Vaqueiro Ferreira Filho <janito@mullvad.net>2020-04-22 11:04:00 -0300
committerJanito Vaqueiro Ferreira Filho <janito@mullvad.net>2020-04-22 11:04:00 -0300
commitb8b36573ed691c704044dff9f22774337a44879d (patch)
treeaf6ce3d69f484426cfd4ac2cee2150c871b1036b
parent106937e21d66b19a9a291e7014b9f81a412e9933 (diff)
parentb18ab26965d4b7cff95e950995bec53ebff5c47e (diff)
downloadmullvadvpn-b8b36573ed691c704044dff9f22774337a44879d.tar.xz
mullvadvpn-b8b36573ed691c704044dff9f22774337a44879d.zip
Merge branch 'dynamic-app-path'
-rw-r--r--CHANGELOG.md2
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/MullvadProblemReport.kt18
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/service/FileMigrator.kt18
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/service/FileResourceExtractor.kt12
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadDaemon.kt8
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt27
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt6
-rw-r--r--mullvad-cli/src/cmds/auto_connect.rs2
-rw-r--r--mullvad-cli/src/cmds/beta_program.rs2
-rw-r--r--mullvad-cli/src/cmds/block_when_disconnected.rs2
-rw-r--r--mullvad-cli/src/cmds/bridge.rs2
-rw-r--r--mullvad-cli/src/cmds/lan.rs2
-rw-r--r--mullvad-cli/src/cmds/tunnel.rs2
-rw-r--r--mullvad-cli/src/cmds/version.rs2
-rw-r--r--mullvad-daemon/src/lib.rs64
-rw-r--r--mullvad-daemon/src/main.rs3
-rw-r--r--mullvad-daemon/src/management_interface.rs9
-rw-r--r--mullvad-daemon/src/settings.rs332
-rw-r--r--mullvad-jni/src/lib.rs98
-rw-r--r--mullvad-problem-report/src/lib.rs22
-rw-r--r--mullvad-types/src/settings/mod.rs232
21 files changed, 483 insertions, 382 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9c69a42001..318deb91bc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -35,6 +35,8 @@ Line wrap the file at 100 chars. Th
#### Android
- Change button colors on problem report no email confirmation dialog to match the desktop version.
+- Fix crash when attempting to run app from the non-default location, such as the SD card or from a
+ different user profile.
### Security
#### macOS
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/MullvadProblemReport.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/MullvadProblemReport.kt
index 40e93ff882..66a55503d6 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/MullvadProblemReport.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/MullvadProblemReport.kt
@@ -7,9 +7,11 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
-const val PROBLEM_REPORT_PATH = "/data/data/net.mullvad.mullvadvpn/problem_report.txt"
+const val PROBLEM_REPORT_FILE = "problem_report.txt"
+
+class MullvadProblemReport(val logDirectory: File) {
+ private val problemReportPath = File(logDirectory, PROBLEM_REPORT_FILE)
-class MullvadProblemReport {
private var collectJob: Deferred<Boolean>? = null
private var sendJob: Deferred<Boolean>? = null
@@ -38,7 +40,7 @@ class MullvadProblemReport {
if (!isActive) {
collectJob = GlobalScope.async(Dispatchers.Default) {
deleteReportFile()
- collectReport(PROBLEM_REPORT_PATH)
+ collectReport(logDirectory.absolutePath, problemReportPath.absolutePath)
}
}
}
@@ -51,7 +53,11 @@ class MullvadProblemReport {
if (currentJob == null || currentJob.isCompleted) {
currentJob = GlobalScope.async(Dispatchers.Default) {
val result = (collectJob?.await() ?: false) &&
- sendProblemReport(userEmail, userMessage, PROBLEM_REPORT_PATH)
+ sendProblemReport(
+ userEmail,
+ userMessage,
+ problemReportPath.absolutePath
+ )
if (result) {
deleteReportFile()
@@ -68,10 +74,10 @@ class MullvadProblemReport {
}
fun deleteReportFile() {
- File(PROBLEM_REPORT_PATH).delete()
+ problemReportPath.delete()
}
- private external fun collectReport(reportPath: String): Boolean
+ private external fun collectReport(logDirectory: String, reportPath: String): Boolean
private external fun sendProblemReport(
userEmail: String,
userMessage: String,
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/FileMigrator.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/FileMigrator.kt
new file mode 100644
index 0000000000..cd325d8a6f
--- /dev/null
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/FileMigrator.kt
@@ -0,0 +1,18 @@
+package net.mullvad.mullvadvpn.service
+
+import android.util.Log
+import java.io.File
+
+class FileMigrator(val oldDirectory: File, val newDirectory: File) {
+ fun migrate(fileName: String) {
+ try {
+ val oldPath = File(oldDirectory, fileName)
+
+ if (oldPath.exists()) {
+ oldPath.renameTo(File(newDirectory, fileName))
+ }
+ } catch (exception: Exception) {
+ Log.w("mullvad", "Failed to migrate $fileName from $oldDirectory to $newDirectory")
+ }
+ }
+}
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/FileResourceExtractor.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/FileResourceExtractor.kt
index aac6175ec6..1ab65fa850 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/FileResourceExtractor.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/FileResourceExtractor.kt
@@ -4,14 +4,16 @@ import android.content.Context
import java.io.File
import java.io.FileOutputStream
-class FileResourceExtractor(val asset: String, val destination: String) {
- fun extract(context: Context) {
- if (!File(destination).exists()) {
- extractFile(context)
+class FileResourceExtractor(val context: Context) {
+ fun extract(asset: String) {
+ val destination = File(context.filesDir, asset)
+
+ if (!destination.exists()) {
+ extractFile(asset, destination)
}
}
- private fun extractFile(context: Context) {
+ private fun extractFile(asset: String, destination: File) {
val destinationStream = FileOutputStream(destination)
context
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadDaemon.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadDaemon.kt
index 60dce6ff10..9c34ecb5d7 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadDaemon.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadDaemon.kt
@@ -24,7 +24,7 @@ class MullvadDaemon(val vpnService: MullvadVpnService) {
init {
System.loadLibrary("mullvad_jni")
- initialize(vpnService)
+ initialize(vpnService, vpnService.cacheDir.absolutePath, vpnService.filesDir.absolutePath)
onSettingsChange.notify(getSettings())
}
@@ -113,7 +113,11 @@ class MullvadDaemon(val vpnService: MullvadVpnService) {
return verifyWireguardKey(daemonInterfaceAddress)
}
- private external fun initialize(vpnService: MullvadVpnService)
+ private external fun initialize(
+ vpnService: MullvadVpnService,
+ cacheDirectory: String,
+ resourceDirectory: String
+ )
private external fun deinitialize()
private external fun connect(daemonInterfaceAddress: Long)
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt
index 4df3ff6791..aba89e9e8a 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt
@@ -4,6 +4,7 @@ import android.content.Intent
import android.net.VpnService
import android.os.Binder
import android.os.IBinder
+import java.io.File
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
@@ -14,10 +15,7 @@ import net.mullvad.talpid.TalpidVpnService
import net.mullvad.talpid.util.EventNotifier
private const val API_ROOT_CA_FILE = "api_root_ca.pem"
-private const val API_ROOT_CA_PATH = "/data/data/net.mullvad.mullvadvpn/api_root_ca.pem"
-
private const val RELAYS_FILE = "relays.json"
-private const val RELAYS_PATH = "/data/data/net.mullvad.mullvadvpn/relays.json"
class MullvadVpnService : TalpidVpnService() {
private enum class PendingAction {
@@ -144,11 +142,7 @@ class MullvadVpnService : TalpidVpnService() {
}
private fun startDaemon() = GlobalScope.launch(Dispatchers.Default) {
- FileResourceExtractor(API_ROOT_CA_FILE, API_ROOT_CA_PATH)
- .extract(application)
-
- FileResourceExtractor(RELAYS_FILE, RELAYS_PATH)
- .extract(application)
+ prepareFiles()
val newDaemon = MullvadDaemon(this@MullvadVpnService).apply {
onSettingsChange.subscribe { settings ->
@@ -191,6 +185,23 @@ class MullvadVpnService : TalpidVpnService() {
))
}
+ private fun prepareFiles() {
+ FileMigrator(File("/data/data/net.mullvad.mullvadvpn"), filesDir).apply {
+ migrate(API_ROOT_CA_FILE)
+ migrate(RELAYS_FILE)
+ migrate("settings.json")
+ migrate("daemon.log")
+ migrate("daemon.old.log")
+ migrate("wireguard.log")
+ migrate("wireguard.old.log")
+ }
+
+ FileResourceExtractor(this).apply {
+ extract(API_ROOT_CA_FILE)
+ extract(RELAYS_FILE)
+ }
+ }
+
private fun stop() {
isStopping = true
stopDaemon()
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt
index 93b85c001b..524d4ab00d 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt
@@ -19,9 +19,10 @@ class MainActivity : FragmentActivity() {
val KEY_SHOULD_CONNECT = "should_connect"
}
- val problemReport = MullvadProblemReport()
val serviceNotifier = EventNotifier<ServiceConnection?>(null)
+ lateinit var problemReport: MullvadProblemReport
+
private var service: MullvadVpnService.LocalBinder? = null
private var serviceConnection: ServiceConnection? = null
private var serviceConnectionSubscription: Int? = null
@@ -64,6 +65,9 @@ class MainActivity : FragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+
+ problemReport = MullvadProblemReport(filesDir)
+
setContentView(R.layout.main)
if (savedInstanceState == null) {
diff --git a/mullvad-cli/src/cmds/auto_connect.rs b/mullvad-cli/src/cmds/auto_connect.rs
index bad4c5c1e6..385a2de7b6 100644
--- a/mullvad-cli/src/cmds/auto_connect.rs
+++ b/mullvad-cli/src/cmds/auto_connect.rs
@@ -49,7 +49,7 @@ impl AutoConnect {
fn get(&self) -> Result<()> {
let mut rpc = new_rpc_client()?;
- let auto_connect = rpc.get_settings()?.get_auto_connect();
+ let auto_connect = rpc.get_settings()?.auto_connect;
println!("Autoconnect: {}", if auto_connect { "on" } else { "off" });
Ok(())
}
diff --git a/mullvad-cli/src/cmds/beta_program.rs b/mullvad-cli/src/cmds/beta_program.rs
index c63027ccfd..602de81db0 100644
--- a/mullvad-cli/src/cmds/beta_program.rs
+++ b/mullvad-cli/src/cmds/beta_program.rs
@@ -29,7 +29,7 @@ impl Command for BetaProgram {
("get", Some(_)) => {
let mut rpc = new_rpc_client()?;
let settings = rpc.get_settings()?;
- let enabled_str = if settings.get_show_beta_releases().unwrap_or(false) {
+ let enabled_str = if settings.show_beta_releases.unwrap_or(false) {
"on"
} else {
"off"
diff --git a/mullvad-cli/src/cmds/block_when_disconnected.rs b/mullvad-cli/src/cmds/block_when_disconnected.rs
index 9d9081a650..cd585720dd 100644
--- a/mullvad-cli/src/cmds/block_when_disconnected.rs
+++ b/mullvad-cli/src/cmds/block_when_disconnected.rs
@@ -49,7 +49,7 @@ impl BlockWhenDisconnected {
fn get(&self) -> Result<()> {
let mut rpc = new_rpc_client()?;
- let block_when_disconnected = rpc.get_settings()?.get_block_when_disconnected();
+ let block_when_disconnected = rpc.get_settings()?.block_when_disconnected;
println!(
"Network traffic will be {} when the VPN is disconnected",
if block_when_disconnected {
diff --git a/mullvad-cli/src/cmds/bridge.rs b/mullvad-cli/src/cmds/bridge.rs
index d0538e0d7c..0e5c339744 100644
--- a/mullvad-cli/src/cmds/bridge.rs
+++ b/mullvad-cli/src/cmds/bridge.rs
@@ -162,7 +162,7 @@ impl Bridge {
let mut rpc = new_rpc_client()?;
let settings = rpc.get_settings()?;
println!("Bridge state - {}", settings.get_bridge_state());
- match settings.get_bridge_settings() {
+ match settings.bridge_settings {
BridgeSettings::Custom(proxy) => {
match proxy {
openvpn::ProxySettings::Local(local_proxy) => {
diff --git a/mullvad-cli/src/cmds/lan.rs b/mullvad-cli/src/cmds/lan.rs
index 3a29bdd226..15d30cde50 100644
--- a/mullvad-cli/src/cmds/lan.rs
+++ b/mullvad-cli/src/cmds/lan.rs
@@ -49,7 +49,7 @@ impl Lan {
fn get(&self) -> Result<()> {
let mut rpc = new_rpc_client()?;
- let allow_lan = rpc.get_settings()?.get_allow_lan();
+ let allow_lan = rpc.get_settings()?.allow_lan;
println!(
"Local network sharing setting: {}",
if allow_lan { "allow" } else { "block" }
diff --git a/mullvad-cli/src/cmds/tunnel.rs b/mullvad-cli/src/cmds/tunnel.rs
index 8b995cdc6b..5349174d4c 100644
--- a/mullvad-cli/src/cmds/tunnel.rs
+++ b/mullvad-cli/src/cmds/tunnel.rs
@@ -258,7 +258,7 @@ impl Tunnel {
fn get_tunnel_options() -> Result<TunnelOptions> {
let mut rpc = new_rpc_client()?;
- Ok(rpc.get_settings()?.get_tunnel_options().clone())
+ Ok(rpc.get_settings()?.tunnel_options)
}
fn process_openvpn_mssfix_unset() -> Result<()> {
diff --git a/mullvad-cli/src/cmds/version.rs b/mullvad-cli/src/cmds/version.rs
index 2acde8f76f..99df7b6d19 100644
--- a/mullvad-cli/src/cmds/version.rs
+++ b/mullvad-cli/src/cmds/version.rs
@@ -20,7 +20,7 @@ impl Command for Version {
println!("\tIs supported: {}", version_info.current_is_supported);
let settings = rpc.get_settings()?;
- let is_updated = if settings.get_show_beta_releases().unwrap_or(false) {
+ let is_updated = if settings.show_beta_releases.unwrap_or(false) {
version_info.latest == current_version
} else {
version_info.latest_stable == current_version
diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs
index 79cb4d192d..6dd6812dd2 100644
--- a/mullvad-daemon/src/lib.rs
+++ b/mullvad-daemon/src/lib.rs
@@ -37,11 +37,12 @@ use mullvad_types::{
RelaySettingsUpdate,
},
relay_list::{Relay, RelayList},
+ settings::Settings,
states::{TargetState, TunnelState},
version::{AppVersion, AppVersionInfo},
wireguard::KeygenEvent,
};
-use settings::Settings;
+use settings::SettingsPersister;
#[cfg(not(target_os = "android"))]
use std::path::Path;
use std::{
@@ -436,7 +437,7 @@ pub struct Daemon<L: EventListener> {
tx: DaemonEventSender,
reconnection_loop_tx: Option<mpsc::Sender<()>>,
event_listener: L,
- settings: Settings,
+ settings: SettingsPersister,
account_history: account_history::AccountHistory,
wg_key_proxy: WireguardKeyProxy<HttpHandle>,
accounts_proxy: AccountsProxy<HttpHandle>,
@@ -460,6 +461,7 @@ where
pub fn start(
log_dir: Option<PathBuf>,
resource_dir: PathBuf,
+ settings_dir: PathBuf,
cache_dir: PathBuf,
event_listener: L,
command_channel: DaemonCommandChannel,
@@ -506,9 +508,9 @@ where
);
tokio_remote.spawn(|_| version_check_future);
- let mut settings = settings::load();
+ let mut settings = SettingsPersister::load(&settings_dir);
- if version::is_beta_version() && settings.get_show_beta_releases().is_none() {
+ if version::is_beta_version() && settings.show_beta_releases.is_none() {
let _ = settings.set_show_beta_releases(true);
}
@@ -543,8 +545,8 @@ where
tx: internal_event_tx.clone(),
};
let tunnel_command_tx = tunnel_state_machine::spawn(
- settings.get_allow_lan(),
- settings.get_block_when_disconnected(),
+ settings.allow_lan,
+ settings.block_when_disconnected,
tunnel_parameters_generator,
log_dir,
resource_dir,
@@ -566,7 +568,7 @@ where
relay_selector.update();
let initial_target_state = if settings.get_account_token().is_some() {
- if settings.get_auto_connect() {
+ if settings.auto_connect {
// Note: Auto-connect overrides the cached target state
info!("Automatically connecting since auto-connect is turned on");
TargetState::Secured
@@ -611,7 +613,7 @@ where
token,
daemon
.settings
- .get_tunnel_options()
+ .tunnel_options
.wireguard
.automatic_rotation
.map(|hours| Duration::from_secs(60u64 * 60u64 * hours as u64)),
@@ -757,7 +759,7 @@ where
self.last_generated_relay = None;
custom_relay
// TODO(emilsp): generate proxy settings for custom tunnels
- .to_tunnel_parameters(self.settings.get_tunnel_options().clone(), None)
+ .to_tunnel_parameters(self.settings.tunnel_options.clone(), None)
.map_err(|e| {
log::error!("Failed to resolve hostname for custom tunnel config: {}", e);
ParameterGenerationError::CustomTunnelHostResultionError
@@ -819,12 +821,12 @@ where
account_token: String,
retry_attempt: u32,
) -> Result<TunnelParameters, Error> {
- let tunnel_options = self.settings.get_tunnel_options().clone();
+ let tunnel_options = self.settings.tunnel_options.clone();
let location = relay.location.as_ref().expect("Relay has no location set");
self.last_generated_bridge_relay = None;
match endpoint {
MullvadEndpoint::OpenVpn(endpoint) => {
- let proxy_settings = match self.settings.get_bridge_settings() {
+ let proxy_settings = match &self.settings.bridge_settings {
BridgeSettings::Normal(settings) => {
let bridge_constraints = InternalBridgeConstraints {
location: settings.location.clone(),
@@ -1254,7 +1256,8 @@ where
fn set_account(&mut self, account_token: Option<String>) -> Result<bool, settings::Error> {
let account_changed = self.settings.set_account_token(account_token.clone())?;
if account_changed {
- self.event_listener.notify_settings(self.settings.clone());
+ self.event_listener
+ .notify_settings(self.settings.to_settings());
// Bump account history if a token was set
if let Some(token) = account_token.clone() {
@@ -1354,7 +1357,8 @@ where
Ok(settings_changed) => {
Self::oneshot_send(tx, (), "update_relay_settings response");
if settings_changed {
- self.event_listener.notify_settings(self.settings.clone());
+ self.event_listener
+ .notify_settings(self.settings.to_settings());
info!("Initiating tunnel restart because the relay settings changed");
self.reconnect_tunnel();
}
@@ -1369,7 +1373,8 @@ where
Ok(settings_changed) => {
Self::oneshot_send(tx, (), "set_allow_lan response");
if settings_changed {
- self.event_listener.notify_settings(self.settings.clone());
+ self.event_listener
+ .notify_settings(self.settings.to_settings());
self.send_tunnel_command(TunnelCommand::AllowLan(allow_lan));
}
}
@@ -1383,7 +1388,8 @@ where
Ok(settings_changed) => {
Self::oneshot_send(tx, (), "set_show_beta_releases response");
if settings_changed {
- self.event_listener.notify_settings(self.settings.clone());
+ self.event_listener
+ .notify_settings(self.settings.to_settings());
}
}
Err(e) => error!("{}", e.display_chain_with_msg("Unable to save settings")),
@@ -1402,7 +1408,8 @@ where
Ok(settings_changed) => {
Self::oneshot_send(tx, (), "set_block_when_disconnected response");
if settings_changed {
- self.event_listener.notify_settings(self.settings.clone());
+ self.event_listener
+ .notify_settings(self.settings.to_settings());
self.send_tunnel_command(TunnelCommand::BlockWhenDisconnected(
block_when_disconnected,
));
@@ -1418,7 +1425,8 @@ where
Ok(settings_changed) => {
Self::oneshot_send(tx, (), "set auto-connect response");
if settings_changed {
- self.event_listener.notify_settings(self.settings.clone());
+ self.event_listener
+ .notify_settings(self.settings.to_settings());
}
}
Err(e) => error!("{}", e.display_chain_with_msg("Unable to save settings")),
@@ -1431,7 +1439,8 @@ where
Ok(settings_changed) => {
Self::oneshot_send(tx, (), "set_openvpn_mssfix response");
if settings_changed {
- self.event_listener.notify_settings(self.settings.clone());
+ self.event_listener
+ .notify_settings(self.settings.to_settings());
if let Some(TunnelType::OpenVpn) = self.get_connected_tunnel_type() {
info!(
"Initiating tunnel restart because the OpenVPN mssfix setting changed"
@@ -1452,7 +1461,8 @@ where
match self.settings.set_bridge_settings(new_settings) {
Ok(settings_changes) => {
if settings_changes {
- self.event_listener.notify_settings(self.settings.clone());
+ self.event_listener
+ .notify_settings(self.settings.to_settings());
self.reconnect_tunnel();
};
Self::oneshot_send(tx, Ok(()), "set_bridge_settings");
@@ -1476,7 +1486,8 @@ where
let result = match self.settings.set_bridge_state(bridge_state) {
Ok(settings_changed) => {
if settings_changed {
- self.event_listener.notify_settings(self.settings.clone());
+ self.event_listener
+ .notify_settings(self.settings.to_settings());
log::info!("Initiating tunnel restart because bridge state changed");
self.reconnect_tunnel();
}
@@ -1500,7 +1511,8 @@ where
Ok(settings_changed) => {
Self::oneshot_send(tx, (), "set_enable_ipv6 response");
if settings_changed {
- self.event_listener.notify_settings(self.settings.clone());
+ self.event_listener
+ .notify_settings(self.settings.to_settings());
info!("Initiating tunnel restart because the enable IPv6 setting changed");
self.reconnect_tunnel();
}
@@ -1515,7 +1527,8 @@ where
Ok(settings_changed) => {
Self::oneshot_send(tx, (), "set_wireguard_mtu response");
if settings_changed {
- self.event_listener.notify_settings(self.settings.clone());
+ self.event_listener
+ .notify_settings(self.settings.to_settings());
if let Some(TunnelType::Wireguard) = self.get_connected_tunnel_type() {
info!(
"Initiating tunnel restart because the WireGuard MTU setting changed"
@@ -1548,7 +1561,8 @@ where
);
}
- self.event_listener.notify_settings(self.settings.clone());
+ self.event_listener
+ .notify_settings(self.settings.to_settings());
}
}
Err(e) => error!("{}", e.display_chain_with_msg("Unable to save settings")),
@@ -1624,7 +1638,7 @@ where
&mut self.account_history,
account_token,
self.settings
- .get_tunnel_options()
+ .tunnel_options
.wireguard
.automatic_rotation
.map(|hours| Duration::from_secs(60u64 * 60u64 * hours as u64)),
@@ -1696,7 +1710,7 @@ where
}
fn on_get_settings(&self, tx: oneshot::Sender<Settings>) {
- Self::oneshot_send(tx, self.settings.clone(), "get_settings response");
+ Self::oneshot_send(tx, self.settings.to_settings(), "get_settings response");
}
fn oneshot_send<T>(tx: oneshot::Sender<T>, t: T, msg: &'static str) {
diff --git a/mullvad-daemon/src/main.rs b/mullvad-daemon/src/main.rs
index 8795861dbe..0b02dfbe5e 100644
--- a/mullvad-daemon/src/main.rs
+++ b/mullvad-daemon/src/main.rs
@@ -111,6 +111,8 @@ fn create_daemon(
log_dir: Option<PathBuf>,
) -> Result<Daemon<ManagementInterfaceEventBroadcaster>, String> {
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"))?;
@@ -120,6 +122,7 @@ fn create_daemon(
Daemon::start(
log_dir,
resource_dir,
+ settings_dir,
cache_dir,
event_listener,
command_channel,
diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs
index e0f7a94b4e..00254472be 100644
--- a/mullvad-daemon/src/management_interface.rs
+++ b/mullvad-daemon/src/management_interface.rs
@@ -13,7 +13,7 @@ use mullvad_types::{
location::GeoIpLocation,
relay_constraints::{BridgeSettings, BridgeState, RelaySettingsUpdate},
relay_list::RelayList,
- settings::{self, Settings},
+ settings::Settings,
states::{TargetState, TunnelState},
version, wireguard, DaemonEvent,
};
@@ -582,12 +582,7 @@ impl ManagementInterfaceApi for ManagementInterface {
let future = self
.send_command_to_daemon(DaemonCommand::SetBridgeSettings(tx, bridge_settings))
.and_then(|_| rx.map_err(|_| Error::internal_error()))
- .and_then(|settings_result| {
- settings_result.map_err(|error| match error {
- settings::Error::InvalidProxyData(reason) => Error::invalid_params(reason),
- _ => Error::internal_error(),
- })
- });
+ .and_then(|settings_result| settings_result.map_err(|_| Error::internal_error()));
Box::new(future)
}
diff --git a/mullvad-daemon/src/settings.rs b/mullvad-daemon/src/settings.rs
index df0ee0b996..daf3225913 100644
--- a/mullvad-daemon/src/settings.rs
+++ b/mullvad-daemon/src/settings.rs
@@ -1,80 +1,304 @@
-#[cfg(windows)]
-use log::{error, warn};
+use log::{debug, error, info};
+use mullvad_types::{
+ relay_constraints::{BridgeSettings, BridgeState, RelaySettingsUpdate},
+ settings::Settings,
+};
+use std::{
+ fs::File,
+ io::{self, BufReader, Read},
+ ops::Deref,
+ path::{Path, PathBuf},
+};
+use talpid_types::ErrorExt;
-use log::info;
+#[cfg(not(target_os = "android"))]
+use std::fs;
#[cfg(windows)]
-use mullvad_types::settings::Error as SettingsError;
+use {log::warn, talpid_core::logging::windows::log_sink};
-pub use mullvad_types::settings::*;
-#[cfg(windows)]
-use std::io::ErrorKind;
+static SETTINGS_FILE: &str = "settings.json";
-#[cfg(windows)]
-use talpid_core::logging::windows::log_sink;
-pub fn load() -> Settings {
- match Settings::load() {
- Ok(mut settings) => {
- // Force IPv6 to be enabled on Android
- if cfg!(target_os = "android") {
- let _ = settings.set_enable_ipv6(true);
+#[derive(err_derive::Error, Debug)]
+pub enum Error {
+ #[error(display = "Unable to remove settings file {}", _0)]
+ #[cfg(not(target_os = "android"))]
+ DeleteError(String, #[error(source)] io::Error),
+
+ #[error(display = "Unable to serialize settings to JSON")]
+ SerializeError(#[error(source)] serde_json::Error),
+
+ #[error(display = "Unable to write settings to {}", _0)]
+ WriteError(String, #[error(source)] io::Error),
+}
+
+#[derive(Debug)]
+enum LoadSettingsError {
+ FileNotFound,
+ Other,
+}
+
+
+#[derive(Debug)]
+pub struct SettingsPersister {
+ settings: Settings,
+ path: PathBuf,
+}
+
+impl SettingsPersister {
+ /// Loads user settings from file. If no file is present it returns the defaults.
+ pub fn load(settings_dir: &Path) -> Self {
+ let path = settings_dir.join(SETTINGS_FILE);
+ let (mut settings, mut should_save) = Self::load_settings(&path);
+
+ // Force IPv6 to be enabled on Android
+ if cfg!(target_os = "android") {
+ should_save |=
+ Self::update_field(&mut settings.tunnel_options.generic.enable_ipv6, true);
+ }
+
+ let mut persister = SettingsPersister { settings, path };
+
+ if should_save {
+ if let Err(error) = persister.save() {
+ error!(
+ "{}",
+ error.display_chain_with_msg("Failed to save updated settings")
+ );
}
- settings
}
- #[cfg(windows)]
- Err(SettingsError::ReadError(ref _path, ref e)) if e.kind() == ErrorKind::NotFound => {
- info!(
- "No settings file found. Attempting migration from Windows Update backup location"
- );
- if migrate_after_windows_update() {
- match Settings::load() {
- Ok(settings) => {
- info!("Successfully loaded migrated settings");
- settings
- }
- Err(_) => {
- warn!("Failed to load migrated settings, using defaults");
- Settings::default()
- }
+
+ persister
+ }
+
+ fn load_settings(path: &Path) -> (Settings, bool) {
+ Self::load_settings_from_file(path)
+ .or_else(|error| match error {
+ #[cfg(windows)]
+ LoadSettingsError::FileNotFound => {
+ Self::try_load_settings_after_windows_update(path)
}
+ _ => Err(error),
+ })
+ .unwrap_or_else(|_| {
+ info!("Failed to load settings, using defaults");
+ (Settings::default(), true)
+ })
+ }
+
+ fn load_settings_from_file(path: &Path) -> Result<(Settings, bool), LoadSettingsError> {
+ let file = File::open(path).map_err(|error| {
+ if error.kind() == io::ErrorKind::NotFound {
+ LoadSettingsError::FileNotFound
} else {
- info!("Failed to migrate settings, using defaults");
- Settings::default()
+ LoadSettingsError::Other
+ }
+ })?;
+
+ info!("Loading settings from {}", path.display());
+ let mut settings_bytes = vec![];
+ BufReader::new(file)
+ .read_to_end(&mut settings_bytes)
+ .map_err(|_| LoadSettingsError::Other)?;
+
+ Settings::load_from_bytes(&settings_bytes)
+ .map(|settings| (settings, false))
+ .or_else(|error| {
+ log::error!(
+ "{}",
+ error.display_chain_with_msg("Failed to parse settings file")
+ );
+ Settings::migrate_from_bytes(&settings_bytes).map(|settings| (settings, true))
+ })
+ .map_err(|_| LoadSettingsError::Other)
+ }
+
+ #[cfg(windows)]
+ fn try_load_settings_after_windows_update(
+ path: &Path,
+ ) -> Result<(Settings, bool), LoadSettingsError> {
+ info!("No settings file found. Attempting migration from Windows Update backup location");
+
+ if Self::migrate_after_windows_update() {
+ let result = Self::load_settings_from_file(path);
+
+ match &result {
+ Ok(_) => info!("Successfully loaded migrated settings"),
+ Err(_) => warn!("Failed to load migrated settings, using defaults"),
}
+
+ result
+ } else {
+ Err(LoadSettingsError::Other)
}
- Err(_) => {
- info!("Failed to load settings, using defaults");
- Settings::default()
+ }
+
+ #[cfg(windows)]
+ fn migrate_after_windows_update() -> bool {
+ match unsafe {
+ ffi::WinUtil_MigrateAfterWindowsUpdate(Some(log_sink), b"Settings migrator\0".as_ptr())
+ } {
+ ffi::WinUtilMigrationStatus::Success => {
+ info!("Migration completed successfully");
+ true
+ }
+ ffi::WinUtilMigrationStatus::Aborted => {
+ error!("Migration was aborted to avoid overwriting current settings");
+ false
+ }
+ ffi::WinUtilMigrationStatus::NothingToMigrate => {
+ info!("Could not migrate settings - no backup present");
+ false
+ }
+ ffi::WinUtilMigrationStatus::Failed | _ => {
+ error!("Migration failed");
+ false
+ }
}
}
-}
-#[cfg(windows)]
-fn migrate_after_windows_update() -> bool {
- match unsafe {
- ffi::WinUtil_MigrateAfterWindowsUpdate(Some(log_sink), b"Settings migrator\0".as_ptr())
- } {
- ffi::WinUtilMigrationStatus::Success => {
- info!("Migration completed successfully");
+ /// Serializes the settings and saves them to the file it was loaded from.
+ fn save(&mut self) -> Result<(), Error> {
+ debug!("Writing settings to {}", self.path.display());
+ let mut file = File::create(&self.path)
+ .map_err(|e| Error::WriteError(self.path.display().to_string(), e))?;
+
+ serde_json::to_writer_pretty(&mut file, &self.settings).map_err(Error::SerializeError)?;
+ file.sync_all()
+ .map_err(|e| Error::WriteError(self.path.display().to_string(), e))
+ }
+
+ /// Resets default settings
+ #[cfg(not(target_os = "android"))]
+ pub fn reset(&mut self) -> Result<(), Error> {
+ self.settings = Settings::default();
+ self.save().or_else(|e| {
+ log::error!(
+ "{}",
+ e.display_chain_with_msg("Unable to save default settings")
+ );
+ log::info!("Will attempt to remove settings file");
+ fs::remove_file(&self.path)
+ .map_err(|e| Error::DeleteError(self.path.display().to_string(), e))
+ })
+ }
+
+ pub fn to_settings(&self) -> Settings {
+ self.settings.clone()
+ }
+
+ /// Changes account number to the one given. Also saves the new settings to disk.
+ /// The boolean in the Result indicates if the account token changed or not
+ pub fn set_account_token(&mut self, account_token: Option<String>) -> Result<bool, Error> {
+ let should_save = self.settings.set_account_token(account_token);
+ self.update(should_save)
+ }
+
+ pub fn update_relay_settings(&mut self, update: RelaySettingsUpdate) -> Result<bool, Error> {
+ let should_save = self.settings.update_relay_settings(update);
+ self.update(should_save)
+ }
+
+ pub fn set_allow_lan(&mut self, allow_lan: bool) -> Result<bool, Error> {
+ let should_save = Self::update_field(&mut self.settings.allow_lan, allow_lan);
+ self.update(should_save)
+ }
+
+ pub fn set_block_when_disconnected(
+ &mut self,
+ block_when_disconnected: bool,
+ ) -> Result<bool, Error> {
+ let should_save = Self::update_field(
+ &mut self.settings.block_when_disconnected,
+ block_when_disconnected,
+ );
+ self.update(should_save)
+ }
+
+ pub fn set_auto_connect(&mut self, auto_connect: bool) -> Result<bool, Error> {
+ let should_save = Self::update_field(&mut self.settings.auto_connect, auto_connect);
+ self.update(should_save)
+ }
+
+ pub fn set_openvpn_mssfix(&mut self, openvpn_mssfix: Option<u16>) -> Result<bool, Error> {
+ let should_save = Self::update_field(
+ &mut self.settings.tunnel_options.openvpn.mssfix,
+ openvpn_mssfix,
+ );
+ self.update(should_save)
+ }
+
+ pub fn set_enable_ipv6(&mut self, enable_ipv6: bool) -> Result<bool, Error> {
+ let should_save = Self::update_field(
+ &mut self.settings.tunnel_options.generic.enable_ipv6,
+ enable_ipv6,
+ );
+ self.update(should_save)
+ }
+
+ pub fn set_wireguard_mtu(&mut self, mtu: Option<u16>) -> Result<bool, Error> {
+ let should_save = Self::update_field(&mut self.settings.tunnel_options.wireguard.mtu, mtu);
+ self.update(should_save)
+ }
+
+ pub fn set_wireguard_rotation_interval(
+ &mut self,
+ automatic_rotation: Option<u32>,
+ ) -> Result<bool, Error> {
+ let should_save = Self::update_field(
+ &mut self.settings.tunnel_options.wireguard.automatic_rotation,
+ automatic_rotation,
+ );
+ self.update(should_save)
+ }
+
+ pub fn set_show_beta_releases(&mut self, show_beta_releases: bool) -> Result<bool, Error> {
+ let should_save = Self::update_field(
+ &mut self.settings.show_beta_releases,
+ Some(show_beta_releases),
+ );
+ self.update(should_save)
+ }
+
+ pub fn set_bridge_settings(&mut self, bridge_settings: BridgeSettings) -> Result<bool, Error> {
+ let should_save = Self::update_field(&mut self.settings.bridge_settings, bridge_settings);
+ self.update(should_save)
+ }
+
+ pub fn set_bridge_state(&mut self, bridge_state: BridgeState) -> Result<bool, Error> {
+ let should_save = self.settings.set_bridge_state(bridge_state);
+ self.update(should_save)
+ }
+
+ fn update_field<T: Eq>(field: &mut T, new_value: T) -> bool {
+ if *field != new_value {
+ *field = new_value;
true
- }
- ffi::WinUtilMigrationStatus::Aborted => {
- error!("Migration was aborted to avoid overwriting current settings");
+ } else {
false
}
- ffi::WinUtilMigrationStatus::NothingToMigrate => {
- info!("Could not migrate settings - no backup present");
- false
- }
- ffi::WinUtilMigrationStatus::Failed | _ => {
- error!("Migration failed");
- false
+ }
+
+ fn update(&mut self, should_save: bool) -> Result<bool, Error> {
+ if should_save {
+ self.save().map(|_| true)
+ } else {
+ Ok(false)
}
}
}
+impl Deref for SettingsPersister {
+ type Target = Settings;
+
+ fn deref(&self) -> &Self::Target {
+ &self.settings
+ }
+}
+
+
#[cfg(windows)]
mod ffi {
use talpid_core::logging::windows::LogSink;
diff --git a/mullvad-jni/src/lib.rs b/mullvad-jni/src/lib.rs
index a95b4ae308..b3996ee507 100644
--- a/mullvad-jni/src/lib.rs
+++ b/mullvad-jni/src/lib.rs
@@ -16,7 +16,6 @@ use jnix::{
},
FromJava, IntoJava, JnixEnv,
};
-use lazy_static::lazy_static;
use mullvad_daemon::{exception_logging, logging, version, Daemon, DaemonCommandChannel};
use mullvad_types::account::AccountData;
use std::{
@@ -29,12 +28,9 @@ use talpid_types::{android::AndroidContext, ErrorExt};
const LOG_FILENAME: &str = "daemon.log";
-lazy_static! {
- static ref LOG_INIT_RESULT: Result<PathBuf, String> =
- start_logging().map_err(|error| error.display_chain());
-}
-
static LOAD_CLASSES: Once = Once::new();
+static LOG_START: Once = Once::new();
+static mut LOG_INIT_RESULT: Option<Result<(), String>> = None;
#[derive(Debug, err_derive::Error)]
#[error(no_from)]
@@ -42,23 +38,14 @@ pub enum Error {
#[error(display = "Failed to create global reference to Java object")]
CreateGlobalReference(#[error(cause)] jnix::jni::errors::Error),
- #[error(display = "Failed to get cache directory path")]
- GetCacheDir(#[error(source)] mullvad_paths::Error),
-
#[error(display = "Failed to get Java VM instance")]
GetJvmInstance(#[error(cause)] jnix::jni::errors::Error),
- #[error(display = "Failed to get log directory path")]
- GetLogDir(#[error(source)] mullvad_paths::Error),
-
#[error(display = "Failed to initialize the mullvad daemon")]
InitializeDaemon(#[error(source)] mullvad_daemon::Error),
#[error(display = "Failed to spawn the JNI event listener")]
SpawnJniEventListener(#[error(source)] jni_event_listener::Error),
-
- #[error(display = "Failed to start logger")]
- StartLogging(#[error(source)] logging::Error),
}
#[derive(IntoJava)]
@@ -95,47 +82,67 @@ pub extern "system" fn Java_net_mullvad_mullvadvpn_service_MullvadDaemon_initial
env: JNIEnv<'_>,
this: JObject<'_>,
vpnService: JObject<'_>,
+ cacheDirectory: JObject<'_>,
+ resourceDirectory: JObject<'_>,
) {
let env = JnixEnv::from(env);
+ let cache_dir = PathBuf::from(String::from_java(&env, cacheDirectory));
+ let resource_dir = PathBuf::from(String::from_java(&env, resourceDirectory));
- match *LOG_INIT_RESULT {
- Ok(ref log_dir) => {
+ match start_logging(&resource_dir) {
+ Ok(()) => {
LOAD_CLASSES.call_once(|| env.preload_classes(classes::CLASSES.iter().cloned()));
- if let Err(error) = initialize(&env, &this, &vpnService, log_dir.clone()) {
+ if let Err(error) = initialize(&env, &this, &vpnService, cache_dir, resource_dir) {
log::error!("{}", error.display_chain());
}
}
- Err(ref message) => env
+ Err(message) => env
.throw(message.as_str())
.expect("Failed to throw exception"),
}
}
-fn start_logging() -> Result<PathBuf, Error> {
- let log_dir = mullvad_paths::log_dir().map_err(Error::GetLogDir)?;
+fn start_logging(log_dir: &Path) -> Result<(), String> {
+ unsafe {
+ LOG_START.call_once(|| LOG_INIT_RESULT = Some(initialize_logging(log_dir)));
+ LOG_INIT_RESULT
+ .clone()
+ .expect("Logging not properly initialized")
+ }
+}
+
+fn initialize_logging(log_dir: &Path) -> Result<(), String> {
let log_file = log_dir.join(LOG_FILENAME);
logging::init_logger(log::LevelFilter::Debug, Some(&log_file), true)
- .map_err(Error::StartLogging)?;
+ .map_err(|error| error.display_chain_with_msg("Failed to start logger"))?;
exception_logging::enable();
log_panics::init();
version::log_version();
- Ok(log_dir)
+ Ok(())
}
fn initialize(
env: &JnixEnv<'_>,
this: &JObject<'_>,
vpn_service: &JObject<'_>,
- log_dir: PathBuf,
+ cache_dir: PathBuf,
+ resource_dir: PathBuf,
) -> Result<(), Error> {
let android_context = create_android_context(env, *vpn_service)?;
let daemon_command_channel = DaemonCommandChannel::new();
let daemon_interface = Box::new(DaemonInterface::new(daemon_command_channel.sender()));
- spawn_daemon(env, this, log_dir, daemon_command_channel, android_context)?;
+ spawn_daemon(
+ env,
+ this,
+ cache_dir,
+ resource_dir,
+ daemon_command_channel,
+ android_context,
+ )?;
set_daemon_interface_address(env, this, Box::into_raw(daemon_interface) as jlong);
@@ -157,7 +164,8 @@ fn create_android_context(
fn spawn_daemon(
env: &JnixEnv<'_>,
this: &JObject<'_>,
- log_dir: PathBuf,
+ cache_dir: PathBuf,
+ resource_dir: PathBuf,
command_channel: DaemonCommandChannel,
android_context: AndroidContext,
) -> Result<(), Error> {
@@ -169,8 +177,17 @@ fn spawn_daemon(
thread::spawn(move || {
let jvm = android_context.jvm.clone();
+ let daemon = Daemon::start(
+ Some(resource_dir.clone()),
+ resource_dir.clone(),
+ resource_dir,
+ cache_dir,
+ listener,
+ command_channel,
+ android_context,
+ );
- match create_daemon(listener, log_dir, command_channel, android_context) {
+ match daemon {
Ok(daemon) => {
let _ = tx.send(Ok(()));
match daemon.run() {
@@ -179,7 +196,7 @@ fn spawn_daemon(
}
}
Err(error) => {
- let _ = tx.send(Err(error));
+ let _ = tx.send(Err(Error::InitializeDaemon(error)));
}
}
@@ -189,26 +206,6 @@ fn spawn_daemon(
rx.recv().unwrap()
}
-fn create_daemon(
- listener: JniEventListener,
- log_dir: PathBuf,
- command_channel: DaemonCommandChannel,
- android_context: AndroidContext,
-) -> Result<Daemon<JniEventListener>, Error> {
- let resource_dir = mullvad_paths::get_resource_dir();
- let cache_dir = mullvad_paths::cache_dir().map_err(Error::GetCacheDir)?;
-
- Daemon::start(
- Some(log_dir),
- resource_dir,
- cache_dir,
- listener,
- command_channel,
- android_context,
- )
- .map_err(Error::InitializeDaemon)
-}
-
fn notify_daemon_stopped(jvm: Arc<JavaVM>, daemon_object: GlobalRef) {
match jvm.attach_current_thread_as_daemon() {
Ok(env) => {
@@ -801,13 +798,16 @@ pub extern "system" fn Java_net_mullvad_mullvadvpn_service_MullvadDaemon_updateR
pub extern "system" fn Java_net_mullvad_mullvadvpn_dataproxy_MullvadProblemReport_collectReport(
env: JNIEnv<'_>,
_: JObject<'_>,
+ logDirectory: JString<'_>,
outputPath: JString<'_>,
) -> jboolean {
let env = JnixEnv::from(env);
+ let log_dir_string = String::from_java(&env, logDirectory);
+ let log_dir = Path::new(&log_dir_string);
let output_path_string = String::from_java(&env, outputPath);
let output_path = Path::new(&output_path_string);
- match mullvad_problem_report::collect_report(&[], output_path, Vec::new()) {
+ match mullvad_problem_report::collect_report(&[], output_path, Vec::new(), log_dir) {
Ok(()) => JNI_TRUE,
Err(error) => {
log::error!(
diff --git a/mullvad-problem-report/src/lib.rs b/mullvad-problem-report/src/lib.rs
index 96d181bc11..bac11df5dc 100644
--- a/mullvad-problem-report/src/lib.rs
+++ b/mullvad-problem-report/src/lib.rs
@@ -100,12 +100,22 @@ pub fn collect_report(
extra_logs: &[&Path],
output_path: &Path,
redact_custom_strings: Vec<String>,
+ #[cfg(target_os = "android")] android_log_dir: &Path,
) -> Result<(), Error> {
let mut problem_report = ProblemReport::new(redact_custom_strings);
- let daemon_logs = mullvad_paths::get_log_dir()
- .map_err(LogError::GetLogDir)
- .and_then(list_logs);
+ let daemon_logs_dir = {
+ #[cfg(target_os = "android")]
+ {
+ Ok(android_log_dir.to_owned())
+ }
+ #[cfg(not(target_os = "android"))]
+ {
+ mullvad_paths::get_log_dir().map_err(LogError::GetLogDir)
+ }
+ };
+
+ let daemon_logs = daemon_logs_dir.and_then(list_logs);
match daemon_logs {
Ok(daemon_logs) => {
let mut other_logs = Vec::new();
@@ -144,7 +154,7 @@ pub fn collect_report(
None => {}
}
#[cfg(target_os = "android")]
- match write_logcat_to_file() {
+ match write_logcat_to_file(android_log_dir) {
Ok(logcat_path) => problem_report.add_log(&logcat_path),
Err(error) => problem_report.add_error("Failed to collect logcat", &error),
}
@@ -227,8 +237,8 @@ fn is_tunnel_log(path: &Path) -> bool {
}
#[cfg(target_os = "android")]
-fn write_logcat_to_file() -> Result<PathBuf, io::Error> {
- let logcat_path = PathBuf::from("/data/data/net.mullvad.mullvadvpn/logcat.txt");
+fn write_logcat_to_file(log_dir: &Path) -> Result<PathBuf, io::Error> {
+ let logcat_path = log_dir.join("logcat.txt");
duct::cmd!("logcat", "-d")
.stderr_to_stdout()
diff --git a/mullvad-types/src/settings/mod.rs b/mullvad-types/src/settings/mod.rs
index b7beecac85..269fc93cf4 100644
--- a/mullvad-types/src/settings/mod.rs
+++ b/mullvad-types/src/settings/mod.rs
@@ -7,15 +7,7 @@ use jnix::IntoJava;
use log::{debug, info};
use serde::{Deserialize, Serialize};
use serde_json;
-use std::{
- fs::{self, File},
- io::{self, Read},
- path::PathBuf,
-};
-use talpid_types::{
- net::{openvpn, wireguard, GenericTunnelOptions},
- ErrorExt,
-};
+use talpid_types::net::{openvpn, wireguard, GenericTunnelOptions};
mod migrations;
@@ -24,33 +16,13 @@ pub type Result<T> = std::result::Result<T, Error>;
#[derive(err_derive::Error, Debug)]
#[error(no_from)]
pub enum Error {
- #[error(display = "Unable to create settings directory")]
- DirectoryError(#[error(source)] mullvad_paths::Error),
-
- #[error(display = "Unable to read settings from {}", _0)]
- ReadError(String, #[error(source)] io::Error),
-
- #[error(display = "Unable to remove settings file {}", _0)]
- DeleteError(String, #[error(source)] io::Error),
-
#[error(display = "Malformed settings")]
ParseError(#[error(source)] serde_json::Error),
- #[error(display = "Unable to serialize settings to JSON")]
- SerializeError(#[error(source)] serde_json::Error),
-
- #[error(display = "Unable to write settings to {}", _0)]
- WriteError(String, #[error(source)] io::Error),
-
- #[error(display = "Invalid OpenVPN proxy configuration: {}", _0)]
- InvalidProxyData(String),
-
#[error(display = "Unable to read any version of the settings")]
NoMatchingVersion,
}
-static SETTINGS_FILE: &str = "settings.json";
-
/// Mullvad daemon settings.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
@@ -61,22 +33,22 @@ pub struct Settings {
account_token: Option<String>,
relay_settings: RelaySettings,
#[cfg_attr(target_os = "android", jnix(skip))]
- bridge_settings: BridgeSettings,
+ pub bridge_settings: BridgeSettings,
#[cfg_attr(target_os = "android", jnix(skip))]
bridge_state: BridgeState,
/// If the daemon should allow communication with private (LAN) networks.
- allow_lan: bool,
+ pub allow_lan: bool,
/// Extra level of kill switch. When this setting is on, the disconnected state will block
/// the firewall to not allow any traffic in or out.
#[cfg_attr(target_os = "android", jnix(skip))]
- block_when_disconnected: bool,
+ pub block_when_disconnected: bool,
/// If the daemon should connect the VPN tunnel directly on start or not.
- auto_connect: bool,
+ pub auto_connect: bool,
/// Options that should be applied to tunnels of a specific type regardless of where the relays
/// might be located.
- tunnel_options: TunnelOptions,
+ pub tunnel_options: TunnelOptions,
/// Whether to notify users of beta updates.
- show_beta_releases: Option<bool>,
+ pub show_beta_releases: Option<bool>,
/// Specifies settings schema version
#[cfg_attr(target_os = "android", jnix(skip))]
settings_version: migrations::SettingsVersion,
@@ -105,68 +77,12 @@ impl Default for Settings {
}
impl Settings {
- /// Loads user settings from file. If no file is present it returns the defaults.
- pub fn load() -> Result<Settings> {
- let path = Self::get_settings_path()?;
- match File::open(&path) {
- Ok(file) => {
- info!("Loading settings from {}", path.display());
- let mut settings_bytes = vec![];
- io::BufReader::new(file)
- .read_to_end(&mut settings_bytes)
- .map_err(|e| Error::ReadError("Failed to read settings file".to_owned(), e))?;
- Self::parse_settings(&mut settings_bytes).or_else(|e| {
- log::error!(
- "{}",
- e.display_chain_with_msg("Failed to parse settings file")
- );
- let settings = migrations::try_migrate_settings(&settings_bytes)?;
- if let Err(e) = settings.save() {
- log::error!("Failed to save settings after migration: {}", e);
- }
- Ok(settings)
- })
- }
- Err(e) => Err(Error::ReadError(path.display().to_string(), e)),
- }
- }
-
- /// Serializes the settings and saves them to the file it was loaded from.
- fn save(&self) -> Result<()> {
- let path = Self::get_settings_path()?;
-
- debug!("Writing settings to {}", path.display());
- let mut file =
- File::create(&path).map_err(|e| Error::WriteError(path.display().to_string(), e))?;
-
- serde_json::to_writer_pretty(&mut file, self).map_err(Error::SerializeError)?;
- file.sync_all()
- .map_err(|e| Error::WriteError(path.display().to_string(), e))
- }
-
- /// Resets default settings
- pub fn reset(&mut self) -> Result<()> {
- *self = Default::default();
- self.save().or_else(|e| {
- log::error!(
- "{}",
- e.display_chain_with_msg("Unable to save default settings")
- );
- log::error!("Will attempt to remove settings file");
- Self::get_settings_path().and_then(|path| {
- fs::remove_file(&path)
- .map_err(|e| Error::DeleteError(path.display().to_string(), e))
- })
- })
+ pub fn load_from_bytes(bytes: &[u8]) -> Result<Self> {
+ serde_json::from_slice(bytes).map_err(Error::ParseError)
}
- fn get_settings_path() -> Result<PathBuf> {
- let dir = ::mullvad_paths::settings_dir().map_err(Error::DirectoryError)?;
- Ok(dir.join(SETTINGS_FILE))
- }
-
- fn parse_settings(bytes: &[u8]) -> Result<Settings> {
- serde_json::from_slice(bytes).map_err(Error::ParseError)
+ pub fn migrate_from_bytes(bytes: &[u8]) -> Result<Self> {
+ migrations::try_migrate_settings(&bytes)
}
pub fn get_account_token(&self) -> Option<String> {
@@ -175,7 +91,7 @@ impl Settings {
/// Changes account number to the one given. Also saves the new settings to disk.
/// The boolean in the Result indicates if the account token changed or not
- pub fn set_account_token(&mut self, mut account_token: Option<String>) -> Result<bool> {
+ pub fn set_account_token(&mut self, mut account_token: Option<String>) -> bool {
if account_token.as_ref().map(String::len) == Some(0) {
debug!("Setting empty account token is treated as unsetting it");
account_token = None;
@@ -189,9 +105,9 @@ impl Settings {
info!("Changing account token")
}
self.account_token = account_token;
- self.save().map(|_| true)
+ true
} else {
- Ok(false)
+ false
}
}
@@ -199,7 +115,7 @@ impl Settings {
self.relay_settings.clone()
}
- pub fn update_relay_settings(&mut self, update: RelaySettingsUpdate) -> Result<bool> {
+ pub fn update_relay_settings(&mut self, update: RelaySettingsUpdate) -> bool {
let update_supports_bridge = update.supports_bridge();
let new_settings = self.relay_settings.merge(update);
if self.relay_settings != new_settings {
@@ -212,117 +128,9 @@ impl Settings {
);
self.relay_settings = new_settings;
- self.save().map(|_| true)
- } else {
- Ok(false)
- }
- }
-
- pub fn get_allow_lan(&self) -> bool {
- self.allow_lan
- }
-
- pub fn set_allow_lan(&mut self, allow_lan: bool) -> Result<bool> {
- if allow_lan != self.allow_lan {
- self.allow_lan = allow_lan;
- self.save().map(|_| true)
- } else {
- Ok(false)
- }
- }
-
- pub fn get_block_when_disconnected(&self) -> bool {
- self.block_when_disconnected
- }
-
- pub fn set_block_when_disconnected(&mut self, block_when_disconnected: bool) -> Result<bool> {
- if block_when_disconnected != self.block_when_disconnected {
- self.block_when_disconnected = block_when_disconnected;
- self.save().map(|_| true)
- } else {
- Ok(false)
- }
- }
-
- pub fn get_auto_connect(&self) -> bool {
- self.auto_connect
- }
-
- pub fn set_auto_connect(&mut self, auto_connect: bool) -> Result<bool> {
- if auto_connect != self.auto_connect {
- self.auto_connect = auto_connect;
- self.save().map(|_| true)
- } else {
- Ok(false)
- }
- }
-
- pub fn set_openvpn_mssfix(&mut self, openvpn_mssfix: Option<u16>) -> Result<bool> {
- if self.tunnel_options.openvpn.mssfix != openvpn_mssfix {
- self.tunnel_options.openvpn.mssfix = openvpn_mssfix;
- self.save().map(|_| true)
- } else {
- Ok(false)
- }
- }
-
- pub fn set_enable_ipv6(&mut self, enable_ipv6: bool) -> Result<bool> {
- if self.tunnel_options.generic.enable_ipv6 != enable_ipv6 {
- self.tunnel_options.generic.enable_ipv6 = enable_ipv6;
- self.save().map(|_| true)
- } else {
- Ok(false)
- }
- }
-
- pub fn set_wireguard_mtu(&mut self, mtu: Option<u16>) -> Result<bool> {
- if self.tunnel_options.wireguard.mtu != mtu {
- self.tunnel_options.wireguard.mtu = mtu;
- self.save().map(|_| true)
- } else {
- Ok(false)
- }
- }
-
- pub fn set_wireguard_rotation_interval(
- &mut self,
- automatic_rotation: Option<u32>,
- ) -> Result<bool> {
- if self.tunnel_options.wireguard.automatic_rotation != automatic_rotation {
- self.tunnel_options.wireguard.automatic_rotation = automatic_rotation;
- self.save().map(|_| true)
- } else {
- Ok(false)
- }
- }
-
- pub fn get_tunnel_options(&self) -> &TunnelOptions {
- &self.tunnel_options
- }
-
- pub fn get_show_beta_releases(&self) -> Option<bool> {
- self.show_beta_releases.clone()
- }
-
- pub fn set_show_beta_releases(&mut self, enabled: bool) -> Result<bool> {
- if Some(enabled) != self.show_beta_releases {
- self.show_beta_releases = Some(enabled);
- self.save().map(|_| true)
- } else {
- Ok(false)
- }
- }
-
- pub fn get_bridge_settings(&self) -> &BridgeSettings {
- &self.bridge_settings
- }
-
- pub fn set_bridge_settings(&mut self, bridge_settings: BridgeSettings) -> Result<bool> {
- if self.bridge_settings != bridge_settings {
- self.bridge_settings = bridge_settings;
- self.save().map(|_| true)
+ true
} else {
- Ok(false)
+ false
}
}
@@ -330,15 +138,15 @@ impl Settings {
&self.bridge_state
}
- pub fn set_bridge_state(&mut self, bridge_state: BridgeState) -> Result<bool> {
+ pub fn set_bridge_state(&mut self, bridge_state: BridgeState) -> bool {
if self.bridge_state != bridge_state {
self.bridge_state = bridge_state;
if self.bridge_state == BridgeState::On {
self.relay_settings.ensure_bridge_compatibility();
}
- self.save().map(|_| true)
+ true
} else {
- Ok(false)
+ false
}
}
}