Compare commits

...

70 Commits

Author SHA1 Message Date
albexk 4f7aee049d Add hostName exclusion from routes for AWG/Wireguard 2024-03-21 12:32:02 +03:00
Iurii Egorov 248bbb5eec udptlspipe tests
Signed-off-by: Iurii Egorov <ye@amnezia.org>
2024-03-20 23:05:34 +03:00
pokamest 294778884b Merge pull request #691 from amnezia-vpn/bugfix/credentials-space-check
fixed checking credentials for spaces
2024-03-18 14:37:35 +00:00
albexk 10caecbffd Fix wg reconnection problem after awg connection (#696)
* Update Android AWG to 0.2.5
2024-03-18 11:20:01 +00:00
pokamest 553a6a73dd Merge pull request #697 from amnezia-vpn/bugfix/Service-crash-after-disconnecting
ISSUE: Service is crashed after disconnecting
2024-03-18 10:52:25 +00:00
Mykola Baibuz e646b85e56 Setup MTU for WG/AWG protocol (#576)
Setup MTU for AWG/WG protocol
2024-03-18 10:41:53 +00:00
Dan Nguyen b7c513c05f ISSUE: Service is crashed after disconnecting
ROOT CAUSE: When disconnecting service, m_logworker is deleted in thread which does not have affinity with m_logworker.
			The time m_logworker is deleted, it may be used by m_logthread and make the service crashed

ACTION: Connect signal finished() of m_logthread to deleteLater() slot of m_logworker to safety delete it.
2024-03-17 07:09:57 +07:00
pokamest 9f82b4c21f Merge pull request #689 from amnezia-vpn/translations/burmese-fix
Shortening of translated text in Burmese
2024-03-16 20:32:37 +00:00
pokamest 02b2da38cf Merge pull request #690 from amnezia-vpn/bugfix/native-config-import-error-handling
added error handling for importing a native config
2024-03-14 17:03:06 +00:00
vladimir.kuznetsov f51077b2be fixed checking credentials for spaces 2024-03-14 15:59:16 +05:00
vladimir.kuznetsov 33f49bfddb added error handling for importing a native config 2024-03-14 12:55:33 +05:00
Andrey Zaharow 9a81f13f81 Short translated text 2024-03-13 22:44:09 +01:00
albexk 915fb6759a Add Android openssl3 libs, fix https connection error (#685)
Add Android openssl3 libs, fix https connection error
2024-03-13 21:22:56 +00:00
Nethius c5a5bfde69 extended the validation of the contents of the imported file (#670)
Extended the validation of the contents of the imported file
2024-03-13 21:22:10 +00:00
Andrey Zaharow 0a90fd110d Add RU translation for Error 1101 text (#683)
* Add RU translation for Error 1101 text
2024-03-12 23:17:18 +00:00
pokamest 541d6eb0b8 Merge pull request #686 from amnezia-vpn/fix/allowips-config-change
Add AllowedIPs config change
2024-03-12 18:49:09 +00:00
pokamest d443a0063d Merge pull request #681 from amnezia-vpn/bugfix/mobile-auto-focus-disable
First element auto-focus disabled for the mobile platforms
2024-03-12 18:48:33 +00:00
pokamest f0c6edb670 Merge pull request #688 from amnezia-vpn/bugfix/sftp-hostname
bugfix/sftp-hostname
2024-03-12 18:47:42 +00:00
vladimir.kuznetsov 9189b53a0d fixed display of hostName on the sftp settings page 2024-03-12 23:43:24 +05:00
Igor Sorokin fceccaefcc Add AllowedIPs config change 2024-03-12 19:57:45 +03:00
pokamest fbeabf43ca Merge pull request #684 from amnezia-vpn/fix/android-remove-ss 2024-03-12 15:17:55 +00:00
albexk 78c7893f90 Remove shadowsocks libs from Android build 2024-03-12 17:17:38 +03:00
Garegin866 cb9a25006c - Removed additional focus frames for buttons inside text fields.
- For mobile platforms, disabled auto-focus on the first element when navigating on the page.
2024-03-12 00:02:47 +04:00
pokamest 0e87550d85 Merge pull request #672 from amnezia-vpn/translations/fix-translations
Fix translations
2024-03-10 16:18:27 -07:00
pokamest dceb0ab832 Merge pull request #674 from amnezia-vpn/version-bump
Bump Android version code to 47
2024-03-10 04:43:44 -07:00
pokamest a33590476a Merge pull request #677 from artromone/fix/logger
added commit hash in logger
2024-03-08 14:25:19 -08:00
Artem Romanovich deaf618520 added commit hash in logger 2024-03-09 00:21:57 +03:00
pokamest 3d8a56d922 Merge pull request #673 from amnezia-vpn/feature/api-request-debug-output
extended debug output for api request
2024-03-07 04:43:07 -08:00
albexk 36af7cf471 Bump Android version code to 47 2024-03-07 14:27:21 +03:00
vladimir.kuznetsov ebd3449b4a extended debug output for api request 2024-03-07 09:18:25 +03:00
Andrey Zaharow 99182f4a67 Fix translations 2024-03-06 23:04:53 +01:00
pokamest da84ba1a4d Text fixes and some ts updates 2024-03-06 18:34:07 +00:00
pokamest bca68fc185 iOS crash fix 2024-03-06 10:07:49 -08:00
pokamest 59a7265bac Merge pull request #671 from amnezia-vpn/bugfix/fade-on-page-start-repalce
fixed screen fade when switching from PageSetupWizardStart to PageStart
2024-03-06 06:36:07 -08:00
vladimir.kuznetsov 9201ca1e03 fixed screen fade when switching from PageSetupWizardStart to PageStart 2024-03-06 14:22:44 +05:00
dimov96 6b6a76d2cc Replace sftp with scp (#602)
Replace sftp with scp
2024-03-06 01:24:28 +00:00
isamnezia 840c388ab9 Add in-app screenshot preventing (#606)
In-app screenshot preventing fixes
2024-03-06 01:18:19 +00:00
Shehab Ahmed 5b4ec608c8 pushing the Burmese translation file (#669)
Burmese translation
2024-03-05 20:49:30 +00:00
pokamest 79ff1b81e0 Merge pull request #666 from amnezia-vpn/bugfix/Revert_PR_596
ISSUE: In start page, icon is highlighted not correctly when press ESC key
2024-03-05 05:07:05 -08:00
pokamest ea67c01da8 Merge pull request #667 from amnezia-vpn/bugfix/http-replacement
removed the replacement of https by http in apiController
2024-03-04 13:07:06 -08:00
vladimir.kuznetsov 1137e169ea removed the replacement of https by http in apiController 2024-03-04 21:45:04 +03:00
Dan Nguyen 17748cca47 ISSUE: In start page, icon is highlighted not correctly when press ESC key
ROOT CAUSE: The button state is decided by the attribute isServerInfoShow and it was added by commit 68fe20ddf6. The logic to decide whether server info showed is not correct

ACTION: Revert commit 68fe20ddf6
2024-03-04 22:35:34 +07:00
albexk 080e1d98c6 Add Quick Settings tile (#660)
* Add Quick Settings tile

- Add multi-client support to AmneziaVpnService
- Make AmneziaActivity permanently connected to AmneziaVpnService while it is running
- Refactor processing of connection state changes on qt side
- Add VpnState DataStore
- Add check if AmneziaVpnService is running

* Add tile reset when the server is removed from the application
2024-03-04 15:08:55 +00:00
isamnezia ca633ae882 Remove VPN configurations after app reset on iOS (#661) 2024-03-04 12:25:49 +00:00
isamnezia bb7b64fb96 Fix QML glitches and crash on iOS (#658)
Fix QML glitches and crash on iOS and Android
2024-03-03 23:28:10 +00:00
AlexanderGalkov bf901631bf Update Dockerfile (#648)
* Update Dockerfile
* update server scripts for cloak and shadowsocks
* specify the latest cloak and shadowsocks releases in server scripts
2024-03-02 19:45:42 +00:00
Shehab Ahmed 0c0ce54b1f fixed the first-generated QR code is visible while generating another QR code bug (#656) 2024-03-02 19:06:33 +00:00
pokamest ee762c4cef Merge pull request #653 from amnezia-vpn/fix/config-from-tg
Fix adding config from bot (iOS)
2024-02-29 07:05:24 -08:00
pokamest ed9efb5a79 Merge pull request #654 from amnezia-vpn/fix/config-sync-ios
Sync configs and fix bug in NE (iOS)
2024-02-29 03:09:20 -08:00
Igor Sorokin 73eb85f2f4 Sync configs 2024-02-29 13:58:11 +03:00
Nethius cd055cff62 removed the display of servers without containers on PageShare (#609)
* removed the display of servers without containers on PageShare

* removed unused isAnyContainerInstalled() from containers model

* added tab navigation to the share connection drawer

* fixed display of default server without containers on PageShare
2024-02-29 10:22:17 +00:00
Igor Sorokin f8b2cce618 Fix adding config from bot 2024-02-29 08:36:56 +03:00
sa6ta6ni6c e648054c7a Misc update README (#652)
Update README.md
2024-02-29 00:14:09 +00:00
pokamest fe558163cc Merge pull request #651 from amnezia-vpn/bugfix/on-escape-pressed
fixed initial value of m_drawerDepth
2024-02-28 06:35:38 -08:00
vladimir.kuznetsov 3883b8ff34 fixed initial value of m_drawerDepth 2024-02-28 17:31:46 +03:00
pokamest d286664763 Merge pull request #649 from sa6ta6ni6c/patch-1
Ru translation update
2024-02-28 05:31:14 -08:00
Nethius b05ad2392b added escape key handler (#461)
Added escape key handler for drawer2type
2024-02-28 12:39:28 +00:00
Nethius 6dbdb85aaf fixed "file does not exist" error when opening a file for saving (#636) 2024-02-28 12:32:25 +00:00
sa6ta6ni6c 26b48cfe4f Обновил перевод 2024-02-27 20:47:41 +00:00
Andrey Zaharow 2f39136143 Fix translations (#646)
Fix awg texts
2024-02-26 21:17:57 +00:00
pokamest 8d0d3c5ce9 Merge pull request #641 from amnezia-vpn/feature/linux-ipv6
Remove ipv6 address for Linux WG/AWG interface
2024-02-26 12:07:50 -08:00
lunardunno 256081e4ed Improved server cleaning (#639)
Deleting the amnezia directory in opt when cleaning the server.
2024-02-26 12:35:31 +00:00
pokamest 1dd7b0a221 Merge pull request #647 from amnezia-vpn/bugfix/ru-translations
fixed ru translations file
2024-02-26 04:17:42 -08:00
vladimir.kuznetsov 82c0b28906 fixed ru translations file 2024-02-26 17:12:46 +05:00
KsZnak 985fe083f0 split-tunneling translate (#640)
Update amneziavpn_ru.ts
2024-02-26 11:53:22 +00:00
pokamest 6a0000dc4b Merge pull request #642 from amnezia-vpn/translations/update_zh_CN
Update amneziavpn_zh_CN.ts
2024-02-26 03:47:31 -08:00
pokamest 1dd2f38066 Merge pull request #643 from amnezia-vpn/translations/update_fa_IR.ts
Update amneziavpn_fa_IR.ts
2024-02-26 03:45:04 -08:00
pokamest 004e1e3ca5 MacOS GH actions QIF fix (#645)
Install Qt Installer Framework 4.6 from R2 to keep compatibility for old MacOS. In addition, update Qt version in build scripts.
2024-02-26 10:44:28 +00:00
KsZnak d3743ad62f Update amneziavpn_zh_CN.ts 2024-02-24 22:06:05 +02:00
Mykola Baibuz 9886987e68 Remove ipv6 address for Linux WG/AWG interface 2024-02-24 16:07:59 +02:00
130 changed files with 5922 additions and 1636 deletions
+7 -2
View File
@@ -227,7 +227,7 @@ jobs:
env: env:
# Keep compat with MacOS 10.15 aka Catalina by Qt 6.4 # Keep compat with MacOS 10.15 aka Catalina by Qt 6.4
QT_VERSION: 6.4.3 QT_VERSION: 6.4.3
QIF_VERSION: 4.7 QIF_VERSION: 4.6
steps: steps:
- name: 'Setup xcode' - name: 'Setup xcode'
@@ -245,10 +245,15 @@ jobs:
modules: 'qtremoteobjects qt5compat qtshadertools' modules: 'qtremoteobjects qt5compat qtshadertools'
dir: ${{ runner.temp }} dir: ${{ runner.temp }}
setup-python: 'true' setup-python: 'true'
tools: 'tools_ifw'
set-env: 'true' set-env: 'true'
extra: '--external 7z --base ${{ env.QT_MIRROR }}' extra: '--external 7z --base ${{ env.QT_MIRROR }}'
- name: 'Install Qt Installer Framework ${{ env.QIF_VERSION }}'
run: |
mkdir -pv ${{ runner.temp }}/Qt/Tools/QtInstallerFramework
wget https://qt.amzsvc.com/tools/ifw/${{ env.QIF_VERSION }}.zip
unzip ${{ env.QIF_VERSION }}.zip -d ${{ runner.temp }}/Qt/Tools/QtInstallerFramework/
- name: 'Get sources' - name: 'Get sources'
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
+2 -2
View File
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN) set(PROJECT AmneziaVPN)
project(${PROJECT} VERSION 4.4.0.0 project(${PROJECT} VERSION 4.4.1.4
DESCRIPTION "AmneziaVPN" DESCRIPTION "AmneziaVPN"
HOMEPAGE_URL "https://amnezia.org/" HOMEPAGE_URL "https://amnezia.org/"
) )
@@ -11,7 +11,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 46) set(APP_ANDROID_VERSION_CODE 49)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux") set(MZ_PLATFORM_NAME "linux")
+39 -35
View File
@@ -7,13 +7,15 @@
Amnezia is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server. Amnezia is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
## Features ## Features
- Very easy to use - enter your ip address, ssh login and password, and Amnezia will automatically install VPN docker containers to your server and connect to VPN.
- OpenVPN, ShadowSocks, WireGuard, IKEv2 protocols support. - Very easy to use - enter your IP address, SSH login, and password, and Amnezia will automatically install VPN docker containers to your server and connect to the VPN.
- OpenVPN, ShadowSocks, WireGuard, and IKEv2 protocols support.
- Masking VPN with OpenVPN over Cloak plugin - Masking VPN with OpenVPN over Cloak plugin
- Split tunneling support - add any sites to client to enable VPN only for them (only for desktops) - Split tunneling support - add any sites to the client to enable VPN only for them (only for desktops)
- Windows, MacOS, Linux, Android, iOS releases. - Windows, MacOS, Linux, Android, iOS releases.
## Links ## Links
[https://amnezia.org](https://amnezia.org) - project website [https://amnezia.org](https://amnezia.org) - project website
[https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit [https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit
[https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Telegram support channel (English) [https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Telegram support channel (English)
@@ -21,13 +23,13 @@ Amnezia is an open-source VPN client, with a key feature that enables you to dep
## Tech ## Tech
AmneziaVPN uses a number of open source projects to work: AmneziaVPN uses several open-source projects to work:
- [OpenSSL](https://www.openssl.org/) - [OpenSSL](https://www.openssl.org/)
- [OpenVPN](https://openvpn.net/) - [OpenVPN](https://openvpn.net/)
- [ShadowSocks](https://shadowsocks.org/) - [ShadowSocks](https://shadowsocks.org/)
- [Qt](https://www.qt.io/) - [Qt](https://www.qt.io/)
- [LibSsh](https://libssh.org) - forked form Qt Creator - [LibSsh](https://libssh.org) - forked from Qt Creator
- and more... - and more...
## Checking out the source code ## Checking out the source code
@@ -43,14 +45,15 @@ git submodule update --init --recursive
Want to contribute? Welcome! Want to contribute? Welcome!
### Building sources and deployment ### Building sources and deployment
Look deploy folder for build scripts.
### How to build iOS app from source code on MacOS Check deploy folder for build scripts.
### How to build an iOS app from source code on MacOS
1. First, make sure you have [XCode](https://developer.apple.com/xcode/) installed, at least version 14 or higher. 1. First, make sure you have [XCode](https://developer.apple.com/xcode/) installed, at least version 14 or higher.
2. We use QT to generate the XCode project. we need QT version 6.6.1. Install QT for macos in [here](https://doc.qt.io/qt-6/macos.html) or [QT Online Installer](https://www.qt.io/download-open-source). Required modules: 2. We use QT to generate the XCode project. We need QT version 6.6.1. Install QT for MacOS [here](https://doc.qt.io/qt-6/macos.html) or [QT Online Installer](https://www.qt.io/download-open-source). Required modules:
- macOS - MacOS
- iOS - iOS
- Qt 5 Compatibility Module - Qt 5 Compatibility Module
- Qt Shader Tools - Qt Shader Tools
@@ -59,18 +62,18 @@ Look deploy folder for build scripts.
- Qt Multimedia - Qt Multimedia
- Qt Remote Objects - Qt Remote Objects
3. Install cmake is require. We recommend cmake version 3.25. You can install cmake in [here](https://cmake.org/download/) 3. Install CMake if required. We recommend CMake version 3.25. You can install CMake [here](https://cmake.org/download/)
4. You also need to install go >= v1.16. If you don't have it done already, 4. You also need to install go >= v1.16. If you don't have it installed already,
download go from the [official website](https://golang.org/dl/) or use Homebrew. download go from the [official website](https://golang.org/dl/) or use Homebrew.
Latest version is recommended. Install gomobile The latest version is recommended. Install gomobile
```bash ```bash
export PATH=$PATH:~/go/bin export PATH=$PATH:~/go/bin
go install golang.org/x/mobile/cmd/gomobile@latest go install golang.org/x/mobile/cmd/gomobile@latest
gomobile init gomobile init
``` ```
5. Build project 5. Build the project
```bash ```bash
export QT_BIN_DIR="<PATH-TO-QT-FOLDER>/Qt/<QT-VERSION>/ios/bin" export QT_BIN_DIR="<PATH-TO-QT-FOLDER>/Qt/<QT-VERSION>/ios/bin"
export QT_MACOS_ROOT_DIR="<PATH-TO-QT-FOLDER>/Qt/<QT-VERSION>/macos" export QT_MACOS_ROOT_DIR="<PATH-TO-QT-FOLDER>/Qt/<QT-VERSION>/macos"
@@ -88,62 +91,63 @@ of the bin folder where gomobile was installed. Usually, it's in `GOPATH`.
export PATH=$(PATH):/path/to/GOPATH/bin export PATH=$(PATH):/path/to/GOPATH/bin
``` ```
5. Open XCode project. You can then run/test/archive/ship the app. 6. Open the XCode project. You can then run /test/archive/ship the app.
If build fails with the following error If the build fails with the following error
``` ```
make: *** make: ***
[$(PROJECTDIR)/client/build/AmneziaVPN.build/Debug-iphoneos/wireguard-go-bridge/goroot/.prepared] [$(PROJECTDIR)/client/build/AmneziaVPN.build/Debug-iphoneos/wireguard-go-bridge/goroot/.prepared]
Error 1 Error 1
``` ```
Add a user defined variable to both AmneziaVPN and WireGuardNetworkExtension targets' build settings with Add a user-defined variable to both AmneziaVPN and WireGuardNetworkExtension targets' build settings with
key `PATH` and value `${PATH}/path/to/bin/folder/with/go/executable`, e.g. `${PATH}:/usr/local/go/bin`. key `PATH` and value `${PATH}/path/to/bin/folder/with/go/executable`, e.g. `${PATH}:/usr/local/go/bin`.
if above error still persists on you M1 Mac, then most probably you need to install arch based cmake if the above error persists on your M1 Mac, then most probably you need to install arch based CMake
``` ```
arch -arm64 brew install cmake arch -arm64 brew install cmake
``` ```
Build might fail with "source files not found" error the first time you try it, because modern XCode build system compiles Build might fail with the "source files not found" error the first time you try it, because the modern XCode build system compiles dependencies in parallel, and some dependencies end up being built after the ones that
dependencies in parallel, and some dependencies end up being built after the ones that require them. In this case, simply restart the build.
require them. In this case simply restart the build.
## How to build the Android app ## How to build the Android app
_tested on Mac OS_
_Tested on Mac OS_
The Android app has the following requirements: The Android app has the following requirements:
* JDK 11 * JDK 11
* Android platform SDK 33 * Android platform SDK 33
* cmake 3.25.0 * CMake 3.25.0
After you have installed QT, QT Creator and Android Studio installed, you need to configure QT Creator correctly. Click in the top menu bar on `QT Creator` -> `Preferences` -> `Devices` and select the tab `Android`. After you have installed QT, QT Creator, and Android Studio, you need to configure QT Creator correctly. Click in the top menu bar on `QT Creator` -> `Preferences` -> `Devices` and select the tab `Android`.
* set path to jdk 11 * set path to JDK 11
* set path to Android SDK ($ANDROID_HOME) * set path to Android SDK ($ANDROID_HOME)
In case you get errors regarding missing SDK or 'sdkmanager not running', you cannot fix them by correcting the paths and you have some spare GBs on your disk, you can let QT Creator install all requirements by choosing an empty folder for `Android SDK location` and click on `Set Up SDK`. Be aware: This will install a second Android SDK and NDK on your machine! In case you get errors regarding missing SDK or 'SDK manager not running', you cannot fix them by correcting the paths. If you have some spare GBs on your disk, you can let QT Creator install all requirements by choosing an empty folder for `Android SDK location` and clicking on `Set Up SDK`. Be aware: This will install a second Android SDK and NDK on your machine! 
Double-check that the right CMake version is configured:  Click on `QT Creator` -> `Preferences` and click on the side menu on `Kits`. Under the center content view's `Kits` tab, you'll find an entry for `CMake Tool`. If the default selected CMake version is lower than 3.25.0, install on your system CMake >= 3.25.0 and choose `System CMake at <path>` from the drop-down list. If this entry is missing, you either have not installed CMake yet or QT Creator hasn't found the path to it. In that case, click in the preferences window on the side menu item `CMake`, then on the tab `Tools` in the center content view, and finally on the button `Add` to set the path to your installed CMake. 
Please make sure that you have selected Android Platform SDK 33 for your project: click in the main view's side menu on `Projects`, and on the left, you'll see a section `Build & Run` showing different Android build targets. You can select any of them, Amnezia VPN's project setup is designed in a way that all Android targets will be built. Click on the targets submenu item `Build` and scroll in the center content view to `Build Steps`. Click on `Details` at the end of the headline `Build Android APK` (the `Details` button might be hidden in case the QT Creator Window is not running in full screen!). Here we are: Choose `android-33` as `Android Build Platform SDK`.
Double check that the right cmake version is configured: Click on `QT Creator` -> `Preferences` and click on the side menu on `Kits`. Under the center content view's `Kits` tab you'll find an entry `CMake Tool`. If the default selected CMake version is lower than 3.25.0, install on your system CMake >= 3.25.0 and choose `System CMake at <path>` from the drop down list. If this entry is missing, you either have not installed CMake yet or QT Creator hasn't found the path to it. In that case click in the preferences window on the side menu item `CMake`, then on the tab `Tools`in the center content view and finally on the Button `Add` to set the path to your installed CMake. That's it! You should be ready to compile the project from QT Creator!
Please make sure that you have selected Android Platform SDK 33 for your project: click in the main view's side menu on on `Projects`, on the left you'll see a section `Build & Run` showing different Android build targets. You can select any of them, Amnezia VPN's project setup is designed in a way that always all Android targets will be build. Click on the targets submenu item `Build` and scroll in the center content view to `Build Steps`. Click on `Details` at the end of the headline `Build Android APK` (The `Details` button might be hidden in case QT Creator Window is not running in full screen!). Here we are: choose `android-33` as `Android Build platform SDK`.
That's it you should be ready to compile the project from QT Creator!
### Development flow ### Development flow
After you've hit the build button, QT-Creator copies the whole project to a folder in the repositories parent directory. The folder should look something like `build-amnezia-client-Android_Qt_<version>_Clang_<architecture>-<BuildType>`.
If you want to develop Amnezia VPNs Android components written in Kotlin, such as components using system APIs, you need to import the generated project in Android Studio with `build-amnezia-client-Android_Qt_<version>_Clang_<architecture>-<BuildType>/client/android-build` as the projects root directory. While you should be able to compile the generated project from Android Studio, you cannot work directly in the repository's Android project. So whenever you are confident with your work in the generated project, you'll need to copy and paste the affected files to the corresponding path in the repositories Android project so that you can add and commit your changes!
You may face compiling issues in QT Creator after you've worked in Android Studio on the generated project. Just do a `./gradlew clean` in the generated project's root directory (`<path>/client/android-build/.`) and you should be good to continue. After you've hit the build button, QT-Creator copies the whole project to a folder in the repository parent directory. The folder should look something like `build-amnezia-client-Android_Qt_<version>_Clang_<architecture>-<BuildType>`.
If you want to develop Amnezia VPNs Android components written in Kotlin, such as components using system APIs, you need to import the generated project in Android Studio with `build-amnezia-client-Android_Qt_<version>_Clang_<architecture>-<BuildType>/client/android-build` as the projects root directory. While you should be able to compile the generated project from Android Studio, you cannot work directly in the repository's Android project. So whenever you are confident with your work in the generated project, you'll need to copy and paste the affected files to the corresponding path in the repository's Android project so that you can add and commit your changes!
You may face compiling issues in QT Creator after you've worked in Android Studio on the generated project. Just do a `./gradlew clean` in the generated project's root directory (`<path>/client/android-build/.`) and you should be good to go.
## License ## License
GPL v.3
GPL v3.0
## Donate ## Donate
Bitcoin: bc1qn9rhsffuxwnhcuuu4qzrwp4upkrq94xnh8r26u Bitcoin: bc1qn9rhsffuxwnhcuuu4qzrwp4upkrq94xnh8r26u
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3 XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3
payeer.com: P2561305 payeer.com: P2561305
ko-fi.com: [https://ko-fi.com/amnezia_vpn](https://ko-fi.com/amnezia_vpn) ko-fi.com: [https://ko-fi.com/amnezia_vpn](https://ko-fi.com/amnezia_vpn)
## Acknowledgments
## etc
This project is tested with BrowserStack. This project is tested with BrowserStack.
We express our gratitude to [BrowserStack](https://www.browserstack.com) for supporting our project. We express our gratitude to [BrowserStack](https://www.browserstack.com) for supporting our project.
+1
View File
@@ -67,6 +67,7 @@ set(AMNEZIAVPN_TS_FILES
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_zh_CN.ts ${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_zh_CN.ts
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_fa_IR.ts ${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_fa_IR.ts
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ar.ts ${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ar.ts
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_my_MM.ts
) )
file(GLOB_RECURSE AMNEZIAVPN_TS_SOURCES *.qrc *.cpp *.h *.ui) file(GLOB_RECURSE AMNEZIAVPN_TS_SOURCES *.qrc *.cpp *.h *.ui)
+21 -1
View File
@@ -24,6 +24,7 @@
#if defined(Q_OS_IOS) #if defined(Q_OS_IOS)
#include "platforms/ios/ios_controller.h" #include "platforms/ios/ios_controller.h"
#include <AmneziaVPN-Swift.h>
#endif #endif
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
@@ -95,7 +96,18 @@ void AmneziaApplication::init()
qFatal("Android logging initialization failed"); qFatal("Android logging initialization failed");
} }
AndroidController::instance()->setSaveLogs(m_settings->isSaveLogs()); AndroidController::instance()->setSaveLogs(m_settings->isSaveLogs());
connect(m_settings.get(), &Settings::saveLogsChanged, AndroidController::instance(), &AndroidController::setSaveLogs); connect(m_settings.get(), &Settings::saveLogsChanged,
AndroidController::instance(), &AndroidController::setSaveLogs);
AndroidController::instance()->setScreenshotsEnabled(m_settings->isScreenshotsEnabled());
connect(m_settings.get(), &Settings::screenshotsEnabledChanged,
AndroidController::instance(), &AndroidController::setScreenshotsEnabled);
connect(m_settings.get(), &Settings::serverRemoved,
AndroidController::instance(), &AndroidController::resetLastServer);
connect(m_settings.get(), &Settings::settingsCleared,
[](){ AndroidController::instance()->resetLastServer(-1); });
connect(AndroidController::instance(), &AndroidController::initConnectionState, this, connect(AndroidController::instance(), &AndroidController::initConnectionState, this,
[this](Vpn::ConnectionState state) { [this](Vpn::ConnectionState state) {
@@ -127,6 +139,14 @@ void AmneziaApplication::init()
m_pageController->goToPageSettingsBackup(); m_pageController->goToPageSettingsBackup();
m_settingsController->importBackupFromOutside(filePath); m_settingsController->importBackupFromOutside(filePath);
}); });
QTimer::singleShot(0, this, [this](){
AmneziaVPN::toggleScreenshots(m_settings->isScreenshotsEnabled());
});
connect(m_settings.get(), &Settings::screenshotsEnabledChanged, [](bool enabled) {
AmneziaVPN::toggleScreenshots(enabled);
});
#endif #endif
m_notificationHandler.reset(NotificationHandler::create(nullptr)); m_notificationHandler.reset(NotificationHandler::create(nullptr));
+20
View File
@@ -56,6 +56,10 @@
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
</intent-filter> </intent-filter>
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
</intent-filter>
<meta-data <meta-data
android:name="android.app.lib_name" android:name="android.app.lib_name"
android:value="-- %%INSERT_APP_LIB_NAME%% --" /> android:value="-- %%INSERT_APP_LIB_NAME%% --" />
@@ -146,6 +150,22 @@
</intent-filter> </intent-filter>
</service> </service>
<service
android:name=".AmneziaTileService"
android:process=":amneziaTileService"
android:icon="@drawable/ic_amnezia_round"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
android:exported="true">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE" />
</intent-filter>
<meta-data
android:name="android.service.quicksettings.TOGGLEABLE_TILE"
android:value="true" />
</service>
<provider <provider
android:name="androidx.core.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="org.amnezia.vpn.qtprovider" android:authorities="org.amnezia.vpn.qtprovider"
+1 -1
View File
@@ -64,7 +64,7 @@ class Awg : Wireguard() {
val configDataJson = config.getJSONObject("awg_config_data") val configDataJson = config.getJSONObject("awg_config_data")
val configData = parseConfigData(configDataJson.getString("config")) val configData = parseConfigData(configDataJson.getString("config"))
return AwgConfig.build { return AwgConfig.build {
configWireguard(configData) configWireguard(configData, configDataJson)
configSplitTunneling(config) configSplitTunneling(config)
configData["Jc"]?.let { setJc(it.toInt()) } configData["Jc"]?.let { setJc(it.toInt()) }
configData["Jmin"]?.let { setJmin(it.toInt()) } configData["Jmin"]?.let { setJmin(it.toInt()) }
+1
View File
@@ -111,4 +111,5 @@ dependencies {
implementation(libs.kotlinx.coroutines) implementation(libs.kotlinx.coroutines)
implementation(libs.bundles.androidx.camera) implementation(libs.bundles.androidx.camera)
implementation(libs.google.mlkit) implementation(libs.google.mlkit)
implementation(libs.androidx.datastore)
} }
+2
View File
@@ -6,6 +6,7 @@ androidx-activity = "1.8.1"
androidx-annotation = "1.7.0" androidx-annotation = "1.7.0"
androidx-camera = "1.3.0" androidx-camera = "1.3.0"
androidx-security-crypto = "1.1.0-alpha06" androidx-security-crypto = "1.1.0-alpha06"
androidx-datastore = "1.1.0-beta01"
kotlinx-coroutines = "1.7.3" kotlinx-coroutines = "1.7.3"
google-mlkit = "17.2.0" google-mlkit = "17.2.0"
@@ -18,6 +19,7 @@ androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.r
androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "androidx-camera" } androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "androidx-camera" }
androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "androidx-camera" } androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "androidx-camera" }
androidx-security-crypto = { module = "androidx.security:security-crypto-ktx", version.ref = "androidx-security-crypto" } androidx-security-crypto = { module = "androidx.security:security-crypto-ktx", version.ref = "androidx-security-crypto" }
androidx-datastore = { module = "androidx.datastore:datastore-preferences", version.ref = "androidx-datastore" }
kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" } kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" }
google-mlkit = { module = "com.google.mlkit:barcode-scanning", version.ref = "google-mlkit" } google-mlkit = { module = "com.google.mlkit:barcode-scanning", version.ref = "google-mlkit" }
@@ -2,9 +2,9 @@ package org.amnezia.vpn.protocol
// keep synchronized with client/platforms/android/android_controller.h ConnectionState // keep synchronized with client/platforms/android/android_controller.h ConnectionState
enum class ProtocolState { enum class ProtocolState {
DISCONNECTED,
CONNECTED, CONNECTED,
CONNECTING, CONNECTING,
DISCONNECTED,
DISCONNECTING, DISCONNECTING,
RECONNECTING, RECONNECTING,
UNKNOWN UNKNOWN
@@ -28,6 +28,10 @@ fun Bundle.putStatus(status: Status) {
putInt(STATE_KEY, status.state.ordinal) putInt(STATE_KEY, status.state.ordinal)
} }
fun Bundle.putStatus(state: ProtocolState) {
putInt(STATE_KEY, state.ordinal)
}
fun Bundle.getStatus(): Status = fun Bundle.getStatus(): Status =
Status.build { Status.build {
setState(ProtocolState.entries[getInt(STATE_KEY)]) setState(ProtocolState.entries[getInt(STATE_KEY)])
+5
View File
@@ -0,0 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<string name="connecting">Подключение</string>
<string name="disconnecting">Отключение</string>
</resources>
+5
View File
@@ -0,0 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<string name="connecting">Connecting</string>
<string name="disconnecting">Disconnecting</string>
</resources>
@@ -14,6 +14,7 @@ import android.os.IBinder
import android.os.Looper import android.os.Looper
import android.os.Message import android.os.Message
import android.os.Messenger import android.os.Messenger
import android.view.WindowManager.LayoutParams
import android.webkit.MimeTypeMap import android.webkit.MimeTypeMap
import android.widget.Toast import android.widget.Toast
import androidx.annotation.MainThread import androidx.annotation.MainThread
@@ -26,9 +27,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.amnezia.vpn.protocol.ProtocolState
import org.amnezia.vpn.protocol.getStatistics import org.amnezia.vpn.protocol.getStatistics
import org.amnezia.vpn.protocol.getStatus import org.amnezia.vpn.protocol.getStatus
import org.amnezia.vpn.qt.QtAndroidController import org.amnezia.vpn.qt.QtAndroidController
@@ -36,11 +35,11 @@ import org.amnezia.vpn.util.Log
import org.qtproject.qt.android.bindings.QtActivity import org.qtproject.qt.android.bindings.QtActivity
private const val TAG = "AmneziaActivity" private const val TAG = "AmneziaActivity"
const val ACTIVITY_MESSENGER_NAME = "Activity"
private const val CHECK_VPN_PERMISSION_ACTION_CODE = 1 private const val CHECK_VPN_PERMISSION_ACTION_CODE = 1
private const val CREATE_FILE_ACTION_CODE = 2 private const val CREATE_FILE_ACTION_CODE = 2
private const val OPEN_FILE_ACTION_CODE = 3 private const val OPEN_FILE_ACTION_CODE = 3
private const val BIND_SERVICE_TIMEOUT = 1000L
class AmneziaActivity : QtActivity() { class AmneziaActivity : QtActivity() {
@@ -58,25 +57,17 @@ class AmneziaActivity : QtActivity() {
val event = msg.extractIpcMessage<ServiceEvent>() val event = msg.extractIpcMessage<ServiceEvent>()
Log.d(TAG, "Handle event: $event") Log.d(TAG, "Handle event: $event")
when (event) { when (event) {
ServiceEvent.CONNECTED -> { ServiceEvent.STATUS_CHANGED -> {
QtAndroidController.onVpnConnected() msg.data?.getStatus()?.let { (state) ->
} Log.d(TAG, "Handle protocol state: $state")
QtAndroidController.onVpnStateChanged(state.ordinal)
ServiceEvent.DISCONNECTED -> { }
QtAndroidController.onVpnDisconnected()
doUnbindService()
}
ServiceEvent.RECONNECTING -> {
QtAndroidController.onVpnReconnecting()
} }
ServiceEvent.STATUS -> { ServiceEvent.STATUS -> {
if (isWaitingStatus) { if (isWaitingStatus) {
isWaitingStatus = false isWaitingStatus = false
msg.data?.getStatus()?.let { (state) -> msg.data?.getStatus()?.let { QtAndroidController.onStatus(it) }
QtAndroidController.onStatus(state.ordinal)
}
} }
} }
@@ -87,7 +78,7 @@ class AmneziaActivity : QtActivity() {
} }
ServiceEvent.ERROR -> { ServiceEvent.ERROR -> {
msg.data?.getString(ERROR_MSG)?.let { error -> msg.data?.getString(MSG_ERROR)?.let { error ->
Log.e(TAG, "From VpnService: $error") Log.e(TAG, "From VpnService: $error")
} }
// todo: add error reporting to Qt // todo: add error reporting to Qt
@@ -109,14 +100,15 @@ class AmneziaActivity : QtActivity() {
// get a messenger from the service to send actions to the service // get a messenger from the service to send actions to the service
vpnServiceMessenger.set(Messenger(service)) vpnServiceMessenger.set(Messenger(service))
// send a messenger to the service to process service events // send a messenger to the service to process service events
vpnServiceMessenger.send { vpnServiceMessenger.send(
Action.REGISTER_CLIENT.packToMessage().apply { Action.REGISTER_CLIENT.packToMessage {
replyTo = activityMessenger putString(MSG_CLIENT_NAME, ACTIVITY_MESSENGER_NAME)
} },
} replyTo = activityMessenger
)
isServiceConnected = true isServiceConnected = true
if (isWaitingStatus) { if (isWaitingStatus) {
vpnServiceMessenger.send(Action.REQUEST_STATUS) vpnServiceMessenger.send(Action.REQUEST_STATUS, replyTo = activityMessenger)
} }
} }
@@ -126,6 +118,7 @@ class AmneziaActivity : QtActivity() {
vpnServiceMessenger.reset() vpnServiceMessenger.reset()
isWaitingStatus = true isWaitingStatus = true
QtAndroidController.onServiceDisconnected() QtAndroidController.onServiceDisconnected()
doBindService()
} }
override fun onBindingDied(name: ComponentName?) { override fun onBindingDied(name: ComponentName?) {
@@ -148,8 +141,11 @@ class AmneziaActivity : QtActivity() {
Log.d(TAG, "Create Amnezia activity: $intent") Log.d(TAG, "Create Amnezia activity: $intent")
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
vpnServiceMessenger = IpcMessenger( vpnServiceMessenger = IpcMessenger(
onDeadObjectException = ::doUnbindService, "VpnService",
messengerName = "VpnService" onDeadObjectException = {
doUnbindService()
doBindService()
}
) )
intent?.let(::processIntent) intent?.let(::processIntent)
} }
@@ -244,10 +240,9 @@ class AmneziaActivity : QtActivity() {
private fun doBindService() { private fun doBindService() {
Log.d(TAG, "Bind service") Log.d(TAG, "Bind service")
Intent(this, AmneziaVpnService::class.java).also { Intent(this, AmneziaVpnService::class.java).also {
bindService(it, serviceConnection, BIND_ABOVE_CLIENT) bindService(it, serviceConnection, BIND_ABOVE_CLIENT and BIND_AUTO_CREATE)
} }
isInBoundState = true isInBoundState = true
handleBindTimeout()
} }
@MainThread @MainThread
@@ -256,26 +251,14 @@ class AmneziaActivity : QtActivity() {
Log.d(TAG, "Unbind service") Log.d(TAG, "Unbind service")
isWaitingStatus = true isWaitingStatus = true
QtAndroidController.onServiceDisconnected() QtAndroidController.onServiceDisconnected()
vpnServiceMessenger.reset()
isServiceConnected = false isServiceConnected = false
vpnServiceMessenger.send(Action.UNREGISTER_CLIENT, activityMessenger)
vpnServiceMessenger.reset()
isInBoundState = false isInBoundState = false
unbindService(serviceConnection) unbindService(serviceConnection)
} }
} }
private fun handleBindTimeout() {
mainScope.launch {
if (isWaitingStatus) {
delay(BIND_SERVICE_TIMEOUT)
if (isWaitingStatus && !isServiceConnected) {
Log.d(TAG, "Bind timeout, reset connection status")
isWaitingStatus = false
QtAndroidController.onStatus(ProtocolState.DISCONNECTED.ordinal)
}
}
}
}
/** /**
* Methods of starting and stopping VpnService * Methods of starting and stopping VpnService
*/ */
@@ -312,7 +295,7 @@ class AmneziaActivity : QtActivity() {
Log.d(TAG, "Connect to VPN") Log.d(TAG, "Connect to VPN")
vpnServiceMessenger.send { vpnServiceMessenger.send {
Action.CONNECT.packToMessage { Action.CONNECT.packToMessage {
putString(VPN_CONFIG, vpnConfig) putString(MSG_VPN_CONFIG, vpnConfig)
} }
} }
} }
@@ -320,7 +303,7 @@ class AmneziaActivity : QtActivity() {
private fun startVpnService(vpnConfig: String) { private fun startVpnService(vpnConfig: String) {
Log.d(TAG, "Start VPN service") Log.d(TAG, "Start VPN service")
Intent(this, AmneziaVpnService::class.java).apply { Intent(this, AmneziaVpnService::class.java).apply {
putExtra(VPN_CONFIG, vpnConfig) putExtra(MSG_VPN_CONFIG, vpnConfig)
}.also { }.also {
ContextCompat.startForegroundService(this, it) ContextCompat.startForegroundService(this, it)
} }
@@ -369,6 +352,22 @@ class AmneziaActivity : QtActivity() {
} }
} }
@Suppress("unused")
fun resetLastServer(index: Int) {
Log.v(TAG, "Reset server: $index")
mainScope.launch {
VpnStateStore.store {
if (index == -1 || it.serverIndex == index) {
VpnState.defaultState
} else if (it.serverIndex > index) {
it.copy(serverIndex = it.serverIndex - 1)
} else {
it
}
}
}
}
@Suppress("unused") @Suppress("unused")
fun saveFile(fileName: String, data: String) { fun saveFile(fileName: String, data: String) {
Log.d(TAG, "Save file $fileName") Log.d(TAG, "Save file $fileName")
@@ -438,7 +437,7 @@ class AmneziaActivity : QtActivity() {
Log.saveLogs = enabled Log.saveLogs = enabled
vpnServiceMessenger.send { vpnServiceMessenger.send {
Action.SET_SAVE_LOGS.packToMessage { Action.SET_SAVE_LOGS.packToMessage {
putBoolean(SAVE_LOGS, enabled) putBoolean(MSG_SAVE_LOGS, enabled)
} }
} }
} }
@@ -455,4 +454,13 @@ class AmneziaActivity : QtActivity() {
Log.v(TAG, "Clear logs") Log.v(TAG, "Clear logs")
Log.clearLogs() Log.clearLogs()
} }
@Suppress("unused")
fun setScreenshotsEnabled(enabled: Boolean) {
Log.v(TAG, "Set screenshots enabled: $enabled")
mainScope.launch {
val flag = if (enabled) 0 else LayoutParams.FLAG_SECURE
window.setFlags(flag, LayoutParams.FLAG_SECURE)
}
}
} }
@@ -18,6 +18,7 @@ class AmneziaApplication : QtApplication(), CameraXConfig.Provider {
super.onCreate() super.onCreate()
Prefs.init(this) Prefs.init(this)
Log.init(this) Log.init(this)
VpnStateStore.init(this)
Log.d(TAG, "Create Amnezia application") Log.d(TAG, "Create Amnezia application")
createNotificationChannel() createNotificationChannel()
} }
@@ -0,0 +1,272 @@
package org.amnezia.vpn
import android.annotation.SuppressLint
import android.app.PendingIntent
import android.content.ComponentName
import android.content.Intent
import android.content.ServiceConnection
import android.net.VpnService
import android.os.Build
import android.os.IBinder
import android.os.Messenger
import android.service.quicksettings.Tile
import android.service.quicksettings.TileService
import androidx.core.content.ContextCompat
import kotlin.LazyThreadSafetyMode.NONE
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import org.amnezia.vpn.protocol.ProtocolState
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
import org.amnezia.vpn.protocol.ProtocolState.CONNECTING
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTING
import org.amnezia.vpn.protocol.ProtocolState.RECONNECTING
import org.amnezia.vpn.protocol.ProtocolState.UNKNOWN
import org.amnezia.vpn.util.Log
private const val TAG = "AmneziaTileService"
private const val DEFAULT_TILE_LABEL = "AmneziaVPN"
class AmneziaTileService : TileService() {
private lateinit var scope: CoroutineScope
private var vpnStateListeningJob: Job? = null
private lateinit var vpnServiceMessenger: IpcMessenger
@Volatile
private var isServiceConnected = false
private var isInBoundState = false
@Volatile
private var isVpnConfigExists = false
private val serviceConnection: ServiceConnection by lazy(NONE) {
object : ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
Log.d(TAG, "Service ${name?.flattenToString()} was connected")
vpnServiceMessenger.set(Messenger(service))
isServiceConnected = true
}
override fun onServiceDisconnected(name: ComponentName?) {
Log.w(TAG, "Service ${name?.flattenToString()} was unexpectedly disconnected")
isServiceConnected = false
vpnServiceMessenger.reset()
updateVpnState(DISCONNECTED)
}
override fun onBindingDied(name: ComponentName?) {
Log.w(TAG, "Binding to the ${name?.flattenToString()} unexpectedly died")
doUnbindService()
doBindService()
}
}
}
override fun onCreate() {
super.onCreate()
Log.d(TAG, "Create Amnezia Tile Service")
scope = CoroutineScope(SupervisorJob())
vpnServiceMessenger = IpcMessenger(
"VpnService",
onDeadObjectException = ::doUnbindService
)
}
override fun onDestroy() {
Log.d(TAG, "Destroy Amnezia Tile Service")
doUnbindService()
scope.cancel()
super.onDestroy()
}
// Workaround for some bugs
override fun onBind(intent: Intent?): IBinder? =
try {
super.onBind(intent)
} catch (e: Throwable) {
Log.e(TAG, "Failed to bind AmneziaTileService: $e")
null
}
override fun onStartListening() {
super.onStartListening()
Log.d(TAG, "Start listening")
if (AmneziaVpnService.isRunning(applicationContext)) {
Log.d(TAG, "Vpn service is running")
doBindService()
} else {
Log.d(TAG, "Vpn service is not running")
isServiceConnected = false
updateVpnState(DISCONNECTED)
}
vpnStateListeningJob = launchVpnStateListening()
}
override fun onStopListening() {
Log.d(TAG, "Stop listening")
vpnStateListeningJob?.cancel()
vpnStateListeningJob = null
doUnbindService()
super.onStopListening()
}
override fun onClick() {
Log.d(TAG, "onClick")
if (isLocked) {
unlockAndRun { onClickInternal() }
} else {
onClickInternal()
}
}
private fun onClickInternal() {
if (isVpnConfigExists) {
Log.d(TAG, "Change VPN state")
if (qsTile.state == Tile.STATE_INACTIVE) {
Log.d(TAG, "Start VPN")
updateVpnState(CONNECTING)
startVpn()
} else if (qsTile.state == Tile.STATE_ACTIVE) {
Log.d(TAG, "Stop vpn")
updateVpnState(DISCONNECTING)
stopVpn()
}
} else {
Log.d(TAG, "Start Activity")
Intent(this, AmneziaActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}.also {
startActivityAndCollapseCompat(it)
}
}
}
private fun doBindService() {
Log.d(TAG, "Bind service")
Intent(this, AmneziaVpnService::class.java).also {
bindService(it, serviceConnection, BIND_ABOVE_CLIENT)
}
isInBoundState = true
}
private fun doUnbindService() {
if (isInBoundState) {
Log.d(TAG, "Unbind service")
isServiceConnected = false
vpnServiceMessenger.reset()
isInBoundState = false
unbindService(serviceConnection)
}
}
private fun startVpn() {
if (isServiceConnected) {
connectToVpn()
} else {
if (checkPermission()) {
startVpnService()
doBindService()
} else {
updateVpnState(DISCONNECTED)
}
}
}
private fun checkPermission() =
if (VpnService.prepare(applicationContext) != null) {
Intent(this, VpnRequestActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}.also {
startActivityAndCollapseCompat(it)
}
false
} else {
true
}
private fun startVpnService() =
ContextCompat.startForegroundService(
applicationContext,
Intent(this, AmneziaVpnService::class.java)
)
private fun connectToVpn() = vpnServiceMessenger.send(Action.CONNECT)
private fun stopVpn() = vpnServiceMessenger.send(Action.DISCONNECT)
@SuppressLint("StartActivityAndCollapseDeprecated")
private fun startActivityAndCollapseCompat(intent: Intent) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
startActivityAndCollapse(
PendingIntent.getActivity(
applicationContext,
0,
intent,
PendingIntent.FLAG_IMMUTABLE
)
)
} else {
@Suppress("DEPRECATION")
startActivityAndCollapse(intent)
}
}
private fun updateVpnState(state: ProtocolState) {
scope.launch {
VpnStateStore.store { it.copy(protocolState = state) }
}
}
private fun launchVpnStateListening() =
scope.launch { VpnStateStore.dataFlow().collectLatest(::updateTile) }
private fun updateTile(vpnState: VpnState) {
Log.d(TAG, "Update tile: $vpnState")
isVpnConfigExists = vpnState.serverName != null
val tile = qsTile ?: return
tile.apply {
label = vpnState.serverName ?: DEFAULT_TILE_LABEL
when (vpnState.protocolState) {
CONNECTED -> {
state = Tile.STATE_ACTIVE
subtitleCompat = null
}
DISCONNECTED, UNKNOWN -> {
state = Tile.STATE_INACTIVE
subtitleCompat = null
}
CONNECTING, RECONNECTING -> {
state = Tile.STATE_UNAVAILABLE
subtitleCompat = resources.getString(R.string.connecting)
}
DISCONNECTING -> {
state = Tile.STATE_UNAVAILABLE
subtitleCompat = resources.getString(R.string.disconnecting)
}
}
updateTile()
}
// double update to fix weird visual glitches
tile.updateTile()
}
private var Tile.subtitleCompat: CharSequence?
set(value) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
this.subtitle = value
}
}
get() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
return this.subtitle
}
return null
}
}
@@ -1,7 +1,10 @@
package org.amnezia.vpn package org.amnezia.vpn
import android.app.ActivityManager
import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE
import android.app.Notification import android.app.Notification
import android.app.PendingIntent import android.app.PendingIntent
import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED
@@ -16,6 +19,7 @@ import android.os.Process
import androidx.annotation.MainThread import androidx.annotation.MainThread
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.ServiceCompat import androidx.core.app.ServiceCompat
import java.util.concurrent.ConcurrentHashMap
import kotlin.LazyThreadSafetyMode.NONE import kotlin.LazyThreadSafetyMode.NONE
import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@@ -26,6 +30,7 @@ import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@@ -39,14 +44,11 @@ import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTING import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTING
import org.amnezia.vpn.protocol.ProtocolState.RECONNECTING import org.amnezia.vpn.protocol.ProtocolState.RECONNECTING
import org.amnezia.vpn.protocol.ProtocolState.UNKNOWN import org.amnezia.vpn.protocol.ProtocolState.UNKNOWN
import org.amnezia.vpn.protocol.Statistics
import org.amnezia.vpn.protocol.Status
import org.amnezia.vpn.protocol.VpnException import org.amnezia.vpn.protocol.VpnException
import org.amnezia.vpn.protocol.VpnStartException import org.amnezia.vpn.protocol.VpnStartException
import org.amnezia.vpn.protocol.awg.Awg import org.amnezia.vpn.protocol.awg.Awg
import org.amnezia.vpn.protocol.cloak.Cloak import org.amnezia.vpn.protocol.cloak.Cloak
import org.amnezia.vpn.protocol.openvpn.OpenVpn import org.amnezia.vpn.protocol.openvpn.OpenVpn
import org.amnezia.vpn.protocol.putStatistics
import org.amnezia.vpn.protocol.putStatus import org.amnezia.vpn.protocol.putStatus
import org.amnezia.vpn.protocol.wireguard.Wireguard import org.amnezia.vpn.protocol.wireguard.Wireguard
import org.amnezia.vpn.util.Log import org.amnezia.vpn.util.Log
@@ -57,12 +59,16 @@ import org.json.JSONObject
private const val TAG = "AmneziaVpnService" private const val TAG = "AmneziaVpnService"
const val VPN_CONFIG = "VPN_CONFIG" const val MSG_VPN_CONFIG = "VPN_CONFIG"
const val ERROR_MSG = "ERROR_MSG" const val MSG_ERROR = "ERROR"
const val SAVE_LOGS = "SAVE_LOGS" const val MSG_SAVE_LOGS = "SAVE_LOGS"
const val MSG_CLIENT_NAME = "CLIENT_NAME"
const val AFTER_PERMISSION_CHECK = "AFTER_PERMISSION_CHECK" const val AFTER_PERMISSION_CHECK = "AFTER_PERMISSION_CHECK"
private const val PREFS_CONFIG_KEY = "LAST_CONF" private const val PREFS_CONFIG_KEY = "LAST_CONF"
private const val PREFS_SERVER_NAME = "LAST_SERVER_NAME"
private const val PREFS_SERVER_INDEX = "LAST_SERVER_INDEX"
private const val PROCESS_NAME = "org.amnezia.vpn:amneziaVpnService"
private const val NOTIFICATION_ID = 1337 private const val NOTIFICATION_ID = 1337
private const val STATISTICS_SENDING_TIMEOUT = 1000L private const val STATISTICS_SENDING_TIMEOUT = 1000L
private const val DISCONNECT_TIMEOUT = 5000L private const val DISCONNECT_TIMEOUT = 5000L
@@ -76,6 +82,8 @@ class AmneziaVpnService : VpnService() {
private var protocol: Protocol? = null private var protocol: Protocol? = null
private val protocolCache = mutableMapOf<String, Protocol>() private val protocolCache = mutableMapOf<String, Protocol>()
private var protocolState = MutableStateFlow(UNKNOWN) private var protocolState = MutableStateFlow(UNKNOWN)
private var serverName: String? = null
private var serverIndex: Int = -1
private val isConnected private val isConnected
get() = protocolState.value == CONNECTED get() = protocolState.value == CONNECTED
@@ -89,8 +97,11 @@ class AmneziaVpnService : VpnService() {
private var connectionJob: Job? = null private var connectionJob: Job? = null
private var disconnectionJob: Job? = null private var disconnectionJob: Job? = null
private var statisticsSendingJob: Job? = null private var statisticsSendingJob: Job? = null
private lateinit var clientMessenger: IpcMessenger
private lateinit var networkState: NetworkState private lateinit var networkState: NetworkState
private val clientMessengers = ConcurrentHashMap<Messenger, IpcMessenger>()
private val isActivityConnected
get() = clientMessengers.any { it.value.name == ACTIVITY_MESSENGER_NAME }
private val connectionExceptionHandler = CoroutineExceptionHandler { _, e -> private val connectionExceptionHandler = CoroutineExceptionHandler { _, e ->
protocolState.value = DISCONNECTED protocolState.value = DISCONNECTED
@@ -116,13 +127,22 @@ class AmneziaVpnService : VpnService() {
Log.d(TAG, "Handle action: $action") Log.d(TAG, "Handle action: $action")
when (action) { when (action) {
Action.REGISTER_CLIENT -> { Action.REGISTER_CLIENT -> {
clientMessenger.set(msg.replyTo) val clientName = msg.data.getString(MSG_CLIENT_NAME)
val messenger = IpcMessenger(msg.replyTo, clientName)
clientMessengers[msg.replyTo] = messenger
Log.d(TAG, "Messenger client '$clientName' was registered")
if (clientName == ACTIVITY_MESSENGER_NAME && isConnected) launchSendingStatistics()
}
Action.UNREGISTER_CLIENT -> {
clientMessengers.remove(msg.replyTo)?.let {
Log.d(TAG, "Messenger client '${it.name}' was unregistered")
if (it.name == ACTIVITY_MESSENGER_NAME) stopSendingStatistics()
}
} }
Action.CONNECT -> { Action.CONNECT -> {
val vpnConfig = msg.data.getString(VPN_CONFIG) connect(msg.data.getString(MSG_VPN_CONFIG))
Prefs.save(PREFS_CONFIG_KEY, vpnConfig)
connect(vpnConfig)
} }
Action.DISCONNECT -> { Action.DISCONNECT -> {
@@ -130,17 +150,17 @@ class AmneziaVpnService : VpnService() {
} }
Action.REQUEST_STATUS -> { Action.REQUEST_STATUS -> {
clientMessenger.send { clientMessengers[msg.replyTo]?.let { clientMessenger ->
ServiceEvent.STATUS.packToMessage { clientMessenger.send {
putStatus(Status.build { ServiceEvent.STATUS.packToMessage {
setState(this@AmneziaVpnService.protocolState.value) putStatus(this@AmneziaVpnService.protocolState.value)
}) }
} }
} }
} }
Action.SET_SAVE_LOGS -> { Action.SET_SAVE_LOGS -> {
Log.saveLogs = msg.data.getBoolean(SAVE_LOGS) Log.saveLogs = msg.data.getBoolean(MSG_SAVE_LOGS)
} }
} }
} }
@@ -189,7 +209,7 @@ class AmneziaVpnService : VpnService() {
Log.d(TAG, "Create Amnezia VPN service") Log.d(TAG, "Create Amnezia VPN service")
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
connectionScope = CoroutineScope(SupervisorJob() + Dispatchers.IO + connectionExceptionHandler) connectionScope = CoroutineScope(SupervisorJob() + Dispatchers.IO + connectionExceptionHandler)
clientMessenger = IpcMessenger(messengerName = "Client") loadServerData()
launchProtocolStateHandler() launchProtocolStateHandler()
networkState = NetworkState(this, ::reconnect) networkState = NetworkState(this, ::reconnect)
} }
@@ -201,15 +221,13 @@ class AmneziaVpnService : VpnService() {
if (isAlwaysOnCompat) { if (isAlwaysOnCompat) {
Log.d(TAG, "Start service via Always-on") Log.d(TAG, "Start service via Always-on")
connect(Prefs.load(PREFS_CONFIG_KEY)) connect()
} else if (intent?.getBooleanExtra(AFTER_PERMISSION_CHECK, false) == true) { } else if (intent?.getBooleanExtra(AFTER_PERMISSION_CHECK, false) == true) {
Log.d(TAG, "Start service after permission check") Log.d(TAG, "Start service after permission check")
connect(Prefs.load(PREFS_CONFIG_KEY)) connect()
} else { } else {
Log.d(TAG, "Start service") Log.d(TAG, "Start service")
val vpnConfig = intent?.getStringExtra(VPN_CONFIG) connect(intent?.getStringExtra(MSG_VPN_CONFIG))
Prefs.save(PREFS_CONFIG_KEY, vpnConfig)
connect(vpnConfig)
} }
ServiceCompat.startForeground(this, NOTIFICATION_ID, notification, foregroundServiceTypeCompat) ServiceCompat.startForeground(this, NOTIFICATION_ID, notification, foregroundServiceTypeCompat)
return START_REDELIVER_INTENT return START_REDELIVER_INTENT
@@ -219,17 +237,16 @@ class AmneziaVpnService : VpnService() {
Log.d(TAG, "onBind by $intent") Log.d(TAG, "onBind by $intent")
if (intent?.action == SERVICE_INTERFACE) return super.onBind(intent) if (intent?.action == SERVICE_INTERFACE) return super.onBind(intent)
isServiceBound = true isServiceBound = true
if (isConnected) launchSendingStatistics()
return vpnServiceMessenger.binder return vpnServiceMessenger.binder
} }
override fun onUnbind(intent: Intent?): Boolean { override fun onUnbind(intent: Intent?): Boolean {
Log.d(TAG, "onUnbind by $intent") Log.d(TAG, "onUnbind by $intent")
if (intent?.action != SERVICE_INTERFACE) { if (intent?.action != SERVICE_INTERFACE) {
isServiceBound = false if (clientMessengers.isEmpty()) {
stopSendingStatistics() isServiceBound = false
clientMessenger.reset() if (isUnknown || isDisconnected) stopService()
if (isUnknown || isDisconnected) stopService() }
} }
return true return true
} }
@@ -238,7 +255,6 @@ class AmneziaVpnService : VpnService() {
Log.d(TAG, "onRebind by $intent") Log.d(TAG, "onRebind by $intent")
if (intent?.action != SERVICE_INTERFACE) { if (intent?.action != SERVICE_INTERFACE) {
isServiceBound = true isServiceBound = true
if (isConnected) launchSendingStatistics()
} }
super.onRebind(intent) super.onRebind(intent)
} }
@@ -278,17 +294,16 @@ class AmneziaVpnService : VpnService() {
*/ */
private fun launchProtocolStateHandler() { private fun launchProtocolStateHandler() {
mainScope.launch { mainScope.launch {
protocolState.collect { protocolState -> // drop first default UNKNOWN state
protocolState.drop(1).collect { protocolState ->
Log.d(TAG, "Protocol state changed: $protocolState") Log.d(TAG, "Protocol state changed: $protocolState")
when (protocolState) { when (protocolState) {
CONNECTED -> { CONNECTED -> {
clientMessenger.send(ServiceEvent.CONNECTED)
networkState.bindNetworkListener() networkState.bindNetworkListener()
if (isServiceBound) launchSendingStatistics() if (isActivityConnected) launchSendingStatistics()
} }
DISCONNECTED -> { DISCONNECTED -> {
clientMessenger.send(ServiceEvent.DISCONNECTED)
networkState.unbindNetworkListener() networkState.unbindNetworkListener()
stopSendingStatistics() stopSendingStatistics()
if (!isServiceBound) stopService() if (!isServiceBound) stopService()
@@ -300,12 +315,19 @@ class AmneziaVpnService : VpnService() {
} }
RECONNECTING -> { RECONNECTING -> {
clientMessenger.send(ServiceEvent.RECONNECTING)
stopSendingStatistics() stopSendingStatistics()
} }
CONNECTING, UNKNOWN -> {} CONNECTING, UNKNOWN -> {}
} }
clientMessengers.send {
ServiceEvent.STATUS_CHANGED.packToMessage {
putStatus(protocolState)
}
}
VpnStateStore.store { VpnState(protocolState, serverName, serverIndex) }
} }
} }
} }
@@ -332,7 +354,17 @@ class AmneziaVpnService : VpnService() {
} }
@MainThread @MainThread
private fun connect(vpnConfig: String?) { private fun connect(vpnConfig: String? = null) {
if (vpnConfig == null) {
connectToVpn(Prefs.load(PREFS_CONFIG_KEY))
} else {
Prefs.save(PREFS_CONFIG_KEY, vpnConfig)
connectToVpn(vpnConfig)
}
}
@MainThread
private fun connectToVpn(vpnConfig: String) {
if (isConnected || protocolState.value == CONNECTING) return if (isConnected || protocolState.value == CONNECTING) return
Log.d(TAG, "Start VPN connection") Log.d(TAG, "Start VPN connection")
@@ -340,6 +372,7 @@ class AmneziaVpnService : VpnService() {
protocolState.value = CONNECTING protocolState.value = CONNECTING
val config = parseConfigToJson(vpnConfig) val config = parseConfigToJson(vpnConfig)
saveServerData(config)
if (config == null) { if (config == null) {
onError("Invalid VPN config") onError("Invalid VPN config")
protocolState.value = DISCONNECTED protocolState.value = DISCONNECTED
@@ -417,24 +450,38 @@ class AmneziaVpnService : VpnService() {
private fun onError(msg: String) { private fun onError(msg: String) {
Log.e(TAG, msg) Log.e(TAG, msg)
mainScope.launch { mainScope.launch {
clientMessenger.send { clientMessengers.send {
ServiceEvent.ERROR.packToMessage { ServiceEvent.ERROR.packToMessage {
putString(ERROR_MSG, msg) putString(MSG_ERROR, msg)
} }
} }
} }
} }
private fun parseConfigToJson(vpnConfig: String?): JSONObject? = private fun parseConfigToJson(vpnConfig: String): JSONObject? =
try { if (vpnConfig.isBlank()) {
vpnConfig?.let {
JSONObject(it)
}
} catch (e: JSONException) {
onError("Invalid VPN config json format: ${e.message}")
null null
} else {
try {
JSONObject(vpnConfig)
} catch (e: JSONException) {
onError("Invalid VPN config json format: ${e.message}")
null
}
} }
private fun saveServerData(config: JSONObject?) {
serverName = config?.opt("description") as String?
serverIndex = config?.opt("serverIndex") as Int? ?: -1
Prefs.save(PREFS_SERVER_NAME, serverName)
Prefs.save(PREFS_SERVER_INDEX, serverIndex)
}
private fun loadServerData() {
serverName = Prefs.load<String>(PREFS_SERVER_NAME).ifBlank { null }
if (serverName != null) serverIndex = Prefs.load(PREFS_SERVER_INDEX)
}
private fun checkPermission(): Boolean = private fun checkPermission(): Boolean =
if (prepare(applicationContext) != null) { if (prepare(applicationContext) != null) {
Intent(this, VpnRequestActivity::class.java).apply { Intent(this, VpnRequestActivity::class.java).apply {
@@ -446,4 +493,12 @@ class AmneziaVpnService : VpnService() {
} else { } else {
true true
} }
companion object {
fun isRunning(context: Context): Boolean =
(context.getSystemService(ACTIVITY_SERVICE) as ActivityManager)
.runningAppProcesses.any {
it.processName == PROCESS_NAME && it.importance <= IMPORTANCE_FOREGROUND_SERVICE
}
}
} }
@@ -20,9 +20,7 @@ sealed interface IpcMessage {
} }
enum class ServiceEvent : IpcMessage { enum class ServiceEvent : IpcMessage {
CONNECTED, STATUS_CHANGED,
DISCONNECTED,
RECONNECTING,
STATUS, STATUS,
STATISTICS_UPDATE, STATISTICS_UPDATE,
ERROR ERROR
@@ -30,6 +28,7 @@ enum class ServiceEvent : IpcMessage {
enum class Action : IpcMessage { enum class Action : IpcMessage {
REGISTER_CLIENT, REGISTER_CLIENT,
UNREGISTER_CLIENT,
CONNECT, CONNECT,
DISCONNECT, DISCONNECT,
REQUEST_STATUS, REQUEST_STATUS,
@@ -9,11 +9,21 @@ import org.amnezia.vpn.util.Log
private const val TAG = "IpcMessenger" private const val TAG = "IpcMessenger"
class IpcMessenger( class IpcMessenger(
messengerName: String? = null,
private val onDeadObjectException: () -> Unit = {}, private val onDeadObjectException: () -> Unit = {},
private val onRemoteException: () -> Unit = {}, private val onRemoteException: () -> Unit = {}
private val messengerName: String = "Unknown"
) { ) {
private var messenger: Messenger? = null private var messenger: Messenger? = null
val name = messengerName ?: "Unknown"
constructor(
messenger: Messenger,
messengerName: String? = null,
onDeadObjectException: () -> Unit = {},
onRemoteException: () -> Unit = {}
) : this(messengerName, onDeadObjectException, onRemoteException) {
this.messenger = messenger
}
fun set(messenger: Messenger) { fun set(messenger: Messenger) {
this.messenger = messenger this.messenger = messenger
@@ -25,19 +35,29 @@ class IpcMessenger(
fun send(msg: () -> Message) = messenger?.sendMsg(msg()) fun send(msg: () -> Message) = messenger?.sendMsg(msg())
fun send(msg: Message, replyTo: Messenger) = messenger?.sendMsg(msg.apply { this.replyTo = replyTo })
fun <T> send(msg: T) fun <T> send(msg: T)
where T : Enum<T>, T : IpcMessage = messenger?.sendMsg(msg.packToMessage()) where T : Enum<T>, T : IpcMessage = messenger?.sendMsg(msg.packToMessage())
fun <T> send(msg: T, replyTo: Messenger)
where T : Enum<T>, T : IpcMessage = messenger?.sendMsg(msg.packToMessage().apply { this.replyTo = replyTo })
private fun Messenger.sendMsg(msg: Message) { private fun Messenger.sendMsg(msg: Message) {
try { try {
send(msg) send(msg)
} catch (e: DeadObjectException) { } catch (e: DeadObjectException) {
Log.w(TAG, "$messengerName messenger is dead") Log.w(TAG, "$name messenger is dead")
messenger = null messenger = null
onDeadObjectException() onDeadObjectException()
} catch (e: RemoteException) { } catch (e: RemoteException) {
Log.w(TAG, "Sending a message to the $messengerName messenger failed: ${e.message}") Log.w(TAG, "Sending a message to the $name messenger failed: ${e.message}")
onRemoteException() onRemoteException()
} }
} }
} }
fun Map<Messenger, IpcMessenger>.send(msg: () -> Message) = this.values.forEach { it.send(msg) }
fun <T> Map<Messenger, IpcMessenger>.send(msg: T)
where T : Enum<T>, T : IpcMessage = this.values.forEach { it.send(msg) }
@@ -0,0 +1,75 @@
package org.amnezia.vpn
import android.app.Application
import androidx.datastore.core.MultiProcessDataStoreFactory
import androidx.datastore.core.Serializer
import androidx.datastore.dataStoreFile
import java.io.InputStream
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.io.OutputStream
import java.io.Serializable
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.withContext
import org.amnezia.vpn.protocol.ProtocolState
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
import org.amnezia.vpn.util.Log
private const val TAG = "VpnState"
private const val STORE_FILE_NAME = "vpnState"
data class VpnState(
val protocolState: ProtocolState,
val serverName: String? = null,
val serverIndex: Int = -1
) : Serializable {
companion object {
private const val serialVersionUID: Long = -1760654961004181606
val defaultState: VpnState = VpnState(DISCONNECTED)
}
}
object VpnStateStore {
private lateinit var app: Application
private val dataStore = MultiProcessDataStoreFactory.create(
serializer = VpnStateSerializer(),
produceFile = { app.dataStoreFile(STORE_FILE_NAME) }
)
fun init(app: Application) {
Log.v(TAG, "Init VpnStateStore")
this.app = app
}
fun dataFlow(): Flow<VpnState> = dataStore.data
suspend fun store(f: (vpnState: VpnState) -> VpnState) {
try {
dataStore.updateData(f)
} catch (e : Exception) {
Log.e(TAG, "Failed to store VpnState: $e")
}
}
}
private class VpnStateSerializer : Serializer<VpnState> {
override val defaultValue: VpnState = VpnState.defaultState
override suspend fun readFrom(input: InputStream): VpnState {
return withContext(Dispatchers.IO) {
ObjectInputStream(input).use {
it.readObject() as VpnState
}
}
}
override suspend fun writeTo(t: VpnState, output: OutputStream) {
withContext(Dispatchers.IO) {
ObjectOutputStream(output).use {
it.writeObject(t)
}
}
}
}
@@ -1,18 +1,23 @@
package org.amnezia.vpn.qt package org.amnezia.vpn.qt
import org.amnezia.vpn.protocol.ProtocolState
import org.amnezia.vpn.protocol.Status
/** /**
* JNI functions of the AndroidController class from android_controller.cpp, * JNI functions of the AndroidController class from android_controller.cpp,
* called by events in the Android part of the client * called by events in the Android part of the client
*/ */
object QtAndroidController { object QtAndroidController {
fun onStatus(status: Status) = onStatus(status.state)
fun onStatus(protocolState: ProtocolState) = onStatus(protocolState.ordinal)
external fun onStatus(stateCode: Int) external fun onStatus(stateCode: Int)
external fun onServiceDisconnected() external fun onServiceDisconnected()
external fun onServiceError() external fun onServiceError()
external fun onVpnPermissionRejected() external fun onVpnPermissionRejected()
external fun onVpnConnected() external fun onVpnStateChanged(stateCode: Int)
external fun onVpnDisconnected()
external fun onVpnReconnecting()
external fun onStatisticsUpdate(rxBytes: Long, txBytes: Long) external fun onStatisticsUpdate(rxBytes: Long, txBytes: Long)
external fun onFileOpened(uri: String) external fun onFileOpened(uri: String)
@@ -1,10 +0,0 @@
package org.amnezia.vpn.protocol.wireguard
object GoBackend {
external fun wgGetConfig(handle: Int): String?
external fun wgGetSocketV4(handle: Int): Int
external fun wgGetSocketV6(handle: Int): Int
external fun wgTurnOff(handle: Int)
external fun wgTurnOn(ifName: String, tunFd: Int, settings: String): Int
external fun wgVersion(): String
}
@@ -0,0 +1,10 @@
package org.amnezia.awg
object GoBackend {
external fun awgGetConfig(handle: Int): String?
external fun awgGetSocketV4(handle: Int): Int
external fun awgGetSocketV6(handle: Int): Int
external fun awgTurnOff(handle: Int)
external fun awgTurnOn(ifName: String, tunFd: Int, settings: String): Int
external fun awgVersion(): String
}
@@ -4,6 +4,7 @@ import android.content.Context
import android.net.VpnService.Builder import android.net.VpnService.Builder
import java.util.TreeMap import java.util.TreeMap
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import org.amnezia.awg.GoBackend
import org.amnezia.vpn.protocol.Protocol import org.amnezia.vpn.protocol.Protocol
import org.amnezia.vpn.protocol.ProtocolState import org.amnezia.vpn.protocol.ProtocolState
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
@@ -61,7 +62,7 @@ open class Wireguard : Protocol() {
override val statistics: Statistics override val statistics: Statistics
get() { get() {
if (tunnelHandle == -1) return Statistics.EMPTY_STATISTICS if (tunnelHandle == -1) return Statistics.EMPTY_STATISTICS
val config = GoBackend.wgGetConfig(tunnelHandle) ?: return Statistics.EMPTY_STATISTICS val config = GoBackend.awgGetConfig(tunnelHandle) ?: return Statistics.EMPTY_STATISTICS
return Statistics.build { return Statistics.build {
var optsCount = 0 var optsCount = 0
config.splitToSequence("\n").forEach { line -> config.splitToSequence("\n").forEach { line ->
@@ -92,12 +93,12 @@ open class Wireguard : Protocol() {
val configDataJson = config.getJSONObject("wireguard_config_data") val configDataJson = config.getJSONObject("wireguard_config_data")
val configData = parseConfigData(configDataJson.getString("config")) val configData = parseConfigData(configDataJson.getString("config"))
return WireguardConfig.build { return WireguardConfig.build {
configWireguard(configData) configWireguard(configData, configDataJson)
configSplitTunneling(config) configSplitTunneling(config)
} }
} }
protected fun WireguardConfig.Builder.configWireguard(configData: Map<String, String>) { protected fun WireguardConfig.Builder.configWireguard(configData: Map<String, String>, configDataJson: JSONObject) {
configData["Address"]?.split(",")?.map { address -> configData["Address"]?.split(",")?.map { address ->
InetNetwork.parse(address.trim()) InetNetwork.parse(address.trim())
}?.forEach(::addAddress) }?.forEach(::addAddress)
@@ -118,7 +119,16 @@ open class Wireguard : Protocol() {
if (routes.any { it !in defRoutes }) disableSplitTunneling() if (routes.any { it !in defRoutes }) disableSplitTunneling()
addRoutes(routes) addRoutes(routes)
configData["MTU"]?.let { setMtu(it.toInt()) } configDataJson.optString("mtu").let { mtu ->
if (mtu.isNotEmpty()) {
setMtu(mtu.toInt())
} else {
configData["MTU"]?.let { setMtu(it.toInt()) }
}
}
configDataJson.getString("hostName").let { excludeRoute(InetNetwork.parse(it)) }
configData["Endpoint"]?.let { setEndpoint(InetEndpoint.parse(it)) } configData["Endpoint"]?.let { setEndpoint(InetEndpoint.parse(it)) }
configData["PersistentKeepalive"]?.let { setPersistentKeepalive(it.toInt()) } configData["PersistentKeepalive"]?.let { setPersistentKeepalive(it.toInt()) }
configData["PrivateKey"]?.let { setPrivateKeyHex(it.base64ToHex()) } configData["PrivateKey"]?.let { setPrivateKeyHex(it.base64ToHex()) }
@@ -149,8 +159,8 @@ open class Wireguard : Protocol() {
if (tunFd == null) { if (tunFd == null) {
throw VpnStartException("Create VPN interface: permission not granted or revoked") throw VpnStartException("Create VPN interface: permission not granted or revoked")
} }
Log.v(TAG, "Wg-go backend ${GoBackend.wgVersion()}") Log.v(TAG, "Wg-go backend ${GoBackend.awgVersion()}")
tunnelHandle = GoBackend.wgTurnOn(ifName, tunFd.detachFd(), config.toWgUserspaceString()) tunnelHandle = GoBackend.awgTurnOn(ifName, tunFd.detachFd(), config.toWgUserspaceString())
} }
if (tunnelHandle < 0) { if (tunnelHandle < 0) {
@@ -158,8 +168,8 @@ open class Wireguard : Protocol() {
throw VpnStartException("Wireguard tunnel creation error") throw VpnStartException("Wireguard tunnel creation error")
} }
if (!protect(GoBackend.wgGetSocketV4(tunnelHandle)) || !protect(GoBackend.wgGetSocketV6(tunnelHandle))) { if (!protect(GoBackend.awgGetSocketV4(tunnelHandle)) || !protect(GoBackend.awgGetSocketV6(tunnelHandle))) {
GoBackend.wgTurnOff(tunnelHandle) GoBackend.awgTurnOff(tunnelHandle)
tunnelHandle = -1 tunnelHandle = -1
throw VpnStartException("Protect VPN interface: permission not granted or revoked") throw VpnStartException("Protect VPN interface: permission not granted or revoked")
} }
@@ -172,7 +182,7 @@ open class Wireguard : Protocol() {
} }
val handleToClose = tunnelHandle val handleToClose = tunnelHandle
tunnelHandle = -1 tunnelHandle = -1
GoBackend.wgTurnOff(handleToClose) GoBackend.awgTurnOff(handleToClose)
state.value = DISCONNECTED state.value = DISCONNECTED
} }
+2 -3
View File
@@ -45,13 +45,12 @@ foreach(abi IN ITEMS ${QT_ANDROID_ABIS})
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/amneziawg/android/${abi}/libwg.so ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/amneziawg/android/${abi}/libwg.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/amneziawg/android/${abi}/libwg-go.so ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/amneziawg/android/${abi}/libwg-go.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/amneziawg/android/${abi}/libwg-quick.so ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/amneziawg/android/${abi}/libwg-quick.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/shadowsocks/android/${abi}/libredsocks.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/shadowsocks/android/${abi}/libsslocal.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/shadowsocks/android/${abi}/libtun2socks.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/libck-ovpn-plugin.so ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/libck-ovpn-plugin.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/libovpn3.so ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/libovpn3.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/libovpnutil.so ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/libovpnutil.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/librsapss.so ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/librsapss.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/libssh/android/${abi}/libssh.so ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/libssh/android/${abi}/libssh.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openssl3/android/${abi}/libcrypto_3.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openssl3/android/${abi}/libssl_3.so
) )
endforeach() endforeach()
+1
View File
@@ -107,6 +107,7 @@ target_sources(${PROJECT} PRIVATE
${CLIENT_ROOT_DIR}/platforms/ios/LogController.swift ${CLIENT_ROOT_DIR}/platforms/ios/LogController.swift
${CLIENT_ROOT_DIR}/platforms/ios/Log.swift ${CLIENT_ROOT_DIR}/platforms/ios/Log.swift
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift ${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
${CLIENT_ROOT_DIR}/platforms/ios/ScreenProtection.swift
) )
target_sources(${PROJECT} PRIVATE target_sources(${PROJECT} PRIVATE
@@ -41,6 +41,8 @@ QString AwgConfigurator::genAwgConfig(const ServerCredentials &credentials, Dock
jsonConfig[config_key::responsePacketMagicHeader] = configMap.value(config_key::responsePacketMagicHeader); jsonConfig[config_key::responsePacketMagicHeader] = configMap.value(config_key::responsePacketMagicHeader);
jsonConfig[config_key::underloadPacketMagicHeader] = configMap.value(config_key::underloadPacketMagicHeader); jsonConfig[config_key::underloadPacketMagicHeader] = configMap.value(config_key::underloadPacketMagicHeader);
jsonConfig[config_key::transportPacketMagicHeader] = configMap.value(config_key::transportPacketMagicHeader); jsonConfig[config_key::transportPacketMagicHeader] = configMap.value(config_key::transportPacketMagicHeader);
jsonConfig[config_key::mtu] = containerConfig.value(ProtocolProps::protoToString(Proto::Awg)).toObject().
value(config_key::mtu).toString(protocols::awg::defaultMtu);
return QJsonDocument(jsonConfig).toJson(); return QJsonDocument(jsonConfig).toJson();
} }
@@ -76,7 +76,7 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(co
if (connData.caCert.isEmpty() || connData.clientCert.isEmpty() || connData.taKey.isEmpty()) { if (connData.caCert.isEmpty() || connData.clientCert.isEmpty() || connData.taKey.isEmpty()) {
if (errorCode) if (errorCode)
*errorCode = ErrorCode::SshSftpFailureError; *errorCode = ErrorCode::SshScpFailureError;
} }
return connData; return connData;
@@ -159,7 +159,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
.arg(connData.clientPubKey, connData.pskKey, connData.clientIP); .arg(connData.clientPubKey, connData.pskKey, connData.clientIP);
e = serverController.uploadTextFileToContainer(container, credentials, configPart, m_serverConfigPath, e = serverController.uploadTextFileToContainer(container, credentials, configPart, m_serverConfigPath,
libssh::SftpOverwriteMode::SftpAppendToExisting); libssh::ScpOverwriteMode::ScpAppendToExisting);
if (e) { if (e) {
if (errorCode) if (errorCode)
@@ -194,6 +194,7 @@ QString WireguardConfigurator::genWireguardConfig(const ServerCredentials &crede
config.replace("$WIREGUARD_SERVER_PUBLIC_KEY", connData.serverPubKey); config.replace("$WIREGUARD_SERVER_PUBLIC_KEY", connData.serverPubKey);
config.replace("$WIREGUARD_PSK", connData.pskKey); config.replace("$WIREGUARD_PSK", connData.pskKey);
const QJsonObject &wireguarConfig = containerConfig.value(ProtocolProps::protoToString(Proto::WireGuard)).toObject();
QJsonObject jConfig; QJsonObject jConfig;
jConfig[config_key::config] = config; jConfig[config_key::config] = config;
@@ -205,6 +206,8 @@ QString WireguardConfigurator::genWireguardConfig(const ServerCredentials &crede
jConfig[config_key::psk_key] = connData.pskKey; jConfig[config_key::psk_key] = connData.pskKey;
jConfig[config_key::server_pub_key] = connData.serverPubKey; jConfig[config_key::server_pub_key] = connData.serverPubKey;
jConfig[config_key::mtu] = wireguarConfig.value(config_key::mtu).toString(protocols::wireguard::defaultMtu);
clientId = connData.clientPubKey; clientId = connData.clientPubKey;
return QJsonDocument(jConfig).toJson(); return QJsonDocument(jConfig).toJson();
+32 -6
View File
@@ -118,7 +118,7 @@ ServerController::runContainerScript(const ServerCredentials &credentials, Docke
ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container, const ServerCredentials &credentials, ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container, const ServerCredentials &credentials,
const QString &file, const QString &path, const QString &file, const QString &path,
libssh::SftpOverwriteMode overwriteMode) libssh::ScpOverwriteMode overwriteMode)
{ {
ErrorCode e = ErrorCode::NoError; ErrorCode e = ErrorCode::NoError;
QString tmpFileName = QString("/tmp/%1.tmp").arg(Utils::getRandomString(16)); QString tmpFileName = QString("/tmp/%1.tmp").arg(Utils::getRandomString(16));
@@ -139,7 +139,7 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
if (e) if (e)
return e; return e;
if (overwriteMode == libssh::SftpOverwriteMode::SftpOverwriteExisting) { if (overwriteMode == libssh::ScpOverwriteMode::ScpOverwriteExisting) {
e = runScript(credentials, e = runScript(credentials,
replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(path), replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(path),
genVarsForScript(credentials, container)), genVarsForScript(credentials, container)),
@@ -147,7 +147,7 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
if (e) if (e)
return e; return e;
} else if (overwriteMode == libssh::SftpOverwriteMode::SftpAppendToExisting) { } else if (overwriteMode == libssh::ScpOverwriteMode::ScpAppendToExisting) {
e = runScript(credentials, e = runScript(credentials,
replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(tmpFileName), replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(tmpFileName),
genVarsForScript(credentials, container)), genVarsForScript(credentials, container)),
@@ -199,7 +199,7 @@ QByteArray ServerController::getTextFileFromContainer(DockerContainer container,
} }
ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data,
const QString &remotePath, libssh::SftpOverwriteMode overwriteMode) const QString &remotePath, libssh::ScpOverwriteMode overwriteMode)
{ {
auto error = m_sshClient.connectToHost(credentials); auto error = m_sshClient.connectToHost(credentials);
if (error != ErrorCode::NoError) { if (error != ErrorCode::NoError) {
@@ -211,7 +211,7 @@ ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credential
localFile.write(data); localFile.write(data);
localFile.close(); localFile.close();
error = m_sshClient.sftpFileCopy(overwriteMode, localFile.fileName(), remotePath, "non_desc"); error = m_sshClient.scpFileCopy(overwriteMode, localFile.fileName(), remotePath, "non_desc");
if (error != ErrorCode::NoError) { if (error != ErrorCode::NoError) {
return error; return error;
@@ -359,7 +359,33 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
} }
if (container == DockerContainer::Awg) { if (container == DockerContainer::Awg) {
return true; if ((oldProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort)
!= newProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort))
|| (oldProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount)
!= newProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount))
|| (oldProtoConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize)
!= newProtoConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize))
|| (oldProtoConfig.value(config_key::junkPacketMaxSize).toString(protocols::awg::defaultJunkPacketMaxSize)
!= newProtoConfig.value(config_key::junkPacketMaxSize).toString(protocols::awg::defaultJunkPacketMaxSize))
|| (oldProtoConfig.value(config_key::initPacketJunkSize).toString(protocols::awg::defaultInitPacketJunkSize)
!= newProtoConfig.value(config_key::initPacketJunkSize).toString(protocols::awg::defaultInitPacketJunkSize))
|| (oldProtoConfig.value(config_key::responsePacketJunkSize).toString(protocols::awg::defaultResponsePacketJunkSize)
!= newProtoConfig.value(config_key::responsePacketJunkSize).toString(protocols::awg::defaultResponsePacketJunkSize))
|| (oldProtoConfig.value(config_key::initPacketMagicHeader).toString(protocols::awg::defaultInitPacketMagicHeader)
!= newProtoConfig.value(config_key::initPacketMagicHeader).toString(protocols::awg::defaultInitPacketMagicHeader))
|| (oldProtoConfig.value(config_key::responsePacketMagicHeader).toString(protocols::awg::defaultResponsePacketMagicHeader)
!= newProtoConfig.value(config_key::responsePacketMagicHeader).toString(protocols::awg::defaultResponsePacketMagicHeader))
|| (oldProtoConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader)
!= newProtoConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader))
|| (oldProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader)
!= newProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader)))
return true;
}
if (container == DockerContainer::WireGuard){
if (oldProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort)
!= newProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort))
return true;
} }
return false; return false;
+2 -2
View File
@@ -38,7 +38,7 @@ public:
ErrorCode uploadTextFileToContainer( ErrorCode uploadTextFileToContainer(
DockerContainer container, const ServerCredentials &credentials, const QString &file, const QString &path, DockerContainer container, const ServerCredentials &credentials, const QString &file, const QString &path,
libssh::SftpOverwriteMode overwriteMode = libssh::SftpOverwriteMode::SftpOverwriteExisting); libssh::ScpOverwriteMode overwriteMode = libssh::ScpOverwriteMode::ScpOverwriteExisting);
QByteArray getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials, QByteArray getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials,
const QString &path, ErrorCode *errorCode = nullptr); const QString &path, ErrorCode *errorCode = nullptr);
@@ -80,7 +80,7 @@ private:
ErrorCode isServerDpkgBusy(const ServerCredentials &credentials, DockerContainer container); ErrorCode isServerDpkgBusy(const ServerCredentials &credentials, DockerContainer container);
ErrorCode uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath, ErrorCode uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath,
libssh::SftpOverwriteMode overwriteMode = libssh::SftpOverwriteMode::SftpOverwriteExisting); libssh::ScpOverwriteMode overwriteMode = libssh::ScpOverwriteMode::ScpOverwriteExisting);
ErrorCode setupServerFirewall(const ServerCredentials &credentials); ErrorCode setupServerFirewall(const ServerCredentials &credentials);
+11 -16
View File
@@ -46,25 +46,12 @@ namespace amnezia
SshPrivateKeyFormatError = 304, SshPrivateKeyFormatError = 304,
SshTimeoutError = 305, SshTimeoutError = 305,
// Ssh sftp errors // Ssh scp errors
SshSftpEofError = 400, SshScpFailureError = 400,
SshSftpNoSuchFileError = 401,
SshSftpPermissionDeniedError = 402,
SshSftpFailureError = 403,
SshSftpBadMessageError = 404,
SshSftpNoConnectionError = 405,
SshSftpConnectionLostError = 406,
SshSftpOpUnsupportedError = 407,
SshSftpInvalidHandleError = 408,
SshSftpNoSuchPathError = 409,
SshSftpFileAlreadyExistsError = 410,
SshSftpWriteProtectError = 411,
SshSftpNoMediaError = 412,
// Local errors // Local errors
OpenVpnConfigMissing = 500, OpenVpnConfigMissing = 500,
OpenVpnManagementServerError = 501, OpenVpnManagementServerError = 501,
ConfigMissing = 502,
// Distro errors // Distro errors
OpenVpnExecutableMissing = 600, OpenVpnExecutableMissing = 600,
@@ -92,7 +79,15 @@ namespace amnezia
// Api errors // Api errors
ApiConfigDownloadError = 1100, ApiConfigDownloadError = 1100,
ApiConfigAlreadyAdded = 1101 ApiConfigAlreadyAdded = 1101,
// QFile errors
OpenError = 1200,
ReadError = 1201,
PermissionsError = 1202,
UnspecifiedError = 1203,
FatalError = 1204,
AbortError = 1205
}; };
} // namespace amnezia } // namespace amnezia
+10 -14
View File
@@ -28,20 +28,8 @@ QString errorString(ErrorCode code) {
case(SshPrivateKeyFormatError): errorMessage = QObject::tr("The selected private key format is not supported, use openssh ED25519 key types or PEM key types"); break; case(SshPrivateKeyFormatError): errorMessage = QObject::tr("The selected private key format is not supported, use openssh ED25519 key types or PEM key types"); break;
case(SshTimeoutError): errorMessage = QObject::tr("Timeout connecting to server"); break; case(SshTimeoutError): errorMessage = QObject::tr("Timeout connecting to server"); break;
// Libssh sftp errors // Ssh scp errors
case(SshSftpEofError): errorMessage = QObject::tr("Sftp error: End-of-file encountered"); break; case(SshScpFailureError): errorMessage = QObject::tr("Scp error: Generic failure"); break;
case(SshSftpNoSuchFileError): errorMessage = QObject::tr("Sftp error: File does not exist"); break;
case(SshSftpPermissionDeniedError): errorMessage = QObject::tr("Sftp error: Permission denied"); break;
case(SshSftpFailureError): errorMessage = QObject::tr("Sftp error: Generic failure"); break;
case(SshSftpBadMessageError): errorMessage = QObject::tr("Sftp error: Garbage received from server"); break;
case(SshSftpNoConnectionError): errorMessage = QObject::tr("Sftp error: No connection has been set up"); break;
case(SshSftpConnectionLostError): errorMessage = QObject::tr("Sftp error: There was a connection, but we lost it"); break;
case(SshSftpOpUnsupportedError): errorMessage = QObject::tr("Sftp error: Operation not supported by libssh yet"); break;
case(SshSftpInvalidHandleError): errorMessage = QObject::tr("Sftp error: Invalid file handle"); break;
case(SshSftpNoSuchPathError): errorMessage = QObject::tr("Sftp error: No such file or directory path exists"); break;
case(SshSftpFileAlreadyExistsError): errorMessage = QObject::tr("Sftp error: An attempt to create an already existing file or directory has been made"); break;
case(SshSftpWriteProtectError): errorMessage = QObject::tr("Sftp error: Write-protected filesystem"); break;
case(SshSftpNoMediaError): errorMessage = QObject::tr("Sftp error: No media was in remote drive"); break;
// Local errors // Local errors
case (OpenVpnConfigMissing): errorMessage = QObject::tr("OpenVPN config missing"); break; case (OpenVpnConfigMissing): errorMessage = QObject::tr("OpenVPN config missing"); break;
@@ -68,6 +56,14 @@ QString errorString(ErrorCode code) {
case (ApiConfigDownloadError): errorMessage = QObject::tr("Error when retrieving configuration from API"); break; case (ApiConfigDownloadError): errorMessage = QObject::tr("Error when retrieving configuration from API"); break;
case (ApiConfigAlreadyAdded): errorMessage = QObject::tr("This config has already been added to the application"); break; case (ApiConfigAlreadyAdded): errorMessage = QObject::tr("This config has already been added to the application"); break;
// QFile errors
case(OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;
case(ReadError): errorMessage = QObject::tr("QFile error: An error occurred when reading from the file"); break;
case(PermissionsError): errorMessage = QObject::tr("QFile error: The file could not be accessed"); break;
case(UnspecifiedError): errorMessage = QObject::tr("QFile error: An unspecified error occurred"); break;
case(FatalError): errorMessage = QObject::tr("QFile error: A fatal error occurred"); break;
case(AbortError): errorMessage = QObject::tr("QFile error: The operation was aborted"); break;
case(InternalError): case(InternalError):
default: default:
errorMessage = QObject::tr("Internal error"); break; errorMessage = QObject::tr("Internal error"); break;
+56 -90
View File
@@ -10,16 +10,10 @@ const uint32_t S_IRWXU = 0644;
#endif #endif
namespace libssh { namespace libssh {
const QString libsshTimeoutError = "Timeout connecting to"; constexpr auto libsshTimeoutError{"Timeout connecting to"};
std::function<QString()> Client::m_passphraseCallback; std::function<QString()> Client::m_passphraseCallback;
Client::Client(QObject *parent) : QObject(parent)
{ }
Client::~Client()
{ }
int Client::callback(const char *prompt, char *buf, size_t len, int echo, int verify, void *userdata) int Client::callback(const char *prompt, char *buf, size_t len, int echo, int verify, void *userdata)
{ {
auto passphrase = m_passphraseCallback(); auto passphrase = m_passphraseCallback();
@@ -171,13 +165,13 @@ namespace libssh {
return ErrorCode::NoError; return ErrorCode::NoError;
}; };
auto error = readOutput(false); auto errorCode = readOutput(false);
if (error != ErrorCode::NoError) { if (errorCode != ErrorCode::NoError) {
return error; return errorCode;
} }
error = readOutput(true); errorCode = readOutput(true);
if (error != ErrorCode::NoError) { if (errorCode != ErrorCode::NoError) {
return error; return errorCode;
} }
} else { } else {
return closeChannel(); return closeChannel();
@@ -222,100 +216,79 @@ namespace libssh {
return fromLibsshErrorCode(); return fromLibsshErrorCode();
} }
ErrorCode Client::sftpFileCopy(const SftpOverwriteMode overwriteMode, const QString& localPath, const QString& remotePath, const QString &fileDesc) ErrorCode Client::scpFileCopy(const ScpOverwriteMode overwriteMode, const QString& localPath, const QString& remotePath, const QString &fileDesc)
{ {
m_sftpSession = sftp_new(m_session); m_scpSession = ssh_scp_new(m_session, SSH_SCP_WRITE, remotePath.toStdString().c_str());
if (m_sftpSession == nullptr) { if (m_scpSession == nullptr) {
return closeSftpSession(); return fromLibsshErrorCode();
} }
int result = sftp_init(m_sftpSession); if (ssh_scp_init(m_scpSession) != SSH_OK) {
auto errorCode = fromLibsshErrorCode();
if (result != SSH_OK) { closeScpSession();
return closeSftpSession(); return errorCode;
} }
QFutureWatcher<ErrorCode> watcher; QFutureWatcher<ErrorCode> watcher;
connect(&watcher, &QFutureWatcher<ErrorCode>::finished, this, &Client::sftpFileCopyFinished); connect(&watcher, &QFutureWatcher<ErrorCode>::finished, this, &Client::scpFileCopyFinished);
QFuture<ErrorCode> future = QtConcurrent::run([this, overwriteMode, &localPath, &remotePath, &fileDesc]() { QFuture<ErrorCode> future = QtConcurrent::run([this, overwriteMode, &localPath, &remotePath, &fileDesc]() {
int accessType = O_WRONLY | O_CREAT | overwriteMode; const int accessType = O_WRONLY | O_CREAT | overwriteMode;
sftp_file file; const int localFileSize = QFileInfo(localPath).size();
const size_t bufferSize = 16384;
char buffer[bufferSize];
file = sftp_open(m_sftpSession, remotePath.toStdString().c_str(), accessType, S_IRWXU); int result = ssh_scp_push_file(m_scpSession, remotePath.toStdString().c_str(), localFileSize, accessType);
if (result != SSH_OK) {
if (file == nullptr) { return fromLibsshErrorCode();
return closeSftpSession();
} }
int localFileSize = QFileInfo(localPath).size();
int chunksCount = localFileSize / (bufferSize);
QFile fin(localPath); QFile fin(localPath);
if (fin.open(QIODevice::ReadOnly)) { if (fin.open(QIODevice::ReadOnly)) {
for (int currentChunkId = 0; currentChunkId < chunksCount; currentChunkId++) { constexpr size_t bufferSize = 16384;
QByteArray chunk = fin.read(bufferSize); int transferred = 0;
if (chunk.size() != bufferSize) return ErrorCode::SshSftpEofError; int currentChunkSize = bufferSize;
int bytesWritten = sftp_write(file, chunk.data(), chunk.size()); while (transferred < localFileSize) {
if (bytesWritten != chunk.size()) { // Last Chunk
fin.close(); if ((localFileSize - transferred) < bufferSize) {
sftp_close(file); currentChunkSize = localFileSize % bufferSize;
return closeSftpSession();
} }
}
int lastChunkSize = localFileSize % bufferSize; QByteArray chunk = fin.read(currentChunkSize);
if (chunk.size() != currentChunkSize) {
if (lastChunkSize != 0) { return fromFileErrorCode(fin.error());
QByteArray lastChunk = fin.read(lastChunkSize);
if (lastChunk.size() != lastChunkSize) return ErrorCode::SshSftpEofError;
int bytesWritten = sftp_write(file, lastChunk.data(), lastChunkSize);
if (bytesWritten != lastChunkSize) {
fin.close();
sftp_close(file);
return closeSftpSession();
} }
result = ssh_scp_write(m_scpSession, chunk.data(), chunk.size());
if (result != SSH_OK) {
return fromLibsshErrorCode();
}
transferred += currentChunkSize;
} }
} else { } else {
sftp_close(file); return fromFileErrorCode(fin.error());
return closeSftpSession();
} }
fin.close(); return ErrorCode::NoError;
int result = sftp_close(file);
if (result != SSH_OK) {
return closeSftpSession();
}
return closeSftpSession();
}); });
watcher.setFuture(future); watcher.setFuture(future);
QEventLoop wait; QEventLoop wait;
QObject::connect(this, &Client::sftpFileCopyFinished, &wait, &QEventLoop::quit); QObject::connect(this, &Client::scpFileCopyFinished, &wait, &QEventLoop::quit);
wait.exec(); wait.exec();
closeScpSession();
return watcher.result(); return watcher.result();
} }
ErrorCode Client::closeSftpSession() void Client::closeScpSession()
{ {
auto errorCode = fromLibsshSftpErrorCode(sftp_get_error(m_sftpSession)); if (m_scpSession != nullptr) {
if (m_sftpSession != nullptr) { ssh_scp_free(m_scpSession);
sftp_free(m_sftpSession); m_scpSession = nullptr;
m_sftpSession = nullptr;
} }
qCritical() << ssh_get_error(m_session);
return errorCode;
} }
ErrorCode Client::fromLibsshErrorCode() ErrorCode Client::fromLibsshErrorCode()
@@ -337,24 +310,17 @@ namespace libssh {
default: return ErrorCode::SshInternalError; default: return ErrorCode::SshInternalError;
} }
} }
ErrorCode Client::fromLibsshSftpErrorCode(int errorCode)
ErrorCode Client::fromFileErrorCode(QFileDevice::FileError fileError)
{ {
switch (errorCode) { switch (fileError) {
case(SSH_FX_OK): return ErrorCode::NoError; case QFileDevice::NoError: return ErrorCode::NoError;
case(SSH_FX_EOF): return ErrorCode::SshSftpEofError; case QFileDevice::ReadError: return ErrorCode::ReadError;
case(SSH_FX_NO_SUCH_FILE): return ErrorCode::SshSftpNoSuchFileError; case QFileDevice::OpenError: return ErrorCode::OpenError;
case(SSH_FX_PERMISSION_DENIED): return ErrorCode::SshSftpPermissionDeniedError; case QFileDevice::PermissionsError: return ErrorCode::PermissionsError;
case(SSH_FX_FAILURE): return ErrorCode::SshSftpFailureError; case QFileDevice::FatalError: return ErrorCode::FatalError;
case(SSH_FX_BAD_MESSAGE): return ErrorCode::SshSftpBadMessageError; case QFileDevice::AbortError: return ErrorCode::AbortError;
case(SSH_FX_NO_CONNECTION): return ErrorCode::SshSftpNoConnectionError; default: return ErrorCode::UnspecifiedError;
case(SSH_FX_CONNECTION_LOST): return ErrorCode::SshSftpConnectionLostError;
case(SSH_FX_OP_UNSUPPORTED): return ErrorCode::SshSftpOpUnsupportedError;
case(SSH_FX_INVALID_HANDLE): return ErrorCode::SshSftpInvalidHandleError;
case(SSH_FX_NO_SUCH_PATH): return ErrorCode::SshSftpNoSuchPathError;
case(SSH_FX_FILE_ALREADY_EXISTS): return ErrorCode::SshSftpFileAlreadyExistsError;
case(SSH_FX_WRITE_PROTECT): return ErrorCode::SshSftpWriteProtectError;
case(SSH_FX_NO_MEDIA): return ErrorCode::SshSftpNoMediaError;
default: return ErrorCode::SshSftpFailureError;
} }
} }
+12 -12
View File
@@ -2,29 +2,29 @@
#define SSHCLIENT_H #define SSHCLIENT_H
#include <QObject> #include <QObject>
#include <QFile>
#include <fcntl.h> #include <fcntl.h>
#include <libssh/libssh.h> #include <libssh/libssh.h>
#include <libssh/sftp.h>
#include "defs.h" #include "defs.h"
using namespace amnezia; using namespace amnezia;
namespace libssh { namespace libssh {
enum SftpOverwriteMode { enum ScpOverwriteMode {
/*! Overwrite any existing files */ /*! Overwrite any existing files */
SftpOverwriteExisting = O_TRUNC, ScpOverwriteExisting = O_TRUNC,
/*! Append new content if the file already exists */ /*! Append new content if the file already exists */
SftpAppendToExisting = O_APPEND ScpAppendToExisting = O_APPEND
}; };
class Client : public QObject class Client : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
Client(QObject *parent = nullptr); Client() = default;
~Client(); ~Client() = default;
ErrorCode connectToHost(const ServerCredentials &credentials); ErrorCode connectToHost(const ServerCredentials &credentials);
void disconnectFromHost(); void disconnectFromHost();
@@ -32,26 +32,26 @@ namespace libssh {
const std::function<ErrorCode (const QString &, Client &)> &cbReadStdOut, const std::function<ErrorCode (const QString &, Client &)> &cbReadStdOut,
const std::function<ErrorCode (const QString &, Client &)> &cbReadStdErr); const std::function<ErrorCode (const QString &, Client &)> &cbReadStdErr);
ErrorCode writeResponse(const QString &data); ErrorCode writeResponse(const QString &data);
ErrorCode sftpFileCopy(const SftpOverwriteMode overwriteMode, ErrorCode scpFileCopy(const ScpOverwriteMode overwriteMode,
const QString &localPath, const QString &localPath,
const QString &remotePath, const QString &remotePath,
const QString& fileDesc); const QString &fileDesc);
ErrorCode getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, const std::function<QString()> &passphraseCallback); ErrorCode getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, const std::function<QString()> &passphraseCallback);
private: private:
ErrorCode closeChannel(); ErrorCode closeChannel();
ErrorCode closeSftpSession(); void closeScpSession();
ErrorCode fromLibsshErrorCode(); ErrorCode fromLibsshErrorCode();
ErrorCode fromLibsshSftpErrorCode(int errorCode); ErrorCode fromFileErrorCode(QFileDevice::FileError fileError);
static int callback(const char *prompt, char *buf, size_t len, int echo, int verify, void *userdata); static int callback(const char *prompt, char *buf, size_t len, int echo, int verify, void *userdata);
ssh_session m_session = nullptr; ssh_session m_session = nullptr;
ssh_channel m_channel = nullptr; ssh_channel m_channel = nullptr;
sftp_session m_sftpSession = nullptr; ssh_scp m_scpSession = nullptr;
static std::function<QString()> m_passphraseCallback; static std::function<QString()> m_passphraseCallback;
signals: signals:
void writeToChannelFinished(); void writeToChannelFinished();
void sftpFileCopyFinished(); void scpFileCopyFinished();
}; };
} }
+17
View File
@@ -251,6 +251,19 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
GETVALUE("serverPskKey", config.m_serverPskKey, String); GETVALUE("serverPskKey", config.m_serverPskKey, String);
GETVALUE("serverPort", config.m_serverPort, Double); GETVALUE("serverPort", config.m_serverPort, Double);
if (!obj.contains("deviceMTU") || obj.value("deviceMTU").toString().toInt() == 0)
{
config.m_deviceMTU = 1280;
} else {
config.m_deviceMTU = obj.value("deviceMTU").toString().toInt();
#ifdef Q_OS_WINDOWS
// For Windows min MTU value is 1280 (the smallest MTU legal with IPv6).
if (config.m_deviceMTU < 1280) {
config.m_deviceMTU = 1280;
}
#endif
}
config.m_deviceIpv4Address = obj.value("deviceIpv4Address").toString(); config.m_deviceIpv4Address = obj.value("deviceIpv4Address").toString();
config.m_deviceIpv6Address = obj.value("deviceIpv6Address").toString(); config.m_deviceIpv6Address = obj.value("deviceIpv6Address").toString();
if (config.m_deviceIpv4Address.isNull() && if (config.m_deviceIpv4Address.isNull() &&
@@ -360,6 +373,10 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
return false; return false;
} }
if (!obj.value("mtu").isNull()) {
config.m_mtu = obj.value("mtu").toString();
}
if (!obj.value("Jc").isNull() && !obj.value("Jmin").isNull() if (!obj.value("Jc").isNull() && !obj.value("Jmin").isNull()
&& !obj.value("Jmax").isNull() && !obj.value("S1").isNull() && !obj.value("Jmax").isNull() && !obj.value("S1").isNull()
&& !obj.value("S2").isNull() && !obj.value("H1").isNull() && !obj.value("S2").isNull() && !obj.value("H1").isNull()
+6
View File
@@ -23,6 +23,7 @@ QJsonObject InterfaceConfig::toJson() const {
json.insert("serverIpv4AddrIn", QJsonValue(m_serverIpv4AddrIn)); json.insert("serverIpv4AddrIn", QJsonValue(m_serverIpv4AddrIn));
json.insert("serverIpv6AddrIn", QJsonValue(m_serverIpv6AddrIn)); json.insert("serverIpv6AddrIn", QJsonValue(m_serverIpv6AddrIn));
json.insert("serverPort", QJsonValue((double)m_serverPort)); json.insert("serverPort", QJsonValue((double)m_serverPort));
json.insert("deviceMTU", QJsonValue(m_deviceMTU));
if ((m_hopType == InterfaceConfig::MultiHopExit) || if ((m_hopType == InterfaceConfig::MultiHopExit) ||
(m_hopType == InterfaceConfig::SingleHop)) { (m_hopType == InterfaceConfig::SingleHop)) {
json.insert("serverIpv4Gateway", QJsonValue(m_serverIpv4Gateway)); json.insert("serverIpv4Gateway", QJsonValue(m_serverIpv4Gateway));
@@ -85,8 +86,13 @@ QString InterfaceConfig::toWgConf(const QMap<QString, QString>& extra) const {
if (addresses.isEmpty()) { if (addresses.isEmpty()) {
return ""; return "";
} }
out << "Address = " << addresses.join(", ") << "\n"; out << "Address = " << addresses.join(", ") << "\n";
if (m_deviceMTU) {
out << "MTU = " << m_deviceMTU << "\n";
}
if (!m_dnsServer.isNull()) { if (!m_dnsServer.isNull()) {
QStringList dnsServers(m_dnsServer); QStringList dnsServers(m_dnsServer);
// If the DNS is not the Gateway, it's a user defined DNS // If the DNS is not the Gateway, it's a user defined DNS
+2
View File
@@ -33,6 +33,7 @@ class InterfaceConfig {
QString m_serverIpv6AddrIn; QString m_serverIpv6AddrIn;
QString m_dnsServer; QString m_dnsServer;
int m_serverPort = 0; int m_serverPort = 0;
int m_deviceMTU = 1280;
QList<IPAddress> m_allowedIPAddressRanges; QList<IPAddress> m_allowedIPAddressRanges;
QStringList m_excludedAddresses; QStringList m_excludedAddresses;
QStringList m_vpnDisabledApps; QStringList m_vpnDisabledApps;
@@ -40,6 +41,7 @@ class InterfaceConfig {
QString m_installationId; QString m_installationId;
#endif #endif
QString m_mtu;
QString m_junkPacketCount; QString m_junkPacketCount;
QString m_junkPacketMinSize; QString m_junkPacketMinSize;
QString m_junkPacketMaxSize; QString m_junkPacketMaxSize;
+3 -6
View File
@@ -26,9 +26,10 @@ int main(int argc, char *argv[])
AllowSetForegroundWindow(ASFW_ANY); AllowSetForegroundWindow(ASFW_ANY);
#endif #endif
// QTBUG-95974 QTBUG-95764 QTBUG-102168
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
// QTBUG-95974 QTBUG-95764 QTBUG-102168
qputenv("QT_ANDROID_DISABLE_ACCESSIBILITY", "1"); qputenv("QT_ANDROID_DISABLE_ACCESSIBILITY", "1");
qputenv("ANDROID_OPENSSL_SUFFIX", "_3");
#endif #endif
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
@@ -48,10 +49,6 @@ int main(int argc, char *argv[])
AllowSetForegroundWindow(0); AllowSetForegroundWindow(0);
#endif #endif
#if defined(Q_OS_IOS)
QtAppDelegateInitialize();
#endif
app.registerTypes(); app.registerTypes();
app.setApplicationName(APPLICATION_NAME); app.setApplicationName(APPLICATION_NAME);
@@ -65,7 +62,7 @@ int main(int argc, char *argv[])
if (doExec) { if (doExec) {
app.init(); app.init();
qInfo().noquote() << QString("Started %1 version %2").arg(APPLICATION_NAME, APP_VERSION); qInfo().noquote() << QString("Started %1 version %2 %3").arg(APPLICATION_NAME, APP_VERSION, GIT_COMMIT_HASH);
qInfo().noquote() << QString("%1 (%2)").arg(QSysInfo::prettyProductName(), QSysInfo::currentCpuArchitecture()); qInfo().noquote() << QString("%1 (%2)").arg(QSysInfo::prettyProductName(), QSysInfo::currentCpuArchitecture());
return app.exec(); return app.exec();
+3 -2
View File
@@ -125,15 +125,16 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
json.insert("privateKey", wgConfig.value(amnezia::config_key::client_priv_key)); json.insert("privateKey", wgConfig.value(amnezia::config_key::client_priv_key));
json.insert("deviceIpv4Address", wgConfig.value(amnezia::config_key::client_ip)); json.insert("deviceIpv4Address", wgConfig.value(amnezia::config_key::client_ip));
// todo review wg ipv6 // todo review wg ipv6
#ifndef Q_OS_WINDOWS #ifdef Q_OS_MACOS
json.insert("deviceIpv6Address", "dead::1"); json.insert("deviceIpv6Address", "dead::1");
#endif #endif
json.insert("serverPublicKey", wgConfig.value(amnezia::config_key::server_pub_key)); json.insert("serverPublicKey", wgConfig.value(amnezia::config_key::server_pub_key));
json.insert("serverPskKey", wgConfig.value(amnezia::config_key::psk_key)); json.insert("serverPskKey", wgConfig.value(amnezia::config_key::psk_key));
json.insert("serverIpv4AddrIn", wgConfig.value(amnezia::config_key::hostName)); json.insert("serverIpv4AddrIn", wgConfig.value(amnezia::config_key::hostName));
// json.insert("serverIpv6AddrIn", QJsonValue(hop.m_server.ipv6AddrIn())); // json.insert("serverIpv6AddrIn", QJsonValue(hop.m_server.ipv6AddrIn()));
json.insert("serverPort", wgConfig.value(amnezia::config_key::port).toInt()); json.insert("deviceMTU", wgConfig.value(amnezia::config_key::mtu));
json.insert("serverPort", wgConfig.value(amnezia::config_key::port).toInt());
json.insert("serverIpv4Gateway", wgConfig.value(amnezia::config_key::hostName)); json.insert("serverIpv4Gateway", wgConfig.value(amnezia::config_key::hostName));
// json.insert("serverIpv6Gateway", QJsonValue(hop.m_server.ipv6Gateway())); // json.insert("serverIpv6Gateway", QJsonValue(hop.m_server.ipv6Gateway()));
json.insert("dnsServer", rawConfig.value(amnezia::config_key::dns1)); json.insert("dnsServer", rawConfig.value(amnezia::config_key::dns1));
+18 -42
View File
@@ -56,26 +56,10 @@ AndroidController::AndroidController() : QObject()
Qt::QueuedConnection); Qt::QueuedConnection);
connect( connect(
this, &AndroidController::vpnConnected, this, this, &AndroidController::vpnStateChanged, this,
[this]() { [this](AndroidController::ConnectionState state) {
qDebug() << "Android event: VPN connected"; qDebug() << "Android event: VPN state changed:" << textConnectionState(state);
emit connectionStateChanged(Vpn::ConnectionState::Connected); emit connectionStateChanged(convertState(state));
},
Qt::QueuedConnection);
connect(
this, &AndroidController::vpnDisconnected, this,
[this]() {
qDebug() << "Android event: VPN disconnected";
emit connectionStateChanged(Vpn::ConnectionState::Disconnected);
},
Qt::QueuedConnection);
connect(
this, &AndroidController::vpnReconnecting, this,
[this]() {
qDebug() << "Android event: VPN reconnecting";
emit connectionStateChanged(Vpn::ConnectionState::Reconnecting);
}, },
Qt::QueuedConnection); Qt::QueuedConnection);
@@ -106,9 +90,7 @@ bool AndroidController::initialize()
{"onServiceDisconnected", "()V", reinterpret_cast<void *>(onServiceDisconnected)}, {"onServiceDisconnected", "()V", reinterpret_cast<void *>(onServiceDisconnected)},
{"onServiceError", "()V", reinterpret_cast<void *>(onServiceError)}, {"onServiceError", "()V", reinterpret_cast<void *>(onServiceError)},
{"onVpnPermissionRejected", "()V", reinterpret_cast<void *>(onVpnPermissionRejected)}, {"onVpnPermissionRejected", "()V", reinterpret_cast<void *>(onVpnPermissionRejected)},
{"onVpnConnected", "()V", reinterpret_cast<void *>(onVpnConnected)}, {"onVpnStateChanged", "(I)V", reinterpret_cast<void *>(onVpnStateChanged)},
{"onVpnDisconnected", "()V", reinterpret_cast<void *>(onVpnDisconnected)},
{"onVpnReconnecting", "()V", reinterpret_cast<void *>(onVpnReconnecting)},
{"onStatisticsUpdate", "(JJ)V", reinterpret_cast<void *>(onStatisticsUpdate)}, {"onStatisticsUpdate", "(JJ)V", reinterpret_cast<void *>(onStatisticsUpdate)},
{"onFileOpened", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onFileOpened)}, {"onFileOpened", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onFileOpened)},
{"onConfigImported", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onConfigImported)}, {"onConfigImported", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onConfigImported)},
@@ -158,6 +140,11 @@ void AndroidController::stop()
callActivityMethod("stop", "()V"); callActivityMethod("stop", "()V");
} }
void AndroidController::resetLastServer(int serverIndex)
{
callActivityMethod("resetLastServer", "(I)V", serverIndex);
}
void AndroidController::saveFile(const QString &fileName, const QString &data) void AndroidController::saveFile(const QString &fileName, const QString &data)
{ {
callActivityMethod("saveFile", "(Ljava/lang/String;Ljava/lang/String;)V", callActivityMethod("saveFile", "(Ljava/lang/String;Ljava/lang/String;)V",
@@ -217,6 +204,11 @@ void AndroidController::clearLogs()
callActivityMethod("clearLogs", "()V"); callActivityMethod("clearLogs", "()V");
} }
void AndroidController::setScreenshotsEnabled(bool enabled)
{
callActivityMethod("setScreenshotsEnabled", "(Z)V", enabled);
}
// Moving log processing to the Android side // Moving log processing to the Android side
jclass AndroidController::log; jclass AndroidController::log;
jmethodID AndroidController::logDebug; jmethodID AndroidController::logDebug;
@@ -370,30 +362,14 @@ void AndroidController::onVpnPermissionRejected(JNIEnv *env, jobject thiz)
} }
// static // static
void AndroidController::onVpnConnected(JNIEnv *env, jobject thiz) void AndroidController::onVpnStateChanged(JNIEnv *env, jobject thiz, jint stateCode)
{ {
Q_UNUSED(env); Q_UNUSED(env);
Q_UNUSED(thiz); Q_UNUSED(thiz);
emit AndroidController::instance()->vpnConnected(); auto state = ConnectionState(stateCode);
}
// static emit AndroidController::instance()->vpnStateChanged(state);
void AndroidController::onVpnDisconnected(JNIEnv *env, jobject thiz)
{
Q_UNUSED(env);
Q_UNUSED(thiz);
emit AndroidController::instance()->vpnDisconnected();
}
// static
void AndroidController::onVpnReconnecting(JNIEnv *env, jobject thiz)
{
Q_UNUSED(env);
Q_UNUSED(thiz);
emit AndroidController::instance()->vpnReconnecting();
} }
// static // static
@@ -20,9 +20,9 @@ public:
// keep synchronized with org.amnezia.vpn.protocol.ProtocolState // keep synchronized with org.amnezia.vpn.protocol.ProtocolState
enum class ConnectionState enum class ConnectionState
{ {
DISCONNECTED,
CONNECTED, CONNECTED,
CONNECTING, CONNECTING,
DISCONNECTED,
DISCONNECTING, DISCONNECTING,
RECONNECTING, RECONNECTING,
UNKNOWN UNKNOWN
@@ -30,6 +30,7 @@ public:
ErrorCode start(const QJsonObject &vpnConfig); ErrorCode start(const QJsonObject &vpnConfig);
void stop(); void stop();
void resetLastServer(int serverIndex);
void setNotificationText(const QString &title, const QString &message, int timerSec); void setNotificationText(const QString &title, const QString &message, int timerSec);
void saveFile(const QString &fileName, const QString &data); void saveFile(const QString &fileName, const QString &data);
QString openFile(const QString &filter); QString openFile(const QString &filter);
@@ -38,6 +39,7 @@ public:
void setSaveLogs(bool enabled); void setSaveLogs(bool enabled);
void exportLogsFile(const QString &fileName); void exportLogsFile(const QString &fileName);
void clearLogs(); void clearLogs();
void setScreenshotsEnabled(bool enabled);
static bool initLogging(); static bool initLogging();
static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message); static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message);
@@ -48,9 +50,7 @@ signals:
void serviceDisconnected(); void serviceDisconnected();
void serviceError(); void serviceError();
void vpnPermissionRejected(); void vpnPermissionRejected();
void vpnConnected(); void vpnStateChanged(ConnectionState state);
void vpnDisconnected();
void vpnReconnecting();
void statisticsUpdated(quint64 rxBytes, quint64 txBytes); void statisticsUpdated(quint64 rxBytes, quint64 txBytes);
void fileOpened(QString uri); void fileOpened(QString uri);
void configImported(QString config); void configImported(QString config);
@@ -77,9 +77,7 @@ private:
static void onServiceDisconnected(JNIEnv *env, jobject thiz); static void onServiceDisconnected(JNIEnv *env, jobject thiz);
static void onServiceError(JNIEnv *env, jobject thiz); static void onServiceError(JNIEnv *env, jobject thiz);
static void onVpnPermissionRejected(JNIEnv *env, jobject thiz); static void onVpnPermissionRejected(JNIEnv *env, jobject thiz);
static void onVpnConnected(JNIEnv *env, jobject thiz); static void onVpnStateChanged(JNIEnv *env, jobject thiz, jint stateCode);
static void onVpnDisconnected(JNIEnv *env, jobject thiz);
static void onVpnReconnecting(JNIEnv *env, jobject thiz);
static void onStatisticsUpdate(JNIEnv *env, jobject thiz, jlong rxBytes, jlong txBytes); static void onStatisticsUpdate(JNIEnv *env, jobject thiz, jlong rxBytes, jlong txBytes);
static void onConfigImported(JNIEnv *env, jobject thiz, jstring data); static void onConfigImported(JNIEnv *env, jobject thiz, jstring data);
static void onFileOpened(JNIEnv *env, jobject thiz, jstring uri); static void onFileOpened(JNIEnv *env, jobject thiz, jstring uri);
+1 -1
View File
@@ -38,7 +38,7 @@ struct Log {
init(_ str: String) { init(_ str: String) {
self.records = str.split(whereSeparator: \.isNewline) self.records = str.split(whereSeparator: \.isNewline)
.compactMap { .compactMap {
Record(String($0))! Record(String($0))
} }
} }
+24
View File
@@ -1,4 +1,5 @@
import Foundation import Foundation
import NetworkExtension
public func swiftUpdateLogData(_ qtString: std.string) -> std.string { public func swiftUpdateLogData(_ qtString: std.string) -> std.string {
let qtLog = Log(String(describing: qtString)) let qtLog = Log(String(describing: qtString))
@@ -24,3 +25,26 @@ public func swiftDeleteLog() {
public func toggleLogging(_ isEnabled: Bool) { public func toggleLogging(_ isEnabled: Bool) {
Log.isLoggingEnabled = isEnabled Log.isLoggingEnabled = isEnabled
} }
public func clearSettings() {
NETunnelProviderManager.loadAllFromPreferences { managers, error in
if let error {
NSLog("clearSettings removeFromPreferences error: \(error.localizedDescription)")
return
}
managers?.forEach { manager in
manager.removeFromPreferences { error in
if let error {
NSLog("NE removeFromPreferences error: \(error.localizedDescription)")
} else {
manager.loadFromPreferences { error in
if let error {
NSLog("NE loadFromPreferences after remove error: \(error.localizedDescription)")
}
}
}
}
}
}
}
@@ -18,40 +18,32 @@ extension PacketTunnelProvider: OpenVPNAdapterDelegate {
// send empty string to NEDNSSettings.matchDomains // send empty string to NEDNSSettings.matchDomains
networkSettings?.dnsSettings?.matchDomains = [""] networkSettings?.dnsSettings?.matchDomains = [""]
if splitTunnelType == "1" { if splitTunnelType == 1 {
var ipv4IncludedRoutes = [NEIPv4Route]() var ipv4IncludedRoutes = [NEIPv4Route]()
let STSdata = Data(splitTunnelSites!.utf8)
do { for allowedIPString in splitTunnelSites {
guard let STSArray = try JSONSerialization.jsonObject(with: STSdata) as? [String] else { return } if let allowedIP = IPAddressRange(from: allowedIPString) {
for allowedIPString in STSArray { ipv4IncludedRoutes.append(NEIPv4Route(
if let allowedIP = IPAddressRange(from: allowedIPString) { destinationAddress: "\(allowedIP.address)",
ipv4IncludedRoutes.append(NEIPv4Route( subnetMask: "\(allowedIP.subnetMask())"))
destinationAddress: "\(allowedIP.address)",
subnetMask: "\(allowedIP.subnetMask())"))
}
} }
} catch {
wg_log(.error, message: "Parse JSONSerialization Error")
} }
networkSettings?.ipv4Settings?.includedRoutes = ipv4IncludedRoutes networkSettings?.ipv4Settings?.includedRoutes = ipv4IncludedRoutes
} else { } else {
if splitTunnelType == "2" { if splitTunnelType == 2 {
var ipv4ExcludedRoutes = [NEIPv4Route]() var ipv4ExcludedRoutes = [NEIPv4Route]()
var ipv4IncludedRoutes = [NEIPv4Route]() var ipv4IncludedRoutes = [NEIPv4Route]()
var ipv6IncludedRoutes = [NEIPv6Route]() var ipv6IncludedRoutes = [NEIPv6Route]()
let STSdata = Data(splitTunnelSites!.utf8)
do { for excludeIPString in splitTunnelSites {
guard let STSArray = try JSONSerialization.jsonObject(with: STSdata) as? [String] else { return } if let excludeIP = IPAddressRange(from: excludeIPString) {
for excludeIPString in STSArray { ipv4ExcludedRoutes.append(NEIPv4Route(
if let excludeIP = IPAddressRange(from: excludeIPString) { destinationAddress: "\(excludeIP.address)",
ipv4ExcludedRoutes.append(NEIPv4Route( subnetMask: "\(excludeIP.subnetMask())"))
destinationAddress: "\(excludeIP.address)",
subnetMask: "\(excludeIP.subnetMask())"))
}
} }
} catch {
wg_log(.error, message: "Parse JSONSerialization Error")
} }
if let allIPv4 = IPAddressRange(from: "0.0.0.0/0") { if let allIPv4 = IPAddressRange(from: "0.0.0.0/0") {
ipv4IncludedRoutes.append(NEIPv4Route( ipv4IncludedRoutes.append(NEIPv4Route(
destinationAddress: "\(allIPv4.address)", destinationAddress: "\(allIPv4.address)",
+82 -90
View File
@@ -50,8 +50,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
private let dispatchQueue = DispatchQueue(label: "PacketTunnel", qos: .utility) private let dispatchQueue = DispatchQueue(label: "PacketTunnel", qos: .utility)
private var openVPNConfig: Data? private var openVPNConfig: Data?
var splitTunnelType: String? var splitTunnelType: Int!
var splitTunnelSites: String? var splitTunnelSites: [String]!
let vpnReachability = OpenVPNReachability() let vpnReachability = OpenVPNReachability()
@@ -81,22 +81,6 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
if action == Constants.kActionStatus { if action == Constants.kActionStatus {
handleStatusAppMessage(messageData, completionHandler: completionHandler) handleStatusAppMessage(messageData, completionHandler: completionHandler)
} }
if action == Constants.kActionStart {
splitTunnelType = message[Constants.kMessageKeySplitTunnelType] as? String
splitTunnelSites = message[Constants.kMessageKeySplitTunnelSites] as? String
}
let callbackWrapper: (NSNumber?) -> Void = { errorCode in
// let tunnelId = self.tunnelConfig?.id ?? ""
let response: [String: Any] = [
Constants.kMessageKeyAction: action,
Constants.kMessageKeyErrorCode: errorCode ?? NSNull(),
Constants.kMessageKeyTunnelId: 0
]
completionHandler(try? JSONSerialization.data(withJSONObject: response, options: []))
}
} }
override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) { override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) {
@@ -169,110 +153,118 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
completionHandler: @escaping (Error?) -> Void) { completionHandler: @escaping (Error?) -> Void) {
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol, guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
let providerConfiguration = protocolConfiguration.providerConfiguration, let providerConfiguration = protocolConfiguration.providerConfiguration,
let wgConfig: Data = providerConfiguration[Constants.wireGuardConfigKey] as? Data else { let wgConfigData: Data = providerConfiguration[Constants.wireGuardConfigKey] as? Data else {
wg_log(.error, message: "Can't start WireGuard config missing") wg_log(.error, message: "Can't start WireGuard config missing")
completionHandler(nil) completionHandler(nil)
return return
} }
guard let wgConfigStr = try? JSONDecoder().decode(WGConfig.self, from: wgConfig).str, do {
let tunnelConfiguration = try? TunnelConfiguration(fromWgQuickConfig: wgConfigStr) let wgConfig = try JSONDecoder().decode(WGConfig.self, from: wgConfigData)
else { let wgConfigStr = wgConfig.str
wg_log(.error, message: "Can't parse WireGuard config") log(.info, message: "wgConfig: \(wgConfig.redux.replacingOccurrences(of: "\n", with: " "))")
completionHandler(nil)
return
}
log(.info, message: "wgConfig: \(wgConfigStr.replacingOccurrences(of: "\n", with: " "))") let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: wgConfigStr)
if tunnelConfiguration.peers.first!.allowedIPs if tunnelConfiguration.peers.first!.allowedIPs
.map({ $0.stringRepresentation }) .map({ $0.stringRepresentation })
.joined(separator: ", ") == "0.0.0.0/0, ::/0" { .joined(separator: ", ") == "0.0.0.0/0, ::/0" {
if splitTunnelType == "1" { if wgConfig.splitTunnelType == 1 {
for index in tunnelConfiguration.peers.indices { for index in tunnelConfiguration.peers.indices {
tunnelConfiguration.peers[index].allowedIPs.removeAll() tunnelConfiguration.peers[index].allowedIPs.removeAll()
var allowedIPs = [IPAddressRange]() var allowedIPs = [IPAddressRange]()
let STSdata = Data(splitTunnelSites!.utf8)
do { for allowedIPString in wgConfig.splitTunnelSites {
guard let STSArray = try JSONSerialization.jsonObject(with: STSdata) as? [String] else { return }
for allowedIPString in STSArray {
if let allowedIP = IPAddressRange(from: allowedIPString) { if let allowedIP = IPAddressRange(from: allowedIPString) {
allowedIPs.append(allowedIP) allowedIPs.append(allowedIP)
} }
} }
} catch {
wg_log(.error, message: "Parse JSONSerialization Error") tunnelConfiguration.peers[index].allowedIPs = allowedIPs
} }
tunnelConfiguration.peers[index].allowedIPs = allowedIPs } else if wgConfig.splitTunnelType == 2 {
} for index in tunnelConfiguration.peers.indices {
} else if splitTunnelType == "2" { var excludeIPs = [IPAddressRange]()
for index in tunnelConfiguration.peers.indices {
var excludeIPs = [IPAddressRange]() for excludeIPString in wgConfig.splitTunnelSites {
let STSdata = Data(splitTunnelSites!.utf8)
do {
guard let STSArray = try JSONSerialization.jsonObject(with: STSdata) as? [String] else { return }
for excludeIPString in STSArray {
if let excludeIP = IPAddressRange(from: excludeIPString) { if let excludeIP = IPAddressRange(from: excludeIPString) {
excludeIPs.append(excludeIP) excludeIPs.append(excludeIP)
} }
} }
} catch {
wg_log(.error, message: "Parse JSONSerialization Error") tunnelConfiguration.peers[index].excludeIPs = excludeIPs
} }
tunnelConfiguration.peers[index].excludeIPs = excludeIPs
} }
} }
}
wg_log(.info, message: "Starting wireguard tunnel from the " + wg_log(.info, message: "Starting wireguard tunnel from the " +
(activationAttemptId == nil ? "OS directly, rather than the app" : "app")) (activationAttemptId == nil ? "OS directly, rather than the app" : "app"))
// Start the tunnel // Start the tunnel
wgAdapter.start(tunnelConfiguration: tunnelConfiguration) { adapterError in wgAdapter.start(tunnelConfiguration: tunnelConfiguration) { adapterError in
guard let adapterError else { guard let adapterError else {
let interfaceName = self.wgAdapter.interfaceName ?? "unknown" let interfaceName = self.wgAdapter.interfaceName ?? "unknown"
wg_log(.info, message: "Tunnel interface is \(interfaceName)") wg_log(.info, message: "Tunnel interface is \(interfaceName)")
completionHandler(nil) completionHandler(nil)
return return
} }
switch adapterError { switch adapterError {
case .cannotLocateTunnelFileDescriptor: case .cannotLocateTunnelFileDescriptor:
wg_log(.error, staticMessage: "Starting tunnel failed: could not determine file descriptor") wg_log(.error, staticMessage: "Starting tunnel failed: could not determine file descriptor")
errorNotifier.notify(PacketTunnelProviderError.couldNotDetermineFileDescriptor) errorNotifier.notify(PacketTunnelProviderError.couldNotDetermineFileDescriptor)
completionHandler(PacketTunnelProviderError.couldNotDetermineFileDescriptor) completionHandler(PacketTunnelProviderError.couldNotDetermineFileDescriptor)
case .dnsResolution(let dnsErrors): case .dnsResolution(let dnsErrors):
let hostnamesWithDnsResolutionFailure = dnsErrors.map { $0.address } let hostnamesWithDnsResolutionFailure = dnsErrors.map { $0.address }
.joined(separator: ", ") .joined(separator: ", ")
wg_log(.error, message: wg_log(.error, message:
"DNS resolution failed for the following hostnames: \(hostnamesWithDnsResolutionFailure)") "DNS resolution failed for the following hostnames: \(hostnamesWithDnsResolutionFailure)")
errorNotifier.notify(PacketTunnelProviderError.dnsResolutionFailure) errorNotifier.notify(PacketTunnelProviderError.dnsResolutionFailure)
completionHandler(PacketTunnelProviderError.dnsResolutionFailure) completionHandler(PacketTunnelProviderError.dnsResolutionFailure)
case .setNetworkSettings(let error): case .setNetworkSettings(let error):
wg_log(.error, message: wg_log(.error, message:
"Starting tunnel failed with setTunnelNetworkSettings returning \(error.localizedDescription)") "Starting tunnel failed with setTunnelNetworkSettings returning \(error.localizedDescription)")
errorNotifier.notify(PacketTunnelProviderError.couldNotSetNetworkSettings) errorNotifier.notify(PacketTunnelProviderError.couldNotSetNetworkSettings)
completionHandler(PacketTunnelProviderError.couldNotSetNetworkSettings) completionHandler(PacketTunnelProviderError.couldNotSetNetworkSettings)
case .startWireGuardBackend(let errorCode): case .startWireGuardBackend(let errorCode):
wg_log(.error, message: "Starting tunnel failed with wgTurnOn returning \(errorCode)") wg_log(.error, message: "Starting tunnel failed with wgTurnOn returning \(errorCode)")
errorNotifier.notify(PacketTunnelProviderError.couldNotStartBackend) errorNotifier.notify(PacketTunnelProviderError.couldNotStartBackend)
completionHandler(PacketTunnelProviderError.couldNotStartBackend) completionHandler(PacketTunnelProviderError.couldNotStartBackend)
case .invalidState: case .invalidState:
fatalError() fatalError()
}
} }
} catch {
log(.error, message: "Can't parse WG config: \(error.localizedDescription)")
completionHandler(nil)
return
} }
} }
private func startOpenVPN(completionHandler: @escaping (Error?) -> Void) { private func startOpenVPN(completionHandler: @escaping (Error?) -> Void) {
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol, guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
let providerConfiguration = protocolConfiguration.providerConfiguration, let providerConfiguration = protocolConfiguration.providerConfiguration,
let ovpnConfiguration: Data = providerConfiguration[Constants.ovpnConfigKey] as? Data else { let openVPNConfigData = providerConfiguration[Constants.ovpnConfigKey] as? Data else {
wg_log(.error, message: "Can't start startOpenVPN()") wg_log(.error, message: "Can't start startOpenVPN()")
return return
} }
setupAndlaunchOpenVPN(withConfig: ovpnConfiguration, completionHandler: completionHandler) do {
log(.info, message: "providerConfiguration: \(String(decoding: openVPNConfigData, as: UTF8.self).replacingOccurrences(of: "\n", with: " "))")
let openVPNConfig = try JSONDecoder().decode(OpenVPNConfig.self, from: openVPNConfigData)
log(.info, message: "openVPNConfig: \(openVPNConfig.str.replacingOccurrences(of: "\n", with: " "))")
let ovpnConfiguration = Data(openVPNConfig.config.utf8)
setupAndlaunchOpenVPN(withConfig: ovpnConfiguration, completionHandler: completionHandler)
} catch {
log(.error, message: "Can't parse OpenVPN config: \(error.localizedDescription)")
if let underlyingError = (error as NSError).userInfo[NSUnderlyingErrorKey] as? NSError {
log(.error, message: "Can't parse OpenVPN config: \(underlyingError.localizedDescription)")
}
return
}
} }
private func stopWireguard(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { private func stopWireguard(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
+4 -1
View File
@@ -1,4 +1,7 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
@interface QtAppDelegate : UIResponder <UIApplicationDelegate> @interface QIOSApplicationDelegate
@end
@interface QIOSApplicationDelegate (AmneziaVPNDelegate)
@end @end
+16 -55
View File
@@ -3,41 +3,17 @@
#include <QFile> #include <QFile>
@implementation QtAppDelegate {
UIView *_screen;
}
+(QtAppDelegate *)sharedQtAppDelegate {
static dispatch_once_t pred;
static QtAppDelegate *shared = nil;
dispatch_once(&pred, ^{
shared = [[super alloc] init];
});
return shared;
}
@implementation QIOSApplicationDelegate (AmneziaVPNDelegate)
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{ {
[application setMinimumBackgroundFetchInterval: UIApplicationBackgroundFetchIntervalMinimum]; [application setMinimumBackgroundFetchInterval: UIApplicationBackgroundFetchIntervalMinimum];
// Override point for customization after application launch. // Override point for customization after application launch.
NSLog(@"Did this launch option happen"); NSLog(@"Application didFinishLaunchingWithOptions");
return YES; return YES;
} }
- (void)applicationWillResignActive:(UIApplication *)application
{
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
_screen = [UIScreen.mainScreen snapshotViewAfterScreenUpdates: false];
UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle: UIBlurEffectStyleDark];
UIVisualEffectView *blurBackground = [[UIVisualEffectView alloc] initWithEffect: blurEffect];
[_screen addSubview: blurBackground];
blurBackground.frame = _screen.frame;
UIWindow *_window = UIApplication.sharedApplication.keyWindow;
[_window addSubview: _screen];
}
- (void)applicationDidEnterBackground:(UIApplication *)application - (void)applicationDidEnterBackground:(UIApplication *)application
{ {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
@@ -51,17 +27,6 @@
NSLog(@"In the foreground"); NSLog(@"In the foreground");
} }
- (void)applicationDidBecomeActive:(UIApplication *)application
{
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
[_screen removeFromSuperview];
}
- (void)applicationWillTerminate:(UIApplication *)application
{
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { -(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
// We will add content here soon. // We will add content here soon.
NSLog(@"In the completionHandler"); NSLog(@"In the completionHandler");
@@ -70,31 +35,27 @@
- (BOOL)application:(UIApplication *)app - (BOOL)application:(UIApplication *)app
openURL:(NSURL *)url openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options { options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
NSLog(@"Application openURL: %@", url);
if (url.fileURL) { if (url.fileURL) {
QString filePath(url.path.UTF8String); QString filePath(url.path.UTF8String);
if (filePath.isEmpty()) return NO; if (filePath.isEmpty()) return NO;
if (filePath.contains("backup")) { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
IosController::Instance()->importBackupFromOutside(filePath); NSLog(@"Application openURL: %@", url);
} else {
QFile file(filePath); if (filePath.contains("backup")) {
bool isOpenFile = file.open(QIODevice::ReadOnly); IosController::Instance()->importBackupFromOutside(filePath);
QByteArray data = file.readAll(); } else {
QFile file(filePath);
IosController::Instance()->importConfigFromOutside(QString(data)); bool isOpenFile = file.open(QIODevice::ReadOnly);
} QByteArray data = file.readAll();
IosController::Instance()->importConfigFromOutside(QString(data));
}
});
return YES; return YES;
} }
return NO; return NO;
} }
void QtAppDelegateInitialize()
{
[[UIApplication sharedApplication] setDelegate: [QtAppDelegate sharedQtAppDelegate]];
NSLog(@"Created a new AppDelegate");
}
@end @end
@@ -0,0 +1,87 @@
import UIKit
public func toggleScreenshots(_ isEnabled: Bool) {
let window = UIApplication.shared.keyWindows.first!
if isEnabled {
ScreenProtection.shared.disable(for: window.rootViewController!.view)
} else {
ScreenProtection.shared.enable(for: window.rootViewController!.view)
}
}
extension UIApplication {
var keyWindows: [UIWindow] {
connectedScenes
.compactMap {
if #available(iOS 15.0, *) {
($0 as? UIWindowScene)?.keyWindow
} else {
($0 as? UIWindowScene)?.windows.first { $0.isKeyWindow }
}
}
}
}
class ScreenProtection {
public static let shared = ScreenProtection()
var pairs = [ProtectionPair]()
private var blurView: UIVisualEffectView?
private var recordingObservation: NSKeyValueObservation?
public func enable(for view: UIView) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
view.subviews.forEach {
self.pairs.append(ProtectionPair(from: $0))
}
}
}
public func disable(for view: UIView) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
self.pairs.forEach {
$0.removeProtection()
}
self.pairs.removeAll()
}
}
}
struct ProtectionPair {
let textField: UITextField
let layer: CALayer
init(from view: UIView) {
let secureTextField = UITextField()
secureTextField.backgroundColor = .clear
secureTextField.translatesAutoresizingMaskIntoConstraints = false
secureTextField.isSecureTextEntry = true
view.insertSubview(secureTextField, at: 0)
secureTextField.isUserInteractionEnabled = false
view.layer.superlayer?.addSublayer(secureTextField.layer)
secureTextField.layer.sublayers?.last?.addSublayer(view.layer)
secureTextField.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true
secureTextField.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true
secureTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
secureTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive = true
self.init(textField: secureTextField, layer: view.layer)
}
init(textField: UITextField, layer: CALayer) {
self.textField = textField
self.layer = layer
}
func removeProtection() {
textField.superview?.superview?.layer.addSublayer(layer)
textField.layer.removeFromSuperlayer()
textField.removeFromSuperview()
}
}
+71 -104
View File
@@ -1,10 +1,43 @@
import Foundation import Foundation
struct WGConfigData: Decodable { struct WGConfig: Decodable {
let initPacketMagicHeader, responsePacketMagicHeader: String? let initPacketMagicHeader, responsePacketMagicHeader: String?
let underloadPacketMagicHeader, transportPacketMagicHeader: String? let underloadPacketMagicHeader, transportPacketMagicHeader: String?
let junkPacketCount, junkPacketMinSize, junkPacketMaxSize: String? let junkPacketCount, junkPacketMinSize, junkPacketMaxSize: String?
let initPacketJunkSize, responsePacketJunkSize: String? let initPacketJunkSize, responsePacketJunkSize: String?
let dns1: String
let dns2: String
let mtu: String
let hostName: String
let port: Int
let clientIP: String
let clientPrivateKey: String
let serverPublicKey: String
let presharedKey: String
var allowedIPs: [String]
var persistentKeepAlive: String
let splitTunnelType: Int
let splitTunnelSites: [String]
enum CodingKeys: String, CodingKey {
case initPacketMagicHeader = "H1", responsePacketMagicHeader = "H2"
case underloadPacketMagicHeader = "H3", transportPacketMagicHeader = "H4"
case junkPacketCount = "Jc", junkPacketMinSize = "Jmin", junkPacketMaxSize = "Jmax"
case initPacketJunkSize = "S1", responsePacketJunkSize = "S2"
case dns1
case dns2
case mtu
case hostName
case port
case clientIP = "client_ip"
case clientPrivateKey = "client_priv_key"
case serverPublicKey = "server_pub_key"
case presharedKey = "psk_key"
case allowedIPs = "allowed_ips"
case persistentKeepAlive = "persistent_keep_alive"
case splitTunnelType
case splitTunnelSites
}
var settings: String { var settings: String {
junkPacketCount == nil ? "" : junkPacketCount == nil ? "" :
@@ -22,114 +55,48 @@ struct WGConfigData: Decodable {
""" """
} }
let clientIP: String
let clientPrivateKey: String
let clientPublicKey: String
let serverPublicKey: String
let presharedKey: String
let hostName: String
let port: Int
var allowedIPs: [String]
var persistentKeepAlive: String
enum CodingKeys: String, CodingKey {
case initPacketMagicHeader = "H1", responsePacketMagicHeader = "H2"
case underloadPacketMagicHeader = "H3", transportPacketMagicHeader = "H4"
case junkPacketCount = "Jc", junkPacketMinSize = "Jmin", junkPacketMaxSize = "Jmax"
case initPacketJunkSize = "S1", responsePacketJunkSize = "S2"
case clientIP = "client_ip" // "10.8.1.16"
case clientPrivateKey = "client_priv_key"
case clientPublicKey = "client_pub_key"
case serverPublicKey = "server_pub_key"
case presharedKey = "psk_key"
case allowedIPs = "allowed_ips"
case persistentKeepAlive = "persistent_keep_alive"
case hostName
case port
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.initPacketMagicHeader = try container.decodeIfPresent(String.self, forKey: .initPacketMagicHeader)
self.responsePacketMagicHeader = try container.decodeIfPresent(String.self, forKey: .responsePacketMagicHeader)
self.underloadPacketMagicHeader = try container.decodeIfPresent(String.self, forKey: .underloadPacketMagicHeader)
self.transportPacketMagicHeader = try container.decodeIfPresent(String.self, forKey: .transportPacketMagicHeader)
self.junkPacketCount = try container.decodeIfPresent(String.self, forKey: .junkPacketCount)
self.junkPacketMinSize = try container.decodeIfPresent(String.self, forKey: .junkPacketMinSize)
self.junkPacketMaxSize = try container.decodeIfPresent(String.self, forKey: .junkPacketMaxSize)
self.initPacketJunkSize = try container.decodeIfPresent(String.self, forKey: .initPacketJunkSize)
self.responsePacketJunkSize = try container.decodeIfPresent(String.self, forKey: .responsePacketJunkSize)
self.clientIP = try container.decode(String.self, forKey: .clientIP)
self.clientPrivateKey = try container.decode(String.self, forKey: .clientPrivateKey)
self.clientPublicKey = try container.decode(String.self, forKey: .clientPublicKey)
self.serverPublicKey = try container.decode(String.self, forKey: .serverPublicKey)
self.presharedKey = try container.decode(String.self, forKey: .presharedKey)
self.allowedIPs = try container.decodeIfPresent([String].self, forKey: .allowedIPs) ?? ["0.0.0.0/0", "::/0"]
self.persistentKeepAlive = try container.decodeIfPresent(String.self, forKey: .persistentKeepAlive) ?? "25"
self.hostName = try container.decode(String.self, forKey: .hostName)
self.port = try container.decode(Int.self, forKey: .port)
}
}
struct WGConfig: Decodable {
let data: WGConfigData
let configVersion: Int
let description: String
let dns1: String
let dns2: String
let hostName: String
let `protocol`: String
let splitTunnelSites: [String]
let splitTunnelType: Int
enum CodingKeys: String, CodingKey {
case awgConfigData = "awg_config_data", wgConfigData = "wireguard_config_data"
case configData
case configVersion = "config_version"
case description
case dns1
case dns2
case hostName
case `protocol`
case splitTunnelSites
case splitTunnelType
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
if container.contains(.awgConfigData) {
self.data = try container.decode(WGConfigData.self, forKey: .awgConfigData)
} else {
self.data = try container.decode(WGConfigData.self, forKey: .wgConfigData)
}
self.configVersion = try container.decode(Int.self, forKey: .configVersion)
self.description = try container.decode(String.self, forKey: .description)
self.dns1 = try container.decode(String.self, forKey: .dns1)
self.dns2 = try container.decode(String.self, forKey: .dns2)
self.hostName = try container.decode(String.self, forKey: .hostName)
self.protocol = try container.decode(String.self, forKey: .protocol)
self.splitTunnelSites = try container.decode([String].self, forKey: .splitTunnelSites)
self.splitTunnelType = try container.decode(Int.self, forKey: .splitTunnelType)
}
var str: String { var str: String {
""" """
[Interface] [Interface]
Address = \(data.clientIP)/32 Address = \(clientIP)
DNS = \(dns1), \(dns2) DNS = \(dns1), \(dns2)
PrivateKey = \(data.clientPrivateKey) MTU = \(mtu)
\(data.settings) PrivateKey = \(clientPrivateKey)
\(settings)
[Peer] [Peer]
PublicKey = \(data.serverPublicKey) PublicKey = \(serverPublicKey)
PresharedKey = \(data.presharedKey) PresharedKey = \(presharedKey)
AllowedIPs = \(data.allowedIPs.joined(separator: ", ")) AllowedIPs = \(allowedIPs.joined(separator: ", "))
Endpoint = \(data.hostName):\(data.port) Endpoint = \(hostName):\(port)
PersistentKeepalive = \(data.persistentKeepAlive) PersistentKeepalive = \(persistentKeepAlive)
"""
}
var redux: String {
"""
[Interface]
Address = \(clientIP)
DNS = \(dns1), \(dns2)
MTU = \(mtu)
PrivateKey = ***
\(settings)
[Peer]
PublicKey = ***
PresharedKey = ***
AllowedIPs = \(allowedIPs.joined(separator: ", "))
Endpoint = \(hostName):\(port)
PersistentKeepalive = \(persistentKeepAlive)
""" """
} }
} }
struct OpenVPNConfig: Decodable {
let config: String
let mtu: String
let splitTunnelType: Int
let splitTunnelSites: [String]
var str: String {
"splitTunnelType: \(splitTunnelType) splitTunnelSites: \(splitTunnelSites) mtu: \(mtu) config: \(config)"
}
}
+150 -43
View File
@@ -235,7 +235,6 @@ void IosController::checkStatus()
m_rxBytes = rxBytes; m_rxBytes = rxBytes;
m_txBytes = txBytes; m_txBytes = txBytes;
}); });
} }
void IosController::vpnStatusDidChange(void *pNotification) void IosController::vpnStatusDidChange(void *pNotification)
@@ -244,13 +243,13 @@ void IosController::vpnStatusDidChange(void *pNotification)
if (session /* && session == TunnelManager.session */ ) { if (session /* && session == TunnelManager.session */ ) {
qDebug() << "IosController::vpnStatusDidChange" << iosStatusToState(session.status) << session; qDebug() << "IosController::vpnStatusDidChange" << iosStatusToState(session.status) << session;
if (session.status == NEVPNStatusDisconnected) { if (session.status == NEVPNStatusDisconnected) {
if (@available(iOS 16.0, *)) { if (@available(iOS 16.0, *)) {
[session fetchLastDisconnectErrorWithCompletionHandler:^(NSError * _Nullable error) { [session fetchLastDisconnectErrorWithCompletionHandler:^(NSError * _Nullable error) {
if (error != nil) { if (error != nil) {
qDebug() << "Disconnect error" << error.domain << error.code << error.localizedDescription; qDebug() << "Disconnect error" << error.domain << error.code << error.localizedDescription;
if ([error.domain isEqualToString:NEVPNConnectionErrorDomain]) { if ([error.domain isEqualToString:NEVPNConnectionErrorDomain]) {
switch (error.code) { switch (error.code) {
case NEVPNConnectionErrorOverslept: case NEVPNConnectionErrorOverslept:
@@ -315,11 +314,11 @@ void IosController::vpnStatusDidChange(void *pNotification)
break; break;
} }
} }
NSError *underlyingError = error.userInfo[@"NSUnderlyingError"]; NSError *underlyingError = error.userInfo[@"NSUnderlyingError"];
if (underlyingError != nil) { if (underlyingError != nil) {
qDebug() << "Disconnect underlying error" << underlyingError.domain << underlyingError.code << underlyingError.localizedDescription; qDebug() << "Disconnect underlying error" << underlyingError.domain << underlyingError.code << underlyingError.localizedDescription;
if ([underlyingError.domain isEqualToString:@"NEAgentErrorDomain"]) { if ([underlyingError.domain isEqualToString:@"NEAgentErrorDomain"]) {
switch (underlyingError.code) { switch (underlyingError.code) {
case 1: case 1:
@@ -342,7 +341,7 @@ void IosController::vpnStatusDidChange(void *pNotification)
qDebug() << "Disconnect error is unavailable on iOS < 16.0"; qDebug() << "Disconnect error is unavailable on iOS < 16.0";
} }
} }
emit connectionStateChanged(iosStatusToState(session.status)); emit connectionStateChanged(iosStatusToState(session.status));
} }
} }
@@ -357,7 +356,29 @@ bool IosController::setupOpenVPN()
QJsonObject ovpn = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::OpenVpn)].toObject(); QJsonObject ovpn = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::OpenVpn)].toObject();
QString ovpnConfig = ovpn[config_key::config].toString(); QString ovpnConfig = ovpn[config_key::config].toString();
return startOpenVPN(ovpnConfig); QJsonObject openVPNConfig {};
openVPNConfig.insert(config_key::config, ovpnConfig);
if (ovpn.contains(config_key::mtu)) {
openVPNConfig.insert(config_key::mtu, ovpn[config_key::mtu]);
} else {
openVPNConfig.insert(config_key::mtu, protocols::openvpn::defaultMtu);
}
openVPNConfig.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(" ");
}
openVPNConfig.insert(config_key::splitTunnelSites, splitTunnelSites);
QJsonDocument openVPNConfigDoc(openVPNConfig);
QString openVPNConfigStr(openVPNConfigDoc.toJson(QJsonDocument::Compact));
return startOpenVPN(openVPNConfigStr);
} }
bool IosController::setupCloak() bool IosController::setupCloak()
@@ -394,27 +415,137 @@ bool IosController::setupCloak()
ovpnConfig.append(cloakBase64); ovpnConfig.append(cloakBase64);
ovpnConfig.append("\n</cloak>\n"); ovpnConfig.append("\n</cloak>\n");
return startOpenVPN(ovpnConfig); QJsonObject openVPNConfig {};
openVPNConfig.insert(config_key::config, ovpnConfig);
if (ovpn.contains(config_key::mtu)) {
openVPNConfig.insert(config_key::mtu, ovpn[config_key::mtu]);
} else {
openVPNConfig.insert(config_key::mtu, protocols::openvpn::defaultMtu);
}
QJsonArray splitTunnelSites = m_rawConfig[config_key::splitTunnelSites].toArray();
for(int index = 0; index < splitTunnelSites.count(); index++) {
splitTunnelSites[index] = splitTunnelSites[index].toString().remove(" ");
}
openVPNConfig.insert(config_key::splitTunnelSites, splitTunnelSites);
QJsonDocument openVPNConfigDoc(openVPNConfig);
QString openVPNConfigStr(openVPNConfigDoc.toJson(QJsonDocument::Compact));
return startOpenVPN(openVPNConfigStr);
} }
bool IosController::setupWireGuard() bool IosController::setupWireGuard()
{ {
QJsonObject config = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::WireGuard)].toObject(); QJsonObject config = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::WireGuard)].toObject();
QJsonDocument doc(m_rawConfig);
QString wgConfig(doc.toJson(QJsonDocument::Compact));
return startWireGuard(wgConfig); QJsonObject wgConfig {};
wgConfig.insert(config_key::dns1, m_rawConfig[config_key::dns1]);
wgConfig.insert(config_key::dns2, m_rawConfig[config_key::dns2]);
if (config.contains(config_key::mtu)) {
wgConfig.insert(config_key::mtu, config[config_key::mtu]);
} else {
wgConfig.insert(config_key::mtu, protocols::wireguard::defaultMtu);
}
wgConfig.insert(config_key::hostName, config[config_key::hostName]);
wgConfig.insert(config_key::port, config[config_key::port]);
wgConfig.insert(config_key::client_ip, config[config_key::client_ip]);
wgConfig.insert(config_key::client_priv_key, config[config_key::client_priv_key]);
wgConfig.insert(config_key::server_pub_key, config[config_key::server_pub_key]);
wgConfig.insert(config_key::psk_key, config[config_key::psk_key]);
wgConfig.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(" ");
}
wgConfig.insert(config_key::splitTunnelSites, splitTunnelSites);
if (config.contains(config_key::allowed_ips) && config[config_key::allowed_ips].isArray()) {
wgConfig.insert(config_key::allowed_ips, config[config_key::allowed_ips]);
} else {
QJsonArray allowed_ips { "0.0.0.0/0", "::/0" };
wgConfig.insert(config_key::allowed_ips, allowed_ips);
}
if (config.contains(config_key::persistent_keep_alive)) {
wgConfig.insert(config_key::persistent_keep_alive, config[config_key::persistent_keep_alive]);
} else {
wgConfig.insert(config_key::persistent_keep_alive, "25");
}
QJsonDocument wgConfigDoc(wgConfig);
QString wgConfigDocStr(wgConfigDoc.toJson(QJsonDocument::Compact));
return startWireGuard(wgConfigDocStr);
} }
bool IosController::setupAwg() bool IosController::setupAwg()
{ {
QJsonObject config = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::Awg)].toObject(); QJsonObject config = m_rawConfig[ProtocolProps::key_proto_config_data(amnezia::Proto::Awg)].toObject();
QJsonDocument doc(m_rawConfig); QJsonObject wgConfig {};
QString wgConfig(doc.toJson(QJsonDocument::Compact)); wgConfig.insert(config_key::dns1, m_rawConfig[config_key::dns1]);
wgConfig.insert(config_key::dns2, m_rawConfig[config_key::dns2]);
return startWireGuard(wgConfig); if (config.contains(config_key::mtu)) {
wgConfig.insert(config_key::mtu, config[config_key::mtu]);
} else {
wgConfig.insert(config_key::mtu, protocols::awg::defaultMtu);
}
wgConfig.insert(config_key::hostName, config[config_key::hostName]);
wgConfig.insert(config_key::port, config[config_key::port]);
wgConfig.insert(config_key::client_ip, config[config_key::client_ip]);
wgConfig.insert(config_key::client_priv_key, config[config_key::client_priv_key]);
wgConfig.insert(config_key::server_pub_key, config[config_key::server_pub_key]);
wgConfig.insert(config_key::psk_key, config[config_key::psk_key]);
wgConfig.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(" ");
}
wgConfig.insert(config_key::splitTunnelSites, splitTunnelSites);
if (config.contains(config_key::allowed_ips) && config[config_key::allowed_ips].isArray()) {
wgConfig.insert(config_key::allowed_ips, config[config_key::allowed_ips]);
} else {
QJsonArray allowed_ips { "0.0.0.0/0", "::/0" };
wgConfig.insert(config_key::allowed_ips, allowed_ips);
}
if (config.contains(config_key::persistent_keep_alive)) {
wgConfig.insert(config_key::persistent_keep_alive, config[config_key::persistent_keep_alive]);
} else {
wgConfig.insert(config_key::persistent_keep_alive, "25");
}
wgConfig.insert(config_key::initPacketMagicHeader, config[config_key::initPacketMagicHeader]);
wgConfig.insert(config_key::responsePacketMagicHeader, config[config_key::responsePacketMagicHeader]);
wgConfig.insert(config_key::underloadPacketMagicHeader, config[config_key::underloadPacketMagicHeader]);
wgConfig.insert(config_key::transportPacketMagicHeader, config[config_key::transportPacketMagicHeader]);
wgConfig.insert(config_key::initPacketJunkSize, config[config_key::initPacketJunkSize]);
wgConfig.insert(config_key::responsePacketJunkSize, config[config_key::responsePacketJunkSize]);
wgConfig.insert(config_key::junkPacketCount, config[config_key::junkPacketCount]);
wgConfig.insert(config_key::junkPacketMinSize, config[config_key::junkPacketMinSize]);
wgConfig.insert(config_key::junkPacketMaxSize, config[config_key::junkPacketMaxSize]);
QJsonDocument wgConfigDoc(wgConfig);
QString wgConfigDocStr(wgConfigDoc.toJson(QJsonDocument::Compact));
return startWireGuard(wgConfigDocStr);
} }
bool IosController::startOpenVPN(const QString &config) bool IosController::startOpenVPN(const QString &config)
@@ -448,23 +579,17 @@ bool IosController::startWireGuard(const QString &config)
void IosController::startTunnel() void IosController::startTunnel()
{ {
NSString *protocolName = @"Unknown"; NSString *protocolName = @"Unknown";
NETunnelProviderProtocol *tunnelProtocol = (NETunnelProviderProtocol *)m_currentTunnel.protocolConfiguration; NETunnelProviderProtocol *tunnelProtocol = (NETunnelProviderProtocol *)m_currentTunnel.protocolConfiguration;
if (tunnelProtocol.providerConfiguration[@"wireguard"] != nil) { if (tunnelProtocol.providerConfiguration[@"wireguard"] != nil) {
protocolName = @"WireGuard"; protocolName = @"WireGuard";
} else if (tunnelProtocol.providerConfiguration[@"ovpn"] != nil) { } else if (tunnelProtocol.providerConfiguration[@"ovpn"] != nil) {
protocolName = @"OpenVPN"; protocolName = @"OpenVPN";
} }
m_rxBytes = 0; m_rxBytes = 0;
m_txBytes = 0; m_txBytes = 0;
int STT = m_rawConfig["splitTunnelType"].toInt();
QJsonArray splitTunnelSites = m_rawConfig["splitTunnelSites"].toArray();
QJsonDocument doc;
doc.setArray(splitTunnelSites);
QString STS(doc.toJson());
[m_currentTunnel setEnabled:YES]; [m_currentTunnel setEnabled:YES];
[m_currentTunnel saveToPreferencesWithCompletionHandler:^(NSError *saveError) { [m_currentTunnel saveToPreferencesWithCompletionHandler:^(NSError *saveError) {
@@ -485,23 +610,6 @@ void IosController::startTunnel()
NSError *startError = nil; NSError *startError = nil;
qDebug() << iosStatusToState(m_currentTunnel.connection.status); qDebug() << iosStatusToState(m_currentTunnel.connection.status);
NSString *actionKey = [NSString stringWithUTF8String:MessageKey::action];
NSString *actionValue = [NSString stringWithUTF8String:Action::start];
NSString *tunnelIdKey = [NSString stringWithUTF8String:MessageKey::tunnelId];
NSString *tunnelIdValue = !m_tunnelId.isEmpty() ? m_tunnelId.toNSString() : @"";
NSString *SplitTunnelTypeKey = [NSString stringWithUTF8String:MessageKey::SplitTunnelType];
NSString *SplitTunnelTypeValue = [NSString stringWithFormat:@"%d",STT];
NSString *SplitTunnelSitesKey = [NSString stringWithUTF8String:MessageKey::SplitTunnelSites];
NSString *SplitTunnelSitesValue = STS.toNSString();
NSDictionary* message = @{actionKey: actionValue, tunnelIdKey: tunnelIdValue,
SplitTunnelTypeKey: SplitTunnelTypeValue, SplitTunnelSitesKey: SplitTunnelSitesValue};
sendVpnExtensionMessage(message);
BOOL started = [m_currentTunnel.connection startVPNTunnelWithOptions:nil andReturnError:&startError]; BOOL started = [m_currentTunnel.connection startVPNTunnelWithOptions:nil andReturnError:&startError];
if (!started || startError) { if (!started || startError) {
@@ -516,7 +624,6 @@ void IosController::startTunnel()
}]; }];
} }
bool IosController::isOurManager(NETunnelProviderManager* manager) { bool IosController::isOurManager(NETunnelProviderManager* manager) {
NETunnelProviderProtocol* tunnelProto = (NETunnelProviderProtocol*)manager.protocolConfiguration; NETunnelProviderProtocol* tunnelProto = (NETunnelProviderProtocol*)manager.protocolConfiguration;
@@ -578,7 +685,7 @@ void IosController::sendVpnExtensionMessage(NSDictionary* message, std::function
NETunnelProviderSession *session = (NETunnelProviderSession *)m_currentTunnel.connection; NETunnelProviderSession *session = (NETunnelProviderSession *)m_currentTunnel.connection;
NSError *sendError = nil; NSError *sendError = nil;
if ([session respondsToSelector:@selector(sendProviderMessage:returnError:responseHandler:)]) { if ([session respondsToSelector:@selector(sendProviderMessage:returnError:responseHandler:)]) {
[session sendProviderMessage:data returnError:&sendError responseHandler:completionHandler]; [session sendProviderMessage:data returnError:&sendError responseHandler:completionHandler];
} else { } else {
@@ -21,7 +21,7 @@
} }
- (void) vpnConfigurationDidChange:(NSNotification *)notification { - (void) vpnConfigurationDidChange:(NSNotification *)notification {
cppController->vpnStatusDidChange(notification); // cppController->vpnStatusDidChange(notification);
} }
@@ -16,9 +16,6 @@
#include "leakdetector.h" #include "leakdetector.h"
#include "logger.h" #include "logger.h"
constexpr uint32_t ETH_MTU = 1500;
constexpr uint32_t WG_MTU_OVERHEAD = 80;
namespace { namespace {
Logger logger("IPUtilsLinux"); Logger logger("IPUtilsLinux");
} }
@@ -38,8 +35,6 @@ bool IPUtilsLinux::addInterfaceIPs(const InterfaceConfig& config) {
} }
bool IPUtilsLinux::setMTUAndUp(const InterfaceConfig& config) { bool IPUtilsLinux::setMTUAndUp(const InterfaceConfig& config) {
Q_UNUSED(config);
// Create socket file descriptor to perform the ioctl operations on // Create socket file descriptor to perform the ioctl operations on
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
if (sockfd < 0) { if (sockfd < 0) {
@@ -56,10 +51,10 @@ bool IPUtilsLinux::setMTUAndUp(const InterfaceConfig& config) {
// FIXME: We need to know how many layers deep this particular // FIXME: We need to know how many layers deep this particular
// interface is into a tunnel to work effectively. Otherwise // interface is into a tunnel to work effectively. Otherwise
// we will run into fragmentation issues. // we will run into fragmentation issues.
ifr.ifr_mtu = ETH_MTU - WG_MTU_OVERHEAD; ifr.ifr_mtu = config.m_deviceMTU;
int ret = ioctl(sockfd, SIOCSIFMTU, &ifr); int ret = ioctl(sockfd, SIOCSIFMTU, &ifr);
if (ret) { if (ret) {
logger.error() << "Failed to set MTU -- Return code: " << ret; logger.error() << "Failed to set MTU -- " << config.m_deviceMTU << " -- Return code: " << ret;
return false; return false;
} }
@@ -18,7 +18,7 @@
#include "logger.h" #include "logger.h"
constexpr const int WG_TUN_PROC_TIMEOUT = 5000; constexpr const int WG_TUN_PROC_TIMEOUT = 5000;
constexpr const char* WG_RUNTIME_DIR = "/var/run/wireguard"; constexpr const char* WG_RUNTIME_DIR = "/var/run/amneziawg";
namespace { namespace {
Logger logger("WireguardUtilsLinux"); Logger logger("WireguardUtilsLinux");
@@ -103,6 +103,10 @@ bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) {
out << "private_key=" << QString(privateKey.toHex()) << "\n"; out << "private_key=" << QString(privateKey.toHex()) << "\n";
out << "replace_peers=true\n"; out << "replace_peers=true\n";
if (config.m_mtu != "") {
out << "mtu=" << config.m_mtu << "\n";
}
if (config.m_junkPacketCount != "") { if (config.m_junkPacketCount != "") {
out << "jc=" << config.m_junkPacketCount << "\n"; out << "jc=" << config.m_junkPacketCount << "\n";
out << "jmin=" << config.m_junkPacketMinSize << "\n"; out << "jmin=" << config.m_junkPacketMinSize << "\n";
@@ -20,9 +20,6 @@
#include "logger.h" #include "logger.h"
#include "macosdaemon.h" #include "macosdaemon.h"
constexpr uint32_t ETH_MTU = 1500;
constexpr uint32_t WG_MTU_OVERHEAD = 80;
namespace { namespace {
Logger logger("IPUtilsMacos"); Logger logger("IPUtilsMacos");
} }
@@ -56,10 +53,10 @@ bool IPUtilsMacos::setMTUAndUp(const InterfaceConfig& config) {
// MTU // MTU
strncpy(ifr.ifr_name, qPrintable(ifname), IFNAMSIZ); strncpy(ifr.ifr_name, qPrintable(ifname), IFNAMSIZ);
ifr.ifr_mtu = ETH_MTU - WG_MTU_OVERHEAD; ifr.ifr_mtu = config.m_deviceMTU;
int ret = ioctl(sockfd, SIOCSIFMTU, &ifr); int ret = ioctl(sockfd, SIOCSIFMTU, &ifr);
if (ret) { if (ret) {
logger.error() << "Failed to set MTU:" << strerror(errno); logger.error() << "Failed to set MTU -- " << config.m_deviceMTU << " -- Return code: " << ret;
return false; return false;
} }
@@ -16,7 +16,7 @@
#include "logger.h" #include "logger.h"
constexpr const int WG_TUN_PROC_TIMEOUT = 5000; constexpr const int WG_TUN_PROC_TIMEOUT = 5000;
constexpr const char* WG_RUNTIME_DIR = "/var/run/wireguard"; constexpr const char* WG_RUNTIME_DIR = "/var/run/amneziawg";
namespace { namespace {
Logger logger("WireguardUtilsMacos"); Logger logger("WireguardUtilsMacos");
@@ -101,6 +101,10 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) {
out << "private_key=" << QString(privateKey.toHex()) << "\n"; out << "private_key=" << QString(privateKey.toHex()) << "\n";
out << "replace_peers=true\n"; out << "replace_peers=true\n";
if (config.m_mtu != "") {
out << "mtu=" << config.m_mtu << "\n";
}
if (config.m_junkPacketCount != "") { if (config.m_junkPacketCount != "") {
out << "jc=" << config.m_junkPacketCount << "\n"; out << "jc=" << config.m_junkPacketCount << "\n";
out << "jmin=" << config.m_junkPacketMinSize << "\n"; out << "jmin=" << config.m_junkPacketMinSize << "\n";
@@ -58,7 +58,6 @@ void WindowsTunnelService::stop() {
if (m_logworker) { if (m_logworker) {
m_logthread.quit(); m_logthread.quit();
m_logthread.wait(); m_logthread.wait();
delete m_logworker;
m_logworker = nullptr; m_logworker = nullptr;
} }
} }
@@ -104,6 +103,7 @@ bool WindowsTunnelService::start(const QString& configData) {
m_logworker = new WindowsTunnelLogger(WindowsCommons::tunnelLogFile()); m_logworker = new WindowsTunnelLogger(WindowsCommons::tunnelLogFile());
m_logworker->moveToThread(&m_logthread); m_logworker->moveToThread(&m_logthread);
connect(&m_logthread, &QThread::finished, m_logworker, &QObject::deleteLater);
m_logthread.start(); m_logthread.start();
SC_HANDLE scm = (SC_HANDLE)m_scm; SC_HANDLE scm = (SC_HANDLE)m_scm;
+6
View File
@@ -20,6 +20,7 @@ namespace amnezia
constexpr char dns1[] = "dns1"; constexpr char dns1[] = "dns1";
constexpr char dns2[] = "dns2"; constexpr char dns2[] = "dns2";
constexpr char serverIndex[] = "serverIndex";
constexpr char description[] = "description"; constexpr char description[] = "description";
constexpr char name[] = "name"; constexpr char name[] = "name";
constexpr char cert[] = "cert"; constexpr char cert[] = "cert";
@@ -44,7 +45,9 @@ namespace amnezia
constexpr char server_priv_key[] = "server_priv_key"; constexpr char server_priv_key[] = "server_priv_key";
constexpr char server_pub_key[] = "server_pub_key"; constexpr char server_pub_key[] = "server_pub_key";
constexpr char psk_key[] = "psk_key"; constexpr char psk_key[] = "psk_key";
constexpr char mtu[] = "mtu";
constexpr char allowed_ips[] = "allowed_ips"; constexpr char allowed_ips[] = "allowed_ips";
constexpr char persistent_keep_alive[] = "persistent_keep_alive";
constexpr char client_ip[] = "client_ip"; // internal ip address constexpr char client_ip[] = "client_ip"; // internal ip address
@@ -102,6 +105,7 @@ namespace amnezia
constexpr char defaultSubnetAddress[] = "10.8.0.0"; constexpr char defaultSubnetAddress[] = "10.8.0.0";
constexpr char defaultSubnetMask[] = "255.255.255.0"; constexpr char defaultSubnetMask[] = "255.255.255.0";
constexpr char defaultSubnetCidr[] = "24"; constexpr char defaultSubnetCidr[] = "24";
constexpr char defaultMtu[] = "1500";
constexpr char serverConfigPath[] = "/opt/amnezia/openvpn/server.conf"; constexpr char serverConfigPath[] = "/opt/amnezia/openvpn/server.conf";
constexpr char caCertPath[] = "/opt/amnezia/openvpn/pki/ca.crt"; constexpr char caCertPath[] = "/opt/amnezia/openvpn/pki/ca.crt";
@@ -148,6 +152,7 @@ namespace amnezia
constexpr char defaultSubnetCidr[] = "24"; constexpr char defaultSubnetCidr[] = "24";
constexpr char defaultPort[] = "51820"; constexpr char defaultPort[] = "51820";
constexpr char defaultMtu[] = "1280";
constexpr char serverConfigPath[] = "/opt/amnezia/wireguard/wg0.conf"; constexpr char serverConfigPath[] = "/opt/amnezia/wireguard/wg0.conf";
constexpr char serverPublicKeyPath[] = "/opt/amnezia/wireguard/wireguard_server_public_key.key"; constexpr char serverPublicKeyPath[] = "/opt/amnezia/wireguard/wireguard_server_public_key.key";
constexpr char serverPskKeyPath[] = "/opt/amnezia/wireguard/wireguard_psk.key"; constexpr char serverPskKeyPath[] = "/opt/amnezia/wireguard/wireguard_psk.key";
@@ -163,6 +168,7 @@ namespace amnezia
namespace awg namespace awg
{ {
constexpr char defaultPort[] = "55424"; constexpr char defaultPort[] = "55424";
constexpr char defaultMtu[] = "1280";
constexpr char serverConfigPath[] = "/opt/amnezia/awg/wg0.conf"; constexpr char serverConfigPath[] = "/opt/amnezia/awg/wg0.conf";
constexpr char serverPublicKeyPath[] = "/opt/amnezia/awg/wireguard_server_public_key.key"; constexpr char serverPublicKeyPath[] = "/opt/amnezia/awg/wireguard_server_public_key.key";
+1
View File
@@ -224,6 +224,7 @@
<file>ui/qml/Pages2/PageShareFullAccess.qml</file> <file>ui/qml/Pages2/PageShareFullAccess.qml</file>
<file>images/controls/close.svg</file> <file>images/controls/close.svg</file>
<file>images/controls/search.svg</file> <file>images/controls/search.svg</file>
<file>ui/qml/Pages2/PageProtocolWireGuardSettings.qml</file>
<file>ui/qml/Components/HomeSplitTunnelingDrawer.qml</file> <file>ui/qml/Components/HomeSplitTunnelingDrawer.qml</file>
<file>images/controls/split-tunneling.svg</file> <file>images/controls/split-tunneling.svg</file>
<file>ui/qml/Controls2/DrawerType2.qml</file> <file>ui/qml/Controls2/DrawerType2.qml</file>
+1 -1
View File
@@ -1,4 +1,4 @@
FROM amneziavpn/amnezia-wg:latest FROM epamiuriiegorov/awg:latest
LABEL maintainer="AmneziaVPN" LABEL maintainer="AmneziaVPN"
@@ -14,6 +14,7 @@ cat > /opt/amnezia/awg/wg0.conf <<EOF
PrivateKey = $WIREGUARD_SERVER_PRIVATE_KEY PrivateKey = $WIREGUARD_SERVER_PRIVATE_KEY
Address = $WIREGUARD_SUBNET_IP/$WIREGUARD_SUBNET_CIDR Address = $WIREGUARD_SUBNET_IP/$WIREGUARD_SUBNET_CIDR
ListenPort = $AWG_SERVER_PORT ListenPort = $AWG_SERVER_PORT
MTU = 1280
Jc = $JUNK_PACKET_COUNT Jc = $JUNK_PACKET_COUNT
Jmin = $JUNK_PACKET_MIN_SIZE Jmin = $JUNK_PACKET_MIN_SIZE
Jmax = $JUNK_PACKET_MAX_SIZE Jmax = $JUNK_PACKET_MAX_SIZE
+1 -1
View File
@@ -5,7 +5,7 @@ sudo docker run -d \
--privileged \ --privileged \
--cap-add=NET_ADMIN \ --cap-add=NET_ADMIN \
--cap-add=SYS_MODULE \ --cap-add=SYS_MODULE \
-p $AWG_SERVER_PORT:$AWG_SERVER_PORT/udp \ -p 443:443 \
-v /lib/modules:/lib/modules \ -v /lib/modules:/lib/modules \
--sysctl="net.ipv4.conf.all.src_valid_mark=1" \ --sysctl="net.ipv4.conf.all.src_valid_mark=1" \
--name $CONTAINER_NAME \ --name $CONTAINER_NAME \
+1
View File
@@ -2,6 +2,7 @@
Address = $WIREGUARD_CLIENT_IP/32 Address = $WIREGUARD_CLIENT_IP/32
DNS = $PRIMARY_DNS, $SECONDARY_DNS DNS = $PRIMARY_DNS, $SECONDARY_DNS
PrivateKey = $WIREGUARD_CLIENT_PRIVATE_KEY PrivateKey = $WIREGUARD_CLIENT_PRIVATE_KEY
MTU = 1280
Jc = $JUNK_PACKET_COUNT Jc = $JUNK_PACKET_COUNT
Jmin = $JUNK_PACKET_MIN_SIZE Jmin = $JUNK_PACKET_MIN_SIZE
Jmax = $JUNK_PACKET_MAX_SIZE Jmax = $JUNK_PACKET_MAX_SIZE
+1 -1
View File
@@ -1 +1 @@
sudo docker build --no-cache --pull -t $CONTAINER_NAME $DOCKERFILE_FOLDER --build-arg SERVER_ARCH=$(uname -m) sudo docker build --no-cache --pull -t $CONTAINER_NAME $DOCKERFILE_FOLDER
+14 -16
View File
@@ -1,9 +1,8 @@
FROM alpine:3.15 FROM alpine:3.15
LABEL maintainer="AmneziaVPN" LABEL maintainer="AmneziaVPN"
ARG SS_RELEASE="v1.13.1" ARG SS_RELEASE="v1.18.1"
ARG CLOAK_RELEASE="v2.5.5" ARG CLOAK_RELEASE="v2.8.0"
ARG SERVER_ARCH
#Install required packages #Install required packages
RUN apk add --no-cache curl openvpn easy-rsa bash netcat-openbsd dumb-init rng-tools RUN apk add --no-cache curl openvpn easy-rsa bash netcat-openbsd dumb-init rng-tools
@@ -16,20 +15,19 @@ RUN mkdir -p /opt/amnezia
RUN echo -e "#!/bin/bash\ntail -f /dev/null" > /opt/amnezia/start.sh RUN echo -e "#!/bin/bash\ntail -f /dev/null" > /opt/amnezia/start.sh
RUN chmod a+x /opt/amnezia/start.sh RUN chmod a+x /opt/amnezia/start.sh
RUN if [ $SERVER_ARCH="x86_64" ]; then CK_ARCH="amd64"; \ RUN SERVER_ARCH=$(uname -m) && \
elif [ $SERVER_ARCH="i686" ]; then CK_ARCH="386"; \ if [ $SERVER_ARCH="x86_64" ]; then CK_ARCH="amd64"; \
elif [ $SERVER_ARCH="aarch64" ]; then CK_ARCH="arm64"; \ elif [ $SERVER_ARCH="i686" ]; then CK_ARCH="386"; \
elif [ $SERVER_ARCH="arm" ]; then CK_ARCH="arm"; \ elif [ $SERVER_ARCH="aarch64" ]; then CK_ARCH="arm64"; \
else exit -1; fi && \ elif [ $SERVER_ARCH="arm" ]; then CK_ARCH="arm"; \
curl -L https://github.com/cbeuw/Cloak/releases/download/${CLOAK_RELEASE}/ck-server-linux-${CK_ARCH}-${CLOAK_RELEASE} > /usr/bin/ck-server else exit -1; fi && \
RUN chmod a+x /usr/bin/ck-server curl -L https://github.com/cbeuw/Cloak/releases/download/${CLOAK_RELEASE}/ck-server-linux-${CK_ARCH}-${CLOAK_RELEASE} > /usr/bin/ck-server && \
chmod a+x /usr/bin/ck-server && \
curl -L https://github.com/shadowsocks/shadowsocks-rust/releases/download/${SS_RELEASE}/shadowsocks-${SS_RELEASE}.${SERVER_ARCH}-unknown-linux-musl.tar.xz > /usr/bin/ss.tar.xz && \
tar -Jxvf /usr/bin/ss.tar.xz -C /usr/bin/ && \
chmod a+x /usr/bin/ssserver
RUN curl -L https://github.com/shadowsocks/shadowsocks-rust/releases/download/${SS_RELEASE}/shadowsocks-${SS_RELEASE}.${SERVER_ARCH}-unknown-linux-musl.tar.xz > /usr/bin/ss.tar.xz # Tune network
RUN tar -Jxvf /usr/bin/ss.tar.xz -C /usr/bin/
RUN chmod a+x /usr/bin/ssserver
# Tune network
RUN echo -e " \n\ RUN echo -e " \n\
fs.file-max = 51200 \n\ fs.file-max = 51200 \n\
\n\ \n\
@@ -1,8 +1,7 @@
FROM alpine:3.15 FROM alpine:3.15
LABEL maintainer="AmneziaVPN" LABEL maintainer="AmneziaVPN"
ARG SS_RELEASE="v1.13.1" ARG SS_RELEASE="v1.18.1"
ARG SERVER_ARCH
#Install required packages #Install required packages
RUN apk add --no-cache curl openvpn easy-rsa bash netcat-openbsd dumb-init rng-tools xz RUN apk add --no-cache curl openvpn easy-rsa bash netcat-openbsd dumb-init rng-tools xz
@@ -15,7 +14,16 @@ RUN mkdir -p /opt/amnezia
RUN echo -e "#!/bin/bash\ntail -f /dev/null" > /opt/amnezia/start.sh RUN echo -e "#!/bin/bash\ntail -f /dev/null" > /opt/amnezia/start.sh
RUN chmod a+x /opt/amnezia/start.sh RUN chmod a+x /opt/amnezia/start.sh
RUN curl -L https://github.com/shadowsocks/shadowsocks-rust/releases/download/${SS_RELEASE}/shadowsocks-${SS_RELEASE}.${SERVER_ARCH}-unknown-linux-musl.tar.xz > /usr/bin/ss.tar.xz;\ RUN SERVER_ARCH=$(uname -m); \
SUFFIX=""; \
if [ ! -z "$(echo ${SERVER_ARCH} | grep -i arm)" ]; then \
if [ ! -z "$(cat /proc/cpuinfo | grep -i vfp)" ]; then \
SUFFIX="eabihf"; \
else \
SUFFIX="eabi"; \
fi; \
fi; \
curl -L https://github.com/shadowsocks/shadowsocks-rust/releases/download/${SS_RELEASE}/shadowsocks-${SS_RELEASE}.${SERVER_ARCH}-unknown-linux-musl${SUFFIX}.tar.xz > /usr/bin/ss.tar.xz;\
tar -Jxvf /usr/bin/ss.tar.xz -C /usr/bin/;\ tar -Jxvf /usr/bin/ss.tar.xz -C /usr/bin/;\
chmod a+x /usr/bin/ssserver; chmod a+x /usr/bin/ssserver;
@@ -1,4 +1,5 @@
sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker stop sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker stop;\
sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker rm -fv sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker rm -fv;\
sudo docker images -a | grep amnezia | awk '{print $3}' | xargs sudo docker rmi sudo docker images -a | grep amnezia | awk '{print $3}' | xargs sudo docker rmi;\
sudo docker network ls | grep amnezia-dns-net | awk '{print $1}' | xargs sudo docker network rm sudo docker network ls | grep amnezia-dns-net | awk '{print $1}' | xargs sudo docker network rm;\
sudo rm -frd /opt/amnezia
+2 -2
View File
@@ -1,3 +1,3 @@
sudo docker stop $CONTAINER_NAME sudo docker stop $CONTAINER_NAME;\
sudo docker rm -fv $CONTAINER_NAME sudo docker rm -fv $CONTAINER_NAME;\
sudo docker rmi $CONTAINER_NAME sudo docker rmi $CONTAINER_NAME
+2
View File
@@ -68,6 +68,7 @@ void Settings::removeServer(int index)
servers.removeAt(index); servers.removeAt(index);
setServersArray(servers); setServersArray(servers);
emit serverRemoved(index);
} }
bool Settings::editServer(int index, const QJsonObject &server) bool Settings::editServer(int index, const QJsonObject &server)
@@ -338,6 +339,7 @@ QString Settings::secondaryDns() const
void Settings::clearSettings() void Settings::clearSettings()
{ {
m_settings.clearSettings(); m_settings.clearSettings();
emit settingsCleared();
} }
ServerCredentials Settings::defaultServerCredentials() const ServerCredentials Settings::defaultServerCredentials() const
+4
View File
@@ -185,12 +185,16 @@ public:
void setScreenshotsEnabled(bool enabled) void setScreenshotsEnabled(bool enabled)
{ {
setValue("Conf/screenshotsEnabled", enabled); setValue("Conf/screenshotsEnabled", enabled);
emit screenshotsEnabledChanged(enabled);
} }
void clearSettings(); void clearSettings();
signals: signals:
void saveLogsChanged(bool enabled); void saveLogsChanged(bool enabled);
void screenshotsEnabledChanged(bool enabled);
void serverRemoved(int serverIndex);
void settingsCleared();
private: private:
QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const; QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const;
+134 -157
View File
@@ -5,45 +5,45 @@
<name>ConnectionController</name> <name>ConnectionController</name>
<message> <message>
<location filename="../ui/controllers/connectionController.h" line="62"/> <location filename="../ui/controllers/connectionController.h" line="62"/>
<location filename="../ui/controllers/connectionController.cpp" line="83"/> <location filename="../ui/controllers/connectionController.cpp" line="84"/>
<location filename="../ui/controllers/connectionController.cpp" line="97"/> <location filename="../ui/controllers/connectionController.cpp" line="98"/>
<location filename="../ui/controllers/connectionController.cpp" line="103"/> <location filename="../ui/controllers/connectionController.cpp" line="104"/>
<source>Connect</source> <source>Connect</source>
<translation>اتصل</translation> <translation>اتصل</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/connectionController.cpp" line="40"/> <location filename="../ui/controllers/connectionController.cpp" line="41"/>
<source>VPN Protocols is not installed. <source>VPN Protocols is not installed.
Please install VPN container at first</source> Please install VPN container at first</source>
<translation>لم يتم تثبيت بروتوكولات VPN, من فضلك قم بتنزيل حاوية VPN اولاً</translation> <translation>لم يتم تثبيت بروتوكولات VPN, من فضلك قم بتنزيل حاوية VPN اولاً</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/connectionController.cpp" line="64"/> <location filename="../ui/controllers/connectionController.cpp" line="65"/>
<source>Connection...</source> <source>Connection...</source>
<translation>اتصال...</translation> <translation>اتصال...</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/connectionController.cpp" line="69"/> <location filename="../ui/controllers/connectionController.cpp" line="70"/>
<source>Connected</source> <source>Connected</source>
<translation>تم الاتصال</translation> <translation>تم الاتصال</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/connectionController.cpp" line="78"/> <location filename="../ui/controllers/connectionController.cpp" line="79"/>
<source>Reconnection...</source> <source>Reconnection...</source>
<translation>إعادة الاتصال...</translation> <translation>إعادة الاتصال...</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/connectionController.cpp" line="88"/> <location filename="../ui/controllers/connectionController.cpp" line="89"/>
<source>Disconnection...</source> <source>Disconnection...</source>
<translation>إنهاء الاتصال...</translation> <translation>إنهاء الاتصال...</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/connectionController.cpp" line="114"/> <location filename="../ui/controllers/connectionController.cpp" line="115"/>
<source>Settings updated successfully, Reconnnection...</source> <source>Settings updated successfully, Reconnnection...</source>
<translation>تم تحديث الاعدادات بنجاح, جاري إعادة الاتصال...</translation> <translation>تم تحديث الاعدادات بنجاح, جاري إعادة الاتصال...</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/connectionController.cpp" line="117"/> <location filename="../ui/controllers/connectionController.cpp" line="118"/>
<source>Settings updated successfully</source> <source>Settings updated successfully</source>
<translation>تم تحديث الاعدادات بنجاح</translation> <translation>تم تحديث الاعدادات بنجاح</translation>
</message> </message>
@@ -324,17 +324,17 @@ Already installed containers were found on the server. All installed containers
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="224"/> <location filename="../ui/qml/Pages2/PageHome.qml" line="223"/>
<source>VPN protocol</source> <source>VPN protocol</source>
<translation>بروتوكول VPN</translation> <translation>بروتوكول VPN</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="272"/> <location filename="../ui/qml/Pages2/PageHome.qml" line="271"/>
<source>Servers</source> <source>Servers</source>
<translation>الخوادم</translation> <translation>الخوادم</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="360"/> <location filename="../ui/qml/Pages2/PageHome.qml" line="359"/>
<source>Unable change server while there is an active connection</source> <source>Unable change server while there is an active connection</source>
<translation>لا يمكن تغير الخادم بينما هناك اتصال مفعل</translation> <translation>لا يمكن تغير الخادم بينما هناك اتصال مفعل</translation>
</message> </message>
@@ -351,51 +351,6 @@ Already installed containers were found on the server. All installed containers
<source>Port</source> <source>Port</source>
<translation>منفذ</translation> <translation>منفذ</translation>
</message> </message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="114"/>
<source>Junk packet count</source>
<translation>عدد الحزم الغير مرغوب فيها</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="139"/>
<source>Junk packet minimum size</source>
<translation>الحد الادني لحجم الحزمة الغير مرغوب فيها</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="159"/>
<source>Junk packet maximum size</source>
<translation>الحجم الاقصي للحزمة الغير مرغوب فيها</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="179"/>
<source>Init packet junk size</source>
<translation>Init packet junk size</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="199"/>
<source>Response packet junk size</source>
<translation>حجم حزمة الاستجابة الغير مرغوب فيها</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="219"/>
<source>Init packet magic header</source>
<translation>إطلاق حزمة magic header</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="239"/>
<source>Response packet magic header</source>
<translation>حزمة الرد magic header</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="259"/>
<source>Transport packet magic header</source>
<translation>نقل حزمة magic header</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="279"/>
<source>Underload packet magic header</source>
<translation>تحميل حزمة magic header</translation>
</message>
<message> <message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="304"/> <location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="304"/>
<source>Remove AmneziaWG</source> <source>Remove AmneziaWG</source>
@@ -1468,7 +1423,7 @@ And if you don&apos;t like the app, all the more support it - the donation will
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="203"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="203"/>
<source>Do you want to clear server from Amnezia software?</source> <source>Do you want to clear server Amnezia-installed services?</source>
<translation>هل تريد حذف الخادم من Amnezia?</translation> <translation>هل تريد حذف الخادم من Amnezia?</translation>
</message> </message>
<message> <message>
@@ -1516,7 +1471,7 @@ And if you don&apos;t like the app, all the more support it - the donation will
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="169"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="169"/>
<source>Remove server from application</source> <source>Remove this server from the app</source>
<translation>احذف خادم من التطبيق</translation> <translation>احذف خادم من التطبيق</translation>
</message> </message>
<message> <message>
@@ -1541,7 +1496,7 @@ And if you don&apos;t like the app, all the more support it - the donation will
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="199"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="199"/>
<source>Clear server from Amnezia software</source> <source>Clear server Amnezia-installed services</source>
<translation>احذف خادم من Amnezia</translation> <translation>احذف خادم من Amnezia</translation>
</message> </message>
<message> <message>
@@ -1748,41 +1703,45 @@ And if you don&apos;t like the app, all the more support it - the donation will
<translation>اتصال الخادم</translation> <translation>اتصال الخادم</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="51"/>
<source>Do not use connection code from public sources. It may have been created to intercept your data. <source>Do not use connection code from public sources. It may have been created to intercept your data.
It&apos;s okay as long as it&apos;s from someone you trust.</source> It&apos;s okay as long as it&apos;s from someone you trust.</source>
<translation>لا تستخدم رمز الاتصال من المصادر العامة. ربما تم إنشاؤه لاعتراض بياناتك <translation type="vanished">لا تستخدم رمز الاتصال من المصادر العامة. ربما تم إنشاؤه لاعتراض بياناتك
لا بأس طالما انه من شخص تثق به.</translation> لا بأس طالما انه من شخص تثق به.</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="61"/> <location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="51"/>
<source>Do not use connection codes from untrusted sources, as they may be created to intercept your data.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="60"/>
<source>What do you have?</source> <source>What do you have?</source>
<translation>ماذا لديك؟</translation> <translation>ماذا لديك؟</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="68"/> <location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="67"/>
<source>File with connection settings or backup</source> <source>File with connection settings or backup</source>
<translation>ملف إعدادات اتصال او نسخ احتياطي</translation> <translation>ملف إعدادات اتصال او نسخ احتياطي</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="68"/> <location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="67"/>
<source>File with connection settings</source> <source>File with connection settings</source>
<translation>ملف إعدادات اتصال</translation> <translation>ملف إعدادات اتصال</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="75"/> <location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="74"/>
<source>Open config file</source> <source>Open config file</source>
<translation>افتح ملف تكوين</translation> <translation>افتح ملف تكوين</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="95"/> <location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="94"/>
<source>QR-code</source> <source>QR-code</source>
<translation>رمز QR</translation> <translation>رمز QR</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="114"/> <location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="113"/>
<source>Key as text</source> <source>Key as text</source>
<translation>مفتاح كنص</translation> <translation>مفتاح كنص</translation>
</message> </message>
@@ -1868,7 +1827,7 @@ It&apos;s okay as long as it&apos;s from someone you trust.</source>
<translation>واصل</translation> <translation>واصل</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="198"/> <location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="197"/>
<source>Set up later</source> <source>Set up later</source>
<translation>إعداد في وقت لاحق</translation> <translation>إعداد في وقت لاحق</translation>
</message> </message>
@@ -1969,32 +1928,32 @@ It&apos;s okay as long as it&apos;s from someone you trust.</source>
<context> <context>
<name>PageSetupWizardStart</name> <name>PageSetupWizardStart</name>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="54"/> <location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="64"/>
<source>Settings restored from backup file</source> <source>Settings restored from backup file</source>
<translation>تم استرداد الإعدادات من ملف نسخة احتياطية</translation> <translation>تم استرداد الإعدادات من ملف نسخة احتياطية</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="106"/> <location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="116"/>
<source>Free service for creating a personal VPN on your server.</source> <source>Free service for creating a personal VPN on your server.</source>
<translation>خدمة مجانية لأنشاء VPN شخصي علي الخادم الشخصي.</translation> <translation>خدمة مجانية لأنشاء VPN شخصي علي الخادم الشخصي.</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="107"/> <location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="117"/>
<source> Helps you access blocked content without revealing your privacy, even to VPN providers.</source> <source> Helps you access blocked content without revealing your privacy, even to VPN providers.</source>
<translation> يساعدك في الولوج للمحتوي المحظور بدون إظهار خصوصيات, حتي لمزود ال VPN.</translation> <translation> يساعدك في الولوج للمحتوي المحظور بدون إظهار خصوصيات, حتي لمزود ال VPN.</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="116"/> <location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="126"/>
<source>I have the data to connect</source> <source>I have the data to connect</source>
<translation>لدي البيانات المطلوبة للأتصال</translation> <translation>لدي البيانات المطلوبة للأتصال</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="136"/> <location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="146"/>
<source>I have nothing</source> <source>I have nothing</source>
<translation>ليس لدي اي شئ</translation> <translation>ليس لدي اي شئ</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="139"/> <location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="149"/>
<source>https://amnezia.org/instructions/0_starter-guide</source> <source>https://amnezia.org/instructions/0_starter-guide</source>
<translation></translation> <translation></translation>
</message> </message>
@@ -2036,9 +1995,13 @@ It&apos;s okay as long as it&apos;s from someone you trust.</source>
<translation>اتصال جديد</translation> <translation>اتصال جديد</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="95"/>
<source>Do not use connection code from public sources. It could be created to intercept your data.</source> <source>Do not use connection code from public sources. It could be created to intercept your data.</source>
<translation>لا تستخدم رمز الاتصال من مصادر مفتوحة, قد تكون مصنوعة للتعارض مع بياناتك.</translation> <translation type="vanished">لا تستخدم رمز الاتصال من مصادر مفتوحة, قد تكون مصنوعة للتعارض مع بياناتك.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="95"/>
<source>Do not use connection codes from untrusted sources, as they may be created to intercept your data.</source>
<translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="110"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="110"/>
@@ -2130,7 +2093,7 @@ It&apos;s okay as long as it&apos;s from someone you trust.</source>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="243"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="243"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="504"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="513"/>
<source>Users</source> <source>Users</source>
<translation>المستخدمين</translation> <translation>المستخدمين</translation>
</message> </message>
@@ -2140,52 +2103,52 @@ It&apos;s okay as long as it&apos;s from someone you trust.</source>
<translation>شارك اتصال VPN بدون القدرة علي إدارة الخادم</translation> <translation>شارك اتصال VPN بدون القدرة علي إدارة الخادم</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="520"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="529"/>
<source>Search</source> <source>Search</source>
<translation>ابحث</translation> <translation>ابحث</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="604"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="613"/>
<source>Creation date: </source> <source>Creation date: </source>
<translation>تاريخ الإنشاء: </translation> <translation>تاريخ الإنشاء: </translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="618"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="627"/>
<source>Rename</source> <source>Rename</source>
<translation>إعادة التسمية</translation> <translation>إعادة التسمية</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="650"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="659"/>
<source>Client name</source> <source>Client name</source>
<translation>اسم العميل</translation> <translation>اسم العميل</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="663"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="672"/>
<source>Save</source> <source>Save</source>
<translation>احفظ</translation> <translation>احفظ</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="695"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="704"/>
<source>Revoke</source> <source>Revoke</source>
<translation>سحب وإبطال</translation> <translation>سحب وإبطال</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="698"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="707"/>
<source>Revoke the config for a user - %1?</source> <source>Revoke the config for a user - %1?</source>
<translation>سحب وإبطال للمستخدم - %1?</translation> <translation>سحب وإبطال للمستخدم - %1?</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="699"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="708"/>
<source>The user will no longer be able to connect to your server.</source> <source>The user will no longer be able to connect to your server.</source>
<translation>المستخدم لن يكون قادر علي الاتصال بعد الان.</translation> <translation>المستخدم لن يكون قادر علي الاتصال بعد الان.</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="700"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="709"/>
<source>Continue</source> <source>Continue</source>
<translation>واصل</translation> <translation>واصل</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="701"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="710"/>
<source>Cancel</source> <source>Cancel</source>
<translation>إلغاء</translation> <translation>إلغاء</translation>
</message> </message>
@@ -2218,8 +2181,8 @@ It&apos;s okay as long as it&apos;s from someone you trust.</source>
<translation type="obsolete">البروتوكولات</translation> <translation type="obsolete">البروتوكولات</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="348"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="356"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="349"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="357"/>
<source>Protocol</source> <source>Protocol</source>
<translation>بروتوكول</translation> <translation>بروتوكول</translation>
</message> </message>
@@ -2239,14 +2202,14 @@ It&apos;s okay as long as it&apos;s from someone you trust.</source>
<translation>اسم المستخدم</translation> <translation>اسم المستخدم</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="449"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="457"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="450"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="458"/>
<source>Connection format</source> <source>Connection format</source>
<translation>تنسيق الاتصال</translation> <translation>تنسيق الاتصال</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="198"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="198"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="487"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="496"/>
<source>Share</source> <source>Share</source>
<translation>شارك</translation> <translation>شارك</translation>
</message> </message>
@@ -2628,147 +2591,161 @@ It&apos;s okay as long as it&apos;s from someone you trust.</source>
<translation>انتهت مدة الاتصال بالخادم</translation> <translation>انتهت مدة الاتصال بالخادم</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="32"/>
<source>Sftp error: End-of-file encountered</source>
<translation></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="33"/>
<source>Sftp error: File does not exist</source> <source>Sftp error: File does not exist</source>
<translation>خطأ Sftp: الملف غير موجود</translation> <translation type="vanished">خطأ Sftp: الملف غير موجود</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="34"/>
<source>Sftp error: Permission denied</source> <source>Sftp error: Permission denied</source>
<translation>خطأ Sftp: تم حظر الصلحيات</translation> <translation type="vanished">خطأ Sftp: تم حظر الصلحيات</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="35"/>
<source>Sftp error: Generic failure</source> <source>Sftp error: Generic failure</source>
<translation>خطأ Sftp: فشل عام</translation> <translation type="vanished">خطأ Sftp: فشل عام</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="36"/>
<source>Sftp error: Garbage received from server</source> <source>Sftp error: Garbage received from server</source>
<translation>خطأ Sftp: تم استلام نفايات من الخادم</translation> <translation type="vanished">خطأ Sftp: تم استلام نفايات من الخادم</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="37"/>
<source>Sftp error: No connection has been set up</source> <source>Sftp error: No connection has been set up</source>
<translation>خطأ Sftp: لم يتم إعداد اتصال</translation> <translation type="vanished">خطأ Sftp: لم يتم إعداد اتصال</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="38"/>
<source>Sftp error: There was a connection, but we lost it</source> <source>Sftp error: There was a connection, but we lost it</source>
<translation>خطأ Sftp: كان هناك اتصال, ولكن خسرناه</translation> <translation type="vanished">خطأ Sftp: كان هناك اتصال, ولكن خسرناه</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="39"/>
<source>Sftp error: Operation not supported by libssh yet</source> <source>Sftp error: Operation not supported by libssh yet</source>
<translation>خطأ Sftp: العملية ليست مدعومة من libssh بعد</translation> <translation type="vanished">خطأ Sftp: العملية ليست مدعومة من libssh بعد</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="40"/>
<source>Sftp error: Invalid file handle</source>
<translation></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="41"/>
<source>Sftp error: No such file or directory path exists</source> <source>Sftp error: No such file or directory path exists</source>
<translation>خطأ Sftp: لا يوجد مسار ملف او مجلد مثل هذا</translation> <translation type="vanished">خطأ Sftp: لا يوجد مسار ملف او مجلد مثل هذا</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="42"/>
<source>Sftp error: An attempt to create an already existing file or directory has been made</source> <source>Sftp error: An attempt to create an already existing file or directory has been made</source>
<translation>خطأ Sftp: محاولة إنشاء ملف او مجلد موجود بالفعل</translation> <translation type="vanished">خطأ Sftp: محاولة إنشاء ملف او مجلد موجود بالفعل</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="43"/>
<source>Sftp error: Write-protected filesystem</source> <source>Sftp error: Write-protected filesystem</source>
<translation>خطأ Sftp: نظام كتابة الملفات محمي</translation> <translation type="vanished">خطأ Sftp: نظام كتابة الملفات محمي</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="44"/>
<source>Sftp error: No media was in remote drive</source> <source>Sftp error: No media was in remote drive</source>
<translation>خطأ Sftp: لا يوجد وسائط في القرص البعيد</translation> <translation type="vanished">خطأ Sftp: لا يوجد وسائط في القرص البعيد</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="65"/> <location filename="../core/errorstrings.cpp" line="53"/>
<source>VPN connection error</source> <source>VPN connection error</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="68"/> <location filename="../core/errorstrings.cpp" line="56"/>
<source>Error when retrieving configuration from API</source> <source>Error when retrieving configuration from API</source>
<translation>خطأ عند استرداد التكوين من API</translation> <translation>خطأ عند استرداد التكوين من API</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="69"/> <location filename="../core/errorstrings.cpp" line="57"/>
<source>This config has already been added to the application</source> <source>This config has already been added to the application</source>
<translation>هذا التكوين بالفعل تمت إضافتة للبرنامج</translation> <translation>هذا التكوين بالفعل تمت إضافتة للبرنامج</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="76"/> <location filename="../core/errorstrings.cpp" line="72"/>
<source>ErrorCode: %1. </source> <source>ErrorCode: %1. </source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="47"/> <location filename="../core/errorstrings.cpp" line="35"/>
<source>OpenVPN config missing</source> <source>OpenVPN config missing</source>
<translation>OpenVpn تكوين مفقود</translation> <translation>OpenVpn تكوين مفقود</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="48"/> <location filename="../core/errorstrings.cpp" line="32"/>
<source>Scp error: Generic failure</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="36"/>
<source>OpenVPN management server error</source> <source>OpenVPN management server error</source>
<translation>OpenVpn خطأ في إدارة الخادم</translation> <translation>OpenVpn خطأ في إدارة الخادم</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="51"/> <location filename="../core/errorstrings.cpp" line="39"/>
<source>OpenVPN executable missing</source> <source>OpenVPN executable missing</source>
<translation>OpenVpn executeable مفقود</translation> <translation>OpenVpn executeable مفقود</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="52"/> <location filename="../core/errorstrings.cpp" line="40"/>
<source>ShadowSocks (ss-local) executable missing</source> <source>ShadowSocks (ss-local) executable missing</source>
<translation>ShadowSocks (ss-local) executable مفقود</translation> <translation>ShadowSocks (ss-local) executable مفقود</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="53"/> <location filename="../core/errorstrings.cpp" line="41"/>
<source>Cloak (ck-client) executable missing</source> <source>Cloak (ck-client) executable missing</source>
<translation>Cloak (ck-client) executable مفقود</translation> <translation>Cloak (ck-client) executable مفقود</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="54"/> <location filename="../core/errorstrings.cpp" line="42"/>
<source>Amnezia helper service error</source> <source>Amnezia helper service error</source>
<translation>خطأ في خدمة مٌساعد Amnezia</translation> <translation>خطأ في خدمة مٌساعد Amnezia</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="55"/> <location filename="../core/errorstrings.cpp" line="43"/>
<source>OpenSSL failed</source> <source>OpenSSL failed</source>
<translation>فشل OpenSSL</translation> <translation>فشل OpenSSL</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="58"/> <location filename="../core/errorstrings.cpp" line="46"/>
<source>Can&apos;t connect: another VPN connection is active</source> <source>Can&apos;t connect: another VPN connection is active</source>
<translation>لا يمكن الاتصال: هناك اتصال VPN اخر بالفعل يعمل</translation> <translation>لا يمكن الاتصال: هناك اتصال VPN اخر بالفعل يعمل</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="59"/> <location filename="../core/errorstrings.cpp" line="47"/>
<source>Can&apos;t setup OpenVPN TAP network adapter</source> <source>Can&apos;t setup OpenVPN TAP network adapter</source>
<translation>لا يمك نتثبيت محول شبكة OpenVPN TAP</translation> <translation>لا يمك نتثبيت محول شبكة OpenVPN TAP</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="60"/> <location filename="../core/errorstrings.cpp" line="48"/>
<source>VPN pool error: no available addresses</source> <source>VPN pool error: no available addresses</source>
<translation>VPN pool error: لا يوجد عنواين مٌتاحة</translation> <translation>VPN pool error: لا يوجد عنواين مٌتاحة</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="62"/> <location filename="../core/errorstrings.cpp" line="50"/>
<source>The config does not contain any containers and credentials for connecting to the server</source> <source>The config does not contain any containers and credentials for connecting to the server</source>
<translation>التكوين لا يحتوي علي اي حاويات و اعتماد للأتصال بالخادم</translation> <translation>التكوين لا يحتوي علي اي حاويات و اعتماد للأتصال بالخادم</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="73"/> <location filename="../core/errorstrings.cpp" line="60"/>
<source>QFile error: The file could not be opened</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="61"/>
<source>QFile error: An error occurred when reading from the file</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="62"/>
<source>QFile error: The file could not be accessed</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="63"/>
<source>QFile error: An unspecified error occurred</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="64"/>
<source>QFile error: A fatal error occurred</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="65"/>
<source>QFile error: The operation was aborted</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="69"/>
<source>Internal error</source> <source>Internal error</source>
<translation>خطأ داخلي</translation> <translation>خطأ داخلي</translation>
</message> </message>
@@ -3068,8 +3045,8 @@ While it offers a blend of security, stability, and speed, it&apos;s essential t
<translation>خادم #1</translation> <translation>خادم #1</translation>
</message> </message>
<message> <message>
<location filename="../settings.cpp" line="206"/> <location filename="../settings.cpp" line="207"/>
<location filename="../settings.cpp" line="213"/> <location filename="../settings.cpp" line="214"/>
<source>Server</source> <source>Server</source>
<translation>خادم</translation> <translation>خادم</translation>
</message> </message>
@@ -3077,17 +3054,17 @@ While it offers a blend of security, stability, and speed, it&apos;s essential t
<context> <context>
<name>SettingsController</name> <name>SettingsController</name>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="139"/> <location filename="../ui/controllers/settingsController.cpp" line="123"/>
<source>Backup file is corrupted</source> <source>Backup file is corrupted</source>
<translation>ملف النسخه الاحتياطيه تالف</translation> <translation>ملف النسخه الاحتياطيه تالف</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="155"/> <location filename="../ui/controllers/settingsController.cpp" line="140"/>
<source>All settings have been reset to default values</source> <source>All settings have been reset to default values</source>
<translation>تم استرجاع جميع الإعدادات للإعدادات الافتراضية</translation> <translation>تم استرجاع جميع الإعدادات للإعدادات الافتراضية</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="161"/> <location filename="../ui/controllers/settingsController.cpp" line="150"/>
<source>Cached profiles cleared</source> <source>Cached profiles cleared</source>
<translation>تم حذف الملفات الشخصية المٌخزنة مؤقتاُ</translation> <translation>تم حذف الملفات الشخصية المٌخزنة مؤقتاُ</translation>
</message> </message>
@@ -3101,28 +3078,28 @@ While it offers a blend of security, stability, and speed, it&apos;s essential t
<translation>احفظ تكوين AmneziaVPN</translation> <translation>احفظ تكوين AmneziaVPN</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="74"/> <location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="85"/>
<source>Share</source> <source>Share</source>
<translation>شارك</translation> <translation>شارك</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="108"/> <location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="121"/>
<source>Copy</source> <source>Copy</source>
<translation>انسخ</translation> <translation>انسخ</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="167"/> <location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="188"/>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="177"/> <location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="198"/>
<source>Copied</source> <source>Copied</source>
<translation>تم النسخ</translation> <translation>تم النسخ</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="126"/> <location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="141"/>
<source>Copy config string</source> <source>Copy config string</source>
<translation>انسخ نص التكوين</translation> <translation>انسخ نص التكوين</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="141"/> <location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="160"/>
<source>Show connection settings</source> <source>Show connection settings</source>
<translation>اظهر إعدادات الاتصال</translation> <translation>اظهر إعدادات الاتصال</translation>
</message> </message>
@@ -3131,7 +3108,7 @@ While it offers a blend of security, stability, and speed, it&apos;s essential t
<translation type="obsolete"></translation> <translation type="obsolete"></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="305"/> <location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="328"/>
<source>To read the QR code in the Amnezia app, select &quot;Add server&quot; &quot;I have data to connect&quot; &quot;QR code, key or settings file&quot;</source> <source>To read the QR code in the Amnezia app, select &quot;Add server&quot; &quot;I have data to connect&quot; &quot;QR code, key or settings file&quot;</source>
<translation>حتي تقرأ رمز ال QR في تطبيق Amnezia, اختار &quot;إضافة خادم&quot; - &quot;لدي بيانات الاتصال&quot; - &quot;رمز Qr, او مفتاح تعريف او ملف إعدادات&quot;</translation> <translation>حتي تقرأ رمز ال QR في تطبيق Amnezia, اختار &quot;إضافة خادم&quot; - &quot;لدي بيانات الاتصال&quot; - &quot;رمز Qr, او مفتاح تعريف او ملف إعدادات&quot;</translation>
</message> </message>
@@ -3223,7 +3200,7 @@ While it offers a blend of security, stability, and speed, it&apos;s essential t
<context> <context>
<name>VpnConnection</name> <name>VpnConnection</name>
<message> <message>
<location filename="../vpnconnection.cpp" line="457"/> <location filename="../vpnconnection.cpp" line="458"/>
<source>Mbps</source> <source>Mbps</source>
<translation></translation> <translation></translation>
</message> </message>
@@ -3307,12 +3284,12 @@ While it offers a blend of security, stability, and speed, it&apos;s essential t
<context> <context>
<name>main2</name> <name>main2</name>
<message> <message>
<location filename="../ui/qml/main2.qml" line="174"/> <location filename="../ui/qml/main2.qml" line="179"/>
<source>Private key passphrase</source> <source>Private key passphrase</source>
<translation>عبارة المرور الخاصة بالمفتاح</translation> <translation>عبارة المرور الخاصة بالمفتاح</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/main2.qml" line="197"/> <location filename="../ui/qml/main2.qml" line="202"/>
<source>Save</source> <source>Save</source>
<translation>احفظ</translation> <translation>احفظ</translation>
</message> </message>
+137 -152
View File
@@ -34,48 +34,48 @@
<context> <context>
<name>ConnectionController</name> <name>ConnectionController</name>
<message> <message>
<location filename="../ui/controllers/connectionController.cpp" line="40"/> <location filename="../ui/controllers/connectionController.cpp" line="41"/>
<source>VPN Protocols is not installed. <source>VPN Protocols is not installed.
Please install VPN container at first</source> Please install VPN container at first</source>
<translation>پروتکل ویپیان نصب نشده است <translation>پروتکل ویپیان نصب نشده است
لطفا کانتینر ویپیان را نصب کنید</translation> لطفا کانتینر ویپیان را نصب کنید</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/connectionController.cpp" line="64"/> <location filename="../ui/controllers/connectionController.cpp" line="65"/>
<source>Connection...</source> <source>Connection...</source>
<translation>در حال ارتباط...</translation> <translation>در حال ارتباط...</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/connectionController.cpp" line="69"/> <location filename="../ui/controllers/connectionController.cpp" line="70"/>
<source>Connected</source> <source>Connected</source>
<translation>متصل</translation> <translation>متصل</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/connectionController.cpp" line="114"/> <location filename="../ui/controllers/connectionController.cpp" line="115"/>
<source>Settings updated successfully, Reconnnection...</source> <source>Settings updated successfully, Reconnnection...</source>
<translation>تنظیمات به روز رسانی شد <translation>تنظیمات به روز رسانی شد
در حال اتصال دوباره...</translation> در حال اتصال دوباره...</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/connectionController.cpp" line="117"/> <location filename="../ui/controllers/connectionController.cpp" line="118"/>
<source>Settings updated successfully</source> <source>Settings updated successfully</source>
<translation>تنظیمات با موفقیت بهروزرسانی شدند</translation> <translation>تنظیمات با موفقیت بهروزرسانی شدند</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/connectionController.cpp" line="78"/> <location filename="../ui/controllers/connectionController.cpp" line="79"/>
<source>Reconnection...</source> <source>Reconnection...</source>
<translation>اتصال دوباره...</translation> <translation>اتصال دوباره...</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/connectionController.h" line="62"/> <location filename="../ui/controllers/connectionController.h" line="62"/>
<location filename="../ui/controllers/connectionController.cpp" line="83"/> <location filename="../ui/controllers/connectionController.cpp" line="84"/>
<location filename="../ui/controllers/connectionController.cpp" line="97"/> <location filename="../ui/controllers/connectionController.cpp" line="98"/>
<location filename="../ui/controllers/connectionController.cpp" line="103"/> <location filename="../ui/controllers/connectionController.cpp" line="104"/>
<source>Connect</source> <source>Connect</source>
<translation>اتصال</translation> <translation>اتصال</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/connectionController.cpp" line="88"/> <location filename="../ui/controllers/connectionController.cpp" line="89"/>
<source>Disconnection...</source> <source>Disconnection...</source>
<translation>قطع ارتباط...</translation> <translation>قطع ارتباط...</translation>
</message> </message>
@@ -335,17 +335,17 @@ Already installed containers were found on the server. All installed containers
<translation>تونل تقسیمشده غیرفعال شده</translation> <translation>تونل تقسیمشده غیرفعال شده</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="224"/> <location filename="../ui/qml/Pages2/PageHome.qml" line="223"/>
<source>VPN protocol</source> <source>VPN protocol</source>
<translation>پروتکل ویپیان</translation> <translation>پروتکل ویپیان</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="272"/> <location filename="../ui/qml/Pages2/PageHome.qml" line="271"/>
<source>Servers</source> <source>Servers</source>
<translation>سرورها</translation> <translation>سرورها</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="360"/> <location filename="../ui/qml/Pages2/PageHome.qml" line="359"/>
<source>Unable change server while there is an active connection</source> <source>Unable change server while there is an active connection</source>
<translation>امکان تغییر سرور در هنگام متصل بودن وجود ندارد</translation> <translation>امکان تغییر سرور در هنگام متصل بودن وجود ندارد</translation>
</message> </message>
@@ -362,51 +362,6 @@ Already installed containers were found on the server. All installed containers
<source>Port</source> <source>Port</source>
<translation>پورت</translation> <translation>پورت</translation>
</message> </message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="114"/>
<source>Junk packet count</source>
<translation>تعداد بستههای ناخواسته</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="139"/>
<source>Junk packet minimum size</source>
<translation>Junk packet minimum size</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="159"/>
<source>Junk packet maximum size</source>
<translation>Junk packet maximum size</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="179"/>
<source>Init packet junk size</source>
<translation>Init packet junk size</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="199"/>
<source>Response packet junk size</source>
<translation>Response packet junk size</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="219"/>
<source>Init packet magic header</source>
<translation>Init packet magic header</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="239"/>
<source>Response packet magic header</source>
<translation>Response packet magic header</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="259"/>
<source>Transport packet magic header</source>
<translation>Transport packet magic header</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="279"/>
<source>Underload packet magic header</source>
<translation>Underload packet magic header</translation>
</message>
<message> <message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="304"/> <location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="304"/>
<source>Remove AmneziaWG</source> <source>Remove AmneziaWG</source>
@@ -1524,7 +1479,7 @@ Already installed containers were found on the server. All installed containers
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="203"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="203"/>
<source>Do you want to clear server from Amnezia software?</source> <source>Do you want to clear server Amnezia-installed services?</source>
<translation>آیا میخواهید سرور را از نرمافزار Amnezia پاک کنید؟</translation> <translation>آیا میخواهید سرور را از نرمافزار Amnezia پاک کنید؟</translation>
</message> </message>
<message> <message>
@@ -1539,11 +1494,11 @@ Already installed containers were found on the server. All installed containers
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="169"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="169"/>
<source>Remove server from application</source> <source>Remove this server from the app</source>
<translation>حذف کردن سرور از نرمافزار</translation> <translation>حذف کردن سرور از نرمافزار</translation>
</message> </message>
<message> <message>
<source>Remove server?</source> <source>Remove server from application?</source>
<translation type="vanished">حذف سرور؟</translation> <translation type="vanished">حذف سرور؟</translation>
</message> </message>
<message> <message>
@@ -1553,11 +1508,11 @@ Already installed containers were found on the server. All installed containers
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="199"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="199"/>
<source>Clear server from Amnezia software</source> <source>Clear server Amnezia-installed services</source>
<translation>پاک کردن سرور از نرمافزار Amnezia</translation> <translation>پاک کردن سرور از نرمافزار Amnezia</translation>
</message> </message>
<message> <message>
<source>Clear server from Amnezia software?</source> <source>Clear server Amnezia-installed services?</source>
<translation type="vanished">سرور از نرمافزار Amnezia پاک شود؟</translation> <translation type="vanished">سرور از نرمافزار Amnezia پاک شود؟</translation>
</message> </message>
<message> <message>
@@ -1760,41 +1715,45 @@ Already installed containers were found on the server. All installed containers
<translation>ارتباط سرور</translation> <translation>ارتباط سرور</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="51"/>
<source>Do not use connection code from public sources. It may have been created to intercept your data. <source>Do not use connection code from public sources. It may have been created to intercept your data.
It&apos;s okay as long as it&apos;s from someone you trust.</source> It&apos;s okay as long as it&apos;s from someone you trust.</source>
<translation>از کد اتصالاتی که در منابع عمومی هستند استفاده نکنید. ممکن است برای شنود اطلاعات شما ایجاد شده باشند. <translation type="vanished">از کد اتصالاتی که در منابع عمومی هستند استفاده نکنید. ممکن است برای شنود اطلاعات شما ایجاد شده باشند.
ایرادی ندارد که از طرف کسی باشد که به او اعتماد دارید.</translation> ایرادی ندارد که از طرف کسی باشد که به او اعتماد دارید.</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="61"/> <location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="51"/>
<source>Do not use connection codes from untrusted sources, as they may be created to intercept your data.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="60"/>
<source>What do you have?</source> <source>What do you have?</source>
<translation>چی داری؟</translation> <translation>چی داری؟</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="68"/> <location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="67"/>
<source>File with connection settings</source> <source>File with connection settings</source>
<translation>فایل شامل تنظیمات اتصال</translation> <translation>فایل شامل تنظیمات اتصال</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="68"/> <location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="67"/>
<source>File with connection settings or backup</source> <source>File with connection settings or backup</source>
<translation>فایل شامل تنظیمات اتصال یا بکآپ</translation> <translation>فایل شامل تنظیمات اتصال یا بکآپ</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="75"/> <location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="74"/>
<source>Open config file</source> <source>Open config file</source>
<translation>باز کردن فایل تنظیمات</translation> <translation>باز کردن فایل تنظیمات</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="95"/> <location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="94"/>
<source>QR-code</source> <source>QR-code</source>
<translation>QR-Code</translation> <translation>QR-Code</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="114"/> <location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="113"/>
<source>Key as text</source> <source>Key as text</source>
<translation>متن شامل کلید</translation> <translation>متن شامل کلید</translation>
</message> </message>
@@ -1897,7 +1856,7 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation>ادامه</translation> <translation>ادامه</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="198"/> <location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="197"/>
<source>Set up later</source> <source>Set up later</source>
<translation>بعدا تنظیم شود</translation> <translation>بعدا تنظیم شود</translation>
</message> </message>
@@ -1998,32 +1957,32 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<context> <context>
<name>PageSetupWizardStart</name> <name>PageSetupWizardStart</name>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="54"/> <location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="64"/>
<source>Settings restored from backup file</source> <source>Settings restored from backup file</source>
<translation>تنظیمات از فایل بکآپ بازیابی شدند</translation> <translation>تنظیمات از فایل بکآپ بازیابی شدند</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="106"/> <location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="116"/>
<source>Free service for creating a personal VPN on your server.</source> <source>Free service for creating a personal VPN on your server.</source>
<translation>سرویس رایگان برای ایجاد ویپیان شخصی بر روی سرور خودتان.</translation> <translation>سرویس رایگان برای ایجاد ویپیان شخصی بر روی سرور خودتان.</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="107"/> <location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="117"/>
<source> Helps you access blocked content without revealing your privacy, even to VPN providers.</source> <source> Helps you access blocked content without revealing your privacy, even to VPN providers.</source>
<translation>به شما کمک میکند که بدون فاش کردن حریم شخصی خودتان حتی برای ارائه دهنده ویپیان به محتوای مسدود شده دسترسی پیدا کنید.</translation> <translation>به شما کمک میکند که بدون فاش کردن حریم شخصی خودتان حتی برای ارائه دهنده ویپیان به محتوای مسدود شده دسترسی پیدا کنید.</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="116"/> <location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="126"/>
<source>I have the data to connect</source> <source>I have the data to connect</source>
<translation>من داده برای اتصال دارم</translation> <translation>من داده برای اتصال دارم</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="136"/> <location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="146"/>
<source>I have nothing</source> <source>I have nothing</source>
<translation>من هیچی ندارم</translation> <translation>من هیچی ندارم</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="139"/> <location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="149"/>
<source>https://amnezia.org/instructions/0_starter-guide</source> <source>https://amnezia.org/instructions/0_starter-guide</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@@ -2064,9 +2023,13 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation>ارتباط جدید</translation> <translation>ارتباط جدید</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="95"/>
<source>Do not use connection code from public sources. It could be created to intercept your data.</source> <source>Do not use connection code from public sources. It could be created to intercept your data.</source>
<translation>از کد اتصالی که در منابع عمومی هست استفاده نکنید. ممکن است برای شنود اطلاعات شما ایجاد شده باشد.</translation> <translation type="vanished">از کد اتصالی که در منابع عمومی هست استفاده نکنید. ممکن است برای شنود اطلاعات شما ایجاد شده باشد.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="95"/>
<source>Do not use connection codes from untrusted sources, as they may be created to intercept your data.</source>
<translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="110"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="110"/>
@@ -2204,7 +2167,7 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="243"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="243"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="504"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="513"/>
<source>Users</source> <source>Users</source>
<translation>کاربران</translation> <translation>کاربران</translation>
</message> </message>
@@ -2214,37 +2177,37 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation>نام کاربری</translation> <translation>نام کاربری</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="520"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="529"/>
<source>Search</source> <source>Search</source>
<translation>جستجو</translation> <translation>جستجو</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="604"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="613"/>
<source>Creation date: </source> <source>Creation date: </source>
<translation>تاریخ ایجاد:</translation> <translation>تاریخ ایجاد:</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="618"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="627"/>
<source>Rename</source> <source>Rename</source>
<translation>تغییر نام</translation> <translation>تغییر نام</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="650"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="659"/>
<source>Client name</source> <source>Client name</source>
<translation>نام کلاینت</translation> <translation>نام کلاینت</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="663"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="672"/>
<source>Save</source> <source>Save</source>
<translation>ذخیره</translation> <translation>ذخیره</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="695"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="704"/>
<source>Revoke</source> <source>Revoke</source>
<translation>ابطال</translation> <translation>ابطال</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="698"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="707"/>
<source>Revoke the config for a user - %1?</source> <source>Revoke the config for a user - %1?</source>
<translation> لغو پیکربندی برای یک کاربر %1؟</translation> <translation> لغو پیکربندی برای یک کاربر %1؟</translation>
</message> </message>
@@ -2253,17 +2216,17 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation type="vanished">ابطال تنظیمات برای کاربر </translation> <translation type="vanished">ابطال تنظیمات برای کاربر </translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="699"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="708"/>
<source>The user will no longer be able to connect to your server.</source> <source>The user will no longer be able to connect to your server.</source>
<translation>کاربر دیگر نمیتواند به سرور وصل شود.</translation> <translation>کاربر دیگر نمیتواند به سرور وصل شود.</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="700"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="709"/>
<source>Continue</source> <source>Continue</source>
<translation>ادامه</translation> <translation>ادامه</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="701"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="710"/>
<source>Cancel</source> <source>Cancel</source>
<translation>کنسل</translation> <translation>کنسل</translation>
</message> </message>
@@ -2281,20 +2244,20 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation type="vanished">به اشتراک گذاری دسترسی به مدیریت سرور. کاربری که دسترسی کامل سرور با او به اشتراک گذاشته میشود میتواند پروتکلها و سرویسها را در سرور حذف یا اضافه کند و یا تنظیمات سرور را تغییر دهد.</translation> <translation type="vanished">به اشتراک گذاری دسترسی به مدیریت سرور. کاربری که دسترسی کامل سرور با او به اشتراک گذاشته میشود میتواند پروتکلها و سرویسها را در سرور حذف یا اضافه کند و یا تنظیمات سرور را تغییر دهد.</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="348"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="356"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="349"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="357"/>
<source>Protocol</source> <source>Protocol</source>
<translation>پروتکل</translation> <translation>پروتکل</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="449"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="457"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="450"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="458"/>
<source>Connection format</source> <source>Connection format</source>
<translation>فرمت ارتباط</translation> <translation>فرمت ارتباط</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="198"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="198"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="487"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="496"/>
<source>Share</source> <source>Share</source>
<translation>اشتراکگذاری</translation> <translation>اشتراکگذاری</translation>
</message> </message>
@@ -2672,91 +2635,113 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="32"/> <location filename="../core/errorstrings.cpp" line="32"/>
<source>Scp error: Generic failure</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Sftp error: End-of-file encountered</source> <source>Sftp error: End-of-file encountered</source>
<translation>Sftp error: End-of-file encountered</translation> <translation type="vanished">Sftp error: End-of-file encountered</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="33"/>
<source>Sftp error: File does not exist</source> <source>Sftp error: File does not exist</source>
<translation>Sftp error: File does not exist</translation> <translation type="vanished">Sftp error: File does not exist</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="34"/>
<source>Sftp error: Permission denied</source> <source>Sftp error: Permission denied</source>
<translation>Sftp error: Permission denied</translation> <translation type="vanished">Sftp error: Permission denied</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="35"/>
<source>Sftp error: Generic failure</source> <source>Sftp error: Generic failure</source>
<translation>Sftp error: Generic failure</translation> <translation type="vanished">Sftp error: Generic failure</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="36"/>
<source>Sftp error: Garbage received from server</source> <source>Sftp error: Garbage received from server</source>
<translation>Sftp error: Garbage received from server</translation> <translation type="vanished">Sftp error: Garbage received from server</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="37"/>
<source>Sftp error: No connection has been set up</source> <source>Sftp error: No connection has been set up</source>
<translation>Sftp error: No connection has been set up</translation> <translation type="vanished">Sftp error: No connection has been set up</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="38"/>
<source>Sftp error: There was a connection, but we lost it</source> <source>Sftp error: There was a connection, but we lost it</source>
<translation>Sftp error: There was a connection, but we lost it</translation> <translation type="vanished">Sftp error: There was a connection, but we lost it</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="39"/>
<source>Sftp error: Operation not supported by libssh yet</source> <source>Sftp error: Operation not supported by libssh yet</source>
<translation>Sftp error: Operation not supported by libssh yet</translation> <translation type="vanished">Sftp error: Operation not supported by libssh yet</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="40"/>
<source>Sftp error: Invalid file handle</source> <source>Sftp error: Invalid file handle</source>
<translation>Sftp error: Invalid file handle</translation> <translation type="vanished">Sftp error: Invalid file handle</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="41"/>
<source>Sftp error: No such file or directory path exists</source> <source>Sftp error: No such file or directory path exists</source>
<translation>Sftp error: No such file or directory path exists</translation> <translation type="vanished">Sftp error: No such file or directory path exists</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="42"/>
<source>Sftp error: An attempt to create an already existing file or directory has been made</source> <source>Sftp error: An attempt to create an already existing file or directory has been made</source>
<translation>Sftp error: An attempt to create an already existing file or directory has been made</translation> <translation type="vanished">Sftp error: An attempt to create an already existing file or directory has been made</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="43"/>
<source>Sftp error: Write-protected filesystem</source> <source>Sftp error: Write-protected filesystem</source>
<translation>Sftp error: Write-protected filesystem</translation> <translation type="vanished">Sftp error: Write-protected filesystem</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="44"/>
<source>Sftp error: No media was in remote drive</source> <source>Sftp error: No media was in remote drive</source>
<translation>Sftp error: No media was in remote drive</translation> <translation type="vanished">Sftp error: No media was in remote drive</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="62"/> <location filename="../core/errorstrings.cpp" line="50"/>
<source>The config does not contain any containers and credentials for connecting to the server</source> <source>The config does not contain any containers and credentials for connecting to the server</source>
<translation>تنظیمات شامل هیچ کانتینر یا اعتبارنامهای برای اتصال به سرور نیست</translation> <translation>تنظیمات شامل هیچ کانتینر یا اعتبارنامهای برای اتصال به سرور نیست</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="65"/> <location filename="../core/errorstrings.cpp" line="53"/>
<source>VPN connection error</source> <source>VPN connection error</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="68"/> <location filename="../core/errorstrings.cpp" line="56"/>
<source>Error when retrieving configuration from API</source> <source>Error when retrieving configuration from API</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="69"/> <location filename="../core/errorstrings.cpp" line="57"/>
<source>This config has already been added to the application</source> <source>This config has already been added to the application</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="76"/> <location filename="../core/errorstrings.cpp" line="60"/>
<source>QFile error: The file could not be opened</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="61"/>
<source>QFile error: An error occurred when reading from the file</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="62"/>
<source>QFile error: The file could not be accessed</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="63"/>
<source>QFile error: An unspecified error occurred</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="64"/>
<source>QFile error: A fatal error occurred</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="65"/>
<source>QFile error: The operation was aborted</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="72"/>
<source>ErrorCode: %1. </source> <source>ErrorCode: %1. </source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@@ -2765,52 +2750,52 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation type="vanished">Failed to save config to disk</translation> <translation type="vanished">Failed to save config to disk</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="47"/> <location filename="../core/errorstrings.cpp" line="35"/>
<source>OpenVPN config missing</source> <source>OpenVPN config missing</source>
<translation>OpenVPN config missing</translation> <translation>OpenVPN config missing</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="48"/> <location filename="../core/errorstrings.cpp" line="36"/>
<source>OpenVPN management server error</source> <source>OpenVPN management server error</source>
<translation>OpenVPN management server error</translation> <translation>OpenVPN management server error</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="51"/> <location filename="../core/errorstrings.cpp" line="39"/>
<source>OpenVPN executable missing</source> <source>OpenVPN executable missing</source>
<translation>OpenVPN executable missing</translation> <translation>OpenVPN executable missing</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="52"/> <location filename="../core/errorstrings.cpp" line="40"/>
<source>ShadowSocks (ss-local) executable missing</source> <source>ShadowSocks (ss-local) executable missing</source>
<translation>ShadowSocks (ss-local) executable missing</translation> <translation>ShadowSocks (ss-local) executable missing</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="53"/> <location filename="../core/errorstrings.cpp" line="41"/>
<source>Cloak (ck-client) executable missing</source> <source>Cloak (ck-client) executable missing</source>
<translation>Cloak (ck-client) executable missing</translation> <translation>Cloak (ck-client) executable missing</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="54"/> <location filename="../core/errorstrings.cpp" line="42"/>
<source>Amnezia helper service error</source> <source>Amnezia helper service error</source>
<translation>Amnezia helper service error</translation> <translation>Amnezia helper service error</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="55"/> <location filename="../core/errorstrings.cpp" line="43"/>
<source>OpenSSL failed</source> <source>OpenSSL failed</source>
<translation>OpenSSL failed</translation> <translation>OpenSSL failed</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="58"/> <location filename="../core/errorstrings.cpp" line="46"/>
<source>Can&apos;t connect: another VPN connection is active</source> <source>Can&apos;t connect: another VPN connection is active</source>
<translation>Can&apos;t connect: another VPN connection is active</translation> <translation>Can&apos;t connect: another VPN connection is active</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="59"/> <location filename="../core/errorstrings.cpp" line="47"/>
<source>Can&apos;t setup OpenVPN TAP network adapter</source> <source>Can&apos;t setup OpenVPN TAP network adapter</source>
<translation>Can&apos;t setup OpenVPN TAP network adapter</translation> <translation>Can&apos;t setup OpenVPN TAP network adapter</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="60"/> <location filename="../core/errorstrings.cpp" line="48"/>
<source>VPN pool error: no available addresses</source> <source>VPN pool error: no available addresses</source>
<translation>VPN pool error: no available addresses</translation> <translation>VPN pool error: no available addresses</translation>
</message> </message>
@@ -2819,7 +2804,7 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation type="vanished">The config does not contain any containers and credentiaks for connecting to the server</translation> <translation type="vanished">The config does not contain any containers and credentiaks for connecting to the server</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="73"/> <location filename="../core/errorstrings.cpp" line="69"/>
<source>Internal error</source> <source>Internal error</source>
<translation>Internal error</translation> <translation>Internal error</translation>
</message> </message>
@@ -3198,8 +3183,8 @@ This means that AmneziaWG keeps the fast performance of the original while addin
<translation>Server #1</translation> <translation>Server #1</translation>
</message> </message>
<message> <message>
<location filename="../settings.cpp" line="206"/> <location filename="../settings.cpp" line="207"/>
<location filename="../settings.cpp" line="213"/> <location filename="../settings.cpp" line="214"/>
<source>Server</source> <source>Server</source>
<translation>Server</translation> <translation>Server</translation>
</message> </message>
@@ -3207,17 +3192,17 @@ This means that AmneziaWG keeps the fast performance of the original while addin
<context> <context>
<name>SettingsController</name> <name>SettingsController</name>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="155"/> <location filename="../ui/controllers/settingsController.cpp" line="140"/>
<source>All settings have been reset to default values</source> <source>All settings have been reset to default values</source>
<translation>تمام تنظیمات به مقادیر پیش فرض ریست شد</translation> <translation>تمام تنظیمات به مقادیر پیش فرض ریست شد</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="161"/> <location filename="../ui/controllers/settingsController.cpp" line="150"/>
<source>Cached profiles cleared</source> <source>Cached profiles cleared</source>
<translation>پروفایل ذخیره شده پاک شد</translation> <translation>پروفایل ذخیره شده پاک شد</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="139"/> <location filename="../ui/controllers/settingsController.cpp" line="123"/>
<source>Backup file is corrupted</source> <source>Backup file is corrupted</source>
<translation>فایل بکآپ خراب شده است</translation> <translation>فایل بکآپ خراب شده است</translation>
</message> </message>
@@ -3231,33 +3216,33 @@ This means that AmneziaWG keeps the fast performance of the original while addin
<translation>ذخیره تنظیمات AmneziaVPN</translation> <translation>ذخیره تنظیمات AmneziaVPN</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="74"/> <location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="85"/>
<source>Share</source> <source>Share</source>
<translation>اشتراکگذاری</translation> <translation>اشتراکگذاری</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="108"/> <location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="121"/>
<source>Copy</source> <source>Copy</source>
<translation>کپی</translation> <translation>کپی</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="167"/> <location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="188"/>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="177"/> <location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="198"/>
<source>Copied</source> <source>Copied</source>
<translation>کپی شد</translation> <translation>کپی شد</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="126"/> <location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="141"/>
<source>Copy config string</source> <source>Copy config string</source>
<translation>کپیکردن متن تنظیمات</translation> <translation>کپیکردن متن تنظیمات</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="141"/> <location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="160"/>
<source>Show connection settings</source> <source>Show connection settings</source>
<translation>نمایش تنظیمات ارتباط</translation> <translation>نمایش تنظیمات ارتباط</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="305"/> <location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="328"/>
<source>To read the QR code in the Amnezia app, select &quot;Add server&quot; &quot;I have data to connect&quot; &quot;QR code, key or settings file&quot;</source> <source>To read the QR code in the Amnezia app, select &quot;Add server&quot; &quot;I have data to connect&quot; &quot;QR code, key or settings file&quot;</source>
<translation>برای خواندن QR Code در نرمافزار AmneziaVPN &quot;اضافه کردن سرور&quot; -&gt; &quot;من داده برای اتصال دارم&quot; -&gt; &quot;QR Code، کلید یا فایل تنظیمات&quot;</translation> <translation>برای خواندن QR Code در نرمافزار AmneziaVPN &quot;اضافه کردن سرور&quot; -&gt; &quot;من داده برای اتصال دارم&quot; -&gt; &quot;QR Code، کلید یا فایل تنظیمات&quot;</translation>
</message> </message>
@@ -3349,7 +3334,7 @@ This means that AmneziaWG keeps the fast performance of the original while addin
<context> <context>
<name>VpnConnection</name> <name>VpnConnection</name>
<message> <message>
<location filename="../vpnconnection.cpp" line="457"/> <location filename="../vpnconnection.cpp" line="458"/>
<source>Mbps</source> <source>Mbps</source>
<translation>Mbps</translation> <translation>Mbps</translation>
</message> </message>
@@ -3453,12 +3438,12 @@ This means that AmneziaWG keeps the fast performance of the original while addin
<context> <context>
<name>main2</name> <name>main2</name>
<message> <message>
<location filename="../ui/qml/main2.qml" line="174"/> <location filename="../ui/qml/main2.qml" line="179"/>
<source>Private key passphrase</source> <source>Private key passphrase</source>
<translation>عبارت کلید خصوصی</translation> <translation>عبارت کلید خصوصی</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/main2.qml" line="197"/> <location filename="../ui/qml/main2.qml" line="202"/>
<source>Save</source> <source>Save</source>
<translation>ذخیره</translation> <translation>ذخیره</translation>
</message> </message>
File diff suppressed because it is too large Load Diff
+187 -201
View File
@@ -5,7 +5,7 @@
<name>AmneziaApplication</name> <name>AmneziaApplication</name>
<message> <message>
<source>Split tunneling for WireGuard is not implemented, the option was disabled</source> <source>Split tunneling for WireGuard is not implemented, the option was disabled</source>
<translation type="vanished">Раздельное туннелирование для &quot;Wireguard&quot; не реализовано,опция отключена</translation> <translation type="vanished">Раздельное туннелирование для &quot;Wireguard&quot; не реализовано, опция отключена</translation>
</message> </message>
<message> <message>
<source>Split tunneling for %1 is not implemented, the option was disabled</source> <source>Split tunneling for %1 is not implemented, the option was disabled</source>
@@ -27,47 +27,47 @@
<context> <context>
<name>ConnectionController</name> <name>ConnectionController</name>
<message> <message>
<location filename="../ui/controllers/connectionController.cpp" line="40"/> <location filename="../ui/controllers/connectionController.cpp" line="41"/>
<source>VPN Protocols is not installed. <source>VPN Protocols is not installed.
Please install VPN container at first</source> Please install VPN container at first</source>
<translation>VPN протоколы не установлены. <translation>VPN протоколы не установлены.
Пожалуйста, установите протокол</translation> Пожалуйста, установите протокол</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/connectionController.cpp" line="64"/> <location filename="../ui/controllers/connectionController.cpp" line="65"/>
<source>Connection...</source> <source>Connection...</source>
<translation>Подключение...</translation> <translation>Подключение...</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/connectionController.cpp" line="69"/> <location filename="../ui/controllers/connectionController.cpp" line="70"/>
<source>Connected</source> <source>Connected</source>
<translation>Подключено</translation> <translation>Подключено</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/connectionController.cpp" line="114"/> <location filename="../ui/controllers/connectionController.cpp" line="115"/>
<source>Settings updated successfully, Reconnnection...</source> <source>Settings updated successfully, Reconnnection...</source>
<translation>Настройки успешно обновлены. Подключение...</translation> <translation>Настройки успешно обновлены, Подключение...</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/connectionController.cpp" line="117"/> <location filename="../ui/controllers/connectionController.cpp" line="118"/>
<source>Settings updated successfully</source> <source>Settings updated successfully</source>
<translation type="unfinished">Настройки успешно обновлены</translation> <translation>Настройки успешно обновлены.</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/connectionController.cpp" line="78"/> <location filename="../ui/controllers/connectionController.cpp" line="79"/>
<source>Reconnection...</source> <source>Reconnection...</source>
<translation>Переподключение...</translation> <translation>Переподключение...</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/connectionController.h" line="62"/> <location filename="../ui/controllers/connectionController.h" line="62"/>
<location filename="../ui/controllers/connectionController.cpp" line="83"/> <location filename="../ui/controllers/connectionController.cpp" line="84"/>
<location filename="../ui/controllers/connectionController.cpp" line="97"/> <location filename="../ui/controllers/connectionController.cpp" line="98"/>
<location filename="../ui/controllers/connectionController.cpp" line="103"/> <location filename="../ui/controllers/connectionController.cpp" line="104"/>
<source>Connect</source> <source>Connect</source>
<translation>Подключиться</translation> <translation>Подключиться</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/connectionController.cpp" line="88"/> <location filename="../ui/controllers/connectionController.cpp" line="89"/>
<source>Disconnection...</source> <source>Disconnection...</source>
<translation>Отключение...</translation> <translation>Отключение...</translation>
</message> </message>
@@ -87,7 +87,7 @@
<message> <message>
<location filename="../ui/qml/Components/ConnectionTypeSelectionDrawer.qml" line="58"/> <location filename="../ui/qml/Components/ConnectionTypeSelectionDrawer.qml" line="58"/>
<source>Open config file, key or QR code</source> <source>Open config file, key or QR code</source>
<translation>Открыть файл конфига, ключ или QR код</translation> <translation>Открыть файл конфигурации, ключ или QR код</translation>
</message> </message>
</context> </context>
<context> <context>
@@ -110,7 +110,7 @@
<message> <message>
<location filename="../ui/qml/Controls2/ContextMenuType.qml" line="28"/> <location filename="../ui/qml/Controls2/ContextMenuType.qml" line="28"/>
<source>&amp;SelectAll</source> <source>&amp;SelectAll</source>
<translation>&amp;ВыбратьВсе</translation> <translation>&amp;Выбрать всё</translation>
</message> </message>
</context> </context>
<context> <context>
@@ -143,43 +143,44 @@
<message> <message>
<location filename="../ui/qml/Components/HomeSplitTunnelingDrawer.qml" line="32"/> <location filename="../ui/qml/Components/HomeSplitTunnelingDrawer.qml" line="32"/>
<source>Split tunneling</source> <source>Split tunneling</source>
<translation type="unfinished">Раздельное VPN-туннелирование</translation> <translation>Раздельное-туннелирование</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Components/HomeSplitTunnelingDrawer.qml" line="33"/> <location filename="../ui/qml/Components/HomeSplitTunnelingDrawer.qml" line="33"/>
<source>Allows you to connect to some sites or applications through a VPN connection and bypass others</source> <source>Allows you to connect to some sites or applications through a VPN connection and bypass others</source>
<translation type="unfinished"></translation> <translation>Позволяет подключаться к одним сайтам или приложениям через защищенное соединение, а к другим в обход него</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Components/HomeSplitTunnelingDrawer.qml" line="42"/> <location filename="../ui/qml/Components/HomeSplitTunnelingDrawer.qml" line="42"/>
<source>Split tunneling on the server</source> <source>Split tunneling on the server</source>
<translation type="unfinished"></translation> <translation>Раздельное-туннелирование на сервере</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Components/HomeSplitTunnelingDrawer.qml" line="43"/> <location filename="../ui/qml/Components/HomeSplitTunnelingDrawer.qml" line="43"/>
<source>Enabled <source>Enabled
Can&apos;t be disabled for current server</source> Can&apos;t be disabled for current server</source>
<translation type="unfinished"></translation> <translation>Включено.
Не может быть отключено для текущего сервера.</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Components/HomeSplitTunnelingDrawer.qml" line="62"/> <location filename="../ui/qml/Components/HomeSplitTunnelingDrawer.qml" line="62"/>
<source>Site-based split tunneling</source> <source>Site-based split tunneling</source>
<translation type="unfinished">Раздельное туннелирование сайтов</translation> <translation>Раздельное туннелирование по сайтам</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Components/HomeSplitTunnelingDrawer.qml" line="63"/> <location filename="../ui/qml/Components/HomeSplitTunnelingDrawer.qml" line="63"/>
<source>Enabled</source> <source>Enabled</source>
<translation type="unfinished">Включено</translation> <translation>Включено</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Components/HomeSplitTunnelingDrawer.qml" line="63"/> <location filename="../ui/qml/Components/HomeSplitTunnelingDrawer.qml" line="63"/>
<source>Disabled</source> <source>Disabled</source>
<translation type="unfinished">Отключено</translation> <translation>Отключено</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Components/HomeSplitTunnelingDrawer.qml" line="79"/> <location filename="../ui/qml/Components/HomeSplitTunnelingDrawer.qml" line="79"/>
<source>App-based split tunneling</source> <source>App-based split tunneling</source>
<translation type="unfinished">Раздельное VPN-туннелирование приложений</translation> <translation>Раздельное VPN-туннелирование по приложениям</translation>
</message> </message>
</context> </context>
<context> <context>
@@ -208,7 +209,7 @@ Can&apos;t be disabled for current server</source>
<location filename="../ui/controllers/installController.cpp" line="149"/> <location filename="../ui/controllers/installController.cpp" line="149"/>
<source> <source>
Added containers that were already installed on the server</source> Added containers that were already installed on the server</source>
<translation type="unfinished">Добавлены сервисы и протоколы, которые были ранее установлены на сервер</translation> <translation>Добавлены сервисы и протоколы, которые были ранее установлены на сервер</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/installController.cpp" line="213"/> <location filename="../ui/controllers/installController.cpp" line="213"/>
@@ -225,7 +226,7 @@ Already installed containers were found on the server. All installed containers
<message> <message>
<location filename="../ui/controllers/installController.cpp" line="305"/> <location filename="../ui/controllers/installController.cpp" line="305"/>
<source>Server &apos;%1&apos; was rebooted</source> <source>Server &apos;%1&apos; was rebooted</source>
<translation type="unfinished"></translation> <translation>Сервер &apos;%1&apos; перезагружен</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/installController.cpp" line="314"/> <location filename="../ui/controllers/installController.cpp" line="314"/>
@@ -318,25 +319,25 @@ Already installed containers were found on the server. All installed containers
<message> <message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="61"/> <location filename="../ui/qml/Pages2/PageHome.qml" line="61"/>
<source>Split tunneling enabled</source> <source>Split tunneling enabled</source>
<translation type="unfinished"></translation> <translation type="unfinished">Раздельное туннелирование включено</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="61"/> <location filename="../ui/qml/Pages2/PageHome.qml" line="61"/>
<source>Split tunneling disabled</source> <source>Split tunneling disabled</source>
<translation type="unfinished"></translation> <translation type="unfinished">Раздельное туннелирование выключено</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="224"/> <location filename="../ui/qml/Pages2/PageHome.qml" line="223"/>
<source>VPN protocol</source> <source>VPN protocol</source>
<translation>VPN протокол</translation> <translation>VPN протокол</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="272"/> <location filename="../ui/qml/Pages2/PageHome.qml" line="271"/>
<source>Servers</source> <source>Servers</source>
<translation>Серверы</translation> <translation>Серверы</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="360"/> <location filename="../ui/qml/Pages2/PageHome.qml" line="359"/>
<source>Unable change server while there is an active connection</source> <source>Unable change server while there is an active connection</source>
<translation>Невозможно изменить сервер при активном соединении</translation> <translation>Невозможно изменить сервер при активном соединении</translation>
</message> </message>
@@ -346,58 +347,13 @@ Already installed containers were found on the server. All installed containers
<message> <message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="85"/> <location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="85"/>
<source>AmneziaWG settings</source> <source>AmneziaWG settings</source>
<translation>AmneziaWG настройки</translation> <translation>настройки AmneziaWG</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="93"/> <location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="93"/>
<source>Port</source> <source>Port</source>
<translation>Порт</translation> <translation>Порт</translation>
</message> </message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="114"/>
<source>Junk packet count</source>
<translation>Junk packet count</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="139"/>
<source>Junk packet minimum size</source>
<translation>Junk packet minimum size</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="159"/>
<source>Junk packet maximum size</source>
<translation>Junk packet maximum size</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="179"/>
<source>Init packet junk size</source>
<translation>Init packet junk size</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="199"/>
<source>Response packet junk size</source>
<translation>Response packet junk size</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="219"/>
<source>Init packet magic header</source>
<translation>Init packet magic header</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="239"/>
<source>Response packet magic header</source>
<translation>Response packet magic header</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="259"/>
<source>Transport packet magic header</source>
<translation>Transport packet magic header</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="279"/>
<source>Underload packet magic header</source>
<translation>Underload packet magic header</translation>
</message>
<message> <message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="304"/> <location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="304"/>
<source>Remove AmneziaWG</source> <source>Remove AmneziaWG</source>
@@ -467,7 +423,7 @@ Already installed containers were found on the server. All installed containers
<message> <message>
<location filename="../ui/qml/Pages2/PageProtocolOpenVpnSettings.qml" line="81"/> <location filename="../ui/qml/Pages2/PageProtocolOpenVpnSettings.qml" line="81"/>
<source>OpenVPN settings</source> <source>OpenVPN settings</source>
<translation>OpenVPN настройки</translation> <translation>настройки OpenVPN</translation>
</message> </message>
<message> <message>
<source>VPN Addresses Subnet</source> <source>VPN Addresses Subnet</source>
@@ -902,11 +858,11 @@ Already installed containers were found on the server. All installed containers
<message> <message>
<location filename="../ui/qml/Pages2/PageServiceTorWebsiteSettings.qml" line="112"/> <location filename="../ui/qml/Pages2/PageServiceTorWebsiteSettings.qml" line="112"/>
<source>When configuring WordPress set the this onion address as domain.</source> <source>When configuring WordPress set the this onion address as domain.</source>
<translation>При настройке WordPress укажите этот onion адрес в качестве домена.</translation> <translation>При настройке WordPress укажите этот Onion адрес в качестве домена.</translation>
</message> </message>
<message> <message>
<source>When configuring WordPress set the this address as domain.</source> <source>When configuring WordPress set the this address as domain.</source>
<translation type="vanished">При настройке WordPress укажите этот onion адрес в качестве домена.</translation> <translation type="vanished">При настройке WordPress укажите этот Onion адрес в качестве домена.</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageServiceTorWebsiteSettings.qml" line="126"/> <location filename="../ui/qml/Pages2/PageServiceTorWebsiteSettings.qml" line="126"/>
@@ -976,17 +932,17 @@ Already installed containers were found on the server. All installed containers
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsAbout.qml" line="56"/> <location filename="../ui/qml/Pages2/PageSettingsAbout.qml" line="56"/>
<source>Support Amnezia</source> <source>Support Amnezia</source>
<translation type="unfinished">Поддержите Amnezia</translation> <translation>Поддержите Amnezia</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsAbout.qml" line="71"/> <location filename="../ui/qml/Pages2/PageSettingsAbout.qml" line="71"/>
<source>This is a free and open source application. If you like it, support the developers with a donation. </source> <source>This is a free and open source application. If you like it, support the developers with a donation. </source>
<translation>Это бесплатное приложение с открытым исходным кодом. Если, оно вам нравится - поддержите разработчиков пожертвованием. </translation> <translation>Это бесплатное приложение с открытым исходным кодом. Если оно вам нравится - поддержите разработчиков пожертвованием. </translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsAbout.qml" line="72"/> <location filename="../ui/qml/Pages2/PageSettingsAbout.qml" line="72"/>
<source>And if you dont like the application, all the more reason to support it - the donation will be used for the improving the application.</source> <source>And if you dont like the application, all the more reason to support it - the donation will be used for the improving the application.</source>
<translation>А, если оно вам не нравится, тем более поддержите-пожертвование пойдет на улучшение приложения.</translation> <translation>А, если оно вам не нравится, тем более поддержите - пожертвование пойдет на улучшение приложения.</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsAbout.qml" line="82"/> <location filename="../ui/qml/Pages2/PageSettingsAbout.qml" line="82"/>
@@ -1254,7 +1210,7 @@ Already installed containers were found on the server. All installed containers
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsConnection.qml" line="49"/> <location filename="../ui/qml/Pages2/PageSettingsConnection.qml" line="49"/>
<source>If AmneziaDNS is installed on the server</source> <source>If AmneziaDNS is installed on the server</source>
<translation>Если он уставновлен на сервере</translation> <translation>Если он установлен на сервере</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsConnection.qml" line="64"/> <location filename="../ui/qml/Pages2/PageSettingsConnection.qml" line="64"/>
@@ -1296,7 +1252,7 @@ Already installed containers were found on the server. All installed containers
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="39"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="39"/>
<source>Default server does not support custom dns</source> <source>Default server does not support custom dns</source>
<translation type="unfinished"></translation> <translation type="unfinished">Сервер по умолчанию не поддерживает пользовательские dns</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="57"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="57"/>
@@ -1499,17 +1455,17 @@ Already installed containers were found on the server. All installed containers
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="173"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="173"/>
<source>Do you want to remove the server from application?</source> <source>Do you want to remove the server from application?</source>
<translation type="unfinished">Вы уверена что хотите удалить сервер из приложения?</translation> <translation type="unfinished">Вы уверены, что хотите удалить сервер из приложения?</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="203"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="203"/>
<source>Do you want to clear server from Amnezia software?</source> <source>Do you want to clear server Amnezia-installed services?</source>
<translation type="unfinished">Вы хотите очистить сервер от всех сервисов Amnezia?</translation> <translation type="unfinished">Вы хотите очистить сервер от всех сервисов Amnezia?</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="230"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="230"/>
<source>Reset API config</source> <source>Reset API config</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="234"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="234"/>
@@ -1518,12 +1474,12 @@ Already installed containers were found on the server. All installed containers
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="169"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="169"/>
<source>Remove server from application</source> <source>Remove this server from the app</source>
<translation>Удалить сервер из приложения</translation> <translation>Удалить сервер из приложения</translation>
</message> </message>
<message> <message>
<source>Remove server?</source> <source>Remove server from application?</source>
<translation type="vanished">Удалить сервер?</translation> <translation type="vanished">Удалить сервер из приложения?</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="174"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="174"/>
@@ -1532,11 +1488,11 @@ Already installed containers were found on the server. All installed containers
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="199"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="199"/>
<source>Clear server from Amnezia software</source> <source>Clear server Amnezia-installed services</source>
<translation>Очистить сервер от протоколов и сервисов Amnezia</translation> <translation>Очистить сервер от протоколов и сервисов Amnezia</translation>
</message> </message>
<message> <message>
<source>Clear server from Amnezia software?</source> <source>Clear server Amnezia-installed services?</source>
<translation type="vanished">Удалить все сервисы и протоколы Amnezia с сервера?</translation> <translation type="vanished">Удалить все сервисы и протоколы Amnezia с сервера?</translation>
</message> </message>
<message> <message>
@@ -1626,7 +1582,7 @@ Already installed containers were found on the server. All installed containers
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<source>Only the sites listed here will be accesed via VPN</source> <source>Only the sites listed here will be accessed via VPN</source>
<translation type="vanished">Только адреса из списка должны открываться через VPN</translation> <translation type="vanished">Только адреса из списка должны открываться через VPN</translation>
</message> </message>
<message> <message>
@@ -1666,17 +1622,17 @@ Already installed containers were found on the server. All installed containers
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="303"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="303"/>
<source>Import / Export Sites</source> <source>Import / Export Sites</source>
<translation>Импорт/экспорт Сайтов</translation> <translation>Импорт/экспорт сайтов</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="69"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="69"/>
<source>Only the sites listed here will be accessed through the VPN</source> <source>Only the sites listed here will be accessed through the VPN</source>
<translation type="unfinished"></translation> <translation>Только адреса из списка должны открываться через VPN</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="33"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="33"/>
<source>Cannot change split tunneling settings during active connection</source> <source>Cannot change split tunneling settings during active connection</source>
<translation type="unfinished"></translation> <translation type="unfinished">Невозможно изменить настройки раздельного туннелирования при включенном VPN</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="262"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="262"/>
@@ -1713,7 +1669,7 @@ Already installed containers were found on the server. All installed containers
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="394"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="394"/>
<source>Replace site list</source> <source>Replace site list</source>
<translation>Заменить список сайтов</translation> <translation>Заменить список с сайтами</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="397"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="397"/>
@@ -1735,41 +1691,45 @@ Already installed containers were found on the server. All installed containers
<translation>Подключение к серверу</translation> <translation>Подключение к серверу</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="51"/>
<source>Do not use connection code from public sources. It may have been created to intercept your data. <source>Do not use connection code from public sources. It may have been created to intercept your data.
It&apos;s okay as long as it&apos;s from someone you trust.</source> It&apos;s okay as long as it&apos;s from someone you trust.</source>
<translation>Не используйте код подключения из публичных источников. Его могли создать, чтобы перехватывать ваши данные.. <translation type="vanished">Не используйте код подключения из публичных источников. Его могли создать, чтобы перехватить ваши данные.
Всё в порядке, если кодом поделился пользователь, которому вы доверяете.</translation> Всё в порядке, если кодом поделился пользователь, которому вы доверяете.</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="61"/> <location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="51"/>
<source>What do you have?</source> <source>Do not use connection codes from untrusted sources, as they may be created to intercept your data.</source>
<translation>Выберете что у вас есть</translation> <translation>Не используйте код подключения из недоверенных источников. Его могли создать, чтобы перехватить ваши данные.</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="68"/> <location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="60"/>
<source>What do you have?</source>
<translation>Выберите что у вас есть</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="67"/>
<source>File with connection settings</source> <source>File with connection settings</source>
<translation>Файл с настройками подключения</translation> <translation>Файл с настройками подключения</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="68"/> <location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="67"/>
<source>File with connection settings or backup</source> <source>File with connection settings or backup</source>
<translation>Файл с настройками подключения или бэкап</translation> <translation>Файл с настройками подключения или бэкап</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="75"/> <location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="74"/>
<source>Open config file</source> <source>Open config file</source>
<translation>Открыть файл с конфигурацией</translation> <translation>Открыть файл с конфигурацией</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="95"/> <location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="94"/>
<source>QR-code</source> <source>QR-code</source>
<translation>QR-код</translation> <translation>QR-код</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="114"/> <location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="113"/>
<source>Key as text</source> <source>Key as text</source>
<translation>Ключ в виде текста</translation> <translation>Ключ в виде текста</translation>
</message> </message>
@@ -1801,7 +1761,7 @@ It&apos;s okay as long as it&apos;s from someone you trust.</source>
<message> <message>
<source>All data you enter will remain strictly confidential <source>All data you enter will remain strictly confidential
and will not be shared or disclosed to the Amnezia or any third parties</source> and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation type="vanished">Все данные, которые вы вводите, останутся строго конфиденциальными и не будут переданы или раскрыты Amnezia или каким-либо третьим сторонам</translation> <translation type="vanished">Все данные, которые вы вводите, останутся строго конфиденциальными и не будут переданы или раскрыты Amnezia или каким-либо третьим лицам</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardCredentials.qml" line="142"/> <location filename="../ui/qml/Pages2/PageSetupWizardCredentials.qml" line="142"/>
@@ -1816,7 +1776,7 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardCredentials.qml" line="46"/> <location filename="../ui/qml/Pages2/PageSetupWizardCredentials.qml" line="46"/>
<source>Configure your server</source> <source>Configure your server</source>
<translation>Настроить ваш сервер</translation> <translation>Настроить свой сервер</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardCredentials.qml" line="54"/> <location filename="../ui/qml/Pages2/PageSetupWizardCredentials.qml" line="54"/>
@@ -1831,12 +1791,12 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardCredentials.qml" line="130"/> <location filename="../ui/qml/Pages2/PageSetupWizardCredentials.qml" line="130"/>
<source>All data you enter will remain strictly confidential and will not be shared or disclosed to the Amnezia or any third parties</source> <source>All data you enter will remain strictly confidential and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation type="unfinished">Все введенные вами данные останутся строго конфиденциальными и не будут переданы или раскрыты Amnezia или третьим лицам</translation> <translation type="unfinished">Все данные, которые вы вводите, останутся строго конфиденциальными и не будут переданы или раскрыты Amnezia или каким-либо третьим лицам</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardCredentials.qml" line="139"/> <location filename="../ui/qml/Pages2/PageSetupWizardCredentials.qml" line="139"/>
<source>Ip address cannot be empty</source> <source>Ip address cannot be empty</source>
<translation>Поле Ip address не может быть пустым</translation> <translation>Поле IP address не может быть пустым</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardCredentials.qml" line="146"/> <location filename="../ui/qml/Pages2/PageSetupWizardCredentials.qml" line="146"/>
@@ -1846,7 +1806,7 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardCredentials.qml" line="150"/> <location filename="../ui/qml/Pages2/PageSetupWizardCredentials.qml" line="150"/>
<source>Password/private key cannot be empty</source> <source>Password/private key cannot be empty</source>
<translation>Поле Password/private key не может быть пустым</translation> <translation>Поле Password/Private key не может быть пустым</translation>
</message> </message>
</context> </context>
<context> <context>
@@ -1854,7 +1814,7 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="67"/> <location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="67"/>
<source>What is the level of internet control in your region?</source> <source>What is the level of internet control in your region?</source>
<translation>Какой уровень контроля интернета в вашем регионе?</translation> <translation>Какой уровень контроля над интернетом в вашем регионе?</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="139"/> <location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="139"/>
@@ -1872,7 +1832,7 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation>Продолжить</translation> <translation>Продолжить</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="198"/> <location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="197"/>
<source>Set up later</source> <source>Set up later</source>
<translation>Настроить позднее</translation> <translation>Настроить позднее</translation>
</message> </message>
@@ -1981,32 +1941,32 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<context> <context>
<name>PageSetupWizardStart</name> <name>PageSetupWizardStart</name>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="54"/> <location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="64"/>
<source>Settings restored from backup file</source> <source>Settings restored from backup file</source>
<translation>Восстановление настроек из бэкап файла</translation> <translation>Восстановление настроек из бэкап файла</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="106"/> <location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="116"/>
<source>Free service for creating a personal VPN on your server.</source> <source>Free service for creating a personal VPN on your server.</source>
<translation>Простое и бесплатное приложение для запуска self-hosted VPN с высокими требованиями к приватности.</translation> <translation>Простое и бесплатное приложение для запуска self-hosted VPN с высокими требованиями к приватности.</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="107"/> <location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="117"/>
<source> Helps you access blocked content without revealing your privacy, even to VPN providers.</source> <source> Helps you access blocked content without revealing your privacy, even to VPN providers.</source>
<translation> Помогает получить доступ к заблокированному контенту, не раскрывая вашу конфиденциальность даже провайдерам VPN.</translation> <translation> Помогает получить доступ к заблокированному контенту, не раскрывая вашу конфиденциальность даже провайдерам VPN.</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="116"/> <location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="126"/>
<source>I have the data to connect</source> <source>I have the data to connect</source>
<translation>У меня есть данные для подключения</translation> <translation>У меня есть данные для подключения</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="136"/> <location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="146"/>
<source>I have nothing</source> <source>I have nothing</source>
<translation>У меня ничего нет</translation> <translation>У меня ничего нет</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="139"/> <location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="149"/>
<source>https://amnezia.org/instructions/0_starter-guide</source> <source>https://amnezia.org/instructions/0_starter-guide</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@@ -2047,9 +2007,13 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation>Новое соединение</translation> <translation>Новое соединение</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="95"/>
<source>Do not use connection code from public sources. It could be created to intercept your data.</source> <source>Do not use connection code from public sources. It could be created to intercept your data.</source>
<translation>Не используйте код подключения из публичных источников. Его могли создать, чтобы перехватывать ваши данные.</translation> <translation type="vanished">Не используйте код подключения из публичных источников. Его могли создать, чтобы перехватить ваши данные.</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="95"/>
<source>Do not use connection codes from untrusted sources, as they may be created to intercept your data.</source>
<translation>Не используйте код подключения из недоверенных источников. Его могли создать, чтобы перехватить ваши данные.</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="110"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="110"/>
@@ -2072,7 +2036,7 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="116"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="116"/>
<source>OpenVpn native format</source> <source>OpenVpn native format</source>
<translation>OpenVpn нативный формат</translation> <translation>OpenVPN нативный формат</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="121"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="121"/>
@@ -2183,7 +2147,7 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="243"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="243"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="504"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="513"/>
<source>Users</source> <source>Users</source>
<translation type="unfinished">Пользователи</translation> <translation type="unfinished">Пользователи</translation>
</message> </message>
@@ -2193,52 +2157,52 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation type="unfinished">Имя пользователя</translation> <translation type="unfinished">Имя пользователя</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="520"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="529"/>
<source>Search</source> <source>Search</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="604"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="613"/>
<source>Creation date: </source> <source>Creation date: </source>
<translation type="unfinished">Дата создания</translation> <translation type="unfinished">Дата создания</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="618"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="627"/>
<source>Rename</source> <source>Rename</source>
<translation type="unfinished">Переименовать</translation> <translation type="unfinished">Переименовать</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="650"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="659"/>
<source>Client name</source> <source>Client name</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="663"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="672"/>
<source>Save</source> <source>Save</source>
<translation type="unfinished">Сохранить</translation> <translation type="unfinished">Сохранить</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="695"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="704"/>
<source>Revoke</source> <source>Revoke</source>
<translation type="unfinished">Отозвать</translation> <translation type="unfinished">Отозвать</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="698"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="707"/>
<source>Revoke the config for a user - %1?</source> <source>Revoke the config for a user - %1?</source>
<translation type="unfinished">Отозвать доступ для пользователя - %1?</translation> <translation type="unfinished">Отозвать доступ для пользователя - %1?</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="699"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="708"/>
<source>The user will no longer be able to connect to your server.</source> <source>The user will no longer be able to connect to your server.</source>
<translation type="unfinished">Пользователь больше не сможет подключаться к вашему серверу</translation> <translation type="unfinished">Пользователь больше не сможет подключаться к вашему серверу</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="700"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="709"/>
<source>Continue</source> <source>Continue</source>
<translation type="unfinished">Продолжить</translation> <translation type="unfinished">Продолжить</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="701"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="710"/>
<source>Cancel</source> <source>Cancel</source>
<translation type="unfinished">Отменить</translation> <translation type="unfinished">Отменить</translation>
</message> </message>
@@ -2252,20 +2216,20 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation>Поделиться доступом к VPN, без возможности управления сервером</translation> <translation>Поделиться доступом к VPN, без возможности управления сервером</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="348"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="356"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="349"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="357"/>
<source>Protocol</source> <source>Protocol</source>
<translation>Протокол</translation> <translation>Протокол</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="449"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="457"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="450"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="458"/>
<source>Connection format</source> <source>Connection format</source>
<translation>Формат подключения</translation> <translation>Формат подключения</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="198"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="198"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="487"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="496"/>
<source>Share</source> <source>Share</source>
<translation>Поделиться</translation> <translation>Поделиться</translation>
</message> </message>
@@ -2333,28 +2297,28 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_win.cpp" line="104"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_win.cpp" line="104"/>
<source>Password entry not found</source> <source>Password entry not found</source>
<translation>Password entry not found</translation> <translation>Пароль не найден</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_win.cpp" line="108"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_win.cpp" line="108"/>
<source>Could not decrypt data</source> <source>Could not decrypt data</source>
<translation>Could not decrypt data</translation> <translation>Не удалось расшифровать данные</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="585"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="585"/>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="593"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="593"/>
<source>Unknown error</source> <source>Unknown error</source>
<translation>Unknown error</translation> <translation>Неизвестная ошибка</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="614"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_unix.cpp" line="614"/>
<source>Could not open wallet: %1; %2</source> <source>Could not open wallet: %1; %2</source>
<translation>Could not open wallet: %1; %2</translation> <translation>Не удалось открыть связку ключей: %1; %2</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_haiku.cpp" line="177"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_haiku.cpp" line="177"/>
<source>Password not found</source> <source>Password not found</source>
<translation>Password not found</translation> <translation>Пароль не найден</translation>
</message> </message>
<message> <message>
<location filename="../3rd/qtkeychain/qtkeychain/keychain_android.cpp" line="173"/> <location filename="../3rd/qtkeychain/qtkeychain/keychain_android.cpp" line="173"/>
@@ -2642,86 +2606,78 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="32"/> <location filename="../core/errorstrings.cpp" line="32"/>
<source>Scp error: Generic failure</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Sftp error: End-of-file encountered</source> <source>Sftp error: End-of-file encountered</source>
<translation>Sftp error: End-of-file encountered</translation> <translation type="vanished">Sftp error: End-of-file encountered</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="33"/>
<source>Sftp error: File does not exist</source> <source>Sftp error: File does not exist</source>
<translation>Sftp error: File does not exist</translation> <translation type="vanished">Sftp error: File does not exist</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="34"/>
<source>Sftp error: Permission denied</source> <source>Sftp error: Permission denied</source>
<translation>Sftp error: Permission denied</translation> <translation type="vanished">Sftp error: Permission denied</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="35"/>
<source>Sftp error: Generic failure</source> <source>Sftp error: Generic failure</source>
<translation>Sftp error: Generic failure</translation> <translation type="vanished">Sftp error: Generic failure</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="36"/>
<source>Sftp error: Garbage received from server</source> <source>Sftp error: Garbage received from server</source>
<translation>Sftp error: Garbage received from server</translation> <translation type="vanished">Sftp error: Garbage received from server</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="37"/>
<source>Sftp error: No connection has been set up</source> <source>Sftp error: No connection has been set up</source>
<translation>Sftp error: No connection has been set up</translation> <translation type="vanished">Sftp error: No connection has been set up</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="38"/>
<source>Sftp error: There was a connection, but we lost it</source> <source>Sftp error: There was a connection, but we lost it</source>
<translation>Sftp error: There was a connection, but we lost it</translation> <translation type="vanished">Sftp error: There was a connection, but we lost it</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="39"/>
<source>Sftp error: Operation not supported by libssh yet</source> <source>Sftp error: Operation not supported by libssh yet</source>
<translation>Sftp error: Operation not supported by libssh yet</translation> <translation type="vanished">Sftp error: Operation not supported by libssh yet</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="40"/>
<source>Sftp error: Invalid file handle</source> <source>Sftp error: Invalid file handle</source>
<translation>Sftp error: Invalid file handle</translation> <translation type="vanished">Sftp error: Invalid file handle</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="41"/>
<source>Sftp error: No such file or directory path exists</source> <source>Sftp error: No such file or directory path exists</source>
<translation>Sftp error: No such file or directory path exists</translation> <translation type="vanished">Sftp error: No such file or directory path exists</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="42"/>
<source>Sftp error: An attempt to create an already existing file or directory has been made</source> <source>Sftp error: An attempt to create an already existing file or directory has been made</source>
<translation>Sftp error: An attempt to create an already existing file or directory has been made</translation> <translation type="vanished">Sftp error: An attempt to create an already existing file or directory has been made</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="43"/>
<source>Sftp error: Write-protected filesystem</source> <source>Sftp error: Write-protected filesystem</source>
<translation>Sftp error: Write-protected filesystem</translation> <translation type="vanished">Sftp error: Write-protected filesystem</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="44"/>
<source>Sftp error: No media was in remote drive</source> <source>Sftp error: No media was in remote drive</source>
<translation>Sftp error: No media was in remote drive</translation> <translation type="vanished">Sftp error: No media was in remote drive</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="62"/> <location filename="../core/errorstrings.cpp" line="50"/>
<source>The config does not contain any containers and credentials for connecting to the server</source> <source>The config does not contain any containers and credentials for connecting to the server</source>
<translation type="unfinished">Конфиг не содержит контейнеров и учетных данных для подключения к серверу</translation> <translation type="unfinished">Конфиг не содержит контейнеров и учетных данных для подключения к серверу</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="68"/> <location filename="../core/errorstrings.cpp" line="56"/>
<source>Error when retrieving configuration from API</source> <source>Error when retrieving configuration from API</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="69"/> <location filename="../core/errorstrings.cpp" line="57"/>
<source>This config has already been added to the application</source> <source>This config has already been added to the application</source>
<translation type="unfinished"></translation> <translation>Этот конфиг уже был добавлен в приложение</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="76"/> <location filename="../core/errorstrings.cpp" line="72"/>
<source>ErrorCode: %1. </source> <source>ErrorCode: %1. </source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@@ -2730,62 +2686,92 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation type="vanished">Failed to save config to disk</translation> <translation type="vanished">Failed to save config to disk</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="47"/> <location filename="../core/errorstrings.cpp" line="35"/>
<source>OpenVPN config missing</source> <source>OpenVPN config missing</source>
<translation>OpenVPN config missing</translation> <translation>OpenVPN config missing</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="48"/> <location filename="../core/errorstrings.cpp" line="36"/>
<source>OpenVPN management server error</source> <source>OpenVPN management server error</source>
<translation>OpenVPN management server error</translation> <translation>OpenVPN management server error</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="51"/> <location filename="../core/errorstrings.cpp" line="39"/>
<source>OpenVPN executable missing</source> <source>OpenVPN executable missing</source>
<translation>OpenVPN executable missing</translation> <translation>OpenVPN executable missing</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="52"/> <location filename="../core/errorstrings.cpp" line="40"/>
<source>ShadowSocks (ss-local) executable missing</source> <source>ShadowSocks (ss-local) executable missing</source>
<translation>ShadowSocks (ss-local) executable missing</translation> <translation>ShadowSocks (ss-local) executable missing</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="53"/> <location filename="../core/errorstrings.cpp" line="41"/>
<source>Cloak (ck-client) executable missing</source> <source>Cloak (ck-client) executable missing</source>
<translation>Cloak (ck-client) executable missing</translation> <translation>Cloak (ck-client) executable missing</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="54"/> <location filename="../core/errorstrings.cpp" line="42"/>
<source>Amnezia helper service error</source> <source>Amnezia helper service error</source>
<translation>Amnezia helper service error</translation> <translation>Amnezia helper service error</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="55"/> <location filename="../core/errorstrings.cpp" line="43"/>
<source>OpenSSL failed</source> <source>OpenSSL failed</source>
<translation>OpenSSL failed</translation> <translation>OpenSSL failed</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="58"/> <location filename="../core/errorstrings.cpp" line="46"/>
<source>Can&apos;t connect: another VPN connection is active</source> <source>Can&apos;t connect: another VPN connection is active</source>
<translation>Can&apos;t connect: another VPN connection is active</translation> <translation>Can&apos;t connect: another VPN connection is active</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="59"/> <location filename="../core/errorstrings.cpp" line="47"/>
<source>Can&apos;t setup OpenVPN TAP network adapter</source> <source>Can&apos;t setup OpenVPN TAP network adapter</source>
<translation>Can&apos;t setup OpenVPN TAP network adapter</translation> <translation>Can&apos;t setup OpenVPN TAP network adapter</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="60"/> <location filename="../core/errorstrings.cpp" line="48"/>
<source>VPN pool error: no available addresses</source> <source>VPN pool error: no available addresses</source>
<translation>VPN pool error: no available addresses</translation> <translation>VPN pool error: no available addresses</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="65"/> <location filename="../core/errorstrings.cpp" line="53"/>
<source>VPN connection error</source> <source>VPN connection error</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="73"/> <location filename="../core/errorstrings.cpp" line="60"/>
<source>QFile error: The file could not be opened</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="61"/>
<source>QFile error: An error occurred when reading from the file</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="62"/>
<source>QFile error: The file could not be accessed</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="63"/>
<source>QFile error: An unspecified error occurred</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="64"/>
<source>QFile error: A fatal error occurred</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="65"/>
<source>QFile error: The operation was aborted</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="69"/>
<source>Internal error</source> <source>Internal error</source>
<translation>Internal error</translation> <translation>Internal error</translation>
</message> </message>
@@ -3122,8 +3108,8 @@ This means that AmneziaWG keeps the fast performance of the original while addin
<translation>Server #1</translation> <translation>Server #1</translation>
</message> </message>
<message> <message>
<location filename="../settings.cpp" line="206"/> <location filename="../settings.cpp" line="207"/>
<location filename="../settings.cpp" line="213"/> <location filename="../settings.cpp" line="214"/>
<source>Server</source> <source>Server</source>
<translation>Server</translation> <translation>Server</translation>
</message> </message>
@@ -3131,17 +3117,17 @@ This means that AmneziaWG keeps the fast performance of the original while addin
<context> <context>
<name>SettingsController</name> <name>SettingsController</name>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="155"/> <location filename="../ui/controllers/settingsController.cpp" line="140"/>
<source>All settings have been reset to default values</source> <source>All settings have been reset to default values</source>
<translation>Все настройки были сброшены к значению &quot;По умолчанию&quot;</translation> <translation>Все настройки были сброшены к значению &quot;По умолчанию&quot;</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="161"/> <location filename="../ui/controllers/settingsController.cpp" line="150"/>
<source>Cached profiles cleared</source> <source>Cached profiles cleared</source>
<translation>Кэш профиля очищен</translation> <translation>Кэш профиля очищен</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="139"/> <location filename="../ui/controllers/settingsController.cpp" line="123"/>
<source>Backup file is corrupted</source> <source>Backup file is corrupted</source>
<translation>Backup файл поврежден</translation> <translation>Backup файл поврежден</translation>
</message> </message>
@@ -3155,33 +3141,33 @@ This means that AmneziaWG keeps the fast performance of the original while addin
<translation>Сохранить config AmneziaVPN</translation> <translation>Сохранить config AmneziaVPN</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="74"/> <location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="85"/>
<source>Share</source> <source>Share</source>
<translation>Поделиться</translation> <translation>Поделиться</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="108"/> <location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="121"/>
<source>Copy</source> <source>Copy</source>
<translation>Скопировать</translation> <translation>Скопировать</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="167"/> <location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="188"/>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="177"/> <location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="198"/>
<source>Copied</source> <source>Copied</source>
<translation>Скопировано</translation> <translation>Скопировано</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="126"/> <location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="141"/>
<source>Copy config string</source> <source>Copy config string</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="141"/> <location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="160"/>
<source>Show connection settings</source> <source>Show connection settings</source>
<translation type="unfinished">Показать настройки подключения</translation> <translation type="unfinished">Показать настройки подключения</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="305"/> <location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="328"/>
<source>To read the QR code in the Amnezia app, select &quot;Add server&quot; &quot;I have data to connect&quot; &quot;QR code, key or settings file&quot;</source> <source>To read the QR code in the Amnezia app, select &quot;Add server&quot; &quot;I have data to connect&quot; &quot;QR code, key or settings file&quot;</source>
<translation>Для считывания QR-кода в приложении Amnezia выберите &quot;Добавить сервер&quot; &quot;У меня есть данные для подключения&quot; &quot;QR-код, ключ или файл настроек&quot;</translation> <translation>Для считывания QR-кода в приложении Amnezia выберите &quot;Добавить сервер&quot; &quot;У меня есть данные для подключения&quot; &quot;QR-код, ключ или файл настроек&quot;</translation>
</message> </message>
@@ -3273,7 +3259,7 @@ This means that AmneziaWG keeps the fast performance of the original while addin
<context> <context>
<name>VpnConnection</name> <name>VpnConnection</name>
<message> <message>
<location filename="../vpnconnection.cpp" line="457"/> <location filename="../vpnconnection.cpp" line="458"/>
<source>Mbps</source> <source>Mbps</source>
<translation>Mbps</translation> <translation>Mbps</translation>
</message> </message>
@@ -3377,12 +3363,12 @@ This means that AmneziaWG keeps the fast performance of the original while addin
<context> <context>
<name>main2</name> <name>main2</name>
<message> <message>
<location filename="../ui/qml/main2.qml" line="174"/> <location filename="../ui/qml/main2.qml" line="179"/>
<source>Private key passphrase</source> <source>Private key passphrase</source>
<translation>Кодовая фраза для закрытого ключа</translation> <translation>Кодовая фраза для закрытого ключа</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/main2.qml" line="197"/> <location filename="../ui/qml/main2.qml" line="202"/>
<source>Save</source> <source>Save</source>
<translation>Сохранить</translation> <translation>Сохранить</translation>
</message> </message>
+140 -155
View File
@@ -20,45 +20,45 @@
<name>ConnectionController</name> <name>ConnectionController</name>
<message> <message>
<location filename="../ui/controllers/connectionController.h" line="62"/> <location filename="../ui/controllers/connectionController.h" line="62"/>
<location filename="../ui/controllers/connectionController.cpp" line="83"/> <location filename="../ui/controllers/connectionController.cpp" line="84"/>
<location filename="../ui/controllers/connectionController.cpp" line="97"/> <location filename="../ui/controllers/connectionController.cpp" line="98"/>
<location filename="../ui/controllers/connectionController.cpp" line="103"/> <location filename="../ui/controllers/connectionController.cpp" line="104"/>
<source>Connect</source> <source>Connect</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/connectionController.cpp" line="40"/> <location filename="../ui/controllers/connectionController.cpp" line="41"/>
<source>VPN Protocols is not installed. <source>VPN Protocols is not installed.
Please install VPN container at first</source> Please install VPN container at first</source>
<translation>VPN协议</translation> <translation>VPN协议</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/connectionController.cpp" line="64"/> <location filename="../ui/controllers/connectionController.cpp" line="65"/>
<source>Connection...</source> <source>Connection...</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/connectionController.cpp" line="69"/> <location filename="../ui/controllers/connectionController.cpp" line="70"/>
<source>Connected</source> <source>Connected</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/connectionController.cpp" line="78"/> <location filename="../ui/controllers/connectionController.cpp" line="79"/>
<source>Reconnection...</source> <source>Reconnection...</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/connectionController.cpp" line="88"/> <location filename="../ui/controllers/connectionController.cpp" line="89"/>
<source>Disconnection...</source> <source>Disconnection...</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/connectionController.cpp" line="114"/> <location filename="../ui/controllers/connectionController.cpp" line="115"/>
<source>Settings updated successfully, Reconnnection...</source> <source>Settings updated successfully, Reconnnection...</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/connectionController.cpp" line="117"/> <location filename="../ui/controllers/connectionController.cpp" line="118"/>
<source>Settings updated successfully</source> <source>Settings updated successfully</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@@ -151,7 +151,7 @@
<message> <message>
<location filename="../ui/qml/Components/HomeSplitTunnelingDrawer.qml" line="33"/> <location filename="../ui/qml/Components/HomeSplitTunnelingDrawer.qml" line="33"/>
<source>Allows you to connect to some sites or applications through a VPN connection and bypass others</source> <source>Allows you to connect to some sites or applications through a VPN connection and bypass others</source>
<translation type="unfinished"></translation> <translation> VPN </translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Components/HomeSplitTunnelingDrawer.qml" line="42"/> <location filename="../ui/qml/Components/HomeSplitTunnelingDrawer.qml" line="42"/>
@@ -345,25 +345,25 @@ Already installed containers were found on the server. All installed containers
<message> <message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="61"/> <location filename="../ui/qml/Pages2/PageHome.qml" line="61"/>
<source>Split tunneling enabled</source> <source>Split tunneling enabled</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="61"/> <location filename="../ui/qml/Pages2/PageHome.qml" line="61"/>
<source>Split tunneling disabled</source> <source>Split tunneling disabled</source>
<translation type="unfinished"></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="224"/> <location filename="../ui/qml/Pages2/PageHome.qml" line="223"/>
<source>VPN protocol</source> <source>VPN protocol</source>
<translation>VPN协议</translation> <translation>VPN协议</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="272"/> <location filename="../ui/qml/Pages2/PageHome.qml" line="271"/>
<source>Servers</source> <source>Servers</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="360"/> <location filename="../ui/qml/Pages2/PageHome.qml" line="359"/>
<source>Unable change server while there is an active connection</source> <source>Unable change server while there is an active connection</source>
<translation></translation> <translation></translation>
</message> </message>
@@ -380,51 +380,6 @@ Already installed containers were found on the server. All installed containers
<source>Port</source> <source>Port</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="114"/>
<source>Junk packet count</source>
<translation></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="139"/>
<source>Junk packet minimum size</source>
<translation></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="159"/>
<source>Junk packet maximum size</source>
<translation></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="179"/>
<source>Init packet junk size</source>
<translation></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="199"/>
<source>Response packet junk size</source>
<translation></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="219"/>
<source>Init packet magic header</source>
<translation></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="239"/>
<source>Response packet magic header</source>
<translation></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="259"/>
<source>Transport packet magic header</source>
<translation></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="279"/>
<source>Underload packet magic header</source>
<translation></translation>
</message>
<message> <message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="304"/> <location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="304"/>
<source>Remove AmneziaWG</source> <source>Remove AmneziaWG</source>
@@ -1538,7 +1493,7 @@ And if you don&apos;t like the app, all the more support it - the donation will
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="203"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="203"/>
<source>Do you want to clear server from Amnezia software?</source> <source>Do you want to clear server Amnezia-installed services?</source>
<translation>Amnezia软件吗</translation> <translation>Amnezia软件吗</translation>
</message> </message>
<message> <message>
@@ -1586,7 +1541,7 @@ And if you don&apos;t like the app, all the more support it - the donation will
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="169"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="169"/>
<source>Remove server from application</source> <source>Remove this server from the app</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
@@ -1605,7 +1560,7 @@ And if you don&apos;t like the app, all the more support it - the donation will
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<source>Remove server?</source> <source>Remove server from application?</source>
<translation type="vanished">?</translation> <translation type="vanished">?</translation>
</message> </message>
<message> <message>
@@ -1615,11 +1570,11 @@ And if you don&apos;t like the app, all the more support it - the donation will
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="199"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="199"/>
<source>Clear server from Amnezia software</source> <source>Clear server Amnezia-installed services</source>
<translation>Amnezia中服务器信息</translation> <translation>Amnezia中服务器信息</translation>
</message> </message>
<message> <message>
<source>Clear server from Amnezia software?</source> <source>Clear server Amnezia-installed services?</source>
<translation type="vanished">Amnezia中服务器信息</translation> <translation type="vanished">Amnezia中服务器信息</translation>
</message> </message>
<message> <message>
@@ -1842,40 +1797,44 @@ And if you don&apos;t like the app, all the more support it - the donation will
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="51"/>
<source>Do not use connection code from public sources. It may have been created to intercept your data. <source>Do not use connection code from public sources. It may have been created to intercept your data.
It&apos;s okay as long as it&apos;s from someone you trust.</source> It&apos;s okay as long as it&apos;s from someone you trust.</source>
<translation>使 <translation type="vanished">使
</translation> </translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="61"/> <location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="51"/>
<source>Do not use connection codes from untrusted sources, as they may be created to intercept your data.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="60"/>
<source>What do you have?</source> <source>What do you have?</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="68"/> <location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="67"/>
<source>File with connection settings or backup</source> <source>File with connection settings or backup</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="68"/> <location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="67"/>
<source>File with connection settings</source> <source>File with connection settings</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="75"/> <location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="74"/>
<source>Open config file</source> <source>Open config file</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="95"/> <location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="94"/>
<source>QR-code</source> <source>QR-code</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="114"/> <location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="113"/>
<source>Key as text</source> <source>Key as text</source>
<translation></translation> <translation></translation>
</message> </message>
@@ -1975,7 +1934,7 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="198"/> <location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="197"/>
<source>Set up later</source> <source>Set up later</source>
<translation></translation> <translation></translation>
</message> </message>
@@ -2084,32 +2043,32 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<context> <context>
<name>PageSetupWizardStart</name> <name>PageSetupWizardStart</name>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="54"/> <location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="64"/>
<source>Settings restored from backup file</source> <source>Settings restored from backup file</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="106"/> <location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="116"/>
<source>Free service for creating a personal VPN on your server.</source> <source>Free service for creating a personal VPN on your server.</source>
<translation>VPN服务</translation> <translation>VPN服务</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="107"/> <location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="117"/>
<source> Helps you access blocked content without revealing your privacy, even to VPN providers.</source> <source> Helps you access blocked content without revealing your privacy, even to VPN providers.</source>
<translation>访使VPN提供商也无法获取</translation> <translation>访使VPN提供商也无法获取</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="116"/> <location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="126"/>
<source>I have the data to connect</source> <source>I have the data to connect</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="136"/> <location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="146"/>
<source>I have nothing</source> <source>I have nothing</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="139"/> <location filename="../ui/qml/Pages2/PageSetupWizardStart.qml" line="149"/>
<source>https://amnezia.org/instructions/0_starter-guide</source> <source>https://amnezia.org/instructions/0_starter-guide</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@@ -2150,9 +2109,13 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="95"/>
<source>Do not use connection code from public sources. It could be created to intercept your data.</source> <source>Do not use connection code from public sources. It could be created to intercept your data.</source>
<translation>使</translation> <translation type="vanished">使</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="95"/>
<source>Do not use connection codes from untrusted sources, as they may be created to intercept your data.</source>
<translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="110"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="110"/>
@@ -2244,7 +2207,7 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="243"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="243"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="504"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="513"/>
<source>Users</source> <source>Users</source>
<translation></translation> <translation></translation>
</message> </message>
@@ -2254,52 +2217,52 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation> VPN 访</translation> <translation> VPN 访</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="520"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="529"/>
<source>Search</source> <source>Search</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="604"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="613"/>
<source>Creation date: </source> <source>Creation date: </source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="618"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="627"/>
<source>Rename</source> <source>Rename</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="650"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="659"/>
<source>Client name</source> <source>Client name</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="663"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="672"/>
<source>Save</source> <source>Save</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="695"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="704"/>
<source>Revoke</source> <source>Revoke</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="698"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="707"/>
<source>Revoke the config for a user - %1?</source> <source>Revoke the config for a user - %1?</source>
<translation>- %1?</translation> <translation>- %1?</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="699"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="708"/>
<source>The user will no longer be able to connect to your server.</source> <source>The user will no longer be able to connect to your server.</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="700"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="709"/>
<source>Continue</source> <source>Continue</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="701"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="710"/>
<source>Cancel</source> <source>Cancel</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@@ -2364,8 +2327,8 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation type="obsolete"></translation> <translation type="obsolete"></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="348"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="356"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="349"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="357"/>
<source>Protocol</source> <source>Protocol</source>
<translation></translation> <translation></translation>
</message> </message>
@@ -2385,14 +2348,14 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="449"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="457"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="450"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="458"/>
<source>Connection format</source> <source>Connection format</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="198"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="198"/>
<location filename="../ui/qml/Pages2/PageShare.qml" line="487"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="496"/>
<source>Share</source> <source>Share</source>
<translation></translation> <translation></translation>
</message> </message>
@@ -2774,86 +2737,108 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="32"/> <location filename="../core/errorstrings.cpp" line="32"/>
<source>Scp error: Generic failure</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Sftp error: End-of-file encountered</source> <source>Sftp error: End-of-file encountered</source>
<translation>Sftp错误: End-of-file encountered</translation> <translation type="vanished">Sftp错误: End-of-file encountered</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="33"/>
<source>Sftp error: File does not exist</source> <source>Sftp error: File does not exist</source>
<translation>Sftp错误: 文件不存在</translation> <translation type="vanished">Sftp错误: 文件不存在</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="34"/>
<source>Sftp error: Permission denied</source> <source>Sftp error: Permission denied</source>
<translation>Sftp错误: 权限不足</translation> <translation type="vanished">Sftp错误: 权限不足</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="35"/>
<source>Sftp error: Generic failure</source> <source>Sftp error: Generic failure</source>
<translation>Sftp错误: 一般失败</translation> <translation type="vanished">Sftp错误: 一般失败</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="36"/>
<source>Sftp error: Garbage received from server</source> <source>Sftp error: Garbage received from server</source>
<translation>Sftp错误: 从服务器收到垃圾信息</translation> <translation type="vanished">Sftp错误: 从服务器收到垃圾信息</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="37"/>
<source>Sftp error: No connection has been set up</source> <source>Sftp error: No connection has been set up</source>
<translation>Sftp 错误: 未建立连接</translation> <translation type="vanished">Sftp 错误: 未建立连接</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="38"/>
<source>Sftp error: There was a connection, but we lost it</source> <source>Sftp error: There was a connection, but we lost it</source>
<translation>Sftp 错误: 已有连接丢失</translation> <translation type="vanished">Sftp 错误: 已有连接丢失</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="39"/>
<source>Sftp error: Operation not supported by libssh yet</source> <source>Sftp error: Operation not supported by libssh yet</source>
<translation>Sftp error: libssh不支持该操作</translation> <translation type="vanished">Sftp error: libssh不支持该操作</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="40"/>
<source>Sftp error: Invalid file handle</source> <source>Sftp error: Invalid file handle</source>
<translation>Sftp error: 无效的文件句柄</translation> <translation type="vanished">Sftp error: 无效的文件句柄</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="41"/>
<source>Sftp error: No such file or directory path exists</source> <source>Sftp error: No such file or directory path exists</source>
<translation>Sftp 错误: 文件夹或文件不存在</translation> <translation type="vanished">Sftp 错误: 文件夹或文件不存在</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="42"/>
<source>Sftp error: An attempt to create an already existing file or directory has been made</source> <source>Sftp error: An attempt to create an already existing file or directory has been made</source>
<translation>Sftp 错误: 文件或目录已存在</translation> <translation type="vanished">Sftp 错误: 文件或目录已存在</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="43"/>
<source>Sftp error: Write-protected filesystem</source> <source>Sftp error: Write-protected filesystem</source>
<translation>Sftp 错误: 文件系统写保护</translation> <translation type="vanished">Sftp 错误: 文件系统写保护</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="44"/>
<source>Sftp error: No media was in remote drive</source> <source>Sftp error: No media was in remote drive</source>
<translation>Sftp 错误: 远程驱动器中没有媒介</translation> <translation type="vanished">Sftp 错误: 远程驱动器中没有媒介</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="65"/> <location filename="../core/errorstrings.cpp" line="53"/>
<source>VPN connection error</source> <source>VPN connection error</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="68"/> <location filename="../core/errorstrings.cpp" line="56"/>
<source>Error when retrieving configuration from API</source> <source>Error when retrieving configuration from API</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="69"/> <location filename="../core/errorstrings.cpp" line="57"/>
<source>This config has already been added to the application</source> <source>This config has already been added to the application</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="76"/> <location filename="../core/errorstrings.cpp" line="60"/>
<source>QFile error: The file could not be opened</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="61"/>
<source>QFile error: An error occurred when reading from the file</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="62"/>
<source>QFile error: The file could not be accessed</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="63"/>
<source>QFile error: An unspecified error occurred</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="64"/>
<source>QFile error: A fatal error occurred</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="65"/>
<source>QFile error: The operation was aborted</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../core/errorstrings.cpp" line="72"/>
<source>ErrorCode: %1. </source> <source>ErrorCode: %1. </source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@@ -2862,57 +2847,57 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation type="vanished"></translation> <translation type="vanished"></translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="47"/> <location filename="../core/errorstrings.cpp" line="35"/>
<source>OpenVPN config missing</source> <source>OpenVPN config missing</source>
<translation>OpenVPN配置丢失</translation> <translation>OpenVPN配置丢失</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="48"/> <location filename="../core/errorstrings.cpp" line="36"/>
<source>OpenVPN management server error</source> <source>OpenVPN management server error</source>
<translation>OpenVPN </translation> <translation>OpenVPN </translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="51"/> <location filename="../core/errorstrings.cpp" line="39"/>
<source>OpenVPN executable missing</source> <source>OpenVPN executable missing</source>
<translation>OpenVPN </translation> <translation>OpenVPN </translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="52"/> <location filename="../core/errorstrings.cpp" line="40"/>
<source>ShadowSocks (ss-local) executable missing</source> <source>ShadowSocks (ss-local) executable missing</source>
<translation>ShadowSocks (ss-local) </translation> <translation>ShadowSocks (ss-local) </translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="53"/> <location filename="../core/errorstrings.cpp" line="41"/>
<source>Cloak (ck-client) executable missing</source> <source>Cloak (ck-client) executable missing</source>
<translation>Cloak (ck-client) </translation> <translation>Cloak (ck-client) </translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="54"/> <location filename="../core/errorstrings.cpp" line="42"/>
<source>Amnezia helper service error</source> <source>Amnezia helper service error</source>
<translation>Amnezia </translation> <translation>Amnezia </translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="55"/> <location filename="../core/errorstrings.cpp" line="43"/>
<source>OpenSSL failed</source> <source>OpenSSL failed</source>
<translation>OpenSSL错误</translation> <translation>OpenSSL错误</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="58"/> <location filename="../core/errorstrings.cpp" line="46"/>
<source>Can&apos;t connect: another VPN connection is active</source> <source>Can&apos;t connect: another VPN connection is active</source>
<translation>VPN连接处于活跃状态</translation> <translation>VPN连接处于活跃状态</translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="59"/> <location filename="../core/errorstrings.cpp" line="47"/>
<source>Can&apos;t setup OpenVPN TAP network adapter</source> <source>Can&apos;t setup OpenVPN TAP network adapter</source>
<translation> OpenVPN TAP </translation> <translation> OpenVPN TAP </translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="60"/> <location filename="../core/errorstrings.cpp" line="48"/>
<source>VPN pool error: no available addresses</source> <source>VPN pool error: no available addresses</source>
<translation>VPN </translation> <translation>VPN </translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="62"/> <location filename="../core/errorstrings.cpp" line="50"/>
<source>The config does not contain any containers and credentials for connecting to the server</source> <source>The config does not contain any containers and credentials for connecting to the server</source>
<translation></translation> <translation></translation>
</message> </message>
@@ -2921,7 +2906,7 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation type="vanished"></translation> <translation type="vanished"></translation>
</message> </message>
<message> <message>
<location filename="../core/errorstrings.cpp" line="73"/> <location filename="../core/errorstrings.cpp" line="69"/>
<source>Internal error</source> <source>Internal error</source>
<translation></translation> <translation></translation>
</message> </message>
@@ -3290,8 +3275,8 @@ While it offers a blend of security, stability, and speed, it&apos;s essential t
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../settings.cpp" line="206"/> <location filename="../settings.cpp" line="207"/>
<location filename="../settings.cpp" line="213"/> <location filename="../settings.cpp" line="214"/>
<source>Server</source> <source>Server</source>
<translation></translation> <translation></translation>
</message> </message>
@@ -3299,17 +3284,17 @@ While it offers a blend of security, stability, and speed, it&apos;s essential t
<context> <context>
<name>SettingsController</name> <name>SettingsController</name>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="139"/> <location filename="../ui/controllers/settingsController.cpp" line="123"/>
<source>Backup file is corrupted</source> <source>Backup file is corrupted</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="155"/> <location filename="../ui/controllers/settingsController.cpp" line="140"/>
<source>All settings have been reset to default values</source> <source>All settings have been reset to default values</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="161"/> <location filename="../ui/controllers/settingsController.cpp" line="150"/>
<source>Cached profiles cleared</source> <source>Cached profiles cleared</source>
<translation></translation> <translation></translation>
</message> </message>
@@ -3323,28 +3308,28 @@ While it offers a blend of security, stability, and speed, it&apos;s essential t
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="74"/> <location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="85"/>
<source>Share</source> <source>Share</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="108"/> <location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="121"/>
<source>Copy</source> <source>Copy</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="167"/> <location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="188"/>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="177"/> <location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="198"/>
<source>Copied</source> <source>Copied</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="126"/> <location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="141"/>
<source>Copy config string</source> <source>Copy config string</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="141"/> <location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="160"/>
<source>Show connection settings</source> <source>Show connection settings</source>
<translation></translation> <translation></translation>
</message> </message>
@@ -3353,7 +3338,7 @@ While it offers a blend of security, stability, and speed, it&apos;s essential t
<translation type="obsolete"></translation> <translation type="obsolete"></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="305"/> <location filename="../ui/qml/Components/ShareConnectionDrawer.qml" line="328"/>
<source>To read the QR code in the Amnezia app, select &quot;Add server&quot; &quot;I have data to connect&quot; &quot;QR code, key or settings file&quot;</source> <source>To read the QR code in the Amnezia app, select &quot;Add server&quot; &quot;I have data to connect&quot; &quot;QR code, key or settings file&quot;</source>
<translation> Amnezia+</translation> <translation> Amnezia+</translation>
</message> </message>
@@ -3445,7 +3430,7 @@ While it offers a blend of security, stability, and speed, it&apos;s essential t
<context> <context>
<name>VpnConnection</name> <name>VpnConnection</name>
<message> <message>
<location filename="../vpnconnection.cpp" line="457"/> <location filename="../vpnconnection.cpp" line="458"/>
<source>Mbps</source> <source>Mbps</source>
<translation></translation> <translation></translation>
</message> </message>
@@ -3549,12 +3534,12 @@ While it offers a blend of security, stability, and speed, it&apos;s essential t
<context> <context>
<name>main2</name> <name>main2</name>
<message> <message>
<location filename="../ui/qml/main2.qml" line="174"/> <location filename="../ui/qml/main2.qml" line="179"/>
<source>Private key passphrase</source> <source>Private key passphrase</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/main2.qml" line="197"/> <location filename="../ui/qml/main2.qml" line="202"/>
<source>Save</source> <source>Save</source>
<translation></translation> <translation></translation>
</message> </message>
+4 -1
View File
@@ -90,7 +90,7 @@ void ApiController::updateServerConfigFromApi()
request.setRawHeader("Authorization", request.setRawHeader("Authorization",
"Api-Key " + serverConfig.value(configKey::accessToken).toString().toUtf8()); "Api-Key " + serverConfig.value(configKey::accessToken).toString().toUtf8());
QString endpoint = serverConfig.value(configKey::apiEdnpoint).toString(); QString endpoint = serverConfig.value(configKey::apiEdnpoint).toString();
request.setUrl(endpoint.replace("https", "http")); // todo remove request.setUrl(endpoint);
QString protocol = serverConfig.value(configKey::protocol).toString(); QString protocol = serverConfig.value(configKey::protocol).toString();
@@ -138,7 +138,10 @@ void ApiController::updateServerConfigFromApi()
serverConfig.insert(config_key::defaultContainer, defaultContainer); serverConfig.insert(config_key::defaultContainer, defaultContainer);
m_serversModel->editServer(serverConfig, m_serversModel->getDefaultServerIndex()); m_serversModel->editServer(serverConfig, m_serversModel->getDefaultServerIndex());
} else { } else {
QString err = reply->errorString();
qDebug() << QString::fromUtf8(reply->readAll());
qDebug() << reply->error(); qDebug() << reply->error();
qDebug() << err;
qDebug() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); qDebug() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
emit errorOccurred(errorString(ApiConfigDownloadError)); emit errorOccurred(errorString(ApiConfigDownloadError));
m_isConfigUpdateStarted = false; m_isConfigUpdateStarted = false;
@@ -25,12 +25,13 @@ ConnectionController::ConnectionController(const QSharedPointer<ServersModel> &s
void ConnectionController::openConnection() void ConnectionController::openConnection()
{ {
if (!m_containersModel->isAnyContainerInstalled()) { int serverIndex = m_serversModel->getDefaultServerIndex();
if (!m_serversModel->data(serverIndex, ServersModel::Roles::HasInstalledContainers).toBool()) {
emit noInstalledContainers(); emit noInstalledContainers();
return; return;
} }
int serverIndex = m_serversModel->getDefaultServerIndex();
ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex); ServerCredentials credentials = m_serversModel->getServerCredentials(serverIndex);
DockerContainer container = qvariant_cast<DockerContainer>(m_serversModel->data(serverIndex, ServersModel::Roles::DefaultContainerRole)); DockerContainer container = qvariant_cast<DockerContainer>(m_serversModel->data(serverIndex, ServersModel::Roles::DefaultContainerRole));
@@ -418,4 +418,6 @@ void ExportController::clearPreviousConfig()
m_config.clear(); m_config.clear();
m_nativeConfigString.clear(); m_nativeConfigString.clear();
m_qrCodes.clear(); m_qrCodes.clear();
emit exportConfigChanged();
} }
+77 -46
View File
@@ -18,7 +18,9 @@ namespace
enum class ConfigTypes { enum class ConfigTypes {
Amnezia, Amnezia,
OpenVpn, OpenVpn,
WireGuard WireGuard,
Backup,
Invalid
}; };
ConfigTypes checkConfigFormat(const QString &config) ConfigTypes checkConfigFormat(const QString &config)
@@ -32,15 +34,23 @@ namespace
const QString wireguardConfigPatternSectionInterface = "[Interface]"; const QString wireguardConfigPatternSectionInterface = "[Interface]";
const QString wireguardConfigPatternSectionPeer = "[Peer]"; const QString wireguardConfigPatternSectionPeer = "[Peer]";
if (config.contains(openVpnConfigPatternCli) const QString amneziaConfigPattern = "containers";
&& (config.contains(openVpnConfigPatternProto1) || config.contains(openVpnConfigPatternProto2)) const QString amneziaFreeConfigPattern = "api_key";
&& (config.contains(openVpnConfigPatternDriver1) || config.contains(openVpnConfigPatternDriver2))) { const QString backupPattern = "Servers/serversList";
if (config.contains(backupPattern)) {
return ConfigTypes::Backup;
} else if (config.contains(amneziaConfigPattern) || config.contains(amneziaFreeConfigPattern)) {
return ConfigTypes::Amnezia;
} else if (config.contains(openVpnConfigPatternCli)
&& (config.contains(openVpnConfigPatternProto1) || config.contains(openVpnConfigPatternProto2))
&& (config.contains(openVpnConfigPatternDriver1) || config.contains(openVpnConfigPatternDriver2))) {
return ConfigTypes::OpenVpn; return ConfigTypes::OpenVpn;
} else if (config.contains(wireguardConfigPatternSectionInterface) } else if (config.contains(wireguardConfigPatternSectionInterface)
&& config.contains(wireguardConfigPatternSectionPeer)) { && config.contains(wireguardConfigPatternSectionPeer)) {
return ConfigTypes::WireGuard; return ConfigTypes::WireGuard;
} }
return ConfigTypes::Amnezia; return ConfigTypes::Invalid;
} }
#if defined Q_OS_ANDROID #if defined Q_OS_ANDROID
@@ -58,34 +68,65 @@ ImportController::ImportController(const QSharedPointer<ServersModel> &serversMo
#endif #endif
} }
void ImportController::extractConfigFromFile(const QString &fileName) bool ImportController::extractConfigFromFile(const QString &fileName)
{ {
QFile file(fileName); QFile file(fileName);
if (file.open(QIODevice::ReadOnly)) { if (file.open(QIODevice::ReadOnly)) {
QString data = file.readAll(); QString data = file.readAll();
extractConfigFromData(data);
m_configFileName = QFileInfo(file.fileName()).fileName(); m_configFileName = QFileInfo(file.fileName()).fileName();
return extractConfigFromData(data);
} }
emit importErrorOccurred(tr("Unable to open file"));
return false;
} }
void ImportController::extractConfigFromData(QString data) bool ImportController::extractConfigFromData(QString data)
{ {
auto configFormat = checkConfigFormat(data); QString config = data;
if (configFormat == ConfigTypes::OpenVpn) { auto configFormat = checkConfigFormat(config);
m_config = extractOpenVpnConfig(data); if (configFormat == ConfigTypes::Invalid) {
} else if (configFormat == ConfigTypes::WireGuard) { data.replace("vpn://", "");
m_config = extractWireGuardConfig(data); QByteArray ba =
} else { QByteArray::fromBase64(data.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
m_config = extractAmneziaConfig(data); QByteArray ba_uncompressed = qUncompress(ba);
} if (!ba_uncompressed.isEmpty()) {
} ba = ba_uncompressed;
}
void ImportController::extractConfigFromCode(QString code) config = ba;
{ configFormat = checkConfigFormat(config);
m_config = extractAmneziaConfig(code); }
m_configFileName = "";
switch (configFormat) {
case ConfigTypes::OpenVpn: {
m_config = extractOpenVpnConfig(config);
return m_config.empty() ? false : true;
}
case ConfigTypes::WireGuard: {
m_config = extractWireGuardConfig(config);
return m_config.empty() ? false : true;
}
case ConfigTypes::Amnezia: {
m_config = QJsonDocument::fromJson(config.toUtf8()).object();
return m_config.empty() ? false : true;
}
case ConfigTypes::Backup: {
if (!m_serversModel->getServersCount()) {
emit restoreAppConfig(config.toUtf8());
} else {
emit importErrorOccurred(tr("Invalid configuration file"));
}
break;
}
case ConfigTypes::Invalid: {
emit importErrorOccurred(tr("Invalid configuration file"));
break;
}
}
return false;
} }
bool ImportController::extractConfigFromQr(const QByteArray &data) bool ImportController::extractConfigFromQr(const QByteArray &data)
@@ -139,28 +180,13 @@ void ImportController::importConfig()
} else { } else {
qDebug() << "Failed to import profile"; qDebug() << "Failed to import profile";
qDebug().noquote() << QJsonDocument(m_config).toJson(); qDebug().noquote() << QJsonDocument(m_config).toJson();
emit importErrorOccurred(errorString(ErrorCode::ImportInvalidConfigError)); emit importErrorOccurred(errorString(ErrorCode::ImportInvalidConfigError), false);
} }
m_config = {}; m_config = {};
m_configFileName.clear(); m_configFileName.clear();
} }
QJsonObject ImportController::extractAmneziaConfig(QString &data)
{
data.replace("vpn://", "");
QByteArray ba = QByteArray::fromBase64(data.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
QByteArray ba_uncompressed = qUncompress(ba);
if (!ba_uncompressed.isEmpty()) {
ba = ba_uncompressed;
}
QJsonObject config = QJsonDocument::fromJson(ba).object();
return config;
}
QJsonObject ImportController::extractOpenVpnConfig(const QString &data) QJsonObject ImportController::extractOpenVpnConfig(const QString &data)
{ {
QJsonObject openVpnConfig; QJsonObject openVpnConfig;
@@ -229,8 +255,8 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data)
if (hostNameAndPortMatch.hasCaptured(1)) { if (hostNameAndPortMatch.hasCaptured(1)) {
hostName = hostNameAndPortMatch.captured(1); hostName = hostNameAndPortMatch.captured(1);
} else { } else {
qDebug() << "Failed to import profile"; qDebug() << "Key parameter 'Endpoint' is missing";
emit importErrorOccurred(errorString(ErrorCode::ImportInvalidConfigError)); emit importErrorOccurred(errorString(ErrorCode::ImportInvalidConfigError), false);
} }
if (hostNameAndPortMatch.hasCaptured(2)) { if (hostNameAndPortMatch.hasCaptured(2)) {
@@ -242,10 +268,11 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data)
lastConfig[config_key::hostName] = hostName; lastConfig[config_key::hostName] = hostName;
lastConfig[config_key::port] = port.toInt(); lastConfig[config_key::port] = port.toInt();
// if (!configMap.value("PrivateKey").isEmpty() && !configMap.value("Address").isEmpty() if (!configMap.value("PrivateKey").isEmpty() && !configMap.value("Address").isEmpty()
// && !configMap.value("PresharedKey").isEmpty() && !configMap.value("PublicKey").isEmpty()) { && !configMap.value("PublicKey").isEmpty()) {
lastConfig[config_key::client_priv_key] = configMap.value("PrivateKey"); lastConfig[config_key::client_priv_key] = configMap.value("PrivateKey");
lastConfig[config_key::client_ip] = configMap.value("Address"); lastConfig[config_key::client_ip] = configMap.value("Address");
if (!configMap.value("PresharedKey").isEmpty()) { if (!configMap.value("PresharedKey").isEmpty()) {
lastConfig[config_key::psk_key] = configMap.value("PresharedKey"); lastConfig[config_key::psk_key] = configMap.value("PresharedKey");
} else if (!configMap.value("PreSharedKey").isEmpty()) { } else if (!configMap.value("PreSharedKey").isEmpty()) {
@@ -253,11 +280,15 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data)
} }
lastConfig[config_key::server_pub_key] = configMap.value("PublicKey"); lastConfig[config_key::server_pub_key] = configMap.value("PublicKey");
// } else { } else {
// qDebug() << "Failed to import profile"; qDebug() << "One of the key parameters is missing (PrivateKey, Address, PublicKey)";
// emit importErrorOccurred(errorString(ErrorCode::ImportInvalidConfigError)); emit importErrorOccurred(errorString(ErrorCode::ImportInvalidConfigError));
// return QJsonObject(); return QJsonObject();
// } }
if (!configMap.value("MTU").isEmpty()) {
lastConfig[config_key::mtu] = configMap.value("MTU");
}
QJsonArray allowedIpsJsonArray = QJsonArray::fromStringList(configMap.value("AllowedIPs").split(",")); QJsonArray allowedIpsJsonArray = QJsonArray::fromStringList(configMap.value("AllowedIPs").split(","));
+6 -5
View File
@@ -18,9 +18,8 @@ public:
public slots: public slots:
void importConfig(); void importConfig();
void extractConfigFromFile(const QString &fileName); bool extractConfigFromFile(const QString &fileName);
void extractConfigFromData(QString data); bool extractConfigFromData(QString data);
void extractConfigFromCode(QString code);
bool extractConfigFromQr(const QByteArray &data); bool extractConfigFromQr(const QByteArray &data);
QString getConfig(); QString getConfig();
QString getConfigFileName(); QString getConfigFileName();
@@ -39,12 +38,14 @@ public slots:
signals: signals:
void importFinished(); void importFinished();
void importErrorOccurred(const QString &errorMessage, bool goToPageHome = false); void importErrorOccurred(const QString &errorMessage, bool goToPageHome);
void importErrorOccurred(const QString &errorMessage);
void qrDecodingFinished(); void qrDecodingFinished();
void restoreAppConfig(const QByteArray &data);
private: private:
QJsonObject extractAmneziaConfig(QString &data);
QJsonObject extractOpenVpnConfig(const QString &data); QJsonObject extractOpenVpnConfig(const QString &data);
QJsonObject extractWireGuardConfig(const QString &data); QJsonObject extractWireGuardConfig(const QString &data);
+36 -4
View File
@@ -10,6 +10,8 @@
#include "core/errorstrings.h" #include "core/errorstrings.h"
#include "core/controllers/serverController.h" #include "core/controllers/serverController.h"
#include "utilities.h" #include "utilities.h"
#include "ui/models/protocols/awgConfigModel.h"
#include "ui/models/protocols/wireguardConfigModel.h"
namespace namespace
{ {
@@ -273,12 +275,16 @@ void InstallController::updateContainer(QJsonObject config)
const DockerContainer container = ContainerProps::containerFromString(config.value(config_key::container).toString()); const DockerContainer container = ContainerProps::containerFromString(config.value(config_key::container).toString());
QJsonObject oldContainerConfig = m_containersModel->getContainerConfig(container); QJsonObject oldContainerConfig = m_containersModel->getContainerConfig(container);
ErrorCode errorCode = ErrorCode::NoError;
ServerController serverController(m_settings); if (isUpdateDockerContainerRequired(container, oldContainerConfig, config)) {
connect(&serverController, &ServerController::serverIsBusy, this, &InstallController::serverIsBusy); ServerController serverController(m_settings);
connect(this, &InstallController::cancelInstallation, &serverController, &ServerController::cancelInstallation); connect(&serverController, &ServerController::serverIsBusy, this, &InstallController::serverIsBusy);
connect(this, &InstallController::cancelInstallation, &serverController, &ServerController::cancelInstallation);
errorCode = serverController.updateContainer(serverCredentials, container, oldContainerConfig, config);
}
auto errorCode = serverController.updateContainer(serverCredentials, container, oldContainerConfig, config);
if (errorCode == ErrorCode::NoError) { if (errorCode == ErrorCode::NoError) {
m_serversModel->updateContainerConfig(container, config); m_serversModel->updateContainerConfig(container, config);
m_protocolModel->updateModel(config); m_protocolModel->updateModel(config);
@@ -514,3 +520,29 @@ void InstallController::addEmptyServer()
emit installServerFinished(tr("Server added successfully")); emit installServerFinished(tr("Server added successfully"));
} }
bool InstallController::isUpdateDockerContainerRequired(const DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig)
{
Proto mainProto = ContainerProps::defaultProtocol(container);
const QJsonObject &oldProtoConfig = oldConfig.value(ProtocolProps::protoToString(mainProto)).toObject();
const QJsonObject &newProtoConfig = newConfig.value(ProtocolProps::protoToString(mainProto)).toObject();
if (container == DockerContainer::Awg) {
const AwgConfig oldConfig(oldProtoConfig);
const AwgConfig newConfig(newProtoConfig);
if (!oldConfig.hasEqualServerSettings(newConfig)) {
return true;
}
} else if (container == DockerContainer::WireGuard) {
const WgConfig oldConfig(oldProtoConfig);
const WgConfig newConfig(newProtoConfig);
if (!oldConfig.hasEqualServerSettings(newConfig)) {
return true;
}
}
return false;
}
@@ -76,6 +76,8 @@ private:
void installContainer(DockerContainer container, QJsonObject &config); void installContainer(DockerContainer container, QJsonObject &config);
bool isServerAlreadyExists(); bool isServerAlreadyExists();
bool isUpdateDockerContainerRequired(const DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig);
QSharedPointer<ServersModel> m_serversModel; QSharedPointer<ServersModel> m_serversModel;
QSharedPointer<ContainersModel> m_containersModel; QSharedPointer<ContainersModel> m_containersModel;
QSharedPointer<ProtocolsModel> m_protocolModel; QSharedPointer<ProtocolsModel> m_protocolModel;
+23 -2
View File
@@ -77,7 +77,16 @@ void PageController::closeWindow()
void PageController::keyPressEvent(Qt::Key key) void PageController::keyPressEvent(Qt::Key key)
{ {
switch (key) { switch (key) {
case Qt::Key_Back: emit closePage(); case Qt::Key_Back:
case Qt::Key_Escape: {
if (m_drawerDepth) {
emit closeTopDrawer();
setDrawerDepth(getDrawerDepth() - 1);
} else {
emit escapePressed();
}
break;
}
default: return; default: return;
} }
} }
@@ -123,7 +132,7 @@ bool PageController::isTriggeredByConnectButton()
return m_isTriggeredByConnectButton; return m_isTriggeredByConnectButton;
} }
void PageController::setTriggeredBtConnectButton(bool trigger) void PageController::setTriggeredByConnectButton(bool trigger)
{ {
m_isTriggeredByConnectButton = trigger; m_isTriggeredByConnectButton = trigger;
} }
@@ -132,3 +141,15 @@ void PageController::closeApplication()
{ {
qApp->quit(); qApp->quit();
} }
void PageController::setDrawerDepth(const int depth)
{
if (depth >= 0) {
m_drawerDepth = depth;
}
}
int PageController::getDrawerDepth()
{
return m_drawerDepth;
}
+10 -2
View File
@@ -83,10 +83,13 @@ public slots:
void showOnStartup(); void showOnStartup();
bool isTriggeredByConnectButton(); bool isTriggeredByConnectButton();
void setTriggeredBtConnectButton(bool trigger); void setTriggeredByConnectButton(bool trigger);
void closeApplication(); void closeApplication();
void setDrawerDepth(const int depth);
int getDrawerDepth();
signals: signals:
void goToPage(PageLoader::PageEnum page, bool slide = true); void goToPage(PageLoader::PageEnum page, bool slide = true);
void goToStartPage(); void goToStartPage();
@@ -105,7 +108,7 @@ signals:
void showNotificationMessage(const QString &message); void showNotificationMessage(const QString &message);
void showBusyIndicator(bool visible); void showBusyIndicator(bool visible);
void enableTabBar(bool enabled); void disableControls(bool disabled);
void hideMainWindow(); void hideMainWindow();
void raiseMainWindow(); void raiseMainWindow();
@@ -113,12 +116,17 @@ signals:
void showPassphraseRequestDrawer(); void showPassphraseRequestDrawer();
void passphraseRequestDrawerClosed(QString passphrase); void passphraseRequestDrawerClosed(QString passphrase);
void escapePressed();
void closeTopDrawer();
private: private:
QSharedPointer<ServersModel> m_serversModel; QSharedPointer<ServersModel> m_serversModel;
std::shared_ptr<Settings> m_settings; std::shared_ptr<Settings> m_settings;
bool m_isTriggeredByConnectButton; bool m_isTriggeredByConnectButton;
int m_drawerDepth = 0;
}; };
#endif // PAGECONTROLLER_H #endif // PAGECONTROLLER_H
+10 -29
View File
@@ -7,9 +7,7 @@
#include "ui/qautostart.h" #include "ui/qautostart.h"
#include "version.h" #include "version.h"
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
#include "platforms/android/android_utils.h"
#include "platforms/android/android_controller.h" #include "platforms/android/android_controller.h"
#include <QJniObject>
#endif #endif
#ifdef Q_OS_IOS #ifdef Q_OS_IOS
@@ -29,20 +27,6 @@ SettingsController::SettingsController(const QSharedPointer<ServersModel> &serve
m_settings(settings) m_settings(settings)
{ {
m_appVersion = QString("%1 (%2, %3)").arg(QString(APP_VERSION), __DATE__, GIT_COMMIT_HASH); m_appVersion = QString("%1 (%2, %3)").arg(QString(APP_VERSION), __DATE__, GIT_COMMIT_HASH);
#ifdef Q_OS_ANDROID
if (!m_settings->isScreenshotsEnabled()) {
// Set security screen for Android app
AndroidUtils::runOnAndroidThreadSync([]() {
QJniObject activity = AndroidUtils::getActivity();
QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;");
if (window.isValid()) {
const int FLAG_SECURE = 8192;
window.callMethod<void>("addFlags", "(I)V", FLAG_SECURE);
}
});
}
#endif
} }
void SettingsController::toggleAmneziaDns(bool enable) void SettingsController::toggleAmneziaDns(bool enable)
@@ -129,6 +113,11 @@ void SettingsController::restoreAppConfig(const QString &fileName)
QByteArray data = file.readAll(); QByteArray data = file.readAll();
restoreAppConfigFromData(data);
}
void SettingsController::restoreAppConfigFromData(const QByteArray &data)
{
bool ok = m_settings->restoreAppConfig(data); bool ok = m_settings->restoreAppConfig(data);
if (ok) { if (ok) {
m_serversModel->resetModel(); m_serversModel->resetModel();
@@ -152,7 +141,12 @@ void SettingsController::clearSettings()
m_languageModel->changeLanguage( m_languageModel->changeLanguage(
static_cast<LanguageSettings::AvailableLanguageEnum>(m_languageModel->getCurrentLanguageIndex())); static_cast<LanguageSettings::AvailableLanguageEnum>(m_languageModel->getCurrentLanguageIndex()));
m_sitesModel->setRouteMode(Settings::RouteMode::VpnAllSites); m_sitesModel->setRouteMode(Settings::RouteMode::VpnAllSites);
emit changeSettingsFinished(tr("All settings have been reset to default values")); emit changeSettingsFinished(tr("All settings have been reset to default values"));
#ifdef Q_OS_IOS
AmneziaVPN::clearSettings();
#endif
} }
void SettingsController::clearCachedProfiles() void SettingsController::clearCachedProfiles()
@@ -199,19 +193,6 @@ bool SettingsController::isScreenshotsEnabled()
void SettingsController::toggleScreenshotsEnabled(bool enable) void SettingsController::toggleScreenshotsEnabled(bool enable)
{ {
m_settings->setScreenshotsEnabled(enable); m_settings->setScreenshotsEnabled(enable);
#ifdef Q_OS_ANDROID
std::string command = enable ? "clearFlags" : "addFlags";
// Set security screen for Android app
AndroidUtils::runOnAndroidThreadSync([&command]() {
QJniObject activity = AndroidUtils::getActivity();
QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;");
if (window.isValid()) {
const int FLAG_SECURE = 8192;
window.callMethod<void>(command.c_str(), "(I)V", FLAG_SECURE);
}
});
#endif
} }
bool SettingsController::isCameraPresent() bool SettingsController::isCameraPresent()
@@ -41,6 +41,7 @@ public slots:
void backupAppConfig(const QString &fileName); void backupAppConfig(const QString &fileName);
void restoreAppConfig(const QString &fileName); void restoreAppConfig(const QString &fileName);
void restoreAppConfigFromData(const QByteArray &data);
QString getAppVersion(); QString getAppVersion();
+4 -4
View File
@@ -92,11 +92,11 @@ QString SystemController::getFileName(const QString &acceptLabel, const QString
mainFileDialog->setProperty("acceptLabel", QVariant::fromValue(acceptLabel)); mainFileDialog->setProperty("acceptLabel", QVariant::fromValue(acceptLabel));
mainFileDialog->setProperty("nameFilters", QVariant::fromValue(QStringList(nameFilter))); mainFileDialog->setProperty("nameFilters", QVariant::fromValue(QStringList(nameFilter)));
if (!selectedFile.isEmpty()) {
mainFileDialog->setProperty("selectedFile", QVariant::fromValue(selectedFile));
}
mainFileDialog->setProperty("isSaveMode", QVariant::fromValue(isSaveMode));
mainFileDialog->setProperty("defaultSuffix", QVariant::fromValue(defaultSuffix)); mainFileDialog->setProperty("defaultSuffix", QVariant::fromValue(defaultSuffix));
mainFileDialog->setProperty("isSaveMode", QVariant::fromValue(isSaveMode));
if (!selectedFile.isEmpty()) {
mainFileDialog->setProperty("selectedFile", QVariant::fromValue(QUrl(selectedFile)));
}
QMetaObject::invokeMethod(mainFileDialog, "open"); QMetaObject::invokeMethod(mainFileDialog, "open");
bool isFileDialogAccepted = false; bool isFileDialogAccepted = false;
-14
View File
@@ -83,20 +83,6 @@ QJsonObject ContainersModel::getContainerConfig(const int containerIndex)
return qvariant_cast<QJsonObject>(data(index(containerIndex), ConfigRole)); return qvariant_cast<QJsonObject>(data(index(containerIndex), ConfigRole));
} }
bool ContainersModel::isAnyContainerInstalled()
{
for (int row=0; row < rowCount(); row++) {
QModelIndex idx = this->index(row, 0);
if (this->data(idx, IsInstalledRole).toBool() &&
this->data(idx, ServiceTypeRole).toInt() == ServiceType::Vpn) {
return true;
}
}
return false;
}
QHash<int, QByteArray> ContainersModel::roleNames() const QHash<int, QByteArray> ContainersModel::roleNames() const
{ {
QHash<int, QByteArray> roles; QHash<int, QByteArray> roles;
-2
View File
@@ -49,8 +49,6 @@ public slots:
QJsonObject getContainerConfig(const int containerIndex); QJsonObject getContainerConfig(const int containerIndex);
bool isAnyContainerInstalled();
protected: protected:
QHash<int, QByteArray> roleNames() const override; QHash<int, QByteArray> roleNames() const override;
+3
View File
@@ -46,6 +46,7 @@ QString LanguageModel::getLocalLanguageName(const LanguageSettings::AvailableLan
case LanguageSettings::AvailableLanguageEnum::China_cn: strLanguage = "\347\256\200\344\275\223\344\270\255\346\226\207"; break; case LanguageSettings::AvailableLanguageEnum::China_cn: strLanguage = "\347\256\200\344\275\223\344\270\255\346\226\207"; break;
case LanguageSettings::AvailableLanguageEnum::Persian: strLanguage = "فارسی"; break; case LanguageSettings::AvailableLanguageEnum::Persian: strLanguage = "فارسی"; break;
case LanguageSettings::AvailableLanguageEnum::Arabic: strLanguage = "العربية"; break; case LanguageSettings::AvailableLanguageEnum::Arabic: strLanguage = "العربية"; break;
case LanguageSettings::AvailableLanguageEnum::Burmese: strLanguage = "မြန်မာဘာသာ"; break;
default: default:
break; break;
} }
@@ -61,6 +62,7 @@ void LanguageModel::changeLanguage(const LanguageSettings::AvailableLanguageEnum
case LanguageSettings::AvailableLanguageEnum::China_cn: emit updateTranslations(QLocale::Chinese); break; case LanguageSettings::AvailableLanguageEnum::China_cn: emit updateTranslations(QLocale::Chinese); break;
case LanguageSettings::AvailableLanguageEnum::Persian: emit updateTranslations(QLocale::Persian); break; case LanguageSettings::AvailableLanguageEnum::Persian: emit updateTranslations(QLocale::Persian); break;
case LanguageSettings::AvailableLanguageEnum::Arabic: emit updateTranslations(QLocale::Arabic); break; case LanguageSettings::AvailableLanguageEnum::Arabic: emit updateTranslations(QLocale::Arabic); break;
case LanguageSettings::AvailableLanguageEnum::Burmese: emit updateTranslations(QLocale::Burmese); break;
default: emit updateTranslations(QLocale::English); break; default: emit updateTranslations(QLocale::English); break;
} }
} }
@@ -74,6 +76,7 @@ int LanguageModel::getCurrentLanguageIndex()
case QLocale::Chinese: return static_cast<int>(LanguageSettings::AvailableLanguageEnum::China_cn); break; case QLocale::Chinese: return static_cast<int>(LanguageSettings::AvailableLanguageEnum::China_cn); break;
case QLocale::Persian: return static_cast<int>(LanguageSettings::AvailableLanguageEnum::Persian); break; case QLocale::Persian: return static_cast<int>(LanguageSettings::AvailableLanguageEnum::Persian); break;
case QLocale::Arabic: return static_cast<int>(LanguageSettings::AvailableLanguageEnum::Arabic); break; case QLocale::Arabic: return static_cast<int>(LanguageSettings::AvailableLanguageEnum::Arabic); break;
case QLocale::Burmese: return static_cast<int>(LanguageSettings::AvailableLanguageEnum::Burmese); break;
default: return static_cast<int>(LanguageSettings::AvailableLanguageEnum::English); break; default: return static_cast<int>(LanguageSettings::AvailableLanguageEnum::English); break;
} }
} }
+2 -1
View File
@@ -14,7 +14,8 @@ namespace LanguageSettings
Russian, Russian,
China_cn, China_cn,
Persian, Persian,
Arabic Arabic,
Burmese
}; };
Q_ENUM_NS(AvailableLanguageEnum) Q_ENUM_NS(AvailableLanguageEnum)
+68 -12
View File
@@ -22,6 +22,7 @@ bool AwgConfigModel::setData(const QModelIndex &index, const QVariant &value, in
switch (role) { switch (role) {
case Roles::PortRole: m_protocolConfig.insert(config_key::port, value.toString()); break; case Roles::PortRole: m_protocolConfig.insert(config_key::port, value.toString()); break;
case Roles::MtuRole: m_protocolConfig.insert(config_key::mtu, value.toString()); break;
case Roles::JunkPacketCountRole: m_protocolConfig.insert(config_key::junkPacketCount, value.toString()); break; case Roles::JunkPacketCountRole: m_protocolConfig.insert(config_key::junkPacketCount, value.toString()); break;
case Roles::JunkPacketMinSizeRole: m_protocolConfig.insert(config_key::junkPacketMinSize, value.toString()); break; case Roles::JunkPacketMinSizeRole: m_protocolConfig.insert(config_key::junkPacketMinSize, value.toString()); break;
case Roles::JunkPacketMaxSizeRole: m_protocolConfig.insert(config_key::junkPacketMaxSize, value.toString()); break; case Roles::JunkPacketMaxSizeRole: m_protocolConfig.insert(config_key::junkPacketMaxSize, value.toString()); break;
@@ -57,6 +58,7 @@ QVariant AwgConfigModel::data(const QModelIndex &index, int role) const
switch (role) { switch (role) {
case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString(); case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString();
case Roles::MtuRole: return m_protocolConfig.value(config_key::mtu).toString();
case Roles::JunkPacketCountRole: return m_protocolConfig.value(config_key::junkPacketCount); case Roles::JunkPacketCountRole: return m_protocolConfig.value(config_key::junkPacketCount);
case Roles::JunkPacketMinSizeRole: return m_protocolConfig.value(config_key::junkPacketMinSize); case Roles::JunkPacketMinSizeRole: return m_protocolConfig.value(config_key::junkPacketMinSize);
case Roles::JunkPacketMaxSizeRole: return m_protocolConfig.value(config_key::junkPacketMaxSize); case Roles::JunkPacketMaxSizeRole: return m_protocolConfig.value(config_key::junkPacketMaxSize);
@@ -80,25 +82,21 @@ void AwgConfigModel::updateModel(const QJsonObject &config)
QJsonObject protocolConfig = config.value(config_key::awg).toObject(); QJsonObject protocolConfig = config.value(config_key::awg).toObject();
m_protocolConfig[config_key::port] = m_protocolConfig[config_key::last_config] = protocolConfig.value(config_key::last_config);
protocolConfig.value(config_key::port).toString(protocols::awg::defaultPort); m_protocolConfig[config_key::port] = protocolConfig.value(config_key::port).toString(protocols::awg::defaultPort);
m_protocolConfig[config_key::mtu] = protocolConfig.value(config_key::mtu).toString(protocols::awg::defaultMtu);
m_protocolConfig[config_key::junkPacketCount] = m_protocolConfig[config_key::junkPacketCount] =
protocolConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount); protocolConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount);
m_protocolConfig[config_key::junkPacketMinSize] = m_protocolConfig[config_key::junkPacketMinSize] =
protocolConfig.value(config_key::junkPacketMinSize) protocolConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize);
.toString(protocols::awg::defaultJunkPacketMinSize);
m_protocolConfig[config_key::junkPacketMaxSize] = m_protocolConfig[config_key::junkPacketMaxSize] =
protocolConfig.value(config_key::junkPacketMaxSize) protocolConfig.value(config_key::junkPacketMaxSize).toString(protocols::awg::defaultJunkPacketMaxSize);
.toString(protocols::awg::defaultJunkPacketMaxSize);
m_protocolConfig[config_key::initPacketJunkSize] = m_protocolConfig[config_key::initPacketJunkSize] =
protocolConfig.value(config_key::initPacketJunkSize) protocolConfig.value(config_key::initPacketJunkSize).toString(protocols::awg::defaultInitPacketJunkSize);
.toString(protocols::awg::defaultInitPacketJunkSize);
m_protocolConfig[config_key::responsePacketJunkSize] = m_protocolConfig[config_key::responsePacketJunkSize] =
protocolConfig.value(config_key::responsePacketJunkSize) protocolConfig.value(config_key::responsePacketJunkSize).toString(protocols::awg::defaultResponsePacketJunkSize);
.toString(protocols::awg::defaultResponsePacketJunkSize);
m_protocolConfig[config_key::initPacketMagicHeader] = m_protocolConfig[config_key::initPacketMagicHeader] =
protocolConfig.value(config_key::initPacketMagicHeader) protocolConfig.value(config_key::initPacketMagicHeader).toString(protocols::awg::defaultInitPacketMagicHeader);
.toString(protocols::awg::defaultInitPacketMagicHeader);
m_protocolConfig[config_key::responsePacketMagicHeader] = m_protocolConfig[config_key::responsePacketMagicHeader] =
protocolConfig.value(config_key::responsePacketMagicHeader) protocolConfig.value(config_key::responsePacketMagicHeader)
.toString(protocols::awg::defaultResponsePacketMagicHeader); .toString(protocols::awg::defaultResponsePacketMagicHeader);
@@ -114,6 +112,19 @@ void AwgConfigModel::updateModel(const QJsonObject &config)
QJsonObject AwgConfigModel::getConfig() QJsonObject AwgConfigModel::getConfig()
{ {
const AwgConfig oldConfig(m_fullConfig.value(config_key::awg).toObject());
const AwgConfig newConfig(m_protocolConfig);
if (!oldConfig.hasEqualServerSettings(newConfig)) {
m_protocolConfig.remove(config_key::last_config);
} else {
auto lastConfig = m_protocolConfig.value(config_key::last_config).toString();
QJsonObject jsonConfig = QJsonDocument::fromJson(lastConfig.toUtf8()).object();
jsonConfig[config_key::mtu] = newConfig.mtu;
m_protocolConfig[config_key::last_config] = QString(QJsonDocument(jsonConfig).toJson());
}
m_fullConfig.insert(config_key::awg, m_protocolConfig); m_fullConfig.insert(config_key::awg, m_protocolConfig);
return m_fullConfig; return m_fullConfig;
} }
@@ -123,6 +134,7 @@ QHash<int, QByteArray> AwgConfigModel::roleNames() const
QHash<int, QByteArray> roles; QHash<int, QByteArray> roles;
roles[PortRole] = "port"; roles[PortRole] = "port";
roles[MtuRole] = "mtu";
roles[JunkPacketCountRole] = "junkPacketCount"; roles[JunkPacketCountRole] = "junkPacketCount";
roles[JunkPacketMinSizeRole] = "junkPacketMinSize"; roles[JunkPacketMinSizeRole] = "junkPacketMinSize";
roles[JunkPacketMaxSizeRole] = "junkPacketMaxSize"; roles[JunkPacketMaxSizeRole] = "junkPacketMaxSize";
@@ -135,3 +147,47 @@ QHash<int, QByteArray> AwgConfigModel::roleNames() const
return roles; return roles;
} }
AwgConfig::AwgConfig(const QJsonObject &jsonConfig)
{
port = jsonConfig.value(config_key::port).toString(protocols::awg::defaultPort);
mtu = jsonConfig.value(config_key::mtu).toString(protocols::awg::defaultMtu);
junkPacketCount = jsonConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount);
junkPacketMinSize =
jsonConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize);
junkPacketMaxSize =
jsonConfig.value(config_key::junkPacketMaxSize).toString(protocols::awg::defaultJunkPacketMaxSize);
initPacketJunkSize =
jsonConfig.value(config_key::initPacketJunkSize).toString(protocols::awg::defaultInitPacketJunkSize);
responsePacketJunkSize =
jsonConfig.value(config_key::responsePacketJunkSize).toString(protocols::awg::defaultResponsePacketJunkSize);
initPacketMagicHeader =
jsonConfig.value(config_key::initPacketMagicHeader).toString(protocols::awg::defaultInitPacketMagicHeader);
responsePacketMagicHeader = jsonConfig.value(config_key::responsePacketMagicHeader)
.toString(protocols::awg::defaultResponsePacketMagicHeader);
underloadPacketMagicHeader = jsonConfig.value(config_key::underloadPacketMagicHeader)
.toString(protocols::awg::defaultUnderloadPacketMagicHeader);
transportPacketMagicHeader = jsonConfig.value(config_key::transportPacketMagicHeader)
.toString(protocols::awg::defaultTransportPacketMagicHeader);
}
bool AwgConfig::hasEqualServerSettings(const AwgConfig &other) const
{
if (port != other.port || junkPacketCount != other.junkPacketCount || junkPacketMinSize != other.junkPacketMinSize
|| junkPacketMaxSize != other.junkPacketMaxSize || initPacketJunkSize != other.initPacketJunkSize
|| responsePacketJunkSize != other.responsePacketJunkSize || initPacketMagicHeader != other.initPacketMagicHeader
|| responsePacketMagicHeader != other.responsePacketMagicHeader
|| underloadPacketMagicHeader != other.underloadPacketMagicHeader
|| transportPacketMagicHeader != other.transportPacketMagicHeader) {
return false;
}
return true;
}
bool AwgConfig::hasEqualClientSettings(const AwgConfig &other) const
{
if (mtu != other.mtu) {
return false;
}
return true;
}
@@ -6,6 +6,27 @@
#include "containers/containers_defs.h" #include "containers/containers_defs.h"
struct AwgConfig
{
AwgConfig(const QJsonObject &jsonConfig);
QString port;
QString mtu;
QString junkPacketCount;
QString junkPacketMinSize;
QString junkPacketMaxSize;
QString initPacketJunkSize;
QString responsePacketJunkSize;
QString initPacketMagicHeader;
QString responsePacketMagicHeader;
QString underloadPacketMagicHeader;
QString transportPacketMagicHeader;
bool hasEqualServerSettings(const AwgConfig &other) const;
bool hasEqualClientSettings(const AwgConfig &other) const;
};
class AwgConfigModel : public QAbstractListModel class AwgConfigModel : public QAbstractListModel
{ {
Q_OBJECT Q_OBJECT
@@ -13,6 +34,7 @@ class AwgConfigModel : public QAbstractListModel
public: public:
enum Roles { enum Roles {
PortRole = Qt::UserRole + 1, PortRole = Qt::UserRole + 1,
MtuRole,
JunkPacketCountRole, JunkPacketCountRole,
JunkPacketMinSizeRole, JunkPacketMinSizeRole,
JunkPacketMaxSizeRole, JunkPacketMaxSizeRole,
@@ -1,5 +1,7 @@
#include "wireguardConfigModel.h" #include "wireguardConfigModel.h"
#include <QJsonDocument>
#include "protocols/protocols_defs.h" #include "protocols/protocols_defs.h"
WireGuardConfigModel::WireGuardConfigModel(QObject *parent) : QAbstractListModel(parent) WireGuardConfigModel::WireGuardConfigModel(QObject *parent) : QAbstractListModel(parent)
@@ -19,8 +21,8 @@ bool WireGuardConfigModel::setData(const QModelIndex &index, const QVariant &val
} }
switch (role) { switch (role) {
case Roles::PortRole: m_protocolConfig.insert(config_key::port, value.toString()); break; case Roles::PortRole: m_protocolConfig.insert(config_key::port, value.toString()); break;
case Roles::CipherRole: m_protocolConfig.insert(config_key::cipher, value.toString()); break; case Roles::MtuRole: m_protocolConfig.insert(config_key::mtu, value.toString()); break;
} }
emit dataChanged(index, index, QList { role }); emit dataChanged(index, index, QList { role });
@@ -34,9 +36,8 @@ QVariant WireGuardConfigModel::data(const QModelIndex &index, int role) const
} }
switch (role) { switch (role) {
case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort); case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString();
case Roles::CipherRole: case Roles::MtuRole: return m_protocolConfig.value(config_key::mtu).toString();
return m_protocolConfig.value(config_key::cipher).toString(protocols::shadowsocks::defaultCipher);
} }
return QVariant(); return QVariant();
@@ -50,11 +51,31 @@ void WireGuardConfigModel::updateModel(const QJsonObject &config)
m_fullConfig = config; m_fullConfig = config;
QJsonObject protocolConfig = config.value(config_key::wireguard).toObject(); QJsonObject protocolConfig = config.value(config_key::wireguard).toObject();
m_protocolConfig[config_key::last_config] = protocolConfig.value(config_key::last_config);
m_protocolConfig[config_key::port] =
protocolConfig.value(config_key::port).toString(protocols::wireguard::defaultPort);
m_protocolConfig[config_key::mtu] =
protocolConfig.value(config_key::mtu).toString(protocols::wireguard::defaultMtu);
endResetModel(); endResetModel();
} }
QJsonObject WireGuardConfigModel::getConfig() QJsonObject WireGuardConfigModel::getConfig()
{ {
const WgConfig oldConfig(m_fullConfig.value(config_key::awg).toObject());
const WgConfig newConfig(m_protocolConfig);
if (!oldConfig.hasEqualServerSettings(newConfig)) {
m_protocolConfig.remove(config_key::last_config);
} else {
auto lastConfig = m_protocolConfig.value(config_key::last_config).toString();
QJsonObject jsonConfig = QJsonDocument::fromJson(lastConfig.toUtf8()).object();
jsonConfig[config_key::mtu] = newConfig.mtu;
m_protocolConfig[config_key::last_config] = QString(QJsonDocument(jsonConfig).toJson());
}
m_fullConfig.insert(config_key::wireguard, m_protocolConfig); m_fullConfig.insert(config_key::wireguard, m_protocolConfig);
return m_fullConfig; return m_fullConfig;
} }
@@ -64,7 +85,29 @@ QHash<int, QByteArray> WireGuardConfigModel::roleNames() const
QHash<int, QByteArray> roles; QHash<int, QByteArray> roles;
roles[PortRole] = "port"; roles[PortRole] = "port";
roles[CipherRole] = "cipher"; roles[MtuRole] = "mtu";
return roles; return roles;
} }
WgConfig::WgConfig(const QJsonObject &jsonConfig)
{
port = jsonConfig.value(config_key::port).toString(protocols::wireguard::defaultPort);
mtu = jsonConfig.value(config_key::mtu).toString(protocols::wireguard::defaultMtu);
}
bool WgConfig::hasEqualServerSettings(const WgConfig &other) const
{
if (port != other.port) {
return false;
}
return true;
}
bool WgConfig::hasEqualClientSettings(const WgConfig &other) const
{
if (mtu != other.mtu) {
return false;
}
return true;
}
@@ -6,6 +6,18 @@
#include "containers/containers_defs.h" #include "containers/containers_defs.h"
struct WgConfig
{
WgConfig(const QJsonObject &jsonConfig);
QString port;
QString mtu;
bool hasEqualServerSettings(const WgConfig &other) const;
bool hasEqualClientSettings(const WgConfig &other) const;
};
class WireGuardConfigModel : public QAbstractListModel class WireGuardConfigModel : public QAbstractListModel
{ {
Q_OBJECT Q_OBJECT
@@ -13,7 +25,7 @@ class WireGuardConfigModel : public QAbstractListModel
public: public:
enum Roles { enum Roles {
PortRole = Qt::UserRole + 1, PortRole = Qt::UserRole + 1,
CipherRole MtuRole
}; };
explicit WireGuardConfigModel(QObject *parent = nullptr); explicit WireGuardConfigModel(QObject *parent = nullptr);
+17 -10
View File
@@ -87,6 +87,9 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const
case DefaultContainerRole: { case DefaultContainerRole: {
return ContainerProps::containerFromString(server.value(config_key::defaultContainer).toString()); return ContainerProps::containerFromString(server.value(config_key::defaultContainer).toString());
} }
case HasInstalledContainers: {
return serverHasInstalledContainers(index.row());
}
case IsServerFromApiRole: { case IsServerFromApiRole: {
return server.value(config_key::configVersion).toInt(); return server.value(config_key::configVersion).toInt();
} }
@@ -302,6 +305,7 @@ QHash<int, QByteArray> ServersModel::roleNames() const
roles[ContainsAmneziaDnsRole] = "containsAmneziaDns"; roles[ContainsAmneziaDnsRole] = "containsAmneziaDns";
roles[DefaultContainerRole] = "defaultContainer"; roles[DefaultContainerRole] = "defaultContainer";
roles[HasInstalledContainers] = "hasInstalledContainers";
roles[IsServerFromApiRole] = "isServerFromApi"; roles[IsServerFromApiRole] = "isServerFromApi";
return roles; return roles;
@@ -548,6 +552,19 @@ bool ServersModel::isServerFromApiAlreadyExists(const quint16 crc)
return false; return false;
} }
bool ServersModel::serverHasInstalledContainers(const int serverIndex) const
{
QJsonObject server = m_servers.at(serverIndex).toObject();
const auto containers = server.value(config_key::containers).toArray();
for (auto it = containers.begin(); it != containers.end(); it++) {
auto container = ContainerProps::containerFromString(it->toObject().value(config_key::container).toString());
if (ContainerProps::containerService(container) == ServiceType::Vpn) {
return true;
}
}
return false;
}
QVariant ServersModel::getDefaultServerData(const QString roleString) QVariant ServersModel::getDefaultServerData(const QString roleString)
{ {
auto roles = roleNames(); auto roles = roleNames();
@@ -560,11 +577,6 @@ QVariant ServersModel::getDefaultServerData(const QString roleString)
return {}; return {};
} }
void ServersModel::setDefaultServerData(const QString roleString, const QVariant &value)
{
}
QVariant ServersModel::getProcessedServerData(const QString roleString) QVariant ServersModel::getProcessedServerData(const QString roleString)
{ {
auto roles = roleNames(); auto roles = roleNames();
@@ -577,11 +589,6 @@ QVariant ServersModel::getProcessedServerData(const QString roleString)
return {}; return {};
} }
void ServersModel::setProcessedServerData(const QString roleString, const QVariant &value)
{
}
bool ServersModel::isDefaultServerDefaultContainerHasSplitTunneling() bool ServersModel::isDefaultServerDefaultContainerHasSplitTunneling()
{ {
auto server = m_servers.at(m_defaultServerIndex).toObject(); auto server = m_servers.at(m_defaultServerIndex).toObject();
+4 -2
View File
@@ -28,6 +28,7 @@ public:
DefaultContainerRole, DefaultContainerRole,
HasInstalledContainers,
IsServerFromApiRole, IsServerFromApiRole,
HasAmneziaDns HasAmneziaDns
@@ -101,10 +102,8 @@ public slots:
bool isServerFromApiAlreadyExists(const quint16 crc); bool isServerFromApiAlreadyExists(const quint16 crc);
QVariant getDefaultServerData(const QString roleString); QVariant getDefaultServerData(const QString roleString);
void setDefaultServerData(const QString roleString, const QVariant &value);
QVariant getProcessedServerData(const QString roleString); QVariant getProcessedServerData(const QString roleString);
void setProcessedServerData(const QString roleString, const QVariant &value);
bool isDefaultServerDefaultContainerHasSplitTunneling(); bool isDefaultServerDefaultContainerHasSplitTunneling();
@@ -123,6 +122,7 @@ signals:
private: private:
ServerCredentials serverCredentials(int index) const; ServerCredentials serverCredentials(int index) const;
void updateContainersModel(); void updateContainersModel();
void updateDefaultServerContainersModel(); void updateDefaultServerContainersModel();
@@ -130,6 +130,8 @@ private:
bool isAmneziaDnsContainerInstalled(const int serverIndex) const; bool isAmneziaDnsContainerInstalled(const int serverIndex) const;
bool serverHasInstalledContainers(const int serverIndex) const;
QJsonArray m_servers; QJsonArray m_servers;
std::shared_ptr<Settings> m_settings; std::shared_ptr<Settings> m_settings;

Some files were not shown because too many files have changed in this diff Show More