fix: stop tunnel regression

Fixes regression where tunnel is stuck in on state after x amount of toggles. Closes #163

Adds obfuscation of potentially sensitive data from logs.
Closes #160

Adds hiding of FAB on scroll to allow users to toggle tunnels when they have many tunnel configs.
Closes #161
This commit is contained in:
Zane Schepke
2024-04-16 00:14:06 -04:00
parent a2b8eb5b0b
commit 5447ec73f7
18 changed files with 108 additions and 61 deletions
@@ -24,7 +24,7 @@ class NotificationActionReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent?) = goAsync {
try {
//TODO fix for manual start changes when enabled
serviceManager.stopVpnService(context)
serviceManager.stopVpnServiceForeground(context)
delay(Constants.TOGGLE_TUNNEL_DELAY)
serviceManager.startVpnServiceForeground(context)
} catch (e: Exception) {
@@ -3,5 +3,6 @@ package com.zaneschepke.wireguardautotunnel.service.foreground
enum class Action {
START,
START_FOREGROUND,
STOP
STOP,
STOP_FOREGROUND
}
@@ -24,7 +24,7 @@ open class ForegroundService : LifecycleService() {
when (action) {
Action.START.name,
Action.START_FOREGROUND.name -> startService(intent.extras)
Action.STOP.name, Action.STOP_FOREGROUND.name -> stopService()
Constants.ALWAYS_ON_VPN_ACTION -> {
Timber.i("Always-on VPN starting service")
startService(intent.extras)
@@ -37,16 +37,9 @@ open class ForegroundService : LifecycleService() {
"with a null intent. It has been probably restarted by the system.",
)
}
// by returning this we make sure the service is restarted if the system kills the service
return START_STICKY
}
override fun onDestroy() {
super.onDestroy()
Timber.d("The service has been destroyed")
stopService()
}
protected open fun startService(extras: Bundle?) {
if (isServiceStarted) return
Timber.d("Starting ${this.javaClass.simpleName}")
@@ -55,12 +48,8 @@ open class ForegroundService : LifecycleService() {
protected open fun stopService() {
Timber.d("Stopping ${this.javaClass.simpleName}")
try {
stopForeground(STOP_FOREGROUND_REMOVE)
stopSelf()
} catch (e: Exception) {
Timber.e(e)
}
stopForeground(STOP_FOREGROUND_REMOVE)
stopSelf()
isServiceStarted = false
}
}
@@ -23,9 +23,8 @@ class ServiceManager(private val appDataRepository: AppDataRepository) {
intent.component?.javaClass
try {
when (action) {
Action.START_FOREGROUND -> context.startForegroundService(intent)
Action.START -> context.startService(intent)
Action.STOP -> context.stopService(intent)
Action.START_FOREGROUND, Action.STOP_FOREGROUND -> context.startForegroundService(intent)
Action.START, Action.STOP -> context.startService(intent)
}
} catch (e: Exception) {
Timber.e(e.message)
@@ -46,6 +45,16 @@ class ServiceManager(private val appDataRepository: AppDataRepository) {
)
}
suspend fun stopVpnServiceForeground(context: Context, isManualStop: Boolean = false) {
if (isManualStop) onManualStop()
Timber.i("Stopping vpn service")
actionOnService(
Action.STOP_FOREGROUND,
context,
WireGuardTunnelService::class.java,
)
}
suspend fun stopVpnService(context: Context, isManualStop: Boolean = false) {
if (isManualStop) onManualStop()
Timber.i("Stopping vpn service")
@@ -18,6 +18,7 @@ import com.zaneschepke.wireguardautotunnel.service.notification.NotificationServ
import com.zaneschepke.wireguardautotunnel.service.tunnel.VpnService
import com.zaneschepke.wireguardautotunnel.util.Constants
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
@@ -56,7 +57,7 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
private val networkEventsFlow = MutableStateFlow(WatcherState())
private lateinit var watcherJob: Job
private var watcherJob: Job? = null
private var wakeLock: PowerManager.WakeLock? = null
private val tag = this.javaClass.name
@@ -74,11 +75,6 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
}
}
override fun onDestroy() {
super.onDestroy()
stopService()
}
override fun startService(extras: Bundle?) {
super.startService(extras)
try {
@@ -139,8 +135,10 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
}
private fun cancelWatcherJob() {
if (this::watcherJob.isInitialized) {
watcherJob.cancel()
try {
watcherJob?.cancel()
} catch (e : CancellationException) {
Timber.i("Watcher job cancelled")
}
}
@@ -236,7 +234,7 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
}
if (results.contains(false)) {
Timber.i("Restarting VPN for ping failure")
serviceManager.stopVpnService(this)
serviceManager.stopVpnServiceForeground(this)
delay(Constants.VPN_RESTART_DELAY)
serviceManager.startVpnServiceForeground(this)
delay(Constants.PING_COOLDOWN)
@@ -324,7 +322,9 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
)
val ssid = wifiService.getNetworkName(it.networkCapabilities)
ssid?.let {
Timber.i("Detected SSID: $ssid")
if(it.contains(Constants.UNREADABLE_SSID)) {
Timber.w("SSID unreadable: missing permissions")
} else Timber.i("Detected valid SSID")
appDataRepository.appState.setCurrentSsid(ssid)
networkEventsFlow.value =
networkEventsFlow.value.copy(
@@ -381,7 +381,7 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
watcherState.isTunnelOffOnMobileDataConditionMet() -> {
Timber.i("$autoTunnel - tunnel off on mobile data met, turning vpn off")
serviceManager.stopVpnService(this)
serviceManager.stopVpnServiceForeground(this)
}
watcherState.isTunnelNotWifiNamePreferredMet(watcherState.currentNetworkSSID) -> {
@@ -407,17 +407,17 @@ class WireGuardConnectivityWatcherService : ForegroundService() {
watcherState.isTrustedWifiConditionMet() -> {
Timber.i("$autoTunnel - tunnel off on trusted wifi condition met, turning vpn off")
serviceManager.stopVpnService(this)
serviceManager.stopVpnServiceForeground(this)
}
watcherState.isTunnelOffOnWifiConditionMet() -> {
Timber.i("$autoTunnel - tunnel off on wifi condition met, turning vpn off")
serviceManager.stopVpnService(this)
serviceManager.stopVpnServiceForeground(this)
}
watcherState.isTunnelOffOnNoConnectivityMet() -> {
Timber.i("$autoTunnel - tunnel off on no connectivity met, turning vpn off")
serviceManager.stopVpnService(this)
serviceManager.stopVpnServiceForeground(this)
}
else -> {
@@ -16,10 +16,12 @@ import com.zaneschepke.wireguardautotunnel.util.Constants
import com.zaneschepke.wireguardautotunnel.util.handshakeStatus
import com.zaneschepke.wireguardautotunnel.util.mapPeerStats
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@AndroidEntryPoint
@@ -35,7 +37,7 @@ class WireGuardTunnelService : ForegroundService() {
@Inject
lateinit var notificationService: NotificationService
private lateinit var job: Job
private var job: Job? = null
private var didShowConnected = false
@@ -49,11 +51,6 @@ class WireGuardTunnelService : ForegroundService() {
}
}
override fun onDestroy() {
super.onDestroy()
}
override fun startService(extras: Bundle?) {
super.startService(extras)
cancelJob()
@@ -182,8 +179,10 @@ class WireGuardTunnelService : ForegroundService() {
}
private fun cancelJob() {
if (this::job.isInitialized) {
job.cancel()
try {
job?.cancel()
} catch (e : CancellationException) {
Timber.i("Tunnel job cancelled")
}
}
}
@@ -40,7 +40,7 @@ class ShortcutsActivity : ComponentActivity() {
this@ShortcutsActivity, tunnelConfig?.id, isManualStart = true,
)
Action.STOP.name -> serviceManager.stopVpnService(
Action.STOP.name -> serviceManager.stopVpnServiceForeground(
this@ShortcutsActivity,
isManualStop = true,
)
@@ -80,7 +80,7 @@ class TunnelControlTile : TileService() {
scope.launch {
try {
if (vpnService.getState() == Tunnel.State.UP) {
serviceManager.stopVpnService(
serviceManager.stopVpnServiceForeground(
this@TunnelControlTile,
isManualStop = true,
)
@@ -10,6 +10,7 @@ import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.module.Kernel
import com.zaneschepke.wireguardautotunnel.module.Userspace
import com.zaneschepke.wireguardautotunnel.util.Constants
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@@ -33,7 +34,7 @@ constructor(
private val scope = CoroutineScope(Dispatchers.IO)
private lateinit var statsJob: Job
private var statsJob: Job? = null
private var backend: Backend = userspaceBackend
@@ -134,8 +135,10 @@ constructor(
}
}
if (state == State.DOWN) {
if (this::statsJob.isInitialized) {
statsJob.cancel()
try {
statsJob?.cancel()
} catch (e : CancellationException) {
Timber.i("Stats job cancelled")
}
}
}
@@ -118,12 +118,12 @@ constructor(
fun readLogCatOutput() =
viewModelScope.launch(viewModelScope.coroutineContext + Dispatchers.IO) {
launch {
Logcatter.logs {
Logcatter.logs(callback = {
logs.add(it)
if (logs.size > Constants.LOG_BUFFER_SIZE) {
logs.removeRange(0, (logs.size - Constants.LOG_BUFFER_SIZE).toInt())
}
}
})
}
}
@@ -71,8 +71,12 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalHapticFeedback
@@ -124,6 +128,25 @@ fun MainScreen(
val sheetState = rememberModalBottomSheetState()
var showBottomSheet by remember { mutableStateOf(false) }
// Nested scroll for control FAB
val nestedScrollConnection = remember {
object : NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
// Hide FAB
if (available.y < -1) {
isVisible.value = false
}
// Show FAB
if (available.y > 1) {
isVisible.value = true
}
return Offset.Zero
}
}
}
var showDeleteTunnelAlertDialog by remember { mutableStateOf(false) }
var selectedTunnel by remember { mutableStateOf<TunnelConfig?>(null) }
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
@@ -377,8 +400,8 @@ fun MainScreen(
verticalArrangement = Arrangement.Top,
modifier =
Modifier
.fillMaxWidth()
.overscroll(ScrollableDefaults.overscrollEffect()),
.fillMaxSize()
.overscroll(ScrollableDefaults.overscrollEffect()).nestedScroll(nestedScrollConnection),
state = rememberLazyListState(0, uiState.tunnels.count()),
userScrollEnabled = true,
reverseLayout = false,
@@ -35,4 +35,6 @@ object Constants {
const val TUNNEL_EXTRA_KEY = "tunnelId"
const val UNREADABLE_SSID = "<unknown ssid>"
}