diff options
| author | Albin <albin@mullvad.net> | 2023-07-25 11:55:44 +0200 |
|---|---|---|
| committer | Albin <albin@mullvad.net> | 2023-07-25 14:08:48 +0200 |
| commit | de4e1d85837d3790eccb6ac0d3a7ed172e68b36c (patch) | |
| tree | d567dcd3b61ca659cbd63d09a200a32f4b207237 /android/lib/talpid/src | |
| parent | 38248fa9a0cbeb1414d1db07719c40b7b0992e93 (diff) | |
| download | mullvadvpn-de4e1d85837d3790eccb6ac0d3a7ed172e68b36c.tar.xz mullvadvpn-de4e1d85837d3790eccb6ac0d3a7ed172e68b36c.zip | |
Move talpid classes to talpid module
Diffstat (limited to 'android/lib/talpid/src')
18 files changed, 470 insertions, 0 deletions
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 new file mode 100644 index 0000000000..de56ebb878 --- /dev/null +++ b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/ConnectivityListener.kt @@ -0,0 +1,69 @@ +package net.mullvad.talpid + +import android.content.Context +import android.net.ConnectivityManager +import android.net.ConnectivityManager.NetworkCallback +import android.net.Network +import android.net.NetworkCapabilities +import android.net.NetworkRequest +import kotlin.properties.Delegates.observable +import net.mullvad.talpid.util.EventNotifier + +class ConnectivityListener { + private val availableNetworks = HashSet<Network>() + + private val callback = + object : NetworkCallback() { + override fun onAvailable(network: Network) { + availableNetworks.add(network) + isConnected = true + } + + override fun onLost(network: Network) { + availableNetworks.remove(network) + isConnected = !availableNetworks.isEmpty() + } + } + + private lateinit var connectivityManager: ConnectivityManager + + val connectivityNotifier = EventNotifier(false) + + var isConnected by + observable(false) { _, oldValue, newValue -> + if (newValue != oldValue) { + if (senderAddress != 0L) { + notifyConnectivityChange(newValue, senderAddress) + } + + connectivityNotifier.notify(newValue) + } + } + + var senderAddress = 0L + + fun register(context: Context) { + 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) + } + + private fun finalize() { + destroySender(senderAddress) + senderAddress = 0L + } + + private external fun notifyConnectivityChange(isConnected: Boolean, senderAddress: Long) + private external fun destroySender(senderAddress: Long) +} diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/CreateTunResult.kt b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/CreateTunResult.kt new file mode 100644 index 0000000000..33f62026d6 --- /dev/null +++ b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/CreateTunResult.kt @@ -0,0 +1,23 @@ +package net.mullvad.talpid + +import java.net.InetAddress + +sealed class CreateTunResult { + open val isOpen + get() = false + + class Success(val tunFd: Int) : CreateTunResult() { + override val isOpen + get() = true + } + + class InvalidDnsServers(val addresses: ArrayList<InetAddress>, val tunFd: Int) : + CreateTunResult() { + override val isOpen + get() = true + } + + object PermissionDenied : CreateTunResult() + + object TunnelDeviceError : CreateTunResult() +} 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 new file mode 100644 index 0000000000..a94bfac428 --- /dev/null +++ b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/TalpidVpnService.kt @@ -0,0 +1,150 @@ +package net.mullvad.talpid + +import android.net.VpnService +import android.os.ParcelFileDescriptor +import java.net.Inet4Address +import java.net.Inet6Address +import java.net.InetAddress +import kotlin.properties.Delegates.observable +import net.mullvad.talpid.tun_provider.TunConfig +import net.mullvad.talpid.util.TalpidSdkUtils.setMeteredIfSupported + +open class TalpidVpnService : VpnService() { + private var activeTunStatus by + observable<CreateTunResult?>(null) { _, oldTunStatus, _ -> + val oldTunFd = + when (oldTunStatus) { + is CreateTunResult.Success -> oldTunStatus.tunFd + is CreateTunResult.InvalidDnsServers -> oldTunStatus.tunFd + else -> null + } + + if (oldTunFd != null) { + ParcelFileDescriptor.adoptFd(oldTunFd).close() + } + } + + private val tunIsOpen + get() = activeTunStatus?.isOpen ?: false + + private var currentTunConfig = defaultTunConfig() + private var tunIsStale = false + + protected var disallowedApps: List<String>? = null + + val connectivityListener = ConnectivityListener() + + override fun onCreate() { + connectivityListener.register(this) + } + + override fun onDestroy() { + connectivityListener.unregister() + } + + fun getTun(config: TunConfig): CreateTunResult { + synchronized(this) { + val tunStatus = activeTunStatus + + if (config == currentTunConfig && tunIsOpen && !tunIsStale) { + return tunStatus!! + } else { + val newTunStatus = createTun(config) + + currentTunConfig = config + activeTunStatus = newTunStatus + tunIsStale = false + + return newTunStatus + } + } + } + + fun createTun() { + synchronized(this) { activeTunStatus = createTun(currentTunConfig) } + } + + fun recreateTunIfOpen(config: TunConfig) { + synchronized(this) { + if (tunIsOpen) { + currentTunConfig = config + activeTunStatus = createTun(config) + } + } + } + + fun closeTun() { + synchronized(this) { activeTunStatus = null } + } + + fun markTunAsStale() { + synchronized(this) { tunIsStale = true } + } + + private fun createTun(config: TunConfig): CreateTunResult { + if (VpnService.prepare(this) != null) { + // VPN permission wasn't granted + return CreateTunResult.PermissionDenied + } + + var invalidDnsServerAddresses = ArrayList<InetAddress>() + + val builder = + Builder().apply { + for (address in config.addresses) { + addAddress(address, prefixForAddress(address)) + } + + for (dnsServer in config.dnsServers) { + try { + addDnsServer(dnsServer) + } catch (exception: IllegalArgumentException) { + invalidDnsServerAddresses.add(dnsServer) + } + } + + for (route in config.routes) { + addRoute(route.address, route.prefixLength.toInt()) + } + + disallowedApps?.let { apps -> + for (app in apps) { + addDisallowedApplication(app) + } + } + setMtu(config.mtu) + setBlocking(false) + setMeteredIfSupported(false) + } + + val vpnInterface = builder.establish() + val tunFd = vpnInterface?.detachFd() + + if (tunFd == null) { + return CreateTunResult.TunnelDeviceError + } + + waitForTunnelUp(tunFd, config.routes.any { route -> route.isIpv6 }) + + if (!invalidDnsServerAddresses.isEmpty()) { + return CreateTunResult.InvalidDnsServers(invalidDnsServerAddresses, tunFd) + } + + return CreateTunResult.Success(tunFd) + } + + fun bypass(socket: Int): Boolean { + return protect(socket) + } + + private fun prefixForAddress(address: InetAddress): Int { + when (address) { + is Inet4Address -> return 32 + is Inet6Address -> return 128 + else -> throw RuntimeException("Invalid IP address (not IPv4 nor IPv6)") + } + } + + private external fun defaultTunConfig(): TunConfig + private external fun waitForTunnelUp(tunFd: Int, isIpv6Enabled: Boolean) +} diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/net/Endpoint.kt b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/net/Endpoint.kt new file mode 100644 index 0000000000..8937bd0122 --- /dev/null +++ b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/net/Endpoint.kt @@ -0,0 +1,8 @@ +package net.mullvad.talpid.net + +import android.os.Parcelable +import java.net.InetSocketAddress +import kotlinx.parcelize.Parcelize + +@Parcelize +data class Endpoint(val address: InetSocketAddress, val protocol: TransportProtocol) : Parcelable diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/net/ObfuscationEndpoint.kt b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/net/ObfuscationEndpoint.kt new file mode 100644 index 0000000000..9ec96b1494 --- /dev/null +++ b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/net/ObfuscationEndpoint.kt @@ -0,0 +1,8 @@ +package net.mullvad.talpid.net + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +data class ObfuscationEndpoint(val endpoint: Endpoint, val obfuscationType: ObfuscationType) : + Parcelable diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/net/ObfuscationType.kt b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/net/ObfuscationType.kt new file mode 100644 index 0000000000..72409d9026 --- /dev/null +++ b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/net/ObfuscationType.kt @@ -0,0 +1,9 @@ +package net.mullvad.talpid.net + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +enum class ObfuscationType : Parcelable { + Udp2Tcp +} diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/net/TransportProtocol.kt b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/net/TransportProtocol.kt new file mode 100644 index 0000000000..89fdedaba1 --- /dev/null +++ b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/net/TransportProtocol.kt @@ -0,0 +1,10 @@ +package net.mullvad.talpid.net + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +enum class TransportProtocol : Parcelable { + Tcp, + Udp +} diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/net/TunnelEndpoint.kt b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/net/TunnelEndpoint.kt new file mode 100644 index 0000000000..9c45833eb2 --- /dev/null +++ b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/net/TunnelEndpoint.kt @@ -0,0 +1,11 @@ +package net.mullvad.talpid.net + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +data class TunnelEndpoint( + val endpoint: Endpoint, + val quantumResistant: Boolean, + val obfuscation: ObfuscationEndpoint? +) : Parcelable diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tun_provider/InetNetwork.kt b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tun_provider/InetNetwork.kt new file mode 100644 index 0000000000..a8490b48bf --- /dev/null +++ b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tun_provider/InetNetwork.kt @@ -0,0 +1,8 @@ +package net.mullvad.talpid.tun_provider + +import java.net.Inet6Address +import java.net.InetAddress + +data class InetNetwork(val address: InetAddress, val prefixLength: Short) { + val isIpv6 = address is Inet6Address +} diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tun_provider/TunConfig.kt b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tun_provider/TunConfig.kt new file mode 100644 index 0000000000..7efd3f7763 --- /dev/null +++ b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tun_provider/TunConfig.kt @@ -0,0 +1,10 @@ +package net.mullvad.talpid.tun_provider + +import java.net.InetAddress + +data class TunConfig( + val addresses: ArrayList<InetAddress>, + val dnsServers: ArrayList<InetAddress>, + val routes: ArrayList<InetNetwork>, + val mtu: Int +) diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tunnel/ActionAfterDisconnect.kt b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tunnel/ActionAfterDisconnect.kt new file mode 100644 index 0000000000..a62abaacd0 --- /dev/null +++ b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tunnel/ActionAfterDisconnect.kt @@ -0,0 +1,11 @@ +package net.mullvad.talpid.tunnel + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +enum class ActionAfterDisconnect : Parcelable { + Nothing, + Block, + Reconnect +} diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tunnel/ErrorState.kt b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tunnel/ErrorState.kt new file mode 100644 index 0000000000..070d190beb --- /dev/null +++ b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tunnel/ErrorState.kt @@ -0,0 +1,6 @@ +package net.mullvad.talpid.tunnel + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize data class ErrorState(val cause: ErrorStateCause, val isBlocking: Boolean) : Parcelable diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tunnel/ErrorStateCause.kt b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tunnel/ErrorStateCause.kt new file mode 100644 index 0000000000..5096e5c693 --- /dev/null +++ b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tunnel/ErrorStateCause.kt @@ -0,0 +1,32 @@ +package net.mullvad.talpid.tunnel + +import android.os.Parcelable +import java.net.InetAddress +import kotlinx.parcelize.Parcelize + +private const val AUTH_FAILED_REASON_EXPIRED_ACCOUNT = "[EXPIRED_ACCOUNT]" + +sealed class ErrorStateCause : Parcelable { + @Parcelize + class AuthFailed(private val reason: String?) : ErrorStateCause() { + fun isCausedByExpiredAccount(): Boolean { + return reason == AUTH_FAILED_REASON_EXPIRED_ACCOUNT + } + } + + @Parcelize object Ipv6Unavailable : ErrorStateCause() + + @Parcelize object SetFirewallPolicyError : ErrorStateCause() + + @Parcelize object SetDnsError : ErrorStateCause() + + @Parcelize class InvalidDnsServers(val addresses: ArrayList<InetAddress>) : ErrorStateCause() + + @Parcelize object StartTunnelError : ErrorStateCause() + + @Parcelize class TunnelParameterError(val error: ParameterGenerationError) : ErrorStateCause() + + @Parcelize object IsOffline : ErrorStateCause() + + @Parcelize object VpnPermissionDenied : ErrorStateCause() +} diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tunnel/ParameterGenerationError.kt b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tunnel/ParameterGenerationError.kt new file mode 100644 index 0000000000..b1504c676f --- /dev/null +++ b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/tunnel/ParameterGenerationError.kt @@ -0,0 +1,8 @@ +package net.mullvad.talpid.tunnel + +enum class ParameterGenerationError { + NoMatchingRelay, + NoMatchingBridgeRelay, + NoWireguardKey, + CustomTunnelHostResultionError +} diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/util/EventNotifier.kt b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/util/EventNotifier.kt new file mode 100644 index 0000000000..148b56eb45 --- /dev/null +++ b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/util/EventNotifier.kt @@ -0,0 +1,76 @@ +package net.mullvad.talpid.util + +import kotlin.properties.Delegates.observable + +// Manages listeners interested in receiving events of type T +// +// The listeners subscribe using an ID object. This ID is used later on for unsubscribing. The only +// requirement is that the object uses the default implementation of the `hashCode` and `equals` +// methods inherited from `Any` (or `Object` in Java). +// +// If the ID object class (or any of its super-classes) overrides `hashCode` or `equals`, +// unsubscribe might not work correctly. +class EventNotifier<T>(private val initialValue: T) { + private val listeners = LinkedHashMap<Any, (T) -> Unit>() + + var latestEvent = initialValue + private set + + fun notify(event: T) { + synchronized(this) { + latestEvent = event + + for (listener in listeners.values) { + listener(event) + } + } + } + + fun notifyIfChanged(event: T) { + synchronized(this) { + if (latestEvent != event) { + notify(event) + } + } + } + + fun subscribe(id: Any, listener: (T) -> Unit) { + subscribe(id, true, listener) + } + + fun subscribe(id: Any, startWithLatestEvent: Boolean, listener: (T) -> Unit) { + synchronized(this) { + listeners.put(id, listener) + if (startWithLatestEvent) listener(latestEvent) + } + } + + fun hasListeners(): Boolean { + synchronized(this) { + return !listeners.isEmpty() + } + } + + fun unsubscribe(id: Any) { + synchronized(this) { listeners.remove(id) } + } + + fun unsubscribeAll() { + synchronized(this) { listeners.clear() } + } + + fun notifiable() = observable(latestEvent) { _, _, newValue -> notify(newValue) } +} + +fun <T> autoSubscribable(id: Any, fallback: T, listener: (T) -> Unit) = + observable<EventNotifier<T>?>(null) { _, old, new -> + if (old != new) { + old?.unsubscribe(id) + + if (new == null) { + listener.invoke(fallback) + } else { + new.subscribe(id, listener) + } + } + } diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/util/EventNotifierExtensions.kt b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/util/EventNotifierExtensions.kt new file mode 100644 index 0000000000..add362fcb1 --- /dev/null +++ b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/util/EventNotifierExtensions.kt @@ -0,0 +1,9 @@ +package net.mullvad.talpid.util + +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.callbackFlow + +fun <T> EventNotifier<T>.callbackFlowFromSubscription(id: Any) = callbackFlow { + this@callbackFlowFromSubscription.subscribe(id) { this.trySend(it) } + awaitClose { this@callbackFlowFromSubscription.unsubscribe(id) } +} diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/util/InetAddressExt.kt b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/util/InetAddressExt.kt new file mode 100644 index 0000000000..d310deb884 --- /dev/null +++ b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/util/InetAddressExt.kt @@ -0,0 +1,10 @@ +package net.mullvad.talpid.util + +import java.net.InetAddress + +fun InetAddress.addressString(): String { + val hostNameAndAddress = this.toString().split('/', limit = 2) + val address = hostNameAndAddress[1] + + return address +} diff --git a/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/util/TalpidSdkUtils.kt b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/util/TalpidSdkUtils.kt new file mode 100644 index 0000000000..9eaecb2e8a --- /dev/null +++ b/android/lib/talpid/src/main/kotlin/net/mullvad/talpid/util/TalpidSdkUtils.kt @@ -0,0 +1,12 @@ +package net.mullvad.talpid.util + +import android.net.VpnService +import android.os.Build + +object TalpidSdkUtils { + fun VpnService.Builder.setMeteredIfSupported(isMetered: Boolean) { + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) { + this.setMetered(isMetered) + } + } +} |
