diff options
| author | David Lönnhager <david.l@mullvad.net> | 2024-11-22 13:39:41 +0100 |
|---|---|---|
| committer | David Lönnhager <david.l@mullvad.net> | 2024-11-22 13:39:41 +0100 |
| commit | f629200a57ca2f78d238ff982af77467181b9b25 (patch) | |
| tree | 849ee45d7fb961a0d495eac00f35908a074df92d /android | |
| parent | 3a872179b34a6fdda2660cd5f14e35f769f913a3 (diff) | |
| parent | ad2fc60c64c78ad78bd9f995a9f5e72f978ac1b9 (diff) | |
| download | mullvadvpn-f629200a57ca2f78d238ff982af77467181b9b25.tar.xz mullvadvpn-f629200a57ca2f78d238ff982af77467181b9b25.zip | |
Merge branch 'android-tokio-shutdown-timeout'
Diffstat (limited to 'android')
6 files changed, 199 insertions, 58 deletions
diff --git a/android/CHANGELOG.md b/android/CHANGELOG.md index ac65d38672..d97bf45ed7 100644 --- a/android/CHANGELOG.md +++ b/android/CHANGELOG.md @@ -32,6 +32,7 @@ Line wrap the file at 100 chars. Th ### Fixed - Fix a bug where the Android account expiry notifications would not be updated if the app was running in the background for a long time. +- Fix ANR due to the tokio runtime being blocked by `getaddrinfo` when dropped. ## [android/2024.8] - 2024-11-01 diff --git a/android/lib/talpid/build.gradle.kts b/android/lib/talpid/build.gradle.kts index a5cd613de1..c53c2add28 100644 --- a/android/lib/talpid/build.gradle.kts +++ b/android/lib/talpid/build.gradle.kts @@ -31,6 +31,7 @@ android { dependencies { implementation(projects.lib.model) + implementation(libs.androidx.ktx) implementation(libs.androidx.lifecycle.service) implementation(libs.kermit) implementation(libs.kotlin.stdlib) 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 edeec9a6fe..4cb67f9945 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 @@ -1,70 +1,79 @@ package net.mullvad.talpid -import android.content.Context import android.net.ConnectivityManager -import android.net.ConnectivityManager.NetworkCallback +import android.net.LinkProperties import android.net.Network import android.net.NetworkCapabilities import android.net.NetworkRequest -import kotlin.properties.Delegates.observable +import java.net.InetAddress +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.flow.scan +import kotlinx.coroutines.flow.stateIn +import net.mullvad.talpid.util.NetworkEvent +import net.mullvad.talpid.util.defaultNetworkFlow +import net.mullvad.talpid.util.networkFlow -class ConnectivityListener { - private val availableNetworks = HashSet<Network>() +class ConnectivityListener(val connectivityManager: ConnectivityManager) { + private lateinit var _isConnected: StateFlow<Boolean> + // Used by JNI + val isConnected + get() = _isConnected.value - private val callback = - object : NetworkCallback() { - override fun onAvailable(network: Network) { - availableNetworks.add(network) - isConnected = true - } + private lateinit var _currentDnsServers: StateFlow<List<InetAddress>> + // Used by JNI + val currentDnsServers + get() = ArrayList(_currentDnsServers.value) - override fun onLost(network: Network) { - availableNetworks.remove(network) - isConnected = availableNetworks.isNotEmpty() - } - } + fun register(scope: CoroutineScope) { + _currentDnsServers = + dnsServerChanges().stateIn(scope, SharingStarted.Eagerly, currentDnsServers()) - private lateinit var connectivityManager: ConnectivityManager + _isConnected = + hasInternetCapability() + .onEach { notifyConnectivityChange(it) } + .stateIn(scope, SharingStarted.Eagerly, false) + } - // Used by JNI - var isConnected by - observable(false) { _, oldValue, newValue -> - if (newValue != oldValue) { - if (senderAddress != 0L) { - notifyConnectivityChange(newValue, senderAddress) - } - } - } + private fun dnsServerChanges(): Flow<List<InetAddress>> = + connectivityManager + .defaultNetworkFlow() + .filterIsInstance<NetworkEvent.LinkPropertiesChanged>() + .map { it.linkProperties.dnsServersWithoutFallback() } + + private fun currentDnsServers(): List<InetAddress> = + connectivityManager + .getLinkProperties(connectivityManager.activeNetwork) + ?.dnsServersWithoutFallback() ?: emptyList() - var senderAddress = 0L + private fun LinkProperties.dnsServersWithoutFallback(): List<InetAddress> = + dnsServers.filter { it.hostAddress != TalpidVpnService.FALLBACK_DUMMY_DNS_SERVER } - fun register(context: Context) { + private fun hasInternetCapability(): Flow<Boolean> { val request = NetworkRequest.Builder() .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN) .build() - connectivityManager = - context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager - - connectivityManager.registerNetworkCallback(request, callback) - } - - fun unregister() { - connectivityManager.unregisterNetworkCallback(callback) - } - - // DROID-1401 - // This function has never been used and should most likely be merged into unregister(), - // along with ensuring that the lifecycle of it is correct. - @Suppress("UnusedPrivateMember") - private fun finalize() { - destroySender(senderAddress) - senderAddress = 0L + return connectivityManager + .networkFlow(request) + .scan(setOf<Network>()) { networks, event -> + when (event) { + is NetworkEvent.Available -> networks + event.network + is NetworkEvent.Lost -> networks - event.network + else -> networks + } + } + .map { it.isNotEmpty() } + .distinctUntilChanged() } - private external fun notifyConnectivityChange(isConnected: Boolean, senderAddress: Long) - - private external fun destroySender(senderAddress: Long) + private external fun notifyConnectivityChange(isConnected: Boolean) } diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/TalpidVpnService.kt b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/TalpidVpnService.kt index 9470c88318..dc1f8d23ca 100644 --- a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/TalpidVpnService.kt +++ b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/TalpidVpnService.kt @@ -1,7 +1,10 @@ package net.mullvad.talpid +import android.net.ConnectivityManager import android.os.ParcelFileDescriptor import androidx.annotation.CallSuper +import androidx.core.content.getSystemService +import androidx.lifecycle.lifecycleScope import co.touchlab.kermit.Logger import java.net.Inet4Address import java.net.Inet6Address @@ -29,18 +32,13 @@ open class TalpidVpnService : LifecycleVpnService() { private var currentTunConfig: TunConfig? = null // Used by JNI - val connectivityListener = ConnectivityListener() + lateinit var connectivityListener: ConnectivityListener @CallSuper override fun onCreate() { super.onCreate() - connectivityListener.register(this) - } - - @CallSuper - override fun onDestroy() { - super.onDestroy() - connectivityListener.unregister() + connectivityListener = ConnectivityListener(getSystemService<ConnectivityManager>()!!) + connectivityListener.register(lifecycleScope) } fun openTun(config: TunConfig): CreateTunResult { @@ -161,7 +159,7 @@ open class TalpidVpnService : LifecycleVpnService() { private external fun waitForTunnelUp(tunFd: Int, isIpv6Enabled: Boolean) companion object { - private const val FALLBACK_DUMMY_DNS_SERVER = "192.0.2.1" + const val FALLBACK_DUMMY_DNS_SERVER = "192.0.2.1" private const val IPV4_PREFIX_LENGTH = 32 private const val IPV6_PREFIX_LENGTH = 128 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 new file mode 100644 index 0000000000..daf155c6e8 --- /dev/null +++ b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/util/ConnectivityManagerUtil.kt @@ -0,0 +1,132 @@ +package net.mullvad.talpid.util + +import android.net.ConnectivityManager +import android.net.ConnectivityManager.NetworkCallback +import android.net.LinkProperties +import android.net.Network +import android.net.NetworkCapabilities +import android.net.NetworkRequest +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.channels.trySendBlocking +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow + +fun ConnectivityManager.defaultNetworkFlow(): Flow<NetworkEvent> = + callbackFlow<NetworkEvent> { + val callback = + object : NetworkCallback() { + override fun onLinkPropertiesChanged( + network: Network, + linkProperties: LinkProperties, + ) { + super.onLinkPropertiesChanged(network, linkProperties) + trySendBlocking(NetworkEvent.LinkPropertiesChanged(network, linkProperties)) + } + + override fun onAvailable(network: Network) { + super.onAvailable(network) + trySendBlocking(NetworkEvent.Available(network)) + } + + override fun onCapabilitiesChanged( + network: Network, + networkCapabilities: NetworkCapabilities, + ) { + super.onCapabilitiesChanged(network, networkCapabilities) + trySendBlocking(NetworkEvent.CapabilitiesChanged(network, networkCapabilities)) + } + + override fun onBlockedStatusChanged(network: Network, blocked: Boolean) { + super.onBlockedStatusChanged(network, blocked) + trySendBlocking(NetworkEvent.BlockedStatusChanged(network, blocked)) + } + + override fun onLosing(network: Network, maxMsToLive: Int) { + super.onLosing(network, maxMsToLive) + trySendBlocking(NetworkEvent.Losing(network, maxMsToLive)) + } + + override fun onLost(network: Network) { + super.onLost(network) + trySendBlocking(NetworkEvent.Lost(network)) + } + + override fun onUnavailable() { + super.onUnavailable() + trySendBlocking(NetworkEvent.Unavailable) + } + } + registerDefaultNetworkCallback(callback) + + awaitClose { unregisterNetworkCallback(callback) } + } + +fun ConnectivityManager.networkFlow(networkRequest: NetworkRequest): Flow<NetworkEvent> = + callbackFlow<NetworkEvent> { + val callback = + object : NetworkCallback() { + override fun onLinkPropertiesChanged( + network: Network, + linkProperties: LinkProperties, + ) { + super.onLinkPropertiesChanged(network, linkProperties) + trySendBlocking(NetworkEvent.LinkPropertiesChanged(network, linkProperties)) + } + + override fun onAvailable(network: Network) { + super.onAvailable(network) + trySendBlocking(NetworkEvent.Available(network)) + } + + override fun onCapabilitiesChanged( + network: Network, + networkCapabilities: NetworkCapabilities, + ) { + super.onCapabilitiesChanged(network, networkCapabilities) + trySendBlocking(NetworkEvent.CapabilitiesChanged(network, networkCapabilities)) + } + + override fun onBlockedStatusChanged(network: Network, blocked: Boolean) { + super.onBlockedStatusChanged(network, blocked) + trySendBlocking(NetworkEvent.BlockedStatusChanged(network, blocked)) + } + + override fun onLosing(network: Network, maxMsToLive: Int) { + super.onLosing(network, maxMsToLive) + trySendBlocking(NetworkEvent.Losing(network, maxMsToLive)) + } + + override fun onLost(network: Network) { + super.onLost(network) + trySendBlocking(NetworkEvent.Lost(network)) + } + + override fun onUnavailable() { + super.onUnavailable() + trySendBlocking(NetworkEvent.Unavailable) + } + } + registerNetworkCallback(networkRequest, callback) + + awaitClose { unregisterNetworkCallback(callback) } + } + +sealed interface NetworkEvent { + data class Available(val network: Network) : NetworkEvent + + data object Unavailable : NetworkEvent + + data class LinkPropertiesChanged(val network: Network, val linkProperties: LinkProperties) : + NetworkEvent + + data class CapabilitiesChanged( + val network: Network, + val networkCapabilities: NetworkCapabilities, + ) : NetworkEvent + + data class BlockedStatusChanged(val network: Network, val blocked: Boolean) : NetworkEvent + + data class Losing(val network: Network, val maxMsToLive: Int) : NetworkEvent + + data class Lost(val network: Network) : NetworkEvent +} diff --git a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt index ebdcbec780..55aa416e53 100644 --- a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt +++ b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadVpnService.kt @@ -203,6 +203,7 @@ class MullvadVpnService : TalpidVpnService() { } override fun onDestroy() { + super.onDestroy() Logger.i("MullvadVpnService: onDestroy") // Shutting down the daemon gracefully managementService.stop() @@ -214,7 +215,6 @@ class MullvadVpnService : TalpidVpnService() { managementService.enterIdle() Logger.i("Shutdown complete") - super.onDestroy() } // If an intent is from the system it is because of the OS starting/stopping the VPN. |
