From b0fe9d8520563e6ef3716446cd9e5d6d01466fea Mon Sep 17 00:00:00 2001 From: zaneschepke Date: Thu, 4 Jun 2026 11:14:54 -0400 Subject: [PATCH] fix: lockdown mode not fully cleaning up hev bridge --- .../AndroidTunnelNotificationService.kt | 97 ++++++++++--------- .../zaneschepke/tunnel/backend/TunnelActor.kt | 3 +- .../tunnel/backend/TunnelBackend.kt | 3 +- .../tunnel/backend/WireGuardTunnelEngine.kt | 40 +++++--- .../zaneschepke/tunnel/service/VpnService.kt | 5 + 5 files changed, 87 insertions(+), 61 deletions(-) diff --git a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/notification/AndroidTunnelNotificationService.kt b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/notification/AndroidTunnelNotificationService.kt index 457f909a..4b092d3d 100644 --- a/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/notification/AndroidTunnelNotificationService.kt +++ b/app/src/main/java/com/zaneschepke/wireguardautotunnel/core/notification/AndroidTunnelNotificationService.kt @@ -29,60 +29,61 @@ class AndroidTunnelNotificationService(private val notificationService: Notifica val context = notificationService.context - val formattedLines = tunnelNotificationLines.values.map { line -> - val status = line.displayState.asLocalizedString(context) - context.getString( - R.string.notification_tunnel_status_format, - line.name, - status, - ) - } + val formattedLines = + tunnelNotificationLines.values.map { line -> + val status = line.displayState.asLocalizedString(context) + context.getString(R.string.notification_tunnel_status_format, line.name, status) + } val description = formattedLines.joinToString("\n") - val actions = if (tunnelNotificationLines.size == 1) { - val tunnelId = tunnelNotificationLines.keys.first() - listOf( - notificationService.createNotificationAction( - notificationAction = NotificationAction.TUNNEL_OFF, - extraId = tunnelId, + val actions = + if (tunnelNotificationLines.size == 1) { + val tunnelId = tunnelNotificationLines.keys.first() + listOf( + notificationService.createNotificationAction( + notificationAction = NotificationAction.TUNNEL_OFF, + extraId = tunnelId, + ) ) - ) - } else { - listOf( - notificationService.createNotificationAction( - notificationAction = NotificationAction.STOP_ALL, - extraId = null, + } else { + listOf( + notificationService.createNotificationAction( + notificationAction = NotificationAction.STOP_ALL, + extraId = null, + ) ) + } + + val title = + when (channel) { + is NotificationChannels.Tunnel.VPN -> context.getString(R.string.vpn) + is NotificationChannels.Tunnel.Proxy -> context.getString(R.string.proxy) + } + + val style = + if (tunnelNotificationLines.size > 1) { + NotificationCompat.InboxStyle() + .setBigContentTitle(title) + .setSummaryText( + "${tunnelNotificationLines.size} ${context.getString(R.string.tunnels).lowercase()}" + ) + .also { inboxStyle -> formattedLines.forEach { inboxStyle.addLine(it) } } + } else { + null + } + + val notification = + notificationService.createNotification( + channel = channel, + title = title, + description = description, + actions = actions, + onGoing = true, + onlyAlertOnce = true, + groupKey = groupKey, + style = style, ) - } - - val title = when (channel) { - is NotificationChannels.Tunnel.VPN -> context.getString(R.string.vpn) - is NotificationChannels.Tunnel.Proxy -> context.getString(R.string.proxy) - } - - val style = if (tunnelNotificationLines.size > 1) { - NotificationCompat.InboxStyle() - .setBigContentTitle(title) - .setSummaryText("${tunnelNotificationLines.size} ${context.getString(R.string.tunnels).lowercase()}") - .also { inboxStyle -> - formattedLines.forEach { inboxStyle.addLine(it) } - } - } else { - null - } - - val notification = notificationService.createNotification( - channel = channel, - title = title, - description = description, - actions = actions, - onGoing = true, - onlyAlertOnce = true, - groupKey = groupKey, - style = style, - ) notificationService.show(notificationId, notification) } diff --git a/tunnel/src/main/java/com/zaneschepke/tunnel/backend/TunnelActor.kt b/tunnel/src/main/java/com/zaneschepke/tunnel/backend/TunnelActor.kt index 5990a310..430ea53b 100644 --- a/tunnel/src/main/java/com/zaneschepke/tunnel/backend/TunnelActor.kt +++ b/tunnel/src/main/java/com/zaneschepke/tunnel/backend/TunnelActor.kt @@ -29,6 +29,7 @@ import com.zaneschepke.tunnel.util.RootShellException import com.zaneschepke.tunnel.util.buildResolvedPeers import com.zaneschepke.tunnel.util.exponentialBackoffForever import kotlin.reflect.KClass +import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -208,7 +209,7 @@ internal class TunnelActor( try { cmd.cmds?.forEach { cmd -> - withTimeout(3_000) { + withTimeout(3_000.milliseconds) { withContext(Dispatchers.IO) { RootShell.run(cmd) } } } diff --git a/tunnel/src/main/java/com/zaneschepke/tunnel/backend/TunnelBackend.kt b/tunnel/src/main/java/com/zaneschepke/tunnel/backend/TunnelBackend.kt index bd8bf780..9ab7c2b9 100644 --- a/tunnel/src/main/java/com/zaneschepke/tunnel/backend/TunnelBackend.kt +++ b/tunnel/src/main/java/com/zaneschepke/tunnel/backend/TunnelBackend.kt @@ -154,7 +154,8 @@ class TunnelBackend( } override suspend fun stop(id: Int): Result = runCatching { - // TODO need a clean localized message for this passed by provider + // TODO need a clean localized message for this passed by provider, but this error should + // never happen val runtime = actor.state.value.byTunnelId[id] ?: throw BackendException.InternalError( diff --git a/tunnel/src/main/java/com/zaneschepke/tunnel/backend/WireGuardTunnelEngine.kt b/tunnel/src/main/java/com/zaneschepke/tunnel/backend/WireGuardTunnelEngine.kt index 686686e8..c103ca37 100644 --- a/tunnel/src/main/java/com/zaneschepke/tunnel/backend/WireGuardTunnelEngine.kt +++ b/tunnel/src/main/java/com/zaneschepke/tunnel/backend/WireGuardTunnelEngine.kt @@ -1,11 +1,13 @@ package com.zaneschepke.tunnel.backend +import android.net.TrafficStats import com.zaneschepke.tunnel.ProxyBackend import com.zaneschepke.tunnel.Tunnel import com.zaneschepke.tunnel.VpnBackend import com.zaneschepke.tunnel.model.BackendMode import com.zaneschepke.tunnel.model.ProxyConfig import com.zaneschepke.tunnel.service.VpnService +import com.zaneschepke.tunnel.service.VpnService.Companion.HEV_BRIDGE_TRAFFIC_TAG import com.zaneschepke.tunnel.state.EngineStartResult import com.zaneschepke.tunnel.state.EngineState import com.zaneschepke.tunnel.state.NativeTunnelStatus @@ -135,29 +137,45 @@ internal class WireGuardTunnelEngine( } @Throws(IOException::class) - private fun getAvailablePort(): Int { - ServerSocket(0).use { socket -> - socket.reuseAddress = true - return socket.localPort + fun getAvailablePort(): Int { + TrafficStats.setThreadStatsTag(HEV_BRIDGE_TRAFFIC_TAG) + + try { + ServerSocket(0).use { + return it.localPort + } + } finally { + TrafficStats.clearThreadStatsTag() } } - // omit peer endpoint while boostrapping + // omit peer endpoint while bootstrapping private fun rewriteDynamicEndpoint(peer: PeerSection): PeerSection { return peer.copy(endpoint = null) } override fun stop(handle: Int, mode: BackendMode) { when (mode) { - is BackendMode.Proxy -> { - ProxyBackend.awgTurnProxyTunnelOff(handle) - } - is BackendMode.Vpn -> { - VpnBackend.awgTurnOff(handle) - } + is BackendMode.Proxy.Standard -> stopProxyTunnel(handle) + is BackendMode.Vpn -> stopVpnTunnel(handle) + is BackendMode.Proxy.KillSwitchPrimary -> stopKillSwitchPrimaryTunnel(handle) } } + private fun stopKillSwitchPrimaryTunnel(handle: Int) { + ProxyBackend.awgTurnProxyTunnelOff(handle) + val service = serviceHolder.getVpnService() + service.stopHevSocks5Bridge() + } + + private fun stopProxyTunnel(handle: Int) { + ProxyBackend.awgTurnProxyTunnelOff(handle) + } + + private fun stopVpnTunnel(handle: Int) { + VpnBackend.awgTurnOff(handle) + } + private fun startVpnTunnel(tunnel: Tunnel, ifName: String, config: Config): Int { val service = serviceHolder.getVpnService() diff --git a/tunnel/src/main/java/com/zaneschepke/tunnel/service/VpnService.kt b/tunnel/src/main/java/com/zaneschepke/tunnel/service/VpnService.kt index cf1933cc..38bc5bfd 100644 --- a/tunnel/src/main/java/com/zaneschepke/tunnel/service/VpnService.kt +++ b/tunnel/src/main/java/com/zaneschepke/tunnel/service/VpnService.kt @@ -1,6 +1,7 @@ package com.zaneschepke.tunnel.service import android.content.Intent +import android.net.TrafficStats import android.os.Build import android.os.ParcelFileDescriptor import android.system.OsConstants @@ -105,6 +106,7 @@ class VpnService : android.net.VpnService(), KillSwitch, SocketProtector { private fun startHevBridge(port: Int, pass: String): Job { val job = serviceScope.launch { + TrafficStats.setThreadStatsTag(HEV_BRIDGE_TRAFFIC_TAG) try { val vpnFd = fd ?: throw IOException("No VPN interface fd available") @@ -145,6 +147,8 @@ class VpnService : android.net.VpnService(), KillSwitch, SocketProtector { Timber.e("Timed out waiting for SOCKS5 proxy to be ready") } catch (e: Exception) { Timber.e(e, "Failed to start HEV bridge") + } finally { + TrafficStats.clearThreadStatsTag() } } @@ -282,6 +286,7 @@ class VpnService : android.net.VpnService(), KillSwitch, SocketProtector { } companion object { + const val HEV_BRIDGE_TRAFFIC_TAG = 0xF00D private const val LOCKDOWN_SESSION_NAME = "Lockdown" private const val LOCALHOST = "127.0.0.1" private const val IPV4_INTERFACE_ADDRESS = "10.0.0.1"