summaryrefslogtreecommitdiffhomepage
path: root/android/lib/common/src
diff options
context:
space:
mode:
authorDavid Göransson <david.goransson@mullvad.net>2025-01-13 11:51:09 +0100
committerDavid Göransson <david.goransson@mullvad.net>2025-02-06 11:02:59 +0100
commit341c10ba38752bc36151b8998064e706f70d9ea6 (patch)
treeafb60c53e267eda0b033f346b64afd9035d7495a /android/lib/common/src
parent612aad8d8d2ae779a4e5e01e85b2848b4fc7de3c (diff)
downloadmullvadvpn-341c10ba38752bc36151b8998064e706f70d9ea6.tar.xz
mullvadvpn-341c10ba38752bc36151b8998064e706f70d9ea6.zip
Replace old waitForTunnelUp function
After invoking VpnService.establish() we will get a tunnel file descriptor that corresponds to the interface that was created. However, this has no guarantee of the routing table beeing up to date, and we might thus send traffic outside the tunnel. Previously this was done through looking at the tunFd to see that traffic is sent to verify that the routing table has changed. If no traffic is seen some traffic is induced to a random IP address to ensure traffic can be seen. This new implementation is slower but won't risk sending UDP traffic to a random public address at the internet.
Diffstat (limited to 'android/lib/common/src')
-rw-r--r--android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/VpnServiceUtils.kt46
1 files changed, 43 insertions, 3 deletions
diff --git a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/VpnServiceUtils.kt b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/VpnServiceUtils.kt
index 59833cb396..06c862936b 100644
--- a/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/VpnServiceUtils.kt
+++ b/android/lib/common/src/main/kotlin/net/mullvad/mullvadvpn/lib/common/util/VpnServiceUtils.kt
@@ -2,10 +2,14 @@ package net.mullvad.mullvadvpn.lib.common.util
import android.content.Context
import android.content.Intent
+import android.net.VpnService
import android.net.VpnService.prepare
+import android.os.ParcelFileDescriptor
import arrow.core.Either
-import arrow.core.flatten
+import arrow.core.flatMap
import arrow.core.left
+import arrow.core.raise.either
+import arrow.core.raise.ensureNotNull
import arrow.core.right
import co.touchlab.kermit.Logger
import net.mullvad.mullvadvpn.lib.common.util.SdkUtils.getInstalledPackagesList
@@ -13,6 +17,8 @@ import net.mullvad.mullvadvpn.lib.model.PrepareError
import net.mullvad.mullvadvpn.lib.model.Prepared
/**
+ * Prepare to establish a VPN connection safely.
+ *
* Invoking VpnService.prepare() can result in 3 out comes:
* 1. IllegalStateException - There is a legacy VPN profile marked as always on
* 2. Intent
@@ -34,7 +40,7 @@ fun Context.prepareVpnSafe(): Either<PrepareError, Prepared> =
else -> throw it
}
}
- .map { intent ->
+ .flatMap { intent ->
if (intent == null) {
Prepared.right()
} else {
@@ -46,7 +52,6 @@ fun Context.prepareVpnSafe(): Either<PrepareError, Prepared> =
}
}
}
- .flatten()
fun Context.getAlwaysOnVpnAppName(): String? {
return resolveAlwaysOnVpnPackageName()
@@ -59,3 +64,38 @@ fun Context.getAlwaysOnVpnAppName(): String? {
?.loadLabel(packageManager)
?.toString()
}
+
+/**
+ * Establish a VPN connection safely.
+ *
+ * This function wraps the [VpnService.Builder.establish] function and catches any exceptions that
+ * may be thrown and type them to a more specific error.
+ *
+ * @return [ParcelFileDescriptor] if successful, [EstablishError] otherwise
+ */
+fun VpnService.Builder.establishSafe(): Either<EstablishError, ParcelFileDescriptor> = either {
+ val vpnInterfaceFd =
+ Either.catch { establish() }
+ .mapLeft {
+ when (it) {
+ is IllegalStateException -> EstablishError.ParameterNotApplied(it)
+ is IllegalArgumentException -> EstablishError.ParameterNotAccepted(it)
+ else -> EstablishError.UnknownError(it)
+ }
+ }
+ .bind()
+
+ ensureNotNull(vpnInterfaceFd) { EstablishError.NullVpnInterface }
+
+ vpnInterfaceFd
+}
+
+sealed interface EstablishError {
+ data class ParameterNotApplied(val exception: IllegalStateException) : EstablishError
+
+ data class ParameterNotAccepted(val exception: IllegalArgumentException) : EstablishError
+
+ data object NullVpnInterface : EstablishError
+
+ data class UnknownError(val error: Throwable) : EstablishError
+}