diff options
| -rw-r--r-- | CHANGELOG.md | 3 | ||||
| -rw-r--r-- | gui/src/main/daemon-rpc.ts | 25 | ||||
| -rw-r--r-- | gui/src/main/index.ts | 2 | ||||
| -rw-r--r-- | gui/src/renderer/app.tsx | 2 | ||||
| -rw-r--r-- | gui/src/renderer/components/Connect.tsx | 4 | ||||
| -rw-r--r-- | gui/src/renderer/components/TunnelControl.tsx | 2 | ||||
| -rw-r--r-- | gui/src/shared/daemon-rpc-types.ts | 14 | ||||
| -rw-r--r-- | gui/src/shared/notifications/error.ts | 130 | ||||
| -rw-r--r-- | mullvad-cli/src/cmds/status.rs | 7 | ||||
| -rw-r--r-- | talpid-core/src/firewall/windows.rs | 117 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/connected_state.rs | 45 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/connecting_state.rs | 49 | ||||
| -rw-r--r-- | talpid-core/src/tunnel_state_machine/error_state.rs | 37 | ||||
| -rw-r--r-- | talpid-types/src/tunnel.rs | 58 | ||||
| -rw-r--r-- | windows/winfw/src/winfw/winfw.cpp | 79 | ||||
| -rw-r--r-- | windows/winfw/src/winfw/winfw.h | 15 |
16 files changed, 400 insertions, 189 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 23a588d85b..51da6a86d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,8 @@ Line wrap the file at 100 chars. Th - Make uninstaller on desktop platforms attempt to remove WireGuard keys from accounts. - Make important notifications not timeout on macOS and remain in the notification list on Linux. - Add exponential backoff to relay list downloader. +- Display the original block reason in the non-blocking error state, and why applying the blocking + policy failed. #### Android - Show a system notification when the account time will soon run out. @@ -46,6 +48,7 @@ Line wrap the file at 100 chars. Th #### Windows - Upgrade Wintun from 0.7 to 0.8.1. +- Display causes of firewall errors in the GUI. #### Linux - Allow users to specify `net_cls` controller mountpoint via `TALPID_NET_CLS_MOUNT_DIR`. The diff --git a/gui/src/main/daemon-rpc.ts b/gui/src/main/daemon-rpc.ts index 656efd760b..6071ce515f 100644 --- a/gui/src/main/daemon-rpc.ts +++ b/gui/src/main/daemon-rpc.ts @@ -259,12 +259,21 @@ const tunnelStateSchema = oneOf( object({ state: enumeration('error'), details: object({ - is_blocking: boolean, + block_failure: maybe( + object({ + reason: enumeration('generic', 'locked'), + details: maybe( + object({ + name: string, + pid: number, + }), + ), + }), + ), cause: oneOf( object({ reason: enumeration( 'ipv6_unavailable', - 'set_firewall_policy_error', 'set_dns_error', 'start_tunnel_error', 'is_offline', @@ -272,6 +281,18 @@ const tunnelStateSchema = oneOf( ), }), object({ + reason: enumeration('set_firewall_policy_error'), + details: object({ + reason: enumeration('generic', 'locked'), + details: maybe( + object({ + name: string, + pid: number, + }), + ), + }), + }), + object({ reason: enumeration('auth_failed'), details: maybe(string), }), diff --git a/gui/src/main/index.ts b/gui/src/main/index.ts index 18dfc67d29..f375de7eca 100644 --- a/gui/src/main/index.ts +++ b/gui/src/main/index.ts @@ -863,7 +863,7 @@ class ApplicationMain { return 'securing'; case 'error': - if (tunnelState.details.isBlocking) { + if (!tunnelState.details.blockFailure) { return 'securing'; } else { return 'unsecured'; diff --git a/gui/src/renderer/app.tsx b/gui/src/renderer/app.tsx index 0dc076378a..c09ca39049 100644 --- a/gui/src/renderer/app.tsx +++ b/gui/src/renderer/app.tsx @@ -634,7 +634,7 @@ export default class AppRenderer { break; case 'error': - actions.updateBlockState(tunnelState.details.isBlocking); + actions.updateBlockState(!tunnelState.details.blockFailure); break; } } diff --git a/gui/src/renderer/components/Connect.tsx b/gui/src/renderer/components/Connect.tsx index 0db4a40141..184465bd7d 100644 --- a/gui/src/renderer/components/Connect.tsx +++ b/gui/src/renderer/components/Connect.tsx @@ -169,7 +169,7 @@ export default class Connect extends React.Component<IProps, IState> { case 'connected': return HeaderBarStyle.success; case 'error': - return status.details.isBlocking ? HeaderBarStyle.success : HeaderBarStyle.error; + return !status.details.blockFailure ? HeaderBarStyle.success : HeaderBarStyle.error; case 'disconnecting': switch (status.details) { case 'block': @@ -223,7 +223,7 @@ export default class Connect extends React.Component<IProps, IState> { case 'connected': return MarkerStyle.secure; case 'error': - return status.details.isBlocking ? MarkerStyle.secure : MarkerStyle.unsecure; + return !status.details.blockFailure ? MarkerStyle.secure : MarkerStyle.unsecure; case 'disconnected': return MarkerStyle.unsecure; case 'disconnecting': diff --git a/gui/src/renderer/components/TunnelControl.tsx b/gui/src/renderer/components/TunnelControl.tsx index 04236750ee..611c24d78e 100644 --- a/gui/src/renderer/components/TunnelControl.tsx +++ b/gui/src/renderer/components/TunnelControl.tsx @@ -176,7 +176,7 @@ export default class TunnelControl extends React.Component<ITunnelControlProps> case 'error': if ( this.props.tunnelState.state === 'error' && - !this.props.tunnelState.details.isBlocking + this.props.tunnelState.details.blockFailure ) { return ( <Wrapper> diff --git a/gui/src/shared/daemon-rpc-types.ts b/gui/src/shared/daemon-rpc-types.ts index bec0275e9b..1f1779a4b0 100644 --- a/gui/src/shared/daemon-rpc-types.ts +++ b/gui/src/shared/daemon-rpc-types.ts @@ -15,6 +15,16 @@ export interface ILocation { bridgeHostname?: string; } +export type FirewallPolicyError = + | { reason: 'generic' } + | { + reason: 'locked'; + details?: { + name: string; + pid: number; + }; + }; + export type TunnelParameterError = | 'no_matching_relay' | 'no_matching_bridge_relay' @@ -25,12 +35,12 @@ export type ErrorStateCause = | { reason: | 'ipv6_unavailable' - | 'set_firewall_policy_error' | 'set_dns_error' | 'start_tunnel_error' | 'is_offline' | 'tap_adapter_problem'; } + | { reason: 'set_firewall_policy_error'; details: FirewallPolicyError } | { reason: 'tunnel_parameter_error'; details: TunnelParameterError } | { reason: 'auth_failed'; details?: string }; @@ -102,7 +112,7 @@ export type TunnelState = | { state: 'error'; details: IErrorState }; export interface IErrorState { - isBlocking: boolean; + blockFailure?: FirewallPolicyError; cause: ErrorStateCause; } diff --git a/gui/src/shared/notifications/error.ts b/gui/src/shared/notifications/error.ts index 1a8499739e..7b438edb54 100644 --- a/gui/src/shared/notifications/error.ts +++ b/gui/src/shared/notifications/error.ts @@ -1,7 +1,13 @@ import { sprintf } from 'sprintf-js'; import { hasExpired } from '../account-expiry'; import { AuthFailureKind, parseAuthFailure } from '../auth-failure'; -import { IErrorState, TunnelState, TunnelParameterError } from '../daemon-rpc-types'; +import { + IErrorState, + TunnelState, + TunnelParameterError, + ErrorStateCause, + FirewallPolicyError, +} from '../daemon-rpc-types'; import { messages } from '../gettext'; import { InAppNotification, @@ -27,7 +33,7 @@ export class ErrorNotificationProvider this.context.tunnelState, this.context.accountExpiry, ), - critical: !this.context.tunnelState.details.isBlocking, + critical: !!this.context.tunnelState.details.blockFailure, } : undefined; } @@ -36,7 +42,7 @@ export class ErrorNotificationProvider return this.context.tunnelState.state === 'error' ? { indicator: 'error', - title: this.context.tunnelState.details.isBlocking + title: !this.context.tunnelState.details.blockFailure ? messages.pgettext('in-app-notifications', 'BLOCKING INTERNET') : messages.pgettext('in-app-notifications', 'YOU MIGHT BE LEAKING NETWORK TRAFFIC'), subtitle: getInAppNotificationSubtitle(this.context.tunnelState), @@ -49,7 +55,7 @@ function getSystemNotificationMessage( tunnelState: { state: 'error'; details: IErrorState }, accountExpiry?: string, ) { - if (!tunnelState.details.isBlocking) { + if (tunnelState.details.blockFailure) { return messages.pgettext('notifications', 'Critical error (your attention is required)'); } else if ( (tunnelState.details.cause.reason === 'auth_failed' && @@ -75,57 +81,85 @@ function getSystemNotificationMessage( } function getInAppNotificationSubtitle(tunnelState: { state: 'error'; details: IErrorState }) { - if (!tunnelState.details.isBlocking) { - return messages.pgettext( + let blockFailureMessage = null; + if (tunnelState.details.blockFailure) { + const extraMessage = getPolicyMessage(tunnelState.details.blockFailure); + blockFailureMessage = `${messages.pgettext( 'in-app-notifications', - 'Failed to block all network traffic. Please troubleshoot or report the problem to us.', - ); - } else { - const blockReason = tunnelState.details.cause; - switch (blockReason.reason) { - case 'auth_failed': - return parseAuthFailure(blockReason.details).message; - case 'ipv6_unavailable': - return messages.pgettext( - 'in-app-notifications', - 'Could not configure IPv6, please enable it on your system or disable it in the app', - ); - case 'set_firewall_policy_error': { - let extraMessage = null; - switch (process.platform) { - case 'linux': - extraMessage = messages.pgettext('in-app-notifications', 'Your kernel may be outdated'); - break; - case 'win32': + 'Failed to block all network traffic', + )}${extraMessage ? '. ' + extraMessage : ''}`; + } + + const blockMessage = getBlockMessage(tunnelState.details.cause); + return blockFailureMessage + ? `${blockFailureMessage}. ${messages.pgettext( + 'in-app-notifications', + 'Original block reason', + )}: ${blockMessage}` + : blockMessage; +} + +function getBlockMessage(blockReason: ErrorStateCause): string { + switch (blockReason.reason) { + case 'auth_failed': + return parseAuthFailure(blockReason.details).message; + case 'ipv6_unavailable': + return messages.pgettext( + 'in-app-notifications', + 'Could not configure IPv6, please enable it on your system or disable it in the app', + ); + case 'set_firewall_policy_error': { + const extraMessage = getPolicyMessage(blockReason.details); + return `${messages.pgettext( + 'in-app-notifications', + 'Failed to apply firewall rules. The device might currently be unsecured', + )}${extraMessage ? '. ' + extraMessage : ''}`; + } + case 'set_dns_error': + return messages.pgettext('in-app-notifications', 'Failed to set system DNS server'); + case 'start_tunnel_error': + return messages.pgettext('in-app-notifications', 'Failed to start tunnel connection'); + case 'tunnel_parameter_error': + return getTunnelParameterMessage(blockReason.details); + case 'is_offline': + return messages.pgettext( + 'in-app-notifications', + 'This device is offline, no tunnels can be established', + ); + case 'tap_adapter_problem': + return messages.pgettext( + 'in-app-notifications', + "Unable to detect a working TAP adapter on this device. If you've disabled it, enable it again. Otherwise, please reinstall the app", + ); + } +} + +function getPolicyMessage(err: FirewallPolicyError): string | null { + let extraMessage = null; + switch (process.platform) { + case 'linux': + extraMessage = messages.pgettext('in-app-notifications', 'Your kernel may be outdated'); + break; + case 'win32': + switch (err.reason) { + case 'locked': + if (err.details) { + // TODO: Check if this message is ok + extraMessage = `${messages.pgettext( + 'in-app-notifications', + 'An application prevented the policy from being set', + )}: ${err.details.name}`; + } else { extraMessage = messages.pgettext( 'in-app-notifications', 'This might be caused by third party security software', ); - break; - } - return `${messages.pgettext( - 'in-app-notifications', - 'Failed to apply firewall rules. The device might currently be unsecured', - )}${extraMessage ? '. ' + extraMessage : ''}`; + } + break; } - case 'set_dns_error': - return messages.pgettext('in-app-notifications', 'Failed to set system DNS server'); - case 'start_tunnel_error': - return messages.pgettext('in-app-notifications', 'Failed to start tunnel connection'); - case 'tunnel_parameter_error': - return getTunnelParameterMessage(blockReason.details); - case 'is_offline': - return messages.pgettext( - 'in-app-notifications', - 'This device is offline, no tunnels can be established', - ); - case 'tap_adapter_problem': - return messages.pgettext( - 'in-app-notifications', - "Unable to detect a working TAP adapter on this device. If you've disabled it, enable it again. Otherwise, please reinstall the app", - ); - } + break; } + return extraMessage; } function getTunnelParameterMessage(err: TunnelParameterError): string { diff --git a/mullvad-cli/src/cmds/status.rs b/mullvad-cli/src/cmds/status.rs index 10834b60b3..ffa834134e 100644 --- a/mullvad-cli/src/cmds/status.rs +++ b/mullvad-cli/src/cmds/status.rs @@ -120,8 +120,11 @@ fn print_blocked_reason(reason: &ErrorStateCause) { println!("Blocked: {}", AuthFailed::from(auth_failure_str)); } #[cfg(target_os = "linux")] - ErrorStateCause::SetFirewallPolicyError => { - println!("Blocked: {}", ErrorStateCause::SetFirewallPolicyError); + ErrorStateCause::SetFirewallPolicyError(error) => { + println!( + "Blocked: {}", + ErrorStateCause::SetFirewallPolicyError(error.clone()) + ); println!("Your kernel might be terribly out of date or missing nftables"); } other => println!("Blocked: {}", other), diff --git a/talpid-core/src/firewall/windows.rs b/talpid-core/src/firewall/windows.rs index 4cd689cd44..14d2854330 100644 --- a/talpid-core/src/firewall/windows.rs +++ b/talpid-core/src/firewall/windows.rs @@ -7,12 +7,13 @@ use super::{FirewallArguments, FirewallPolicy, FirewallT}; use crate::winnet; use log::{debug, error, trace}; use std::os::windows::ffi::OsStrExt; -use talpid_types::net::Endpoint; +use talpid_types::{net::Endpoint, tunnel::FirewallPolicyError}; use widestring::WideCString; /// Errors that can happen when configuring the Windows firewall. #[derive(err_derive::Error, Debug)] +#[error(no_from)] pub enum Error { /// Failure to initialize windows firewall module #[error(display = "Failed to initialize windows firewall module")] @@ -24,19 +25,19 @@ pub enum Error { /// Failure to apply a firewall _connecting_ policy #[error(display = "Failed to apply connecting firewall policy")] - ApplyingConnectingPolicy, + ApplyingConnectingPolicy(#[error(source)] FirewallPolicyError), /// Failure to apply a firewall _connected_ policy #[error(display = "Failed to apply connected firewall policy")] - ApplyingConnectedPolicy, + ApplyingConnectedPolicy(#[error(source)] FirewallPolicyError), /// Failure to apply firewall _blocked_ policy #[error(display = "Failed to apply blocked firewall policy")] - ApplyingBlockedPolicy, + ApplyingBlockedPolicy(#[error(source)] FirewallPolicyError), /// Failure to reset firewall policies #[error(display = "Failed to reset firewall policies")] - ResettingPolicy, + ResettingPolicy(#[error(source)] FirewallPolicyError), /// Failure to set TAP adapter metric #[error(display = "Unable to set TAP adapter metric")] @@ -111,7 +112,7 @@ impl FirewallT for Firewall { } fn reset_policy(&mut self) -> Result<(), Self::Error> { - unsafe { WinFw_Reset().into_result() }?; + unsafe { WinFw_Reset().into_result().map_err(Error::ResettingPolicy) }?; Ok(()) } } @@ -141,8 +142,6 @@ impl Firewall { ) -> Result<(), Error> { trace!("Applying 'connecting' firewall policy"); let ip_str = Self::widestring_ip(endpoint.address.ip()); - - // ip_str has to outlive winfw_relay let winfw_relay = WinFwRelay { ip: ip_str.as_ptr(), port: endpoint.address.port(), @@ -152,31 +151,23 @@ impl Firewall { let mut relay_client: Vec<u16> = relay_client.as_os_str().encode_wide().collect(); relay_client.push(0u16); - if pingable_hosts.is_empty() { - unsafe { - return WinFw_ApplyPolicyConnecting( - winfw_settings, - &winfw_relay, - relay_client.as_ptr(), - ptr::null(), - ) - .into_result(); - } - } - - let pingable_addresses = pingable_hosts - .iter() - .map(|ip| Self::widestring_ip(*ip)) - .collect::<Vec<_>>(); - let pingable_address_ptrs = pingable_addresses - .iter() - .map(|ip| ip.as_ptr()) - .collect::<Vec<_>>(); + let pingable_hosts = if !pingable_hosts.is_empty() { + let pingable_addresses = pingable_hosts + .iter() + .map(|ip| Self::widestring_ip(*ip)) + .collect::<Vec<_>>(); + let pingable_address_ptrs = pingable_addresses + .iter() + .map(|ip| ip.as_ptr()) + .collect::<Vec<_>>(); - let pingable_hosts = WinFwPingableHosts { - interfaceAlias: ptr::null(), - addresses: pingable_address_ptrs.as_ptr(), - num_addresses: pingable_addresses.len(), + Some(WinFwPingableHosts { + interfaceAlias: ptr::null(), + addresses: pingable_address_ptrs.as_ptr(), + num_addresses: pingable_addresses.len(), + }) + } else { + None }; unsafe { @@ -184,9 +175,10 @@ impl Firewall { winfw_settings, &winfw_relay, relay_client.as_ptr(), - &pingable_hosts, + pingable_hosts.as_ptr(), ) .into_result() + .map_err(Error::ApplyingConnectingPolicy) } } @@ -246,15 +238,32 @@ impl Firewall { v6_gateway_ptr, ) .into_result() + .map_err(Error::ApplyingConnectedPolicy) } } fn set_blocked_state(&mut self, winfw_settings: &WinFwSettings) -> Result<(), Error> { trace!("Applying 'blocked' firewall policy"); - unsafe { WinFw_ApplyPolicyBlocked(winfw_settings).into_result() } + unsafe { + WinFw_ApplyPolicyBlocked(winfw_settings) + .into_result() + .map_err(Error::ApplyingBlockedPolicy) + } } } +trait NullablePointer<T> { + fn as_ptr(&self) -> *const T; +} + +impl<T> NullablePointer<T> for Option<T> { + fn as_ptr(&self) -> *const T { + match self { + Some(ref value) => value, + None => ptr::null(), + } + } +} #[allow(non_snake_case)] mod winfw { @@ -319,10 +328,34 @@ mod winfw { ffi_error!(InitializationResult, Error::Initialization); ffi_error!(DeinitializationResult, Error::Deinitialization); - ffi_error!(ApplyConnectingResult, Error::ApplyingConnectingPolicy); - ffi_error!(ApplyConnectedResult, Error::ApplyingConnectedPolicy); - ffi_error!(ApplyBlockedResult, Error::ApplyingBlockedPolicy); - ffi_error!(ResettingPolicyResult, Error::ResettingPolicy); + + #[derive(Debug)] + #[allow(dead_code)] + #[repr(u32)] + pub enum WinFwPolicyStatus { + Success = 0, + GeneralFailure = 1, + LockTimeout = 2, + } + + impl WinFwPolicyStatus { + pub fn into_result(self) -> Result<(), super::FirewallPolicyError> { + match self { + WinFwPolicyStatus::Success => Ok(()), + WinFwPolicyStatus::GeneralFailure => Err(super::FirewallPolicyError::Generic), + WinFwPolicyStatus::LockTimeout => { + // TODO: Obtain application name and string from WinFw + Err(super::FirewallPolicyError::Locked(None)) + } + } + } + } + + impl Into<Result<(), super::FirewallPolicyError>> for WinFwPolicyStatus { + fn into(self) -> Result<(), super::FirewallPolicyError> { + self.into_result() + } + } extern "system" { #[link_name = "WinFw_Initialize"] @@ -349,7 +382,7 @@ mod winfw { relay: &WinFwRelay, relayClient: *const libc::wchar_t, pingable_hosts: *const WinFwPingableHosts, - ) -> ApplyConnectingResult; + ) -> WinFwPolicyStatus; #[link_name = "WinFw_ApplyPolicyConnected"] pub fn WinFw_ApplyPolicyConnected( @@ -359,12 +392,12 @@ mod winfw { tunnelIfaceAlias: *const libc::wchar_t, v4Gateway: *const libc::wchar_t, v6Gateway: *const libc::wchar_t, - ) -> ApplyConnectedResult; + ) -> WinFwPolicyStatus; #[link_name = "WinFw_ApplyPolicyBlocked"] - pub fn WinFw_ApplyPolicyBlocked(settings: &WinFwSettings) -> ApplyBlockedResult; + pub fn WinFw_ApplyPolicyBlocked(settings: &WinFwSettings) -> WinFwPolicyStatus; #[link_name = "WinFw_Reset"] - pub fn WinFw_Reset() -> ResettingPolicyResult; + pub fn WinFw_Reset() -> WinFwPolicyStatus; } } diff --git a/talpid-core/src/tunnel_state_machine/connected_state.rs b/talpid-core/src/tunnel_state_machine/connected_state.rs index 7ddc9bedeb..43595a4e79 100644 --- a/talpid-core/src/tunnel_state_machine/connected_state.rs +++ b/talpid-core/src/tunnel_state_machine/connected_state.rs @@ -12,7 +12,7 @@ use futures01::{ }; use talpid_types::{ net::{Endpoint, TunnelParameters}, - tunnel::ErrorStateCause, + tunnel::{ErrorStateCause, FirewallPolicyError}, BoxedError, ErrorExt, }; @@ -51,7 +51,7 @@ impl ConnectedState { fn set_firewall_policy( &self, shared_values: &mut SharedTunnelStateValues, - ) -> Result<(), crate::firewall::Error> { + ) -> Result<(), FirewallPolicyError> { // If a proxy is specified we need to pass it on as the peer endpoint. let peer_endpoint = self.get_endpoint_from_params(); @@ -65,7 +65,24 @@ impl ConnectedState { &self.tunnel_parameters, ), }; - shared_values.firewall.apply_policy(policy) + shared_values + .firewall + .apply_policy(policy) + .map_err(|error| { + log::error!( + "{}", + error.display_chain_with_msg( + "Failed to apply firewall policy for connected state" + ) + ); + #[cfg(windows)] + match error { + crate::firewall::Error::ApplyingConnectedPolicy(policy_error) => policy_error, + _ => FirewallPolicyError::Generic, + } + #[cfg(not(windows))] + FirewallPolicyError::Generic + }) } fn get_endpoint_from_params(&self) -> Endpoint { @@ -140,18 +157,10 @@ impl ConnectedState { } else { match self.set_firewall_policy(shared_values) { Ok(()) => SameState(self), - Err(error) => { - log::error!( - "{}", - error.display_chain_with_msg( - "Failed to apply firewall policy for connected state" - ) - ); - self.disconnect( - shared_values, - AfterDisconnect::Block(ErrorStateCause::SetFirewallPolicyError), - ) - } + Err(error) => self.disconnect( + shared_values, + AfterDisconnect::Block(ErrorStateCause::SetFirewallPolicyError(error)), + ), } } } @@ -237,16 +246,12 @@ impl TunnelState for ConnectedState { let tunnel_endpoint = connected_state.tunnel_parameters.get_tunnel_endpoint(); if let Err(error) = connected_state.set_firewall_policy(shared_values) { - log::error!( - "{}", - error.display_chain_with_msg("Failed to apply firewall policy for connected state") - ); DisconnectingState::enter( shared_values, ( connected_state.close_handle, connected_state.tunnel_close_event, - AfterDisconnect::Block(ErrorStateCause::SetFirewallPolicyError), + AfterDisconnect::Block(ErrorStateCause::SetFirewallPolicyError(error)), ), ) } else if let Err(error) = connected_state.set_dns(shared_values) { diff --git a/talpid-core/src/tunnel_state_machine/connecting_state.rs b/talpid-core/src/tunnel_state_machine/connecting_state.rs index bb0cd3db19..d206b34a23 100644 --- a/talpid-core/src/tunnel_state_machine/connecting_state.rs +++ b/talpid-core/src/tunnel_state_machine/connecting_state.rs @@ -23,7 +23,7 @@ use std::{ }; use talpid_types::{ net::{openvpn, TunnelParameters}, - tunnel::ErrorStateCause, + tunnel::{ErrorStateCause, FirewallPolicyError}, ErrorExt, }; @@ -47,7 +47,7 @@ impl ConnectingState { fn set_firewall_policy( shared_values: &mut SharedTunnelStateValues, params: &TunnelParameters, - ) -> Result<(), crate::firewall::Error> { + ) -> Result<(), FirewallPolicyError> { let proxy = &get_openvpn_proxy_settings(¶ms); let endpoint = params.get_tunnel_endpoint().endpoint; @@ -63,7 +63,22 @@ impl ConnectingState { #[cfg(windows)] relay_client: TunnelMonitor::get_relay_client(&shared_values.resource_dir, ¶ms), }; - shared_values.firewall.apply_policy(policy) + shared_values + .firewall + .apply_policy(policy) + .map_err(|error| { + error!( + "{}", + error.display_chain_with_msg( + "Failed to apply firewall policy for connecting state" + ) + ); + match error { + #[cfg(windows)] + crate::firewall::Error::ApplyingConnectingPolicy(policy_error) => policy_error, + _ => FirewallPolicyError::Generic, + } + }) } fn start_tunnel( @@ -206,19 +221,10 @@ impl ConnectingState { } else { match Self::set_firewall_policy(shared_values, &self.tunnel_parameters) { Ok(()) => SameState(self), - Err(error) => { - error!( - "{}", - error.display_chain_with_msg( - "Failed to apply firewall policy for connecting state" - ) - ); - - self.disconnect( - shared_values, - AfterDisconnect::Block(ErrorStateCause::SetFirewallPolicyError), - ) - } + Err(error) => self.disconnect( + shared_values, + AfterDisconnect::Block(ErrorStateCause::SetFirewallPolicyError(error)), + ), } } } @@ -352,13 +358,10 @@ impl TunnelState for ConnectingState { } Ok(tunnel_parameters) => { if let Err(error) = Self::set_firewall_policy(shared_values, &tunnel_parameters) { - error!( - "{}", - error.display_chain_with_msg( - "Failed to apply firewall policy for connecting state" - ) - ); - ErrorState::enter(shared_values, ErrorStateCause::SetFirewallPolicyError) + ErrorState::enter( + shared_values, + ErrorStateCause::SetFirewallPolicyError(error), + ) } else { #[cfg(target_os = "linux")] if let Err(error) = shared_values.route_manager.enable_exclusions_routes() { diff --git a/talpid-core/src/tunnel_state_machine/error_state.rs b/talpid-core/src/tunnel_state_machine/error_state.rs index 692a69b3d3..48ffc39e04 100644 --- a/talpid-core/src/tunnel_state_machine/error_state.rs +++ b/talpid-core/src/tunnel_state_machine/error_state.rs @@ -5,7 +5,7 @@ use super::{ use crate::firewall::FirewallPolicy; use futures01::{sync::mpsc, Stream}; use talpid_types::{ - tunnel::{self as talpid_tunnel, ErrorStateCause}, + tunnel::{self as talpid_tunnel, ErrorStateCause, FirewallPolicyError}, ErrorExt, }; @@ -16,23 +16,29 @@ pub struct ErrorState { impl ErrorState { /// Returns true if firewall policy was applied successfully - fn set_firewall_policy(shared_values: &mut SharedTunnelStateValues) -> bool { + fn set_firewall_policy( + shared_values: &mut SharedTunnelStateValues, + ) -> Result<(), FirewallPolicyError> { let policy = FirewallPolicy::Blocked { allow_lan: shared_values.allow_lan, }; - match shared_values.firewall.apply_policy(policy) { - Ok(()) => true, - Err(error) => { + shared_values + .firewall + .apply_policy(policy) + .map_err(|error| { log::error!( "{}", error.display_chain_with_msg( "Failed to apply firewall policy for blocked state" ) ); - false - } - } + match error { + #[cfg(windows)] + crate::firewall::Error::ApplyingBlockedPolicy(policy_error) => policy_error, + _ => FirewallPolicyError::Generic, + } + }) } /// Returns true if a new tunnel device was successfully created. @@ -61,14 +67,21 @@ impl TunnelState for ErrorState { block_reason: Self::Bootstrap, ) -> (TunnelStateWrapper, TunnelStateTransition) { #[cfg(not(target_os = "android"))] - let is_blocking = Self::set_firewall_policy(shared_values); + let block_failure = Self::set_firewall_policy(shared_values).err(); #[cfg(target_os = "android")] - let is_blocking = Self::create_blocking_tun(shared_values); + let block_failure = if !Self::create_blocking_tun(shared_values) { + Some(FirewallPolicyError::Generic) + } else { + None + }; ( TunnelStateWrapper::from(ErrorState { block_reason: block_reason.clone(), }), - TunnelStateTransition::Error(talpid_tunnel::ErrorState::new(block_reason, is_blocking)), + TunnelStateTransition::Error(talpid_tunnel::ErrorState::new( + block_reason, + block_failure, + )), ) } @@ -84,7 +97,7 @@ impl TunnelState for ErrorState { if let Err(error_state_cause) = shared_values.set_allow_lan(allow_lan) { NewState(Self::enter(shared_values, error_state_cause)) } else { - Self::set_firewall_policy(shared_values); + let _ = Self::set_firewall_policy(shared_values); SameState(self) } } diff --git a/talpid-types/src/tunnel.rs b/talpid-types/src/tunnel.rs index dda152d3f1..00e5bcad50 100644 --- a/talpid-types/src/tunnel.rs +++ b/talpid-types/src/tunnel.rs @@ -41,20 +41,28 @@ pub enum ActionAfterDisconnect { pub struct ErrorState { /// Reason why the tunnel state machine ended up in the error state cause: ErrorStateCause, - /// Indicates whether the daemon is currently blocking all traffic. This _should_ always be - /// true - in the case it is not, the user should be notified that no traffic is being blocked. - /// A false value means there was a serious error and the intended security properties are not + /// Indicates whether the daemon is currently blocking all traffic. This _should_ always + /// succeed - in the case it does not, the user should be notified that no traffic is being + /// blocked. + /// An error value means there was a serious error and the intended security properties are not /// being upheld. - is_blocking: bool, + #[cfg_attr( + target_os = "android", + jnix(map = "|block_failure| block_failure.is_none()") + )] + block_failure: Option<FirewallPolicyError>, } impl ErrorState { - pub fn new(cause: ErrorStateCause, is_blocking: bool) -> Self { - Self { cause, is_blocking } + pub fn new(cause: ErrorStateCause, block_failure: Option<FirewallPolicyError>) -> Self { + Self { + cause, + block_failure, + } } pub fn is_blocking(&self) -> bool { - self.is_blocking + self.block_failure.is_none() } pub fn cause(&self) -> &ErrorStateCause { @@ -75,7 +83,7 @@ pub enum ErrorStateCause { /// Failed to configure IPv6 because it's disabled in the platform. Ipv6Unavailable, /// Failed to set firewall policy. - SetFirewallPolicyError, + SetFirewallPolicyError(FirewallPolicyError), /// Failed to set system DNS server. SetDnsError, /// Failed to start connection to remote server. @@ -111,6 +119,30 @@ pub enum ParameterGenerationError { CustomTunnelHostResultionError, } +/// Application that prevents setting the firewall policy. +#[cfg(windows)] +#[derive(Debug, Serialize, Clone, PartialEq, Deserialize)] +pub struct BlockingApplication { + pub name: String, + pub pid: u32, +} + +/// Errors that can occur when setting the firewall policy. +#[derive(err_derive::Error, Debug, Serialize, Clone, PartialEq, Deserialize)] +#[serde(rename_all = "snake_case")] +#[serde(tag = "reason", content = "details")] +#[cfg_attr(target_os = "android", derive(IntoJava))] +#[cfg_attr(target_os = "android", jnix(package = "net.mullvad.talpid.tunnel"))] +pub enum FirewallPolicyError { + /// General firewall failure + #[error(display = "Failed to set firewall policy")] + Generic, + /// An application prevented the firewall policy from being set + #[cfg(windows)] + #[error(display = "An application prevented the firewall policy from being set")] + Locked(Option<BlockingApplication>), +} + impl fmt::Display for ErrorStateCause { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use self::ErrorStateCause::*; @@ -126,7 +158,15 @@ impl fmt::Display for ErrorStateCause { ); } Ipv6Unavailable => "Failed to configure IPv6 because it's disabled in the platform", - SetFirewallPolicyError => "Failed to set firewall policy", + SetFirewallPolicyError(ref err) => { + return match err { + #[cfg(windows)] + FirewallPolicyError::Locked(Some(value)) => { + write!(f, "{}: {} (pid {})", err, value.name, value.pid) + } + _ => write!(f, "{}", err), + }; + } SetDnsError => "Failed to set system DNS server", StartTunnelError => "Failed to start connection to remote server", TunnelParameterError(ref err) => { diff --git a/windows/winfw/src/winfw/winfw.cpp b/windows/winfw/src/winfw/winfw.cpp index 3ce26376f7..30d55488b9 100644 --- a/windows/winfw/src/winfw/winfw.cpp +++ b/windows/winfw/src/winfw/winfw.cpp @@ -42,6 +42,23 @@ std::optional<FwContext::PingableHosts> ConvertPingableHosts(const PingableHosts return converted; } +WINFW_POLICY_STATUS +HandlePolicyException(const common::error::WindowsException &err) +{ + if (nullptr != g_logSink) + { + g_logSink(MULLVAD_LOG_LEVEL_ERROR, err.what(), g_logSinkContext); + } + + if (FWP_E_TIMEOUT == err.errorCode()) + { + // TODO: Detect software that may cause this + return WINFW_POLICY_STATUS_LOCK_TIMEOUT; + } + + return WINFW_POLICY_STATUS_GENERAL_FAILURE; +} + } // anonymous namespace WINFW_LINKAGE @@ -176,7 +193,7 @@ WinFw_Deinitialize(WINFW_CLEANUP_POLICY cleanupPolicy) } WINFW_LINKAGE -bool +WINFW_POLICY_STATUS WINFW_API WinFw_ApplyPolicyConnecting( const WinFwSettings *settings, @@ -187,7 +204,7 @@ WinFw_ApplyPolicyConnecting( { if (nullptr == g_fwContext) { - return false; + return WINFW_POLICY_STATUS_GENERAL_FAILURE; } try @@ -212,7 +229,11 @@ WinFw_ApplyPolicyConnecting( *relay, relayClient, ConvertPingableHosts(pingableHosts) - ); + ) ? WINFW_POLICY_STATUS_SUCCESS : WINFW_POLICY_STATUS_GENERAL_FAILURE; + } + catch (common::error::WindowsException &err) + { + return HandlePolicyException(err); } catch (std::exception &err) { @@ -221,16 +242,16 @@ WinFw_ApplyPolicyConnecting( g_logSink(MULLVAD_LOG_LEVEL_ERROR, err.what(), g_logSinkContext); } - return false; + return WINFW_POLICY_STATUS_GENERAL_FAILURE; } catch (...) { - return false; + return WINFW_POLICY_STATUS_GENERAL_FAILURE; } } WINFW_LINKAGE -bool +WINFW_POLICY_STATUS WINFW_API WinFw_ApplyPolicyConnected( const WinFwSettings *settings, @@ -243,7 +264,7 @@ WinFw_ApplyPolicyConnected( { if (nullptr == g_fwContext) { - return false; + return WINFW_POLICY_STATUS_GENERAL_FAILURE; } try @@ -286,7 +307,11 @@ WinFw_ApplyPolicyConnected( relayClient, tunnelInterfaceAlias, tunnelDnsServers - ); + ) ? WINFW_POLICY_STATUS_SUCCESS : WINFW_POLICY_STATUS_GENERAL_FAILURE; + } + catch (common::error::WindowsException &err) + { + return HandlePolicyException(err); } catch (std::exception &err) { @@ -295,16 +320,16 @@ WinFw_ApplyPolicyConnected( g_logSink(MULLVAD_LOG_LEVEL_ERROR, err.what(), g_logSinkContext); } - return false; + return WINFW_POLICY_STATUS_GENERAL_FAILURE; } catch (...) { - return false; + return WINFW_POLICY_STATUS_GENERAL_FAILURE; } } WINFW_LINKAGE -bool +WINFW_POLICY_STATUS WINFW_API WinFw_ApplyPolicyBlocked( const WinFwSettings *settings @@ -312,7 +337,7 @@ WinFw_ApplyPolicyBlocked( { if (nullptr == g_fwContext) { - return false; + return WINFW_POLICY_STATUS_GENERAL_FAILURE; } try @@ -322,7 +347,13 @@ WinFw_ApplyPolicyBlocked( THROW_ERROR("Invalid argument: settings"); } - return g_fwContext->applyPolicyBlocked(*settings); + return g_fwContext->applyPolicyBlocked(*settings) + ? WINFW_POLICY_STATUS_SUCCESS + : WINFW_POLICY_STATUS_GENERAL_FAILURE; + } + catch (common::error::WindowsException &err) + { + return HandlePolicyException(err); } catch (std::exception &err) { @@ -331,16 +362,16 @@ WinFw_ApplyPolicyBlocked( g_logSink(MULLVAD_LOG_LEVEL_ERROR, err.what(), g_logSinkContext); } - return false; + return WINFW_POLICY_STATUS_GENERAL_FAILURE; } catch (...) { - return false; + return WINFW_POLICY_STATUS_GENERAL_FAILURE; } } WINFW_LINKAGE -bool +WINFW_POLICY_STATUS WINFW_API WinFw_Reset() { @@ -348,10 +379,18 @@ WinFw_Reset() { if (nullptr == g_fwContext) { - return ObjectPurger::Execute(ObjectPurger::GetRemoveAllFunctor()); + return ObjectPurger::Execute(ObjectPurger::GetRemoveAllFunctor()) + ? WINFW_POLICY_STATUS_SUCCESS + : WINFW_POLICY_STATUS_GENERAL_FAILURE; } - return g_fwContext->reset(); + return g_fwContext->reset() + ? WINFW_POLICY_STATUS_SUCCESS + : WINFW_POLICY_STATUS_GENERAL_FAILURE; + } + catch (common::error::WindowsException &err) + { + return HandlePolicyException(err); } catch (std::exception &err) { @@ -360,10 +399,10 @@ WinFw_Reset() g_logSink(MULLVAD_LOG_LEVEL_ERROR, err.what(), g_logSinkContext); } - return false; + return WINFW_POLICY_STATUS_GENERAL_FAILURE; } catch (...) { - return false; + return WINFW_POLICY_STATUS_GENERAL_FAILURE; } } diff --git a/windows/winfw/src/winfw/winfw.h b/windows/winfw/src/winfw/winfw.h index ca4e4b8317..796b034762 100644 --- a/windows/winfw/src/winfw/winfw.h +++ b/windows/winfw/src/winfw/winfw.h @@ -132,6 +132,13 @@ typedef struct tag_PingableHosts } PingableHosts; +enum WINFW_POLICY_STATUS +{ + WINFW_POLICY_STATUS_SUCCESS = 0, + WINFW_POLICY_STATUS_GENERAL_FAILURE = 1, + WINFW_POLICY_STATUS_LOCK_TIMEOUT = 2, +}; + // // ApplyPolicyConnecting: // @@ -142,7 +149,7 @@ PingableHosts; // extern "C" WINFW_LINKAGE -bool +WINFW_POLICY_STATUS WINFW_API WinFw_ApplyPolicyConnecting( const WinFwSettings *settings, @@ -169,7 +176,7 @@ WinFw_ApplyPolicyConnecting( // extern "C" WINFW_LINKAGE -bool +WINFW_POLICY_STATUS WINFW_API WinFw_ApplyPolicyConnected( const WinFwSettings *settings, @@ -188,7 +195,7 @@ WinFw_ApplyPolicyConnected( // extern "C" WINFW_LINKAGE -bool +WINFW_POLICY_STATUS WINFW_API WinFw_ApplyPolicyBlocked( const WinFwSettings *settings @@ -201,6 +208,6 @@ WinFw_ApplyPolicyBlocked( // extern "C" WINFW_LINKAGE -bool +WINFW_POLICY_STATUS WINFW_API WinFw_Reset(); |
