Skip to content

Commit

Permalink
change to uniffi callback tun statuses (#660)
Browse files Browse the repository at this point in the history
* change to uniffi callback tun statuses
  • Loading branch information
zaneschepke authored Jul 11, 2024
1 parent b2b24b1 commit 6fce3f2
Show file tree
Hide file tree
Showing 13 changed files with 824 additions and 133 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ class VpnQuickTile : TileService() {
applicationScope.launch {
setTileDescription(this@VpnQuickTile.getString(R.string.disconnecting))
qsTile.updateTile()
vpnClient.get().stop(this@VpnQuickTile, true)
vpnClient.get().stop(true)
job = updateOnState(VpnState.Down)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class NymVpnManager @Inject constructor(
private val context: Context,
) : VpnManager {
override suspend fun stopVpn(foreground: Boolean) {
vpnClient.get().stop(context, foreground)
vpnClient.get().stop(foreground)
NymVpn.requestTileServiceStateUpdate()
}

Expand Down
1 change: 0 additions & 1 deletion nym-vpn-android/nym_vpn_client/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ android {
}

dependencies {
implementation(project(":logcat_helper"))
coreLibraryDesugaring(libs.com.android.tools.desugar)

implementation(libs.androidx.core.ktx)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package net.nymtech.vpn
import android.content.Context
import android.content.Intent
import android.net.VpnService
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
Expand All @@ -12,8 +13,6 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import net.nymtech.logcathelper.LogcatHelper
import net.nymtech.logcathelper.model.LogLevel
import net.nymtech.vpn.model.Environment
import net.nymtech.vpn.model.ErrorState
import net.nymtech.vpn.model.VpnClientState
Expand All @@ -22,10 +21,10 @@ import net.nymtech.vpn.model.VpnState
import net.nymtech.vpn.util.Constants
import net.nymtech.vpn.util.InvalidCredentialException
import net.nymtech.vpn.util.ServiceManager
import net.nymtech.vpn.util.safeCollect
import nym_vpn_lib.EntryPoint
import nym_vpn_lib.ExitPoint
import nym_vpn_lib.FfiException
import nym_vpn_lib.TunStatus
import nym_vpn_lib.TunnelStatusListener
import nym_vpn_lib.VpnConfig
import nym_vpn_lib.checkCredential
import nym_vpn_lib.runVpn
Expand Down Expand Up @@ -65,7 +64,7 @@ object NymVpnClient {
return NymVpn
}
}
internal object NymVpn : VpnClient {
internal object NymVpn : VpnClient, TunnelStatusListener {

private val ioDispatcher = Dispatchers.IO

Expand All @@ -74,19 +73,17 @@ object NymVpnClient {
override var mode: VpnMode = NymVpnClientInit.mode
private val environment: Environment = NymVpnClientInit.environment

private var logsJob: Job? = null
private var statsJob: Job? = null

private val _state = MutableStateFlow(VpnClientState())
override val stateFlow: Flow<VpnClientState> = _state.asStateFlow()

override suspend fun validateCredential(credential: String): Result<Instant?> {
return withContext(ioDispatcher) {
try {
val expiry = checkCredential(credential)
Result.success(expiry)
} catch (_: FfiException) {
Result.failure(InvalidCredentialException("Credential invalid or expired"))
runCatching {
checkCredential(credential)
}.onFailure {
return@withContext Result.failure(InvalidCredentialException("Credential invalid or expired"))
}
}
}
Expand All @@ -97,32 +94,30 @@ object NymVpnClient {
return@withContext Result.failure(it)
}
if (_state.value.vpnState == VpnState.Down) {
setVpnState(VpnState.Connecting.InitializingClient)
clearErrorStatus()
if (foreground) ServiceManager.startVpnServiceForeground(context) else ServiceManager.startVpnService(context)
}
Result.success(Unit)
}
}

override suspend fun stop(context: Context, foreground: Boolean) {
override suspend fun stop(foreground: Boolean) {
withContext(ioDispatcher) {
clearStatisticState()
setVpnState(VpnState.Disconnecting)
try {
runCatching {
stopVpn()
} catch (e: FfiException) {
Timber.e(e)
}.onFailure {
Timber.e(it)
}
delay(1000)
handleClientShutdown(context)
}
}

private fun handleClientShutdown(context: Context) {
ServiceManager.stopVpnService(context)
private fun onDisconnect() {
clearStatisticState()
cancelJobs()
statsJob?.cancel()
}

private fun onConnect() = CoroutineScope(ioDispatcher).launch {
startConnectionTimer()
}

override fun prepare(context: Context): Intent? {
Expand All @@ -132,11 +127,6 @@ object NymVpnClient {
return _state.value
}

private fun cancelJobs() {
statsJob?.cancel()
logsJob?.cancel()
}

private fun clearErrorStatus() {
_state.update {
it.copy(
Expand Down Expand Up @@ -175,7 +165,7 @@ object NymVpnClient {
}
}

internal fun setVpnState(state: VpnState) {
private fun setVpnState(state: VpnState) {
if (state != _state.value.vpnState) {
_state.update {
it.copy(
Expand All @@ -190,18 +180,9 @@ object NymVpnClient {
else -> false
}

internal suspend fun connect(context: Context) {
cancelJobs()
internal suspend fun connect() {
withContext(ioDispatcher) {
logsJob = launch(ioDispatcher) {
monitorLogs(context)
}

statsJob = launch {
startConnectionTimer()
}

try {
runCatching {
runVpn(
VpnConfig(
environment.apiUrl,
Expand All @@ -210,28 +191,13 @@ object NymVpnClient {
exitPoint,
isTwoHop(mode),
null,
this@NymVpn,
),
)
} catch (e: FfiException) {
Timber.e(e)
}.onFailure {
// TODO better handle error messaging based on failure message
Timber.e(it)
setErrorState(ErrorState.GatewayLookupFailure)
handleClientShutdown(context)
}
}
}

private suspend fun monitorLogs(context: Context) {
LogcatHelper.init(context = context).liveLogs.safeCollect {
if (it.tag.contains(Constants.NYM_VPN_LIB_TAG)) {
when (it.level) {
LogLevel.ERROR -> {
parseErrorMessageForState(it.message) { handleClientShutdown(context) }
}
LogLevel.INFO -> {
parseInfoMessageForState(it.message)
}
else -> Unit
}
}
}
}
Expand All @@ -249,33 +215,21 @@ object NymVpnClient {
}
}

private fun parseInfoMessageForState(message: String) {
// TODO make this more robust in the future
with(message) {
when {
contains("Mixnet processor is running") -> setVpnState(VpnState.Up)
contains("Setting up connection monitor") -> setVpnState(VpnState.Up)
contains(
"Obtaining initial network topology",
) -> setVpnState(VpnState.Connecting.EstablishingConnection)
}
}
}

private fun parseErrorMessageForState(message: String, onError: () -> Unit) {
with(message) {
val errorState = when {
contains("failed to lookup described gateways") -> ErrorState.GatewayLookupFailure
contains("invalid peer certificate") -> ErrorState.BadGatewayPeerCertificate
contains("No address associated with hostname") -> ErrorState.BadGatewayNoHostnameAddress
contains("halted unexpectedly") -> ErrorState.VpnHaltedUnexpectedly(message)
else -> null
override fun onTunStatusChange(status: TunStatus) {
val vpnState = when (status) {
TunStatus.INITIALIZING_CLIENT -> VpnState.Connecting.InitializingClient
TunStatus.ESTABLISHING_CONNECTION -> VpnState.Connecting.EstablishingConnection
TunStatus.DOWN -> VpnState.Down
TunStatus.UP -> {
statsJob = onConnect()
VpnState.Up
}
errorState?.let {
setErrorState(it)
onError()
TunStatus.DISCONNECTING -> {
onDisconnect()
VpnState.Disconnecting
}
}
setVpnState(vpnState)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import kotlinx.coroutines.newSingleThreadContext
import net.nymtech.vpn.model.VpnState
import net.nymtech.vpn.tun_provider.TunConfig
import net.nymtech.vpn.util.Action
import net.nymtech.vpn.util.Constants
Expand Down Expand Up @@ -81,7 +80,7 @@ class NymVpnService : VpnService() {
CoroutineScope(vpnThread).launch {
val logLevel = if (BuildConfig.DEBUG) "info" else "info"
initVPN(this@NymVpnService, logLevel)
NymVpnClient.NymVpn.connect(this@NymVpnService)
NymVpnClient.NymVpn.connect()
}
}
}
Expand All @@ -97,10 +96,9 @@ class NymVpnService : VpnService() {

override fun onDestroy() {
connectivityListener.unregister()
NymVpnClient.NymVpn.setVpnState(VpnState.Down)
stopForeground(STOP_FOREGROUND_REMOVE)
Timber.i("VpnService destroyed")
vpnThread.cancel()
Timber.i("VpnService destroyed")
}

fun getTun(config: TunConfig): CreateTunResult {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ interface VpnClient {
@Throws(InvalidCredentialException::class)
suspend fun start(context: Context, credential: String, foreground: Boolean = false): Result<Unit>

suspend fun stop(context: Context, foreground: Boolean = false)
suspend fun stop(foreground: Boolean = false)

fun prepare(context: Context): Intent?

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import android.system.Os
object Constants {
const val NYM_VPN_LIB = "nym_vpn_lib"

const val NYM_VPN_LIB_TAG = "libnymvpn"

// Add Rust environment vars for lib
const val DEFAULT_COUNTRY_ISO = "DE"

Expand Down

This file was deleted.

Loading

0 comments on commit 6fce3f2

Please sign in to comment.