diff --git a/gui/locales/messages.pot b/gui/locales/messages.pot index 5969e4ffd36b..730b75aacaa7 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 "" @@ -1934,6 +1945,10 @@ msgctxt "vpn-settings-view" msgid "IPv4 is always enabled and the majority of websites and applications use this protocol. We do not recommend enabling IPv6 unless you know you need it." 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 9f1f2d760c1f..31f6ddcfabfb 100644 --- a/gui/src/main/daemon-rpc.ts +++ b/gui/src/main/daemon-rpc.ts @@ -589,8 +589,8 @@ export class DaemonRpc { await this.callBool(this.client.setEnableDaita, value); } - public async setDaitaUseAnywhere(value: boolean): Promise { - await this.callBool(this.client.setDaitaUseAnywhere, value); + public async setDaitaSmartRouting(value: boolean): Promise { + await this.callBool(this.client.setDaitaSmartRouting, value); } public async listDevices(accountToken: AccountToken): Promise> { @@ -1180,8 +1180,8 @@ function convertFromFeatureIndicator( return FeatureIndicator.customMssFix; case grpcTypes.FeatureIndicator.DAITA: return FeatureIndicator.daita; - case grpcTypes.FeatureIndicator.DAITA_USE_ANYWHERE: - return FeatureIndicator.daitaUseAnywhere; + 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 6ec4e0b09d2f..03537ba581a6 100644 --- a/gui/src/main/settings.ts +++ b/gui/src/main/settings.ts @@ -110,8 +110,8 @@ export default class Settings implements Readonly { IpcMainEventChannel.settings.handleSetEnableDaita((value) => { return this.daemonRpc.setEnableDaita(value); }); - IpcMainEventChannel.settings.handleSetDaitaUseAnywhere((value) => { - return this.daemonRpc.setDaitaUseAnywhere(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 168e88dcb6f6..a4d5fc2fada4 100644 --- a/gui/src/renderer/app.tsx +++ b/gui/src/renderer/app.tsx @@ -346,8 +346,8 @@ export default class AppRenderer { IpcRendererEventChannel.settings.setObfuscationSettings(obfuscationSettings); public setEnableDaita = (value: boolean) => IpcRendererEventChannel.settings.setEnableDaita(value); - public setDaitaUseAnywhere = (value: boolean) => - IpcRendererEventChannel.settings.setDaitaUseAnywhere(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 e90729507c16..75b8df936c3b 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() { + diff --git a/gui/src/renderer/components/DaitaSettings.tsx b/gui/src/renderer/components/DaitaSettings.tsx new file mode 100644 index 000000000000..3176192ad10d --- /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 ( + + + + + + + {strings.daita} + + + + {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 }, + )} + + + + + + + + {strings.daita} + + {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.', + )} + + + + + + + + + + + + + + ); +} + +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 ( + <> + + + + {messages.gettext('Enable')} + + + + + + + + + + {messages.gettext('Smart routing')} + + + + + + + + + + + + {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 }, + )} + + + + + + {messages.gettext('Disable anyway')} + , + + {messages.pgettext('wireguard-settings-view', 'Use Smart routing')} + , + ]} + close={hideConfirmationDialog}> + + {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 }, + )} + + + + ); +} + +export function SmartRoutingModalMessage() { + return ( + + {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, + }, + )} + + ); +} diff --git a/gui/src/renderer/components/WireguardSettings.tsx b/gui/src/renderer/components/WireguardSettings.tsx index 95bedbb33a9b..8f2329d77f05 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() { - + @@ -528,117 +527,16 @@ function MtuSetting() { ); } -function DaitaSettings() { - const { setEnableDaita, setDaitaUseAnywhere } = 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 useAnywhere = useSelector((state) => state.settings.wireguard.daita?.useAnywhere ?? false); - - const [confirmationDialogVisible, showConfirmationDialog, hideConfirmationDialog] = useBoolean(); - - const setDaita = useCallback((value: boolean) => { - if (value) { - showConfirmationDialog(); - } else { - void setEnableDaita(value); - } - }, []); - - const setUseAnywhere = useCallback((value: boolean) => { - void setDaitaUseAnywhere(value); - }, []); - - const confirmDaita = useCallback(() => { - void setEnableDaita(true); - hideConfirmationDialog(); - }, []); return ( - <> - - - - {strings.daita} - - - - {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 }, - )} - - - {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 }, - )} - - - - - - - - - - - {messages.gettext('Use anywhere')} - - - - {sprintf( - messages.pgettext( - 'wireguard-settings-view', - '¯\\_(ツ)_/¯', // TODO - ), - { - daita: strings.daita, - }, - )} - - - - - - - - - {messages.gettext('Enable anyway')} - , - - {messages.gettext('Back')} - , - ]} - close={hideConfirmationDialog}> - - { - // 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.', - ) - } - - - {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 }, - )} - - - + + {strings.daita} + {daita ? messages.gettext('On') : messages.gettext('Off')} + ); } diff --git a/gui/src/renderer/components/main-view/FeatureIndicators.tsx b/gui/src/renderer/components/main-view/FeatureIndicators.tsx index 97ff92742b54..4d189e712c5d 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(); const ellipsisSpacerRef = useStyledRef(); @@ -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 ( 0}> - + {messages.pgettext('connect-view', 'Active features')} @@ -172,16 +201,20 @@ export default function FeatureIndicators(props: FeatureIndicatorsProps) { - {sortedIndicators.map((indicator) => ( - - {getFeatureIndicatorLabel(indicator)} - - ))} + {sortedIndicators.map((indicator) => { + const onClick = getFeatureIndicatorOnClick(indicator); + return ( + + {getFeatureIndicatorLabel(indicator)} + {onClick ? : null} + + ); + })} - { // 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 }) } + + + + {messages.gettext('Got it!')} + , + ]} + close={hideDaitaSmartRoutingDialogue}> + + ); } @@ -216,8 +269,13 @@ function getFeatureIndicatorLabel(indicator: FeatureIndicator) { switch (indicator) { case FeatureIndicator.daita: return strings.daita; - case FeatureIndicator.daitaUseAnywhere: - return messages.pgettext('wireguard-settings-view', 'DAITA: Use Anywhere'); + 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 ba86709080b7..241d11fbdcf0 100644 --- a/gui/src/renderer/components/select-location/RelayListContext.tsx +++ b/gui/src/renderer/components/select-location/RelayListContext.tsx @@ -62,7 +62,9 @@ interface RelayListContextProviderProps { export function RelayListContextProvider(props: RelayListContextProviderProps) { const { locationType, searchTerm } = useSelectLocationContext(); const daita = useSelector((state) => state.settings.wireguard.daita?.enabled ?? false); - const useAnywhere = useSelector((state) => state.settings.wireguard.daita?.useAnywhere ?? false); + const smartRouting = useSelector( + (state) => state.settings.wireguard.daita?.smartRouting ?? false, + ); const fullRelayList = useSelector((state) => state.settings.relayLocations); const relaySettings = useNormalRelaySettings(); @@ -79,7 +81,7 @@ export function RelayListContextProvider(props: RelayListContextProviderProps) { return filterLocationsByDaita( relayListForEndpointType, daita, - useAnywhere, + 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 36a69667da65..9cd66f98f435 100644 --- a/gui/src/renderer/components/select-location/SelectLocation.tsx +++ b/gui/src/renderer/components/select-location/SelectLocation.tsx @@ -68,10 +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 useAnywhere = useSelector((state) => state.settings.wireguard.daita?.useAnywhere ?? false); + const smartRouting = useSelector( + (state) => state.settings.wireguard.daita?.smartRouting ?? false, + ); const showDaitaFilter = daitaFilterActive( daita, - useAnywhere, + 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 675a4fc88bc3..661d8ecc2950 100644 --- a/gui/src/renderer/lib/filter-locations.ts +++ b/gui/src/renderer/lib/filter-locations.ts @@ -37,19 +37,19 @@ export function filterLocationsByEndPointType( export function filterLocationsByDaita( locations: IRelayLocationCountryRedux[], daita: boolean, - useAnywhere: boolean, + smartRouting: boolean, locationType: LocationType, tunnelProtocol: LiftedConstraint, multihop: boolean, ): IRelayLocationCountryRedux[] { - return daitaFilterActive(daita, useAnywhere, locationType, tunnelProtocol, multihop) + return daitaFilterActive(daita, smartRouting, locationType, tunnelProtocol, multihop) ? filterLocationsImpl(locations, (relay: IRelayLocationRelayRedux) => relay.daita) : locations; } export function daitaFilterActive( daita: boolean, - useAnywhere: boolean, + smartRouting: boolean, locationType: LocationType, tunnelProtocol: LiftedConstraint, multihop: boolean, @@ -57,7 +57,7 @@ export function daitaFilterActive( const isEntry = multihop ? locationType === LocationType.entry : locationType === LocationType.exit; - return daita && (!useAnywhere || multihop) && 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 0ccc3679ff81..5204dc666c61 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 f888413047af..33ff6bb8ce3d 100644 --- a/gui/src/shared/daemon-rpc-types.ts +++ b/gui/src/shared/daemon-rpc-types.ts @@ -183,7 +183,7 @@ export interface ITunnelStateRelayInfo { // The order of the variants match the priority order and can be sorted on. export enum FeatureIndicator { daita, - daitaUseAnywhere, + daitaSmartRouting, quantumResistance, multihop, bridgeMode, @@ -552,7 +552,7 @@ export interface RelayOverride { export interface IDaitaSettings { enabled: boolean; - useAnywhere: 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 78acc9fae99b..4e08e7109d47 100644 --- a/gui/src/shared/ipc-schema.ts +++ b/gui/src/shared/ipc-schema.ts @@ -195,7 +195,7 @@ export const ipcSchema = { testCustomApiAccessMethod: invoke(), clearAllRelayOverrides: invoke(), setEnableDaita: invoke(), - setDaitaUseAnywhere: invoke(), + setDaitaSmartRouting: invoke(), }, guiSettings: { '': notifyRenderer(), diff --git a/mullvad-cli/src/cmds/tunnel.rs b/mullvad-cli/src/cmds/tunnel.rs index c17877b438da..da66cac5d505 100644 --- a/mullvad-cli/src/cmds/tunnel.rs +++ b/mullvad-cli/src/cmds/tunnel.rs @@ -41,9 +41,9 @@ pub enum TunnelOptions { /// Configure whether to enable DAITA #[arg(long)] daita: Option, - /// Configure whether to enable DAITA "use anywhere" + /// Configure whether to enable DAITA smart routing #[arg(long)] - daita_use_anywhere: Option, + daita_smart_routing: Option, /// The key rotation interval. Number of hours, or 'any' #[arg(long)] rotation_interval: Option>, @@ -138,7 +138,7 @@ impl Tunnel { mtu, quantum_resistant, daita, - daita_use_anywhere, + daita_smart_routing, rotation_interval, rotate_key, } => { @@ -146,7 +146,7 @@ impl Tunnel { mtu, quantum_resistant, daita, - daita_use_anywhere, + daita_smart_routing, rotation_interval, rotate_key, ) @@ -178,7 +178,7 @@ impl Tunnel { mtu: Option>, quantum_resistant: Option, daita: Option, - daita_use_anywhere: Option, + daita_smart_routing: Option, rotation_interval: Option>, rotate_key: Option, ) -> Result<()> { @@ -197,11 +197,12 @@ impl Tunnel { 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_use_anywhere) = daita_use_anywhere { - rpc.set_daita_use_anywhere(*daita_use_anywhere).await?; - println!("DAITA 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 e6b4698c7384..097dcc6bf65d 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -266,7 +266,7 @@ pub enum DaemonCommand { #[cfg(daita)] SetEnableDaita(ResponseTx<(), settings::Error>, bool), #[cfg(daita)] - SetDaitaUseAnywhere(ResponseTx<(), settings::Error>, bool), + SetDaitaSmartRouting(ResponseTx<(), settings::Error>, bool), #[cfg(daita)] SetDaitaSettings(ResponseTx<(), settings::Error>, DaitaSettings), /// Set DNS options or servers to use @@ -1261,7 +1261,7 @@ impl Daemon { #[cfg(daita)] SetEnableDaita(tx, value) => self.on_set_daita_enabled(tx, value).await, #[cfg(daita)] - SetDaitaUseAnywhere(tx, value) => self.on_set_daita_use_anywhere(tx, value).await, + 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 @@ -2335,11 +2335,19 @@ impl Daemon { async fn on_set_daita_enabled(&mut self, tx: ResponseTx<(), settings::Error>, value: bool) { use mullvad_types::{constraints::Constraint, Intersection}; - match self + let result = self .settings - .update(|settings| settings.tunnel_options.wireguard.daita.enabled = value) - .await - { + .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 { @@ -2364,7 +2372,7 @@ impl Daemon { } #[cfg(daita)] - async fn on_set_daita_use_anywhere( + async fn on_set_daita_smart_routing( &mut self, tx: ResponseTx<(), settings::Error>, value: bool, @@ -2373,11 +2381,11 @@ impl Daemon { match self .settings - .update(|settings| settings.tunnel_options.wireguard.daita.use_anywhere = value) + .update(|settings| settings.tunnel_options.wireguard.daita.smart_routing = value) .await { Ok(settings_changed) => { - Self::oneshot_send(tx, Ok(()), "set_daita_use_anywhere response"); + 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 @@ -2398,7 +2406,7 @@ impl Daemon { } Err(e) => { log::error!("{}", e.display_chain_with_msg("Unable to save settings")); - Self::oneshot_send(tx, Err(e), "set_daita_use_anywhere response"); + Self::oneshot_send(tx, Err(e), "set_daita_smart_routing response"); } } } @@ -3012,12 +3020,12 @@ fn new_selector_config(settings: &Settings) -> SelectorConfig { #[cfg(daita)] daita: settings.tunnel_options.wireguard.daita.enabled, #[cfg(daita)] - daita_use_anywhere: settings.tunnel_options.wireguard.daita.use_anywhere, + daita_smart_routing: settings.tunnel_options.wireguard.daita.smart_routing, #[cfg(not(daita))] daita: false, #[cfg(not(daita))] - daita_use_anywhere: false, + 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 594fd7a19e63..331754d1f25f 100644 --- a/mullvad-daemon/src/management_interface.rs +++ b/mullvad-daemon/src/management_interface.rs @@ -352,11 +352,11 @@ impl ManagementService for ManagementServiceImpl { } #[cfg(daita)] - async fn set_daita_use_anywhere(&self, request: Request) -> ServiceResult<()> { + async fn set_daita_smart_routing(&self, request: Request) -> ServiceResult<()> { let value = request.into_inner(); - log::debug!("set_daita_use_anywhere({value})"); + log::debug!("set_daita_smart_routing({value})"); let (tx, rx) = oneshot::channel(); - self.send_command_to_daemon(DaemonCommand::SetDaitaUseAnywhere(tx, value))?; + self.send_command_to_daemon(DaemonCommand::SetDaitaSmartRouting(tx, value))?; self.wait_for_result(rx).await?.map(Response::new)?; Ok(Response::new(())) } @@ -381,7 +381,7 @@ impl ManagementService for ManagementServiceImpl { } #[cfg(not(daita))] - async fn set_daita_use_anywhere(&self, _: Request) -> ServiceResult<()> { + async fn set_daita_smart_routing(&self, _: Request) -> ServiceResult<()> { Ok(Response::new(())) } diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index 29acf34923cd..d701fc8e4f9b 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -49,7 +49,7 @@ service ManagementService { 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 SetDaitaUseAnywhere(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) {} @@ -264,7 +264,7 @@ enum FeatureIndicator { CUSTOM_MTU = 11; CUSTOM_MSS_FIX = 12; DAITA = 13; - DAITA_USE_ANYWHERE = 14; + DAITA_SMART_ROUTING = 14; } message ObfuscationEndpoint { @@ -546,7 +546,7 @@ message QuantumResistantState { message DaitaSettings { bool enabled = 1; - bool use_anywhere = 2; + bool smart_routing = 2; } message TunnelOptions { diff --git a/mullvad-management-interface/src/client.rs b/mullvad-management-interface/src/client.rs index 14676ceeb962..f782d3ab3242 100644 --- a/mullvad-management-interface/src/client.rs +++ b/mullvad-management-interface/src/client.rs @@ -385,9 +385,9 @@ impl MullvadProxyClient { } #[cfg(daita)] - pub async fn set_daita_use_anywhere(&mut self, value: bool) -> Result<()> { + pub async fn set_daita_smart_routing(&mut self, value: bool) -> Result<()> { self.0 - .set_daita_use_anywhere(value) + .set_daita_smart_routing(value) .await .map_err(Error::Rpc)?; Ok(()) diff --git a/mullvad-management-interface/src/types/conversions/features.rs b/mullvad-management-interface/src/types/conversions/features.rs index 971c2a01155f..85c85b8b7786 100644 --- a/mullvad-management-interface/src/types/conversions/features.rs +++ b/mullvad-management-interface/src/types/conversions/features.rs @@ -18,7 +18,7 @@ impl From 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::DaitaUseAnywhere => DaitaUseAnywhere, + mullvad_types::features::FeatureIndicator::DaitaSmartRouting => DaitaSmartRouting, } } } @@ -40,7 +40,7 @@ impl From for mullvad_types::features::FeatureIndicator proto::FeatureIndicator::CustomMtu => Self::CustomMtu, proto::FeatureIndicator::CustomMssFix => Self::CustomMssFix, proto::FeatureIndicator::Daita => Self::Daita, - proto::FeatureIndicator::DaitaUseAnywhere => Self::DaitaUseAnywhere, + proto::FeatureIndicator::DaitaSmartRouting => Self::DaitaSmartRouting, } } } diff --git a/mullvad-management-interface/src/types/conversions/wireguard.rs b/mullvad-management-interface/src/types/conversions/wireguard.rs index cf30a8cc99cd..9e40b4b52600 100644 --- a/mullvad-management-interface/src/types/conversions/wireguard.rs +++ b/mullvad-management-interface/src/types/conversions/wireguard.rs @@ -78,7 +78,7 @@ impl From for proto::DaitaSettings { fn from(settings: mullvad_types::wireguard::DaitaSettings) -> Self { proto::DaitaSettings { enabled: settings.enabled, - use_anywhere: settings.use_anywhere, + smart_routing: settings.smart_routing, } } } @@ -88,7 +88,7 @@ impl From for mullvad_types::wireguard::DaitaSettings { fn from(settings: proto::DaitaSettings) -> Self { mullvad_types::wireguard::DaitaSettings { enabled: settings.enabled, - use_anywhere: settings.use_anywhere, + 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 1635de88570c..fb3c37064c52 100644 --- a/mullvad-relay-selector/src/relay_selector/mod.rs +++ b/mullvad-relay-selector/src/relay_selector/mod.rs @@ -127,7 +127,7 @@ pub struct AdditionalWireguardConstraints { /// If true and multihop is disabled, will set up multihop with an automatic entry relay if /// DAITA is enabled. - pub daita_use_anywhere: bool, + pub daita_smart_routing: bool, /// If enabled, select relays that support PQ. pub quantum_resistant: QuantumResistantState, @@ -350,7 +350,7 @@ impl<'a> TryFrom> for RelayQuery { } = wireguard_constraints; let AdditionalWireguardConstraints { daita, - daita_use_anywhere, + daita_smart_routing, quantum_resistant, } = additional_constraints; WireguardRelayQuery { @@ -360,7 +360,7 @@ impl<'a> TryFrom> for RelayQuery { entry_location, obfuscation: ObfuscationQuery::from(obfuscation_settings), daita: Constraint::Only(daita), - daita_use_anywhere: Constraint::Only(daita_use_anywhere), + daita_smart_routing: Constraint::Only(daita_smart_routing), quantum_resistant, } } @@ -739,18 +739,18 @@ impl RelaySelector { Result::<_, Error>::Ok(!candidates.is_empty()) }; - // is `use_anywhere` enabled? - let use_anywhere = || { + // is `smart_routing` enabled? + let smart_routing = || { query .wireguard_constraints() - .daita_use_anywhere + .daita_smart_routing .intersection(Constraint::Only(true)) .is_some() }; - // if we found no matching relays because DAITA was enabled, and `use_anywhere` is enabled, + // 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()? && use_anywhere() { + if candidates.is_empty() && using_daita() && no_relay_because_daita()? && smart_routing() { return Self::get_wireguard_auto_multihop_config(query, custom_lists, parsed_relays); } diff --git a/mullvad-relay-selector/src/relay_selector/query.rs b/mullvad-relay-selector/src/relay_selector/query.rs index bdf6843d41f3..4a9cd7c12b2c 100644 --- a/mullvad-relay-selector/src/relay_selector/query.rs +++ b/mullvad-relay-selector/src/relay_selector/query.rs @@ -261,7 +261,7 @@ pub struct WireguardRelayQuery { pub entry_location: Constraint, pub obfuscation: ObfuscationQuery, pub daita: Constraint, - pub daita_use_anywhere: Constraint, + pub daita_smart_routing: Constraint, pub quantum_resistant: QuantumResistantState, } @@ -347,7 +347,7 @@ impl WireguardRelayQuery { entry_location: Constraint::Any, obfuscation: ObfuscationQuery::Auto, daita: Constraint::Any, - daita_use_anywhere: Constraint::Any, + daita_smart_routing: Constraint::Any, quantum_resistant: QuantumResistantState::Auto, } } @@ -663,15 +663,16 @@ pub mod builder { } // impl-block for after DAITA is set - impl RelayQueryBuilder> { - /// Enable DAITA use_anywhere. - pub fn daita_use_anywhere(mut self, constraint: impl Into>) -> Self { - self.query.wireguard_constraints.daita_use_anywhere = constraint.into(); + impl + RelayQueryBuilder> + { + /// Enable DAITA smart routing. + pub fn daita_smart_routing(mut self, constraint: impl Into>) -> Self { + self.query.wireguard_constraints.daita_smart_routing = constraint.into(); self } } - impl RelayQueryBuilder> { /// 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 fc08ee23c18f..dc05d04eb2aa 100644 --- a/mullvad-relay-selector/tests/relay_selector.rs +++ b/mullvad-relay-selector/tests/relay_selector.rs @@ -1451,7 +1451,7 @@ fn test_daita() { let query = RelayQueryBuilder::new() .wireguard() .daita() - .daita_use_anywhere(false) + .daita_smart_routing(false) .build(); let relay = unwrap_entry_relay(relay_selector.get_relay_by_query(query).unwrap()); assert!( @@ -1463,23 +1463,23 @@ fn test_daita() { let query = RelayQueryBuilder::new() .wireguard() .daita() - .daita_use_anywhere(false) + .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 use_anywhere + // Should be able to connect to non-DAITA relay with smart_routing let query = RelayQueryBuilder::new() .wireguard() .daita() - .daita_use_anywhere(true) + .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_use_anywhere"); + .expect("Expected to find a relay with daita_smart_routing"); match relay { GetRelay::Wireguard { inner: WireguardConfig::Multihop { exit, entry }, @@ -1493,16 +1493,16 @@ fn test_daita() { ), } - // Should be able to connect to DAITA relay with use_anywhere + // Should be able to connect to DAITA relay with smart_routing let query = RelayQueryBuilder::new() .wireguard() .daita() - .daita_use_anywhere(true) + .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_use_anywhere"); + .expect("Expected to find a relay with daita_smart_routing"); match relay { GetRelay::Wireguard { inner: WireguardConfig::Singlehop { exit }, @@ -1537,7 +1537,7 @@ fn test_daita() { let query = RelayQueryBuilder::new() .wireguard() .daita() - .daita_use_anywhere(false) + .daita_smart_routing(false) .multihop() .build(); let relay = relay_selector.get_relay_by_query(query).unwrap(); @@ -1557,7 +1557,7 @@ fn test_daita() { let query = RelayQueryBuilder::new() .wireguard() .daita() - .daita_use_anywhere(false) + .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 592d2382e2af..ba40e42d1800 100644 --- a/mullvad-types/src/features.rs +++ b/mullvad-types/src/features.rs @@ -65,8 +65,14 @@ pub enum FeatureIndicator { ServerIpOverride, CustomMtu, CustomMssFix, + + /// Whether DAITA (without smart routing) is in use. + /// Mutually exclusive with [FeatureIndicator::DaitaSmartRouting]. Daita, - DaitaUseAnywhere, + + /// Whether DAITA (with smart routing) is in use. + /// Mutually exclusive with [FeatureIndicator::Daita]. + DaitaSmartRouting, } impl FeatureIndicator { @@ -86,7 +92,7 @@ impl FeatureIndicator { FeatureIndicator::CustomMtu => "Custom MTU", FeatureIndicator::CustomMssFix => "Custom MSS", FeatureIndicator::Daita => "DAITA", - FeatureIndicator::DaitaUseAnywhere => "Use Anywhere (DAITA)", + FeatureIndicator::DaitaSmartRouting => "DAITA: Smart Routing", } } } @@ -146,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() @@ -160,20 +165,28 @@ pub fn compute_feature_indicators( let mtu = settings.tunnel_options.wireguard.mtu.is_some(); - #[cfg(daita)] - let daita = endpoint.daita; + let mut daita_smart_routing = false; + let mut multihop = false; - #[cfg(daita)] - let daita_use_anywhere = - if let crate::relay_constraints::RelaySettings::Normal(constraints) = - &settings.relay_settings + 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 "use_anywhere" by checking if multihop is + // Detect whether we're using "smart_routing" by checking if multihop is // in use but not explicitly enabled. - daita && multihop && !constraints.wireguard_constraints.use_multihop - } else { - false - }; + 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 && !daita_smart_routing; vec![ (quantum_resistant, FeatureIndicator::QuantumResistance), @@ -183,8 +196,7 @@ pub fn compute_feature_indicators( (mtu, FeatureIndicator::CustomMtu), #[cfg(daita)] (daita, FeatureIndicator::Daita), - #[cfg(daita)] - (daita_use_anywhere, FeatureIndicator::DaitaUseAnywhere), + (daita_smart_routing, FeatureIndicator::DaitaSmartRouting), ] } }; @@ -370,11 +382,13 @@ mod tests { }; expected_indicators .0 - .insert(FeatureIndicator::DaitaUseAnywhere); + .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, - "DaitaUseAnywhere should be enable" + "DaitaSmartRouting should be enabled" ); } @@ -395,7 +409,7 @@ mod tests { FeatureIndicator::CustomMtu => {} FeatureIndicator::CustomMssFix => {} FeatureIndicator::Daita => {} - FeatureIndicator::DaitaUseAnywhere => {} + FeatureIndicator::DaitaSmartRouting => {} } } } diff --git a/mullvad-types/src/wireguard.rs b/mullvad-types/src/wireguard.rs index 8f666575895c..2850e7f10c38 100644 --- a/mullvad-types/src/wireguard.rs +++ b/mullvad-types/src/wireguard.rs @@ -82,7 +82,7 @@ pub struct DaitaSettings { pub enabled: bool, #[serde(default)] - pub use_anywhere: bool, + pub smart_routing: bool, } /// Contains account specific wireguard data