summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJanito Vaqueiro Ferreira Filho <janito@mullvad.net>2018-09-03 08:09:30 -0300
committerJanito Vaqueiro Ferreira Filho <janito@mullvad.net>2018-09-03 08:09:30 -0300
commit9e440721700a7ab6a3d0d5c81c4d42949ea7108c (patch)
tree159c1ac066c679e3d9204c79b15e118426678e4f
parenta23afed45caa953cb9765991a4692b66b3a39329 (diff)
parent13e272fe2c22a5b026aa4dc8cae80e9a68df4dc2 (diff)
downloadmullvadvpn-9e440721700a7ab6a3d0d5c81c4d42949ea7108c.tar.xz
mullvadvpn-9e440721700a7ab6a3d0d5c81c4d42949ea7108c.zip
Merge branch 'blocked-state'
-rw-r--r--CHANGELOG.md2
-rw-r--r--gui/packages/desktop/src/renderer/app.js49
-rw-r--r--gui/packages/desktop/src/renderer/components/Connect.js13
-rw-r--r--gui/packages/desktop/src/renderer/errors.js17
-rw-r--r--gui/packages/desktop/src/renderer/lib/daemon-rpc.js48
-rw-r--r--gui/packages/desktop/src/renderer/redux/connection/actions.js14
-rw-r--r--gui/packages/desktop/src/renderer/redux/connection/reducers.js13
-rw-r--r--mullvad-cli/src/cmds/status.rs1
-rw-r--r--mullvad-daemon/src/main.rs10
-rw-r--r--talpid-core/src/tunnel_state_machine/blocked_state.rs44
-rw-r--r--talpid-core/src/tunnel_state_machine/connected_state.rs27
-rw-r--r--talpid-core/src/tunnel_state_machine/connecting_state.rs54
-rw-r--r--talpid-core/src/tunnel_state_machine/disconnected_state.rs14
-rw-r--r--talpid-core/src/tunnel_state_machine/disconnecting_state.rs32
-rw-r--r--talpid-core/src/tunnel_state_machine/mod.rs37
-rw-r--r--talpid-types/src/tunnel.rs28
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(&parameters)?;
@@ -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)
+ }
}