summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/data/DummyRelayItems.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayItemExtensions.kt17
-rw-r--r--android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/CustomListLocationsViewModelTest.kt2
-rw-r--r--android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt14
-rw-r--r--android/lib/daemon-grpc/src/test/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/RelayNameComparatorTest.kt20
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RelayItem.kt1
-rw-r--r--android/lib/ui/component/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/component/relaylist/RelayItemPreviewData.kt1
-rw-r--r--desktop/packages/mullvad-vpn/locales/messages.pot7
-rw-r--r--desktop/packages/mullvad-vpn/src/main/grpc-type-convertions.ts38
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/select-location/RelayListContext.tsx19
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/components/select-location/SelectLocation.tsx25
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/lib/filter-locations.ts10
-rw-r--r--desktop/packages/mullvad-vpn/src/renderer/redux/settings/reducers.ts2
-rw-r--r--desktop/packages/mullvad-vpn/src/shared/daemon-rpc-types.ts9
-rw-r--r--desktop/packages/mullvad-vpn/test/e2e/mocked/select-location/helpers.ts8
-rw-r--r--desktop/packages/mullvad-vpn/test/e2e/mocked/select-location/mock-data.ts16
-rw-r--r--desktop/packages/mullvad-vpn/test/e2e/mocked/select-location/select-location.spec.ts159
-rw-r--r--mullvad-api/src/relay_list.rs78
-rw-r--r--mullvad-management-interface/proto/management_interface.proto37
-rw-r--r--mullvad-management-interface/src/types/conversions/mod.rs19
-rw-r--r--mullvad-management-interface/src/types/conversions/relay_list.rs157
-rw-r--r--mullvad-relay-selector/src/relay_selector/helpers.rs33
-rw-r--r--mullvad-relay-selector/src/relay_selector/matcher.rs2
-rw-r--r--mullvad-relay-selector/tests/relay_selector.rs130
-rw-r--r--mullvad-types/src/relay_list.rs128
25 files changed, 540 insertions, 394 deletions
diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/data/DummyRelayItems.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/data/DummyRelayItems.kt
index 8e4024a4d5..27b5951cea 100644
--- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/data/DummyRelayItems.kt
+++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/data/DummyRelayItems.kt
@@ -22,6 +22,7 @@ private val DUMMY_RELAY_1 =
provider = ProviderId("PROVIDER RENTED"),
ownership = Ownership.Rented,
daita = false,
+ quic = false,
)
private val DUMMY_RELAY_2 =
RelayItem.Location.Relay(
@@ -34,6 +35,7 @@ private val DUMMY_RELAY_2 =
provider = ProviderId("PROVIDER OWNED"),
ownership = Ownership.MullvadOwned,
daita = false,
+ quic = false,
)
private val DUMMY_RELAY_CITY_1 =
RelayItem.Location.City(
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayItemExtensions.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayItemExtensions.kt
index 6fe027249e..4803b966a9 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayItemExtensions.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayItemExtensions.kt
@@ -95,23 +95,16 @@ private fun RelayItem.Location.City.filter(
}
}
-private fun RelayItem.Location.Relay.hasMatchingDaitaSetting(filterDaita: Boolean): Boolean {
- return if (filterDaita) daita else true
-}
+private fun RelayItem.Location.Relay.hasMatchingDaitaSetting(filterDaita: Boolean): Boolean =
+ if (filterDaita) daita else true
private fun RelayItem.Location.Relay.filter(
ownership: Constraint<Ownership>,
providers: Constraint<Providers>,
daita: Boolean,
-): RelayItem.Location.Relay? {
- return if (
- hasMatchingDaitaSetting(daita) && hasOwnership(ownership) && hasProvider(providers)
- ) {
- this
- } else {
- null
- }
-}
+): RelayItem.Location.Relay? =
+ if (hasMatchingDaitaSetting(daita) && hasOwnership(ownership) && hasProvider(providers)) this
+ else null
fun List<RelayItem.Location.Country>.findByGeoLocationId(
geoLocationId: GeoLocationId
diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/CustomListLocationsViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/CustomListLocationsViewModelTest.kt
index 9b31f8bf24..a3257f04d9 100644
--- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/CustomListLocationsViewModelTest.kt
+++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/CustomListLocationsViewModelTest.kt
@@ -364,6 +364,7 @@ class CustomListLocationsViewModelTest {
provider = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
daita = false,
+ quic = false,
)
),
)
@@ -381,6 +382,7 @@ class CustomListLocationsViewModelTest {
provider = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
daita = false,
+ quic = false,
)
}
}
diff --git a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt
index 02609da2f8..140cf5aafb 100644
--- a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt
+++ b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt
@@ -79,7 +79,6 @@ import net.mullvad.mullvadvpn.lib.model.TunnelState
import net.mullvad.mullvadvpn.lib.model.Udp2TcpObfuscationSettings
import net.mullvad.mullvadvpn.lib.model.WireguardConstraints
import net.mullvad.mullvadvpn.lib.model.WireguardEndpointData
-import net.mullvad.mullvadvpn.lib.model.WireguardRelayEndpointData
import net.mullvad.mullvadvpn.lib.model.WireguardTunnelOptions
internal fun ManagementInterface.TunnelState.toDomain(): TunnelState =
@@ -544,9 +543,6 @@ internal fun ManagementInterface.WireguardEndpointData.toDomain(): WireguardEndp
shadowsocksPortRangesList.map { it.toDomain() },
)
-internal fun ManagementInterface.WireguardRelayEndpointData.toDomain(): WireguardRelayEndpointData =
- WireguardRelayEndpointData(daita)
-
internal fun ManagementInterface.PortRange.toDomain(): PortRange = PortRange(first..last)
/**
@@ -581,7 +577,7 @@ internal fun ManagementInterface.RelayListCity.toDomain(
id = cityCode,
relays =
relaysList
- .filter { it.endpointType == ManagementInterface.Relay.RelayType.WIREGUARD }
+ .filter { it.endpointData.hasWireguard() }
.map { it.toDomain(cityCode) }
.sortedWith(RelayNameComparator),
)
@@ -595,12 +591,8 @@ internal fun ManagementInterface.Relay.toDomain(
active = active,
provider = ProviderId(provider),
ownership = if (owned) Ownership.MullvadOwned else Ownership.Rented,
- daita =
- if (
- hasEndpointData() && endpointType == ManagementInterface.Relay.RelayType.WIREGUARD
- ) {
- ManagementInterface.WireguardRelayEndpointData.parseFrom(endpointData.value).daita
- } else false,
+ daita = endpointData.wireguard.daita,
+ quic = endpointData.wireguard.hasQuic(),
)
private fun Instant.atDefaultZone() = atZone(ZoneId.systemDefault())
diff --git a/android/lib/daemon-grpc/src/test/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/RelayNameComparatorTest.kt b/android/lib/daemon-grpc/src/test/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/RelayNameComparatorTest.kt
index df710649c5..e45c8087fd 100644
--- a/android/lib/daemon-grpc/src/test/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/RelayNameComparatorTest.kt
+++ b/android/lib/daemon-grpc/src/test/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/RelayNameComparatorTest.kt
@@ -26,6 +26,7 @@ class RelayNameComparatorTest {
provider = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
daita = false,
+ quic = false,
)
val relay10 =
RelayItem.Location.Relay(
@@ -34,6 +35,7 @@ class RelayNameComparatorTest {
provider = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
daita = false,
+ quic = false,
)
relay9 assertOrderBothDirection relay10
@@ -48,6 +50,7 @@ class RelayNameComparatorTest {
provider = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
daita = false,
+ quic = false,
)
val relay9b =
RelayItem.Location.Relay(
@@ -56,6 +59,7 @@ class RelayNameComparatorTest {
provider = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
daita = false,
+ quic = false,
)
assertTrue(RelayNameComparator.compare(relay9a, relay9b) == 0)
@@ -71,6 +75,7 @@ class RelayNameComparatorTest {
provider = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
daita = false,
+ quic = false,
)
val relay1 =
RelayItem.Location.Relay(
@@ -79,6 +84,7 @@ class RelayNameComparatorTest {
provider = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
daita = false,
+ quic = false,
)
val relay3 =
RelayItem.Location.Relay(
@@ -87,6 +93,7 @@ class RelayNameComparatorTest {
provider = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
daita = false,
+ quic = false,
)
val relay100 =
RelayItem.Location.Relay(
@@ -95,6 +102,7 @@ class RelayNameComparatorTest {
provider = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
daita = false,
+ quic = false,
)
relay001 assertOrderBothDirection relay1
@@ -112,6 +120,7 @@ class RelayNameComparatorTest {
provider = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
daita = false,
+ quic = false,
)
val relay9b =
RelayItem.Location.Relay(
@@ -120,6 +129,7 @@ class RelayNameComparatorTest {
provider = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
daita = false,
+ quic = false,
)
assertTrue(RelayNameComparator.compare(relay9a, relay9b) == 0)
@@ -135,6 +145,7 @@ class RelayNameComparatorTest {
provider = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
daita = false,
+ quic = false,
)
val relay005 =
RelayItem.Location.Relay(
@@ -143,6 +154,7 @@ class RelayNameComparatorTest {
provider = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
daita = false,
+ quic = false,
)
relay001 assertOrderBothDirection relay005
@@ -157,6 +169,7 @@ class RelayNameComparatorTest {
provider = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
daita = false,
+ quic = false,
)
val relayAr8 =
RelayItem.Location.Relay(
@@ -165,6 +178,7 @@ class RelayNameComparatorTest {
provider = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
daita = false,
+ quic = false,
)
val relaySe5 =
RelayItem.Location.Relay(
@@ -173,6 +187,7 @@ class RelayNameComparatorTest {
provider = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
daita = false,
+ quic = false,
)
val relaySe10 =
RelayItem.Location.Relay(
@@ -181,6 +196,7 @@ class RelayNameComparatorTest {
provider = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
daita = false,
+ quic = false,
)
relayAr2 assertOrderBothDirection relayAr8
@@ -197,6 +213,7 @@ class RelayNameComparatorTest {
provider = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
daita = false,
+ quic = false,
)
val relay2w =
RelayItem.Location.Relay(
@@ -205,6 +222,7 @@ class RelayNameComparatorTest {
provider = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
daita = false,
+ quic = false,
)
relay2c assertOrderBothDirection relay2w
@@ -219,6 +237,7 @@ class RelayNameComparatorTest {
provider = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
daita = false,
+ quic = false,
)
val relay22b =
RelayItem.Location.Relay(
@@ -227,6 +246,7 @@ class RelayNameComparatorTest {
provider = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
daita = false,
+ quic = false,
)
relay22a assertOrderBothDirection relay22b
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RelayItem.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RelayItem.kt
index 27ce80c016..b1df67fea6 100644
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RelayItem.kt
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RelayItem.kt
@@ -85,6 +85,7 @@ sealed interface RelayItem {
val ownership: Ownership,
override val active: Boolean,
val daita: Boolean,
+ val quic: Boolean,
) : Location {
override val name: String = id.code
override val hasChildren: Boolean = false
diff --git a/android/lib/ui/component/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/component/relaylist/RelayItemPreviewData.kt b/android/lib/ui/component/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/component/relaylist/RelayItemPreviewData.kt
index 35397a6a27..724c567595 100644
--- a/android/lib/ui/component/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/component/relaylist/RelayItemPreviewData.kt
+++ b/android/lib/ui/component/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/component/relaylist/RelayItemPreviewData.kt
@@ -51,6 +51,7 @@ private fun generateRelayItemRelay(
provider = ProviderId("Provider"),
ownership = Ownership.MullvadOwned,
daita = daita,
+ quic = false,
)
private fun String.generateCountryCode() =
diff --git a/desktop/packages/mullvad-vpn/locales/messages.pot b/desktop/packages/mullvad-vpn/locales/messages.pot
index 0f1f997ba8..6283cf0273 100644
--- a/desktop/packages/mullvad-vpn/locales/messages.pot
+++ b/desktop/packages/mullvad-vpn/locales/messages.pot
@@ -1944,6 +1944,13 @@ msgctxt "select-location-view"
msgid "Name is already taken."
msgstr ""
+#. Label for indicator that shows that obfuscation is being used as a filter.
+#. Available placeholders:
+#. %(obfuscation)s - type of obfuscation in use
+msgctxt "select-location-view"
+msgid "Obfuscation: %(obfuscation)s"
+msgstr ""
+
msgctxt "select-location-view"
msgid "Open %(daita)s settings"
msgstr ""
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 6d8ebcb17b..4b79b0b1d9 100644
--- a/desktop/packages/mullvad-vpn/src/main/grpc-type-convertions.ts
+++ b/desktop/packages/mullvad-vpn/src/main/grpc-type-convertions.ts
@@ -52,7 +52,7 @@ import {
ObfuscationType,
Ownership,
ProxyType,
- RelayEndpointType,
+ Quic,
RelayLocation,
RelayLocationGeographical,
RelayProtocol,
@@ -118,28 +118,36 @@ function convertFromRelayListCity(city: grpcTypes.RelayListCity): IRelayListCity
function convertFromRelayListRelay(relay: grpcTypes.Relay): IRelayListHostname {
const relayObject = relay.toObject();
- let daita = false;
- if (relayObject.endpointType === grpcTypes.Relay.RelayType.WIREGUARD) {
- const endpointDataU8 = relay.getEndpointData()?.getValue_asU8();
- if (endpointDataU8) {
- daita = grpcTypes.WireguardRelayEndpointData.deserializeBinary(endpointDataU8).getDaita();
- }
- }
+ // The relay type is determined by the variant of the extra endpoint data
+ const wireguard = relayObject.endpointData?.wireguard;
+ const openvpn = relayObject.endpointData?.openvpn;
+ const bridge = relayObject.endpointData?.bridge;
+
+ const endpointType = wireguard
+ ? 'wireguard'
+ : openvpn
+ ? 'openvpn'
+ : bridge
+ ? 'bridge'
+ : /*This case should never happen ..*/ 'bridge';
+
+ const daita = wireguard ? wireguard.daita : false;
+ const quic = wireguard?.quic ? quicFromRelayType(wireguard.quic) : undefined;
return {
...relayObject,
- endpointType: convertFromRelayType(relayObject.endpointType),
+ endpointType,
daita,
+ quic,
};
}
-function convertFromRelayType(relayType: grpcTypes.Relay.RelayType): RelayEndpointType {
- const protocolMap: Record<grpcTypes.Relay.RelayType, RelayEndpointType> = {
- [grpcTypes.Relay.RelayType.OPENVPN]: 'openvpn',
- [grpcTypes.Relay.RelayType.BRIDGE]: 'bridge',
- [grpcTypes.Relay.RelayType.WIREGUARD]: 'wireguard',
+function quicFromRelayType(quic: grpcTypes.Relay.RelayData.Wireguard.Quic.AsObject): Quic {
+ return {
+ domain: quic.domain,
+ token: quic.token,
+ addrIn: quic.addrInList,
};
- return protocolMap[relayType];
}
function convertFromWireguardKey(publicKey: Uint8Array | string): string {
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 07b2aa9c62..d300c56e7f 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
@@ -1,11 +1,16 @@
import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react';
-import { compareRelayLocation, RelayLocation } from '../../../shared/daemon-rpc-types';
+import {
+ compareRelayLocation,
+ ObfuscationType,
+ RelayLocation,
+} from '../../../shared/daemon-rpc-types';
import {
EndpointType,
filterLocations,
filterLocationsByDaita,
filterLocationsByEndPointType,
+ filterLocationsByQuic,
getLocationsExpandedBySearch,
searchForLocations,
} from '../../lib/filter-locations';
@@ -68,6 +73,9 @@ export function RelayListContextProvider(props: RelayListContextProviderProps) {
const { locationType, searchTerm } = useSelectLocationContext();
const daita = useSelector((state) => state.settings.wireguard.daita?.enabled ?? false);
const directOnly = useSelector((state) => state.settings.wireguard.daita?.directOnly ?? false);
+ const quic = useSelector(
+ (state) => state.settings.obfuscationSettings.selectedObfuscation === ObfuscationType.quic,
+ );
const fullRelayList = useSelector((state) => state.settings.relayLocations);
const relaySettings = useNormalRelaySettings();
@@ -99,11 +107,16 @@ export function RelayListContextProvider(props: RelayListContextProviderProps) {
relaySettings?.wireguard.useMultihop,
]);
+ // Only show relays that have QUIC endpoints when QUIC obfuscation is enabled.
+ const relayListForQuic = useMemo(() => {
+ return filterLocationsByQuic(relayListForDaita, quic, tunnelProtocol);
+ }, [quic, relayListForDaita, tunnelProtocol]);
+
// Filters the relays to only keep the relays matching the currently selected filters, e.g.
// ownership and providers
const relayListForFilters = useMemo(() => {
- return filterLocations(relayListForDaita, relaySettings?.ownership, relaySettings?.providers);
- }, [relaySettings?.ownership, relaySettings?.providers, relayListForDaita]);
+ return filterLocations(relayListForQuic, relaySettings?.ownership, relaySettings?.providers);
+ }, [relaySettings?.ownership, relaySettings?.providers, relayListForQuic]);
// 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 d5be26cbf4..14f9bef50b 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
@@ -2,7 +2,7 @@ import { useCallback, useState } from 'react';
import { sprintf } from 'sprintf-js';
import { strings } from '../../../shared/constants';
-import { Ownership } from '../../../shared/daemon-rpc-types';
+import { ObfuscationType, Ownership } from '../../../shared/daemon-rpc-types';
import { messages } from '../../../shared/gettext';
import { RoutePath } from '../../../shared/routes';
import { Button, FilterChip, Flex, IconButton, LabelTiny } from '../../lib/components';
@@ -62,6 +62,9 @@ export default function SelectLocation() {
const filteredProviders = useFilteredProviders(providers, ownership);
const daita = useSelector((state) => state.settings.wireguard.daita?.enabled ?? false);
const directOnly = useSelector((state) => state.settings.wireguard.daita?.directOnly ?? false);
+ const showQuicFilter = useSelector(
+ (state) => state.settings.obfuscationSettings.selectedObfuscation === ObfuscationType.quic,
+ );
const showDaitaFilter = daitaFilterActive(
daita,
directOnly,
@@ -119,7 +122,8 @@ export default function SelectLocation() {
const showOwnershipFilter = ownership !== Ownership.any;
const showProvidersFilter = providers.length > 0;
- const showFilters = showOwnershipFilter || showProvidersFilter || showDaitaFilter;
+ const showFilters =
+ showOwnershipFilter || showProvidersFilter || showDaitaFilter || showQuicFilter;
return (
<BackAction action={onClose}>
<Layout>
@@ -199,6 +203,23 @@ export default function SelectLocation() {
</FilterChip.Text>
</FilterChip>
)}
+
+ {showQuicFilter && (
+ <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: 'QUIC' },
+ )}
+ </FilterChip.Text>
+ </FilterChip>
+ )}
</Flex>
)}
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 f73bd14a9e..29ce00f1ff 100644
--- a/desktop/packages/mullvad-vpn/src/renderer/lib/filter-locations.ts
+++ b/desktop/packages/mullvad-vpn/src/renderer/lib/filter-locations.ts
@@ -32,6 +32,16 @@ export function filterLocationsByEndPointType(
return filterLocationsImpl(locations, getTunnelProtocolFilter(endpointType, tunnelProtocol));
}
+export function filterLocationsByQuic(
+ locations: IRelayLocationCountryRedux[],
+ quic: boolean,
+ tunnelProtocol: TunnelProtocol,
+): IRelayLocationCountryRedux[] {
+ const quicFilterActive = quic && tunnelProtocol !== 'openvpn';
+ const quickOnRelay = (relay: IRelayLocationRelayRedux) => relay.quic !== undefined;
+ return quicFilterActive ? filterLocationsImpl(locations, quickOnRelay) : locations;
+}
+
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 909004faf6..9ac8bb3a61 100644
--- a/desktop/packages/mullvad-vpn/src/renderer/redux/settings/reducers.ts
+++ b/desktop/packages/mullvad-vpn/src/renderer/redux/settings/reducers.ts
@@ -15,6 +15,7 @@ import {
ObfuscationSettings,
ObfuscationType,
Ownership,
+ Quic,
RelayEndpointType,
RelayLocation,
RelayOverride,
@@ -77,6 +78,7 @@ export interface IRelayLocationRelayRedux {
weight: number;
endpointType: RelayEndpointType;
daita: boolean;
+ quic?: Quic;
}
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 63ec8d0b9b..49164a3770 100644
--- a/desktop/packages/mullvad-vpn/src/shared/daemon-rpc-types.ts
+++ b/desktop/packages/mullvad-vpn/src/shared/daemon-rpc-types.ts
@@ -340,6 +340,7 @@ export type ConnectionConfig =
addresses: string[];
endpoint: string;
};
+
ipv4Gateway: string;
ipv6Gateway?: string;
};
@@ -396,8 +397,16 @@ export interface IRelayListHostname {
owned: boolean;
endpointType: RelayEndpointType;
daita: boolean;
+ // The absence of this value signals that the relay does not deploy QUIC.
+ quic?: Quic;
}
+export type Quic = {
+ domain: string;
+ token: string;
+ addrIn: string[];
+};
+
export type RelayEndpointType = 'wireguard' | 'openvpn' | 'bridge';
export interface ITunnelOptions {
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 7490adc6ac..d04dac2f3b 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
@@ -51,6 +51,13 @@ export const createHelpers = (page: Page, routes: RoutesObjectModel, utils: Mock
),
);
+ const locateRelaysByObfuscation = (relayList: IRelayList): LocatedRelay[] =>
+ relayList.countries.flatMap((country) =>
+ country.cities.flatMap((city) =>
+ city.relays.filter((relay) => relay.quic).map((relay) => ({ country, city, relay })),
+ ),
+ );
+
const resetOwnership = async () => {
await routes.filter.expandOwnership();
await routes.filter.selectOwnershipOption('Any');
@@ -104,6 +111,7 @@ export const createHelpers = (page: Page, routes: RoutesObjectModel, utils: Mock
expandLocatedRelays,
locateRelaysByProvider,
locateRelaysByOwner,
+ locateRelaysByObfuscation,
resetOwnership,
resetProviders,
resetView,
diff --git a/desktop/packages/mullvad-vpn/test/e2e/mocked/select-location/mock-data.ts b/desktop/packages/mullvad-vpn/test/e2e/mocked/select-location/mock-data.ts
index 6e60e29c8d..f3039c2537 100644
--- a/desktop/packages/mullvad-vpn/test/e2e/mocked/select-location/mock-data.ts
+++ b/desktop/packages/mullvad-vpn/test/e2e/mocked/select-location/mock-data.ts
@@ -45,6 +45,22 @@ const relayList: IRelayList = {
endpointType: 'wireguard',
daita: true,
},
+ {
+ hostname: 'se-got-wg-104',
+ provider: 'mullvad',
+ ipv4AddrIn: '10.0.0.4',
+ includeInCountry: true,
+ active: true,
+ weight: 0,
+ owned: true,
+ endpointType: 'wireguard',
+ daita: true,
+ quic: {
+ addrIn: [],
+ domain: '',
+ token: '',
+ },
+ },
],
},
],
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 4065f08b74..ab4cd3736d 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
@@ -7,6 +7,7 @@ import {
IRelayListWithEndpointData,
ISettings,
IWireguardEndpointData,
+ ObfuscationType,
Ownership,
} from '../../../../src/shared/daemon-rpc-types';
import { RoutePath } from '../../../../src/shared/routes';
@@ -121,100 +122,124 @@ test.describe('Select location', () => {
});
test.describe('Filter', () => {
- test.beforeEach(async () => {
- await helpers.resetView();
- await helpers.resetProviders();
- await helpers.resetOwnership();
- });
+ test.describe('Applied from filter view', () => {
+ test.beforeEach(async () => {
+ await helpers.resetView();
+ await helpers.resetProviders();
+ await helpers.resetOwnership();
+ });
- test.describe('Filter by provider', () => {
- test('Should deselect all providers when clicking all providers checkbox', async () => {
- await routes.filter.expandProviders();
- await routes.filter.checkAllProvidersCheckbox();
- expect(await helpers.areAllCheckboxesChecked()).toBe(false);
+ test.describe('Filter by provider', () => {
+ test('Should deselect all providers when clicking all providers checkbox', async () => {
+ await routes.filter.expandProviders();
+ await routes.filter.checkAllProvidersCheckbox();
+ expect(await helpers.areAllCheckboxesChecked()).toBe(false);
- await routes.filter.checkAllProvidersCheckbox();
- expect(await helpers.areAllCheckboxesChecked()).toBe(true);
- });
+ await routes.filter.checkAllProvidersCheckbox();
+ expect(await helpers.areAllCheckboxesChecked()).toBe(true);
+ });
- test('Should apply filter when selecting provider', async () => {
- await routes.filter.expandProviders();
- await routes.filter.checkAllProvidersCheckbox();
- expect(await helpers.areAllCheckboxesChecked()).toBe(false);
+ test('Should apply filter when selecting provider', async () => {
+ await routes.filter.expandProviders();
+ await routes.filter.checkAllProvidersCheckbox();
+ expect(await helpers.areAllCheckboxesChecked()).toBe(false);
- // Select one provider
- const provider = relayList.countries[0].cities[0].relays[0].provider;
- await routes.filter.checkProviderCheckbox(provider);
+ // Select one provider
+ const provider = relayList.countries[0].cities[0].relays[0].provider;
+ await routes.filter.checkProviderCheckbox(provider);
- await helpers.updateMockRelayFilter({
- providers: [provider],
- });
+ await helpers.updateMockRelayFilter({
+ providers: [provider],
+ });
- await routes.filter.applyFilter();
- await util.waitForRoute(RoutePath.selectLocation);
- const providerFilterChip = routes.selectLocation.getFilterChip('Providers: 1');
- await expect(providerFilterChip).toBeVisible();
+ await routes.filter.applyFilter();
+ await util.waitForRoute(RoutePath.selectLocation);
+ const providerFilterChip = routes.selectLocation.getFilterChip('Providers: 1');
+ await expect(providerFilterChip).toBeVisible();
- const locatedRelays = helpers.locateRelaysByProvider(relayList, provider);
- const relays = locatedRelays.map((locatedRelay) => locatedRelay.relay);
- const relayNames = relays.map((relay) => relay.hostname);
+ const locatedRelays = helpers.locateRelaysByProvider(relayList, provider);
+ const relays = locatedRelays.map((locatedRelay) => locatedRelay.relay);
+ const relayNames = relays.map((relay) => relay.hostname);
- // Expand all accordions
- await helpers.expandLocatedRelays(locatedRelays);
+ // Expand all accordions
+ await helpers.expandLocatedRelays(locatedRelays);
- const buttons = routes.selectLocation.getRelaysMatching(relayNames);
+ const buttons = routes.selectLocation.getRelaysMatching(relayNames);
- // Expect all filtered relays to have a button
- await expect(buttons).toHaveCount(relays.length);
+ // Expect all filtered relays to have a button
+ await expect(buttons).toHaveCount(relays.length);
- // Clear filter
- await providerFilterChip.click();
+ // Clear filter
+ await providerFilterChip.click();
- // Get all relays and expand accordions
- const allLocatedRelays = helpers.locateRelaysByProvider(relayList);
- await helpers.expandLocatedRelays(allLocatedRelays);
+ // Get all relays and expand accordions
+ const allLocatedRelays = helpers.locateRelaysByProvider(relayList);
+ await helpers.expandLocatedRelays(allLocatedRelays);
- // Should not have same length as all relays
- await expect(buttons).not.toHaveCount(allLocatedRelays.length);
+ // Should not have same length as all relays
+ await expect(buttons).not.toHaveCount(allLocatedRelays.length);
+ });
});
- });
- test.describe('Filter by ownership', () => {
- test('Should apply filter when selecting ownership', async () => {
- // Select rented only
- await routes.filter.expandOwnership();
- await routes.filter.selectOwnershipOption('Rented only');
- await helpers.updateMockRelayFilter({
- ownership: Ownership.rented,
- });
+ test.describe('Filter by ownership', () => {
+ test('Should apply filter when selecting ownership', async () => {
+ // Select rented only
+ await routes.filter.expandOwnership();
+ await routes.filter.selectOwnershipOption('Rented only');
+ await helpers.updateMockRelayFilter({
+ ownership: Ownership.rented,
+ });
+
+ await routes.filter.applyFilter();
+ await util.waitForRoute(RoutePath.selectLocation);
+
+ const ownerFilterChip = routes.selectLocation.getFilterChip('Rented');
+ await expect(ownerFilterChip).toBeVisible();
+
+ const locatedRelays = helpers.locateRelaysByOwner(relayList, false);
+ const relays = locatedRelays.map((locatedRelay) => locatedRelay.relay);
+ const relayNames = relays.map((relay) => relay.hostname);
+
+ // Expand all accordions
+ await helpers.expandLocatedRelays(locatedRelays);
+
+ const buttons = routes.selectLocation.getRelaysMatching(relayNames);
+
+ // Expect all filtered relays to have a button
+ await expect(buttons).toHaveCount(relays.length);
- await routes.filter.applyFilter();
- await util.waitForRoute(RoutePath.selectLocation);
+ // Clear filter
+ await ownerFilterChip.click();
- const ownerFilterChip = routes.selectLocation.getFilterChip('Rented');
- await expect(ownerFilterChip).toBeVisible();
+ // Get all relays and expand accordions
+ const allLocatedRelays = helpers.locateRelaysByOwner(relayList);
+ await helpers.expandLocatedRelays(allLocatedRelays);
- const locatedRelays = helpers.locateRelaysByOwner(relayList, false);
+ // Should not have same length as all relays
+ await expect(buttons).not.toHaveCount(allLocatedRelays.length);
+ });
+ });
+ });
+ test.describe('Filter by obfuscation', () => {
+ test('Should apply filter when QUIC obfuscation is selected', async () => {
+ const settings = getDefaultSettings();
+ if ('normal' in settings.relaySettings) {
+ settings.obfuscationSettings.selectedObfuscation = ObfuscationType.quic;
+ }
+ await util.sendMockIpcResponse<ISettings>({
+ channel: 'settings-',
+ response: settings,
+ });
+ const locatedRelays = helpers.locateRelaysByObfuscation(relayList);
const relays = locatedRelays.map((locatedRelay) => locatedRelay.relay);
const relayNames = relays.map((relay) => relay.hostname);
- // Expand all accordions
await helpers.expandLocatedRelays(locatedRelays);
const buttons = routes.selectLocation.getRelaysMatching(relayNames);
// Expect all filtered relays to have a button
await expect(buttons).toHaveCount(relays.length);
-
- // Clear filter
- await ownerFilterChip.click();
-
- // Get all relays and expand accordions
- const allLocatedRelays = helpers.locateRelaysByOwner(relayList);
- await helpers.expandLocatedRelays(allLocatedRelays);
-
- // Should not have same length as all relays
- await expect(buttons).not.toHaveCount(allLocatedRelays.length);
});
});
});
diff --git a/mullvad-api/src/relay_list.rs b/mullvad-api/src/relay_list.rs
index abca6dd2b7..28ce37aec0 100644
--- a/mullvad-api/src/relay_list.rs
+++ b/mullvad-api/src/relay_list.rs
@@ -7,7 +7,7 @@ use mullvad_types::{location, relay_list};
use talpid_types::net::wireguard;
use std::{
- collections::BTreeMap,
+ collections::{BTreeMap, HashSet},
future::Future,
net::{IpAddr, Ipv4Addr, Ipv6Addr},
ops::RangeInclusive,
@@ -167,7 +167,6 @@ fn into_mullvad_relay(
relay: Relay,
location: location::Location,
endpoint_data: relay_list::RelayEndpointData,
- features: relay_list::Features,
) -> relay_list::Relay {
relay_list::Relay {
hostname: relay.hostname,
@@ -182,7 +181,6 @@ fn into_mullvad_relay(
weight: relay.weight,
endpoint_data,
location,
- features,
}
}
@@ -247,21 +245,11 @@ struct Relay {
impl Relay {
fn into_openvpn_mullvad_relay(self, location: location::Location) -> relay_list::Relay {
- into_mullvad_relay(
- self,
- location,
- relay_list::RelayEndpointData::Openvpn,
- relay_list::Features::empty(),
- )
+ into_mullvad_relay(self, location, relay_list::RelayEndpointData::Openvpn)
}
fn into_bridge_mullvad_relay(self, location: location::Location) -> relay_list::Relay {
- into_mullvad_relay(
- self,
- location,
- relay_list::RelayEndpointData::Bridge,
- relay_list::Features::empty(),
- )
+ into_mullvad_relay(self, location, relay_list::RelayEndpointData::Bridge)
}
fn convert_to_lowercase(&mut self) {
@@ -353,25 +341,63 @@ struct WireGuardRelay {
#[serde(default)]
shadowsocks_extra_addr_in: Vec<IpAddr>,
#[serde(default)]
- features: relay_list::Features,
+ features: Features,
}
impl WireGuardRelay {
fn into_mullvad_relay(self, location: location::Location) -> relay_list::Relay {
- // Sanity check that new 'features' key is in sync with the old Relay keys.
- if self.features.daita() {
+ // Sanity check that new 'features' key is in sync with the old, superceded keys.
+ // TODO: Remove `self.daita` (and this check 👇) when `features` key has been completely
+ // rolled out to production.
+ if self.features.daita.is_some() {
debug_assert!(self.daita)
}
- into_mullvad_relay(
- self.relay,
- location,
+
+ let relay = self.relay;
+ let endpoint_data =
relay_list::RelayEndpointData::Wireguard(relay_list::WireguardRelayEndpointData {
public_key: self.public_key,
- daita: self.daita,
- shadowsocks_extra_addr_in: self.shadowsocks_extra_addr_in,
- }),
- self.features,
- )
+ // FIXME: This hack is forward-compatible with 'features' being rolled out.
+ // Should unwrap to 'false' once 'daita' field is removed.
+ daita: self.features.daita.map(|_| true).unwrap_or(self.daita),
+ shadowsocks_extra_addr_in: HashSet::from_iter(self.shadowsocks_extra_addr_in),
+ quic: self.features.quic.map(relay_list::Quic::from),
+ });
+
+ into_mullvad_relay(relay, location, endpoint_data)
+ }
+}
+
+/// Extra features enabled on some (Wireguard) relay, such as obfuscation daemons or Daita.
+#[derive(Debug, Default, Clone, serde::Deserialize)]
+struct Features {
+ daita: Option<Daita>,
+ quic: Option<Quic>,
+}
+
+/// DAITA doesn't have any configuration options (exposed by the API).
+///
+/// Note, an empty struct is not the same as an empty tuple struct according to serde_json!
+#[derive(Debug, Clone, serde::Deserialize)]
+struct Daita {}
+
+/// Parameters for setting up a QUIC obfuscator (connecting to a masque-proxy running on a relay).
+#[derive(Debug, Clone, serde::Deserialize)]
+struct Quic {
+ /// In-addresses for the QUIC obfuscator.
+ ///
+ /// There may be 0, 1 or 2 in IPs, depending on how many masque-proxy daemons running on the
+ /// relay. Hopefully the API will tell use the correct amount🤞.
+ addr_in: Vec<IpAddr>,
+ /// Authorization token
+ token: String,
+ /// Hostname where masque proxy is hosted
+ domain: String,
+}
+
+impl From<Quic> for relay_list::Quic {
+ fn from(value: Quic) -> Self {
+ Self::new(value.addr_in, value.token, value.domain)
}
}
diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto
index 199f4f2000..4d405bcde2 100644
--- a/mullvad-management-interface/proto/management_interface.proto
+++ b/mullvad-management-interface/proto/management_interface.proto
@@ -6,7 +6,6 @@ import "google/protobuf/empty.proto";
import "google/protobuf/timestamp.proto";
import "google/protobuf/wrappers.proto";
import "google/protobuf/duration.proto";
-import "google/protobuf/any.proto";
service ManagementService {
// Control and get tunnel state
@@ -717,10 +716,27 @@ message RelayListCity {
}
message Relay {
- enum RelayType {
- OPENVPN = 0;
- BRIDGE = 1;
- WIREGUARD = 2;
+ message RelayData {
+ message OpenVPN {}
+ message Bridge {}
+ message Wireguard {
+ message Quic {
+ string domain = 1;
+ string token = 2;
+ repeated string addr_in = 3;
+ }
+
+ bytes public_key = 1;
+ bool daita = 2;
+ Quic quic = 3;
+ repeated string shadowsocks_extra_addr_in = 4;
+ }
+
+ oneof data {
+ Wireguard wireguard = 1;
+ OpenVPN openvpn = 2;
+ Bridge bridge = 3;
+ }
}
string hostname = 1;
@@ -731,15 +747,8 @@ message Relay {
bool owned = 6;
string provider = 7;
fixed64 weight = 8;
- RelayType endpoint_type = 9;
- google.protobuf.Any endpoint_data = 10;
- Location location = 11;
-}
-
-message WireguardRelayEndpointData {
- bytes public_key = 1;
- bool daita = 2;
- repeated string shadowsocks_extra_addr_in = 3;
+ RelayData endpoint_data = 9;
+ Location location = 10;
}
message Location {
diff --git a/mullvad-management-interface/src/types/conversions/mod.rs b/mullvad-management-interface/src/types/conversions/mod.rs
index 0654cbb641..3c610b8d1b 100644
--- a/mullvad-management-interface/src/types/conversions/mod.rs
+++ b/mullvad-management-interface/src/types/conversions/mod.rs
@@ -60,22 +60,3 @@ impl From<FromProtobufTypeError> for crate::Status {
}
}
}
-
-/// Converts any message to `google.protobuf.Any`.
-fn to_proto_any<T: prost::Message>(type_name: &str, message: T) -> prost_types::Any {
- prost_types::Any {
- type_url: format!("type.googleapis.com/{type_name}"),
- value: message.encode_to_vec(),
- }
-}
-
-/// Tries to convert a message from `google.protobuf.Any` to `T`.
-fn try_from_proto_any<T: prost::Message + Default>(
- type_name: &str,
- any_value: prost_types::Any,
-) -> Option<T> {
- if any_value.type_url != format!("type.googleapis.com/{type_name}") {
- return None;
- }
- T::decode(any_value.value.as_slice()).ok()
-}
diff --git a/mullvad-management-interface/src/types/conversions/relay_list.rs b/mullvad-management-interface/src/types/conversions/relay_list.rs
index b002634177..08ec379ba9 100644
--- a/mullvad-management-interface/src/types/conversions/relay_list.rs
+++ b/mullvad-management-interface/src/types/conversions/relay_list.rs
@@ -1,14 +1,11 @@
use std::{
- net::{Ipv4Addr, Ipv6Addr},
+ collections::HashSet,
+ net::{IpAddr, Ipv4Addr, Ipv6Addr},
ops::RangeInclusive,
str::FromStr,
};
-use crate::types::{
- FromProtobufTypeError,
- conversions::{bytes_to_pubkey, to_proto_any, try_from_proto_any},
- proto,
-};
+use crate::types::{FromProtobufTypeError, conversions::bytes_to_pubkey, proto};
use super::net::try_transport_protocol_from_i32;
@@ -125,25 +122,29 @@ impl From<mullvad_types::relay_list::Relay> for proto::Relay {
owned: relay.owned,
provider: relay.provider,
weight: relay.weight,
- endpoint_type: match &relay.endpoint_data {
- MullvadEndpointData::Openvpn => proto::relay::RelayType::Openvpn as i32,
- MullvadEndpointData::Bridge => proto::relay::RelayType::Bridge as i32,
- MullvadEndpointData::Wireguard(_) => proto::relay::RelayType::Wireguard as i32,
- },
- endpoint_data: match relay.endpoint_data {
- MullvadEndpointData::Wireguard(data) => Some(to_proto_any(
- "mullvad_daemon.management_interface/WireguardRelayEndpointData",
- proto::WireguardRelayEndpointData {
- public_key: data.public_key.as_bytes().to_vec(),
- daita: data.daita,
- shadowsocks_extra_addr_in: data
- .shadowsocks_extra_addr_in
- .iter()
+ endpoint_data: {
+ use proto::relay::RelayData;
+ use proto::relay::relay_data::{Bridge, Data, OpenVpn, Wireguard, wireguard};
+ let data = match relay.endpoint_data {
+ MullvadEndpointData::Wireguard(data) => {
+ let shadowsocks_extra_addr_in = data
+ .shadowsocks_extra_in_addrs()
.map(|addr| addr.to_string())
- .collect(),
- },
- )),
- _ => None,
+ .collect();
+ let public_key = data.public_key.as_bytes().to_vec();
+ let daita = data.daita;
+ let quic = data.quic.map(wireguard::Quic::from);
+ Data::Wireguard(Wireguard {
+ public_key,
+ daita,
+ shadowsocks_extra_addr_in,
+ quic,
+ })
+ }
+ MullvadEndpointData::Bridge => Data::Bridge(Bridge {}),
+ MullvadEndpointData::Openvpn => Data::Openvpn(OpenVpn {}),
+ };
+ Some(RelayData { data: Some(data) })
},
location: Some(proto::Location {
country: relay.location.country,
@@ -157,6 +158,38 @@ impl From<mullvad_types::relay_list::Relay> for proto::Relay {
}
}
+impl From<mullvad_types::relay_list::Quic> for proto::relay::relay_data::wireguard::Quic {
+ fn from(quic: mullvad_types::relay_list::Quic) -> Self {
+ let domain = quic.hostname().to_owned();
+ let token = quic.auth_token().to_owned();
+ let addr_in = quic.in_addr().map(|ip| ip.to_string()).collect();
+ Self {
+ domain,
+ token,
+ addr_in,
+ }
+ }
+}
+
+impl TryFrom<proto::relay::relay_data::wireguard::Quic> for mullvad_types::relay_list::Quic {
+ type Error = FromProtobufTypeError;
+
+ fn try_from(value: proto::relay::relay_data::wireguard::Quic) -> Result<Self, Self::Error> {
+ let domain = value.domain;
+ let token = value.token;
+ fn parse_addr(addr: String) -> Result<IpAddr, FromProtobufTypeError> {
+ addr.parse()
+ .map_err(|_err| FromProtobufTypeError::InvalidArgument("Invalid IP address"))
+ }
+ let addr_in = value
+ .addr_in
+ .into_iter()
+ .map(parse_addr)
+ .collect::<Result<Vec<IpAddr>, FromProtobufTypeError>>()?;
+ Ok(Self::new(addr_in, token, domain))
+ }
+}
+
impl TryFrom<proto::RelayList> for mullvad_types::relay_list::RelayList {
type Error = FromProtobufTypeError;
@@ -236,44 +269,43 @@ impl TryFrom<proto::Relay> for mullvad_types::relay_list::Relay {
relay_list::{Relay as MullvadRelay, RelayEndpointData as MullvadEndpointData},
};
- let endpoint_data = match relay.endpoint_type {
- i if i == proto::relay::RelayType::Openvpn as i32 => MullvadEndpointData::Openvpn,
- i if i == proto::relay::RelayType::Bridge as i32 => MullvadEndpointData::Bridge,
- i if i == proto::relay::RelayType::Wireguard as i32 => {
- let data = relay
- .endpoint_data
- .ok_or(FromProtobufTypeError::InvalidArgument(
- "missing endpoint wg data",
- ))?;
- let data: proto::WireguardRelayEndpointData = try_from_proto_any(
- "mullvad_daemon.management_interface/WireguardRelayEndpointData",
- data,
- )
+ let endpoint_data = {
+ let data = relay
+ .endpoint_data
+ .and_then(|endpoint| endpoint.data)
.ok_or(FromProtobufTypeError::InvalidArgument(
- "invalid endpoint wg data",
- ))?;
- MullvadEndpointData::Wireguard(
- mullvad_types::relay_list::WireguardRelayEndpointData {
- public_key: bytes_to_pubkey(&data.public_key)?,
- daita: data.daita,
- shadowsocks_extra_addr_in: data
- .shadowsocks_extra_addr_in
- .iter()
- .map(|addr| {
- addr.parse().map_err(|_err| {
- FromProtobufTypeError::InvalidArgument(
- "invalid relay IPv6 address",
- )
- })
- })
- .collect::<Result<_, FromProtobufTypeError>>()?,
- },
- )
- }
- _ => {
- return Err(FromProtobufTypeError::InvalidArgument(
"invalid relay endpoint type",
- ));
+ ))?;
+ match data {
+ proto::relay::relay_data::Data::Openvpn(_openvpn) => MullvadEndpointData::Openvpn,
+ proto::relay::relay_data::Data::Bridge(_bridge) => MullvadEndpointData::Bridge,
+ proto::relay::relay_data::Data::Wireguard(wireguard) => {
+ fn parse_addr(addr: &str) -> Result<IpAddr, FromProtobufTypeError> {
+ addr.parse().map_err(|_err| {
+ FromProtobufTypeError::InvalidArgument("Invalid IP address")
+ })
+ }
+
+ let public_key = bytes_to_pubkey(&wireguard.public_key)?;
+ let daita = wireguard.daita;
+ let quic = wireguard
+ .quic
+ .map(mullvad_types::relay_list::Quic::try_from)
+ .transpose()?;
+ let shadowsocks_extra_addr_in = wireguard
+ .shadowsocks_extra_addr_in
+ .iter()
+ .map(String::as_ref)
+ .map(parse_addr)
+ .collect::<Result<HashSet<IpAddr>, FromProtobufTypeError>>()?;
+ let data = mullvad_types::relay_list::WireguardRelayEndpointData {
+ public_key,
+ daita,
+ quic,
+ shadowsocks_extra_addr_in,
+ };
+ MullvadEndpointData::Wireguard(data)
+ }
}
};
@@ -286,10 +318,6 @@ impl TryFrom<proto::Relay> for mullvad_types::relay_list::Relay {
})
.transpose()?;
- // TODO: Eventually, we will need to decide how to represent extra relay features in the
- // protobuf message.
- let features = mullvad_types::relay_list::Features::default();
-
let relay = MullvadRelay {
hostname: relay.hostname,
ipv4_addr_in: relay.ipv4_addr_in.parse().map_err(|_err| {
@@ -316,7 +344,6 @@ impl TryFrom<proto::Relay> for mullvad_types::relay_list::Relay {
})
.ok_or("missing relay location")
.map_err(FromProtobufTypeError::InvalidArgument)?,
- features,
};
Ok(relay)
diff --git a/mullvad-relay-selector/src/relay_selector/helpers.rs b/mullvad-relay-selector/src/relay_selector/helpers.rs
index 7135d41fd0..1a5a11e69c 100644
--- a/mullvad-relay-selector/src/relay_selector/helpers.rs
+++ b/mullvad-relay-selector/src/relay_selector/helpers.rs
@@ -124,7 +124,7 @@ pub fn get_shadowsocks_obfuscator(
let port = settings.port;
let extra_addrs = match &relay.endpoint_data {
mullvad_types::relay_list::RelayEndpointData::Wireguard(wg) => {
- &wg.shadowsocks_extra_addr_in
+ wg.shadowsocks_extra_in_addrs()
}
_ => panic!("expected wireguard relay"),
};
@@ -132,7 +132,7 @@ pub fn get_shadowsocks_obfuscator(
let endpoint = get_shadowsocks_obfuscator_inner(
endpoint.peer.endpoint.ip(),
non_extra_port_ranges,
- extra_addrs,
+ extra_addrs.copied(),
port,
)?;
@@ -143,7 +143,7 @@ pub fn get_shadowsocks_obfuscator(
}
pub fn get_quic_obfuscator(relay: Relay, ip_version: IpVersion) -> Option<SelectedObfuscator> {
- let quic = relay.features.quic()?;
+ let quic = relay.wireguard()?.quic()?;
let config = {
let hostname = quic.hostname().to_string();
let endpoint = match ip_version {
@@ -168,14 +168,13 @@ pub fn get_quic_obfuscator(relay: Relay, ip_version: IpVersion) -> Option<Select
fn get_shadowsocks_obfuscator_inner<R: RangeBounds<u16> + Iterator<Item = u16> + Clone>(
wg_in_addr: IpAddr,
wg_in_addr_port_ranges: &[R],
- extra_in_addrs: &[IpAddr],
+ extra_in_addrs: impl IntoIterator<Item = IpAddr>,
desired_port: Constraint<u16>,
) -> Result<SocketAddr, Error> {
// Filter out addresses for the wrong address family
let extra_in_addrs: Vec<_> = extra_in_addrs
- .iter()
+ .into_iter()
.filter(|addr| addr.is_ipv4() == wg_in_addr.is_ipv4())
- .copied()
.collect();
let in_ip = extra_in_addrs
@@ -244,7 +243,7 @@ mod tests {
SHADOWSOCKS_EXTRA_PORT_RANGES, get_shadowsocks_obfuscator_inner, port_if_in_range,
};
use mullvad_types::constraints::Constraint;
- use std::{net::IpAddr, ops::RangeInclusive};
+ use std::{iter, net::IpAddr, ops::RangeInclusive};
/// Test whether select ports are available when relay has no extra IPs
#[test]
@@ -255,7 +254,7 @@ mod tests {
let wg_in_ip: IpAddr = "1.2.3.4".parse().unwrap();
let selected_addr =
- get_shadowsocks_obfuscator_inner(wg_in_ip, PORT_RANGES, &[], Constraint::Any)
+ get_shadowsocks_obfuscator_inner(wg_in_ip, PORT_RANGES, iter::empty(), Constraint::Any)
.expect("should find valid port without constraint");
assert_eq!(selected_addr.ip(), wg_in_ip);
@@ -267,7 +266,7 @@ mod tests {
let selected_addr = get_shadowsocks_obfuscator_inner(
wg_in_ip,
PORT_RANGES,
- &[],
+ iter::empty(),
Constraint::Only(WITHIN_RANGE_PORT),
)
.expect("should find within-range port");
@@ -281,7 +280,7 @@ mod tests {
let selected_addr = get_shadowsocks_obfuscator_inner(
wg_in_ip,
PORT_RANGES,
- &[],
+ iter::empty(),
Constraint::Only(OUT_OF_RANGE_PORT),
);
assert!(
@@ -297,13 +296,13 @@ mod tests {
const OUT_OF_RANGE_PORT: u16 = 1;
let wg_in_ip: IpAddr = "1.2.3.4".parse().unwrap();
- let extra_in_addrs: &[IpAddr] =
- &["1.3.3.7".parse().unwrap(), "192.0.2.123".parse().unwrap()];
+ let extra_in_addrs: Vec<IpAddr> =
+ vec!["1.3.3.7".parse().unwrap(), "192.0.2.123".parse().unwrap()];
let selected_addr = get_shadowsocks_obfuscator_inner(
wg_in_ip,
PORT_RANGES,
- extra_in_addrs,
+ extra_in_addrs.clone(),
Constraint::Any,
)
.expect("should find valid port without constraint");
@@ -317,7 +316,7 @@ mod tests {
let selected_addr = get_shadowsocks_obfuscator_inner(
wg_in_ip,
PORT_RANGES,
- extra_in_addrs,
+ extra_in_addrs.clone(),
Constraint::Only(OUT_OF_RANGE_PORT),
)
.expect("expected selected address to be returned");
@@ -340,12 +339,12 @@ mod tests {
const OUT_OF_RANGE_PORT: u16 = 1;
let wg_in_ip: IpAddr = "1.2.3.4".parse().unwrap();
- let extra_in_addrs: &[IpAddr] = &["::2".parse().unwrap()];
+ let extra_in_addrs: Vec<IpAddr> = vec!["::2".parse().unwrap()];
let selected_addr = get_shadowsocks_obfuscator_inner(
wg_in_ip,
PORT_RANGES,
- extra_in_addrs,
+ extra_in_addrs.clone(),
Constraint::Any,
)
.expect("should find valid port without constraint");
@@ -359,7 +358,7 @@ mod tests {
let selected_addr = get_shadowsocks_obfuscator_inner(
wg_in_ip,
PORT_RANGES,
- extra_in_addrs,
+ extra_in_addrs.clone(),
Constraint::Only(OUT_OF_RANGE_PORT),
);
assert!(
diff --git a/mullvad-relay-selector/src/relay_selector/matcher.rs b/mullvad-relay-selector/src/relay_selector/matcher.rs
index 8133677b87..52f0cc600a 100644
--- a/mullvad-relay-selector/src/relay_selector/matcher.rs
+++ b/mullvad-relay-selector/src/relay_selector/matcher.rs
@@ -145,7 +145,7 @@ fn filter_on_obfuscation(
)
}
// QUIC is only enabled on some relays
- ObfuscationQuery::Quic => relay.features.quic().is_some(),
+ ObfuscationQuery::Quic => relay.wireguard().is_some_and(|wg| wg.quic().is_some()),
// Other relays are compatible with this query
ObfuscationQuery::Off | ObfuscationQuery::Auto | ObfuscationQuery::Udp2tcp(_) => true,
}
diff --git a/mullvad-relay-selector/tests/relay_selector.rs b/mullvad-relay-selector/tests/relay_selector.rs
index 532e78a076..ad91c192d5 100644
--- a/mullvad-relay-selector/tests/relay_selector.rs
+++ b/mullvad-relay-selector/tests/relay_selector.rs
@@ -27,9 +27,9 @@ use mullvad_types::{
RelayConstraints, RelayOverride, RelaySettings, TransportPort,
},
relay_list::{
- BridgeEndpointData, Features, OpenVpnEndpoint, OpenVpnEndpointData, Quic, Relay,
- RelayEndpointData, RelayList, RelayListCity, RelayListCountry, ShadowsocksEndpointData,
- WireguardEndpointData, WireguardRelayEndpointData,
+ BridgeEndpointData, OpenVpnEndpoint, OpenVpnEndpointData, Quic, Relay, RelayEndpointData,
+ RelayList, RelayListCity, RelayListCountry, ShadowsocksEndpointData, WireguardEndpointData,
+ WireguardRelayEndpointData,
},
};
@@ -42,6 +42,10 @@ static DUMMY_LOCATION: LazyLock<Location> = LazyLock::new(|| Location {
longitude: 11.97,
});
+static WIREGUARD_PUBKEY: LazyLock<PublicKey> = LazyLock::new(|| {
+ PublicKey::from_base64("BLNHNoGO88LjV/wDBa7CUUwUzPq/fO2UwcGLy56hKy4=").unwrap()
+});
+
static RELAYS: LazyLock<RelayList> = LazyLock::new(|| RelayList {
etag: None,
countries: vec![RelayListCountry {
@@ -64,25 +68,19 @@ static RELAYS: LazyLock<RelayList> = LazyLock::new(|| RelayList {
owned: true,
provider: "provider0".to_string(),
weight: 1,
- endpoint_data: RelayEndpointData::Wireguard(WireguardRelayEndpointData {
- public_key: PublicKey::from_base64(
- "BLNHNoGO88LjV/wDBa7CUUwUzPq/fO2UwcGLy56hKy4=",
- )
- .unwrap(),
- daita: true,
- shadowsocks_extra_addr_in: vec![],
- }),
+ endpoint_data: RelayEndpointData::Wireguard(
+ WireguardRelayEndpointData::new(WIREGUARD_PUBKEY.clone())
+ .set_daita(true)
+ .set_quic(Quic::new(
+ vec![
+ "185.213.154.68".parse().unwrap(),
+ "2a03:1b20:5:f011::a09f".parse().unwrap(),
+ ],
+ "Bearer test".to_owned(),
+ "se9-wireguard.blockerad.eu".to_owned(),
+ )),
+ ),
location: DUMMY_LOCATION.clone(),
- features: Features::default()
- .configure_daita()
- .configure_quic(Quic::new(
- vec![
- "185.213.154.68".parse().unwrap(),
- "2a03:1b20:5:f011::a09f".parse().unwrap(),
- ],
- "Bearer test".to_owned(),
- "se9-wireguard.blockerad.eu".to_owned(),
- )),
},
Relay {
hostname: "se10-wireguard".to_string(),
@@ -95,16 +93,11 @@ static RELAYS: LazyLock<RelayList> = LazyLock::new(|| RelayList {
owned: false,
provider: "provider1".to_string(),
weight: 1,
- endpoint_data: RelayEndpointData::Wireguard(WireguardRelayEndpointData {
- public_key: PublicKey::from_base64(
- "BLNHNoGO88LjV/wDBa7CUUwUzPq/fO2UwcGLy56hKy4=",
- )
- .unwrap(),
- daita: false,
- shadowsocks_extra_addr_in: vec![],
- }),
+ endpoint_data: RelayEndpointData::Wireguard(WireguardRelayEndpointData::new(
+ PublicKey::from_base64("BLNHNoGO88LjV/wDBa7CUUwUzPq/fO2UwcGLy56hKy4=")
+ .unwrap(),
+ )),
location: DUMMY_LOCATION.clone(),
- features: Features::default(),
},
Relay {
hostname: "se11-wireguard".to_string(),
@@ -117,16 +110,14 @@ static RELAYS: LazyLock<RelayList> = LazyLock::new(|| RelayList {
owned: false,
provider: "provider2".to_string(),
weight: 1,
- endpoint_data: RelayEndpointData::Wireguard(WireguardRelayEndpointData {
- public_key: PublicKey::from_base64(
- "BLNHNoGO88LjV/wDBa7CUUwUzPq/fO2UwcGLy56hKy4=",
+ endpoint_data: RelayEndpointData::Wireguard(
+ WireguardRelayEndpointData::new(
+ PublicKey::from_base64("BLNHNoGO88LjV/wDBa7CUUwUzPq/fO2UwcGLy56hKy4=")
+ .unwrap(),
)
- .unwrap(),
- daita: true,
- shadowsocks_extra_addr_in: vec![],
- }),
+ .set_daita(true),
+ ),
location: DUMMY_LOCATION.clone(),
- features: Features::default().configure_daita(),
},
Relay {
hostname: "se-got-001".to_string(),
@@ -141,7 +132,6 @@ static RELAYS: LazyLock<RelayList> = LazyLock::new(|| RelayList {
weight: 1,
endpoint_data: RelayEndpointData::Openvpn,
location: DUMMY_LOCATION.clone(),
- features: Features::default(),
},
Relay {
hostname: "se-got-002".to_string(),
@@ -156,7 +146,6 @@ static RELAYS: LazyLock<RelayList> = LazyLock::new(|| RelayList {
weight: 1,
endpoint_data: RelayEndpointData::Openvpn,
location: DUMMY_LOCATION.clone(),
- features: Features::default(),
},
Relay {
hostname: "se-got-br-001".to_string(),
@@ -171,7 +160,6 @@ static RELAYS: LazyLock<RelayList> = LazyLock::new(|| RelayList {
weight: 1,
endpoint_data: RelayEndpointData::Bridge,
location: DUMMY_LOCATION.clone(),
- features: Features::default(),
},
SHADOWSOCKS_RELAY.clone(),
],
@@ -250,13 +238,13 @@ static SHADOWSOCKS_RELAY: LazyLock<Relay> = LazyLock::new(|| Relay {
owned: true,
provider: "provider0".to_string(),
weight: 1,
- endpoint_data: RelayEndpointData::Wireguard(WireguardRelayEndpointData {
- public_key: PublicKey::from_base64("eaNHNoGO88LjV/wDBa7CUUwUzPq/fO2UwcGLy56hKy4=").unwrap(),
- daita: false,
- shadowsocks_extra_addr_in: SHADOWSOCKS_RELAY_EXTRA_ADDRS.to_vec(),
- }),
+ endpoint_data: RelayEndpointData::Wireguard(
+ WireguardRelayEndpointData::new(
+ PublicKey::from_base64("eaNHNoGO88LjV/wDBa7CUUwUzPq/fO2UwcGLy56hKy4=").unwrap(),
+ )
+ .add_shadowsocks_extra_in_addrs(SHADOWSOCKS_RELAY_EXTRA_ADDRS.iter().copied()),
+ ),
location: DUMMY_LOCATION.clone(),
- features: Features::default(),
});
const SHADOWSOCKS_RELAY_IPV4: Ipv4Addr = Ipv4Addr::new(123, 123, 123, 1);
const SHADOWSOCKS_RELAY_IPV6: Ipv6Addr = Ipv6Addr::new(0x123, 0, 0, 0, 0, 0, 0, 2);
@@ -586,16 +574,10 @@ fn test_wireguard_entry() {
owned: true,
provider: "provider0".to_string(),
weight: 1,
- endpoint_data: RelayEndpointData::Wireguard(WireguardRelayEndpointData {
- public_key: PublicKey::from_base64(
- "BLNHNoGO88LjV/wDBa7CUUwUzPq/fO2UwcGLy56hKy4=",
- )
- .unwrap(),
- daita: false,
- shadowsocks_extra_addr_in: vec![],
- }),
+ endpoint_data: RelayEndpointData::Wireguard(
+ WireguardRelayEndpointData::new(WIREGUARD_PUBKEY.clone()),
+ ),
location: DUMMY_LOCATION.clone(),
- features: Features::default(),
},
Relay {
hostname: "se10-wireguard".to_string(),
@@ -608,16 +590,10 @@ fn test_wireguard_entry() {
owned: false,
provider: "provider1".to_string(),
weight: 1,
- endpoint_data: RelayEndpointData::Wireguard(WireguardRelayEndpointData {
- public_key: PublicKey::from_base64(
- "BLNHNoGO88LjV/wDBa7CUUwUzPq/fO2UwcGLy56hKy4=",
- )
- .unwrap(),
- daita: false,
- shadowsocks_extra_addr_in: vec![],
- }),
+ endpoint_data: RelayEndpointData::Wireguard(
+ WireguardRelayEndpointData::new(WIREGUARD_PUBKEY.clone()),
+ ),
location: DUMMY_LOCATION.clone(),
- features: Features::default(),
},
],
}],
@@ -1280,16 +1256,10 @@ fn test_include_in_country() {
owned: true,
provider: "31173".to_string(),
weight: 1,
- endpoint_data: RelayEndpointData::Wireguard(WireguardRelayEndpointData {
- public_key: PublicKey::from_base64(
- "BLNHNoGO88LjV/wDBa7CUUwUzPq/fO2UwcGLy56hKy4=",
- )
- .unwrap(),
- shadowsocks_extra_addr_in: vec![],
- daita: false,
- }),
+ endpoint_data: RelayEndpointData::Wireguard(
+ WireguardRelayEndpointData::new(WIREGUARD_PUBKEY.clone()),
+ ),
location: DUMMY_LOCATION.clone(),
- features: Features::default(),
},
Relay {
hostname: "se10-wireguard".to_string(),
@@ -1302,16 +1272,10 @@ fn test_include_in_country() {
owned: false,
provider: "31173".to_string(),
weight: 1,
- endpoint_data: RelayEndpointData::Wireguard(WireguardRelayEndpointData {
- public_key: PublicKey::from_base64(
- "BLNHNoGO88LjV/wDBa7CUUwUzPq/fO2UwcGLy56hKy4=",
- )
- .unwrap(),
- shadowsocks_extra_addr_in: vec![],
- daita: false,
- }),
+ endpoint_data: RelayEndpointData::Wireguard(
+ WireguardRelayEndpointData::new(WIREGUARD_PUBKEY.clone()),
+ ),
location: DUMMY_LOCATION.clone(),
- features: Features::default(),
},
],
}],
diff --git a/mullvad-types/src/relay_list.rs b/mullvad-types/src/relay_list.rs
index 99aaf60059..c6f1693c14 100644
--- a/mullvad-types/src/relay_list.rs
+++ b/mullvad-types/src/relay_list.rs
@@ -1,6 +1,7 @@
use crate::location::{CityCode, CountryCode, Location};
use serde::{Deserialize, Serialize};
use std::{
+ collections::HashSet,
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
ops::RangeInclusive,
};
@@ -88,69 +89,28 @@ pub struct Relay {
pub weight: u64,
pub endpoint_data: RelayEndpointData,
pub location: Location,
- #[serde(default)]
- pub features: Features,
-}
-
-/// Extra features enabled on some (Wireguard) relay, such as obfuscation daemons or Daita.
-#[derive(Debug, Clone, Deserialize, Serialize)]
-pub struct Features {
- daita: Option<Daita>,
- quic: Option<Quic>,
}
-impl Features {
- /// Equivalent to a relay without any additional features.
- pub fn empty() -> Features {
- Features {
- daita: None,
- quic: None,
+impl Relay {
+ /// If self is a Wireguard relay, we sometimes want to peek on its extra data.
+ pub fn wireguard(&self) -> Option<&WireguardRelayEndpointData> {
+ match &self.endpoint_data {
+ RelayEndpointData::Wireguard(wireguard_relay_endpoint_data) => {
+ Some(wireguard_relay_endpoint_data)
+ }
+ RelayEndpointData::Openvpn | RelayEndpointData::Bridge => None,
}
}
-
- /// Whether Daita is enabled
- pub fn daita(&self) -> bool {
- self.daita.is_some()
- }
-
- /// Whether Quic is enabled and its config
- pub fn quic(&self) -> Option<&Quic> {
- self.quic.as_ref()
- }
-
- /// Enable Daita for this relay
- pub fn configure_daita(self) -> Self {
- let daita = Some(Daita {});
- Self { daita, ..self }
- }
-
- /// Configure QUIC for this relay
- pub fn configure_quic(self, options: Quic) -> Self {
- let quic = Some(options);
- Self { quic, ..self }
- }
-}
-
-impl Default for Features {
- fn default() -> Self {
- Features::empty()
- }
}
-/// DAITA doesn't have any configuration options (exposed by the API).
-///
-/// Note, an empty struct is not the same as an empty tuple struct according to serde_json!
-#[derive(Debug, Clone, Deserialize, Serialize)]
-pub struct Daita {}
-
/// Parameters for setting up a QUIC obfuscator (connecting to a masque-proxy running on a relay).
-#[derive(Debug, Clone, Deserialize, Serialize)]
+#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]
pub struct Quic {
/// In-addresses for the QUIC obfuscator.
///
/// There may be 0, 1 or 2 in IPs, depending on how many masque-proxy daemons running on the
/// relay. Hopefully the API will tell use the correct amount🤞.
- addr_in: Vec<IpAddr>,
+ addr_in: HashSet<IpAddr>,
/// Authorization token
token: String,
/// Hostname where masque proxy is hosted
@@ -158,7 +118,8 @@ pub struct Quic {
}
impl Quic {
- pub fn new(addr_in: Vec<IpAddr>, token: String, domain: String) -> Self {
+ pub fn new(addr_in: impl IntoIterator<Item = IpAddr>, token: String, domain: String) -> Self {
+ let addr_in = HashSet::from_iter(addr_in);
Self {
addr_in,
token,
@@ -201,6 +162,10 @@ impl Quic {
pub fn auth_token(&self) -> &str {
&self.token
}
+
+ pub fn in_addr(&self) -> impl Iterator<Item = IpAddr> {
+ self.addr_in.iter().copied()
+ }
}
impl Relay {
@@ -230,7 +195,7 @@ impl PartialEq for Relay {
/// # Example
///
/// ```rust
- /// # use mullvad_types::{relay_list::{Relay, Features}, relay_list::{RelayEndpointData, WireguardRelayEndpointData}};
+ /// # use mullvad_types::{relay_list::Relay, relay_list::{RelayEndpointData, WireguardRelayEndpointData}};
/// # use talpid_types::net::wireguard::PublicKey;
///
/// let relay = Relay {
@@ -250,7 +215,8 @@ impl PartialEq for Relay {
/// # )
/// # .unwrap(),
/// # daita: false,
- /// # shadowsocks_extra_addr_in: vec![],
+ /// # shadowsocks_extra_addr_in: Default::default(),
+ /// # quic: None,
/// # }),
/// # location: mullvad_types::location::Location {
/// # country: "Sweden".to_string(),
@@ -260,7 +226,6 @@ impl PartialEq for Relay {
/// # latitude: 57.71,
/// # longitude: 11.97,
/// # },
- /// # features: Features::default(),
/// };
///
/// let mut different_relay = relay.clone();
@@ -338,17 +303,62 @@ impl Default for WireguardEndpointData {
}
/// Contains data about specific WireGuard endpoints, i.e. their public keys.
-#[derive(Clone, Eq, PartialEq, Hash, Deserialize, Serialize, Debug)]
+#[derive(Clone, Eq, PartialEq, Deserialize, Serialize, Debug)]
pub struct WireguardRelayEndpointData {
/// Public key used by the relay peer
pub public_key: wireguard::PublicKey,
- /// Whether the server supports DAITA
- /// FIXME: This has been superceded by [Features] + [Daita].
+ /// Whether the relay supports DAITA
#[serde(default)]
pub daita: bool,
+ /// Parameters for connecting to the masque-proxy running on the relay.
+ #[serde(default)]
+ pub quic: Option<Quic>,
/// Optional IP addresses used by Shadowsocks
#[serde(default)]
- pub shadowsocks_extra_addr_in: Vec<IpAddr>,
+ pub shadowsocks_extra_addr_in: HashSet<IpAddr>,
+}
+
+impl WireguardRelayEndpointData {
+ pub fn new(public_key: wireguard::PublicKey) -> Self {
+ Self {
+ public_key,
+ daita: Default::default(),
+ quic: Default::default(),
+ shadowsocks_extra_addr_in: Default::default(),
+ }
+ }
+
+ pub fn set_daita(self, enabled: bool) -> Self {
+ Self {
+ daita: enabled,
+ ..self
+ }
+ }
+
+ pub fn set_quic(self, quic: Quic) -> Self {
+ Self {
+ quic: Some(quic),
+ ..self
+ }
+ }
+
+ /// Add `in_addrs` to the existing shadowsocks extra in addressess.
+ pub fn add_shadowsocks_extra_in_addrs(self, in_addrs: impl Iterator<Item = IpAddr>) -> Self {
+ let in_addrs = self.shadowsocks_extra_in_addrs().copied().chain(in_addrs);
+ Self {
+ shadowsocks_extra_addr_in: HashSet::from_iter(in_addrs),
+ ..self
+ }
+ }
+
+ pub fn shadowsocks_extra_in_addrs(&self) -> impl Iterator<Item = &IpAddr> {
+ self.shadowsocks_extra_addr_in.iter()
+ }
+
+ // Is this really needed if `self.quic` is pub?
+ pub fn quic(&self) -> Option<&Quic> {
+ self.quic.as_ref()
+ }
}
#[derive(Debug, Default, Clone, Deserialize, Serialize)]