diff options
| author | Janito Vaqueiro Ferreira Filho <janito@mullvad.net> | 2018-09-03 08:09:30 -0300 |
|---|---|---|
| committer | Janito Vaqueiro Ferreira Filho <janito@mullvad.net> | 2018-09-03 08:09:30 -0300 |
| commit | 9e440721700a7ab6a3d0d5c81c4d42949ea7108c (patch) | |
| tree | 159c1ac066c679e3d9204c79b15e118426678e4f | |
| parent | a23afed45caa953cb9765991a4692b66b3a39329 (diff) | |
| parent | 13e272fe2c22a5b026aa4dc8cae80e9a68df4dc2 (diff) | |
| download | mullvadvpn-9e440721700a7ab6a3d0d5c81c4d42949ea7108c.tar.xz mullvadvpn-9e440721700a7ab6a3d0d5c81c4d42949ea7108c.zip | |
Merge branch 'blocked-state'
| -rw-r--r-- | CHANGELOG.md | 2 | ||||
| -rw-r--r-- | gui/packages/desktop/src/renderer/app.js | 49 | ||||
| -rw-r--r-- | gui/packages/desktop/src/renderer/components/Connect.js | 13 | ||||
| -rw-r--r-- | gui/packages/desktop/src/renderer/errors.js | 17 | ||||
| -rw-r--r-- | gui/packages/desktop/src/renderer/lib/daemon-rpc.js | 48 | ||||
| -rw-r--r-- | gui/packages/desktop/src/renderer/redux/connection/actions.js | 14 | ||||
| -rw-r--r-- | gui/packages/desktop/src/renderer/redux/connection/reducers.js | 13 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/status.rs | 1 | ||||
| -rw-r--r-- | mullvad-daemon/src/main.rs | 10 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/blocked_state.rs | 44 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/connected_state.rs | 27 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/connecting_state.rs | 54 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/disconnected_state.rs | 14 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/disconnecting_state.rs | 32 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/mod.rs | 37 | ||||
| -rw-r--r-- | talpid-types/src/tunnel.rs | 28 |
16 files changed, 302 insertions, 101 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index a38cf03320..e78c30f784 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,8 @@ Line wrap the file at 100 chars. Th - Add option to enable or disable IPv6 on the tunnel interface. - Log panics in the daemon to the log file. - Warn in the Settings screen if a new version is available. +- Enter a "blocked" state in case of connection error, which prevents leaking connections until the + user specifically requests to disconnect. #### Windows - Extend uninstaller to also remove logs, cache and optionally settings. diff --git a/gui/packages/desktop/src/renderer/app.js b/gui/packages/desktop/src/renderer/app.js index e88d3d619c..b693a84544 100644 --- a/gui/packages/desktop/src/renderer/app.js +++ b/gui/packages/desktop/src/renderer/app.js @@ -402,10 +402,9 @@ export default class AppRenderer { actions.settings.updateAutoConnect(autoConnect); } - async _fetchSecurityState() { - const securityState = await this._daemonRpc.getState(); - const connectionState = this._tunnelStateToConnectionState(securityState); - this._updateConnectionState(connectionState); + async _fetchTunnelState() { + const tunnelState = await this._daemonRpc.getState(); + this._updateConnectionState(tunnelState); } async _fetchTunnelOptions() { @@ -502,11 +501,9 @@ export default class AppRenderer { } if (newState) { - const connectionState = this._tunnelStateToConnectionState(newState); + log.debug(`Got new state from daemon '${JSON.stringify(newState)}'`); - log.debug(`Got new state from daemon '${newState}', translated to '${connectionState}'`); - - this._updateConnectionState(connectionState); + this._updateConnectionState(newState); this._refreshStateOnChange(); } }); @@ -514,7 +511,7 @@ export default class AppRenderer { _fetchInitialState() { return Promise.all([ - this._fetchSecurityState(), + this._fetchTunnelState(), this.fetchRelaySettings(), this._fetchRelayLocations(), this._fetchAllowLan(), @@ -530,6 +527,7 @@ export default class AppRenderer { const iconTypes: { [ConnectionState]: TrayIconType } = { connected: 'secured', connecting: 'securing', + blocked: 'securing', }; const type = iconTypes[connectionState] || 'unsecured'; @@ -545,28 +543,44 @@ export default class AppRenderer { } _tunnelStateToConnectionState(tunnelState: TunnelState): ConnectionState { - if (tunnelState === 'disconnected' || tunnelState === 'disconnecting') { - return 'disconnected'; - } else if (tunnelState === 'connected' || tunnelState === 'connecting') { - return tunnelState; + switch (tunnelState.state) { + case 'disconnected': + // Fall through + case 'disconnecting': + return 'disconnected'; + case 'connected': + return 'connected'; + case 'connecting': + return 'connecting'; + case 'blocked': + return 'blocked'; + default: + throw new Error('Unknown tunnel state: ' + (tunnelState: empty)); } - throw new Error('Unsupported state/target state combination: ' + JSON.stringify(tunnelState)); } - _updateConnectionState(connectionState: ConnectionState) { + _updateConnectionState(tunnelState: TunnelState) { const actions = this._reduxActions; - switch (connectionState) { + switch (tunnelState.state) { case 'connecting': actions.connection.connecting(); break; case 'connected': actions.connection.connected(); break; + case 'disconnecting': + // Fall through case 'disconnected': actions.connection.disconnected(); break; + case 'blocked': + actions.connection.blocked(tunnelState.details); + break; + default: + log.error(`Unexpected TunnelState: ${(tunnelState: empty)}`); } + const connectionState = this._tunnelStateToConnectionState(tunnelState); this._updateTrayIcon(connectionState); this._showNotification(connectionState); } @@ -582,6 +596,9 @@ export default class AppRenderer { case 'disconnected': this._notificationController.show('Unsecured'); break; + case 'blocked': + this._notificationController.show('Blocked all connections'); + break; default: log.error(`Unexpected ConnectionState: ${(connectionState: empty)}`); return; diff --git a/gui/packages/desktop/src/renderer/components/Connect.js b/gui/packages/desktop/src/renderer/components/Connect.js index d9c3768df6..bf08000ce5 100644 --- a/gui/packages/desktop/src/renderer/components/Connect.js +++ b/gui/packages/desktop/src/renderer/components/Connect.js @@ -10,7 +10,7 @@ import * as AppButton from './AppButton'; import Img from './Img'; import Map from './Map'; import styles from './ConnectStyles'; -import { NoCreditError, NoInternetError } from '../errors'; +import { BlockedError, NoCreditError, NoInternetError } from '../errors'; import WindowStateObserver from '../lib/window-state-observer'; import type { HeaderBarStyle } from './HeaderBar'; @@ -103,6 +103,11 @@ export default class Connect extends Component<Props, State> { message = 'Your internet connection will be secured when you get back online'; } + if (error instanceof BlockedError) { + title = 'Blocked'; + message = error.message; + } + return ( <View style={styles.connect}> <View style={styles.status_icon}> @@ -345,6 +350,7 @@ export default class Connect extends Component<Props, State> { return 'error'; case 'connecting': case 'connected': + case 'blocked': return 'success'; default: throw new Error(`Invalid ConnectionState: ${(status: empty)}`); @@ -392,6 +398,11 @@ export default class Connect extends Component<Props, State> { return new NoCreditError(); } + // Tunnel is blocked due to an error? + if (this.props.connection.status === 'blocked') { + return new BlockedError(this.props.connection.blockReason); + } + return null; } } diff --git a/gui/packages/desktop/src/renderer/errors.js b/gui/packages/desktop/src/renderer/errors.js index 7464851a1f..ddf0c239bb 100644 --- a/gui/packages/desktop/src/renderer/errors.js +++ b/gui/packages/desktop/src/renderer/errors.js @@ -1,5 +1,22 @@ // @flow +import type { BlockReason } from './lib/daemon-rpc'; + +export class BlockedError extends Error { + constructor(reason: BlockReason) { + switch (reason) { + case 'set_security_policy_error': + super('Failed to apply security policy'); + break; + case 'start_tunnel_error': + super('Failed to start tunnel connection'); + break; + default: + super(`Unknown error: ${(reason: empty)}`); + } + } +} + export class NoCreditError extends Error { constructor() { super("Account doesn't have enough credit available for connection"); diff --git a/gui/packages/desktop/src/renderer/lib/daemon-rpc.js b/gui/packages/desktop/src/renderer/lib/daemon-rpc.js index e590534d69..ae2e501268 100644 --- a/gui/packages/desktop/src/renderer/lib/daemon-rpc.js +++ b/gui/packages/desktop/src/renderer/lib/daemon-rpc.js @@ -41,7 +41,29 @@ const LocationSchema = object({ mullvad_exit_ip: boolean, }); -export type TunnelState = 'disconnected' | 'connecting' | 'connected' | 'disconnecting'; +export type BlockReason = 'set_security_policy_error' | 'start_tunnel_error'; +export type DisconnectedState = { + state: 'disconnected', +}; +export type ConnectingState = { + state: 'connecting', +}; +export type ConnectedState = { + state: 'connected', +}; +export type DisconnectingState = { + state: 'disconnecting', +}; +export type BlockedState = { + state: 'blocked', + details: BlockReason, +}; +export type TunnelState = + | DisconnectedState + | ConnectingState + | ConnectedState + | DisconnectingState + | BlockedState; export type RelayProtocol = 'tcp' | 'udp'; export type RelayLocation = {| city: [string, string] |} | {| country: string |}; @@ -196,13 +218,23 @@ const AccountDataSchema = object({ expiry: string, }); -const allTunnelStates: Array<TunnelState> = [ - 'disconnected', - 'connecting', - 'connected', - 'disconnecting', -]; -const TunnelStateSchema = enumeration(...allTunnelStates); +const allBlockReasons: Array<BlockReason> = ['set_security_policy_error', 'start_tunnel_error']; +const BlockedStateSchema = object({ + state: enumeration('blocked'), + details: enumeration(...allBlockReasons), +}); +const ConnectedStateSchema = object({ state: enumeration('connected') }); +const ConnectingStateSchema = object({ state: enumeration('connecting') }); +const DisconnectedStateSchema = object({ state: enumeration('disconnected') }); +const DisconnectingStateSchema = object({ state: enumeration('disconnecting') }); + +const TunnelStateSchema = oneOf( + BlockedStateSchema, + ConnectedStateSchema, + ConnectingStateSchema, + DisconnectedStateSchema, + DisconnectingStateSchema, +); export type AppVersionInfo = { currentIsSupported: boolean, diff --git a/gui/packages/desktop/src/renderer/redux/connection/actions.js b/gui/packages/desktop/src/renderer/redux/connection/actions.js index a4c5c68890..052d206986 100644 --- a/gui/packages/desktop/src/renderer/redux/connection/actions.js +++ b/gui/packages/desktop/src/renderer/redux/connection/actions.js @@ -14,6 +14,11 @@ type DisconnectedAction = { type: 'DISCONNECTED', }; +type BlockedAction = { + type: 'BLOCKED', + reason: string, +}; + type NewLocationAction = { type: 'NEW_LOCATION', newLocation: { @@ -39,6 +44,7 @@ export type ConnectionAction = | ConnectingAction | ConnectedAction | DisconnectedAction + | BlockedAction | OnlineAction | OfflineAction; @@ -60,6 +66,13 @@ function disconnected(): DisconnectedAction { }; } +function blocked(reason: string): BlockedAction { + return { + type: 'BLOCKED', + reason, + }; +} + function newLocation(newLoc: $PropertyType<NewLocationAction, 'newLocation'>): NewLocationAction { return { type: 'NEW_LOCATION', @@ -84,6 +97,7 @@ export default { connecting, connected, disconnected, + blocked, online, offline, }; diff --git a/gui/packages/desktop/src/renderer/redux/connection/reducers.js b/gui/packages/desktop/src/renderer/redux/connection/reducers.js index c75d0f2a8f..d425dec438 100644 --- a/gui/packages/desktop/src/renderer/redux/connection/reducers.js +++ b/gui/packages/desktop/src/renderer/redux/connection/reducers.js @@ -3,7 +3,7 @@ import type { ReduxAction } from '../store'; import type { Ip } from '../../lib/daemon-rpc'; -export type ConnectionState = 'disconnected' | 'connecting' | 'connected'; +export type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'blocked'; export type ConnectionReduxState = { status: ConnectionState, isOnline: boolean, @@ -12,6 +12,7 @@ export type ConnectionReduxState = { longitude: ?number, country: ?string, city: ?string, + blockReason: ?string, }; const initialState: ConnectionReduxState = { @@ -22,6 +23,7 @@ const initialState: ConnectionReduxState = { longitude: null, country: null, city: null, + blockReason: null, }; export default function( @@ -33,13 +35,16 @@ export default function( return { ...state, ...action.newLocation }; case 'CONNECTING': - return { ...state, ...{ status: 'connecting' } }; + return { ...state, ...{ status: 'connecting', blockReason: null } }; case 'CONNECTED': - return { ...state, ...{ status: 'connected' } }; + return { ...state, ...{ status: 'connected', blockReason: null } }; case 'DISCONNECTED': - return { ...state, ...{ status: 'disconnected' } }; + return { ...state, ...{ status: 'disconnected', blockReason: null } }; + + case 'BLOCKED': + return { ...state, ...{ status: 'blocked', blockReason: action.reason } }; case 'ONLINE': return { ...state, ...{ isOnline: true } }; diff --git a/mullvad-cli/src/cmds/status.rs b/mullvad-cli/src/cmds/status.rs index 7ebf8cd36b..1266299c17 100644 --- a/mullvad-cli/src/cmds/status.rs +++ b/mullvad-cli/src/cmds/status.rs @@ -43,6 +43,7 @@ impl Command for Status { fn print_state(state: TunnelStateTransition) { print!("Tunnel status: "); match state { + Blocked(reason) => println!("Blocked ({})", reason), Connected => println!("Connected"), Connecting => println!("Connecting..."), Disconnected => println!("Disconnected"), diff --git a/mullvad-daemon/src/main.rs b/mullvad-daemon/src/main.rs index 9ed938649e..b82430098e 100644 --- a/mullvad-daemon/src/main.rs +++ b/mullvad-daemon/src/main.rs @@ -335,9 +335,13 @@ impl Daemon { debug!("New tunnel state: {:?}", tunnel_state); - if tunnel_state == Disconnected { - self.state.disconnected(); - self.current_relay = None; + match tunnel_state { + Disconnected => { + self.state.disconnected(); + self.current_relay = None; + } + Blocked(ref reason) => info!("Blocking all network connections, reason: {}", reason), + _ => {} } self.tunnel_state = tunnel_state; diff --git a/talpid-core/src/tunnel_state_machine/blocked_state.rs b/talpid-core/src/tunnel_state_machine/blocked_state.rs new file mode 100644 index 0000000000..f63326b800 --- /dev/null +++ b/talpid-core/src/tunnel_state_machine/blocked_state.rs @@ -0,0 +1,44 @@ +use futures::sync::mpsc; +use futures::Stream; + +use talpid_types::tunnel::BlockReason; + +use super::{ + ConnectingState, DisconnectedState, EventConsequence, SharedTunnelStateValues, TunnelCommand, + TunnelState, TunnelStateTransition, TunnelStateWrapper, +}; + +/// No tunnel is running and all network connections are blocked. +pub struct BlockedState; + +impl TunnelState for BlockedState { + type Bootstrap = BlockReason; + + fn enter( + _: &mut SharedTunnelStateValues, + block_reason: Self::Bootstrap, + ) -> (TunnelStateWrapper, TunnelStateTransition) { + ( + TunnelStateWrapper::from(BlockedState), + TunnelStateTransition::Blocked(block_reason), + ) + } + + fn handle_event( + self, + commands: &mut mpsc::UnboundedReceiver<TunnelCommand>, + shared_values: &mut SharedTunnelStateValues, + ) -> EventConsequence<Self> { + use self::EventConsequence::*; + + match try_handle_event!(self, commands.poll()) { + Ok(TunnelCommand::Connect(parameters)) => { + NewState(ConnectingState::enter(shared_values, parameters)) + } + Ok(TunnelCommand::Disconnect) | Err(_) => { + NewState(DisconnectedState::enter(shared_values, ())) + } + _ => SameState(self), + } + } +} diff --git a/talpid-core/src/tunnel_state_machine/connected_state.rs b/talpid-core/src/tunnel_state_machine/connected_state.rs index 1b119eb387..02a44c8be0 100644 --- a/talpid-core/src/tunnel_state_machine/connected_state.rs +++ b/talpid-core/src/tunnel_state_machine/connected_state.rs @@ -1,11 +1,13 @@ +use error_chain::ChainedError; use futures::sync::{mpsc, oneshot}; use futures::{Async, Future, Stream}; use talpid_types::net::TunnelEndpoint; +use talpid_types::tunnel::BlockReason; use super::{ AfterDisconnect, ConnectingState, DisconnectingState, EventConsequence, Result, ResultExt, - SharedTunnelStateValues, StateEntryResult, TunnelCommand, TunnelParameters, TunnelState, + SharedTunnelStateValues, TunnelCommand, TunnelParameters, TunnelState, TunnelStateTransition, TunnelStateWrapper, }; use security::{NetworkSecurity, SecurityPolicy}; @@ -92,13 +94,14 @@ impl ConnectedState { match self.set_security_policy(shared_values) { Ok(()) => SameState(self), Err(error) => { - error!("{}", error.chain_err(|| "Failed to update security policy")); + error!("{}", error.display_chain()); + NewState(DisconnectingState::enter( shared_values, ( self.close_handle, self.tunnel_close_event, - AfterDisconnect::Nothing, + AfterDisconnect::Block(BlockReason::SetSecurityPolicyError), ), )) } @@ -152,22 +155,26 @@ impl TunnelState for ConnectedState { fn enter( shared_values: &mut SharedTunnelStateValues, bootstrap: Self::Bootstrap, - ) -> StateEntryResult { + ) -> (TunnelStateWrapper, TunnelStateTransition) { let connected_state = ConnectedState::from(bootstrap); match connected_state.set_security_policy(shared_values) { - Ok(()) => Ok(TunnelStateWrapper::from(connected_state)), - Err(error) => Err(( - error, + Ok(()) => ( + TunnelStateWrapper::from(connected_state), + TunnelStateTransition::Connected, + ), + Err(error) => { + error!("{}", error.display_chain()); + DisconnectingState::enter( shared_values, ( connected_state.close_handle, connected_state.tunnel_close_event, - AfterDisconnect::Nothing, + AfterDisconnect::Block(BlockReason::SetSecurityPolicyError), ), - ).expect("Failed to disconnect after failed transition to connected state"), - )), + ) + } } } diff --git a/talpid-core/src/tunnel_state_machine/connecting_state.rs b/talpid-core/src/tunnel_state_machine/connecting_state.rs index 2588da0e77..a87fa1384e 100644 --- a/talpid-core/src/tunnel_state_machine/connecting_state.rs +++ b/talpid-core/src/tunnel_state_machine/connecting_state.rs @@ -4,16 +4,18 @@ use std::sync::Mutex; use std::thread; use std::time::{Duration, Instant}; +use error_chain::ChainedError; use futures::sink::Wait; use futures::sync::{mpsc, oneshot}; use futures::{Async, Future, Sink, Stream}; use talpid_types::net::{TunnelEndpoint, TunnelEndpointData}; +use talpid_types::tunnel::BlockReason; use super::{ - AfterDisconnect, ConnectedState, ConnectedStateBootstrap, DisconnectedState, - DisconnectingState, EventConsequence, Result, ResultExt, SharedTunnelStateValues, - StateEntryResult, TunnelCommand, TunnelParameters, TunnelState, TunnelStateWrapper, + AfterDisconnect, BlockedState, ConnectedState, ConnectedStateBootstrap, DisconnectingState, + EventConsequence, Result, ResultExt, SharedTunnelStateValues, TunnelCommand, TunnelParameters, + TunnelState, TunnelStateTransition, TunnelStateWrapper, }; use logging; use security::{NetworkSecurity, SecurityPolicy}; @@ -39,12 +41,7 @@ pub struct ConnectingState { } impl ConnectingState { - fn new( - shared_values: &mut SharedTunnelStateValues, - parameters: TunnelParameters, - ) -> Result<Self> { - Self::set_security_policy(shared_values, parameters.endpoint, parameters.allow_lan)?; - + fn new(parameters: TunnelParameters) -> Result<Self> { let tunnel_endpoint = parameters.endpoint; let (tunnel_events, tunnel_close_event, close_handle) = Self::start_tunnel(¶meters)?; @@ -141,7 +138,7 @@ impl ConnectingState { Ok(_) => debug!("Tunnel has finished without errors"), Err(error) => { let chained_error = error.chain_err(|| "Tunnel has stopped unexpectedly"); - warn!("{}", chained_error); + warn!("{}", chained_error.display_chain()); } } @@ -205,13 +202,14 @@ impl ConnectingState { match Self::set_security_policy(shared_values, self.tunnel_endpoint, allow_lan) { Ok(()) => SameState(self), Err(error) => { - error!("{}", error.chain_err(|| "Failed to update security policy")); + error!("{}", error.display_chain()); + NewState(DisconnectingState::enter( shared_values, ( self.close_handle, self.tunnel_close_event, - AfterDisconnect::Nothing, + AfterDisconnect::Block(BlockReason::SetSecurityPolicyError), ), )) } @@ -269,17 +267,27 @@ impl TunnelState for ConnectingState { fn enter( shared_values: &mut SharedTunnelStateValues, parameters: Self::Bootstrap, - ) -> StateEntryResult { - Self::new(shared_values, parameters) - .map(TunnelStateWrapper::from) - .chain_err(|| "Failed to start tunnel") - .map_err(|error| { - ( - error, - DisconnectedState::enter(shared_values, ()) - .expect("Failed to transition to fallback disconnected state"), - ) - }) + ) -> (TunnelStateWrapper, TunnelStateTransition) { + if let Err(error) = + Self::set_security_policy(shared_values, parameters.endpoint, parameters.allow_lan) + { + error!("{}", error.display_chain()); + + return BlockedState::enter(shared_values, BlockReason::StartTunnelError); + } + + match Self::new(parameters) { + Ok(connecting_state) => ( + TunnelStateWrapper::from(connecting_state), + TunnelStateTransition::Connecting, + ), + Err(error) => { + let chained_error = error.chain_err(|| "Failed to start tunnel"); + error!("{}", chained_error.display_chain()); + + BlockedState::enter(shared_values, BlockReason::StartTunnelError) + } + } } fn handle_event( diff --git a/talpid-core/src/tunnel_state_machine/disconnected_state.rs b/talpid-core/src/tunnel_state_machine/disconnected_state.rs index 694e280ec6..f92630606c 100644 --- a/talpid-core/src/tunnel_state_machine/disconnected_state.rs +++ b/talpid-core/src/tunnel_state_machine/disconnected_state.rs @@ -3,8 +3,8 @@ use futures::sync::mpsc; use futures::Stream; use super::{ - ConnectingState, Error, EventConsequence, SharedTunnelStateValues, StateEntryResult, - TunnelCommand, TunnelState, TunnelStateWrapper, + ConnectingState, Error, EventConsequence, SharedTunnelStateValues, TunnelCommand, TunnelState, + TunnelStateTransition, TunnelStateWrapper, }; use security::NetworkSecurity; @@ -24,10 +24,16 @@ impl DisconnectedState { impl TunnelState for DisconnectedState { type Bootstrap = (); - fn enter(shared_values: &mut SharedTunnelStateValues, _: Self::Bootstrap) -> StateEntryResult { + fn enter( + shared_values: &mut SharedTunnelStateValues, + _: Self::Bootstrap, + ) -> (TunnelStateWrapper, TunnelStateTransition) { Self::reset_security_policy(shared_values); - Ok(TunnelStateWrapper::from(DisconnectedState)) + ( + TunnelStateWrapper::from(DisconnectedState), + TunnelStateTransition::Disconnected, + ) } fn handle_event( diff --git a/talpid-core/src/tunnel_state_machine/disconnecting_state.rs b/talpid-core/src/tunnel_state_machine/disconnecting_state.rs index 0643fef13c..6fbdacffe2 100644 --- a/talpid-core/src/tunnel_state_machine/disconnecting_state.rs +++ b/talpid-core/src/tunnel_state_machine/disconnecting_state.rs @@ -4,9 +4,12 @@ use error_chain::ChainedError; use futures::sync::{mpsc, oneshot}; use futures::{Async, Future, Stream}; +use talpid_types::tunnel::BlockReason; + use super::{ - ConnectingState, DisconnectedState, EventConsequence, ResultExt, SharedTunnelStateValues, - StateEntryResult, TunnelCommand, TunnelParameters, TunnelState, TunnelStateWrapper, + BlockedState, ConnectingState, DisconnectedState, EventConsequence, ResultExt, + SharedTunnelStateValues, TunnelCommand, TunnelParameters, TunnelState, TunnelStateTransition, + TunnelStateWrapper, }; use tunnel::CloseHandle; @@ -32,6 +35,11 @@ impl DisconnectingState { Ok(TunnelCommand::Connect(parameters)) => Reconnect(parameters), _ => Nothing, }, + AfterDisconnect::Block(reason) => match event { + Ok(TunnelCommand::Connect(parameters)) => Reconnect(parameters), + Ok(TunnelCommand::Disconnect) => Nothing, + _ => AfterDisconnect::Block(reason), + }, AfterDisconnect::Reconnect(mut tunnel_parameters) => match event { Ok(TunnelCommand::Connect(parameters)) => Reconnect(parameters), Ok(TunnelCommand::AllowLan(allow_lan)) => { @@ -57,9 +65,13 @@ impl DisconnectingState { } } - fn after_disconnect(self, shared_values: &mut SharedTunnelStateValues) -> StateEntryResult { + fn after_disconnect( + self, + shared_values: &mut SharedTunnelStateValues, + ) -> (TunnelStateWrapper, TunnelStateTransition) { 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) } @@ -73,7 +85,7 @@ impl TunnelState for DisconnectingState { fn enter( _: &mut SharedTunnelStateValues, (close_handle, exited, after_disconnect): Self::Bootstrap, - ) -> StateEntryResult { + ) -> (TunnelStateWrapper, TunnelStateTransition) { thread::spawn(move || { let close_result = close_handle .close() @@ -84,10 +96,13 @@ impl TunnelState for DisconnectingState { } }); - Ok(TunnelStateWrapper::from(DisconnectingState { - exited, - after_disconnect, - })) + ( + TunnelStateWrapper::from(DisconnectingState { + exited, + after_disconnect, + }), + TunnelStateTransition::Disconnecting, + ) } fn handle_event( @@ -103,5 +118,6 @@ impl TunnelState for DisconnectingState { /// Which state should be transitioned to after disconnection is complete. pub enum AfterDisconnect { Nothing, + Block(BlockReason), Reconnect(TunnelParameters), } diff --git a/talpid-core/src/tunnel_state_machine/mod.rs b/talpid-core/src/tunnel_state_machine/mod.rs index 01a168e2b6..20f1529b01 100644 --- a/talpid-core/src/tunnel_state_machine/mod.rs +++ b/talpid-core/src/tunnel_state_machine/mod.rs @@ -1,6 +1,7 @@ #[macro_use] mod macros; +mod blocked_state; mod connected_state; mod connecting_state; mod disconnected_state; @@ -19,6 +20,7 @@ use tokio_core::reactor::Core; use talpid_types::net::{TunnelEndpoint, TunnelOptions}; use talpid_types::tunnel::TunnelStateTransition; +use self::blocked_state::BlockedState; use self::connected_state::{ConnectedState, ConnectedStateBootstrap}; use self::connecting_state::ConnectingState; use self::disconnected_state::DisconnectedState; @@ -148,8 +150,7 @@ impl TunnelStateMachine { NetworkSecurityImpl::new(cache_dir).chain_err(|| ErrorKind::NetworkSecurityError)?; let mut shared_values = SharedTunnelStateValues { security }; - let initial_state = TunnelStateWrapper::new(&mut shared_values, ()) - .expect("Failed to create initial tunnel state"); + let initial_state = TunnelStateWrapper::new(&mut shared_values, ()); Ok(TunnelStateMachine { current_state: Some(initial_state), @@ -199,8 +200,7 @@ impl<T: TunnelState> From<EventConsequence<T>> for TunnelStateMachineAction { use self::TunnelStateMachineAction::*; match event_consequence { - NewState(Ok(state_wrapper)) | NewState(Err((_, state_wrapper))) => { - let transition = state_wrapper.info(); + NewState((state_wrapper, transition)) => { Notify(Some(state_wrapper), Ok(Async::Ready(Some(transition)))) } SameState(state) => Repeat(state.into()), @@ -218,7 +218,7 @@ struct SharedTunnelStateValues { /// Asynchronous result of an attempt to progress a state. enum EventConsequence<T: TunnelState> { /// Transition to a new state. - NewState(StateEntryResult), + NewState((TunnelStateWrapper, TunnelStateTransition)), /// An event was received, but it was ignored by the state so no transition is performed. SameState(T), /// No events were received, the event loop should block until one becomes available. @@ -247,11 +247,6 @@ where } } -/// Result of entering a `T: TunnelState`. -/// -/// It is either the state itself when successful, or an error paired with a fallback state. -type StateEntryResult = ::std::result::Result<TunnelStateWrapper, (Error, TunnelStateWrapper)>; - /// Trait that contains the method all states should implement to handle an event and advance the /// state machine. trait TunnelState: Into<TunnelStateWrapper> + Sized { @@ -265,7 +260,7 @@ trait TunnelState: Into<TunnelStateWrapper> + Sized { fn enter( shared_values: &mut SharedTunnelStateValues, bootstrap: Self::Bootstrap, - ) -> StateEntryResult; + ) -> (TunnelStateWrapper, TunnelStateTransition); /// Main state function. /// @@ -293,14 +288,17 @@ enum TunnelStateWrapper { Connecting(ConnectingState), Connected(ConnectedState), Disconnecting(DisconnectingState), + Blocked(BlockedState), } impl TunnelStateWrapper { fn new( shared_values: &mut SharedTunnelStateValues, bootstrap: <DisconnectedState as TunnelState>::Bootstrap, - ) -> StateEntryResult { - DisconnectedState::enter(shared_values, bootstrap) + ) -> TunnelStateWrapper { + let (new_state, _transition) = DisconnectedState::enter(shared_values, bootstrap); + + new_state } fn handle_event( @@ -326,16 +324,7 @@ impl TunnelStateWrapper { Connecting, Connected, Disconnecting, - } - } - - /// Returns information describing the state. - fn info(&self) -> TunnelStateTransition { - match *self { - TunnelStateWrapper::Disconnected(_) => TunnelStateTransition::Disconnected, - TunnelStateWrapper::Connecting(_) => TunnelStateTransition::Connecting, - TunnelStateWrapper::Connected(_) => TunnelStateTransition::Connected, - TunnelStateWrapper::Disconnecting(_) => TunnelStateTransition::Disconnecting, + Blocked, } } } @@ -354,6 +343,7 @@ impl_from_for_tunnel_state!(Disconnected(DisconnectedState)); impl_from_for_tunnel_state!(Connecting(ConnectingState)); impl_from_for_tunnel_state!(Connected(ConnectedState)); impl_from_for_tunnel_state!(Disconnecting(DisconnectingState)); +impl_from_for_tunnel_state!(Blocked(BlockedState)); impl Debug for TunnelStateWrapper { fn fmt(&self, formatter: &mut Formatter) -> FmtResult { @@ -364,6 +354,7 @@ impl Debug for TunnelStateWrapper { Connecting(_) => write!(formatter, "TunnelStateWrapper::Connecting(_)"), Connected(_) => write!(formatter, "TunnelStateWrapper::Connected(_)"), Disconnecting(_) => write!(formatter, "TunnelStateWrapper::Disconnecting(_)"), + Blocked(_) => write!(formatter, "TunnelStateWrapper::Blocked(_)"), } } } diff --git a/talpid-types/src/tunnel.rs b/talpid-types/src/tunnel.rs index 4727964fe6..e5454988c5 100644 --- a/talpid-types/src/tunnel.rs +++ b/talpid-types/src/tunnel.rs @@ -1,6 +1,9 @@ +use std::fmt; + /// Event resulting from a transition to a new tunnel state. #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "lowercase")] +#[serde(rename_all = "snake_case")] +#[serde(tag = "state", content = "details")] pub enum TunnelStateTransition { /// No connection is established and network is unsecured. Disconnected, @@ -10,4 +13,27 @@ pub enum TunnelStateTransition { Connected, /// Disconnecting tunnel. Disconnecting, + /// Tunnel is disconnected but secured by blocking all connections. + Blocked(BlockReason), +} + +/// Reason for entering the blocked state. +#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum BlockReason { + /// Failed to set security policy + SetSecurityPolicyError, + /// Failed to start connection to remote server + StartTunnelError, +} + +impl fmt::Display for BlockReason { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + let description = match *self { + BlockReason::SetSecurityPolicyError => "Failed to set security policy", + BlockReason::StartTunnelError => "Failed to start connection to remote server", + }; + + write!(formatter, "{}", description) + } } |
