Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

change to uniffi callback tun statuses #660

Merged
merged 9 commits into from
Jul 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading