diff options
| author | Jonatan Rhodin <jonatan.rhodin@mullvad.net> | 2025-03-24 15:29:09 +0100 |
|---|---|---|
| committer | Jonatan Rhodin <jonatan.rhodin@mullvad.net> | 2025-03-24 15:29:09 +0100 |
| commit | e8de4aa7d85ec0c8aa1a30ad59d729645d97d60b (patch) | |
| tree | d3ba125be6b7318f90887248febda6fa1e2bd36b /android/app | |
| parent | 7ecd5e2642f65457275a932bc087a153566ef4ce (diff) | |
| parent | a4cd52381c23f17892f243cbd97be24bb38becae (diff) | |
| download | mullvadvpn-e8de4aa7d85ec0c8aa1a30ad59d729645d97d60b.tar.xz mullvadvpn-e8de4aa7d85ec0c8aa1a30ad59d729645d97d60b.zip | |
Merge branch 'workaround-ipv6-leak-when-disabling-in-tunnel-ipv6-droid-1669'
Diffstat (limited to 'android/app')
12 files changed, 155 insertions, 65 deletions
diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/dialog/DnsDialogTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/dialog/DnsDialogTest.kt index 56bdc562fd..44cc53a256 100644 --- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/dialog/DnsDialogTest.kt +++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/dialog/DnsDialogTest.kt @@ -21,9 +21,9 @@ class DnsDialogTest { DnsDialogViewState( input = "", validationError = null, - isLocal = false, isAllowLanEnabled = false, index = null, + isIpv6Enabled = true, ) private fun ComposeContext.initDialog( @@ -48,7 +48,7 @@ class DnsDialogTest { fun testDnsDialogLanWarningShownWhenLanTrafficDisabledAndLocalAddressUsed() = composeExtension.use { // Arrange - initDialog(defaultState.copy(isAllowLanEnabled = false, isLocal = true)) + initDialog(defaultState.copy(isAllowLanEnabled = false, input = localIpAddress)) // Assert onNodeWithText(LOCAL_DNS_SERVER_WARNING).assertExists() @@ -58,7 +58,7 @@ class DnsDialogTest { fun testDnsDialogLanWarningNotShownWhenLanTrafficEnabledAndLocalAddressUsed() = composeExtension.use { // Arrange - initDialog(defaultState.copy(isAllowLanEnabled = true, isLocal = true)) + initDialog(defaultState.copy(isAllowLanEnabled = true, input = localIpAddress)) // Assert onNodeWithText(LOCAL_DNS_SERVER_WARNING).assertDoesNotExist() @@ -68,7 +68,7 @@ class DnsDialogTest { fun testDnsDialogLanWarningNotShownWhenLanTrafficEnabledAndNonLocalAddressUsed() = composeExtension.use { // Arrange - initDialog(defaultState.copy(isAllowLanEnabled = true, isLocal = false)) + initDialog(defaultState.copy(isAllowLanEnabled = true, input = publicIpAddress)) // Assert onNodeWithText(LOCAL_DNS_SERVER_WARNING).assertDoesNotExist() @@ -78,7 +78,7 @@ class DnsDialogTest { fun testDnsDialogLanWarningNotShownWhenLanTrafficDisabledAndNonLocalAddressUsed() = composeExtension.use { // Arrange - initDialog(defaultState.copy(isAllowLanEnabled = false, isLocal = false)) + initDialog(defaultState.copy(isAllowLanEnabled = false, input = publicIpAddress)) // Assert onNodeWithText(LOCAL_DNS_SERVER_WARNING).assertDoesNotExist() @@ -105,7 +105,7 @@ class DnsDialogTest { // Arrange initDialog( defaultState.copy( - input = "192.168.0.1", + input = localIpAddress, validationError = ValidationError.DuplicateAddress, ) ) @@ -120,5 +120,7 @@ class DnsDialogTest { "\"Local Network Sharing\" under VPN settings." private const val invalidIpAddress = "300.300.300.300" + private const val localIpAddress = "192.168.0.1" + private const val publicIpAddress = "1.1.1.1" } } diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreenTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreenTest.kt index 073c81b6f8..75d4f20f6e 100644 --- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreenTest.kt +++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreenTest.kt @@ -74,6 +74,7 @@ class VpnSettingsScreenTest { navigateToUdp2TcpSettings: () -> Unit = {}, onToggleAutoStartAndConnectOnBoot: (Boolean) -> Unit = {}, onSelectDeviceIpVersion: (Constraint<IpVersion>) -> Unit = {}, + onToggleIpv6Toggle: (Boolean) -> Unit = {}, ) { setContentWithTheme { VpnSettingsScreen( @@ -106,6 +107,7 @@ class VpnSettingsScreenTest { navigateToUdp2TcpSettings = navigateToUdp2TcpSettings, onToggleAutoStartAndConnectOnBoot = onToggleAutoStartAndConnectOnBoot, onSelectDeviceIpVersion = onSelectDeviceIpVersion, + onToggleIpv6Toggle = onToggleIpv6Toggle, ) } } @@ -154,9 +156,9 @@ class VpnSettingsScreenTest { isCustomDnsEnabled = true, customDnsItems = listOf( - CustomDnsItem(address = DUMMY_DNS_ADDRESS, false), - CustomDnsItem(address = DUMMY_DNS_ADDRESS_2, false), - CustomDnsItem(address = DUMMY_DNS_ADDRESS_3, false), + CustomDnsItem(address = DUMMY_DNS_ADDRESS, false, false), + CustomDnsItem(address = DUMMY_DNS_ADDRESS_2, false, false), + CustomDnsItem(address = DUMMY_DNS_ADDRESS_3, false, false), ), ) ) @@ -176,7 +178,8 @@ class VpnSettingsScreenTest { state = VpnSettingsUiState.createDefault( isCustomDnsEnabled = false, - customDnsItems = listOf(CustomDnsItem(address = DUMMY_DNS_ADDRESS, false)), + customDnsItems = + listOf(CustomDnsItem(address = DUMMY_DNS_ADDRESS, false, false)), ) ) onNodeWithTag(LAZY_LIST_VPN_SETTINGS_TEST_TAG) @@ -196,7 +199,13 @@ class VpnSettingsScreenTest { isCustomDnsEnabled = true, isLocalNetworkSharingEnabled = true, customDnsItems = - listOf(CustomDnsItem(address = DUMMY_DNS_ADDRESS, isLocal = true)), + listOf( + CustomDnsItem( + address = DUMMY_DNS_ADDRESS, + isLocal = true, + isIpv6 = false, + ) + ), ) ) @@ -213,7 +222,13 @@ class VpnSettingsScreenTest { VpnSettingsUiState.createDefault( isCustomDnsEnabled = true, customDnsItems = - listOf(CustomDnsItem(address = DUMMY_DNS_ADDRESS, isLocal = false)), + listOf( + CustomDnsItem( + address = DUMMY_DNS_ADDRESS, + isLocal = false, + isIpv6 = false, + ) + ), ) ) @@ -230,7 +245,13 @@ class VpnSettingsScreenTest { VpnSettingsUiState.createDefault( isCustomDnsEnabled = true, customDnsItems = - listOf(CustomDnsItem(address = DUMMY_DNS_ADDRESS, isLocal = false)), + listOf( + CustomDnsItem( + address = DUMMY_DNS_ADDRESS, + isLocal = false, + isIpv6 = false, + ) + ), ) ) @@ -247,7 +268,13 @@ class VpnSettingsScreenTest { VpnSettingsUiState.createDefault( isCustomDnsEnabled = true, customDnsItems = - listOf(CustomDnsItem(address = DUMMY_DNS_ADDRESS, isLocal = true)), + listOf( + CustomDnsItem( + address = DUMMY_DNS_ADDRESS, + isLocal = true, + isIpv6 = false, + ) + ), ) ) @@ -417,7 +444,7 @@ class VpnSettingsScreenTest { state = VpnSettingsUiState.createDefault( isCustomDnsEnabled = true, - customDnsItems = listOf(CustomDnsItem("1.1.1.1", false)), + customDnsItems = listOf(CustomDnsItem("1.1.1.1", false, false)), ), navigateToDns = mockedClickHandler, ) diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/DnsCell.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/DnsCell.kt index 8fe5f32b99..cc9ed30d9d 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/DnsCell.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/DnsCell.kt @@ -1,6 +1,7 @@ package net.mullvad.mullvadvpn.compose.cell import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.padding import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Error import androidx.compose.material.icons.rounded.Error @@ -9,18 +10,26 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp import net.mullvad.mullvadvpn.R import net.mullvad.mullvadvpn.lib.theme.AppTheme +import net.mullvad.mullvadvpn.lib.theme.Dimens +import net.mullvad.mullvadvpn.lib.theme.color.AlphaInvisible +import net.mullvad.mullvadvpn.lib.theme.color.AlphaVisible @Preview @Composable private fun PreviewDnsCell() { AppTheme { - DnsCell(address = "0.0.0.0", isUnreachableLocalDnsWarningVisible = true, onClick = {}) + DnsCell( + address = "0.0.0.0", + isUnreachableLocalDnsWarningVisible = true, + isUnreachableIpv6DnsWarningVisible = false, + onClick = {}, + ) } } @@ -28,22 +37,37 @@ private fun PreviewDnsCell() { fun DnsCell( address: String, isUnreachableLocalDnsWarningVisible: Boolean, + isUnreachableIpv6DnsWarningVisible: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier, ) { val titleModifier = Modifier - val startPadding = 54.dp + val startPadding = Dimens.cellStartPadding BaseCell( headlineContent = { DnsTitle(address = address, modifier = titleModifier) }, - bodyView = { - if (isUnreachableLocalDnsWarningVisible) { - Icon( - imageVector = Icons.Rounded.Error, - contentDescription = stringResource(id = R.string.confirm_local_dns), - tint = MaterialTheme.colorScheme.error, - ) - } + iconView = { + Icon( + modifier = + Modifier.padding(end = Dimens.verticalDividerPadding) + .alpha( + when { + isUnreachableLocalDnsWarningVisible || + isUnreachableIpv6DnsWarningVisible -> AlphaVisible + else -> AlphaInvisible + } + ), + imageVector = Icons.Rounded.Error, + contentDescription = + when { + isUnreachableLocalDnsWarningVisible -> + stringResource(id = R.string.confirm_local_dns) + isUnreachableIpv6DnsWarningVisible -> + stringResource(id = R.string.confirm_ipv6_dns) + else -> null + }, + tint = MaterialTheme.colorScheme.error, + ) }, onCellClicked = { onClick.invoke() }, background = MaterialTheme.colorScheme.surfaceContainerLow, diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/DnsDialog.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/DnsDialog.kt index 6f9c8126c0..4fee24a8f9 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/DnsDialog.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/DnsDialog.kt @@ -39,7 +39,7 @@ private fun PreviewDnsDialogEdit() { @Preview @Composable private fun PreviewDnsDialogEditAllowLanDisabled() { - AppTheme { DnsDialog(DnsDialogViewState("192.168.1.1", null, true, false, 0), {}, {}, {}, {}) } + AppTheme { DnsDialog(DnsDialogViewState("192.168.1.1", null, false, false, 0), {}, {}, {}, {}) } } data class DnsDialogNavArgs(val index: Int? = null, val initialValue: String? = null) @@ -94,15 +94,15 @@ fun DnsDialog( placeholderText = stringResource(R.string.custom_dns_hint), errorText = when { - state.validationError is ValidationError.DuplicateAddress -> { + state.validationError is ValidationError.DuplicateAddress -> stringResource(R.string.duplicate_address_warning) - } - state.isLocal && !state.isAllowLanEnabled -> { + // Ordering is important, as we consider the lan error to have higher + // priority than the ipv6 error + state.isLocal && !state.isAllowLanEnabled -> stringResource(id = R.string.confirm_local_dns) - } - else -> { - null - } + state.isIpv6 && !state.isIpv6Enabled -> + stringResource(id = R.string.confirm_ipv6_dns) + else -> null }, modifier = Modifier.fillMaxWidth(), ) diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/VpnSettingsUiStatePreviewParameterProvider.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/VpnSettingsUiStatePreviewParameterProvider.kt index 796965d856..55c8802c7f 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/VpnSettingsUiStatePreviewParameterProvider.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/VpnSettingsUiStatePreviewParameterProvider.kt @@ -21,7 +21,7 @@ class VpnSettingsUiStatePreviewParameterProvider : PreviewParameterProvider<VpnS mtu = Mtu(MTU), isLocalNetworkSharingEnabled = true, isCustomDnsEnabled = true, - customDnsItems = listOf(CustomDnsItem("0.0.0.0", false)), + customDnsItems = listOf(CustomDnsItem("0.0.0.0", false, false)), contentBlockersOptions = DefaultDnsOptions( blockAds = 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 a257341175..0504201d58 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 @@ -142,6 +142,7 @@ private fun PreviewVpnSettings( navigateToWireguardPortDialog = {}, navigateToServerIpOverrides = {}, onSelectDeviceIpVersion = {}, + onToggleIpv6Toggle = {}, ) } } @@ -271,6 +272,7 @@ fun VpnSettings( dropUnlessResumed { navigator.navigate(Udp2TcpSettingsDestination) }, onToggleAutoStartAndConnectOnBoot = vm::onToggleAutoStartAndConnectOnBoot, onSelectDeviceIpVersion = vm::onDeviceIpVersionSelected, + onToggleIpv6Toggle = vm::setIpv6Enabled, ) } @@ -308,9 +310,9 @@ fun VpnSettingsScreen( navigateToUdp2TcpSettings: () -> Unit, onToggleAutoStartAndConnectOnBoot: (Boolean) -> Unit, onSelectDeviceIpVersion: (ipVersion: Constraint<IpVersion>) -> Unit, + onToggleIpv6Toggle: (Boolean) -> Unit, ) { var expandContentBlockersState by rememberSaveable { mutableStateOf(false) } - val biggerPadding = 54.dp val topPadding = 6.dp ScaffoldWithMediumTopBar( @@ -467,6 +469,7 @@ fun VpnSettingsScreen( address = item.address, isUnreachableLocalDnsWarningVisible = item.isLocal && !state.isLocalNetworkSharingEnabled, + isUnreachableIpv6DnsWarningVisible = item.isIpv6 && !state.isIpv6Enabled, onClick = { navigateToDns(index, item.address) }, modifier = Modifier.animateItem(), ) @@ -484,7 +487,7 @@ fun VpnSettingsScreen( }, bodyView = {}, background = MaterialTheme.colorScheme.surfaceContainerLow, - startPadding = biggerPadding, + startPadding = Dimens.cellStartPaddingLarge, ) } } @@ -682,6 +685,16 @@ fun VpnSettingsScreen( } item { + HeaderSwitchComposeCell( + title = stringResource(R.string.enable_ipv6), + isToggled = state.isIpv6Enabled, + isEnabled = true, + onCellClicked = { newValue -> onToggleIpv6Toggle(newValue) }, + ) + Spacer(modifier = Modifier.height(Dimens.cellVerticalSpacing)) + } + + item { MtuComposeCell(mtuValue = state.mtu, onEditMtu = { navigateToMtuDialog(state.mtu) }) } item { MtuSubtitle(modifier = Modifier.testTag(LAZY_LIST_LAST_ITEM_TEST_TAG)) } 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 ec069001cc..3756d547f9 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 @@ -26,6 +26,7 @@ data class VpnSettingsUiState( val systemVpnSettingsAvailable: Boolean, val autoStartAndConnectOnBoot: Boolean, val deviceIpVersion: Constraint<IpVersion>, + val isIpv6Enabled: Boolean, ) { val isCustomWireguardPort = selectedWireguardPort is Constraint.Only && @@ -51,6 +52,7 @@ data class VpnSettingsUiState( systemVpnSettingsAvailable: Boolean = false, autoStartAndConnectOnBoot: Boolean = false, deviceIpVersion: Constraint<IpVersion> = Constraint.Any, + isIpv6Enabled: Boolean = true, ) = VpnSettingsUiState( mtu, @@ -68,6 +70,7 @@ data class VpnSettingsUiState( systemVpnSettingsAvailable, autoStartAndConnectOnBoot, deviceIpVersion, + isIpv6Enabled, ) } } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/SettingsRepository.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/SettingsRepository.kt index e6b03ee599..e5453fe57b 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/SettingsRepository.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/SettingsRepository.kt @@ -74,4 +74,6 @@ class SettingsRepository( suspend fun setDaitaEnabled(enabled: Boolean) = managementService.setDaitaEnabled(enabled) suspend fun setDaitaDirectOnly(enabled: Boolean) = managementService.setDaitaDirectOnly(enabled) + + suspend fun setIpv6Enabled(enabled: Boolean) = managementService.setIpv6Enabled(enabled) } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/DnsDialogViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/DnsDialogViewModel.kt index ab067e8145..c79682039b 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/DnsDialogViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/DnsDialogViewModel.kt @@ -7,6 +7,7 @@ import arrow.core.Either import arrow.core.raise.either import arrow.core.raise.ensure import com.ramcosta.composedestinations.generated.destinations.DnsDestination +import java.net.Inet6Address import java.net.InetAddress import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers @@ -34,13 +35,22 @@ sealed interface DnsDialogSideEffect { data class DnsDialogViewState( val input: String, val validationError: ValidationError?, - val isLocal: Boolean, val isAllowLanEnabled: Boolean, + val isIpv6Enabled: Boolean, val index: Int?, ) { val isNewEntry = index == null + val isIpv6: Boolean = input.isIpv6() + val isLocal: Boolean = input.isLocalAddress() fun isValid() = validationError == null + + private fun String.isLocalAddress(): Boolean = + isValid() && InetAddress.getByName(this).isLocalAddress() + + private fun String.isIpv6(): Boolean = isValid() && InetAddress.getByName(this) is Inet6Address + + private fun InetAddress.isLocalAddress(): Boolean = isLinkLocalAddress || isSiteLocalAddress } sealed class ValidationError { @@ -67,12 +77,25 @@ class DnsDialogViewModel( input, currentIndex, settings -> - createViewState(settings.addresses(), currentIndex, settings.allowLan, input) + DnsDialogViewState( + input = input, + validationError = + input.validateDnsEntry(currentIndex, settings.addresses()).leftOrNull(), + isAllowLanEnabled = settings.allowLan, + isIpv6Enabled = settings.tunnelOptions.genericOptions.enableIpv6, + index = currentIndex, + ) } .stateIn( viewModelScope, SharingStarted.Lazily, - createViewState(emptyList(), null, false, _ipAddressInput.value), + DnsDialogViewState( + input = _ipAddressInput.value, + validationError = null, + isAllowLanEnabled = false, + isIpv6Enabled = false, + index = null, + ), ) private val _uiSideEffect = Channel<DnsDialogSideEffect>() @@ -82,20 +105,6 @@ class DnsDialogViewModel( viewModelScope.launch { settings.emit(repository.settingsUpdates.filterNotNull().first()) } } - private fun createViewState( - customDnsList: List<InetAddress>, - currentIndex: Int?, - isAllowLanEnabled: Boolean, - input: String, - ): DnsDialogViewState = - DnsDialogViewState( - input, - input.validateDnsEntry(currentIndex, customDnsList).leftOrNull(), - input.isLocalAddress(), - isAllowLanEnabled = isAllowLanEnabled, - currentIndex, - ) - private fun String.validateDnsEntry( index: Int?, dnsList: List<InetAddress>, @@ -147,14 +156,6 @@ class DnsDialogViewModel( return inetAddressValidator.isValid(this) } - private fun String.isLocalAddress(): Boolean { - return isValidIp() && InetAddress.getByName(this).isLocalAddress() - } - - private fun InetAddress.isLocalAddress(): Boolean { - return isLinkLocalAddress || isSiteLocalAddress - } - private fun InetAddress.isDuplicateDnsEntry( currentIndex: Int? = null, dnsList: List<InetAddress>, diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModel.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModel.kt index 2273ada5e0..aa00ebdca3 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModel.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModel.kt @@ -3,6 +3,7 @@ package net.mullvad.mullvadvpn.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import co.touchlab.kermit.Logger +import java.net.Inet6Address import java.net.InetAddress import java.net.UnknownHostException import kotlinx.coroutines.CoroutineDispatcher @@ -85,6 +86,7 @@ class VpnSettingsViewModel( systemVpnSettingsAvailable = systemVpnSettingsUseCase(), autoStartAndConnectOnBoot = autoStartAndConnectOnBoot, deviceIpVersion = settings?.getDeviceIpVersion() ?: Constraint.Any, + ipv6Enabled = settings?.tunnelOptions?.genericOptions?.enableIpv6 == true, ) } .stateIn( @@ -253,6 +255,14 @@ class VpnSettingsViewModel( } } + fun setIpv6Enabled(enable: Boolean) { + viewModelScope.launch(dispatcher) { + repository.setIpv6Enabled(enable).onLeft { + _uiSideEffect.send(VpnSettingsSideEffect.ShowToast.GenericError) + } + } + } + private fun updateDefaultDnsOptionsViaRepository(contentBlockersOption: DefaultDnsOptions) = viewModelScope.launch(dispatcher) { repository @@ -275,7 +285,11 @@ class VpnSettingsViewModel( private fun List<InetAddress>.asStringAddressList(): List<CustomDnsItem> { return map { - CustomDnsItem(address = it.hostAddress ?: EMPTY_STRING, isLocal = it.isLocalAddress()) + CustomDnsItem( + address = it.hostAddress ?: EMPTY_STRING, + isLocal = it.isLocalAddress(), + isIpv6 = it is Inet6Address, + ) } } diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelState.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelState.kt index 6e91294257..f4f1a8dbcd 100644 --- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelState.kt +++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelState.kt @@ -26,6 +26,7 @@ data class VpnSettingsViewModelState( val systemVpnSettingsAvailable: Boolean, val autoStartAndConnectOnBoot: Boolean, val deviceIpVersion: Constraint<IpVersion>, + val ipv6Enabled: Boolean, ) { val isCustomWireguardPort = selectedWireguardPort is Constraint.Only && @@ -48,6 +49,7 @@ data class VpnSettingsViewModelState( systemVpnSettingsAvailable, autoStartAndConnectOnBoot, deviceIpVersion, + ipv6Enabled, ) companion object { @@ -68,16 +70,17 @@ data class VpnSettingsViewModelState( systemVpnSettingsAvailable = false, autoStartAndConnectOnBoot = false, deviceIpVersion = Constraint.Any, + ipv6Enabled = false, ) } } -data class CustomDnsItem(val address: String, val isLocal: Boolean) { +data class CustomDnsItem(val address: String, val isLocal: Boolean, val isIpv6: Boolean) { companion object { private const val EMPTY_STRING = "" fun default(): CustomDnsItem { - return CustomDnsItem(address = EMPTY_STRING, isLocal = false) + return CustomDnsItem(address = EMPTY_STRING, isLocal = false, isIpv6 = false) } } } diff --git a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelTest.kt b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelTest.kt index 9d7ed39622..c91d6d9a20 100644 --- a/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelTest.kt +++ b/android/app/src/test/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelTest.kt @@ -174,6 +174,7 @@ class VpnSettingsViewModelTest { daitaSettings = DaitaSettings(enabled = false, directOnly = false), ), dnsOptions = mockk(relaxed = true), + genericOptions = mockk(relaxed = true), ) // Act, Assert |
