summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt3
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/RelayOverridesRepository.kt44
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/SettingsRepository.kt17
-rw-r--r--android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/SettingsListener.kt4
-rw-r--r--android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/Event.kt5
-rw-r--r--android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/Request.kt9
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/RelayOverride.kt12
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Settings.kt3
-rw-r--r--android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/SettingsPatchError.kt24
-rw-r--r--android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadDaemon.kt25
-rw-r--r--android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/JsonSettings.kt48
-rw-r--r--android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/RelayOverrides.kt37
-rw-r--r--android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/ServiceEndpoint.kt4
13 files changed, 232 insertions, 3 deletions
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt
index c3eb19b270..13b5e7a2db 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/di/UiModule.kt
@@ -105,8 +105,9 @@ val uiModule = module {
androidContext().getSharedPreferences(APP_PREFERENCES_NAME, Context.MODE_PRIVATE)
)
}
- single { SettingsRepository(get()) }
+ single { SettingsRepository(get(), get()) }
single { MullvadProblemReport(get()) }
+ single { RelayOverridesRepository(get(), get()) }
single { CustomListsRepository(get(), get(), get()) }
single { AccountExpiryNotificationUseCase(get()) }
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/RelayOverridesRepository.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/RelayOverridesRepository.kt
new file mode 100644
index 0000000000..835cab4710
--- /dev/null
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/RelayOverridesRepository.kt
@@ -0,0 +1,44 @@
+package net.mullvad.mullvadvpn.repository
+
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.stateIn
+import net.mullvad.mullvadvpn.lib.ipc.MessageHandler
+import net.mullvad.mullvadvpn.lib.ipc.Request
+import net.mullvad.mullvadvpn.model.RelayOverride
+import net.mullvad.mullvadvpn.ui.serviceconnection.ServiceConnectionManager
+import net.mullvad.mullvadvpn.ui.serviceconnection.settingsListener
+import net.mullvad.mullvadvpn.util.callbackFlowFromNotifier
+import net.mullvad.mullvadvpn.util.flatMapReadyConnectionOrDefault
+
+class RelayOverridesRepository(
+ private val serviceConnectionManager: ServiceConnectionManager,
+ private val messageHandler: MessageHandler,
+ dispatcher: CoroutineDispatcher = Dispatchers.IO,
+) {
+ fun clearAllOverrides() {
+ messageHandler.trySendRequest(Request.ClearAllRelayOverrides)
+ }
+
+ val relayOverrides: StateFlow<List<RelayOverride>?> =
+ serviceConnectionManager.connectionState
+ .flatMapReadyConnectionOrDefault(flowOf()) { state ->
+ callbackFlowFromNotifier(state.container.settingsListener.settingsNotifier)
+ }
+ .mapNotNull { it?.relayOverrides?.toList() }
+ .onStart {
+ serviceConnectionManager
+ .settingsListener()
+ ?.settingsNotifier
+ ?.latestEvent
+ ?.relayOverrides
+ ?.toList()
+ }
+ .stateIn(CoroutineScope(dispatcher), SharingStarted.Eagerly, null)
+}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/SettingsRepository.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/SettingsRepository.kt
index 81c4b85b88..7d61feaf0c 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/SettingsRepository.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/repository/SettingsRepository.kt
@@ -4,11 +4,18 @@ import java.net.InetAddress
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.async
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.withContext
+import net.mullvad.mullvadvpn.lib.ipc.Event.ApplyJsonSettingsResult
+import net.mullvad.mullvadvpn.lib.ipc.MessageHandler
+import net.mullvad.mullvadvpn.lib.ipc.Request
+import net.mullvad.mullvadvpn.lib.ipc.events
import net.mullvad.mullvadvpn.model.CustomDnsOptions
import net.mullvad.mullvadvpn.model.DefaultDnsOptions
import net.mullvad.mullvadvpn.model.DnsOptions
@@ -24,7 +31,8 @@ import net.mullvad.mullvadvpn.util.flatMapReadyConnectionOrDefault
class SettingsRepository(
private val serviceConnectionManager: ServiceConnectionManager,
- dispatcher: CoroutineDispatcher = Dispatchers.IO
+ private val messageHandler: MessageHandler,
+ private val dispatcher: CoroutineDispatcher = Dispatchers.IO,
) {
val settingsUpdates: StateFlow<Settings?> =
serviceConnectionManager.connectionState
@@ -92,4 +100,11 @@ class SettingsRepository(
fun setLocalNetworkSharing(isEnabled: Boolean) {
serviceConnectionManager.settingsListener()?.allowLan = isEnabled
}
+
+ suspend fun applySettingsPatch(json: String) =
+ withContext(dispatcher) {
+ val deferred = async { messageHandler.events<ApplyJsonSettingsResult>().first() }
+ messageHandler.trySendRequest(Request.ApplyJsonSettings(json))
+ deferred.await()
+ }
}
diff --git a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/SettingsListener.kt b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/SettingsListener.kt
index d996c432ad..e2ccc2e470 100644
--- a/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/SettingsListener.kt
+++ b/android/app/src/main/kotlin/net/mullvad/mullvadvpn/ui/serviceconnection/SettingsListener.kt
@@ -68,4 +68,8 @@ class SettingsListener(private val connection: Messenger, eventDispatcher: Event
settings = newSettings
}
+
+ fun applySettingsPatch(json: String) {
+ connection.send(Request.ApplyJsonSettings(json).message)
+ }
}
diff --git a/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/Event.kt b/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/Event.kt
index cce2ab1f87..36ea17036e 100644
--- a/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/Event.kt
+++ b/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/Event.kt
@@ -14,6 +14,7 @@ import net.mullvad.mullvadvpn.model.PlayPurchaseVerifyResult
import net.mullvad.mullvadvpn.model.RelayList
import net.mullvad.mullvadvpn.model.RemoveDeviceResult
import net.mullvad.mullvadvpn.model.Settings
+import net.mullvad.mullvadvpn.model.SettingsPatchError
import net.mullvad.mullvadvpn.model.TunnelState
import net.mullvad.mullvadvpn.model.UpdateCustomListResult
@@ -71,6 +72,10 @@ sealed class Event : Message.EventMessage() {
@Parcelize data class UpdateCustomListResultEvent(val result: UpdateCustomListResult) : Event()
+ @Parcelize data class ExportJsonSettingsResult(val json: String) : Event()
+
+ @Parcelize data class ApplyJsonSettingsResult(val error: SettingsPatchError?) : Event()
+
companion object {
private const val MESSAGE_KEY = "event"
diff --git a/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/Request.kt b/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/Request.kt
index fe9d3b46d9..4bcf871acc 100644
--- a/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/Request.kt
+++ b/android/lib/ipc/src/main/kotlin/net/mullvad/mullvadvpn/lib/ipc/Request.kt
@@ -13,6 +13,7 @@ import net.mullvad.mullvadvpn.model.Ownership
import net.mullvad.mullvadvpn.model.PlayPurchase
import net.mullvad.mullvadvpn.model.Providers
import net.mullvad.mullvadvpn.model.QuantumResistantState
+import net.mullvad.mullvadvpn.model.RelayOverride
import net.mullvad.mullvadvpn.model.WireguardConstraints
// Requests that the service can handle
@@ -117,6 +118,14 @@ sealed class Request : Message.RequestMessage() {
@Parcelize data class UpdateCustomList(val customList: CustomList) : Request()
+ @Parcelize data object ClearAllRelayOverrides : Request()
+
+ @Parcelize data class ApplyJsonSettings(val json: String) : Request()
+
+ @Parcelize data object ExportJsonSettings : Request()
+
+ @Parcelize data class SetRelayOverride(val override: RelayOverride) : Request()
+
companion object {
private const val MESSAGE_KEY = "request"
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/RelayOverride.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/RelayOverride.kt
new file mode 100644
index 0000000000..f738218ee7
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/RelayOverride.kt
@@ -0,0 +1,12 @@
+package net.mullvad.mullvadvpn.model
+
+import android.os.Parcelable
+import java.net.InetAddress
+import kotlinx.parcelize.Parcelize
+
+@Parcelize
+data class RelayOverride(
+ val hostname: String,
+ val ipv4AddressIn: InetAddress?,
+ val ipv6AddressIn: InetAddress?
+) : Parcelable
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Settings.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Settings.kt
index 304edc404a..847b80cd70 100644
--- a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Settings.kt
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/Settings.kt
@@ -11,5 +11,6 @@ data class Settings(
val allowLan: Boolean,
val autoConnect: Boolean,
val tunnelOptions: TunnelOptions,
- val showBetaReleases: Boolean
+ val relayOverrides: ArrayList<RelayOverride>,
+ val showBetaReleases: Boolean,
) : Parcelable
diff --git a/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/SettingsPatchError.kt b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/SettingsPatchError.kt
new file mode 100644
index 0000000000..5e3cb29911
--- /dev/null
+++ b/android/lib/model/src/main/kotlin/net/mullvad/mullvadvpn/model/SettingsPatchError.kt
@@ -0,0 +1,24 @@
+package net.mullvad.mullvadvpn.model
+
+import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
+
+@Parcelize
+sealed class SettingsPatchError : Parcelable {
+ // E.g hostname is number instead of String
+ data class InvalidOrMissingValue(val value: String) : SettingsPatchError()
+
+ // E.g. Unexpected top-level key?
+ data class UnknownOrProhibitedKey(val value: String) : SettingsPatchError()
+
+ // Bad JSON
+ data object ParsePatch : SettingsPatchError()
+
+ data object RecursionLimit : SettingsPatchError()
+
+ // Patch was deserialized but was not valid domain data?
+ data object DeserializePatched : SettingsPatchError()
+
+ // Failed to apply patch
+ data object ApplyPatch : SettingsPatchError()
+}
diff --git a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadDaemon.kt b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadDaemon.kt
index f99d36c679..1d87987cf3 100644
--- a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadDaemon.kt
+++ b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/MullvadDaemon.kt
@@ -20,10 +20,12 @@ import net.mullvad.mullvadvpn.model.PlayPurchaseInitResult
import net.mullvad.mullvadvpn.model.PlayPurchaseVerifyResult
import net.mullvad.mullvadvpn.model.QuantumResistantState
import net.mullvad.mullvadvpn.model.RelayList
+import net.mullvad.mullvadvpn.model.RelayOverride
import net.mullvad.mullvadvpn.model.RelaySettings
import net.mullvad.mullvadvpn.model.RemoveDeviceEvent
import net.mullvad.mullvadvpn.model.RemoveDeviceResult
import net.mullvad.mullvadvpn.model.Settings
+import net.mullvad.mullvadvpn.model.SettingsPatchError
import net.mullvad.mullvadvpn.model.TunnelState
import net.mullvad.mullvadvpn.model.UpdateCustomListResult
import net.mullvad.mullvadvpn.model.VoucherSubmissionResult
@@ -202,6 +204,15 @@ class MullvadDaemon(
fun updateCustomList(customList: CustomList): UpdateCustomListResult =
updateCustomList(daemonInterfaceAddress, customList)
+ fun clearAllRelayOverrides() = clearAllRelayOverrides(daemonInterfaceAddress)
+
+ fun applyJsonSettings(json: String) = applyJsonSettings(daemonInterfaceAddress, json)
+
+ fun exportJsonSettings(): String = exportJsonSettings(daemonInterfaceAddress)
+
+ fun setRelayOverride(relayOverride: RelayOverride) =
+ setRelayOverride(daemonInterfaceAddress, relayOverride)
+
fun onDestroy() {
onSettingsChange.unsubscribeAll()
onTunnelStateChange.unsubscribeAll()
@@ -323,6 +334,20 @@ class MullvadDaemon(
customList: CustomList
): UpdateCustomListResult
+ private external fun clearAllRelayOverrides(daemonInterfaceAddress: Long)
+
+ private external fun applyJsonSettings(
+ daemonInterfaceAddress: Long,
+ json: String
+ ): SettingsPatchError
+
+ private external fun exportJsonSettings(daemonInterfaceAddress: Long): String
+
+ private external fun setRelayOverride(
+ daemonInterfaceAddress: Long,
+ relayOverride: RelayOverride
+ )
+
@Suppress("unused")
private fun notifyAppVersionInfoEvent(appVersionInfo: AppVersionInfo) {
onAppVersionInfoChange?.invoke(appVersionInfo)
diff --git a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/JsonSettings.kt b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/JsonSettings.kt
new file mode 100644
index 0000000000..65d7b6cff0
--- /dev/null
+++ b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/JsonSettings.kt
@@ -0,0 +1,48 @@
+package net.mullvad.mullvadvpn.service.endpoint
+
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.launch
+import net.mullvad.mullvadvpn.lib.ipc.Event
+import net.mullvad.mullvadvpn.lib.ipc.Request
+
+class JsonSettings(
+ private val endpoint: ServiceEndpoint,
+ dispatcher: CoroutineDispatcher = Dispatchers.IO
+) {
+ private val scope: CoroutineScope = CoroutineScope(SupervisorJob() + dispatcher)
+ private val daemon
+ get() = endpoint.intermittentDaemon
+
+ init {
+ scope.launch {
+ endpoint.dispatcher.parsedMessages
+ .filterIsInstance<Request.ApplyJsonSettings>()
+ .collect { applyJsonSettings(it.json) }
+ }
+
+ scope.launch {
+ endpoint.dispatcher.parsedMessages
+ .filterIsInstance<Request.ExportJsonSettings>()
+ .collect { exportJsonSettings() }
+ }
+ }
+
+ private suspend fun applyJsonSettings(json: String) {
+ val result = daemon.await().applyJsonSettings(json)
+ endpoint.sendEvent(Event.ApplyJsonSettingsResult(result))
+ }
+
+ private suspend fun exportJsonSettings() {
+ val json = daemon.await().exportJsonSettings()
+ endpoint.sendEvent(Event.ExportJsonSettingsResult(json))
+ }
+
+ fun onDestroy() {
+ scope.cancel()
+ }
+}
diff --git a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/RelayOverrides.kt b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/RelayOverrides.kt
new file mode 100644
index 0000000000..cda7a5b94b
--- /dev/null
+++ b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/RelayOverrides.kt
@@ -0,0 +1,37 @@
+package net.mullvad.mullvadvpn.service.endpoint
+
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.flow.filterIsInstance
+import kotlinx.coroutines.launch
+import net.mullvad.mullvadvpn.lib.ipc.Request
+
+class RelayOverrides(
+ private val endpoint: ServiceEndpoint,
+ dispatcher: CoroutineDispatcher = Dispatchers.IO
+) {
+ private val scope: CoroutineScope = CoroutineScope(SupervisorJob() + dispatcher)
+ private val daemon
+ get() = endpoint.intermittentDaemon
+
+ init {
+ scope.launch {
+ endpoint.dispatcher.parsedMessages
+ .filterIsInstance<Request.SetRelayOverride>()
+ .collect { daemon.await().setRelayOverride(it.override) }
+ }
+
+ scope.launch {
+ endpoint.dispatcher.parsedMessages
+ .filterIsInstance<Request.ClearAllRelayOverrides>()
+ .collect { daemon.await().clearAllRelayOverrides() }
+ }
+ }
+
+ fun onDestroy() {
+ scope.cancel()
+ }
+}
diff --git a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/ServiceEndpoint.kt b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/ServiceEndpoint.kt
index 5485c528b0..f8fc6aaf64 100644
--- a/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/ServiceEndpoint.kt
+++ b/android/service/src/main/kotlin/net/mullvad/mullvadvpn/service/endpoint/ServiceEndpoint.kt
@@ -46,6 +46,8 @@ class ServiceEndpoint(
val appVersionInfoCache = AppVersionInfoCache(this)
val authTokenCache = AuthTokenCache(this)
val customDns = CustomDns(this)
+ val relayOverrides = RelayOverrides(this)
+ val jsonSettings = JsonSettings(this)
val relayListListener = RelayListListener(this)
val splitTunneling = SplitTunneling(SplitTunnelingPersistence(context), this)
val voucherRedeemer = VoucherRedeemer(this, accountCache)
@@ -83,6 +85,8 @@ class ServiceEndpoint(
voucherRedeemer.onDestroy()
playPurchaseHandler.onDestroy()
customLists.onDestroy()
+ relayOverrides.onDestroy()
+ jsonSettings.onDestroy()
}
internal fun sendEvent(event: Event) {