diff options
| -rw-r--r-- | mullvad-cli/src/cmds/status.rs | 5 | ||||
| -rw-r--r-- | mullvad-daemon/src/lib.rs | 19 | ||||
| -rw-r--r-- | mullvad-daemon/src/management_interface.rs | 5 | ||||
| -rw-r--r-- | mullvad-daemon/src/version_check.rs | 211 | ||||
| -rw-r--r-- | mullvad-types/src/lib.rs | 3 | ||||
| -rw-r--r-- | mullvad-types/src/version.rs | 2 |
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: |
