Various fixes

This commit is contained in:
pokamest 2022-02-09 15:23:20 +03:00
parent cb21991efa
commit 505c9c6218
5 changed files with 306 additions and 295 deletions

View file

@ -37,12 +37,13 @@ class VPNService : android.net.VpnService() {
SharedLibraryLoader.loadSharedLibrary(this, "ovpn3") SharedLibraryLoader.loadSharedLibrary(this, "ovpn3")
Log.i(tag, "Loaded libs") Log.i(tag, "Loaded libs")
Log.e(tag, "Wireguard Version ${wgVersion()}") Log.e(tag, "Wireguard Version ${wgVersion()}")
mOpenVPNThreadv3 = OpenVPNThreadv3 (this) mOpenVPNThreadv3 = OpenVPNThreadv3(this)
mAlreadyInitialised = true mAlreadyInitialised = true
} }
override fun onUnbind(intent: Intent?): Boolean { override fun onUnbind(intent: Intent?): Boolean {
Log.v(tag, "Got Unbind request")
if (!isUp) { if (!isUp) {
// If the Qt Client got closed while we were not connected // If the Qt Client got closed while we were not connected
// we do not need to stay as a foreground service. // we do not need to stay as a foreground service.
@ -52,9 +53,9 @@ class VPNService : android.net.VpnService() {
} }
/** /**
* EntryPoint for the Service, gets Called when AndroidController.cpp * EntryPoint for the Service, gets Called when AndroidController.cpp
* calles bindService. Returns the [VPNServiceBinder] so QT can send Requests to it. * calles bindService. Returns the [VPNServiceBinder] so QT can send Requests to it.
*/ */
override fun onBind(intent: Intent?): IBinder? { override fun onBind(intent: Intent?): IBinder? {
Log.v(tag, "Got Bind request") Log.v(tag, "Got Bind request")
init() init()
@ -62,10 +63,10 @@ class VPNService : android.net.VpnService() {
} }
/** /**
* Might be the entryPoint if the Service gets Started via an * Might be the entryPoint if the Service gets Started via an
* Service Intent: Might be from Always-On-Vpn from Settings * Service Intent: Might be from Always-On-Vpn from Settings
* or from Booting the device and having "connect on boot" enabled. * or from Booting the device and having "connect on boot" enabled.
*/ */
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
init() init()
intent?.let { intent?.let {
@ -84,28 +85,28 @@ class VPNService : android.net.VpnService() {
Log.e( Log.e(
tag, tag,
"VPN service was triggered without defining a Server or having a tunnel" "VPN service was triggered without defining a Server or having a tunnel"
) )
return super.onStartCommand(intent, flags, startId) return super.onStartCommand(intent, flags, startId)
}
this.mConfig = JSONObject(lastConfString)
} }
this.mConfig = JSONObject(lastConfString)
return super.onStartCommand(intent, flags, startId)
} }
// Invoked when the application is revoked. return super.onStartCommand(intent, flags, startId)
// At this moment, the VPN interface is already deactivated by the system. }
override fun onRevoke() {
this.turnOff()
super.onRevoke()
}
var connectionTime: Long = 0 // Invoked when the application is revoked.
// At this moment, the VPN interface is already deactivated by the system.
override fun onRevoke() {
this.turnOff()
super.onRevoke()
}
var connectionTime: Long = 0
get() { get() {
return mConnectionTime return mConnectionTime
} }
var isUp: Boolean var isUp: Boolean
get() { get() {
return currentTunnelHandle >= 0 return currentTunnelHandle >= 0
} }
@ -118,7 +119,7 @@ class VPNService : android.net.VpnService() {
mBinder.dispatchEvent(VPNServiceBinder.EVENTS.disconnected, "") mBinder.dispatchEvent(VPNServiceBinder.EVENTS.disconnected, "")
mConnectionTime = 0 mConnectionTime = 0
} }
val status: JSONObject val status: JSONObject
get() { get() {
val deviceIpv4: String = "" val deviceIpv4: String = ""
return JSONObject().apply { return JSONObject().apply {
@ -128,299 +129,308 @@ class VPNService : android.net.VpnService() {
putOpt("deviceIpv4", mConfig?.getJSONObject("device")?.getString("ipv4Address")) putOpt("deviceIpv4", mConfig?.getJSONObject("device")?.getString("ipv4Address"))
} }
} }
/*
* Checks if the VPN Permission is given.
* If the permission is given, returns true
* Requests permission and returns false if not.
*/
fun checkPermissions(): Boolean {
// See https://developer.android.com/guide/topics/connectivity/vpn#connect_a_service
// Call Prepare, if we get an Intent back, we dont have the VPN Permission
// from the user. So we need to pass this to our main Activity and exit here.
val intent = prepare(this)
if (intent == null) {
Log.e(tag, "VPN Permission Already Present")
return true
}
Log.e(tag, "Requesting VPN Permission")
return false
}
fun turnOn(json: JSONObject?): Int { /*
if (!checkPermissions()) { * Checks if the VPN Permission is given.
Log.e(tag, "turn on was called without no permissions present!") * If the permission is given, returns true
isUp = false * Requests permission and returns false if not.
return 0 */
} fun checkPermissions(): Boolean {
Log.i(tag, "Permission okay") // See https://developer.android.com/guide/topics/connectivity/vpn#connect_a_service
mConfig = json!! // Call Prepare, if we get an Intent back, we dont have the VPN Permission
mProtocol = mConfig!!.getString("protocol") // from the user. So we need to pass this to our main Activity and exit here.
when (mProtocol) { val intent = prepare(this)
"openvpn" -> startOpenVpn() if (intent == null) {
"wireguard" -> startWireGuard() Log.e(tag, "VPN Permission Already Present")
else -> {
Log.e(tag, "No protocol")
return 0
}
}
return 1
}
fun establish(): ParcelFileDescriptor? {
mbuilder.allowFamily(OsConstants.AF_INET)
mbuilder.allowFamily(OsConstants.AF_INET6)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) mbuilder.setMetered(false)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) setUnderlyingNetworks(null)
return mbuilder.establish()
}
fun setMtu(mtu: Int) {
mbuilder.setMtu(mtu)
}
fun addAddress(ip: String, len: Int){
Log.v(tag, "mbuilder.addAddress($ip, $len)")
mbuilder.addAddress(ip, len)
}
fun addRoute(ip: String, len: Int){
Log.v(tag, "mbuilder.addRoute($ip, $len)")
mbuilder.addRoute(ip, len)
}
fun addDNS(ip: String){
Log.v(tag, "mbuilder.addDnsServer($ip)")
mbuilder.addDnsServer(ip)
if ("samsung".equals(Build.BRAND) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
mbuilder.addRoute(ip, 32)
}
}
fun setSessionName(name: String){
Log.v(tag, "mbuilder.setSession($name)")
mbuilder.setSession(name)
}
fun addHttpProxy(host: String, port: Int): Boolean{
val proxyInfo = ProxyInfo.buildDirectProxy(host, port)
Log.v(tag, "mbuilder.addHttpProxy($host, $port)")
mbuilder.setHttpProxy(proxyInfo)
return true return true
} }
Log.e(tag, "Requesting VPN Permission")
return false
}
fun setDomain(domain: String) { fun turnOn(json: JSONObject?): Int {
Log.v(tag, "mbuilder.setDomain($domain)") if (!checkPermissions()) {
mbuilder.addSearchDomain(domain) Log.e(tag, "turn on was called without no permissions present!")
}
fun turnOff() {
Log.v(tag, "Try to disable tunnel")
when(mProtocol){
"wireguard" -> wgTurnOff(currentTunnelHandle)
"openvpn" -> ovpnTurnOff()
else -> {
Log.e(tag, "No protocol")
}
}
currentTunnelHandle = -1
stopForeground(true)
isUp = false isUp = false
stopSelf(); return 0
} }
Log.i(tag, "Permission okay")
mConfig = json!!
mProtocol = mConfig!!.getString("protocol")
when (mProtocol) {
"openvpn" -> startOpenVpn()
"wireguard" -> startWireGuard()
else -> {
Log.e(tag, "No protocol")
return 0
}
}
return 1
}
fun establish(): ParcelFileDescriptor? {
mbuilder.allowFamily(OsConstants.AF_INET)
mbuilder.allowFamily(OsConstants.AF_INET6)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) mbuilder.setMetered(false)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) setUnderlyingNetworks(null)
return mbuilder.establish()
}
fun setMtu(mtu: Int) {
mbuilder.setMtu(mtu)
}
fun addAddress(ip: String, len: Int) {
Log.v(tag, "mbuilder.addAddress($ip, $len)")
mbuilder.addAddress(ip, len)
}
fun addRoute(ip: String, len: Int) {
Log.v(tag, "mbuilder.addRoute($ip, $len)")
mbuilder.addRoute(ip, len)
}
fun addDNS(ip: String) {
Log.v(tag, "mbuilder.addDnsServer($ip)")
mbuilder.addDnsServer(ip)
if ("samsung".equals(Build.BRAND) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mbuilder.addRoute(ip, 32)
}
}
fun setSessionName(name: String) {
Log.v(tag, "mbuilder.setSession($name)")
mbuilder.setSession(name)
}
fun addHttpProxy(host: String, port: Int): Boolean {
val proxyInfo = ProxyInfo.buildDirectProxy(host, port)
Log.v(tag, "mbuilder.addHttpProxy($host, $port)")
mbuilder.setHttpProxy(proxyInfo)
return true
}
fun setDomain(domain: String) {
Log.v(tag, "mbuilder.setDomain($domain)")
mbuilder.addSearchDomain(domain)
}
fun turnOff() {
Log.v(tag, "Try to disable tunnel")
when (mProtocol) {
"wireguard" -> wgTurnOff(currentTunnelHandle)
"openvpn" -> ovpnTurnOff()
else -> {
Log.e(tag, "No protocol")
}
}
currentTunnelHandle = -1
stopForeground(true)
isUp = false
stopSelf();
}
private fun ovpnTurnOff() { private fun ovpnTurnOff() {
mOpenVPNThreadv3?.stop() mOpenVPNThreadv3?.stop()
mOpenVPNThreadv3 = null mOpenVPNThreadv3 = null
Log.e(tag, "mOpenVPNThreadv3 stop!") Log.e(tag, "mOpenVPNThreadv3 stop!")
} }
/**
* Configures an Android VPN Service Tunnel /**
* with a given Wireguard Config * Configures an Android VPN Service Tunnel
*/ * with a given Wireguard Config
private fun setupBuilder(config: Config, builder: Builder) { */
// Setup Split tunnel private fun setupBuilder(config: Config, builder: Builder) {
for (excludedApplication in config.`interface`.excludedApplications) // Setup Split tunnel
for (excludedApplication in config.`interface`.excludedApplications)
builder.addDisallowedApplication(excludedApplication) builder.addDisallowedApplication(excludedApplication)
// Device IP // Device IP
for (addr in config.`interface`.addresses) builder.addAddress(addr.address, addr.mask) for (addr in config.`interface`.addresses) builder.addAddress(addr.address, addr.mask)
// DNS // DNS
for (addr in config.`interface`.dnsServers) builder.addDnsServer(addr.hostAddress) for (addr in config.`interface`.dnsServers) builder.addDnsServer(addr.hostAddress)
// Add All routes the VPN may route tos // Add All routes the VPN may route tos
for (peer in config.peers) { for (peer in config.peers) {
for (addr in peer.allowedIps) { for (addr in peer.allowedIps) {
builder.addRoute(addr.address, addr.mask) builder.addRoute(addr.address, addr.mask)
}
} }
builder.allowFamily(OsConstants.AF_INET)
builder.allowFamily(OsConstants.AF_INET6)
builder.setMtu(config.`interface`.mtu.orElse(1280))
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) builder.setMetered(false)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) setUnderlyingNetworks(null)
builder.setBlocking(true)
} }
builder.allowFamily(OsConstants.AF_INET)
builder.allowFamily(OsConstants.AF_INET6)
builder.setMtu(config.`interface`.mtu.orElse(1280))
/** if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) builder.setMetered(false)
* Gets config value for {key} from the Current if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) setUnderlyingNetworks(null)
* running Wireguard tunnel
*/ builder.setBlocking(true)
private fun getConfigValue(key: String): String? { }
if (!isUp) {
return null /**
} * Gets config value for {key} from the Current
val config = wgGetConfig(currentTunnelHandle) ?: return null * running Wireguard tunnel
val lines = config.split("\n") */
for (line in lines) { private fun getConfigValue(key: String): String? {
val parts = line.split("=") if (!isUp) {
val k = parts.first()
val value = parts.last()
if (key == k) {
return value
}
}
return null return null
} }
val config = wgGetConfig(currentTunnelHandle) ?: return null
val lines = config.split("\n")
for (line in lines) {
val parts = line.split("=")
val k = parts.first()
val value = parts.last()
if (key == k) {
return value
}
}
return null
}
private fun parseConfigData(data: String): Map<String, Map<String, String>> { private fun parseConfigData(data: String): Map<String, Map<String, String>> {
val parseData = mutableMapOf<String, Map<String, String>>() val parseData = mutableMapOf<String, Map<String, String>>()
var currentSection: Pair<String, MutableMap<String, String>>? = null var currentSection: Pair<String, MutableMap<String, String>>? = null
data.lines().forEach { line -> data.lines().forEach { line ->
if (line.isNotEmpty()) { if (line.isNotEmpty()) {
if (line.startsWith('[')) { if (line.startsWith('[')) {
currentSection?.let { currentSection?.let {
parseData.put(it.first, it.second) parseData.put(it.first, it.second)
}
currentSection = line.substring(1, line.indexOfLast { it == ']' }) to mutableMapOf()
} else {
val parameter = line.split("=", limit = 2)
currentSection!!.second.put(parameter.first().trim(), parameter.last().trim())
} }
currentSection =
line.substring(1, line.indexOfLast { it == ']' }) to mutableMapOf()
} else {
val parameter = line.split("=", limit = 2)
currentSection!!.second.put(parameter.first().trim(), parameter.last().trim())
} }
} }
currentSection?.let { }
parseData.put(it.first, it.second) currentSection?.let {
parseData.put(it.first, it.second)
}
return parseData
}
/**
* Create a Wireguard [Config] from a [json] string -
* The [json] will be created in AndroidVpnProtocol.cpp
*/
private fun buildWireugardConfig(obj: JSONObject): Config {
val confBuilder = Config.Builder()
val wireguardConfigData = obj.getJSONObject("wireguard_config_data")
val config = parseConfigData(wireguardConfigData.getString("config"))
val peerBuilder = Peer.Builder()
val peerConfig = config["Peer"]!!
peerBuilder.setPublicKey(Key.fromBase64(peerConfig["PublicKey"]))
peerConfig["PresharedKey"]?.let {
peerBuilder.setPreSharedKey(Key.fromBase64(it))
}
val allowedIPList = peerConfig["AllowedIPs"]?.split(",") ?: emptyList()
if (allowedIPList.isEmpty()) {
val internet = InetNetwork.parse("0.0.0.0/0") // aka The whole internet.
peerBuilder.addAllowedIp(internet)
} else {
allowedIPList.forEach {
val network = InetNetwork.parse(it.trim())
peerBuilder.addAllowedIp(network)
} }
return parseData }
peerBuilder.setEndpoint(InetEndpoint.parse(peerConfig["Endpoint"]))
peerConfig["PersistentKeepalive"]?.let {
peerBuilder.setPersistentKeepalive(it.toInt())
}
confBuilder.addPeer(peerBuilder.build())
val ifaceBuilder = Interface.Builder()
val ifaceConfig = config["Interface"]!!
ifaceBuilder.parsePrivateKey(ifaceConfig["PrivateKey"])
ifaceBuilder.addAddress(InetNetwork.parse(ifaceConfig["Address"]))
ifaceConfig["DNS"]!!.split(",").forEach {
ifaceBuilder.addDnsServer(InetNetwork.parse(it.trim()).address)
}
/*val jExcludedApplication = obj.getJSONArray("excludedApps")
(0 until jExcludedApplication.length()).toList().forEach {
val appName = jExcludedApplication.get(it).toString()
ifaceBuilder.excludeApplication(appName)
}*/
confBuilder.setInterface(ifaceBuilder.build())
return confBuilder.build()
}
fun getVpnConfig(): JSONObject {
return mConfig!!
}
private fun startOpenVpn() {
mOpenVPNThreadv3 = OpenVPNThreadv3(this)
Thread({
mOpenVPNThreadv3?.run()
}).start()
}
private fun startWireGuard() {
val wireguard_conf = buildWireugardConfig(mConfig!!)
if (currentTunnelHandle != -1) {
Log.e(tag, "Tunnel already up")
// Turn the tunnel down because this might be a switch
wgTurnOff(currentTunnelHandle)
}
val wgConfig: String = wireguard_conf!!.toWgUserspaceString()
val builder = Builder()
setupBuilder(wireguard_conf, builder)
builder.setSession("avpn0")
builder.establish().use { tun ->
if (tun == null) return
Log.i(tag, "Go backend " + wgVersion())
currentTunnelHandle = wgTurnOn("avpn0", tun.detachFd(), wgConfig)
}
if (currentTunnelHandle < 0) {
Log.e(tag, "Activation Error Code -> $currentTunnelHandle")
isUp = false
return
}
protect(wgGetSocketV4(currentTunnelHandle))
protect(wgGetSocketV6(currentTunnelHandle))
isUp = true
// Store the config in case the service gets
// asked boot vpn from the OS
val prefs = Prefs.get(this)
prefs.edit()
.putString("lastConf", mConfig.toString())
.apply()
NotificationUtil.show(this) // Go foreground
}
companion object {
@JvmStatic
fun startService(c: Context) {
c.applicationContext.startService(
Intent(c.applicationContext, VPNService::class.java).apply {
putExtra("startOnly", true)
})
} }
/** @JvmStatic
* Create a Wireguard [Config] from a [json] string - private external fun wgGetConfig(handle: Int): String?
* The [json] will be created in AndroidVpnProtocol.cpp
*/
private fun buildWireugardConfig(obj: JSONObject): Config {
val confBuilder = Config.Builder()
val wireguardConfigData = obj.getJSONObject("wireguard_config_data")
val config = parseConfigData(wireguardConfigData.getString("config"))
val peerBuilder = Peer.Builder()
val peerConfig = config["Peer"]!!
peerBuilder.setPublicKey(Key.fromBase64(peerConfig["PublicKey"]))
peerConfig["PresharedKey"]?.let {
peerBuilder.setPreSharedKey(Key.fromBase64(it))
}
val allowedIPList = peerConfig["AllowedIPs"]?.split(",") ?: emptyList()
if (allowedIPList.isEmpty()) {
val internet = InetNetwork.parse("0.0.0.0/0") // aka The whole internet.
peerBuilder.addAllowedIp(internet)
} else {
allowedIPList.forEach {
val network = InetNetwork.parse(it.trim())
peerBuilder.addAllowedIp(network)
}
}
peerBuilder.setEndpoint(InetEndpoint.parse(peerConfig["Endpoint"]))
peerConfig["PersistentKeepalive"]?.let {
peerBuilder.setPersistentKeepalive(it.toInt())
}
confBuilder.addPeer(peerBuilder.build())
val ifaceBuilder = Interface.Builder() @JvmStatic
val ifaceConfig = config["Interface"]!! private external fun wgGetSocketV4(handle: Int): Int
ifaceBuilder.parsePrivateKey(ifaceConfig["PrivateKey"])
ifaceBuilder.addAddress(InetNetwork.parse(ifaceConfig["Address"]))
ifaceConfig["DNS"]!!.split(",").forEach {
ifaceBuilder.addDnsServer(InetNetwork.parse(it.trim()).address)
}
/*val jExcludedApplication = obj.getJSONArray("excludedApps")
(0 until jExcludedApplication.length()).toList().forEach {
val appName = jExcludedApplication.get(it).toString()
ifaceBuilder.excludeApplication(appName)
}*/
confBuilder.setInterface(ifaceBuilder.build())
return confBuilder.build() @JvmStatic
} private external fun wgGetSocketV6(handle: Int): Int
fun getVpnConfig(): JSONObject { @JvmStatic
return mConfig!! private external fun wgTurnOff(handle: Int)
}
private fun startOpenVpn() { @JvmStatic
mOpenVPNThreadv3 = OpenVPNThreadv3 (this) private external fun wgTurnOn(ifName: String, tunFd: Int, settings: String): Int
Thread ({
mOpenVPNThreadv3?.run()
}).start()
}
private fun startWireGuard(){ @JvmStatic
val wireguard_conf = buildWireugardConfig(mConfig!!) private external fun wgVersion(): String?
if (currentTunnelHandle != -1) { }
Log.e(tag, "Tunnel already up") }
// Turn the tunnel down because this might be a switch
wgTurnOff(currentTunnelHandle)
}
val wgConfig: String = wireguard_conf!!.toWgUserspaceString()
val builder = Builder()
setupBuilder(wireguard_conf, builder)
builder.setSession("avpn0")
builder.establish().use { tun ->
if (tun == null)return
Log.i(tag, "Go backend " + wgVersion())
currentTunnelHandle = wgTurnOn("avpn0", tun.detachFd(), wgConfig)
}
if (currentTunnelHandle < 0) {
Log.e(tag, "Activation Error Code -> $currentTunnelHandle")
isUp = false
return
}
protect(wgGetSocketV4(currentTunnelHandle))
protect(wgGetSocketV6(currentTunnelHandle))
isUp = true
// Store the config in case the service gets
// asked boot vpn from the OS
val prefs = Prefs.get(this)
prefs.edit()
.putString("lastConf", mConfig.toString())
.apply()
NotificationUtil.show(this) // Go foreground
}
companion object {
@JvmStatic
fun startService(c: Context) {
c.applicationContext.startService(
Intent(c.applicationContext, VPNService::class.java).apply {
putExtra("startOnly", true)
})
}
@JvmStatic
private external fun wgGetConfig(handle: Int): String?
@JvmStatic
private external fun wgGetSocketV4(handle: Int): Int
@JvmStatic
private external fun wgGetSocketV6(handle: Int): Int
@JvmStatic
private external fun wgTurnOff(handle: Int)
@JvmStatic
private external fun wgTurnOn(ifName: String, tunFd: Int, settings: String): Int
@JvmStatic
private external fun wgVersion(): String?
}
}

View file

@ -1,4 +1,4 @@
QT += widgets core gui network xml remoteobjects quick QT += widgets core gui network xml remoteobjects quick svg
TARGET = AmneziaVPN TARGET = AmneziaVPN
TEMPLATE = app TEMPLATE = app

View file

@ -38,8 +38,6 @@ void QrDecoderLogic::onDetectedQrCode(const QString &code)
if (magic == amnezia::qrMagicCode) { if (magic == amnezia::qrMagicCode) {
qDebug() << "QrDecoderLogic::onDetectedQrCode magic code detected" << magic << ba.size();
quint8 chunksCount; s >> chunksCount; quint8 chunksCount; s >> chunksCount;
if (totalChunksCount() != chunksCount) { if (totalChunksCount() != chunksCount) {
m_chunks.clear(); m_chunks.clear();

View file

@ -12,6 +12,7 @@ BasicButtonType {
anchors.left: parent.left anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
svg.source: root.icon.source svg.source: root.icon.source
color: "#100A44"
width: 25 width: 25
height: 25 height: 25
} }

View file

@ -112,6 +112,7 @@ UiLogic::~UiLogic()
{ {
emit hide(); emit hide();
#ifdef AMNEZIA_DESKTOP
if (m_vpnConnection->connectionState() != VpnProtocol::VpnConnectionState::Disconnected) { if (m_vpnConnection->connectionState() != VpnProtocol::VpnConnectionState::Disconnected) {
m_vpnConnection->disconnectFromVpn(); m_vpnConnection->disconnectFromVpn();
for (int i = 0; i < 50; i++) { for (int i = 0; i < 50; i++) {
@ -122,6 +123,7 @@ UiLogic::~UiLogic()
} }
} }
} }
#endif
m_vpnConnection->deleteLater(); m_vpnConnection->deleteLater();
m_vpnConnectionThread.quit(); m_vpnConnectionThread.quit();