diff options
Diffstat (limited to 'android')
7 files changed, 55 insertions, 29 deletions
diff --git a/android/CHANGELOG.md b/android/CHANGELOG.md index 2bbb8e5f91..9d7c4963a8 100644 --- a/android/CHANGELOG.md +++ b/android/CHANGELOG.md @@ -37,7 +37,7 @@ Line wrap the file at 100 chars. Th - Remove Google's resolvers from encrypted DNS proxy. ### Fixed -- Will no longer try to connect over IPv6 if IPv6 is not available. +- Will no longer try to connect using an IP version if that IP version is not available. ## [android/2025.1-beta1] - 2025-03-05 diff --git a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/talpid/util/ConnectivityManagerUtilKtTest.kt b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/talpid/util/ConnectivityManagerUtilKtTest.kt index 354b6f585d..39df5de05b 100644 --- a/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/talpid/util/ConnectivityManagerUtilKtTest.kt +++ b/android/app/src/androidTest/kotlin/net/mullvad/mullvadvpn/talpid/util/ConnectivityManagerUtilKtTest.kt @@ -20,6 +20,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.test.runTest import net.mullvad.talpid.model.Connectivity +import net.mullvad.talpid.model.IpAvailability import net.mullvad.talpid.util.NetworkEvent import net.mullvad.talpid.util.UnderlyingConnectivityStatusResolver import net.mullvad.talpid.util.defaultNetworkEvents @@ -53,7 +54,7 @@ class ConnectivityManagerUtilKtTest { connectivityManager.hasInternetConnectivity(mockResolver).test { // Since initial state and listener both return `true` within debounce we only see one // event - assertEquals(Connectivity.Status(true, true), awaitItem()) + assertEquals(Connectivity.Online(IpAvailability.Ipv4AndIpv6), awaitItem()) expectNoEvents() } } @@ -66,7 +67,7 @@ class ConnectivityManagerUtilKtTest { connectivityManager.hasInternetConnectivity(mockResolver).test { // Initially offline and no network events, so we should get a single `false` event - assertEquals(Connectivity.Status(false, false), awaitItem()) + assertEquals(Connectivity.Offline, awaitItem()) expectNoEvents() } } @@ -88,8 +89,8 @@ class ConnectivityManagerUtilKtTest { } connectivityManager.hasInternetConnectivity(mockResolver).test { - assertEquals(Connectivity.Status(false, false), awaitItem()) - assertEquals(Connectivity.Status(true, true), awaitItem()) + assertEquals(Connectivity.Offline, awaitItem()) + assertEquals(Connectivity.Online(IpAvailability.Ipv4AndIpv6), awaitItem()) expectNoEvents() } } @@ -113,8 +114,8 @@ class ConnectivityManagerUtilKtTest { } connectivityManager.hasInternetConnectivity(mockResolver).test { - assertEquals(Connectivity.Status(true, true), awaitItem()) - assertEquals(Connectivity.Status(false, false), awaitItem()) + assertEquals(Connectivity.Online(IpAvailability.Ipv4AndIpv6), awaitItem()) + assertEquals(Connectivity.Offline, awaitItem()) expectNoEvents() } } @@ -150,7 +151,7 @@ class ConnectivityManagerUtilKtTest { connectivityManager.hasInternetConnectivity(mockResolver).test { // We should always only see us being online - assertEquals(Connectivity.Status(ipv4 = true, ipv6 = false), awaitItem()) + assertEquals(Connectivity.Online(IpAvailability.Ipv4), awaitItem()) expectNoEvents() } } @@ -184,7 +185,7 @@ class ConnectivityManagerUtilKtTest { connectivityManager.hasInternetConnectivity(mockResolver).test { // We should always only see us being online, small offline state is caught by debounce - assertEquals(Connectivity.Status(ipv4 = true, ipv6 = false), awaitItem()) + assertEquals(Connectivity.Online(IpAvailability.Ipv4), awaitItem()) expectNoEvents() } } @@ -218,11 +219,11 @@ class ConnectivityManagerUtilKtTest { connectivityManager.hasInternetConnectivity(mockResolver).test { // Wifi is online - assertEquals(Connectivity.Status(false, true), awaitItem()) + assertEquals(Connectivity.Online(IpAvailability.Ipv6), awaitItem()) // We didn't get any network within debounce time, so we are offline - assertEquals(Connectivity.Status(false, false), awaitItem()) + assertEquals(Connectivity.Offline, awaitItem()) // Cellular network is online - assertEquals(Connectivity.Status(false, true), awaitItem()) + assertEquals(Connectivity.Online(IpAvailability.Ipv6), awaitItem()) expectNoEvents() } } @@ -250,9 +251,9 @@ class ConnectivityManagerUtilKtTest { connectivityManager.hasInternetConnectivity(mockResolver).test { // Ipv6 network is online - assertEquals(Connectivity.Status(false, true), awaitItem()) + assertEquals(Connectivity.Online(IpAvailability.Ipv6), awaitItem()) // Ipv4 network is online - assertEquals(Connectivity.Status(true, false), awaitItem()) + assertEquals(Connectivity.Online(IpAvailability.Ipv4), awaitItem()) expectNoEvents() } } @@ -265,7 +266,8 @@ class ConnectivityManagerUtilKtTest { every { capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) } returns false val mockResolver = mockk<UnderlyingConnectivityStatusResolver>() - every { mockResolver.currentStatus() } returns Connectivity.Status(true, true) + every { mockResolver.currentStatus() } returns + Connectivity.Online(IpAvailability.Ipv4AndIpv6) every { connectivityManager.defaultNetworkEvents() } returns callbackFlow { @@ -276,7 +278,7 @@ class ConnectivityManagerUtilKtTest { connectivityManager.hasInternetConnectivity(mockResolver).test { // Network is online - assertEquals(Connectivity.Status(true, true), awaitItem()) + assertEquals(Connectivity.Online(IpAvailability.Ipv4AndIpv6), awaitItem()) } verify(exactly = 1) { mockResolver.currentStatus() } diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/ConnectivityListener.kt b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/ConnectivityListener.kt index ede883a837..460dac8f68 100644 --- a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/ConnectivityListener.kt +++ b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/ConnectivityListener.kt @@ -66,7 +66,7 @@ class ConnectivityListener( _isConnected = connectivityManager .hasInternetConnectivity(resolver) - .onEach { notifyConnectivityChange(it.ipv4, it.ipv6) } + .onEach { notifyConnectivityChange(it) } .stateIn( scope + Dispatchers.IO, SharingStarted.Eagerly, @@ -99,7 +99,7 @@ class ConnectivityListener( linkProperties?.dnsServersWithoutFallback(), ) - private external fun notifyConnectivityChange(isIPv4: Boolean, isIPv6: Boolean) + private external fun notifyConnectivityChange(connectivity: Connectivity) private external fun notifyDefaultNetworkChange(networkState: NetworkState?) } diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/model/Connectivity.kt b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/model/Connectivity.kt index b87eaaacc8..46195636bc 100644 --- a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/model/Connectivity.kt +++ b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/model/Connectivity.kt @@ -1,8 +1,30 @@ package net.mullvad.talpid.model +import android.net.LinkAddress +import java.net.Inet4Address +import java.net.Inet6Address + sealed class Connectivity { - data class Status(val ipv4: Boolean, val ipv6: Boolean) : Connectivity() + data class Online(val ipAvailability: IpAvailability) : Connectivity() + + data object Offline : Connectivity() // Required by jni data object PresumeOnline : Connectivity() + + companion object { + fun fromIpAvailability(ipv4: Boolean, ipv6: Boolean) = + when { + ipv4 && ipv6 -> Online(IpAvailability.Ipv4AndIpv6) + ipv4 -> Online(IpAvailability.Ipv4) + ipv6 -> Online(IpAvailability.Ipv6) + else -> Offline + } + + fun fromLinkAddresses(linkAddresses: List<LinkAddress>): Connectivity { + val ipv4 = linkAddresses.any { it.address is Inet4Address } + val ipv6 = linkAddresses.any { it.address is Inet6Address } + return fromIpAvailability(ipv4, ipv6) + } + } } diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/model/IpAvailability.kt b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/model/IpAvailability.kt new file mode 100644 index 0000000000..916f9d0a88 --- /dev/null +++ b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/model/IpAvailability.kt @@ -0,0 +1,7 @@ +package net.mullvad.talpid.model + +enum class IpAvailability { + Ipv4, + Ipv6, + Ipv4AndIpv6, +} diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/util/ConnectivityManagerUtil.kt b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/util/ConnectivityManagerUtil.kt index 7a0208eaa1..5409bc2f99 100644 --- a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/util/ConnectivityManagerUtil.kt +++ b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/util/ConnectivityManagerUtil.kt @@ -6,8 +6,6 @@ import android.net.LinkProperties import android.net.Network import android.net.NetworkCapabilities import co.touchlab.kermit.Logger -import java.net.Inet4Address -import java.net.Inet6Address import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.channels.awaitClose @@ -137,7 +135,7 @@ internal fun ConnectivityManager.activeRawNetworkState(): RawNetworkState? = @OptIn(FlowPreview::class) fun ConnectivityManager.hasInternetConnectivity( resolver: UnderlyingConnectivityStatusResolver -): Flow<Connectivity.Status> = +): Flow<Connectivity> = this.defaultRawNetworkStateFlow() .debounce(CONNECTIVITY_DEBOUNCE) .map { resolveConnectivityStatus(it, resolver) } @@ -146,7 +144,7 @@ fun ConnectivityManager.hasInternetConnectivity( internal fun resolveConnectivityStatus( currentRawNetworkState: RawNetworkState?, resolver: UnderlyingConnectivityStatusResolver, -): Connectivity.Status = +): Connectivity = if (currentRawNetworkState.isVpn()) { // If the default network is a VPN we need to use a socket to check // the underlying network @@ -158,10 +156,7 @@ internal fun resolveConnectivityStatus( } private fun RawNetworkState?.toConnectivityStatus() = - Connectivity.Status( - ipv4 = this?.linkProperties?.linkAddresses?.any { it.address is Inet4Address } == true, - ipv6 = this?.linkProperties?.linkAddresses?.any { it.address is Inet6Address } == true, - ) + Connectivity.fromLinkAddresses(this?.linkProperties?.linkAddresses ?: emptyList()) private fun RawNetworkState?.isVpn(): Boolean = this?.networkCapabilities?.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) == false diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/util/UnderlyingConnectivityStatusResolver.kt b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/util/UnderlyingConnectivityStatusResolver.kt index 0024f63357..1a5cb33be4 100644 --- a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/util/UnderlyingConnectivityStatusResolver.kt +++ b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/util/UnderlyingConnectivityStatusResolver.kt @@ -14,8 +14,8 @@ import net.mullvad.talpid.model.Connectivity class UnderlyingConnectivityStatusResolver( private val protect: (socket: DatagramSocket) -> Boolean ) { - fun currentStatus(): Connectivity.Status = - Connectivity.Status(ipv4 = hasIpv4(), ipv6 = hasIpv6()) + fun currentStatus(): Connectivity = + Connectivity.fromIpAvailability(ipv4 = hasIpv4(), ipv6 = hasIpv6()) private fun hasIpv4(): Boolean = hasIpVersion(Inet4Address.getByName(PUBLIC_IPV4_ADDRESS), protect) |
