fix: lockdown mode not fully cleaning up hev bridge

This commit is contained in:
zaneschepke
2026-06-04 11:14:54 -04:00
parent d80ea167f4
commit b0fe9d8520
5 changed files with 87 additions and 61 deletions
@@ -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)
}
@@ -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) }
}
}
@@ -154,7 +154,8 @@ class TunnelBackend(
}
override suspend fun stop(id: Int): Result<Unit> = 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(
@@ -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()
@@ -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"