1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
|
use crate::{
api,
settings::{self, MadeChanges},
Daemon, EventListener,
};
use mullvad_api::rest;
use mullvad_types::{
access_method::{self, AccessMethod, AccessMethodSetting},
settings::Settings,
};
#[derive(err_derive::Error, Debug)]
pub enum Error {
/// Can not add access method
#[error(display = "Cannot add custom access method")]
Add,
/// Can not remove built-in access method
#[error(display = "Cannot remove built-in access method")]
RemoveBuiltIn,
/// Can not find access method
#[error(display = "Cannot find custom access method {}", _0)]
NoSuchMethod(access_method::Id),
/// Access method could not be rotate
#[error(display = "Access method could not be rotated")]
RotationFailed,
/// Some error occured in the daemon's state of handling
/// [`AccessMethodSetting`]s & [`ApiConnectionMode`]s.
#[error(display = "Error occured when handling connection settings & details")]
ConnectionMode(#[error(source)] api::Error),
#[error(display = "API endpoint rotation failed")]
Rest(#[error(source)] rest::Error),
/// Access methods settings error
#[error(display = "Settings error")]
Settings(#[error(source)] settings::Error),
}
impl<L> Daemon<L>
where
L: EventListener + Clone + Send + 'static,
{
/// Add a [`AccessMethod`] to the daemon's settings.
///
/// If the daemon settings are successfully updated, the
/// [`access_method::Id`] of the newly created [`AccessMethodSetting`]
/// (which has been derived from the [`AccessMethod`]) is returned.
pub async fn add_access_method(
&mut self,
name: String,
enabled: bool,
access_method: AccessMethod,
) -> Result<access_method::Id, Error> {
let access_method_setting = AccessMethodSetting::new(name, enabled, access_method);
let id = access_method_setting.get_id();
self.settings
.update(|settings| settings.api_access_methods.append(access_method_setting))
.await
.map(|did_change| self.notify_on_change(did_change))
.map(|_| id)
.map_err(Error::Settings)
}
/// Remove a [`AccessMethodSetting`] from the daemon's saved settings.
///
/// If the [`AccessMethodSetting`] which is currently in use happens to be
/// removed, the daemon should force a rotation of the active API endpoint.
pub async fn remove_access_method(
&mut self,
access_method: access_method::Id,
) -> Result<(), Error> {
match self.settings.api_access_methods.find_by_id(&access_method) {
// Make sure that we are not trying to remove a built-in API access
// method
Some(api_access_method) if api_access_method.is_builtin() => {
return Err(Error::RemoveBuiltIn)
}
// If the currently active access method is removed, a new access
// method should trigger
Some(api_access_method)
if api_access_method.get_id()
== self.get_current_access_method().await?.get_id() =>
{
self.force_api_endpoint_rotation().await?;
}
_ => (),
}
self.settings
.update(|settings| settings.api_access_methods.remove(&access_method))
.await
.map(|did_change| self.notify_on_change(did_change))
.map(|_| ())
.map_err(Error::Settings)
}
/// Set a [`AccessMethodSetting`] as the current API access method.
///
/// If successful, the daemon will force a rotation of the active API access
/// method, which means that subsequent API calls will use the new
/// [`AccessMethodSetting`] to figure out the API endpoint.
pub async fn set_api_access_method(
&mut self,
access_method: access_method::Id,
) -> Result<(), Error> {
let access_method = self.get_api_access_method(access_method)?;
self.connection_modes_handler
.set_access_method(access_method)
.await?;
// Force a rotation of Access Methods.
self.force_api_endpoint_rotation().await
}
pub fn get_api_access_method(
&mut self,
access_method: access_method::Id,
) -> Result<AccessMethodSetting, Error> {
self.settings
.api_access_methods
.find_by_id(&access_method)
.ok_or(Error::NoSuchMethod(access_method))
.cloned()
}
/// "Updates" an [`AccessMethodSetting`] by replacing the existing entry
/// with the argument `access_method_update` if an existing entry with
/// matching [`access_method::Id`] is found.
///
/// If the currently active [`AccessMethodSetting`] is updated, the daemon
/// will automatically use this updated [`AccessMethodSetting`] when
/// performing subsequent API calls.
pub async fn update_access_method(
&mut self,
access_method_update: AccessMethodSetting,
) -> Result<(), Error> {
// If the currently active access method is updated, we need to re-set
// it after updating the settings.
let current = self.get_current_access_method().await?;
// If the currently active access method is updated, we need to re-set it.
let mut refresh = None;
let settings_update = |settings: &mut Settings| {
let access_methods = &mut settings.api_access_methods;
if let Some(access_method) =
access_methods.find_by_id_mut(&access_method_update.get_id())
{
*access_method = access_method_update;
if access_method.get_id() == current.get_id() {
refresh = Some(access_method.get_id())
}
// We have to be a bit careful. If we are about to disable the last
// remaining enabled access method, we would cause an inconsistent state
// in the daemon's settings. Therefore, we have to safeguard against
// this by explicitly checking for any update which would cause the last
// enabled access method to become disabled. In that case, we should
// re-enable the `Direct` access method.
if access_methods.collect_enabled().is_empty() {
if let Some(direct) = access_methods.get_direct() {
direct.enabled = true;
} else {
// If the `Direct` access method does not exist within the
// settings for some reason, the settings are in an
// inconsistent state. We don't have much choice but to
// reset these settings to their default value.
log::warn!("The built-in access methods can not be found. This might be due to a corrupt settings file");
*access_methods = access_method::Settings::default();
}
}
}
};
self.settings
.update(settings_update)
.await
.map(|did_change| self.notify_on_change(did_change))
.map_err(Error::Settings)?;
if let Some(id) = refresh {
self.set_api_access_method(id).await?;
}
Ok(())
}
/// Return the [`AccessMethodSetting`] which is currently used to access the
/// Mullvad API.
pub async fn get_current_access_method(&self) -> Result<AccessMethodSetting, Error> {
self.connection_modes_handler
.get_current()
.await
.map(|current| current.setting)
.map_err(Error::ConnectionMode)
}
/// Change which [`AccessMethodSetting`] which will be used as the Mullvad
/// API endpoint.
async fn force_api_endpoint_rotation(&self) -> Result<(), Error> {
self.api_handle
.service()
.next_api_endpoint()
.await
.map_err(|error| {
log::error!("Failed to rotate API endpoint: {}", error);
Error::RotationFailed
})
}
/// If settings were changed due to an update, notify all listeners.
fn notify_on_change(&mut self, settings_changed: MadeChanges) -> &mut Self {
if settings_changed {
self.event_listener
.notify_settings(self.settings.to_settings());
let handle = self.connection_modes_handler.clone();
let new_access_methods = self.settings.api_access_methods.collect_enabled();
tokio::spawn(async move {
match handle.update_access_methods(new_access_methods).await {
Ok(_) => (),
Err(api::Error::NoAccessMethods) | Err(_) => {
// `access_methods` was empty! This implies that the user
// disabled all access methods. If we ever get into this
// state, we should default to using the direct access
// method.
let default = access_method::Settings::direct();
handle.update_access_methods(vec![default]).await.expect("Failed to create the data structure responsible for managing access methods");
}
}
});
};
self
}
}
|