diff options
| -rw-r--r-- | CHANGELOG.md | 2 | ||||
| -rw-r--r-- | Cargo.lock | 1 | ||||
| -rw-r--r-- | mullvad-daemon/src/lib.rs | 5 | ||||
| -rw-r--r-- | mullvad-problem-report/src/lib.rs | 7 | ||||
| -rw-r--r-- | mullvad-rpc/Cargo.toml | 1 | ||||
| -rw-r--r-- | mullvad-rpc/src/address_cache.rs | 111 | ||||
| -rw-r--r-- | mullvad-rpc/src/bin/address_cache.rs | 2 | ||||
| -rw-r--r-- | mullvad-rpc/src/lib.rs | 42 | ||||
| -rw-r--r-- | mullvad-setup/src/main.rs | 23 |
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)) } |
