summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorLinus Färnstrand <linus@mullvad.net>2019-10-07 16:25:58 +0200
committerLinus Färnstrand <linus@mullvad.net>2019-10-07 16:25:58 +0200
commit48d9e233e6bbe9c56372ee59324b786f770363d0 (patch)
tree00ed94738e56b11e77023e5d95ddc54a1a5b7116
parent2db11de43396abfe1f6a295d9fd22511898f7a07 (diff)
parent4c8ff3f8ec7baf674b95536a94e18e79440b428f (diff)
downloadmullvadvpn-48d9e233e6bbe9c56372ee59324b786f770363d0.tar.xz
mullvadvpn-48d9e233e6bbe9c56372ee59324b786f770363d0.zip
Merge branch 'cache-version-check-in-daemon'
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadDaemon.kt5
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadVpnService.kt4
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/AppVersionInfoCache.kt93
-rw-r--r--android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/AppVersionInfoFetcher.kt69
-rw-r--r--gui/src/main/daemon-rpc.ts3
-rw-r--r--gui/src/main/index.ts26
-rw-r--r--gui/src/shared/daemon-rpc-types.ts3
-rw-r--r--mullvad-cli/src/cmds/status.rs5
-rw-r--r--mullvad-daemon/src/lib.rs91
-rw-r--r--mullvad-daemon/src/main.rs9
-rw-r--r--mullvad-daemon/src/management_interface.rs18
-rw-r--r--mullvad-daemon/src/version_check.rs177
-rw-r--r--mullvad-jni/src/daemon_interface.rs5
-rw-r--r--mullvad-jni/src/jni_event_listener.rs39
-rw-r--r--mullvad-jni/src/lib.rs1
-rw-r--r--mullvad-types/src/lib.rs3
-rw-r--r--mullvad-types/src/version.rs2
17 files changed, 351 insertions, 202 deletions
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadDaemon.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadDaemon.kt
index fe1ba2bc71..02596f6996 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadDaemon.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadDaemon.kt
@@ -14,6 +14,7 @@ import net.mullvad.mullvadvpn.util.EventNotifier
class MullvadDaemon(val vpnService: MullvadVpnService) {
val onSettingsChange = EventNotifier<Settings?>(null)
+ var onAppVersionInfoChange: ((AppVersionInfo) -> Unit)? = null
var onKeygenEvent: ((KeygenEvent) -> Unit)? = null
var onRelayListChange: ((RelayList) -> Unit)? = null
var onTunnelStateChange: ((TunnelState) -> Unit)? = null
@@ -43,6 +44,10 @@ class MullvadDaemon(val vpnService: MullvadVpnService) {
private external fun initialize(vpnService: MullvadVpnService)
+ private fun notifyAppVersionInfoEvent(appVersionInfo: AppVersionInfo) {
+ onAppVersionInfoChange?.invoke(appVersionInfo)
+ }
+
private fun notifyKeygenEvent(event: KeygenEvent) {
onKeygenEvent?.invoke(event)
}
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadVpnService.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadVpnService.kt
index e36c184bfe..388e4c0a5d 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadVpnService.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/MullvadVpnService.kt
@@ -10,7 +10,6 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
-import net.mullvad.mullvadvpn.dataproxy.AppVersionInfoFetcher
import net.mullvad.mullvadvpn.dataproxy.ConnectionProxy
import net.mullvad.mullvadvpn.model.TunConfig
@@ -23,7 +22,6 @@ class MullvadVpnService : VpnService() {
private lateinit var daemon: Deferred<MullvadDaemon>
private lateinit var connectionProxy: ConnectionProxy
private lateinit var notificationManager: ForegroundNotificationManager
- private lateinit var versionInfoFetcher: AppVersionInfoFetcher
override fun onCreate() {
setUp()
@@ -95,7 +93,6 @@ class MullvadVpnService : VpnService() {
daemon = startDaemon()
connectionProxy = ConnectionProxy(this, daemon)
notificationManager = startNotificationManager()
- versionInfoFetcher = AppVersionInfoFetcher(daemon, this)
}
private fun startDaemon() = GlobalScope.async(Dispatchers.Default) {
@@ -130,6 +127,5 @@ class MullvadVpnService : VpnService() {
private fun tearDown() {
connectionProxy.onDestroy()
notificationManager.onDestroy()
- versionInfoFetcher.stop()
}
}
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/AppVersionInfoCache.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/AppVersionInfoCache.kt
index 9bc6011fbc..cf62ffed0b 100644
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/AppVersionInfoCache.kt
+++ b/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/AppVersionInfoCache.kt
@@ -1,27 +1,27 @@
package net.mullvad.mullvadvpn.dataproxy
import android.content.Context
-import android.content.SharedPreferences
-import android.content.SharedPreferences.OnSharedPreferenceChangeListener
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import net.mullvad.mullvadvpn.MainActivity
+import net.mullvad.mullvadvpn.model.AppVersionInfo
class AppVersionInfoCache(val parentActivity: MainActivity) {
companion object {
- val KEY_CURRENT_IS_SUPPORTED = "current_is_supported"
- val KEY_CURRENT_IS_OUTDATED = "current_is_outdated"
- val KEY_LAST_UPDATED = "last_updated"
- val KEY_LATEST_STABLE = "latest_stable"
- val KEY_LATEST = "latest"
- val SHARED_PREFERENCES = "app_version_info_cache"
+ val LEGACY_SHARED_PREFERENCES = "app_version_info_cache"
}
- private val preferences: SharedPreferences
- get() = parentActivity.getSharedPreferences(SHARED_PREFERENCES, Context.MODE_PRIVATE)
+ private val daemon = parentActivity.daemon
+ private val setUpJob = setUp()
- private val fetchCurrentVersionJob = fetchCurrentVersion()
+ private var appVersionInfo: AppVersionInfo? = null
+ set(value) {
+ synchronized(this) {
+ field = value
+ updateUpgradeVersion()
+ }
+ }
var onUpdate: (() -> Unit)? = null
set(value) {
@@ -29,62 +29,59 @@ class AppVersionInfoCache(val parentActivity: MainActivity) {
value?.invoke()
}
+ val latestStable
+ get() = appVersionInfo?.latestStable
+ val latest
+ get() = appVersionInfo?.latest
+ val isSupported
+ get() = appVersionInfo?.currentIsSupported ?: true
+ var isOutdated = false
+ get() = appVersionInfo?.currentIsOutdated ?: false
+
var version: String? = null
private set
var isStable = true
private set
- var lastUpdated = 0L
- private set
- var isSupported = true
- private set
- var isOutdated = false
- private set
- var latestStable: String? = null
- private set
- var latest: String? = null
- private set
-
var upgradeVersion: String? = null
private set
- private val listener = object : OnSharedPreferenceChangeListener {
- override fun onSharedPreferenceChanged(preferences: SharedPreferences, key: String) {
- when (key) {
- KEY_CURRENT_IS_SUPPORTED -> isSupported = preferences.getBoolean(key, isSupported)
- KEY_CURRENT_IS_OUTDATED -> isOutdated = preferences.getBoolean(key, isOutdated)
- KEY_LAST_UPDATED -> lastUpdated = preferences.getLong(key, lastUpdated)
- KEY_LATEST_STABLE -> latestStable = preferences.getString(key, latestStable)
- KEY_LATEST -> latest = preferences.getString(key, latest)
- else -> return
- }
-
- updateUpgradeVersion()
- }
- }
-
fun onCreate() {
- preferences.registerOnSharedPreferenceChangeListener(listener)
-
- lastUpdated = preferences.getLong(KEY_LAST_UPDATED, 0L)
- isSupported = preferences.getBoolean(KEY_CURRENT_IS_SUPPORTED, true)
- isOutdated = preferences.getBoolean(KEY_CURRENT_IS_OUTDATED, false)
- latestStable = preferences.getString(KEY_LATEST_STABLE, null)
- latest = preferences.getString(KEY_LATEST, null)
+ parentActivity.getSharedPreferences(LEGACY_SHARED_PREFERENCES, Context.MODE_PRIVATE)
+ .edit()
+ .clear()
+ .commit()
}
fun onDestroy() {
- fetchCurrentVersionJob.cancel()
- preferences.unregisterOnSharedPreferenceChangeListener(listener)
+ setUpJob.cancel()
+ tearDown()
}
- private fun fetchCurrentVersion() = GlobalScope.launch(Dispatchers.Default) {
- val currentVersion = parentActivity.daemon.await().getCurrentVersion()
+ private fun setUp() = GlobalScope.launch(Dispatchers.Default) {
+ val daemon = this@AppVersionInfoCache.daemon.await()
+ val currentVersion = daemon.getCurrentVersion()
version = currentVersion
isStable = !currentVersion.contains("-")
updateUpgradeVersion()
+
+ daemon.onAppVersionInfoChange = { newAppVersionInfo ->
+ appVersionInfo = newAppVersionInfo
+ }
+
+ synchronized(this@AppVersionInfoCache) {
+ val initialVersionInfo = daemon.getVersionInfo()
+
+ if (appVersionInfo == null) {
+ appVersionInfo = initialVersionInfo
+ }
+ }
+ }
+
+ private fun tearDown() = GlobalScope.launch(Dispatchers.Default) {
+ daemon.await().onAppVersionInfoChange = null
}
private fun updateUpgradeVersion() {
diff --git a/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/AppVersionInfoFetcher.kt b/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/AppVersionInfoFetcher.kt
deleted file mode 100644
index f067660ee5..0000000000
--- a/android/src/main/kotlin/net/mullvad/mullvadvpn/dataproxy/AppVersionInfoFetcher.kt
+++ /dev/null
@@ -1,69 +0,0 @@
-package net.mullvad.mullvadvpn.dataproxy
-
-import android.content.Context
-import java.util.Calendar
-import kotlinx.coroutines.Deferred
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.launch
-import net.mullvad.mullvadvpn.MullvadDaemon
-
-val ONE_DAY_IN_MILLISECONDS = 24L * 60L * 60L * 1000L
-val ONE_MINUTE_IN_MILLISECONDS = 60L * 1000L
-
-class AppVersionInfoFetcher(val daemon: Deferred<MullvadDaemon>, val context: Context) {
- private val preferences =
- context.getSharedPreferences(AppVersionInfoCache.SHARED_PREFERENCES, Context.MODE_PRIVATE)
-
- private val mainLoop = run()
-
- fun stop() {
- mainLoop.cancel()
- }
-
- private fun run() = GlobalScope.launch(Dispatchers.Default) {
- while (true) {
- delay(calculateDelay())
- fetch()
- }
- }
-
- private fun calculateDelay(): Long {
- val now = Calendar.getInstance().timeInMillis
- val lastUpdated = preferences.getLong(AppVersionInfoCache.KEY_LAST_UPDATED, 0)
- val delta = now - lastUpdated
-
- if (delta < 0 || delta >= ONE_DAY_IN_MILLISECONDS) {
- return 0
- } else {
- return ONE_DAY_IN_MILLISECONDS - delta
- }
- }
-
- private suspend fun fetch() {
- var now = Calendar.getInstance().timeInMillis
- var versionInfo = daemon.await().getVersionInfo()
- var attempt = 0
-
- while (attempt < 5 && versionInfo == null) {
- delay(ONE_MINUTE_IN_MILLISECONDS)
- now = Calendar.getInstance().timeInMillis
- versionInfo = daemon.await().getVersionInfo()
- attempt += 1
- }
-
- if (versionInfo != null) {
- preferences.edit().apply {
- with(AppVersionInfoCache) {
- putLong(KEY_LAST_UPDATED, now)
- putBoolean(KEY_CURRENT_IS_SUPPORTED, versionInfo.currentIsSupported)
- putBoolean(KEY_CURRENT_IS_OUTDATED, versionInfo.currentIsOutdated)
- putString(KEY_LATEST_STABLE, versionInfo.latestStable)
- putString(KEY_LATEST, versionInfo.latest)
- commit()
- }
- }
- }
- }
-}
diff --git a/gui/src/main/daemon-rpc.ts b/gui/src/main/daemon-rpc.ts
index 6105f75c56..5fcc9171e0 100644
--- a/gui/src/main/daemon-rpc.ts
+++ b/gui/src/main/daemon-rpc.ts
@@ -368,6 +368,9 @@ const daemonEventSchema = oneOf(
object({
wireguard_key: keygenEventSchema,
}),
+ object({
+ app_version_info: appVersionInfoSchema,
+ }),
);
export class ResponseParseError extends Error {
diff --git a/gui/src/main/index.ts b/gui/src/main/index.ts
index 9397d45467..0672effcca 100644
--- a/gui/src/main/index.ts
+++ b/gui/src/main/index.ts
@@ -48,8 +48,6 @@ import ReconnectionBackoff from './reconnection-backoff';
import TrayIconController, { TrayIconType } from './tray-icon-controller';
import WindowController from './window-controller';
-const VERSION_UPDATE_INTERVAL = 24 * 60 * 60 * 1000;
-
const DAEMON_RPC_PATH =
process.platform === 'win32' ? '//./pipe/Mullvad VPN' : '/var/run/mullvad-vpn';
@@ -141,7 +139,6 @@ class ApplicationMain {
latest: '',
nextUpgrade: undefined,
};
- private latestVersionInterval?: NodeJS.Timeout;
// The UI locale which is set once from onReady handler
private locale = 'en';
@@ -446,9 +443,6 @@ class ApplicationMain {
// fetch the latest version info in background
this.fetchLatestVersion();
- // start periodic updates
- this.startLatestVersionPeriodicUpdates();
-
// notify user about inconsistent version
if (
process.env.NODE_ENV !== 'development' &&
@@ -478,9 +472,6 @@ class ApplicationMain {
if (wasConnected) {
this.connectedToDaemon = false;
- // stop periodic updates
- this.stopLatestVersionPeriodicUpdates();
-
// update the tray icon to indicate that the computer is not secure anymore
this.updateTrayIcon({ state: 'disconnected' }, false);
@@ -540,6 +531,8 @@ class ApplicationMain {
);
} else if ('wireguardKey' in daemonEvent) {
this.handleWireguardKeygenEvent(daemonEvent.wireguardKey);
+ } else if ('appVersionInfo' in daemonEvent) {
+ this.setLatestVersion(daemonEvent.appVersionInfo);
}
},
(error: Error) => {
@@ -795,21 +788,6 @@ class ApplicationMain {
}
}
- private startLatestVersionPeriodicUpdates() {
- const handler = () => {
- this.fetchLatestVersion();
- };
- this.latestVersionInterval = global.setInterval(handler, VERSION_UPDATE_INTERVAL);
- }
-
- private stopLatestVersionPeriodicUpdates() {
- if (this.latestVersionInterval) {
- clearInterval(this.latestVersionInterval);
-
- this.latestVersionInterval = undefined;
- }
- }
-
private shouldSuppressNotifications(isCriticalNotification: boolean): boolean {
const isVisible = this.windowController ? this.windowController.isVisible() : false;
diff --git a/gui/src/shared/daemon-rpc-types.ts b/gui/src/shared/daemon-rpc-types.ts
index 346b766e1b..7ce5e93111 100644
--- a/gui/src/shared/daemon-rpc-types.ts
+++ b/gui/src/shared/daemon-rpc-types.ts
@@ -86,7 +86,8 @@ export type DaemonEvent =
| { tunnelState: TunnelState }
| { settings: ISettings }
| { relayList: IRelayList }
- | { wireguardKey: KeygenEvent };
+ | { wireguardKey: KeygenEvent }
+ | { appVersionInfo: IAppVersionInfo };
export interface ITunnelStateRelayInfo {
endpoint: ITunnelEndpoint;
diff --git a/mullvad-cli/src/cmds/status.rs b/mullvad-cli/src/cmds/status.rs
index f3750962f6..bd68ae64ae 100644
--- a/mullvad-cli/src/cmds/status.rs
+++ b/mullvad-cli/src/cmds/status.rs
@@ -70,6 +70,11 @@ impl Command for Status {
println!("New relay list: {:#?}", relay_list);
}
}
+ DaemonEvent::AppVersionInfo(app_version_info) => {
+ if verbose {
+ println!("New app version info: {:#?}", app_version_info);
+ }
+ }
DaemonEvent::WireguardKey(key_event) => {
if verbose {
println!("{}", key_event);
diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs
index 783239f5ea..b4ca11d32e 100644
--- a/mullvad-daemon/src/lib.rs
+++ b/mullvad-daemon/src/lib.rs
@@ -20,6 +20,7 @@ mod relays;
mod rpc_uniqueness_check;
mod settings;
pub mod version;
+mod version_check;
pub use crate::management_interface::ManagementCommand;
use crate::management_interface::{
@@ -31,7 +32,7 @@ use futures::{
Future, Sink,
};
use log::{debug, error, info, warn};
-use mullvad_rpc::{AccountsProxy, AppVersionProxy, HttpHandle, WireguardKeyProxy};
+use mullvad_rpc::{AccountsProxy, HttpHandle, WireguardKeyProxy};
use mullvad_types::{
account::{AccountData, AccountToken},
endpoint::MullvadEndpoint,
@@ -155,6 +156,8 @@ pub(crate) enum InternalDaemonEvent {
AccountToken,
oneshot::Sender<std::result::Result<String, mullvad_rpc::Error>>,
),
+ /// The background job fetching new `AppVersionInfo`s got a new info object.
+ NewAppVersionInfo(AppVersionInfo),
}
impl From<TunnelStateTransition> for InternalDaemonEvent {
@@ -235,6 +238,10 @@ pub trait EventListener {
/// Notify that the relay list changed.
fn notify_relay_list(&self, relay_list: RelayList);
+ /// Notify that info about the latest available app version changed.
+ /// Or some flag about the currently running version is changed.
+ fn notify_app_version(&self, app_version_info: AppVersionInfo);
+
/// Notify clients of a key generation event.
fn notify_key_event(&self, key_event: KeygenEvent);
}
@@ -252,14 +259,13 @@ pub struct Daemon<L: EventListener = ManagementInterfaceEventBroadcaster> {
account_history: account_history::AccountHistory,
wg_key_proxy: WireguardKeyProxy<HttpHandle>,
accounts_proxy: AccountsProxy<HttpHandle>,
- version_proxy: AppVersionProxy<HttpHandle>,
https_handle: mullvad_rpc::rest::RequestSender,
wireguard_key_manager: wireguard::KeyManager,
tokio_remote: tokio_core::reactor::Remote,
relay_selector: relays::RelaySelector,
last_generated_relay: Option<Relay>,
last_generated_bridge_relay: Option<Relay>,
- version: String,
+ app_version_info: AppVersionInfo,
shutdown_callbacks: Vec<Box<dyn FnOnce()>>,
}
@@ -268,7 +274,6 @@ impl Daemon<ManagementInterfaceEventBroadcaster> {
log_dir: Option<PathBuf>,
resource_dir: PathBuf,
cache_dir: PathBuf,
- version: String,
) -> Result<Self> {
if rpc_uniqueness_check::is_another_instance_running() {
return Err(Error::DaemonIsAlreadyRunning);
@@ -284,7 +289,6 @@ impl Daemon<ManagementInterfaceEventBroadcaster> {
log_dir,
resource_dir,
cache_dir,
- version,
)
}
@@ -332,7 +336,6 @@ where
log_dir: Option<PathBuf>,
resource_dir: PathBuf,
cache_dir: PathBuf,
- version: String,
) -> Result<Self> {
let (tx, rx) = mpsc::channel();
@@ -344,7 +347,6 @@ where
log_dir,
resource_dir,
cache_dir,
- version,
)
}
@@ -356,7 +358,6 @@ where
log_dir: Option<PathBuf>,
resource_dir: PathBuf,
cache_dir: PathBuf,
- version: String,
) -> Result<Self> {
let ca_path = resource_dir.join(mullvad_paths::resources::API_CA_FILENAME);
@@ -379,8 +380,6 @@ where
let on_relay_list_update = move |relay_list: &RelayList| {
relay_list_listener.notify_relay_list(relay_list.clone());
};
-
-
let relay_selector = relays::RelaySelector::new(
rpc_handle.clone(),
on_relay_list_update,
@@ -388,6 +387,36 @@ where
&cache_dir,
);
+ let version_check_internal_event_tx = internal_event_tx.clone();
+ let on_version_check_update = move |app_version_info: &AppVersionInfo| {
+ let _ = version_check_internal_event_tx.send(InternalDaemonEvent::NewAppVersionInfo(
+ app_version_info.clone(),
+ ));
+ };
+ let app_version_info = match version_check::load_cache(&cache_dir) {
+ Ok(app_version_info) => app_version_info,
+ Err(error) => {
+ log::warn!(
+ "{}",
+ error.display_chain_with_msg("Unable to load cached version info")
+ );
+ // If we don't have a cache, start out with sane defaults.
+ AppVersionInfo {
+ current_is_supported: true,
+ current_is_outdated: false,
+ latest_stable: version::PRODUCT_VERSION.to_owned(),
+ latest: version::PRODUCT_VERSION.to_owned(),
+ }
+ }
+ };
+ let version_check_future = version_check::VersionUpdater::new(
+ rpc_handle.clone(),
+ cache_dir.clone(),
+ on_version_check_update,
+ app_version_info.clone(),
+ );
+ tokio_remote.spawn(|_| version_check_future);
+
let settings = settings::load();
let account_history =
@@ -431,14 +460,13 @@ where
account_history,
wg_key_proxy: WireguardKeyProxy::new(rpc_handle.clone()),
accounts_proxy: AccountsProxy::new(rpc_handle.clone()),
- version_proxy: AppVersionProxy::new(rpc_handle),
https_handle,
+ wireguard_key_manager,
tokio_remote,
relay_selector,
last_generated_relay: None,
last_generated_bridge_relay: None,
- version,
- wireguard_key_manager,
+ app_version_info,
shutdown_callbacks: vec![],
};
@@ -505,6 +533,9 @@ where
TriggerShutdown => self.trigger_shutdown_event(),
WgKeyEvent(key_event) => self.handle_wireguard_key_event(key_event),
NewAccountEvent(account_token, tx) => self.handle_new_account_event(account_token, tx),
+ NewAppVersionInfo(app_version_info) => {
+ self.handle_new_app_version_info(app_version_info)
+ }
}
Ok(())
}
@@ -862,6 +893,11 @@ where
};
}
+ fn handle_new_app_version_info(&mut self, app_version_info: AppVersionInfo) {
+ self.app_version_info = app_version_info.clone();
+ self.event_listener.notify_app_version(app_version_info);
+ }
+
fn on_set_target_state(
&mut self,
tx: oneshot::Sender<std::result::Result<(), ()>>,
@@ -1056,27 +1092,20 @@ where
}
}
- fn on_get_version_info(
- &mut self,
- tx: oneshot::Sender<BoxFuture<AppVersionInfo, mullvad_rpc::Error>>,
- ) {
- #[cfg(target_os = "linux")]
- const PLATFORM: &str = "linux";
- #[cfg(target_os = "macos")]
- const PLATFORM: &str = "macos";
- #[cfg(target_os = "windows")]
- const PLATFORM: &str = "windows";
- #[cfg(target_os = "android")]
- const PLATFORM: &str = "android";
-
- let fut = self
- .version_proxy
- .app_version_check(&self.version, PLATFORM);
- Self::oneshot_send(tx, Box::new(fut), "get_version_info response");
+ fn on_get_version_info(&mut self, tx: oneshot::Sender<AppVersionInfo>) {
+ Self::oneshot_send(
+ tx,
+ self.app_version_info.clone(),
+ "get_version_info response",
+ );
}
fn on_get_current_version(&mut self, tx: oneshot::Sender<AppVersion>) {
- Self::oneshot_send(tx, self.version.clone(), "get_current_version response");
+ Self::oneshot_send(
+ tx,
+ version::PRODUCT_VERSION.to_owned(),
+ "get_current_version response",
+ );
}
#[cfg(not(target_os = "android"))]
diff --git a/mullvad-daemon/src/main.rs b/mullvad-daemon/src/main.rs
index 9d4ca0fb56..26e463ef70 100644
--- a/mullvad-daemon/src/main.rs
+++ b/mullvad-daemon/src/main.rs
@@ -110,13 +110,8 @@ fn create_daemon(log_dir: Option<PathBuf>) -> Result<Daemon, String> {
let cache_dir = mullvad_paths::cache_dir()
.map_err(|e| e.display_chain_with_msg("Unable to get cache dir"))?;
- Daemon::start(
- log_dir,
- resource_dir,
- cache_dir,
- version::PRODUCT_VERSION.to_owned(),
- )
- .map_err(|e| e.display_chain_with_msg("Unable to initialize daemon"))
+ Daemon::start(log_dir, resource_dir, cache_dir)
+ .map_err(|e| e.display_chain_with_msg("Unable to initialize daemon"))
}
#[cfg(unix)]
diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs
index aa0ae1bb37..afa6ad165e 100644
--- a/mullvad-daemon/src/management_interface.rs
+++ b/mullvad-daemon/src/management_interface.rs
@@ -233,7 +233,7 @@ pub enum ManagementCommand {
/// Verify if the currently set wireguard key is valid.
VerifyWireguardKey(OneshotSender<bool>),
/// Get information about the currently running and latest app versions
- GetVersionInfo(OneshotSender<BoxFuture<version::AppVersionInfo, mullvad_rpc::Error>>),
+ GetVersionInfo(OneshotSender<version::AppVersionInfo>),
/// Get current version of the app
GetCurrentVersion(OneshotSender<version::AppVersion>),
#[cfg(not(target_os = "android"))]
@@ -315,6 +315,11 @@ impl EventListener for ManagementInterfaceEventBroadcaster {
self.notify(DaemonEvent::RelayList(relay_list));
}
+ fn notify_app_version(&self, app_version_info: version::AppVersionInfo) {
+ log::debug!("Broadcasting new app version info");
+ self.notify(DaemonEvent::AppVersionInfo(app_version_info));
+ }
+
fn notify_key_event(&self, key_event: mullvad_types::wireguard::KeygenEvent) {
log::debug!("Broadcasting new wireguard key event");
self.notify(DaemonEvent::WireguardKey(key_event));
@@ -713,16 +718,7 @@ impl<T: From<ManagementCommand> + 'static + Send> ManagementInterfaceApi
let (tx, rx) = sync::oneshot::channel();
let future = self
.send_command_to_daemon(ManagementCommand::GetVersionInfo(tx))
- .and_then(|_| rx.map_err(|_| Error::internal_error()))
- .and_then(|version_future| {
- version_future.map_err(|error| {
- log::error!(
- "Unable to get version data from API: {}",
- error.display_chain()
- );
- Self::map_rpc_error(&error)
- })
- });
+ .and_then(|_| rx.map_err(|_| Error::internal_error()));
Box::new(future)
}
diff --git a/mullvad-daemon/src/version_check.rs b/mullvad-daemon/src/version_check.rs
new file mode 100644
index 0000000000..65530ae20f
--- /dev/null
+++ b/mullvad-daemon/src/version_check.rs
@@ -0,0 +1,177 @@
+use futures::{Async, Future, Poll};
+use mullvad_rpc::{AppVersionProxy, HttpHandle};
+use mullvad_types::version::AppVersionInfo;
+use std::{
+ fs::File,
+ io,
+ path::{Path, PathBuf},
+ time::{Duration, Instant},
+};
+use talpid_types::ErrorExt;
+use tokio_timer::{TimeoutError, Timer};
+
+const VERSION_INFO_FILENAME: &str = "version-info.json";
+
+const DOWNLOAD_TIMEOUT: Duration = Duration::from_secs(15);
+/// How often the updater should wake up to check the in-memory cache.
+/// This exist to prevent problems around sleeping. If you set it to sleep
+/// for `UPDATE_INTERVAL` directly and the computer is suspended, that clock
+/// won't tick, and the next update will be after 24 hours of the computer being *on*.
+const UPDATE_CHECK_INTERVAL: Duration = Duration::from_secs(60 * 5);
+/// Wait this long until next check after a successful check
+const UPDATE_INTERVAL: Duration = Duration::from_secs(60 * 60 * 24);
+/// Wait this long until next try if an update failed
+const UPDATE_INTERVAL_ERROR: Duration = Duration::from_secs(60 * 60 * 6);
+
+#[cfg(target_os = "linux")]
+const PLATFORM: &str = "linux";
+#[cfg(target_os = "macos")]
+const PLATFORM: &str = "macos";
+#[cfg(target_os = "windows")]
+const PLATFORM: &str = "windows";
+#[cfg(target_os = "android")]
+const PLATFORM: &str = "android";
+
+
+#[derive(err_derive::Error, Debug)]
+pub enum Error {
+ #[error(display = "Failed to open app version cache file for reading")]
+ ReadCachedRelays(#[error(cause)] io::Error),
+
+ #[error(display = "Failed to open app version cache file for writing")]
+ WriteRelayCache(#[error(cause)] io::Error),
+
+ #[error(display = "Failure in serialization of the version info")]
+ Serialize(#[error(cause)] serde_json::Error),
+
+ #[error(display = "Timed out when trying to check the latest app version")]
+ DownloadTimeout,
+
+ #[error(display = "Failed to check the latest app version")]
+ Download(#[error(cause)] mullvad_rpc::Error),
+}
+
+impl<F> From<TimeoutError<F>> for Error {
+ fn from(_: TimeoutError<F>) -> Error {
+ Error::DownloadTimeout
+ }
+}
+
+
+pub struct VersionUpdater<F: Fn(&AppVersionInfo) + Send + 'static> {
+ version_proxy: AppVersionProxy<HttpHandle>,
+ cache_path: PathBuf,
+ on_version_update: F,
+ last_app_version_info: AppVersionInfo,
+ next_update_time: Instant,
+ state: VersionUpdaterState,
+}
+
+enum VersionUpdaterState {
+ Sleeping(tokio_timer::Sleep),
+ Updating(Box<dyn Future<Item = AppVersionInfo, Error = Error> + Send + 'static>),
+}
+
+impl<F: Fn(&AppVersionInfo) + Send + 'static> VersionUpdater<F> {
+ pub fn new(
+ rpc_handle: HttpHandle,
+ cache_dir: PathBuf,
+ on_version_update: F,
+ last_app_version_info: AppVersionInfo,
+ ) -> Self {
+ let version_proxy = AppVersionProxy::new(rpc_handle);
+ let cache_path = cache_dir.join(VERSION_INFO_FILENAME);
+ Self {
+ version_proxy,
+ cache_path,
+ on_version_update,
+ last_app_version_info,
+ next_update_time: Instant::now(),
+ state: VersionUpdaterState::Sleeping(Self::create_sleep_future()),
+ }
+ }
+
+ fn create_sleep_future() -> tokio_timer::Sleep {
+ Timer::default().sleep(UPDATE_CHECK_INTERVAL)
+ }
+
+ fn create_update_future(
+ &mut self,
+ ) -> Box<dyn Future<Item = AppVersionInfo, Error = Error> + Send + 'static> {
+ let download_future = self
+ .version_proxy
+ .app_version_check(&crate::version::PRODUCT_VERSION.to_owned(), PLATFORM)
+ .map_err(Error::Download);
+ let future = Timer::default().timeout(download_future, DOWNLOAD_TIMEOUT);
+ Box::new(future)
+ }
+
+ fn write_cache(&self) -> Result<(), Error> {
+ log::debug!(
+ "Writing version check cache to {}",
+ self.cache_path.display()
+ );
+ let file = File::create(&self.cache_path).map_err(Error::WriteRelayCache)?;
+ serde_json::to_writer_pretty(io::BufWriter::new(file), &self.last_app_version_info)
+ .map_err(Error::Serialize)
+ }
+}
+
+impl<F: Fn(&AppVersionInfo) + Send + 'static> Future for VersionUpdater<F> {
+ type Item = ();
+ type Error = ();
+
+ fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
+ loop {
+ let next_state = match &mut self.state {
+ VersionUpdaterState::Sleeping(timer) => match timer.poll() {
+ Ok(Async::NotReady) => return Ok(Async::NotReady),
+ Err(e) => {
+ log::error!("Version check sleep error: {}", e);
+ return Err(());
+ }
+ Ok(Async::Ready(())) => {
+ if Instant::now() > self.next_update_time {
+ VersionUpdaterState::Updating(self.create_update_future())
+ } else {
+ VersionUpdaterState::Sleeping(Self::create_sleep_future())
+ }
+ }
+ },
+ VersionUpdaterState::Updating(future) => match future.poll() {
+ Ok(Async::NotReady) => return Ok(Async::NotReady),
+ Err(error) => {
+ log::error!("{}", error.display_chain_with_msg("Version check failed"));
+ self.next_update_time = Instant::now() + UPDATE_INTERVAL_ERROR;
+ VersionUpdaterState::Sleeping(Self::create_sleep_future())
+ }
+ Ok(Async::Ready(app_version_info)) => {
+ if app_version_info != self.last_app_version_info {
+ self.next_update_time = Instant::now() + UPDATE_INTERVAL;
+ log::debug!("Got new version check: {:?}", app_version_info);
+ (self.on_version_update)(&app_version_info);
+ self.last_app_version_info = app_version_info;
+ if let Err(e) = self.write_cache() {
+ log::error!(
+ "{}",
+ e.display_chain_with_msg(
+ "Unable to cache version check response"
+ )
+ );
+ }
+ }
+ VersionUpdaterState::Sleeping(Self::create_sleep_future())
+ }
+ },
+ };
+ self.state = next_state;
+ }
+ }
+}
+
+pub fn load_cache(cache_dir: &Path) -> Result<AppVersionInfo, Error> {
+ let path = cache_dir.join(VERSION_INFO_FILENAME);
+ log::debug!("Loading version check cache from {}", path.display());
+ let file = File::open(path).map_err(Error::ReadCachedRelays)?;
+ serde_json::from_reader(io::BufReader::new(file)).map_err(Error::Serialize)
+}
diff --git a/mullvad-jni/src/daemon_interface.rs b/mullvad-jni/src/daemon_interface.rs
index 69d16d4034..2820315c74 100644
--- a/mullvad-jni/src/daemon_interface.rs
+++ b/mullvad-jni/src/daemon_interface.rs
@@ -133,10 +133,7 @@ impl DaemonInterface {
self.send_command(ManagementCommand::GetVersionInfo(tx))?;
- rx.wait()
- .map_err(|_| Error::NoResponse)?
- .wait()
- .map_err(Error::RpcError)
+ rx.wait().map_err(|_| Error::NoResponse)
}
pub fn get_wireguard_key(&self) -> Result<Option<wireguard::PublicKey>> {
diff --git a/mullvad-jni/src/jni_event_listener.rs b/mullvad-jni/src/jni_event_listener.rs
index cb0e14fb40..737fa1b9b2 100644
--- a/mullvad-jni/src/jni_event_listener.rs
+++ b/mullvad-jni/src/jni_event_listener.rs
@@ -6,7 +6,8 @@ use jni::{
};
use mullvad_daemon::EventListener;
use mullvad_types::{
- relay_list::RelayList, settings::Settings, states::TunnelState, wireguard::KeygenEvent,
+ relay_list::RelayList, settings::Settings, states::TunnelState, version::AppVersionInfo,
+ wireguard::KeygenEvent,
};
use std::{sync::mpsc, thread};
use talpid_types::ErrorExt;
@@ -28,6 +29,7 @@ enum Event {
RelayList(RelayList),
Settings(Settings),
Tunnel(TunnelState),
+ AppVersionInfo(AppVersionInfo),
}
#[derive(Clone, Debug)]
@@ -55,11 +57,16 @@ impl EventListener for JniEventListener {
fn notify_relay_list(&self, relay_list: RelayList) {
let _ = self.0.send(Event::RelayList(relay_list));
}
+
+ fn notify_app_version(&self, app_version_info: AppVersionInfo) {
+ let _ = self.0.send(Event::AppVersionInfo(app_version_info));
+ }
}
struct JniEventHandler<'env> {
env: AttachGuard<'env>,
mullvad_ipc_client: JObject<'env>,
+ notify_app_version_info_event: JMethodID<'env>,
notify_keygen_event: JMethodID<'env>,
notify_relay_list_event: JMethodID<'env>,
notify_settings_event: JMethodID<'env>,
@@ -104,6 +111,12 @@ impl<'env> JniEventHandler<'env> {
events: mpsc::Receiver<Event>,
) -> Result<Self, Error> {
let class = get_class("net/mullvad/mullvadvpn/MullvadDaemon");
+ let notify_app_version_info_event = Self::get_method_id(
+ &env,
+ &class,
+ "notifyAppVersionInfoEvent",
+ "(Lnet/mullvad/mullvadvpn/model/AppVersionInfo;)V",
+ )?;
let notify_keygen_event = Self::get_method_id(
&env,
&class,
@@ -132,6 +145,7 @@ impl<'env> JniEventHandler<'env> {
Ok(JniEventHandler {
env,
mullvad_ipc_client,
+ notify_app_version_info_event,
notify_keygen_event,
notify_relay_list_event,
notify_settings_event,
@@ -157,6 +171,9 @@ impl<'env> JniEventHandler<'env> {
Event::RelayList(relay_list) => self.handle_relay_list_event(relay_list),
Event::Settings(settings) => self.handle_settings(settings),
Event::Tunnel(tunnel_event) => self.handle_tunnel_event(tunnel_event),
+ Event::AppVersionInfo(app_version_info) => {
+ self.handle_app_version_info_event(app_version_info)
+ }
}
}
}
@@ -232,4 +249,24 @@ impl<'env> JniEventHandler<'env> {
);
}
}
+
+ fn handle_app_version_info_event(&self, app_version_info: AppVersionInfo) {
+ let java_app_version_info = self.env.auto_local(app_version_info.into_java(&self.env));
+
+ let result = self.env.call_method_unchecked(
+ self.mullvad_ipc_client,
+ self.notify_app_version_info_event,
+ JavaType::Primitive(Primitive::Void),
+ &[JValue::Object(java_app_version_info.as_obj())],
+ );
+
+ if let Err(error) = result {
+ log::error!(
+ "{}",
+ error.display_chain_with_msg(
+ "Failed to call MullvadDaemon.notifyAppVersionInfoEvent"
+ )
+ );
+ }
+ }
}
diff --git a/mullvad-jni/src/lib.rs b/mullvad-jni/src/lib.rs
index c1d85a5a6c..e4ed6c83a5 100644
--- a/mullvad-jni/src/lib.rs
+++ b/mullvad-jni/src/lib.rs
@@ -228,7 +228,6 @@ fn create_daemon(
Some(log_dir),
resource_dir,
cache_dir,
- version::PRODUCT_VERSION.to_owned(),
)
.map_err(Error::InitializeDaemon)?;
diff --git a/mullvad-types/src/lib.rs b/mullvad-types/src/lib.rs
index 448befd16f..3456c5579b 100644
--- a/mullvad-types/src/lib.rs
+++ b/mullvad-types/src/lib.rs
@@ -35,6 +35,9 @@ pub enum DaemonEvent {
/// The daemon got an updated relay list.
RelayList(relay_list::RelayList),
+ /// The daemon got update version info.
+ AppVersionInfo(version::AppVersionInfo),
+
/// Key event
WireguardKey(wireguard::KeygenEvent),
}
diff --git a/mullvad-types/src/version.rs b/mullvad-types/src/version.rs
index 695c686fc4..6e626104d7 100644
--- a/mullvad-types/src/version.rs
+++ b/mullvad-types/src/version.rs
@@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize};
/// AppVersionInfo represents the current stable and the current latest release versions of the
/// Mullvad VPN app.
-#[derive(Debug, Clone, Serialize, Deserialize)]
+#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub struct AppVersionInfo {
/// False if Mullvad has stopped supporting the currently running version. This could mean
/// a number of things. For example: