summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorLinus Färnstrand <linus@mullvad.net>2019-10-02 10:49:24 +0200
committerLinus Färnstrand <linus@mullvad.net>2019-10-07 16:24:41 +0200
commit960c04edba557b7458ef7da4ebde7aff72c4905d (patch)
treed0ac544db41e6f14cda8ba625bf458406fde04f5
parent2db11de43396abfe1f6a295d9fd22511898f7a07 (diff)
downloadmullvadvpn-960c04edba557b7458ef7da4ebde7aff72c4905d.tar.xz
mullvadvpn-960c04edba557b7458ef7da4ebde7aff72c4905d.zip
Implement fetching app version every 24 hours and broadcast to frontend
-rw-r--r--mullvad-cli/src/cmds/status.rs5
-rw-r--r--mullvad-daemon/src/lib.rs19
-rw-r--r--mullvad-daemon/src/management_interface.rs5
-rw-r--r--mullvad-daemon/src/version_check.rs211
-rw-r--r--mullvad-types/src/lib.rs3
-rw-r--r--mullvad-types/src/version.rs2
6 files changed, 242 insertions, 3 deletions
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..1979447737 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::{
@@ -235,6 +236,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);
}
@@ -379,8 +384,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 +391,18 @@ where
&cache_dir,
);
+ let version_check_listener = event_listener.clone();
+ let on_version_check_update = move |app_version_info: &AppVersionInfo| {
+ version_check_listener.notify_app_version(app_version_info.clone());
+ };
+ let version_check_future = version_check::spawn(
+ version.clone(),
+ rpc_handle.clone(),
+ on_version_check_update,
+ &cache_dir,
+ );
+ tokio_remote.spawn(|_| version_check_future);
+
let settings = settings::load();
let account_history =
diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs
index aa0ae1bb37..59d9f91097 100644
--- a/mullvad-daemon/src/management_interface.rs
+++ b/mullvad-daemon/src/management_interface.rs
@@ -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));
diff --git a/mullvad-daemon/src/version_check.rs b/mullvad-daemon/src/version_check.rs
new file mode 100644
index 0000000000..29a857eabb
--- /dev/null
+++ b/mullvad-daemon/src/version_check.rs
@@ -0,0 +1,211 @@
+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 check is very cheap. The only reason to not have it very often is because if downloading
+/// constantly fails it will try very often and fill the logs etc.
+const UPDATE_CHECK_INTERVAL: Duration = Duration::from_secs(60 * 5);
+const UPDATE_INTERVAL: Duration = Duration::from_secs(60 * 60 * 24);
+
+#[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";
+
+pub fn spawn<F: Fn(&AppVersionInfo) + Send + 'static>(
+ version: String,
+ rpc_handle: HttpHandle,
+ on_version_update: F,
+ cache_dir: &Path,
+) -> VersionUpdater<F> {
+ let version_proxy = AppVersionProxy::new(rpc_handle);
+ let cache_path = cache_dir.join(VERSION_INFO_FILENAME);
+
+ let last_app_version_info = match load_cache(&cache_path) {
+ Ok(app_version_info) => Some(app_version_info),
+ Err(error) => {
+ log::warn!(
+ "{}",
+ error.display_chain_with_msg("Unable to load cached version info")
+ );
+ None
+ }
+ };
+
+ VersionUpdater::new(
+ version,
+ version_proxy,
+ cache_path,
+ on_version_update,
+ last_app_version_info,
+ )
+}
+
+pub struct VersionUpdater<F: Fn(&AppVersionInfo) + Send + 'static> {
+ version: String,
+ version_proxy: AppVersionProxy<HttpHandle>,
+ cache_path: PathBuf,
+ on_version_update: F,
+ last_app_version_info: Option<AppVersionInfo>,
+ next_update_time: Instant,
+ state: Option<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(
+ version: String,
+ version_proxy: AppVersionProxy<HttpHandle>,
+ cache_path: PathBuf,
+ on_version_update: F,
+ last_app_version_info: Option<AppVersionInfo>,
+ ) -> Self {
+ Self {
+ version,
+ version_proxy,
+ cache_path,
+ on_version_update,
+ last_app_version_info,
+ next_update_time: Instant::now(),
+ state: Some(VersionUpdaterState::Sleeping(Self::create_sleep_future())),
+ }
+ }
+
+ fn poll_sleep(&mut self, timer: &mut tokio_timer::Sleep) -> Option<VersionUpdaterState> {
+ let should_progress = match timer.poll() {
+ Err(e) => {
+ log::error!("Version check sleep error: {}", e);
+ true
+ }
+ Ok(Async::Ready(())) => true,
+ Ok(Async::NotReady) => false,
+ };
+ if should_progress {
+ let now = Instant::now();
+ Some(if now > self.next_update_time {
+ self.next_update_time = now + UPDATE_INTERVAL;
+ VersionUpdaterState::Updating(self.create_update_future())
+ } else {
+ VersionUpdaterState::Sleeping(Self::create_sleep_future())
+ })
+ } else {
+ None
+ }
+ }
+
+ fn poll_updater(
+ &mut self,
+ future: &mut Box<dyn Future<Item = AppVersionInfo, Error = Error> + Send + 'static>,
+ ) -> Option<VersionUpdaterState> {
+ let should_progress = match future.poll() {
+ Err(error) => {
+ log::error!("{}", error.display_chain_with_msg("Version check failed"));
+ true
+ }
+ Ok(Async::Ready(app_version_info)) => {
+ if Some(&app_version_info) != self.last_app_version_info.as_ref() {
+ log::debug!("Got new version check: {:?}", app_version_info);
+ write_cache(&app_version_info, &self.cache_path).unwrap();
+ (self.on_version_update)(&app_version_info);
+ self.last_app_version_info = Some(app_version_info);
+ }
+ true
+ }
+ Ok(Async::NotReady) => false,
+ };
+ if should_progress {
+ Some(VersionUpdaterState::Sleeping(Self::create_sleep_future()))
+ } else {
+ None
+ }
+ }
+
+ 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(&self.version, PLATFORM)
+ .map_err(Error::Download);
+ let future = Timer::default().timeout(download_future, DOWNLOAD_TIMEOUT);
+ Box::new(future)
+ }
+}
+
+impl<F: Fn(&AppVersionInfo) + Send + 'static> Future for VersionUpdater<F> {
+ type Item = ();
+ type Error = ();
+
+ fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
+ let mut state = self.state.take().expect("No state in VersionUpdater");
+ while let Some(new_state) = match &mut state {
+ VersionUpdaterState::Sleeping(sleep) => self.poll_sleep(sleep),
+ VersionUpdaterState::Updating(future) => self.poll_updater(future),
+ } {
+ state = new_state;
+ }
+ self.state = Some(state);
+ Ok(Async::NotReady)
+ }
+}
+
+fn load_cache(path: &Path) -> Result<AppVersionInfo, Error> {
+ 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)
+}
+
+fn write_cache(app_version_info: &AppVersionInfo, path: &Path) -> Result<(), Error> {
+ log::debug!("Writing version check cache to {}", path.display());
+ let file = File::create(path).map_err(Error::WriteRelayCache)?;
+ serde_json::to_writer_pretty(io::BufWriter::new(file), app_version_info)
+ .map_err(Error::Serialize)
+}
+
+#[derive(err_derive::Error, Debug)]
+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
+ }
+}
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: