summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJonatan Rhodin <jonatan.rhodin@mullvad.net>2025-10-09 11:42:31 +0200
committerJonatan Rhodin <jonatan.rhodin@mullvad.net>2025-10-09 11:42:31 +0200
commit6992fc2142a654eb2b5ff18d903ec35c78cfc649 (patch)
tree212faf16c79f62ef993ffc881ce39bd861d245e9
parentcc3d7a2295765fb47949fc73b8bcf7cb76cea552 (diff)
parent99d60e623c91a60bec642425fcc7e5b16e457952 (diff)
downloadmullvadvpn-6992fc2142a654eb2b5ff18d903ec35c78cfc649.tar.xz
mullvadvpn-6992fc2142a654eb2b5ff18d903ec35c78cfc649.zip
Merge branch 'implement-lwo-in-the-android-app-droid-2226'
-rw-r--r--android/CHANGELOG.md2
-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/compose/cell/FilterRow.kt6
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/ObfuscationModeCell.kt1
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/connectioninfo/FeatureIndicatorsPanel.kt3
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt3
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreen.kt15
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/VpnSettingItem.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/VpnSettingsUiState.kt2
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/relaylist/RelayItemExtensions.kt20
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/FilterChipUseCase.kt9
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/FilteredRelayListUseCase.kt11
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/customlists/FilterCustomListsRelayItemUseCase.kt19
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/Filter.kt7
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/Settings.kt2
-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/FromDomain.kt1
-rw-r--r--android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt9
-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/FeatureIndicator.kt1
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ObfuscationMode.kt1
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ObfuscationType.kt1
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/RelayItem.kt1
-rw-r--r--android/lib/resource/src/main/res/values/strings_non_translatable.xml1
-rw-r--r--android/lib/ui/component/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/component/relaylist/RelayItemPreviewData.kt1
-rw-r--r--android/lib/ui/tag/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/tag/TestTagConstants.kt2
-rw-r--r--android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/page/VpnSettingsPage.kt9
-rw-r--r--android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/ConnectionTest.kt51
-rw-r--r--android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/constant/Relays.kt4
-rw-r--r--android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/RelayProvider.kt8
-rw-r--r--mullvad-relay-selector/src/relay_selector/mod.rs1
-rw-r--r--mullvad-relay-selector/tests/relay_selector.rs1
32 files changed, 196 insertions, 22 deletions
diff --git a/android/CHANGELOG.md b/android/CHANGELOG.md
index 1bd115a0c9..23780b6ade 100644
--- a/android/CHANGELOG.md
+++ b/android/CHANGELOG.md
@@ -22,6 +22,8 @@ Line wrap the file at 100 chars. Th
* **Security**: in case of vulnerabilities.
## [Unreleased]
+### Added
+- Add LWO obfuscation.
## [android/2025.8] - 2025-09-22
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 8e46a555c9..c6c69e088b 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
@@ -23,6 +23,7 @@ private val DUMMY_RELAY_1 =
ownership = Ownership.Rented,
daita = false,
quic = null,
+ lwo = false,
)
private val DUMMY_RELAY_2 =
RelayItem.Location.Relay(
@@ -36,6 +37,7 @@ private val DUMMY_RELAY_2 =
ownership = Ownership.MullvadOwned,
daita = false,
quic = null,
+ lwo = false,
)
private val DUMMY_RELAY_CITY_1 =
RelayItem.Location.City(
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/FilterRow.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/FilterRow.kt
index 508d4af359..eafcb58d3d 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/FilterRow.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/FilterRow.kt
@@ -57,6 +57,7 @@ fun FilterRow(
is FilterChip.Entry -> EntryFilterChip()
is FilterChip.Exit -> ExitFilterChip()
is FilterChip.Quic -> QuicFilterChip()
+ is FilterChip.Lwo -> LwoFilterChip()
}
}
}
@@ -116,6 +117,11 @@ fun QuicFilterChip() {
)
}
+@Composable
+fun LwoFilterChip() {
+ MullvadFilterChip(text = stringResource(id = R.string.lwo), onRemoveClick = {}, enabled = false)
+}
+
private fun Ownership.stringResources(): Int =
when (this) {
Ownership.MullvadOwned -> R.string.owned
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/ObfuscationModeCell.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/ObfuscationModeCell.kt
index bc0c52db83..8a5f94d730 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/ObfuscationModeCell.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/ObfuscationModeCell.kt
@@ -123,6 +123,7 @@ private fun ObfuscationMode.toTitle() =
ObfuscationMode.Udp2Tcp -> stringResource(id = R.string.upd_over_tcp)
ObfuscationMode.Shadowsocks -> stringResource(id = R.string.shadowsocks)
ObfuscationMode.Quic -> stringResource(id = R.string.quic)
+ ObfuscationMode.Lwo -> stringResource(id = R.string.lwo)
}
@Composable
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/connectioninfo/FeatureIndicatorsPanel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/connectioninfo/FeatureIndicatorsPanel.kt
index 98abee9589..6c404a5bc9 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/connectioninfo/FeatureIndicatorsPanel.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/component/connectioninfo/FeatureIndicatorsPanel.kt
@@ -131,7 +131,8 @@ private fun FeatureIndicator.text(): String {
FeatureIndicator.SPLIT_TUNNELING -> R.string.split_tunneling
FeatureIndicator.SHADOWSOCKS,
FeatureIndicator.UDP_2_TCP,
- FeatureIndicator.QUIC -> R.string.feature_obfuscation
+ FeatureIndicator.QUIC,
+ FeatureIndicator.LWO -> R.string.feature_obfuscation
FeatureIndicator.LAN_SHARING -> R.string.local_network_sharing
FeatureIndicator.DNS_CONTENT_BLOCKERS -> R.string.dns_content_blockers
FeatureIndicator.CUSTOM_DNS -> R.string.feature_custom_dns
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt
index 13b6a518cf..c7074b10a6 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/ConnectScreen.kt
@@ -807,5 +807,6 @@ private fun FeatureIndicator.destination() =
FeatureIndicator.DNS_CONTENT_BLOCKERS,
FeatureIndicator.CUSTOM_DNS,
FeatureIndicator.CUSTOM_MTU,
- FeatureIndicator.QUIC -> VpnSettingsDestination(scrollToFeature = this, isModal = true)
+ FeatureIndicator.QUIC,
+ FeatureIndicator.LWO -> VpnSettingsDestination(scrollToFeature = this, isModal = true)
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreen.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreen.kt
index a997b39ad4..1cba4093f1 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreen.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreen.kt
@@ -121,6 +121,7 @@ import net.mullvad.mullvadvpn.lib.ui.tag.LAZY_LIST_WIREGUARD_CUSTOM_PORT_NUMBER_
import net.mullvad.mullvadvpn.lib.ui.tag.LAZY_LIST_WIREGUARD_CUSTOM_PORT_TEXT_TEST_TAG
import net.mullvad.mullvadvpn.lib.ui.tag.LAZY_LIST_WIREGUARD_OBFUSCATION_TITLE_TEST_TAG
import net.mullvad.mullvadvpn.lib.ui.tag.LAZY_LIST_WIREGUARD_PORT_ITEM_X_TEST_TAG
+import net.mullvad.mullvadvpn.lib.ui.tag.WIREGUARD_OBFUSCATION_LWO_CELL_TEST_TAG
import net.mullvad.mullvadvpn.lib.ui.tag.WIREGUARD_OBFUSCATION_OFF_CELL_TEST_TAG
import net.mullvad.mullvadvpn.lib.ui.tag.WIREGUARD_OBFUSCATION_QUIC_CELL_TEST_TAG
import net.mullvad.mullvadvpn.lib.ui.tag.WIREGUARD_OBFUSCATION_SHADOWSOCKS_CELL_TEST_TAG
@@ -479,7 +480,8 @@ fun VpnSettingsContent(
when (initialScrollToFeature) {
FeatureIndicator.UDP_2_TCP,
FeatureIndicator.SHADOWSOCKS,
- FeatureIndicator.QUIC -> VpnSettingItem.ObfuscationHeader::class
+ FeatureIndicator.QUIC,
+ FeatureIndicator.LWO -> VpnSettingItem.ObfuscationHeader::class
FeatureIndicator.LAN_SHARING -> VpnSettingItem.LocalNetworkSharingSetting::class
FeatureIndicator.QUANTUM_RESISTANCE -> VpnSettingItem.QuantumResistanceHeader::class
FeatureIndicator.DNS_CONTENT_BLOCKERS -> VpnSettingItem.DnsContentBlockersHeader::class
@@ -867,6 +869,17 @@ fun VpnSettingsContent(
)
}
+ is VpnSettingItem.ObfuscationItem.Lwo ->
+ item(key = it::class.simpleName) {
+ SelectableCell(
+ title = stringResource(id = R.string.lwo),
+ isSelected = it.selected,
+ modifier = Modifier.animateItem(),
+ testTag = WIREGUARD_OBFUSCATION_LWO_CELL_TEST_TAG,
+ onCellClicked = { onSelectObfuscationMode(ObfuscationMode.Lwo) },
+ )
+ }
+
is VpnSettingItem.QuantumItem ->
item(key = it::class.simpleName + it.quantumResistantState) {
SelectableCell(
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/VpnSettingItem.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/VpnSettingItem.kt
index 3ce6637115..fe3ee1a50a 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/VpnSettingItem.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/VpnSettingItem.kt
@@ -106,6 +106,8 @@ sealed interface VpnSettingItem {
data class Quic(override val selected: Boolean) : ObfuscationItem
+ data class Lwo(override val selected: Boolean) : ObfuscationItem
+
data class Off(override val selected: Boolean) : ObfuscationItem
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/VpnSettingsUiState.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/VpnSettingsUiState.kt
index 08fafc56c0..d860aa3c21 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/VpnSettingsUiState.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/VpnSettingsUiState.kt
@@ -209,6 +209,8 @@ data class VpnSettingsUiState(val settings: List<VpnSettingItem>, val isModal: B
VpnSettingItem.ObfuscationItem.Quic(obfuscationMode == ObfuscationMode.Quic)
)
add(VpnSettingItem.Divider)
+ add(VpnSettingItem.ObfuscationItem.Lwo(obfuscationMode == ObfuscationMode.Lwo))
+ add(VpnSettingItem.Divider)
add(VpnSettingItem.ObfuscationItem.Off(obfuscationMode == ObfuscationMode.Off))
add(VpnSettingItem.Spacer)
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 c59b0124c3..182044b6fc 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
@@ -60,17 +60,18 @@ fun RelayItem.CustomList.filter(
providers: Constraint<Providers>,
daita: Boolean,
quic: Boolean,
+ lwo: Boolean,
ipVersion: Constraint<IpVersion>,
): RelayItem.CustomList {
val newLocations =
locations.mapNotNull {
when (it) {
is RelayItem.Location.Country ->
- it.filter(ownership, providers, daita, quic, ipVersion)
+ it.filter(ownership, providers, daita, quic, lwo, ipVersion)
is RelayItem.Location.City ->
- it.filter(ownership, providers, daita, quic, ipVersion)
+ it.filter(ownership, providers, daita, quic, lwo, ipVersion)
is RelayItem.Location.Relay ->
- it.filter(ownership, providers, daita, quic, ipVersion)
+ it.filter(ownership, providers, daita, quic, lwo, ipVersion)
}
}
return copy(locations = newLocations)
@@ -81,9 +82,10 @@ fun RelayItem.Location.Country.filter(
providers: Constraint<Providers>,
daita: Boolean,
quic: Boolean,
+ lwo: Boolean,
ipVersion: Constraint<IpVersion>,
): RelayItem.Location.Country? {
- val cities = cities.mapNotNull { it.filter(ownership, providers, daita, quic, ipVersion) }
+ val cities = cities.mapNotNull { it.filter(ownership, providers, daita, quic, lwo, ipVersion) }
return if (cities.isNotEmpty()) {
this.copy(cities = cities)
} else {
@@ -96,9 +98,10 @@ private fun RelayItem.Location.City.filter(
providers: Constraint<Providers>,
daita: Boolean,
quic: Boolean,
+ lwo: Boolean,
ipVersion: Constraint<IpVersion>,
): RelayItem.Location.City? {
- val relays = relays.mapNotNull { it.filter(ownership, providers, daita, quic, ipVersion) }
+ val relays = relays.mapNotNull { it.filter(ownership, providers, daita, quic, lwo, ipVersion) }
return if (relays.isNotEmpty()) {
this.copy(relays = relays)
} else {
@@ -109,12 +112,16 @@ private fun RelayItem.Location.City.filter(
private fun RelayItem.Location.Relay.requiredFeatures(
requireDaita: Boolean,
requireQuic: Boolean,
+ requireLwo: Boolean,
ipVersion: Constraint<IpVersion>,
): Boolean =
when {
+ // Can not require LWO and require QUIC at the same time
requireDaita && requireQuic -> daita && quic?.supports(ipVersion) == true
+ requireDaita && requireLwo -> daita && lwo
requireDaita -> daita
requireQuic -> quic?.supports(ipVersion) == true
+ requireLwo -> lwo
else -> true
}
@@ -130,10 +137,11 @@ private fun RelayItem.Location.Relay.filter(
providers: Constraint<Providers>,
daita: Boolean,
quic: Boolean,
+ lwo: Boolean,
ipVersion: Constraint<IpVersion>,
): RelayItem.Location.Relay? =
if (
- requiredFeatures(daita, quic, ipVersion) &&
+ requiredFeatures(daita, quic, lwo, ipVersion) &&
hasOwnership(ownership) &&
hasProvider(providers)
)
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/FilterChipUseCase.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/FilterChipUseCase.kt
index 12ec3a296c..c115d86b2a 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/FilterChipUseCase.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/FilterChipUseCase.kt
@@ -11,8 +11,10 @@ import net.mullvad.mullvadvpn.lib.model.Settings
import net.mullvad.mullvadvpn.repository.RelayListFilterRepository
import net.mullvad.mullvadvpn.repository.SettingsRepository
import net.mullvad.mullvadvpn.util.isDaitaAndDirectOnly
+import net.mullvad.mullvadvpn.util.isLwoEnabled
import net.mullvad.mullvadvpn.util.isQuicEnabled
import net.mullvad.mullvadvpn.util.shouldFilterByDaita
+import net.mullvad.mullvadvpn.util.shouldFilterByLwo
import net.mullvad.mullvadvpn.util.shouldFilterByQuic
typealias ModelOwnership = Ownership
@@ -84,6 +86,11 @@ class FilterChipUseCase(
) {
add(FilterChip.Quic)
}
+ if (
+ shouldFilterByLwo(settings?.isLwoEnabled() == true, relayListType = relayListType)
+ ) {
+ add(FilterChip.Lwo)
+ }
}
}
}
@@ -100,4 +107,6 @@ sealed interface FilterChip {
data object Exit : FilterChip
data object Quic : FilterChip
+
+ data object Lwo : FilterChip
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/FilteredRelayListUseCase.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/FilteredRelayListUseCase.kt
index 2681cfa3ad..eb41e3b918 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/FilteredRelayListUseCase.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/FilteredRelayListUseCase.kt
@@ -14,8 +14,10 @@ import net.mullvad.mullvadvpn.repository.SettingsRepository
import net.mullvad.mullvadvpn.repository.WireguardConstraintsRepository
import net.mullvad.mullvadvpn.util.ipVersionConstraint
import net.mullvad.mullvadvpn.util.isDaitaAndDirectOnly
+import net.mullvad.mullvadvpn.util.isLwoEnabled
import net.mullvad.mullvadvpn.util.isQuicEnabled
import net.mullvad.mullvadvpn.util.shouldFilterByDaita
+import net.mullvad.mullvadvpn.util.shouldFilterByLwo
import net.mullvad.mullvadvpn.util.shouldFilterByQuic
class FilteredRelayListUseCase(
@@ -42,7 +44,12 @@ class FilteredRelayListUseCase(
),
shouldFilterByQuic =
shouldFilterByQuic(
- settings?.isQuicEnabled() == true,
+ isQuicEnabled = settings?.isQuicEnabled() == true,
+ relayListType = relayListType,
+ ),
+ shouldFilterByLwo =
+ shouldFilterByLwo(
+ isLwoEnable = settings?.isLwoEnabled() == true,
relayListType = relayListType,
),
constraintIpVersion = settings?.ipVersionConstraint() ?: Constraint.Any,
@@ -54,6 +61,7 @@ class FilteredRelayListUseCase(
providers: Constraint<Providers>,
shouldFilterByDaita: Boolean,
shouldFilterByQuic: Boolean,
+ shouldFilterByLwo: Boolean,
constraintIpVersion: Constraint<IpVersion>,
) = mapNotNull {
it.filter(
@@ -61,6 +69,7 @@ class FilteredRelayListUseCase(
providers,
shouldFilterByDaita,
shouldFilterByQuic,
+ shouldFilterByLwo,
constraintIpVersion,
)
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/customlists/FilterCustomListsRelayItemUseCase.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/customlists/FilterCustomListsRelayItemUseCase.kt
index 6604a76805..d351b7633c 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/customlists/FilterCustomListsRelayItemUseCase.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/usecase/customlists/FilterCustomListsRelayItemUseCase.kt
@@ -14,8 +14,10 @@ import net.mullvad.mullvadvpn.repository.SettingsRepository
import net.mullvad.mullvadvpn.repository.WireguardConstraintsRepository
import net.mullvad.mullvadvpn.util.ipVersionConstraint
import net.mullvad.mullvadvpn.util.isDaitaAndDirectOnly
+import net.mullvad.mullvadvpn.util.isLwoEnabled
import net.mullvad.mullvadvpn.util.isQuicEnabled
import net.mullvad.mullvadvpn.util.shouldFilterByDaita
+import net.mullvad.mullvadvpn.util.shouldFilterByLwo
import net.mullvad.mullvadvpn.util.shouldFilterByQuic
class FilterCustomListsRelayItemUseCase(
@@ -43,7 +45,12 @@ class FilterCustomListsRelayItemUseCase(
),
quic =
shouldFilterByQuic(
- settings?.isQuicEnabled() == true,
+ isQuicEnabled = settings?.isQuicEnabled() == true,
+ relayListType = relayListType,
+ ),
+ lwo =
+ shouldFilterByLwo(
+ isLwoEnable = settings?.isLwoEnabled() == true,
relayListType = relayListType,
),
ipVersion = settings?.ipVersionConstraint() ?: Constraint.Any,
@@ -55,8 +62,16 @@ class FilterCustomListsRelayItemUseCase(
providers: Constraint<Providers>,
daita: Boolean,
quic: Boolean,
+ lwo: Boolean,
ipVersion: Constraint<IpVersion>,
) = mapNotNull {
- it.filter(ownership, providers, daita = daita, quic = quic, ipVersion = ipVersion)
+ it.filter(
+ ownership,
+ providers,
+ daita = daita,
+ quic = quic,
+ lwo = lwo,
+ ipVersion = ipVersion,
+ )
}
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/Filter.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/Filter.kt
index 4fa7ba231c..32dabab1c0 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/Filter.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/Filter.kt
@@ -16,3 +16,10 @@ fun shouldFilterByQuic(isQuicEnabled: Boolean, relayListType: RelayListType) =
is RelayListType.Multihop ->
isQuicEnabled && relayListType.multihopRelayListType == MultihopRelayListType.ENTRY
}
+
+fun shouldFilterByLwo(isLwoEnable: Boolean, relayListType: RelayListType) =
+ when (relayListType) {
+ RelayListType.Single -> isLwoEnable
+ is RelayListType.Multihop ->
+ isLwoEnable && relayListType.multihopRelayListType == MultihopRelayListType.ENTRY
+ }
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/Settings.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/Settings.kt
index 4da463ab51..45354bad21 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/Settings.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/util/Settings.kt
@@ -22,6 +22,8 @@ fun Settings.isDaitaAndDirectOnly() = isDaitaEnabled() && isDaitaDirectOnly()
fun Settings.isQuicEnabled() = obfuscationSettings.selectedObfuscationMode == ObfuscationMode.Quic
+fun Settings.isLwoEnabled() = obfuscationSettings.selectedObfuscationMode == ObfuscationMode.Lwo
+
fun Settings.ipVersionConstraint() = relaySettings.relayConstraints.wireguardConstraints.ipVersion
fun Settings.isDaitaEnabled() = daitaSettings().enabled
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 fc6dcf79c0..100c665781 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
@@ -365,6 +365,7 @@ class CustomListLocationsViewModelTest {
ownership = Ownership.MullvadOwned,
daita = false,
quic = null,
+ lwo = false,
)
),
)
@@ -383,6 +384,7 @@ class CustomListLocationsViewModelTest {
ownership = Ownership.MullvadOwned,
daita = false,
quic = null,
+ lwo = false,
)
}
}
diff --git a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/FromDomain.kt b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/FromDomain.kt
index 2424ff00a7..5eb933e687 100644
--- a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/FromDomain.kt
+++ b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/FromDomain.kt
@@ -93,6 +93,7 @@ internal fun ObfuscationMode.fromDomain():
ObfuscationMode.Shadowsocks ->
ManagementInterface.ObfuscationSettings.SelectedObfuscation.SHADOWSOCKS
ObfuscationMode.Quic -> ManagementInterface.ObfuscationSettings.SelectedObfuscation.QUIC
+ ObfuscationMode.Lwo -> ManagementInterface.ObfuscationSettings.SelectedObfuscation.LWO
ObfuscationMode.Auto -> ManagementInterface.ObfuscationSettings.SelectedObfuscation.AUTO
ObfuscationMode.Off -> ManagementInterface.ObfuscationSettings.SelectedObfuscation.OFF
}
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 65ab420be3..26207c5d57 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
@@ -226,8 +226,7 @@ internal fun ManagementInterface.ObfuscationEndpoint.ObfuscationType.toDomain():
ManagementInterface.ObfuscationEndpoint.ObfuscationType.SHADOWSOCKS ->
ObfuscationType.Shadowsocks
ManagementInterface.ObfuscationEndpoint.ObfuscationType.QUIC -> ObfuscationType.Quic
- ManagementInterface.ObfuscationEndpoint.ObfuscationType.LWO ->
- throw IllegalArgumentException("Unsupported obfuscation type")
+ ManagementInterface.ObfuscationEndpoint.ObfuscationType.LWO -> ObfuscationType.Lwo
ManagementInterface.ObfuscationEndpoint.ObfuscationType.UNRECOGNIZED ->
throw IllegalArgumentException("Unrecognized obfuscation type")
}
@@ -434,8 +433,7 @@ internal fun ManagementInterface.ObfuscationSettings.SelectedObfuscation.toDomai
ManagementInterface.ObfuscationSettings.SelectedObfuscation.SHADOWSOCKS ->
ObfuscationMode.Shadowsocks
ManagementInterface.ObfuscationSettings.SelectedObfuscation.QUIC -> ObfuscationMode.Quic
- ManagementInterface.ObfuscationSettings.SelectedObfuscation.LWO ->
- throw IllegalArgumentException("Unsupported obfuscation type")
+ ManagementInterface.ObfuscationSettings.SelectedObfuscation.LWO -> ObfuscationMode.Lwo
ManagementInterface.ObfuscationSettings.SelectedObfuscation.UNRECOGNIZED ->
throw IllegalArgumentException("Unrecognized selected obfuscation")
}
@@ -606,6 +604,7 @@ internal fun ManagementInterface.Relay.toDomain(
} else {
null
},
+ lwo = endpointData.wireguard.lwo,
)
private fun ManagementInterface.Relay.RelayData.Wireguard.Quic.toDomain(): Quic =
@@ -730,7 +729,7 @@ internal fun ManagementInterface.FeatureIndicator.toDomain() =
ManagementInterface.FeatureIndicator.MULTIHOP -> FeatureIndicator.MULTIHOP
ManagementInterface.FeatureIndicator.DAITA_MULTIHOP -> FeatureIndicator.DAITA_MULTIHOP
ManagementInterface.FeatureIndicator.QUIC -> FeatureIndicator.QUIC
- ManagementInterface.FeatureIndicator.LWO,
+ ManagementInterface.FeatureIndicator.LWO -> FeatureIndicator.LWO
ManagementInterface.FeatureIndicator.LOCKDOWN_MODE,
ManagementInterface.FeatureIndicator.BRIDGE_MODE,
ManagementInterface.FeatureIndicator.CUSTOM_MSS_FIX,
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 310b2ab1fd..e6b110c5b3 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
@@ -27,6 +27,7 @@ class RelayNameComparatorTest {
ownership = Ownership.MullvadOwned,
daita = false,
quic = null,
+ lwo = false,
)
val relay10 =
RelayItem.Location.Relay(
@@ -36,6 +37,7 @@ class RelayNameComparatorTest {
ownership = Ownership.MullvadOwned,
daita = false,
quic = null,
+ lwo = false,
)
relay9 assertOrderBothDirection relay10
@@ -51,6 +53,7 @@ class RelayNameComparatorTest {
ownership = Ownership.MullvadOwned,
daita = false,
quic = null,
+ lwo = false,
)
val relay9b =
RelayItem.Location.Relay(
@@ -60,6 +63,7 @@ class RelayNameComparatorTest {
ownership = Ownership.MullvadOwned,
daita = false,
quic = null,
+ lwo = false,
)
assertTrue(RelayNameComparator.compare(relay9a, relay9b) == 0)
@@ -76,6 +80,7 @@ class RelayNameComparatorTest {
ownership = Ownership.MullvadOwned,
daita = false,
quic = null,
+ lwo = false,
)
val relay1 =
RelayItem.Location.Relay(
@@ -85,6 +90,7 @@ class RelayNameComparatorTest {
ownership = Ownership.MullvadOwned,
daita = false,
quic = null,
+ lwo = false,
)
val relay3 =
RelayItem.Location.Relay(
@@ -94,6 +100,7 @@ class RelayNameComparatorTest {
ownership = Ownership.MullvadOwned,
daita = false,
quic = null,
+ lwo = false,
)
val relay100 =
RelayItem.Location.Relay(
@@ -103,6 +110,7 @@ class RelayNameComparatorTest {
ownership = Ownership.MullvadOwned,
daita = false,
quic = null,
+ lwo = false,
)
relay001 assertOrderBothDirection relay1
@@ -121,6 +129,7 @@ class RelayNameComparatorTest {
ownership = Ownership.MullvadOwned,
daita = false,
quic = null,
+ lwo = false,
)
val relay9b =
RelayItem.Location.Relay(
@@ -130,6 +139,7 @@ class RelayNameComparatorTest {
ownership = Ownership.MullvadOwned,
daita = false,
quic = null,
+ lwo = false,
)
assertTrue(RelayNameComparator.compare(relay9a, relay9b) == 0)
@@ -146,6 +156,7 @@ class RelayNameComparatorTest {
ownership = Ownership.MullvadOwned,
daita = false,
quic = null,
+ lwo = false,
)
val relay005 =
RelayItem.Location.Relay(
@@ -155,6 +166,7 @@ class RelayNameComparatorTest {
ownership = Ownership.MullvadOwned,
daita = false,
quic = null,
+ lwo = false,
)
relay001 assertOrderBothDirection relay005
@@ -170,6 +182,7 @@ class RelayNameComparatorTest {
ownership = Ownership.MullvadOwned,
daita = false,
quic = null,
+ lwo = false,
)
val relayAr8 =
RelayItem.Location.Relay(
@@ -179,6 +192,7 @@ class RelayNameComparatorTest {
ownership = Ownership.MullvadOwned,
daita = false,
quic = null,
+ lwo = false,
)
val relaySe5 =
RelayItem.Location.Relay(
@@ -188,6 +202,7 @@ class RelayNameComparatorTest {
ownership = Ownership.MullvadOwned,
daita = false,
quic = null,
+ lwo = false,
)
val relaySe10 =
RelayItem.Location.Relay(
@@ -197,6 +212,7 @@ class RelayNameComparatorTest {
ownership = Ownership.MullvadOwned,
daita = false,
quic = null,
+ lwo = false,
)
relayAr2 assertOrderBothDirection relayAr8
@@ -214,6 +230,7 @@ class RelayNameComparatorTest {
ownership = Ownership.MullvadOwned,
daita = false,
quic = null,
+ lwo = false,
)
val relay2w =
RelayItem.Location.Relay(
@@ -223,6 +240,7 @@ class RelayNameComparatorTest {
ownership = Ownership.MullvadOwned,
daita = false,
quic = null,
+ lwo = false,
)
relay2c assertOrderBothDirection relay2w
@@ -238,6 +256,7 @@ class RelayNameComparatorTest {
ownership = Ownership.MullvadOwned,
daita = false,
quic = null,
+ lwo = false,
)
val relay22b =
RelayItem.Location.Relay(
@@ -247,6 +266,7 @@ class RelayNameComparatorTest {
ownership = Ownership.MullvadOwned,
daita = false,
quic = null,
+ lwo = false,
)
relay22a assertOrderBothDirection relay22b
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/FeatureIndicator.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/FeatureIndicator.kt
index 6d7951749b..84f516f7bd 100644
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/FeatureIndicator.kt
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/FeatureIndicator.kt
@@ -10,6 +10,7 @@ enum class FeatureIndicator {
UDP_2_TCP,
SHADOWSOCKS,
QUIC,
+ LWO,
LAN_SHARING,
DNS_CONTENT_BLOCKERS,
CUSTOM_DNS,
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ObfuscationMode.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ObfuscationMode.kt
index 8926ded829..bdf8d28c5c 100644
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ObfuscationMode.kt
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ObfuscationMode.kt
@@ -6,4 +6,5 @@ enum class ObfuscationMode {
Udp2Tcp,
Shadowsocks,
Quic,
+ Lwo,
}
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ObfuscationType.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ObfuscationType.kt
index 5eb0ad5548..e84d6ec769 100644
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ObfuscationType.kt
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/lib/model/ObfuscationType.kt
@@ -4,4 +4,5 @@ enum class ObfuscationType {
Udp2Tcp,
Shadowsocks,
Quic,
+ Lwo,
}
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 197e8e95ce..492069a62b 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
@@ -86,6 +86,7 @@ sealed interface RelayItem {
override val active: Boolean,
val daita: Boolean,
val quic: Quic?,
+ val lwo: Boolean,
) : Location {
override val name: String = id.code
override val hasChildren: Boolean = false
diff --git a/android/lib/resource/src/main/res/values/strings_non_translatable.xml b/android/lib/resource/src/main/res/values/strings_non_translatable.xml
index 3595fe7096..0c87a19c25 100644
--- a/android/lib/resource/src/main/res/values/strings_non_translatable.xml
+++ b/android/lib/resource/src/main/res/values/strings_non_translatable.xml
@@ -12,6 +12,7 @@
<string name="socks5_remote" translatable="false">SOCKS5</string>
<string name="shadowsocks" translatable="false">Shadowsocks</string>
<string name="quic" translatable="false">QUIC</string>
+ <string name="lwo" translatable="false">LWO</string>
<string name="local_network_sharing_ip_ranges" translatable="false">
<![CDATA[<ul><li>10.0.0.0/8</li><li>172.16.0.0/12</li><li>192.168.0.0/16</li><li>169.254.0.0/16</li><li>fe80::/10</li><li>fc00::/7</li></ul>]]>
</string>
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 2e4e228439..be4c5d4539 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
@@ -52,6 +52,7 @@ private fun generateRelayItemRelay(
ownership = Ownership.MullvadOwned,
daita = daita,
quic = null,
+ lwo = false,
)
private fun String.generateCountryCode() =
diff --git a/android/lib/ui/tag/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/tag/TestTagConstants.kt b/android/lib/ui/tag/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/tag/TestTagConstants.kt
index 9b7be4dc07..419459031d 100644
--- a/android/lib/ui/tag/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/tag/TestTagConstants.kt
+++ b/android/lib/ui/tag/src/main/kotlin/net/mullvad/mullvadvpn/lib/ui/tag/TestTagConstants.kt
@@ -32,8 +32,8 @@ const val WIREGUARD_OBFUSCATION_SHADOWSOCKS_CELL_TEST_TAG =
"wireguard_obfuscation_shadowsocks_cell_test_tag"
const val WIREGUARD_OBFUSCATION_UDP_OVER_TCP_CELL_TEST_TAG =
"wireguard_obfuscation_udp_over_tcp_cell_test_tag"
-
const val WIREGUARD_OBFUSCATION_QUIC_CELL_TEST_TAG = "wireguard_obfuscation_quic_cell_test_tag"
+const val WIREGUARD_OBFUSCATION_LWO_CELL_TEST_TAG = "wireguard_obfuscation_lwo_cell_test_tag"
// Account screen
const val MANAGE_DEVICES_BUTTON_TEST_TAG = "manage_devices_button_test_tag"
diff --git a/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/page/VpnSettingsPage.kt b/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/page/VpnSettingsPage.kt
index 1e4f0dd4e1..ad779595fa 100644
--- a/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/page/VpnSettingsPage.kt
+++ b/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/page/VpnSettingsPage.kt
@@ -8,6 +8,7 @@ import net.mullvad.mullvadvpn.lib.ui.tag.LAZY_LIST_QUANTUM_ITEM_ON_TEST_TAG
import net.mullvad.mullvadvpn.lib.ui.tag.LAZY_LIST_VPN_SETTINGS_TEST_TAG
import net.mullvad.mullvadvpn.lib.ui.tag.LAZY_LIST_WIREGUARD_CUSTOM_PORT_TEXT_TEST_TAG
import net.mullvad.mullvadvpn.lib.ui.tag.SWITCH_TEST_TAG
+import net.mullvad.mullvadvpn.lib.ui.tag.WIREGUARD_OBFUSCATION_LWO_CELL_TEST_TAG
import net.mullvad.mullvadvpn.lib.ui.tag.WIREGUARD_OBFUSCATION_OFF_CELL_TEST_TAG
import net.mullvad.mullvadvpn.lib.ui.tag.WIREGUARD_OBFUSCATION_QUIC_CELL_TEST_TAG
import net.mullvad.mullvadvpn.lib.ui.tag.WIREGUARD_OBFUSCATION_SHADOWSOCKS_CELL_TEST_TAG
@@ -40,6 +41,10 @@ class VpnSettingsPage internal constructor() : Page() {
scrollUntilCell(WIREGUARD_OBFUSCATION_QUIC_CELL_TEST_TAG)
}
+ fun scrollUntilWireGuardObfuscationLwoCell() {
+ scrollUntilCell(WIREGUARD_OBFUSCATION_LWO_CELL_TEST_TAG)
+ }
+
fun scrollUntilWireGuardObfuscationOffCell() {
scrollUntilCell(WIREGUARD_OBFUSCATION_OFF_CELL_TEST_TAG)
}
@@ -64,6 +69,10 @@ class VpnSettingsPage internal constructor() : Page() {
uiDevice.clickObjectAwaitIsChecked(By.res(WIREGUARD_OBFUSCATION_QUIC_CELL_TEST_TAG))
}
+ fun clickWireguardObfuscationLwoCell() {
+ uiDevice.clickObjectAwaitIsChecked(By.res(WIREGUARD_OBFUSCATION_LWO_CELL_TEST_TAG))
+ }
+
fun clickWireGuardObfuscationOffCell() {
uiDevice.clickObjectAwaitIsChecked(By.res(WIREGUARD_OBFUSCATION_OFF_CELL_TEST_TAG))
}
diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/ConnectionTest.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/ConnectionTest.kt
index e9c503e5b6..7c4a6534a8 100644
--- a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/ConnectionTest.kt
+++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/ConnectionTest.kt
@@ -292,6 +292,57 @@ class ConnectionTest : EndToEndTest() {
@Test
@HasDependencyOnLocalAPI
@ClearFirewallRules
+ fun testLwo() = runTest {
+ app.launchAndLogIn(accountTestRule.validAccountNumber)
+ on<ConnectPage> { enableLocalNetworkSharingStory() }
+
+ on<ConnectPage> { clickSelectLocation() }
+
+ on<SelectLocationPage> {
+ val lwoRelay = relayProvider.getLwoRelay()
+ clickLocationExpandButton(lwoRelay.country)
+ clickLocationExpandButton(lwoRelay.city)
+ scrollUntilCell(lwoRelay.relay)
+ clickLocationCell(lwoRelay.relay)
+ }
+
+ device.acceptVpnPermissionDialog()
+
+ var relayIpAddress: String? = null
+
+ on<ConnectPage> {
+ waitForConnectedLabel()
+ relayIpAddress = extractInIpv4Address()
+ clickDisconnect()
+ }
+
+ // Block UDP traffic to the relay
+ val firewallRule = DropRule.blockWireGuardTrafficRule(relayIpAddress!!)
+ firewallClient.createRule(firewallRule)
+
+ // Enable QUIC
+ on<ConnectPage> { clickSettings() }
+
+ on<SettingsPage> { clickVpnSettings() }
+
+ on<VpnSettingsPage> {
+ scrollUntilWireGuardObfuscationLwoCell()
+ clickWireguardObfuscationLwoCell()
+ }
+
+ device.pressBack()
+ device.pressBack()
+
+ on<ConnectPage> {
+ clickConnect()
+ waitForConnectedLabel(timeout = EXTREMELY_LONG_TIMEOUT)
+ clickDisconnect()
+ }
+ }
+
+ @Test
+ @HasDependencyOnLocalAPI
+ @ClearFirewallRules
fun testShadowsocks() = runTest {
app.launchAndLogIn(accountTestRule.validAccountNumber)
on<ConnectPage> { enableLocalNetworkSharingStory() }
diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/constant/Relays.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/constant/Relays.kt
index 7d098fff8f..1bb20564de 100644
--- a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/constant/Relays.kt
+++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/constant/Relays.kt
@@ -5,15 +5,15 @@ import net.mullvad.mullvadvpn.test.e2e.misc.TestRelay
object Stagemole {
val DEFAULT_RELAY = Relays.gotWg001
val DAITA_RELAY = Relays.gotWg002RelaySoftware
-
val QUIC_RELAY = Relays.stoWg001
+ val LWO_RELAY = Relays.stoWg001
}
object Production {
val DEFAULT_RELAY = Relays.gotWg001
val DAITA_RELAY = Relays.gotWg002
-
val QUIC_RELAY = Relays.stoWg204
+ val LWO_RELAY = Relays.stoWg204
}
private object Relays {
diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/RelayProvider.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/RelayProvider.kt
index ee27f95148..953aa7164d 100644
--- a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/RelayProvider.kt
+++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/RelayProvider.kt
@@ -29,6 +29,14 @@ class RelayProvider(val currentFlavor: String = BuildConfig.FLAVOR_billing) {
else -> error("Invalid flavor: $currentFlavor")
}
}
+
+ fun getLwoRelay(): TestRelay {
+ return when (currentFlavor) {
+ "play" -> Stagemole.LWO_RELAY
+ "oss" -> Production.LWO_RELAY
+ else -> error("Invalid flavor: $currentFlavor")
+ }
+ }
}
data class TestRelay(val country: String, val city: String, val relay: String)
diff --git a/mullvad-relay-selector/src/relay_selector/mod.rs b/mullvad-relay-selector/src/relay_selector/mod.rs
index 2bf71877da..dc454a2cfd 100644
--- a/mullvad-relay-selector/src/relay_selector/mod.rs
+++ b/mullvad-relay-selector/src/relay_selector/mod.rs
@@ -80,7 +80,6 @@ pub static WIREGUARD_RETRY_ORDER: LazyLock<Vec<RelayQuery>> = LazyLock::new(|| {
.ip_version(IpVersion::V6)
.build(),
// 7
- #[cfg(not(target_os = "android"))]
RelayQueryBuilder::wireguard().lwo().build(),
]
});
diff --git a/mullvad-relay-selector/tests/relay_selector.rs b/mullvad-relay-selector/tests/relay_selector.rs
index e1c786b7e3..7f35ff7084 100644
--- a/mullvad-relay-selector/tests/relay_selector.rs
+++ b/mullvad-relay-selector/tests/relay_selector.rs
@@ -340,7 +340,6 @@ fn assert_wireguard_retry_order() {
.ip_version(IpVersion::V6)
.build(),
// 7
- #[cfg(not(target_os = "android"))]
RelayQueryBuilder::wireguard().lwo().build(),
];