summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDavid Lönnhager <david.l@mullvad.net>2025-09-24 14:47:01 +0200
committerDavid Lönnhager <david.l@mullvad.net>2025-09-30 16:09:04 +0200
commit514b4a8d15c4e3fd8da884cefcb395588d341623 (patch)
tree4a5704083a7760615be6028e02248bac7d2be090
parentffa9bc85f446b21cf6017b979bb6602684033b04 (diff)
downloadmullvadvpn-514b4a8d15c4e3fd8da884cefcb395588d341623.tar.xz
mullvadvpn-514b4a8d15c4e3fd8da884cefcb395588d341623.zip
Add LWO to UI
-rw-r--r--desktop/packages/mullvad-vpn/src/main/daemon-rpc.ts5
-rw-r--r--desktop/packages/mullvad-vpn/src/main/grpc-type-convertions.ts5
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/select-location/RelayListContext.tsx12
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/select-location/SelectLocation.tsx28
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/views/wireguard-settings/components/obfuscation-settings/ObfuscationSettings.tsx3
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/filter-locations.ts25
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/redux/settings/reducers.ts1
-rw-r--r--desktop/packages/mullvad-vpn/src/shared/daemon-rpc-types.ts2
-rw-r--r--desktop/packages/mullvad-vpn/test/e2e/mock-data.ts7
-rw-r--r--desktop/packages/mullvad-vpn/test/e2e/mocked/select-location/helpers.ts9
-rw-r--r--desktop/packages/mullvad-vpn/test/e2e/mocked/select-location/select-location.spec.ts25
11 files changed, 115 insertions, 7 deletions
diff --git a/desktop/packages/mullvad-vpn/src/main/daemon-rpc.ts b/desktop/packages/mullvad-vpn/src/main/daemon-rpc.ts
index 8db5d1b5f2..b7871f4c76 100644
--- a/desktop/packages/mullvad-vpn/src/main/daemon-rpc.ts
+++ b/desktop/packages/mullvad-vpn/src/main/daemon-rpc.ts
@@ -364,6 +364,11 @@ export class DaemonRpc extends GrpcClient {
grpcTypes.ObfuscationSettings.SelectedObfuscation.QUIC,
);
break;
+ case ObfuscationType.lwo:
+ grpcObfuscationSettings.setSelectedObfuscation(
+ grpcTypes.ObfuscationSettings.SelectedObfuscation.LWO,
+ );
+ break;
}
if (obfuscationSettings.udp2tcpSettings) {
diff --git a/desktop/packages/mullvad-vpn/src/main/grpc-type-convertions.ts b/desktop/packages/mullvad-vpn/src/main/grpc-type-convertions.ts
index f4a8945bc6..f29979c520 100644
--- a/desktop/packages/mullvad-vpn/src/main/grpc-type-convertions.ts
+++ b/desktop/packages/mullvad-vpn/src/main/grpc-type-convertions.ts
@@ -133,12 +133,14 @@ function convertFromRelayListRelay(relay: grpcTypes.Relay): IRelayListHostname {
const daita = wireguard ? wireguard.daita : false;
const quic = wireguard?.quic ? quicFromRelayType(wireguard.quic) : undefined;
+ const lwo = wireguard ? wireguard.lwo : false;
return {
...relayObject,
endpointType,
daita,
quic,
+ lwo,
};
}
@@ -727,6 +729,9 @@ function convertFromObfuscationSettings(
case grpcTypes.ObfuscationSettings.SelectedObfuscation.QUIC:
selectedObfuscationType = ObfuscationType.quic;
break;
+ case grpcTypes.ObfuscationSettings.SelectedObfuscation.LWO:
+ selectedObfuscationType = ObfuscationType.lwo;
+ break;
}
return {
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/select-location/RelayListContext.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/select-location/RelayListContext.tsx
index 59a48c4130..643b8a6dbb 100644
--- a/desktop/packages/mullvad-vpn/src/renderer/components/select-location/RelayListContext.tsx
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/select-location/RelayListContext.tsx
@@ -10,6 +10,7 @@ import {
filterLocations,
filterLocationsByDaita,
filterLocationsByEndPointType,
+ filterLocationsByLwo,
filterLocationsByQuic,
getLocationsExpandedBySearch,
searchForLocations,
@@ -76,6 +77,9 @@ export function RelayListContextProvider(props: RelayListContextProviderProps) {
const quic = useSelector(
(state) => state.settings.obfuscationSettings.selectedObfuscation === ObfuscationType.quic,
);
+ const lwo = useSelector(
+ (state) => state.settings.obfuscationSettings.selectedObfuscation === ObfuscationType.lwo,
+ );
const fullRelayList = useSelector((state) => state.settings.relayLocations);
const relaySettings = useNormalRelaySettings();
@@ -113,12 +117,16 @@ export function RelayListContextProvider(props: RelayListContextProviderProps) {
ipVersion,
);
}, [quic, relayListForDaita, locationType, tunnelProtocol, multihop, ipVersion]);
+ // Only show relays that have LWO endpoints when LWO is enabled.
+ const relayListForLwo = useMemo(() => {
+ return filterLocationsByLwo(relayListForQuic, lwo, tunnelProtocol, locationType, multihop);
+ }, [lwo, relayListForQuic, locationType, tunnelProtocol, multihop]);
// Filters the relays to only keep the relays matching the currently selected filters, e.g.
// ownership and providers
const relayListForFilters = useMemo(() => {
- return filterLocations(relayListForQuic, relaySettings?.ownership, relaySettings?.providers);
- }, [relaySettings?.ownership, relaySettings?.providers, relayListForQuic]);
+ return filterLocations(relayListForLwo, relaySettings?.ownership, relaySettings?.providers);
+ }, [relaySettings?.ownership, relaySettings?.providers, relayListForLwo]);
// Filters the relays based on the provided search term
const relayListForSearch = useMemo(() => {
diff --git a/desktop/packages/mullvad-vpn/src/renderer/components/select-location/SelectLocation.tsx b/desktop/packages/mullvad-vpn/src/renderer/components/select-location/SelectLocation.tsx
index 2bcae3428b..5c75cefdaf 100644
--- a/desktop/packages/mullvad-vpn/src/renderer/components/select-location/SelectLocation.tsx
+++ b/desktop/packages/mullvad-vpn/src/renderer/components/select-location/SelectLocation.tsx
@@ -11,6 +11,7 @@ import { useRelaySettingsUpdater } from '../../lib/constraint-updater';
import {
daitaFilterActive,
filterSpecialLocations,
+ lwoFilterActive,
quicFilterActive,
} from '../../lib/filter-locations';
import { useHistory } from '../../lib/history';
@@ -70,7 +71,11 @@ export default function SelectLocation() {
const quic = useSelector(
(state) => state.settings.obfuscationSettings.selectedObfuscation === ObfuscationType.quic,
);
+ const lwo = useSelector(
+ (state) => state.settings.obfuscationSettings.selectedObfuscation === ObfuscationType.lwo,
+ );
const showQuicFilter = quicFilterActive(quic, locationType, tunnelProtocol, multihop);
+ const showLwoFilter = lwoFilterActive(lwo, locationType, tunnelProtocol, multihop);
const showDaitaFilter = daitaFilterActive(
daita,
directOnly,
@@ -129,7 +134,11 @@ export default function SelectLocation() {
const showOwnershipFilter = ownership !== Ownership.any;
const showProvidersFilter = providers.length > 0;
const showFilters =
- showOwnershipFilter || showProvidersFilter || showDaitaFilter || showQuicFilter;
+ showOwnershipFilter ||
+ showProvidersFilter ||
+ showDaitaFilter ||
+ showQuicFilter ||
+ showLwoFilter;
return (
<BackAction action={onClose}>
<Layout>
@@ -226,6 +235,23 @@ export default function SelectLocation() {
</FilterChip.Text>
</FilterChip>
)}
+
+ {showLwoFilter && (
+ <FilterChip as="div">
+ <FilterChip.Text>
+ {sprintf(
+ // TRANSLATORS: Label for indicator that shows that obfuscation is being used as a filter.
+ // TRANSLATORS: Available placeholders:
+ // TRANSLATORS: %(obfuscation)s - type of obfuscation in use
+ messages.pgettext(
+ 'select-location-view',
+ 'Obfuscation: %(obfuscation)s',
+ ),
+ { obfuscation: 'LWO' },
+ )}
+ </FilterChip.Text>
+ </FilterChip>
+ )}
</Flex>
)}
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
index aa9b1e7835..7058548c36 100644
--- 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
@@ -107,6 +107,9 @@ export function ObfuscationSettings() {
<SettingsListbox.BaseOption value={ObfuscationType.quic}>
{messages.pgettext('wireguard-settings-view', 'QUIC')}
</SettingsListbox.BaseOption>
+ <SettingsListbox.BaseOption value={ObfuscationType.lwo}>
+ {messages.pgettext('wireguard-settings-view', 'LWO')}
+ </SettingsListbox.BaseOption>
<SettingsListbox.BaseOption value={ObfuscationType.off}>
{messages.gettext('Off')}
</SettingsListbox.BaseOption>
diff --git a/desktop/packages/mullvad-vpn/src/renderer/lib/filter-locations.ts b/desktop/packages/mullvad-vpn/src/renderer/lib/filter-locations.ts
index 2c125cdb9a..0149f57752 100644
--- a/desktop/packages/mullvad-vpn/src/renderer/lib/filter-locations.ts
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/filter-locations.ts
@@ -50,6 +50,19 @@ export function filterLocationsByQuic(
: locations;
}
+export function filterLocationsByLwo(
+ locations: IRelayLocationCountryRedux[],
+ lwo: boolean,
+ tunnelProtocol: TunnelProtocol,
+ locationType: LocationType,
+ multihop: boolean,
+): IRelayLocationCountryRedux[] {
+ const lwoOnRelay = (relay: IRelayLocationRelayRedux) => relay.lwo;
+ return lwoFilterActive(lwo, locationType, tunnelProtocol, multihop)
+ ? filterLocationsImpl(locations, lwoOnRelay)
+ : locations;
+}
+
export function quicFilterActive(
quic: boolean,
locationType: LocationType,
@@ -62,6 +75,18 @@ export function quicFilterActive(
return quic && isEntry && tunnelProtocol !== 'openvpn';
}
+export function lwoFilterActive(
+ lwo: boolean,
+ locationType: LocationType,
+ tunnelProtocol: TunnelProtocol,
+ multihop: boolean,
+) {
+ const isEntry = multihop
+ ? locationType === LocationType.entry
+ : locationType === LocationType.exit;
+ return lwo && isEntry && tunnelProtocol !== 'openvpn';
+}
+
export function filterLocationsByDaita(
locations: IRelayLocationCountryRedux[],
daita: boolean,
diff --git a/desktop/packages/mullvad-vpn/src/renderer/redux/settings/reducers.ts b/desktop/packages/mullvad-vpn/src/renderer/redux/settings/reducers.ts
index 285a0500e2..0def2b2d98 100644
--- a/desktop/packages/mullvad-vpn/src/renderer/redux/settings/reducers.ts
+++ b/desktop/packages/mullvad-vpn/src/renderer/redux/settings/reducers.ts
@@ -79,6 +79,7 @@ export interface IRelayLocationRelayRedux {
endpointType: RelayEndpointType;
daita: boolean;
quic?: Quic;
+ lwo: boolean;
}
export interface IRelayLocationCityRedux {
diff --git a/desktop/packages/mullvad-vpn/src/shared/daemon-rpc-types.ts b/desktop/packages/mullvad-vpn/src/shared/daemon-rpc-types.ts
index ab7773d1a3..f42a9292e9 100644
--- a/desktop/packages/mullvad-vpn/src/shared/daemon-rpc-types.ts
+++ b/desktop/packages/mullvad-vpn/src/shared/daemon-rpc-types.ts
@@ -399,6 +399,7 @@ export interface IRelayListHostname {
daita: boolean;
// The absence of this value signals that the relay does not deploy QUIC.
quic?: Quic;
+ lwo: boolean;
}
export type Quic = {
@@ -527,6 +528,7 @@ export enum ObfuscationType {
udp2tcp,
shadowsocks,
quic,
+ lwo,
}
export type ObfuscationSettings = {
diff --git a/desktop/packages/mullvad-vpn/test/e2e/mock-data.ts b/desktop/packages/mullvad-vpn/test/e2e/mock-data.ts
index 4bd4436772..0c21bab06b 100644
--- a/desktop/packages/mullvad-vpn/test/e2e/mock-data.ts
+++ b/desktop/packages/mullvad-vpn/test/e2e/mock-data.ts
@@ -22,6 +22,7 @@ const relayList: IRelayList = {
owned: true,
endpointType: 'wireguard',
daita: true,
+ lwo: true,
},
{
hostname: 'mullvad-wireguard-23',
@@ -33,6 +34,7 @@ const relayList: IRelayList = {
owned: true,
endpointType: 'wireguard',
daita: true,
+ lwo: false,
},
{
hostname: 'another-provider-wireguard-1',
@@ -44,6 +46,7 @@ const relayList: IRelayList = {
owned: false,
endpointType: 'wireguard',
daita: true,
+ lwo: false,
},
{
hostname: 'mullvad-wireguard-quic-1',
@@ -60,6 +63,7 @@ const relayList: IRelayList = {
domain: '',
token: '',
},
+ lwo: false,
},
{
hostname: 'mullvad-openvpn-1',
@@ -70,7 +74,8 @@ const relayList: IRelayList = {
weight: 0,
owned: true,
endpointType: 'openvpn',
- daita: true,
+ daita: false,
+ lwo: false,
},
],
},
diff --git a/desktop/packages/mullvad-vpn/test/e2e/mocked/select-location/helpers.ts b/desktop/packages/mullvad-vpn/test/e2e/mocked/select-location/helpers.ts
index ea3e033c30..1a30729ac8 100644
--- a/desktop/packages/mullvad-vpn/test/e2e/mocked/select-location/helpers.ts
+++ b/desktop/packages/mullvad-vpn/test/e2e/mocked/select-location/helpers.ts
@@ -62,10 +62,15 @@ export const createHelpers = (page: Page, routes: RoutesObjectModel, utils: Mock
),
);
- const locateRelaysByObfuscation = (relayList: IRelayList): LocatedRelay[] =>
+ const locateRelaysByObfuscation = (
+ relayList: IRelayList,
+ relayCondition: (relay: IRelayListHostname) => boolean,
+ ): LocatedRelay[] =>
relayList.countries.flatMap((country) =>
country.cities.flatMap((city) =>
- city.relays.filter((relay) => relay.quic).map((relay) => ({ country, city, relay })),
+ city.relays
+ .filter((relay) => relayCondition(relay))
+ .map((relay) => ({ country, city, relay })),
),
);
diff --git a/desktop/packages/mullvad-vpn/test/e2e/mocked/select-location/select-location.spec.ts b/desktop/packages/mullvad-vpn/test/e2e/mocked/select-location/select-location.spec.ts
index d4ea5343f7..3826d9e27e 100644
--- a/desktop/packages/mullvad-vpn/test/e2e/mocked/select-location/select-location.spec.ts
+++ b/desktop/packages/mullvad-vpn/test/e2e/mocked/select-location/select-location.spec.ts
@@ -289,7 +289,30 @@ test.describe('Select location', () => {
}
await util.ipc.settings[''].notify(settings);
- const locatedRelays = helpers.locateRelaysByObfuscation(relayList);
+ const locatedRelays = helpers.locateRelaysByObfuscation(
+ relayList,
+ (relay) => 'quic' in relay,
+ );
+ const relays = locatedRelays.map((locatedRelay) => locatedRelay.relay);
+ const relayNames = relays.map((relay) => relay.hostname);
+
+ await helpers.expandLocatedRelays(locatedRelays);
+
+ const buttons = routes.selectLocation.getRelaysMatching(relayNames);
+
+ // Expect all filtered relays to have a button
+ await expect(buttons).toHaveCount(relays.length);
+ });
+ });
+ test.describe('Filter by LWO', () => {
+ test('Should apply filter when LWO obfuscation is selected', async () => {
+ const settings = getDefaultSettings();
+ if ('normal' in settings.relaySettings) {
+ settings.obfuscationSettings.selectedObfuscation = ObfuscationType.lwo;
+ }
+ await util.ipc.settings[''].notify(settings);
+
+ const locatedRelays = helpers.locateRelaysByObfuscation(relayList, (relay) => relay.lwo);
const relays = locatedRelays.map((locatedRelay) => locatedRelay.relay);
const relayNames = relays.map((relay) => relay.hostname);