summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2022-09-13 15:59:41 +0200
committerDavid Lönnhager <david.l@mullvad.net>2022-09-13 15:59:41 +0200
commit847a2de761d1ee221ea0c909e3caf564b974b646 (patch)
tree6e78dfc97b20170f64004096837ad715a726aeb8
parentd0a830effe2b17bad73fc43541eac4c600cbace1 (diff)
parentf04c60af533f107eab1b39fb36e695cff3d01d60 (diff)
downloadmullvadvpn-847a2de761d1ee221ea0c909e3caf564b974b646.tar.xz
mullvadvpn-847a2de761d1ee221ea0c909e3caf564b974b646.zip
Merge branch 'timeout-check-account-expiry'
-rw-r--r--CHANGELOG.md1
-rw-r--r--mullvad-daemon/src/device/api.rs40
-rw-r--r--mullvad-daemon/src/device/mod.rs208
-rw-r--r--mullvad-daemon/src/device/service.rs12
-rw-r--r--mullvad-daemon/src/lib.rs70
-rw-r--r--mullvad-daemon/src/management_interface.rs24
6 files changed, 283 insertions, 72 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 853af3f5aa..5d18ade54e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -67,6 +67,7 @@ Line wrap the file at 100 chars. Th
- Enable interface settings when app is logged out
- Fix 'mullvad status -v' to include the port of the endpoint when connecting over TCP.
- Check whether the device is valid when reconnecting from the error state.
+- Stop reconnecting when the account has run out of time.
#### Windows
- Only use the most recent list of apps to split when resuming from hibernation/sleep if applying
diff --git a/mullvad-daemon/src/device/api.rs b/mullvad-daemon/src/device/api.rs
index 6e00b669ca..93dae78261 100644
--- a/mullvad-daemon/src/device/api.rs
+++ b/mullvad-daemon/src/device/api.rs
@@ -1,7 +1,8 @@
use std::pin::Pin;
+use chrono::{DateTime, Utc};
use futures::{future::FusedFuture, Future};
-use mullvad_types::{device::Device, wireguard::WireguardData};
+use mullvad_types::{account::VoucherSubmission, device::Device, wireguard::WireguardData};
use super::{Error, PrivateAccountAndDevice, ResponseTx};
@@ -34,6 +35,18 @@ impl CurrentApiCall {
self.current_call = Some(Call::Validation(validation));
}
+ pub fn set_expiry_check(&mut self, expiry_call: ApiCall<DateTime<Utc>>) {
+ self.current_call = Some(Call::ExpiryCheck(expiry_call));
+ }
+
+ pub fn set_voucher_submission(
+ &mut self,
+ voucher_call: ApiCall<VoucherSubmission>,
+ tx: ResponseTx<VoucherSubmission>,
+ ) {
+ self.current_call = Some(Call::VoucherSubmission(voucher_call, Some(tx)));
+ }
+
pub fn is_validating(&self) -> bool {
matches!(
&self.current_call,
@@ -41,6 +54,10 @@ impl CurrentApiCall {
)
}
+ pub fn is_checking_expiry(&self) -> bool {
+ matches!(&self.current_call, Some(Call::ExpiryCheck(_)))
+ }
+
pub fn is_running_timed_totation(&self) -> bool {
matches!(&self.current_call, Some(Call::TimerKeyRotation(_)))
}
@@ -88,6 +105,11 @@ enum Call {
TimerKeyRotation(ApiCall<WireguardData>),
OneshotKeyRotation(ApiCall<WireguardData>),
Validation(ApiCall<Device>),
+ VoucherSubmission(
+ ApiCall<VoucherSubmission>,
+ Option<ResponseTx<VoucherSubmission>>,
+ ),
+ ExpiryCheck(ApiCall<DateTime<Utc>>),
}
impl futures::Future for Call {
@@ -110,6 +132,17 @@ impl futures::Future for Call {
Pin::new(call).poll(cx).map(ApiResult::Rotation)
}
Validation(call) => Pin::new(call).poll(cx).map(ApiResult::Validation),
+ VoucherSubmission(call, tx) => {
+ if let std::task::Poll::Ready(response) = Pin::new(call).poll(cx) {
+ std::task::Poll::Ready(ApiResult::VoucherSubmission(
+ response,
+ tx.take().unwrap(),
+ ))
+ } else {
+ std::task::Poll::Pending
+ }
+ }
+ ExpiryCheck(call) => Pin::new(call).poll(cx).map(ApiResult::ExpiryCheck),
}
}
}
@@ -118,4 +151,9 @@ pub(crate) enum ApiResult {
Login(Result<PrivateAccountAndDevice, Error>, ResponseTx<()>),
Rotation(Result<WireguardData, Error>),
Validation(Result<Device, Error>),
+ VoucherSubmission(
+ Result<VoucherSubmission, Error>,
+ ResponseTx<VoucherSubmission>,
+ ),
+ ExpiryCheck(Result<DateTime<Utc>, Error>),
}
diff --git a/mullvad-daemon/src/device/mod.rs b/mullvad-daemon/src/device/mod.rs
index 886680fd39..b2b9976501 100644
--- a/mullvad-daemon/src/device/mod.rs
+++ b/mullvad-daemon/src/device/mod.rs
@@ -6,7 +6,7 @@ use futures::{
use mullvad_api::rest;
use mullvad_types::{
- account::AccountToken,
+ account::{AccountToken, VoucherSubmission},
device::{
AccountAndDevice, Device, DeviceEvent, DeviceEventCause, DeviceId, DeviceName, DevicePort,
DeviceState,
@@ -56,6 +56,10 @@ pub enum Error {
InvalidDevice,
#[error(display = "Invalid account")]
InvalidAccount,
+ #[error(display = "Invalid voucher code")]
+ InvalidVoucher,
+ #[error(display = "The voucher has already been used")]
+ UsedVoucher,
#[error(display = "Failed to read or write device cache")]
DeviceIoError(#[error(source)] io::Error),
#[error(display = "Failed parse device cache")]
@@ -221,6 +225,14 @@ impl From<PrivateDevice> for Device {
}
#[derive(Clone)]
+pub(crate) enum AccountEvent {
+ /// Emitted when the device state changes.
+ Device(PrivateDeviceEvent),
+ /// Emitted when the account expiry is fetched.
+ Expiry(DateTime<Utc>),
+}
+
+#[derive(Clone)]
pub(crate) enum PrivateDeviceEvent {
/// Logged in on a new device.
Login(PrivateAccountAndDevice),
@@ -243,10 +255,8 @@ impl From<PrivateDeviceEvent> for DeviceEvent {
PrivateDeviceEvent::Updated(_) => DeviceEventCause::Updated,
PrivateDeviceEvent::RotatedKey(_) => DeviceEventCause::RotatedKey,
};
- DeviceEvent {
- cause,
- new_state: DeviceState::from(event.state()),
- }
+ let new_state = DeviceState::from(event.state());
+ DeviceEvent { cause, new_state }
}
}
@@ -299,6 +309,8 @@ enum AccountManagerCommand {
RotateKey(ResponseTx<()>),
SetRotationInterval(RotationInterval, ResponseTx<()>),
ValidateDevice(ResponseTx<()>),
+ SubmitVoucher(String, ResponseTx<VoucherSubmission>),
+ CheckExpiry(ResponseTx<DateTime<Utc>>),
Shutdown(oneshot::Sender<()>),
}
@@ -347,6 +359,15 @@ impl AccountManagerHandle {
.await
}
+ pub async fn submit_voucher(&self, voucher: String) -> Result<VoucherSubmission, Error> {
+ self.send_command(move |tx| AccountManagerCommand::SubmitVoucher(voucher, tx))
+ .await
+ }
+
+ pub async fn check_expiry(&self) -> Result<DateTime<Utc>, Error> {
+ self.send_command(AccountManagerCommand::CheckExpiry).await
+ }
+
pub async fn shutdown(self) {
let (tx, rx) = oneshot::channel();
let _ = self
@@ -369,12 +390,14 @@ impl AccountManagerHandle {
pub(crate) struct AccountManager {
cacher: DeviceCacher,
+ account_service: AccountService,
device_service: DeviceService,
data: PrivateDeviceState,
rotation_interval: RotationInterval,
- listeners: Vec<Box<dyn Sender<PrivateDeviceEvent> + Send>>,
+ listeners: Vec<Box<dyn Sender<AccountEvent> + Send>>,
last_validation: Option<SystemTime>,
validation_requests: Vec<ResponseTx<()>>,
+ expiry_requests: Vec<ResponseTx<DateTime<Utc>>>,
rotation_requests: Vec<ResponseTx<()>>,
data_requests: Vec<ResponseTx<PrivateDeviceState>>,
}
@@ -386,7 +409,7 @@ impl AccountManager {
rest_handle: rest::MullvadRestHandle,
settings_dir: &Path,
initial_rotation_interval: RotationInterval,
- listener_tx: impl Sender<PrivateDeviceEvent> + Send + 'static,
+ listener_tx: impl Sender<AccountEvent> + Send + 'static,
) -> Result<(AccountManagerHandle, PrivateDeviceState), Error> {
let (cacher, data) = DeviceCacher::new(settings_dir).await?;
let token = data.device().map(|state| state.account_token.clone());
@@ -399,12 +422,14 @@ impl AccountManager {
let device_service = DeviceService::new(rest_handle, api_availability);
let manager = AccountManager {
cacher,
+ account_service: account_service.clone(),
device_service: device_service.clone(),
data: data.clone(),
rotation_interval: initial_rotation_interval,
listeners: vec![Box::new(listener_tx)],
last_validation: None,
validation_requests: vec![],
+ expiry_requests: vec![],
rotation_requests: vec![],
data_requests: vec![],
};
@@ -485,6 +510,12 @@ impl AccountManager {
Some(AccountManagerCommand::ValidateDevice(tx)) => {
self.handle_validation_request(tx, &mut current_api_call);
},
+ Some(AccountManagerCommand::SubmitVoucher(voucher, tx)) => {
+ self.handle_voucher_submission(tx, voucher, &mut current_api_call);
+ },
+ Some(AccountManagerCommand::CheckExpiry(tx)) => {
+ self.handle_expiry_request(tx, &mut current_api_call);
+ },
None => {
break;
@@ -535,6 +566,59 @@ impl AccountManager {
}
}
+ fn handle_voucher_submission(
+ &mut self,
+ tx: ResponseTx<VoucherSubmission>,
+ voucher: String,
+ current_api_call: &mut api::CurrentApiCall,
+ ) {
+ if current_api_call.is_logging_in() {
+ let _ = tx.send(Err(Error::AccountChange));
+ return;
+ }
+
+ let create_submission = move || {
+ let old_config = self.data.device().ok_or(Error::NoDevice)?;
+ let account_token = old_config.account_token.clone();
+ let account_service = self.account_service.clone();
+ Ok(async move { account_service.submit_voucher(account_token, voucher).await })
+ };
+
+ match create_submission() {
+ Ok(call) => {
+ current_api_call.set_voucher_submission(Box::pin(call), tx);
+ }
+ Err(err) => {
+ let _ = tx.send(Err(err));
+ }
+ }
+ }
+
+ fn handle_expiry_request(
+ &mut self,
+ tx: ResponseTx<DateTime<Utc>>,
+ current_api_call: &mut api::CurrentApiCall,
+ ) {
+ if current_api_call.is_logging_in() {
+ let _ = tx.send(Err(Error::AccountChange));
+ return;
+ }
+ if current_api_call.is_checking_expiry() {
+ self.expiry_requests.push(tx);
+ return;
+ }
+
+ match self.expiry_call() {
+ Ok(call) => {
+ current_api_call.set_expiry_check(Box::pin(call));
+ self.expiry_requests.push(tx);
+ }
+ Err(err) => {
+ let _ = tx.send(Err(err));
+ }
+ }
+ }
+
async fn consume_api_result(
&mut self,
result: api::ApiResult,
@@ -545,6 +629,10 @@ impl AccountManager {
Login(data, tx) => self.consume_login(data, tx).await,
Rotation(rotation_response) => self.consume_rotation_result(rotation_response).await,
Validation(data_response) => self.consume_validation(data_response, api_call).await,
+ VoucherSubmission(data_response, tx) => {
+ self.consume_voucher_result(data_response, tx).await
+ }
+ ExpiryCheck(data_response) => self.consume_expiry_result(data_response).await,
}
}
@@ -559,6 +647,61 @@ impl AccountManager {
Self::drain_requests(&mut self.data_requests, || Ok(data.clone()));
}
+ async fn consume_voucher_result(
+ &mut self,
+ response: Result<VoucherSubmission, Error>,
+ tx: ResponseTx<VoucherSubmission>,
+ ) {
+ match &response {
+ Ok(submission) => {
+ // Send expiry update event
+ let event = AccountEvent::Expiry(submission.new_expiry);
+ self.listeners
+ .retain(|listener| listener.send(event.clone()).is_ok());
+ }
+ Err(Error::InvalidAccount) => {
+ self.revoke_device(|| Error::InvalidAccount).await;
+ }
+ Err(Error::InvalidDevice) => {
+ self.revoke_device(|| Error::InvalidDevice).await;
+ }
+ Err(err) => log::error!("Failed to submit voucher: {}", err),
+ }
+ let _ = tx.send(response);
+ }
+
+ async fn consume_expiry_result(&mut self, response: Result<DateTime<Utc>, Error>) {
+ match response {
+ Ok(expiry) => {
+ if expiry > chrono::Utc::now() {
+ log::debug!("Account has time left");
+ } else {
+ log::debug!("Account has no time left");
+ }
+
+ // Send expiry update event
+ let event = AccountEvent::Expiry(expiry);
+ self.listeners
+ .retain(|listener| listener.send(event.clone()).is_ok());
+
+ Self::drain_requests(&mut self.expiry_requests, || Ok(expiry));
+ }
+ Err(Error::InvalidAccount) => {
+ self.revoke_device(|| Error::InvalidAccount).await;
+ }
+ Err(Error::InvalidDevice) => {
+ self.revoke_device(|| Error::InvalidDevice).await;
+ }
+ Err(err) => {
+ log::error!("Failed to check account expiry: {}", err);
+ let cloneable_err = Arc::new(err);
+ Self::drain_requests(&mut self.expiry_requests, || {
+ Err(Error::ResponseFailure(cloneable_err.clone()))
+ });
+ }
+ }
+ }
+
async fn consume_validation(
&mut self,
response: Result<Device, Error>,
@@ -602,10 +745,10 @@ impl AccountManager {
}
}
Err(Error::InvalidAccount) => {
- self.invalidate_current_data(|| Error::InvalidAccount).await;
+ self.revoke_device(|| Error::InvalidAccount).await;
}
Err(Error::InvalidDevice) => {
- self.invalidate_current_data(|| Error::InvalidDevice).await;
+ self.revoke_device(|| Error::InvalidDevice).await;
}
Err(err) => {
log::error!("Failed to validate device: {}", err);
@@ -642,27 +785,26 @@ impl AccountManager {
match self.set(PrivateDeviceEvent::RotatedKey(config)).await {
Ok(_) => {
Self::drain_requests(&mut self.rotation_requests, || Ok(()));
-
Self::drain_requests(&mut self.validation_requests, || Ok(()));
}
Err(err) => {
- self.drain_requests_with_err(err);
+ self.drain_device_requests_with_err(err);
}
}
}
Err(Error::InvalidAccount) => {
- self.invalidate_current_data(|| Error::InvalidAccount).await;
+ self.revoke_device(|| Error::InvalidAccount).await;
}
Err(Error::InvalidDevice) => {
- self.invalidate_current_data(|| Error::InvalidDevice).await;
+ self.revoke_device(|| Error::InvalidDevice).await;
}
Err(err) => {
- self.drain_requests_with_err(err);
+ self.drain_device_requests_with_err(err);
}
}
}
- fn drain_requests_with_err(&mut self, err: Error) {
+ fn drain_device_requests_with_err(&mut self, err: Error) {
let cloneable_err = Arc::new(err);
Self::drain_requests(&mut self.rotation_requests, || {
Err(Error::ResponseFailure(cloneable_err.clone()))
@@ -696,7 +838,7 @@ impl AccountManager {
})
}
- async fn invalidate_current_data(&mut self, err_constructor: impl Fn() -> Error) {
+ async fn revoke_device(&mut self, err_constructor: impl Fn() -> Error) {
log::debug!("Invalidating the current device");
if let Err(err) = self.cacher.write(&PrivateDeviceState::Revoked).await {
@@ -709,9 +851,13 @@ impl AccountManager {
Self::drain_requests(&mut self.validation_requests, || Err(err_constructor()));
Self::drain_requests(&mut self.rotation_requests, || Err(err_constructor()));
+ Self::drain_requests(&mut self.expiry_requests, || Err(err_constructor()));
- self.listeners
- .retain(|listener| listener.send(PrivateDeviceEvent::Revoked).is_ok());
+ self.listeners.retain(|listener| {
+ listener
+ .send(AccountEvent::Device(PrivateDeviceEvent::Revoked))
+ .is_ok()
+ });
}
async fn logout(&mut self, tx: ResponseTx<()>) {
@@ -727,8 +873,11 @@ impl AccountManager {
let old_config = self.data.logout();
- self.listeners
- .retain(|listener| listener.send(PrivateDeviceEvent::Logout).is_ok());
+ self.listeners.retain(|listener| {
+ listener
+ .send(AccountEvent::Device(PrivateDeviceEvent::Logout))
+ .is_ok()
+ });
if let Some(old_config) = old_config {
let logout_call = tokio::spawn(Box::pin(self.logout_api_call(old_config)));
@@ -776,6 +925,7 @@ impl AccountManager {
self.data = device_state;
+ let event = AccountEvent::Device(event);
self.listeners
.retain(|listener| listener.send(event.clone()).is_ok());
@@ -836,6 +986,13 @@ impl AccountManager {
Ok(self.fetch_device_config(old_config))
}
+ fn expiry_call(&self) -> Result<impl Future<Output = Result<DateTime<Utc>, Error>>, Error> {
+ let old_config = self.data.device().ok_or(Error::NoDevice)?;
+ let account_token = old_config.account_token.clone();
+ let account_service = self.account_service.clone();
+ Ok(async move { account_service.check_expiry_2(account_token).await })
+ }
+
fn needs_validation(&mut self) -> bool {
if !self.data.logged_in() {
return true;
@@ -988,10 +1145,12 @@ impl TunnelStateChangeHandler {
if !check_validity.swap(false, Ordering::SeqCst) {
return;
}
- if let Err(error) = handle.validate_device().await {
+ if let Err(error) = Self::check_validity(handle).await {
log::error!(
"{}",
- error.display_chain_with_msg("Failed to check device validity")
+ error.display_chain_with_msg(
+ "Failed to check device or account validity"
+ )
);
if error.is_network_error() || error.is_aborted() {
check_validity.store(true, Ordering::SeqCst);
@@ -1009,4 +1168,9 @@ impl TunnelStateChangeHandler {
_ => (),
}
}
+
+ pub async fn check_validity(handle: AccountManagerHandle) -> Result<(), Error> {
+ handle.validate_device().await?;
+ handle.check_expiry().await.map(|_expiry| ())
+ }
}
diff --git a/mullvad-daemon/src/device/service.rs b/mullvad-daemon/src/device/service.rs
index 90a44d5fe1..1f574f272f 100644
--- a/mullvad-daemon/src/device/service.rs
+++ b/mullvad-daemon/src/device/service.rs
@@ -310,11 +310,15 @@ impl AccountService {
result
}
+ pub async fn check_expiry_2(&self, token: AccountToken) -> Result<DateTime<Utc>, Error> {
+ self.check_expiry(token).await.map_err(map_rest_error)
+ }
+
pub async fn submit_voucher(
- &mut self,
+ &self,
account_token: AccountToken,
voucher: String,
- ) -> Result<VoucherSubmission, rest::Error> {
+ ) -> Result<VoucherSubmission, Error> {
let mut proxy = self.proxy.clone();
let api_handle = self.api_availability.clone();
let result = retry_future_n(
@@ -328,7 +332,7 @@ impl AccountService {
self.initial_check_abort_handle.abort();
self.api_availability.resume_background();
}
- result
+ result.map_err(map_rest_error)
}
}
@@ -422,6 +426,8 @@ fn map_rest_error(error: rest::Error) -> Error {
mullvad_api::DEVICE_NOT_FOUND => Error::InvalidDevice,
mullvad_api::INVALID_ACCOUNT => Error::InvalidAccount,
mullvad_api::MAX_DEVICES_REACHED => Error::MaxDevicesReached,
+ mullvad_api::INVALID_VOUCHER => Error::InvalidVoucher,
+ mullvad_api::VOUCHER_USED => Error::UsedVoucher,
_ => Error::OtherRestError(error),
},
error => Error::OtherRestError(error),
diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs
index 238cfb86b0..49f27751cc 100644
--- a/mullvad-daemon/src/lib.rs
+++ b/mullvad-daemon/src/lib.rs
@@ -28,7 +28,7 @@ pub mod version;
mod version_check;
use crate::target_state::PersistentTargetState;
-use device::{PrivateAccountAndDevice, PrivateDeviceEvent};
+use device::{AccountEvent, PrivateAccountAndDevice, PrivateDeviceEvent};
use futures::{
channel::{mpsc, oneshot},
future::{abortable, AbortHandle, Future, LocalBoxFuture},
@@ -126,6 +126,9 @@ pub enum Error {
#[error(display = "Failed to update device")]
UpdateDeviceError(#[error(source)] device::Error),
+ #[error(display = "Failed to submit voucher")]
+ VoucherSubmission(#[error(source)] device::Error),
+
#[cfg(target_os = "linux")]
#[error(display = "Unable to initialize split tunneling")]
InitSplitTunneling(#[error(source)] split_tunnel::Error),
@@ -301,7 +304,7 @@ pub(crate) enum InternalDaemonEvent {
/// The background job fetching new `AppVersionInfo`s got a new info object.
NewAppVersionInfo(AppVersionInfo),
/// Sent when a device is updated in any way (key rotation, login, logout, etc.).
- DeviceEvent(PrivateDeviceEvent),
+ DeviceEvent(AccountEvent),
/// Handles updates from versions without devices.
DeviceMigrationEvent(Result<PrivateAccountAndDevice, device::Error>),
/// The split tunnel paths or state were updated.
@@ -333,8 +336,8 @@ impl From<AppVersionInfo> for InternalDaemonEvent {
}
}
-impl From<PrivateDeviceEvent> for InternalDaemonEvent {
- fn from(event: PrivateDeviceEvent) -> Self {
+impl From<AccountEvent> for InternalDaemonEvent {
+ fn from(event: AccountEvent) -> Self {
InternalDaemonEvent::DeviceEvent(event)
}
}
@@ -892,6 +895,8 @@ where
}
if let ErrorStateCause::AuthFailed(_) = error_state.cause() {
+ // If time is added outside of the app, no notifications
+ // are received. So we must continually try to reconnect.
self.schedule_reconnect(Duration::from_secs(60))
}
}
@@ -1039,9 +1044,9 @@ where
self.event_listener.notify_app_version(app_version_info);
}
- async fn handle_device_event(&mut self, event: PrivateDeviceEvent) {
+ async fn handle_device_event(&mut self, event: AccountEvent) {
match &event {
- PrivateDeviceEvent::Login(device) => {
+ AccountEvent::Device(PrivateDeviceEvent::Login(device)) => {
if let Err(error) = self.account_history.set(device.account_token.clone()).await {
log::error!(
"{}",
@@ -1053,26 +1058,43 @@ where
self.reconnect_tunnel();
}
}
- PrivateDeviceEvent::Logout => {
+ AccountEvent::Device(PrivateDeviceEvent::Logout) => {
log::info!("Disconnecting because account token was cleared");
self.set_target_state(TargetState::Unsecured).await;
}
- PrivateDeviceEvent::Revoked => {
+ AccountEvent::Device(PrivateDeviceEvent::Revoked) => {
// If we're currently in a secured state, reconnect to make sure we immediately
// enter the error state.
if *self.target_state == TargetState::Secured {
self.connect_tunnel();
}
}
- PrivateDeviceEvent::RotatedKey(_) => {
+ AccountEvent::Device(PrivateDeviceEvent::RotatedKey(_)) => {
if self.get_target_tunnel_type() == Some(TunnelType::Wireguard) {
self.schedule_reconnect(WG_RECONNECT_DELAY);
}
}
+ AccountEvent::Expiry(expiry) if *self.target_state == TargetState::Secured => {
+ if expiry >= &chrono::Utc::now() {
+ if let TunnelState::Error(ref state) = self.tunnel_state {
+ if matches!(state.cause(), ErrorStateCause::AuthFailed(_)) {
+ log::debug!("Reconnecting since the account has time on it");
+ self.connect_tunnel();
+ }
+ }
+ } else if self.get_target_tunnel_type() == Some(TunnelType::Wireguard) {
+ log::debug!("Entering blocking state since the account is out of time");
+ self.send_tunnel_command(TunnelCommand::Block(ErrorStateCause::AuthFailed(
+ None,
+ )))
+ }
+ }
_ => (),
}
- self.event_listener
- .notify_device_event(DeviceEvent::from(event));
+ if let AccountEvent::Device(event) = event {
+ self.event_listener
+ .notify_device_event(DeviceEvent::from(event));
+ }
}
async fn handle_device_migration_event(
@@ -1297,21 +1319,17 @@ where
tx: ResponseTx<VoucherSubmission, Error>,
voucher: String,
) {
- if let Ok(Some(device)) = self.account_manager.data().await.map(|s| s.into_device()) {
- let mut account = self.account_manager.account_service.clone();
- tokio::spawn(async move {
- Self::oneshot_send(
- tx,
- account
- .submit_voucher(device.account_token, voucher)
- .await
- .map_err(Error::RestError),
- "submit_voucher response",
- );
- });
- } else {
- Self::oneshot_send(tx, Err(Error::NoAccountToken), "submit_voucher response");
- }
+ let manager = self.account_manager.clone();
+ tokio::spawn(async move {
+ Self::oneshot_send(
+ tx,
+ manager
+ .submit_voucher(voucher)
+ .await
+ .map_err(Error::VoucherSubmission),
+ "submit_voucher response",
+ );
+ });
}
fn on_get_relay_locations(&mut self, tx: oneshot::Sender<RelayList>) {
diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs
index 68ceed4c16..bac3ed0f93 100644
--- a/mullvad-daemon/src/management_interface.rs
+++ b/mullvad-daemon/src/management_interface.rs
@@ -493,10 +493,7 @@ impl ManagementService for ManagementServiceImpl {
}),
})
})
- .map_err(|error| match error {
- crate::Error::RestError(error) => map_rest_voucher_error(error),
- error => map_daemon_error(error),
- })
+ .map_err(map_daemon_error)
}
// Device management
@@ -954,6 +951,7 @@ fn map_daemon_error(error: crate::Error) -> Status {
DaemonError::ListDevicesError(error) => map_device_error(&error),
DaemonError::RemoveDeviceError(error) => map_device_error(&error),
DaemonError::UpdateDeviceError(error) => map_device_error(&error),
+ DaemonError::VoucherSubmission(error) => map_device_error(&error),
#[cfg(windows)]
DaemonError::SplitTunnelError(error) => map_split_tunnel_error(error),
DaemonError::AccountHistory(error) => map_account_history_error(error),
@@ -981,22 +979,6 @@ fn map_split_tunnel_error(error: talpid_core::split_tunnel::Error) -> Status {
}
}
-/// Converts a REST API voucher error into a tonic status.
-fn map_rest_voucher_error(error: RestError) -> Status {
- match error {
- RestError::ApiError(StatusCode::BAD_REQUEST, message) => match &message.as_str() {
- &mullvad_api::INVALID_VOUCHER => Status::new(Code::NotFound, INVALID_VOUCHER_MESSAGE),
-
- &mullvad_api::VOUCHER_USED => {
- Status::new(Code::ResourceExhausted, USED_VOUCHER_MESSAGE)
- }
-
- error => Status::unknown(format!("Voucher error: {}", error)),
- },
- error => map_rest_error(&error),
- }
-}
-
/// Converts a REST API error into a tonic status.
fn map_rest_error(error: &RestError) -> Status {
match error {
@@ -1034,6 +1016,8 @@ fn map_device_error(error: &device::Error) -> Status {
device::Error::InvalidDevice | device::Error::NoDevice => {
Status::new(Code::NotFound, error.to_string())
}
+ device::Error::InvalidVoucher => Status::new(Code::NotFound, INVALID_VOUCHER_MESSAGE),
+ device::Error::UsedVoucher => Status::new(Code::ResourceExhausted, USED_VOUCHER_MESSAGE),
device::Error::DeviceIoError(ref _error) => {
Status::new(Code::Unavailable, error.to_string())
}