Skip to content

Commit

Permalink
Add setting to leak traffic to apple networks
Browse files Browse the repository at this point in the history
Co-authored-by: David Lönnhager <david.l@mullvad.net>
  • Loading branch information
hulthe and dlon committed Sep 25, 2024
1 parent 78c7edb commit d0b2b24
Show file tree
Hide file tree
Showing 29 changed files with 415 additions and 8 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ Line wrap the file at 100 chars. Th
#### Windows
- Add experimental support for Windows ARM64.

#### macOS
- Add "Apple services bypass" toggle that let's users unblock certain Apple-owned networks.
This is a temporary fix to the MacOS 15 issues where some Apple services are being blocked.

### Changed
- Never use OpenVPN as a fallback protocol when any of the following features is enabled:
multihop, quantum-resistant tunnels, or DAITA.
Expand Down
20 changes: 20 additions & 0 deletions gui/locales/messages.pot
Original file line number Diff line number Diff line change
Expand Up @@ -1527,10 +1527,30 @@ msgctxt "settings-view"
msgid "App version"
msgstr ""

msgctxt "settings-view"
msgid "Apple services bypass"
msgstr ""

msgctxt "settings-view"
msgid "Attention: this traffic will go outside of the VPN tunnel. Any application that tries to can bypass the VPN tunnel and send traffic to these Apple networks."
msgstr ""

msgctxt "settings-view"
msgid "Enabling this setting allows traffic to specific Apple-owned networks to go outside of the VPN tunnel, allowing services like iMessage and FaceTime to work whilst using Mullvad."
msgstr ""

msgctxt "settings-view"
msgid "Some Apple services have an issue where the network settings set by Mullvad get ignored, this in turn blocks certain apps."
msgstr ""

msgctxt "settings-view"
msgid "Support"
msgstr ""

msgctxt "settings-view"
msgid "This a temporary fix and we are currently working on a long-term solution."
msgstr ""

msgctxt "settings-view"
msgid "Update available. Install the latest app version to stay up to date."
msgstr ""
Expand Down
4 changes: 4 additions & 0 deletions gui/src/main/daemon-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,10 @@ export class DaemonRpc {
await this.call<grpcTypes.DnsOptions, Empty>(this.client.setDnsOptions, dnsOptions);
}

public async setAppleServicesBypass(enabled: boolean): Promise<void> {
await this.callBool<Empty>(this.client.setAppleServicesBypass, enabled);
}

public async getVersionInfo(): Promise<IAppVersionInfo> {
const response = await this.callEmpty<grpcTypes.AppVersionInfo>(this.client.getVersionInfo);
return response.toObject();
Expand Down
1 change: 1 addition & 0 deletions gui/src/main/default-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ export function getDefaultSettings(): ISettings {
customLists: [],
apiAccessMethods: getDefaultApiAccessMethods(),
relayOverrides: [],
appleServicesBypass: false,
};
}

Expand Down
3 changes: 2 additions & 1 deletion gui/src/main/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ import NotificationController, {
NotificationControllerDelegate,
NotificationSender,
} from './notification-controller';
import { isMacOs13OrNewer } from './platform-version';
import { isMacOs13OrNewer, isMacOs14p6OrNewer } from './platform-version';
import * as problemReport from './problem-report';
import { resolveBin } from './proc';
import ReconnectionBackoff from './reconnection-backoff';
Expand Down Expand Up @@ -774,6 +774,7 @@ class ApplicationMain
navigationHistory: this.navigationHistory,
currentApiAccessMethod: this.currentApiAccessMethod,
isMacOs13OrNewer: isMacOs13OrNewer(),
isMacOs14p6OrNewer: isMacOs14p6OrNewer(),
}));

IpcMainEventChannel.map.handleGetData(async () => ({
Expand Down
15 changes: 11 additions & 4 deletions gui/src/main/platform-version.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,31 @@
import os from 'os';

export function isMacOs11OrNewer() {
export function isMacOs11OrNewer(): boolean {
const [major] = parseVersion();
return process.platform === 'darwin' && major >= 20;
}

export function isMacOs13OrNewer() {
export function isMacOs13OrNewer(): boolean {
const [major] = parseVersion();
return process.platform === 'darwin' && major >= 22;
}

export function isMacOs14p6OrNewer(): boolean {
const [major, minor] = parseVersion();
const darwin24 = major >= 24;
const darwin236 = major == 23 && minor >= 6; // 23.6 is used by macOS 14.6
return process.platform === 'darwin' && (darwin236 || darwin24);
}

// Windows 11 has the internal version 10.0.22000+.
export function isWindows11OrNewer() {
export function isWindows11OrNewer(): boolean {
const [major, minor, patch] = parseVersion();
return (
process.platform === 'win32' && (major > 10 || (major === 10 && (minor > 0 || patch >= 22000)))
);
}

function parseVersion() {
function parseVersion(): number[] {
return os
.release()
.split('.')
Expand Down
6 changes: 6 additions & 0 deletions gui/src/main/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ export default class Settings implements Readonly<ISettings> {
IpcMainEventChannel.settings.handleSetDnsOptions((dns) => {
return this.daemonRpc.setDnsOptions(dns);
});
IpcMainEventChannel.settings.handleSetAppleServicesBypass((enabled) => {
return this.daemonRpc.setAppleServicesBypass(enabled);
});
IpcMainEventChannel.autoStart.handleSet((autoStart: boolean) => {
return this.setAutoStart(autoStart);
});
Expand Down Expand Up @@ -187,6 +190,9 @@ export default class Settings implements Readonly<ISettings> {
public get relayOverrides() {
return this.settingsValue.relayOverrides;
}
public get appleServicesBypass() {
return this.settingsValue.appleServicesBypass;
}

public get gui() {
return this.guiSettings;
Expand Down
4 changes: 4 additions & 0 deletions gui/src/renderer/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ export default class AppRenderer {
this.setChangelog(initialState.changelog, initialState.forceShowChanges);
this.setCurrentApiAccessMethod(initialState.currentApiAccessMethod);
this.reduxActions.userInterface.setIsMacOs13OrNewer(initialState.isMacOs13OrNewer);
this.reduxActions.userInterface.setIsMacOs14p6OrNewer(initialState.isMacOs14p6OrNewer);

if (initialState.macOsScrollbarVisibility !== undefined) {
this.reduxActions.userInterface.setMacOsScrollbarVisibility(
Expand Down Expand Up @@ -321,6 +322,8 @@ export default class AppRenderer {
IpcRendererEventChannel.settings.updateBridgeSettings(bridgeSettings);
public setDnsOptions = (dnsOptions: IDnsOptions) =>
IpcRendererEventChannel.settings.setDnsOptions(dnsOptions);
public setAppleServicesBypass = (enabled: boolean) =>
IpcRendererEventChannel.settings.setAppleServicesBypass(enabled);
public clearAccountHistory = () => IpcRendererEventChannel.accountHistory.clear();
public setAutoConnect = (value: boolean) =>
IpcRendererEventChannel.guiSettings.setAutoConnect(value);
Expand Down Expand Up @@ -826,6 +829,7 @@ export default class AppRenderer {
reduxSettings.updateWireguardDaita(newSettings.tunnelOptions.wireguard.daita);
reduxSettings.updateBridgeState(newSettings.bridgeState);
reduxSettings.updateDnsOptions(newSettings.tunnelOptions.dns);
reduxSettings.updateAppleServicesBypass(newSettings.appleServicesBypass);
reduxSettings.updateSplitTunnelingState(newSettings.splitTunnel.enableExclusions);
reduxSettings.updateObfuscationSettings(newSettings.obfuscationSettings);
reduxSettings.updateCustomLists(newSettings.customLists);
Expand Down
68 changes: 67 additions & 1 deletion gui/src/renderer/components/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,20 @@ import { useAppContext } from '../context';
import { useHistory } from '../lib/history';
import { RoutePath } from '../lib/routes';
import { useSelector } from '../redux/store';
import { AriaDescribed, AriaDescription, AriaDescriptionGroup } from './AriaGroup';
import {
AriaDescribed,
AriaDescription,
AriaDescriptionGroup,
AriaDetails,
AriaInput,
AriaInputGroup,
AriaLabel,
} from './AriaGroup';
import * as Cell from './cell';
import InfoButton from './InfoButton';
import { BackAction } from './KeyboardNavigation';
import { Layout, SettingsContainer } from './Layout';
import { ModalMessage } from './Modal';
import { NavigationBar, NavigationContainer, NavigationItems, TitleBarItem } from './NavigationBar';
import SettingsHeader, { HeaderTitle } from './SettingsHeader';
import {
Expand All @@ -28,6 +38,8 @@ export default function Support() {
const connectedToDaemon = useSelector((state) => state.userInterface.connectedToDaemon);
const isMacOs13OrNewer = useSelector((state) => state.userInterface.isMacOs13OrNewer);

const isMacOs14p6OrNewer = useSelector((state) => state.userInterface.isMacOs14p6OrNewer);

const showSubSettings = loginState.type === 'ok' && connectedToDaemon;
const showSplitTunneling = window.env.platform !== 'darwin' || isMacOs13OrNewer;

Expand Down Expand Up @@ -77,6 +89,12 @@ export default function Support() {
<ApiAccessMethodsButton />
</Cell.Group>

{isMacOs14p6OrNewer ? (
<Cell.Group>
<AppleServicesBypass />
</Cell.Group>
) : null}

<Cell.Group>
<SupportButton />
<AppVersionButton />
Expand Down Expand Up @@ -227,6 +245,54 @@ function SupportButton() {
);
}

function AppleServicesBypass() {
const { setAppleServicesBypass } = useAppContext();
const appleServicesBypass = useSelector((state) => state.settings.appleServicesBypass);

return (
<AriaInputGroup>
<Cell.Container>
<AriaLabel>
<Cell.InputLabel>
{messages.pgettext('settings-view', 'Apple services bypass')}
</Cell.InputLabel>
</AriaLabel>
<AriaDetails>
<InfoButton>
<ModalMessage>
{messages.pgettext(
'settings-view',
'Some Apple services have an issue where the network settings set by Mullvad get ignored, this in turn blocks certain apps.',
)}
</ModalMessage>
<ModalMessage>
{messages.pgettext(
'settings-view',
'Enabling this setting allows traffic to specific Apple-owned networks to go outside of the VPN tunnel, allowing services like iMessage and FaceTime to work whilst using Mullvad.',
)}
</ModalMessage>
<ModalMessage>
{messages.pgettext(
'settings-view',
'Attention: this traffic will go outside of the VPN tunnel. Any application that tries to can bypass the VPN tunnel and send traffic to these Apple networks.',
)}
</ModalMessage>
<ModalMessage>
{messages.pgettext(
'settings-view',
'This a temporary fix and we are currently working on a long-term solution.',
)}
</ModalMessage>
</InfoButton>
</AriaDetails>
<AriaInput>
<Cell.Switch isOn={appleServicesBypass} onChange={setAppleServicesBypass} />
</AriaInput>
</Cell.Container>
</AriaInputGroup>
);
}

function DebugButton() {
const history = useHistory();
const navigate = useCallback(() => history.push(RoutePath.debug), [history]);
Expand Down
14 changes: 14 additions & 0 deletions gui/src/renderer/redux/settings/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ export interface IUpdateDnsOptionsAction {
dns: IDnsOptions;
}

export interface ISetAppleServicesBypass {
type: 'SET_APPLE_SERVICES_BYPASS';
enabled: boolean;
}

export interface IUpdateSplitTunnelingStateAction {
type: 'UPDATE_SPLIT_TUNNELING_STATE';
enabled: boolean;
Expand Down Expand Up @@ -145,6 +150,7 @@ export type SettingsAction =
| IUpdateWireguardDaitaAction
| IUpdateAutoStartAction
| IUpdateDnsOptionsAction
| ISetAppleServicesBypass
| IUpdateSplitTunnelingStateAction
| ISetSplitTunnelingApplicationsAction
| ISetObfuscationSettings
Expand Down Expand Up @@ -273,6 +279,13 @@ function updateDnsOptions(dns: IDnsOptions): IUpdateDnsOptionsAction {
};
}

function updateAppleServicesBypass(enabled: boolean): ISetAppleServicesBypass {
return {
type: 'SET_APPLE_SERVICES_BYPASS',
enabled,
};
}

function updateSplitTunnelingState(enabled: boolean): IUpdateSplitTunnelingStateAction {
return {
type: 'UPDATE_SPLIT_TUNNELING_STATE',
Expand Down Expand Up @@ -343,6 +356,7 @@ export default {
updateWireguardDaita,
updateAutoStart,
updateDnsOptions,
updateAppleServicesBypass,
updateSplitTunnelingState,
setSplitTunnelingApplications,
updateObfuscationSettings,
Expand Down
8 changes: 8 additions & 0 deletions gui/src/renderer/redux/settings/reducers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ export interface ISettingsReduxState {
daita?: IDaitaSettings;
};
dns: IDnsOptions;
appleServicesBypass: boolean;
splitTunneling: boolean;
splitTunnelingApplications: ISplitTunnelingApplication[];
obfuscationSettings: ObfuscationSettings;
Expand Down Expand Up @@ -181,6 +182,7 @@ const initialState: ISettingsReduxState = {
addresses: [],
},
},
appleServicesBypass: false,
splitTunneling: false,
splitTunnelingApplications: [],
obfuscationSettings: {
Expand Down Expand Up @@ -310,6 +312,12 @@ export default function (
dns: action.dns,
};

case 'SET_APPLE_SERVICES_BYPASS':
return {
...state,
appleServicesBypass: action.enabled,
};

case 'UPDATE_SPLIT_TUNNELING_STATE':
return {
...state,
Expand Down
16 changes: 15 additions & 1 deletion gui/src/renderer/redux/userinterface/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ export interface ISetIsMacOs13OrNewer {
isMacOs13OrNewer: boolean;
}

export interface ISetIsMacOs14p6OrNewer {
type: 'SET_IS_MACOS14_6_OR_NEWER';
isMacOs14p6OrNewer: boolean;
}

export type UserInterfaceAction =
| IUpdateLocaleAction
| IUpdateWindowArrowPositionAction
Expand All @@ -73,7 +78,8 @@ export type UserInterfaceAction =
| ISetForceShowChanges
| ISetIsPerformingPostUpgrade
| ISetSelectLocationView
| ISetIsMacOs13OrNewer;
| ISetIsMacOs13OrNewer
| ISetIsMacOs14p6OrNewer;

function updateLocale(locale: string): IUpdateLocaleAction {
return {
Expand Down Expand Up @@ -160,6 +166,13 @@ function setIsMacOs13OrNewer(isMacOs13OrNewer: boolean): ISetIsMacOs13OrNewer {
};
}

function setIsMacOs14p6OrNewer(isMacOs14p6OrNewer: boolean): ISetIsMacOs14p6OrNewer {
return {
type: 'SET_IS_MACOS14_6_OR_NEWER',
isMacOs14p6OrNewer,
};
}

export default {
updateLocale,
updateWindowArrowPosition,
Expand All @@ -173,4 +186,5 @@ export default {
setIsPerformingPostUpgrade,
setSelectLocationView,
setIsMacOs13OrNewer,
setIsMacOs14p6OrNewer,
};
8 changes: 8 additions & 0 deletions gui/src/renderer/redux/userinterface/reducers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export interface IUserInterfaceReduxState {
isPerformingPostUpgrade: boolean;
selectLocationView: LocationType;
isMacOs13OrNewer: boolean;
isMacOs14p6OrNewer: boolean;
}

const initialState: IUserInterfaceReduxState = {
Expand All @@ -30,6 +31,7 @@ const initialState: IUserInterfaceReduxState = {
isPerformingPostUpgrade: false,
selectLocationView: LocationType.exit,
isMacOs13OrNewer: true,
isMacOs14p6OrNewer: true,
};

export default function (
Expand Down Expand Up @@ -88,6 +90,12 @@ export default function (
isMacOs13OrNewer: action.isMacOs13OrNewer,
};

case 'SET_IS_MACOS14_6_OR_NEWER':
return {
...state,
isMacOs14p6OrNewer: action.isMacOs14p6OrNewer,
};

default:
return state;
}
Expand Down
Loading

0 comments on commit d0b2b24

Please sign in to comment.