mirror of
https://github.com/wgtunnel/android.git
synced 2026-07-03 14:07:49 +02:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6c3c6891eb | |||
| af1848f12d | |||
| 96cffdfa7d | |||
| afebd975ea | |||
| 588a2a18bd | |||
| 221b38a119 | |||
| 0008d8b9bb | |||
| 9f85638b9a |
@@ -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
|
||||
}
|
||||
|
||||
+9
-1
@@ -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) }
|
||||
|
||||
+10
-1
@@ -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) } }
|
||||
|
||||
+13
-1
@@ -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,
|
||||
|
||||
+1
-5
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+7
-1
@@ -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,
|
||||
|
||||
+11
-1
@@ -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) } }
|
||||
|
||||
+12
-2
@@ -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 {
|
||||
|
||||
+7
-1
@@ -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,
|
||||
|
||||
+1
-7
@@ -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) ->
|
||||
|
||||
@@ -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.
|
||||
+41
-9
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user