summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJoakim Hulthe <joakim.hulthe@mullvad.net>2024-09-17 11:55:02 +0200
committerJoakim Hulthe <joakim.hulthe@mullvad.net>2024-09-17 11:55:02 +0200
commitf0d4d64d1042248b255b3b7289bb4073088327cc (patch)
tree59337b2a334557cba533858b27a9c58eaa5ad055
parent13af28efa7f73843327b017234640d2c58bbbeea (diff)
parentd77bd4d9d9412aab5705d949570f419f03415734 (diff)
downloadmullvadvpn-f0d4d64d1042248b255b3b7289bb4073088327cc.tar.xz
mullvadvpn-f0d4d64d1042248b255b3b7289bb4073088327cc.zip
Merge branch 'implement-use-anywhere-daita-setting-des-1068'
-rw-r--r--android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt1
-rw-r--r--gui/locales/messages.pot38
-rw-r--r--gui/src/main/daemon-rpc.ts16
-rw-r--r--gui/src/main/settings.ts7
-rw-r--r--gui/src/renderer/app.tsx7
-rw-r--r--gui/src/renderer/components/AppRouter.tsx2
-rw-r--r--gui/src/renderer/components/DaitaSettings.tsx193
-rw-r--r--gui/src/renderer/components/WireguardSettings.tsx90
-rw-r--r--gui/src/renderer/components/main-view/FeatureIndicators.tsx86
-rw-r--r--gui/src/renderer/components/select-location/RelayListContext.tsx5
-rw-r--r--gui/src/renderer/components/select-location/SelectLocation.tsx4
-rw-r--r--gui/src/renderer/lib/filter-locations.ts6
-rw-r--r--gui/src/renderer/lib/routes.ts1
-rw-r--r--gui/src/shared/daemon-rpc-types.ts2
-rw-r--r--gui/src/shared/ipc-schema.ts4
-rw-r--r--mullvad-api/src/relay_list.rs2
-rw-r--r--mullvad-cli/build.rs7
-rw-r--r--mullvad-cli/src/cmds/relay.rs24
-rw-r--r--mullvad-cli/src/cmds/tunnel.rs26
-rw-r--r--mullvad-daemon/src/lib.rs94
-rw-r--r--mullvad-daemon/src/management_interface.rs30
-rw-r--r--mullvad-daemon/src/tunnel.rs4
-rw-r--r--mullvad-management-interface/proto/management_interface.proto8
-rw-r--r--mullvad-management-interface/src/client.rs15
-rw-r--r--mullvad-management-interface/src/types/conversions/features.rs2
-rw-r--r--mullvad-management-interface/src/types/conversions/relay_list.rs34
-rw-r--r--mullvad-management-interface/src/types/conversions/wireguard.rs2
-rw-r--r--mullvad-relay-selector/src/relay_selector/mod.rs124
-rw-r--r--mullvad-relay-selector/src/relay_selector/parsed_relays.rs4
-rw-r--r--mullvad-relay-selector/src/relay_selector/query.rs40
-rw-r--r--mullvad-relay-selector/tests/relay_selector.rs84
-rw-r--r--mullvad-types/src/features.rs52
-rw-r--r--mullvad-types/src/location.rs5
-rw-r--r--mullvad-types/src/relay_constraints.rs21
-rw-r--r--mullvad-types/src/relay_list.rs11
-rw-r--r--mullvad-types/src/wireguard.rs3
-rw-r--r--test/test-manager/src/tests/daita.rs200
-rw-r--r--test/test-manager/src/tests/dns.rs2
-rw-r--r--test/test-manager/src/tests/helpers.rs27
-rw-r--r--test/test-manager/src/tests/mod.rs6
-rw-r--r--test/test-manager/src/tests/relay_ip_overrides.rs7
-rw-r--r--test/test-manager/src/tests/tunnel.rs32
42 files changed, 1041 insertions, 287 deletions
diff --git a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt
index 1ff297312b..a1020b71d0 100644
--- a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt
+++ b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt
@@ -609,6 +609,7 @@ internal fun ManagementInterface.FeatureIndicator.toDomain() =
FeatureIndicator.SERVER_IP_OVERRIDE
ManagementInterface.FeatureIndicator.CUSTOM_MTU -> FeatureIndicator.CUSTOM_MTU
ManagementInterface.FeatureIndicator.DAITA -> FeatureIndicator.DAITA
+ ManagementInterface.FeatureIndicator.DAITA_SMART_ROUTING,
ManagementInterface.FeatureIndicator.LOCKDOWN_MODE,
ManagementInterface.FeatureIndicator.SHADOWSOCKS,
ManagementInterface.FeatureIndicator.MULTIHOP,
diff --git a/gui/locales/messages.pot b/gui/locales/messages.pot
index 5969e4ffd3..730b75aaca 100644
--- a/gui/locales/messages.pot
+++ b/gui/locales/messages.pot
@@ -5,6 +5,11 @@ msgstr ""
msgid "%(amount)d more..."
msgstr ""
+#. This refers to the Smart Routing setting in the VPN settings view.
+#. This is displayed when both Smart Routing and DAITA features are on.
+msgid "%(daita)s: Smart routing"
+msgstr ""
+
msgid "%(duration)s was added, account paid until %(expiry)s."
msgstr ""
@@ -125,6 +130,9 @@ msgstr ""
msgid "Disable"
msgstr ""
+msgid "Disable anyway"
+msgstr ""
+
msgid "Disconnect"
msgstr ""
@@ -245,6 +253,9 @@ msgstr ""
msgid "Settings"
msgstr ""
+msgid "Smart routing"
+msgstr ""
+
msgid "System default"
msgstr ""
@@ -1935,6 +1946,10 @@ msgid "IPv4 is always enabled and the majority of websites and applications use
msgstr ""
msgctxt "vpn-settings-view"
+msgid "Is automatically enabled with %(daita)s, makes it possible to use %(daita)s with any server by using multihop. This might increase latency."
+msgstr ""
+
+msgctxt "vpn-settings-view"
msgid "It does this by allowing network communication outside the tunnel to local multicast and broadcast ranges as well as to and from these private IP ranges:"
msgstr ""
@@ -2065,7 +2080,7 @@ msgid "%(wireguard)s settings"
msgstr ""
msgctxt "wireguard-settings-view"
-msgid "Attention: Since this increases your total network traffic, be cautious if you have a limited data plan. It can also negatively impact your network speed. Please consider this if you want to enable %(daita)s."
+msgid "Hides patterns in your encrypted VPN traffic. Since this increases your total network traffic, be cautious if you have a limited data plan. It can also negatively impact your network speed and battery usage."
msgstr ""
msgctxt "wireguard-settings-view"
@@ -2080,6 +2095,15 @@ msgctxt "wireguard-settings-view"
msgid "MTU"
msgstr ""
+#. Warning text in a dialog that is displayed after a setting is toggled.
+msgctxt "wireguard-settings-view"
+msgid "Not all our servers are %(daita)s-enabled. In order to use the internet, you might have to select a new location after disabling, or you can continue using %(daita)s with Smart routing."
+msgstr ""
+
+msgctxt "wireguard-settings-view"
+msgid "Not all our servers are %(daita)s-enabled. Smart routing allows %(daita)s to be used at any location. It does this by using multihop in the background to route your traffic via the closest %(daita)s-enabled server first."
+msgstr ""
+
msgctxt "wireguard-settings-view"
msgid "Obfuscation"
msgstr ""
@@ -2134,11 +2158,6 @@ msgctxt "wireguard-settings-view"
msgid "This allows access to %(wireguard)s for devices that only support IPv6."
msgstr ""
-#. Warning text in a dialog that is displayed after a setting is toggled.
-msgctxt "wireguard-settings-view"
-msgid "This feature isn’t available on all servers. You might need to change location after enabling."
-msgstr ""
-
msgctxt "wireguard-settings-view"
msgid "This feature makes the WireGuard tunnel resistant to potential attacks from quantum computers."
msgstr ""
@@ -2151,6 +2170,10 @@ msgctxt "wireguard-settings-view"
msgid "UDP-over-TCP port"
msgstr ""
+msgctxt "wireguard-settings-view"
+msgid "Use Smart routing"
+msgstr ""
+
#. Text describing the valid port range for a port selector.
msgctxt "wireguard-settings-view"
msgid "Valid range: %(min)s - %(max)s"
@@ -2541,6 +2564,9 @@ msgstr ""
msgid "This address has already been entered."
msgstr ""
+msgid "This feature isn’t available on all servers. You might need to change location after enabling."
+msgstr ""
+
msgid "This field is required"
msgstr ""
diff --git a/gui/src/main/daemon-rpc.ts b/gui/src/main/daemon-rpc.ts
index c85189cea7..31f6ddcfab 100644
--- a/gui/src/main/daemon-rpc.ts
+++ b/gui/src/main/daemon-rpc.ts
@@ -38,7 +38,6 @@ import {
IAppVersionInfo,
IBridgeConstraints,
ICustomList,
- IDaitaSettings,
IDevice,
IDeviceRemoval,
IDnsOptions,
@@ -586,13 +585,12 @@ export class DaemonRpc {
await this.callBool(this.client.prepareRestartV2, quit);
}
- public async setDaitaSettings(daitaSettings: IDaitaSettings): Promise<void> {
- const grpcDaitaSettings = new grpcTypes.DaitaSettings();
- grpcDaitaSettings.setEnabled(daitaSettings.enabled);
- await this.call<grpcTypes.DaitaSettings, Empty>(
- this.client.setDaitaSettings,
- grpcDaitaSettings,
- );
+ public async setEnableDaita(value: boolean): Promise<void> {
+ await this.callBool(this.client.setEnableDaita, value);
+ }
+
+ public async setDaitaSmartRouting(value: boolean): Promise<void> {
+ await this.callBool(this.client.setDaitaSmartRouting, value);
}
public async listDevices(accountToken: AccountToken): Promise<Array<IDevice>> {
@@ -1182,6 +1180,8 @@ function convertFromFeatureIndicator(
return FeatureIndicator.customMssFix;
case grpcTypes.FeatureIndicator.DAITA:
return FeatureIndicator.daita;
+ case grpcTypes.FeatureIndicator.DAITA_SMART_ROUTING:
+ return FeatureIndicator.daitaSmartRouting;
case grpcTypes.FeatureIndicator.SHADOWSOCKS:
return FeatureIndicator.shadowsocks;
}
diff --git a/gui/src/main/settings.ts b/gui/src/main/settings.ts
index 22238c72c4..03537ba581 100644
--- a/gui/src/main/settings.ts
+++ b/gui/src/main/settings.ts
@@ -107,8 +107,11 @@ export default class Settings implements Readonly<ISettings> {
const settings = await fs.readFile(path);
return this.daemonRpc.applyJsonSettings(settings.toString());
});
- IpcMainEventChannel.settings.handleSetDaitaSettings((daitaSettings) => {
- return this.daemonRpc.setDaitaSettings(daitaSettings);
+ IpcMainEventChannel.settings.handleSetEnableDaita((value) => {
+ return this.daemonRpc.setEnableDaita(value);
+ });
+ IpcMainEventChannel.settings.handleSetDaitaSmartRouting((value) => {
+ return this.daemonRpc.setDaitaSmartRouting(value);
});
IpcMainEventChannel.guiSettings.handleSetEnableSystemNotifications((flag: boolean) => {
diff --git a/gui/src/renderer/app.tsx b/gui/src/renderer/app.tsx
index a4c76aa2d1..a4d5fc2fad 100644
--- a/gui/src/renderer/app.tsx
+++ b/gui/src/renderer/app.tsx
@@ -19,7 +19,6 @@ import {
IAccountData,
IAppVersionInfo,
ICustomList,
- IDaitaSettings,
IDevice,
IDeviceRemoval,
IDnsOptions,
@@ -345,8 +344,10 @@ export default class AppRenderer {
IpcRendererEventChannel.splitTunneling.forgetManuallyAddedApplication(application);
public setObfuscationSettings = (obfuscationSettings: ObfuscationSettings) =>
IpcRendererEventChannel.settings.setObfuscationSettings(obfuscationSettings);
- public setDaitaSettings = (daitaSettings: IDaitaSettings) =>
- IpcRendererEventChannel.settings.setDaitaSettings(daitaSettings);
+ public setEnableDaita = (value: boolean) =>
+ IpcRendererEventChannel.settings.setEnableDaita(value);
+ public setDaitaSmartRouting = (value: boolean) =>
+ IpcRendererEventChannel.settings.setDaitaSmartRouting(value);
public collectProblemReport = (toRedact: string | undefined) =>
IpcRendererEventChannel.problemReport.collectLogs(toRedact);
public viewLog = (path: string) => IpcRendererEventChannel.problemReport.viewLog(path);
diff --git a/gui/src/renderer/components/AppRouter.tsx b/gui/src/renderer/components/AppRouter.tsx
index e90729507c..75b8df936c 100644
--- a/gui/src/renderer/components/AppRouter.tsx
+++ b/gui/src/renderer/components/AppRouter.tsx
@@ -8,6 +8,7 @@ import { ITransitionSpecification, transitions, useHistory } from '../lib/histor
import { RoutePath } from '../lib/routes';
import Account from './Account';
import ApiAccessMethods from './ApiAccessMethods';
+import DaitaSettings from './DaitaSettings';
import Debug from './Debug';
import { DeviceRevokedView } from './DeviceRevokedView';
import { EditApiAccessMethod } from './EditApiAccessMethod';
@@ -85,6 +86,7 @@ export default function AppRouter() {
<Route exact path={RoutePath.userInterfaceSettings} component={UserInterfaceSettings} />
<Route exact path={RoutePath.vpnSettings} component={VpnSettings} />
<Route exact path={RoutePath.wireguardSettings} component={WireguardSettings} />
+ <Route exact path={RoutePath.daitaSettings} component={DaitaSettings} />
<Route exact path={RoutePath.udpOverTcp} component={UdpOverTcp} />
<Route exact path={RoutePath.shadowsocks} component={Shadowsocks} />
<Route exact path={RoutePath.openVpnSettings} component={OpenVpnSettings} />
diff --git a/gui/src/renderer/components/DaitaSettings.tsx b/gui/src/renderer/components/DaitaSettings.tsx
new file mode 100644
index 0000000000..3176192ad1
--- /dev/null
+++ b/gui/src/renderer/components/DaitaSettings.tsx
@@ -0,0 +1,193 @@
+import { useCallback } from 'react';
+import { sprintf } from 'sprintf-js';
+import styled from 'styled-components';
+
+import { strings } from '../../config.json';
+import { messages } from '../../shared/gettext';
+import { useAppContext } from '../context';
+import { useHistory } from '../lib/history';
+import { useBoolean } from '../lib/utilityHooks';
+import { useSelector } from '../redux/store';
+import { AriaDescription, AriaInput, AriaInputGroup, AriaLabel } from './AriaGroup';
+import * as Cell from './cell';
+import InfoButton from './InfoButton';
+import { BackAction } from './KeyboardNavigation';
+import { Layout, SettingsContainer } from './Layout';
+import { ModalAlert, ModalAlertType, ModalMessage } from './Modal';
+import {
+ NavigationBar,
+ NavigationContainer,
+ NavigationInfoButton,
+ NavigationItems,
+ NavigationScrollbars,
+ TitleBarItem,
+} from './NavigationBar';
+import SettingsHeader, { HeaderSubTitle, HeaderTitle } from './SettingsHeader';
+import { SmallButton, SmallButtonColor } from './SmallButton';
+
+const StyledContent = styled.div({
+ display: 'flex',
+ flexDirection: 'column',
+ flex: 1,
+ marginBottom: '2px',
+});
+
+export default function DaitaSettings() {
+ const { pop } = useHistory();
+
+ return (
+ <BackAction action={pop}>
+ <Layout>
+ <SettingsContainer>
+ <NavigationContainer>
+ <NavigationBar>
+ <NavigationItems>
+ <TitleBarItem>{strings.daita}</TitleBarItem>
+
+ <NavigationInfoButton>
+ <ModalMessage>
+ {sprintf(
+ messages.pgettext(
+ 'wireguard-settings-view',
+ '%(daita)s (%(daitaFull)s) hides patterns in your encrypted VPN traffic. If anyone is monitoring your connection, this makes it significantly harder for them to identify what websites you are visiting. It does this by carefully adding network noise and making all network packets the same size.',
+ ),
+ { daita: strings.daita, daitaFull: strings.daitaFull },
+ )}
+ </ModalMessage>
+ </NavigationInfoButton>
+ </NavigationItems>
+ </NavigationBar>
+
+ <NavigationScrollbars>
+ <SettingsHeader>
+ <HeaderTitle>{strings.daita}</HeaderTitle>
+ <HeaderSubTitle>
+ {messages.pgettext(
+ 'wireguard-settings-view',
+ 'Hides patterns in your encrypted VPN traffic. Since this increases your total network traffic, be cautious if you have a limited data plan. It can also negatively impact your network speed and battery usage.',
+ )}
+ </HeaderSubTitle>
+ </SettingsHeader>
+
+ <StyledContent>
+ <Cell.Group>
+ <DaitaToggle />
+ </Cell.Group>
+ </StyledContent>
+ </NavigationScrollbars>
+ </NavigationContainer>
+ </SettingsContainer>
+ </Layout>
+ </BackAction>
+ );
+}
+
+function DaitaToggle() {
+ const { setEnableDaita, setDaitaSmartRouting } = useAppContext();
+ const daita = useSelector((state) => state.settings.wireguard.daita?.enabled ?? false);
+ const smartRouting = useSelector(
+ (state) => state.settings.wireguard.daita?.smartRouting ?? false,
+ );
+
+ const [confirmationDialogVisible, showConfirmationDialog, hideConfirmationDialog] = useBoolean();
+
+ const setDaita = useCallback((value: boolean) => {
+ void setEnableDaita(value);
+ }, []);
+
+ const setSmartRouting = useCallback((value: boolean) => {
+ if (value) {
+ void setDaitaSmartRouting(value);
+ } else {
+ showConfirmationDialog();
+ }
+ }, []);
+
+ const confirmDisableSmartRouting = useCallback(() => {
+ void setDaitaSmartRouting(false);
+ hideConfirmationDialog();
+ }, []);
+
+ return (
+ <>
+ <AriaInputGroup>
+ <Cell.Container>
+ <AriaLabel>
+ <Cell.InputLabel>{messages.gettext('Enable')}</Cell.InputLabel>
+ </AriaLabel>
+ <AriaInput>
+ <Cell.Switch isOn={daita} onChange={setDaita} />
+ </AriaInput>
+ </Cell.Container>
+ </AriaInputGroup>
+ <AriaInputGroup>
+ <Cell.Container disabled={!daita}>
+ <AriaLabel>
+ <Cell.InputLabel>{messages.gettext('Smart routing')}</Cell.InputLabel>
+ </AriaLabel>
+ <InfoButton>
+ <SmartRoutingModalMessage />
+ </InfoButton>
+ <AriaInput>
+ <Cell.Switch isOn={smartRouting} onChange={setSmartRouting} />
+ </AriaInput>
+ </Cell.Container>
+ <Cell.CellFooter>
+ <AriaDescription>
+ <Cell.CellFooterText>
+ {sprintf(
+ messages.pgettext(
+ 'vpn-settings-view',
+ 'Is automatically enabled with %(daita)s, makes it possible to use %(daita)s with any server by using multihop. This might increase latency.',
+ ),
+ { daita: strings.daita },
+ )}
+ </Cell.CellFooterText>
+ </AriaDescription>
+ </Cell.CellFooter>
+ </AriaInputGroup>
+ <ModalAlert
+ isOpen={confirmationDialogVisible}
+ type={ModalAlertType.caution}
+ gridButtons={[
+ <SmallButton
+ key="confirm"
+ onClick={confirmDisableSmartRouting}
+ color={SmallButtonColor.blue}>
+ {messages.gettext('Disable anyway')}
+ </SmallButton>,
+ <SmallButton key="cancel" onClick={hideConfirmationDialog} color={SmallButtonColor.blue}>
+ {messages.pgettext('wireguard-settings-view', 'Use Smart routing')}
+ </SmallButton>,
+ ]}
+ close={hideConfirmationDialog}>
+ <ModalMessage>
+ {sprintf(
+ // TRANSLATORS: Warning text in a dialog that is displayed after a setting is toggled.
+ messages.pgettext(
+ 'wireguard-settings-view',
+ 'Not all our servers are %(daita)s-enabled. In order to use the internet, you might have to select a new location after disabling, or you can continue using %(daita)s with Smart routing.',
+ ),
+ { daita: strings.daita },
+ )}
+ </ModalMessage>
+ </ModalAlert>
+ </>
+ );
+}
+
+export function SmartRoutingModalMessage() {
+ return (
+ <ModalMessage>
+ {sprintf(
+ messages.pgettext(
+ 'wireguard-settings-view',
+ 'Not all our servers are %(daita)s-enabled. Smart routing allows %(daita)s to be used at any location. It does this by using multihop in the background to route your traffic via the closest %(daita)s-enabled server first.',
+ ),
+ {
+ daita: strings.daita,
+ },
+ )}
+ </ModalMessage>
+ );
+}
diff --git a/gui/src/renderer/components/WireguardSettings.tsx b/gui/src/renderer/components/WireguardSettings.tsx
index 1f4709159f..8f2329d77f 100644
--- a/gui/src/renderer/components/WireguardSettings.tsx
+++ b/gui/src/renderer/components/WireguardSettings.tsx
@@ -22,7 +22,6 @@ import * as AppButton from './AppButton';
import { AriaDescription, AriaInput, AriaInputGroup, AriaLabel } from './AriaGroup';
import * as Cell from './cell';
import Selector, { SelectorItem, SelectorWithCustomItem } from './cell/Selector';
-import InfoButton from './InfoButton';
import { BackAction } from './KeyboardNavigation';
import { Layout, SettingsContainer } from './Layout';
import { ModalAlert, ModalAlertType, ModalMessage } from './Modal';
@@ -98,7 +97,7 @@ export default function WireguardSettings() {
</Cell.Group>
<Cell.Group>
- <DaitaSettings />
+ <DaitaButton />
</Cell.Group>
<Cell.Group>
@@ -528,89 +527,16 @@ function MtuSetting() {
);
}
-function DaitaSettings() {
- const { setDaitaSettings } = useAppContext();
+function DaitaButton() {
+ const history = useHistory();
+ const navigate = useCallback(() => history.push(RoutePath.daitaSettings), [history]);
const daita = useSelector((state) => state.settings.wireguard.daita?.enabled ?? false);
- const [confirmationDialogVisible, showConfirmationDialog, hideConfirmationDialog] = useBoolean();
-
- const setDaita = useCallback((value: boolean) => {
- if (value) {
- showConfirmationDialog();
- } else {
- void setDaitaSettings({ enabled: value });
- }
- }, []);
-
- const confirmDaita = useCallback(() => {
- void setDaitaSettings({ enabled: true });
- hideConfirmationDialog();
- }, []);
-
return (
- <>
- <AriaInputGroup>
- <Cell.Container>
- <AriaLabel>
- <Cell.InputLabel>{strings.daita}</Cell.InputLabel>
- </AriaLabel>
- <InfoButton>
- <ModalMessage>
- {sprintf(
- messages.pgettext(
- 'wireguard-settings-view',
- '%(daita)s (%(daitaFull)s) hides patterns in your encrypted VPN traffic. If anyone is monitoring your connection, this makes it significantly harder for them to identify what websites you are visiting. It does this by carefully adding network noise and making all network packets the same size.',
- ),
- { daita: strings.daita, daitaFull: strings.daitaFull },
- )}
- </ModalMessage>
- <ModalMessage>
- {sprintf(
- messages.pgettext(
- 'wireguard-settings-view',
- 'Attention: Since this increases your total network traffic, be cautious if you have a limited data plan. It can also negatively impact your network speed. Please consider this if you want to enable %(daita)s.',
- ),
- { daita: strings.daita },
- )}
- </ModalMessage>
- </InfoButton>
- <AriaInput>
- <Cell.Switch isOn={daita} onChange={setDaita} />
- </AriaInput>
- </Cell.Container>
- </AriaInputGroup>
- <ModalAlert
- isOpen={confirmationDialogVisible}
- type={ModalAlertType.caution}
- buttons={[
- <AppButton.BlueButton key="confirm" onClick={confirmDaita}>
- {messages.gettext('Enable anyway')}
- </AppButton.BlueButton>,
- <AppButton.BlueButton key="back" onClick={hideConfirmationDialog}>
- {messages.gettext('Back')}
- </AppButton.BlueButton>,
- ]}
- close={hideConfirmationDialog}>
- <ModalMessage>
- {
- // TRANSLATORS: Warning text in a dialog that is displayed after a setting is toggled.
- messages.pgettext(
- 'wireguard-settings-view',
- 'This feature isn’t available on all servers. You might need to change location after enabling.',
- )
- }
- </ModalMessage>
- <ModalMessage>
- {sprintf(
- messages.pgettext(
- 'wireguard-settings-view',
- 'Attention: Since this increases your total network traffic, be cautious if you have a limited data plan. It can also negatively impact your network speed. Please consider this if you want to enable %(daita)s.',
- ),
- { daita: strings.daita },
- )}
- </ModalMessage>
- </ModalAlert>
- </>
+ <Cell.CellNavigationButton onClick={navigate}>
+ <Cell.Label>{strings.daita}</Cell.Label>
+ <Cell.SubText>{daita ? messages.gettext('On') : messages.gettext('Off')}</Cell.SubText>
+ </Cell.CellNavigationButton>
);
}
diff --git a/gui/src/renderer/components/main-view/FeatureIndicators.tsx b/gui/src/renderer/components/main-view/FeatureIndicators.tsx
index 68ee0ae5ae..4d189e712c 100644
--- a/gui/src/renderer/components/main-view/FeatureIndicators.tsx
+++ b/gui/src/renderer/components/main-view/FeatureIndicators.tsx
@@ -5,9 +5,13 @@ import styled from 'styled-components';
import { colors, strings } from '../../../config.json';
import { FeatureIndicator } from '../../../shared/daemon-rpc-types';
import { messages } from '../../../shared/gettext';
-import { useStyledRef } from '../../lib/utilityHooks';
+import { useBoolean, useStyledRef } from '../../lib/utilityHooks';
import { useSelector } from '../../redux/store';
import { tinyText } from '../common-styles';
+import { SmartRoutingModalMessage } from '../DaitaSettings';
+import { InfoIcon } from '../InfoButton';
+import { ModalAlert, ModalAlertType } from '../Modal';
+import { SmallButton, SmallButtonColor } from '../SmallButton';
import { ConnectionPanelAccordion } from './styles';
const LINE_HEIGHT = 22;
@@ -43,8 +47,9 @@ const StyledFeatureIndicatorsWrapper = styled.div<{ $expanded: boolean }>((props
}));
const StyledFeatureIndicatorLabel = styled.span<{ $expanded: boolean }>(tinyText, (props) => ({
- display: 'inline',
- padding: '2px 8px',
+ display: 'flex',
+ gap: '4px',
+ padding: '1px 7px',
justifyContent: 'center',
alignItems: 'center',
borderRadius: '4px',
@@ -53,6 +58,15 @@ const StyledFeatureIndicatorLabel = styled.span<{ $expanded: boolean }>(tinyText
fontWeight: 400,
whiteSpace: 'nowrap',
visibility: props.$expanded ? 'visible' : 'hidden',
+
+ // Style clickable feature indicators with a border and on-hover effect
+ boxSizing: 'border-box', // make border act as padding rather than margin
+ border: 'solid 1px',
+ borderColor: props.onClick ? colors.blue : colors.darkerBlue,
+ transition: 'background ease-in-out 300ms',
+ '&&:hover': {
+ background: props.onClick ? colors.blue60 : undefined,
+ },
}));
const StyledBaseEllipsis = styled.span<{ $display: boolean }>(tinyText, (props) => ({
@@ -88,6 +102,11 @@ interface FeatureIndicatorsProps {
// we can count those and add another ellipsis element which is visible and place it after the last
// visible indicator.
export default function FeatureIndicators(props: FeatureIndicatorsProps) {
+ const [
+ daitaSmartRoutingDialogueVisible,
+ showDaitaSmartRoutingDialogue,
+ hideDaitaSmartRoutingDialogue,
+ ] = useBoolean();
const tunnelState = useSelector((state) => state.connection.status);
const ellipsisRef = useStyledRef<HTMLSpanElement>();
const ellipsisSpacerRef = useStyledRef<HTMLSpanElement>();
@@ -106,6 +125,16 @@ export default function FeatureIndicators(props: FeatureIndicatorsProps) {
const ellipsis = messages.gettext('%(amount)d more...');
+ // Returns an optional callback for clickable feature indicators, or undefined.
+ const getFeatureIndicatorOnClick = (indicator: FeatureIndicator) => {
+ switch (indicator) {
+ case FeatureIndicator.daitaSmartRouting:
+ return showDaitaSmartRoutingDialogue;
+ default:
+ return undefined;
+ }
+ };
+
useEffect(() => {
// We need to defer the visibility logic one painting cycle to make sure the elements are
// rendered and available.
@@ -164,7 +193,7 @@ export default function FeatureIndicators(props: FeatureIndicatorsProps) {
return (
<StyledAccordion expanded={featureIndicatorsVisible && featureIndicators.current.length > 0}>
- <StyledFeatureIndicatorsContainer onClick={props.expandIsland} $expanded={props.expanded}>
+ <StyledFeatureIndicatorsContainer $expanded={props.expanded}>
<StyledAccordion expanded={props.expanded}>
<StyledTitle>{messages.pgettext('connect-view', 'Active features')}</StyledTitle>
</StyledAccordion>
@@ -172,16 +201,20 @@ export default function FeatureIndicators(props: FeatureIndicatorsProps) {
<StyledFeatureIndicatorsWrapper
ref={featureIndicatorsContainerRef}
$expanded={props.expanded}>
- {sortedIndicators.map((indicator) => (
- <StyledFeatureIndicatorLabel
- key={indicator.toString()}
- data-testid="feature-indicator"
- $expanded={props.expanded}>
- {getFeatureIndicatorLabel(indicator)}
- </StyledFeatureIndicatorLabel>
- ))}
+ {sortedIndicators.map((indicator) => {
+ const onClick = getFeatureIndicatorOnClick(indicator);
+ return (
+ <StyledFeatureIndicatorLabel
+ key={indicator.toString()}
+ data-testid="feature-indicator"
+ onClick={onClick}
+ $expanded={props.expanded}>
+ {getFeatureIndicatorLabel(indicator)}
+ {onClick ? <InfoIcon size={10} /> : null}
+ </StyledFeatureIndicatorLabel>
+ );
+ })}
</StyledFeatureIndicatorsWrapper>
- <StyledEllipsis $display={!props.expanded} ref={ellipsisRef} />
<StyledEllipsisSpacer $display={!props.expanded} ref={ellipsisSpacerRef}>
{
// Mock amount for the spacer ellipsis. This needs to be wider than the real
@@ -189,8 +222,28 @@ export default function FeatureIndicators(props: FeatureIndicatorsProps) {
sprintf(ellipsis, { amount: 222 })
}
</StyledEllipsisSpacer>
+ <StyledEllipsis
+ onClick={props.expandIsland}
+ $display={!props.expanded}
+ ref={ellipsisRef}
+ />
</StyledFeatureIndicators>
</StyledFeatureIndicatorsContainer>
+
+ <ModalAlert
+ isOpen={daitaSmartRoutingDialogueVisible}
+ type={ModalAlertType.info}
+ gridButtons={[
+ <SmallButton
+ key="dismiss"
+ onClick={hideDaitaSmartRoutingDialogue}
+ color={SmallButtonColor.blue}>
+ {messages.gettext('Got it!')}
+ </SmallButton>,
+ ]}
+ close={hideDaitaSmartRoutingDialogue}>
+ <SmartRoutingModalMessage />
+ </ModalAlert>
</StyledAccordion>
);
}
@@ -216,6 +269,13 @@ function getFeatureIndicatorLabel(indicator: FeatureIndicator) {
switch (indicator) {
case FeatureIndicator.daita:
return strings.daita;
+ case FeatureIndicator.daitaSmartRouting:
+ return sprintf(
+ // TRANSLATORS: This refers to the Smart Routing setting in the VPN settings view.
+ // TRANSLATORS: This is displayed when both Smart Routing and DAITA features are on.
+ messages.gettext('%(daita)s: Smart routing'),
+ { daita: strings.daita },
+ );
case FeatureIndicator.udp2tcp:
case FeatureIndicator.shadowsocks:
return messages.pgettext('wireguard-settings-view', 'Obfuscation');
diff --git a/gui/src/renderer/components/select-location/RelayListContext.tsx b/gui/src/renderer/components/select-location/RelayListContext.tsx
index 13e2dc28e8..241d11fbdc 100644
--- a/gui/src/renderer/components/select-location/RelayListContext.tsx
+++ b/gui/src/renderer/components/select-location/RelayListContext.tsx
@@ -62,6 +62,10 @@ interface RelayListContextProviderProps {
export function RelayListContextProvider(props: RelayListContextProviderProps) {
const { locationType, searchTerm } = useSelectLocationContext();
const daita = useSelector((state) => state.settings.wireguard.daita?.enabled ?? false);
+ const smartRouting = useSelector(
+ (state) => state.settings.wireguard.daita?.smartRouting ?? false,
+ );
+
const fullRelayList = useSelector((state) => state.settings.relayLocations);
const relaySettings = useNormalRelaySettings();
@@ -77,6 +81,7 @@ export function RelayListContextProvider(props: RelayListContextProviderProps) {
return filterLocationsByDaita(
relayListForEndpointType,
daita,
+ smartRouting,
locationType,
relaySettings?.tunnelProtocol ?? 'any',
relaySettings?.wireguard.useMultihop ?? false,
diff --git a/gui/src/renderer/components/select-location/SelectLocation.tsx b/gui/src/renderer/components/select-location/SelectLocation.tsx
index f6cf772019..9cd66f98f4 100644
--- a/gui/src/renderer/components/select-location/SelectLocation.tsx
+++ b/gui/src/renderer/components/select-location/SelectLocation.tsx
@@ -68,8 +68,12 @@ export default function SelectLocation() {
const providers = relaySettings?.providers ?? [];
const filteredProviders = useFilteredProviders(providers, ownership);
const daita = useSelector((state) => state.settings.wireguard.daita?.enabled ?? false);
+ const smartRouting = useSelector(
+ (state) => state.settings.wireguard.daita?.smartRouting ?? false,
+ );
const showDaitaFilter = daitaFilterActive(
daita,
+ smartRouting,
locationType,
relaySettings?.tunnelProtocol ?? 'any',
relaySettings?.wireguard.useMultihop ?? false,
diff --git a/gui/src/renderer/lib/filter-locations.ts b/gui/src/renderer/lib/filter-locations.ts
index 78126f1fb4..661d8ecc29 100644
--- a/gui/src/renderer/lib/filter-locations.ts
+++ b/gui/src/renderer/lib/filter-locations.ts
@@ -37,17 +37,19 @@ export function filterLocationsByEndPointType(
export function filterLocationsByDaita(
locations: IRelayLocationCountryRedux[],
daita: boolean,
+ smartRouting: boolean,
locationType: LocationType,
tunnelProtocol: LiftedConstraint<TunnelProtocol>,
multihop: boolean,
): IRelayLocationCountryRedux[] {
- return daitaFilterActive(daita, locationType, tunnelProtocol, multihop)
+ return daitaFilterActive(daita, smartRouting, locationType, tunnelProtocol, multihop)
? filterLocationsImpl(locations, (relay: IRelayLocationRelayRedux) => relay.daita)
: locations;
}
export function daitaFilterActive(
daita: boolean,
+ smartRouting: boolean,
locationType: LocationType,
tunnelProtocol: LiftedConstraint<TunnelProtocol>,
multihop: boolean,
@@ -55,7 +57,7 @@ export function daitaFilterActive(
const isEntry = multihop
? locationType === LocationType.entry
: locationType === LocationType.exit;
- return daita && isEntry && tunnelProtocol !== 'openvpn';
+ return daita && (!smartRouting || multihop) && isEntry && tunnelProtocol !== 'openvpn';
}
export function filterLocations(
diff --git a/gui/src/renderer/lib/routes.ts b/gui/src/renderer/lib/routes.ts
index 0ccc3679ff..5204dc666c 100644
--- a/gui/src/renderer/lib/routes.ts
+++ b/gui/src/renderer/lib/routes.ts
@@ -15,6 +15,7 @@ export enum RoutePath {
userInterfaceSettings = '/settings/interface',
vpnSettings = '/settings/vpn',
wireguardSettings = '/settings/advanced/wireguard',
+ daitaSettings = '/settings/advanced/wireguard/daita',
udpOverTcp = '/settings/advanced/wireguard/udp-over-tcp',
shadowsocks = '/settings/advanced/shadowsocks',
openVpnSettings = '/settings/advanced/openvpn',
diff --git a/gui/src/shared/daemon-rpc-types.ts b/gui/src/shared/daemon-rpc-types.ts
index b84ee8a72e..33ff6bb8ce 100644
--- a/gui/src/shared/daemon-rpc-types.ts
+++ b/gui/src/shared/daemon-rpc-types.ts
@@ -183,6 +183,7 @@ export interface ITunnelStateRelayInfo {
// The order of the variants match the priority order and can be sorted on.
export enum FeatureIndicator {
daita,
+ daitaSmartRouting,
quantumResistance,
multihop,
bridgeMode,
@@ -551,6 +552,7 @@ export interface RelayOverride {
export interface IDaitaSettings {
enabled: boolean;
+ smartRouting: boolean;
}
export function parseSocketAddress(socketAddrStr: string): ISocketAddress {
diff --git a/gui/src/shared/ipc-schema.ts b/gui/src/shared/ipc-schema.ts
index acbb6366d2..4e08e7109d 100644
--- a/gui/src/shared/ipc-schema.ts
+++ b/gui/src/shared/ipc-schema.ts
@@ -14,7 +14,6 @@ import {
IAccountData,
IAppVersionInfo,
ICustomList,
- IDaitaSettings,
IDevice,
IDeviceRemoval,
IDnsOptions,
@@ -195,7 +194,8 @@ export const ipcSchema = {
testApiAccessMethodById: invoke<string, boolean>(),
testCustomApiAccessMethod: invoke<CustomProxy, boolean>(),
clearAllRelayOverrides: invoke<void, void>(),
- setDaitaSettings: invoke<IDaitaSettings, void>(),
+ setEnableDaita: invoke<boolean, void>(),
+ setDaitaSmartRouting: invoke<boolean, void>(),
},
guiSettings: {
'': notifyRenderer<IGuiSettingsState>(),
diff --git a/mullvad-api/src/relay_list.rs b/mullvad-api/src/relay_list.rs
index afe71c829d..5f2b2d6d81 100644
--- a/mullvad-api/src/relay_list.rs
+++ b/mullvad-api/src/relay_list.rs
@@ -162,7 +162,7 @@ fn into_mullvad_relay(
provider: relay.provider,
weight: relay.weight,
endpoint_data,
- location: Some(location),
+ location,
}
}
diff --git a/mullvad-cli/build.rs b/mullvad-cli/build.rs
index ee547da265..de110d3a76 100644
--- a/mullvad-cli/build.rs
+++ b/mullvad-cli/build.rs
@@ -15,11 +15,4 @@ fn main() {
));
res.compile().expect("Unable to generate windows resources");
}
- let target_os = std::env::var("CARGO_CFG_TARGET_OS").expect("CARGO_CFG_TARGET_OS not set");
-
- // Enable DAITA by default on desktop
- println!("cargo::rustc-check-cfg=cfg(daita)");
- if let "linux" | "windows" | "macos" = target_os.as_str() {
- println!(r#"cargo::rustc-cfg=daita"#);
- }
}
diff --git a/mullvad-cli/src/cmds/relay.rs b/mullvad-cli/src/cmds/relay.rs
index 68292c0ad1..4fe11b2a8f 100644
--- a/mullvad-cli/src/cmds/relay.rs
+++ b/mullvad-cli/src/cmds/relay.rs
@@ -4,7 +4,7 @@ use itertools::Itertools;
use mullvad_management_interface::MullvadProxyClient;
use mullvad_types::{
constraints::{Constraint, Match},
- location::{CountryCode, Location},
+ location::CountryCode,
relay_constraints::{
GeographicLocationConstraint, LocationConstraint, LocationConstraintFormatter,
OpenVpnConstraints, Ownership, Provider, Providers, RelayConstraints, RelayOverride,
@@ -542,7 +542,6 @@ impl Relay {
allowed_ips: all_of_the_internet(),
endpoint: SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), port),
psk: None,
- #[cfg(daita)]
constant_packet_size: false,
},
exit_peer: None,
@@ -921,15 +920,11 @@ fn parse_transport_port(
fn relay_to_geographical_constraint(
relay: mullvad_types::relay_list::Relay,
-) -> Option<GeographicLocationConstraint> {
- relay.location.map(
- |Location {
- country_code,
- city_code,
- ..
- }| {
- GeographicLocationConstraint::Hostname(country_code, city_code, relay.hostname)
- },
+) -> GeographicLocationConstraint {
+ GeographicLocationConstraint::Hostname(
+ relay.location.country_code,
+ relay.location.city_code,
+ relay.hostname,
)
}
@@ -952,10 +947,9 @@ pub async fn resolve_location_constraint(
.find(|relay| relay.hostname.to_lowercase() == location_constraint_args.country)
{
if relay_filter(&matching_relay) {
- Ok(Constraint::Only(
- relay_to_geographical_constraint(matching_relay)
- .context("Selected relay did not contain a valid location")?,
- ))
+ Ok(Constraint::Only(relay_to_geographical_constraint(
+ matching_relay,
+ )))
} else {
bail!(
"The relay `{}` is not valid for this operation",
diff --git a/mullvad-cli/src/cmds/tunnel.rs b/mullvad-cli/src/cmds/tunnel.rs
index 0937cc8229..da66cac5d5 100644
--- a/mullvad-cli/src/cmds/tunnel.rs
+++ b/mullvad-cli/src/cmds/tunnel.rs
@@ -1,8 +1,6 @@
use anyhow::Result;
use clap::Subcommand;
use mullvad_management_interface::MullvadProxyClient;
-#[cfg(daita)]
-use mullvad_types::wireguard::DaitaSettings;
use mullvad_types::{
constraints::Constraint,
wireguard::{QuantumResistantState, RotationInterval, DEFAULT_ROTATION_INTERVAL},
@@ -41,9 +39,11 @@ pub enum TunnelOptions {
#[arg(long)]
quantum_resistant: Option<QuantumResistantState>,
/// Configure whether to enable DAITA
- #[cfg(daita)]
#[arg(long)]
daita: Option<BooleanOption>,
+ /// Configure whether to enable DAITA smart routing
+ #[arg(long)]
+ daita_smart_routing: Option<BooleanOption>,
/// The key rotation interval. Number of hours, or 'any'
#[arg(long)]
rotation_interval: Option<Constraint<RotationInterval>>,
@@ -101,7 +101,6 @@ impl Tunnel {
tunnel_options.wireguard.quantum_resistant,
);
- #[cfg(daita)]
print_option!("DAITA", tunnel_options.wireguard.daita.enabled);
let key = rpc.get_wireguard_key().await?;
@@ -138,16 +137,16 @@ impl Tunnel {
TunnelOptions::Wireguard {
mtu,
quantum_resistant,
- #[cfg(daita)]
daita,
+ daita_smart_routing,
rotation_interval,
rotate_key,
} => {
Self::handle_wireguard(
mtu,
quantum_resistant,
- #[cfg(daita)]
daita,
+ daita_smart_routing,
rotation_interval,
rotate_key,
)
@@ -178,7 +177,8 @@ impl Tunnel {
async fn handle_wireguard(
mtu: Option<Constraint<u16>>,
quantum_resistant: Option<QuantumResistantState>,
- #[cfg(daita)] daita: Option<BooleanOption>,
+ daita: Option<BooleanOption>,
+ daita_smart_routing: Option<BooleanOption>,
rotation_interval: Option<Constraint<RotationInterval>>,
rotate_key: Option<RotateKey>,
) -> Result<()> {
@@ -194,11 +194,15 @@ impl Tunnel {
println!("Quantum resistant setting has been updated");
}
- #[cfg(daita)]
- if let Some(daita) = daita {
- rpc.set_daita_settings(DaitaSettings { enabled: *daita })
- .await?;
+ if let Some(enable_daita) = daita {
+ rpc.set_enable_daita(*enable_daita).await?;
println!("DAITA setting has been updated");
+ println!("Smart routing setting has been updated");
+ }
+
+ if let Some(daita_smart_routing) = daita_smart_routing {
+ rpc.set_daita_smart_routing(*daita_smart_routing).await?;
+ println!("Smart routing setting has been updated");
}
if let Some(interval) = rotation_interval {
diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs
index a25a835227..097dcc6bf6 100644
--- a/mullvad-daemon/src/lib.rs
+++ b/mullvad-daemon/src/lib.rs
@@ -264,6 +264,10 @@ pub enum DaemonCommand {
SetQuantumResistantTunnel(ResponseTx<(), settings::Error>, QuantumResistantState),
/// Set DAITA settings for the tunnel
#[cfg(daita)]
+ SetEnableDaita(ResponseTx<(), settings::Error>, bool),
+ #[cfg(daita)]
+ SetDaitaSmartRouting(ResponseTx<(), settings::Error>, bool),
+ #[cfg(daita)]
SetDaitaSettings(ResponseTx<(), settings::Error>, DaitaSettings),
/// Set DNS options or servers to use
SetDnsOptions(ResponseTx<(), settings::Error>, DnsOptions),
@@ -1255,6 +1259,10 @@ impl Daemon {
.await
}
#[cfg(daita)]
+ SetEnableDaita(tx, value) => self.on_set_daita_enabled(tx, value).await,
+ #[cfg(daita)]
+ SetDaitaSmartRouting(tx, value) => self.on_set_daita_smart_routing(tx, value).await,
+ #[cfg(daita)]
SetDaitaSettings(tx, daita_settings) => {
self.on_set_daita_settings(tx, daita_settings).await
}
@@ -2324,6 +2332,86 @@ impl Daemon {
}
#[cfg(daita)]
+ async fn on_set_daita_enabled(&mut self, tx: ResponseTx<(), settings::Error>, value: bool) {
+ use mullvad_types::{constraints::Constraint, Intersection};
+
+ let result = self
+ .settings
+ .update(|settings| {
+ settings.tunnel_options.wireguard.daita.enabled = value;
+
+ // enable smart-routing automatically with daita
+ if cfg!(not(target_os = "android")) {
+ settings.tunnel_options.wireguard.daita.smart_routing = value
+ }
+ })
+ .await;
+
+ match result {
+ Ok(settings_changed) => {
+ Self::oneshot_send(tx, Ok(()), "set_daita_enabled response");
+ let RelaySettings::Normal(constraints) = &self.settings.relay_settings else {
+ return; // DAITA is not supported for custom relays
+ };
+
+ let wireguard_enabled = constraints
+ .tunnel_protocol
+ .intersection(Constraint::Only(TunnelType::Wireguard))
+ .is_some();
+
+ if settings_changed && wireguard_enabled {
+ log::info!("Reconnecting because DAITA settings changed");
+ self.reconnect_tunnel();
+ }
+ }
+ Err(e) => {
+ log::error!("{}", e.display_chain_with_msg("Unable to save settings"));
+ Self::oneshot_send(tx, Err(e), "set_daita_enabled response");
+ }
+ }
+ }
+
+ #[cfg(daita)]
+ async fn on_set_daita_smart_routing(
+ &mut self,
+ tx: ResponseTx<(), settings::Error>,
+ value: bool,
+ ) {
+ use mullvad_types::{constraints::Constraint, Intersection};
+
+ match self
+ .settings
+ .update(|settings| settings.tunnel_options.wireguard.daita.smart_routing = value)
+ .await
+ {
+ Ok(settings_changed) => {
+ Self::oneshot_send(tx, Ok(()), "set_daita_smart_routing response");
+
+ let RelaySettings::Normal(constraints) = &self.settings.relay_settings else {
+ return; // DAITA is not supported for custom relays
+ };
+
+ let wireguard_enabled = constraints
+ .tunnel_protocol
+ .intersection(Constraint::Only(TunnelType::Wireguard))
+ .is_some();
+
+ let multihop_enabled = constraints.wireguard_constraints.use_multihop;
+ let daita_enabled = self.settings.tunnel_options.wireguard.daita.enabled;
+
+ if settings_changed && wireguard_enabled && daita_enabled && !multihop_enabled {
+ log::info!("Reconnecting because DAITA settings changed");
+ self.reconnect_tunnel();
+ }
+ }
+ Err(e) => {
+ log::error!("{}", e.display_chain_with_msg("Unable to save settings"));
+ Self::oneshot_send(tx, Err(e), "set_daita_smart_routing response");
+ }
+ }
+ }
+
+ #[cfg(daita)]
async fn on_set_daita_settings(
&mut self,
tx: ResponseTx<(), settings::Error>,
@@ -2931,8 +3019,14 @@ fn new_selector_config(settings: &Settings) -> SelectorConfig {
wireguard: AdditionalWireguardConstraints {
#[cfg(daita)]
daita: settings.tunnel_options.wireguard.daita.enabled,
+ #[cfg(daita)]
+ daita_smart_routing: settings.tunnel_options.wireguard.daita.smart_routing,
+
#[cfg(not(daita))]
daita: false,
+ #[cfg(not(daita))]
+ daita_smart_routing: false,
+
quantum_resistant: settings.tunnel_options.wireguard.quantum_resistant,
},
};
diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs
index 4d0f558a97..331754d1f2 100644
--- a/mullvad-daemon/src/management_interface.rs
+++ b/mullvad-daemon/src/management_interface.rs
@@ -342,6 +342,26 @@ impl ManagementService for ManagementServiceImpl {
}
#[cfg(daita)]
+ async fn set_enable_daita(&self, request: Request<bool>) -> ServiceResult<()> {
+ let value = request.into_inner();
+ log::debug!("set_enable_daita({value})");
+ let (tx, rx) = oneshot::channel();
+ self.send_command_to_daemon(DaemonCommand::SetEnableDaita(tx, value))?;
+ self.wait_for_result(rx).await?.map(Response::new)?;
+ Ok(Response::new(()))
+ }
+
+ #[cfg(daita)]
+ async fn set_daita_smart_routing(&self, request: Request<bool>) -> ServiceResult<()> {
+ let value = request.into_inner();
+ log::debug!("set_daita_smart_routing({value})");
+ let (tx, rx) = oneshot::channel();
+ self.send_command_to_daemon(DaemonCommand::SetDaitaSmartRouting(tx, value))?;
+ self.wait_for_result(rx).await?.map(Response::new)?;
+ Ok(Response::new(()))
+ }
+
+ #[cfg(daita)]
async fn set_daita_settings(
&self,
request: Request<types::DaitaSettings>,
@@ -356,6 +376,16 @@ impl ManagementService for ManagementServiceImpl {
}
#[cfg(not(daita))]
+ async fn set_enable_daita(&self, _: Request<bool>) -> ServiceResult<()> {
+ Ok(Response::new(()))
+ }
+
+ #[cfg(not(daita))]
+ async fn set_daita_smart_routing(&self, _: Request<bool>) -> ServiceResult<()> {
+ Ok(Response::new(()))
+ }
+
+ #[cfg(not(daita))]
async fn set_daita_settings(&self, _: Request<types::DaitaSettings>) -> ServiceResult<()> {
Ok(Response::new(()))
}
diff --git a/mullvad-daemon/src/tunnel.rs b/mullvad-daemon/src/tunnel.rs
index 72ec5087fc..73bda59635 100644
--- a/mullvad-daemon/src/tunnel.rs
+++ b/mullvad-daemon/src/tunnel.rs
@@ -128,7 +128,7 @@ impl ParametersGenerator {
hostname = exit.hostname.clone();
obfuscator_hostname = take_hostname(obfuscator);
bridge_hostname = None;
- location = exit.location.as_ref().cloned().unwrap();
+ location = exit.location.clone();
}
#[cfg(not(target_os = "android"))]
LastSelectedRelays::OpenVpn { relay, bridge, .. } => {
@@ -136,7 +136,7 @@ impl ParametersGenerator {
bridge_hostname = take_hostname(bridge);
entry_hostname = None;
obfuscator_hostname = None;
- location = relay.location.as_ref().cloned().unwrap();
+ location = relay.location.clone();
}
};
diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto
index e705c63788..d701fc8e4f 100644
--- a/mullvad-management-interface/proto/management_interface.proto
+++ b/mullvad-management-interface/proto/management_interface.proto
@@ -48,6 +48,8 @@ service ManagementService {
rpc SetWireguardMtu(google.protobuf.UInt32Value) returns (google.protobuf.Empty) {}
rpc SetEnableIpv6(google.protobuf.BoolValue) returns (google.protobuf.Empty) {}
rpc SetQuantumResistantTunnel(QuantumResistantState) returns (google.protobuf.Empty) {}
+ rpc SetEnableDaita(google.protobuf.BoolValue) returns (google.protobuf.Empty) {}
+ rpc SetDaitaSmartRouting(google.protobuf.BoolValue) returns (google.protobuf.Empty) {}
rpc SetDaitaSettings(DaitaSettings) returns (google.protobuf.Empty) {}
rpc SetDnsOptions(DnsOptions) returns (google.protobuf.Empty) {}
rpc SetRelayOverride(RelayOverride) returns (google.protobuf.Empty) {}
@@ -262,6 +264,7 @@ enum FeatureIndicator {
CUSTOM_MTU = 11;
CUSTOM_MSS_FIX = 12;
DAITA = 13;
+ DAITA_SMART_ROUTING = 14;
}
message ObfuscationEndpoint {
@@ -541,7 +544,10 @@ message QuantumResistantState {
State state = 1;
}
-message DaitaSettings { bool enabled = 1; }
+message DaitaSettings {
+ bool enabled = 1;
+ bool smart_routing = 2;
+}
message TunnelOptions {
message OpenvpnOptions { optional uint32 mssfix = 1; }
diff --git a/mullvad-management-interface/src/client.rs b/mullvad-management-interface/src/client.rs
index 3b2cabc33e..f782d3ab32 100644
--- a/mullvad-management-interface/src/client.rs
+++ b/mullvad-management-interface/src/client.rs
@@ -379,6 +379,21 @@ impl MullvadProxyClient {
}
#[cfg(daita)]
+ pub async fn set_enable_daita(&mut self, value: bool) -> Result<()> {
+ self.0.set_enable_daita(value).await.map_err(Error::Rpc)?;
+ Ok(())
+ }
+
+ #[cfg(daita)]
+ pub async fn set_daita_smart_routing(&mut self, value: bool) -> Result<()> {
+ self.0
+ .set_daita_smart_routing(value)
+ .await
+ .map_err(Error::Rpc)?;
+ Ok(())
+ }
+
+ #[cfg(daita)]
pub async fn set_daita_settings(&mut self, settings: DaitaSettings) -> Result<()> {
let settings = types::DaitaSettings::from(settings);
self.0
diff --git a/mullvad-management-interface/src/types/conversions/features.rs b/mullvad-management-interface/src/types/conversions/features.rs
index ac235d8163..85c85b8b77 100644
--- a/mullvad-management-interface/src/types/conversions/features.rs
+++ b/mullvad-management-interface/src/types/conversions/features.rs
@@ -18,6 +18,7 @@ impl From<mullvad_types::features::FeatureIndicator> for proto::FeatureIndicator
mullvad_types::features::FeatureIndicator::CustomMtu => CustomMtu,
mullvad_types::features::FeatureIndicator::CustomMssFix => CustomMssFix,
mullvad_types::features::FeatureIndicator::Daita => Daita,
+ mullvad_types::features::FeatureIndicator::DaitaSmartRouting => DaitaSmartRouting,
}
}
}
@@ -39,6 +40,7 @@ impl From<proto::FeatureIndicator> for mullvad_types::features::FeatureIndicator
proto::FeatureIndicator::CustomMtu => Self::CustomMtu,
proto::FeatureIndicator::CustomMssFix => Self::CustomMssFix,
proto::FeatureIndicator::Daita => Self::Daita,
+ proto::FeatureIndicator::DaitaSmartRouting => Self::DaitaSmartRouting,
}
}
}
diff --git a/mullvad-management-interface/src/types/conversions/relay_list.rs b/mullvad-management-interface/src/types/conversions/relay_list.rs
index a8b69d87fa..11718973c1 100644
--- a/mullvad-management-interface/src/types/conversions/relay_list.rs
+++ b/mullvad-management-interface/src/types/conversions/relay_list.rs
@@ -144,13 +144,13 @@ impl From<mullvad_types::relay_list::Relay> for proto::Relay {
)),
_ => None,
},
- location: relay.location.map(|location| proto::Location {
- country: location.country,
- country_code: location.country_code,
- city: location.city,
- city_code: location.city_code,
- latitude: location.latitude,
- longitude: location.longitude,
+ location: Some(proto::Location {
+ country: relay.location.country,
+ country_code: relay.location.country_code,
+ city: relay.location.city,
+ city_code: relay.location.city_code,
+ latitude: relay.location.latitude,
+ longitude: relay.location.longitude,
}),
}
}
@@ -299,14 +299,18 @@ impl TryFrom<proto::Relay> for mullvad_types::relay_list::Relay {
provider: relay.provider,
weight: relay.weight,
endpoint_data,
- location: relay.location.map(|location| MullvadLocation {
- country: location.country,
- country_code: location.country_code,
- city: location.city,
- city_code: location.city_code,
- latitude: location.latitude,
- longitude: location.longitude,
- }),
+ location: relay
+ .location
+ .map(|location| MullvadLocation {
+ country: location.country,
+ country_code: location.country_code,
+ city: location.city,
+ city_code: location.city_code,
+ latitude: location.latitude,
+ longitude: location.longitude,
+ })
+ .ok_or("missing relay location")
+ .map_err(FromProtobufTypeError::InvalidArgument)?,
})
}
}
diff --git a/mullvad-management-interface/src/types/conversions/wireguard.rs b/mullvad-management-interface/src/types/conversions/wireguard.rs
index b4e3bcaef7..9e40b4b526 100644
--- a/mullvad-management-interface/src/types/conversions/wireguard.rs
+++ b/mullvad-management-interface/src/types/conversions/wireguard.rs
@@ -78,6 +78,7 @@ impl From<mullvad_types::wireguard::DaitaSettings> for proto::DaitaSettings {
fn from(settings: mullvad_types::wireguard::DaitaSettings) -> Self {
proto::DaitaSettings {
enabled: settings.enabled,
+ smart_routing: settings.smart_routing,
}
}
}
@@ -87,6 +88,7 @@ impl From<proto::DaitaSettings> for mullvad_types::wireguard::DaitaSettings {
fn from(settings: proto::DaitaSettings) -> Self {
mullvad_types::wireguard::DaitaSettings {
enabled: settings.enabled,
+ smart_routing: settings.smart_routing,
}
}
}
diff --git a/mullvad-relay-selector/src/relay_selector/mod.rs b/mullvad-relay-selector/src/relay_selector/mod.rs
index dc495b05aa..fb3c37064c 100644
--- a/mullvad-relay-selector/src/relay_selector/mod.rs
+++ b/mullvad-relay-selector/src/relay_selector/mod.rs
@@ -124,6 +124,11 @@ pub struct AdditionalWireguardConstraints {
/// If true, select WireGuard relays that support DAITA. If false, select any
/// server.
pub daita: bool,
+
+ /// If true and multihop is disabled, will set up multihop with an automatic entry relay if
+ /// DAITA is enabled.
+ pub daita_smart_routing: bool,
+
/// If enabled, select relays that support PQ.
pub quantum_resistant: QuantumResistantState,
}
@@ -345,6 +350,7 @@ impl<'a> TryFrom<NormalSelectorConfig<'a>> for RelayQuery {
} = wireguard_constraints;
let AdditionalWireguardConstraints {
daita,
+ daita_smart_routing,
quantum_resistant,
} = additional_constraints;
WireguardRelayQuery {
@@ -354,6 +360,7 @@ impl<'a> TryFrom<NormalSelectorConfig<'a>> for RelayQuery {
entry_location,
obfuscation: ObfuscationQuery::from(obfuscation_settings),
daita: Constraint::Only(daita),
+ daita_smart_routing: Constraint::Only(daita_smart_routing),
quantum_resistant,
}
}
@@ -718,12 +725,89 @@ impl RelaySelector {
parsed_relays: &ParsedRelays,
) -> Result<WireguardConfig, Error> {
let candidates = filter_matching_relay_list(query, parsed_relays, custom_lists);
+
+ // are we using daita?
+ let using_daita = || query.wireguard_constraints().daita == Constraint::Only(true);
+
+ // is the `candidates` list empty because DAITA is enabled?
+ let no_relay_because_daita = || {
+ let mut query = query.clone();
+ let mut wireguard_constraints = query.wireguard_constraints().clone();
+ wireguard_constraints.daita = Constraint::Any;
+ query.set_wireguard_constraints(wireguard_constraints)?;
+ let candidates = filter_matching_relay_list(&query, parsed_relays, custom_lists);
+ Result::<_, Error>::Ok(!candidates.is_empty())
+ };
+
+ // is `smart_routing` enabled?
+ let smart_routing = || {
+ query
+ .wireguard_constraints()
+ .daita_smart_routing
+ .intersection(Constraint::Only(true))
+ .is_some()
+ };
+
+ // if we found no matching relays because DAITA was enabled, and `smart_routing` is enabled,
+ // try enabling multihop and connecting using an automatically selected entry relay.
+ if candidates.is_empty() && using_daita() && no_relay_because_daita()? && smart_routing() {
+ return Self::get_wireguard_auto_multihop_config(query, custom_lists, parsed_relays);
+ }
+
helpers::pick_random_relay(&candidates)
.cloned()
.map(WireguardConfig::singlehop)
.ok_or(Error::NoRelay)
}
+ /// Select a valid Wireguard exit relay, together with with an automatically chosen entry relay.
+ ///
+ /// # Returns
+ /// * An `Err` if no entry/exit relay can be chosen
+ /// * `Ok(WireguardInner::Multihop)` otherwise
+ fn get_wireguard_auto_multihop_config(
+ query: &RelayQuery,
+ custom_lists: &CustomListsSettings,
+ parsed_relays: &ParsedRelays,
+ ) -> Result<WireguardConfig, Error> {
+ let mut exit_relay_query = query.clone();
+
+ // DAITA should only be enabled for the entry relay
+ let mut wireguard_constraints = exit_relay_query.wireguard_constraints().clone();
+ wireguard_constraints.daita = Constraint::Only(false);
+ exit_relay_query.set_wireguard_constraints(wireguard_constraints)?;
+
+ let exit_candidates =
+ filter_matching_relay_list(&exit_relay_query, parsed_relays, custom_lists);
+ let exit = helpers::pick_random_relay(&exit_candidates).ok_or(Error::NoRelay)?;
+
+ // generate a list of potential entry relays, disregarding any location constraint
+ let mut entry_query = query.clone();
+ entry_query.set_location(Constraint::Any)?;
+ let mut entry_candidates =
+ filter_matching_relay_list(&entry_query, parsed_relays, custom_lists)
+ .into_iter()
+ .map(|entry| RelayWithDistance::new_with_distance_from(entry, &exit.location))
+ .collect_vec();
+
+ // sort entry relay candidates by distance, and pick one from those that are closest
+ entry_candidates.sort_unstable_by(|a, b| a.distance.total_cmp(&b.distance));
+ let smallest_distance = entry_candidates.first().map(|relay| relay.distance);
+ let smallest_distance = smallest_distance.unwrap_or_default();
+ let entry_candidates = entry_candidates
+ .into_iter()
+ // only consider the relay(s) with the smallest distance. note that the list is sorted.
+ // NOTE: we could relax this requirement, but since so few relays support DAITA
+ // (and this function is only used for daita) we might end up picking relays that are
+ // needlessly far away. Consider making this closure configurable if needed.
+ .take_while(|relay| relay.distance <= smallest_distance)
+ .map(|relay_with_distance| relay_with_distance.relay)
+ .collect_vec();
+ let entry = pick_random_excluding(&entry_candidates, exit).ok_or(Error::NoRelay)?;
+
+ Ok(WireguardConfig::multihop(exit.clone(), entry.clone()))
+ }
+
/// This function selects a valid entry and exit relay to be used in a multihop configuration.
///
/// # Returns
@@ -757,11 +841,6 @@ impl RelaySelector {
let entry_candidates =
filter_matching_relay_list(&entry_relay_query, parsed_relays, custom_lists);
- fn pick_random_excluding<'a>(list: &'a [Relay], exclude: &'a Relay) -> Option<&'a Relay> {
- list.iter()
- .filter(|&a| a != exclude)
- .choose(&mut thread_rng())
- }
// We avoid picking the same relay for entry and exit by choosing one and excluding it when
// choosing the other.
let (exit, entry) = match (exit_candidates.as_slice(), entry_candidates.as_slice()) {
@@ -938,10 +1017,9 @@ impl RelaySelector {
Err(Error::NoBridge)
}
TransportProtocol::Tcp => {
- let location = relay.location.as_ref().ok_or(Error::NoRelay)?;
Self::get_bridge_for(
bridge_query,
- location,
+ &relay.location,
// FIXME: This is temporary while talpid-core only supports TCP proxies
TransportProtocol::Tcp,
parsed_relays,
@@ -1011,19 +1089,10 @@ impl RelaySelector {
const MIN_BRIDGE_COUNT: usize = 5;
let location = location.into();
- #[derive(Clone)]
- struct RelayWithDistance {
- relay: Relay,
- distance: f64,
- }
-
// Filter out all candidate bridges.
let matching_bridges: Vec<RelayWithDistance> = relays
.into_iter()
- .map(|relay| RelayWithDistance {
- distance: relay.location.as_ref().unwrap().distance_from(&location),
- relay,
- })
+ .map(|relay| RelayWithDistance::new_with_distance_from(relay, location))
.sorted_unstable_by_key(|relay| relay.distance as usize)
.take(MIN_BRIDGE_COUNT)
.collect();
@@ -1059,7 +1128,7 @@ impl RelaySelector {
let matching_locations: Vec<Location> =
filter_matching_relay_list(query, parsed_relays, custom_lists)
.into_iter()
- .filter_map(|relay| relay.location)
+ .map(|relay| relay.location)
.unique_by(|location| location.city.clone())
.collect();
@@ -1084,3 +1153,22 @@ impl RelaySelector {
helpers::pick_random_relay(&candidates).cloned()
}
}
+
+fn pick_random_excluding<'a>(list: &'a [Relay], exclude: &'a Relay) -> Option<&'a Relay> {
+ list.iter()
+ .filter(|&a| a != exclude)
+ .choose(&mut thread_rng())
+}
+
+#[derive(Clone)]
+struct RelayWithDistance {
+ distance: f64,
+ relay: Relay,
+}
+
+impl RelayWithDistance {
+ fn new_with_distance_from(relay: Relay, from: impl Into<Coordinates>) -> Self {
+ let distance = relay.location.distance_from(from);
+ RelayWithDistance { relay, distance }
+ }
+}
diff --git a/mullvad-relay-selector/src/relay_selector/parsed_relays.rs b/mullvad-relay-selector/src/relay_selector/parsed_relays.rs
index 35c78a52aa..3a1663d9dd 100644
--- a/mullvad-relay-selector/src/relay_selector/parsed_relays.rs
+++ b/mullvad-relay-selector/src/relay_selector/parsed_relays.rs
@@ -168,14 +168,14 @@ impl ParsedRelays {
for city in &mut country.cities {
for relay in &mut city.relays {
// Append location data
- relay.location = Some(Location {
+ relay.location = Location {
country: country.name.clone(),
country_code: country.code.clone(),
city: city.name.clone(),
city_code: city.code.clone(),
latitude: city.latitude,
longitude: city.longitude,
- });
+ };
// Append overrides
if let Some(overrides) = remaining_overrides.remove(&relay.hostname) {
diff --git a/mullvad-relay-selector/src/relay_selector/query.rs b/mullvad-relay-selector/src/relay_selector/query.rs
index 67fe43e2a3..2dc81b0833 100644
--- a/mullvad-relay-selector/src/relay_selector/query.rs
+++ b/mullvad-relay-selector/src/relay_selector/query.rs
@@ -28,14 +28,14 @@
//! queries and ensure that queries are built in a type-safe manner, reducing the risk
//! of runtime errors and improving code readability.
-use crate::{AdditionalWireguardConstraints, Error};
+use crate::Error;
use mullvad_types::{
constraints::Constraint,
relay_constraints::{
BridgeConstraints, BridgeSettings, BridgeState, BridgeType, LocationConstraint,
ObfuscationSettings, OpenVpnConstraints, Ownership, Providers, RelayConstraints,
- SelectedObfuscation, ShadowsocksSettings, TransportPort, Udp2TcpObfuscationSettings,
- WireguardConstraints,
+ RelaySettings, SelectedObfuscation, ShadowsocksSettings, TransportPort,
+ Udp2TcpObfuscationSettings, WireguardConstraints,
},
wireguard::QuantumResistantState,
Intersection,
@@ -244,6 +244,13 @@ impl Default for RelayQuery {
}
}
+impl From<RelayQuery> for RelaySettings {
+ fn from(query: RelayQuery) -> Self {
+ let (relay_constraints, ..) = query.into_settings();
+ RelaySettings::from(relay_constraints)
+ }
+}
+
/// A query for a relay with Wireguard-specific properties, such as `multihop` and [wireguard
/// obfuscation][`SelectedObfuscation`].
///
@@ -261,6 +268,7 @@ pub struct WireguardRelayQuery {
pub entry_location: Constraint<LocationConstraint>,
pub obfuscation: ObfuscationQuery,
pub daita: Constraint<bool>,
+ pub daita_smart_routing: Constraint<bool>,
pub quantum_resistant: QuantumResistantState,
}
@@ -346,6 +354,7 @@ impl WireguardRelayQuery {
entry_location: Constraint::Any,
obfuscation: ObfuscationQuery::Auto,
daita: Constraint::Any,
+ daita_smart_routing: Constraint::Any,
quantum_resistant: QuantumResistantState::Auto,
}
}
@@ -367,14 +376,14 @@ impl Default for WireguardRelayQuery {
}
}
-impl From<WireguardRelayQuery> for AdditionalWireguardConstraints {
- /// The mapping from [`WireguardRelayQuery`] to [`AdditionalWireguardConstraints`].
+impl From<WireguardRelayQuery> for WireguardConstraints {
+ /// The mapping from [`WireguardRelayQuery`] to [`WireguardConstraints`].
fn from(value: WireguardRelayQuery) -> Self {
- AdditionalWireguardConstraints {
- daita: value
- .daita
- .unwrap_or(AdditionalWireguardConstraints::default().daita),
- quantum_resistant: value.quantum_resistant,
+ WireguardConstraints {
+ port: value.port,
+ ip_version: value.ip_version,
+ entry_location: value.entry_location,
+ use_multihop: value.use_multihop.unwrap_or(false),
}
}
}
@@ -660,6 +669,17 @@ pub mod builder {
}
}
+ // impl-block for after DAITA is set
+ impl<Multihop, Obfuscation, QuantumResistant>
+ RelayQueryBuilder<Wireguard<Multihop, Obfuscation, bool, QuantumResistant>>
+ {
+ /// Enable DAITA smart routing.
+ pub fn daita_smart_routing(mut self, constraint: impl Into<Constraint<bool>>) -> Self {
+ self.query.wireguard_constraints.daita_smart_routing = constraint.into();
+ self
+ }
+ }
+
impl<Multihop, Obfuscation, Daita> RelayQueryBuilder<Wireguard<Multihop, Obfuscation, Daita, Any>> {
/// Enable PQ support.
pub fn quantum_resistant(
diff --git a/mullvad-relay-selector/tests/relay_selector.rs b/mullvad-relay-selector/tests/relay_selector.rs
index bdfd8e19d0..dc05d04eb2 100644
--- a/mullvad-relay-selector/tests/relay_selector.rs
+++ b/mullvad-relay-selector/tests/relay_selector.rs
@@ -21,6 +21,7 @@ use mullvad_relay_selector::{
use mullvad_types::{
constraints::Constraint,
endpoint::MullvadEndpoint,
+ location::Location,
relay_constraints::{
BridgeConstraints, BridgeState, GeographicLocationConstraint, Ownership, Providers,
RelayOverride, TransportPort,
@@ -32,6 +33,15 @@ use mullvad_types::{
},
};
+static DUMMY_LOCATION: LazyLock<Location> = LazyLock::new(|| Location {
+ country: "Sweden".to_string(),
+ country_code: "se".to_string(),
+ city: "Gothenburg".to_string(),
+ city_code: "got".to_string(),
+ latitude: 57.71,
+ longitude: 11.97,
+});
+
static RELAYS: LazyLock<RelayList> = LazyLock::new(|| RelayList {
etag: None,
countries: vec![RelayListCountry {
@@ -62,7 +72,7 @@ static RELAYS: LazyLock<RelayList> = LazyLock::new(|| RelayList {
daita: true,
shadowsocks_extra_addr_in: vec![],
}),
- location: None,
+ location: DUMMY_LOCATION.clone(),
},
Relay {
hostname: "se10-wireguard".to_string(),
@@ -83,7 +93,7 @@ static RELAYS: LazyLock<RelayList> = LazyLock::new(|| RelayList {
daita: false,
shadowsocks_extra_addr_in: vec![],
}),
- location: None,
+ location: DUMMY_LOCATION.clone(),
},
Relay {
hostname: "se-got-001".to_string(),
@@ -97,7 +107,7 @@ static RELAYS: LazyLock<RelayList> = LazyLock::new(|| RelayList {
provider: "provider2".to_string(),
weight: 1,
endpoint_data: RelayEndpointData::Openvpn,
- location: None,
+ location: DUMMY_LOCATION.clone(),
},
Relay {
hostname: "se-got-002".to_string(),
@@ -111,7 +121,7 @@ static RELAYS: LazyLock<RelayList> = LazyLock::new(|| RelayList {
provider: "provider0".to_string(),
weight: 1,
endpoint_data: RelayEndpointData::Openvpn,
- location: None,
+ location: DUMMY_LOCATION.clone(),
},
Relay {
hostname: "se-got-br-001".to_string(),
@@ -125,7 +135,7 @@ static RELAYS: LazyLock<RelayList> = LazyLock::new(|| RelayList {
provider: "provider3".to_string(),
weight: 1,
endpoint_data: RelayEndpointData::Bridge,
- location: None,
+ location: DUMMY_LOCATION.clone(),
},
SHADOWSOCKS_RELAY.clone(),
],
@@ -209,7 +219,7 @@ static SHADOWSOCKS_RELAY: LazyLock<Relay> = LazyLock::new(|| Relay {
daita: false,
shadowsocks_extra_addr_in: SHADOWSOCKS_RELAY_EXTRA_ADDRS.to_vec(),
}),
- location: None,
+ location: DUMMY_LOCATION.clone(),
});
const SHADOWSOCKS_RELAY_IPV4: Ipv4Addr = Ipv4Addr::new(123, 123, 123, 1);
const SHADOWSOCKS_RELAY_IPV6: Ipv6Addr = Ipv6Addr::new(0x123, 0, 0, 0, 0, 0, 0, 2);
@@ -497,7 +507,7 @@ fn test_wireguard_entry() {
daita: false,
shadowsocks_extra_addr_in: vec![],
}),
- location: None,
+ location: DUMMY_LOCATION.clone(),
},
Relay {
hostname: "se10-wireguard".to_string(),
@@ -518,7 +528,7 @@ fn test_wireguard_entry() {
daita: false,
shadowsocks_extra_addr_in: vec![],
}),
- location: None,
+ location: DUMMY_LOCATION.clone(),
},
],
}],
@@ -1169,7 +1179,7 @@ fn test_include_in_country() {
shadowsocks_extra_addr_in: vec![],
daita: false,
}),
- location: None,
+ location: DUMMY_LOCATION.clone(),
},
Relay {
hostname: "se10-wireguard".to_string(),
@@ -1190,7 +1200,7 @@ fn test_include_in_country() {
shadowsocks_extra_addr_in: vec![],
daita: false,
}),
- location: None,
+ location: DUMMY_LOCATION.clone(),
},
],
}],
@@ -1438,7 +1448,11 @@ fn test_daita() {
let relay_selector = RelaySelector::from_list(SelectorConfig::default(), RELAYS.clone());
// Only pick relays that support DAITA
- let query = RelayQueryBuilder::new().wireguard().daita().build();
+ let query = RelayQueryBuilder::new()
+ .wireguard()
+ .daita()
+ .daita_smart_routing(false)
+ .build();
let relay = unwrap_entry_relay(relay_selector.get_relay_by_query(query).unwrap());
assert!(
supports_daita(&relay),
@@ -1449,12 +1463,58 @@ fn test_daita() {
let query = RelayQueryBuilder::new()
.wireguard()
.daita()
+ .daita_smart_routing(false)
.location(NON_DAITA_RELAY_LOCATION.clone())
.build();
relay_selector
.get_relay_by_query(query)
.expect_err("Expected to find no matching relay");
+ // Should be able to connect to non-DAITA relay with smart_routing
+ let query = RelayQueryBuilder::new()
+ .wireguard()
+ .daita()
+ .daita_smart_routing(true)
+ .location(NON_DAITA_RELAY_LOCATION.clone())
+ .build();
+ let relay = relay_selector
+ .get_relay_by_query(query)
+ .expect("Expected to find a relay with daita_smart_routing");
+ match relay {
+ GetRelay::Wireguard {
+ inner: WireguardConfig::Multihop { exit, entry },
+ ..
+ } => {
+ assert!(supports_daita(&entry), "entry relay must support DAITA");
+ assert!(!supports_daita(&exit), "exit relay must not support DAITA");
+ }
+ wrong_relay => panic!(
+ "Relay selector should have picked two Wireguard relays, instead chose {wrong_relay:?}"
+ ),
+ }
+
+ // Should be able to connect to DAITA relay with smart_routing
+ let query = RelayQueryBuilder::new()
+ .wireguard()
+ .daita()
+ .daita_smart_routing(true)
+ .location(DAITA_RELAY_LOCATION.clone())
+ .build();
+ let relay = relay_selector
+ .get_relay_by_query(query)
+ .expect("Expected to find a relay with daita_smart_routing");
+ match relay {
+ GetRelay::Wireguard {
+ inner: WireguardConfig::Singlehop { exit },
+ ..
+ } => {
+ assert!(supports_daita(&exit), "entry relay must support DAITA");
+ }
+ wrong_relay => panic!(
+ "Relay selector should have picked a single Wireguard relay, instead chose {wrong_relay:?}"
+ ),
+ }
+
// DAITA-supporting relays can be picked even when it is disabled
let query = RelayQueryBuilder::new()
.wireguard()
@@ -1477,6 +1537,7 @@ fn test_daita() {
let query = RelayQueryBuilder::new()
.wireguard()
.daita()
+ .daita_smart_routing(false)
.multihop()
.build();
let relay = relay_selector.get_relay_by_query(query).unwrap();
@@ -1496,6 +1557,7 @@ fn test_daita() {
let query = RelayQueryBuilder::new()
.wireguard()
.daita()
+ .daita_smart_routing(false)
.multihop()
.location(NON_DAITA_RELAY_LOCATION.clone())
.build();
diff --git a/mullvad-types/src/features.rs b/mullvad-types/src/features.rs
index f847c94af0..ba40e42d18 100644
--- a/mullvad-types/src/features.rs
+++ b/mullvad-types/src/features.rs
@@ -65,7 +65,14 @@ pub enum FeatureIndicator {
ServerIpOverride,
CustomMtu,
CustomMssFix,
+
+ /// Whether DAITA (without smart routing) is in use.
+ /// Mutually exclusive with [FeatureIndicator::DaitaSmartRouting].
Daita,
+
+ /// Whether DAITA (with smart routing) is in use.
+ /// Mutually exclusive with [FeatureIndicator::Daita].
+ DaitaSmartRouting,
}
impl FeatureIndicator {
@@ -85,6 +92,7 @@ impl FeatureIndicator {
FeatureIndicator::CustomMtu => "Custom MTU",
FeatureIndicator::CustomMssFix => "Custom MSS",
FeatureIndicator::Daita => "DAITA",
+ FeatureIndicator::DaitaSmartRouting => "DAITA: Smart Routing",
}
}
}
@@ -144,7 +152,6 @@ pub fn compute_feature_indicators(
}
TunnelType::Wireguard => {
let quantum_resistant = endpoint.quantum_resistant;
- let multihop = endpoint.entry_endpoint.is_some();
let udp_tcp = endpoint
.obfuscation
.as_ref()
@@ -158,8 +165,28 @@ pub fn compute_feature_indicators(
let mtu = settings.tunnel_options.wireguard.mtu.is_some();
+ let mut daita_smart_routing = false;
+ let mut multihop = false;
+
+ if let crate::relay_constraints::RelaySettings::Normal(constraints) =
+ &settings.relay_settings
+ {
+ multihop = endpoint.entry_endpoint.is_some()
+ && constraints.wireguard_constraints.use_multihop;
+
+ #[cfg(daita)]
+ {
+ // Detect whether we're using "smart_routing" by checking if multihop is
+ // in use but not explicitly enabled.
+ daita_smart_routing = endpoint.daita
+ && endpoint.entry_endpoint.is_some()
+ && !constraints.wireguard_constraints.use_multihop
+ }
+ };
+
+ // Daita is mutually exclusive with DaitaSmartRouting
#[cfg(daita)]
- let daita = endpoint.daita;
+ let daita = endpoint.daita && !daita_smart_routing;
vec![
(quantum_resistant, FeatureIndicator::QuantumResistance),
@@ -169,6 +196,7 @@ pub fn compute_feature_indicators(
(mtu, FeatureIndicator::CustomMtu),
#[cfg(daita)]
(daita, FeatureIndicator::Daita),
+ (daita_smart_routing, FeatureIndicator::DaitaSmartRouting),
]
}
};
@@ -190,6 +218,8 @@ mod tests {
Endpoint, ObfuscationEndpoint, TransportProtocol,
};
+ use crate::relay_constraints::RelaySettings;
+
use super::*;
#[test]
@@ -302,6 +332,9 @@ mod tests {
address: SocketAddr::from(([1, 2, 3, 4], 443)),
protocol: TransportProtocol::Tcp,
});
+ if let RelaySettings::Normal(constraints) = &mut settings.relay_settings {
+ constraints.wireguard_constraints.use_multihop = true;
+ };
expected_indicators.0.insert(FeatureIndicator::Multihop);
assert_eq!(
compute_feature_indicators(&settings, &endpoint, false),
@@ -343,6 +376,20 @@ mod tests {
compute_feature_indicators(&settings, &endpoint, false),
expected_indicators
);
+
+ if let RelaySettings::Normal(constraints) = &mut settings.relay_settings {
+ constraints.wireguard_constraints.use_multihop = false;
+ };
+ expected_indicators
+ .0
+ .insert(FeatureIndicator::DaitaSmartRouting);
+ expected_indicators.0.remove(&FeatureIndicator::Daita);
+ expected_indicators.0.remove(&FeatureIndicator::Multihop);
+ assert_eq!(
+ compute_feature_indicators(&settings, &endpoint, false),
+ expected_indicators,
+ "DaitaSmartRouting should be enabled"
+ );
}
// NOTE: If this match statement fails to compile, it means that a new feature indicator has
@@ -362,6 +409,7 @@ mod tests {
FeatureIndicator::CustomMtu => {}
FeatureIndicator::CustomMssFix => {}
FeatureIndicator::Daita => {}
+ FeatureIndicator::DaitaSmartRouting => {}
}
}
}
diff --git a/mullvad-types/src/location.rs b/mullvad-types/src/location.rs
index 7807b65d6a..d8a47176c1 100644
--- a/mullvad-types/src/location.rs
+++ b/mullvad-types/src/location.rs
@@ -19,7 +19,8 @@ pub struct Location {
const RAIDUS_OF_EARTH: f64 = 6372.8;
impl Location {
- pub fn distance_from(&self, other: &Coordinates) -> f64 {
+ pub fn distance_from(&self, other: impl Into<Coordinates>) -> f64 {
+ let other: Coordinates = other.into();
haversine_dist_deg(
self.latitude,
self.longitude,
@@ -33,7 +34,7 @@ impl Location {
}
}
-#[derive(Debug, Clone, PartialEq)]
+#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Coordinates {
pub latitude: f64,
pub longitude: f64,
diff --git a/mullvad-types/src/relay_constraints.rs b/mullvad-types/src/relay_constraints.rs
index f3e38adc79..2b3d5099d2 100644
--- a/mullvad-types/src/relay_constraints.rs
+++ b/mullvad-types/src/relay_constraints.rs
@@ -210,21 +210,18 @@ impl GeographicLocationConstraint {
impl Match<Relay> for GeographicLocationConstraint {
fn matches(&self, relay: &Relay) -> bool {
match self {
- GeographicLocationConstraint::Country(ref country) => relay
- .location
- .as_ref()
- .map_or(false, |loc| loc.country_code == *country),
+ GeographicLocationConstraint::Country(ref country) => {
+ relay.location.country_code == *country
+ }
GeographicLocationConstraint::City(ref country, ref city) => {
- relay.location.as_ref().map_or(false, |loc| {
- loc.country_code == *country && loc.city_code == *city
- })
+ let loc = &relay.location;
+ loc.country_code == *country && loc.city_code == *city
}
GeographicLocationConstraint::Hostname(ref country, ref city, ref hostname) => {
- relay.location.as_ref().map_or(false, |loc| {
- loc.country_code == *country
- && loc.city_code == *city
- && relay.hostname == *hostname
- })
+ let loc = &relay.location;
+ loc.country_code == *country
+ && loc.city_code == *city
+ && relay.hostname == *hostname
}
}
}
diff --git a/mullvad-types/src/relay_list.rs b/mullvad-types/src/relay_list.rs
index 1693720d4a..afe8ba6378 100644
--- a/mullvad-types/src/relay_list.rs
+++ b/mullvad-types/src/relay_list.rs
@@ -90,7 +90,7 @@ pub struct Relay {
pub provider: String,
pub weight: u64,
pub endpoint_data: RelayEndpointData,
- pub location: Option<Location>,
+ pub location: Location,
}
impl Relay {
@@ -134,7 +134,14 @@ impl PartialEq for Relay {
/// # daita: false,
/// # shadowsocks_extra_addr_in: vec![],
/// # }),
- /// # location: None,
+ /// # location: mullvad_types::location::Location {
+ /// # country: "Sweden".to_string(),
+ /// # country_code: "se".to_string(),
+ /// # city: "Gothenburg".to_string(),
+ /// # city_code: "got".to_string(),
+ /// # latitude: 57.71,
+ /// # longitude: 11.97,
+ /// # },
/// };
///
/// let mut different_relay = relay.clone();
diff --git a/mullvad-types/src/wireguard.rs b/mullvad-types/src/wireguard.rs
index c920e9f0af..2850e7f10c 100644
--- a/mullvad-types/src/wireguard.rs
+++ b/mullvad-types/src/wireguard.rs
@@ -80,6 +80,9 @@ pub struct QuantumResistantStateParseError;
#[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq)]
pub struct DaitaSettings {
pub enabled: bool,
+
+ #[serde(default)]
+ pub smart_routing: bool,
}
/// Contains account specific wireguard data
diff --git a/test/test-manager/src/tests/daita.rs b/test/test-manager/src/tests/daita.rs
new file mode 100644
index 0000000000..912c811ecc
--- /dev/null
+++ b/test/test-manager/src/tests/daita.rs
@@ -0,0 +1,200 @@
+use anyhow::{anyhow, bail, ensure, Context};
+use futures::StreamExt;
+use mullvad_management_interface::{client::DaemonEvent, MullvadProxyClient};
+use mullvad_relay_selector::query::builder::RelayQueryBuilder;
+use mullvad_types::{
+ relay_constraints::GeographicLocationConstraint, relay_list::RelayEndpointData,
+ states::TunnelState,
+};
+use talpid_types::{net::TunnelEndpoint, tunnel::ErrorStateCause};
+use test_macro::test_function;
+use test_rpc::ServiceClient;
+
+use super::{helpers, Error, TestContext};
+
+/// Test that daita and daita_smart_routing works by connecting
+/// - to a non-DAITA relay with singlehop (should block)
+/// - to a DAITA relay with singlehop
+/// - to a DAITA relay with auto-multihop using smart_routing
+/// - to a DAITA relay with explicit multihop
+/// - to a non-DAITA relay with multihop (should block)
+///
+/// # Limitations
+///
+/// The test does not analyze any traffic, nor verify that DAITA is in use in any way except
+/// by looking at [TunnelEndpoint::daita].
+#[test_function]
+pub async fn test_daita(
+ _ctx: TestContext,
+ _rpc: ServiceClient,
+ mut mullvad_client: MullvadProxyClient,
+) -> anyhow::Result<()> {
+ let relay_list = mullvad_client.get_relay_locations().await?;
+ let wg_relays = relay_list
+ .relays()
+ .flat_map(|relay| match &relay.endpoint_data {
+ RelayEndpointData::Wireguard(wireguard) => Some((relay, wireguard)),
+ _ => None,
+ });
+
+ // Select two relays to use for the test, one with DAITA and one without.
+ let daita_relay = wg_relays
+ .clone()
+ .find(|(_relay, wireguard_data)| wireguard_data.daita)
+ .map(|(relay, _)| relay)
+ .context("Failed to find a daita wireguard relay")?;
+ log::info!("Selected daita relay: {}", daita_relay.hostname);
+ let daita_relay_location = GeographicLocationConstraint::hostname(
+ &daita_relay.location.country_code,
+ &daita_relay.location.city_code,
+ &daita_relay.hostname,
+ );
+
+ let non_daita_relay = wg_relays
+ .clone()
+ .find(|(_relay, wireguard_data)| !wireguard_data.daita)
+ .map(|(relay, _)| relay)
+ .context("Failed to find a non-daita wireguard relay")?;
+ let non_daita_relay_location = GeographicLocationConstraint::hostname(
+ &non_daita_relay.location.country_code,
+ &non_daita_relay.location.city_code,
+ &non_daita_relay.hostname,
+ );
+ log::info!("Selected non-daita relay: {}", non_daita_relay.hostname);
+
+ let non_daita_location_query = RelayQueryBuilder::new()
+ .wireguard()
+ .location(non_daita_relay_location.clone())
+ .build();
+
+ let daita_location_query = RelayQueryBuilder::new()
+ .wireguard()
+ .location(daita_relay_location.clone())
+ .build();
+
+ let daita_to_non_daita_multihop_query = RelayQueryBuilder::new()
+ .wireguard()
+ .multihop()
+ .entry(daita_relay_location.clone())
+ .location(non_daita_relay_location.clone())
+ .build();
+
+ let non_daita_multihop_query = RelayQueryBuilder::new()
+ .wireguard()
+ .multihop()
+ .entry(non_daita_relay_location.clone())
+ .build();
+
+ let mut events = mullvad_client
+ .events_listen()
+ .await?
+ .inspect(|event| log::debug!("New daemon event: {event:?}"));
+
+ log::info!("Connecting to non-daita relay with DAITA smart routing");
+ {
+ helpers::set_relay_settings(&mut mullvad_client, non_daita_location_query.clone()).await?;
+ mullvad_client.set_enable_daita(true).await?;
+ mullvad_client.connect_tunnel().await?;
+ let state = wait_for_daemon_reconnect(&mut events)
+ .await
+ .context("Failed to connect with smart_routing enabled")?;
+
+ let endpoint: &TunnelEndpoint = state.endpoint().ok_or(anyhow!("No endpoint"))?;
+ ensure!(endpoint.daita, "DAITA must be used");
+ ensure!(endpoint.entry_endpoint.is_some(), "multihop must be used");
+
+ log::info!("Successfully multihopped with use smart_routing");
+ }
+
+ log::info!("Connecting to non-daita relay with DAITA but no smart routing");
+ {
+ mullvad_client.set_daita_smart_routing(false).await?;
+
+ let result = wait_for_daemon_reconnect(&mut events).await;
+ let Err(Error::UnexpectedErrorState(state)) = result else {
+ bail!("Connection failed unsuccessfully, reason: {:?}", result);
+ };
+ let ErrorStateCause::TunnelParameterError(_) = state.cause() else {
+ bail!("Connection failed unsuccessfully, cause: {}", state.cause());
+ };
+
+ log::info!("Failed to connect, this is expected!");
+ }
+
+ log::info!("Connecting to daita relay with smart_routing");
+ {
+ helpers::set_relay_settings(&mut mullvad_client, daita_location_query).await?;
+
+ let state = wait_for_daemon_reconnect(&mut events)
+ .await
+ .context("Failed to connect to daita location with smart_routing enabled")?;
+
+ let endpoint = state.endpoint().context("No endpoint")?;
+ ensure!(endpoint.daita, "DAITA must be used");
+ ensure!(
+ endpoint.entry_endpoint.is_none(),
+ "multihop must not be used"
+ );
+
+ log::info!("Successfully singlehopped with smart_routing");
+ }
+
+ log::info!("Connecting to daita relay with multihop");
+ {
+ helpers::set_relay_settings(&mut mullvad_client, daita_to_non_daita_multihop_query).await?;
+ let state = wait_for_daemon_reconnect(&mut events)
+ .await
+ .context("Failed to connect via daita location with multihop enabled")?;
+
+ let endpoint = state.endpoint().context("No endpoint")?;
+ ensure!(endpoint.daita, "DAITA must be used");
+ ensure!(endpoint.entry_endpoint.is_some(), "multihop must be used");
+
+ log::info!("Successfully connected with multihop");
+ }
+
+ log::info!("Connecting to non_daita relay with multihop");
+ {
+ helpers::set_relay_settings(&mut mullvad_client, non_daita_multihop_query).await?;
+ let result = wait_for_daemon_reconnect(&mut events).await;
+ let Err(Error::UnexpectedErrorState(state)) = result else {
+ bail!("Connection failed unsuccessfully, reason: {:?}", result);
+ };
+ let ErrorStateCause::TunnelParameterError(_) = state.cause() else {
+ bail!("Connection failed unsuccessfully, cause: {}", state.cause());
+ };
+
+ log::info!("Failed to connect, this is expected!");
+ }
+
+ Ok(())
+}
+
+async fn wait_for_daemon_reconnect(
+ mut event_stream: impl futures::Stream<Item = Result<DaemonEvent, mullvad_management_interface::Error>>
+ + Unpin,
+) -> Result<TunnelState, Error> {
+ // wait until the daemon informs us that it's trying to connect
+ helpers::find_daemon_event(&mut event_stream, |event| match event {
+ DaemonEvent::TunnelState(state) => Some(match state {
+ TunnelState::Connecting { .. } => Ok(state),
+ TunnelState::Connected { .. } => return None,
+ TunnelState::Disconnecting { .. } => return None,
+ TunnelState::Disconnected { .. } => Err(Error::UnexpectedTunnelState(Box::new(state))),
+ TunnelState::Error(state) => Err(Error::UnexpectedErrorState(state)),
+ }),
+ _ => None,
+ })
+ .await??;
+
+ // then wait until the daemon informs us that it connected (or failed)
+ helpers::find_daemon_event(&mut event_stream, |event| match event {
+ DaemonEvent::TunnelState(state) => match state {
+ TunnelState::Connecting { .. } => None,
+ TunnelState::Connected { .. } => Some(Ok(state)),
+ _ => Some(Err(Error::UnexpectedTunnelState(Box::new(state)))),
+ },
+ _ => None,
+ })
+ .await?
+}
diff --git a/test/test-manager/src/tests/dns.rs b/test/test-manager/src/tests/dns.rs
index d7ea3d021d..69f98450ca 100644
--- a/test/test-manager/src/tests/dns.rs
+++ b/test/test-manager/src/tests/dns.rs
@@ -642,7 +642,7 @@ async fn connect_local_wg_relay(mullvad_client: &mut MullvadProxyClient) -> Resu
.set_quantum_resistant_tunnel(QuantumResistantState::Off)
.await?;
mullvad_client
- .set_daita_settings(DaitaSettings { enabled: false })
+ .set_daita_settings(DaitaSettings::default())
.await?;
let peer_addr: SocketAddr = SocketAddr::new(
diff --git a/test/test-manager/src/tests/helpers.rs b/test/test-manager/src/tests/helpers.rs
index 6c51b1f185..3eb0be0cc2 100644
--- a/test/test-manager/src/tests/helpers.rs
+++ b/test/test-manager/src/tests/helpers.rs
@@ -17,7 +17,6 @@ use mullvad_relay_selector::{
};
use mullvad_types::{
constraints::Constraint,
- location::Location,
relay_constraints::{
GeographicLocationConstraint, LocationConstraint, RelayConstraints, RelaySettings,
},
@@ -710,7 +709,7 @@ pub async fn constrain_to_relay(
..
}
| GetRelay::OpenVpn { exit, .. } => {
- let location = into_constraint(&exit)?;
+ let location = into_constraint(&exit);
let (mut relay_constraints, ..) = query.into_settings();
relay_constraints.location = location;
Ok((exit, relay_constraints))
@@ -736,22 +735,14 @@ pub async fn constrain_to_relay(
/// # Panics
///
/// The relay must have a location set.
-pub fn into_constraint(relay: &Relay) -> anyhow::Result<Constraint<LocationConstraint>> {
- relay
- .location
- .as_ref()
- .map(
- |Location {
- country_code,
- city_code,
- ..
- }| {
- GeographicLocationConstraint::hostname(country_code, city_code, &relay.hostname)
- },
- )
- .map(LocationConstraint::Location)
- .map(Constraint::Only)
- .ok_or(anyhow!("relay is missing location"))
+pub fn into_constraint(relay: &Relay) -> Constraint<LocationConstraint> {
+ let constraint = GeographicLocationConstraint::hostname(
+ relay.location.country_code.clone(),
+ relay.location.city_code.clone(),
+ &relay.hostname,
+ );
+
+ Constraint::Only(LocationConstraint::Location(constraint))
}
/// Ping monitoring made easy!
diff --git a/test/test-manager/src/tests/mod.rs b/test/test-manager/src/tests/mod.rs
index 7e4cbc9eb6..bc17a7f3f6 100644
--- a/test/test-manager/src/tests/mod.rs
+++ b/test/test-manager/src/tests/mod.rs
@@ -2,6 +2,7 @@ mod access_methods;
mod account;
pub mod config;
mod cve_2019_14899;
+mod daita;
mod dns;
mod helpers;
mod install;
@@ -57,7 +58,10 @@ pub enum Error {
#[error("The daemon returned an error: {0}")]
Daemon(String),
- #[error("The daemon ended up in the error state")]
+ #[error("The daemon ended up in the the wrong tunnel-state: {0:?}")]
+ UnexpectedTunnelState(Box<mullvad_types::states::TunnelState>),
+
+ #[error("The daemon ended up in the error state: {0:?}")]
UnexpectedErrorState(talpid_types::tunnel::ErrorState),
#[error("The gRPC client ran into an error: {0}")]
diff --git a/test/test-manager/src/tests/relay_ip_overrides.rs b/test/test-manager/src/tests/relay_ip_overrides.rs
index 48df8ff0e4..3805412ee9 100644
--- a/test/test-manager/src/tests/relay_ip_overrides.rs
+++ b/test/test-manager/src/tests/relay_ip_overrides.rs
@@ -298,12 +298,7 @@ async fn pick_a_relay(
let relay_ip = relay.ipv4_addr_in;
let hostname = relay.hostname.clone();
- let city = relay
- .location
- .as_ref()
- .ok_or(anyhow!("Got Relay with an unknown location"))?
- .city_code
- .clone();
+ let city = relay.location.city_code.clone();
log::info!("selected {hostname} ({relay_ip})");
let location = GeographicLocationConstraint::Hostname(country, city, hostname.clone()).into();
diff --git a/test/test-manager/src/tests/tunnel.rs b/test/test-manager/src/tests/tunnel.rs
index 575339c5a8..6fb57adc14 100644
--- a/test/test-manager/src/tests/tunnel.rs
+++ b/test/test-manager/src/tests/tunnel.rs
@@ -390,38 +390,6 @@ pub async fn test_wireguard_autoconnect(
Ok(())
}
-/// Test connecting to a WireGuard relay using DAITA.
-///
-/// # Limitations
-///
-/// The test does not analyze any traffic, nor verify that DAITA is in use.
-#[test_function]
-pub async fn test_daita(
- _: TestContext,
- rpc: ServiceClient,
- mut mullvad_client: MullvadProxyClient,
-) -> anyhow::Result<()> {
- log::info!("Connecting to relay with DAITA");
-
- apply_settings_from_relay_query(
- &mut mullvad_client,
- RelayQueryBuilder::new().wireguard().build(),
- )
- .await?;
-
- mullvad_client
- .set_daita_settings(wireguard::DaitaSettings { enabled: true })
- .await
- .context("Failed to enable daita")?;
-
- connect_and_wait(&mut mullvad_client).await?;
-
- log::info!("Check that the connection works");
- let _ = helpers::geoip_lookup_with_retries(&rpc).await?;
-
- Ok(())
-}
-
/// Test whether the daemon automatically connects on reboot when using
/// OpenVPN.
///