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,7 +49,7 @@ and [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/)
|
|||||||
|
|
||||||
## About
|
## 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>
|
||||||
|
|
||||||
@@ -67,21 +67,18 @@ WG Tunnel is an alternative Android client for WireGuard and AmneziaWG, inspired
|
|||||||
|
|
||||||
## Features
|
## 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 your device's active network details.
|
||||||
- **Auto-Tunneling**: Automatically activate tunnels based on Wi-Fi SSID, Ethernet connections, or mobile data networks.
|
- **Deferred Endpoint Bootstrapping:** Safely resolves endpoints and updates peers after the tunnel is up for better reliability and leak protection on startup.
|
||||||
- **Split Tunneling**: Flexible support for routing specific apps or traffic through the VPN.
|
- **Handshake Monitoring:** Real-time handshake monitoring for instant tunnel health feedback.
|
||||||
- **WireGuard Modes**: Full compatibility with WireGuard in both kernel and userspace implementations.
|
- **AmneziaWG Support:** Full support for AmneziaWG 2.0, providing robust censorship protection.
|
||||||
- **AmneziaWG Integration**: Userspace mode for AmneziaWG, providing robust censorship evasion.
|
- **Split Tunneling:** Flexible support for routing specific apps or traffic through the VPN.
|
||||||
- **Always-On VPN**: Ensures continuous protection with Android's Always-On VPN feature.
|
- **Local Proxy Mode:** Expose WireGuard tunnels over a local SOCKS5 or HTTP proxy to browsers or firewall apps (like AdGuard).
|
||||||
- **Quick Controls**: Quick Settings tile and home screen shortcuts for easy VPN toggling.
|
- **Lockdown Mode:** Advanced in-app kill switch that blocks all traffic while the tunnel is down.
|
||||||
- **Automation Support**: Intent-based automation for controlling tunnels.
|
- **Quick Controls:** Quick Settings tile and home screen shortcuts for easy toggling.
|
||||||
- **Auto-Restore**: Seamlessly restores auto-tunneling and active tunnels after device restarts or app updates.
|
- **Remote Control Support:** Intent-based automation for controlling tunnels and auto-tunneling from automation apps (like Tasker).
|
||||||
- **Proxying Options**: Built-in HTTP and SOCKS5 proxy support within tunnels.
|
- **Dynamic DNS Handling:** Automatically detect and update endpoints on server IP changes without requiring a restart.
|
||||||
- **Lockdown Mode**: Custom kill switch for maximum leak prevention and security.
|
- **IPv6 Endpoints:** Automatically upgrade to IPv6 endpoints or fall back to IPv4 based on network conditions without requiring a restart.
|
||||||
- **Dynamic DNS Handling**: Detects and updates DNS changes without tunnel restarts.
|
- **Android TV Support:** Full support for nearly all features on Android TV.
|
||||||
- **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.
|
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,9 @@ sealed class Route : NavKey {
|
|||||||
|
|
||||||
@Keep @Serializable data object Display : Route()
|
@Keep @Serializable data object Display : Route()
|
||||||
|
|
||||||
@Keep @Serializable data object Tunnels : Route(), SecureRoute {
|
@Keep
|
||||||
|
@Serializable
|
||||||
|
data object Tunnels : Route(), SecureRoute {
|
||||||
override val requiresProtection: Boolean
|
override val requiresProtection: Boolean
|
||||||
get() = true
|
get() = true
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-1
@@ -1,10 +1,12 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.ui.screens.settings.logs.components
|
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.Arrangement
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
|
import androidx.compose.material3.scrollbar
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -18,7 +20,13 @@ fun LogList(
|
|||||||
) {
|
) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
state = lazyColumnListState,
|
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),
|
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||||
) {
|
) {
|
||||||
itemsIndexed(items = logs, key = { index, _ -> index }) { _, log -> LogItem(log = log) }
|
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
|
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.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.scrollbar
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -13,10 +15,17 @@ import com.zaneschepke.wireguardautotunnel.ui.screens.support.donate.crypto.comp
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AddressesScreen() {
|
fun AddressesScreen() {
|
||||||
|
val scrollState = rememberScrollState()
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.Start,
|
horizontalAlignment = Alignment.Start,
|
||||||
verticalArrangement = Arrangement.Top,
|
verticalArrangement = Arrangement.Top,
|
||||||
modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()),
|
modifier =
|
||||||
|
Modifier.fillMaxSize()
|
||||||
|
.verticalScroll(scrollState)
|
||||||
|
.scrollbar(
|
||||||
|
state = scrollState.scrollIndicatorState,
|
||||||
|
orientation = Orientation.Vertical,
|
||||||
|
),
|
||||||
) {
|
) {
|
||||||
val clipboard = rememberClipboardHelper()
|
val clipboard = rememberClipboardHelper()
|
||||||
Address.allAddresses.forEach { AddressItem(it) { address -> clipboard.copy(address) } }
|
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 LicenseFileEntry
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.gestures.Orientation
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
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.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.outlined.Launch
|
import androidx.compose.material.icons.automirrored.outlined.Launch
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.scrollbar
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -24,8 +27,17 @@ import com.zaneschepke.wireguardautotunnel.util.extensions.openWebUrl
|
|||||||
@Composable
|
@Composable
|
||||||
fun LicenseList(licenses: List<LicenseFileEntry>) {
|
fun LicenseList(licenses: List<LicenseFileEntry>) {
|
||||||
val context = LocalContext.current
|
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 ->
|
items(licenses) { entry ->
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
|||||||
+1
-5
@@ -43,11 +43,7 @@ fun PeerStatisticsSection(peer: ActivePeer) {
|
|||||||
color = color,
|
color = color,
|
||||||
)
|
)
|
||||||
peer.endpoint?.let {
|
peer.endpoint?.let {
|
||||||
StatText(
|
StatText(stringResource(R.string.endpoint_template, it), style = style, color = color)
|
||||||
stringResource(R.string.endpoint_template, it),
|
|
||||||
style = style,
|
|
||||||
color = color
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-1
@@ -1,6 +1,7 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.components
|
package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.components
|
||||||
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
|
import androidx.compose.foundation.gestures.Orientation
|
||||||
import androidx.compose.foundation.gestures.ScrollableDefaults
|
import androidx.compose.foundation.gestures.ScrollableDefaults
|
||||||
import androidx.compose.foundation.gestures.detectTapGestures
|
import androidx.compose.foundation.gestures.detectTapGestures
|
||||||
import androidx.compose.foundation.layout.size
|
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.Icons
|
||||||
import androidx.compose.material.icons.rounded.Circle
|
import androidx.compose.material.icons.rounded.Circle
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.scrollbar
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -78,7 +80,11 @@ fun TunnelList(
|
|||||||
viewModel.clearSelectedTunnels()
|
viewModel.clearSelectedTunnels()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.overscroll(rememberOverscrollEffect()),
|
.overscroll(rememberOverscrollEffect())
|
||||||
|
.scrollbar(
|
||||||
|
state = lazyListState.scrollIndicatorState,
|
||||||
|
orientation = Orientation.Vertical,
|
||||||
|
),
|
||||||
state = lazyListState,
|
state = lazyListState,
|
||||||
userScrollEnabled = true,
|
userScrollEnabled = true,
|
||||||
reverseLayout = false,
|
reverseLayout = false,
|
||||||
|
|||||||
+11
-1
@@ -1,5 +1,6 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.settings.config
|
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.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
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.foundation.verticalScroll
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.scrollbar
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -57,6 +59,8 @@ fun ConfigScreen(
|
|||||||
|
|
||||||
var showQrModal by rememberSaveable { mutableStateOf(false) }
|
var showQrModal by rememberSaveable { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val scrollState = rememberScrollState()
|
||||||
|
|
||||||
val rawConfig by
|
val rawConfig by
|
||||||
remember(liveConfig, uiState.activeConfig, uiState.tunnel?.quickConfig) {
|
remember(liveConfig, uiState.activeConfig, uiState.tunnel?.quickConfig) {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
@@ -90,7 +94,13 @@ fun ConfigScreen(
|
|||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.Start,
|
horizontalAlignment = Alignment.Start,
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp, Alignment.Top),
|
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
|
val displayText by
|
||||||
remember(rawConfig, showKeys) { derivedStateOf { maskSensitive(rawConfig, showKeys) } }
|
remember(rawConfig, showKeys) { derivedStateOf { maskSensitive(rawConfig, showKeys) } }
|
||||||
|
|||||||
+12
-2
@@ -1,5 +1,6 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.settings.config.edit
|
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.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Spacer
|
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.material.icons.outlined.HdrAuto
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.scrollbar
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@@ -49,11 +51,12 @@ fun ConfigEditScreen(
|
|||||||
val uiState by viewModel.collectAsState()
|
val uiState by viewModel.collectAsState()
|
||||||
|
|
||||||
if (uiState.isLoading) return
|
if (uiState.isLoading) return
|
||||||
|
|
||||||
val locale = Locale.current.platformLocale
|
val locale = Locale.current.platformLocale
|
||||||
|
|
||||||
var showSelectionDialog by rememberSaveable { mutableStateOf(false) }
|
var showSelectionDialog by rememberSaveable { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val scrollState = rememberScrollState()
|
||||||
|
|
||||||
sharedViewModel.collectSideEffect { sideEffect ->
|
sharedViewModel.collectSideEffect { sideEffect ->
|
||||||
when (sideEffect) {
|
when (sideEffect) {
|
||||||
is LocalSideEffect.SaveChanges -> {
|
is LocalSideEffect.SaveChanges -> {
|
||||||
@@ -104,7 +107,14 @@ fun ConfigEditScreen(
|
|||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.Start,
|
horizontalAlignment = Alignment.Start,
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp, Alignment.Top),
|
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) {
|
if (uiState.isGlobalConfig) {
|
||||||
Column {
|
Column {
|
||||||
|
|||||||
+7
-1
@@ -1,5 +1,6 @@
|
|||||||
package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.sort
|
package com.zaneschepke.wireguardautotunnel.ui.screens.tunnels.sort
|
||||||
|
|
||||||
|
import androidx.compose.foundation.gestures.Orientation
|
||||||
import androidx.compose.foundation.gestures.ScrollableDefaults
|
import androidx.compose.foundation.gestures.ScrollableDefaults
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Row
|
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.material.icons.filled.DragHandle
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.scrollbar
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@@ -107,7 +109,11 @@ fun SortScreen(sharedViewModel: SharedAppViewModel = koinActivityViewModel()) {
|
|||||||
Modifier.pointerInput(Unit) {
|
Modifier.pointerInput(Unit) {
|
||||||
if (tunnelsUiState.tunnels.isEmpty()) return@pointerInput
|
if (tunnelsUiState.tunnels.isEmpty()) return@pointerInput
|
||||||
}
|
}
|
||||||
.overscroll(rememberOverscrollEffect()),
|
.overscroll(rememberOverscrollEffect())
|
||||||
|
.scrollbar(
|
||||||
|
state = lazyListState.scrollIndicatorState,
|
||||||
|
orientation = Orientation.Vertical,
|
||||||
|
),
|
||||||
state = lazyListState,
|
state = lazyListState,
|
||||||
userScrollEnabled = true,
|
userScrollEnabled = true,
|
||||||
reverseLayout = false,
|
reverseLayout = false,
|
||||||
|
|||||||
+1
-7
@@ -73,13 +73,7 @@ class SharedAppViewModel(
|
|||||||
tunnelCoordinator.backendStatus,
|
tunnelCoordinator.backendStatus,
|
||||||
selectedTunnelsRepository.flow,
|
selectedTunnelsRepository.flow,
|
||||||
) { tunnels, backendStatus, selectedTuns ->
|
) { tunnels, backendStatus, selectedTuns ->
|
||||||
val activeTunnelIds = backendStatus.activeTunnels.keys
|
val sortedTunnels = tunnels.sortedBy { it.position }
|
||||||
|
|
||||||
val sortedTunnels =
|
|
||||||
tunnels.sortedWith(
|
|
||||||
compareByDescending<TunnelConfig> { it.id in activeTunnelIds }
|
|
||||||
.thenBy { it.position }
|
|
||||||
)
|
|
||||||
|
|
||||||
val displayStates =
|
val displayStates =
|
||||||
backendStatus.activeTunnels.mapValues { (_, activeTunnel) ->
|
backendStatus.activeTunnels.mapValues { (_, activeTunnel) ->
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
object Constants {
|
object Constants {
|
||||||
const val VERSION_NAME = "5.0.1"
|
const val VERSION_NAME = "5.0.2"
|
||||||
const val VERSION_CODE = 50001
|
const val VERSION_CODE = 50002
|
||||||
const val TARGET_SDK = 37
|
const val TARGET_SDK = 37
|
||||||
const val MIN_SDK = 26
|
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.
|
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.
|
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.
|
- **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.
|
- **Local Proxy Mode:** Expose WireGuard tunnels over a local SOCKS5 or HTTP proxy to browsers or firewall apps (like AdGuard).
|
||||||
- **AmneziaWG Integration:** Full support for AmneziaWG, providing robust censorship evasion.
|
- **Lockdown Mode:** Advanced in-app kill switch that blocks all traffic while the tunnel is down.
|
||||||
- **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.
|
||||||
- **Quick Controls:** Quick Settings tile and home screen shortcuts for easy toggling actions.
|
- **Remote Control Support:** Intent-based automation for controlling tunnels and auto-tunneling from automation apps (like Tasker).
|
||||||
- **Automation Support:** Intent-based automation for controlling tunnels and auto-tunneling.
|
- **Dynamic DNS Handling:** Automatically detect and update endpoints on server IP changes without requiring a restart.
|
||||||
- **Dynamic DNS Handling:** Detects and updates DNS changes without tunnel restarts.
|
- **IPv6 Endpoints:** Automatically upgrade to IPv6 endpoints or fall back to IPv4 based on network conditions without requiring a restart.
|
||||||
- **Monitoring Tools:** Advanced tunnel monitoring features for tunnel performance monitoring.
|
- **Android TV Support:** Full support for nearly all features on Android TV.
|
||||||
- **Android TV Support:** Android TV support for nearly all app features.
|
|
||||||
+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.ROOT
|
||||||
import com.zaneschepke.networkmonitor.AndroidNetworkMonitor.WifiDetectionMethod.SHIZUKU
|
import com.zaneschepke.networkmonitor.AndroidNetworkMonitor.WifiDetectionMethod.SHIZUKU
|
||||||
import com.zaneschepke.networkmonitor.shizuku.ShizukuShell
|
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.getWifiSsid
|
||||||
import com.zaneschepke.networkmonitor.util.hasRequiredLocationPermissions
|
import com.zaneschepke.networkmonitor.util.hasRequiredLocationPermissions
|
||||||
import com.zaneschepke.networkmonitor.util.isAirplaneModeOn
|
import com.zaneschepke.networkmonitor.util.isAirplaneModeOn
|
||||||
@@ -415,6 +416,7 @@ class AndroidNetworkMonitor(
|
|||||||
return lastActive.ssid
|
return lastActive.ssid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Timber.d("Triggering new location ping")
|
||||||
wifiManager?.getWifiSsid() ?: ANDROID_UNKNOWN_SSID
|
wifiManager?.getWifiSsid() ?: ANDROID_UNKNOWN_SSID
|
||||||
}
|
}
|
||||||
ROOT ->
|
ROOT ->
|
||||||
@@ -529,17 +531,47 @@ class AndroidNetworkMonitor(
|
|||||||
) &&
|
) &&
|
||||||
caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
|
caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
|
||||||
} == true -> {
|
} == true -> {
|
||||||
|
|
||||||
val wifiEvent = networkData.wifiNetworkEvent
|
val wifiEvent = networkData.wifiNetworkEvent
|
||||||
val ssid =
|
val currentNetworkId = wifiEvent.network.toString()
|
||||||
getSsidByDetectionMethod(
|
val lastActive = lastKnownActiveNetwork.value
|
||||||
detectionMethod,
|
|
||||||
wifiEvent.networkCapabilities,
|
// Use cache in legacy mode
|
||||||
wifiEvent.network,
|
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(
|
ActiveNetwork.Wifi(
|
||||||
ssid,
|
ssid,
|
||||||
wifiManager?.getCurrentSecurityType(),
|
securityType,
|
||||||
wifiEvent.network.toString(),
|
currentNetworkId,
|
||||||
wifiEvent.network,
|
wifiEvent.network,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import kotlinx.coroutines.withContext
|
|||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
fun WifiManager.getCurrentSecurityType(): WifiSecurityType? {
|
fun WifiManager.getLegacySecurityType(): WifiSecurityType? {
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
WifiSecurityType.from(connectionInfo.currentSecurityType)
|
WifiSecurityType.from(connectionInfo.currentSecurityType)
|
||||||
} else {
|
} 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")
|
@Suppress("DEPRECATION")
|
||||||
suspend fun WifiManager?.getWifiSsid(): String {
|
suspend fun WifiManager?.getWifiSsid(): String {
|
||||||
return withContext(Dispatchers.IO) {
|
return withContext(Dispatchers.IO) {
|
||||||
|
|||||||
@@ -98,10 +98,11 @@ class TunnelBackend(
|
|||||||
private var dnsConfigJob: Job? = null
|
private var dnsConfigJob: Job? = null
|
||||||
|
|
||||||
private val statusCallback = StatusCallback { handle, code ->
|
private val statusCallback = StatusCallback { handle, code ->
|
||||||
val state = Tunnel.State.fromNative(code)
|
val state = Tunnel.State.fromNative(code) ?: return@StatusCallback
|
||||||
state?.let { nativeState ->
|
val tunnelId = byHandle[handle] ?: return@StatusCallback
|
||||||
val tunnelId = byHandle[handle] ?: return@let
|
val current = _status.value.activeTunnels[tunnelId]?.transportState
|
||||||
updateTunnelTransportState(tunnelId, nativeState)
|
if (current != state) {
|
||||||
|
updateTunnelTransportState(tunnelId, state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,7 +141,7 @@ class TunnelBackend(
|
|||||||
mode.config.`interface`.postUp?.let { runScripts(it, tunnel.id) }
|
mode.config.`interface`.postUp?.let { runScripts(it, tunnel.id) }
|
||||||
|
|
||||||
tunnelJobs[result.tunnelId] =
|
tunnelJobs[result.tunnelId] =
|
||||||
startTunnelJobs(result.handle, tunnel, mode, result.removedPeerEndpoint)
|
startTunnelJobs(result.handle, tunnel, mode, result.replacedWithNonRoutable)
|
||||||
}
|
}
|
||||||
.onFailure { cleanup(tunnel.id) }
|
.onFailure { cleanup(tunnel.id) }
|
||||||
}
|
}
|
||||||
@@ -398,16 +399,16 @@ class TunnelBackend(
|
|||||||
handle: Int,
|
handle: Int,
|
||||||
tunnel: Tunnel,
|
tunnel: Tunnel,
|
||||||
mode: BackendMode,
|
mode: BackendMode,
|
||||||
removedPeerEndpoint: Boolean,
|
replacedWithNonRoutable: Boolean,
|
||||||
): Job {
|
): Job {
|
||||||
return scope.launch {
|
return scope.launch {
|
||||||
supervisorScope {
|
supervisorScope {
|
||||||
if (removedPeerEndpoint) {
|
if (replacedWithNonRoutable) {
|
||||||
updateTunnelBootstrapState(tunnel.id, BootstrapState.ResolvingDns)
|
updateTunnelBootstrapState(tunnel.id, BootstrapState.ResolvingDns)
|
||||||
startDnsBootstrapJob(handle, tunnel, mode)
|
startDnsBootstrapJob(handle, tunnel, mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (removedPeerEndpoint) {
|
if (replacedWithNonRoutable) {
|
||||||
when (val strategy = tunnel.ipStrategy) {
|
when (val strategy = tunnel.ipStrategy) {
|
||||||
Tunnel.IpStrategy.Ipv4Only -> Unit
|
Tunnel.IpStrategy.Ipv4Only -> Unit
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ internal class WireGuardTunnelEngine(private val serviceHolder: ServiceHolder) :
|
|||||||
|
|
||||||
val ifName = WGT_INTERFACE_PREFIX + tunnel.id
|
val ifName = WGT_INTERFACE_PREFIX + tunnel.id
|
||||||
|
|
||||||
val (config, removedPeerEndpoint) = buildConfig(mode)
|
val (config, replacedWithNonRoutable) = buildConfig(mode)
|
||||||
|
|
||||||
// guard against static listenPort issues
|
// guard against static listenPort issues
|
||||||
val listenPort = config.`interface`.listenPort
|
val listenPort = config.`interface`.listenPort
|
||||||
@@ -77,7 +77,7 @@ internal class WireGuardTunnelEngine(private val serviceHolder: ServiceHolder) :
|
|||||||
handle = handle,
|
handle = handle,
|
||||||
interfaceName = ifName,
|
interfaceName = ifName,
|
||||||
mode = mode,
|
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> {
|
private fun buildConfig(mode: BackendMode): Pair<Config, Boolean> {
|
||||||
var removedPeerEndpoint = false
|
var replacedWithNonRoutable = false
|
||||||
return mode.config.copy(
|
return mode.config.copy(
|
||||||
peers =
|
peers =
|
||||||
mode.config.peers.map { peer ->
|
mode.config.peers.map { peer ->
|
||||||
if (!peer.isStaticallyConfigured) {
|
// keep support for valid configs with no endpoints
|
||||||
removedPeerEndpoint = true
|
// replace domain configs with nonroutable and let the boostrap job update this
|
||||||
rewriteDynamicEndpoint(peer)
|
// 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
|
} else peer
|
||||||
}
|
}
|
||||||
) to removedPeerEndpoint
|
) to replacedWithNonRoutable
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildBridgeProxyConfig(): ProxyConfig {
|
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) {
|
override suspend fun stop(handle: Int, mode: BackendMode) {
|
||||||
when (mode) {
|
when (mode) {
|
||||||
is BackendMode.Proxy.Standard -> stopProxyTunnel(handle)
|
is BackendMode.Proxy.Standard -> stopProxyTunnel(handle)
|
||||||
@@ -270,6 +269,7 @@ internal class WireGuardTunnelEngine(private val serviceHolder: ServiceHolder) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
const val TEST_NET_IP = "192.0.2.1"
|
||||||
const val WGT_INTERFACE_PREFIX = "wgtun"
|
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.Backend
|
||||||
import com.zaneschepke.tunnel.backend.ServiceHolder
|
import com.zaneschepke.tunnel.backend.ServiceHolder
|
||||||
import com.zaneschepke.tunnel.backend.ServiceHolder.Companion.alwaysOnCallback
|
import com.zaneschepke.tunnel.backend.ServiceHolder.Companion.alwaysOnCallback
|
||||||
|
import kotlin.concurrent.atomics.ExperimentalAtomicApi
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.java.KoinJavaComponent.inject
|
import org.koin.java.KoinJavaComponent.inject
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import kotlin.concurrent.atomics.ExperimentalAtomicApi
|
|
||||||
|
|
||||||
class TunnelService : LifecycleService() {
|
class TunnelService : LifecycleService() {
|
||||||
|
|
||||||
@@ -56,10 +56,11 @@ class TunnelService : LifecycleService() {
|
|||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
|
ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
|
||||||
serviceHolder.signalTunnelServiceDestroyed()
|
serviceHolder.signalTunnelServiceDestroyed()
|
||||||
if(!userActivatedShutdown) {
|
if (!userActivatedShutdown) {
|
||||||
Timber.d("Service being killed by system, clean up tunnels")
|
Timber.d("Service being killed by system, clean up tunnels")
|
||||||
shutdownScope.launch {
|
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()
|
backend.stopAllActiveTunnels()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ import com.zaneschepke.tunnel.model.KillSwitchConfig
|
|||||||
import com.zaneschepke.tunnel.util.parseDns
|
import com.zaneschepke.tunnel.util.parseDns
|
||||||
import com.zaneschepke.tunnel.util.parseInetNetwork
|
import com.zaneschepke.tunnel.util.parseInetNetwork
|
||||||
import com.zaneschepke.wireguardautotunnel.parser.Config
|
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.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@@ -29,9 +32,6 @@ import kotlinx.coroutines.delay
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.java.KoinJavaComponent.inject
|
import org.koin.java.KoinJavaComponent.inject
|
||||||
import timber.log.Timber
|
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 {
|
class VpnService : android.net.VpnService(), KillSwitch, SocketProtector {
|
||||||
|
|
||||||
@@ -72,10 +72,11 @@ class VpnService : android.net.VpnService(), KillSwitch, SocketProtector {
|
|||||||
hevBridgeJob?.cancel()
|
hevBridgeJob?.cancel()
|
||||||
serviceScope.cancel()
|
serviceScope.cancel()
|
||||||
stopHevSocks5Bridge()
|
stopHevSocks5Bridge()
|
||||||
if(!userActivatedShutdown) {
|
if (!userActivatedShutdown) {
|
||||||
Timber.d("Service being killed by system, clean up tunnels")
|
Timber.d("Service being killed by system, clean up tunnels")
|
||||||
shutdownScope.launch {
|
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()
|
backend.stopAllActiveTunnels()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -227,7 +228,6 @@ class VpnService : android.net.VpnService(), KillSwitch, SocketProtector {
|
|||||||
?.map { it.trim() }
|
?.map { it.trim() }
|
||||||
?.filter { it.isNotEmpty() }
|
?.filter { it.isNotEmpty() }
|
||||||
?.forEach { entry ->
|
?.forEach { entry ->
|
||||||
|
|
||||||
val (address, prefix) = entry.parseInetNetwork()
|
val (address, prefix) = entry.parseInetNetwork()
|
||||||
|
|
||||||
if (prefix == 0) {
|
if (prefix == 0) {
|
||||||
|
|||||||
@@ -7,5 +7,5 @@ data class EngineStartResult(
|
|||||||
val handle: Int,
|
val handle: Int,
|
||||||
val interfaceName: String,
|
val interfaceName: String,
|
||||||
val mode: BackendMode,
|
val mode: BackendMode,
|
||||||
val removedPeerEndpoint: Boolean,
|
val replacedWithNonRoutable: Boolean,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ var (
|
|||||||
cancelFuncs map[int32]context.CancelFunc
|
cancelFuncs map[int32]context.CancelFunc
|
||||||
tag string
|
tag string
|
||||||
virtualTunnelHandles map[int32]*wireproxyawg.VirtualTun
|
virtualTunnelHandles map[int32]*wireproxyawg.VirtualTun
|
||||||
|
lastTunnelStatus sync.Map
|
||||||
tunnelMu sync.RWMutex
|
tunnelMu sync.RWMutex
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -81,6 +82,13 @@ func awgStartProxy(interfaceName string, config string, uapiPath string, bypass
|
|||||||
}
|
}
|
||||||
|
|
||||||
statusCB := func(code device.StatusCode) {
|
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))
|
go C.awgNotifyStatus(C.int32_t(handle), C.int32_t(code))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,6 +287,7 @@ func awgTurnProxyTunnelOff(virtualTunnelHandle int32) {
|
|||||||
virtualTun.Dev.Close()
|
virtualTun.Dev.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lastTunnelStatus.Delete(virtualTunnelHandle)
|
||||||
shared.ReleaseHandle(virtualTunnelHandle)
|
shared.ReleaseHandle(virtualTunnelHandle)
|
||||||
|
|
||||||
C.awgNotifyStatus(
|
C.awgNotifyStatus(
|
||||||
|
|||||||
@@ -30,9 +30,10 @@ type TunnelHandle struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
tag string
|
tag string
|
||||||
tunnelHandles = make(map[int32]TunnelHandle)
|
tunnelHandles = make(map[int32]TunnelHandle)
|
||||||
tunnelMu sync.RWMutex
|
lastTunnelStatus sync.Map
|
||||||
|
tunnelMu sync.RWMutex
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -67,6 +68,13 @@ func awgTurnOn(interfaceName string, tunFd int32, settings string, uapiPath stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
statusCB := func(code device.StatusCode) {
|
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))
|
go C.awgNotifyStatus(C.int32_t(handle), C.int32_t(code))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,6 +204,7 @@ func awgTurnOff(tunnelHandle int32) {
|
|||||||
handle.device.Close()
|
handle.device.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lastTunnelStatus.Delete(tunnelHandle)
|
||||||
shared.ReleaseHandle(tunnelHandle)
|
shared.ReleaseHandle(tunnelHandle)
|
||||||
|
|
||||||
C.awgNotifyStatus(
|
C.awgNotifyStatus(
|
||||||
|
|||||||
Reference in New Issue
Block a user