Compare commits

...

8 Commits

Author SHA1 Message Date
zaneschepke 6c3c6891eb chore: release v5.0.2 2026-06-14 01:59:14 -04:00
zaneschepke af1848f12d fix: legacy mode triggering location pings more often than necessary due to network security check
#1062
2026-06-13 12:01:20 -04:00
zaneschepke 96cffdfa7d fix: optimize tunnel status callback 2026-06-13 00:51:41 -04:00
zaneschepke afebd975ea fix: remove automatic active tunnel to top, add scrollbars 2026-06-12 02:33:28 -04:00
zaneschepke 588a2a18bd fix: use testnet ip during bootstrap phase 2026-06-11 18:45:28 -04:00
zaneschepke 221b38a119 chore: change proxy mode wording for consistency
#1268
2026-06-10 12:19:11 -04:00
zaneschepke 0008d8b9bb chore: update docs and fastlane metadata for latest features
closes #1268
2026-06-10 12:12:16 -04:00
zaneschepke 9f85638b9a chore: fix whitespace in changelog 2026-06-08 20:47:24 -04:00
24 changed files with 215 additions and 92 deletions
+14 -17
View File
@@ -49,8 +49,8 @@ and [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/)
## About
WG Tunnel is an alternative Android client for WireGuard and AmneziaWG, inspired by the official WireGuard Android app. It fills gaps in the official client by adding advanced features like auto-tunneling (on-demand VPN activation), while seamlessly supporting both protocols across app modes—including Kernel (for direct WireGuard kernel integration; AmneziaWG not supported), VPN (standard system-level tunneling), Lockdown (a custom kill switch for leak prevention), and Proxy (built-in HTTP/SOCKS5 forwarding)—for enhanced privacy, censorship resistance, and flexibility.
WG Tunnel is an alternative Android client for WireGuard and AmneziaWG, inspired by the official WireGuard Android app. It fills gaps in the official client by adding advanced features like auto-tunneling, AmneziaWG support, different app modes like **Lockdown** (a custom kill switch for leak prevention), and **Local Proxy** (expose a tunnel over a local SOCKS5/HTTP proxy server) for enhanced privacy, censorship resistance, and flexibility.
</div>
<div style="text-align: left;">
@@ -67,21 +67,18 @@ WG Tunnel is an alternative Android client for WireGuard and AmneziaWG, inspired
## Features
- **Tunnel Import Methods**: Easily add tunnels using .conf files, ZIP archives, manual entry, or QR code scanning.
- **Auto-Tunneling**: Automatically activate tunnels based on Wi-Fi SSID, Ethernet connections, or mobile data networks.
- **Split Tunneling**: Flexible support for routing specific apps or traffic through the VPN.
- **WireGuard Modes**: Full compatibility with WireGuard in both kernel and userspace implementations.
- **AmneziaWG Integration**: Userspace mode for AmneziaWG, providing robust censorship evasion.
- **Always-On VPN**: Ensures continuous protection with Android's Always-On VPN feature.
- **Quick Controls**: Quick Settings tile and home screen shortcuts for easy VPN toggling.
- **Automation Support**: Intent-based automation for controlling tunnels.
- **Auto-Restore**: Seamlessly restores auto-tunneling and active tunnels after device restarts or app updates.
- **Proxying Options**: Built-in HTTP and SOCKS5 proxy support within tunnels.
- **Lockdown Mode**: Custom kill switch for maximum leak prevention and security.
- **Dynamic DNS Handling**: Detects and updates DNS changes without tunnel restarts.
- **Monitoring Tools**: Advanced tunnel monitoring features for tunnel performance monitoring.
- **Android TV Support**: Android TV support for secure streaming and browsing.
- **Advanced DNS**: DNS over HTTPS support for tunnel endpoint resolutions.
- **Auto-Tunneling:** Automatically activate tunnels based on your device's active network details.
- **Deferred Endpoint Bootstrapping:** Safely resolves endpoints and updates peers after the tunnel is up for better reliability and leak protection on startup.
- **Handshake Monitoring:** Real-time handshake monitoring for instant tunnel health feedback.
- **AmneziaWG Support:** Full support for AmneziaWG 2.0, providing robust censorship protection.
- **Split Tunneling:** Flexible support for routing specific apps or traffic through the VPN.
- **Local Proxy Mode:** Expose WireGuard tunnels over a local SOCKS5 or HTTP proxy to browsers or firewall apps (like AdGuard).
- **Lockdown Mode:** Advanced in-app kill switch that blocks all traffic while the tunnel is down.
- **Quick Controls:** Quick Settings tile and home screen shortcuts for easy toggling.
- **Remote Control Support:** Intent-based automation for controlling tunnels and auto-tunneling from automation apps (like Tasker).
- **Dynamic DNS Handling:** Automatically detect and update endpoints on server IP changes without requiring a restart.
- **IPv6 Endpoints:** Automatically upgrade to IPv6 endpoints or fall back to IPv4 based on network conditions without requiring a restart.
- **Android TV Support:** Full support for nearly all features on Android TV.
## Building
@@ -42,7 +42,9 @@ sealed class Route : NavKey {
@Keep @Serializable data object Display : Route()
@Keep @Serializable data object Tunnels : Route(), SecureRoute {
@Keep
@Serializable
data object Tunnels : Route(), SecureRoute {
override val requiresProtection: Boolean
get() = true
}
@@ -1,10 +1,12 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.logs.components
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material3.scrollbar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@@ -18,7 +20,13 @@ fun LogList(
) {
LazyColumn(
state = lazyColumnListState,
modifier = modifier.padding(horizontal = 12.dp),
modifier =
modifier
.padding(horizontal = 12.dp)
.scrollbar(
state = lazyColumnListState.scrollIndicatorState,
orientation = Orientation.Vertical,
),
verticalArrangement = Arrangement.spacedBy(2.dp),
) {
itemsIndexed(items = logs, key = { index, _ -> index }) { _, log -> LogItem(log = log) }
@@ -1,10 +1,12 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.support.donate.crypto
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.scrollbar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -13,10 +15,17 @@ import com.zaneschepke.wireguardautotunnel.ui.screens.support.donate.crypto.comp
@Composable
fun AddressesScreen() {
val scrollState = rememberScrollState()
Column(
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.Top,
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()),
modifier =
Modifier.fillMaxSize()
.verticalScroll(scrollState)
.scrollbar(
state = scrollState.scrollIndicatorState,
orientation = Orientation.Vertical,
),
) {
val clipboard = rememberClipboardHelper()
Address.allAddresses.forEach { AddressItem(it) { address -> clipboard.copy(address) } }
@@ -2,6 +2,7 @@ package com.zaneschepke.wireguardautotunnel.ui.screens.support.license.component
import LicenseFileEntry
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -9,11 +10,13 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.Launch
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.scrollbar
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -24,8 +27,17 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
@Composable
fun LicenseList(licenses: List<LicenseFileEntry>) {
val context = LocalContext.current
val lazyListState = rememberLazyListState()
LazyColumn(modifier = Modifier.fillMaxSize()) {
LazyColumn(
modifier =
Modifier.fillMaxSize()
.scrollbar(
state = lazyListState.scrollIndicatorState,
orientation = Orientation.Vertical,
),
state = lazyListState,
) {
items(licenses) { entry ->
Row(
verticalAlignment = Alignment.CenterVertically,
@@ -43,11 +43,7 @@ fun PeerStatisticsSection(peer: ActivePeer) {
color = color,
)
peer.endpoint?.let {
StatText(
stringResource(R.string.endpoint_template, it),
style = style,
color = color
)
StatText(stringResource(R.string.endpoint_template, it), style = style, color = color)
}
}
}
@@ -1,6 +1,7 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.components
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.ScrollableDefaults
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.size
@@ -12,6 +13,7 @@ import androidx.compose.foundation.rememberOverscrollEffect
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Circle
import androidx.compose.material3.Icon
import androidx.compose.material3.scrollbar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@@ -78,7 +80,11 @@ fun TunnelList(
viewModel.clearSelectedTunnels()
}
}
.overscroll(rememberOverscrollEffect()),
.overscroll(rememberOverscrollEffect())
.scrollbar(
state = lazyListState.scrollIndicatorState,
orientation = Orientation.Vertical,
),
state = lazyListState,
userScrollEnabled = true,
reverseLayout = false,
@@ -1,5 +1,6 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.settings.config
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
@@ -9,6 +10,7 @@ import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.scrollbar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
@@ -57,6 +59,8 @@ fun ConfigScreen(
var showQrModal by rememberSaveable { mutableStateOf(false) }
val scrollState = rememberScrollState()
val rawConfig by
remember(liveConfig, uiState.activeConfig, uiState.tunnel?.quickConfig) {
derivedStateOf {
@@ -90,7 +94,13 @@ fun ConfigScreen(
Column(
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()),
modifier =
Modifier.fillMaxSize()
.verticalScroll(scrollState)
.scrollbar(
state = scrollState.scrollIndicatorState,
orientation = Orientation.Vertical,
),
) {
val displayText by
remember(rawConfig, showKeys) { derivedStateOf { maskSensitive(rawConfig, showKeys) } }
@@ -1,5 +1,6 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.settings.config.edit
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
@@ -12,6 +13,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.HdrAuto
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.material3.scrollbar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -49,11 +51,12 @@ fun ConfigEditScreen(
val uiState by viewModel.collectAsState()
if (uiState.isLoading) return
val locale = Locale.current.platformLocale
var showSelectionDialog by rememberSaveable { mutableStateOf(false) }
val scrollState = rememberScrollState()
sharedViewModel.collectSideEffect { sideEffect ->
when (sideEffect) {
is LocalSideEffect.SaveChanges -> {
@@ -104,7 +107,14 @@ fun ConfigEditScreen(
Column(
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.Top),
modifier = Modifier.fillMaxSize().imePadding().verticalScroll(rememberScrollState()),
modifier =
Modifier.fillMaxSize()
.imePadding()
.verticalScroll(scrollState)
.scrollbar(
state = scrollState.scrollIndicatorState,
orientation = Orientation.Vertical,
),
) {
if (uiState.isGlobalConfig) {
Column {
@@ -1,5 +1,6 @@
package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.sort
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.ScrollableDefaults
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
@@ -17,6 +18,7 @@ import androidx.compose.material.icons.filled.ArrowUpward
import androidx.compose.material.icons.filled.DragHandle
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.scrollbar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -107,7 +109,11 @@ fun SortScreen(sharedViewModel: SharedAppViewModel = koinActivityViewModel()) {
Modifier.pointerInput(Unit) {
if (tunnelsUiState.tunnels.isEmpty()) return@pointerInput
}
.overscroll(rememberOverscrollEffect()),
.overscroll(rememberOverscrollEffect())
.scrollbar(
state = lazyListState.scrollIndicatorState,
orientation = Orientation.Vertical,
),
state = lazyListState,
userScrollEnabled = true,
reverseLayout = false,
@@ -73,13 +73,7 @@ class SharedAppViewModel(
tunnelCoordinator.backendStatus,
selectedTunnelsRepository.flow,
) { tunnels, backendStatus, selectedTuns ->
val activeTunnelIds = backendStatus.activeTunnels.keys
val sortedTunnels =
tunnels.sortedWith(
compareByDescending<TunnelConfig> { it.id in activeTunnelIds }
.thenBy { it.position }
)
val sortedTunnels = tunnels.sortedBy { it.position }
val displayStates =
backendStatus.activeTunnels.mapValues { (_, activeTunnel) ->
+2 -2
View File
@@ -1,6 +1,6 @@
object Constants {
const val VERSION_NAME = "5.0.1"
const val VERSION_CODE = 50001
const val VERSION_NAME = "5.0.2"
const val VERSION_CODE = 50002
const val TARGET_SDK = 37
const val MIN_SDK = 26
@@ -0,0 +1,4 @@
What's new:
- Battery usage optimizations
- Legacy Wi-Fi mode location access optimizations
- Revert active to top of tunnel list
@@ -1,13 +1,17 @@
WG Tunnel is a WireGuard VPN client that strikes the balance between simplicity and robustness, making it the ideal client for casual and power users alike.
Whether you simply want to automate when you're connected to your VPN or you're a power user with advanced privacy use cases, WG Tunnel has you covered.
- **Auto-Tunneling:** Automatically activate tunnels based on Wi-Fi SSID, Ethernet connections, or mobile data networks.
Features:
- **Auto-Tunneling:** Automatically activate tunnels based on your device's active network details.
- **Deferred Endpoint Bootstrapping:** Safely resolves endpoints and updates peers after the tunnel is up for better reliability and leak protection on startup.
- **Handshake Monitoring:** Real-time handshake monitoring for instant tunnel health feedback.
- **AmneziaWG Support:** Full support for AmneziaWG 2.0, providing robust censorship protection.
- **Split Tunneling:** Flexible support for routing specific apps or traffic through the VPN.
- **App Modes:** Support for multiple tunnel modes, including standard VPN, kernel, lockdown (custom kill switch), and proxy modes.
- **AmneziaWG Integration:** Full support for AmneziaWG, providing robust censorship evasion.
- **Proxying Options:** Built-in HTTP and SOCKS5 proxy support allowing third-party apps to tunnel their traffic.
- **Quick Controls:** Quick Settings tile and home screen shortcuts for easy toggling actions.
- **Automation Support:** Intent-based automation for controlling tunnels and auto-tunneling.
- **Dynamic DNS Handling:** Detects and updates DNS changes without tunnel restarts.
- **Monitoring Tools:** Advanced tunnel monitoring features for tunnel performance monitoring.
- **Android TV Support:** Android TV support for nearly all app features.
- **Local Proxy Mode:** Expose WireGuard tunnels over a local SOCKS5 or HTTP proxy to browsers or firewall apps (like AdGuard).
- **Lockdown Mode:** Advanced in-app kill switch that blocks all traffic while the tunnel is down.
- **Quick Controls:** Quick Settings tile and home screen shortcuts for easy toggling.
- **Remote Control Support:** Intent-based automation for controlling tunnels and auto-tunneling from automation apps (like Tasker).
- **Dynamic DNS Handling:** Automatically detect and update endpoints on server IP changes without requiring a restart.
- **IPv6 Endpoints:** Automatically upgrade to IPv6 endpoints or fall back to IPv4 based on network conditions without requiring a restart.
- **Android TV Support:** Full support for nearly all features on Android TV.
@@ -18,7 +18,8 @@ import com.zaneschepke.networkmonitor.AndroidNetworkMonitor.WifiDetectionMethod.
import com.zaneschepke.networkmonitor.AndroidNetworkMonitor.WifiDetectionMethod.ROOT
import com.zaneschepke.networkmonitor.AndroidNetworkMonitor.WifiDetectionMethod.SHIZUKU
import com.zaneschepke.networkmonitor.shizuku.ShizukuShell
import com.zaneschepke.networkmonitor.util.getCurrentSecurityType
import com.zaneschepke.networkmonitor.util.getLegacySecurityType
import com.zaneschepke.networkmonitor.util.getWifiSecurityType
import com.zaneschepke.networkmonitor.util.getWifiSsid
import com.zaneschepke.networkmonitor.util.hasRequiredLocationPermissions
import com.zaneschepke.networkmonitor.util.isAirplaneModeOn
@@ -415,6 +416,7 @@ class AndroidNetworkMonitor(
return lastActive.ssid
}
}
Timber.d("Triggering new location ping")
wifiManager?.getWifiSsid() ?: ANDROID_UNKNOWN_SSID
}
ROOT ->
@@ -529,17 +531,47 @@ class AndroidNetworkMonitor(
) &&
caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
} == true -> {
val wifiEvent = networkData.wifiNetworkEvent
val ssid =
getSsidByDetectionMethod(
detectionMethod,
wifiEvent.networkCapabilities,
wifiEvent.network,
)
val currentNetworkId = wifiEvent.network.toString()
val lastActive = lastKnownActiveNetwork.value
// Use cache in legacy mode
val (ssid, securityType) =
if (
detectionMethod == LEGACY &&
lastActive is ActiveNetwork.Wifi &&
lastActive.networkId == currentNetworkId &&
lastActive.ssid != ANDROID_UNKNOWN_SSID
) {
Timber.d(
"Using cached SSID and Security Type to prevent location ping"
)
lastActive.ssid to lastActive.securityType
} else {
// Fallback
val fetchedSsid =
getSsidByDetectionMethod(
detectionMethod,
wifiEvent.networkCapabilities,
wifiEvent.network,
)
val fetchedSecurity =
if (
detectionMethod == DEFAULT &&
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
) {
wifiEvent.networkCapabilities.getWifiSecurityType()
} else {
wifiManager?.getLegacySecurityType()
}
fetchedSsid to fetchedSecurity
}
ActiveNetwork.Wifi(
ssid,
wifiManager?.getCurrentSecurityType(),
wifiEvent.network.toString(),
securityType,
currentNetworkId,
wifiEvent.network,
)
}
@@ -16,7 +16,7 @@ import kotlinx.coroutines.withContext
import timber.log.Timber
@Suppress("DEPRECATION")
fun WifiManager.getCurrentSecurityType(): WifiSecurityType? {
fun WifiManager.getLegacySecurityType(): WifiSecurityType? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
WifiSecurityType.from(connectionInfo.currentSecurityType)
} else {
@@ -24,6 +24,19 @@ fun WifiManager.getCurrentSecurityType(): WifiSecurityType? {
}
}
fun NetworkCapabilities.getWifiSecurityType(): WifiSecurityType? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val transportInfo = this.transportInfo
if (transportInfo is WifiInfo) {
WifiSecurityType.from(transportInfo.currentSecurityType)
} else {
null
}
} else {
null
}
}
@Suppress("DEPRECATION")
suspend fun WifiManager?.getWifiSsid(): String {
return withContext(Dispatchers.IO) {
@@ -98,10 +98,11 @@ class TunnelBackend(
private var dnsConfigJob: Job? = null
private val statusCallback = StatusCallback { handle, code ->
val state = Tunnel.State.fromNative(code)
state?.let { nativeState ->
val tunnelId = byHandle[handle] ?: return@let
updateTunnelTransportState(tunnelId, nativeState)
val state = Tunnel.State.fromNative(code) ?: return@StatusCallback
val tunnelId = byHandle[handle] ?: return@StatusCallback
val current = _status.value.activeTunnels[tunnelId]?.transportState
if (current != state) {
updateTunnelTransportState(tunnelId, state)
}
}
@@ -140,7 +141,7 @@ class TunnelBackend(
mode.config.`interface`.postUp?.let { runScripts(it, tunnel.id) }
tunnelJobs[result.tunnelId] =
startTunnelJobs(result.handle, tunnel, mode, result.removedPeerEndpoint)
startTunnelJobs(result.handle, tunnel, mode, result.replacedWithNonRoutable)
}
.onFailure { cleanup(tunnel.id) }
}
@@ -398,16 +399,16 @@ class TunnelBackend(
handle: Int,
tunnel: Tunnel,
mode: BackendMode,
removedPeerEndpoint: Boolean,
replacedWithNonRoutable: Boolean,
): Job {
return scope.launch {
supervisorScope {
if (removedPeerEndpoint) {
if (replacedWithNonRoutable) {
updateTunnelBootstrapState(tunnel.id, BootstrapState.ResolvingDns)
startDnsBootstrapJob(handle, tunnel, mode)
}
if (removedPeerEndpoint) {
if (replacedWithNonRoutable) {
when (val strategy = tunnel.ipStrategy) {
Tunnel.IpStrategy.Ipv4Only -> Unit
@@ -27,7 +27,7 @@ internal class WireGuardTunnelEngine(private val serviceHolder: ServiceHolder) :
val ifName = WGT_INTERFACE_PREFIX + tunnel.id
val (config, removedPeerEndpoint) = buildConfig(mode)
val (config, replacedWithNonRoutable) = buildConfig(mode)
// guard against static listenPort issues
val listenPort = config.`interface`.listenPort
@@ -77,7 +77,7 @@ internal class WireGuardTunnelEngine(private val serviceHolder: ServiceHolder) :
handle = handle,
interfaceName = ifName,
mode = mode,
removedPeerEndpoint = removedPeerEndpoint,
replacedWithNonRoutable = replacedWithNonRoutable,
)
}
@@ -91,16 +91,20 @@ internal class WireGuardTunnelEngine(private val serviceHolder: ServiceHolder) :
}
private fun buildConfig(mode: BackendMode): Pair<Config, Boolean> {
var removedPeerEndpoint = false
var replacedWithNonRoutable = false
return mode.config.copy(
peers =
mode.config.peers.map { peer ->
if (!peer.isStaticallyConfigured) {
removedPeerEndpoint = true
rewriteDynamicEndpoint(peer)
// keep support for valid configs with no endpoints
// replace domain configs with nonroutable and let the boostrap job update this
// with the real ip later
if (!peer.isStaticallyConfigured && peer.endpoint != null) {
replacedWithNonRoutable = true
val port = peer.endpoint!!.substringAfterLast(":")
peer.copy(endpoint = "$TEST_NET_IP:$port", persistentKeepalive = 0)
} else peer
}
) to removedPeerEndpoint
) to replacedWithNonRoutable
}
private fun buildBridgeProxyConfig(): ProxyConfig {
@@ -149,11 +153,6 @@ internal class WireGuardTunnelEngine(private val serviceHolder: ServiceHolder) :
}
}
// omit peer endpoint while bootstrapping
private fun rewriteDynamicEndpoint(peer: PeerSection): PeerSection {
return peer.copy(endpoint = null)
}
override suspend fun stop(handle: Int, mode: BackendMode) {
when (mode) {
is BackendMode.Proxy.Standard -> stopProxyTunnel(handle)
@@ -270,6 +269,7 @@ internal class WireGuardTunnelEngine(private val serviceHolder: ServiceHolder) :
}
companion object {
const val TEST_NET_IP = "192.0.2.1"
const val WGT_INTERFACE_PREFIX = "wgtun"
}
}
@@ -6,13 +6,13 @@ import androidx.lifecycle.LifecycleService
import com.zaneschepke.tunnel.backend.Backend
import com.zaneschepke.tunnel.backend.ServiceHolder
import com.zaneschepke.tunnel.backend.ServiceHolder.Companion.alwaysOnCallback
import kotlin.concurrent.atomics.ExperimentalAtomicApi
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import org.koin.java.KoinJavaComponent.inject
import timber.log.Timber
import kotlin.concurrent.atomics.ExperimentalAtomicApi
class TunnelService : LifecycleService() {
@@ -56,10 +56,11 @@ class TunnelService : LifecycleService() {
override fun onDestroy() {
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
serviceHolder.signalTunnelServiceDestroyed()
if(!userActivatedShutdown) {
if (!userActivatedShutdown) {
Timber.d("Service being killed by system, clean up tunnels")
shutdownScope.launch {
// TODO eventually, this should only shut down proxy mode tunnels with future multi tunnel
// TODO eventually, this should only shut down proxy mode tunnels with future multi
// tunnel
backend.stopAllActiveTunnels()
}
}
@@ -20,6 +20,9 @@ import com.zaneschepke.tunnel.model.KillSwitchConfig
import com.zaneschepke.tunnel.util.parseDns
import com.zaneschepke.tunnel.util.parseInetNetwork
import com.zaneschepke.wireguardautotunnel.parser.Config
import java.io.IOException
import kotlin.concurrent.atomics.ExperimentalAtomicApi
import kotlin.time.Duration.Companion.milliseconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@@ -29,9 +32,6 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.koin.java.KoinJavaComponent.inject
import timber.log.Timber
import java.io.IOException
import kotlin.concurrent.atomics.ExperimentalAtomicApi
import kotlin.time.Duration.Companion.milliseconds
class VpnService : android.net.VpnService(), KillSwitch, SocketProtector {
@@ -72,10 +72,11 @@ class VpnService : android.net.VpnService(), KillSwitch, SocketProtector {
hevBridgeJob?.cancel()
serviceScope.cancel()
stopHevSocks5Bridge()
if(!userActivatedShutdown) {
if (!userActivatedShutdown) {
Timber.d("Service being killed by system, clean up tunnels")
shutdownScope.launch {
// TODO eventually, this should only shut down vpn mode tunnels with future multi tunnel
// TODO eventually, this should only shut down vpn mode tunnels with future
// multi tunnel
backend.stopAllActiveTunnels()
}
}
@@ -227,7 +228,6 @@ class VpnService : android.net.VpnService(), KillSwitch, SocketProtector {
?.map { it.trim() }
?.filter { it.isNotEmpty() }
?.forEach { entry ->
val (address, prefix) = entry.parseInetNetwork()
if (prefix == 0) {
@@ -7,5 +7,5 @@ data class EngineStartResult(
val handle: Int,
val interfaceName: String,
val mode: BackendMode,
val removedPeerEndpoint: Boolean,
val replacedWithNonRoutable: Boolean,
)
+9
View File
@@ -24,6 +24,7 @@ var (
cancelFuncs map[int32]context.CancelFunc
tag string
virtualTunnelHandles map[int32]*wireproxyawg.VirtualTun
lastTunnelStatus sync.Map
tunnelMu sync.RWMutex
)
@@ -81,6 +82,13 @@ func awgStartProxy(interfaceName string, config string, uapiPath string, bypass
}
statusCB := func(code device.StatusCode) {
key := handle
if prev, loaded := lastTunnelStatus.LoadOrStore(key, code); loaded {
if prev == code {
return // duplicate, skip
}
lastTunnelStatus.Store(key, code)
}
go C.awgNotifyStatus(C.int32_t(handle), C.int32_t(code))
}
@@ -279,6 +287,7 @@ func awgTurnProxyTunnelOff(virtualTunnelHandle int32) {
virtualTun.Dev.Close()
}
lastTunnelStatus.Delete(virtualTunnelHandle)
shared.ReleaseHandle(virtualTunnelHandle)
C.awgNotifyStatus(
+12 -3
View File
@@ -30,9 +30,10 @@ type TunnelHandle struct {
}
var (
tag string
tunnelHandles = make(map[int32]TunnelHandle)
tunnelMu sync.RWMutex
tag string
tunnelHandles = make(map[int32]TunnelHandle)
lastTunnelStatus sync.Map
tunnelMu sync.RWMutex
)
func init() {
@@ -67,6 +68,13 @@ func awgTurnOn(interfaceName string, tunFd int32, settings string, uapiPath stri
}
statusCB := func(code device.StatusCode) {
key := handle
if prev, loaded := lastTunnelStatus.LoadOrStore(key, code); loaded {
if prev == code {
return // duplicate, skip
}
lastTunnelStatus.Store(key, code)
}
go C.awgNotifyStatus(C.int32_t(handle), C.int32_t(code))
}
@@ -196,6 +204,7 @@ func awgTurnOff(tunnelHandle int32) {
handle.device.Close()
}
lastTunnelStatus.Delete(tunnelHandle)
shared.ReleaseHandle(tunnelHandle)
C.awgNotifyStatus(