mirror of
https://github.com/wgtunnel/android.git
synced 2026-07-03 14:07:49 +02:00
fix: race conditions (#621)
This commit is contained in:
+14
-2
@@ -73,7 +73,7 @@ class ServiceManager @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun startBackgroundService(tunnelConf: TunnelConf) {
|
||||
fun startTunnelForegroundService(tunnelConf: TunnelConf) {
|
||||
applicationScope.launch(ioDispatcher) {
|
||||
if (backgroundService.isCompleted) return@launch
|
||||
runCatching {
|
||||
@@ -88,7 +88,19 @@ class ServiceManager @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun stopBackgroundService() {
|
||||
fun updateTunnelForegroundServiceNotification(tunnelConf: TunnelConf) {
|
||||
applicationScope.launch(ioDispatcher) {
|
||||
if (!backgroundService.isCompleted) return@launch
|
||||
runCatching {
|
||||
val service = backgroundService.await()
|
||||
service.start(tunnelConf)
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun stopTunnelForegroundService() {
|
||||
applicationScope.launch(ioDispatcher) {
|
||||
if (!backgroundService.isCompleted) return@launch
|
||||
runCatching {
|
||||
|
||||
+1
-1
@@ -49,12 +49,12 @@ class TunnelForegroundService : LifecycleService() {
|
||||
}
|
||||
|
||||
fun stop() {
|
||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
serviceManager.backgroundService = CompletableDeferred()
|
||||
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.zaneschepke.wireguardautotunnel.core.tunnel
|
||||
|
||||
import com.wireguard.android.backend.BackendException
|
||||
import com.wireguard.android.backend.Tunnel
|
||||
import com.zaneschepke.networkmonitor.NetworkMonitor
|
||||
import com.zaneschepke.networkmonitor.NetworkStatus
|
||||
@@ -8,12 +9,10 @@ import com.zaneschepke.wireguardautotunnel.WireGuardAutoTunnel
|
||||
import com.zaneschepke.wireguardautotunnel.core.notification.NotificationManager
|
||||
import com.zaneschepke.wireguardautotunnel.core.notification.WireGuardNotification
|
||||
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
||||
import com.zaneschepke.wireguardautotunnel.core.tunnel.TunnelProvider.Companion.CHECK_INTERVAL
|
||||
import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
|
||||
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendError
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
|
||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelState
|
||||
@@ -22,24 +21,24 @@ import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
|
||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||
import com.zaneschepke.wireguardautotunnel.util.StringValue
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.asTunnelState
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.cancelWithMessage
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.toBackendError
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.cancelAndJoin
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
open class BaseTunnel(
|
||||
abstract class BaseTunnel(
|
||||
@IoDispatcher private val ioDispatcher: CoroutineDispatcher,
|
||||
@ApplicationScope private val applicationScope: CoroutineScope,
|
||||
private val networkMonitor: NetworkMonitor,
|
||||
@@ -48,167 +47,98 @@ open class BaseTunnel(
|
||||
private val notificationManager: NotificationManager,
|
||||
) : TunnelProvider {
|
||||
|
||||
companion object {
|
||||
const val CHECK_INTERVAL = 1000L
|
||||
}
|
||||
|
||||
internal val tunnels = MutableStateFlow<List<TunnelConf>>(emptyList())
|
||||
private val _activeTunnels = MutableStateFlow<Map<Int, TunnelState>>(emptyMap())
|
||||
override val activeTunnels = _activeTunnels.asStateFlow()
|
||||
|
||||
private val _tunnelStates = MutableStateFlow<Map<Int, TunnelState>>(emptyMap())
|
||||
|
||||
private val tunnelJobs = ConcurrentHashMap<Int, Job>()
|
||||
|
||||
private val isNetworkAvailable = AtomicBoolean(false)
|
||||
protected val tunnelJobs = ConcurrentHashMap<Int, MutableList<Job>>()
|
||||
private val mutex = Mutex()
|
||||
private val isNetworkConnected = MutableStateFlow(true)
|
||||
|
||||
init {
|
||||
applicationScope.launch(ioDispatcher) {
|
||||
launch { startNetworkJob() }
|
||||
launch { monitorNetworkStatus() }
|
||||
launch { monitorTunnelConfigChanges() }
|
||||
tunnels.collect { tuns ->
|
||||
val previousTunIds = tunnelJobs.keys.toSet()
|
||||
val currentTunIds = tuns.map { it.id }.toSet()
|
||||
val newTuns = tuns.filter { it.id !in previousTunIds }
|
||||
val removedTunIds = previousTunIds - currentTunIds
|
||||
launch { monitorTunnels() }
|
||||
}
|
||||
}
|
||||
|
||||
newTuns.forEach { tun ->
|
||||
Timber.d("Starting tunnel jobs for tun ${tun.name} (ID: ${tun.id})")
|
||||
tunnelJobs[tun.id] = startTunnelJobs(tun)
|
||||
private suspend fun monitorTunnels() {
|
||||
tunnels.collectLatest { newTunnels ->
|
||||
mutex.withLock {
|
||||
val previousIds = tunnelJobs.keys
|
||||
val currentIds = newTunnels.map { it.id }.toSet()
|
||||
val added = newTunnels.filter { it.id !in previousIds && it.isActive }
|
||||
val removed = previousIds - currentIds
|
||||
|
||||
added.forEach { tunnel ->
|
||||
Timber.d("Starting jobs for tunnel ${tunnel.id}: ${tunnel.tunName}")
|
||||
if (tunnelJobs[tunnel.id] == null) {
|
||||
tunnelJobs[tunnel.id] = mutableListOf(startTunnelJobs(tunnel))
|
||||
}
|
||||
}
|
||||
|
||||
removedTunIds.forEach { tunId ->
|
||||
tunnelJobs[tunId]?.cancelWithMessage("Canceling tunnel jobs for tunnel ID: $tunId")
|
||||
tunnelJobs.remove(tunId)
|
||||
_tunnelStates.update { it - tunId }
|
||||
removed.forEach { id ->
|
||||
Timber.d("Stopping jobs for tunnel $id")
|
||||
tunnelJobs[id]?.forEach { it.cancelAndJoin() }
|
||||
tunnelJobs.remove(id)
|
||||
_activeTunnels.update { it - id }
|
||||
serviceManager.updateTunnelTile()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startTunnelJobs(tunnel: TunnelConf) = applicationScope.launch(ioDispatcher) {
|
||||
launch { startTunnelStatisticsJob(tunnel) }
|
||||
if (tunnel.isPingEnabled) launch { startPingJob(tunnel) }
|
||||
}
|
||||
|
||||
private fun updateTunnelState(tunnelId: Int, newState: TunnelStatus) {
|
||||
Timber.d("Updating tunnel state for ID $tunnelId to $newState")
|
||||
_tunnelStates.update { current ->
|
||||
val currentState = current[tunnelId]
|
||||
val updatedState = currentState?.copy(state = newState) ?: TunnelState(state = newState)
|
||||
val newMap = current + (tunnelId to updatedState)
|
||||
Timber.d("New tunnel states: $newMap")
|
||||
newMap
|
||||
protected fun startTunnelJobs(tunnel: TunnelConf): Job {
|
||||
return applicationScope.launch(ioDispatcher) {
|
||||
val jobs = mutableListOf<Job>()
|
||||
jobs += launch { updateTunnelStatistics(tunnel) }
|
||||
if (tunnel.isPingEnabled) jobs += launch { monitorTunnelPing(tunnel) }
|
||||
jobs.forEach { it.join() }
|
||||
}
|
||||
}
|
||||
|
||||
internal fun beforeStartTunnel(tunnelConf: TunnelConf) {
|
||||
tunnelConf.setStateChangeCallback { state ->
|
||||
Timber.d("New tunnel state $state")
|
||||
when (state) {
|
||||
is Tunnel.State -> updateTunnelState(tunnelConf.id, state.asTunnelState())
|
||||
is org.amnezia.awg.backend.Tunnel.State -> updateTunnelState(tunnelConf.id, state.asTunnelState())
|
||||
}
|
||||
applicationScope.launch(ioDispatcher) {
|
||||
serviceManager.updateTunnelTile()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun startTunnel(tunnelConf: TunnelConf) {
|
||||
applicationScope.launch(ioDispatcher) {
|
||||
serviceManager.startBackgroundService(tunnelConf)
|
||||
appDataRepository.tunnels.save(tunnelConf.copy(isActive = true))
|
||||
addToActiveTunnels(tunnelConf)
|
||||
}
|
||||
}
|
||||
|
||||
override fun stopTunnel(tunnelConf: TunnelConf?) {
|
||||
// Default empty implementation; subclasses override
|
||||
}
|
||||
|
||||
override suspend fun bounceTunnel(tunnelConf: TunnelConf) {
|
||||
stopTunnel(tunnelConf)
|
||||
delay(1000)
|
||||
startTunnel(tunnelConf)
|
||||
}
|
||||
|
||||
override suspend fun setBackendState(backendState: BackendState, allowedIps: Collection<String>) {
|
||||
// Default empty implementation
|
||||
}
|
||||
|
||||
override suspend fun runningTunnelNames(): Set<String> {
|
||||
return emptySet()
|
||||
}
|
||||
|
||||
override fun getStatistics(tunnelConf: TunnelConf): TunnelStatistics {
|
||||
throw NotImplementedError("Get statistics not implemented in base class")
|
||||
}
|
||||
|
||||
override val activeTunnels: StateFlow<Map<Int, TunnelState>>
|
||||
get() = _tunnelStates.asStateFlow()
|
||||
|
||||
internal suspend fun onTunnelStop(tunnelConf: TunnelConf) {
|
||||
appDataRepository.tunnels.save(tunnelConf.copy(isActive = false))
|
||||
removeFromActiveTunnels(tunnelConf)
|
||||
if (tunnels.value.isEmpty()) serviceManager.stopBackgroundService()
|
||||
}
|
||||
|
||||
internal fun stopAllTunnels() {
|
||||
tunnels.value.forEach {
|
||||
stopTunnel(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addToActiveTunnels(conf: TunnelConf) {
|
||||
tunnels.update {
|
||||
it.toMutableList().apply {
|
||||
add(conf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeFromActiveTunnels(conf: TunnelConf) {
|
||||
tunnels.update {
|
||||
it.toMutableList().apply {
|
||||
remove(conf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun startNetworkJob() = coroutineScope {
|
||||
networkMonitor.getNetworkStatusFlow(includeWifiSsid = false, useRootShell = false)
|
||||
.flowOn(ioDispatcher).collect {
|
||||
isNetworkAvailable.set(it !is NetworkStatus.Disconnected)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun startPingJob(tunnel: TunnelConf) = coroutineScope {
|
||||
while (isActive) {
|
||||
private suspend fun updateTunnelStatistics(tunnel: TunnelConf) {
|
||||
while (true) {
|
||||
runCatching {
|
||||
if (isNetworkAvailable.get() && tunnel.isActive) {
|
||||
val stats = getStatistics(tunnel)
|
||||
updateTunnelState(tunnel.id, stats = stats)
|
||||
}.onFailure { e ->
|
||||
Timber.e(e, "Failed to update stats for ${tunnel.tunName}")
|
||||
}
|
||||
delay(CHECK_INTERVAL)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun monitorTunnelPing(tunnel: TunnelConf) {
|
||||
while (true) {
|
||||
runCatching {
|
||||
if (isNetworkConnected.value && tunnel.isActive) {
|
||||
val pingSuccess = tunnel.isTunnelPingable(ioDispatcher)
|
||||
handlePingResult(tunnel, pingSuccess)
|
||||
if (!pingSuccess) bounceTunnel(tunnel)
|
||||
}
|
||||
delay(tunnel.pingInterval ?: Constants.PING_INTERVAL)
|
||||
}.onFailure { e ->
|
||||
Timber.e(e, "Ping failed for ${tunnel.tunName}")
|
||||
}
|
||||
delay(tunnel.pingInterval ?: Constants.PING_INTERVAL)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun handlePingResult(tunnel: TunnelConf, pingSuccess: Boolean) {
|
||||
if (!pingSuccess) {
|
||||
if (isNetworkAvailable.get()) {
|
||||
Timber.i("Ping result: target was not reachable, bouncing the tunnel")
|
||||
bounceTunnel(tunnel)
|
||||
delay(tunnel.pingCooldown ?: Constants.PING_COOLDOWN)
|
||||
} else {
|
||||
Timber.i("Ping result: target was not reachable, but no network available")
|
||||
}
|
||||
} else {
|
||||
Timber.i("Ping result: all ping targets were reached successfully")
|
||||
protected fun handleBackendThrowable(throwable: Throwable) {
|
||||
val backendError = when (throwable) {
|
||||
is BackendException -> throwable.toBackendError()
|
||||
is org.amnezia.awg.backend.BackendException -> throwable.toBackendError()
|
||||
else -> BackendError.Unknown
|
||||
}
|
||||
}
|
||||
|
||||
internal fun handleBackendThrowable(backendError: BackendError) {
|
||||
val message = when (backendError) {
|
||||
BackendError.Config -> StringValue.StringResource(R.string.start_failed_config)
|
||||
BackendError.DNS -> StringValue.StringResource(R.string.dns_error)
|
||||
BackendError.Unauthorized -> StringValue.StringResource(R.string.unauthorized)
|
||||
BackendError.Unknown -> StringValue.StringResource(R.string.unknown_error)
|
||||
}
|
||||
if (WireGuardAutoTunnel.isForeground()) {
|
||||
SnackbarController.showMessage(message)
|
||||
@@ -224,33 +154,129 @@ open class BaseTunnel(
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun monitorTunnelConfigChanges() = coroutineScope {
|
||||
appDataRepository.tunnels.flow.collect { storageTuns ->
|
||||
storageTuns.forEach { storageTun ->
|
||||
val currentTun = tunnels.value.firstOrNull { it.id == storageTun.id }
|
||||
if (currentTun != null) {
|
||||
if (!currentTun.isQuickConfigMatching(storageTun)) {
|
||||
Timber.d("Tunnel config changed for ID $storageTun, bouncing tunnel")
|
||||
bounceTunnel(storageTun)
|
||||
protected fun updateTunnelState(tunnelId: Int, state: TunnelStatus? = null, stats: TunnelStatistics? = null) {
|
||||
applicationScope.launch(ioDispatcher) {
|
||||
mutex.withLock {
|
||||
_activeTunnels.update { current ->
|
||||
val existing = current[tunnelId] ?: TunnelState()
|
||||
val newState = state ?: existing.state
|
||||
if (existing.state == newState && stats == null) {
|
||||
Timber.d("Skipping redundant state update for $tunnelId: $newState")
|
||||
current
|
||||
} else {
|
||||
val updated = existing.copy(
|
||||
state = newState,
|
||||
statistics = stats ?: existing.statistics,
|
||||
)
|
||||
current + (tunnelId to updated)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun startTunnelStatisticsJob(tunnel: TunnelConf) = coroutineScope {
|
||||
while (this.isActive) {
|
||||
runCatching {
|
||||
val stats = getStatistics(tunnel)
|
||||
_tunnelStates.update { currentStates ->
|
||||
val updatedState = currentStates[tunnel.id]?.copy(statistics = stats)
|
||||
?: TunnelState(statistics = stats)
|
||||
currentStates + (tunnel.id to updatedState)
|
||||
protected suspend fun configureTunnel(tunnelConf: TunnelConf) {
|
||||
tunnelConf.setStateChangeCallback { state ->
|
||||
Timber.d("State change callback triggered for tunnel ${tunnelConf.id}: ${tunnelConf.tunName} with state $state at ${System.currentTimeMillis()}")
|
||||
when (state) {
|
||||
is Tunnel.State -> updateTunnelState(tunnelConf.id, state.asTunnelState())
|
||||
is org.amnezia.awg.backend.Tunnel.State -> updateTunnelState(tunnelConf.id, state.asTunnelState())
|
||||
}
|
||||
applicationScope.launch(ioDispatcher) { serviceManager.updateTunnelTile() }
|
||||
}
|
||||
}
|
||||
|
||||
override fun startTunnel(tunnelConf: TunnelConf) {
|
||||
applicationScope.launch(ioDispatcher) {
|
||||
mutex.withLock {
|
||||
val currentState = _activeTunnels.value[tunnelConf.id]?.state
|
||||
if (currentState == TunnelStatus.UP || currentState == TunnelStatus.STARTING) {
|
||||
Timber.w("Tunnel ${tunnelConf.id} is already $currentState, skipping start (possible duplicate call)")
|
||||
return@launch
|
||||
}
|
||||
|
||||
val existingNames = tunnels.value.map { it.tunName }.toSet()
|
||||
if (tunnelConf.tunName in existingNames && tunnels.value.any { it.id != tunnelConf.id && it.tunName == tunnelConf.tunName }) {
|
||||
Timber.w("Duplicate tunName ${tunnelConf.tunName} detected for tunnel ${tunnelConf.id}")
|
||||
}
|
||||
|
||||
val lockedConf = tunnelConf.copyWithCallback(isActive = true)
|
||||
Timber.d("Starting tunnel with TunnelConf: $lockedConf")
|
||||
if (tunnels.value.isEmpty()) {
|
||||
Timber.d("No active tunnels, starting background service for ${lockedConf.id}")
|
||||
serviceManager.startTunnelForegroundService(lockedConf)
|
||||
} else {
|
||||
Timber.d("Tunnels already active, updating service notification for ${lockedConf.id}")
|
||||
serviceManager.updateTunnelForegroundServiceNotification(lockedConf)
|
||||
}
|
||||
appDataRepository.tunnels.save(lockedConf)
|
||||
tunnels.update { current ->
|
||||
current.filter { it.id != lockedConf.id } + lockedConf
|
||||
}
|
||||
delay(CHECK_INTERVAL)
|
||||
}.onFailure { exception ->
|
||||
Timber.e(exception, "Failed to update tunnel statistics for ${tunnel.tunName}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun stopTunnel(tunnelConf: TunnelConf?) {
|
||||
tunnelConf?.let {
|
||||
applicationScope.launch(ioDispatcher) {
|
||||
mutex.withLock {
|
||||
val lockedConf = it.copyWithCallback(isActive = false)
|
||||
Timber.d("Stopping tunnel with TunnelConf: $lockedConf")
|
||||
tunnels.update { tunnels -> tunnels.filter { t -> t.id != lockedConf.id } }
|
||||
appDataRepository.tunnels.save(lockedConf)
|
||||
if (tunnels.value.isEmpty()) {
|
||||
Timber.d("No tunnels active, stopping background service")
|
||||
serviceManager.stopTunnelForegroundService()
|
||||
} else {
|
||||
Timber.d("Other tunnels still active, updating service notification")
|
||||
val nextActive = tunnels.value.firstOrNull { it.isActive }
|
||||
if (nextActive != null) {
|
||||
Timber.d("Next active tunnel: ${nextActive.id}")
|
||||
serviceManager.updateTunnelForegroundServiceNotification(nextActive)
|
||||
} else {
|
||||
Timber.w("No active tunnels found in _tunnels, forcing service stop")
|
||||
serviceManager.stopTunnelForegroundService()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun bounceTunnel(tunnelConf: TunnelConf) {
|
||||
stopTunnel(tunnelConf)
|
||||
delay(1000)
|
||||
startTunnel(tunnelConf)
|
||||
}
|
||||
|
||||
private suspend fun monitorNetworkStatus() {
|
||||
networkMonitor.getNetworkStatusFlow(includeWifiSsid = false, useRootShell = false)
|
||||
.flowOn(ioDispatcher)
|
||||
.collectLatest { status ->
|
||||
val isAvailable = status !is NetworkStatus.Disconnected
|
||||
isNetworkConnected.value = isAvailable
|
||||
Timber.d("Network status: $isAvailable")
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun monitorTunnelConfigChanges() {
|
||||
appDataRepository.tunnels.flow.collectLatest { storedTunnels ->
|
||||
mutex.withLock {
|
||||
storedTunnels.forEach { stored ->
|
||||
val current = tunnels.value.find { it.id == stored.id }
|
||||
if (current != null && !current.isQuickConfigMatching(stored)) {
|
||||
Timber.d("Config changed for ${stored.id}, bouncing")
|
||||
bounceTunnel(stored)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getStatistics(tunnelConf: TunnelConf): TunnelStatistics? {
|
||||
throw NotImplementedError("Must be implemented by subclass")
|
||||
}
|
||||
|
||||
override suspend fun runningTunnelNames(): Set<String> = tunnels.value.map { it.tunName }.toSet()
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.zaneschepke.wireguardautotunnel.core.tunnel
|
||||
|
||||
import com.wireguard.android.backend.Backend
|
||||
import com.wireguard.android.backend.BackendException
|
||||
import com.wireguard.android.backend.Tunnel
|
||||
import com.zaneschepke.networkmonitor.NetworkMonitor
|
||||
import com.zaneschepke.wireguardautotunnel.core.notification.NotificationManager
|
||||
@@ -10,14 +9,16 @@ import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
|
||||
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
|
||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
|
||||
import com.zaneschepke.wireguardautotunnel.domain.state.WireGuardStatistics
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.toBackendError
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import javax.inject.Inject
|
||||
|
||||
class KernelTunnel @Inject constructor(
|
||||
@@ -30,41 +31,88 @@ class KernelTunnel @Inject constructor(
|
||||
networkMonitor: NetworkMonitor,
|
||||
) : BaseTunnel(ioDispatcher, applicationScope, networkMonitor, appDataRepository, serviceManager, notificationManager) {
|
||||
|
||||
private val startedTunnels = ConcurrentHashMap<Int, TunnelConf>()
|
||||
|
||||
override fun startTunnel(tunnelConf: TunnelConf) {
|
||||
Timber.d("Starting tunnel ${tunnelConf.id} kernel")
|
||||
Timber.i("Starting tunnel ${tunnelConf.id} kernel")
|
||||
applicationScope.launch(ioDispatcher) {
|
||||
if (tunnels.value.any { it.id == tunnelConf.id }) return@launch Timber.w("Tunnel already running")
|
||||
runCatching {
|
||||
Timber.d("Setting backend state UP")
|
||||
super.beforeStartTunnel(tunnelConf)
|
||||
backend.setState(tunnelConf, Tunnel.State.UP, tunnelConf.toWgConfig())
|
||||
Timber.d("Calling super.startTunnel")
|
||||
updateTunnelState(tunnelConf.id, TunnelStatus.STARTING)
|
||||
Timber.d("Set STARTING state for tunnel ${tunnelConf.id} at ${System.currentTimeMillis()}")
|
||||
|
||||
runBlocking { configureTunnel(tunnelConf) }
|
||||
Timber.d("Callback set for tunnel ${tunnelConf.id} at ${System.currentTimeMillis()}")
|
||||
|
||||
super.startTunnel(tunnelConf)
|
||||
}.onFailure {
|
||||
Timber.e(it, "Failed to start tunnel ${tunnelConf.id} kernel")
|
||||
onTunnelStop(tunnelConf)
|
||||
if (it is BackendException) {
|
||||
handleBackendThrowable(it.toBackendError())
|
||||
Timber.d("Calling backend.setState UP for tunnel ${tunnelConf.id}")
|
||||
backend.setState(tunnelConf, Tunnel.State.UP, tunnelConf.toWgConfig())
|
||||
startedTunnels[tunnelConf.id] = tunnelConf
|
||||
|
||||
val backendState = backend.getState(tunnelConf)
|
||||
if (backendState == Tunnel.State.UP) {
|
||||
updateTunnelState(tunnelConf.id, TunnelStatus.UP)
|
||||
Timber.d("Confirmed UP state for tunnel ${tunnelConf.id} at ${System.currentTimeMillis()}")
|
||||
} else {
|
||||
Timber.e(it)
|
||||
Timber.w("Tunnel ${tunnelConf.id} not UP after setState, state: $backendState")
|
||||
}
|
||||
|
||||
// Start stats jobs only after UP is confirmed
|
||||
tunnelJobs[tunnelConf.id] = mutableListOf(startTunnelJobs(tunnelConf))
|
||||
Timber.d("Started stats jobs for tunnel ${tunnelConf.id} at ${System.currentTimeMillis()}")
|
||||
}.onFailure { exception ->
|
||||
Timber.e(exception, "Failed to start tunnel ${tunnelConf.id} kernel")
|
||||
stopTunnel(tunnelConf)
|
||||
handleBackendThrowable(exception)
|
||||
}.onSuccess {
|
||||
Timber.i("Tunnel ${tunnelConf.id} started successfully")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getStatistics(tunnelConf: TunnelConf): TunnelStatistics {
|
||||
return WireGuardStatistics(backend.getStatistics(tunnelConf))
|
||||
override fun getStatistics(tunnelConf: TunnelConf): TunnelStatistics? {
|
||||
return try {
|
||||
WireGuardStatistics(backend.getStatistics(tunnelConf))
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun stopTunnel(tunnelConf: TunnelConf?) {
|
||||
applicationScope.launch(ioDispatcher) {
|
||||
runCatching {
|
||||
tunnels.value.firstOrNull { it.id == tunnelConf?.id }?.let {
|
||||
backend.setState(it, Tunnel.State.DOWN, it.toWgConfig())
|
||||
onTunnelStop(it)
|
||||
} ?: stopAllTunnels()
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
val originalTunnel = tunnelConf?.let { startedTunnels.getOrDefault(it.id, null) }
|
||||
if (originalTunnel != null) {
|
||||
Timber.i(
|
||||
"Stopping tunnel ${originalTunnel.id} kernel",
|
||||
)
|
||||
// updateTunnelState(tunnelConf.id, TunnelStatus.STOPPING)
|
||||
backend.setState(originalTunnel, Tunnel.State.DOWN, originalTunnel.toWgConfig())
|
||||
super.stopTunnel(originalTunnel)
|
||||
startedTunnels.remove(originalTunnel.id)
|
||||
tunnelJobs[originalTunnel.id]?.forEach { it.cancel() }
|
||||
tunnelJobs.remove(originalTunnel.id)
|
||||
if (backend.getState(originalTunnel) == Tunnel.State.DOWN) {
|
||||
updateTunnelState(originalTunnel.id, TunnelStatus.DOWN)
|
||||
Timber.d("Confirmed DOWN state for tunnel ${originalTunnel.id}")
|
||||
}
|
||||
} else {
|
||||
Timber.w("Tunnel not found in startedTunnels, stopping all tunnels")
|
||||
startedTunnels.forEach { (_, config) ->
|
||||
// updateTunnelState(config.id, TunnelStatus.STOPPING)
|
||||
val state = backend.setState(config, Tunnel.State.DOWN, config.toWgConfig())
|
||||
super.stopTunnel(tunnelConf)
|
||||
if (state == Tunnel.State.DOWN) {
|
||||
startedTunnels.remove(config.id)
|
||||
tunnelJobs[config.id]?.forEach { it.cancel() }
|
||||
tunnelJobs.remove(config.id)
|
||||
updateTunnelState(config.id, TunnelStatus.DOWN)
|
||||
Timber.d("Confirmed DOWN state for tunnel ${config.id} after fallback")
|
||||
}
|
||||
}
|
||||
}
|
||||
}.onFailure { e ->
|
||||
Timber.e(e, "Failed to stop tunnel ${tunnelConf?.id}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ class TunnelManager @Inject constructor(
|
||||
return tunnelProviderFlow.value.runningTunnelNames()
|
||||
}
|
||||
|
||||
override fun getStatistics(tunnelConf: TunnelConf): TunnelStatistics {
|
||||
override fun getStatistics(tunnelConf: TunnelConf): TunnelStatistics? {
|
||||
return tunnelProviderFlow.value.getStatistics(tunnelConf)
|
||||
}
|
||||
|
||||
|
||||
@@ -7,15 +7,11 @@ import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
interface TunnelProvider {
|
||||
companion object {
|
||||
const val CHECK_INTERVAL = 1000L
|
||||
}
|
||||
|
||||
fun startTunnel(tunnelConf: TunnelConf)
|
||||
fun stopTunnel(tunnelConf: TunnelConf? = null)
|
||||
suspend fun bounceTunnel(tunnelConf: TunnelConf)
|
||||
suspend fun setBackendState(backendState: BackendState, allowedIps: Collection<String>)
|
||||
suspend fun runningTunnelNames(): Set<String>
|
||||
fun getStatistics(tunnelConf: TunnelConf): TunnelStatistics
|
||||
fun getStatistics(tunnelConf: TunnelConf): TunnelStatistics?
|
||||
val activeTunnels: StateFlow<Map<Int, TunnelState>>
|
||||
}
|
||||
|
||||
+87
-27
@@ -1,6 +1,5 @@
|
||||
package com.zaneschepke.wireguardautotunnel.core.tunnel
|
||||
|
||||
import com.wireguard.android.backend.BackendException
|
||||
import com.zaneschepke.networkmonitor.NetworkMonitor
|
||||
import com.zaneschepke.wireguardautotunnel.core.notification.NotificationManager
|
||||
import com.zaneschepke.wireguardautotunnel.core.service.ServiceManager
|
||||
@@ -8,17 +7,19 @@ import com.zaneschepke.wireguardautotunnel.di.ApplicationScope
|
||||
import com.zaneschepke.wireguardautotunnel.di.IoDispatcher
|
||||
import com.zaneschepke.wireguardautotunnel.domain.entity.TunnelConf
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.BackendState
|
||||
import com.zaneschepke.wireguardautotunnel.domain.enums.TunnelStatus
|
||||
import com.zaneschepke.wireguardautotunnel.domain.repository.AppDataRepository
|
||||
import com.zaneschepke.wireguardautotunnel.domain.state.AmneziaStatistics
|
||||
import com.zaneschepke.wireguardautotunnel.domain.state.TunnelStatistics
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.asAmBackendState
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.toBackendError
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.amnezia.awg.backend.Backend
|
||||
import org.amnezia.awg.backend.Tunnel
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import javax.inject.Inject
|
||||
|
||||
class UserspaceTunnel @Inject constructor(
|
||||
@@ -31,28 +32,55 @@ class UserspaceTunnel @Inject constructor(
|
||||
networkMonitor: NetworkMonitor,
|
||||
) : BaseTunnel(ioDispatcher, applicationScope, networkMonitor, appDataRepository, serviceManager, notificationManager) {
|
||||
|
||||
private val startedTunnels = ConcurrentHashMap<Int, TunnelConf>()
|
||||
|
||||
override fun startTunnel(tunnelConf: TunnelConf) {
|
||||
Timber.i("Starting tunnel ${tunnelConf.id} userspace")
|
||||
applicationScope.launch(ioDispatcher) {
|
||||
Timber.d("Starting tunnel ${tunnelConf.id} userspace")
|
||||
if (tunnels.value.any { it.id == tunnelConf.id }) return@launch Timber.w("Tunnel already running")
|
||||
if (tunnels.value.isNotEmpty()) {
|
||||
Timber.d("Stopping all tunnels")
|
||||
stopAllTunnels()
|
||||
}
|
||||
runCatching {
|
||||
Timber.d("Setting backend state UP")
|
||||
super.beforeStartTunnel(tunnelConf)
|
||||
backend.setState(tunnelConf, Tunnel.State.UP, tunnelConf.toAmConfig())
|
||||
Timber.d("Calling super.startTunnel")
|
||||
stopActiveTunnels(tunnelConf)
|
||||
updateTunnelState(tunnelConf.id, TunnelStatus.STARTING)
|
||||
Timber.d("Set STARTING state for tunnel ${tunnelConf.id} at ${System.currentTimeMillis()}")
|
||||
|
||||
runBlocking { configureTunnel(tunnelConf) }
|
||||
Timber.d("Callback set for tunnel ${tunnelConf.id} at ${System.currentTimeMillis()}")
|
||||
|
||||
super.startTunnel(tunnelConf)
|
||||
}.onFailure {
|
||||
Timber.e(it, "Failed to start tunnel ${tunnelConf.id} userspace")
|
||||
onTunnelStop(tunnelConf)
|
||||
if (it is BackendException) {
|
||||
handleBackendThrowable(it.toBackendError())
|
||||
Timber.d("Calling backend.setState UP for tunnel ${tunnelConf.id}")
|
||||
backend.setState(tunnelConf, org.amnezia.awg.backend.Tunnel.State.UP, tunnelConf.toAmConfig())
|
||||
startedTunnels[tunnelConf.id] = tunnelConf
|
||||
|
||||
val backendState = backend.getState(tunnelConf)
|
||||
if (backendState == org.amnezia.awg.backend.Tunnel.State.UP) {
|
||||
updateTunnelState(tunnelConf.id, TunnelStatus.UP)
|
||||
Timber.d("Confirmed UP state for tunnel ${tunnelConf.id} at ${System.currentTimeMillis()}")
|
||||
} else {
|
||||
Timber.e(it)
|
||||
Timber.w("Tunnel ${tunnelConf.id} not UP after setState, state: $backendState")
|
||||
}
|
||||
|
||||
// Start stats jobs only after UP is confirmed
|
||||
tunnelJobs[tunnelConf.id] = mutableListOf(startTunnelJobs(tunnelConf))
|
||||
Timber.d("Started stats jobs for tunnel ${tunnelConf.id} at ${System.currentTimeMillis()}")
|
||||
}.onFailure { exception ->
|
||||
Timber.e(exception, "Failed to start tunnel ${tunnelConf.id} userspace")
|
||||
stopTunnel(tunnelConf)
|
||||
handleBackendThrowable(exception)
|
||||
}.onSuccess {
|
||||
Timber.i("Tunnel ${tunnelConf.id} started successfully")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun stopActiveTunnels(tunnelConf: TunnelConf) {
|
||||
val runningTunnels = activeTunnels.value.filter { (id, state) ->
|
||||
id != tunnelConf.id && state.state == TunnelStatus.UP
|
||||
}
|
||||
runningTunnels.forEach { (id, _) ->
|
||||
val runningTunnel = startedTunnels[id]
|
||||
if (runningTunnel != null) {
|
||||
Timber.i("Stopping running tunnel ${runningTunnel.id} before starting ${tunnelConf.id}")
|
||||
stopTunnel(runningTunnel)
|
||||
delay(300)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,17 +88,44 @@ class UserspaceTunnel @Inject constructor(
|
||||
override fun stopTunnel(tunnelConf: TunnelConf?) {
|
||||
applicationScope.launch(ioDispatcher) {
|
||||
runCatching {
|
||||
tunnels.value.firstOrNull { it.id == tunnelConf?.id }?.let {
|
||||
backend.setState(it, Tunnel.State.DOWN, it.toAmConfig())
|
||||
onTunnelStop(it)
|
||||
} ?: stopAllTunnels()
|
||||
}.onFailure {
|
||||
Timber.e(it)
|
||||
val originalTunnel = tunnelConf?.let { startedTunnels.getOrDefault(it.id, null) }
|
||||
if (originalTunnel != null) {
|
||||
Timber.i(
|
||||
"Stopping tunnel ${originalTunnel.id} userspace",
|
||||
)
|
||||
// updateTunnelState(tunnelConf.id, TunnelStatus.STOPPING)
|
||||
backend.setState(originalTunnel, org.amnezia.awg.backend.Tunnel.State.DOWN, originalTunnel.toAmConfig())
|
||||
super.stopTunnel(originalTunnel)
|
||||
startedTunnels.remove(originalTunnel.id)
|
||||
tunnelJobs[originalTunnel.id]?.forEach { it.cancel() }
|
||||
tunnelJobs.remove(originalTunnel.id)
|
||||
if (backend.getState(originalTunnel) == org.amnezia.awg.backend.Tunnel.State.DOWN) {
|
||||
updateTunnelState(originalTunnel.id, TunnelStatus.DOWN)
|
||||
Timber.d("Confirmed DOWN state for tunnel ${originalTunnel.id}")
|
||||
}
|
||||
} else {
|
||||
Timber.w("Tunnel not found in startedTunnels, stopping all tunnels")
|
||||
startedTunnels.forEach { (_, config) ->
|
||||
// updateTunnelState(config.id, TunnelStatus.STOPPING)
|
||||
val state = backend.setState(config, org.amnezia.awg.backend.Tunnel.State.DOWN, config.toAmConfig())
|
||||
super.stopTunnel(tunnelConf)
|
||||
if (state == org.amnezia.awg.backend.Tunnel.State.DOWN) {
|
||||
startedTunnels.remove(config.id)
|
||||
tunnelJobs[config.id]?.forEach { it.cancel() }
|
||||
tunnelJobs.remove(config.id)
|
||||
updateTunnelState(config.id, TunnelStatus.DOWN)
|
||||
Timber.d("Confirmed DOWN state for tunnel ${config.id} after fallback")
|
||||
}
|
||||
}
|
||||
}
|
||||
}.onFailure { e ->
|
||||
Timber.e(e, "Failed to stop tunnel ${tunnelConf?.id}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun setBackendState(backendState: BackendState, allowedIps: Collection<String>) {
|
||||
Timber.d("Setting backend state: $backendState with allowedIps: $allowedIps")
|
||||
backend.setBackendState(backendState.asAmBackendState(), allowedIps)
|
||||
}
|
||||
|
||||
@@ -78,7 +133,12 @@ class UserspaceTunnel @Inject constructor(
|
||||
return backend.runningTunnelNames
|
||||
}
|
||||
|
||||
override fun getStatistics(tunnelConf: TunnelConf): TunnelStatistics {
|
||||
return AmneziaStatistics(backend.getStatistics(tunnelConf))
|
||||
override fun getStatistics(tunnelConf: TunnelConf): TunnelStatistics? {
|
||||
return try {
|
||||
AmneziaStatistics(backend.getStatistics(tunnelConf))
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Failed to get stats for ${tunnelConf.tunName}")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
package com.zaneschepke.wireguardautotunnel.domain.entity
|
||||
|
||||
import com.wireguard.android.backend.Tunnel
|
||||
import com.wireguard.config.Config
|
||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.isReachable
|
||||
import com.zaneschepke.wireguardautotunnel.util.extensions.toWgQuickString
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.Transient
|
||||
import org.amnezia.awg.backend.Tunnel
|
||||
import timber.log.Timber
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import java.io.InputStream
|
||||
import java.net.InetAddress
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.nio.charset.StandardCharsets
|
||||
|
||||
data class TunnelConf(
|
||||
val id: Int = 0,
|
||||
@@ -29,10 +32,41 @@ data class TunnelConf(
|
||||
val isIpv4Preferred: Boolean = true,
|
||||
@Transient
|
||||
private var stateChangeCallback: ((Any) -> Unit)? = null,
|
||||
) : Tunnel, com.wireguard.android.backend.Tunnel {
|
||||
) : Tunnel, org.amnezia.awg.backend.Tunnel {
|
||||
|
||||
fun setStateChangeCallback(callback: (Any) -> Unit) {
|
||||
stateChangeCallback = callback
|
||||
// Mutex to protect stateChangeCallback access
|
||||
private val callbackMutex = Mutex()
|
||||
|
||||
suspend fun setStateChangeCallback(callback: (Any) -> Unit) {
|
||||
callbackMutex.withLock {
|
||||
stateChangeCallback = callback
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure callback is copied over
|
||||
fun copyWithCallback(
|
||||
id: Int = this.id,
|
||||
tunName: String = this.tunName,
|
||||
wgQuick: String = this.wgQuick,
|
||||
tunnelNetworks: List<String> = this.tunnelNetworks,
|
||||
isMobileDataTunnel: Boolean = this.isMobileDataTunnel,
|
||||
isPrimaryTunnel: Boolean = this.isPrimaryTunnel,
|
||||
amQuick: String = this.amQuick,
|
||||
isActive: Boolean = this.isActive,
|
||||
isPingEnabled: Boolean = this.isPingEnabled,
|
||||
pingInterval: Long? = this.pingInterval,
|
||||
pingCooldown: Long? = this.pingCooldown,
|
||||
pingIp: String? = this.pingIp,
|
||||
isEthernetTunnel: Boolean = this.isEthernetTunnel,
|
||||
isIpv4Preferred: Boolean = this.isIpv4Preferred,
|
||||
): TunnelConf {
|
||||
return TunnelConf(
|
||||
id, tunName, wgQuick, tunnelNetworks, isMobileDataTunnel, isPrimaryTunnel,
|
||||
amQuick, isActive, isPingEnabled, pingInterval, pingCooldown, pingIp,
|
||||
isEthernetTunnel, isIpv4Preferred,
|
||||
).apply {
|
||||
stateChangeCallback = this@TunnelConf.stateChangeCallback
|
||||
}
|
||||
}
|
||||
|
||||
fun toAmConfig(): org.amnezia.awg.config.Config {
|
||||
@@ -43,25 +77,40 @@ data class TunnelConf(
|
||||
return configFromWgQuick(wgQuick)
|
||||
}
|
||||
|
||||
override fun getName(): String {
|
||||
return tunName
|
||||
}
|
||||
override fun getName(): String = tunName
|
||||
|
||||
override fun isIpv4ResolutionPreferred(): Boolean {
|
||||
return isIpv4Preferred
|
||||
}
|
||||
override fun isIpv4ResolutionPreferred(): Boolean = isIpv4Preferred
|
||||
|
||||
override fun onStateChange(newState: com.wireguard.android.backend.Tunnel.State) {
|
||||
stateChangeCallback?.invoke(newState)
|
||||
override fun onStateChange(newState: org.amnezia.awg.backend.Tunnel.State) {
|
||||
Timber.d("onStateChange called for tunnel $id: $tunName with state $newState")
|
||||
runBlocking {
|
||||
callbackMutex.withLock {
|
||||
if (stateChangeCallback != null) {
|
||||
Timber.d("Invoking stateChangeCallback for tunnel $id: $tunName with state $newState")
|
||||
stateChangeCallback?.invoke(newState)
|
||||
} else {
|
||||
Timber.w("No stateChangeCallback set for tunnel $id: $tunName")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStateChange(newState: Tunnel.State) {
|
||||
stateChangeCallback?.invoke(newState)
|
||||
Timber.d("onStateChange called for tunnel $id: $tunName with state $newState")
|
||||
runBlocking {
|
||||
callbackMutex.withLock {
|
||||
if (stateChangeCallback != null) {
|
||||
Timber.d("Invoking stateChangeCallback for tunnel $id: $tunName with state $newState")
|
||||
stateChangeCallback?.invoke(newState)
|
||||
} else {
|
||||
Timber.w("No stateChangeCallback set for tunnel $id: $tunName")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun isQuickConfigMatching(updatedConf: TunnelConf): Boolean {
|
||||
return updatedConf.wgQuick == wgQuick ||
|
||||
updatedConf.amQuick == amQuick
|
||||
return updatedConf.wgQuick == wgQuick || updatedConf.amQuick == amQuick
|
||||
}
|
||||
|
||||
fun isPingConfigMatching(updatedConf: TunnelConf): Boolean {
|
||||
@@ -78,7 +127,6 @@ data class TunnelConf(
|
||||
return@withContext InetAddress.getByName(pingIp)
|
||||
.isReachable(Constants.PING_TIMEOUT.toInt())
|
||||
}
|
||||
Timber.i("Pinging all peers")
|
||||
config.peers.map { peer ->
|
||||
peer.isReachable(isIpv4Preferred)
|
||||
}.all { true }
|
||||
@@ -88,14 +136,14 @@ data class TunnelConf(
|
||||
companion object {
|
||||
fun configFromWgQuick(wgQuick: String): Config {
|
||||
val inputStream: InputStream = wgQuick.byteInputStream()
|
||||
return inputStream.bufferedReader(Charsets.UTF_8).use {
|
||||
return inputStream.bufferedReader(StandardCharsets.UTF_8).use {
|
||||
Config.parse(it)
|
||||
}
|
||||
}
|
||||
|
||||
fun configFromAmQuick(amQuick: String): org.amnezia.awg.config.Config {
|
||||
val inputStream: InputStream = amQuick.byteInputStream()
|
||||
return inputStream.bufferedReader(Charsets.UTF_8).use {
|
||||
return inputStream.bufferedReader(StandardCharsets.UTF_8).use {
|
||||
org.amnezia.awg.config.Config.parse(it)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,4 +4,5 @@ sealed class BackendError() {
|
||||
data object DNS : BackendError()
|
||||
data object Unauthorized : BackendError()
|
||||
data object Config : BackendError()
|
||||
data object Unknown : BackendError()
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.zaneschepke.wireguardautotunnel.domain.enums
|
||||
enum class TunnelStatus {
|
||||
UP,
|
||||
DOWN,
|
||||
STARTING,
|
||||
STOPPING,
|
||||
;
|
||||
|
||||
fun isDown(): Boolean {
|
||||
|
||||
Reference in New Issue
Block a user