diff options
| -rw-r--r-- | CHANGELOG.md | 8 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/relay.rs | 2 | ||||
| -rw-r--r-- | mullvad-daemon/src/lib.rs | 234 | ||||
| -rw-r--r-- | mullvad-daemon/src/relays.rs | 78 | ||||
| -rw-r--r-- | mullvad-types/src/custom_tunnel.rs | 12 | ||||
| -rw-r--r-- | mullvad-types/src/relay_constraints.rs | 82 | ||||
| -rw-r--r-- | mullvad-types/src/settings.rs | 2 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/blocked_state.rs | 4 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/connected_state.rs | 29 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/connecting_state.rs | 97 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/disconnected_state.rs | 4 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/disconnecting_state.rs | 41 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/mod.rs | 26 | ||||
| -rw-r--r-- | talpid-types/src/net.rs | 27 |
14 files changed, 415 insertions, 231 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 49b695afb9..d355213654 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,15 +23,17 @@ Line wrap the file at 100 chars. Th ## [Unreleased] +### Added +- Fall back and try to connect over TCP port 443 if protocol is set to automatic and two attempts + with UDP fail in a row. If that also fails, alternate between UDP and TCP with random ports. + ### Fixed - Place Mssfix setting inside scrollable area +- Pick new random relay for each reconnect attempt instead of just retrying with the same one. #### Linux - The app will have it's window resized correctly when display scaling settings are changed. This should also fix bad window behaviour on startup. - -### Fixed -#### Linux - Fixed systemd-resolved DNS management. diff --git a/mullvad-cli/src/cmds/relay.rs b/mullvad-cli/src/cmds/relay.rs index eda57070fb..e056505069 100644 --- a/mullvad-cli/src/cmds/relay.rs +++ b/mullvad-cli/src/cmds/relay.rs @@ -205,7 +205,7 @@ impl Relay { fn get(&self) -> Result<()> { let mut rpc = new_rpc_client()?; let constraints = rpc.get_settings()?.get_relay_settings(); - println!("Current constraints: {:#?}", constraints); + println!("Current constraints: {}", constraints); Ok(()) } diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index 063e68d56e..36306f1d76 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -44,13 +44,12 @@ mod relays; mod rpc_uniqueness_check; use error_chain::ChainedError; -use futures::sync::mpsc::UnboundedSender; -use futures::{Future, Sink}; -use jsonrpc_core::futures::sync::oneshot::{self, Sender as OneshotSender}; - +use futures::{ + sync::{mpsc::UnboundedSender, oneshot}, + Future, Sink, +}; use management_interface::{BoxFuture, ManagementCommand, ManagementInterfaceServer}; use mullvad_rpc::{AccountsProxy, AppVersionProxy, HttpHandle}; - use mullvad_types::{ account::{AccountData, AccountToken}, location::GeoIpLocation, @@ -60,17 +59,12 @@ use mullvad_types::{ states::TargetState, version::{AppVersion, AppVersionInfo}, }; - use std::{mem, path::PathBuf, sync::mpsc, thread, time::Duration}; - use talpid_core::{ mpsc::IntoSender, - tunnel_state_machine::{self, TunnelCommand, TunnelParameters}, -}; -use talpid_types::{ - net::TunnelEndpoint, - tunnel::{BlockReason, TunnelStateTransition}, + tunnel_state_machine::{self, TunnelCommand, TunnelParameters, TunnelParametersGenerator}, }; +use talpid_types::tunnel::{BlockReason, TunnelStateTransition}; error_chain!{ @@ -97,6 +91,8 @@ type SyncUnboundedSender<T> = ::futures::sink::Wait<UnboundedSender<T>>; pub enum DaemonEvent { /// Tunnel has changed state. TunnelStateTransition(TunnelStateTransition), + /// Request from the `MullvadTunnelParametersGenerator` to obtain a new relay. + GenerateTunnelParameters(mpsc::Sender<TunnelParameters>, u32), /// An event coming from the JSONRPC-2.0 management interface. ManagementInterfaceEvent(ManagementCommand), /// Triggered if the server hosting the JSONRPC-2.0 management interface dies unexpectedly. @@ -178,7 +174,7 @@ pub struct Daemon { https_handle: mullvad_rpc::rest::RequestSender, tokio_remote: tokio_core::reactor::Remote, relay_selector: relays::RelaySelector, - current_relay: Option<Relay>, + last_generated_relay: Option<Relay>, version: String, } @@ -214,8 +210,10 @@ impl Daemon { let settings = Settings::load().chain_err(|| "Unable to read settings")?; let (tx, rx) = mpsc::channel(); + let tunnel_parameters_generator = MullvadTunnelParametersGenerator { tx: tx.clone() }; let tunnel_command_tx = tunnel_state_machine::spawn( settings.get_allow_lan(), + tunnel_parameters_generator, log_dir, resource_dir, cache_dir.clone(), @@ -245,7 +243,7 @@ impl Daemon { https_handle, tokio_remote, relay_selector, - current_relay: None, + last_generated_relay: None, version, }) } @@ -292,11 +290,9 @@ impl Daemon { /// Consume the `Daemon` and run the main event loop. Blocks until an error happens or a /// shutdown event is received. pub fn run(mut self) -> Result<()> { - if self.settings.get_auto_connect() { + if self.settings.get_auto_connect() && self.settings.get_account_token().is_some() { info!("Automatically connecting since auto-connect is turned on"); - if self.set_target_state(TargetState::Secured).is_err() { - warn!("Aborting auto-connect since no account token is set"); - } + self.set_target_state(TargetState::Secured); } while let Ok(event) = self.rx.recv() { self.handle_event(event)?; @@ -313,6 +309,9 @@ impl Daemon { TunnelStateTransition(transition) => { Ok(self.handle_tunnel_state_transition(transition)) } + GenerateTunnelParameters(tunnel_parameters_tx, retry_attempt) => { + Ok(self.handle_generate_tunnel_parameters(tunnel_parameters_tx, retry_attempt)) + } ManagementInterfaceEvent(event) => Ok(self.handle_management_interface_event(event)), ManagementInterfaceExited => self.handle_management_interface_exited(), TriggerShutdown => Ok(self.handle_trigger_shutdown_event()), @@ -326,10 +325,7 @@ impl Daemon { debug!("New tunnel state: {:?}", tunnel_state); match tunnel_state { - Disconnected => { - self.state.disconnected(); - self.current_relay = None; - } + Disconnected => self.state.disconnected(), Blocked(ref reason) => { info!("Blocking all network connections, reason: {}", reason); @@ -346,6 +342,44 @@ impl Daemon { .notify_new_state(tunnel_state); } + fn handle_generate_tunnel_parameters( + &mut self, + tunnel_parameters_tx: mpsc::Sender<TunnelParameters>, + retry_attempt: u32, + ) { + let result = self + .settings + .get_account_token() + .ok_or(Error::from("No account token configured")) + .map(|account_token| { + match self.settings.get_relay_settings() { + RelaySettings::CustomTunnelEndpoint(custom_relay) => custom_relay + .to_tunnel_endpoint() + .chain_err(|| "Custom tunnel endpoint could not be resolved"), + RelaySettings::Normal(constraints) => self + .relay_selector + .get_tunnel_endpoint(&constraints, retry_attempt) + .chain_err(|| "No valid relay servers match the current settings") + .map(|(relay, endpoint)| { + self.last_generated_relay = Some(relay); + endpoint + }), + } + .map(|endpoint| { + tunnel_parameters_tx + .send(TunnelParameters { + endpoint, + options: self.settings.get_tunnel_options().clone(), + username: account_token, + }) + .map_err(|_| Error::from("Tunnel parameters receiver stopped listening")) + }) + }); + if let Err(error) = result { + error!("{}", error.display_chain()); + } + } + fn schedule_reconnect(&mut self, delay: Duration) { let tunnel_command_tx = self.tx.clone(); let (tx, rx) = mpsc::channel(); @@ -393,49 +427,59 @@ impl Daemon { fn on_set_target_state( &mut self, - tx: OneshotSender<::std::result::Result<(), ()>>, + tx: oneshot::Sender<::std::result::Result<(), ()>>, new_target_state: TargetState, ) { if self.state.is_running() { - Self::oneshot_send(tx, self.set_target_state(new_target_state), "targe state"); + self.set_target_state(new_target_state); } else { warn!("Ignoring target state change request due to shutdown"); - Self::oneshot_send(tx, Ok(()), "targe state"); } + Self::oneshot_send(tx, Ok(()), "targe state"); } - fn on_get_state(&self, tx: OneshotSender<TunnelStateTransition>) { + fn on_get_state(&self, tx: oneshot::Sender<TunnelStateTransition>) { Self::oneshot_send(tx, self.tunnel_state.clone(), "current state"); } - fn on_get_current_location(&self, tx: OneshotSender<GeoIpLocation>) { - if let Some(ref relay) = self.current_relay { - let location = relay.location.as_ref().cloned().unwrap(); - let hostname = relay.hostname.clone(); - let geo_ip_location = GeoIpLocation { - country: location.country, - city: Some(location.city), - latitude: location.latitude, - longitude: location.longitude, - mullvad_exit_ip: true, - hostname: Some(hostname), - }; - Self::oneshot_send(tx, geo_ip_location, "current location"); - } else { - let https_handle = self.https_handle.clone(); - self.tokio_remote.spawn(move |_| { - geoip::send_location_request(https_handle) - .map(move |location| Self::oneshot_send(tx, location, "current location")) - .map_err(|e| { - warn!("Unable to fetch GeoIP location: {}", e.display_chain()); - }) - }); + fn on_get_current_location(&self, tx: oneshot::Sender<GeoIpLocation>) { + use self::TunnelStateTransition::*; + match self.tunnel_state { + Disconnected => { + let https_handle = self.https_handle.clone(); + self.tokio_remote.spawn(move |_| { + geoip::send_location_request(https_handle) + .map(move |location| Self::oneshot_send(tx, location, "current location")) + .map_err(|e| { + warn!("Unable to fetch GeoIP location: {}", e.display_chain()); + }) + }); + } + Connecting | Connected | Disconnecting(..) => { + if let Some(ref relay) = self.last_generated_relay { + let location = relay.location.as_ref().cloned().unwrap(); + let hostname = relay.hostname.clone(); + let geo_ip_location = GeoIpLocation { + country: location.country, + city: Some(location.city), + latitude: location.latitude, + longitude: location.longitude, + mullvad_exit_ip: true, + hostname: Some(hostname), + }; + Self::oneshot_send(tx, geo_ip_location, "current location"); + } + } + Blocked(..) => { + // We are not online at all at this stage. Return error. + mem::drop(tx); + } } } fn on_get_account_data( &mut self, - tx: OneshotSender<BoxFuture<AccountData, mullvad_rpc::Error>>, + tx: oneshot::Sender<BoxFuture<AccountData, mullvad_rpc::Error>>, account_token: AccountToken, ) { let rpc_call = self @@ -445,12 +489,12 @@ impl Daemon { Self::oneshot_send(tx, Box::new(rpc_call), "account data") } - fn on_get_relay_locations(&mut self, tx: OneshotSender<RelayList>) { + fn on_get_relay_locations(&mut self, tx: oneshot::Sender<RelayList>) { Self::oneshot_send(tx, self.relay_selector.get_locations(), "relay locations"); } - fn on_set_account(&mut self, tx: OneshotSender<()>, account_token: Option<String>) { + fn on_set_account(&mut self, tx: oneshot::Sender<()>, account_token: Option<String>) { let account_token_cleared = account_token.is_none(); let save_result = self.settings.set_account_token(account_token); @@ -462,7 +506,7 @@ impl Daemon { .notify_settings(&self.settings); if account_token_cleared { info!("Disconnecting because account token was cleared"); - let _ = self.set_target_state(TargetState::Unsecured); + self.set_target_state(TargetState::Unsecured); } else { info!("Initiating tunnel restart because the account token changed"); self.reconnect_tunnel(); @@ -475,7 +519,7 @@ impl Daemon { fn on_get_version_info( &mut self, - tx: OneshotSender<BoxFuture<AppVersionInfo, mullvad_rpc::Error>>, + tx: oneshot::Sender<BoxFuture<AppVersionInfo, mullvad_rpc::Error>>, ) { let fut = self .version_proxy @@ -488,11 +532,11 @@ impl Daemon { Self::oneshot_send(tx, Box::new(fut), "get_version_info response"); } - fn on_get_current_version(&mut self, tx: OneshotSender<AppVersion>) { + fn on_get_current_version(&mut self, tx: oneshot::Sender<AppVersion>) { Self::oneshot_send(tx, self.version.clone(), "get_current_version response"); } - fn on_update_relay_settings(&mut self, tx: OneshotSender<()>, update: RelaySettingsUpdate) { + fn on_update_relay_settings(&mut self, tx: oneshot::Sender<()>, update: RelaySettingsUpdate) { let save_result = self.settings.update_relay_settings(update); match save_result.chain_err(|| "Unable to save settings") { Ok(settings_changed) => { @@ -508,7 +552,7 @@ impl Daemon { } } - fn on_set_allow_lan(&mut self, tx: OneshotSender<()>, allow_lan: bool) { + fn on_set_allow_lan(&mut self, tx: oneshot::Sender<()>, allow_lan: bool) { let save_result = self.settings.set_allow_lan(allow_lan); match save_result.chain_err(|| "Unable to save settings") { Ok(settings_changed) => { @@ -523,7 +567,7 @@ impl Daemon { } } - fn on_set_auto_connect(&mut self, tx: OneshotSender<()>, auto_connect: bool) { + fn on_set_auto_connect(&mut self, tx: oneshot::Sender<()>, auto_connect: bool) { let save_result = self.settings.set_auto_connect(auto_connect); match save_result.chain_err(|| "Unable to save settings") { Ok(settings_changed) => { @@ -537,7 +581,7 @@ impl Daemon { } } - fn on_set_openvpn_mssfix(&mut self, tx: OneshotSender<()>, mssfix_arg: Option<u16>) { + fn on_set_openvpn_mssfix(&mut self, tx: oneshot::Sender<()>, mssfix_arg: Option<u16>) { let save_result = self.settings.set_openvpn_mssfix(mssfix_arg); match save_result.chain_err(|| "Unable to save settings") { Ok(settings_changed) => { @@ -551,7 +595,7 @@ impl Daemon { } } - fn on_set_enable_ipv6(&mut self, tx: OneshotSender<()>, enable_ipv6: bool) { + fn on_set_enable_ipv6(&mut self, tx: oneshot::Sender<()>, enable_ipv6: bool) { let save_result = self.settings.set_enable_ipv6(enable_ipv6); match save_result.chain_err(|| "Unable to save settings") { Ok(settings_changed) => { @@ -567,11 +611,11 @@ impl Daemon { } } - fn on_get_settings(&self, tx: OneshotSender<Settings>) { + fn on_get_settings(&self, tx: oneshot::Sender<Settings>) { Self::oneshot_send(tx, self.settings.clone(), "get_settings response"); } - fn oneshot_send<T>(tx: OneshotSender<T>, t: T, msg: &'static str) { + fn oneshot_send<T>(tx: oneshot::Sender<T>, t: T, msg: &'static str) { if let Err(_) = tx.send(t) { warn!("Unable to send {} to management interface client", msg); } @@ -589,45 +633,19 @@ impl Daemon { /// Set the target state of the client. If it changed trigger the operations needed to /// progress towards that state. /// Returns an error if trying to set secured state, but no account token is present. - fn set_target_state(&mut self, new_state: TargetState) -> ::std::result::Result<(), ()> { + fn set_target_state(&mut self, new_state: TargetState) { if new_state != self.target_state || self.tunnel_state.is_blocked() { debug!("Target state {:?} => {:?}", self.target_state, new_state); self.target_state = new_state; match self.target_state { - TargetState::Secured => match self.settings.get_account_token() { - Some(account_token) => self.connect_tunnel(account_token), - None => { - self.set_target_state(TargetState::Unsecured)?; - return Err(()); - } - }, + TargetState::Secured => self.connect_tunnel(), TargetState::Unsecured => self.disconnect_tunnel(), } } - Ok(()) } - fn connect_tunnel(&mut self, account_token: AccountToken) { - let command = match self.settings.get_relay_settings() { - RelaySettings::CustomTunnelEndpoint(custom_relay) => custom_relay - .to_tunnel_endpoint() - .chain_err(|| "Custom tunnel endpoint could not be resolved"), - RelaySettings::Normal(constraints) => self - .relay_selector - .get_tunnel_endpoint(&constraints) - .chain_err(|| "No valid relay servers match the current settings") - .map(|(relay, endpoint)| { - self.current_relay = Some(relay); - endpoint - }), - } - .map(|endpoint| self.build_tunnel_parameters(account_token, endpoint)) - .map(|parameters| TunnelCommand::Connect(parameters)) - .unwrap_or_else(|error| { - error!("{}", error.display_chain()); - TunnelCommand::Block(BlockReason::NoMatchingRelay) - }); - self.send_tunnel_command(command); + fn connect_tunnel(&mut self) { + self.send_tunnel_command(TunnelCommand::Connect); } fn disconnect_tunnel(&mut self) { @@ -636,21 +654,7 @@ impl Daemon { fn reconnect_tunnel(&mut self) { if self.target_state == TargetState::Secured { - if let Some(account_token) = self.settings.get_account_token() { - self.connect_tunnel(account_token); - } - } - } - - fn build_tunnel_parameters( - &self, - account_token: AccountToken, - endpoint: TunnelEndpoint, - ) -> TunnelParameters { - TunnelParameters { - endpoint, - options: self.settings.get_tunnel_options().clone(), - username: account_token, + self.connect_tunnel(); } } @@ -691,3 +695,21 @@ impl Drop for Daemon { } } } + + +struct MullvadTunnelParametersGenerator { + tx: mpsc::Sender<DaemonEvent>, +} + +impl TunnelParametersGenerator for MullvadTunnelParametersGenerator { + fn generate(&mut self, retry_attempt: u32) -> Option<TunnelParameters> { + let (response_tx, response_rx) = mpsc::channel(); + self.tx + .send(DaemonEvent::GenerateTunnelParameters( + response_tx, + retry_attempt, + )) + .ok() + .and_then(|_| response_rx.recv().ok()) + } +} diff --git a/mullvad-daemon/src/relays.rs b/mullvad-daemon/src/relays.rs index 4fb90695cb..a36ded1628 100644 --- a/mullvad-daemon/src/relays.rs +++ b/mullvad-daemon/src/relays.rs @@ -178,40 +178,68 @@ impl RelaySelector { pub fn get_tunnel_endpoint( &mut self, constraints: &RelayConstraints, + retry_attempt: u32, ) -> Result<(Relay, TunnelEndpoint)> { + let preferred_constraints = Self::preferred_constraints(constraints, retry_attempt); + if let Some((relay, endpoint)) = self.get_tunnel_endpoint_internal(&preferred_constraints) { + debug!( + "Relay matched on highest preference for retry attempt {}", + retry_attempt + ); + Ok((relay, endpoint)) + } else if let Some((relay, endpoint)) = self.get_tunnel_endpoint_internal(constraints) { + debug!( + "Relay matched on second preference for retry attempt {}", + retry_attempt + ); + Ok((relay, endpoint)) + } else { + warn!("No relays matching {}", constraints); + bail!(ErrorKind::NoRelay); + } + } + + fn preferred_constraints( + original_constraints: &RelayConstraints, + retry_attempt: u32, + ) -> RelayConstraints { + // Prefer UDP by default. But if that has failed a couple of times, then try TCP port 443, + // which works for many with UDP problems. After that, just alternate between protocols. + let (preferred_port, preferred_protocol) = match retry_attempt { + 0 | 1 => (Constraint::Any, TransportProtocol::Udp), + 2 | 3 => (Constraint::Only(443), TransportProtocol::Tcp), + attempt if attempt % 2 == 0 => (Constraint::Any, TransportProtocol::Udp), + _ => (Constraint::Any, TransportProtocol::Tcp), + }; + // Highest priority preference. Where we prefer OpenVPN using UDP. But without changing // any constraints that are explicitly specified. - let tunnel_constraints1 = match constraints.tunnel { + let tunnel_constraints = match original_constraints.tunnel { + // No constraints, we use our preferred ones. Constraint::Any => TunnelConstraints::OpenVpn(OpenVpnConstraints { - port: Constraint::Any, - protocol: Constraint::Only(TransportProtocol::Udp), + port: preferred_port, + protocol: Constraint::Only(preferred_protocol), }), Constraint::Only(TunnelConstraints::OpenVpn(ref openvpn_constraints)) => { - TunnelConstraints::OpenVpn(OpenVpnConstraints { - port: openvpn_constraints.port.clone(), - protocol: Constraint::Only( - openvpn_constraints - .protocol - .clone() - .unwrap_or(TransportProtocol::Udp), - ), - }) + match openvpn_constraints { + // Constrained to OpenVpn, but port/protocol not constrained. Use our preferred. + OpenVpnConstraints { + port: Constraint::Any, + protocol: Constraint::Any, + } => TunnelConstraints::OpenVpn(OpenVpnConstraints { + port: preferred_port, + protocol: Constraint::Only(preferred_protocol), + }), + // Other constraints, use the original constraints. + openvpn_constraints => TunnelConstraints::OpenVpn(openvpn_constraints.clone()), + } } + // Non-OpenVPN constraints. Respect and keep those constraints. Constraint::Only(ref tunnel_constraints) => tunnel_constraints.clone(), }; - let relay_constraints1 = RelayConstraints { - location: constraints.location.clone(), - tunnel: Constraint::Only(tunnel_constraints1), - }; - - if let Some((relay, endpoint)) = self.get_tunnel_endpoint_internal(&relay_constraints1) { - debug!("Relay matched on highest preference"); - Ok((relay, endpoint)) - } else if let Some((relay, endpoint)) = self.get_tunnel_endpoint_internal(constraints) { - debug!("Relay matched on second preference"); - Ok((relay, endpoint)) - } else { - bail!(ErrorKind::NoRelay); + RelayConstraints { + location: original_constraints.location.clone(), + tunnel: Constraint::Only(tunnel_constraints), } } diff --git a/mullvad-types/src/custom_tunnel.rs b/mullvad-types/src/custom_tunnel.rs index 5542ef9e71..9be3ec11f4 100644 --- a/mullvad-types/src/custom_tunnel.rs +++ b/mullvad-types/src/custom_tunnel.rs @@ -1,5 +1,7 @@ -use std::net::{IpAddr, ToSocketAddrs}; - +use std::{ + fmt, + net::{IpAddr, ToSocketAddrs}, +}; use talpid_types::net::{TunnelEndpoint, TunnelEndpointData}; error_chain!{ @@ -26,6 +28,12 @@ impl CustomTunnelEndpoint { } } +impl fmt::Display for CustomTunnelEndpoint { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{} over {}", self.host, self.tunnel) + } +} + /// Does a DNS lookup if the host isn't an IP. /// Returns the first IPv4 address if one exists, otherwise the first IPv6 address. /// Rust only provides means to resolve a socket addr, not just a host, for some reason. So diff --git a/mullvad-types/src/relay_constraints.rs b/mullvad-types/src/relay_constraints.rs index f0a56a9d76..2a2507fa47 100644 --- a/mullvad-types/src/relay_constraints.rs +++ b/mullvad-types/src/relay_constraints.rs @@ -24,6 +24,13 @@ impl<T: fmt::Debug + Clone + Eq + PartialEq> Constraint<T> { Constraint::Only(value) => value, } } + + pub fn or(self, other: Constraint<T>) -> Constraint<T> { + match self { + Constraint::Any => other, + Constraint::Only(value) => Constraint::Only(value), + } + } } impl<T: fmt::Debug + Clone + Eq + PartialEq> Default for Constraint<T> { @@ -50,6 +57,17 @@ pub enum RelaySettings { Normal(RelayConstraints), } +impl fmt::Display for RelaySettings { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match self { + RelaySettings::CustomTunnelEndpoint(endpoint) => { + write!(f, "custom endpoint {}", endpoint) + } + RelaySettings::Normal(constraints) => constraints.fmt(f), + } + } +} + impl Default for RelaySettings { fn default() -> Self { RelaySettings::Normal(RelayConstraints::default()) @@ -88,6 +106,20 @@ impl RelayConstraints { } } +impl fmt::Display for RelayConstraints { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match self.tunnel { + Constraint::Any => write!(f, "any relay")?, + Constraint::Only(ref tunnel_constraint) => tunnel_constraint.fmt(f)?, + } + write!(f, " in ")?; + match self.location { + Constraint::Any => write!(f, "any location"), + Constraint::Only(ref location_constraint) => location_constraint.fmt(f), + } + } +} + #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "snake_case")] @@ -100,6 +132,18 @@ pub enum LocationConstraint { Hostname(CountryCode, CityCode, Hostname), } +impl fmt::Display for LocationConstraint { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match self { + LocationConstraint::Country(country) => write!(f, "country {}", country), + LocationConstraint::City(country, city) => write!(f, "city {}, {}", city, country), + LocationConstraint::Hostname(country, city, hostname) => { + write!(f, "city {}, {}, hostname {}", city, country, hostname) + } + } + } +} + #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] pub enum TunnelConstraints { @@ -109,6 +153,21 @@ pub enum TunnelConstraints { Wireguard(WireguardConstraints), } +impl fmt::Display for TunnelConstraints { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match self { + TunnelConstraints::OpenVpn(openvpn_constraints) => { + write!(f, "OpenVPN over ")?; + openvpn_constraints.fmt(f) + } + TunnelConstraints::Wireguard(wireguard_constraints) => { + write!(f, "Wireguard over ")?; + wireguard_constraints.fmt(f) + } + } + } +} + impl Match<OpenVpnEndpointData> for TunnelConstraints { fn matches(&self, endpoint: &OpenVpnEndpointData) -> bool { match *self { @@ -133,6 +192,20 @@ pub struct OpenVpnConstraints { pub protocol: Constraint<TransportProtocol>, } +impl fmt::Display for OpenVpnConstraints { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match self.port { + Constraint::Any => write!(f, "any port")?, + Constraint::Only(port) => write!(f, "port {}", port)?, + } + write!(f, " over ")?; + match self.protocol { + Constraint::Any => write!(f, "any protocol"), + Constraint::Only(protocol) => write!(f, "{}", protocol), + } + } +} + impl Match<OpenVpnEndpointData> for OpenVpnConstraints { fn matches(&self, endpoint: &OpenVpnEndpointData) -> bool { self.port.matches(&endpoint.port) && self.protocol.matches(&endpoint.protocol) @@ -144,6 +217,15 @@ pub struct WireguardConstraints { pub port: Constraint<u16>, } +impl fmt::Display for WireguardConstraints { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match self.port { + Constraint::Any => write!(f, "any port"), + Constraint::Only(port) => write!(f, "port {}", port), + } + } +} + impl Match<WireguardEndpointData> for WireguardConstraints { fn matches(&self, endpoint: &WireguardEndpointData) -> bool { self.port.matches(&endpoint.port) diff --git a/mullvad-types/src/settings.rs b/mullvad-types/src/settings.rs index 231a1b638d..bca23e1786 100644 --- a/mullvad-types/src/settings.rs +++ b/mullvad-types/src/settings.rs @@ -136,7 +136,7 @@ impl Settings { let new_settings = self.relay_settings.merge(update); if self.relay_settings != new_settings { debug!( - "changing relay settings from {:?} to {:?}", + "changing relay settings from {} to {}", self.relay_settings, new_settings ); diff --git a/talpid-core/src/tunnel_state_machine/blocked_state.rs b/talpid-core/src/tunnel_state_machine/blocked_state.rs index 33cdd3a1e1..8c73ccd0c5 100644 --- a/talpid-core/src/tunnel_state_machine/blocked_state.rs +++ b/talpid-core/src/tunnel_state_machine/blocked_state.rs @@ -55,9 +55,7 @@ impl TunnelState for BlockedState { Self::set_security_policy(shared_values); SameState(self) } - Ok(TunnelCommand::Connect(parameters)) => { - NewState(ConnectingState::enter(shared_values, parameters)) - } + Ok(TunnelCommand::Connect) => NewState(ConnectingState::enter(shared_values, 0)), Ok(TunnelCommand::Disconnect) | Err(_) => { NewState(DisconnectedState::enter(shared_values, ())) } diff --git a/talpid-core/src/tunnel_state_machine/connected_state.rs b/talpid-core/src/tunnel_state_machine/connected_state.rs index 7d04bf0c3b..a969ece39f 100644 --- a/talpid-core/src/tunnel_state_machine/connected_state.rs +++ b/talpid-core/src/tunnel_state_machine/connected_state.rs @@ -79,20 +79,14 @@ impl ConnectedState { } } } - Ok(TunnelCommand::Connect(parameters)) => { - if parameters != self.tunnel_parameters { - NewState(DisconnectingState::enter( - shared_values, - ( - self.close_handle, - self.tunnel_close_event, - AfterDisconnect::Reconnect(parameters), - ), - )) - } else { - SameState(self) - } - } + Ok(TunnelCommand::Connect) => NewState(DisconnectingState::enter( + shared_values, + ( + self.close_handle, + self.tunnel_close_event, + AfterDisconnect::Reconnect(0), + ), + )), Ok(TunnelCommand::Disconnect) | Err(_) => NewState(DisconnectingState::enter( shared_values, ( @@ -124,7 +118,7 @@ impl ConnectedState { ( self.close_handle, self.tunnel_close_event, - AfterDisconnect::Reconnect(self.tunnel_parameters), + AfterDisconnect::Reconnect(0), ), )), Ok(_) => SameState(self), @@ -144,10 +138,7 @@ impl ConnectedState { } info!("Tunnel closed. Reconnecting."); - NewState(ConnectingState::enter( - shared_values, - self.tunnel_parameters, - )) + NewState(ConnectingState::enter(shared_values, 0)) } } diff --git a/talpid-core/src/tunnel_state_machine/connecting_state.rs b/talpid-core/src/tunnel_state_machine/connecting_state.rs index bcf1c422e4..1e0880141e 100644 --- a/talpid-core/src/tunnel_state_machine/connecting_state.rs +++ b/talpid-core/src/tunnel_state_machine/connecting_state.rs @@ -47,6 +47,7 @@ pub struct ConnectingState { tunnel_parameters: TunnelParameters, tunnel_close_event: oneshot::Receiver<()>, close_handle: CloseHandle, + retry_attempt: u32, } impl ConnectingState { @@ -68,6 +69,7 @@ impl ConnectingState { parameters: TunnelParameters, log_dir: &Option<PathBuf>, resource_dir: &Path, + retry_attempt: u32, ) -> Result<Self> { let (event_tx, event_rx) = mpsc::unbounded(); let monitor = Self::spawn_tunnel_monitor(¶meters, log_dir, resource_dir, event_tx)?; @@ -79,6 +81,7 @@ impl ConnectingState { tunnel_parameters: parameters, tunnel_close_event, close_handle, + retry_attempt, }) } @@ -185,20 +188,14 @@ impl ConnectingState { } } } - Ok(TunnelCommand::Connect(parameters)) => { - if parameters != self.tunnel_parameters { - NewState(DisconnectingState::enter( - shared_values, - ( - self.close_handle, - self.tunnel_close_event, - AfterDisconnect::Reconnect(parameters), - ), - )) - } else { - SameState(self) - } - } + Ok(TunnelCommand::Connect) => NewState(DisconnectingState::enter( + shared_values, + ( + self.close_handle, + self.tunnel_close_event, + AfterDisconnect::Reconnect(0), + ), + )), Ok(TunnelCommand::Disconnect) | Err(_) => NewState(DisconnectingState::enter( shared_values, ( @@ -245,7 +242,7 @@ impl ConnectingState { ( self.close_handle, self.tunnel_close_event, - AfterDisconnect::Reconnect(self.tunnel_parameters), + AfterDisconnect::Reconnect(self.retry_attempt + 1), ), )) } @@ -262,47 +259,61 @@ impl ConnectingState { Err(_cancelled) => warn!("Tunnel monitor thread has stopped unexpectedly"), } - info!("Tunnel closed. Reconnecting."); + info!( + "Tunnel closed. Reconnecting, attempt {}.", + self.retry_attempt + 1 + ); EventConsequence::NewState(ConnectingState::enter( shared_values, - self.tunnel_parameters, + self.retry_attempt + 1, )) } } impl TunnelState for ConnectingState { - type Bootstrap = TunnelParameters; + type Bootstrap = u32; fn enter( shared_values: &mut SharedTunnelStateValues, - parameters: Self::Bootstrap, + retry_attempt: u32, ) -> (TunnelStateWrapper, TunnelStateTransition) { - if let Err(error) = Self::set_security_policy(shared_values, parameters.endpoint) { - error!("{}", error.display_chain()); - return BlockedState::enter(shared_values, BlockReason::StartTunnelError); - } - - match Self::start_tunnel( - parameters, - &shared_values.log_dir, - &shared_values.resource_dir, - ) { - Ok(connecting_state) => ( - TunnelStateWrapper::from(connecting_state), - TunnelStateTransition::Connecting, - ), - Err(error) => { - let block_reason = match *error.kind() { - ErrorKind::TunnelMonitorError(tunnel::ErrorKind::EnableIpv6Error) => { - BlockReason::Ipv6Unavailable - } - _ => BlockReason::StartTunnelError, - }; + match shared_values + .tunnel_parameters_generator + .generate(retry_attempt) + { + None => BlockedState::enter(shared_values, BlockReason::NoMatchingRelay), + Some(tunnel_parameters) => { + if let Err(error) = + Self::set_security_policy(shared_values, tunnel_parameters.endpoint) + { + error!("{}", error.display_chain()); + BlockedState::enter(shared_values, BlockReason::StartTunnelError) + } else { + match Self::start_tunnel( + tunnel_parameters, + &shared_values.log_dir, + &shared_values.resource_dir, + retry_attempt, + ) { + Ok(connecting_state) => ( + TunnelStateWrapper::from(connecting_state), + TunnelStateTransition::Connecting, + ), + Err(error) => { + let block_reason = match *error.kind() { + ErrorKind::TunnelMonitorError( + tunnel::ErrorKind::EnableIpv6Error, + ) => BlockReason::Ipv6Unavailable, + _ => BlockReason::StartTunnelError, + }; - let chained_error = error.chain_err(|| "Failed to start tunnel"); - error!("{}", chained_error.display_chain()); + let chained_error = error.chain_err(|| "Failed to start tunnel"); + error!("{}", chained_error.display_chain()); - BlockedState::enter(shared_values, block_reason) + BlockedState::enter(shared_values, block_reason) + } + } + } } } } diff --git a/talpid-core/src/tunnel_state_machine/disconnected_state.rs b/talpid-core/src/tunnel_state_machine/disconnected_state.rs index 3d4518a050..227e019323 100644 --- a/talpid-core/src/tunnel_state_machine/disconnected_state.rs +++ b/talpid-core/src/tunnel_state_machine/disconnected_state.rs @@ -46,9 +46,7 @@ impl TunnelState for DisconnectedState { shared_values.allow_lan = allow_lan; SameState(self) } - Ok(TunnelCommand::Connect(parameters)) => { - NewState(ConnectingState::enter(shared_values, parameters)) - } + Ok(TunnelCommand::Connect) => NewState(ConnectingState::enter(shared_values, 0)), Ok(TunnelCommand::Block(reason)) => { NewState(BlockedState::enter(shared_values, reason)) } diff --git a/talpid-core/src/tunnel_state_machine/disconnecting_state.rs b/talpid-core/src/tunnel_state_machine/disconnecting_state.rs index 5028792c11..864b11ab4b 100644 --- a/talpid-core/src/tunnel_state_machine/disconnecting_state.rs +++ b/talpid-core/src/tunnel_state_machine/disconnecting_state.rs @@ -8,8 +8,7 @@ use talpid_types::tunnel::{ActionAfterDisconnect, BlockReason}; use super::{ BlockedState, ConnectingState, DisconnectedState, EventConsequence, ResultExt, - SharedTunnelStateValues, TunnelCommand, TunnelParameters, TunnelState, TunnelStateTransition, - TunnelStateWrapper, + SharedTunnelStateValues, TunnelCommand, TunnelState, TunnelStateTransition, TunnelStateWrapper, }; use tunnel::CloseHandle; @@ -26,8 +25,6 @@ impl DisconnectingState { commands: &mut mpsc::UnboundedReceiver<TunnelCommand>, shared_values: &mut SharedTunnelStateValues, ) -> EventConsequence<Self> { - use self::AfterDisconnect::*; - let event = try_handle_event!(self, commands.poll()); let after_disconnect = self.after_disconnect; @@ -35,30 +32,30 @@ impl DisconnectingState { AfterDisconnect::Nothing => match event { Ok(TunnelCommand::AllowLan(allow_lan)) => { shared_values.allow_lan = allow_lan; - Nothing + AfterDisconnect::Nothing } - Ok(TunnelCommand::Connect(parameters)) => Reconnect(parameters), - Ok(TunnelCommand::Block(reason)) => Block(reason), - _ => Nothing, + Ok(TunnelCommand::Connect) => AfterDisconnect::Reconnect(0), + Ok(TunnelCommand::Block(reason)) => AfterDisconnect::Block(reason), + _ => AfterDisconnect::Nothing, }, AfterDisconnect::Block(reason) => match event { Ok(TunnelCommand::AllowLan(allow_lan)) => { shared_values.allow_lan = allow_lan; - Block(reason) + AfterDisconnect::Block(reason) } - Ok(TunnelCommand::Connect(parameters)) => Reconnect(parameters), - Ok(TunnelCommand::Disconnect) => Nothing, - Ok(TunnelCommand::Block(new_reason)) => Block(new_reason), - Err(_) => Block(reason), + Ok(TunnelCommand::Connect) => AfterDisconnect::Reconnect(0), + Ok(TunnelCommand::Disconnect) => AfterDisconnect::Nothing, + Ok(TunnelCommand::Block(new_reason)) => AfterDisconnect::Block(new_reason), + Err(_) => AfterDisconnect::Block(reason), }, - AfterDisconnect::Reconnect(tunnel_parameters) => match event { + AfterDisconnect::Reconnect(retry_attempt) => match event { Ok(TunnelCommand::AllowLan(allow_lan)) => { shared_values.allow_lan = allow_lan; - Reconnect(tunnel_parameters) + AfterDisconnect::Reconnect(retry_attempt) } - Ok(TunnelCommand::Connect(parameters)) => Reconnect(parameters), - Ok(TunnelCommand::Disconnect) | Err(_) => Nothing, - Ok(TunnelCommand::Block(reason)) => Block(reason), + Ok(TunnelCommand::Connect) => AfterDisconnect::Reconnect(retry_attempt), + Ok(TunnelCommand::Disconnect) | Err(_) => AfterDisconnect::Nothing, + Ok(TunnelCommand::Block(reason)) => AfterDisconnect::Block(reason), }, }; @@ -84,8 +81,8 @@ impl DisconnectingState { match self.after_disconnect { AfterDisconnect::Nothing => DisconnectedState::enter(shared_values, ()), AfterDisconnect::Block(reason) => BlockedState::enter(shared_values, reason), - AfterDisconnect::Reconnect(tunnel_parameters) => { - ConnectingState::enter(shared_values, tunnel_parameters) + AfterDisconnect::Reconnect(retry_attempt) => { + ConnectingState::enter(shared_values, retry_attempt) } } } @@ -133,7 +130,7 @@ impl TunnelState for DisconnectingState { pub enum AfterDisconnect { Nothing, Block(BlockReason), - Reconnect(TunnelParameters), + Reconnect(u32), } impl AfterDisconnect { @@ -142,7 +139,7 @@ impl AfterDisconnect { match self { AfterDisconnect::Nothing => ActionAfterDisconnect::Nothing, AfterDisconnect::Block(..) => ActionAfterDisconnect::Block, - AfterDisconnect::Reconnect(_) => ActionAfterDisconnect::Reconnect, + AfterDisconnect::Reconnect(..) => ActionAfterDisconnect::Reconnect, } } } diff --git a/talpid-core/src/tunnel_state_machine/mod.rs b/talpid-core/src/tunnel_state_machine/mod.rs index 91ca9aa7ad..66e86de9de 100644 --- a/talpid-core/src/tunnel_state_machine/mod.rs +++ b/talpid-core/src/tunnel_state_machine/mod.rs @@ -44,6 +44,7 @@ error_chain! { /// Spawn the tunnel state machine thread, returning a channel for sending tunnel commands. pub fn spawn<P, T>( allow_lan: bool, + tunnel_parameters_generator: impl TunnelParametersGenerator, log_dir: Option<PathBuf>, resource_dir: PathBuf, cache_dir: P, @@ -59,6 +60,7 @@ where thread::spawn(move || { match create_event_loop( allow_lan, + tunnel_parameters_generator, log_dir, resource_dir, cache_dir, @@ -92,6 +94,7 @@ where fn create_event_loop<T>( allow_lan: bool, + tunnel_parameters_generator: impl TunnelParametersGenerator, log_dir: Option<PathBuf>, resource_dir: PathBuf, cache_dir: impl AsRef<Path>, @@ -102,8 +105,14 @@ where T: From<TunnelStateTransition> + Send + 'static, { let reactor = Core::new().chain_err(|| ErrorKind::ReactorError)?; - let state_machine = - TunnelStateMachine::new(allow_lan, log_dir, resource_dir, cache_dir, commands)?; + let state_machine = TunnelStateMachine::new( + allow_lan, + tunnel_parameters_generator, + log_dir, + resource_dir, + cache_dir, + commands, + )?; let future = state_machine.for_each(move |state_change_event| { state_change_listener @@ -119,7 +128,7 @@ pub enum TunnelCommand { /// Enable or disable LAN access in the firewall. AllowLan(bool), /// Open tunnel connection. - Connect(TunnelParameters), + Connect, /// Close tunnel connection. Disconnect, /// Disconnect any open tunnel and block all network access @@ -152,6 +161,7 @@ struct TunnelStateMachine { impl TunnelStateMachine { fn new( allow_lan: bool, + tunnel_parameters_generator: impl TunnelParametersGenerator, log_dir: Option<PathBuf>, resource_dir: PathBuf, cache_dir: impl AsRef<Path>, @@ -162,6 +172,7 @@ impl TunnelStateMachine { let mut shared_values = SharedTunnelStateValues { security, allow_lan, + tunnel_parameters_generator: Box::new(tunnel_parameters_generator), log_dir, resource_dir, }; @@ -225,12 +236,21 @@ impl<T: TunnelState> From<EventConsequence<T>> for TunnelStateMachineAction { } } +/// Trait for any type that can provide a stream of `TunnelParameters` to the `TunnelStateMachine`. +pub trait TunnelParametersGenerator: Send + 'static { + /// Given the number of consecutive failed retry attempts, it should yield a `TunnelParameters` + /// to establish a tunnel with. + /// If this returns `None` then the state machine goes into the `Blocked` state. + fn generate(&mut self, retry_attempt: u32) -> Option<TunnelParameters>; +} /// Values that are common to all tunnel states. struct SharedTunnelStateValues { security: NetworkSecurity, /// Should LAN access be allowed outside the tunnel. allow_lan: bool, + /// The generator of new `TunnelParameter`s + tunnel_parameters_generator: Box<dyn TunnelParametersGenerator>, /// Directory to store tunnel log file. log_dir: Option<PathBuf>, /// Resource directory path. diff --git a/talpid-types/src/net.rs b/talpid-types/src/net.rs index 31edfb7d48..e00d0d9fe4 100644 --- a/talpid-types/src/net.rs +++ b/talpid-types/src/net.rs @@ -33,6 +33,21 @@ pub enum TunnelEndpointData { Wireguard(WireguardEndpointData), } +impl fmt::Display for TunnelEndpointData { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match self { + TunnelEndpointData::OpenVpn(openvpn_data) => { + write!(f, "OpenVPN ")?; + openvpn_data.fmt(f) + } + TunnelEndpointData::Wireguard(wireguard_data) => { + write!(f, "Wireguard ")?; + wireguard_data.fmt(f) + } + } + } +} + impl TunnelEndpointData { pub fn port(self) -> u16 { match self { @@ -55,11 +70,23 @@ pub struct OpenVpnEndpointData { pub protocol: TransportProtocol, } +impl fmt::Display for OpenVpnEndpointData { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!(f, "{} port {}", self.protocol, self.port) + } +} + #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)] pub struct WireguardEndpointData { pub port: u16, } +impl fmt::Display for WireguardEndpointData { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!(f, "port {}", self.port) + } +} + /// Represents a network layer IP address together with the transport layer protocol and port. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] |
