summaryrefslogtreecommitdiffhomepage
path: root/android/app/src/main
diff options
context:
space:
mode:
authorAlbin <albin@mullvad.net>2022-09-14 16:29:55 +0200
committerAlbin <albin@mullvad.net>2022-09-14 16:31:54 +0200
commit2657f4e5214c5607fb4495f806f0abdc7b6b4e05 (patch)
tree4f550e24ce20870ab58520d0d5161066e1cc3914 /android/app/src/main
parent4ae656001172d7e00c4c0f6f943ef42d2047a1c2 (diff)
downloadmullvadvpn-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/main')
-rw-r--r--android/app/src/main/kotlin/net/mullvad/talpid/ConnectivityListener.kt66
-rw-r--r--android/app/src/main/kotlin/net/mullvad/talpid/CreateTunResult.kt25
-rw-r--r--android/app/src/main/kotlin/net/mullvad/talpid/TalpidVpnService.kt157
-rw-r--r--android/app/src/main/kotlin/net/mullvad/talpid/net/Endpoint.kt8
-rw-r--r--android/app/src/main/kotlin/net/mullvad/talpid/net/TransportProtocol.kt9
-rw-r--r--android/app/src/main/kotlin/net/mullvad/talpid/net/TunnelEndpoint.kt10
-rw-r--r--android/app/src/main/kotlin/net/mullvad/talpid/net/wireguard/TunnelOptions.kt10
-rw-r--r--android/app/src/main/kotlin/net/mullvad/talpid/tun_provider/InetNetwork.kt8
-rw-r--r--android/app/src/main/kotlin/net/mullvad/talpid/tun_provider/TunConfig.kt10
-rw-r--r--android/app/src/main/kotlin/net/mullvad/talpid/tunnel/ActionAfterDisconnect.kt9
-rw-r--r--android/app/src/main/kotlin/net/mullvad/talpid/tunnel/ErrorState.kt7
-rw-r--r--android/app/src/main/kotlin/net/mullvad/talpid/tunnel/ErrorStateCause.kt34
-rw-r--r--android/app/src/main/kotlin/net/mullvad/talpid/tunnel/ParameterGenerationError.kt5
-rw-r--r--android/app/src/main/kotlin/net/mullvad/talpid/util/EventNotifier.kt82
-rw-r--r--android/app/src/main/kotlin/net/mullvad/talpid/util/EventNotifierExtensions.kt13
-rw-r--r--android/app/src/main/kotlin/net/mullvad/talpid/util/InetAddressExt.kt10
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
+}