diff options
| author | Albin <albin@mullvad.net> | 2022-09-14 16:29:55 +0200 |
|---|---|---|
| committer | Albin <albin@mullvad.net> | 2022-09-14 16:31:54 +0200 |
| commit | 2657f4e5214c5607fb4495f806f0abdc7b6b4e05 (patch) | |
| tree | 4f550e24ce20870ab58520d0d5161066e1cc3914 /android/app/src | |
| parent | 4ae656001172d7e00c4c0f6f943ef42d2047a1c2 (diff) | |
| download | mullvadvpn-2657f4e5214c5607fb4495f806f0abdc7b6b4e05.tar.xz mullvadvpn-2657f4e5214c5607fb4495f806f0abdc7b6b4e05.zip | |
Revert "Merge branch 'migrate-android-talpid-to-dedicated-subproject'"
This reverts commit 0c44c211bdfc0756ad556835d5a988799ee6dfbc, reversing
changes made to 49c3a16d927dd9f47a4b8d3780a25a7c5716bc6a.
Diffstat (limited to 'android/app/src')
16 files changed, 463 insertions, 0 deletions
diff --git a/android/app/src/main/kotlin/net/mullvad/talpid/ConnectivityListener.kt b/android/app/src/main/kotlin/net/mullvad/talpid/ConnectivityListener.kt new file mode 100644 index 0000000000..51ed63bd0a --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/talpid/ConnectivityListener.kt @@ -0,0 +1,66 @@ +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/app/src/main/kotlin/net/mullvad/talpid/CreateTunResult.kt b/android/app/src/main/kotlin/net/mullvad/talpid/CreateTunResult.kt new file mode 100644 index 0000000000..150382bb1a --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/talpid/CreateTunResult.kt @@ -0,0 +1,25 @@ +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/app/src/main/kotlin/net/mullvad/talpid/TalpidVpnService.kt b/android/app/src/main/kotlin/net/mullvad/talpid/TalpidVpnService.kt new file mode 100644 index 0000000000..7a9160c684 --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/talpid/TalpidVpnService.kt @@ -0,0 +1,157 @@ +package net.mullvad.talpid + +import android.net.VpnService +import android.os.Build +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 + +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) + } + } + + if (Build.VERSION.SDK_INT >= 29) { + setMetered(false) + } + + setMtu(config.mtu) + setBlocking(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/app/src/main/kotlin/net/mullvad/talpid/net/Endpoint.kt b/android/app/src/main/kotlin/net/mullvad/talpid/net/Endpoint.kt new file mode 100644 index 0000000000..8937bd0122 --- /dev/null +++ b/android/app/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/app/src/main/kotlin/net/mullvad/talpid/net/TransportProtocol.kt b/android/app/src/main/kotlin/net/mullvad/talpid/net/TransportProtocol.kt new file mode 100644 index 0000000000..5efb1bcb1c --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/talpid/net/TransportProtocol.kt @@ -0,0 +1,9 @@ +package net.mullvad.talpid.net + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +enum class TransportProtocol : Parcelable { + Tcp, Udp +} diff --git a/android/app/src/main/kotlin/net/mullvad/talpid/net/TunnelEndpoint.kt b/android/app/src/main/kotlin/net/mullvad/talpid/net/TunnelEndpoint.kt new file mode 100644 index 0000000000..5c081b392e --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/talpid/net/TunnelEndpoint.kt @@ -0,0 +1,10 @@ +package net.mullvad.talpid.net + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +data class TunnelEndpoint( + val endpoint: Endpoint, + val quantumResistant: Boolean +) : Parcelable diff --git a/android/app/src/main/kotlin/net/mullvad/talpid/net/wireguard/TunnelOptions.kt b/android/app/src/main/kotlin/net/mullvad/talpid/net/wireguard/TunnelOptions.kt new file mode 100644 index 0000000000..79e8ce544c --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/talpid/net/wireguard/TunnelOptions.kt @@ -0,0 +1,10 @@ +package net.mullvad.talpid.net.wireguard + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +data class TunnelOptions( + val mtu: Int?, + val usePqSafePsk: Boolean +) : Parcelable diff --git a/android/app/src/main/kotlin/net/mullvad/talpid/tun_provider/InetNetwork.kt b/android/app/src/main/kotlin/net/mullvad/talpid/tun_provider/InetNetwork.kt new file mode 100644 index 0000000000..a8490b48bf --- /dev/null +++ b/android/app/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/app/src/main/kotlin/net/mullvad/talpid/tun_provider/TunConfig.kt b/android/app/src/main/kotlin/net/mullvad/talpid/tun_provider/TunConfig.kt new file mode 100644 index 0000000000..7efd3f7763 --- /dev/null +++ b/android/app/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/app/src/main/kotlin/net/mullvad/talpid/tunnel/ActionAfterDisconnect.kt b/android/app/src/main/kotlin/net/mullvad/talpid/tunnel/ActionAfterDisconnect.kt new file mode 100644 index 0000000000..365ac0811b --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/talpid/tunnel/ActionAfterDisconnect.kt @@ -0,0 +1,9 @@ +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/app/src/main/kotlin/net/mullvad/talpid/tunnel/ErrorState.kt b/android/app/src/main/kotlin/net/mullvad/talpid/tunnel/ErrorState.kt new file mode 100644 index 0000000000..2c5ba00bf5 --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/talpid/tunnel/ErrorState.kt @@ -0,0 +1,7 @@ +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/app/src/main/kotlin/net/mullvad/talpid/tunnel/ErrorStateCause.kt b/android/app/src/main/kotlin/net/mullvad/talpid/tunnel/ErrorStateCause.kt new file mode 100644 index 0000000000..f5b79bdfd5 --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/talpid/tunnel/ErrorStateCause.kt @@ -0,0 +1,34 @@ +package net.mullvad.talpid.tunnel + +import android.os.Parcelable +import java.net.InetAddress +import kotlinx.parcelize.Parcelize + +sealed class ErrorStateCause : Parcelable { + @Parcelize + class AuthFailed(val reason: String?) : ErrorStateCause() + + @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/app/src/main/kotlin/net/mullvad/talpid/tunnel/ParameterGenerationError.kt b/android/app/src/main/kotlin/net/mullvad/talpid/tunnel/ParameterGenerationError.kt new file mode 100644 index 0000000000..51fa8ac461 --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/talpid/tunnel/ParameterGenerationError.kt @@ -0,0 +1,5 @@ +package net.mullvad.talpid.tunnel + +enum class ParameterGenerationError { + NoMatchingRelay, NoMatchingBridgeRelay, NoWireguardKey, CustomTunnelHostResultionError +} diff --git a/android/app/src/main/kotlin/net/mullvad/talpid/util/EventNotifier.kt b/android/app/src/main/kotlin/net/mullvad/talpid/util/EventNotifier.kt new file mode 100644 index 0000000000..fb038f1243 --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/talpid/util/EventNotifier.kt @@ -0,0 +1,82 @@ +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/app/src/main/kotlin/net/mullvad/talpid/util/EventNotifierExtensions.kt b/android/app/src/main/kotlin/net/mullvad/talpid/util/EventNotifierExtensions.kt new file mode 100644 index 0000000000..454cae6133 --- /dev/null +++ b/android/app/src/main/kotlin/net/mullvad/talpid/util/EventNotifierExtensions.kt @@ -0,0 +1,13 @@ +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/app/src/main/kotlin/net/mullvad/talpid/util/InetAddressExt.kt b/android/app/src/main/kotlin/net/mullvad/talpid/util/InetAddressExt.kt new file mode 100644 index 0000000000..d310deb884 --- /dev/null +++ b/android/app/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 +} |
