feat: get ssid with root shell

closes #322
This commit is contained in:
Zane Schepke 2024-10-17 22:40:06 -04:00
parent 09b669f54b
commit bc811f74ef
7 changed files with 86 additions and 48 deletions

View File

@ -2,11 +2,13 @@ package com.zaneschepke.wireguardautotunnel.service.foreground
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.NetworkCapabilities
import android.os.IBinder import android.os.IBinder
import android.os.PowerManager import android.os.PowerManager
import androidx.core.app.ServiceCompat import androidx.core.app.ServiceCompat
import androidx.lifecycle.LifecycleService import androidx.lifecycle.LifecycleService
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.wireguard.android.util.RootShell
import com.zaneschepke.wireguardautotunnel.R import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.data.domain.Settings import com.zaneschepke.wireguardautotunnel.data.domain.Settings
import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig import com.zaneschepke.wireguardautotunnel.data.domain.TunnelConfig
@ -23,6 +25,7 @@ import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelService
import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState import com.zaneschepke.wireguardautotunnel.service.tunnel.TunnelState
import com.zaneschepke.wireguardautotunnel.util.Constants import com.zaneschepke.wireguardautotunnel.util.Constants
import com.zaneschepke.wireguardautotunnel.util.extensions.cancelWithMessage import com.zaneschepke.wireguardautotunnel.util.extensions.cancelWithMessage
import com.zaneschepke.wireguardautotunnel.util.extensions.getCurrentWifiName
import com.zaneschepke.wireguardautotunnel.util.extensions.isMatchingToWildcardList import com.zaneschepke.wireguardautotunnel.util.extensions.isMatchingToWildcardList
import com.zaneschepke.wireguardautotunnel.util.extensions.isReachable import com.zaneschepke.wireguardautotunnel.util.extensions.isReachable
import com.zaneschepke.wireguardautotunnel.util.extensions.onNotRunning import com.zaneschepke.wireguardautotunnel.util.extensions.onNotRunning
@ -46,6 +49,9 @@ import javax.inject.Provider
class AutoTunnelService : LifecycleService() { class AutoTunnelService : LifecycleService() {
private val foregroundId = 122 private val foregroundId = 122
@Inject
lateinit var rootShell: Provider<RootShell>
@Inject @Inject
lateinit var wifiService: NetworkService<WifiService> lateinit var wifiService: NetworkService<WifiService>
@ -397,6 +403,14 @@ class AutoTunnelService : LifecycleService() {
} }
} }
private fun updateWifi(connected: Boolean) {
autoTunnelStateFlow.update {
it.copy(
isWifiConnected = connected,
)
}
}
private suspend fun watchForEthernetConnectivityChanges() { private suspend fun watchForEthernetConnectivityChanges() {
withContext(ioDispatcher) { withContext(ioDispatcher) {
Timber.i("Starting ethernet data watcher") Timber.i("Starting ethernet data watcher")
@ -428,21 +442,13 @@ class AutoTunnelService : LifecycleService() {
when (status) { when (status) {
is NetworkStatus.Available -> { is NetworkStatus.Available -> {
Timber.i("Gained Wi-Fi connection") Timber.i("Gained Wi-Fi connection")
autoTunnelStateFlow.update { updateWifi(true)
it.copy(
isWifiConnected = true,
)
}
} }
is NetworkStatus.CapabilitiesChanged -> { is NetworkStatus.CapabilitiesChanged -> {
Timber.i("Wifi capabilities changed") Timber.i("Wifi capabilities changed")
autoTunnelStateFlow.update { updateWifi(true)
it.copy( val ssid = getWifiSSID(status.networkCapabilities)
isWifiConnected = true,
)
}
val ssid = wifiService.getNetworkName(status.networkCapabilities)
ssid?.let { name -> ssid?.let { name ->
if (name.contains(Constants.UNREADABLE_SSID)) { if (name.contains(Constants.UNREADABLE_SSID)) {
Timber.w("SSID unreadable: missing permissions") Timber.w("SSID unreadable: missing permissions")
@ -459,11 +465,7 @@ class AutoTunnelService : LifecycleService() {
} }
is NetworkStatus.Unavailable -> { is NetworkStatus.Unavailable -> {
autoTunnelStateFlow.update { updateWifi(false)
it.copy(
isWifiConnected = false,
)
}
Timber.i("Lost Wi-Fi connection") Timber.i("Lost Wi-Fi connection")
} }
} }
@ -471,6 +473,16 @@ class AutoTunnelService : LifecycleService() {
} }
} }
private suspend fun getWifiSSID(networkCapabilities: NetworkCapabilities): String? {
return withContext(ioDispatcher) {
try {
rootShell.get().getCurrentWifiName()
} catch (_: Exception) {
wifiService.getNetworkName(networkCapabilities)
}
}
}
private suspend fun getMobileDataTunnel(): TunnelConfig? { private suspend fun getMobileDataTunnel(): TunnelConfig? {
return appDataRepository.tunnels.findByMobileDataTunnel().firstOrNull() return appDataRepository.tunnels.findByMobileDataTunnel().firstOrNull()
} }

View File

@ -1,7 +1,6 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.settings package com.zaneschepke.wireguardautotunnel.ui.screens.settings
import android.Manifest import android.Manifest
import android.app.Activity
import android.content.Context.POWER_SERVICE import android.content.Context.POWER_SERVICE
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
@ -101,7 +100,7 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
val interactionSource = remember { MutableInteractionSource() } val interactionSource = remember { MutableInteractionSource() }
val isRunningOnTv = context.isRunningOnTv() val isRunningOnTv = context.isRunningOnTv()
val kernelSupport by viewModel.kernelSupport.collectAsStateWithLifecycle() val settingsUiState by viewModel.uiState.collectAsStateWithLifecycle()
val fineLocationState = rememberPermissionState(Manifest.permission.ACCESS_FINE_LOCATION) val fineLocationState = rememberPermissionState(Manifest.permission.ACCESS_FINE_LOCATION)
var currentText by remember { mutableStateOf("") } var currentText by remember { mutableStateOf("") }
@ -115,10 +114,6 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
val screenPadding = 5.dp val screenPadding = 5.dp
val fillMaxWidth = .85f val fillMaxWidth = .85f
LaunchedEffect(Unit) {
viewModel.checkKernelSupport()
}
LaunchedEffect(uiState.settings.trustedNetworkSSIDs) { LaunchedEffect(uiState.settings.trustedNetworkSSIDs) {
currentText = "" currentText = ""
} }
@ -127,7 +122,7 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
rememberLauncherForActivityResult( rememberLauncherForActivityResult(
ActivityResultContracts.StartActivityForResult(), ActivityResultContracts.StartActivityForResult(),
) { result: ActivityResult -> ) { result: ActivityResult ->
if (result.resultCode == Activity.RESULT_OK) { if (result.resultCode == RESULT_OK) {
result.data result.data
// Handle the Intent // Handle the Intent
} }
@ -262,6 +257,18 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
) )
} }
fun onAutoTunnelWifiChecked() {
when (false) {
isBackgroundLocationGranted -> showLocationDialog = true
fineLocationState.status.isGranted -> showLocationDialog = true
viewModel.isLocationEnabled(context) ->
showLocationServicesAlertDialog = true
else -> {
viewModel.onToggleTunnelOnWifi()
}
}
}
Column( Column(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Top, verticalArrangement = Arrangement.Top,
@ -311,18 +318,8 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
checked = uiState.settings.isTunnelOnWifiEnabled, checked = uiState.settings.isTunnelOnWifiEnabled,
padding = screenPadding, padding = screenPadding,
onCheckChanged = { checked -> onCheckChanged = { checked ->
if (!checked) viewModel.onToggleTunnelOnWifi() if (!checked || settingsUiState.isRooted) viewModel.onToggleTunnelOnWifi().also { return@ConfigurationToggle }
if (checked) { onAutoTunnelWifiChecked()
when (false) {
isBackgroundLocationGranted -> showLocationDialog = true
fineLocationState.status.isGranted -> showLocationDialog = true
viewModel.isLocationEnabled(context) ->
showLocationServicesAlertDialog = true
else -> {
viewModel.onToggleTunnelOnWifi()
}
}
}
}, },
modifier = modifier =
if (uiState.settings.isAutoTunnelEnabled) { if (uiState.settings.isAutoTunnelEnabled) {
@ -491,7 +488,7 @@ fun SettingsScreen(viewModel: SettingsViewModel = hiltViewModel(), appViewModel:
uiState.settings.isAutoTunnelEnabled || uiState.settings.isAutoTunnelEnabled ||
uiState.settings.isAlwaysOnVpnEnabled || uiState.settings.isAlwaysOnVpnEnabled ||
(uiState.vpnState.status == TunnelState.UP) || (uiState.vpnState.status == TunnelState.UP) ||
!kernelSupport !settingsUiState.isKernelAvailable
), ),
checked = uiState.settings.isKernelEnabled, checked = uiState.settings.isKernelEnabled,
padding = screenPadding, padding = screenPadding,

View File

@ -0,0 +1,6 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.settings
data class SettingsUiState(
val isRooted: Boolean = false,
val isKernelAvailable: Boolean = false,
)

View File

@ -13,13 +13,14 @@ import com.zaneschepke.wireguardautotunnel.data.repository.AppDataRepository
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager import com.zaneschepke.wireguardautotunnel.service.foreground.ServiceManager
import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController import com.zaneschepke.wireguardautotunnel.ui.common.snackbar.SnackbarController
import com.zaneschepke.wireguardautotunnel.util.Constants
import com.zaneschepke.wireguardautotunnel.util.FileUtils import com.zaneschepke.wireguardautotunnel.util.FileUtils
import com.zaneschepke.wireguardautotunnel.util.StringValue import com.zaneschepke.wireguardautotunnel.util.StringValue
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -38,8 +39,16 @@ constructor(
@IoDispatcher private val ioDispatcher: CoroutineDispatcher, @IoDispatcher private val ioDispatcher: CoroutineDispatcher,
) : ViewModel() { ) : ViewModel() {
private val _kernelSupport = MutableStateFlow(false) private val _uiState = MutableStateFlow(SettingsUiState())
val kernelSupport = _kernelSupport.asStateFlow() val uiState = _uiState.onStart {
_uiState.update {
it.copy(isKernelAvailable = isKernelSupported(), isRooted = isRooted())
}
}.stateIn(
viewModelScope,
SharingStarted.WhileSubscribed(Constants.SUBSCRIPTION_TIMEOUT),
SettingsUiState(),
)
private val settings = appDataRepository.settings.getSettingsFlow() private val settings = appDataRepository.settings.getSettingsFlow()
.stateIn(viewModelScope, SharingStarted.Eagerly, Settings()) .stateIn(viewModelScope, SharingStarted.Eagerly, Settings())
@ -211,14 +220,10 @@ constructor(
} }
} }
fun checkKernelSupport() = viewModelScope.launch { private suspend fun isKernelSupported(): Boolean {
val kernelSupport = return withContext(ioDispatcher) {
withContext(ioDispatcher) {
WgQuickBackend.hasKernelSupport() WgQuickBackend.hasKernelSupport()
} }
_kernelSupport.update {
kernelSupport
}
} }
fun onToggleRestartAtBoot() = viewModelScope.launch { fun onToggleRestartAtBoot() = viewModelScope.launch {
@ -231,6 +236,17 @@ constructor(
} }
} }
private suspend fun isRooted(): Boolean {
return try {
withContext(ioDispatcher) {
rootShell.get().start()
}
true
} catch (_: Exception) {
false
}
}
private suspend fun requestRoot(): Result<Unit> { private suspend fun requestRoot(): Result<Unit> {
return withContext(ioDispatcher) { return withContext(ioDispatcher) {
kotlin.runCatching { kotlin.runCatching {

View File

@ -7,6 +7,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.zaneschepke.logcatter.LogReader import com.zaneschepke.logcatter.LogReader
import com.zaneschepke.logcatter.model.LogMessage import com.zaneschepke.logcatter.model.LogMessage
import com.zaneschepke.wireguardautotunnel.R
import com.zaneschepke.wireguardautotunnel.module.IoDispatcher import com.zaneschepke.wireguardautotunnel.module.IoDispatcher
import com.zaneschepke.wireguardautotunnel.module.MainDispatcher import com.zaneschepke.wireguardautotunnel.module.MainDispatcher
import com.zaneschepke.wireguardautotunnel.util.Constants import com.zaneschepke.wireguardautotunnel.util.Constants
@ -15,7 +16,6 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.launchShareFile
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import com.zaneschepke.wireguardautotunnel.R
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import timber.log.Timber import timber.log.Timber

View File

@ -1,6 +1,7 @@
package com.zaneschepke.wireguardautotunnel.util.extensions package com.zaneschepke.wireguardautotunnel.util.extensions
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import com.wireguard.android.util.RootShell
import com.wireguard.config.Peer import com.wireguard.config.Peer
import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus import com.zaneschepke.wireguardautotunnel.service.tunnel.HandshakeStatus
import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics import com.zaneschepke.wireguardautotunnel.service.tunnel.statistics.TunnelStatistics
@ -78,3 +79,9 @@ fun Config.toWgQuickString(): String {
} }
return lines.joinToString(System.lineSeparator()) return lines.joinToString(System.lineSeparator())
} }
fun RootShell.getCurrentWifiName(): String? {
val response = mutableListOf<String>()
this.run(response, "dumpsys wifi | grep -o \"SSID: [^,]*\" | cut -d ' ' -f2- | tr -d '\"'")
return response.lastOrNull()
}

View File

@ -21,7 +21,7 @@ pinLockCompose = "1.0.4"
roomVersion = "2.6.1" roomVersion = "2.6.1"
timber = "5.0.1" timber = "5.0.1"
tunnel = "1.2.1" tunnel = "1.2.1"
androidGradlePlugin = "8.7.0" androidGradlePlugin = "8.7.1"
kotlin = "2.0.21" kotlin = "2.0.21"
ksp = "2.0.21-1.0.25" ksp = "2.0.21-1.0.25"
composeBom = "2024.09.03" composeBom = "2024.09.03"