diff options
| author | Tobias Järvelöv <tobias.jarvelov@mullvad.net> | 2025-09-30 10:57:14 +0200 |
|---|---|---|
| committer | Tobias Järvelöv <tobias.jarvelov@mullvad.net> | 2025-09-30 10:57:14 +0200 |
| commit | ceca0206a3af013470ab77d34468e76aa129fd78 (patch) | |
| tree | ba85d992bf54bb5c04abc687af37251b00ba99a1 | |
| parent | a937623372b83783d5867e45c14fc32dd1db5bb5 (diff) | |
| parent | cfa2ca239265663b50e580e0828e406581172df5 (diff) | |
| download | mullvadvpn-ceca0206a3af013470ab77d34468e76aa129fd78.tar.xz mullvadvpn-ceca0206a3af013470ab77d34468e76aa129fd78.zip | |
Merge branch 'rename-block_when_disconnected-to-lockdown_mode-des-1334'
36 files changed, 643 insertions, 194 deletions
diff --git a/Cargo.lock b/Cargo.lock index 801bf03b00..8fada8070e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3045,6 +3045,7 @@ dependencies = [ "fern", "futures", "hickory-resolver", + "insta", "ipnetwork", "libc", "log", diff --git a/desktop/packages/mullvad-vpn/src/main/daemon-rpc.ts b/desktop/packages/mullvad-vpn/src/main/daemon-rpc.ts index c3594b9590..8db5d1b5f2 100644 --- a/desktop/packages/mullvad-vpn/src/main/daemon-rpc.ts +++ b/desktop/packages/mullvad-vpn/src/main/daemon-rpc.ts @@ -297,8 +297,8 @@ export class DaemonRpc extends GrpcClient { await this.callBool(this.client.setEnableIpv6, enableIpv6); } - public async setBlockWhenDisconnected(blockWhenDisconnected: boolean): Promise<void> { - await this.callBool(this.client.setBlockWhenDisconnected, blockWhenDisconnected); + public async setLockdownMode(lockdownMode: boolean): Promise<void> { + await this.callBool(this.client.setLockdownMode, lockdownMode); } public async setBridgeState(bridgeState: BridgeState): Promise<void> { diff --git a/desktop/packages/mullvad-vpn/src/main/default-settings.ts b/desktop/packages/mullvad-vpn/src/main/default-settings.ts index a80afa03e1..9ba2b18e9b 100644 --- a/desktop/packages/mullvad-vpn/src/main/default-settings.ts +++ b/desktop/packages/mullvad-vpn/src/main/default-settings.ts @@ -34,7 +34,7 @@ export function getDefaultSettings(): ISettings { return { allowLan: false, autoConnect: false, - blockWhenDisconnected: false, + lockdownMode: false, showBetaReleases: false, splitTunnel: { enableExclusions: false, diff --git a/desktop/packages/mullvad-vpn/src/main/index.ts b/desktop/packages/mullvad-vpn/src/main/index.ts index 0b582c3cd6..d56247dd4d 100644 --- a/desktop/packages/mullvad-vpn/src/main/index.ts +++ b/desktop/packages/mullvad-vpn/src/main/index.ts @@ -695,7 +695,7 @@ class ApplicationMain // update the tray icon to indicate that the computer is not secure anymore this.userInterface?.updateTray(false, { state: 'disconnected', - lockedDown: this.settings.blockWhenDisconnected, + lockedDown: this.settings.lockdownMode, }); // notify renderer process diff --git a/desktop/packages/mullvad-vpn/src/main/settings.ts b/desktop/packages/mullvad-vpn/src/main/settings.ts index bfe2bc140d..20c000a3dc 100644 --- a/desktop/packages/mullvad-vpn/src/main/settings.ts +++ b/desktop/packages/mullvad-vpn/src/main/settings.ts @@ -37,8 +37,8 @@ export default class Settings implements Readonly<ISettings> { IpcMainEventChannel.settings.handleSetEnableIpv6((enableIpv6) => this.daemonRpc.setEnableIpv6(enableIpv6), ); - IpcMainEventChannel.settings.handleSetBlockWhenDisconnected((blockWhenDisconnected) => - this.daemonRpc.setBlockWhenDisconnected(blockWhenDisconnected), + IpcMainEventChannel.settings.handleSetLockdownMode((lockdownMode) => + this.daemonRpc.setLockdownMode(lockdownMode), ); IpcMainEventChannel.settings.handleSetBridgeState(async (bridgeState) => { await this.daemonRpc.setBridgeState(bridgeState); @@ -158,8 +158,8 @@ export default class Settings implements Readonly<ISettings> { public get autoConnect() { return this.settingsValue.autoConnect; } - public get blockWhenDisconnected() { - return this.settingsValue.blockWhenDisconnected; + public get lockdownMode() { + return this.settingsValue.lockdownMode; } public get showBetaReleases() { return this.settingsValue.showBetaReleases; diff --git a/desktop/packages/mullvad-vpn/src/renderer/app.tsx b/desktop/packages/mullvad-vpn/src/renderer/app.tsx index 69ae19338d..5b7f625019 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/app.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/app.tsx @@ -594,10 +594,10 @@ export default class AppRenderer { actions.settings.updateBridgeState(bridgeState); }; - public setBlockWhenDisconnected = async (blockWhenDisconnected: boolean) => { + public setLockdownMode = async (lockdownMode: boolean) => { const actions = this.reduxActions; - await IpcRendererEventChannel.settings.setBlockWhenDisconnected(blockWhenDisconnected); - actions.settings.updateBlockWhenDisconnected(blockWhenDisconnected); + await IpcRendererEventChannel.settings.setLockdownMode(lockdownMode); + actions.settings.updateLockdownMode(lockdownMode); }; public setOpenVpnMssfix = async (mssfix?: number) => { @@ -956,7 +956,7 @@ export default class AppRenderer { reduxSettings.updateAllowLan(newSettings.allowLan); reduxSettings.updateEnableIpv6(newSettings.tunnelOptions.generic.enableIpv6); - reduxSettings.updateBlockWhenDisconnected(newSettings.blockWhenDisconnected); + reduxSettings.updateLockdownMode(newSettings.lockdownMode); reduxSettings.updateShowBetaReleases(newSettings.showBetaReleases); reduxSettings.updateOpenVpnMssfix(newSettings.tunnelOptions.openvpn.mssfix); reduxSettings.updateWireguardMtu(newSettings.tunnelOptions.wireguard.mtu); diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/ExpiredAccountErrorView.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/ExpiredAccountErrorView.tsx index 28e00e4151..61db44db59 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/ExpiredAccountErrorView.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/ExpiredAccountErrorView.tsx @@ -34,7 +34,7 @@ import { ModalAlert, ModalAlertType, ModalMessage } from './Modal'; enum RecoveryAction { openBrowser, disconnect, - disableBlockedWhenDisconnected, + disableLockdownMode, } export default function ExpiredAccountErrorView() { @@ -103,7 +103,7 @@ function ExpiredAccountErrorViewComponent() { </FlexColumn> </Footer> - <BlockWhenDisconnectedAlert /> + <LockdownModeAlert /> </StyledContainer> </StyledCustomScrollbars> </Layout> @@ -183,7 +183,7 @@ function Content() { } function ExternalPaymentButton() { - const { setShowBlockWhenDisconnectedAlert } = useExpiredAccountContext(); + const { setShowLockdownModeAlert } = useExpiredAccountContext(); const { recoveryAction } = useRecoveryAction(); const { openUrlWithAuth } = useAppContext(); const isNewAccount = useIsNewAccount(); @@ -193,8 +193,8 @@ function ExternalPaymentButton() { : messages.gettext('Buy more credit'); const [openExternalPayment, openingExternalPayment] = useExclusiveTask(async () => { - if (recoveryAction === RecoveryAction.disableBlockedWhenDisconnected) { - setShowBlockWhenDisconnectedAlert(true); + if (recoveryAction === RecoveryAction.disableLockdownMode) { + setShowLockdownModeAlert(true); } else { await openUrlWithAuth(urls.purchase); } @@ -215,38 +215,37 @@ function ExternalPaymentButton() { ); } -function BlockWhenDisconnectedAlert() { - const { showBlockWhenDisconnectedAlert, setShowBlockWhenDisconnectedAlert } = - useExpiredAccountContext(); - const { setBlockWhenDisconnected } = useAppContext(); - const blockWhenDisconnected = useSelector((state) => state.settings.blockWhenDisconnected); +function LockdownModeAlert() { + const { showLockdownModeAlert, setShowLockdownModeAlert } = useExpiredAccountContext(); + const { setLockdownMode } = useAppContext(); + const lockdownMode = useSelector((state) => state.settings.lockdownMode); - const onCloseBlockWhenDisconnectedInstructions = useCallback(() => { - setShowBlockWhenDisconnectedAlert(false); - }, [setShowBlockWhenDisconnectedAlert]); + const onCloseLockdownModeInstructions = useCallback(() => { + setShowLockdownModeAlert(false); + }, [setShowLockdownModeAlert]); const onChange = useCallback( - async (blockWhenDisconnected: boolean) => { + async (lockdownMode: boolean) => { try { - await setBlockWhenDisconnected(blockWhenDisconnected); + await setLockdownMode(lockdownMode); } catch (e) { const error = e as Error; - log.error('Failed to update block when disconnected', error.message); + log.error('Failed to update lockdown mode', error.message); } }, - [setBlockWhenDisconnected], + [setLockdownMode], ); return ( <ModalAlert - isOpen={showBlockWhenDisconnectedAlert} + isOpen={showLockdownModeAlert} type={ModalAlertType.caution} buttons={[ - <Button key="cancel" onClick={onCloseBlockWhenDisconnectedInstructions}> + <Button key="cancel" onClick={onCloseLockdownModeInstructions}> <Button.Text>{messages.gettext('Close')}</Button.Text> </Button>, ]} - close={onCloseBlockWhenDisconnectedInstructions}> + close={onCloseLockdownModeInstructions}> <ModalMessage> {messages.pgettext( 'connect-view', @@ -261,28 +260,28 @@ function BlockWhenDisconnectedAlert() { </ModalMessage> <StyledModalCellContainer> <Cell.Label>{messages.pgettext('vpn-settings-view', 'Lockdown mode')}</Cell.Label> - <Cell.Switch isOn={blockWhenDisconnected} onChange={onChange} /> + <Cell.Switch isOn={lockdownMode} onChange={onChange} /> </StyledModalCellContainer> </ModalAlert> ); } type ExpiredAccountContextType = { - setShowBlockWhenDisconnectedAlert: (val: boolean) => void; - showBlockWhenDisconnectedAlert: boolean; + setShowLockdownModeAlert: (val: boolean) => void; + showLockdownModeAlert: boolean; }; const ExpiredAccountContext = createContext<ExpiredAccountContextType | undefined>(undefined); const ExpiredAccountContextProvider = ({ children }: { children: ReactNode }) => { - const [showBlockWhenDisconnectedAlert, setShowBlockWhenDisconnectedAlert] = useState(false); + const [showLockdownModeAlert, setShowLockdownModeAlert] = useState(false); const value: ExpiredAccountContextType = useMemo( () => ({ - setShowBlockWhenDisconnectedAlert, - showBlockWhenDisconnectedAlert, + setShowLockdownModeAlert, + showLockdownModeAlert, }), - [setShowBlockWhenDisconnectedAlert, showBlockWhenDisconnectedAlert], + [setShowLockdownModeAlert, showLockdownModeAlert], ); return <ExpiredAccountContext.Provider value={value}>{children}</ExpiredAccountContext.Provider>; }; @@ -300,13 +299,13 @@ const useExpiredAccountContext = () => { const useRecoveryAction = () => { const isBlocked = useSelector((state) => state.connection.isBlocked); - const blockWhenDisconnected = useSelector((state) => state.settings.blockWhenDisconnected); + const lockdownMode = useSelector((state) => state.settings.lockdownMode); let recoveryAction: RecoveryAction; - if (blockWhenDisconnected && isBlocked) { - recoveryAction = RecoveryAction.disableBlockedWhenDisconnected; - } else if (!blockWhenDisconnected && isBlocked) { + if (lockdownMode && isBlocked) { + recoveryAction = RecoveryAction.disableLockdownMode; + } else if (!lockdownMode && isBlocked) { recoveryAction = RecoveryAction.disconnect; } else { recoveryAction = RecoveryAction.openBrowser; @@ -316,7 +315,7 @@ const useRecoveryAction = () => { switch (recoveryAction) { case RecoveryAction.openBrowser: - case RecoveryAction.disableBlockedWhenDisconnected: + case RecoveryAction.disableLockdownMode: recoveryMessage = messages.pgettext( 'connect-view', 'Either buy credit on our website or redeem a voucher.', diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/NotificationArea.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/NotificationArea.tsx index 64c82197eb..bf61ff8003 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/NotificationArea.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/NotificationArea.tsx @@ -3,13 +3,13 @@ import { useCallback, useState } from 'react'; import { messages } from '../../shared/gettext'; import log from '../../shared/logging'; import { - BlockWhenDisconnectedNotificationProvider, CloseToAccountExpiryNotificationProvider, ConnectingNotificationProvider, ErrorNotificationProvider, InAppNotificationAction, InAppNotificationProvider, InconsistentVersionNotificationProvider, + LockdownModeNotificationProvider, ReconnectingNotificationProvider, UnsupportedVersionNotificationProvider, } from '../../shared/notifications'; @@ -69,9 +69,7 @@ export default function NotificationArea(props: IProps) { const allowedPortRanges = useSelector((state) => state.settings.wireguardEndpointData.portRanges); const relaySettings = useSelector((state) => state.settings.relaySettings); - const blockWhenDisconnectedSetting = useSelector( - (state: IReduxState) => state.settings.blockWhenDisconnected, - ); + const lockdownModeSetting = useSelector((state: IReduxState) => state.settings.lockdownMode); const hasExcludedApps = useSelector( (state: IReduxState) => state.settings.splitTunneling && state.settings.splitTunnelingApplications.length > 0, @@ -122,9 +120,9 @@ export default function NotificationArea(props: IProps) { const notificationProviders: InAppNotificationProvider[] = [ new ConnectingNotificationProvider({ tunnelState }), new ReconnectingNotificationProvider(tunnelState), - new BlockWhenDisconnectedNotificationProvider({ + new LockdownModeNotificationProvider({ tunnelState, - blockWhenDisconnectedSetting, + lockdownModeSetting, hasExcludedApps, }), new AppUpgradeErrorNotificationProvider({ diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/login/LoginView.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/views/login/LoginView.tsx index 87de6e1a61..939eb82e20 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/views/login/LoginView.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/login/LoginView.tsx @@ -552,23 +552,23 @@ function AccountDropdownItem({ label, onRemove, onSelect, value }: AccountDropdo } function BlockMessage() { - const { setBlockWhenDisconnected, disconnectTunnel } = useAppContext(); + const { setLockdownMode, disconnectTunnel } = useAppContext(); const tunnelState = useSelector((state) => state.connection.status); - const blockWhenDisconnected = tunnelState.state === 'disconnected' && tunnelState.lockedDown; + const lockdownMode = tunnelState.state === 'disconnected' && tunnelState.lockedDown; const unlock = useCallback(() => { - if (blockWhenDisconnected) { - void setBlockWhenDisconnected(false); + if (lockdownMode) { + void setLockdownMode(false); } if (tunnelState.state === 'error') { void disconnectTunnel(); } - }, [blockWhenDisconnected, tunnelState, setBlockWhenDisconnected, disconnectTunnel]); + }, [lockdownMode, tunnelState, setLockdownMode, disconnectTunnel]); const lockdownModeSettingName = messages.pgettext('vpn-settings-view', 'Lockdown mode'); const message = formatHtml( - blockWhenDisconnected + lockdownMode ? sprintf( // TRANSLATORS: This is a warning message shown when the app is blocking the users // TRANSLATORS: internet connection while logged out. @@ -585,9 +585,7 @@ function BlockMessage() { // TRANSLATORS: internet connection while logged out. messages.pgettext('login-view', 'Our kill switch is currently blocking your connection.'), ); - const buttonText = blockWhenDisconnected - ? messages.gettext('Disable') - : messages.gettext('Unblock'); + const buttonText = lockdownMode ? messages.gettext('Disable') : messages.gettext('Unblock'); return ( <StyledBlockMessageContainer> diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/vpn-settings/components/lockdown-mode-setting/LockdownModeSetting.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/views/vpn-settings/components/lockdown-mode-setting/LockdownModeSetting.tsx index 30da61d848..381e8605ea 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/components/views/vpn-settings/components/lockdown-mode-setting/LockdownModeSetting.tsx +++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/vpn-settings/components/lockdown-mode-setting/LockdownModeSetting.tsx @@ -11,22 +11,22 @@ import { ModalAlert, ModalAlertType, ModalMessage } from '../../../../Modal'; import { SettingsToggleListItem } from '../../../../settings-toggle-list-item'; export function LockdownModeSetting() { - const blockWhenDisconnected = useSelector((state) => state.settings.blockWhenDisconnected); - const { setBlockWhenDisconnected: setBlockWhenDisconnectedImpl } = useAppContext(); + const lockdownMode = useSelector((state) => state.settings.lockdownMode); + const { setLockdownMode: setLockdownModeImpl } = useAppContext(); const [confirmationDialogVisible, showConfirmationDialog, hideConfirmationDialog] = useBoolean(false); - const setBlockWhenDisconnected = useCallback( - async (blockWhenDisconnected: boolean) => { + const setLockdownMode = useCallback( + async (lockdownMode: boolean) => { try { - await setBlockWhenDisconnectedImpl(blockWhenDisconnected); + await setLockdownModeImpl(lockdownMode); } catch (e) { const error = e as Error; - log.error('Failed to update block when disconnected', error.message); + log.error('Failed to update lockdown mode', error.message); } }, - [setBlockWhenDisconnectedImpl], + [setLockdownModeImpl], ); const setLockDownMode = useCallback( @@ -34,21 +34,21 @@ export function LockdownModeSetting() { if (newValue) { showConfirmationDialog(); } else { - await setBlockWhenDisconnected(false); + await setLockdownMode(false); } }, - [setBlockWhenDisconnected, showConfirmationDialog], + [setLockdownMode, showConfirmationDialog], ); const confirmLockdownMode = useCallback(async () => { hideConfirmationDialog(); - await setBlockWhenDisconnected(true); - }, [hideConfirmationDialog, setBlockWhenDisconnected]); + await setLockdownMode(true); + }, [hideConfirmationDialog, setLockdownMode]); return ( <SettingsToggleListItem anchorId="lockdown-mode-setting" - checked={blockWhenDisconnected} + checked={lockdownMode} onCheckedChange={setLockDownMode}> <SettingsToggleListItem.Label> {messages.pgettext('vpn-settings-view', 'Lockdown mode')} diff --git a/desktop/packages/mullvad-vpn/src/renderer/redux/settings/actions.ts b/desktop/packages/mullvad-vpn/src/renderer/redux/settings/actions.ts index d2a3fb1c4a..c87b9a73a0 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/redux/settings/actions.ts +++ b/desktop/packages/mullvad-vpn/src/renderer/redux/settings/actions.ts @@ -43,9 +43,9 @@ export interface IUpdateEnableIpv6Action { enableIpv6: boolean; } -export interface IUpdateBlockWhenDisconnectedAction { - type: 'UPDATE_BLOCK_WHEN_DISCONNECTED'; - blockWhenDisconnected: boolean; +export interface IUpdateLockdownModeAction { + type: 'UPDATE_LOCKDOWN_MODE'; + lockdownMode: boolean; } export interface IUpdateShowBetaReleasesAction { @@ -135,7 +135,7 @@ export type SettingsAction = | IUpdateWireguardEndpointData | IUpdateAllowLanAction | IUpdateEnableIpv6Action - | IUpdateBlockWhenDisconnectedAction + | IUpdateLockdownModeAction | IUpdateShowBetaReleasesAction | IUpdateBridgeSettingsAction | IUpdateBridgeStateAction @@ -199,12 +199,10 @@ function updateEnableIpv6(enableIpv6: boolean): IUpdateEnableIpv6Action { }; } -function updateBlockWhenDisconnected( - blockWhenDisconnected: boolean, -): IUpdateBlockWhenDisconnectedAction { +function updateLockdownMode(lockdownMode: boolean): IUpdateLockdownModeAction { return { - type: 'UPDATE_BLOCK_WHEN_DISCONNECTED', - blockWhenDisconnected, + type: 'UPDATE_LOCKDOWN_MODE', + lockdownMode, }; } @@ -333,7 +331,7 @@ export default { updateWireguardEndpointData, updateAllowLan, updateEnableIpv6, - updateBlockWhenDisconnected, + updateLockdownMode, updateShowBetaReleases, updateBridgeSettings, updateBridgeState, diff --git a/desktop/packages/mullvad-vpn/src/renderer/redux/settings/reducers.ts b/desktop/packages/mullvad-vpn/src/renderer/redux/settings/reducers.ts index 9ac8bb3a61..285a0500e2 100644 --- a/desktop/packages/mullvad-vpn/src/renderer/redux/settings/reducers.ts +++ b/desktop/packages/mullvad-vpn/src/renderer/redux/settings/reducers.ts @@ -105,7 +105,7 @@ export interface ISettingsReduxState { enableIpv6: boolean; bridgeSettings: BridgeSettingsRedux; bridgeState: BridgeState; - blockWhenDisconnected: boolean; + lockdownMode: boolean; showBetaReleases: boolean; openVpn: { mssfix?: number; @@ -166,7 +166,7 @@ const initialState: ISettingsReduxState = { custom: undefined, }, bridgeState: 'auto', - blockWhenDisconnected: false, + lockdownMode: false, showBetaReleases: false, openVpn: {}, wireguard: {}, @@ -242,10 +242,10 @@ export default function ( enableIpv6: action.enableIpv6, }; - case 'UPDATE_BLOCK_WHEN_DISCONNECTED': + case 'UPDATE_LOCKDOWN_MODE': return { ...state, - blockWhenDisconnected: action.blockWhenDisconnected, + lockdownMode: action.lockdownMode, }; case 'UPDATE_SHOW_BETA_NOTIFICATIONS': diff --git a/desktop/packages/mullvad-vpn/src/shared/daemon-rpc-types.ts b/desktop/packages/mullvad-vpn/src/shared/daemon-rpc-types.ts index 026df6d189..f1c3dd8ac4 100644 --- a/desktop/packages/mullvad-vpn/src/shared/daemon-rpc-types.ts +++ b/desktop/packages/mullvad-vpn/src/shared/daemon-rpc-types.ts @@ -492,7 +492,7 @@ export type AccessMethodExistsError = { type: 'name already exists' }; export interface ISettings { allowLan: boolean; autoConnect: boolean; - blockWhenDisconnected: boolean; + lockdownMode: boolean; showBetaReleases: boolean; relaySettings: RelaySettings; tunnelOptions: ITunnelOptions; diff --git a/desktop/packages/mullvad-vpn/src/shared/ipc-schema.ts b/desktop/packages/mullvad-vpn/src/shared/ipc-schema.ts index a685a2974e..dae1de0da3 100644 --- a/desktop/packages/mullvad-vpn/src/shared/ipc-schema.ts +++ b/desktop/packages/mullvad-vpn/src/shared/ipc-schema.ts @@ -195,7 +195,7 @@ export const ipcSchema = { setAllowLan: invoke<boolean, void>(), setShowBetaReleases: invoke<boolean, void>(), setEnableIpv6: invoke<boolean, void>(), - setBlockWhenDisconnected: invoke<boolean, void>(), + setLockdownMode: invoke<boolean, void>(), setBridgeState: invoke<BridgeState, void>(), setOpenVpnMssfix: invoke<number | undefined, void>(), setWireguardMtu: invoke<number | undefined, void>(), diff --git a/desktop/packages/mullvad-vpn/src/shared/notifications/block-when-disconnected.ts b/desktop/packages/mullvad-vpn/src/shared/notifications/block-when-disconnected.ts index 6b8022c381..9950aa3b8b 100644 --- a/desktop/packages/mullvad-vpn/src/shared/notifications/block-when-disconnected.ts +++ b/desktop/packages/mullvad-vpn/src/shared/notifications/block-when-disconnected.ts @@ -12,21 +12,20 @@ import { SystemNotificationSeverityType, } from './notification'; -interface BlockWhenDisconnectedNotificationContext { +interface LockdownModeNotificationContext { tunnelState: TunnelState; - blockWhenDisconnectedSetting: boolean; + lockdownModeSetting: boolean; hasExcludedApps: boolean; } -export class BlockWhenDisconnectedNotificationProvider +export class LockdownModeNotificationProvider implements InAppNotificationProvider, SystemNotificationProvider { - public constructor(private context: BlockWhenDisconnectedNotificationContext) {} + public constructor(private context: LockdownModeNotificationContext) {} public mayDisplay() { return ( - (this.context.tunnelState.state === 'disconnecting' && - this.context.blockWhenDisconnectedSetting) || + (this.context.tunnelState.state === 'disconnecting' && this.context.lockdownModeSetting) || (this.context.tunnelState.state === 'disconnected' && this.context.tunnelState.lockedDown) ); } diff --git a/mullvad-cli/src/cmds/lockdown.rs b/mullvad-cli/src/cmds/lockdown.rs index 001f195fda..c165770dfa 100644 --- a/mullvad-cli/src/cmds/lockdown.rs +++ b/mullvad-cli/src/cmds/lockdown.rs @@ -22,14 +22,14 @@ impl LockdownMode { async fn set(policy: BooleanOption) -> Result<()> { let mut rpc = MullvadProxyClient::new().await?; - rpc.set_block_when_disconnected(*policy).await?; + rpc.set_lockdown_mode(*policy).await?; println!("Changed lockdown mode setting"); Ok(()) } async fn get() -> Result<()> { let mut rpc = MullvadProxyClient::new().await?; - let state = BooleanOption::from(rpc.get_settings().await?.block_when_disconnected); + let state = BooleanOption::from(rpc.get_settings().await?.lockdown_mode); println!("Block traffic when the VPN is disconnected: {state}"); Ok(()) } diff --git a/mullvad-daemon/Cargo.toml b/mullvad-daemon/Cargo.toml index 857c629e92..e98b126942 100644 --- a/mullvad-daemon/Cargo.toml +++ b/mullvad-daemon/Cargo.toml @@ -27,7 +27,7 @@ libc = "0.2" log = { workspace = true } regex = "1.0" serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true } +serde_json = { workspace = true, features = ["std"] } tokio = { workspace = true, features = ["fs", "io-util", "rt-multi-thread", "sync", "time"] } tokio-stream = { version = "0.1", features = ["sync"]} socket2 = { workspace = true } @@ -55,6 +55,7 @@ log-panics = "2.0.0" mullvad-management-interface = { path = "../mullvad-management-interface" } [dev-dependencies] +insta = { workspace = true, features = ["json"] } talpid-time = { path = "../talpid-time", features = ["test"] } tokio = { workspace = true, features = ["test-util"] } diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index 33f904947c..5783baef4e 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -88,7 +88,7 @@ use std::{ #[cfg(target_os = "android")] use talpid_core::connectivity_listener::ConnectivityListener; #[cfg(not(target_os = "android"))] -use talpid_core::tunnel_state_machine::BlockWhenDisconnected; +use talpid_core::tunnel_state_machine::LockdownMode; use talpid_core::{ mpsc::Sender, split_tunnel, @@ -271,9 +271,9 @@ pub enum DaemonCommand { SetAllowLan(ResponseTx<(), settings::Error>, bool), /// Set the beta program setting. SetShowBetaReleases(ResponseTx<(), settings::Error>, bool), - /// Set the block_when_disconnected setting. + /// Set the lockdown_mode setting. #[cfg(not(target_os = "android"))] - SetBlockWhenDisconnected(ResponseTx<(), settings::Error>, bool), + SetLockdownMode(ResponseTx<(), settings::Error>, bool), /// Set the auto-connect setting. SetAutoConnect(ResponseTx<(), settings::Error>, bool), /// Set the mssfix argument for OpenVPN @@ -883,9 +883,7 @@ impl Daemon { tunnel_state_machine::InitialTunnelState { allow_lan: settings.allow_lan, #[cfg(not(target_os = "android"))] - block_when_disconnected: BlockWhenDisconnected::from( - settings.block_when_disconnected, - ), + lockdown_mode: LockdownMode::from(settings.lockdown_mode), dns_config: dns::addresses_from_options(&settings.tunnel_options.dns_options), allowed_endpoint: access_mode_handler .get_current() @@ -973,7 +971,7 @@ impl Daemon { tunnel_state: TunnelState::Disconnected { location: None, #[cfg(not(target_os = "android"))] - locked_down: settings.block_when_disconnected, + locked_down: settings.lockdown_mode, }, target_state, #[cfg(target_os = "linux")] @@ -1432,9 +1430,8 @@ impl Daemon { SetAllowLan(tx, allow_lan) => self.on_set_allow_lan(tx, allow_lan).await, SetShowBetaReleases(tx, enabled) => self.on_set_show_beta_releases(tx, enabled).await, #[cfg(not(target_os = "android"))] - SetBlockWhenDisconnected(tx, block_when_disconnected) => { - self.on_set_block_when_disconnected(tx, block_when_disconnected) - .await + SetLockdownMode(tx, lockdown_mode) => { + self.on_set_lockdown_mode(tx, lockdown_mode).await } SetAutoConnect(tx, auto_connect) => self.on_set_auto_connect(tx, auto_connect).await, SetOpenVpnMssfix(tx, mssfix_arg) => self.on_set_openvpn_mssfix(tx, mssfix_arg).await, @@ -2456,31 +2453,31 @@ impl Daemon { } #[cfg(not(target_os = "android"))] - async fn on_set_block_when_disconnected( + async fn on_set_lockdown_mode( &mut self, tx: ResponseTx<(), settings::Error>, - block_when_disconnected: bool, + lockdown_mode: bool, ) { match self .settings - .update(move |settings| settings.block_when_disconnected = block_when_disconnected) + .update(move |settings| settings.lockdown_mode = lockdown_mode) .await { Ok(settings_changed) => { if settings_changed { - self.send_tunnel_command(TunnelCommand::BlockWhenDisconnected( - BlockWhenDisconnected::from(block_when_disconnected), + self.send_tunnel_command(TunnelCommand::LockdownMode( + LockdownMode::from(lockdown_mode), oneshot_map(tx, |tx, ()| { - Self::oneshot_send(tx, Ok(()), "set_block_when_disconnected response"); + Self::oneshot_send(tx, Ok(()), "set_lockdown_mode response"); }), )); } else { - Self::oneshot_send(tx, Ok(()), "set_block_when_disconnected response"); + Self::oneshot_send(tx, Ok(()), "set_lockdown_mode response"); } } Err(e) => { log::error!("{}", e.display_chain_with_msg("Unable to save settings")); - Self::oneshot_send(tx, Err(e), "set_block_when_disconnected response"); + Self::oneshot_send(tx, Err(e), "set_lockdown_mode response"); } } } @@ -3191,8 +3188,8 @@ impl Daemon { #[cfg(not(target_os = "android"))] { let (tx, _rx) = oneshot::channel(); - self.send_tunnel_command(TunnelCommand::BlockWhenDisconnected( - BlockWhenDisconnected::from(self.settings.block_when_disconnected), + self.send_tunnel_command(TunnelCommand::LockdownMode( + LockdownMode::from(self.settings.lockdown_mode), tx, )); } @@ -3254,10 +3251,7 @@ impl Daemon { { log::debug!("Blocking firewall during shutdown"); let (tx, _rx) = oneshot::channel(); - self.send_tunnel_command(TunnelCommand::BlockWhenDisconnected( - BlockWhenDisconnected::yes(), - tx, - )); + self.send_tunnel_command(TunnelCommand::LockdownMode(LockdownMode::yes(), tx)); } self.disconnect_tunnel(); @@ -3277,14 +3271,13 @@ impl Daemon { // non-persistent. If the installation of the new version fails and // the user is left in blocked state with no app, they can reboot // to regain internet access. - self.settings.settings().block_when_disconnected - || self.settings.settings().auto_connect + self.settings.settings().lockdown_mode || self.settings.settings().auto_connect } else { true }; let (tx, _rx) = oneshot::channel(); - self.send_tunnel_command(TunnelCommand::BlockWhenDisconnected( - BlockWhenDisconnected::yes().persist(persist), + self.send_tunnel_command(TunnelCommand::LockdownMode( + LockdownMode::yes().persist(persist), tx, )); } diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs index 7407208d55..a584b95f38 100644 --- a/mullvad-daemon/src/management_interface.rs +++ b/mullvad-daemon/src/management_interface.rs @@ -285,22 +285,19 @@ impl ManagementService for ManagementServiceImpl { } #[cfg(not(target_os = "android"))] - async fn set_block_when_disconnected(&self, request: Request<bool>) -> ServiceResult<()> { - let block_when_disconnected = request.into_inner(); - log::debug!("set_block_when_disconnected({})", block_when_disconnected); + async fn set_lockdown_mode(&self, request: Request<bool>) -> ServiceResult<()> { + let lockdown_mode = request.into_inner(); + log::debug!("set_lockdown_mode({})", lockdown_mode); let (tx, rx) = oneshot::channel(); - self.send_command_to_daemon(DaemonCommand::SetBlockWhenDisconnected( - tx, - block_when_disconnected, - ))?; + self.send_command_to_daemon(DaemonCommand::SetLockdownMode(tx, lockdown_mode))?; self.wait_for_result(rx).await??; Ok(Response::new(())) } #[cfg(target_os = "android")] - async fn set_block_when_disconnected(&self, request: Request<bool>) -> ServiceResult<()> { - let block_when_disconnected = request.into_inner(); - log::debug!("set_block_when_disconnected({})", block_when_disconnected); + async fn set_lockdown_mode(&self, request: Request<bool>) -> ServiceResult<()> { + let lockdown_mode = request.into_inner(); + log::debug!("set_lockdown_mode({})", lockdown_mode); Err(Status::unimplemented( "Setting Lockdown mode on Android is not supported - this is handled by the OS, not the daemon", )) diff --git a/mullvad-daemon/src/migrations/mod.rs b/mullvad-daemon/src/migrations/mod.rs index 251546ef57..284928073e 100644 --- a/mullvad-daemon/src/migrations/mod.rs +++ b/mullvad-daemon/src/migrations/mod.rs @@ -47,6 +47,7 @@ mod account_history; mod device; mod v1; mod v10; +mod v11; mod v2; mod v3; mod v4; @@ -210,6 +211,8 @@ async fn migrate_settings( v10::migrate(settings)?; + v11::migrate(settings)?; + Ok(migration_data) } diff --git a/mullvad-daemon/src/migrations/snapshots/mullvad_daemon__migrations__v11__test__v11_to_v12_migration_access_method_name_duplicates.snap b/mullvad-daemon/src/migrations/snapshots/mullvad_daemon__migrations__v11__test__v11_to_v12_migration_access_method_name_duplicates.snap new file mode 100644 index 0000000000..0bd3fe26ff --- /dev/null +++ b/mullvad-daemon/src/migrations/snapshots/mullvad_daemon__migrations__v11__test__v11_to_v12_migration_access_method_name_duplicates.snap @@ -0,0 +1,136 @@ +--- +source: mullvad-daemon/src/migrations/v11.rs +expression: "serde_json::to_string_pretty(&old_settings).unwrap()" +--- +{ + "api_access_methods": { + "custom": [ + { + "access_method": { + "custom": { + "shadowsocks": { + "cipher": "aes-128-cfb", + "endpoint": "127.0.0.1:80", + "password": "" + } + } + }, + "enabled": true, + "id": "90d35296-3823-4805-8926-720fff53c752", + "name": "test_3" + }, + { + "access_method": { + "custom": { + "shadowsocks": { + "cipher": "aes-256-cfb", + "endpoint": "127.0.0.1:443", + "password": "secret" + } + } + }, + "enabled": true, + "id": "d879f6e9-c052-4452-8e53-088183d01c0a", + "name": "test_2" + }, + { + "access_method": { + "custom": { + "shadowsocks": { + "cipher": "aes-256-gcm", + "endpoint": "127.0.0.1:443", + "password": "" + } + } + }, + "enabled": true, + "id": "7b49cef8-5a9f-4bbc-9bee-00841edc98e9", + "name": "test" + }, + { + "access_method": { + "custom": { + "shadowsocks": { + "cipher": "aes-128-gcm", + "endpoint": "127.0.0.1:80", + "password": "" + } + } + }, + "enabled": true, + "id": "0bafc4ed-cd4f-4368-b067-74527f42451b", + "name": "test_1" + }, + { + "access_method": { + "custom": { + "shadowsocks": { + "cipher": "aes-128-cfb", + "endpoint": "127.0.0.1:443", + "password": "" + } + } + }, + "enabled": false, + "id": "09d032bc-7e3a-4d85-a63f-528b6c4b890e", + "name": "test_2_1" + }, + { + "access_method": { + "custom": { + "shadowsocks": { + "cipher": "aes-256-gcm", + "endpoint": "127.0.0.1:80", + "password": "secret" + } + } + }, + "enabled": false, + "id": "4471b3f5-a87e-4355-aea8-72c4c4936479", + "name": "test_1_1" + }, + { + "access_method": { + "custom": { + "shadowsocks": { + "cipher": "aes-128-cfb", + "endpoint": "127.0.0.1:443", + "password": "" + } + } + }, + "enabled": false, + "id": "d284a9d5-307b-4959-94a6-89fef8187807", + "name": "test_4" + }, + { + "access_method": { + "custom": { + "shadowsocks": { + "cipher": "aes-256-cfb", + "endpoint": "127.0.0.1:9090", + "password": "" + } + } + }, + "enabled": false, + "id": "6f8db7c3-2258-46c0-8b7d-2016dd9e5739", + "name": "other_name" + }, + { + "access_method": { + "custom": { + "shadowsocks": { + "cipher": "aes-128-gcm", + "endpoint": "127.0.0.1:8080", + "password": "" + } + } + }, + "enabled": true, + "id": "ffdf9900-e843-4298-9478-a9dfbaa63b17", + "name": "test_5" + } + ] + } +} diff --git a/mullvad-daemon/src/migrations/v11.rs b/mullvad-daemon/src/migrations/v11.rs new file mode 100644 index 0000000000..905361bad6 --- /dev/null +++ b/mullvad-daemon/src/migrations/v11.rs @@ -0,0 +1,326 @@ +use super::{Error, Result}; +use mullvad_types::settings::SettingsVersion; + +/// The migration handles: +/// - Renaming of block_when_disconnected option to lockdown_mode. +/// - API access method names must now be unique and duplicates will be renamed. +pub fn migrate(settings: &mut serde_json::Value) -> Result<()> { + if !(version(settings) == Some(SettingsVersion::V11)) { + return Ok(()); + } + + log::info!("Migrating settings format to v12"); + + migrate_block_when_disconnected(settings)?; + migrate_duplicated_api_access_method_names(settings)?; + + settings["settings_version"] = serde_json::json!(SettingsVersion::V12); + + Ok(()) +} + +fn version(settings: &serde_json::Value) -> Option<SettingsVersion> { + settings + .get("settings_version") + .and_then(|version| serde_json::from_value(version.clone()).ok()) +} + +fn migrate_block_when_disconnected(settings: &mut serde_json::Value) -> Result<()> { + let key_name_before = "block_when_disconnected"; + let key_name_after = "lockdown_mode"; + + let settings_map = settings + .as_object_mut() + .ok_or(Error::InvalidSettingsContent)?; + + // Get the old key's value and insert the new key with that value + let value = settings_map + .get(key_name_before) + .ok_or(Error::InvalidSettingsContent)?; + settings_map.insert(key_name_after.to_string(), value.clone()); + + // Remove the old key + settings_map.remove(key_name_before); + + Ok(()) +} + +fn generate_access_method_name_initial_suffix( + access_method_names: &[impl AsRef<str>], + access_method_name: &String, +) -> usize { + let access_method_name_count = access_method_names + .iter() + .filter(|name| name.as_ref() == access_method_name) + .count(); + + let mut suffix = 1; + if access_method_name_count > 1 { + suffix = access_method_name_count - 1 + } + + suffix +} + +/// Only consider renaming access methods with a duplicate name if it has a higher index +/// thab other access methods. This is to ensure that older entries' names are preserved, +/// in favor of renaming newer access methods. +fn get_should_rename_api_access_method( + access_method_names: &[impl AsRef<str>], + access_method_name: &String, + access_method_name_index: usize, +) -> bool { + access_method_names.iter().enumerate().any(|(index, name)| { + access_method_name_index > index && name.as_ref() == *access_method_name + }) +} + +fn generate_access_method_name( + access_method_names: &[impl AsRef<str>], + access_method_name: &String, + access_method_name_index: usize, + access_method_name_suffix: usize, +) -> String { + // Generate a new name for the access method + let generated_access_method_name = format!("{access_method_name}_{access_method_name_suffix}"); + + // Verify if the generated name is unique or if a new name should be generated + let should_rename_api_access_method = get_should_rename_api_access_method( + access_method_names, + &generated_access_method_name, + access_method_name_index, + ); + if should_rename_api_access_method { + // Increment the suffix for the next attempt to generate a new access method name + generate_access_method_name( + access_method_names, + access_method_name, + access_method_name_index, + access_method_name_suffix + 1, + ) + } else { + generated_access_method_name + } +} + +fn migrate_duplicated_api_access_method_names(settings: &mut serde_json::Value) -> Result<()> { + let settings_map = settings + .as_object_mut() + .ok_or(Error::InvalidSettingsContent)?; + + let mut custom_api_access_methods: Vec<&mut String> = settings_map + .get_mut("api_access_methods") + .and_then(serde_json::Value::as_object_mut) + .and_then(|api_access_method| api_access_method.get_mut("custom")?.as_array_mut()) + .into_iter() + .flat_map(|array| array.iter_mut()) + // Take a &mut to each custom api access method name as a String + .filter_map(|custom_api_access_method| custom_api_access_method.as_object_mut()?.get_mut("name")) + .filter_map(|custom_api_access_method| match custom_api_access_method { + serde_json::Value::String(custom_api_access_method_name) => { + Some(custom_api_access_method_name) + } + _ => None, + }) + .collect(); + + for index in 0..custom_api_access_methods.len() { + let access_method_name = &*custom_api_access_methods[index]; + + let should_rename_api_access_method = get_should_rename_api_access_method( + &custom_api_access_methods, + access_method_name, + index, + ); + if should_rename_api_access_method { + let access_method_name_suffix = generate_access_method_name_initial_suffix( + &custom_api_access_methods, + access_method_name, + ); + + let generated_access_method_name = generate_access_method_name( + &custom_api_access_methods, + access_method_name, + index, + access_method_name_suffix, + ); + + // Update the access method's name to the new unique name that was generated + *custom_api_access_methods[index] = generated_access_method_name; + } + } + + Ok(()) +} + +#[cfg(test)] +mod test { + use serde_json::json; + + use crate::migrations::v11::migrate_block_when_disconnected; + use crate::migrations::v11::migrate_duplicated_api_access_method_names; + + /// "block_when_disconnected" is renamed to "lockdown_mode" + #[test] + fn test_v11_to_v12_migration_block_when_disconnected_disabled() { + let mut old_settings = json!({ + "block_when_disconnected": false, + }); + migrate_block_when_disconnected(&mut old_settings).unwrap(); + let new_settings: serde_json::Value = json!({ + "lockdown_mode": false, + }); + assert_eq!(&old_settings, &new_settings); + } + + #[test] + fn test_v11_to_v12_migration_block_when_disconnected_enabled() { + let mut old_settings = json!({ + "block_when_disconnected": true, + }); + migrate_block_when_disconnected(&mut old_settings).unwrap(); + let new_settings: serde_json::Value = json!({ + "lockdown_mode": true, + }); + assert_eq!(&old_settings, &new_settings); + } + + // custom access method's names are renamed if they are not unique + #[test] + fn test_v11_to_v12_migration_access_method_name_duplicates() { + let mut old_settings = json!({ + "api_access_methods": { + "custom": [ + { + "id": "90d35296-3823-4805-8926-720fff53c752", + "name": "test_3", + "enabled": true, + "access_method": { + "custom": { + "shadowsocks": { + "endpoint": "127.0.0.1:80", + "password": "", + "cipher": "aes-128-cfb" + } + } + } + }, + { + "id": "d879f6e9-c052-4452-8e53-088183d01c0a", + "name": "test_2", + "enabled": true, + "access_method": { + "custom": { + "shadowsocks": { + "endpoint": "127.0.0.1:443", + "password": "secret", + "cipher": "aes-256-cfb" + } + } + } + }, + { + "id": "7b49cef8-5a9f-4bbc-9bee-00841edc98e9", + "name": "test", + "enabled": true, + "access_method": { + "custom": { + "shadowsocks": { + "endpoint": "127.0.0.1:443", + "password": "", + "cipher": "aes-256-gcm" + } + } + } + }, + { + "id": "0bafc4ed-cd4f-4368-b067-74527f42451b", + "name": "test_1", + "enabled": true, + "access_method": { + "custom": { + "shadowsocks": { + "endpoint": "127.0.0.1:80", + "password": "", + "cipher": "aes-128-gcm" + } + } + } + }, + { + "id": "09d032bc-7e3a-4d85-a63f-528b6c4b890e", + "name": "test_2", + "enabled": false, + "access_method": { + "custom": { + "shadowsocks": { + "endpoint": "127.0.0.1:443", + "password": "", + "cipher": "aes-128-cfb" + } + } + } + }, + { + "id": "4471b3f5-a87e-4355-aea8-72c4c4936479", + "name": "test_1", + "enabled": false, + "access_method": { + "custom": { + "shadowsocks": { + "endpoint": "127.0.0.1:80", + "password": "secret", + "cipher": "aes-256-gcm" + } + } + } + }, + { + "id": "d284a9d5-307b-4959-94a6-89fef8187807", + "name": "test", + "enabled": false, + "access_method": { + "custom": { + "shadowsocks": { + "endpoint": "127.0.0.1:443", + "password": "", + "cipher": "aes-128-cfb" + } + } + } + }, + { + "id": "6f8db7c3-2258-46c0-8b7d-2016dd9e5739", + "name": "other_name", + "enabled": false, + "access_method": { + "custom": { + "shadowsocks": { + "endpoint": "127.0.0.1:9090", + "password": "", + "cipher": "aes-256-cfb" + } + } + } + }, + { + "id": "ffdf9900-e843-4298-9478-a9dfbaa63b17", + "name": "test", + "enabled": true, + "access_method": { + "custom": { + "shadowsocks": { + "endpoint": "127.0.0.1:8080", + "password": "", + "cipher": "aes-128-gcm" + } + } + } + } + ] + } + }); + migrate_duplicated_api_access_method_names(&mut old_settings).unwrap(); + insta::assert_snapshot!(serde_json::to_string_pretty(&old_settings).unwrap()); + } +} diff --git a/mullvad-daemon/src/settings/mod.rs b/mullvad-daemon/src/settings/mod.rs index a1d9434299..2fb0abcad0 100644 --- a/mullvad-daemon/src/settings/mod.rs +++ b/mullvad-daemon/src/settings/mod.rs @@ -193,7 +193,7 @@ impl SettingsPersister { // On android lockdown mode is handled by the OS so setting this to true // has no effect. #[cfg(not(target_os = "android"))] - block_when_disconnected: true, + lockdown_mode: true, ..Self::default_settings() }; @@ -613,7 +613,7 @@ mod test { }, "bridge_state": "auto", "allow_lan": true, - "block_when_disconnected": false, + "lockdown_mode": false, "auto_connect": true, "tunnel_options": { "openvpn": { @@ -690,7 +690,7 @@ mod test { ); assert!( - !settings.block_when_disconnected, + !settings.lockdown_mode, "The daemon should not block the internet if settings are missing" ); } @@ -720,7 +720,7 @@ mod test { ); assert!( - settings.block_when_disconnected, + settings.lockdown_mode, "The daemon should block the internet if settings are corrupt" ); } diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index ff3206cb6a..207b977070 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -43,7 +43,7 @@ service ManagementService { rpc ResetSettings(google.protobuf.Empty) returns (google.protobuf.Empty) {} rpc SetAllowLan(google.protobuf.BoolValue) returns (google.protobuf.Empty) {} rpc SetShowBetaReleases(google.protobuf.BoolValue) returns (google.protobuf.Empty) {} - rpc SetBlockWhenDisconnected(google.protobuf.BoolValue) returns (google.protobuf.Empty) {} + rpc SetLockdownMode(google.protobuf.BoolValue) returns (google.protobuf.Empty) {} rpc SetAutoConnect(google.protobuf.BoolValue) returns (google.protobuf.Empty) {} rpc SetOpenvpnMssfix(google.protobuf.UInt32Value) returns (google.protobuf.Empty) {} rpc SetWireguardMtu(google.protobuf.UInt32Value) returns (google.protobuf.Empty) {} @@ -531,7 +531,7 @@ message Settings { BridgeSettings bridge_settings = 2; BridgeState bridge_state = 3; bool allow_lan = 4; - bool block_when_disconnected = 5; + bool lockdown_mode = 5; bool auto_connect = 6; TunnelOptions tunnel_options = 7; bool show_beta_releases = 8; diff --git a/mullvad-management-interface/src/client.rs b/mullvad-management-interface/src/client.rs index afadfd0875..aa8b67d930 100644 --- a/mullvad-management-interface/src/client.rs +++ b/mullvad-management-interface/src/client.rs @@ -265,8 +265,8 @@ impl MullvadProxyClient { Ok(()) } - pub async fn set_block_when_disconnected(&mut self, state: bool) -> Result<()> { - self.0.set_block_when_disconnected(state).await?; + pub async fn set_lockdown_mode(&mut self, state: bool) -> Result<()> { + self.0.set_lockdown_mode(state).await?; Ok(()) } diff --git a/mullvad-management-interface/src/types/conversions/settings.rs b/mullvad-management-interface/src/types/conversions/settings.rs index e9f68524ce..8e1275b72b 100644 --- a/mullvad-management-interface/src/types/conversions/settings.rs +++ b/mullvad-management-interface/src/types/conversions/settings.rs @@ -34,9 +34,9 @@ impl From<&mullvad_types::settings::Settings> for proto::Settings { bridge_state: Some(proto::BridgeState::from(settings.bridge_state)), allow_lan: settings.allow_lan, #[cfg(not(target_os = "android"))] - block_when_disconnected: settings.block_when_disconnected, + lockdown_mode: settings.lockdown_mode, #[cfg(target_os = "android")] - block_when_disconnected: false, + lockdown_mode: false, auto_connect: settings.auto_connect, tunnel_options: Some(proto::TunnelOptions::from(&settings.tunnel_options)), show_beta_releases: settings.show_beta_releases, @@ -180,7 +180,7 @@ impl TryFrom<proto::Settings> for mullvad_types::settings::Settings { bridge_state, allow_lan: settings.allow_lan, #[cfg(not(target_os = "android"))] - block_when_disconnected: settings.block_when_disconnected, + lockdown_mode: settings.lockdown_mode, auto_connect: settings.auto_connect, tunnel_options: mullvad_types::settings::TunnelOptions::try_from(tunnel_options)?, relay_overrides: settings diff --git a/mullvad-types/src/features.rs b/mullvad-types/src/features.rs index a16f7ca33f..8dc8f5cad5 100644 --- a/mullvad-types/src/features.rs +++ b/mullvad-types/src/features.rs @@ -138,7 +138,7 @@ pub fn compute_feature_indicators( let split_tunneling = false; #[cfg(not(target_os = "android"))] - let lockdown_mode = settings.block_when_disconnected; + let lockdown_mode = settings.lockdown_mode; let lan_sharing = settings.allow_lan; let dns_content_blockers = settings .tunnel_options @@ -267,7 +267,7 @@ mod tests { If this is not true anymore, please update this test." ); - settings.block_when_disconnected = true; + settings.lockdown_mode = true; expected_indicators.0.insert(FeatureIndicator::LockdownMode); assert_eq!( diff --git a/mullvad-types/src/settings/mod.rs b/mullvad-types/src/settings/mod.rs index ae55fd3d79..1263089d8f 100644 --- a/mullvad-types/src/settings/mod.rs +++ b/mullvad-types/src/settings/mod.rs @@ -20,7 +20,7 @@ mod dns; /// latest version that exists in `SettingsVersion`. /// This should be bumped when a new version is introduced along with a migration /// being added to `mullvad-daemon`. -pub const CURRENT_SETTINGS_VERSION: SettingsVersion = SettingsVersion::V11; +pub const CURRENT_SETTINGS_VERSION: SettingsVersion = SettingsVersion::V12; #[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Copy)] #[repr(u32)] @@ -35,6 +35,7 @@ pub enum SettingsVersion { V9 = 9, V10 = 10, V11 = 11, + V12 = 12, } impl<'de> Deserialize<'de> for SettingsVersion { @@ -53,6 +54,7 @@ impl<'de> Deserialize<'de> for SettingsVersion { v if v == SettingsVersion::V9 as u32 => Ok(SettingsVersion::V9), v if v == SettingsVersion::V10 as u32 => Ok(SettingsVersion::V10), v if v == SettingsVersion::V11 as u32 => Ok(SettingsVersion::V11), + v if v == SettingsVersion::V12 as u32 => Ok(SettingsVersion::V12), v => Err(serde::de::Error::custom(format!( "{v} is not a valid SettingsVersion" ))), @@ -88,7 +90,7 @@ pub struct Settings { /// Extra level of kill switch. When this setting is on, the disconnected state will block /// the firewall to not allow any traffic in or out. #[cfg(not(target_os = "android"))] - pub block_when_disconnected: bool, + pub lockdown_mode: bool, /// If the daemon should connect the VPN tunnel directly on start or not. pub auto_connect: bool, /// Options that should be applied to tunnels of a specific type regardless of where the relays @@ -270,7 +272,7 @@ impl Default for Settings { api_access_methods: access_method::Settings::default(), allow_lan: false, #[cfg(not(target_os = "android"))] - block_when_disconnected: false, + lockdown_mode: false, auto_connect: false, tunnel_options: TunnelOptions::default(), relay_overrides: vec![], diff --git a/talpid-core/src/tunnel_state_machine/connected_state.rs b/talpid-core/src/tunnel_state_machine/connected_state.rs index 5076338653..b488c4dec0 100644 --- a/talpid-core/src/tunnel_state_machine/connected_state.rs +++ b/talpid-core/src/tunnel_state_machine/connected_state.rs @@ -337,8 +337,8 @@ impl ConnectedState { consequence } #[cfg(not(target_os = "android"))] - Some(TunnelCommand::BlockWhenDisconnected(block_when_disconnected, complete_tx)) => { - shared_values.block_when_disconnected = block_when_disconnected; + Some(TunnelCommand::LockdownMode(lockdown_mode, complete_tx)) => { + shared_values.lockdown_mode = lockdown_mode; let _ = complete_tx.send(()); SameState(self) } diff --git a/talpid-core/src/tunnel_state_machine/connecting_state.rs b/talpid-core/src/tunnel_state_machine/connecting_state.rs index 479238af60..f338a9b7c3 100644 --- a/talpid-core/src/tunnel_state_machine/connecting_state.rs +++ b/talpid-core/src/tunnel_state_machine/connecting_state.rs @@ -474,8 +474,8 @@ impl ConnectingState { consequence } #[cfg(not(target_os = "android"))] - Some(TunnelCommand::BlockWhenDisconnected(block_when_disconnected, complete_tx)) => { - shared_values.block_when_disconnected = block_when_disconnected; + Some(TunnelCommand::LockdownMode(lockdown_mode, complete_tx)) => { + shared_values.lockdown_mode = lockdown_mode; let _ = complete_tx.send(()); SameState(self) } diff --git a/talpid-core/src/tunnel_state_machine/disconnected_state.rs b/talpid-core/src/tunnel_state_machine/disconnected_state.rs index 024958bdc6..3d1e98830e 100644 --- a/talpid-core/src/tunnel_state_machine/disconnected_state.rs +++ b/talpid-core/src/tunnel_state_machine/disconnected_state.rs @@ -30,7 +30,7 @@ impl DisconnectedState { ); } #[cfg(target_os = "macos")] - if shared_values.block_when_disconnected.bool() { + if shared_values.lockdown_mode.bool() { if let Err(err) = Self::setup_local_dns_config(shared_values) { log::error!( "{}", @@ -64,7 +64,7 @@ impl DisconnectedState { // Being disconnected and having lockdown mode enabled implies that your internet // access is locked down #[cfg(not(target_os = "android"))] - locked_down: shared_values.block_when_disconnected.bool(), + locked_down: shared_values.lockdown_mode.bool(), }, ) } @@ -74,13 +74,13 @@ impl DisconnectedState { shared_values: &mut SharedTunnelStateValues, should_reset_firewall: bool, ) { - let result = if shared_values.block_when_disconnected.bool() { + let result = if shared_values.lockdown_mode.bool() { #[cfg(target_os = "windows")] { - // Respect the persist flag of BlockWhenDisconnected. + // Respect the persist flag of LockdownMode. shared_values .firewall - .persist(shared_values.block_when_disconnected.should_persist()); + .persist(shared_values.lockdown_mode.should_persist()); } let policy = FirewallPolicy::Blocked { @@ -118,7 +118,7 @@ impl DisconnectedState { shared_values: &mut SharedTunnelStateValues, should_reset_firewall: bool, ) { - if should_reset_firewall && !shared_values.block_when_disconnected.bool() { + if should_reset_firewall && !shared_values.lockdown_mode.bool() { if let Err(error) = shared_values.split_tunnel.clear_tunnel_addresses() { log::error!( "{}", @@ -193,9 +193,9 @@ impl TunnelState for DisconnectedState { SameState(self) } #[cfg(not(target_os = "android"))] - Some(TunnelCommand::BlockWhenDisconnected(block_when_disconnected, complete_tx)) => { - if shared_values.block_when_disconnected != block_when_disconnected { - shared_values.block_when_disconnected = block_when_disconnected; + Some(TunnelCommand::LockdownMode(lockdown_mode, complete_tx)) => { + if shared_values.lockdown_mode != lockdown_mode { + shared_values.lockdown_mode = lockdown_mode; // TODO: Investigate if we can simply return // `NewState(Self::enter(shared_values, true))`. @@ -206,7 +206,7 @@ impl TunnelState for DisconnectedState { #[cfg(windows)] Self::register_split_tunnel_addresses(shared_values, true); #[cfg(target_os = "macos")] - if block_when_disconnected.bool() { + if lockdown_mode.bool() { if let Err(err) = Self::setup_local_dns_config(shared_values) { log::error!( "{}", diff --git a/talpid-core/src/tunnel_state_machine/disconnecting_state.rs b/talpid-core/src/tunnel_state_machine/disconnecting_state.rs index a93be7b740..51807b76da 100644 --- a/talpid-core/src/tunnel_state_machine/disconnecting_state.rs +++ b/talpid-core/src/tunnel_state_machine/disconnecting_state.rs @@ -51,8 +51,8 @@ impl DisconnectingState { let _ = complete_tx.send(()); } #[cfg(not(target_os = "android"))] - Some(TunnelCommand::BlockWhenDisconnected(block_when_disconnected, complete_tx)) => { - shared_values.block_when_disconnected = block_when_disconnected; + Some(TunnelCommand::LockdownMode(lockdown_mode, complete_tx)) => { + shared_values.lockdown_mode = lockdown_mode; let _ = complete_tx.send(()); } Some(TunnelCommand::Connectivity(connectivity)) => { diff --git a/talpid-core/src/tunnel_state_machine/error_state.rs b/talpid-core/src/tunnel_state_machine/error_state.rs index 32e4747f47..e6f1008a73 100644 --- a/talpid-core/src/tunnel_state_machine/error_state.rs +++ b/talpid-core/src/tunnel_state_machine/error_state.rs @@ -177,8 +177,8 @@ impl TunnelState for ErrorState { consequence } #[cfg(not(target_os = "android"))] - Some(TunnelCommand::BlockWhenDisconnected(block_when_disconnected, complete_tx)) => { - shared_values.block_when_disconnected = block_when_disconnected; + Some(TunnelCommand::LockdownMode(lockdown_mode, complete_tx)) => { + shared_values.lockdown_mode = lockdown_mode; let _ = complete_tx.send(()); SameState(self) } diff --git a/talpid-core/src/tunnel_state_machine/mod.rs b/talpid-core/src/tunnel_state_machine/mod.rs index 0dd029cf86..16e15d08a0 100644 --- a/talpid-core/src/tunnel_state_machine/mod.rs +++ b/talpid-core/src/tunnel_state_machine/mod.rs @@ -95,7 +95,7 @@ pub struct InitialTunnelState { pub allow_lan: bool, /// Block traffic unless connected to the VPN. #[cfg(not(target_os = "android"))] - pub block_when_disconnected: BlockWhenDisconnected, + pub lockdown_mode: LockdownMode, /// DNS configuration to use pub dns_config: DnsConfig, /// A single endpoint that is allowed to communicate outside the tunnel, i.e. @@ -199,9 +199,9 @@ pub enum TunnelCommand { AllowEndpoint(AllowedEndpoint, oneshot::Sender<()>), /// Set DNS configuration to use. Dns(crate::dns::DnsConfig, oneshot::Sender<()>), - /// Enable or disable the block_when_disconnected feature. + /// Enable or disable the lockdown_mode feature. #[cfg(not(target_os = "android"))] - BlockWhenDisconnected(BlockWhenDisconnected, oneshot::Sender<()>), + LockdownMode(LockdownMode, oneshot::Sender<()>), /// Notify the state machine of the connectivity of the device. Connectivity(Connectivity), /// Open tunnel connection. @@ -236,12 +236,12 @@ enum EventResult { } /// If firewall should apply blocking rules in the disconnected state. -/// Argument of TunnelCommand::BlockWhenDisconnected message. +/// Argument of TunnelCommand::LockdownMode message. /// /// Semantically equivalent to a boolean value, but is grouped togetether with the persist /// parameter on Windows for cohesiveness. #[derive(Clone, Copy, Debug)] -pub enum BlockWhenDisconnected { +pub enum LockdownMode { /// Firewall should *not* apply blocking rules. Disabled, /// Firewall should apply blocking rules. @@ -251,28 +251,28 @@ pub enum BlockWhenDisconnected { }, } -impl BlockWhenDisconnected { +impl LockdownMode { /// `true`. Apply blocking firewall rules in the disconnected state. pub const fn yes() -> Self { - BlockWhenDisconnected::Enabled { persist: true } + LockdownMode::Enabled { persist: true } } /// `false`. Do *not* apply blocking firewall rules in the disconnected state. pub const fn no() -> Self { - BlockWhenDisconnected::Disabled + LockdownMode::Disabled } /// [self] as a boolean value. pub const fn bool(&self) -> bool { - matches!(self, BlockWhenDisconnected::Enabled { .. }) + matches!(self, LockdownMode::Enabled { .. }) } - /// If [BlockWhenDisconnected] should persist across reboots. + /// If [LockdownMode] should persist across reboots. /// /// Semantically meaningless on non-Windows platforms, will always return true. pub const fn should_persist(&self) -> bool { if cfg!(target_os = "windows") { - matches!(&self, BlockWhenDisconnected::Enabled { persist: true }) + matches!(&self, LockdownMode::Enabled { persist: true }) } else { true } @@ -288,24 +288,24 @@ impl BlockWhenDisconnected { #[cfg(target_os = "windows")] pub fn persist(self, persist: bool) -> Self { match self { - BlockWhenDisconnected::Disabled => BlockWhenDisconnected::Disabled, + LockdownMode::Disabled => LockdownMode::Disabled, // Forget previous value of persist - BlockWhenDisconnected::Enabled { .. } => BlockWhenDisconnected::Enabled { persist }, + LockdownMode::Enabled { .. } => LockdownMode::Enabled { persist }, } } } -impl From<bool> for BlockWhenDisconnected { +impl From<bool> for LockdownMode { fn from(block: bool) -> Self { if block { - BlockWhenDisconnected::yes() + LockdownMode::yes() } else { - BlockWhenDisconnected::no() + LockdownMode::no() } } } -impl PartialEq for BlockWhenDisconnected { +impl PartialEq for LockdownMode { fn eq(&self, other: &Self) -> bool { self.bool() == other.bool() } @@ -385,9 +385,7 @@ impl TunnelStateMachine { let fw_args = FirewallArguments { #[cfg(not(target_os = "android"))] - initial_state: if args.settings.block_when_disconnected.bool() - || !args.settings.reset_firewall - { + initial_state: if args.settings.lockdown_mode.bool() || !args.settings.reset_firewall { InitialFirewallState::Blocked(args.settings.allowed_endpoint.clone()) } else { InitialFirewallState::None @@ -470,7 +468,7 @@ impl TunnelStateMachine { _offline_monitor: offline_monitor, allow_lan: args.settings.allow_lan, #[cfg(not(target_os = "android"))] - block_when_disconnected: args.settings.block_when_disconnected, + lockdown_mode: args.settings.lockdown_mode, connectivity, dns_config: args.settings.dns_config, allowed_endpoint: args.settings.allowed_endpoint, @@ -565,7 +563,7 @@ struct SharedTunnelStateValues { allow_lan: bool, /// Should network access be allowed when in the disconnected state. #[cfg(not(target_os = "android"))] - block_when_disconnected: BlockWhenDisconnected, + lockdown_mode: LockdownMode, /// True when the computer is known to be offline. connectivity: Connectivity, /// DNS configuration to use. diff --git a/test/test-manager/src/tests/settings.rs b/test/test-manager/src/tests/settings.rs index bc7ad3287d..d07a55c5c9 100644 --- a/test/test-manager/src/tests/settings.rs +++ b/test/test-manager/src/tests/settings.rs @@ -112,7 +112,7 @@ pub async fn test_lockdown( // Enable lockdown mode // mullvad_client - .set_block_when_disconnected(true) + .set_lockdown_mode(true) .await .expect("failed to enable lockdown mode"); diff --git a/test/test-manager/src/tests/windows.rs b/test/test-manager/src/tests/windows.rs index e857b6901f..0923825969 100644 --- a/test/test-manager/src/tests/windows.rs +++ b/test/test-manager/src/tests/windows.rs @@ -22,7 +22,7 @@ async fn test_clearing_blocked_state_on_failed_upgrade( { let settings = mullvad_client.get_settings().await?; ensure!( - !settings.block_when_disconnected, + !settings.lockdown_mode, "Block when disconnected should be disabled" ); ensure!(!settings.auto_connect, "Auto connect should be disabled"); @@ -88,10 +88,10 @@ async fn test_not_clearing_blocked_state_on_failed_upgrade_with_lockdown_mode( // Make sure that lockdown mode is enabled. // If it is not, then blocking firewall rules *will not* persist after a reboot. { - mullvad_client.set_block_when_disconnected(true).await?; + mullvad_client.set_lockdown_mode(true).await?; let settings = mullvad_client.get_settings().await?; ensure!( - settings.block_when_disconnected, + settings.lockdown_mode, "Block when disconnected should be enabled" ); ensure!(!settings.auto_connect, "Auto connect should be disabled"); |
