summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md2
-rw-r--r--Cargo.lock1
-rw-r--r--mullvad-daemon/src/lib.rs5
-rw-r--r--mullvad-problem-report/src/lib.rs7
-rw-r--r--mullvad-rpc/Cargo.toml1
-rw-r--r--mullvad-rpc/src/address_cache.rs111
-rw-r--r--mullvad-rpc/src/bin/address_cache.rs2
-rw-r--r--mullvad-rpc/src/lib.rs42
-rw-r--r--mullvad-setup/src/main.rs23
9 files changed, 130 insertions, 64 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6706c48985..c7bcd6ad37 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -38,6 +38,8 @@ Line wrap the file at 100 chars. Th
### Changed
- Use the API to fetch API IP addresses instead of DNS.
- Remove WireGuard keys during uninstallation after the firewall is unlocked.
+- Randomly select addresses to use for communicating with the API.
+- Bundle a list of API addresses to use instead of assuming that the primary address can be reached.
#### Android
- Remove the Quit button.
diff --git a/Cargo.lock b/Cargo.lock
index 2819952a10..0d7cbadde1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1385,6 +1385,7 @@ dependencies = [
"ipnetwork",
"log 0.4.11",
"mullvad-types",
+ "rand",
"regex",
"serde",
"serde_json",
diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs
index 0599cb052a..b4e5dd3279 100644
--- a/mullvad-daemon/src/lib.rs
+++ b/mullvad-daemon/src/lib.rs
@@ -494,9 +494,10 @@ where
let (tunnel_state_machine_shutdown_tx, tunnel_state_machine_shutdown_signal) =
oneshot::channel();
- let mut rpc_runtime = mullvad_rpc::MullvadRpcRuntime::with_cache_dir(
+ let mut rpc_runtime = mullvad_rpc::MullvadRpcRuntime::with_cache(
tokio::runtime::Handle::current(),
- &cache_dir,
+ &resource_dir,
+ Some(&cache_dir),
)
.await
.map_err(Error::InitRpcFactory)?;
diff --git a/mullvad-problem-report/src/lib.rs b/mullvad-problem-report/src/lib.rs
index 1c3fa00461..eac0dd4638 100644
--- a/mullvad-problem-report/src/lib.rs
+++ b/mullvad-problem-report/src/lib.rs
@@ -271,7 +271,12 @@ pub fn send_problem_report(
.build()
.map_err(Error::CreateRuntime)?;
- let mut rpc_manager = mullvad_rpc::MullvadRpcRuntime::new(runtime.handle().clone())
+ let mut rpc_manager = runtime
+ .block_on(mullvad_rpc::MullvadRpcRuntime::with_cache(
+ runtime.handle().clone(),
+ &mullvad_paths::get_resource_dir(),
+ None,
+ ))
.map_err(Error::CreateRpcClientError)?;
let rpc_client = mullvad_rpc::ProblemReportProxy::new(rpc_manager.mullvad_rest_handle());
diff --git a/mullvad-rpc/Cargo.toml b/mullvad-rpc/Cargo.toml
index fd8d34bc95..cf27d2e287 100644
--- a/mullvad-rpc/Cargo.toml
+++ b/mullvad-rpc/Cargo.toml
@@ -15,6 +15,7 @@ http = "0.2"
hyper = "0.13"
ipnetwork = "0.16"
log = "0.4"
+rand = "0.7"
regex = "1"
serde = "1"
serde_json = "1.0"
diff --git a/mullvad-rpc/src/address_cache.rs b/mullvad-rpc/src/address_cache.rs
index 7c1859173e..ae0b916ad4 100644
--- a/mullvad-rpc/src/address_cache.rs
+++ b/mullvad-rpc/src/address_cache.rs
@@ -1,6 +1,8 @@
+use super::API_ADDRESS;
+use rand::seq::SliceRandom;
use std::{
io,
- net::{IpAddr, SocketAddr},
+ net::SocketAddr,
path::Path,
sync::{Arc, Mutex},
};
@@ -9,7 +11,18 @@ use tokio::{
io::{AsyncBufReadExt, AsyncWriteExt, BufReader},
};
-const FALLBACK_API_ADDRESS: (IpAddr, u16) = (crate::API_IP, 443);
+#[derive(err_derive::Error, Debug)]
+#[error(no_from)]
+pub enum Error {
+ #[error(display = "Failed to open the address cache file")]
+ OpenAddressCache(#[error(source)] io::Error),
+
+ #[error(display = "Failed to read the address cache file")]
+ ReadAddressCache(#[error(source)] io::Error),
+
+ #[error(display = "The address cache is empty")]
+ EmptyAddressCache,
+}
#[derive(Clone)]
pub struct AddressCache {
@@ -18,20 +31,20 @@ pub struct AddressCache {
}
impl AddressCache {
- pub fn new() -> Self {
- Self {
- inner: Arc::new(Mutex::new(Default::default())),
- cache_path: None,
- }
+ /// Initialize cache using the given list, and write changes to `cache_path`.
+ pub fn new(addresses: Vec<SocketAddr>, cache_path: Option<Box<Path>>) -> Result<Self, Error> {
+ log::trace!("Using API addresses: {:?}", addresses);
+ let cache = AddressCacheInner::from_addresses(addresses)?;
+ Ok(Self {
+ inner: Arc::new(Mutex::new(cache)),
+ cache_path: cache_path.map(|cache| Arc::from(cache)),
+ })
}
- pub async fn with_cache(cache_path: Box<Path>) -> Self {
- let cache = AddressCacheInner::from_cache_file(&cache_path)
- .await
- .unwrap_or_default();
- let inner = Arc::new(Mutex::new(cache));
- let cache_path = Some(cache_path.into());
- Self { inner, cache_path }
+ /// Initialize cache using `read_path`, and write changes to `cache_path`.
+ pub async fn from_file(read_path: &Path, cache_path: Option<Box<Path>>) -> Result<Self, Error> {
+ log::trace!("Loading API addresses from {:?}", read_path);
+ Self::new(read_address_file(read_path).await?, cache_path)
}
pub fn get_address(&self) -> SocketAddr {
@@ -43,12 +56,12 @@ impl AddressCache {
fn get_address_inner(inner: &AddressCacheInner) -> SocketAddr {
if inner.addresses.is_empty() {
- return FALLBACK_API_ADDRESS.into();
+ return API_ADDRESS.into();
}
*inner
.addresses
.get(inner.choice % inner.addresses.len())
- .unwrap_or(&FALLBACK_API_ADDRESS.into())
+ .unwrap_or(&API_ADDRESS.into())
}
pub fn register_failure(&self, failed_addr: SocketAddr, err: &dyn std::error::Error) {
@@ -67,11 +80,15 @@ impl AddressCache {
}
}
- pub async fn set_addresses(&self, addresses: Vec<SocketAddr>) -> io::Result<()> {
+ pub async fn set_addresses(&self, mut addresses: Vec<SocketAddr>) -> io::Result<()> {
let should_update = {
let mut inner = self.inner.lock().unwrap();
- if addresses != inner.addresses {
+ addresses.sort();
+ let mut current_sorted = inner.addresses.clone();
+ current_sorted.sort();
+ if addresses != current_sorted {
inner.addresses = addresses.clone();
+ inner.shuffle();
inner.choice = 0;
true
} else {
@@ -120,37 +137,43 @@ struct AddressCacheInner {
}
impl AddressCacheInner {
- async fn from_cache_file(path: &Path) -> io::Result<Self> {
- let file = fs::File::open(path).await?;
- let mut lines = BufReader::new(file).lines();
- let mut addresses = vec![];
- while let Some(line) = lines.next_line().await? {
- // for line in lines.next_line() {
- match line.trim().parse() {
- Ok(address) => addresses.push(address),
- Err(err) => {
- log::error!("Failed to parse cached address line: {}", err);
- }
- }
+ fn from_addresses(addresses: Vec<SocketAddr>) -> Result<Self, Error> {
+ if addresses.is_empty() {
+ return Err(Error::EmptyAddressCache);
}
-
- if !addresses.contains(&FALLBACK_API_ADDRESS.into()) {
- addresses.push(FALLBACK_API_ADDRESS.into());
- }
-
- Ok(Self {
+ let mut cache = Self {
addresses,
- ..Default::default()
- })
+ choice: 0,
+ last_try: None,
+ };
+ cache.shuffle();
+ Ok(cache)
+ }
+
+ fn shuffle(&mut self) {
+ let mut rng = rand::thread_rng();
+ (&mut self.addresses[..]).shuffle(&mut rng);
}
}
-impl Default for AddressCacheInner {
- fn default() -> Self {
- Self {
- addresses: vec![FALLBACK_API_ADDRESS.into()],
- choice: 0,
- last_try: None,
+async fn read_address_file(path: &Path) -> Result<Vec<SocketAddr>, Error> {
+ let file = fs::File::open(path)
+ .await
+ .map_err(|error| Error::OpenAddressCache(error))?;
+ let mut lines = BufReader::new(file).lines();
+ let mut addresses = vec![];
+ while let Some(line) = lines
+ .next_line()
+ .await
+ .map_err(|error| Error::ReadAddressCache(error))?
+ {
+ // for line in lines.next_line() {
+ match line.trim().parse() {
+ Ok(address) => addresses.push(address),
+ Err(err) => {
+ log::error!("Failed to parse cached address line: {}", err);
+ }
}
}
+ Ok(addresses)
}
diff --git a/mullvad-rpc/src/bin/address_cache.rs b/mullvad-rpc/src/bin/address_cache.rs
index 203c5e79bb..efb2501ba9 100644
--- a/mullvad-rpc/src/bin/address_cache.rs
+++ b/mullvad-rpc/src/bin/address_cache.rs
@@ -1,4 +1,4 @@
-/// Generate a first list of IP addresses for the Mullvad VPN to use to talk to the API.
+/// Generate a first list of IP addresses for Mullvad VPN to use to talk to the API.
use mullvad_rpc::{rest::Error as RestError, ApiProxy, MullvadRpcRuntime};
use std::process;
use talpid_types::ErrorExt;
diff --git a/mullvad-rpc/src/lib.rs b/mullvad-rpc/src/lib.rs
index 323937ea9a..d92be6e8be 100644
--- a/mullvad-rpc/src/lib.rs
+++ b/mullvad-rpc/src/lib.rs
@@ -12,7 +12,7 @@ use std::{
net::{IpAddr, Ipv4Addr, SocketAddr},
path::Path,
};
-use talpid_types::net::wireguard;
+use talpid_types::{net::wireguard, ErrorExt};
pub mod rest;
@@ -35,6 +35,7 @@ pub const INVALID_VOUCHER: &str = "INVALID_VOUCHER";
const API_HOST: &str = "api.mullvad.net";
pub const API_IP_CACHE_FILENAME: &str = "api-ip-address.txt";
const API_IP: IpAddr = IpAddr::V4(Ipv4Addr::new(193, 138, 218, 78));
+const API_ADDRESS: (IpAddr, u16) = (crate::API_IP, 443);
/// A type that helps with the creation of RPC connections.
@@ -48,6 +49,9 @@ pub struct MullvadRpcRuntime {
pub enum Error {
#[error(display = "Failed to construct a rest client")]
RestError(#[error(source)] rest::Error),
+
+ #[error(display = "Failed to load address cache")]
+ AddressCacheError(#[error(source)] address_cache::Error),
}
impl MullvadRpcRuntime {
@@ -56,17 +60,41 @@ impl MullvadRpcRuntime {
Ok(MullvadRpcRuntime {
https_connector: HttpsConnectorWithSni::new(),
handle,
- address_cache: AddressCache::new(),
+ address_cache: AddressCache::new(vec![API_ADDRESS.into()], None)?,
})
}
- /// Create a new `MullvadRpcRuntime` using the specified cache directory.
- pub async fn with_cache_dir(
+ /// Create a new `MullvadRpcRuntime` using the specified directories.
+ /// Try to use the cache directory first, and fall back on the resource directory
+ /// if it fails.
+ pub async fn with_cache(
handle: tokio::runtime::Handle,
- cache_dir: &Path,
+ resource_dir: &Path,
+ cache_dir: Option<&Path>,
) -> Result<Self, Error> {
- let cache_file = cache_dir.join(API_IP_CACHE_FILENAME);
- let address_cache = AddressCache::with_cache(cache_file.into_boxed_path()).await;
+ let resource_file = resource_dir.join(API_IP_CACHE_FILENAME);
+
+ let address_cache = if let Some(cache_dir) = cache_dir {
+ let cache_file = cache_dir.join(API_IP_CACHE_FILENAME);
+ let cache_file_boxed = cache_file.clone().into_boxed_path();
+
+ match AddressCache::from_file(&cache_file, Some(cache_file_boxed.clone())).await {
+ Ok(cache) => cache,
+ Err(error) => {
+ if cache_file.exists() {
+ log::error!(
+ "{}",
+ error.display_chain_with_msg(
+ "Failed to load cached API addresses. Falling back on bundled list"
+ )
+ );
+ }
+ AddressCache::from_file(&resource_file, Some(cache_file_boxed)).await?
+ }
+ }
+ } else {
+ AddressCache::from_file(&resource_file, None).await?
+ };
let https_connector = HttpsConnectorWithSni::new();
diff --git a/mullvad-setup/src/main.rs b/mullvad-setup/src/main.rs
index 0e0776fe92..396b86fa2b 100644
--- a/mullvad-setup/src/main.rs
+++ b/mullvad-setup/src/main.rs
@@ -109,12 +109,15 @@ async fn reset_firewall() -> Result<(), Error> {
}
async fn clear_history() -> Result<(), Error> {
- let (cache_path, settings_path) = get_paths()?;
+ let (cache_path, resource_path, settings_path) = get_paths()?;
- let mut rpc_runtime =
- MullvadRpcRuntime::with_cache_dir(tokio::runtime::Handle::current(), &cache_path)
- .await
- .map_err(Error::RpcInitializationError)?;
+ let mut rpc_runtime = MullvadRpcRuntime::with_cache(
+ tokio::runtime::Handle::current(),
+ &resource_path,
+ Some(&cache_path),
+ )
+ .await
+ .map_err(Error::RpcInitializationError)?;
let mut account_history = account_history::AccountHistory::new(
&cache_path,
@@ -131,18 +134,20 @@ async fn clear_history() -> Result<(), Error> {
}
#[cfg(not(windows))]
-fn get_paths() -> Result<(PathBuf, PathBuf), Error> {
+fn get_paths() -> Result<(PathBuf, PathBuf, PathBuf), Error> {
let cache_path = mullvad_paths::cache_dir().map_err(Error::CachePathError)?;
+ let resource_path = mullvad_paths::get_resource_dir();
let settings_path = mullvad_paths::settings_dir().map_err(Error::SettingsPathError)?;
- Ok((cache_path, settings_path))
+ Ok((cache_path, resource_path, settings_path))
}
#[cfg(windows)]
-fn get_paths() -> Result<(PathBuf, PathBuf), Error> {
+fn get_paths() -> Result<(PathBuf, PathBuf, PathBuf), Error> {
let settings_path =
daemon_paths::get_mullvad_daemon_settings_path().map_err(Error::CachePathError)?;
+ let resource_path = mullvad_paths::get_resource_dir();
let cache_path =
daemon_paths::get_mullvad_daemon_cache_path().map_err(Error::SettingsPathError)?;
- Ok((cache_path, settings_path))
+ Ok((cache_path, resource_path, settings_path))
}