summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md3
-rw-r--r--gui/src/main/daemon-rpc.ts25
-rw-r--r--gui/src/main/index.ts2
-rw-r--r--gui/src/renderer/app.tsx2
-rw-r--r--gui/src/renderer/components/Connect.tsx4
-rw-r--r--gui/src/renderer/components/TunnelControl.tsx2
-rw-r--r--gui/src/shared/daemon-rpc-types.ts14
-rw-r--r--gui/src/shared/notifications/error.ts130
-rw-r--r--mullvad-cli/src/cmds/status.rs7
-rw-r--r--talpid-core/src/firewall/windows.rs117
-rw-r--r--talpid-core/src/tunnel_state_machine/connected_state.rs45
-rw-r--r--talpid-core/src/tunnel_state_machine/connecting_state.rs49
-rw-r--r--talpid-core/src/tunnel_state_machine/error_state.rs37
-rw-r--r--talpid-types/src/tunnel.rs58
-rw-r--r--windows/winfw/src/winfw/winfw.cpp79
-rw-r--r--windows/winfw/src/winfw/winfw.h15
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(&params);
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, &params),
};
- 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();