summaryrefslogtreecommitdiffhomepage
path: root/android/lib/daemon-grpc/src
diff options
context:
space:
mode:
authorJonatan Rhodin <jonatan.rhodin@mullvad.net>2024-06-14 15:13:29 +0200
committerJonatan Rhodin <jonatan.rhodin@mullvad.net>2024-06-14 15:13:29 +0200
commit4c999e3a422b933934f93950473a669cf8afb805 (patch)
treec160ac08bbbb8e0a342dc57720ccd33a0a94dbc2 /android/lib/daemon-grpc/src
parent6fb4623503fda3df77a9c7c3cc152a45506ba1a4 (diff)
parentcef76db82f08abf08b59624a87e6991ba4e5e39a (diff)
downloadmullvadvpn-4c999e3a422b933934f93950473a669cf8afb805.tar.xz
mullvadvpn-4c999e3a422b933934f93950473a669cf8afb805.zip
Merge branch 'android-api-access-method'
Diffstat (limited to 'android/lib/daemon-grpc/src')
-rw-r--r--android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/ManagementService.kt70
-rw-r--r--android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/FromDomain.kt77
-rw-r--r--android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt59
3 files changed, 204 insertions, 2 deletions
diff --git a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/ManagementService.kt b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/ManagementService.kt
index f8073323c8..987b7e56ea 100644
--- a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/ManagementService.kt
+++ b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/ManagementService.kt
@@ -3,6 +3,8 @@ package net.mullvad.mullvadvpn.lib.daemon.grpc
import android.net.LocalSocketAddress
import android.util.Log
import arrow.core.Either
+import arrow.core.raise.either
+import arrow.core.raise.ensure
import arrow.optics.copy
import arrow.optics.dsl.index
import arrow.optics.typeclasses.Index
@@ -42,7 +44,11 @@ import net.mullvad.mullvadvpn.lib.daemon.grpc.util.LogInterceptor
import net.mullvad.mullvadvpn.lib.daemon.grpc.util.connectivityFlow
import net.mullvad.mullvadvpn.lib.model.AccountData
import net.mullvad.mullvadvpn.lib.model.AccountNumber
+import net.mullvad.mullvadvpn.lib.model.AddApiAccessMethodError
import net.mullvad.mullvadvpn.lib.model.AddSplitTunnelingAppError
+import net.mullvad.mullvadvpn.lib.model.ApiAccessMethod
+import net.mullvad.mullvadvpn.lib.model.ApiAccessMethodId
+import net.mullvad.mullvadvpn.lib.model.ApiAccessMethodSetting
import net.mullvad.mullvadvpn.lib.model.AppId
import net.mullvad.mullvadvpn.lib.model.AppVersionInfo as ModelAppVersionInfo
import net.mullvad.mullvadvpn.lib.model.ClearAllOverridesError
@@ -67,6 +73,7 @@ import net.mullvad.mullvadvpn.lib.model.GetAccountHistoryError
import net.mullvad.mullvadvpn.lib.model.GetDeviceListError
import net.mullvad.mullvadvpn.lib.model.GetDeviceStateError
import net.mullvad.mullvadvpn.lib.model.LoginAccountError
+import net.mullvad.mullvadvpn.lib.model.NewAccessMethodSetting
import net.mullvad.mullvadvpn.lib.model.ObfuscationSettings
import net.mullvad.mullvadvpn.lib.model.Ownership as ModelOwnership
import net.mullvad.mullvadvpn.lib.model.PlayPurchase
@@ -84,9 +91,11 @@ import net.mullvad.mullvadvpn.lib.model.RelayItemId as ModelRelayItemId
import net.mullvad.mullvadvpn.lib.model.RelayList as ModelRelayList
import net.mullvad.mullvadvpn.lib.model.RelayList
import net.mullvad.mullvadvpn.lib.model.RelaySettings
+import net.mullvad.mullvadvpn.lib.model.RemoveApiAccessMethodError
import net.mullvad.mullvadvpn.lib.model.RemoveSplitTunnelingAppError
import net.mullvad.mullvadvpn.lib.model.SelectedObfuscation
import net.mullvad.mullvadvpn.lib.model.SetAllowLanError
+import net.mullvad.mullvadvpn.lib.model.SetApiAccessMethodError
import net.mullvad.mullvadvpn.lib.model.SetAutoConnectError
import net.mullvad.mullvadvpn.lib.model.SetDnsOptionsError
import net.mullvad.mullvadvpn.lib.model.SetObfuscationOptionsError
@@ -96,8 +105,11 @@ import net.mullvad.mullvadvpn.lib.model.SetWireguardMtuError
import net.mullvad.mullvadvpn.lib.model.SetWireguardQuantumResistantError
import net.mullvad.mullvadvpn.lib.model.Settings as ModelSettings
import net.mullvad.mullvadvpn.lib.model.SettingsPatchError
+import net.mullvad.mullvadvpn.lib.model.TestApiAccessMethodError
import net.mullvad.mullvadvpn.lib.model.TunnelState as ModelTunnelState
+import net.mullvad.mullvadvpn.lib.model.UnknownApiAccessMethodError
import net.mullvad.mullvadvpn.lib.model.UnknownCustomListError
+import net.mullvad.mullvadvpn.lib.model.UpdateApiAccessMethodError
import net.mullvad.mullvadvpn.lib.model.UpdateCustomListError
import net.mullvad.mullvadvpn.lib.model.WebsiteAuthToken
import net.mullvad.mullvadvpn.lib.model.WireguardConstraints as ModelWireguardConstraints
@@ -161,6 +173,10 @@ class ManagementService(
val wireguardEndpointData: Flow<ModelWireguardEndpointData> =
relayList.mapNotNull { it.wireguardEndpointData }
+ private val _mutableCurrentAccessMethod = MutableStateFlow<ApiAccessMethodSetting?>(null)
+ val currentAccessMethod: Flow<ApiAccessMethodSetting> =
+ _mutableCurrentAccessMethod.filterNotNull()
+
fun start() {
// Just to ensure that connection is set up since the connection won't be setup without a
// call to the daemon
@@ -196,9 +212,11 @@ class ManagementService(
_mutableVersionInfo.update { event.versionInfo.toDomain() }
ManagementInterface.DaemonEvent.EventCase.DEVICE ->
_mutableDeviceState.update { event.device.newState.toDomain() }
+ ManagementInterface.DaemonEvent.EventCase.NEW_ACCESS_METHOD -> {
+ _mutableCurrentAccessMethod.update { event.newAccessMethod.toDomain() }
+ }
ManagementInterface.DaemonEvent.EventCase.REMOVE_DEVICE -> {}
ManagementInterface.DaemonEvent.EventCase.EVENT_NOT_SET -> {}
- ManagementInterface.DaemonEvent.EventCase.NEW_ACCESS_METHOD -> {}
}
}
}
@@ -297,6 +315,7 @@ class ManagementService(
async { _mutableSettings.update { getSettings() } },
async { _mutableVersionInfo.update { getVersionInfo() } },
async { _mutableRelayList.update { getRelayList() } },
+ async { _mutableCurrentAccessMethod.update { getCurrentApiAccessMethod() } }
)
}
}
@@ -572,6 +591,55 @@ class ManagementService(
Either.catch { grpc.getWwwAuthToken(Empty.getDefaultInstance()) }
.map { WebsiteAuthToken.fromString(it.value) }
+ suspend fun addApiAccessMethod(
+ newAccessMethodSetting: NewAccessMethodSetting
+ ): Either<AddApiAccessMethodError, ApiAccessMethodId> =
+ Either.catch { grpc.addApiAccessMethod(newAccessMethodSetting.fromDomain()) }
+ .mapLeft(AddApiAccessMethodError::Unknown)
+ .map { ApiAccessMethodId.fromString(it.value) }
+
+ suspend fun removeApiAccessMethod(
+ apiAccessMethodId: ApiAccessMethodId
+ ): Either<RemoveApiAccessMethodError, Unit> =
+ Either.catch { grpc.removeApiAccessMethod(apiAccessMethodId.fromDomain()) }
+ .mapLeft(RemoveApiAccessMethodError::Unknown)
+ .mapEmpty()
+
+ suspend fun setApiAccessMethod(
+ apiAccessMethodId: ApiAccessMethodId
+ ): Either<SetApiAccessMethodError, Unit> =
+ Either.catch { grpc.setApiAccessMethod(apiAccessMethodId.fromDomain()) }
+ .mapLeft(SetApiAccessMethodError::Unknown)
+ .mapEmpty()
+
+ suspend fun updateApiAccessMethod(
+ apiAccessMethodSetting: ApiAccessMethodSetting
+ ): Either<UpdateApiAccessMethodError, Unit> =
+ Either.catch { grpc.updateApiAccessMethod(apiAccessMethodSetting.fromDomain()) }
+ .mapLeft(::UnknownApiAccessMethodError)
+ .mapEmpty()
+
+ private suspend fun getCurrentApiAccessMethod(): ApiAccessMethodSetting =
+ grpc.getCurrentApiAccessMethod(Empty.getDefaultInstance()).toDomain()
+
+ suspend fun testCustomApiAccessMethod(
+ customProxy: ApiAccessMethod.CustomProxy
+ ): Either<TestApiAccessMethodError, Unit> =
+ Either.catch { grpc.testCustomApiAccessMethod(customProxy.fromDomain()) }
+ .mapLeftStatus { TestApiAccessMethodError.Grpc }
+ .map { result ->
+ either { ensure(result.value) { TestApiAccessMethodError.CouldNotAccess } }
+ }
+
+ suspend fun testApiAccessMethodById(
+ apiAccessMethodId: ApiAccessMethodId
+ ): Either<TestApiAccessMethodError, Unit> =
+ Either.catch { grpc.testApiAccessMethodById(apiAccessMethodId.fromDomain()) }
+ .mapLeftStatus { TestApiAccessMethodError.Grpc }
+ .map { result ->
+ either { ensure(result.value) { TestApiAccessMethodError.CouldNotAccess } }
+ }
+
private fun <A> Either<A, Empty>.mapEmpty() = map {}
private inline fun <B, C> Either<Throwable, B>.mapLeftStatus(
diff --git a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/FromDomain.kt b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/FromDomain.kt
index 4efd8c452e..014bafb85b 100644
--- a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/FromDomain.kt
+++ b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/FromDomain.kt
@@ -1,6 +1,9 @@
package net.mullvad.mullvadvpn.lib.daemon.grpc.mapper
import mullvad_daemon.management_interface.ManagementInterface
+import net.mullvad.mullvadvpn.lib.model.ApiAccessMethod
+import net.mullvad.mullvadvpn.lib.model.ApiAccessMethodId
+import net.mullvad.mullvadvpn.lib.model.ApiAccessMethodSetting
import net.mullvad.mullvadvpn.lib.model.Constraint
import net.mullvad.mullvadvpn.lib.model.CustomDnsOptions
import net.mullvad.mullvadvpn.lib.model.CustomList
@@ -9,6 +12,7 @@ import net.mullvad.mullvadvpn.lib.model.DefaultDnsOptions
import net.mullvad.mullvadvpn.lib.model.DnsOptions
import net.mullvad.mullvadvpn.lib.model.DnsState
import net.mullvad.mullvadvpn.lib.model.GeoLocationId
+import net.mullvad.mullvadvpn.lib.model.NewAccessMethodSetting
import net.mullvad.mullvadvpn.lib.model.ObfuscationSettings
import net.mullvad.mullvadvpn.lib.model.Ownership
import net.mullvad.mullvadvpn.lib.model.PlayPurchase
@@ -18,6 +22,8 @@ import net.mullvad.mullvadvpn.lib.model.Providers
import net.mullvad.mullvadvpn.lib.model.RelayItemId
import net.mullvad.mullvadvpn.lib.model.RelaySettings
import net.mullvad.mullvadvpn.lib.model.SelectedObfuscation
+import net.mullvad.mullvadvpn.lib.model.SocksAuth
+import net.mullvad.mullvadvpn.lib.model.TransportProtocol
import net.mullvad.mullvadvpn.lib.model.Udp2TcpObfuscationSettings
import net.mullvad.mullvadvpn.lib.model.WireguardConstraints
@@ -160,3 +166,74 @@ internal fun PlayPurchase.fromDomain(): ManagementInterface.PlayPurchase =
.setPurchaseToken(purchaseToken.fromDomain())
.setProductId(productId)
.build()
+
+internal fun NewAccessMethodSetting.fromDomain(): ManagementInterface.NewAccessMethodSetting =
+ ManagementInterface.NewAccessMethodSetting.newBuilder()
+ .setName(name.value)
+ .setEnabled(enabled)
+ .setAccessMethod(
+ ManagementInterface.AccessMethod.newBuilder().setCustom(apiAccessMethod.fromDomain())
+ )
+ .build()
+
+internal fun ApiAccessMethod.fromDomain(): ManagementInterface.AccessMethod =
+ ManagementInterface.AccessMethod.newBuilder()
+ .let {
+ when (this) {
+ ApiAccessMethod.Direct ->
+ it.setDirect(ManagementInterface.AccessMethod.Direct.getDefaultInstance())
+ ApiAccessMethod.Bridges ->
+ it.setBridges(ManagementInterface.AccessMethod.Bridges.getDefaultInstance())
+ is ApiAccessMethod.CustomProxy -> it.setCustom(this.fromDomain())
+ }
+ }
+ .build()
+
+internal fun ApiAccessMethod.CustomProxy.fromDomain(): ManagementInterface.CustomProxy =
+ ManagementInterface.CustomProxy.newBuilder()
+ .let {
+ when (this) {
+ is ApiAccessMethod.CustomProxy.Shadowsocks -> it.setShadowsocks(this.fromDomain())
+ is ApiAccessMethod.CustomProxy.Socks5Remote -> it.setSocks5Remote(this.fromDomain())
+ }
+ }
+ .build()
+
+internal fun ApiAccessMethod.CustomProxy.Socks5Remote.fromDomain():
+ ManagementInterface.Socks5Remote =
+ ManagementInterface.Socks5Remote.newBuilder().setIp(ip).setPort(port.value).let {
+ auth?.let { auth -> it.setAuth(auth.fromDomain()) }
+ it.build()
+ }
+
+internal fun SocksAuth.fromDomain(): ManagementInterface.SocksAuth =
+ ManagementInterface.SocksAuth.newBuilder().setUsername(username).setPassword(password).build()
+
+internal fun ApiAccessMethod.CustomProxy.Shadowsocks.fromDomain(): ManagementInterface.Shadowsocks =
+ ManagementInterface.Shadowsocks.newBuilder()
+ .setIp(ip)
+ .setCipher(cipher.label)
+ .setPort(port.value)
+ .let {
+ if (password != null) {
+ it.setPassword(password)
+ }
+ it.build()
+ }
+
+internal fun TransportProtocol.fromDomain(): ManagementInterface.TransportProtocol =
+ when (this) {
+ TransportProtocol.Tcp -> ManagementInterface.TransportProtocol.TCP
+ TransportProtocol.Udp -> ManagementInterface.TransportProtocol.UDP
+ }
+
+internal fun ApiAccessMethodId.fromDomain(): ManagementInterface.UUID =
+ ManagementInterface.UUID.newBuilder().setValue(value.toString()).build()
+
+internal fun ApiAccessMethodSetting.fromDomain(): ManagementInterface.AccessMethodSetting =
+ ManagementInterface.AccessMethodSetting.newBuilder()
+ .setName(name.value)
+ .setId(id.fromDomain())
+ .setEnabled(enabled)
+ .setAccessMethod(apiAccessMethod.fromDomain())
+ .build()
diff --git a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt
index 59a94f62dc..13ebe74350 100644
--- a/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt
+++ b/android/lib/daemon-grpc/src/main/kotlin/net/mullvad/mullvadvpn/lib/daemon/grpc/mapper/ToDomain.kt
@@ -13,8 +13,13 @@ import net.mullvad.mullvadvpn.lib.model.AccountData
import net.mullvad.mullvadvpn.lib.model.AccountId
import net.mullvad.mullvadvpn.lib.model.AccountNumber
import net.mullvad.mullvadvpn.lib.model.ActionAfterDisconnect
+import net.mullvad.mullvadvpn.lib.model.ApiAccessMethod
+import net.mullvad.mullvadvpn.lib.model.ApiAccessMethodId
+import net.mullvad.mullvadvpn.lib.model.ApiAccessMethodName
+import net.mullvad.mullvadvpn.lib.model.ApiAccessMethodSetting
import net.mullvad.mullvadvpn.lib.model.AppId
import net.mullvad.mullvadvpn.lib.model.AppVersionInfo
+import net.mullvad.mullvadvpn.lib.model.Cipher
import net.mullvad.mullvadvpn.lib.model.Constraint
import net.mullvad.mullvadvpn.lib.model.CustomDnsOptions
import net.mullvad.mullvadvpn.lib.model.CustomList
@@ -53,6 +58,7 @@ import net.mullvad.mullvadvpn.lib.model.RelayOverride
import net.mullvad.mullvadvpn.lib.model.RelaySettings
import net.mullvad.mullvadvpn.lib.model.SelectedObfuscation
import net.mullvad.mullvadvpn.lib.model.Settings
+import net.mullvad.mullvadvpn.lib.model.SocksAuth
import net.mullvad.mullvadvpn.lib.model.SplitTunnelSettings
import net.mullvad.mullvadvpn.lib.model.TransportProtocol
import net.mullvad.mullvadvpn.lib.model.TunnelEndpoint
@@ -246,7 +252,8 @@ internal fun ManagementInterface.Settings.toDomain(): Settings =
tunnelOptions = tunnelOptions.toDomain(),
relayOverrides = relayOverridesList.map { it.toDomain() },
showBetaReleases = showBetaReleases,
- splitTunnelSettings = splitTunnel.toDomain()
+ splitTunnelSettings = splitTunnel.toDomain(),
+ apiAccessMethodSettings = apiAccessMethods.toDomain()
)
internal fun ManagementInterface.RelayOverride.toDomain(): RelayOverride =
@@ -519,3 +526,53 @@ internal fun ManagementInterface.SplitTunnelSettings.toDomain(): SplitTunnelSett
internal fun ManagementInterface.PlayPurchasePaymentToken.toDomain(): PlayPurchasePaymentToken =
PlayPurchasePaymentToken(value = token)
+
+internal fun ManagementInterface.ApiAccessMethodSettings.toDomain(): List<ApiAccessMethodSetting> =
+ listOf(direct.toDomain(), mullvadBridges.toDomain()).plus(customList.map { it.toDomain() })
+
+internal fun ManagementInterface.AccessMethodSetting.toDomain(): ApiAccessMethodSetting =
+ ApiAccessMethodSetting(
+ id = ApiAccessMethodId.fromString(id.value),
+ name = ApiAccessMethodName.fromString(name),
+ enabled = enabled,
+ apiAccessMethod = accessMethod.toDomain()
+ )
+
+internal fun ManagementInterface.AccessMethod.toDomain(): ApiAccessMethod =
+ when {
+ hasDirect() -> ApiAccessMethod.Direct
+ hasBridges() -> ApiAccessMethod.Bridges
+ hasCustom() -> custom.toDomain()
+ else -> error("Type not found")
+ }
+
+internal fun ManagementInterface.CustomProxy.toDomain(): ApiAccessMethod.CustomProxy =
+ when {
+ hasShadowsocks() -> shadowsocks.toDomain()
+ hasSocks5Remote() -> socks5Remote.toDomain()
+ hasSocks5Local() -> error("Socks5 local not supported")
+ else -> error("Custom proxy not found")
+ }
+
+internal fun ManagementInterface.Shadowsocks.toDomain(): ApiAccessMethod.CustomProxy.Shadowsocks =
+ ApiAccessMethod.CustomProxy.Shadowsocks(
+ ip = ip,
+ port = Port(port),
+ password = password,
+ cipher = Cipher.fromString(cipher)
+ )
+
+internal fun ManagementInterface.Socks5Remote.toDomain(): ApiAccessMethod.CustomProxy.Socks5Remote =
+ ApiAccessMethod.CustomProxy.Socks5Remote(
+ ip = ip,
+ port = Port(port),
+ auth =
+ if (hasAuth()) {
+ auth.toDomain()
+ } else {
+ null
+ }
+ )
+
+internal fun ManagementInterface.SocksAuth.toDomain(): SocksAuth =
+ SocksAuth(username = username, password = password)