Add network state listening and reconnection
Vpn reconnects when the default network is changed
This commit is contained in:
parent
8cc5846808
commit
1576aed1ea
15 changed files with 240 additions and 223 deletions
|
|
@ -15,6 +15,8 @@
|
||||||
<!-- %%INSERT_FEATURES -->
|
<!-- %%INSERT_FEATURES -->
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<!-- To request network state for android < 31 -->
|
||||||
|
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,7 @@ open class OpenVpn : Protocol() {
|
||||||
configBuilder = configBuilder,
|
configBuilder = configBuilder,
|
||||||
state = state,
|
state = state,
|
||||||
getLocalNetworks = { ipv6 -> getLocalNetworks(context, ipv6) },
|
getLocalNetworks = { ipv6 -> getLocalNetworks(context, ipv6) },
|
||||||
establish = makeEstablish(configBuilder, vpnBuilder),
|
establish = makeEstablish(vpnBuilder),
|
||||||
protect = protect,
|
protect = protect,
|
||||||
onError = onError
|
onError = onError
|
||||||
)
|
)
|
||||||
|
|
@ -109,14 +109,20 @@ open class OpenVpn : Protocol() {
|
||||||
openVpnClient = null
|
openVpnClient = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun reconnectVpn(vpnBuilder: Builder) {
|
||||||
|
openVpnClient?.let {
|
||||||
|
it.establish = makeEstablish(vpnBuilder)
|
||||||
|
it.reconnect(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected open fun parseConfig(config: JSONObject): ClientAPI_Config {
|
protected open fun parseConfig(config: JSONObject): ClientAPI_Config {
|
||||||
val openVpnConfig = ClientAPI_Config()
|
val openVpnConfig = ClientAPI_Config()
|
||||||
openVpnConfig.content = config.getJSONObject("openvpn_config_data").getString("config")
|
openVpnConfig.content = config.getJSONObject("openvpn_config_data").getString("config")
|
||||||
return openVpnConfig
|
return openVpnConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun makeEstablish(configBuilder: OpenVpnConfig.Builder, vpnBuilder: Builder): () -> Int =
|
private fun makeEstablish(vpnBuilder: Builder): (OpenVpnConfig.Builder) -> Int = { configBuilder ->
|
||||||
{
|
|
||||||
val openVpnConfig = configBuilder.build()
|
val openVpnConfig = configBuilder.build()
|
||||||
buildVpnInterface(openVpnConfig, vpnBuilder)
|
buildVpnInterface(openVpnConfig, vpnBuilder)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package org.amnezia.vpn.protocol.openvpn
|
||||||
import android.net.ProxyInfo
|
import android.net.ProxyInfo
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.getAndUpdate
|
||||||
import net.openvpn.ovpn3.ClientAPI_Config
|
import net.openvpn.ovpn3.ClientAPI_Config
|
||||||
import net.openvpn.ovpn3.ClientAPI_EvalConfig
|
import net.openvpn.ovpn3.ClientAPI_EvalConfig
|
||||||
import net.openvpn.ovpn3.ClientAPI_Event
|
import net.openvpn.ovpn3.ClientAPI_Event
|
||||||
|
|
@ -14,6 +15,7 @@ import net.openvpn.ovpn3.ClientAPI_TransportStats
|
||||||
import org.amnezia.vpn.protocol.ProtocolState
|
import org.amnezia.vpn.protocol.ProtocolState
|
||||||
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
|
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
|
||||||
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
||||||
|
import org.amnezia.vpn.protocol.ProtocolState.RECONNECTING
|
||||||
import org.amnezia.vpn.util.Log
|
import org.amnezia.vpn.util.Log
|
||||||
import org.amnezia.vpn.util.net.InetNetwork
|
import org.amnezia.vpn.util.net.InetNetwork
|
||||||
import org.amnezia.vpn.util.net.parseInetAddress
|
import org.amnezia.vpn.util.net.parseInetAddress
|
||||||
|
|
@ -25,7 +27,7 @@ class OpenVpnClient(
|
||||||
private val configBuilder: OpenVpnConfig.Builder,
|
private val configBuilder: OpenVpnConfig.Builder,
|
||||||
private val state: MutableStateFlow<ProtocolState>,
|
private val state: MutableStateFlow<ProtocolState>,
|
||||||
private val getLocalNetworks: (Boolean) -> List<InetNetwork>,
|
private val getLocalNetworks: (Boolean) -> List<InetNetwork>,
|
||||||
private val establish: () -> Int,
|
internal var establish: (OpenVpnConfig.Builder) -> Int,
|
||||||
private val protect: (Int) -> Boolean,
|
private val protect: (Int) -> Boolean,
|
||||||
private val onError: (String) -> Unit
|
private val onError: (String) -> Unit
|
||||||
) : ClientAPI_OpenVPNClient() {
|
) : ClientAPI_OpenVPNClient() {
|
||||||
|
|
@ -51,6 +53,7 @@ class OpenVpnClient(
|
||||||
// Should be called first.
|
// Should be called first.
|
||||||
override fun tun_builder_new(): Boolean {
|
override fun tun_builder_new(): Boolean {
|
||||||
Log.v(TAG, "tun_builder_new")
|
Log.v(TAG, "tun_builder_new")
|
||||||
|
configBuilder.clearAddresses()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -147,7 +150,7 @@ class OpenVpnClient(
|
||||||
// Always called last after tun_builder session has been configured.
|
// Always called last after tun_builder session has been configured.
|
||||||
override fun tun_builder_establish(): Int {
|
override fun tun_builder_establish(): Int {
|
||||||
Log.v(TAG, "tun_builder_establish")
|
Log.v(TAG, "tun_builder_establish")
|
||||||
return establish()
|
return establish(configBuilder)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Callback to reroute default gateway to VPN interface.
|
// Callback to reroute default gateway to VPN interface.
|
||||||
|
|
@ -368,6 +371,12 @@ class OpenVpnClient(
|
||||||
"COMPRESSION_ENABLED", "WARN" -> Log.w(TAG, "$name: $info")
|
"COMPRESSION_ENABLED", "WARN" -> Log.w(TAG, "$name: $info")
|
||||||
"CONNECTED" -> state.value = CONNECTED
|
"CONNECTED" -> state.value = CONNECTED
|
||||||
"DISCONNECTED" -> state.value = DISCONNECTED
|
"DISCONNECTED" -> state.value = DISCONNECTED
|
||||||
|
"RECONNECTING" -> {
|
||||||
|
state.getAndUpdate { state ->
|
||||||
|
if (state == DISCONNECTED || state == CONNECTED) RECONNECTING
|
||||||
|
else state
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (event.error || event.fatal) {
|
if (event.error || event.fatal) {
|
||||||
state.value = DISCONNECTED
|
state.value = DISCONNECTED
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,8 @@ abstract class Protocol {
|
||||||
|
|
||||||
abstract fun stopVpn()
|
abstract fun stopVpn()
|
||||||
|
|
||||||
|
abstract fun reconnectVpn(vpnBuilder: Builder)
|
||||||
|
|
||||||
protected fun ProtocolConfig.Builder.configSplitTunnel(config: JSONObject) {
|
protected fun ProtocolConfig.Builder.configSplitTunnel(config: JSONObject) {
|
||||||
val splitTunnelType = config.optInt("splitTunnelType")
|
val splitTunnelType = config.optInt("splitTunnelType")
|
||||||
if (splitTunnelType == SPLIT_TUNNEL_DISABLE) return
|
if (splitTunnelType == SPLIT_TUNNEL_DISABLE) return
|
||||||
|
|
@ -85,33 +87,62 @@ abstract class Protocol {
|
||||||
protected open fun buildVpnInterface(config: ProtocolConfig, vpnBuilder: Builder) {
|
protected open fun buildVpnInterface(config: ProtocolConfig, vpnBuilder: Builder) {
|
||||||
vpnBuilder.setSession(VPN_SESSION_NAME)
|
vpnBuilder.setSession(VPN_SESSION_NAME)
|
||||||
|
|
||||||
for (addr in config.addresses) vpnBuilder.addAddress(addr)
|
for (addr in config.addresses) {
|
||||||
|
Log.d(TAG, "addAddress: $addr")
|
||||||
for (addr in config.dnsServers) vpnBuilder.addDnsServer(addr)
|
vpnBuilder.addAddress(addr)
|
||||||
// fix for Samsung android ignoring DNS servers outside the VPN route range
|
|
||||||
if (Build.BRAND == "samsung") {
|
|
||||||
for (addr in config.dnsServers) vpnBuilder.addRoute(InetNetwork(addr))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
config.searchDomain?.let { vpnBuilder.addSearchDomain(it) }
|
for (addr in config.dnsServers) {
|
||||||
|
Log.d(TAG, "addDnsServer: $addr")
|
||||||
|
vpnBuilder.addDnsServer(addr)
|
||||||
|
}
|
||||||
|
// fix for Samsung android ignoring DNS servers outside the VPN route range
|
||||||
|
if (Build.BRAND == "samsung") {
|
||||||
|
for (addr in config.dnsServers) {
|
||||||
|
Log.d(TAG, "addRoute: $addr")
|
||||||
|
vpnBuilder.addRoute(InetNetwork(addr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (addr in config.routes) vpnBuilder.addRoute(addr)
|
config.searchDomain?.let {
|
||||||
|
Log.d(TAG, "addSearchDomain: $it")
|
||||||
|
vpnBuilder.addSearchDomain(it)
|
||||||
|
}
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
|
for (addr in config.routes) {
|
||||||
for (addr in config.excludedRoutes) vpnBuilder.excludeRoute(addr)
|
Log.d(TAG, "addRoute: $addr")
|
||||||
|
vpnBuilder.addRoute(addr)
|
||||||
|
}
|
||||||
|
|
||||||
for (app in config.excludedApplications) vpnBuilder.addDisallowedApplication(app)
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
for (addr in config.excludedRoutes) {
|
||||||
|
Log.d(TAG, "excludeRoute: $addr")
|
||||||
|
vpnBuilder.excludeRoute(addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (app in config.excludedApplications) {
|
||||||
|
Log.d(TAG, "addDisallowedApplication: $app")
|
||||||
|
vpnBuilder.addDisallowedApplication(app)
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "setMtu: ${config.mtu}")
|
||||||
vpnBuilder.setMtu(config.mtu)
|
vpnBuilder.setMtu(config.mtu)
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
config.httpProxy?.let { vpnBuilder.setHttpProxy(it) }
|
config.httpProxy?.let {
|
||||||
|
Log.d(TAG, "setHttpProxy: $it")
|
||||||
|
vpnBuilder.setHttpProxy(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (config.allowAllAF) {
|
if (config.allowAllAF) {
|
||||||
|
Log.d(TAG, "allowFamily")
|
||||||
vpnBuilder.allowFamily(OsConstants.AF_INET)
|
vpnBuilder.allowFamily(OsConstants.AF_INET)
|
||||||
vpnBuilder.allowFamily(OsConstants.AF_INET6)
|
vpnBuilder.allowFamily(OsConstants.AF_INET6)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "setBlocking: ${config.blockingMode}")
|
||||||
vpnBuilder.setBlocking(config.blockingMode)
|
vpnBuilder.setBlocking(config.blockingMode)
|
||||||
vpnBuilder.setUnderlyingNetworks(null)
|
vpnBuilder.setUnderlyingNetworks(null)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ open class ProtocolConfig protected constructor(
|
||||||
|
|
||||||
fun addAddress(addr: InetNetwork) = apply { this.addresses += addr }
|
fun addAddress(addr: InetNetwork) = apply { this.addresses += addr }
|
||||||
fun addAddresses(addresses: List<InetNetwork>) = apply { this.addresses += addresses }
|
fun addAddresses(addresses: List<InetNetwork>) = apply { this.addresses += addresses }
|
||||||
|
fun clearAddresses() = apply { this.addresses.clear() }
|
||||||
|
|
||||||
fun addDnsServer(dnsServer: InetAddress) = apply { this.dnsServers += dnsServer }
|
fun addDnsServer(dnsServer: InetAddress) = apply { this.dnsServers += dnsServer }
|
||||||
fun addDnsServers(dnsServers: List<InetAddress>) = apply { this.dnsServers += dnsServers }
|
fun addDnsServers(dnsServers: List<InetAddress>) = apply { this.dnsServers += dnsServers }
|
||||||
|
|
|
||||||
|
|
@ -6,5 +6,6 @@ enum class ProtocolState {
|
||||||
CONNECTING,
|
CONNECTING,
|
||||||
DISCONNECTED,
|
DISCONNECTED,
|
||||||
DISCONNECTING,
|
DISCONNECTING,
|
||||||
|
RECONNECTING,
|
||||||
UNKNOWN
|
UNKNOWN
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,10 @@ class AmneziaActivity : QtActivity() {
|
||||||
QtAndroidController.onVpnDisconnected()
|
QtAndroidController.onVpnDisconnected()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ServiceEvent.RECONNECTING -> {
|
||||||
|
QtAndroidController.onVpnReconnecting()
|
||||||
|
}
|
||||||
|
|
||||||
ServiceEvent.STATUS -> {
|
ServiceEvent.STATUS -> {
|
||||||
if (isWaitingStatus) {
|
if (isWaitingStatus) {
|
||||||
isWaitingStatus = false
|
isWaitingStatus = false
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
|
||||||
import org.amnezia.vpn.protocol.ProtocolState.CONNECTING
|
import org.amnezia.vpn.protocol.ProtocolState.CONNECTING
|
||||||
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
||||||
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTING
|
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTING
|
||||||
|
import org.amnezia.vpn.protocol.ProtocolState.RECONNECTING
|
||||||
import org.amnezia.vpn.protocol.ProtocolState.UNKNOWN
|
import org.amnezia.vpn.protocol.ProtocolState.UNKNOWN
|
||||||
import org.amnezia.vpn.protocol.Statistics
|
import org.amnezia.vpn.protocol.Statistics
|
||||||
import org.amnezia.vpn.protocol.Status
|
import org.amnezia.vpn.protocol.Status
|
||||||
|
|
@ -49,6 +50,7 @@ import org.amnezia.vpn.protocol.putStatistics
|
||||||
import org.amnezia.vpn.protocol.putStatus
|
import org.amnezia.vpn.protocol.putStatus
|
||||||
import org.amnezia.vpn.protocol.wireguard.Wireguard
|
import org.amnezia.vpn.protocol.wireguard.Wireguard
|
||||||
import org.amnezia.vpn.util.Log
|
import org.amnezia.vpn.util.Log
|
||||||
|
import org.amnezia.vpn.util.net.NetworkState
|
||||||
import org.json.JSONException
|
import org.json.JSONException
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
|
@ -85,6 +87,7 @@ class AmneziaVpnService : VpnService() {
|
||||||
private var disconnectionJob: Job? = null
|
private var disconnectionJob: Job? = null
|
||||||
private var statisticsSendingJob: Job? = null
|
private var statisticsSendingJob: Job? = null
|
||||||
private lateinit var clientMessenger: IpcMessenger
|
private lateinit var clientMessenger: IpcMessenger
|
||||||
|
private lateinit var networkState: NetworkState
|
||||||
|
|
||||||
private val connectionExceptionHandler = CoroutineExceptionHandler { _, e ->
|
private val connectionExceptionHandler = CoroutineExceptionHandler { _, e ->
|
||||||
protocolState.value = DISCONNECTED
|
protocolState.value = DISCONNECTED
|
||||||
|
|
@ -181,6 +184,7 @@ class AmneziaVpnService : VpnService() {
|
||||||
connectionScope = CoroutineScope(SupervisorJob() + Dispatchers.IO + connectionExceptionHandler)
|
connectionScope = CoroutineScope(SupervisorJob() + Dispatchers.IO + connectionExceptionHandler)
|
||||||
clientMessenger = IpcMessenger(messengerName = "Client")
|
clientMessenger = IpcMessenger(messengerName = "Client")
|
||||||
launchProtocolStateHandler()
|
launchProtocolStateHandler()
|
||||||
|
networkState = NetworkState(this, ::reconnect)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
|
|
@ -206,7 +210,7 @@ class AmneziaVpnService : VpnService() {
|
||||||
|
|
||||||
override fun onBind(intent: Intent?): IBinder? {
|
override fun onBind(intent: Intent?): IBinder? {
|
||||||
Log.d(TAG, "onBind by $intent")
|
Log.d(TAG, "onBind by $intent")
|
||||||
if (intent?.action == "android.net.VpnService") return super.onBind(intent)
|
if (intent?.action == SERVICE_INTERFACE) return super.onBind(intent)
|
||||||
isServiceBound = true
|
isServiceBound = true
|
||||||
if (isConnected) launchSendingStatistics()
|
if (isConnected) launchSendingStatistics()
|
||||||
return vpnServiceMessenger.binder
|
return vpnServiceMessenger.binder
|
||||||
|
|
@ -214,7 +218,7 @@ class AmneziaVpnService : VpnService() {
|
||||||
|
|
||||||
override fun onUnbind(intent: Intent?): Boolean {
|
override fun onUnbind(intent: Intent?): Boolean {
|
||||||
Log.d(TAG, "onUnbind by $intent")
|
Log.d(TAG, "onUnbind by $intent")
|
||||||
if (intent?.action != "android.net.VpnService") {
|
if (intent?.action != SERVICE_INTERFACE) {
|
||||||
isServiceBound = false
|
isServiceBound = false
|
||||||
stopSendingStatistics()
|
stopSendingStatistics()
|
||||||
clientMessenger.reset()
|
clientMessenger.reset()
|
||||||
|
|
@ -225,7 +229,7 @@ class AmneziaVpnService : VpnService() {
|
||||||
|
|
||||||
override fun onRebind(intent: Intent?) {
|
override fun onRebind(intent: Intent?) {
|
||||||
Log.d(TAG, "onRebind by $intent")
|
Log.d(TAG, "onRebind by $intent")
|
||||||
if (intent?.action != "android.net.VpnService") {
|
if (intent?.action != SERVICE_INTERFACE) {
|
||||||
isServiceBound = true
|
isServiceBound = true
|
||||||
if (isConnected) launchSendingStatistics()
|
if (isConnected) launchSendingStatistics()
|
||||||
}
|
}
|
||||||
|
|
@ -272,16 +276,24 @@ class AmneziaVpnService : VpnService() {
|
||||||
when (protocolState) {
|
when (protocolState) {
|
||||||
CONNECTED -> {
|
CONNECTED -> {
|
||||||
clientMessenger.send(ServiceEvent.CONNECTED)
|
clientMessenger.send(ServiceEvent.CONNECTED)
|
||||||
|
networkState.bindNetworkListener()
|
||||||
if (isServiceBound) launchSendingStatistics()
|
if (isServiceBound) launchSendingStatistics()
|
||||||
}
|
}
|
||||||
|
|
||||||
DISCONNECTED -> {
|
DISCONNECTED -> {
|
||||||
clientMessenger.send(ServiceEvent.DISCONNECTED)
|
clientMessenger.send(ServiceEvent.DISCONNECTED)
|
||||||
|
networkState.unbindNetworkListener()
|
||||||
stopSendingStatistics()
|
stopSendingStatistics()
|
||||||
if (!isServiceBound) stopService()
|
if (!isServiceBound) stopService()
|
||||||
}
|
}
|
||||||
|
|
||||||
DISCONNECTING -> {
|
DISCONNECTING -> {
|
||||||
|
networkState.unbindNetworkListener()
|
||||||
|
stopSendingStatistics()
|
||||||
|
}
|
||||||
|
|
||||||
|
RECONNECTING -> {
|
||||||
|
clientMessenger.send(ServiceEvent.RECONNECTING)
|
||||||
stopSendingStatistics()
|
stopSendingStatistics()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -367,6 +379,19 @@ class AmneziaVpnService : VpnService() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainThread
|
||||||
|
private fun reconnect() {
|
||||||
|
if (!isConnected) return
|
||||||
|
|
||||||
|
Log.v(TAG, "Reconnect VPN")
|
||||||
|
|
||||||
|
protocolState.value = RECONNECTING
|
||||||
|
|
||||||
|
connectionJob = connectionScope.launch {
|
||||||
|
protocol?.reconnectVpn(Builder())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@MainThread
|
@MainThread
|
||||||
private fun getProtocol(protocolName: String): Protocol =
|
private fun getProtocol(protocolName: String): Protocol =
|
||||||
protocolCache[protocolName]
|
protocolCache[protocolName]
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ sealed interface IpcMessage {
|
||||||
enum class ServiceEvent : IpcMessage {
|
enum class ServiceEvent : IpcMessage {
|
||||||
CONNECTED,
|
CONNECTED,
|
||||||
DISCONNECTED,
|
DISCONNECTED,
|
||||||
|
RECONNECTING,
|
||||||
STATUS,
|
STATUS,
|
||||||
STATISTICS_UPDATE,
|
STATISTICS_UPDATE,
|
||||||
ERROR
|
ERROR
|
||||||
|
|
|
||||||
|
|
@ -1,195 +0,0 @@
|
||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
package org.amnezia.vpn
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.*
|
|
||||||
import android.net.*
|
|
||||||
import android.system.ErrnoException
|
|
||||||
import android.net.NetworkCapabilities
|
|
||||||
import android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL
|
|
||||||
import android.net.NetworkCapabilities.NET_CAPABILITY_DUN
|
|
||||||
import android.net.NetworkCapabilities.NET_CAPABILITY_FOREGROUND
|
|
||||||
import android.net.NetworkCapabilities.NET_CAPABILITY_FOTA
|
|
||||||
import android.net.NetworkCapabilities.NET_CAPABILITY_IA
|
|
||||||
import android.net.NetworkCapabilities.NET_CAPABILITY_IMS
|
|
||||||
import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
|
|
||||||
import android.net.NetworkCapabilities.NET_CAPABILITY_MCX
|
|
||||||
import android.net.NetworkCapabilities.NET_CAPABILITY_MMS
|
|
||||||
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED
|
|
||||||
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED
|
|
||||||
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING
|
|
||||||
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED
|
|
||||||
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN
|
|
||||||
import android.net.NetworkCapabilities.NET_CAPABILITY_SUPL
|
|
||||||
import android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED
|
|
||||||
import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED
|
|
||||||
import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
|
|
||||||
import android.net.NetworkCapabilities.NET_CAPABILITY_WIFI_P2P
|
|
||||||
import android.net.NetworkCapabilities.NET_CAPABILITY_XCAP
|
|
||||||
import android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH
|
|
||||||
import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
|
|
||||||
import android.net.NetworkCapabilities.TRANSPORT_ETHERNET
|
|
||||||
import android.net.NetworkCapabilities.TRANSPORT_LOWPAN
|
|
||||||
import android.net.NetworkCapabilities.TRANSPORT_USB
|
|
||||||
import android.net.NetworkCapabilities.TRANSPORT_VPN
|
|
||||||
import android.net.NetworkCapabilities.TRANSPORT_WIFI
|
|
||||||
import android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE
|
|
||||||
import java.io.Closeable
|
|
||||||
import java.util.EnumSet
|
|
||||||
import java.io.File
|
|
||||||
import androidx.core.app.ActivityCompat
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import java.io.FileDescriptor
|
|
||||||
import java.io.IOException
|
|
||||||
import java.lang.Exception
|
|
||||||
import org.amnezia.vpn.util.Log
|
|
||||||
|
|
||||||
class NetworkState(var service: AmneziaVpnService) {
|
|
||||||
private var mService: AmneziaVpnService = service
|
|
||||||
var mCurrentContext: Context = service
|
|
||||||
private val tag = "NetworkState"
|
|
||||||
private var active = false
|
|
||||||
private var listeningForDefaultNetwork = false
|
|
||||||
private var metered = false
|
|
||||||
|
|
||||||
|
|
||||||
enum class Transport(val systemConstant: Int) {
|
|
||||||
BLUETOOTH(TRANSPORT_BLUETOOTH),
|
|
||||||
CELLULAR(TRANSPORT_CELLULAR),
|
|
||||||
ETHERNET(TRANSPORT_ETHERNET),
|
|
||||||
VPN(TRANSPORT_VPN),
|
|
||||||
WIFI(TRANSPORT_WIFI),
|
|
||||||
WIFI_AWARE(if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) TRANSPORT_WIFI_AWARE else UNSUPPORTED_TRANSPORT),
|
|
||||||
LOWPAN(if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) TRANSPORT_LOWPAN else UNSUPPORTED_TRANSPORT),
|
|
||||||
USB(if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) TRANSPORT_USB else UNSUPPORTED_TRANSPORT)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
private const val UNSUPPORTED_TRANSPORT: Int = -1 // The TRANSPORT_* constants are non-negative.
|
|
||||||
private const val NOT_VPN = "NOT_VPN"
|
|
||||||
|
|
||||||
private val defaultNetworkRequest = NetworkRequest.Builder()
|
|
||||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
|
||||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private data class NetworkTransports(
|
|
||||||
val network: Network,
|
|
||||||
val transports: Set<Transport>
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun getTransports(networkCapabilities: NetworkCapabilities): EnumSet<Transport> =
|
|
||||||
Transport.values().mapNotNullTo(EnumSet.noneOf(Transport::class.java)) {
|
|
||||||
if (networkCapabilities.hasTransport(it.systemConstant)) it else null
|
|
||||||
}
|
|
||||||
|
|
||||||
private var defaultNetworkCapabilities: Map<String, Boolean> = LinkedHashMap()
|
|
||||||
private var defaultNetwork: NetworkTransports? = null
|
|
||||||
val defaultNetworkTransports: Set<Transport>
|
|
||||||
get() = defaultNetwork?.transports ?: emptySet()
|
|
||||||
|
|
||||||
private val capabilitiesConstantMap = mutableMapOf(
|
|
||||||
"MMS" to NET_CAPABILITY_MMS,
|
|
||||||
"SUPL" to NET_CAPABILITY_SUPL,
|
|
||||||
"DUN" to NET_CAPABILITY_DUN,
|
|
||||||
"FOTA" to NET_CAPABILITY_FOTA,
|
|
||||||
"IMS" to NET_CAPABILITY_IMS,
|
|
||||||
"WIFI_P2P" to NET_CAPABILITY_WIFI_P2P,
|
|
||||||
"IA" to NET_CAPABILITY_IA,
|
|
||||||
"XCAP" to NET_CAPABILITY_XCAP,
|
|
||||||
"NOT_METERED" to NET_CAPABILITY_NOT_METERED,
|
|
||||||
"INTERNET" to NET_CAPABILITY_INTERNET,
|
|
||||||
NOT_VPN to NET_CAPABILITY_NOT_VPN,
|
|
||||||
"TRUSTED" to NET_CAPABILITY_TRUSTED,
|
|
||||||
"TEMP NOT METERED" to NET_CAPABILITY_TEMPORARILY_NOT_METERED,
|
|
||||||
"NOT SUSPENDED" to NET_CAPABILITY_MCX,
|
|
||||||
).apply {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
||||||
put("VALIDATED", NET_CAPABILITY_VALIDATED)
|
|
||||||
put("CAPTIVE PORTAL", NET_CAPABILITY_CAPTIVE_PORTAL)
|
|
||||||
}
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
||||||
put("NOT ROAMING", NET_CAPABILITY_NOT_ROAMING)
|
|
||||||
put("TRUSTED", NET_CAPABILITY_FOREGROUND)
|
|
||||||
put("NOT CONGESTED", NET_CAPABILITY_NOT_CONGESTED)
|
|
||||||
put("NOT SUSPENDED", NET_CAPABILITY_NOT_SUSPENDED)
|
|
||||||
}
|
|
||||||
} as Map<String, Int>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private val connectivity by lazy { mCurrentContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager }
|
|
||||||
|
|
||||||
private var mLastNetworkCapabilities: String? = null
|
|
||||||
|
|
||||||
private val defaultNetworkCallback = object : ConnectivityManager.NetworkCallback() {
|
|
||||||
override fun onAvailable(network: Network) {
|
|
||||||
super.onAvailable(network)
|
|
||||||
|
|
||||||
|
|
||||||
Log.i(tag, "onAvailable $network")
|
|
||||||
}
|
|
||||||
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
|
|
||||||
val newCapabilities = capabilitiesConstantMap.mapValues {
|
|
||||||
networkCapabilities.hasCapability(it.value)
|
|
||||||
}
|
|
||||||
val newTransports = getTransports(networkCapabilities)
|
|
||||||
val capabilitiesChanged = defaultNetworkCapabilities != newCapabilities
|
|
||||||
if (defaultNetwork?.network != network ||
|
|
||||||
defaultNetwork?.transports != newTransports ||
|
|
||||||
capabilitiesChanged
|
|
||||||
) {
|
|
||||||
Log.i(
|
|
||||||
tag,
|
|
||||||
"default network: $network; transports: ${newTransports.joinToString(", ")}; " +
|
|
||||||
"capabilities: $newCapabilities"
|
|
||||||
)
|
|
||||||
defaultNetwork = NetworkTransports(network, newTransports)
|
|
||||||
}
|
|
||||||
if (capabilitiesChanged) {
|
|
||||||
// mService.networkChange()
|
|
||||||
|
|
||||||
Log.i(tag, "onCapabilitiesChanged capabilitiesChanged $network $networkCapabilities")
|
|
||||||
defaultNetworkCapabilities = newCapabilities
|
|
||||||
}
|
|
||||||
super.onCapabilitiesChanged(network, networkCapabilities)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBlockedStatusChanged(network: Network, blocked: Boolean) {
|
|
||||||
super.onBlockedStatusChanged(network, blocked)
|
|
||||||
Log.i(tag, "onBlockedStatusChanged $network $blocked")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
override fun onLost(network: Network) {
|
|
||||||
super.onLost(network)
|
|
||||||
Log.i(tag, "onLost")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun bindNetworkListener() {
|
|
||||||
if (Build.VERSION.SDK_INT >= 28) {
|
|
||||||
// we want REQUEST here instead of LISTEN
|
|
||||||
connectivity.requestNetwork(defaultNetworkRequest, defaultNetworkCallback)
|
|
||||||
listeningForDefaultNetwork = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun unBindNetworkListener() {
|
|
||||||
if (Build.VERSION.SDK_INT >= 28) {
|
|
||||||
connectivity.unregisterNetworkCallback(defaultNetworkCallback)
|
|
||||||
listeningForDefaultNetwork = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -12,6 +12,7 @@ object QtAndroidController {
|
||||||
external fun onVpnPermissionRejected()
|
external fun onVpnPermissionRejected()
|
||||||
external fun onVpnConnected()
|
external fun onVpnConnected()
|
||||||
external fun onVpnDisconnected()
|
external fun onVpnDisconnected()
|
||||||
|
external fun onVpnReconnecting()
|
||||||
external fun onStatisticsUpdate(rxBytes: Long, txBytes: Long)
|
external fun onStatisticsUpdate(rxBytes: Long, txBytes: Long)
|
||||||
|
|
||||||
external fun onConfigImported()
|
external fun onConfigImported()
|
||||||
|
|
|
||||||
104
client/android/utils/src/main/kotlin/net/NetworkState.kt
Normal file
104
client/android/utils/src/main/kotlin/net/NetworkState.kt
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
package org.amnezia.vpn.util.net
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.ConnectivityManager
|
||||||
|
import android.net.ConnectivityManager.NetworkCallback
|
||||||
|
import android.net.Network
|
||||||
|
import android.net.NetworkCapabilities
|
||||||
|
import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
|
||||||
|
import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
|
||||||
|
import android.net.NetworkRequest
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Handler
|
||||||
|
import kotlin.LazyThreadSafetyMode.NONE
|
||||||
|
import org.amnezia.vpn.util.Log
|
||||||
|
|
||||||
|
private const val TAG = "NetworkState"
|
||||||
|
|
||||||
|
class NetworkState(
|
||||||
|
private val context: Context,
|
||||||
|
private val onNetworkChange: () -> Unit
|
||||||
|
) {
|
||||||
|
private var currentNetwork: Network? = null
|
||||||
|
private var validated: Boolean = false
|
||||||
|
private var isListenerBound = false
|
||||||
|
|
||||||
|
private val handler: Handler by lazy(NONE) {
|
||||||
|
Handler(context.mainLooper)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val connectivityManager: ConnectivityManager by lazy(NONE) {
|
||||||
|
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||||
|
}
|
||||||
|
|
||||||
|
private val networkRequest: NetworkRequest by lazy(NONE) {
|
||||||
|
NetworkRequest.Builder()
|
||||||
|
.addCapability(NET_CAPABILITY_INTERNET)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val networkCallback: NetworkCallback by lazy(NONE) {
|
||||||
|
object : NetworkCallback() {
|
||||||
|
override fun onAvailable(network: Network) {
|
||||||
|
Log.d(TAG, "onAvailable: $network")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
|
||||||
|
Log.d(TAG, "onCapabilitiesChanged: $network, $networkCapabilities")
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
checkNetworkState(network, networkCapabilities)
|
||||||
|
} else {
|
||||||
|
handler.post {
|
||||||
|
checkNetworkState(network, networkCapabilities)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkNetworkState(network: Network, networkCapabilities: NetworkCapabilities) {
|
||||||
|
if (currentNetwork == null) {
|
||||||
|
currentNetwork = network
|
||||||
|
validated = networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)
|
||||||
|
} else {
|
||||||
|
if (currentNetwork != network) {
|
||||||
|
currentNetwork = network
|
||||||
|
validated = false
|
||||||
|
}
|
||||||
|
if (!validated) {
|
||||||
|
validated = networkCapabilities.hasCapability(NET_CAPABILITY_VALIDATED)
|
||||||
|
if (validated) onNetworkChange()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBlockedStatusChanged(network: Network, blocked: Boolean) {
|
||||||
|
Log.d(TAG, "onBlockedStatusChanged: $network, $blocked")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLost(network: Network) {
|
||||||
|
Log.d(TAG, "onLost: $network")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bindNetworkListener() {
|
||||||
|
if (isListenerBound) return
|
||||||
|
Log.v(TAG, "Bind network listener")
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
|
connectivityManager.registerBestMatchingNetworkCallback(networkRequest, networkCallback, handler)
|
||||||
|
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
connectivityManager.requestNetwork(networkRequest, networkCallback, handler)
|
||||||
|
} else {
|
||||||
|
connectivityManager.requestNetwork(networkRequest, networkCallback)
|
||||||
|
}
|
||||||
|
isListenerBound = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unbindNetworkListener() {
|
||||||
|
if (!isListenerBound) return
|
||||||
|
Log.v(TAG, "Unbind network listener")
|
||||||
|
connectivityManager.unregisterNetworkCallback(networkCallback)
|
||||||
|
isListenerBound = false
|
||||||
|
currentNetwork = null
|
||||||
|
validated = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -173,4 +173,8 @@ open class Wireguard : Protocol() {
|
||||||
GoBackend.wgTurnOff(handleToClose)
|
GoBackend.wgTurnOff(handleToClose)
|
||||||
state.value = DISCONNECTED
|
state.value = DISCONNECTED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun reconnectVpn(vpnBuilder: Builder) {
|
||||||
|
state.value = CONNECTED
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,14 @@ AndroidController::AndroidController() : QObject()
|
||||||
},
|
},
|
||||||
Qt::QueuedConnection);
|
Qt::QueuedConnection);
|
||||||
|
|
||||||
|
connect(
|
||||||
|
this, &AndroidController::vpnReconnecting, this,
|
||||||
|
[this]() {
|
||||||
|
qDebug() << "Android event: VPN reconnecting";
|
||||||
|
emit connectionStateChanged(Vpn::ConnectionState::Reconnecting);
|
||||||
|
},
|
||||||
|
Qt::QueuedConnection);
|
||||||
|
|
||||||
connect(
|
connect(
|
||||||
this, &AndroidController::configImported, this,
|
this, &AndroidController::configImported, this,
|
||||||
[]() {
|
[]() {
|
||||||
|
|
@ -101,6 +109,7 @@ bool AndroidController::initialize()
|
||||||
{"onVpnPermissionRejected", "()V", reinterpret_cast<void *>(onVpnPermissionRejected)},
|
{"onVpnPermissionRejected", "()V", reinterpret_cast<void *>(onVpnPermissionRejected)},
|
||||||
{"onVpnConnected", "()V", reinterpret_cast<void *>(onVpnConnected)},
|
{"onVpnConnected", "()V", reinterpret_cast<void *>(onVpnConnected)},
|
||||||
{"onVpnDisconnected", "()V", reinterpret_cast<void *>(onVpnDisconnected)},
|
{"onVpnDisconnected", "()V", reinterpret_cast<void *>(onVpnDisconnected)},
|
||||||
|
{"onVpnReconnecting", "()V", reinterpret_cast<void *>(onVpnReconnecting)},
|
||||||
{"onStatisticsUpdate", "(JJ)V", reinterpret_cast<void *>(onStatisticsUpdate)},
|
{"onStatisticsUpdate", "(JJ)V", reinterpret_cast<void *>(onStatisticsUpdate)},
|
||||||
{"onConfigImported", "()V", reinterpret_cast<void *>(onConfigImported)},
|
{"onConfigImported", "()V", reinterpret_cast<void *>(onConfigImported)},
|
||||||
{"decodeQrCode", "(Ljava/lang/String;)Z", reinterpret_cast<bool *>(decodeQrCode)}
|
{"decodeQrCode", "(Ljava/lang/String;)Z", reinterpret_cast<bool *>(decodeQrCode)}
|
||||||
|
|
@ -187,6 +196,7 @@ Vpn::ConnectionState AndroidController::convertState(AndroidController::Connecti
|
||||||
case AndroidController::ConnectionState::CONNECTING: return Vpn::ConnectionState::Connecting;
|
case AndroidController::ConnectionState::CONNECTING: return Vpn::ConnectionState::Connecting;
|
||||||
case AndroidController::ConnectionState::DISCONNECTED: return Vpn::ConnectionState::Disconnected;
|
case AndroidController::ConnectionState::DISCONNECTED: return Vpn::ConnectionState::Disconnected;
|
||||||
case AndroidController::ConnectionState::DISCONNECTING: return Vpn::ConnectionState::Disconnecting;
|
case AndroidController::ConnectionState::DISCONNECTING: return Vpn::ConnectionState::Disconnecting;
|
||||||
|
case AndroidController::ConnectionState::RECONNECTING: return Vpn::ConnectionState::Reconnecting;
|
||||||
case AndroidController::ConnectionState::UNKNOWN: return Vpn::ConnectionState::Unknown;
|
case AndroidController::ConnectionState::UNKNOWN: return Vpn::ConnectionState::Unknown;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -199,6 +209,7 @@ QString AndroidController::textConnectionState(AndroidController::ConnectionStat
|
||||||
case AndroidController::ConnectionState::CONNECTING: return "CONNECTING";
|
case AndroidController::ConnectionState::CONNECTING: return "CONNECTING";
|
||||||
case AndroidController::ConnectionState::DISCONNECTED: return "DISCONNECTED";
|
case AndroidController::ConnectionState::DISCONNECTED: return "DISCONNECTED";
|
||||||
case AndroidController::ConnectionState::DISCONNECTING: return "DISCONNECTING";
|
case AndroidController::ConnectionState::DISCONNECTING: return "DISCONNECTING";
|
||||||
|
case AndroidController::ConnectionState::RECONNECTING: return "RECONNECTING";
|
||||||
case AndroidController::ConnectionState::UNKNOWN: return "UNKNOWN";
|
case AndroidController::ConnectionState::UNKNOWN: return "UNKNOWN";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -260,6 +271,15 @@ void AndroidController::onVpnDisconnected(JNIEnv *env, jobject thiz)
|
||||||
emit AndroidController::instance()->vpnDisconnected();
|
emit AndroidController::instance()->vpnDisconnected();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
void AndroidController::onVpnReconnecting(JNIEnv *env, jobject thiz)
|
||||||
|
{
|
||||||
|
Q_UNUSED(env);
|
||||||
|
Q_UNUSED(thiz);
|
||||||
|
|
||||||
|
emit AndroidController::instance()->vpnReconnecting();
|
||||||
|
}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
void AndroidController::onStatisticsUpdate(JNIEnv *env, jobject thiz, jlong rxBytes, jlong txBytes)
|
void AndroidController::onStatisticsUpdate(JNIEnv *env, jobject thiz, jlong rxBytes, jlong txBytes)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ public:
|
||||||
CONNECTING,
|
CONNECTING,
|
||||||
DISCONNECTED,
|
DISCONNECTED,
|
||||||
DISCONNECTING,
|
DISCONNECTING,
|
||||||
|
RECONNECTING,
|
||||||
UNKNOWN
|
UNKNOWN
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -40,6 +41,7 @@ signals:
|
||||||
void vpnPermissionRejected();
|
void vpnPermissionRejected();
|
||||||
void vpnConnected();
|
void vpnConnected();
|
||||||
void vpnDisconnected();
|
void vpnDisconnected();
|
||||||
|
void vpnReconnecting();
|
||||||
void statisticsUpdated(quint64 rxBytes, quint64 txBytes);
|
void statisticsUpdated(quint64 rxBytes, quint64 txBytes);
|
||||||
void configImported();
|
void configImported();
|
||||||
void importConfigFromOutside(QString &data);
|
void importConfigFromOutside(QString &data);
|
||||||
|
|
@ -60,6 +62,7 @@ private:
|
||||||
static void onVpnPermissionRejected(JNIEnv *env, jobject thiz);
|
static void onVpnPermissionRejected(JNIEnv *env, jobject thiz);
|
||||||
static void onVpnConnected(JNIEnv *env, jobject thiz);
|
static void onVpnConnected(JNIEnv *env, jobject thiz);
|
||||||
static void onVpnDisconnected(JNIEnv *env, jobject thiz);
|
static void onVpnDisconnected(JNIEnv *env, jobject thiz);
|
||||||
|
static void onVpnReconnecting(JNIEnv *env, jobject thiz);
|
||||||
static void onStatisticsUpdate(JNIEnv *env, jobject thiz, jlong rxBytes, jlong txBytes);
|
static void onStatisticsUpdate(JNIEnv *env, jobject thiz, jlong rxBytes, jlong txBytes);
|
||||||
static void onConfigImported(JNIEnv *env, jobject thiz);
|
static void onConfigImported(JNIEnv *env, jobject thiz);
|
||||||
static bool decodeQrCode(JNIEnv *env, jobject thiz, jstring data);
|
static bool decodeQrCode(JNIEnv *env, jobject thiz, jstring data);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue