Skip to content

Commit 16196ea

Browse files
committedMar 14, 2025
Make it possible to disable IPv6 in the tunnel
1 parent 39859b5 commit 16196ea

File tree

16 files changed

+100
-121
lines changed

16 files changed

+100
-121
lines changed
 

‎android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/cell/DnsCell.kt

+14-1
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,20 @@ import net.mullvad.mullvadvpn.lib.theme.AppTheme
2020
@Composable
2121
private fun PreviewDnsCell() {
2222
AppTheme {
23-
DnsCell(address = "0.0.0.0", isUnreachableLocalDnsWarningVisible = true, onClick = {})
23+
DnsCell(
24+
address = "0.0.0.0",
25+
isUnreachableLocalDnsWarningVisible = true,
26+
isUnreachableIpv6DnsWarningVisible = false,
27+
onClick = {},
28+
)
2429
}
2530
}
2631

2732
@Composable
2833
fun DnsCell(
2934
address: String,
3035
isUnreachableLocalDnsWarningVisible: Boolean,
36+
isUnreachableIpv6DnsWarningVisible: Boolean,
3137
onClick: () -> Unit,
3238
modifier: Modifier = Modifier,
3339
) {
@@ -44,6 +50,13 @@ fun DnsCell(
4450
tint = MaterialTheme.colorScheme.error,
4551
)
4652
}
53+
if (isUnreachableIpv6DnsWarningVisible) {
54+
Icon(
55+
imageVector = Icons.Rounded.Error,
56+
contentDescription = stringResource(id = R.string.confirm_ipv6_dns),
57+
tint = MaterialTheme.colorScheme.error,
58+
)
59+
}
4760
},
4861
onCellClicked = { onClick.invoke() },
4962
background = MaterialTheme.colorScheme.surfaceContainerLow,

‎android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/dialog/DnsDialog.kt

+30-3
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,43 @@ import org.koin.androidx.compose.koinViewModel
2727
@Preview
2828
@Composable
2929
private fun PreviewDnsDialogNew() {
30-
AppTheme { DnsDialog(DnsDialogViewState("1.1.1.1", null, false, false, null), {}, {}, {}, {}) }
30+
AppTheme {
31+
DnsDialog(
32+
DnsDialogViewState("1.1.1.1", null, false, false, false, false, null),
33+
{},
34+
{},
35+
{},
36+
{},
37+
)
38+
}
3139
}
3240

3341
@Preview
3442
@Composable
3543
private fun PreviewDnsDialogEdit() {
36-
AppTheme { DnsDialog(DnsDialogViewState("1.1.1.1", null, false, false, 0), {}, {}, {}, {}) }
44+
AppTheme {
45+
DnsDialog(
46+
DnsDialogViewState("1.1.1.1", null, false, false, false, false, 0),
47+
{},
48+
{},
49+
{},
50+
{},
51+
)
52+
}
3753
}
3854

3955
@Preview
4056
@Composable
4157
private fun PreviewDnsDialogEditAllowLanDisabled() {
42-
AppTheme { DnsDialog(DnsDialogViewState("192.168.1.1", null, true, false, 0), {}, {}, {}, {}) }
58+
AppTheme {
59+
DnsDialog(
60+
DnsDialogViewState("192.168.1.1", null, true, false, false, false, 0),
61+
{},
62+
{},
63+
{},
64+
{},
65+
)
66+
}
4367
}
4468

4569
data class DnsDialogNavArgs(val index: Int? = null, val initialValue: String? = null)
@@ -100,6 +124,9 @@ fun DnsDialog(
100124
state.isLocal && !state.isAllowLanEnabled -> {
101125
stringResource(id = R.string.confirm_local_dns)
102126
}
127+
state.isIpv6 && !state.isIpv6Enabled -> {
128+
stringResource(id = R.string.confirm_ipv6_dns)
129+
}
103130
else -> {
104131
null
105132
}

‎android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/preview/VpnSettingsUiStatePreviewParameterProvider.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class VpnSettingsUiStatePreviewParameterProvider : PreviewParameterProvider<VpnS
2121
mtu = Mtu(MTU),
2222
isLocalNetworkSharingEnabled = true,
2323
isCustomDnsEnabled = true,
24-
customDnsItems = listOf(CustomDnsItem("0.0.0.0", false)),
24+
customDnsItems = listOf(CustomDnsItem("0.0.0.0", false, false)),
2525
contentBlockersOptions =
2626
DefaultDnsOptions(
2727
blockAds = true,

‎android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/screen/VpnSettingsScreen.kt

+2-13
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,6 @@ private fun PreviewVpnSettings(
143143
navigateToServerIpOverrides = {},
144144
onSelectDeviceIpVersion = {},
145145
onToggleIPv6Toggle = {},
146-
onToggleRouteIpv6Traffic = {},
147146
)
148147
}
149148
}
@@ -274,7 +273,6 @@ fun VpnSettings(
274273
onToggleAutoStartAndConnectOnBoot = vm::onToggleAutoStartAndConnectOnBoot,
275274
onSelectDeviceIpVersion = vm::onDeviceIpVersionSelected,
276275
onToggleIPv6Toggle = vm::setIpV6Enabled,
277-
onToggleRouteIpv6Traffic = vm::onToggleRouteIpv6Traffic,
278276
)
279277
}
280278

@@ -313,7 +311,6 @@ fun VpnSettingsScreen(
313311
onToggleAutoStartAndConnectOnBoot: (Boolean) -> Unit,
314312
onSelectDeviceIpVersion: (ipVersion: Constraint<IpVersion>) -> Unit,
315313
onToggleIPv6Toggle: (Boolean) -> Unit,
316-
onToggleRouteIpv6Traffic: (Boolean) -> Unit,
317314
) {
318315
var expandContentBlockersState by rememberSaveable { mutableStateOf(false) }
319316
val biggerPadding = 54.dp
@@ -473,6 +470,8 @@ fun VpnSettingsScreen(
473470
address = item.address,
474471
isUnreachableLocalDnsWarningVisible =
475472
item.isLocal && !state.isLocalNetworkSharingEnabled,
473+
isUnreachableIpv6DnsWarningVisible =
474+
item.isIpv6 && !state.isIPv6Enabled,
476475
onClick = { navigateToDns(index, item.address) },
477476
modifier = Modifier.animateItem(),
478477
)
@@ -702,16 +701,6 @@ fun VpnSettingsScreen(
702701
Spacer(modifier = Modifier.height(Dimens.cellVerticalSpacing))
703702
}
704703

705-
item {
706-
HeaderSwitchComposeCell(
707-
title = "Route IPv6 traffic",
708-
isToggled = state.routeIpv6Traffic || state.isIPv6Enabled,
709-
isEnabled = !state.isIPv6Enabled,
710-
onCellClicked = { newValue -> onToggleRouteIpv6Traffic(newValue) },
711-
)
712-
Spacer(modifier = Modifier.height(Dimens.cellVerticalSpacing))
713-
}
714-
715704
item { ServerIpOverrides(navigateToServerIpOverrides) }
716705
}
717706
}

‎android/app/src/main/kotlin/net/mullvad/mullvadvpn/compose/state/VpnSettingsUiState.kt

-3
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ data class VpnSettingsUiState(
2727
val autoStartAndConnectOnBoot: Boolean,
2828
val deviceIpVersion: Constraint<IpVersion>,
2929
val isIPv6Enabled: Boolean,
30-
val routeIpv6Traffic: Boolean,
3130
) {
3231
val isCustomWireguardPort =
3332
selectedWireguardPort is Constraint.Only &&
@@ -54,7 +53,6 @@ data class VpnSettingsUiState(
5453
autoStartAndConnectOnBoot: Boolean = false,
5554
deviceIpVersion: Constraint<IpVersion> = Constraint.Any,
5655
isIPv6Enabled: Boolean = true,
57-
routeIpv6Traffic: Boolean = true,
5856
) =
5957
VpnSettingsUiState(
6058
mtu,
@@ -73,7 +71,6 @@ data class VpnSettingsUiState(
7371
autoStartAndConnectOnBoot,
7472
deviceIpVersion,
7573
isIPv6Enabled,
76-
routeIpv6Traffic,
7774
)
7875
}
7976
}

‎android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ val uiModule = module {
235235
viewModel { SettingsViewModel(get(), get(), get(), get(), IS_PLAY_BUILD) }
236236
viewModel { SplashViewModel(get(), get(), get(), get()) }
237237
viewModel { VoucherDialogViewModel(get()) }
238-
viewModel { VpnSettingsViewModel(get(), get(), get(), get(), get(), get(), get()) }
238+
viewModel { VpnSettingsViewModel(get(), get(), get(), get(), get()) }
239239
viewModel { WelcomeViewModel(get(), get(), get(), get(), isPlayBuild = IS_PLAY_BUILD) }
240240
viewModel { ReportProblemViewModel(get(), get()) }
241241
viewModel { ViewLogsViewModel(get()) }

‎android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/MainActivity.kt

+1-2
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ import net.mullvad.mullvadvpn.lib.endpoint.ApiEndpointFromIntentHolder
3333
import net.mullvad.mullvadvpn.lib.endpoint.getApiEndpointConfigurationExtras
3434
import net.mullvad.mullvadvpn.lib.model.PrepareError
3535
import net.mullvad.mullvadvpn.lib.model.Prepared
36-
import net.mullvad.mullvadvpn.lib.model.modelModule
3736
import net.mullvad.mullvadvpn.lib.theme.AppTheme
3837
import net.mullvad.mullvadvpn.repository.SplashCompleteRepository
3938
import net.mullvad.mullvadvpn.repository.UserPreferencesRepository
@@ -66,7 +65,7 @@ class MainActivity : ComponentActivity(), AndroidScopeComponent {
6665
private var isReadyNextDraw: Boolean = false
6766

6867
override fun onCreate(savedInstanceState: Bundle?) {
69-
loadKoinModules(listOf(uiModule, paymentModule, modelModule))
68+
loadKoinModules(listOf(uiModule, paymentModule))
7069

7170
lifecycle.addObserver(mullvadAppViewModel)
7271

‎android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/DnsDialogViewModel.kt

+28-6
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import arrow.core.Either
77
import arrow.core.raise.either
88
import arrow.core.raise.ensure
99
import com.ramcosta.composedestinations.generated.destinations.DnsDestination
10+
import java.net.Inet6Address
1011
import java.net.InetAddress
1112
import kotlinx.coroutines.CoroutineDispatcher
1213
import kotlinx.coroutines.Dispatchers
@@ -36,6 +37,8 @@ data class DnsDialogViewState(
3637
val validationError: ValidationError?,
3738
val isLocal: Boolean,
3839
val isAllowLanEnabled: Boolean,
40+
val isIpv6: Boolean,
41+
val isIpv6Enabled: Boolean,
3942
val index: Int?,
4043
) {
4144
val isNewEntry = index == null
@@ -67,12 +70,24 @@ class DnsDialogViewModel(
6770
input,
6871
currentIndex,
6972
settings ->
70-
createViewState(settings.addresses(), currentIndex, settings.allowLan, input)
73+
createViewState(
74+
settings.addresses(),
75+
currentIndex,
76+
settings.allowLan,
77+
settings.tunnelOptions.genericOptions.enableIpv6,
78+
input,
79+
)
7180
}
7281
.stateIn(
7382
viewModelScope,
7483
SharingStarted.Lazily,
75-
createViewState(emptyList(), null, false, _ipAddressInput.value),
84+
createViewState(
85+
customDnsList = emptyList(),
86+
currentIndex = null,
87+
isAllowLanEnabled = false,
88+
isIpv6Enabled = false,
89+
input = _ipAddressInput.value,
90+
),
7691
)
7792

7893
private val _uiSideEffect = Channel<DnsDialogSideEffect>()
@@ -86,14 +101,17 @@ class DnsDialogViewModel(
86101
customDnsList: List<InetAddress>,
87102
currentIndex: Int?,
88103
isAllowLanEnabled: Boolean,
104+
isIpv6Enabled: Boolean,
89105
input: String,
90106
): DnsDialogViewState =
91107
DnsDialogViewState(
92-
input,
93-
input.validateDnsEntry(currentIndex, customDnsList).leftOrNull(),
94-
input.isLocalAddress(),
108+
input = input,
109+
validationError = input.validateDnsEntry(currentIndex, customDnsList).leftOrNull(),
110+
isLocal = input.isLocalAddress(),
111+
isIpv6 = input.isIpv6(),
95112
isAllowLanEnabled = isAllowLanEnabled,
96-
currentIndex,
113+
isIpv6Enabled = isIpv6Enabled,
114+
index = currentIndex,
97115
)
98116

99117
private fun String.validateDnsEntry(
@@ -151,6 +169,10 @@ class DnsDialogViewModel(
151169
return isValidIp() && InetAddress.getByName(this).isLocalAddress()
152170
}
153171

172+
private fun String.isIpv6(): Boolean {
173+
return isValidIp() && InetAddress.getByName(this) is Inet6Address
174+
}
175+
154176
private fun InetAddress.isLocalAddress(): Boolean {
155177
return isLinkLocalAddress || isSiteLocalAddress
156178
}

‎android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModel.kt

+7-18
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ package net.mullvad.mullvadvpn.viewmodel
33
import androidx.lifecycle.ViewModel
44
import androidx.lifecycle.viewModelScope
55
import co.touchlab.kermit.Logger
6+
import java.net.Inet6Address
67
import java.net.InetAddress
78
import java.net.UnknownHostException
89
import kotlinx.coroutines.CoroutineDispatcher
910
import kotlinx.coroutines.Dispatchers
1011
import kotlinx.coroutines.channels.Channel
11-
import kotlinx.coroutines.delay
1212
import kotlinx.coroutines.flow.MutableStateFlow
1313
import kotlinx.coroutines.flow.SharingStarted
1414
import kotlinx.coroutines.flow.combine
@@ -29,8 +29,6 @@ import net.mullvad.mullvadvpn.lib.model.ObfuscationMode
2929
import net.mullvad.mullvadvpn.lib.model.Port
3030
import net.mullvad.mullvadvpn.lib.model.QuantumResistantState
3131
import net.mullvad.mullvadvpn.lib.model.Settings
32-
import net.mullvad.mullvadvpn.lib.model.TunnelPreferencesRepository
33-
import net.mullvad.mullvadvpn.lib.shared.ConnectionProxy
3432
import net.mullvad.mullvadvpn.repository.AutoStartAndConnectOnBootRepository
3533
import net.mullvad.mullvadvpn.repository.RelayListRepository
3634
import net.mullvad.mullvadvpn.repository.SettingsRepository
@@ -54,8 +52,6 @@ class VpnSettingsViewModel(
5452
private val systemVpnSettingsUseCase: SystemVpnSettingsAvailableUseCase,
5553
private val autoStartAndConnectOnBootRepository: AutoStartAndConnectOnBootRepository,
5654
private val wireguardConstraintsRepository: WireguardConstraintsRepository,
57-
private val tunnelPreferencesRepository: TunnelPreferencesRepository,
58-
private val connectionProxy: ConnectionProxy,
5955
private val dispatcher: CoroutineDispatcher = Dispatchers.IO,
6056
) : ViewModel() {
6157

@@ -70,8 +66,7 @@ class VpnSettingsViewModel(
7066
relayListRepository.portRanges,
7167
customPort,
7268
autoStartAndConnectOnBootRepository.autoStartAndConnectOnBoot,
73-
tunnelPreferencesRepository.preferencesFlow,
74-
) { settings, portRanges, customWgPort, autoStartAndConnectOnBoot, preferences ->
69+
) { settings, portRanges, customWgPort, autoStartAndConnectOnBoot ->
7570
VpnSettingsViewModelState(
7671
mtuValue = settings?.tunnelOptions?.wireguard?.mtu,
7772
isLocalNetworkSharingEnabled = settings?.allowLan == true,
@@ -92,7 +87,6 @@ class VpnSettingsViewModel(
9287
autoStartAndConnectOnBoot = autoStartAndConnectOnBoot,
9388
deviceIpVersion = settings?.getDeviceIpVersion() ?: Constraint.Any,
9489
ipv6Enabled = settings?.tunnelOptions?.genericOptions?.enableIpv6 == true,
95-
routeIpv6 = preferences.routeIpV6,
9690
)
9791
}
9892
.stateIn(
@@ -269,15 +263,6 @@ class VpnSettingsViewModel(
269263
}
270264
}
271265

272-
fun onToggleRouteIpv6Traffic(enable: Boolean) {
273-
viewModelScope.launch(dispatcher) {
274-
tunnelPreferencesRepository.setRouteIpv6(enable)
275-
connectionProxy.disconnect()
276-
delay(1000L)
277-
connectionProxy.connect()
278-
}
279-
}
280-
281266
private fun updateDefaultDnsOptionsViaRepository(contentBlockersOption: DefaultDnsOptions) =
282267
viewModelScope.launch(dispatcher) {
283268
repository
@@ -300,7 +285,11 @@ class VpnSettingsViewModel(
300285

301286
private fun List<InetAddress>.asStringAddressList(): List<CustomDnsItem> {
302287
return map {
303-
CustomDnsItem(address = it.hostAddress ?: EMPTY_STRING, isLocal = it.isLocalAddress())
288+
CustomDnsItem(
289+
address = it.hostAddress ?: EMPTY_STRING,
290+
isLocal = it.isLocalAddress(),
291+
isIpv6 = it is Inet6Address,
292+
)
304293
}
305294
}
306295

‎android/app/src/main/kotlin/net/mullvad/mullvadvpn/viewmodel/VpnSettingsViewModelState.kt

+2-5
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ data class VpnSettingsViewModelState(
2727
val autoStartAndConnectOnBoot: Boolean,
2828
val deviceIpVersion: Constraint<IpVersion>,
2929
val ipv6Enabled: Boolean,
30-
val routeIpv6: Boolean,
3130
) {
3231
val isCustomWireguardPort =
3332
selectedWireguardPort is Constraint.Only &&
@@ -51,7 +50,6 @@ data class VpnSettingsViewModelState(
5150
autoStartAndConnectOnBoot,
5251
deviceIpVersion,
5352
ipv6Enabled,
54-
routeIpv6,
5553
)
5654

5755
companion object {
@@ -73,17 +71,16 @@ data class VpnSettingsViewModelState(
7371
autoStartAndConnectOnBoot = false,
7472
deviceIpVersion = Constraint.Any,
7573
ipv6Enabled = false,
76-
routeIpv6 = false,
7774
)
7875
}
7976
}
8077

81-
data class CustomDnsItem(val address: String, val isLocal: Boolean) {
78+
data class CustomDnsItem(val address: String, val isLocal: Boolean, val isIpv6: Boolean) {
8279
companion object {
8380
private const val EMPTY_STRING = ""
8481

8582
fun default(): CustomDnsItem {
86-
return CustomDnsItem(address = EMPTY_STRING, isLocal = false)
83+
return CustomDnsItem(address = EMPTY_STRING, isLocal = false, isIpv6 = false)
8784
}
8885
}
8986
}

0 commit comments

Comments
 (0)
Please sign in to comment.