Compare commits

...

59 Commits

Author SHA1 Message Date
NickVs2015 89b94d2588 fix: resolve flush error and fix comment 2026-04-11 00:12:58 +03:00
NickVs2015 9166e17a23 fix: add support system with nscd service 2026-04-10 23:08:32 +03:00
NickVs2015 37d2b8716d fix: infinite reconnection when NM up 2026-04-10 23:06:16 +03:00
NickVs2015 92b168100a fix: Linux killswitch changes 2026-04-10 23:06:16 +03:00
NickVs2015 0f6db8a238 fix: Linux dns without dbus 2026-04-10 23:06:16 +03:00
NickVs2015 7c075625d2 fix: revert wireguardutilslinux 2026-04-03 20:21:00 +03:00
NickVs2015 c6a40b09c9 fix: reconnect 2026-04-03 20:11:28 +03:00
NickVs2015 560c4070b4 fix: add NM_STATE_DISABLED and check getGatewayAndIface more carefully 2026-04-03 08:47:15 +03:00
NickVs2015 f6806459fd fix: restore reconnect time 2026-04-02 10:26:16 +03:00
NickVs2015 646b1561f8 revert: restore linuxfirewall.cpp 2026-04-01 00:57:11 +03:00
NickVs2015 6d1e10a2e3 feat: catch state changed via check gateway 2026-04-01 00:43:46 +03:00
NickVs2015 af0d561c5c fix: add dns load/unload 2026-03-31 23:14:17 +03:00
NickVs2015 214b18f65f fix: improve reeconnection 2026-03-31 14:08:04 +03:00
NickVs2015 bd554fb730 fix: Dbus error, fix race conditional 2026-03-31 10:35:04 +03:00
NickVs2015 316e64122e fix: add linux reconnection 2026-03-26 22:31:26 +03:00
NickVs2015 bf3d11e5c4 feat: renewal new status logic (#2409)
* fix: renewal add status logic

* fix: wakeup activity resumed android
2026-03-25 19:48:32 +08:00
NickVs2015 9a0222aee3 fix: ui fixes for renewal subscription (#2406) 2026-03-25 12:34:42 +08:00
NickVs2015 f0f0f7c5be feat: add subscription renewal (#2389)
* feat: add renewal subsribe

* fix: after review
2026-03-24 22:45:02 +08:00
NickVs2015 36b1a863bf fix: black screen resume / pause (#2400) 2026-03-24 22:13:31 +08:00
yyy-amnezia 4103c5bbcf refactor: extract and simplify OpenVPN reachability and network change handling logic (#2402) 2026-03-24 22:12:59 +08:00
vkamn fa69da6d56 chore: send app version in services request (#2403) 2026-03-24 20:25:04 +08:00
yyy-amnezia aaf2c9ddeb feat: add Xray split tunnel support for iOS PacketTunnelProvider (#2332) 2026-03-24 16:07:36 +08:00
Mitternacht822 dbbc7119ec feat: add warning info for ssh keys (#2252)
* fix: fixed da typo

* feat: added warning about available ssh keys info
2026-03-24 16:06:40 +08:00
vkamn c57162c4cc feat: add base amnezia trial support (#2366)
* feat: add base amnezia trial support

* feat: add external-trial
2026-03-24 10:29:51 +08:00
NickVs2015 40e39895c9 fix openfile deadlock (#2373) 2026-03-21 11:46:46 +08:00
vkamn ec3ab2a03c chore: update licnese file (#2376) 2026-03-20 21:04:13 +08:00
yyy-amnezia ddecfcad26 fix: apple platform network switch fix (#2359)
* Apple platform network switch fix

* macos_ne exclusion fixed
2026-03-20 20:51:36 +08:00
NickVs2015 67bd880cdf fix: swap buffers error (#2347) 2026-03-16 13:03:20 +08:00
vkamn 477afb9d85 chore: bump version (#2336) 2026-03-10 22:22:37 +08:00
NickVs2015 f969fcdbb8 fix: restore dpad functionality ATV (#2335) 2026-03-10 22:19:55 +08:00
vkamn b0ca16d861 chore: bump version (#2331) 2026-03-09 18:29:56 +08:00
NickVs2015 9963359948 fix: disable gamepad for GP (#2321) 2026-03-09 17:39:50 +08:00
vkamn ca639d293d chore: bump version (#2319) 2026-03-06 23:11:03 +08:00
NickVs2015 83d045af64 fix: GP requrements (#2312) 2026-03-06 17:05:16 +08:00
NickVs2015 aea8ff4961 fix: add handle handleContextCreationFailure (#2309) 2026-03-03 22:04:45 +08:00
vkamn 1892db4375 fix: remove nested qeventloop from isConfigValid (also rename to validateConfig) (#2305)
* fix: remove nested qeventloop from isConfigValid (also rename to validateConfig)

* chore: bump version
2026-03-03 20:58:32 +08:00
NickVs2015 c86a641e05 fix: add suppord android 9 gamepad and remote control (#2302) 2026-03-03 15:14:51 +08:00
vkamn befb2bf19a chore: bump version (#2295) 2026-02-27 23:33:37 +08:00
vkamn 7ad6bc340c chore: add translations for ru (#2285)
* chore: add translations for ru

* chore: text fixes
2026-02-27 20:00:31 +08:00
vkamn 9164e38c34 fix: restore backup android (#2291)
* fix: fixed restore backup on android

* chore: add resume helper for android

* chore: add ResumeHelper.runWhenActive call after all native android dialogs

* fix: add permission for tv file picker

* fix: add file picker handler in kotlin

---------

Co-authored-by: NickVs2015 <nv@amnezia.org>
2026-02-27 18:43:36 +08:00
vkamn 8f7559f01b chore: revert PR #2222 (#2290) 2026-02-27 14:29:25 +08:00
vkamn af56200735 fix: fixed adding s3 s4 when updating the server conf for awg lagacy (#2289) 2026-02-27 14:11:40 +08:00
vkamn 3874050fae fix: again fixed s3, s4 ranges (#2288) 2026-02-27 13:37:49 +08:00
vkamn 3087163e34 fix: fixed s3, s4 ranges (#2283) 2026-02-26 22:31:41 +08:00
Mitternacht822 1fa152845c fix: generate native awg config as qr series (#2221) 2026-02-26 22:31:18 +08:00
vkamn 50e23ef233 fix: awg config update (#2281)
* fix: fixed client config update for awg container

* chore: bump version
2026-02-26 22:12:58 +08:00
Yaroslav Gurov ea648466de chore: remove redundant VpnConnection usage from SitesController (#2278) 2026-02-26 17:55:08 +08:00
Yaroslav Gurov b782775016 fix: change event looping to mutexes for settings and secureqsettings (#2270) 2026-02-26 11:41:08 +08:00
NickVs2015 89a7fe1081 fix: fixed remote control for ATV (#2277) 2026-02-26 11:40:16 +08:00
Yaroslav Gurov e8bb096025 fix: ios wrong awg blob (#2272) 2026-02-24 17:56:17 +07:00
Mitternacht822 fd5c7c8322 fix: copy LICENSE to build as LICENSE.txt for WiX CPack (#2265)
* fix(installer): copy LICENSE to build as LICENSE.txt for WiX CPack

* fix: fixed a typo

* fix: fixed a typo
2026-02-24 14:07:48 +08:00
Yaroslav Gurov e798d0f503 feat: update amneziawg-android dependencies (#2269) 2026-02-24 00:54:55 +08:00
Yaroslav Gurov bbb0abb596 feat: update xray (#2267) 2026-02-24 00:27:29 +08:00
vkamn 0925aec86a chore: bump version (#2264) 2026-02-23 18:01:59 +08:00
Yaroslav Gurov b084c4c284 fix: ios connection status stuck (#2263) 2026-02-23 18:00:13 +08:00
vkamn 87288ebccd chore: bump version (#2262) 2026-02-23 17:16:24 +08:00
vkamn fcd7eadf4c chore: bump version (#2261) 2026-02-23 15:38:27 +08:00
Mitternacht822 0373338fb7 fix: randomized baseUrls traversal order in GatewayController::getProxyUrls (#2247) 2026-02-23 15:33:35 +08:00
Yaroslav Gurov 42f070fe9d fix: handle Android disconnected status properly (#2255) 2026-02-23 15:31:15 +08:00
75 changed files with 2100 additions and 1196 deletions
+5 -3
View File
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN) set(PROJECT AmneziaVPN)
set(AMNEZIAVPN_VERSION 4.8.13.1) set(AMNEZIAVPN_VERSION 4.8.14.5)
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION} project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
DESCRIPTION "AmneziaVPN" DESCRIPTION "AmneziaVPN"
@@ -12,7 +12,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
set(RELEASE_DATE "${CURRENT_DATE}") set(RELEASE_DATE "${CURRENT_DATE}")
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
set(APP_ANDROID_VERSION_CODE 2107) set(APP_ANDROID_VERSION_CODE 2118)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux") set(MZ_PLATFORM_NAME "linux")
@@ -61,7 +61,9 @@ if(WIN32 AND NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
set(CPACK_PACKAGE_VENDOR "AmneziaVPN") set(CPACK_PACKAGE_VENDOR "AmneziaVPN")
set(CPACK_PACKAGE_VERSION ${AMNEZIAVPN_VERSION}) set(CPACK_PACKAGE_VERSION ${AMNEZIAVPN_VERSION})
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "AmneziaVPN client") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "AmneziaVPN client")
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE") set(AMNEZIA_LICENSE_TXT "${CMAKE_BINARY_DIR}/LICENSE.txt")
configure_file("${CMAKE_SOURCE_DIR}/LICENSE" "${AMNEZIA_LICENSE_TXT}" COPYONLY)
set(CPACK_RESOURCE_FILE_LICENSE "${AMNEZIA_LICENSE_TXT}")
set(CPACK_PACKAGE_INSTALL_DIRECTORY "AmneziaVPN") set(CPACK_PACKAGE_INSTALL_DIRECTORY "AmneziaVPN")
set(CPACK_PACKAGE_DIRECTORY "${CMAKE_BINARY_DIR}") set(CPACK_PACKAGE_DIRECTORY "${CMAKE_BINARY_DIR}")
set(CPACK_PACKAGE_EXECUTABLES "AmneziaVPN" "AmneziaVPN") set(CPACK_PACKAGE_EXECUTABLES "AmneziaVPN" "AmneziaVPN")
+1 -1
View File
@@ -179,7 +179,7 @@ You may face compiling issues in QT Creator after you've worked in Android Studi
## License ## License
GPL v3.0 This project is licensed under the GNU General Public License v3.0 (see LICENSE) and also includes third-party components distributed under their own terms (see THIRD_PARTY_LICENSES.md).
## Donate ## Donate
+149
View File
@@ -0,0 +1,149 @@
# Third-Party Licenses
This project is licensed under the GNU General Public License v3.0.
This file lists third-party software components used by this repository.
Each component is distributed under its own license as linked below.
---
## QtKeychain
- Source: https://github.com/frankosterfeld/qtkeychain
- License: BSD License
- License Text: https://www.gnu.org/licenses/license-list.html#ModifiedBSD
---
## QSimpleCrypto
- Source: https://github.com/n1flh31mur/QSimpleCrypto
- License: Apache License 2.0
- License Text: https://github.com/n1flh31mur/QSimpleCrypto/blob/master/LICENSE
---
## SortFilterProxyModel
- Source: https://github.com/oKcerG/SortFilterProxyModel
- License: MIT License
- License Text: https://github.com/oKcerG/SortFilterProxyModel/blob/master/LICENSE
---
## QJsonStruct
- Source: https://github.com/Qv2ray/QJsonStruct
- License: MIT License
- License Text: https://github.com/Qv2ray/QJsonStruct/blob/master/LICENSE
---
## QR Code Generator (qrcodegen)
- Source: https://github.com/nayuki/QR-Code-generator
- License: MIT License
- License Text: https://www.nayuki.io/page/qr-code-generator-library
---
## Qt Gamepad
- Source: https://github.com/qt/qtgamepad
- License: GNU General Public License v3.0 (GPL-3.0)
- License Text: https://www.gnu.org/licenses/gpl-3.0.en.html
---
## AmneziaWG Apple (WireGuard)
- Source: https://github.com/amnezia-vpn/amneziawg-apple
- License: MIT License
- License Text: https://github.com/amnezia-vpn/amneziawg-apple/blob/master/COPYING
---
## AmneziaWG Android
- Source: https://github.com/amnezia-vpn/amneziawg-go
- License: MIT License
- License Text: https://github.com/amnezia-vpn/amneziawg-go/blob/master/LICENSE
---
## Xray Core
- Source: https://github.com/XTLS/Xray-core
- License: Mozilla Public License 2.0 (MPL-2.0)
- License Text: https://github.com/XTLS/Xray-core/blob/main/LICENSE
---
## Cloak
- Source: https://github.com/cbeuw/Cloak
- License: GNU General Public License v3.0 (GPL-3.0)
- License Text: https://github.com/cbeuw/Cloak/blob/master/LICENSE
---
## Shadowsocks
- Source: https://github.com/shadowsocks/shadowsocks-libev
- License: GPL-3.0-or-later
- License Text: http://www.gnu.org/licenses/
---
## OpenSSL
- Source: https://github.com/openssl/openssl
- License: Apache License 2.0
- License Text: https://www.openssl.org/source/license.html
---
## libssh
- Source: https://www.libssh.org/
- License: GNU Lesser General Public License (LGPL)
- License Text: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
---
## OpenVPNAdapter
- Source: https://github.com/ss-abramchuk/OpenVPNAdapter
- License: GNU Affero General Public License v3.0 (AGPL-3.0)
- License Text: https://github.com/ss-abramchuk/OpenVPNAdapter/blob/master/LICENSE
---
## Wintun
- Source: https://www.wintun.net/
- License: Prebuilt Binaries License
- License Text: https://github.com/WireGuard/wintun/blob/master/prebuilt-binaries-license.txt
---
## Mullvad Split Tunnel Driver
- Source: https://github.com/mullvad/win-split-tunnel
- License: GNU General Public License v3.0 (GPL-3.0) and Mozilla Public License Version 2.0
- License Text: https://github.com/mullvad/win-split-tunnel/blob/master/LICENSE-GPL.md https://github.com/mullvad/win-split-tunnel/blob/master/LICENSE-MPL.txt
---
## tun2socks
- Source: https://github.com/eycorsican/go-tun2socks
- License: MIT License
- License Text: https://github.com/eycorsican/go-tun2socks/blob/master/LICENSE
---
## TAP-Windows Driver
- Source: https://github.com/OpenVPN/tap-windows6
- License: tap-windows6 license
- License Text: https://github.com/OpenVPN/tap-windows6/blob/master/COPYING
+1
View File
@@ -78,6 +78,7 @@ set(AMNEZIAVPN_TS_FILES
) )
file(GLOB_RECURSE AMNEZIAVPN_TS_SOURCES *.qrc *.cpp *.h *.ui) file(GLOB_RECURSE AMNEZIAVPN_TS_SOURCES *.qrc *.cpp *.h *.ui)
list(FILTER AMNEZIAVPN_TS_SOURCES EXCLUDE REGEX "qtgamepad/examples")
qt_create_translation(AMNEZIAVPN_QM_FILES ${AMNEZIAVPN_TS_SOURCES} ${AMNEZIAVPN_TS_FILES}) qt_create_translation(AMNEZIAVPN_QM_FILES ${AMNEZIAVPN_TS_SOURCES} ${AMNEZIAVPN_TS_FILES})
+10
View File
@@ -109,6 +109,16 @@ void AmneziaApplication::init()
// install filter on main window // install filter on main window
if (auto win = qobject_cast<QQuickWindow*>(obj)) { if (auto win = qobject_cast<QQuickWindow*>(obj)) {
win->installEventFilter(this); win->installEventFilter(this);
#ifdef Q_OS_ANDROID
QObject::connect(win, &QQuickWindow::sceneGraphError,
[](QQuickWindow::SceneGraphError, const QString &msg) {
qWarning() << "Scene graph error (suppressed):" << msg;
});
// Keep graphics context alive across hide/show cycles to avoid
// eglSwapBuffers/makeCurrent being called on a context Android has reclaimed.
win->setPersistentSceneGraph(true);
win->setPersistentGraphics(true);
#endif
win->show(); win->show();
} }
}, },
@@ -75,6 +75,8 @@ private const val OPEN_FILE_ACTION_CODE = 3
private const val CHECK_NOTIFICATION_PERMISSION_ACTION_CODE = 4 private const val CHECK_NOTIFICATION_PERMISSION_ACTION_CODE = 4
private const val PREFS_NOTIFICATION_PERMISSION_ASKED = "NOTIFICATION_PERMISSION_ASKED" private const val PREFS_NOTIFICATION_PERMISSION_ASKED = "NOTIFICATION_PERMISSION_ASKED"
private const val OPEN_FILE_AFTER_RESUME_DELAY_MS = 400L
private const val KEY_PENDING_OPEN_FILE_URI = "pending_open_file_uri"
class AmneziaActivity : QtActivity() { class AmneziaActivity : QtActivity() {
@@ -90,10 +92,12 @@ class AmneziaActivity : QtActivity() {
private val actionResultHandlers = mutableMapOf<Int, ActivityResultHandler>() private val actionResultHandlers = mutableMapOf<Int, ActivityResultHandler>()
private val permissionRequestHandlers = mutableMapOf<Int, PermissionRequestHandler>() private val permissionRequestHandlers = mutableMapOf<Int, PermissionRequestHandler>()
private var isActivityResumed = false private var isActivityResumed = false
private var hasWindowFocus = false private var hasWindowFocus = false
private val resumeHandler = Handler(Looper.getMainLooper()) private val resumeHandler = Handler(Looper.getMainLooper())
private var pendingOpenFileUri: String? = null
private var openFileDeliveryScheduled = false
private val vpnServiceEventHandler: Handler by lazy(NONE) { private val vpnServiceEventHandler: Handler by lazy(NONE) {
object : Handler(Looper.getMainLooper()) { object : Handler(Looper.getMainLooper()) {
@@ -196,11 +200,18 @@ class AmneziaActivity : QtActivity() {
doBindService() doBindService()
} }
) )
pendingOpenFileUri = savedInstanceState?.getString(KEY_PENDING_OPEN_FILE_URI)
openFileDeliveryScheduled = false
registerBroadcastReceivers() registerBroadcastReceivers()
intent?.let(::processIntent) intent?.let(::processIntent)
runBlocking { vpnProto = proto.await() } runBlocking { vpnProto = proto.await() }
} }
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
pendingOpenFileUri?.let { outState.putString(KEY_PENDING_OPEN_FILE_URI, it) }
}
private fun loadLibs() { private fun loadLibs() {
listOf( listOf(
"rsapss", "rsapss",
@@ -270,6 +281,7 @@ class AmneziaActivity : QtActivity() {
hasWindowFocus = false hasWindowFocus = false
// Cancel all pending operations when activity stops // Cancel all pending operations when activity stops
resumeHandler.removeCallbacksAndMessages(null) resumeHandler.removeCallbacksAndMessages(null)
openFileDeliveryScheduled = false
Log.d(TAG, "Stop Amnezia activity") Log.d(TAG, "Stop Amnezia activity")
doUnbindService() doUnbindService()
mainScope.launch { mainScope.launch {
@@ -283,43 +295,55 @@ class AmneziaActivity : QtActivity() {
super.onWindowFocusChanged(hasFocus) super.onWindowFocusChanged(hasFocus)
hasWindowFocus = hasFocus hasWindowFocus = hasFocus
Log.d(TAG, "Window focus changed: hasFocus=$hasFocus") Log.d(TAG, "Window focus changed: hasFocus=$hasFocus")
// Cancel pending operations if window loses focus
if (!hasFocus) { if (!hasFocus) {
// Cancel pending operations if window loses focus
resumeHandler.removeCallbacksAndMessages(null) resumeHandler.removeCallbacksAndMessages(null)
} else if (isActivityResumed && Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
window.decorView.apply {
invalidate()
resumeHandler.postDelayed({
if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
sendTouch(1f, 1f)
}
}, 50)
resumeHandler.postDelayed({
if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
sendTouch(2f, 2f)
requestLayout()
invalidate()
}
}, 150)
}
} }
} }
override fun dispatchKeyEvent(event: KeyEvent): Boolean { override fun dispatchKeyEvent(event: KeyEvent): Boolean {
val deviceId = event.deviceId
val keyCode = event.keyCode val keyCode = event.keyCode
val pressed = event.action == KeyEvent.ACTION_DOWN val pressed = event.action == KeyEvent.ACTION_DOWN
val source = event.source
if (deviceId < 0 && pressed) { when (keyCode) {
when (keyCode) { KeyEvent.KEYCODE_BUTTON_A,
KeyEvent.KEYCODE_BUTTON_A, KeyEvent.KEYCODE_BUTTON_B,
KeyEvent.KEYCODE_BUTTON_B, KeyEvent.KEYCODE_BUTTON_X,
KeyEvent.KEYCODE_BUTTON_X, KeyEvent.KEYCODE_BUTTON_Y,
KeyEvent.KEYCODE_BUTTON_Y, KeyEvent.KEYCODE_BUTTON_START,
KeyEvent.KEYCODE_BUTTON_START, KeyEvent.KEYCODE_BUTTON_SELECT -> {
KeyEvent.KEYCODE_BUTTON_SELECT, nativeGamepadKeyEvent(0, keyCode, pressed)
KeyEvent.KEYCODE_DPAD_CENTER -> {
nativeGamepadKeyEvent(0, keyCode, true)
nativeGamepadKeyEvent(0, keyCode, false)
return true return true
}
} }
} KeyEvent.KEYCODE_DPAD_CENTER,
KeyEvent.KEYCODE_DPAD_UP,
// Real gamepad events (deviceId >= 0) KeyEvent.KEYCODE_DPAD_DOWN,
if (deviceId >= 0) { KeyEvent.KEYCODE_DPAD_LEFT,
val isGamepad = (source and InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD KeyEvent.KEYCODE_DPAD_RIGHT -> {
val isJoystick = (source and InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK val syntheticKeyCode = if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) KeyEvent.KEYCODE_ENTER else keyCode
val isDpad = (source and InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD val synthetic = KeyEvent(
if (isGamepad || isJoystick || isDpad) { event.downTime, event.eventTime, event.action, syntheticKeyCode,
nativeGamepadKeyEvent(deviceId, keyCode, pressed) event.repeatCount, event.metaState, -1, event.scanCode,
return true event.flags, InputDevice.SOURCE_KEYBOARD
)
return super.dispatchKeyEvent(synthetic)
} }
} }
@@ -329,10 +353,18 @@ class AmneziaActivity : QtActivity() {
private external fun nativeGamepadKeyEvent(deviceId: Int, keyCode: Int, pressed: Boolean) private external fun nativeGamepadKeyEvent(deviceId: Int, keyCode: Int, pressed: Boolean)
override fun onPause() { override fun onPause() {
// Notify Qt to stop rendering BEFORE super.onPause() destroys the EGL surface.
// Using a coroutine here would be too late — the surface is gone by the time
// the coroutine runs. A direct synchronous call gives Qt's render thread the
// best chance to process visible=false before surface destruction.
if (qtInitialized.isCompleted) {
QtAndroidController.onActivityPaused()
}
super.onPause() super.onPause()
isActivityResumed = false isActivityResumed = false
// Cancel all pending operations when activity pauses // Cancel all pending operations when activity pauses
resumeHandler.removeCallbacksAndMessages(null) resumeHandler.removeCallbacksAndMessages(null)
openFileDeliveryScheduled = false
Log.d(TAG, "Pause Amnezia activity") Log.d(TAG, "Pause Amnezia activity")
} }
@@ -340,6 +372,24 @@ class AmneziaActivity : QtActivity() {
super.onResume() super.onResume()
isActivityResumed = true isActivityResumed = true
Log.d(TAG, "Resume Amnezia activity") Log.d(TAG, "Resume Amnezia activity")
if (qtInitialized.isCompleted) {
QtAndroidController.onActivityResumed()
}
if (pendingOpenFileUri != null && !openFileDeliveryScheduled) {
val uri = pendingOpenFileUri!!
openFileDeliveryScheduled = true
resumeHandler.postDelayed({
if (!isFinishing && !isDestroyed) {
pendingOpenFileUri = null
openFileDeliveryScheduled = false
mainScope.launch {
qtInitialized.await()
QtAndroidController.onFileOpened(uri)
}
}
}, OPEN_FILE_AFTER_RESUME_DELAY_MS)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
window.decorView.apply { window.decorView.apply {
@@ -351,13 +401,13 @@ class AmneziaActivity : QtActivity() {
sendTouch(1f, 1f) sendTouch(1f, 1f)
} }
}, 100) }, 100)
resumeHandler.postDelayed({ resumeHandler.postDelayed({
if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) { if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
sendTouch(2f, 2f) sendTouch(2f, 2f)
} }
}, 200) }, 200)
resumeHandler.postDelayed({ resumeHandler.postDelayed({
if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) { if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
requestLayout() requestLayout()
@@ -403,25 +453,25 @@ class AmneziaActivity : QtActivity() {
ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { view, windowInsets -> ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { view, windowInsets ->
val imeInsets = windowInsets.getInsets(WindowInsetsCompat.Type.ime()) val imeInsets = windowInsets.getInsets(WindowInsetsCompat.Type.ime())
val imeVisible = windowInsets.isVisible(WindowInsetsCompat.Type.ime()) val imeVisible = windowInsets.isVisible(WindowInsetsCompat.Type.ime())
val imeHeight = if (imeVisible) imeInsets.bottom else 0 val imeHeight = if (imeVisible) imeInsets.bottom else 0
val density = resources.displayMetrics.density val density = resources.displayMetrics.density
val imeHeightDp = (imeHeight / density).toInt() val imeHeightDp = (imeHeight / density).toInt()
// Also track system bars (navigation bar, status bar) changes // Also track system bars (navigation bar, status bar) changes
val systemBarsInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) val systemBarsInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val navBarHeight = systemBarsInsets.bottom val navBarHeight = systemBarsInsets.bottom
val navBarHeightDp = (navBarHeight / density).toInt() val navBarHeightDp = (navBarHeight / density).toInt()
val statusBarHeight = systemBarsInsets.top val statusBarHeight = systemBarsInsets.top
val statusBarHeightDp = (statusBarHeight / density).toInt() val statusBarHeightDp = (statusBarHeight / density).toInt()
mainScope.launch { mainScope.launch {
qtInitialized.await() qtInitialized.await()
QtAndroidController.onImeInsetsChanged(imeHeightDp) QtAndroidController.onImeInsetsChanged(imeHeightDp)
QtAndroidController.onSystemBarsInsetsChanged(navBarHeightDp, statusBarHeightDp) QtAndroidController.onSystemBarsInsetsChanged(navBarHeightDp, statusBarHeightDp)
} }
// Return windowInsets instead of CONSUMED to allow proper handling // Return windowInsets instead of CONSUMED to allow proper handling
windowInsets windowInsets
} }
@@ -757,9 +807,13 @@ class AmneziaActivity : QtActivity() {
grantUriPermission(packageName, this, Intent.FLAG_GRANT_READ_URI_PERMISSION) grantUriPermission(packageName, this, Intent.FLAG_GRANT_READ_URI_PERMISSION)
}?.toString() ?: "" }?.toString() ?: ""
Log.v(TAG, "Open file: $uri") Log.v(TAG, "Open file: $uri")
mainScope.launch { if (uri.isNotEmpty()) {
qtInitialized.await() pendingOpenFileUri = uri
QtAndroidController.onFileOpened(uri) } else {
mainScope.launch {
qtInitialized.await()
QtAndroidController.onFileOpened(uri)
}
} }
} }
)) ))
@@ -788,7 +842,7 @@ class AmneziaActivity : QtActivity() {
@Suppress("unused") @Suppress("unused")
fun getFd(fileName: String): Int { fun getFd(fileName: String): Int {
Log.v(TAG, "Get fd for $fileName") Log.v(TAG, "Get fd for $fileName")
return blockingCall { return blockingCall(Dispatchers.IO) {
try { try {
pfd = contentResolver.openFileDescriptor(Uri.parse(fileName), "r") pfd = contentResolver.openFileDescriptor(Uri.parse(fileName), "r")
pfd?.fd ?: -1 pfd?.fd ?: -1
@@ -33,7 +33,10 @@ class TvFilePicker : ComponentActivity() {
return intent return intent
} }
}) { }) {
setResult(RESULT_OK, Intent().apply { data = it }) setResult(RESULT_OK, Intent().apply {
data = it
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
})
finish() finish()
} }
@@ -31,4 +31,7 @@ object QtAndroidController {
external fun onImeInsetsChanged(heightDp: Int) external fun onImeInsetsChanged(heightDp: Int)
external fun onSystemBarsInsetsChanged(navBarHeightDp: Int, statusBarHeightDp: Int) external fun onSystemBarsInsetsChanged(navBarHeightDp: Int, statusBarHeightDp: Int)
external fun onActivityPaused()
external fun onActivityResumed()
} }
+1 -1
View File
@@ -163,7 +163,7 @@ add_custom_command(TARGET ${PROJECT} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory COMMAND ${CMAKE_COMMAND} -E make_directory
$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks $<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks
COMMAND /usr/bin/find "$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework" -name "*.sha256" -delete COMMAND /usr/bin/find "$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework" -name "*.sha256" -delete
COMMAND /usr/bin/codesign --force --sign "Apple Distribution" COMMAND /usr/bin/codesign --force --sign "Apple Distribution: Privacy Technologies OU"
"$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework/Versions/Current/OpenVPNAdapter" "$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework/Versions/Current/OpenVPNAdapter"
COMMAND ${QT_BIN_DIR_DETECTED}/macdeployqt $<TARGET_BUNDLE_DIR:AmneziaVPN> -appstore-compliant -qmldir=${CMAKE_CURRENT_SOURCE_DIR} COMMAND ${QT_BIN_DIR_DETECTED}/macdeployqt $<TARGET_BUNDLE_DIR:AmneziaVPN> -appstore-compliant -qmldir=${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Signing OpenVPNAdapter framework" COMMENT "Signing OpenVPNAdapter framework"
+4 -1
View File
@@ -10,8 +10,10 @@ namespace apiDefs
AmneziaFreeV3, AmneziaFreeV3,
AmneziaPremiumV1, AmneziaPremiumV1,
AmneziaPremiumV2, AmneziaPremiumV2,
AmneziaTrialV2,
SelfHosted, SelfHosted,
ExternalPremium ExternalPremium,
ExternalTrial
}; };
enum ConfigSource { enum ConfigSource {
@@ -32,6 +34,7 @@ namespace apiDefs
constexpr QLatin1String stackType("stack_type"); constexpr QLatin1String stackType("stack_type");
constexpr QLatin1String serviceType("service_type"); constexpr QLatin1String serviceType("service_type");
constexpr QLatin1String cliVersion("cli_version"); constexpr QLatin1String cliVersion("cli_version");
constexpr QLatin1String cliName("cli_name");
constexpr QLatin1String supportedProtocols("supported_protocols"); constexpr QLatin1String supportedProtocols("supported_protocols");
constexpr QLatin1String vpnKey("vpn_key"); constexpr QLatin1String vpnKey("vpn_key");
+14 -2
View File
@@ -58,18 +58,24 @@ apiDefs::ConfigType apiUtils::getConfigType(const QJsonObject &serverConfigObjec
}; };
case apiDefs::ConfigSource::AmneziaGateway: { case apiDefs::ConfigSource::AmneziaGateway: {
constexpr QLatin1String servicePremium("amnezia-premium"); constexpr QLatin1String servicePremium("amnezia-premium");
constexpr QLatin1String serviceTrial("amnezia-trial");
constexpr QLatin1String serviceFree("amnezia-free"); constexpr QLatin1String serviceFree("amnezia-free");
constexpr QLatin1String serviceExternalPremium("external-premium"); constexpr QLatin1String serviceExternalPremium("external-premium");
constexpr QLatin1String serviceExternalTrial("external-trial");
auto apiConfigObject = serverConfigObject.value(apiDefs::key::apiConfig).toObject(); auto apiConfigObject = serverConfigObject.value(apiDefs::key::apiConfig).toObject();
auto serviceType = apiConfigObject.value(apiDefs::key::serviceType).toString(); auto serviceType = apiConfigObject.value(apiDefs::key::serviceType).toString();
if (serviceType == servicePremium) { if (serviceType == servicePremium) {
return apiDefs::ConfigType::AmneziaPremiumV2; return apiDefs::ConfigType::AmneziaPremiumV2;
} else if (serviceType == serviceTrial) {
return apiDefs::ConfigType::AmneziaTrialV2;
} else if (serviceType == serviceFree) { } else if (serviceType == serviceFree) {
return apiDefs::ConfigType::AmneziaFreeV3; return apiDefs::ConfigType::AmneziaFreeV3;
} else if (serviceType == serviceExternalPremium) { } else if (serviceType == serviceExternalPremium) {
return apiDefs::ConfigType::ExternalPremium; return apiDefs::ConfigType::ExternalPremium;
} else if (serviceType == serviceExternalTrial) {
return apiDefs::ConfigType::ExternalTrial;
} }
} }
default: { default: {
@@ -90,6 +96,7 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
const int httpStatusCodeConflict = 409; const int httpStatusCodeConflict = 409;
const int httpStatusCodeNotFound = 404; const int httpStatusCodeNotFound = 404;
const int httpStatusCodeNotImplemented = 501; const int httpStatusCodeNotImplemented = 501;
const int httpStatusCodeUnprocessableEntity = 422;
if (!sslErrors.empty()) { if (!sslErrors.empty()) {
qDebug().noquote() << sslErrors; qDebug().noquote() << sslErrors;
@@ -122,6 +129,8 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
return amnezia::ErrorCode::ApiNotFoundError; return amnezia::ErrorCode::ApiNotFoundError;
} else if (httpStatusFromBody == httpStatusCodeNotImplemented) { } else if (httpStatusFromBody == httpStatusCodeNotImplemented) {
return amnezia::ErrorCode::ApiUpdateRequestError; return amnezia::ErrorCode::ApiUpdateRequestError;
} else if (httpStatusFromBody == httpStatusCodeUnprocessableEntity) {
return amnezia::ErrorCode::ApiSubscriptionExpiredError;
} }
return amnezia::ErrorCode::ApiConfigDownloadError; return amnezia::ErrorCode::ApiConfigDownloadError;
} }
@@ -133,7 +142,8 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
bool apiUtils::isPremiumServer(const QJsonObject &serverConfigObject) bool apiUtils::isPremiumServer(const QJsonObject &serverConfigObject)
{ {
static const QSet<apiDefs::ConfigType> premiumTypes = { apiDefs::ConfigType::AmneziaPremiumV1, apiDefs::ConfigType::AmneziaPremiumV2, static const QSet<apiDefs::ConfigType> premiumTypes = { apiDefs::ConfigType::AmneziaPremiumV1, apiDefs::ConfigType::AmneziaPremiumV2,
apiDefs::ConfigType::ExternalPremium }; apiDefs::ConfigType::AmneziaTrialV2, apiDefs::ConfigType::ExternalPremium,
apiDefs::ConfigType::ExternalTrial };
return premiumTypes.contains(getConfigType(serverConfigObject)); return premiumTypes.contains(getConfigType(serverConfigObject));
} }
@@ -177,7 +187,9 @@ QString apiUtils::getPremiumV1VpnKey(const QJsonObject &serverConfigObject)
QString apiUtils::getPremiumV2VpnKey(const QJsonObject &serverConfigObject) QString apiUtils::getPremiumV2VpnKey(const QJsonObject &serverConfigObject)
{ {
if (apiUtils::getConfigType(serverConfigObject) != apiDefs::ConfigType::AmneziaPremiumV2) { auto configType = apiUtils::getConfigType(serverConfigObject);
if (configType != apiDefs::ConfigType::AmneziaPremiumV2 && configType != apiDefs::ConfigType::AmneziaTrialV2
&& configType != apiDefs::ConfigType::ExternalPremium && configType != apiDefs::ConfigType::ExternalTrial) {
return {}; return {};
} }
+8 -2
View File
@@ -135,7 +135,7 @@ void CoreController::initControllers()
new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, m_appSplitTunnelingModel, m_settings)); new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, m_appSplitTunnelingModel, m_settings));
m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get()); m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get());
m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel)); m_sitesController.reset(new SitesController(m_settings, m_sitesModel));
m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get()); m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get());
m_allowedDnsController.reset(new AllowedDnsController(m_settings, m_allowedDnsModel)); m_allowedDnsController.reset(new AllowedDnsController(m_settings, m_allowedDnsModel));
@@ -153,6 +153,8 @@ void CoreController::initControllers()
m_apiConfigsController.reset(new ApiConfigsController(m_serversModel, m_apiServicesModel, m_settings)); m_apiConfigsController.reset(new ApiConfigsController(m_serversModel, m_apiServicesModel, m_settings));
m_engine->rootContext()->setContextProperty("ApiConfigsController", m_apiConfigsController.get()); m_engine->rootContext()->setContextProperty("ApiConfigsController", m_apiConfigsController.get());
connect(m_apiConfigsController.get(), &ApiConfigsController::subscriptionRefreshNeeded,
this, [this]() { m_apiSettingsController->getAccountInfo(false); });
m_apiNewsController.reset(new ApiNewsController(m_newsModel, m_settings, m_serversModel, this)); m_apiNewsController.reset(new ApiNewsController(m_newsModel, m_settings, m_serversModel, this));
m_engine->rootContext()->setContextProperty("ApiNewsController", m_apiNewsController.get()); m_engine->rootContext()->setContextProperty("ApiNewsController", m_apiNewsController.get());
@@ -368,7 +370,11 @@ void CoreController::initPrepareConfigHandler()
return; return;
} }
if (!m_installController->isConfigValid()) { m_installController->validateConfig();
});
connect(m_installController.get(), &InstallController::configValidated, this, [this](bool isValid) {
if (!isValid) {
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
return; return;
} }
@@ -46,6 +46,7 @@ namespace
constexpr int httpStatusCodeConflict = 409; constexpr int httpStatusCodeConflict = 409;
constexpr int httpStatusCodeNotImplemented = 501; constexpr int httpStatusCodeNotImplemented = 501;
constexpr int httpStatusCodeUnprocessableEntity = 422;
} }
GatewayController::GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs, GatewayController::GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
@@ -337,6 +338,9 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
} else { } else {
baseUrls = QString(PROD_S3_ENDPOINT).split(", "); baseUrls = QString(PROD_S3_ENDPOINT).split(", ");
} }
std::random_device randomDevice;
std::mt19937 generator(randomDevice());
std::shuffle(baseUrls.begin(), baseUrls.end(), generator);
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY; QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
@@ -448,6 +452,8 @@ bool GatewayController::shouldBypassProxy(const QNetworkReply::NetworkError &rep
} }
} else if (httpStatus == httpStatusCodeConflict) { } else if (httpStatus == httpStatusCodeConflict) {
return false; return false;
} else if (httpStatus == httpStatusCodeUnprocessableEntity) {
return false;
} else if (replyError != QNetworkReply::NetworkError::NoError) { } else if (replyError != QNetworkReply::NetworkError::NoError) {
qDebug() << replyError; qDebug() << replyError;
return true; return true;
+77 -91
View File
@@ -292,117 +292,103 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
return { resGateway, QNetworkInterface::interfaceFromIndex(resIndex) }; return { resGateway, QNetworkInterface::interfaceFromIndex(resIndex) };
#endif #endif
#ifdef Q_OS_LINUX #ifdef Q_OS_LINUX
constexpr int BUFFER_SIZE = 100; static constexpr size_t BUFFER_SIZE = 8192;
int received_bytes = 0, msg_len = 0, route_attribute_len = 0;
int sock = -1, msgseq = 0;
struct nlmsghdr *nlh, *nlmsg;
struct rtmsg *route_entry;
// This struct contain route attributes (route type)
struct rtattr *route_attribute;
char gateway_address[INET_ADDRSTRLEN], interface[IF_NAMESIZE];
char msgbuf[BUFFER_SIZE], buffer[BUFFER_SIZE];
char *ptr = buffer;
struct timeval tv;
if ((sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) { int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
if (sock < 0) {
perror("socket failed"); perror("socket failed");
return {}; return {};
} }
memset(msgbuf, 0, sizeof(msgbuf)); struct timeval tv { 1, 0 };
memset(gateway_address, 0, sizeof(gateway_address)); setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
memset(interface, 0, sizeof(interface));
memset(buffer, 0, sizeof(buffer));
/* point the header and the msg structure pointers into the buffer */ struct {
nlmsg = (struct nlmsghdr *)msgbuf; struct nlmsghdr hdr;
struct rtmsg rt;
} req {};
req.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
req.hdr.nlmsg_type = RTM_GETROUTE;
req.hdr.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST;
req.hdr.nlmsg_seq = 1;
req.hdr.nlmsg_pid = static_cast<uint32_t>(getpid());
req.rt.rtm_family = AF_INET;
/* Fill in the nlmsg header*/ if (send(sock, &req, req.hdr.nlmsg_len, 0) < 0) {
nlmsg->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
nlmsg->nlmsg_type = RTM_GETROUTE; // Get the routes from kernel routing table .
nlmsg->nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST; // The message is a request for dump.
nlmsg->nlmsg_seq = msgseq++; // Sequence of the message packet.
nlmsg->nlmsg_pid = getpid(); // PID of process sending the request.
/* 1 Sec Timeout to avoid stall */
tv.tv_sec = 1;
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (struct timeval *)&tv, sizeof(struct timeval));
/* send msg */
if (send(sock, nlmsg, nlmsg->nlmsg_len, 0) < 0) {
perror("send failed"); perror("send failed");
close(sock);
return {}; return {};
} }
/* receive response */ char buffer[BUFFER_SIZE];
do size_t total_len = 0;
{ bool done = false;
received_bytes = recv(sock, ptr, sizeof(buffer) - msg_len, 0);
if (received_bytes < 0) {
perror("Error in recv");
return {};
}
nlh = (struct nlmsghdr *) ptr; while (!done && total_len < BUFFER_SIZE) {
ssize_t n = recv(sock, buffer + total_len, BUFFER_SIZE - total_len, 0);
/* Check if the header is valid */ if (n <= 0)
if((NLMSG_OK(nlmsg, received_bytes) == 0) ||
(nlmsg->nlmsg_type == NLMSG_ERROR))
{
perror("Error in received packet");
return {};
}
/* If we received all data break */
if (nlh->nlmsg_type == NLMSG_DONE)
break; break;
else {
ptr += received_bytes;
msg_len += received_bytes;
}
/* Break if its not a multi part message */ int scan_len = static_cast<int>(n);
if ((nlmsg->nlmsg_flags & NLM_F_MULTI) == 0) for (struct nlmsghdr *h = reinterpret_cast<struct nlmsghdr *>(buffer + total_len);
break; NLMSG_OK(h, scan_len);
} h = NLMSG_NEXT(h, scan_len))
while ((nlmsg->nlmsg_seq != msgseq) || (nlmsg->nlmsg_pid != getpid()));
/* parse response */
for ( ; NLMSG_OK(nlh, received_bytes); nlh = NLMSG_NEXT(nlh, received_bytes))
{
/* Get the route data */
route_entry = (struct rtmsg *) NLMSG_DATA(nlh);
/* We are just interested in main routing table */
if (route_entry->rtm_table != RT_TABLE_MAIN)
continue;
route_attribute = (struct rtattr *) RTM_RTA(route_entry);
route_attribute_len = RTM_PAYLOAD(nlh);
/* Loop through all attributes */
for ( ; RTA_OK(route_attribute, route_attribute_len);
route_attribute = RTA_NEXT(route_attribute, route_attribute_len))
{ {
switch(route_attribute->rta_type) { if (h->nlmsg_type == NLMSG_DONE || h->nlmsg_type == NLMSG_ERROR) {
case RTA_OIF: done = true;
if_indextoname(*(int *)RTA_DATA(route_attribute), interface);
break;
case RTA_GATEWAY:
inet_ntop(AF_INET, RTA_DATA(route_attribute),
gateway_address, sizeof(gateway_address));
break;
default:
break; break;
} }
} }
total_len += static_cast<size_t>(n);
if ((*gateway_address) && (*interface)) {
qDebug() << "Gateway " << gateway_address << " for interface " << interface;
break;
}
} }
QString resultGw;
QString resultIf;
int remaining = static_cast<int>(total_len);
for (struct nlmsghdr *nlh = reinterpret_cast<struct nlmsghdr *>(buffer);
NLMSG_OK(nlh, remaining);
nlh = NLMSG_NEXT(nlh, remaining))
{
if (nlh->nlmsg_type == NLMSG_DONE || nlh->nlmsg_type == NLMSG_ERROR)
break;
if (nlh->nlmsg_type != RTM_NEWROUTE)
continue;
struct rtmsg *rt = static_cast<struct rtmsg *>(NLMSG_DATA(nlh));
if (rt->rtm_table != RT_TABLE_MAIN || rt->rtm_family != AF_INET)
continue;
char route_gw[INET_ADDRSTRLEN] = {};
char route_if[IF_NAMESIZE] = {};
int attr_len = RTM_PAYLOAD(nlh);
for (struct rtattr *rta = RTM_RTA(rt);
RTA_OK(rta, attr_len);
rta = RTA_NEXT(rta, attr_len))
{
if (rta->rta_type == RTA_GATEWAY)
inet_ntop(AF_INET, RTA_DATA(rta), route_gw, sizeof(route_gw));
else if (rta->rta_type == RTA_OIF)
if_indextoname(*static_cast<int *>(RTA_DATA(rta)), route_if);
}
if (!route_gw[0] || !route_if[0])
continue;
QString ifStr(route_if);
if (ifStr.startsWith(QLatin1String("amn")) ||
ifStr.startsWith(QLatin1String("tun")))
continue;
resultGw = QString::fromLatin1(route_gw);
resultIf = QString::fromLatin1(route_if);
qDebug() << "Gateway " << route_gw << " for interface " << route_if;
break;
}
close(sock); close(sock);
return { gateway_address, QNetworkInterface::interfaceFromName(interface) }; return { resultGw, QNetworkInterface::interfaceFromName(resultIf) };
#endif #endif
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS) && !defined(MACOS_NE) #if defined(Q_OS_MAC) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
QString gateway; QString gateway;
+20 -4
View File
@@ -142,10 +142,6 @@ bool Daemon::activate(const InterfaceConfig& config) {
return false; return false;
} }
if (!maybeUpdateResolvers(config)) {
return false;
}
// set routing // set routing
for (const IPAddress& ip : config.m_allowedIPAddressRanges) { for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
if (!wgutils()->updateRoutePrefix(ip)) { if (!wgutils()->updateRoutePrefix(ip)) {
@@ -215,6 +211,11 @@ bool Daemon::addExclusionRoute(const IPAddress& prefix) {
return true; return true;
} }
if (!wgutils()->addExclusionRoute(prefix)) { if (!wgutils()->addExclusionRoute(prefix)) {
if (!m_pendingExclusionRoutes.contains(prefix)) {
logger.warning() << "Exclusion route deferred (no gateway):"
<< prefix.toString();
m_pendingExclusionRoutes.append(prefix);
}
return false; return false;
} }
m_excludedAddrSet[prefix] = 1; m_excludedAddrSet[prefix] = 1;
@@ -480,6 +481,7 @@ bool Daemon::deactivate(bool emitSignals) {
wgutils()->deleteExclusionRoute(iterator.key()); wgutils()->deleteExclusionRoute(iterator.key());
} }
m_excludedAddrSet.clear(); m_excludedAddrSet.clear();
m_pendingExclusionRoutes.clear();
m_connections.clear(); m_connections.clear();
// Delete the interface // Delete the interface
@@ -589,6 +591,19 @@ void Daemon::checkHandshake() {
logger.debug() << "Checking for handshake..."; logger.debug() << "Checking for handshake...";
if (!m_pendingExclusionRoutes.isEmpty()) {
QList<IPAddress> stillPending;
for (const IPAddress& prefix : m_pendingExclusionRoutes) {
if (!wgutils()->addExclusionRoute(prefix)) {
stillPending.append(prefix);
} else {
logger.debug() << "Deferred exclusion route added:" << prefix.toString();
m_excludedAddrSet[prefix] = 1;
}
}
m_pendingExclusionRoutes = stillPending;
}
int pendingHandshakes = 0; int pendingHandshakes = 0;
QList<WireguardUtils::PeerStatus> peers = wgutils()->getPeerStatus(); QList<WireguardUtils::PeerStatus> peers = wgutils()->getPeerStatus();
for (ConnectionState& connection : m_connections) { for (ConnectionState& connection : m_connections) {
@@ -605,6 +620,7 @@ void Daemon::checkHandshake() {
} }
if (status.m_handshake != 0) { if (status.m_handshake != 0) {
connection.m_date.setMSecsSinceEpoch(status.m_handshake); connection.m_date.setMSecsSinceEpoch(status.m_handshake);
maybeUpdateResolvers(config);
emit connected(status.m_pubkey); emit connected(status.m_pubkey);
} }
} }
+1
View File
@@ -87,6 +87,7 @@ class Daemon : public QObject {
}; };
QMap<InterfaceConfig::HopType, ConnectionState> m_connections; QMap<InterfaceConfig::HopType, ConnectionState> m_connections;
QHash<IPAddress, int> m_excludedAddrSet; QHash<IPAddress, int> m_excludedAddrSet;
QList<IPAddress> m_pendingExclusionRoutes;
QTimer m_handshakeTimer; QTimer m_handshakeTimer;
}; };
@@ -101,7 +101,9 @@ bool AndroidController::initialize()
{"onAuthResult", "(Z)V", reinterpret_cast<void *>(onAuthResult)}, {"onAuthResult", "(Z)V", reinterpret_cast<void *>(onAuthResult)},
{"decodeQrCode", "(Ljava/lang/String;)Z", reinterpret_cast<bool *>(decodeQrCode)}, {"decodeQrCode", "(Ljava/lang/String;)Z", reinterpret_cast<bool *>(decodeQrCode)},
{"onImeInsetsChanged", "(I)V", reinterpret_cast<void *>(onImeInsetsChanged)}, {"onImeInsetsChanged", "(I)V", reinterpret_cast<void *>(onImeInsetsChanged)},
{"onSystemBarsInsetsChanged", "(II)V", reinterpret_cast<void *>(onSystemBarsInsetsChanged)} {"onSystemBarsInsetsChanged", "(II)V", reinterpret_cast<void *>(onSystemBarsInsetsChanged)},
{"onActivityPaused", "()V", reinterpret_cast<void *>(onActivityPaused)},
{"onActivityResumed", "()V", reinterpret_cast<void *>(onActivityResumed)}
}; };
QJniEnvironment env; QJniEnvironment env;
@@ -558,3 +560,22 @@ void AndroidController::onSystemBarsInsetsChanged(JNIEnv *env, jobject thiz, jin
emit AndroidController::instance()->systemBarsInsetsChanged(navBarHeightDp, statusBarHeightDp); emit AndroidController::instance()->systemBarsInsetsChanged(navBarHeightDp, statusBarHeightDp);
} }
// static
void AndroidController::onActivityPaused(JNIEnv *env, jobject thiz)
{
Q_UNUSED(env);
Q_UNUSED(thiz);
emit AndroidController::instance()->activityPaused();
}
// static
void AndroidController::onActivityResumed(JNIEnv *env, jobject thiz)
{
Q_UNUSED(env);
Q_UNUSED(thiz);
emit AndroidController::instance()->activityResumed();
}
@@ -75,6 +75,8 @@ signals:
void authenticationResult(bool result); void authenticationResult(bool result);
void imeInsetsChanged(int heightDp); void imeInsetsChanged(int heightDp);
void systemBarsInsetsChanged(int navBarHeightDp, int statusBarHeightDp); void systemBarsInsetsChanged(int navBarHeightDp, int statusBarHeightDp);
void activityPaused();
void activityResumed();
private: private:
bool isWaitingStatus = true; bool isWaitingStatus = true;
@@ -105,6 +107,8 @@ private:
static bool decodeQrCode(JNIEnv *env, jobject thiz, jstring data); static bool decodeQrCode(JNIEnv *env, jobject thiz, jstring data);
static void onImeInsetsChanged(JNIEnv *env, jobject thiz, jint heightDp); static void onImeInsetsChanged(JNIEnv *env, jobject thiz, jint heightDp);
static void onSystemBarsInsetsChanged(JNIEnv *env, jobject thiz, jint navBarHeightDp, jint statusBarHeightDp); static void onSystemBarsInsetsChanged(JNIEnv *env, jobject thiz, jint navBarHeightDp, jint statusBarHeightDp);
static void onActivityPaused(JNIEnv *env, jobject thiz);
static void onActivityResumed(JNIEnv *env, jobject thiz);
template <typename Ret, typename ...Args> template <typename Ret, typename ...Args>
static auto callActivityMethod(const char *methodName, const char *signature, Args &&...args); static auto callActivityMethod(const char *methodName, const char *signature, Args &&...args);
@@ -126,8 +126,7 @@ extension PacketTunnelProvider {
} }
vpnReachability.startTracking { [weak self] status in vpnReachability.startTracking { [weak self] status in
guard status == .reachableViaWiFi else { return } self?.handleOpenVPNReachabilityChange(status)
self?.ovpnAdapter?.reconnect(afterTimeInterval: 5)
} }
startHandler = completionHandler startHandler = completionHandler
@@ -21,6 +21,44 @@ extension Constants {
} }
extension PacketTunnelProvider { extension PacketTunnelProvider {
private func applyXraySplitTunnel(_ xrayConfig: XrayConfig,
settings: NEPacketTunnelNetworkSettings) {
guard let splitTunnelType = xrayConfig.splitTunnelType else {
return
}
guard let splitTunnelSites = xrayConfig.splitTunnelSites else {
xrayLog(.error, message: "Split tunnel sites are not set")
return
}
if splitTunnelType == 1 {
var ipv4IncludedRoutes = [NEIPv4Route]()
for allowedIPString in splitTunnelSites {
if let allowedIP = IPAddressRange(from: allowedIPString) {
ipv4IncludedRoutes.append(NEIPv4Route(
destinationAddress: "\(allowedIP.address)",
subnetMask: "\(allowedIP.subnetMask())"))
}
}
settings.ipv4Settings?.includedRoutes = ipv4IncludedRoutes
} else if splitTunnelType == 2 {
var ipv4ExcludedRoutes = [NEIPv4Route]()
for excludedIPString in splitTunnelSites {
if let excludedIP = IPAddressRange(from: excludedIPString) {
ipv4ExcludedRoutes.append(NEIPv4Route(
destinationAddress: "\(excludedIP.address)",
subnetMask: "\(excludedIP.subnetMask())"))
}
}
settings.ipv4Settings?.excludedRoutes = ipv4ExcludedRoutes
}
}
func startXray(completionHandler: @escaping (Error?) -> Void) { func startXray(completionHandler: @escaping (Error?) -> Void) {
// Xray configuration // Xray configuration
@@ -72,6 +110,7 @@ extension PacketTunnelProvider {
settings.dnsSettings = !dnsArray.isEmpty settings.dnsSettings = !dnsArray.isEmpty
? NEDNSSettings(servers: dnsArray) ? NEDNSSettings(servers: dnsArray)
: NEDNSSettings(servers: ["1.1.1.1"]) : NEDNSSettings(servers: ["1.1.1.1"])
applyXraySplitTunnel(xrayConfig, settings: settings)
let xrayConfigData = xrayConfig.config.data(using: .utf8) let xrayConfigData = xrayConfig.config.data(using: .utf8)
+135 -9
View File
@@ -41,10 +41,15 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
var ovpnAdapter: OpenVPNAdapter? var ovpnAdapter: OpenVPNAdapter?
private lazy var openVPNPacketFlowAdapter = PacketTunnelFlowAdapter(flow: packetFlow) private lazy var openVPNPacketFlowAdapter = PacketTunnelFlowAdapter(flow: packetFlow)
private let pathMonitorQueue = DispatchQueue(label: Constants.processQueueName + ".path-monitor") private let pathMonitorQueue = DispatchQueue(label: Constants.processQueueName + ".path-monitor")
private let networkChangeQueue = DispatchQueue(label: Constants.processQueueName + ".network-change")
private let pathMonitor = NWPathMonitor() private let pathMonitor = NWPathMonitor()
private var didReceiveInitialPathUpdate = false private var didReceiveInitialPathUpdate = false
private var currentPath: Network.NWPath? private var currentPath: Network.NWPath?
private var currentPathSignature: String? private var currentPathSignature: String?
private var pendingOpenVPNReconnectWorkItem: DispatchWorkItem?
private var pendingNetworkChangeWorkItem: DispatchWorkItem?
private var isApplyingNetworkChange = false
private var lastOpenVPNReachabilityStatus: OpenVPNReachabilityStatus?
var splitTunnelType: Int? var splitTunnelType: Int?
var splitTunnelSites: [String]? var splitTunnelSites: [String]?
@@ -78,14 +83,22 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
guard hasMeaningfulChange, let proto = self.protoType else { return } guard hasMeaningfulChange, let proto = self.protoType else { return }
// WireGuard/AWG manages network changes internally; avoid restarting the tunnel here. // WireGuard/AWG manages network changes internally in its own adapter.
if proto == .wireguard { if proto == .wireguard {
return return
} }
DispatchQueue.main.async { if proto == .openvpn {
self.handle(networkChange: path) { _ in } self.scheduleOpenVPNReconnect(reason: "NWPath changed")
return
} }
if self.isApplyingNetworkChange || self.reasserting {
xrayLog(.debug, message: "Ignoring path change while xray restart is in progress")
return
}
self.scheduleNetworkChangeHandling(for: proto, path: path)
} }
pathMonitor.start(queue: pathMonitorQueue) pathMonitor.start(queue: pathMonitorQueue)
@@ -197,6 +210,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
return return
} }
cancelPendingOpenVPNReconnect()
cancelPendingNetworkChangeHandling()
didReceiveInitialPathUpdate = false didReceiveInitialPathUpdate = false
updateActiveInterfaceIndexForCurrentPath() updateActiveInterfaceIndexForCurrentPath()
@@ -215,6 +230,9 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
cancelPendingOpenVPNReconnect()
cancelPendingNetworkChangeHandling()
guard let protoType else { guard let protoType else {
completionHandler() completionHandler()
return return
@@ -259,9 +277,111 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
} }
private func handle(networkChange changePath: Network.NWPath, completion: @escaping (Error?) -> Void) { private func handle(networkChange changePath: Network.NWPath, completion: @escaping (Error?) -> Void) {
guard protoType == .xray else {
updateActiveInterfaceIndex(for: changePath)
completion(nil)
return
}
updateActiveInterfaceIndex(for: changePath) updateActiveInterfaceIndex(for: changePath)
wg_log(.info, message: "Tunnel restarted.") reasserting = true
startTunnel(options: nil, completionHandler: completion) xrayLog(.info, message: "Applying network change to xray tunnel")
stopXray { }
startXray { [weak self] error in
self?.reasserting = false
completion(error)
}
}
private func scheduleNetworkChangeHandling(for proto: TunnelProtoType, path: Network.NWPath) {
guard proto == .xray else { return }
pendingNetworkChangeWorkItem?.cancel()
let workItem = DispatchWorkItem { [weak self] in
guard let self else { return }
self.pendingNetworkChangeWorkItem = nil
if self.isApplyingNetworkChange || self.reasserting {
xrayLog(.debug, message: "Skipping network change while restart is already in progress")
return
}
self.isApplyingNetworkChange = true
DispatchQueue.main.async {
self.handle(networkChange: path) { [weak self] _ in
self?.networkChangeQueue.async {
self?.isApplyingNetworkChange = false
}
}
}
}
pendingNetworkChangeWorkItem = workItem
networkChangeQueue.asyncAfter(deadline: .now() + 1.0, execute: workItem)
}
private func scheduleOpenVPNReconnect(reason: String) {
guard protoType == .openvpn else { return }
pendingOpenVPNReconnectWorkItem?.cancel()
let workItem = DispatchWorkItem { [weak self] in
guard let self else { return }
self.pendingOpenVPNReconnectWorkItem = nil
guard self.protoType == .openvpn else { return }
if self.reasserting {
ovpnLog(.debug, message: "Skipping OpenVPN reconnect while session is already reasserting")
return
}
DispatchQueue.main.async { [weak self] in
guard let self else { return }
guard !self.reasserting else {
ovpnLog(.debug, message: "Skipping OpenVPN reconnect while session is already reasserting")
return
}
ovpnLog(.info, message: "\(reason), reconnecting OpenVPN session")
self.ovpnAdapter?.reconnect(afterTimeInterval: 1)
}
}
pendingOpenVPNReconnectWorkItem = workItem
networkChangeQueue.asyncAfter(deadline: .now() + 1.0, execute: workItem)
}
func handleOpenVPNReachabilityChange(_ status: OpenVPNReachabilityStatus) {
defer { lastOpenVPNReachabilityStatus = status }
guard let previousStatus = lastOpenVPNReachabilityStatus else {
return
}
guard previousStatus != status else {
return
}
switch status {
case .reachableViaWiFi, .reachableViaWWAN:
scheduleOpenVPNReconnect(reason: "Reachability changed")
default:
break
}
}
private func cancelPendingOpenVPNReconnect() {
pendingOpenVPNReconnectWorkItem?.cancel()
pendingOpenVPNReconnectWorkItem = nil
lastOpenVPNReachabilityStatus = nil
}
private func cancelPendingNetworkChangeHandling() {
pendingNetworkChangeWorkItem?.cancel()
pendingNetworkChangeWorkItem = nil
isApplyingNetworkChange = false
} }
} }
@@ -271,8 +391,14 @@ private extension PacketTunnelProvider {
signatureComponents.append(path.isExpensive ? "exp" : "noexp") signatureComponents.append(path.isExpensive ? "exp" : "noexp")
signatureComponents.append(path.isConstrained ? "con" : "nocon") signatureComponents.append(path.isConstrained ? "con" : "nocon")
let preferredTypes: [NWInterface.InterfaceType] = [.wiredEthernet, .wifi, .cellular, .loopback, .other] // Ignore loopback and tunnel-style `.other` interfaces so Xray does not
let sortedInterfaces = path.availableInterfaces.sorted { lhs, rhs in // react to its own utun lifecycle as if the physical uplink changed.
let preferredTypes: [NWInterface.InterfaceType] = [.wiredEthernet, .wifi, .cellular]
let externalInterfaces = path.availableInterfaces.filter { interface in
interface.type == .wiredEthernet || interface.type == .wifi || interface.type == .cellular
}
let sortedInterfaces = externalInterfaces.sorted { lhs, rhs in
if lhs.type == rhs.type { if lhs.type == rhs.type {
return lhs.index < rhs.index return lhs.index < rhs.index
} }
@@ -293,8 +419,8 @@ private extension PacketTunnelProvider {
case .wiredEthernet: typeName = "ethernet" case .wiredEthernet: typeName = "ethernet"
case .wifi: typeName = "wifi" case .wifi: typeName = "wifi"
case .cellular: typeName = "cellular" case .cellular: typeName = "cellular"
case .loopback: typeName = "loopback" case .loopback, .other:
case .other: typeName = "other" continue
@unknown default: typeName = "unknown" @unknown default: typeName = "unknown"
} }
signatureComponents.append("\(typeName):\(interface.index)") signatureComponents.append("\(typeName):\(interface.index)")
+2
View File
@@ -3,5 +3,7 @@ import Foundation
struct XrayConfig: Decodable { struct XrayConfig: Decodable {
let dns1: String? let dns1: String?
let dns2: String? let dns2: String?
let splitTunnelType: Int?
let splitTunnelSites: [String]?
let config: String let config: String
} }
+9
View File
@@ -684,6 +684,15 @@ bool IosController::setupXray()
QJsonObject finalConfig; QJsonObject finalConfig;
finalConfig.insert(config_key::dns1, m_rawConfig[config_key::dns1].toString()); finalConfig.insert(config_key::dns1, m_rawConfig[config_key::dns1].toString());
finalConfig.insert(config_key::dns2, m_rawConfig[config_key::dns2].toString()); finalConfig.insert(config_key::dns2, m_rawConfig[config_key::dns2].toString());
finalConfig.insert(config_key::splitTunnelType, m_rawConfig[config_key::splitTunnelType]);
QJsonArray splitTunnelSites = m_rawConfig[config_key::splitTunnelSites].toArray();
for(int index = 0; index < splitTunnelSites.count(); index++) {
splitTunnelSites[index] = splitTunnelSites[index].toString().remove(" ");
}
finalConfig.insert(config_key::splitTunnelSites, splitTunnelSites);
finalConfig.insert(config_key::config, xrayConfigStr); finalConfig.insert(config_key::config, xrayConfigStr);
QJsonDocument finalConfigDoc(finalConfig); QJsonDocument finalConfigDoc(finalConfig);
+127 -174
View File
@@ -4,20 +4,14 @@
#include "dnsutilslinux.h" #include "dnsutilslinux.h"
#include <net/if.h> #include <QDir>
#include <QFile>
#include <QDBusVariant> #include <QFileInfo>
#include <QtDBus/QtDBus> #include <QProcess>
#include "leakdetector.h" #include "leakdetector.h"
#include "logger.h" #include "logger.h"
constexpr const char* DBUS_RESOLVE_SERVICE = "org.freedesktop.resolve1";
constexpr const char* DBUS_RESOLVE_PATH = "/org/freedesktop/resolve1";
constexpr const char* DBUS_RESOLVE_MANAGER = "org.freedesktop.resolve1.Manager";
constexpr const char* DBUS_PROPERTY_INTERFACE =
"org.freedesktop.DBus.Properties";
namespace { namespace {
Logger logger("DnsUtilsLinux"); Logger logger("DnsUtilsLinux");
} }
@@ -25,188 +19,147 @@ Logger logger("DnsUtilsLinux");
DnsUtilsLinux::DnsUtilsLinux(QObject* parent) : DnsUtils(parent) { DnsUtilsLinux::DnsUtilsLinux(QObject* parent) : DnsUtils(parent) {
MZ_COUNT_CTOR(DnsUtilsLinux); MZ_COUNT_CTOR(DnsUtilsLinux);
logger.debug() << "DnsUtilsLinux created."; logger.debug() << "DnsUtilsLinux created.";
QDBusConnection conn = QDBusConnection::systemBus();
m_resolver = new QDBusInterface(DBUS_RESOLVE_SERVICE, DBUS_RESOLVE_PATH,
DBUS_RESOLVE_MANAGER, conn, this);
} }
DnsUtilsLinux::~DnsUtilsLinux() { DnsUtilsLinux::~DnsUtilsLinux() {
MZ_COUNT_DTOR(DnsUtilsLinux); MZ_COUNT_DTOR(DnsUtilsLinux);
for (auto iterator = m_linkDomains.constBegin();
iterator != m_linkDomains.constEnd(); ++iterator) {
QList<QVariant> argumentList;
argumentList << QVariant::fromValue(iterator.key());
argumentList << QVariant::fromValue(iterator.value());
m_resolver->asyncCallWithArgumentList(QStringLiteral("SetLinkDomains"),
argumentList);
}
if (m_ifindex > 0) {
m_resolver->asyncCall(QStringLiteral("RevertLink"), m_ifindex);
}
logger.debug() << "DnsUtilsLinux destroyed."; logger.debug() << "DnsUtilsLinux destroyed.";
} }
void DnsUtilsLinux::writeResolvConf(const QList<QHostAddress>& resolvers) {
if (resolvers.isEmpty()) return;
static const QString kPath = QStringLiteral("/etc/resolv.conf");
if (m_resolvConfOriginal.isEmpty()) {
QFileInfo fi(kPath);
if (fi.isSymLink()) {
m_resolvConfOriginal = fi.symLinkTarget();
logger.debug() << "Saved resolv.conf symlink target:"
<< m_resolvConfOriginal;
if (!m_stateFilePath.isEmpty()) {
QFile sf(m_stateFilePath);
if (sf.open(QIODevice::WriteOnly | QIODevice::Text))
sf.write(m_resolvConfOriginal.toUtf8());
}
} else {
QFile orig(kPath);
if (orig.open(QIODevice::ReadOnly | QIODevice::Text)) {
QByteArray content = orig.readAll();
orig.close();
m_resolvConfOriginal = QStringLiteral("__file__");
logger.debug() << "resolv.conf is a regular file; saved content for restore";
if (!m_stateFilePath.isEmpty()) {
QFile sf(m_stateFilePath);
if (sf.open(QIODevice::WriteOnly | QIODevice::Text)) {
sf.write("__file__:");
sf.write(content);
}
}
} else {
m_resolvConfOriginal = QStringLiteral("__file__");
}
}
}
QFile::remove(kPath);
QFile f(kPath);
if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) {
logger.warning() << "Failed to write" << kPath << ":" << f.errorString();
if (m_resolvConfOriginal != QStringLiteral("__file__"))
QFile::link(m_resolvConfOriginal, kPath);
m_resolvConfOriginal.clear();
if (!m_stateFilePath.isEmpty()) QFile::remove(m_stateFilePath);
return;
}
for (const auto& r : resolvers) {
if (r.protocol() == QAbstractSocket::IPv4Protocol)
f.write(
QStringLiteral("nameserver %1\n").arg(r.toString()).toUtf8());
}
f.close();
logger.debug() << "Wrote resolv.conf with" << resolvers.size()
<< "DNS servers";
}
void DnsUtilsLinux::restoreResolvConf() {
if (m_resolvConfOriginal.isEmpty()) return;
const QString original = m_resolvConfOriginal;
m_resolvConfOriginal.clear();
if (!m_stateFilePath.isEmpty())
QFile::remove(m_stateFilePath);
static const char* kPath = "/etc/resolv.conf";
QFile::remove(kPath);
if (original == QStringLiteral("__file__")) {
if (!m_resolvConfSavedContent.isEmpty()) {
QFile f(kPath);
if (f.open(QIODevice::WriteOnly | QIODevice::Text)) {
f.write(m_resolvConfSavedContent.toUtf8());
logger.debug() << "Restored resolv.conf from saved content";
}
m_resolvConfSavedContent.clear();
} else if (QFile::exists(QStringLiteral("/run/systemd/resolve/stub-resolv.conf"))) {
QFile::link(QStringLiteral("/run/systemd/resolve/stub-resolv.conf"), kPath);
logger.debug() << "Restored resolv.conf symlink to stub-resolv.conf";
}
} else {
QFile::link(original, kPath);
logger.debug() << "Restored resolv.conf symlink to" << original;
}
}
bool DnsUtilsLinux::updateResolvers(const QString& ifname, bool DnsUtilsLinux::updateResolvers(const QString& ifname,
const QList<QHostAddress>& resolvers) { const QList<QHostAddress>& resolvers) {
m_ifindex = if_nametoindex(qPrintable(ifname)); m_stateFilePath = QStringLiteral("/run/amnezia-dns-%1").arg(ifname);
if (m_ifindex <= 0) {
logger.error() << "Unable to resolve ifindex for" << ifname;
return false;
}
setLinkDNS(m_ifindex, resolvers); writeResolvConf(resolvers);
setLinkDefaultRoute(m_ifindex, true);
updateLinkDomains();
return true; return true;
} }
bool DnsUtilsLinux::restoreResolvers() { bool DnsUtilsLinux::restoreResolvers() {
for (auto iterator = m_linkDomains.constBegin();
iterator != m_linkDomains.constEnd(); ++iterator) { if (m_resolvConfOriginal.isEmpty()) {
setLinkDomains(iterator.key(), iterator.value()); QStringList candidates;
if (!m_stateFilePath.isEmpty()) {
candidates << m_stateFilePath;
} else {
QDir runDir(QStringLiteral("/run"));
for (const QString& name : runDir.entryList(
{QStringLiteral("amnezia-dns-*")}, QDir::Files)) {
candidates << runDir.filePath(name);
}
}
for (const QString& path : candidates) {
QFile sf(path);
if (sf.open(QIODevice::ReadOnly)) {
QByteArray data = sf.readAll();
sf.close();
m_stateFilePath = path;
if (data.startsWith("__file__:")) {
m_resolvConfOriginal = QStringLiteral("__file__");
m_resolvConfSavedContent = QString::fromUtf8(data.mid(9));
} else {
m_resolvConfOriginal = QString::fromUtf8(data).trimmed();
}
logger.debug() << "Recovered DNS original from" << path << ":"
<< m_resolvConfOriginal;
break;
}
}
} }
m_linkDomains.clear();
/* Revert the VPN interface's DNS configuration */ const bool hadDnsState = !m_resolvConfOriginal.isEmpty();
if (m_ifindex > 0) { restoreResolvConf();
QList<QVariant> argumentList = {QVariant::fromValue(m_ifindex)};
QDBusPendingReply<> reply = m_resolver->asyncCallWithArgumentList(
QStringLiteral("RevertLink"), argumentList);
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this); if (hadDnsState && QFile::exists(QStringLiteral("/run/systemd/resolve"))) {
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this, QProcess::startDetached("systemctl", {"restart", "systemd-resolved"});
SLOT(dnsCallCompleted(QDBusPendingCallWatcher*)));
m_ifindex = 0;
} }
return true; return true;
} }
void DnsUtilsLinux::dnsCallCompleted(QDBusPendingCallWatcher* call) {
QDBusPendingReply<> reply = *call;
if (reply.isError()) {
logger.error() << "Error received from the DBus service";
}
delete call;
}
void DnsUtilsLinux::setLinkDNS(int ifindex,
const QList<QHostAddress>& resolvers) {
QList<DnsResolver> resolverList;
char ifnamebuf[IF_NAMESIZE];
const char* ifname = if_indextoname(ifindex, ifnamebuf);
for (const auto& ip : resolvers) {
resolverList.append(ip);
if (ifname) {
logger.debug() << "Adding DNS resolver" << ip.toString() << "via"
<< ifname;
}
}
QList<QVariant> argumentList;
argumentList << QVariant::fromValue(ifindex);
argumentList << QVariant::fromValue(resolverList);
QDBusPendingReply<> reply = m_resolver->asyncCallWithArgumentList(
QStringLiteral("SetLinkDNS"), argumentList);
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
SLOT(dnsCallCompleted(QDBusPendingCallWatcher*)));
}
void DnsUtilsLinux::setLinkDomains(int ifindex,
const QList<DnsLinkDomain>& domains) {
char ifnamebuf[IF_NAMESIZE];
const char* ifname = if_indextoname(ifindex, ifnamebuf);
if (ifname) {
for (const auto& d : domains) {
// The DNS search domains often winds up revealing user's ISP which
// can correlate back to their location.
logger.debug() << "Setting DNS domain:" << logger.sensitive(d.domain)
<< "via" << ifname << (d.search ? "search" : "");
}
}
QList<QVariant> argumentList;
argumentList << QVariant::fromValue(ifindex);
argumentList << QVariant::fromValue(domains);
QDBusPendingReply<> reply = m_resolver->asyncCallWithArgumentList(
QStringLiteral("SetLinkDomains"), argumentList);
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
SLOT(dnsCallCompleted(QDBusPendingCallWatcher*)));
}
void DnsUtilsLinux::setLinkDefaultRoute(int ifindex, bool enable) {
QList<QVariant> argumentList;
argumentList << QVariant::fromValue(ifindex);
argumentList << QVariant::fromValue(enable);
QDBusPendingReply<> reply = m_resolver->asyncCallWithArgumentList(
QStringLiteral("SetLinkDefaultRoute"), argumentList);
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
SLOT(dnsCallCompleted(QDBusPendingCallWatcher*)));
}
void DnsUtilsLinux::updateLinkDomains() {
/* Get the list of search domains, and remove any others that might conspire
* to satisfy DNS resolution. Unfortunately, this is a pain because Qt doesn't
* seem to be able to demarshall complex property types.
*/
QDBusMessage message = QDBusMessage::createMethodCall(
DBUS_RESOLVE_SERVICE, DBUS_RESOLVE_PATH, DBUS_PROPERTY_INTERFACE, "Get");
message << QString(DBUS_RESOLVE_MANAGER);
message << QString("Domains");
QDBusPendingReply<QVariant> reply =
m_resolver->connection().asyncCall(message);
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
SLOT(dnsDomainsReceived(QDBusPendingCallWatcher*)));
}
void DnsUtilsLinux::dnsDomainsReceived(QDBusPendingCallWatcher* call) {
QDBusPendingReply<QVariant> reply = *call;
if (reply.isError()) {
logger.error() << "Error retrieving the DNS domains from the DBus service";
delete call;
return;
}
/* Update the state of the DNS domains */
m_linkDomains.clear();
QDBusArgument args = qvariant_cast<QDBusArgument>(reply.value());
QList<DnsDomain> list = qdbus_cast<QList<DnsDomain>>(args);
for (const auto& d : list) {
if (d.ifindex == 0) {
continue;
}
m_linkDomains[d.ifindex].append(DnsLinkDomain(d.domain, d.search));
}
/* Drop any competing root search domains. */
DnsLinkDomain root = DnsLinkDomain(".", true);
for (auto iterator = m_linkDomains.constBegin();
iterator != m_linkDomains.constEnd(); ++iterator) {
if (!iterator.value().contains(root)) {
continue;
}
QList<DnsLinkDomain> newlist = iterator.value();
newlist.removeAll(root);
setLinkDomains(iterator.key(), newlist);
}
/* Add a root search domain for the new interface. */
QList<DnsLinkDomain> newlist = {root};
setLinkDomains(m_ifindex, newlist);
delete call;
}
static DnsMetatypeRegistrationProxy s_dnsMetatypeProxy;
+10 -15
View File
@@ -5,11 +5,11 @@
#ifndef DNSUTILSLINUX_H #ifndef DNSUTILSLINUX_H
#define DNSUTILSLINUX_H #define DNSUTILSLINUX_H
#include <QDBusInterface> #include <QHostAddress>
#include <QDBusPendingCallWatcher> #include <QList>
#include <QString>
#include "daemon/dnsutils.h" #include "daemon/dnsutils.h"
#include "dbustypeslinux.h"
class DnsUtilsLinux final : public DnsUtils { class DnsUtilsLinux final : public DnsUtils {
Q_OBJECT Q_OBJECT
@@ -23,19 +23,14 @@ class DnsUtilsLinux final : public DnsUtils {
bool restoreResolvers() override; bool restoreResolvers() override;
private: private:
void setLinkDNS(int ifindex, const QList<QHostAddress>& resolvers); void writeResolvConf(const QList<QHostAddress>& resolvers);
void setLinkDomains(int ifindex, const QList<DnsLinkDomain>& domains); void restoreResolvConf();
void setLinkDefaultRoute(int ifindex, bool enable);
void updateLinkDomains();
private slots:
void dnsCallCompleted(QDBusPendingCallWatcher*);
void dnsDomainsReceived(QDBusPendingCallWatcher*);
private: private:
int m_ifindex = 0;
QMap<int, DnsLinkDomainList> m_linkDomains; QString m_resolvConfOriginal;
QDBusInterface* m_resolver = nullptr; QString m_resolvConfSavedContent;
QString m_stateFilePath;
}; };
#endif // DNSUTILSLINUX_H #endif
+29 -16
View File
@@ -32,6 +32,7 @@
#include "linuxfirewall.h" #include "linuxfirewall.h"
#include "logger.h" #include "logger.h"
#include <QHostAddress>
#include <QProcess> #include <QProcess>
#define BRAND_CODE "amn" #define BRAND_CODE "amn"
@@ -108,7 +109,7 @@ int LinuxFirewall::linkChain(LinuxFirewall::IPVersion ip, const QString& chain,
// (we can't safely delete all rules at once since rule numbers change) // (we can't safely delete all rules at once since rule numbers change)
// TODO: occasionally this script results in warnings in logs "Bad rule (does a matching rule exist in the chain?)" - this happens when // TODO: occasionally this script results in warnings in logs "Bad rule (does a matching rule exist in the chain?)" - this happens when
// the e.g OUTPUT chain is empty but this script attempts to delete things from it anyway. It doesn't cause any problems, but we should still fix at some point.. // the e.g OUTPUT chain is empty but this script attempts to delete things from it anyway. It doesn't cause any problems, but we should still fix at some point..
return execute(QStringLiteral("if ! %1 -L %2 -n --line-numbers -t %4 2> /dev/null | awk 'int($1) == 1 && $2 == \"%3\" { found=1 } END { if(found==1) { exit 0 } else { exit 1 } }' ; then %1 -I %2 -j %3 -t %4 && %1 -L %2 -n --line-numbers -t %4 2> /dev/null | awk 'int($1) > 1 && $2 == \"%3\" { print $1; exit }' | xargs %1 -t %4 -D %2 ; fi").arg(cmd, parent, chain, tableName)); return execute(QStringLiteral("if ! %1 -L %2 -n --line-numbers -t %4 2> /dev/null | awk 'int($1) == 1 && $2 == \"%3\" { found=1 } END { if(found==1) { exit 0 } else { exit 1 } }' ; then %1 -I %2 -j %3 -t %4 && %1 -L %2 -n --line-numbers -t %4 2> /dev/null | awk 'int($1) > 1 && $2 == \"%3\" { print $1; exit }' | xargs -r %1 -t %4 -D %2 ; fi").arg(cmd, parent, chain, tableName));
} }
else else
return execute(QStringLiteral("if ! %1 -C %2 -j %3 -t %4 2> /dev/null ; then %1 -A %2 -j %3 -t %4; fi").arg(cmd, parent, chain, tableName)); return execute(QStringLiteral("if ! %1 -C %2 -j %3 -t %4 2> /dev/null ; then %1 -A %2 -j %3 -t %4; fi").arg(cmd, parent, chain, tableName));
@@ -192,12 +193,8 @@ QStringList LinuxFirewall::getDNSRules(const QStringList& servers)
QStringList result; QStringList result;
for (const QString& server : servers) for (const QString& server : servers)
{ {
result << QStringLiteral("-o amn0+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server); result << QStringLiteral("-d %1 -p udp --dport 53 -j ACCEPT").arg(server);
result << QStringLiteral("-o amn0+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server); result << QStringLiteral("-d %1 -p tcp --dport 53 -j ACCEPT").arg(server);
result << QStringLiteral("-o tun0+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server);
result << QStringLiteral("-o tun0+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server);
result << QStringLiteral("-o tun2+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server);
result << QStringLiteral("-o tun2+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server);
} }
return result; return result;
} }
@@ -245,7 +242,7 @@ void LinuxFirewall::install()
QStringLiteral("-o lo+ -j ACCEPT"), QStringLiteral("-o lo+ -j ACCEPT"),
}); });
installAnchor(IPv4, QStringLiteral("320.allowDNS"), {}); installAnchor(Both, QStringLiteral("320.allowDNS"), {});
installAnchor(Both, QStringLiteral("310.blockDNS"), { installAnchor(Both, QStringLiteral("310.blockDNS"), {
QStringLiteral("-p udp --dport 53 -j REJECT"), QStringLiteral("-p udp --dport 53 -j REJECT"),
@@ -289,6 +286,7 @@ void LinuxFirewall::install()
installAnchor(Both, QStringLiteral("100.blockAll"), { installAnchor(Both, QStringLiteral("100.blockAll"), {
QStringLiteral("-j REJECT"), QStringLiteral("-j REJECT"),
}); });
installAnchor(Both, QStringLiteral("400.allowPIA"), {});
// NAT rules // NAT rules
installAnchor(Both, QStringLiteral("100.transIp"), { installAnchor(Both, QStringLiteral("100.transIp"), {
@@ -351,7 +349,7 @@ void LinuxFirewall::uninstall()
// Remove filter anchors // Remove filter anchors
uninstallAnchor(Both, QStringLiteral("000.allowLoopback")); uninstallAnchor(Both, QStringLiteral("000.allowLoopback"));
uninstallAnchor(Both, QStringLiteral("400.allowPIA")); uninstallAnchor(Both, QStringLiteral("400.allowPIA"));
uninstallAnchor(IPv4, QStringLiteral("320.allowDNS")); uninstallAnchor(Both, QStringLiteral("320.allowDNS"));
uninstallAnchor(Both, QStringLiteral("310.blockDNS")); uninstallAnchor(Both, QStringLiteral("310.blockDNS"));
uninstallAnchor(Both, QStringLiteral("300.allowLAN")); uninstallAnchor(Both, QStringLiteral("300.allowLAN"));
uninstallAnchor(Both, QStringLiteral("290.allowDHCP")); uninstallAnchor(Both, QStringLiteral("290.allowDHCP"));
@@ -372,6 +370,8 @@ void LinuxFirewall::uninstall()
teardownTrafficSplitting(); teardownTrafficSplitting();
anchorCallbacks.clear();
logger.debug() << "LinuxFirewall::uninstall() complete"; logger.debug() << "LinuxFirewall::uninstall() complete";
} }
@@ -445,12 +445,17 @@ void LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPVersion ip, const QString
void LinuxFirewall::updateDNSServers(const QStringList& servers) void LinuxFirewall::updateDNSServers(const QStringList& servers)
{ {
static QStringList existingServers {};
existingServers = servers;
execute(QStringLiteral("iptables -F %1.320.allowDNS").arg(kAnchorName)); execute(QStringLiteral("iptables -F %1.320.allowDNS").arg(kAnchorName));
for (const QString& rule : getDNSRules(servers)) execute(QStringLiteral("ip6tables -F %1.320.allowDNS").arg(kAnchorName));
execute(QStringLiteral("iptables -A %1.320.allowDNS %2").arg(kAnchorName, rule));
for (const QString& server : servers) {
if (server.isEmpty()) continue;
const QString cmd = (QHostAddress(server).protocol() == QAbstractSocket::IPv6Protocol)
? QStringLiteral("ip6tables")
: QStringLiteral("iptables");
execute(QStringLiteral("%1 -A %2.320.allowDNS -d %3 -p udp --dport 53 -j ACCEPT").arg(cmd, kAnchorName, server));
execute(QStringLiteral("%1 -A %2.320.allowDNS -d %3 -p tcp --dport 53 -j ACCEPT").arg(cmd, kAnchorName, server));
}
} }
void LinuxFirewall::updateAllowNets(const QStringList& servers) void LinuxFirewall::updateAllowNets(const QStringList& servers)
@@ -495,13 +500,20 @@ int LinuxFirewall::execute(const QString &command, bool ignoreErrors)
logger.debug() << "(" << exitCode << ") $ " << command; logger.debug() << "(" << exitCode << ") $ " << command;
if (!out.isEmpty()) if (!out.isEmpty())
logger.info() << out; logger.info() << out;
if (!err.isEmpty()) if (!err.isEmpty() && !ignoreErrors)
logger.warning() << err; logger.warning() << err;
return exitCode; return exitCode;
} }
void LinuxFirewall::setupTrafficSplitting() void LinuxFirewall::setupTrafficSplitting()
{ {
execute(QStringLiteral("grep -q '%1' /etc/iproute2/rt_tables || printf '200\\t%1\\n' >> /etc/iproute2/rt_tables").arg(kRtableName));
execute(QStringLiteral(
"if [ ! -d /sys/fs/cgroup/net_cls ] ; then "
"mkdir -p /sys/fs/cgroup/net_cls && "
"mount -t cgroup -o net_cls cgroup /sys/fs/cgroup/net_cls ; fi"));
auto cGroupDir = "/sys/fs/cgroup/net_cls/" BRAND_CODE "vpnexclusions/"; auto cGroupDir = "/sys/fs/cgroup/net_cls/" BRAND_CODE "vpnexclusions/";
logger.info() << "Should be setting up cgroup in" << cGroupDir << "for traffic splitting"; logger.info() << "Should be setting up cgroup in" << cGroupDir << "for traffic splitting";
execute(QStringLiteral("if [ ! -d %1 ] ; then mkdir %1 ; sleep 0.1 ; echo %2 > %1/net_cls.classid ; fi").arg(cGroupDir).arg(kCGroupId)); execute(QStringLiteral("if [ ! -d %1 ] ; then mkdir %1 ; sleep 0.1 ; echo %2 > %1/net_cls.classid ; fi").arg(cGroupDir).arg(kCGroupId));
@@ -513,6 +525,7 @@ void LinuxFirewall::teardownTrafficSplitting()
{ {
logger.info() << "Tearing down cgroup and routing rules"; logger.info() << "Tearing down cgroup and routing rules";
execute(QStringLiteral("if ip rule list | grep -q %1; then ip rule del from all fwmark %1 lookup %2 2> /dev/null ; fi").arg(kPacketTag, kRtableName)); execute(QStringLiteral("if ip rule list | grep -q %1; then ip rule del from all fwmark %1 lookup %2 2> /dev/null ; fi").arg(kPacketTag, kRtableName));
execute(QStringLiteral("ip route flush table %1").arg(kRtableName)); execute(QStringLiteral("ip route flush table %1").arg(kRtableName), true);
execute(QStringLiteral("ip route flush cache")); execute(QStringLiteral("ip route flush cache"));
execute(QStringLiteral("sed -i '/%1/d' /etc/iproute2/rt_tables").arg(kRtableName));
} }
@@ -164,8 +164,13 @@ bool LinuxRouteMonitor::rtmSendRoute(int action, int flags, int type,
} }
if (rtm->rtm_type == RTN_THROW) { if (rtm->rtm_type == RTN_THROW) {
QString gateway = NetworkUtilities::getGatewayAndIface().first;
if (gateway.isEmpty()) {
logger.warning() << "No default gateway available, skipping exclusion route";
return false;
}
struct in_addr ip4; struct in_addr ip4;
inet_pton(AF_INET, NetworkUtilities::getGatewayAndIface().first.toUtf8(), &ip4); inet_pton(AF_INET, gateway.toUtf8(), &ip4);
nlmsg_append_attr(nlmsg, sizeof(buf), RTA_GATEWAY, &ip4, sizeof(ip4)); nlmsg_append_attr(nlmsg, sizeof(buf), RTA_GATEWAY, &ip4, sizeof(ip4));
nlmsg_append_attr32(nlmsg, sizeof(buf), RTA_PRIORITY, 0); nlmsg_append_attr32(nlmsg, sizeof(buf), RTA_PRIORITY, 0);
rtm->rtm_type = RTN_UNICAST; rtm->rtm_type = RTN_UNICAST;
@@ -44,6 +44,9 @@ void LinuxNetworkWatcher::initialize() {
connect(m_worker, &LinuxNetworkWatcherWorker::wakeup, this, connect(m_worker, &LinuxNetworkWatcherWorker::wakeup, this,
&NetworkWatcherImpl::wakeup); &NetworkWatcherImpl::wakeup);
connect(m_worker, &LinuxNetworkWatcherWorker::networkChanged, this,
[this]() { emit networkChanged(""); });
// Let's wait a few seconds to allow the UI to be fully loaded and shown. // Let's wait a few seconds to allow the UI to be fully loaded and shown.
// This is not strictly needed, but it's better for user experience because // This is not strictly needed, but it's better for user experience because
// it makes the UI faster to appear, plus it gives a bit of delay between the // it makes the UI faster to appear, plus it gives a bit of delay between the
@@ -4,6 +4,7 @@
#include "linuxnetworkwatcherworker.h" #include "linuxnetworkwatcherworker.h"
#include <QTimer>
#include <QtDBus/QtDBus> #include <QtDBus/QtDBus>
#include "leakdetector.h" #include "leakdetector.h"
@@ -37,6 +38,7 @@
enum NMState { enum NMState {
NM_STATE_UNKNOWN = 0, NM_STATE_UNKNOWN = 0,
NM_STATE_ASLEEP = 10, NM_STATE_ASLEEP = 10,
NM_STATE_DISABLED = 10,
NM_STATE_DISCONNECTED = 20, NM_STATE_DISCONNECTED = 20,
NM_STATE_DISCONNECTING = 30, NM_STATE_DISCONNECTING = 30,
NM_STATE_CONNECTING = 40, NM_STATE_CONNECTING = 40,
@@ -122,6 +124,12 @@ void LinuxNetworkWatcherWorker::initialize() {
SLOT(propertyChanged(QString, QVariantMap, QStringList))); SLOT(propertyChanged(QString, QVariantMap, QStringList)));
} }
QVariant currentState = nm.property("State");
if (currentState.isValid()) {
m_previousNMState = currentState.toUInt();
logger.debug() << "Initial NM state:" << m_previousNMState;
}
QDBusConnection::systemBus().connect(DBUS_NETWORKMANAGER, QDBusConnection::systemBus().connect(DBUS_NETWORKMANAGER,
DBUS_NETWORKMANAGER_PATH, DBUS_NETWORKMANAGER_PATH,
DBUS_NETWORKMANAGER, DBUS_NETWORKMANAGER,
@@ -199,10 +207,13 @@ void LinuxNetworkWatcherWorker::checkDevices() {
void LinuxNetworkWatcherWorker::NMStateChanged(quint32 state) void LinuxNetworkWatcherWorker::NMStateChanged(quint32 state)
{ {
if (state == NM_STATE_ASLEEP) { logger.debug() << "NMStateChanged " << state;
emit wakeup();
}
logger.debug() << "NMStateChanged " << state; if (state == NM_STATE_ASLEEP || state == NM_STATE_DISABLED) {
} emit wakeup();
} else if (state >= NM_STATE_CONNECTED_SITE && m_previousNMState < NM_STATE_CONNECTED_SITE) {
emit networkChanged();
}
m_previousNMState = state;
}
@@ -24,6 +24,7 @@ class LinuxNetworkWatcherWorker final : public QObject {
signals: signals:
void unsecuredNetwork(const QString& networkName, const QString& networkId); void unsecuredNetwork(const QString& networkName, const QString& networkId);
void wakeup(); void wakeup();
void networkChanged();
public slots: public slots:
void initialize(); void initialize();
@@ -34,10 +35,12 @@ class LinuxNetworkWatcherWorker final : public QObject {
void NMStateChanged(quint32 state); void NMStateChanged(quint32 state);
private: private:
// We collect the list of DBus wifi network device paths during the // We collect the list of DBus wifi network device paths during the
// initialization. When a property of them changes, we check if the access // initialization. When a property of them changes, we check if the access
// point is active and unsecure. // point is active and unsecure.
QStringList m_devicePaths; QStringList m_devicePaths;
quint32 m_previousNMState = 0;
}; };
#endif // LINUXNETWORKWATCHERWORKER_H #endif // LINUXNETWORKWATCHERWORKER_H
+1
View File
@@ -135,6 +135,7 @@
<file>ui/qml/Components/InstalledAppsDrawer.qml</file> <file>ui/qml/Components/InstalledAppsDrawer.qml</file>
<file>ui/qml/Components/QuestionDrawer.qml</file> <file>ui/qml/Components/QuestionDrawer.qml</file>
<file>ui/qml/Components/SelectLanguageDrawer.qml</file> <file>ui/qml/Components/SelectLanguageDrawer.qml</file>
<file>ui/qml/Components/SubscriptionExpiredDrawer.qml</file>
<file>ui/qml/Components/ServersListView.qml</file> <file>ui/qml/Components/ServersListView.qml</file>
<file>ui/qml/Components/SettingsContainersListView.qml</file> <file>ui/qml/Components/SettingsContainersListView.qml</file>
<file>ui/qml/Components/TransportProtoSelector.qml</file> <file>ui/qml/Components/TransportProtoSelector.qml</file>
+14 -21
View File
@@ -35,13 +35,12 @@ SecureQSettings::SecureQSettings(const QString &organization, const QString &app
} }
} }
m_settings.setValue("Conf/encrypted", true); m_settings.setValue("Conf/encrypted", true);
m_settings.sync();
} }
} }
QVariant SecureQSettings::value(const QString &key, const QVariant &defaultValue) const QVariant SecureQSettings::value(const QString &key, const QVariant &defaultValue) const
{ {
QMutexLocker locker(&mutex); QMutexLocker locker(&m_mutex);
if (m_cache.contains(key)) { if (m_cache.contains(key)) {
return m_cache.value(key); return m_cache.value(key);
@@ -85,7 +84,7 @@ QVariant SecureQSettings::value(const QString &key, const QVariant &defaultValue
void SecureQSettings::setValue(const QString &key, const QVariant &value) void SecureQSettings::setValue(const QString &key, const QVariant &value)
{ {
QMutexLocker locker(&mutex); QMutexLocker locker(&m_mutex);
if (encryptionRequired() && encryptedKeys.contains(key)) { if (encryptionRequired() && encryptedKeys.contains(key)) {
if (!getEncKey().isEmpty() && !getEncIv().isEmpty()) { if (!getEncKey().isEmpty() && !getEncIv().isEmpty()) {
@@ -107,26 +106,20 @@ void SecureQSettings::setValue(const QString &key, const QVariant &value)
} }
m_cache.insert(key, value); m_cache.insert(key, value);
sync();
} }
void SecureQSettings::remove(const QString &key) void SecureQSettings::remove(const QString &key)
{ {
QMutexLocker locker(&mutex); QMutexLocker locker(&m_mutex);
m_settings.remove(key); m_settings.remove(key);
m_cache.remove(key); m_cache.remove(key);
sync();
}
void SecureQSettings::sync()
{
m_settings.sync();
} }
QByteArray SecureQSettings::backupAppConfig() const QByteArray SecureQSettings::backupAppConfig() const
{ {
QMutexLocker locker(&m_mutex);
QJsonObject cfg; QJsonObject cfg;
const auto needToBackup = [this](const auto &key) { const auto needToBackup = [this](const auto &key) {
@@ -161,6 +154,8 @@ QByteArray SecureQSettings::backupAppConfig() const
bool SecureQSettings::restoreAppConfig(const QByteArray &json) bool SecureQSettings::restoreAppConfig(const QByteArray &json)
{ {
QMutexLocker locker(&m_mutex);
QJsonObject cfg = QJsonDocument::fromJson(json).object(); QJsonObject cfg = QJsonDocument::fromJson(json).object();
if (cfg.isEmpty()) if (cfg.isEmpty())
return false; return false;
@@ -173,10 +168,16 @@ bool SecureQSettings::restoreAppConfig(const QByteArray &json)
setValue(key, cfg.value(key).toVariant()); setValue(key, cfg.value(key).toVariant());
} }
sync();
return true; return true;
} }
void SecureQSettings::clearSettings()
{
QMutexLocker locker(&m_mutex);
m_settings.clear();
m_cache.clear();
}
QByteArray SecureQSettings::encryptText(const QByteArray &value) const QByteArray SecureQSettings::encryptText(const QByteArray &value) const
{ {
QSimpleCrypto::QBlockCipher cipher; QSimpleCrypto::QBlockCipher cipher;
@@ -294,11 +295,3 @@ void SecureQSettings::setSecTag(const QString &tag, const QByteArray &data)
qCritical() << "SecureQSettings::setSecTag Error:" << job->errorString(); qCritical() << "SecureQSettings::setSecTag Error:" << job->errorString();
} }
} }
void SecureQSettings::clearSettings()
{
QMutexLocker locker(&mutex);
m_settings.clear();
m_cache.clear();
sync();
}
+6 -7
View File
@@ -16,14 +16,16 @@ public:
explicit SecureQSettings(const QString &organization, const QString &application = QString(), explicit SecureQSettings(const QString &organization, const QString &application = QString(),
QObject *parent = nullptr); QObject *parent = nullptr);
Q_INVOKABLE QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const; QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const;
Q_INVOKABLE void setValue(const QString &key, const QVariant &value); void setValue(const QString &key, const QVariant &value);
void remove(const QString &key); void remove(const QString &key);
void sync();
QByteArray backupAppConfig() const; QByteArray backupAppConfig() const;
bool restoreAppConfig(const QByteArray &json); bool restoreAppConfig(const QByteArray &json);
void clearSettings();
private:
QByteArray encryptText(const QByteArray &value) const; QByteArray encryptText(const QByteArray &value) const;
QByteArray decryptText(const QByteArray &ba) const; QByteArray decryptText(const QByteArray &ba) const;
@@ -35,9 +37,6 @@ public:
static QByteArray getSecTag(const QString &tag); static QByteArray getSecTag(const QString &tag);
static void setSecTag(const QString &tag, const QByteArray &data); static void setSecTag(const QString &tag, const QByteArray &data);
void clearSettings();
private:
QSettings m_settings; QSettings m_settings;
mutable QHash<QString, QVariant> m_cache; mutable QHash<QString, QVariant> m_cache;
@@ -53,7 +52,7 @@ private:
const QByteArray magicString { "EncData" }; // Magic keyword used for mark encrypted QByteArray const QByteArray magicString { "EncData" }; // Magic keyword used for mark encrypted QByteArray
mutable QMutex mutex; mutable QRecursiveMutex m_mutex;
}; };
#endif // SECUREQSETTINGS_H #endif // SECUREQSETTINGS_H
+34 -57
View File
@@ -21,10 +21,10 @@ Settings::Settings(QObject *parent) : QObject(parent), m_settings(ORGANIZATION_N
{ {
// Import old settings // Import old settings
if (serversCount() == 0) { if (serversCount() == 0) {
QString user = value("Server/userName").toString(); QString user = m_settings.value("Server/userName").toString();
QString password = value("Server/password").toString(); QString password = m_settings.value("Server/password").toString();
QString serverName = value("Server/serverName").toString(); QString serverName = m_settings.value("Server/serverName").toString();
int port = value("Server/serverPort").toInt(); int port = m_settings.value("Server/serverPort").toInt();
if (!user.isEmpty() && !password.isEmpty() && !serverName.isEmpty()) { if (!user.isEmpty() && !password.isEmpty() && !serverName.isEmpty()) {
QJsonObject server; QJsonObject server;
@@ -222,7 +222,7 @@ QString Settings::nextAvailableServerName() const
void Settings::setSaveLogs(bool enabled) void Settings::setSaveLogs(bool enabled)
{ {
setValue("Conf/saveLogs", enabled); m_settings.setValue("Conf/saveLogs", enabled);
#ifndef Q_OS_ANDROID #ifndef Q_OS_ANDROID
if (!isSaveLogs()) { if (!isSaveLogs()) {
Logger::deInit(); Logger::deInit();
@@ -242,12 +242,12 @@ void Settings::setSaveLogs(bool enabled)
QDateTime Settings::getLogEnableDate() QDateTime Settings::getLogEnableDate()
{ {
return value("Conf/logEnableDate").toDateTime(); return m_settings.value("Conf/logEnableDate").toDateTime();
} }
void Settings::setLogEnableDate(QDateTime date) void Settings::setLogEnableDate(QDateTime date)
{ {
setValue("Conf/logEnableDate", date); m_settings.setValue("Conf/logEnableDate", date);
} }
QString Settings::routeModeString(RouteMode mode) const QString Settings::routeModeString(RouteMode mode) const
@@ -261,17 +261,17 @@ QString Settings::routeModeString(RouteMode mode) const
Settings::RouteMode Settings::routeMode() const Settings::RouteMode Settings::routeMode() const
{ {
return static_cast<RouteMode>(value("Conf/routeMode", 0).toInt()); return static_cast<RouteMode>(m_settings.value("Conf/routeMode", 0).toInt());
} }
bool Settings::isSitesSplitTunnelingEnabled() const bool Settings::isSitesSplitTunnelingEnabled() const
{ {
return value("Conf/sitesSplitTunnelingEnabled", false).toBool(); return m_settings.value("Conf/sitesSplitTunnelingEnabled", false).toBool();
} }
void Settings::setSitesSplitTunnelingEnabled(bool enabled) void Settings::setSitesSplitTunnelingEnabled(bool enabled)
{ {
setValue("Conf/sitesSplitTunnelingEnabled", enabled); m_settings.setValue("Conf/sitesSplitTunnelingEnabled", enabled);
} }
bool Settings::addVpnSite(RouteMode mode, const QString &site, const QString &ip) bool Settings::addVpnSite(RouteMode mode, const QString &site, const QString &ip)
@@ -359,12 +359,12 @@ void Settings::removeAllVpnSites(RouteMode mode)
QString Settings::primaryDns() const QString Settings::primaryDns() const
{ {
return value("Conf/primaryDns", cloudFlareNs1).toString(); return m_settings.value("Conf/primaryDns", cloudFlareNs1).toString();
} }
QString Settings::secondaryDns() const QString Settings::secondaryDns() const
{ {
return value("Conf/secondaryDns", cloudFlareNs2).toString(); return m_settings.value("Conf/secondaryDns", cloudFlareNs2).toString();
} }
void Settings::clearSettings() void Settings::clearSettings()
@@ -386,18 +386,18 @@ QString Settings::appsRouteModeString(AppsRouteMode mode) const
Settings::AppsRouteMode Settings::getAppsRouteMode() const Settings::AppsRouteMode Settings::getAppsRouteMode() const
{ {
return static_cast<AppsRouteMode>(value("Conf/appsRouteMode", 0).toInt()); return static_cast<AppsRouteMode>(m_settings.value("Conf/appsRouteMode", 0).toInt());
} }
void Settings::setAppsRouteMode(AppsRouteMode mode) void Settings::setAppsRouteMode(AppsRouteMode mode)
{ {
setValue("Conf/appsRouteMode", mode); m_settings.setValue("Conf/appsRouteMode", mode);
} }
QVector<InstalledAppInfo> Settings::getVpnApps(AppsRouteMode mode) const QVector<InstalledAppInfo> Settings::getVpnApps(AppsRouteMode mode) const
{ {
QVector<InstalledAppInfo> apps; QVector<InstalledAppInfo> apps;
auto appsArray = value("Conf/" + appsRouteModeString(mode)).toJsonArray(); auto appsArray = m_settings.value("Conf/" + appsRouteModeString(mode)).toJsonArray();
for (const auto &app : appsArray) { for (const auto &app : appsArray) {
InstalledAppInfo appInfo; InstalledAppInfo appInfo;
appInfo.appName = app.toObject().value("appName").toString(); appInfo.appName = app.toObject().value("appName").toString();
@@ -419,43 +419,42 @@ void Settings::setVpnApps(AppsRouteMode mode, const QVector<InstalledAppInfo> &a
appInfo.insert("appPath", app.appPath); appInfo.insert("appPath", app.appPath);
appsArray.push_back(appInfo); appsArray.push_back(appInfo);
} }
setValue("Conf/" + appsRouteModeString(mode), appsArray); m_settings.setValue("Conf/" + appsRouteModeString(mode), appsArray);
m_settings.sync();
} }
bool Settings::isAppsSplitTunnelingEnabled() const bool Settings::isAppsSplitTunnelingEnabled() const
{ {
return value("Conf/appsSplitTunnelingEnabled", false).toBool(); return m_settings.value("Conf/appsSplitTunnelingEnabled", false).toBool();
} }
void Settings::setAppsSplitTunnelingEnabled(bool enabled) void Settings::setAppsSplitTunnelingEnabled(bool enabled)
{ {
setValue("Conf/appsSplitTunnelingEnabled", enabled); m_settings.setValue("Conf/appsSplitTunnelingEnabled", enabled);
} }
bool Settings::isKillSwitchEnabled() const bool Settings::isKillSwitchEnabled() const
{ {
return value("Conf/killSwitchEnabled", true).toBool(); return m_settings.value("Conf/killSwitchEnabled", true).toBool();
} }
void Settings::setKillSwitchEnabled(bool enabled) void Settings::setKillSwitchEnabled(bool enabled)
{ {
setValue("Conf/killSwitchEnabled", enabled); m_settings.setValue("Conf/killSwitchEnabled", enabled);
} }
bool Settings::isStrictKillSwitchEnabled() const bool Settings::isStrictKillSwitchEnabled() const
{ {
return value("Conf/strictKillSwitchEnabled", false).toBool(); return m_settings.value("Conf/strictKillSwitchEnabled", false).toBool();
} }
void Settings::setStrictKillSwitchEnabled(bool enabled) void Settings::setStrictKillSwitchEnabled(bool enabled)
{ {
setValue("Conf/strictKillSwitchEnabled", enabled); m_settings.setValue("Conf/strictKillSwitchEnabled", enabled);
} }
QString Settings::getInstallationUuid(const bool needCreate) QString Settings::getInstallationUuid(const bool needCreate)
{ {
auto uuid = value("Conf/installationUuid", "").toString(); auto uuid = m_settings.value("Conf/installationUuid", "").toString();
if (needCreate && uuid.isEmpty()) { if (needCreate && uuid.isEmpty()) {
uuid = QUuid::createUuid().toString(); uuid = QUuid::createUuid().toString();
@@ -476,7 +475,7 @@ QString Settings::getInstallationUuid(const bool needCreate)
void Settings::setInstallationUuid(const QString &uuid) void Settings::setInstallationUuid(const QString &uuid)
{ {
setValue("Conf/installationUuid", uuid); m_settings.setValue("Conf/installationUuid", uuid);
} }
ServerCredentials Settings::defaultServerCredentials() const ServerCredentials Settings::defaultServerCredentials() const
@@ -497,28 +496,6 @@ ServerCredentials Settings::serverCredentials(int index) const
return credentials; return credentials;
} }
QVariant Settings::value(const QString &key, const QVariant &defaultValue) const
{
QVariant returnValue;
if (QThread::currentThread() == QCoreApplication::instance()->thread()) {
returnValue = m_settings.value(key, defaultValue);
} else {
QMetaObject::invokeMethod(&m_settings, "value", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVariant, returnValue),
Q_ARG(const QString &, key), Q_ARG(const QVariant &, defaultValue));
}
return returnValue;
}
void Settings::setValue(const QString &key, const QVariant &value)
{
if (QThread::currentThread() == QCoreApplication::instance()->thread()) {
m_settings.setValue(key, value);
} else {
QMetaObject::invokeMethod(&m_settings, "setValue", Qt::BlockingQueuedConnection, Q_ARG(const QString &, key),
Q_ARG(const QVariant &, value));
}
}
void Settings::resetGatewayEndpoint() void Settings::resetGatewayEndpoint()
{ {
m_gatewayEndpoint = gatewayEndpoint; m_gatewayEndpoint = gatewayEndpoint;
@@ -541,50 +518,50 @@ QString Settings::getGatewayEndpoint(bool isTestPurchase)
bool Settings::isDevGatewayEnv(bool isTestPurchase) bool Settings::isDevGatewayEnv(bool isTestPurchase)
{ {
return isTestPurchase ? true : value("Conf/devGatewayEnv", false).toBool(); return isTestPurchase ? true : m_settings.value("Conf/devGatewayEnv", false).toBool();
} }
void Settings::toggleDevGatewayEnv(bool enabled) void Settings::toggleDevGatewayEnv(bool enabled)
{ {
setValue("Conf/devGatewayEnv", enabled); m_settings.setValue("Conf/devGatewayEnv", enabled);
} }
bool Settings::isHomeAdLabelVisible() bool Settings::isHomeAdLabelVisible()
{ {
return value("Conf/homeAdLabelVisible", true).toBool(); return m_settings.value("Conf/homeAdLabelVisible", true).toBool();
} }
void Settings::disableHomeAdLabel() void Settings::disableHomeAdLabel()
{ {
setValue("Conf/homeAdLabelVisible", false); m_settings.setValue("Conf/homeAdLabelVisible", false);
} }
bool Settings::isPremV1MigrationReminderActive() bool Settings::isPremV1MigrationReminderActive()
{ {
return value("Conf/premV1MigrationReminderActive", true).toBool(); return m_settings.value("Conf/premV1MigrationReminderActive", true).toBool();
} }
void Settings::disablePremV1MigrationReminder() void Settings::disablePremV1MigrationReminder()
{ {
setValue("Conf/premV1MigrationReminderActive", false); m_settings.setValue("Conf/premV1MigrationReminderActive", false);
} }
QStringList Settings::allowedDnsServers() const QStringList Settings::allowedDnsServers() const
{ {
return value("Conf/allowedDnsServers").toStringList(); return m_settings.value("Conf/allowedDnsServers").toStringList();
} }
void Settings::setAllowedDnsServers(const QStringList &servers) void Settings::setAllowedDnsServers(const QStringList &servers)
{ {
setValue("Conf/allowedDnsServers", servers); m_settings.setValue("Conf/allowedDnsServers", servers);
} }
QStringList Settings::readNewsIds() const QStringList Settings::readNewsIds() const
{ {
return value("News/readIds").toStringList(); return m_settings.value("News/readIds").toStringList();
} }
void Settings::setReadNewsIds(const QStringList &ids) void Settings::setReadNewsIds(const QStringList &ids)
{ {
setValue("News/readIds", ids); m_settings.setValue("News/readIds", ids);
} }
+21 -25
View File
@@ -29,11 +29,11 @@ public:
QJsonArray serversArray() const QJsonArray serversArray() const
{ {
return QJsonDocument::fromJson(value("Servers/serversList").toByteArray()).array(); return QJsonDocument::fromJson(m_settings.value("Servers/serversList").toByteArray()).array();
} }
void setServersArray(const QJsonArray &servers) void setServersArray(const QJsonArray &servers)
{ {
setValue("Servers/serversList", QJsonDocument(servers).toJson()); m_settings.setValue("Servers/serversList", QJsonDocument(servers).toJson());
} }
// Servers section // Servers section
@@ -45,11 +45,11 @@ public:
int defaultServerIndex() const int defaultServerIndex() const
{ {
return value("Servers/defaultServerIndex", 0).toInt(); return m_settings.value("Servers/defaultServerIndex", 0).toInt();
} }
void setDefaultServer(int index) void setDefaultServer(int index)
{ {
setValue("Servers/defaultServerIndex", index); m_settings.setValue("Servers/defaultServerIndex", index);
} }
QJsonObject defaultServer() const QJsonObject defaultServer() const
{ {
@@ -78,34 +78,34 @@ public:
// App settings section // App settings section
bool isAutoConnect() const bool isAutoConnect() const
{ {
return value("Conf/autoConnect", false).toBool(); return m_settings.value("Conf/autoConnect", false).toBool();
} }
void setAutoConnect(bool enabled) void setAutoConnect(bool enabled)
{ {
setValue("Conf/autoConnect", enabled); m_settings.setValue("Conf/autoConnect", enabled);
} }
bool isStartMinimized() const bool isStartMinimized() const
{ {
return value("Conf/startMinimized", false).toBool(); return m_settings.value("Conf/startMinimized", false).toBool();
} }
void setStartMinimized(bool enabled) void setStartMinimized(bool enabled)
{ {
setValue("Conf/startMinimized", enabled); m_settings.setValue("Conf/startMinimized", enabled);
} }
bool isNewsNotifications() const bool isNewsNotifications() const
{ {
return value("Conf/newsNotifications", true).toBool(); return m_settings.value("Conf/newsNotifications", true).toBool();
} }
void setNewsNotifications(bool enabled) void setNewsNotifications(bool enabled)
{ {
setValue("Conf/newsNotifications", enabled); m_settings.setValue("Conf/newsNotifications", enabled);
} }
bool isSaveLogs() const bool isSaveLogs() const
{ {
return value("Conf/saveLogs", false).toBool(); return m_settings.value("Conf/saveLogs", false).toBool();
} }
void setSaveLogs(bool enabled); void setSaveLogs(bool enabled);
@@ -122,19 +122,18 @@ public:
QString routeModeString(RouteMode mode) const; QString routeModeString(RouteMode mode) const;
RouteMode routeMode() const; RouteMode routeMode() const;
void setRouteMode(RouteMode mode) { setValue("Conf/routeMode", mode); } void setRouteMode(RouteMode mode) { m_settings.setValue("Conf/routeMode", mode); }
bool isSitesSplitTunnelingEnabled() const; bool isSitesSplitTunnelingEnabled() const;
void setSitesSplitTunnelingEnabled(bool enabled); void setSitesSplitTunnelingEnabled(bool enabled);
QVariantMap vpnSites(RouteMode mode) const QVariantMap vpnSites(RouteMode mode) const
{ {
return value("Conf/" + routeModeString(mode)).toMap(); return m_settings.value("Conf/" + routeModeString(mode)).toMap();
} }
void setVpnSites(RouteMode mode, const QVariantMap &sites) void setVpnSites(RouteMode mode, const QVariantMap &sites)
{ {
setValue("Conf/" + routeModeString(mode), sites); m_settings.setValue("Conf/" + routeModeString(mode), sites);
m_settings.sync();
} }
bool addVpnSite(RouteMode mode, const QString &site, const QString &ip = ""); bool addVpnSite(RouteMode mode, const QString &site, const QString &ip = "");
void addVpnSites(RouteMode mode, const QMap<QString, QString> &sites); // map <site, ip> void addVpnSites(RouteMode mode, const QMap<QString, QString> &sites); // map <site, ip>
@@ -147,11 +146,11 @@ public:
bool useAmneziaDns() const bool useAmneziaDns() const
{ {
return value("Conf/useAmneziaDns", true).toBool(); return m_settings.value("Conf/useAmneziaDns", true).toBool();
} }
void setUseAmneziaDns(bool enabled) void setUseAmneziaDns(bool enabled)
{ {
setValue("Conf/useAmneziaDns", enabled); m_settings.setValue("Conf/useAmneziaDns", enabled);
} }
QString primaryDns() const; QString primaryDns() const;
@@ -160,13 +159,13 @@ public:
// QString primaryDns() const { return m_primaryDns; } // QString primaryDns() const { return m_primaryDns; }
void setPrimaryDns(const QString &primaryDns) void setPrimaryDns(const QString &primaryDns)
{ {
setValue("Conf/primaryDns", primaryDns); m_settings.setValue("Conf/primaryDns", primaryDns);
} }
// QString secondaryDns() const { return m_secondaryDns; } // QString secondaryDns() const { return m_secondaryDns; }
void setSecondaryDns(const QString &secondaryDns) void setSecondaryDns(const QString &secondaryDns)
{ {
setValue("Conf/secondaryDns", secondaryDns); m_settings.setValue("Conf/secondaryDns", secondaryDns);
} }
// static constexpr char openNicNs5[] = "94.103.153.176"; // static constexpr char openNicNs5[] = "94.103.153.176";
@@ -188,16 +187,16 @@ public:
}; };
void setAppLanguage(QLocale locale) void setAppLanguage(QLocale locale)
{ {
setValue("Conf/appLanguage", locale.name()); m_settings.setValue("Conf/appLanguage", locale.name());
}; };
bool isScreenshotsEnabled() const bool isScreenshotsEnabled() const
{ {
return value("Conf/screenshotsEnabled", true).toBool(); return m_settings.value("Conf/screenshotsEnabled", true).toBool();
} }
void setScreenshotsEnabled(bool enabled) void setScreenshotsEnabled(bool enabled)
{ {
setValue("Conf/screenshotsEnabled", enabled); m_settings.setValue("Conf/screenshotsEnabled", enabled);
emit screenshotsEnabledChanged(enabled); emit screenshotsEnabledChanged(enabled);
} }
@@ -255,9 +254,6 @@ signals:
void settingsCleared(); void settingsCleared();
private: private:
QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const;
void setValue(const QString &key, const QVariant &value);
void setInstallationUuid(const QString &uuid); void setInstallationUuid(const QString &uuid);
mutable SecureQSettings m_settings; mutable SecureQSettings m_settings;
File diff suppressed because it is too large Load Diff
@@ -366,6 +366,8 @@ bool ApiConfigsController::fillAvailableServices()
{ {
QJsonObject apiPayload; QJsonObject apiPayload;
apiPayload[configKey::osVersion] = QSysInfo::productType(); apiPayload[configKey::osVersion] = QSysInfo::productType();
apiPayload[configKey::appVersion] = QString(APP_VERSION);
apiPayload[apiDefs::key::cliName] = QString(APPLICATION_NAME);
apiPayload[apiDefs::key::appLanguage] = m_settings->getAppLanguage().name().split("_").first(); apiPayload[apiDefs::key::appLanguage] = m_settings->getAppLanguage().name().split("_").first();
QByteArray responseBody; QByteArray responseBody;
@@ -447,7 +449,7 @@ bool ApiConfigsController::importService()
importSerivceFromAppStore(); importSerivceFromAppStore();
return true; return true;
} }
} else { } else if (m_apiServicesModel->getSelectedServiceType() == serviceType::amneziaFree) {
importServiceFromGateway(); importServiceFromGateway();
return true; return true;
} }
@@ -721,6 +723,7 @@ bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const
} }
bool isTestPurchase = apiConfig.value(apiDefs::key::isTestPurchase).toBool(false); bool isTestPurchase = apiConfig.value(apiDefs::key::isTestPurchase).toBool(false);
bool wasSubscriptionExpired = m_serversModel->data(serverIndex, ServersModel::IsSubscriptionExpiredRole).toBool();
QByteArray responseBody; QByteArray responseBody;
ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody, isTestPurchase); ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody, isTestPurchase);
@@ -747,6 +750,11 @@ bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const
newServerConfig.insert(config_key::nameOverriddenByUser, true); newServerConfig.insert(config_key::nameOverriddenByUser, true);
} }
m_serversModel->editServer(newServerConfig, serverIndex); m_serversModel->editServer(newServerConfig, serverIndex);
if (wasSubscriptionExpired) {
emit subscriptionRefreshNeeded();
}
if (reloadServiceConfig) { if (reloadServiceConfig) {
emit reloadServerFromApiFinished(tr("API config reloaded")); emit reloadServerFromApiFinished(tr("API config reloaded"));
} else if (newCountryName.isEmpty()) { } else if (newCountryName.isEmpty()) {
@@ -756,7 +764,11 @@ bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const
} }
return true; return true;
} else { } else {
emit errorOccurred(errorCode); if (errorCode == ErrorCode::ApiSubscriptionExpiredError) {
emit subscriptionExpiredOnServer();
} else {
emit errorOccurred(errorCode);
}
return false; return false;
} }
} }
@@ -43,6 +43,8 @@ public slots:
signals: signals:
void errorOccurred(ErrorCode errorCode); void errorOccurred(ErrorCode errorCode);
void subscriptionExpiredOnServer();
void subscriptionRefreshNeeded();
void installServerFromApiFinished(const QString &message); void installServerFromApiFinished(const QString &message);
void changeApiCountryFinished(const QString &message); void changeApiCountryFinished(const QString &message);
@@ -1,6 +1,7 @@
#include "apiSettingsController.h" #include "apiSettingsController.h"
#include <QEventLoop> #include <QEventLoop>
#include <QJsonDocument>
#include <QTimer> #include <QTimer>
#include "core/api/apiUtils.h" #include "core/api/apiUtils.h"
@@ -77,6 +78,13 @@ bool ApiSettingsController::getAccountInfo(bool reload)
QJsonObject accountInfo = QJsonDocument::fromJson(responseBody).object(); QJsonObject accountInfo = QJsonDocument::fromJson(responseBody).object();
m_apiAccountInfoModel->updateModel(accountInfo, serverConfig); m_apiAccountInfoModel->updateModel(accountInfo, serverConfig);
QString subscriptionEndDate = accountInfo.value(apiDefs::key::subscriptionEndDate).toString();
if (!subscriptionEndDate.isEmpty()) {
apiConfig.insert(apiDefs::key::subscriptionEndDate, subscriptionEndDate);
serverConfig.insert(configKey::apiConfig, apiConfig);
m_serversModel->editServer(serverConfig, processedIndex);
}
if (reload) { if (reload) {
updateApiCountryModel(); updateApiCountryModel();
updateApiDevicesModel(); updateApiDevicesModel();
@@ -85,6 +93,42 @@ bool ApiSettingsController::getAccountInfo(bool reload)
return true; return true;
} }
void ApiSettingsController::getRenewalLink()
{
auto processedIndex = m_serversModel->getProcessedServerIndex();
auto serverConfig = m_serversModel->getServerConfig(processedIndex);
auto apiConfig = serverConfig.value(configKey::apiConfig).toObject();
auto authData = serverConfig.value(configKey::authData).toObject();
bool isTestPurchase = apiConfig.value(apiDefs::key::isTestPurchase).toBool(false);
auto gatewayController = QSharedPointer<GatewayController>::create(m_settings->getGatewayEndpoint(isTestPurchase),
m_settings->isDevGatewayEnv(isTestPurchase),
requestTimeoutMsecs,
m_settings->isStrictKillSwitchEnabled());
QJsonObject apiPayload;
apiPayload[configKey::userCountryCode] = apiConfig.value(configKey::userCountryCode).toString();
apiPayload[configKey::serviceType] = apiConfig.value(configKey::serviceType).toString();
apiPayload[configKey::authData] = authData;
apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION);
apiPayload[apiDefs::key::appLanguage] = m_settings->getAppLanguage().name().split("_").first();
auto future = gatewayController->postAsync(QString("%1v1/renewal_link"), apiPayload);
future.then(this, [this, gatewayController](QPair<ErrorCode, QByteArray> result) {
auto [errorCode, responseBody] = result;
if (errorCode != ErrorCode::NoError) {
emit errorOccurred(errorCode);
return;
}
QJsonObject responseJson = QJsonDocument::fromJson(responseBody).object();
QString url = responseJson.value("renewal_url").toString();
if (!url.isEmpty()) {
emit renewalLinkReceived(url);
}
});
}
void ApiSettingsController::updateApiCountryModel() void ApiSettingsController::updateApiCountryModel()
{ {
m_apiCountryModel->updateModel(m_apiAccountInfoModel->getAvailableCountries(), ""); m_apiCountryModel->updateModel(m_apiAccountInfoModel->getAvailableCountries(), "");
@@ -21,9 +21,11 @@ public slots:
bool getAccountInfo(bool reload); bool getAccountInfo(bool reload);
void updateApiCountryModel(); void updateApiCountryModel();
void updateApiDevicesModel(); void updateApiDevicesModel();
void getRenewalLink();
signals: signals:
void errorOccurred(ErrorCode errorCode); void errorOccurred(ErrorCode errorCode);
void renewalLinkReceived(const QString &url);
private: private:
QSharedPointer<ServersModel> m_serversModel; QSharedPointer<ServersModel> m_serversModel;
+2 -5
View File
@@ -7,7 +7,6 @@
#include <QFileInfo> #include <QFileInfo>
#include <QImage> #include <QImage>
#include <QStandardPaths> #include <QStandardPaths>
#include "core/controllers/vpnConfigurationController.h" #include "core/controllers/vpnConfigurationController.h"
#include "core/qrCodeUtils.h" #include "core/qrCodeUtils.h"
#include "core/serialization/serialization.h" #include "core/serialization/serialization.h"
@@ -170,8 +169,7 @@ void ExportController::generateWireGuardConfig(const QString &clientName)
m_config.append(line + "\n"); m_config.append(line + "\n");
} }
auto qr = qrCodeUtils::generateQrCode(m_config.toUtf8()); m_qrCodes = qrCodeUtils::generateQrCodeImageSeries(m_config.toUtf8());
m_qrCodes << qrCodeUtils::svgToBase64(QString::fromStdString(toSvgString(qr, 1)));
emit exportConfigChanged(); emit exportConfigChanged();
} }
@@ -191,8 +189,7 @@ void ExportController::generateAwgConfig(const QString &clientName)
m_config.append(line + "\n"); m_config.append(line + "\n");
} }
auto qr = qrCodeUtils::generateQrCode(m_config.toUtf8()); m_qrCodes = qrCodeUtils::generateQrCodeImageSeries(m_config.toUtf8());
m_qrCodes << qrCodeUtils::svgToBase64(QString::fromStdString(toSvgString(qr, 1)));
emit exportConfigChanged(); emit exportConfigChanged();
} }
+68 -53
View File
@@ -83,8 +83,8 @@ void InstallController::install(DockerContainer container, int port, TransportPr
int s1 = QRandomGenerator::global()->bounded(15, 150); int s1 = QRandomGenerator::global()->bounded(15, 150);
int s2 = QRandomGenerator::global()->bounded(15, 150); int s2 = QRandomGenerator::global()->bounded(15, 150);
int s3 = QRandomGenerator::global()->bounded(0, 64); int s3 = QRandomGenerator::global()->bounded(1, 64);
int s4 = QRandomGenerator::global()->bounded(0, 20); int s4 = QRandomGenerator::global()->bounded(1, 20);
// Ensure all values are unique and don't create equal packet sizes // Ensure all values are unique and don't create equal packet sizes
QSet<int> usedValues; QSet<int> usedValues;
@@ -97,12 +97,12 @@ void InstallController::install(DockerContainer container, int port, TransportPr
while (usedValues.contains(s3) || s1 + AwgConstant::messageInitiationSize == s3 + AwgConstant::messageCookieReplySize while (usedValues.contains(s3) || s1 + AwgConstant::messageInitiationSize == s3 + AwgConstant::messageCookieReplySize
|| s2 + AwgConstant::messageResponseSize == s3 + AwgConstant::messageCookieReplySize) { || s2 + AwgConstant::messageResponseSize == s3 + AwgConstant::messageCookieReplySize) {
s3 = QRandomGenerator::global()->bounded(0, 64); s3 = QRandomGenerator::global()->bounded(1, 64);
} }
usedValues.insert(s3); usedValues.insert(s3);
while (usedValues.contains(s4)) { while (usedValues.contains(s4)) {
s4 = QRandomGenerator::global()->bounded(0, 20); s4 = QRandomGenerator::global()->bounded(1, 20);
} }
QString initPacketJunkSize = QString::number(s1); QString initPacketJunkSize = QString::number(s1);
@@ -987,79 +987,94 @@ void InstallController::addEmptyServer()
emit installServerFinished(tr("Server added successfully")); emit installServerFinished(tr("Server added successfully"));
} }
bool InstallController::isConfigValid() void InstallController::validateConfig()
{ {
int serverIndex = m_serversModel->getDefaultServerIndex(); int serverIndex = m_serversModel->getDefaultServerIndex();
QJsonObject serverConfigObject = m_serversModel->getServerConfig(serverIndex); QJsonObject serverConfigObject = m_serversModel->getServerConfig(serverIndex);
if (apiUtils::isServerFromApi(serverConfigObject)) { if (apiUtils::isServerFromApi(serverConfigObject)) {
return true; emit configValidated(true);
return;
} }
if (!m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) { if (!m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) {
emit noInstalledContainers(); emit noInstalledContainers();
return false; emit configValidated(false);
return;
} }
DockerContainer container = qvariant_cast<DockerContainer>(m_serversModel->data(serverIndex, ServersModel::Roles::DefaultContainerRole)); DockerContainer container = qvariant_cast<DockerContainer>(m_serversModel->data(serverIndex, ServersModel::Roles::DefaultContainerRole));
if (container == DockerContainer::None) { if (container == DockerContainer::None) {
emit installationErrorOccurred(ErrorCode::NoInstalledContainersError); emit installationErrorOccurred(ErrorCode::NoInstalledContainersError);
return false; emit configValidated(false);
return;
} }
QSharedPointer<ServerController> serverController(new ServerController(m_settings));
VpnConfigurationsController vpnConfigurationController(m_settings, serverController);
QJsonObject containerConfig = m_containersModel->getContainerConfig(container); QJsonObject containerConfig = m_containersModel->getContainerConfig(container);
ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex); ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex);
QSharedPointer<ServerController> serverController(new ServerController(m_settings));
QFutureWatcher<ErrorCode> watcher; auto isProtocolConfigExists = [](const QJsonObject &containerConfig, const DockerContainer container) {
for (Proto protocol : ContainerProps::protocolsForContainer(container)) {
QString protocolConfig =
containerConfig.value(ProtocolProps::protoToString(protocol)).toObject().value(config_key::last_config).toString();
QFuture<ErrorCode> future = QtConcurrent::run([this, container, &credentials, &containerConfig, &serverController]() { if (protocolConfig.isEmpty()) {
ErrorCode errorCode = ErrorCode::NoError; return false;
auto isProtocolConfigExists = [](const QJsonObject &containerConfig, const DockerContainer container) {
for (Proto protocol : ContainerProps::protocolsForContainer(container)) {
QString protocolConfig =
containerConfig.value(ProtocolProps::protoToString(protocol)).toObject().value(config_key::last_config).toString();
if (protocolConfig.isEmpty()) {
return false;
}
}
return true;
};
if (!isProtocolConfigExists(containerConfig, container)) {
VpnConfigurationsController vpnConfigurationController(m_settings, serverController);
errorCode = vpnConfigurationController.createProtocolConfigForContainer(credentials, container, containerConfig);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
m_serversModel->updateContainerConfig(container, containerConfig);
errorCode = m_clientManagementModel->appendClient(container, credentials, containerConfig,
QString("Admin [%1]").arg(QSysInfo::prettyProductName()), serverController);
if (errorCode != ErrorCode::NoError) {
return errorCode;
} }
} }
return errorCode; return true;
}); };
QEventLoop wait; if (isProtocolConfigExists(containerConfig, container)) {
connect(&watcher, &QFutureWatcher<ErrorCode>::finished, &wait, &QEventLoop::quit); emit configValidated(true);
watcher.setFuture(future); return;
wait.exec();
ErrorCode errorCode = watcher.result();
if (errorCode != ErrorCode::NoError) {
emit installationErrorOccurred(errorCode);
return false;
} }
return true;
struct ValidationResult {
ErrorCode errorCode = ErrorCode::NoError;
QJsonObject containerConfig;
};
QFuture<ValidationResult> future =
QtConcurrent::run([settings = m_settings, serverController, credentials, containerConfig, container]() mutable {
ValidationResult result;
result.containerConfig = containerConfig;
VpnConfigurationsController vpnConfigurationController(settings, serverController);
result.errorCode = vpnConfigurationController.createProtocolConfigForContainer(credentials, container,
result.containerConfig);
return result;
});
auto *watcher = new QFutureWatcher<ValidationResult>(this);
connect(watcher, &QFutureWatcher<ValidationResult>::finished, this,
[this, watcher, container, credentials, serverController]() {
auto result = watcher->result();
watcher->deleteLater();
if (result.errorCode != ErrorCode::NoError) {
emit installationErrorOccurred(result.errorCode);
emit configValidated(false);
return;
}
m_serversModel->updateContainerConfig(container, result.containerConfig);
ErrorCode appendError = m_clientManagementModel->appendClient(
container, credentials, result.containerConfig,
QString("Admin [%1]").arg(QSysInfo::prettyProductName()), serverController);
if (appendError != ErrorCode::NoError) {
emit installationErrorOccurred(appendError);
emit configValidated(false);
return;
}
emit configValidated(true);
});
watcher->setFuture(future);
} }
bool InstallController::isUpdateDockerContainerRequired(const DockerContainer container, const QJsonObject &oldConfig, bool InstallController::isUpdateDockerContainerRequired(const DockerContainer container, const QJsonObject &oldConfig,
@@ -1070,7 +1085,7 @@ bool InstallController::isUpdateDockerContainerRequired(const DockerContainer co
const QJsonObject &oldProtoConfig = oldConfig.value(ProtocolProps::protoToString(mainProto)).toObject(); const QJsonObject &oldProtoConfig = oldConfig.value(ProtocolProps::protoToString(mainProto)).toObject();
const QJsonObject &newProtoConfig = newConfig.value(ProtocolProps::protoToString(mainProto)).toObject(); const QJsonObject &newProtoConfig = newConfig.value(ProtocolProps::protoToString(mainProto)).toObject();
if (container == DockerContainer::Awg2) { if (ContainerProps::isAwgContainer(container)) {
const AwgConfig oldConfig(oldProtoConfig); const AwgConfig oldConfig(oldProtoConfig);
const AwgConfig newConfig(newProtoConfig); const AwgConfig newConfig(newProtoConfig);
+2 -1
View File
@@ -50,9 +50,10 @@ public slots:
void addEmptyServer(); void addEmptyServer();
bool isConfigValid(); void validateConfig();
signals: signals:
void configValidated(bool isValid);
void installContainerFinished(const QString &finishMessage, bool isServiceInstall); void installContainerFinished(const QString &finishMessage, bool isServiceInstall);
void installServerFinished(const QString &finishMessage); void installServerFinished(const QString &finishMessage);
+7 -6
View File
@@ -45,6 +45,8 @@ SettingsController::SettingsController(const QSharedPointer<ServersModel> &serve
emit safeAreaBottomMarginChanged(); emit safeAreaBottomMarginChanged();
emit safeAreaTopMarginChanged(); emit safeAreaTopMarginChanged();
}); });
connect(AndroidController::instance(), &AndroidController::activityPaused, this, &SettingsController::activityPaused);
connect(AndroidController::instance(), &AndroidController::activityResumed, this, &SettingsController::activityResumed);
#endif #endif
m_isDevModeEnabled = m_settings->isDevGatewayEnv(); m_isDevModeEnabled = m_settings->isDevGatewayEnv();
@@ -178,12 +180,11 @@ void SettingsController::backupAppConfig(const QString &fileName)
void SettingsController::restoreAppConfig(const QString &fileName) void SettingsController::restoreAppConfig(const QString &fileName)
{ {
QFile file(fileName); QByteArray data;
if (!SystemController::readFile(fileName, data)) {
file.open(QIODevice::ReadOnly); emit changeSettingsErrorOccurred(tr("Can't open file: %1").arg(fileName));
return;
QByteArray data = file.readAll(); }
restoreAppConfigFromData(data); restoreAppConfigFromData(data);
} }
@@ -141,6 +141,9 @@ signals:
void safeAreaTopMarginChanged(); void safeAreaTopMarginChanged();
void safeAreaBottomMarginChanged(); void safeAreaBottomMarginChanged();
void activityPaused();
void activityResumed();
void isHomeAdLabelVisibleChanged(bool visible); void isHomeAdLabelVisibleChanged(bool visible);
void startMinimizedChanged(); void startMinimizedChanged();
+6 -25
View File
@@ -7,10 +7,8 @@
#include "systemController.h" #include "systemController.h"
#include "core/networkUtilities.h" #include "core/networkUtilities.h"
SitesController::SitesController(const std::shared_ptr<Settings> &settings, SitesController::SitesController(const std::shared_ptr<Settings> &settings, const QSharedPointer<SitesModel> &sitesModel, QObject *parent)
const QSharedPointer<VpnConnection> &vpnConnection, : QObject(parent), m_settings(settings), m_sitesModel(sitesModel)
const QSharedPointer<SitesModel> &sitesModel, QObject *parent)
: QObject(parent), m_settings(settings), m_vpnConnection(vpnConnection), m_sitesModel(sitesModel)
{ {
} }
@@ -34,32 +32,20 @@ void SitesController::addSite(QString hostname)
hostname = hostname.split("/", Qt::SkipEmptyParts).first(); hostname = hostname.split("/", Qt::SkipEmptyParts).first();
} }
const auto &processSite = [this](const QString &hostname, const QString &ip) { const auto &resolveCallback = [this](const QHostInfo &hostInfo) {
m_sitesModel->addSite(hostname, ip);
if (!ip.isEmpty()) {
QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection,
Q_ARG(QStringList, QStringList() << ip));
} else if (NetworkUtilities::ipAddressWithSubnetRegExp().exactMatch(hostname)) {
QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection,
Q_ARG(QStringList, QStringList() << hostname));
}
};
const auto &resolveCallback = [this, processSite](const QHostInfo &hostInfo) {
const QList<QHostAddress> &addresses = hostInfo.addresses(); const QList<QHostAddress> &addresses = hostInfo.addresses();
for (const QHostAddress &addr : hostInfo.addresses()) { for (const QHostAddress &addr : hostInfo.addresses()) {
if (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) { if (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) {
processSite(hostInfo.hostName(), addr.toString()); m_sitesModel->addSite(hostInfo.hostName(), addr.toString());
break; break;
} }
} }
}; };
if (NetworkUtilities::ipAddressWithSubnetRegExp().exactMatch(hostname)) { if (NetworkUtilities::ipAddressWithSubnetRegExp().exactMatch(hostname)) {
processSite(hostname, ""); m_sitesModel->addSite(hostname, "");
} else { } else {
processSite(hostname, ""); m_sitesModel->addSite(hostname, "");
QHostInfo::lookupHost(hostname, this, resolveCallback); QHostInfo::lookupHost(hostname, this, resolveCallback);
} }
@@ -72,9 +58,6 @@ void SitesController::removeSite(int index)
auto hostname = m_sitesModel->data(modelIndex, SitesModel::Roles::UrlRole).toString(); auto hostname = m_sitesModel->data(modelIndex, SitesModel::Roles::UrlRole).toString();
m_sitesModel->removeSite(modelIndex); m_sitesModel->removeSite(modelIndex);
QMetaObject::invokeMethod(m_vpnConnection.get(), "deleteRoutes", Qt::QueuedConnection,
Q_ARG(QStringList, QStringList() << hostname));
emit finished(tr("Site removed: %1").arg(hostname)); emit finished(tr("Site removed: %1").arg(hostname));
} }
@@ -128,8 +111,6 @@ void SitesController::importSites(const QString &fileName, bool replaceExisting)
m_sitesModel->addSites(sites, replaceExisting); m_sitesModel->addSites(sites, replaceExisting);
QMetaObject::invokeMethod(m_vpnConnection.get(), "addRoutes", Qt::QueuedConnection, Q_ARG(QStringList, ips));
emit finished(tr("Import completed")); emit finished(tr("Import completed"));
} }
+2 -5
View File
@@ -11,9 +11,8 @@ class SitesController : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit SitesController(const std::shared_ptr<Settings> &settings, explicit SitesController(const std::shared_ptr<Settings> &settings, const QSharedPointer<SitesModel> &sitesModel,
const QSharedPointer<VpnConnection> &vpnConnection, QObject *parent = nullptr);
const QSharedPointer<SitesModel> &sitesModel, QObject *parent = nullptr);
public slots: public slots:
void addSite(QString hostname); void addSite(QString hostname);
@@ -31,8 +30,6 @@ signals:
private: private:
std::shared_ptr<Settings> m_settings; std::shared_ptr<Settings> m_settings;
QSharedPointer<VpnConnection> m_vpnConnection;
QSharedPointer<SitesModel> m_sitesModel; QSharedPointer<SitesModel> m_sitesModel;
}; };
+19 -2
View File
@@ -1,5 +1,6 @@
#include "apiAccountInfoModel.h" #include "apiAccountInfoModel.h"
#include <QDateTime>
#include <QJsonObject> #include <QJsonObject>
#include "core/api/apiUtils.h" #include "core/api/apiUtils.h"
@@ -32,7 +33,7 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
} }
return apiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate) ? tr("<p><a style=\"color: #EB5757;\">Inactive</a>") return apiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate) ? tr("<p><a style=\"color: #EB5757;\">Inactive</a>")
: tr("Active"); : tr("<p><a style=\"color: #28c840;\">Active</a>");
} }
case EndDateRole: { case EndDateRole: {
if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaFreeV3) { if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaFreeV3) {
@@ -52,7 +53,9 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
} }
case IsComponentVisibleRole: { case IsComponentVisibleRole: {
return m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2 return m_accountInfoData.configType == apiDefs::ConfigType::AmneziaPremiumV2
|| m_accountInfoData.configType == apiDefs::ConfigType::ExternalPremium; || m_accountInfoData.configType == apiDefs::ConfigType::AmneziaTrialV2
|| m_accountInfoData.configType == apiDefs::ConfigType::ExternalPremium
|| m_accountInfoData.configType == apiDefs::ConfigType::ExternalTrial;
} }
case HasExpiredWorkerRole: { case HasExpiredWorkerRole: {
for (int i = 0; i < m_issuedConfigsInfo.size(); i++) { for (int i = 0; i < m_issuedConfigsInfo.size(); i++) {
@@ -73,6 +76,18 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
} }
return false; return false;
} }
case IsSubscriptionExpiredRole: {
if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaFreeV3) return false;
if (m_accountInfoData.subscriptionEndDate.isEmpty()) return false;
return apiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate);
}
case IsSubscriptionExpiringSoonRole: {
if (m_accountInfoData.configType == apiDefs::ConfigType::AmneziaFreeV3) return false;
if (m_accountInfoData.subscriptionEndDate.isEmpty()) return false;
if (apiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate)) return false;
QDateTime endDate = QDateTime::fromString(m_accountInfoData.subscriptionEndDate, Qt::ISODateWithMs);
return endDate <= QDateTime::currentDateTimeUtc().addDays(10);
}
} }
return QVariant(); return QVariant();
@@ -164,6 +179,8 @@ QHash<int, QByteArray> ApiAccountInfoModel::roleNames() const
roles[IsComponentVisibleRole] = "isComponentVisible"; roles[IsComponentVisibleRole] = "isComponentVisible";
roles[HasExpiredWorkerRole] = "hasExpiredWorker"; roles[HasExpiredWorkerRole] = "hasExpiredWorker";
roles[IsProtocolSelectionSupportedRole] = "isProtocolSelectionSupported"; roles[IsProtocolSelectionSupportedRole] = "isProtocolSelectionSupported";
roles[IsSubscriptionExpiredRole] = "isSubscriptionExpired";
roles[IsSubscriptionExpiringSoonRole] = "isSubscriptionExpiringSoon";
return roles; return roles;
} }
+3 -2
View File
@@ -19,7 +19,9 @@ public:
EndDateRole, EndDateRole,
IsComponentVisibleRole, IsComponentVisibleRole,
HasExpiredWorkerRole, HasExpiredWorkerRole,
IsProtocolSelectionSupportedRole IsProtocolSelectionSupportedRole,
IsSubscriptionExpiredRole,
IsSubscriptionExpiringSoonRole
}; };
explicit ApiAccountInfoModel(QObject *parent = nullptr); explicit ApiAccountInfoModel(QObject *parent = nullptr);
@@ -31,7 +33,6 @@ public:
public slots: public slots:
void updateModel(const QJsonObject &accountInfoObject, const QJsonObject &serverConfig); void updateModel(const QJsonObject &accountInfoObject, const QJsonObject &serverConfig);
QVariant data(const QString &roleString); QVariant data(const QString &roleString);
QJsonArray getAvailableCountries(); QJsonArray getAvailableCountries();
QJsonArray getIssuedConfigsInfo(); QJsonArray getIssuedConfigsInfo();
+5 -2
View File
@@ -41,6 +41,7 @@ namespace
{ {
constexpr char amneziaFree[] = "amnezia-free"; constexpr char amneziaFree[] = "amnezia-free";
constexpr char amneziaPremium[] = "amnezia-premium"; constexpr char amneziaPremium[] = "amnezia-premium";
constexpr char amneziaTrial[] = "amnezia-trial";
} }
} }
@@ -69,7 +70,7 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
} }
case CardDescriptionRole: { case CardDescriptionRole: {
auto speed = apiServiceData.serviceInfo.speed; auto speed = apiServiceData.serviceInfo.speed;
if (serviceType == serviceType::amneziaPremium) { if (serviceType == serviceType::amneziaPremium || serviceType == serviceType::amneziaTrial) {
return apiServiceData.serviceInfo.cardDescription.arg(speed); return apiServiceData.serviceInfo.cardDescription.arg(speed);
} else if (serviceType == serviceType::amneziaFree) { } else if (serviceType == serviceType::amneziaFree) {
QString description = apiServiceData.serviceInfo.cardDescription; QString description = apiServiceData.serviceInfo.cardDescription;
@@ -124,8 +125,10 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
case OrderRole: { case OrderRole: {
if (serviceType == serviceType::amneziaPremium) { if (serviceType == serviceType::amneziaPremium) {
return 0; return 0;
} else if (serviceType == serviceType::amneziaFree) { } else if (serviceType == serviceType::amneziaTrial) {
return 1; return 1;
} else if (serviceType == serviceType::amneziaFree) {
return 2;
} }
} }
} }
@@ -141,10 +141,12 @@ void AwgConfigModel::updateModel(const QJsonObject &config)
serverProtocolConfig.value(config_key::initPacketJunkSize).toString(protocols::awg::defaultInitPacketJunkSize); serverProtocolConfig.value(config_key::initPacketJunkSize).toString(protocols::awg::defaultInitPacketJunkSize);
m_serverProtocolConfig[config_key::responsePacketJunkSize] = m_serverProtocolConfig[config_key::responsePacketJunkSize] =
serverProtocolConfig.value(config_key::responsePacketJunkSize).toString(protocols::awg::defaultResponsePacketJunkSize); serverProtocolConfig.value(config_key::responsePacketJunkSize).toString(protocols::awg::defaultResponsePacketJunkSize);
m_serverProtocolConfig[config_key::cookieReplyPacketJunkSize] = if (protocolVersion == protocols::awg::awgV2) {
serverProtocolConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize); m_serverProtocolConfig[config_key::cookieReplyPacketJunkSize] =
m_serverProtocolConfig[config_key::transportPacketJunkSize] = serverProtocolConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize);
serverProtocolConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize); m_serverProtocolConfig[config_key::transportPacketJunkSize] =
serverProtocolConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize);
}
m_serverProtocolConfig[config_key::initPacketMagicHeader] = m_serverProtocolConfig[config_key::initPacketMagicHeader] =
serverProtocolConfig.value(config_key::initPacketMagicHeader).toString(protocols::awg::defaultInitPacketMagicHeader); serverProtocolConfig.value(config_key::initPacketMagicHeader).toString(protocols::awg::defaultInitPacketMagicHeader);
m_serverProtocolConfig[config_key::responsePacketMagicHeader] = m_serverProtocolConfig[config_key::responsePacketMagicHeader] =
+17
View File
@@ -179,6 +179,20 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const
case AdEndpointRole: { case AdEndpointRole: {
return apiConfig.value(apiDefs::key::serviceInfo).toObject().value(apiDefs::key::adEndpoint).toString(); return apiConfig.value(apiDefs::key::serviceInfo).toObject().value(apiDefs::key::adEndpoint).toString();
} }
case IsSubscriptionExpiredRole: {
if (configVersion != apiDefs::ConfigSource::AmneziaGateway) return false;
QString endDate = apiConfig.value(apiDefs::key::subscriptionEndDate).toString();
if (endDate.isEmpty()) return false;
return apiUtils::isSubscriptionExpired(endDate);
}
case IsSubscriptionExpiringSoonRole: {
if (configVersion != apiDefs::ConfigSource::AmneziaGateway) return false;
QString endDate = apiConfig.value(apiDefs::key::subscriptionEndDate).toString();
if (endDate.isEmpty()) return false;
if (apiUtils::isSubscriptionExpired(endDate)) return false;
QDateTime endDateTime = QDateTime::fromString(endDate, Qt::ISODateWithMs);
return endDateTime <= QDateTime::currentDateTimeUtc().addDays(10);
}
} }
return QVariant(); return QVariant();
@@ -443,6 +457,9 @@ QHash<int, QByteArray> ServersModel::roleNames() const
roles[AdDescriptionRole] = "adDescription"; roles[AdDescriptionRole] = "adDescription";
roles[AdEndpointRole] = "adEndpoint"; roles[AdEndpointRole] = "adEndpoint";
roles[IsSubscriptionExpiredRole] = "isSubscriptionExpired";
roles[IsSubscriptionExpiringSoonRole] = "isSubscriptionExpiringSoon";
return roles; return roles;
} }
+3
View File
@@ -52,6 +52,9 @@ public:
AdDescriptionRole, AdDescriptionRole,
AdEndpointRole, AdEndpointRole,
IsSubscriptionExpiredRole,
IsSubscriptionExpiringSoonRole,
HasAmneziaDns HasAmneziaDns
}; };
@@ -126,6 +126,18 @@ ListViewType {
} }
} }
CaptionTextType {
visible: isServerFromGatewayApi && (isSubscriptionExpired || isSubscriptionExpiringSoon)
Layout.fillWidth: true
Layout.leftMargin: 64
Layout.bottomMargin: 8
text: isSubscriptionExpired ? qsTr("Subscription expired. Please renew.") : qsTr("Subscription expiring soon.")
color: isSubscriptionExpired ? AmneziaStyle.color.vibrantRed : AmneziaStyle.color.goldenApricot
wrapMode: Text.WordWrap
}
DividerType { DividerType {
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: 0 Layout.leftMargin: 0
@@ -0,0 +1,93 @@
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import PageEnum 1.0
import Style 1.0
import "../Controls2"
import "../Controls2/TextTypes"
DrawerType2 {
id: root
expandedStateContent: ColumnLayout {
id: content
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
spacing: 0
onImplicitHeightChanged: {
root.expandedHeight = content.implicitHeight + 32 + SettingsController.safeAreaBottomMargin
}
Item {
Layout.fillWidth: true
Layout.topMargin: 24
Layout.rightMargin: 16
Layout.leftMargin: 16
implicitHeight: titleText.implicitHeight
Header2TextType {
id: titleText
anchors.left: parent.left
anchors.right: parent.right
text: qsTr("Amnezia Premium subscription has expired")
horizontalAlignment: Text.AlignLeft
}
}
ParagraphTextType {
Layout.fillWidth: true
Layout.topMargin: 8
Layout.rightMargin: 16
Layout.leftMargin: 16
text: qsTr("Renew your subscription to continue using VPN")
horizontalAlignment: Text.AlignLeft
}
BasicButtonType {
Layout.fillWidth: true
Layout.topMargin: 16
Layout.rightMargin: 16
Layout.leftMargin: 16
text: qsTr("Renew")
defaultColor: AmneziaStyle.color.paleGray
hoveredColor: AmneziaStyle.color.lightGray
pressedColor: AmneziaStyle.color.mutedGray
textColor: AmneziaStyle.color.midnightBlack
clickedFunc: function() {
ApiSettingsController.getRenewalLink()
}
}
BasicButtonType {
Layout.alignment: Qt.AlignHCenter
Layout.topMargin: 8
Layout.bottomMargin: 8
implicitHeight: 25
defaultColor: AmneziaStyle.color.transparent
hoveredColor: AmneziaStyle.color.translucentWhite
pressedColor: AmneziaStyle.color.sheerWhite
textColor: AmneziaStyle.color.goldenApricot
text: qsTr("Support")
clickedFunc: function() {
root.closeTriggered()
PageController.goToPage(PageEnum.PageSettingsApiSupport)
}
}
}
}
@@ -1,6 +1,7 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import Qt5Compat.GraphicalEffects
import Style 1.0 import Style 1.0
@@ -37,6 +38,7 @@ Item {
property int borderFocusedWidth: 1 property int borderFocusedWidth: 1
property string rightImageColor: AmneziaStyle.color.paleGray property string rightImageColor: AmneziaStyle.color.paleGray
property string leftImageColor: ""
property bool descriptionOnTop: false property bool descriptionOnTop: false
property bool hideDescription: true property bool hideDescription: true
@@ -140,6 +142,14 @@ Item {
anchors.centerIn: parent anchors.centerIn: parent
source: leftImageSource source: leftImageSource
visible: leftImageColor === ""
}
ColorOverlay {
anchors.fill: leftImage
source: leftImage
color: leftImageColor
visible: leftImageColor !== ""
} }
} }
+3 -2
View File
@@ -8,9 +8,10 @@ Item {
id: root id: root
property StackView stackView: StackView.view property StackView stackView: StackView.view
property bool enableTimer: true
onVisibleChanged: { onVisibleChanged: {
if (visible) { if (visible && enableTimer) {
timer.start() timer.start()
} }
} }
@@ -24,6 +25,6 @@ Item {
FocusController.setFocusOnDefaultItem() FocusController.setFocusOnDefaultItem()
} }
repeat: false // Stop the timer after one trigger repeat: false // Stop the timer after one trigger
running: true // Start the timer running: enableTimer // Start the timer
} }
} }
@@ -330,6 +330,8 @@ PageType {
AwgTextField { AwgTextField {
id: cookieReplyPacketJunkSizeTextField id: cookieReplyPacketJunkSizeTextField
visible: isAwg2
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.rightMargin: 16 Layout.rightMargin: 16
@@ -342,6 +344,8 @@ PageType {
AwgTextField { AwgTextField {
id: transportPacketJunkSizeTextField id: transportPacketJunkSizeTextField
visible: isAwg2
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.rightMargin: 16 Layout.rightMargin: 16
@@ -18,12 +18,23 @@ PageType {
id: root id: root
property var processedServer property var processedServer
property bool subscriptionExpired: false
property bool subscriptionExpiringSoon: false
function updateSubscriptionState() {
root.subscriptionExpired = ServersModel.getProcessedServerData("isSubscriptionExpired")
root.subscriptionExpiringSoon = ServersModel.getProcessedServerData("isSubscriptionExpiringSoon")
}
Component.onCompleted: {
root.updateSubscriptionState()
}
Connections { Connections {
target: ServersModel target: ServersModel
function onProcessedServerChanged() { function onProcessedServerChanged() {
root.processedServer = proxyServersModel.get(0) root.processedServer = proxyServersModel.get(0)
root.updateSubscriptionState()
} }
} }
@@ -76,12 +87,11 @@ PageType {
Layout.fillWidth: true Layout.fillWidth: true
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.rightMargin: 16 Layout.rightMargin: 16
Layout.bottomMargin: 10 Layout.bottomMargin: 4
actionButtonImage: "qrc:/images/controls/settings.svg" actionButtonImage: "qrc:/images/controls/settings.svg"
headerText: root.processedServer.name headerText: root.processedServer.name
descriptionText: qsTr("Location for connection")
actionButtonFunction: function() { actionButtonFunction: function() {
PageController.showBusyIndicator(true) PageController.showBusyIndicator(true)
@@ -94,6 +104,50 @@ PageType {
PageController.goToPage(PageEnum.PageSettingsApiServerInfo) PageController.goToPage(PageEnum.PageSettingsApiServerInfo)
} }
} }
CaptionTextType {
visible: root.subscriptionExpired || root.subscriptionExpiringSoon
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 4
text: root.subscriptionExpired ? qsTr("Subscription expired") : qsTr("Subscription expiring soon")
color: root.subscriptionExpired ? AmneziaStyle.color.vibrantRed : AmneziaStyle.color.goldenApricot
}
BasicButtonType {
visible: root.subscriptionExpired || root.subscriptionExpiringSoon
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 8
Layout.bottomMargin: 4
defaultColor: AmneziaStyle.color.paleGray
hoveredColor: AmneziaStyle.color.lightGray
pressedColor: AmneziaStyle.color.mutedGray
textColor: AmneziaStyle.color.midnightBlack
text: qsTr("Renew subscription")
clickedFunc: function() {
ApiSettingsController.getRenewalLink()
}
}
CaptionTextType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: (root.subscriptionExpired || root.subscriptionExpiringSoon) ? 8 : 4
Layout.bottomMargin: 8
text: qsTr("Location for connection")
color: AmneziaStyle.color.mutedGray
}
} }
delegate: ColumnLayout { delegate: ColumnLayout {
@@ -2,6 +2,7 @@ import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Dialogs import QtQuick.Dialogs
import Qt5Compat.GraphicalEffects
import SortFilterProxyModel 0.2 import SortFilterProxyModel 0.2
@@ -52,6 +53,26 @@ PageType {
property var processedServer property var processedServer
property bool isSubscriptionExpired: false
property bool isSubscriptionExpiringSoon: false
function updateSubscriptionState() {
root.isSubscriptionExpired = ApiAccountInfoModel.data("isSubscriptionExpired")
root.isSubscriptionExpiringSoon = ApiAccountInfoModel.data("isSubscriptionExpiringSoon")
}
Component.onCompleted: {
root.updateSubscriptionState()
}
Connections {
target: ApiAccountInfoModel
function onModelReset() {
root.updateSubscriptionState()
}
}
Connections { Connections {
target: ServersModel target: ServersModel
@@ -108,12 +129,66 @@ PageType {
actionButtonImage: "qrc:/images/controls/edit-3.svg" actionButtonImage: "qrc:/images/controls/edit-3.svg"
headerText: root.processedServer.name headerText: root.processedServer.name
descriptionText: ApiAccountInfoModel.data("serviceDescription")
actionButtonFunction: function() { actionButtonFunction: function() {
serverNameEditDrawer.openTriggered() serverNameEditDrawer.openTriggered()
} }
} }
Text {
visible: root.isSubscriptionExpired || root.isSubscriptionExpiringSoon
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 4
text: root.isSubscriptionExpired
? qsTr("Subscription expired")
: qsTr("Subscription expiring soon")
color: root.isSubscriptionExpired
? AmneziaStyle.color.vibrantRed
: AmneziaStyle.color.goldenApricot
font.pixelSize: 14
font.weight: Font.Medium
wrapMode: Text.WordWrap
}
ParagraphTextType {
visible: ApiAccountInfoModel.data("serviceDescription") !== ""
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 16
Layout.bottomMargin: root.isSubscriptionExpired || root.isSubscriptionExpiringSoon ? 0 : 10
text: ApiAccountInfoModel.data("serviceDescription")
color: AmneziaStyle.color.mutedGray
}
BasicButtonType {
visible: root.isSubscriptionExpired || root.isSubscriptionExpiringSoon
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 8
Layout.bottomMargin: 8
text: qsTr("Renew subscription")
defaultColor: AmneziaStyle.color.paleGray
hoveredColor: AmneziaStyle.color.lightGray
pressedColor: AmneziaStyle.color.mutedGray
textColor: AmneziaStyle.color.midnightBlack
clickedFunc: function() {
ApiSettingsController.getRenewalLink()
}
}
} }
delegate: ColumnLayout { delegate: ColumnLayout {
@@ -151,6 +226,54 @@ PageType {
readonly property bool isVisibleForAmneziaFree: ApiAccountInfoModel.data("isComponentVisible") readonly property bool isVisibleForAmneziaFree: ApiAccountInfoModel.data("isComponentVisible")
Item {
visible: !root.isSubscriptionExpired && !root.isSubscriptionExpiringSoon
Layout.fillWidth: true
implicitHeight: renewRow.implicitHeight + 32
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: ApiSettingsController.getRenewalLink()
}
Row {
id: renewRow
anchors.centerIn: parent
spacing: 12
Item {
width: renewIcon.implicitWidth
height: renewIcon.implicitHeight
anchors.verticalCenter: parent.verticalCenter
Image {
id: renewIcon
source: "qrc:/images/controls/refresh-cw.svg"
}
ColorOverlay {
anchors.fill: renewIcon
source: renewIcon
color: AmneziaStyle.color.goldenApricot
}
}
Text {
text: qsTr("Renew subscription")
color: AmneziaStyle.color.goldenApricot
font.pixelSize: 18
font.weight: Font.Medium
anchors.verticalCenter: parent.verticalCenter
}
}
}
DividerType {
visible: !root.isSubscriptionExpired && !root.isSubscriptionExpiringSoon
}
SwitcherType { SwitcherType {
id: switcher id: switcher
@@ -177,10 +300,14 @@ PageType {
} }
} }
DividerType {
visible: footer.isVisibleForAmneziaFree
}
WarningType { WarningType {
id: warning id: warning
Layout.topMargin: 32 Layout.topMargin: 24
Layout.rightMargin: 16 Layout.rightMargin: 16
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.fillWidth: true Layout.fillWidth: true
@@ -204,7 +331,7 @@ PageType {
id: vpnKey id: vpnKey
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: warning.visible ? 16 : 32 Layout.topMargin: warning.visible ? 16 : 0
visible: footer.isVisibleForAmneziaFree visible: footer.isVisibleForAmneziaFree
@@ -178,7 +178,7 @@ PageType {
Layout.margins: 16 Layout.margins: 16
text: qsTr("News Notification") text: qsTr("News Notification")
descriptionText: qsTr("Show notification icon when has unread news") descriptionText: qsTr("Show a notification icon for unread news")
checked: SettingsController.isNewsNotificationsEnabled() checked: SettingsController.isNewsNotificationsEnabled()
onToggled: function() { onToggled: function() {
@@ -1,226 +1,226 @@
import QtQuick import QtQuick
import QtQuick.Controls import QtQuick.Controls
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Dialogs import QtQuick.Dialogs
import PageEnum 1.0 import PageEnum 1.0
import Style 1.0 import Style 1.0
import "./" import "./"
import "../Controls2" import "../Controls2"
import "../Controls2/TextTypes" import "../Controls2/TextTypes"
import "../Config" import "../Config"
import "../Components" import "../Components"
PageType { PageType {
id: root id: root
BackButtonType { BackButtonType {
id: backButton id: backButton
anchors.top: parent.top anchors.top: parent.top
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.topMargin: 20 + SettingsController.safeAreaTopMargin anchors.topMargin: 20 + SettingsController.safeAreaTopMargin
onFocusChanged: { onFocusChanged: {
if (this.activeFocus) { if (this.activeFocus) {
listView.positionViewAtBeginning() listView.positionViewAtBeginning()
} }
} }
} }
ListViewType { ListViewType {
id: listView id: listView
anchors.top: backButton.bottom anchors.top: backButton.bottom
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.right: parent.right anchors.right: parent.right
anchors.left: parent.left anchors.left: parent.left
header: ColumnLayout { header: ColumnLayout {
width: listView.width width: listView.width
BaseHeaderType { BaseHeaderType {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 8 Layout.topMargin: 8
Layout.rightMargin: 16 Layout.rightMargin: 16
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.bottomMargin: 32 Layout.bottomMargin: 32
headerText: ApiServicesModel.getSelectedServiceData("name") headerText: ApiServicesModel.getSelectedServiceData("name")
descriptionText: ApiServicesModel.getSelectedServiceData("serviceDescription") descriptionText: ApiServicesModel.getSelectedServiceData("serviceDescription")
} }
} }
model: inputFields model: inputFields
spacing: 0 spacing: 0
delegate: ColumnLayout { delegate: ColumnLayout {
width: listView.width width: listView.width
LabelWithImageType { LabelWithImageType {
Layout.fillWidth: true Layout.fillWidth: true
Layout.margins: 16 Layout.margins: 16
imageSource: imagePath imageSource: imagePath
leftText: lText leftText: lText
rightText: rText rightText: rText
visible: isVisible visible: isVisible
} }
} }
footer: ColumnLayout { footer: ColumnLayout {
width: listView.width width: listView.width
spacing: 0 spacing: 0
ParagraphTextType { ParagraphTextType {
Layout.fillWidth: true Layout.fillWidth: true
Layout.rightMargin: 16 Layout.rightMargin: 16
Layout.leftMargin: 16 Layout.leftMargin: 16
onLinkActivated: function(link) { onLinkActivated: function(link) {
Qt.openUrlExternally(link) Qt.openUrlExternally(link)
} }
textFormat: Text.RichText textFormat: Text.RichText
text: { text: {
var text = ApiServicesModel.getSelectedServiceData("features") var text = ApiServicesModel.getSelectedServiceData("features")
return text.replace("%1", LanguageModel.getCurrentSiteUrl("free")).replace("/free", "") // todo link should come from gateway return text.replace("%1", LanguageModel.getCurrentSiteUrl("free")).replace("/free", "") // todo link should come from gateway
} }
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
acceptedButtons: Qt.NoButton acceptedButtons: Qt.NoButton
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
} }
} }
ParagraphTextType { ParagraphTextType {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 16 Layout.topMargin: 16
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.rightMargin: 16 Layout.rightMargin: 16
visible: (Qt.platform.os === "ios" || IsMacOsNeBuild) && ApiServicesModel.getSelectedServiceType() === "amnezia-premium" visible: (Qt.platform.os === "ios" || IsMacOsNeBuild) && ApiServicesModel.getSelectedServiceType() === "amnezia-premium"
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
textFormat: Text.PlainText textFormat: Text.PlainText
color: AmneziaStyle.color.mutedGray color: AmneziaStyle.color.mutedGray
font.pixelSize: 12 font.pixelSize: 12
text: qsTr("Charged to your Apple ID at confirmation. Renews automatically unless auto-renew is turned off at least 24 hours before period end. Manage in Apple ID settings.") text: qsTr("Charged to your Apple ID at confirmation. Renews automatically unless auto-renew is turned off at least 24 hours before period end. Manage in Apple ID settings.")
} }
BasicButtonType { BasicButtonType {
id: continueButton id: continueButton
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 32 Layout.topMargin: 32
Layout.bottomMargin: 16 Layout.bottomMargin: 16
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.rightMargin: 16 Layout.rightMargin: 16
text: ApiServicesModel.getSelectedServiceType() === "amnezia-premium" ? qsTr("Subscribe Now") : qsTr("Connect") text: ApiServicesModel.getSelectedServiceType() === "amnezia-premium" ? qsTr("Subscribe Now") : (ApiServicesModel.getSelectedServiceType() === "amnezia-trial" ? qsTr("Try Trial") : qsTr("Connect"))
clickedFunc: function() { clickedFunc: function() {
PageController.showBusyIndicator(true) PageController.showBusyIndicator(true)
var result = ApiConfigsController.importService() var result = ApiConfigsController.importService()
PageController.showBusyIndicator(false) PageController.showBusyIndicator(false)
if (!result) { if (!result) {
var endpoint = ApiServicesModel.getStoreEndpoint() var endpoint = ApiServicesModel.getStoreEndpoint()
Qt.openUrlExternally(endpoint) Qt.openUrlExternally(endpoint)
PageController.closePage() PageController.closePage()
PageController.closePage() PageController.closePage()
} }
} }
} }
ParagraphTextType { ParagraphTextType {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 16 Layout.topMargin: 16
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.rightMargin: 16 Layout.rightMargin: 16
Layout.bottomMargin: 32 Layout.bottomMargin: 32
visible: (Qt.platform.os === "ios" || IsMacOsNeBuild) && ApiServicesModel.getSelectedServiceType() === "amnezia-premium" visible: (Qt.platform.os === "ios" || IsMacOsNeBuild) && ApiServicesModel.getSelectedServiceType() === "amnezia-premium"
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
textFormat: Text.RichText textFormat: Text.RichText
color: AmneziaStyle.color.mutedGray color: AmneziaStyle.color.mutedGray
font.pixelSize: 12 font.pixelSize: 12
text: { text: {
var termsUrl = "https://www.apple.com/legal/internet-services/itunes/dev/stdeula/" var termsUrl = "https://www.apple.com/legal/internet-services/itunes/dev/stdeula/"
var privacyUrl = LanguageModel.getCurrentSiteUrl("policy") var privacyUrl = LanguageModel.getCurrentSiteUrl("policy")
return qsTr("By continuing, you agree to the <a href=\"%1\" style=\"color: #FBB26A;\">Terms of Use</a> and <a href=\"%2\" style=\"color: #FBB26A;\">Privacy Policy</a>").arg(termsUrl).arg(privacyUrl) return qsTr("By continuing, you agree to the <a href=\"%1\" style=\"color: #FBB26A;\">Terms of Use</a> and <a href=\"%2\" style=\"color: #FBB26A;\">Privacy Policy</a>").arg(termsUrl).arg(privacyUrl)
} }
onLinkActivated: function(link) { onLinkActivated: function(link) {
Qt.openUrlExternally(link) Qt.openUrlExternally(link)
} }
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
acceptedButtons: Qt.NoButton acceptedButtons: Qt.NoButton
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
} }
} }
} }
} }
property list<QtObject> inputFields: [ property list<QtObject> inputFields: [
region, region,
price, price,
timeLimit, timeLimit,
speed, speed,
features features
] ]
QtObject { QtObject {
id: region id: region
readonly property string imagePath: "qrc:/images/controls/map-pin.svg" readonly property string imagePath: "qrc:/images/controls/map-pin.svg"
readonly property string lText: qsTr("For the region") readonly property string lText: qsTr("For the region")
readonly property string rText: ApiServicesModel.getSelectedServiceData("region") readonly property string rText: ApiServicesModel.getSelectedServiceData("region")
property bool isVisible: true property bool isVisible: true
} }
QtObject { QtObject {
id: price id: price
readonly property string imagePath: "qrc:/images/controls/tag.svg" readonly property string imagePath: "qrc:/images/controls/tag.svg"
readonly property string lText: qsTr("Price") readonly property string lText: qsTr("Price")
readonly property string rText: ApiServicesModel.getSelectedServiceData("price") readonly property string rText: ApiServicesModel.getSelectedServiceData("price")
property bool isVisible: true property bool isVisible: true
} }
QtObject { QtObject {
id: timeLimit id: timeLimit
readonly property string imagePath: "qrc:/images/controls/history.svg" readonly property string imagePath: "qrc:/images/controls/history.svg"
readonly property string lText: qsTr("Work period") readonly property string lText: qsTr("Work period")
readonly property string rText: ApiServicesModel.getSelectedServiceData("timeLimit") readonly property string rText: ApiServicesModel.getSelectedServiceData("timeLimit")
property bool isVisible: rText !== "" property bool isVisible: rText !== ""
} }
QtObject { QtObject {
id: speed id: speed
readonly property string imagePath: "qrc:/images/controls/gauge.svg" readonly property string imagePath: "qrc:/images/controls/gauge.svg"
readonly property string lText: qsTr("Speed") readonly property string lText: qsTr("Speed")
readonly property string rText: ApiServicesModel.getSelectedServiceData("speed") readonly property string rText: ApiServicesModel.getSelectedServiceData("speed")
property bool isVisible: true property bool isVisible: true
} }
QtObject { QtObject {
id: features id: features
readonly property string imagePath: "qrc:/images/controls/info.svg" readonly property string imagePath: "qrc:/images/controls/info.svg"
readonly property string lText: qsTr("Features") readonly property string lText: qsTr("Features")
readonly property string rText: "" readonly property string rText: ""
property bool isVisible: true property bool isVisible: true
} }
} }
@@ -79,11 +79,23 @@ PageType {
} }
textField.onTextChanged: { textField.onTextChanged: {
if (headerText == qsTr("Password or SSH private key")) { if (headerText === qsTr("Password or SSH private key")) {
buttonImageSource = textField.text !== "" ? imageSource : "" buttonImageSource = textField.text !== "" ? imageSource : ""
} }
} }
} }
WarningType {
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 8
visible: title === qsTr("Password or SSH private key")
backGroundColor: AmneziaStyle.color.translucentWhite
iconPath: "qrc:/images/controls/alert-circle.svg"
textString: qsTr("SSH key requirements: supported ED25519 or RSA in PEM. Paste the private key including BEGIN/END lines. If your key doesnt work, generate a compatible one.")
}
} }
footer: ColumnLayout { footer: ColumnLayout {
@@ -13,6 +13,7 @@ import "../Components"
PageType { PageType {
id: root id: root
enableTimer: (SettingsController.isOnTv()) ? false : true
ColumnLayout { ColumnLayout {
id: content id: content
@@ -45,4 +46,22 @@ PageType {
} }
} }
} }
Timer {
interval: 250
running: SettingsController.isOnTv()
repeat: true
onTriggered: {
startButton.forceActiveFocus()
if (startButton.activeFocus) {
running = false
}
}
}
onVisibleChanged: {
if (visible && SettingsController.isOnTv()) {
startButton.forceActiveFocus()
}
}
} }
+7 -1
View File
@@ -505,7 +505,13 @@ PageType {
exportTypeSelector.currentIndex = 0 exportTypeSelector.currentIndex = 0
} }
selectedIndex = exportTypeSelector.currentIndex selectedIndex = exportTypeSelector.currentIndex
exportTypeSelector.text = selectedText if (model.length > 0 && model[selectedIndex] && model[selectedIndex].name !== undefined) {
exportTypeSelectorListView.selectedText = model[selectedIndex].name
exportTypeSelector.text = model[selectedIndex].name
} else {
exportTypeSelectorListView.selectedText = ""
exportTypeSelector.text = ""
}
} }
rootWidth: root.width rootWidth: root.width
+1 -2
View File
@@ -278,7 +278,6 @@ PageType {
} }
Keys.onPressed: function(event) { Keys.onPressed: function(event) {
console.debug(">>>> ", event.key, " Event is caught by StartPage")
switch (event.key) { switch (event.key) {
case Qt.Key_Tab: case Qt.Key_Tab:
case Qt.Key_Down: case Qt.Key_Down:
@@ -304,7 +303,7 @@ PageType {
anchors.right: parent.right anchors.right: parent.right
anchors.left: parent.left anchors.left: parent.left
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
// Also adjust TabBar position when keyboard appears (Android 14+ workaround) // Also adjust TabBar position when keyboard appears (Android 14+ workaround)
anchors.bottomMargin: SettingsController.imeHeight anchors.bottomMargin: SettingsController.imeHeight
+48 -3
View File
@@ -21,15 +21,27 @@ Window {
function onStateChanged() { function onStateChanged() {
if (Qt.platform.os === "android") { if (Qt.platform.os === "android") {
if (Qt.application.state === Qt.ApplicationActive) { if (Qt.application.state === Qt.ApplicationActive) {
root.visible = true
refreshTimer.restart() refreshTimer.restart()
} else if (Qt.application.state === Qt.ApplicationSuspended ||
Qt.application.state === Qt.ApplicationInactive) {
console.log("QML: Application going to background, state:", Qt.application.state)
} }
} }
} }
} }
// Hide the window immediately when Android Activity.onPause() fires so that
// Qt's render loop stops before the EGL surface is disconnected. This
// prevents "QRhiGles2: Failed to make context current" and the resulting
// black screen that appears after swiping home and returning.
Connections {
target: SettingsController
function onActivityPaused() {
if (Qt.platform.os === "android") root.visible = false
}
function onActivityResumed() {
if (Qt.platform.os === "android") root.visible = true
}
}
Timer { Timer {
id: refreshTimer id: refreshTimer
interval: 150 interval: 150
@@ -56,6 +68,11 @@ Window {
PageController.closeWindow() PageController.closeWindow()
} }
onSceneGraphError: function(error, message) {
// Prevent qFatal crash on Android when EGL context is lost
console.warn("Scene graph error:", error, message)
}
title: "AmneziaVPN" title: "AmneziaVPN"
Item { // This item is needed for focus handling Item { // This item is needed for focus handling
@@ -271,6 +288,34 @@ Window {
} }
} }
Item {
objectName: "subscriptionExpiredDrawerItem"
anchors.fill: parent
SubscriptionExpiredDrawer {
id: subscriptionExpiredDrawer
anchors.fill: parent
}
}
Connections {
target: ApiConfigsController
function onSubscriptionExpiredOnServer() {
subscriptionExpiredDrawer.openTriggered()
}
}
Connections {
target: ApiSettingsController
function onRenewalLinkReceived(url) {
Qt.openUrlExternally(url)
}
}
Item { Item {
objectName: "busyIndicatorItem" objectName: "busyIndicatorItem"
+48 -28
View File
@@ -39,7 +39,7 @@ VpnConnection::VpnConnection(std::shared_ptr<Settings> settings, QObject *parent
{ {
#if defined(Q_OS_IOS) || defined(MACOS_NE) #if defined(Q_OS_IOS) || defined(MACOS_NE)
m_checkTimer.setInterval(1000); m_checkTimer.setInterval(1000);
connect(IosController::Instance(), &IosController::connectionStateChanged, this, &VpnConnection::onConnectionStateChanged); connect(IosController::Instance(), &IosController::connectionStateChanged, this, &VpnConnection::setConnectionState);
connect(IosController::Instance(), &IosController::bytesChanged, this, &VpnConnection::onBytesChanged); connect(IosController::Instance(), &IosController::bytesChanged, this, &VpnConnection::onBytesChanged);
#endif #endif
} }
@@ -57,11 +57,15 @@ void VpnConnection::onKillSwitchModeChanged(bool enabled)
{ {
#ifdef AMNEZIA_DESKTOP #ifdef AMNEZIA_DESKTOP
IpcClient::withInterface([enabled](QSharedPointer<IpcInterfaceReplica> iface){ IpcClient::withInterface([enabled](QSharedPointer<IpcInterfaceReplica> iface){
QRemoteObjectPendingReply<bool> reply = iface->refreshKillSwitch(enabled); auto reply = iface->refreshKillSwitch(enabled);
if (reply.waitForFinished() && reply.returnValue()) auto *watcher = new QRemoteObjectPendingCallWatcher(reply);
qDebug() << "VpnConnection::onKillSwitchModeChanged: Killswitch refreshed"; QObject::connect(watcher, &QRemoteObjectPendingCallWatcher::finished, [watcher]() {
else if (watcher->returnValue().toBool())
qWarning() << "VpnConnection::onKillSwitchModeChanged: Failed to execute remote refreshKillSwitch call"; qDebug() << "VpnConnection::onKillSwitchModeChanged: Killswitch refreshed";
else
qWarning() << "VpnConnection::onKillSwitchModeChanged: Failed to refresh killswitch";
watcher->deleteLater();
});
}); });
#endif #endif
} }
@@ -76,15 +80,19 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state)
case Vpn::ConnectionState::Connected: { case Vpn::ConnectionState::Connected: {
iface->resetIpStack(); iface->resetIpStack();
auto flushDns = iface->flushDns();
if (flushDns.waitForFinished() && flushDns.returnValue())
qDebug() << "VpnConnection::onConnectionStateChanged: Successfully flushed DNS";
else
qWarning() << "VpnConnection::onConnectionStateChanged: Failed to clear saved routes";
if (!ContainerProps::isAwgContainer(container) && if (!ContainerProps::isAwgContainer(container) &&
container != DockerContainer::WireGuard) { container != DockerContainer::WireGuard) {
auto flushDnsReply = iface->flushDns();
auto *flushDnsWatcher = new QRemoteObjectPendingCallWatcher(flushDnsReply, this);
QObject::connect(flushDnsWatcher, &QRemoteObjectPendingCallWatcher::finished, this,
[flushDnsWatcher]() {
if (flushDnsWatcher->returnValue().toBool())
qDebug() << "VpnConnection::onConnectionStateChanged: Successfully flushed DNS";
else
qWarning() << "VpnConnection::onConnectionStateChanged: Failed to flush DNS";
flushDnsWatcher->deleteLater();
});
QString dns1 = m_vpnConfiguration.value(config_key::dns1).toString(); QString dns1 = m_vpnConfiguration.value(config_key::dns1).toString();
QString dns2 = m_vpnConfiguration.value(config_key::dns2).toString(); QString dns2 = m_vpnConfiguration.value(config_key::dns2).toString();
@@ -109,17 +117,25 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state)
} break; } break;
case Vpn::ConnectionState::Disconnected: case Vpn::ConnectionState::Disconnected:
case Vpn::ConnectionState::Error: { case Vpn::ConnectionState::Error: {
auto flushDns = iface->flushDns(); auto flushDnsReply = iface->flushDns();
if (flushDns.waitForFinished() && flushDns.returnValue()) auto *flushDnsWatcher = new QRemoteObjectPendingCallWatcher(flushDnsReply);
qDebug() << "VpnConnection::onConnectionStateChanged: Successfully flushed DNS"; QObject::connect(flushDnsWatcher, &QRemoteObjectPendingCallWatcher::finished, [flushDnsWatcher]() {
else if (flushDnsWatcher->returnValue().toBool())
qWarning() << "VpnConnection::onConnectionStateChanged: Failed to flush DNS"; qDebug() << "VpnConnection::onConnectionStateChanged: Successfully flushed DNS";
else
qWarning() << "VpnConnection::onConnectionStateChanged: Failed to flush DNS";
flushDnsWatcher->deleteLater();
});
auto clearSavedRoutes = iface->clearSavedRoutes(); auto clearSavedRoutesReply = iface->clearSavedRoutes();
if (clearSavedRoutes.waitForFinished() && clearSavedRoutes.returnValue()) auto *clearRoutesWatcher = new QRemoteObjectPendingCallWatcher(clearSavedRoutesReply);
qDebug() << "VpnConnection::onConnectionStateChanged: Successfully cleared saved routes"; QObject::connect(clearRoutesWatcher, &QRemoteObjectPendingCallWatcher::finished, [clearRoutesWatcher]() {
else if (clearRoutesWatcher->returnValue().toBool())
qWarning() << "VpnConnection::onConnectionStateChanged: Failed to clear saved routes"; qDebug() << "VpnConnection::onConnectionStateChanged: Successfully cleared saved routes";
else
qWarning() << "VpnConnection::onConnectionStateChanged: Failed to clear saved routes";
clearRoutesWatcher->deleteLater();
});
} break; } break;
default: default:
break; break;
@@ -182,8 +198,12 @@ void VpnConnection::addSitesRoutes(const QString &gw, Settings::RouteMode mode)
} }
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) { IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
auto reply = iface->flushDns(); auto reply = iface->flushDns();
if (reply.waitForFinished() || !reply.returnValue()) auto *w = new QRemoteObjectPendingCallWatcher(reply);
qWarning() << "VpnConnection::addSitesRoutes: Failed to flush DNS"; QObject::connect(w, &QRemoteObjectPendingCallWatcher::finished, [w]() {
if (!w->returnValue().toBool())
qWarning() << "VpnConnection::addSitesRoutes: Failed to flush DNS";
w->deleteLater();
});
}); });
break; break;
} }
@@ -219,8 +239,8 @@ ErrorCode VpnConnection::lastError() const
return m_vpnProtocol.data()->lastError(); return m_vpnProtocol.data()->lastError();
} }
void VpnConnection::connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, void VpnConnection::connectToVpn(int serverIndex, const amnezia::ServerCredentials &credentials,
const QJsonObject &vpnConfiguration) amnezia::DockerContainer container, const QJsonObject &vpnConfiguration)
{ {
qDebug() << QString("Trying to connect to VPN, server index is %1, container is %2, route mode is") qDebug() << QString("Trying to connect to VPN, server index is %1, container is %2, route mode is")
.arg(serverIndex) .arg(serverIndex)
@@ -464,7 +484,7 @@ void VpnConnection::disconnectFromVpn()
*connection = connect(AndroidController::instance(), &AndroidController::vpnStateChanged, this, *connection = connect(AndroidController::instance(), &AndroidController::vpnStateChanged, this,
[this, connection](AndroidController::ConnectionState state) { [this, connection](AndroidController::ConnectionState state) {
if (state == AndroidController::ConnectionState::DISCONNECTED) { if (state == AndroidController::ConnectionState::DISCONNECTED) {
onConnectionStateChanged(Vpn::ConnectionState::Disconnected); setConnectionState(Vpn::ConnectionState::Disconnected);
disconnect(*connection); disconnect(*connection);
delete connection; delete connection;
} }
+2 -1
View File
@@ -44,7 +44,8 @@ public:
#endif #endif
public slots: public slots:
void connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &vpnConfiguration); void connectToVpn(int serverIndex, const amnezia::ServerCredentials &credentials,
amnezia::DockerContainer container, const QJsonObject &vpnConfiguration);
void reconnectToVpn(); void reconnectToVpn();
void disconnectFromVpn(); void disconnectFromVpn();
@@ -14,8 +14,6 @@ sc stop AmneziaVPN-service
sc delete AmneziaVPN-service sc delete AmneziaVPN-service
sc stop AmneziaWGTunnel$AmneziaVPN sc stop AmneziaWGTunnel$AmneziaVPN
sc delete AmneziaWGTunnel$AmneziaVPN sc delete AmneziaWGTunnel$AmneziaVPN
sc stop AmneziaVPNSplitTunnel
sc delete AmneziaVPNSplitTunnel
taskkill /IM "AmneziaVPN-service.exe" /F taskkill /IM "AmneziaVPN-service.exe" /F
taskkill /IM "AmneziaVPN.exe" /F taskkill /IM "AmneziaVPN.exe" /F
@@ -14,8 +14,6 @@ sc stop AmneziaVPN-service
sc delete AmneziaVPN-service sc delete AmneziaVPN-service
sc stop AmneziaWGTunnel$AmneziaVPN sc stop AmneziaWGTunnel$AmneziaVPN
sc delete AmneziaWGTunnel$AmneziaVPN sc delete AmneziaWGTunnel$AmneziaVPN
sc stop AmneziaVPNSplitTunnel
sc delete AmneziaVPNSplitTunnel
taskkill /IM "AmneziaVPN-service.exe" /F taskkill /IM "AmneziaVPN-service.exe" /F
taskkill /IM "AmneziaVPN.exe" /F taskkill /IM "AmneziaVPN.exe" /F
+11 -5
View File
@@ -53,7 +53,9 @@ bool KillSwitch::refresh(bool enabled)
#endif #endif
#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS) #if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
m_appSettigns->setValue("Conf/strictKillSwitchEnabled", enabled); if (!m_appSettigns.isNull()) {
m_appSettigns->setValue("Conf/strictKillSwitchEnabled", enabled);
}
#endif #endif
if (isStrictKillSwitchEnabled()) { if (isStrictKillSwitchEnabled()) {
@@ -71,7 +73,11 @@ bool KillSwitch::isStrictKillSwitchEnabled()
+ "\\" + QString(APPLICATION_NAME), QSettings::NativeFormat); + "\\" + QString(APPLICATION_NAME), QSettings::NativeFormat);
return RegHLM.value("strictKillSwitchEnabled", false).toBool(); return RegHLM.value("strictKillSwitchEnabled", false).toBool();
#endif #endif
m_appSettigns->sync(); #if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
if (m_appSettigns.isNull()) {
return false;
}
#endif
return m_appSettigns->value("Conf/strictKillSwitchEnabled", false).toBool(); return m_appSettigns->value("Conf/strictKillSwitchEnabled", false).toBool();
} }
@@ -87,7 +93,7 @@ bool KillSwitch::disableKillSwitch() {
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("290.allowDHCP"), false); LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("290.allowDHCP"), false);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("300.allowLAN"), false); LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("300.allowLAN"), false);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("310.blockDNS"), false); LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("310.blockDNS"), false);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("320.allowDNS"), false); LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("320.allowDNS"), false);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("400.allowPIA"), false); LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("400.allowPIA"), false);
} else { } else {
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("000.allowLoopback"), true); LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("000.allowLoopback"), true);
@@ -99,7 +105,7 @@ bool KillSwitch::disableKillSwitch() {
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("290.allowDHCP"), true); LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("290.allowDHCP"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("300.allowLAN"), true); LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("300.allowLAN"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("310.blockDNS"), false); LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("310.blockDNS"), false);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("320.allowDNS"), true); LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("320.allowDNS"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("400.allowPIA"), false); LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("400.allowPIA"), false);
LinuxFirewall::uninstall(); LinuxFirewall::uninstall();
} }
@@ -336,7 +342,7 @@ bool KillSwitch::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterIn
} }
LinuxFirewall::updateDNSServers(dnsServers); LinuxFirewall::updateDNSServers(dnsServers);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("320.allowDNS"), true); LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("320.allowDNS"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("400.allowPIA"), true); LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("400.allowPIA"), true);
#endif #endif
+1 -1
View File
@@ -22,7 +22,7 @@ public:
bool isStrictKillSwitchEnabled(); bool isStrictKillSwitchEnabled();
private: private:
KillSwitch(QObject* parent) {}; explicit KillSwitch(QObject* parent) : QObject(parent) {}
QStringList m_allowedRanges; QStringList m_allowedRanges;
QSharedPointer<SecureQSettings> m_appSettigns; QSharedPointer<SecureQSettings> m_appSettigns;
+14 -19
View File
@@ -120,18 +120,20 @@ bool RouterLinux::routeDelete(const QString &ipWithSubnet, const QString &gw, co
((struct sockaddr_in *)&route.rt_gateway)->sin_family = AF_INET; ((struct sockaddr_in *)&route.rt_gateway)->sin_family = AF_INET;
((struct sockaddr_in *)&route.rt_gateway)->sin_addr.s_addr = inet_addr(gw.toStdString().c_str()); ((struct sockaddr_in *)&route.rt_gateway)->sin_addr.s_addr = inet_addr(gw.toStdString().c_str());
((struct sockaddr_in *)&route.rt_gateway)->sin_port = 0; ((struct sockaddr_in *)&route.rt_gateway)->sin_port = 0;
// set host rejecting // set host rejecting
((struct sockaddr_in *)&route.rt_dst)->sin_family = AF_INET; ((struct sockaddr_in *)&route.rt_dst)->sin_family = AF_INET;
((struct sockaddr_in *)&route.rt_dst)->sin_addr.s_addr = inet_addr(ip.toStdString().c_str()); ((struct sockaddr_in *)&route.rt_dst)->sin_addr.s_addr = inet_addr(ip.toStdString().c_str());
((struct sockaddr_in *)&route.rt_dst)->sin_port = 0; ((struct sockaddr_in *)&route.rt_dst)->sin_port = 0;
// set mask // set mask
((struct sockaddr_in *)&route.rt_genmask)->sin_family = AF_INET; ((struct sockaddr_in *)&route.rt_genmask)->sin_family = AF_INET;
((struct sockaddr_in *)&route.rt_genmask)->sin_addr.s_addr = inet_addr(mask.toStdString().c_str()); ((struct sockaddr_in *)&route.rt_genmask)->sin_addr.s_addr = inet_addr(mask.toStdString().c_str());
((struct sockaddr_in *)&route.rt_genmask)->sin_port = 0; ((struct sockaddr_in *)&route.rt_genmask)->sin_port = 0;
route.rt_flags = RTF_UP | RTF_GATEWAY; route.rt_flags = RTF_UP | RTF_GATEWAY;
route.rt_metric = 0;
//route.rt_dev = "ens33"; //route.rt_dev = "ens33";
route.rt_metric = 0;
if (ioctl(sock, SIOCDELRT, &route) < 0) if (ioctl(sock, SIOCDELRT, &route) < 0)
{ {
@@ -155,36 +157,29 @@ bool RouterLinux::routeDeleteList(const QString &gw, const QStringList &ips)
bool RouterLinux::isServiceActive(const QString &serviceName) { bool RouterLinux::isServiceActive(const QString &serviceName) {
QProcess process; QProcess process;
process.start("systemctl", { "is-active", "--quiet", serviceName }); process.start("systemctl", { "is-active", "--quiet", serviceName });
process.waitForFinished(); if (!process.waitForFinished(2000)) {
process.kill();
process.waitForFinished(500);
}
return process.exitCode() == 0; return process.exitCode() == 0;
} }
bool RouterLinux::flushDns() bool RouterLinux::flushDns()
{ {
QProcess p;
p.setProcessChannelMode(QProcess::MergedChannels);
//check what the dns manager use //check what the dns manager use
if (isServiceActive("nscd.service")) { if (isServiceActive("nscd.service")) {
qDebug() << "Restarting nscd.service"; qDebug() << "Flushing nscd cache";
p.start("systemctl", { "restart", "nscd" }); QProcess::startDetached("nscd", { "--invalidate=hosts" });
return true;
} else if (isServiceActive("systemd-resolved.service")) { } else if (isServiceActive("systemd-resolved.service")) {
qDebug() << "Restarting systemd-resolved.service"; qDebug() << "Flushing systemd-resolved cache";
p.start("systemctl", { "restart", "systemd-resolved" }); QProcess::startDetached("resolvectl", { "flush-caches" });
qDebug().noquote() << "Flush dns requested (non-blocking)";
return true;
} else { } else {
qDebug() << "No suitable DNS manager found."; qDebug() << "No suitable DNS manager found.";
return false; return false;
} }
p.waitForFinished();
QByteArray output(p.readAll());
if (output.isEmpty())
qDebug().noquote() << "Flush dns completed";
else
qDebug().noquote() << "OUTPUT systemctl restart nscd/systemd-resolved: " + output;
return true;
} }
bool RouterLinux::createTun(const QString &dev, const QString &subnet) { bool RouterLinux::createTun(const QString &dev, const QString &subnet) {