From 5103bc640eda049b76a17ddb22f6421a15752e91 Mon Sep 17 00:00:00 2001 From: albexk Date: Thu, 11 Dec 2025 13:51:19 +0300 Subject: [PATCH] feat: implement reconnection in AWG by turning the VPN off and on (#2046) --- .../amnezia/vpn/protocol/openvpn/OpenVpn.kt | 2 +- .../protocolApi/src/main/kotlin/Protocol.kt | 2 +- .../src/org/amnezia/vpn/AmneziaVpnService.kt | 2 +- .../vpn/protocol/wireguard/Wireguard.kt | 34 ++++++++++++++----- client/android/xray/src/main/kotlin/Xray.kt | 2 +- 5 files changed, 29 insertions(+), 13 deletions(-) diff --git a/client/android/openvpn/src/main/kotlin/org/amnezia/vpn/protocol/openvpn/OpenVpn.kt b/client/android/openvpn/src/main/kotlin/org/amnezia/vpn/protocol/openvpn/OpenVpn.kt index 22fe35cd5..7c259a148 100644 --- a/client/android/openvpn/src/main/kotlin/org/amnezia/vpn/protocol/openvpn/OpenVpn.kt +++ b/client/android/openvpn/src/main/kotlin/org/amnezia/vpn/protocol/openvpn/OpenVpn.kt @@ -93,7 +93,7 @@ open class OpenVpn : Protocol() { openVpnClient = null } - override fun reconnectVpn(vpnBuilder: Builder) { + override fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean) { openVpnClient?.let { it.establish = makeEstablish(vpnBuilder) it.reconnect(0) diff --git a/client/android/protocolApi/src/main/kotlin/Protocol.kt b/client/android/protocolApi/src/main/kotlin/Protocol.kt index 6e682aa42..db9eb915f 100644 --- a/client/android/protocolApi/src/main/kotlin/Protocol.kt +++ b/client/android/protocolApi/src/main/kotlin/Protocol.kt @@ -42,7 +42,7 @@ abstract class Protocol { abstract fun stopVpn() - abstract fun reconnectVpn(vpnBuilder: Builder) + abstract fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean) protected fun ProtocolConfig.Builder.configSplitTunneling(config: JSONObject) { if (!allowSplitTunneling) { diff --git a/client/android/src/org/amnezia/vpn/AmneziaVpnService.kt b/client/android/src/org/amnezia/vpn/AmneziaVpnService.kt index 8d108bc3e..fbdc7f83a 100644 --- a/client/android/src/org/amnezia/vpn/AmneziaVpnService.kt +++ b/client/android/src/org/amnezia/vpn/AmneziaVpnService.kt @@ -565,7 +565,7 @@ open class AmneziaVpnService : VpnService() { protocolState.value = RECONNECTING connectionJob = connectionScope.launch { - vpnProto?.protocol?.reconnectVpn(Builder()) + vpnProto?.protocol?.reconnectVpn(Builder(), ::protect) } } diff --git a/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/Wireguard.kt b/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/Wireguard.kt index ee2ecec66..a96aefa66 100644 --- a/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/Wireguard.kt +++ b/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/Wireguard.kt @@ -12,6 +12,7 @@ import org.amnezia.vpn.protocol.Protocol import org.amnezia.vpn.protocol.ProtocolState.CONNECTED import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED import org.amnezia.vpn.protocol.Statistics +import org.amnezia.vpn.protocol.VpnException import org.amnezia.vpn.protocol.VpnStartException import org.amnezia.vpn.util.LibraryLoader.loadSharedLibrary import org.amnezia.vpn.util.Log @@ -27,6 +28,7 @@ private const val TAG = "Wireguard" open class Wireguard : Protocol() { private var tunnelHandle: Int = -1 + private var config: WireguardConfig? = null // save config for reconnect protected open val ifName: String = "amn0" private lateinit var scope: CoroutineScope private var statusJob: Job? = null @@ -61,6 +63,7 @@ open class Wireguard : Protocol() { override suspend fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) { val wireguardConfig = parseConfig(config) start(wireguardConfig, vpnBuilder, protect) + this.config = wireguardConfig } protected open fun parseConfig(config: JSONObject): WireguardConfig { @@ -133,8 +136,13 @@ open class Wireguard : Protocol() { configData.optStringOrNull("I5")?.let { setI5(it) } } - private fun start(config: WireguardConfig, vpnBuilder: Builder, protect: (Int) -> Boolean) { - if (tunnelHandle != -1) { + private fun start( + config: WireguardConfig, + vpnBuilder: Builder, + protect: (Int) -> Boolean, + stopExistingVpn: Boolean = false + ) { + if (!stopExistingVpn && tunnelHandle != -1) { Log.w(TAG, "Tunnel already up") return } @@ -142,6 +150,9 @@ open class Wireguard : Protocol() { buildVpnInterface(config, vpnBuilder) vpnBuilder.establish().use { tunFd -> + if (stopExistingVpn && tunnelHandle != -1) { + turnOffVpn() + } if (tunFd == null) { throw VpnStartException("Create VPN interface: permission not granted or revoked") } @@ -198,20 +209,25 @@ open class Wireguard : Protocol() { return lastHandshake } - override fun stopVpn() { - if (tunnelHandle == -1) { - Log.w(TAG, "Tunnel already down") - return - } + private fun turnOffVpn() { statusJob?.cancel() statusJob = null val handleToClose = tunnelHandle tunnelHandle = -1 GoBackend.awgTurnOff(handleToClose) + } + + override fun stopVpn() { + if (tunnelHandle == -1) { + Log.w(TAG, "Tunnel already down") + return + } + turnOffVpn() state.value = DISCONNECTED } - override fun reconnectVpn(vpnBuilder: Builder) { - state.value = CONNECTED + override fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean) { + val config = this.config ?: throw VpnException("Reconnect config is empty") + start(config, vpnBuilder, protect, true) } } diff --git a/client/android/xray/src/main/kotlin/Xray.kt b/client/android/xray/src/main/kotlin/Xray.kt index 082425253..d0a1c4e38 100644 --- a/client/android/xray/src/main/kotlin/Xray.kt +++ b/client/android/xray/src/main/kotlin/Xray.kt @@ -157,7 +157,7 @@ class Xray : Protocol() { state.value = DISCONNECTED } - override fun reconnectVpn(vpnBuilder: Builder) { + override fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean) { state.value = CONNECTED }