diff options
| author | Jonatan Rhodin <jonatan.rhodin@mullvad.net> | 2025-10-09 11:42:31 +0200 |
|---|---|---|
| committer | Jonatan Rhodin <jonatan.rhodin@mullvad.net> | 2025-10-09 11:42:31 +0200 |
| commit | 6992fc2142a654eb2b5ff18d903ec35c78cfc649 (patch) | |
| tree | 212faf16c79f62ef993ffc881ce39bd861d245e9 | |
| parent | cc3d7a2295765fb47949fc73b8bcf7cb76cea552 (diff) | |
| parent | 99d60e623c91a60bec642425fcc7e5b16e457952 (diff) | |
| download | mullvadvpn-6992fc2142a654eb2b5ff18d903ec35c78cfc649.tar.xz mullvadvpn-6992fc2142a654eb2b5ff18d903ec35c78cfc649.zip | |
Merge branch 'implement-lwo-in-the-android-app-droid-2226'
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(), ]; |
