summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorOliver <oliver@mohlin.dev>2025-08-28 11:42:51 +0200
committerTobias Järvelöv <tobias.jarvelov@mullvad.net>2025-09-22 12:35:43 +0200
commit0ada6b7e47dfcd008ada1d2fb4d178c95501612a (patch)
tree5b0bfd910d339fb8276d97b4912e1370e9adc9ed
parente4d719bfaf940880684ac8e9ab61ad909c2d8798 (diff)
downloadmullvadvpn-0ada6b7e47dfcd008ada1d2fb4d178c95501612a.tar.xz
mullvadvpn-0ada6b7e47dfcd008ada1d2fb4d178c95501612a.zip
Move wireguard settings component to separate folders
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/WireguardSettingsView.tsx421
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/index.ts5
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/ip-version-setting/IpVersionSetting.tsx86
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/ip-version-setting/index.ts1
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/mtu-setting/MtuSetting.tsx93
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/mtu-setting/index.ts1
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/obfuscation-settings/ObfuscationSettings.tsx109
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/obfuscation-settings/index.ts1
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/port-setting/PortSelector.tsx105
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/port-setting/index.ts1
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/quantum-resistant-setting/QuantumResistantSetting.tsx74
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/quantum-resistant-setting/index.ts1
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/index.ts2
13 files changed, 485 insertions, 415 deletions
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/WireguardSettingsView.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/WireguardSettingsView.tsx
index 283bc5e83d..0750310ab2 100644
--- a/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/WireguardSettingsView.tsx
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/WireguardSettingsView.tsx
@@ -1,27 +1,9 @@
-import { useCallback, useMemo } from 'react';
import { sprintf } from 'sprintf-js';
-import styled from 'styled-components';
import { strings } from '../../../../shared/constants';
-import {
- Constraint,
- IpVersion,
- ObfuscationType,
- wrapConstraint,
-} from '../../../../shared/daemon-rpc-types';
import { messages } from '../../../../shared/gettext';
-import log from '../../../../shared/logging';
-import { RoutePath } from '../../../../shared/routes';
-import { removeNonNumericCharacters } from '../../../../shared/string-helpers';
-import { isInRanges } from '../../../../shared/utils';
-import { useAppContext } from '../../../context';
-import { useRelaySettingsUpdater } from '../../../lib/constraint-updater';
import { useHistory } from '../../../lib/history';
-import { useSelector } from '../../../redux/store';
import { AppNavigationHeader } from '../..';
-import { AriaDescription, AriaInput, AriaInputGroup, AriaLabel } from '../../AriaGroup';
-import * as Cell from '../../cell';
-import Selector, { SelectorItem, SelectorWithCustomItem } from '../../cell/Selector';
import { BackAction } from '../../KeyboardNavigation';
import {
Layout,
@@ -30,22 +12,16 @@ import {
SettingsGroup,
SettingsStack,
} from '../../Layout';
-import { ModalMessage } from '../../Modal';
import { NavigationContainer } from '../../NavigationContainer';
import { NavigationScrollbars } from '../../NavigationScrollbars';
import SettingsHeader, { HeaderTitle } from '../../SettingsHeader';
-
-const MIN_WIREGUARD_MTU_VALUE = 1280;
-const MAX_WIREGUARD_MTU_VALUE = 1420;
-const WIREUGARD_UDP_PORTS = [51820, 53];
-
-function mapPortToSelectorItem(value: number): SelectorItem<number> {
- return { label: value.toString(), value };
-}
-
-const StyledSelectorContainer = styled.div({
- flex: 0,
-});
+import {
+ IpVersionSetting,
+ MtuSetting,
+ ObfuscationSettings,
+ PortSelector,
+ QuantumResistantSetting,
+} from './components';
export function WireguardSettingsView() {
const { pop } = useHistory();
@@ -106,386 +82,3 @@ export function WireguardSettingsView() {
</BackAction>
);
}
-
-function PortSelector() {
- const relaySettings = useSelector((state) => state.settings.relaySettings);
- const relaySettingsUpdater = useRelaySettingsUpdater();
- const allowedPortRanges = useSelector((state) => state.settings.wireguardEndpointData.portRanges);
-
- const wireguardPortItems = useMemo<Array<SelectorItem<number>>>(
- () => WIREUGARD_UDP_PORTS.map(mapPortToSelectorItem),
- [],
- );
-
- const port = useMemo(() => {
- const port = 'normal' in relaySettings ? relaySettings.normal.wireguard.port : 'any';
- return port === 'any' ? null : port;
- }, [relaySettings]);
-
- const setWireguardPort = useCallback(
- async (port: number | null) => {
- try {
- await relaySettingsUpdater((settings) => {
- settings.wireguardConstraints.port = wrapConstraint(port);
- return settings;
- });
- } catch (e) {
- const error = e as Error;
- log.error('Failed to update relay settings', error.message);
- }
- },
- [relaySettingsUpdater],
- );
-
- const parseValue = useCallback((port: string) => parseInt(port), []);
-
- const validateValue = useCallback(
- (value: number) => isInRanges(value, allowedPortRanges),
- [allowedPortRanges],
- );
-
- const portRangesText = allowedPortRanges
- .map(([start, end]) => (start === end ? start : `${start}-${end}`))
- .join(', ');
-
- return (
- <AriaInputGroup>
- <StyledSelectorContainer>
- <SelectorWithCustomItem
- // TRANSLATORS: The title for the WireGuard port selector.
- title={messages.pgettext('wireguard-settings-view', 'Port')}
- items={wireguardPortItems}
- value={port}
- onSelect={setWireguardPort}
- inputPlaceholder={messages.pgettext('wireguard-settings-view', 'Port')}
- automaticValue={null}
- parseValue={parseValue}
- modifyValue={removeNonNumericCharacters}
- validateValue={validateValue}
- maxLength={5}
- details={
- <>
- <ModalMessage>
- {messages.pgettext(
- 'wireguard-settings-view',
- 'The automatic setting will randomly choose from the valid port ranges shown below.',
- )}
- </ModalMessage>
- <ModalMessage>
- {sprintf(
- messages.pgettext(
- 'wireguard-settings-view',
- 'The custom port can be any value inside the valid ranges: %(portRanges)s.',
- ),
- { portRanges: portRangesText },
- )}
- </ModalMessage>
- </>
- }
- />
- </StyledSelectorContainer>
- </AriaInputGroup>
- );
-}
-
-function ObfuscationSettings() {
- const { setObfuscationSettings } = useAppContext();
- const obfuscationSettings = useSelector((state) => state.settings.obfuscationSettings);
-
- // TRANSLATORS: Text showing currently selected port.
- // TRANSLATORS: Available placeholders:
- // TRANSLATORS: %(port)s - Can be either a number between 1 and 65535 or the text "Automatic".
- const subLabelTemplate = messages.pgettext('wireguard-settings-view', 'Port: %(port)s');
-
- const obfuscationType = obfuscationSettings.selectedObfuscation;
- const obfuscationTypeItems: SelectorItem<ObfuscationType>[] = useMemo(
- () => [
- {
- label: messages.pgettext('wireguard-settings-view', 'Shadowsocks'),
- subLabel: sprintf(subLabelTemplate, {
- port: formatPortForSubLabel(obfuscationSettings.shadowsocksSettings.port),
- }),
- value: ObfuscationType.shadowsocks,
- details: {
- path: RoutePath.shadowsocks,
- ariaLabel: messages.pgettext('accessibility', 'Shadowsocks settings'),
- },
- },
- {
- label: messages.pgettext('wireguard-settings-view', 'UDP-over-TCP'),
- subLabel: sprintf(subLabelTemplate, {
- port: formatPortForSubLabel(obfuscationSettings.udp2tcpSettings.port),
- }),
- value: ObfuscationType.udp2tcp,
- details: {
- path: RoutePath.udpOverTcp,
- ariaLabel: messages.pgettext('accessibility', 'UDP-over-TCP settings'),
- },
- },
- {
- label: messages.pgettext('wireguard-settings-view', 'QUIC'),
- value: ObfuscationType.quic,
- },
- {
- label: messages.gettext('Off'),
- value: ObfuscationType.off,
- },
- ],
- [
- obfuscationSettings.shadowsocksSettings.port,
- obfuscationSettings.udp2tcpSettings.port,
- subLabelTemplate,
- ],
- );
-
- const selectObfuscationType = useCallback(
- async (value: ObfuscationType) => {
- await setObfuscationSettings({
- ...obfuscationSettings,
- selectedObfuscation: value,
- });
- },
- [setObfuscationSettings, obfuscationSettings],
- );
-
- return (
- <AriaInputGroup>
- <StyledSelectorContainer>
- <Selector
- // TRANSLATORS: The title for the WireGuard obfuscation selector.
- title={messages.pgettext('wireguard-settings-view', 'Obfuscation')}
- details={
- <ModalMessage>
- {
- // TRANSLATORS: Describes what WireGuard obfuscation does, how it works and when
- // TRANSLATORS: it would be useful to enable it.
- messages.pgettext(
- 'wireguard-settings-view',
- 'Obfuscation hides the WireGuard traffic inside another protocol. It can be used to help circumvent censorship and other types of filtering, where a plain WireGuard connection would be blocked.',
- )
- }
- </ModalMessage>
- }
- items={obfuscationTypeItems}
- value={obfuscationType}
- onSelect={selectObfuscationType}
- automaticValue={ObfuscationType.auto}
- automaticTestId="automatic-obfuscation"
- />
- </StyledSelectorContainer>
- </AriaInputGroup>
- );
-}
-
-function formatPortForSubLabel(port: Constraint<number>): string {
- return port === 'any' ? messages.gettext('Automatic') : `${port.only}`;
-}
-
-function IpVersionSetting() {
- const relaySettingsUpdater = useRelaySettingsUpdater();
- const relaySettings = useSelector((state) => state.settings.relaySettings);
- const ipVersion = useMemo(() => {
- const ipVersion = 'normal' in relaySettings ? relaySettings.normal.wireguard.ipVersion : 'any';
- return ipVersion === 'any' ? null : ipVersion;
- }, [relaySettings]);
-
- const ipVersionItems: SelectorItem<IpVersion>[] = useMemo(
- () => [
- {
- label: messages.gettext('IPv4'),
- value: 'ipv4',
- },
- {
- label: messages.gettext('IPv6'),
- value: 'ipv6',
- },
- ],
- [],
- );
-
- const setIpVersion = useCallback(
- async (ipVersion: IpVersion | null) => {
- try {
- await relaySettingsUpdater((settings) => {
- settings.wireguardConstraints.ipVersion = wrapConstraint(ipVersion);
- return settings;
- });
- } catch (e) {
- const error = e as Error;
- log.error('Failed to update relay settings', error.message);
- }
- },
- [relaySettingsUpdater],
- );
-
- return (
- <AriaInputGroup>
- <StyledSelectorContainer>
- <Selector
- // TRANSLATORS: The title for the WireGuard IP version selector.
- title={messages.pgettext('wireguard-settings-view', 'IP version')}
- items={ipVersionItems}
- value={ipVersion}
- onSelect={setIpVersion}
- automaticValue={null}
- />
- </StyledSelectorContainer>
- <Cell.CellFooter>
- <AriaDescription>
- <Cell.CellFooterText>
- {sprintf(
- // TRANSLATORS: The hint displayed below the WireGuard IP version selector.
- // TRANSLATORS: Available placeholders:
- // TRANSLATORS: %(wireguard)s - Will be replaced with the string "WireGuard"
- messages.pgettext(
- 'wireguard-settings-view',
- 'This allows access to %(wireguard)s for devices that only support IPv6.',
- ),
- { wireguard: strings.wireguard },
- )}
- </Cell.CellFooterText>
- </AriaDescription>
- </Cell.CellFooter>
- </AriaInputGroup>
- );
-}
-
-function mtuIsValid(mtu: string): boolean {
- const parsedMtu = mtu ? parseInt(mtu) : undefined;
- return (
- parsedMtu === undefined ||
- (parsedMtu >= MIN_WIREGUARD_MTU_VALUE && parsedMtu <= MAX_WIREGUARD_MTU_VALUE)
- );
-}
-
-function MtuSetting() {
- const { setWireguardMtu: setWireguardMtuImpl } = useAppContext();
- const mtu = useSelector((state) => state.settings.wireguard.mtu);
-
- const setMtu = useCallback(
- async (mtu?: number) => {
- try {
- await setWireguardMtuImpl(mtu);
- } catch (e) {
- const error = e as Error;
- log.error('Failed to update mtu value', error.message);
- }
- },
- [setWireguardMtuImpl],
- );
-
- const onSubmit = useCallback(
- async (value: string) => {
- const parsedValue = value === '' ? undefined : parseInt(value, 10);
- if (mtuIsValid(value)) {
- await setMtu(parsedValue);
- }
- },
- [setMtu],
- );
-
- return (
- <AriaInputGroup>
- <Cell.Container>
- <AriaLabel>
- <Cell.InputLabel>{messages.pgettext('wireguard-settings-view', 'MTU')}</Cell.InputLabel>
- </AriaLabel>
- <AriaInput>
- <Cell.AutoSizingTextInput
- initialValue={mtu ? mtu.toString() : ''}
- inputMode={'numeric'}
- maxLength={4}
- placeholder={messages.gettext('Default')}
- onSubmitValue={onSubmit}
- validateValue={mtuIsValid}
- submitOnBlur={true}
- modifyValue={removeNonNumericCharacters}
- />
- </AriaInput>
- </Cell.Container>
- <Cell.CellFooter>
- <AriaDescription>
- <Cell.CellFooterText>
- {sprintf(
- // TRANSLATORS: The hint displayed below the WireGuard MTU input field.
- // TRANSLATORS: Available placeholders:
- // TRANSLATORS: %(wireguard)s - Will be replaced with the string "WireGuard"
- // TRANSLATORS: %(max)d - the maximum possible wireguard mtu value
- // TRANSLATORS: %(min)d - the minimum possible wireguard mtu value
- messages.pgettext(
- 'wireguard-settings-view',
- 'Set %(wireguard)s MTU value. Valid range: %(min)d - %(max)d.',
- ),
- {
- wireguard: strings.wireguard,
- min: MIN_WIREGUARD_MTU_VALUE,
- max: MAX_WIREGUARD_MTU_VALUE,
- },
- )}
- </Cell.CellFooterText>
- </AriaDescription>
- </Cell.CellFooter>
- </AriaInputGroup>
- );
-}
-
-function QuantumResistantSetting() {
- const { setWireguardQuantumResistant } = useAppContext();
- const quantumResistant = useSelector((state) => state.settings.wireguard.quantumResistant);
-
- const items: SelectorItem<boolean>[] = useMemo(
- () => [
- {
- label: messages.gettext('On'),
- value: true,
- },
- {
- label: messages.gettext('Off'),
- value: false,
- },
- ],
- [],
- );
-
- const selectQuantumResistant = useCallback(
- async (quantumResistant: boolean | null) => {
- await setWireguardQuantumResistant(quantumResistant ?? undefined);
- },
- [setWireguardQuantumResistant],
- );
-
- return (
- <AriaInputGroup>
- <StyledSelectorContainer>
- <Selector
- title={
- // TRANSLATORS: The title for the WireGuard quantum resistance selector. This setting
- // TRANSLATORS: makes the cryptography resistant to the future abilities of quantum
- // TRANSLATORS: computers.
- messages.pgettext('wireguard-settings-view', 'Quantum-resistant tunnel')
- }
- details={
- <>
- <ModalMessage>
- {messages.pgettext(
- 'wireguard-settings-view',
- 'This feature makes the WireGuard tunnel resistant to potential attacks from quantum computers.',
- )}
- </ModalMessage>
- <ModalMessage>
- {messages.pgettext(
- 'wireguard-settings-view',
- 'It does this by performing an extra key exchange using a quantum safe algorithm and mixing the result into WireGuard’s regular encryption. This extra step uses approximately 500 kiB of traffic every time a new tunnel is established.',
- )}
- </ModalMessage>
- </>
- }
- items={items}
- value={quantumResistant ?? null}
- onSelect={selectQuantumResistant}
- automaticValue={null}
- />
- </StyledSelectorContainer>
- </AriaInputGroup>
- );
-}
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/index.ts b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/index.ts
new file mode 100644
index 0000000000..42aaeef491
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/index.ts
@@ -0,0 +1,5 @@
+export * from './ip-version-setting';
+export * from './mtu-setting';
+export * from './obfuscation-settings';
+export * from './port-setting';
+export * from './quantum-resistant-setting';
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/ip-version-setting/IpVersionSetting.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/ip-version-setting/IpVersionSetting.tsx
new file mode 100644
index 0000000000..4d72450bd3
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/ip-version-setting/IpVersionSetting.tsx
@@ -0,0 +1,86 @@
+import { useCallback, useMemo } from 'react';
+import { sprintf } from 'sprintf-js';
+import styled from 'styled-components';
+
+import { strings } from '../../../../../../shared/constants';
+import { IpVersion, wrapConstraint } from '../../../../../../shared/daemon-rpc-types';
+import { messages } from '../../../../../../shared/gettext';
+import log from '../../../../../../shared/logging';
+import { useRelaySettingsUpdater } from '../../../../../lib/constraint-updater';
+import { useSelector } from '../../../../../redux/store';
+import { AriaDescription, AriaInputGroup } from '../../../../AriaGroup';
+import * as Cell from '../../../../cell';
+import Selector, { SelectorItem } from '../../../../cell/Selector';
+
+const StyledSelectorContainer = styled.div({
+ flex: 0,
+});
+
+export function IpVersionSetting() {
+ const relaySettingsUpdater = useRelaySettingsUpdater();
+ const relaySettings = useSelector((state) => state.settings.relaySettings);
+ const ipVersion = useMemo(() => {
+ const ipVersion = 'normal' in relaySettings ? relaySettings.normal.wireguard.ipVersion : 'any';
+ return ipVersion === 'any' ? null : ipVersion;
+ }, [relaySettings]);
+
+ const ipVersionItems: SelectorItem<IpVersion>[] = useMemo(
+ () => [
+ {
+ label: messages.gettext('IPv4'),
+ value: 'ipv4',
+ },
+ {
+ label: messages.gettext('IPv6'),
+ value: 'ipv6',
+ },
+ ],
+ [],
+ );
+
+ const setIpVersion = useCallback(
+ async (ipVersion: IpVersion | null) => {
+ try {
+ await relaySettingsUpdater((settings) => {
+ settings.wireguardConstraints.ipVersion = wrapConstraint(ipVersion);
+ return settings;
+ });
+ } catch (e) {
+ const error = e as Error;
+ log.error('Failed to update relay settings', error.message);
+ }
+ },
+ [relaySettingsUpdater],
+ );
+
+ return (
+ <AriaInputGroup>
+ <StyledSelectorContainer>
+ <Selector
+ // TRANSLATORS: The title for the WireGuard IP version selector.
+ title={messages.pgettext('wireguard-settings-view', 'IP version')}
+ items={ipVersionItems}
+ value={ipVersion}
+ onSelect={setIpVersion}
+ automaticValue={null}
+ />
+ </StyledSelectorContainer>
+ <Cell.CellFooter>
+ <AriaDescription>
+ <Cell.CellFooterText>
+ {sprintf(
+ // TRANSLATORS: The hint displayed below the WireGuard IP version selector.
+ // TRANSLATORS: Available placeholders:
+ // TRANSLATORS: %(wireguard)s - Will be replaced with the string "WireGuard"
+ messages.pgettext(
+ 'wireguard-settings-view',
+ 'This allows access to %(wireguard)s for devices that only support IPv6.',
+ ),
+ { wireguard: strings.wireguard },
+ )}
+ </Cell.CellFooterText>
+ </AriaDescription>
+ </Cell.CellFooter>
+ </AriaInputGroup>
+ );
+}
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/ip-version-setting/index.ts b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/ip-version-setting/index.ts
new file mode 100644
index 0000000000..947b3403ce
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/ip-version-setting/index.ts
@@ -0,0 +1 @@
+export * from './IpVersionSetting';
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/mtu-setting/MtuSetting.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/mtu-setting/MtuSetting.tsx
new file mode 100644
index 0000000000..3b8ed56c81
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/mtu-setting/MtuSetting.tsx
@@ -0,0 +1,93 @@
+import { useCallback } from 'react';
+import { sprintf } from 'sprintf-js';
+
+import { strings } from '../../../../../../shared/constants';
+import { messages } from '../../../../../../shared/gettext';
+import log from '../../../../../../shared/logging';
+import { removeNonNumericCharacters } from '../../../../../../shared/string-helpers';
+import { useAppContext } from '../../../../../context';
+import { useSelector } from '../../../../../redux/store';
+import { AriaDescription, AriaInput, AriaInputGroup, AriaLabel } from '../../../../AriaGroup';
+import * as Cell from '../../../../cell';
+
+const MIN_WIREGUARD_MTU_VALUE = 1280;
+const MAX_WIREGUARD_MTU_VALUE = 1420;
+
+function mtuIsValid(mtu: string): boolean {
+ const parsedMtu = mtu ? parseInt(mtu) : undefined;
+ return (
+ parsedMtu === undefined ||
+ (parsedMtu >= MIN_WIREGUARD_MTU_VALUE && parsedMtu <= MAX_WIREGUARD_MTU_VALUE)
+ );
+}
+
+export function MtuSetting() {
+ const { setWireguardMtu: setWireguardMtuImpl } = useAppContext();
+ const mtu = useSelector((state) => state.settings.wireguard.mtu);
+
+ const setMtu = useCallback(
+ async (mtu?: number) => {
+ try {
+ await setWireguardMtuImpl(mtu);
+ } catch (e) {
+ const error = e as Error;
+ log.error('Failed to update mtu value', error.message);
+ }
+ },
+ [setWireguardMtuImpl],
+ );
+
+ const onSubmit = useCallback(
+ async (value: string) => {
+ const parsedValue = value === '' ? undefined : parseInt(value, 10);
+ if (mtuIsValid(value)) {
+ await setMtu(parsedValue);
+ }
+ },
+ [setMtu],
+ );
+
+ return (
+ <AriaInputGroup>
+ <Cell.Container>
+ <AriaLabel>
+ <Cell.InputLabel>{messages.pgettext('wireguard-settings-view', 'MTU')}</Cell.InputLabel>
+ </AriaLabel>
+ <AriaInput>
+ <Cell.AutoSizingTextInput
+ initialValue={mtu ? mtu.toString() : ''}
+ inputMode={'numeric'}
+ maxLength={4}
+ placeholder={messages.gettext('Default')}
+ onSubmitValue={onSubmit}
+ validateValue={mtuIsValid}
+ submitOnBlur={true}
+ modifyValue={removeNonNumericCharacters}
+ />
+ </AriaInput>
+ </Cell.Container>
+ <Cell.CellFooter>
+ <AriaDescription>
+ <Cell.CellFooterText>
+ {sprintf(
+ // TRANSLATORS: The hint displayed below the WireGuard MTU input field.
+ // TRANSLATORS: Available placeholders:
+ // TRANSLATORS: %(wireguard)s - Will be replaced with the string "WireGuard"
+ // TRANSLATORS: %(max)d - the maximum possible wireguard mtu value
+ // TRANSLATORS: %(min)d - the minimum possible wireguard mtu value
+ messages.pgettext(
+ 'wireguard-settings-view',
+ 'Set %(wireguard)s MTU value. Valid range: %(min)d - %(max)d.',
+ ),
+ {
+ wireguard: strings.wireguard,
+ min: MIN_WIREGUARD_MTU_VALUE,
+ max: MAX_WIREGUARD_MTU_VALUE,
+ },
+ )}
+ </Cell.CellFooterText>
+ </AriaDescription>
+ </Cell.CellFooter>
+ </AriaInputGroup>
+ );
+}
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/mtu-setting/index.ts b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/mtu-setting/index.ts
new file mode 100644
index 0000000000..8925e802c8
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/mtu-setting/index.ts
@@ -0,0 +1 @@
+export * from './MtuSetting';
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/obfuscation-settings/ObfuscationSettings.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/obfuscation-settings/ObfuscationSettings.tsx
new file mode 100644
index 0000000000..8ad91f387e
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/obfuscation-settings/ObfuscationSettings.tsx
@@ -0,0 +1,109 @@
+import { useCallback, useMemo } from 'react';
+import { sprintf } from 'sprintf-js';
+import styled from 'styled-components';
+
+import { Constraint, ObfuscationType } from '../../../../../../shared/daemon-rpc-types';
+import { messages } from '../../../../../../shared/gettext';
+import { RoutePath } from '../../../../../../shared/routes';
+import { useAppContext } from '../../../../../context';
+import { useSelector } from '../../../../../redux/store';
+import { AriaInputGroup } from '../../../../AriaGroup';
+import Selector, { SelectorItem } from '../../../../cell/Selector';
+import { ModalMessage } from '../../../../Modal';
+
+const StyledSelectorContainer = styled.div({
+ flex: 0,
+});
+
+export function formatPortForSubLabel(port: Constraint<number>): string {
+ return port === 'any' ? messages.gettext('Automatic') : `${port.only}`;
+}
+
+export function ObfuscationSettings() {
+ const { setObfuscationSettings } = useAppContext();
+ const obfuscationSettings = useSelector((state) => state.settings.obfuscationSettings);
+
+ // TRANSLATORS: Text showing currently selected port.
+ // TRANSLATORS: Available placeholders:
+ // TRANSLATORS: %(port)s - Can be either a number between 1 and 65535 or the text "Automatic".
+ const subLabelTemplate = messages.pgettext('wireguard-settings-view', 'Port: %(port)s');
+
+ const obfuscationType = obfuscationSettings.selectedObfuscation;
+ const obfuscationTypeItems: SelectorItem<ObfuscationType>[] = useMemo(
+ () => [
+ {
+ label: messages.pgettext('wireguard-settings-view', 'Shadowsocks'),
+ subLabel: sprintf(subLabelTemplate, {
+ port: formatPortForSubLabel(obfuscationSettings.shadowsocksSettings.port),
+ }),
+ value: ObfuscationType.shadowsocks,
+ details: {
+ path: RoutePath.shadowsocks,
+ ariaLabel: messages.pgettext('accessibility', 'Shadowsocks settings'),
+ },
+ },
+ {
+ label: messages.pgettext('wireguard-settings-view', 'UDP-over-TCP'),
+ subLabel: sprintf(subLabelTemplate, {
+ port: formatPortForSubLabel(obfuscationSettings.udp2tcpSettings.port),
+ }),
+ value: ObfuscationType.udp2tcp,
+ details: {
+ path: RoutePath.udpOverTcp,
+ ariaLabel: messages.pgettext('accessibility', 'UDP-over-TCP settings'),
+ },
+ },
+ {
+ label: messages.pgettext('wireguard-settings-view', 'QUIC'),
+ value: ObfuscationType.quic,
+ },
+ {
+ label: messages.gettext('Off'),
+ value: ObfuscationType.off,
+ },
+ ],
+ [
+ obfuscationSettings.shadowsocksSettings.port,
+ obfuscationSettings.udp2tcpSettings.port,
+ subLabelTemplate,
+ ],
+ );
+
+ const selectObfuscationType = useCallback(
+ async (value: ObfuscationType) => {
+ await setObfuscationSettings({
+ ...obfuscationSettings,
+ selectedObfuscation: value,
+ });
+ },
+ [setObfuscationSettings, obfuscationSettings],
+ );
+
+ return (
+ <AriaInputGroup>
+ <StyledSelectorContainer>
+ <Selector
+ // TRANSLATORS: The title for the WireGuard obfuscation selector.
+ title={messages.pgettext('wireguard-settings-view', 'Obfuscation')}
+ details={
+ <ModalMessage>
+ {
+ // TRANSLATORS: Describes what WireGuard obfuscation does, how it works and when
+ // TRANSLATORS: it would be useful to enable it.
+ messages.pgettext(
+ 'wireguard-settings-view',
+ 'Obfuscation hides the WireGuard traffic inside another protocol. It can be used to help circumvent censorship and other types of filtering, where a plain WireGuard connection would be blocked.',
+ )
+ }
+ </ModalMessage>
+ }
+ items={obfuscationTypeItems}
+ value={obfuscationType}
+ onSelect={selectObfuscationType}
+ automaticValue={ObfuscationType.auto}
+ automaticTestId="automatic-obfuscation"
+ />
+ </StyledSelectorContainer>
+ </AriaInputGroup>
+ );
+}
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/obfuscation-settings/index.ts b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/obfuscation-settings/index.ts
new file mode 100644
index 0000000000..16dbafd65d
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/obfuscation-settings/index.ts
@@ -0,0 +1 @@
+export * from './ObfuscationSettings';
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/port-setting/PortSelector.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/port-setting/PortSelector.tsx
new file mode 100644
index 0000000000..f11abde22b
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/port-setting/PortSelector.tsx
@@ -0,0 +1,105 @@
+import { useCallback, useMemo } from 'react';
+import { sprintf } from 'sprintf-js';
+import styled from 'styled-components';
+
+import { wrapConstraint } from '../../../../../../shared/daemon-rpc-types';
+import { messages } from '../../../../../../shared/gettext';
+import log from '../../../../../../shared/logging';
+import { removeNonNumericCharacters } from '../../../../../../shared/string-helpers';
+import { isInRanges } from '../../../../../../shared/utils';
+import { useRelaySettingsUpdater } from '../../../../../lib/constraint-updater';
+import { useSelector } from '../../../../../redux/store';
+import { AriaInputGroup } from '../../../../AriaGroup';
+import { SelectorItem, SelectorWithCustomItem } from '../../../../cell/Selector';
+import { ModalMessage } from '../../../../Modal';
+
+const WIREUGARD_UDP_PORTS = [51820, 53];
+
+function mapPortToSelectorItem(value: number): SelectorItem<number> {
+ return { label: value.toString(), value };
+}
+
+const StyledSelectorContainer = styled.div({
+ flex: 0,
+});
+
+export function PortSelector() {
+ const relaySettings = useSelector((state) => state.settings.relaySettings);
+ const relaySettingsUpdater = useRelaySettingsUpdater();
+ const allowedPortRanges = useSelector((state) => state.settings.wireguardEndpointData.portRanges);
+
+ const wireguardPortItems = useMemo<Array<SelectorItem<number>>>(
+ () => WIREUGARD_UDP_PORTS.map(mapPortToSelectorItem),
+ [],
+ );
+
+ const port = useMemo(() => {
+ const port = 'normal' in relaySettings ? relaySettings.normal.wireguard.port : 'any';
+ return port === 'any' ? null : port;
+ }, [relaySettings]);
+
+ const setWireguardPort = useCallback(
+ async (port: number | null) => {
+ try {
+ await relaySettingsUpdater((settings) => {
+ settings.wireguardConstraints.port = wrapConstraint(port);
+ return settings;
+ });
+ } catch (e) {
+ const error = e as Error;
+ log.error('Failed to update relay settings', error.message);
+ }
+ },
+ [relaySettingsUpdater],
+ );
+
+ const parseValue = useCallback((port: string) => parseInt(port), []);
+
+ const validateValue = useCallback(
+ (value: number) => isInRanges(value, allowedPortRanges),
+ [allowedPortRanges],
+ );
+
+ const portRangesText = allowedPortRanges
+ .map(([start, end]) => (start === end ? start : `${start}-${end}`))
+ .join(', ');
+
+ return (
+ <AriaInputGroup>
+ <StyledSelectorContainer>
+ <SelectorWithCustomItem
+ // TRANSLATORS: The title for the WireGuard port selector.
+ title={messages.pgettext('wireguard-settings-view', 'Port')}
+ items={wireguardPortItems}
+ value={port}
+ onSelect={setWireguardPort}
+ inputPlaceholder={messages.pgettext('wireguard-settings-view', 'Port')}
+ automaticValue={null}
+ parseValue={parseValue}
+ modifyValue={removeNonNumericCharacters}
+ validateValue={validateValue}
+ maxLength={5}
+ details={
+ <>
+ <ModalMessage>
+ {messages.pgettext(
+ 'wireguard-settings-view',
+ 'The automatic setting will randomly choose from the valid port ranges shown below.',
+ )}
+ </ModalMessage>
+ <ModalMessage>
+ {sprintf(
+ messages.pgettext(
+ 'wireguard-settings-view',
+ 'The custom port can be any value inside the valid ranges: %(portRanges)s.',
+ ),
+ { portRanges: portRangesText },
+ )}
+ </ModalMessage>
+ </>
+ }
+ />
+ </StyledSelectorContainer>
+ </AriaInputGroup>
+ );
+}
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/port-setting/index.ts b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/port-setting/index.ts
new file mode 100644
index 0000000000..b936bda065
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/port-setting/index.ts
@@ -0,0 +1 @@
+export * from './PortSelector';
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/quantum-resistant-setting/QuantumResistantSetting.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/quantum-resistant-setting/QuantumResistantSetting.tsx
new file mode 100644
index 0000000000..f853d913a4
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/quantum-resistant-setting/QuantumResistantSetting.tsx
@@ -0,0 +1,74 @@
+import { useCallback, useMemo } from 'react';
+import styled from 'styled-components';
+
+import { messages } from '../../../../../../shared/gettext';
+import { useAppContext } from '../../../../../context';
+import { useSelector } from '../../../../../redux/store';
+import { AriaInputGroup } from '../../../../AriaGroup';
+import Selector, { SelectorItem } from '../../../../cell/Selector';
+import { ModalMessage } from '../../../../Modal';
+
+const StyledSelectorContainer = styled.div({
+ flex: 0,
+});
+
+export function QuantumResistantSetting() {
+ const { setWireguardQuantumResistant } = useAppContext();
+ const quantumResistant = useSelector((state) => state.settings.wireguard.quantumResistant);
+
+ const items: SelectorItem<boolean>[] = useMemo(
+ () => [
+ {
+ label: messages.gettext('On'),
+ value: true,
+ },
+ {
+ label: messages.gettext('Off'),
+ value: false,
+ },
+ ],
+ [],
+ );
+
+ const selectQuantumResistant = useCallback(
+ async (quantumResistant: boolean | null) => {
+ await setWireguardQuantumResistant(quantumResistant ?? undefined);
+ },
+ [setWireguardQuantumResistant],
+ );
+
+ return (
+ <AriaInputGroup>
+ <StyledSelectorContainer>
+ <Selector
+ title={
+ // TRANSLATORS: The title for the WireGuard quantum resistance selector. This setting
+ // TRANSLATORS: makes the cryptography resistant to the future abilities of quantum
+ // TRANSLATORS: computers.
+ messages.pgettext('wireguard-settings-view', 'Quantum-resistant tunnel')
+ }
+ details={
+ <>
+ <ModalMessage>
+ {messages.pgettext(
+ 'wireguard-settings-view',
+ 'This feature makes the WireGuard tunnel resistant to potential attacks from quantum computers.',
+ )}
+ </ModalMessage>
+ <ModalMessage>
+ {messages.pgettext(
+ 'wireguard-settings-view',
+ 'It does this by performing an extra key exchange using a quantum safe algorithm and mixing the result into WireGuard’s regular encryption. This extra step uses approximately 500 kiB of traffic every time a new tunnel is established.',
+ )}
+ </ModalMessage>
+ </>
+ }
+ items={items}
+ value={quantumResistant ?? null}
+ onSelect={selectQuantumResistant}
+ automaticValue={null}
+ />
+ </StyledSelectorContainer>
+ </AriaInputGroup>
+ );
+}
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/quantum-resistant-setting/index.ts b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/quantum-resistant-setting/index.ts
new file mode 100644
index 0000000000..9f4ee97b0b
--- /dev/null
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/quantum-resistant-setting/index.ts
@@ -0,0 +1 @@
+export * from './QuantumResistantSetting';
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/index.ts b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/index.ts
index 8b6d14ab5a..6f59813edc 100644
--- a/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/index.ts
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/index.ts
@@ -1 +1 @@
-export * from './WireguardSettings';
+export * from './WireguardSettingsView';