diff --git a/.clang-format b/.clang-format
new file mode 100644
index 00000000..5c459fd2
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,39 @@
+BasedOnStyle: WebKit
+AccessModifierOffset: '-4'
+AlignAfterOpenBracket: Align
+AlignConsecutiveMacros: 'true'
+AlignTrailingComments: 'true'
+AllowAllArgumentsOnNextLine: 'true'
+AllowAllParametersOfDeclarationOnNextLine: 'true'
+AllowShortBlocksOnASingleLine: 'false'
+AllowShortCaseLabelsOnASingleLine: 'true'
+AllowShortEnumsOnASingleLine: 'false'
+AllowShortFunctionsOnASingleLine: None
+AlwaysBreakTemplateDeclarations: 'No'
+BreakBeforeBinaryOperators: NonAssignment
+BreakBeforeBraces: Custom
+BraceWrapping:
+ AfterClass: true
+ AfterControlStatement: false
+ AfterEnum: false
+ AfterFunction: true
+ AfterNamespace: true
+ AfterObjCDeclaration: false
+ AfterStruct: true
+ AfterUnion: false
+ BeforeCatch: false
+ BeforeElse: false
+ IndentBraces: false
+BreakConstructorInitializers: BeforeColon
+ColumnLimit: '120'
+CommentPragmas: '"^!|^:"'
+ConstructorInitializerAllOnOneLineOrOnePerLine: 'true'
+ConstructorInitializerIndentWidth: '4'
+ContinuationIndentWidth: '8'
+IndentPPDirectives: BeforeHash
+NamespaceIndentation: All
+PenaltyExcessCharacter: '10'
+PointerAlignment: Right
+SortIncludes: 'true'
+SpaceAfterTemplateKeyword: 'false'
+Standard: Auto
diff --git a/.clang-format-ignore b/.clang-format-ignore
new file mode 100644
index 00000000..4019357f
--- /dev/null
+++ b/.clang-format-ignore
@@ -0,0 +1,20 @@
+/client/3rd
+/client/3rd-prebuild
+/client/android
+/client/cmake
+/client/core/serialization
+/client/daemon
+/client/fonts
+/client/images
+/client/ios
+/client/mozilla
+/client/platforms/dummy
+/client/platforms/linux
+/client/platforms/macos
+/client/platforms/windows
+/client/server_scripts
+/client/translations
+/deploy
+/docs
+/metadata
+/service/src
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index e8ad8fdf..0c9dfb32 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -10,13 +10,18 @@ env:
jobs:
Build-Linux-Ubuntu:
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-22.04
env:
QT_VERSION: 6.6.2
QIF_VERSION: 4.7
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
+ PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
+ DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
+ DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
+ FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
+ PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
steps:
- name: 'Install Qt'
@@ -83,7 +88,12 @@ jobs:
QIF_VERSION: 4.7
BUILD_ARCH: 64
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
+ PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
+ DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
+ DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
+ FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
+ PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
steps:
- name: 'Get sources'
@@ -146,7 +156,12 @@ jobs:
CC: cc
CXX: c++
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
+ PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
+ DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
+ DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
+ FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
+ PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
steps:
- name: 'Setup xcode'
@@ -181,7 +196,7 @@ jobs:
- name: 'Install go'
uses: actions/setup-go@v5
with:
- go-version: '1.20'
+ go-version: '1.24'
cache: false
- name: 'Setup gomobile'
@@ -208,7 +223,11 @@ jobs:
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/ios/bin"
export QT_MACOS_ROOT_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos"
export PATH=$PATH:~/go/bin
- sh deploy/build_ios.sh
+ sh deploy/build_ios.sh | \
+ sed -e '/-Xcc -DPROD_AGW_PUBLIC_KEY/,/-Xcc/ { /-Xcc/!d; }' -e '/-Xcc -DPROD_AGW_PUBLIC_KEY/d' | \
+ sed -e '/-Xcc -DDEV_AGW_PUBLIC_KEY/,/-Xcc/ { /-Xcc/!d; }' -e '/-Xcc -DDEV_AGW_PUBLIC_KEY/d' | \
+ sed -e '/-DPROD_AGW_PUBLIC_KEY/,/-D/ { /-D/!d; }' -e '/-DPROD_AGW_PUBLIC_KEY/d' | \
+ sed -e '/-DDEV_AGW_PUBLIC_KEY/,/-D/ { /-D/!d; }' -e '/-DDEV_AGW_PUBLIC_KEY/d'
env:
IOS_TRUST_CERT_BASE64: ${{ secrets.IOS_TRUST_CERT_BASE64 }}
IOS_SIGNING_CERT_BASE64: ${{ secrets.IOS_SIGNING_CERT_BASE64 }}
@@ -230,21 +249,88 @@ jobs:
# ------------------------------------------------------
- Build-MacOS:
+ Build-MacOS-old:
runs-on: macos-latest
env:
# Keep compat with MacOS 10.15 aka Catalina by Qt 6.4
QT_VERSION: 6.4.3
- QIF_VERSION: 4.6
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
+ PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
+ DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
+ DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
+ FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
+ PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
steps:
- name: 'Setup xcode'
uses: maxim-lobanov/setup-xcode@v1
with:
- xcode-version: '14.3.1'
+ xcode-version: '15.4.0'
+
+ - name: 'Install Qt'
+ uses: jurplel/install-qt-action@v3
+ with:
+ version: ${{ env.QT_VERSION }}
+ host: 'mac'
+ target: 'desktop'
+ arch: 'clang_64'
+ modules: 'qtremoteobjects qt5compat qtshadertools'
+ dir: ${{ runner.temp }}
+ setup-python: 'true'
+ set-env: 'true'
+ extra: '--external 7z --base ${{ env.QT_MIRROR }}'
+
+
+ - name: 'Get sources'
+ uses: actions/checkout@v4
+ with:
+ submodules: 'true'
+ fetch-depth: 10
+
+ - name: 'Setup ccache'
+ uses: hendrikmuhs/ccache-action@v1.2
+
+ - name: 'Build project'
+ run: |
+ export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin"
+ bash deploy/build_macos.sh
+
+ - name: 'Upload installer artifact'
+ uses: actions/upload-artifact@v4
+ with:
+ name: AmneziaVPN_MacOS_old_installer
+ path: deploy/build/pkg/AmneziaVPN.pkg
+ retention-days: 7
+
+ - name: 'Upload unpacked artifact'
+ uses: actions/upload-artifact@v4
+ with:
+ name: AmneziaVPN_MacOS_old_unpacked
+ path: deploy/build/client/AmneziaVPN.app
+ retention-days: 7
+
+# ------------------------------------------------------
+
+ Build-MacOS:
+ runs-on: macos-latest
+
+ env:
+ QT_VERSION: 6.8.0
+ PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
+ PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
+ DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
+ DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
+ DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
+ FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
+ PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
+
+ steps:
+ - name: 'Setup xcode'
+ uses: maxim-lobanov/setup-xcode@v1
+ with:
+ xcode-version: '15.4.0'
- name: 'Install Qt'
uses: jurplel/install-qt-action@v3
@@ -259,11 +345,6 @@ jobs:
set-env: 'true'
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'
uses: actions/checkout@v4
@@ -277,14 +358,13 @@ jobs:
- name: 'Build project'
run: |
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin"
- export QIF_BIN_DIR="${{ runner.temp }}/Qt/Tools/QtInstallerFramework/${{ env.QIF_VERSION }}/bin"
bash deploy/build_macos.sh
- name: 'Upload installer artifact'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_MacOS_installer
- path: AmneziaVPN.dmg
+ path: deploy/build/pkg/AmneziaVPN.pkg
retention-days: 7
- name: 'Upload unpacked artifact'
@@ -301,10 +381,15 @@ jobs:
env:
ANDROID_BUILD_PLATFORM: android-34
- QT_VERSION: 6.7.2
+ QT_VERSION: 6.7.3
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
+ PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
+ DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
+ DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
+ FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
+ PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
steps:
- name: 'Install desktop Qt'
@@ -316,7 +401,8 @@ jobs:
arch: 'linux_gcc_64'
modules: ${{ env.QT_MODULES }}
dir: ${{ runner.temp }}
- extra: '--external 7z --base ${{ env.QT_MIRROR }}'
+ py7zrversion: '==0.22.*'
+ extra: '--base ${{ env.QT_MIRROR }}'
- name: 'Install android_x86_64 Qt'
uses: jurplel/install-qt-action@v4
@@ -327,7 +413,8 @@ jobs:
arch: 'android_x86_64'
modules: ${{ env.QT_MODULES }}
dir: ${{ runner.temp }}
- extra: '--external 7z --base ${{ env.QT_MIRROR }}'
+ py7zrversion: '==0.22.*'
+ extra: '--base ${{ env.QT_MIRROR }}'
- name: 'Install android_x86 Qt'
uses: jurplel/install-qt-action@v4
@@ -338,7 +425,8 @@ jobs:
arch: 'android_x86'
modules: ${{ env.QT_MODULES }}
dir: ${{ runner.temp }}
- extra: '--external 7z --base ${{ env.QT_MIRROR }}'
+ py7zrversion: '==0.22.*'
+ extra: '--base ${{ env.QT_MIRROR }}'
- name: 'Install android_armv7 Qt'
uses: jurplel/install-qt-action@v4
@@ -349,7 +437,8 @@ jobs:
arch: 'android_armv7'
modules: ${{ env.QT_MODULES }}
dir: ${{ runner.temp }}
- extra: '--external 7z --base ${{ env.QT_MIRROR }}'
+ py7zrversion: '==0.22.*'
+ extra: '--base ${{ env.QT_MIRROR }}'
- name: 'Install android_arm64_v8a Qt'
uses: jurplel/install-qt-action@v4
@@ -360,7 +449,8 @@ jobs:
arch: 'android_arm64_v8a'
modules: ${{ env.QT_MODULES }}
dir: ${{ runner.temp }}
- extra: '--external 7z --base ${{ env.QT_MIRROR }}'
+ py7zrversion: '==0.22.*'
+ extra: '--base ${{ env.QT_MIRROR }}'
- name: 'Grant execute permission for qt-cmake'
shell: bash
diff --git a/.github/workflows/tag-deploy.yml b/.github/workflows/tag-deploy.yml
index dffb3ab1..31c334bf 100644
--- a/.github/workflows/tag-deploy.yml
+++ b/.github/workflows/tag-deploy.yml
@@ -16,7 +16,12 @@ jobs:
QT_VERSION: 6.4.1
QIF_VERSION: 4.5
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
+ PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
+ DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
+ DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
+ FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
+ PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
steps:
- name: 'Install desktop Qt'
diff --git a/.github/workflows/tag-upload.yml b/.github/workflows/tag-upload.yml
index 22629ed3..9ac2da58 100644
--- a/.github/workflows/tag-upload.yml
+++ b/.github/workflows/tag-upload.yml
@@ -1,64 +1,41 @@
name: 'Upload a new version'
on:
- push:
- tags:
- - '[0-9]+.[0-9]+.[0-9]+.[0-9]+'
+ workflow_dispatch:
+ inputs:
+ RELEASE_VERSION:
+ description: 'Release version (e.g. 1.2.3.4)'
+ required: true
+ type: string
jobs:
- upload:
+ Upload-S3:
runs-on: ubuntu-latest
- name: upload
steps:
- - name: Checkout CMakeLists.txt
+ - name: Checkout
uses: actions/checkout@v4
with:
- ref: ${{ github.ref_name }}
+ ref: ${{ inputs.RELEASE_VERSION }}
sparse-checkout: |
CMakeLists.txt
+ deploy/deploy_s3.sh
sparse-checkout-cone-mode: false
- name: Verify git tag
run: |
- GIT_TAG=${{ github.ref_name }}
+ TAG_NAME=${{ inputs.RELEASE_VERSION }}
CMAKE_TAG=$(grep 'project.*VERSION' CMakeLists.txt | sed -E 's/.* ([0-9]+.[0-9]+.[0-9]+.[0-9]+)$/\1/')
-
- if [[ "$GIT_TAG" == "$CMAKE_TAG" ]]; then
- echo "Git tag ($GIT_TAG) and version in CMakeLists.txt ($CMAKE_TAG) are the same. Continuing..."
+ if [[ "$TAG_NAME" == "$CMAKE_TAG" ]]; then
+ echo "Git tag ($TAG_NAME) matches CMakeLists.txt version ($CMAKE_TAG)."
else
- echo "Git tag ($GIT_TAG) and version in CMakeLists.txt ($CMAKE_TAG) are not the same! Cancelling..."
+ echo "::error::Mismatch: Git tag ($TAG_NAME) != CMakeLists.txt version ($CMAKE_TAG). Exiting with error..."
exit 1
fi
- - name: Download artifacts from the "${{ github.ref_name }}" tag
- uses: robinraju/release-downloader@v1.8
+ - name: Setup Rclone
+ uses: AnimMouse/setup-rclone@v1
with:
- tag: ${{ github.ref_name }}
- fileName: "AmneziaVPN_(Linux_|)${{ github.ref_name }}*"
- out-file-path: ${{ github.ref_name }}
+ rclone_config: ${{ secrets.RCLONE_CONFIG }}
- - name: Upload beta version
- uses: jakejarvis/s3-sync-action@master
- if: contains(github.event.base_ref, 'dev')
- with:
- args: --include "AmneziaVPN*" --delete
- env:
- AWS_S3_BUCKET: updates
- AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
- AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
- AWS_S3_ENDPOINT: https://${{ vars.CF_ACCOUNT_ID }}.r2.cloudflarestorage.com
- SOURCE_DIR: ${{ github.ref_name }}
- DEST_DIR: beta/${{ github.ref_name }}
-
- - name: Upload stable version
- uses: jakejarvis/s3-sync-action@master
- if: contains(github.event.base_ref, 'master')
- with:
- args: --include "AmneziaVPN*" --delete
- env:
- AWS_S3_BUCKET: updates
- AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
- AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
- AWS_S3_ENDPOINT: https://${{ vars.CF_ACCOUNT_ID }}.r2.cloudflarestorage.com
- SOURCE_DIR: ${{ github.ref_name }}
- DEST_DIR: stable/${{ github.ref_name }}
+ - name: Send dist to S3
+ run: bash deploy/deploy_s3.sh ${{ inputs.RELEASE_VERSION }}
diff --git a/.gitignore b/.gitignore
index 5b90fd55..503adc2d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -133,4 +133,8 @@ client/3rd/ShadowSocks/ss_ios.xcconfig
out/
# CMake files
-CMakeFiles/
\ No newline at end of file
+CMakeFiles/
+
+ios-ne-build.sh
+macos-ne-build.sh
+macos-signed-build.sh
diff --git a/.gitmodules b/.gitmodules
index 3ceaa56e..90edb582 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,6 +1,3 @@
-[submodule "client/3rd/OpenVPNAdapter"]
- path = client/3rd/OpenVPNAdapter
- url = https://github.com/amnezia-vpn/OpenVPNAdapter.git
[submodule "client/3rd/qtkeychain"]
path = client/3rd/qtkeychain
url = https://github.com/frankosterfeld/qtkeychain.git
@@ -10,6 +7,7 @@
[submodule "client/3rd-prebuilt"]
path = client/3rd-prebuilt
url = https://github.com/amnezia-vpn/3rd-prebuilt
+ branch = feature/special-handshake
[submodule "client/3rd/amneziawg-apple"]
path = client/3rd/amneziawg-apple
url = https://github.com/amnezia-vpn/amneziawg-apple
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b84c25f6..fec613de 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN)
-project(${PROJECT} VERSION 4.8.0.5
+project(${PROJECT} VERSION 4.8.8.1
DESCRIPTION "AmneziaVPN"
HOMEPAGE_URL "https://amnezia.org/"
)
@@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
set(RELEASE_DATE "${CURRENT_DATE}")
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
-set(APP_ANDROID_VERSION_CODE 61)
+set(APP_ANDROID_VERSION_CODE 2087)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux")
diff --git a/README.md b/README.md
index e4a6bf0c..992c3ad0 100644
--- a/README.md
+++ b/README.md
@@ -1,30 +1,31 @@
# Amnezia VPN
-## _The best client for self-hosted VPN_
+
+### _The best client for self-hosted VPN_
+
[](https://github.com/amnezia-vpn/amnezia-client/actions/workflows/deploy.yml?query=branch:dev)
[](https://gitpod.io/#https://github.com/amnezia-vpn/amnezia-client)
-Amnezia is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
+### [English]([https://github.com/amnezia-vpn/amnezia-client/blob/dev/README_RU.md](https://github.com/amnezia-vpn/amnezia-client/tree/dev?tab=readme-ov-file#)) | [Русский](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README_RU.md)
-
-
+[Amnezia](https://amnezia.org) is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
-
-
-
-
+[](https://amnezia.org)
-
+### [Website](https://amnezia.org) | [Alt website link](https://storage.googleapis.com/amnezia/amnezia.org) | [Documentation](https://docs.amnezia.org) | [Troubleshooting](https://docs.amnezia.org/troubleshooting)
-
-
+> [!TIP]
+> If the [Amnezia website](https://amnezia.org) is blocked in your region, you can use an [Alternative website link](https://storage.googleapis.com/amnezia/amnezia.org ).
+
+
[All releases](https://github.com/amnezia-vpn/amnezia-client/releases)
-
+
+
## Features
@@ -37,7 +38,8 @@ Amnezia is an open-source VPN client, with a key feature that enables you to dep
## Links
-- [https://amnezia.org](https://amnezia.org) - project website
+- [https://amnezia.org](https://amnezia.org) - Project website | [Alternative link (mirror)](https://storage.googleapis.com/kldscp/amnezia.org)
+- [https://docs.amnezia.org](https://docs.amnezia.org) - Documentation
- [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_ir](https://t.me/amnezia_vpn_ir) - Telegram support channel (Farsi)
@@ -183,11 +185,11 @@ GPL v3.0
Patreon: [https://www.patreon.com/amneziavpn](https://www.patreon.com/amneziavpn)
-Bitcoin: bc1q26eevjcg9j0wuyywd2e3uc9cs2w58lpkpjxq6p
+Bitcoin: bc1qmhtgcf9637rl3kqyy22r2a8wa8laka4t9rx2mf
USDT BEP20: 0x6abD576765a826f87D1D95183438f9408C901bE4
USDT TRC20: TELAitazF1MZGmiNjTcnxDjEiH5oe7LC9d
-XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3
-
+XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3
+TON: UQDpU1CyKRmg7L8mNScKk9FRc2SlESuI7N-Hby4nX-CcVmns
## Acknowledgments
This project is tested with BrowserStack.
diff --git a/README_RU.md b/README_RU.md
new file mode 100644
index 00000000..44681875
--- /dev/null
+++ b/README_RU.md
@@ -0,0 +1,181 @@
+# Amnezia VPN
+
+### _Лучший клиент для создания VPN на собственном сервере_
+
+[](https://github.com/amnezia-vpn/amnezia-client/actions/workflows/deploy.yml?query=branch:dev)
+[](https://gitpod.io/#https://github.com/amnezia-vpn/amnezia-client)
+
+### [English](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README.md) | Русский
+[AmneziaVPN](https://amnezia.org) — это open source VPN-клиент, ключевая особенность которого заключается в возможности развернуть собственный VPN на вашем сервере.
+
+[](https://amnezia.org)
+
+### [Сайт](https://amnezia.org) | [Зеркало сайта](https://storage.googleapis.com/amnezia/amnezia.org) | [Документация](https://docs.amnezia.org) | [Решение проблем](https://docs.amnezia.org/troubleshooting)
+
+> [!TIP]
+> Если [сайт Amnezia](https://amnezia.org) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/amnezia/amnezia.org).
+
+
+
+
+[Все релизы](https://github.com/amnezia-vpn/amnezia-client/releases)
+
+
+
+
+
+## Особенности
+
+- Простой в использовании — введите IP-адрес, SSH-логин и пароль, и Amnezia автоматически установит VPN-контейнеры Docker на ваш сервер и подключится к VPN.
+- Классические VPN-протоколы: OpenVPN, WireGuard и IKEv2.
+- Протоколы с маскировкой трафика (обфускацией): OpenVPN с плагином [Cloak](https://github.com/cbeuw/Cloak), Shadowsocks (OpenVPN over Shadowsocks), [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/) and XRay.
+- Поддержка Split Tunneling — добавляйте любые сайты или приложения в список, чтобы включить VPN только для них.
+- Поддерживает платформы: Windows, macOS, Linux, Android, iOS.
+- Поддержка конфигурации протокола AmneziaWG на [бета-прошивке Keenetic](https://docs.keenetic.com/ua/air/kn-1611/en/6319-latest-development-release.html#UUID-186c4108-5afd-c10b-f38a-cdff6c17fab3_section-idm33192196168192-improved).
+
+## Ссылки
+
+- [https://amnezia.org](https://amnezia.org) - Веб-сайт проекта | [Альтернативная ссылка (зеркало)](https://storage.googleapis.com/kldscp/amnezia.org)
+- [https://docs.amnezia.org](https://docs.amnezia.org) - Документация
+- [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 (Английский)
+- [https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Канал поддержки в Telegram (Фарси)
+- [https://t.me/amnezia_vpn_mm](https://t.me/amnezia_vpn_mm) - Канал поддержки в Telegram (Мьянма)
+- [https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - Канал поддержки в Telegram (Русский)
+- [https://vpnpay.io/en/amnezia-premium/](https://vpnpay.io/en/amnezia-premium/) - Amnezia Premium | [Зеркало](https://storage.googleapis.com/kldscp/vpnpay.io/ru/amnezia-premium\)
+
+## Технологии
+
+AmneziaVPN использует несколько проектов с открытым исходным кодом:
+
+- [OpenSSL](https://www.openssl.org/)
+- [OpenVPN](https://openvpn.net/)
+- [Shadowsocks](https://shadowsocks.org/)
+- [Qt](https://www.qt.io/)
+- [LibSsh](https://libssh.org)
+- и другие...
+
+## Проверка исходного кода
+После клонирования репозитория обязательно загрузите все подмодули.
+
+```bash
+git submodule update --init --recursive
+```
+
+
+## Разработка
+Хотите внести свой вклад? Добро пожаловать!
+
+### Помощь с переводами
+
+Загрузите самые актуальные файлы перевода.
+
+Перейдите на [вкладку "Actions"](https://github.com/amnezia-vpn/amnezia-client/actions?query=is%3Asuccess+branch%3Adev), нажмите на первую строку. Затем прокрутите вниз до раздела "Artifacts" и скачайте "AmneziaVPN_translations".
+
+Распакуйте этот файл. Каждый файл с расширением *.ts содержит строки для соответствующего языка.
+
+Переведите или исправьте строки в одном или нескольких файлах *.ts и загрузите их обратно в этот репозиторий в папку ``client/translations``. Это можно сделать через веб-интерфейс или любым другим знакомым вам способом.
+
+### Сборка исходного кода и деплой
+Проверьте папку deploy для скриптов сборки.
+
+### Как собрать iOS-приложение из исходного кода на MacOS
+1. Убедитесь, что у вас установлен Xcode версии 14 или выше.
+2. Для генерации проекта Xcode используется QT. Требуется версия QT 6.6.2. Установите QT для MacOS здесь или через QT Online Installer. Необходимые модули:
+- MacOS
+- iOS
+- Модуль совместимости с Qt 5
+- Qt Shader Tools
+- Дополнительные библиотеки:
+ - Qt Image Formats
+ - Qt Multimedia
+ - Qt Remote Objects
+
+
+3. Установите CMake, если это необходимо. Рекомендуемая версия — 3.25. Скачать CMake можно здесь.
+4. Установите Go версии >= v1.16. Если Go ещё не установлен, скачайте его с [официального сайта](https://golang.org/dl/) или используйте Homebrew. Установите gomobile:
+
+```bash
+export PATH=$PATH:~/go/bin
+go install golang.org/x/mobile/cmd/gomobile@latest
+gomobile init
+```
+
+5. Соберите проект:
+```bash
+export QT_BIN_DIR="/Qt//ios/bin"
+export QT_MACOS_ROOT_DIR="/Qt//macos"
+export QT_IOS_BIN=$QT_BIN_DIR
+export PATH=$PATH:~/go/bin
+mkdir build-ios
+$QT_IOS_BIN/qt-cmake . -B build-ios -GXcode -DQT_HOST_PATH=$QT_MACOS_ROOT_DIR
+```
+Замените и на ваши значения.
+
+Если появляется ошибка gomobile: command not found, убедитесь, что PATH настроен на папку bin, где установлен gomobile:
+```bash
+export PATH=$(PATH):/path/to/GOPATH/bin
+```
+
+6. Откройте проект в Xcode. Теперь вы можете тестировать, архивировать или публиковать приложение.
+
+Если сборка завершится с ошибкой:
+```
+make: ***
+[$(PROJECTDIR)/client/build/AmneziaVPN.build/Debug-iphoneos/wireguard-go-bridge/goroot/.prepared]
+Error 1
+```
+Добавьте пользовательскую переменную PATH в настройки сборки для целей AmneziaVPN и WireGuardNetworkExtension с ключом `PATH` и значением `${PATH}/path/to/bin/folder/with/go/executable`, e.g. `${PATH}:/usr/local/go/bin`.
+
+Если ошибка повторяется на Mac с M1, установите версию CMake для архитектуры ARM:
+```
+arch -arm64 brew install cmake
+```
+
+ При первой попытке сборка может завершиться с ошибкой source files not found. Это происходит из-за параллельной компиляции зависимостей в XCode. Просто перезапустите сборку.
+
+
+## Как собрать Android-приложение
+Сборка тестировалась на MacOS. Требования:
+- JDK 11
+- Android SDK 33
+- CMake 3.25.0
+
+Установите QT, QT Creator и Android Studio.
+Настройте QT Creator:
+
+- В меню QT Creator перейдите в `QT Creator` -> `Preferences` -> `Devices` ->`Android`.
+- Укажите путь к JDK 11.
+- Укажите путь к Android SDK (`$ANDROID_HOME`)
+
+Если вы сталкиваетесь с ошибками, связанными с отсутствием SDK или сообщением «SDK manager not running», их нельзя исправить просто корректировкой путей. Если у вас есть несколько свободных гигабайт на диске, вы можете позволить Qt Creator установить все необходимые компоненты, выбрав пустую папку для расположения Android SDK и нажав кнопку **Set Up SDK**. Учтите: это установит второй Android SDK и NDK на вашем компьютере!
+
+Убедитесь, что настроена правильная версия CMake: перейдите в **Qt Creator -> Preferences** и в боковом меню выберите пункт **Kits**. В центральной части окна, на вкладке **Kits**, найдите запись для инструмента **CMake Tool**. Если выбранная по умолчанию версия CMake ниже 3.25.0, установите на свою систему CMake версии 3.25.0 или выше, а затем выберите опцию **System CMake at <путь>** из выпадающего списка. Если этот пункт отсутствует, это может означать, что вы еще не установили CMake, или Qt Creator не смог найти путь к нему. В таком случае в окне **Preferences** перейдите в боковое меню **CMake**, затем во вкладку **Tools** в центральной части окна и нажмите кнопку **Add**, чтобы указать путь к установленному CMake.
+
+Убедитесь, что для вашего проекта выбрана Android Platform SDK 33: в главном окне на боковой панели выберите пункт **Projects**, и слева вы увидите раздел **Build & Run**, показывающий различные целевые Android-платформы. Вы можете выбрать любую из них, так как настройка проекта Amnezia VPN разработана таким образом, чтобы все Android-цели могли быть собраны. Перейдите в подраздел **Build** и прокрутите центральную часть окна до раздела **Build Steps**. Нажмите **Details** в заголовке **Build Android APK** (кнопка **Details** может быть скрыта, если окно Qt Creator не запущено в полноэкранном режиме!). Вот здесь выберите **android-33** в качестве Android Build Platform SDK.
+
+### Разработка Android-компонентов
+
+После сборки QT Creator копирует проект в отдельную папку, например, `build-amnezia-client-Android_Qt__Clang_-`. Для разработки Android-компонентов откройте сгенерированный проект в Android Studio, указав папку `build-amnezia-client-Android_Qt__Clang_-/client/android-build` в качестве корневой.
+Изменения в сгенерированном проекте нужно вручную перенести в репозиторий. После этого можно коммитить изменения.
+Если возникают проблемы со сборкой в QT Creator после работы в Android Studio, выполните команду `./gradlew clean` в корневой папке сгенерированного проекта (`/client/android-build/.`).
+
+
+## Лицензия
+
+GPL v3.0
+
+## Донаты
+
+Patreon: [https://www.patreon.com/amneziavpn](https://www.patreon.com/amneziavpn)
+
+Bitcoin: bc1qmhtgcf9637rl3kqyy22r2a8wa8laka4t9rx2mf
+USDT BEP20: 0x6abD576765a826f87D1D95183438f9408C901bE4
+USDT TRC20: TELAitazF1MZGmiNjTcnxDjEiH5oe7LC9d
+XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3
+TON: UQDpU1CyKRmg7L8mNScKk9FRc2SlESuI7N-Hby4nX-CcVmns
+
+## Благодарности
+
+Этот проект тестируется с помощью BrowserStack.
+Мы выражаем благодарность [BrowserStack](https://www.browserstack.com) за поддержку нашего проекта.
diff --git a/client/3rd-prebuilt b/client/3rd-prebuilt
index ba580dc5..840b7b07 160000
--- a/client/3rd-prebuilt
+++ b/client/3rd-prebuilt
@@ -1 +1 @@
-Subproject commit ba580dc5bd7784f7b1e110ff0365f3286e549a61
+Subproject commit 840b7b070e6ac8b90dda2fac6e98859b23727c0c
diff --git a/client/3rd/OpenVPNAdapter b/client/3rd/OpenVPNAdapter
deleted file mode 160000
index dea60409..00000000
--- a/client/3rd/OpenVPNAdapter
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit dea6040996298e947d63fb172709e6abfec2ba93
diff --git a/client/3rd/SingleApplication/singleapplication.cmake b/client/3rd/SingleApplication/singleapplication.cmake
deleted file mode 100644
index 78abfa8a..00000000
--- a/client/3rd/SingleApplication/singleapplication.cmake
+++ /dev/null
@@ -1,25 +0,0 @@
-include_directories(${CMAKE_CURRENT_LIST_DIR})
-
-find_package(Qt6 REQUIRED COMPONENTS
- Core Network
-)
-set(LIBS ${LIBS} Qt6::Core Qt6::Network)
-
-
-set(HEADERS ${HEADERS}
- ${CMAKE_CURRENT_LIST_DIR}/singleapplication.h
- ${CMAKE_CURRENT_LIST_DIR}/singleapplication_p.h
-)
-
-set(SOURCES ${SOURCES}
- ${CMAKE_CURRENT_LIST_DIR}/singleapplication.cpp
- ${CMAKE_CURRENT_LIST_DIR}/singleapplication_p.cpp
-)
-
-if(WIN32)
- if(MSVC)
- set(LIBS ${LIBS} Advapi32.lib)
- elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
- set(LIBS ${LIBS} advapi32)
- endif()
-endif()
diff --git a/client/3rd/SingleApplication/singleapplication.cpp b/client/3rd/SingleApplication/singleapplication.cpp
deleted file mode 100644
index 7e153a00..00000000
--- a/client/3rd/SingleApplication/singleapplication.cpp
+++ /dev/null
@@ -1,274 +0,0 @@
-// The MIT License (MIT)
-//
-// Copyright (c) Itay Grudev 2015 - 2020
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-#include
-#include
-#include
-
-#include "singleapplication.h"
-#include "singleapplication_p.h"
-
-/**
- * @brief Constructor. Checks and fires up LocalServer or closes the program
- * if another instance already exists
- * @param argc
- * @param argv
- * @param allowSecondary Whether to enable secondary instance support
- * @param options Optional flags to toggle specific behaviour
- * @param timeout Maximum time blocking functions are allowed during app load
- */
-SingleApplication::SingleApplication( int &argc, char *argv[], bool allowSecondary, Options options, int timeout, const QString &userData )
- : app_t( argc, argv ), d_ptr( new SingleApplicationPrivate( this ) )
-{
- Q_D( SingleApplication );
-
-#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
- // On Android and iOS since the library is not supported fallback to
- // standard QApplication behaviour by simply returning at this point.
- qWarning() << "SingleApplication is not supported on Android and iOS systems.";
- return;
-#endif
-
- // Store the current mode of the program
- d->options = options;
-
- // Add any unique user data
- if ( ! userData.isEmpty() )
- d->addAppData( userData );
-
- // Generating an application ID used for identifying the shared memory
- // block and QLocalServer
- d->genBlockServerName();
-
- // To mitigate QSharedMemory issues with large amount of processes
- // attempting to attach at the same time
- SingleApplicationPrivate::randomSleep();
-
-#ifdef Q_OS_UNIX
- // By explicitly attaching it and then deleting it we make sure that the
- // memory is deleted even after the process has crashed on Unix.
- d->memory = new QSharedMemory( d->blockServerName );
- d->memory->attach();
- delete d->memory;
-#endif
- // Guarantee thread safe behaviour with a shared memory block.
- d->memory = new QSharedMemory( d->blockServerName );
-
- // Create a shared memory block
- if( d->memory->create( sizeof( InstancesInfo ) )){
- // Initialize the shared memory block
- if( ! d->memory->lock() ){
- qCritical() << "SingleApplication: Unable to lock memory block after create.";
- abortSafely();
- }
- d->initializeMemoryBlock();
- } else {
- if( d->memory->error() == QSharedMemory::AlreadyExists ){
- // Attempt to attach to the memory segment
- if( ! d->memory->attach() ){
- qCritical() << "SingleApplication: Unable to attach to shared memory block.";
- abortSafely();
- }
- if( ! d->memory->lock() ){
- qCritical() << "SingleApplication: Unable to lock memory block after attach.";
- abortSafely();
- }
- } else {
- qCritical() << "SingleApplication: Unable to create block.";
- abortSafely();
- }
- }
-
- auto *inst = static_cast( d->memory->data() );
- QElapsedTimer time;
- time.start();
-
- // Make sure the shared memory block is initialised and in consistent state
- while( true ){
- // If the shared memory block's checksum is valid continue
- if( d->blockChecksum() == inst->checksum ) break;
-
- // If more than 5s have elapsed, assume the primary instance crashed and
- // assume it's position
- if( time.elapsed() > 5000 ){
- qWarning() << "SingleApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure.";
- d->initializeMemoryBlock();
- }
-
- // Otherwise wait for a random period and try again. The random sleep here
- // limits the probability of a collision between two racing apps and
- // allows the app to initialise faster
- if( ! d->memory->unlock() ){
- qDebug() << "SingleApplication: Unable to unlock memory for random wait.";
- qDebug() << d->memory->errorString();
- }
- SingleApplicationPrivate::randomSleep();
- if( ! d->memory->lock() ){
- qCritical() << "SingleApplication: Unable to lock memory after random wait.";
- abortSafely();
- }
- }
-
- if( inst->primary == false ){
- d->startPrimary();
- if( ! d->memory->unlock() ){
- qDebug() << "SingleApplication: Unable to unlock memory after primary start.";
- qDebug() << d->memory->errorString();
- }
- return;
- }
-
- // Check if another instance can be started
- if( allowSecondary ){
- d->startSecondary();
- if( d->options & Mode::SecondaryNotification ){
- d->connectToPrimary( timeout, SingleApplicationPrivate::SecondaryInstance );
- }
- if( ! d->memory->unlock() ){
- qDebug() << "SingleApplication: Unable to unlock memory after secondary start.";
- qDebug() << d->memory->errorString();
- }
- return;
- }
-
- if( ! d->memory->unlock() ){
- qDebug() << "SingleApplication: Unable to unlock memory at end of execution.";
- qDebug() << d->memory->errorString();
- }
-
- d->connectToPrimary( timeout, SingleApplicationPrivate::NewInstance );
-
- delete d;
-
- ::exit( EXIT_SUCCESS );
-}
-
-SingleApplication::~SingleApplication()
-{
- Q_D( SingleApplication );
- delete d;
-}
-
-/**
- * Checks if the current application instance is primary.
- * @return Returns true if the instance is primary, false otherwise.
- */
-bool SingleApplication::isPrimary() const
-{
- Q_D( const SingleApplication );
- return d->server != nullptr;
-}
-
-/**
- * Checks if the current application instance is secondary.
- * @return Returns true if the instance is secondary, false otherwise.
- */
-bool SingleApplication::isSecondary() const
-{
- Q_D( const SingleApplication );
- return d->server == nullptr;
-}
-
-/**
- * Allows you to identify an instance by returning unique consecutive instance
- * ids. It is reset when the first (primary) instance of your app starts and
- * only incremented afterwards.
- * @return Returns a unique instance id.
- */
-quint32 SingleApplication::instanceId() const
-{
- Q_D( const SingleApplication );
- return d->instanceNumber;
-}
-
-/**
- * Returns the OS PID (Process Identifier) of the process running the primary
- * instance. Especially useful when SingleApplication is coupled with OS.
- * specific APIs.
- * @return Returns the primary instance PID.
- */
-qint64 SingleApplication::primaryPid() const
-{
- Q_D( const SingleApplication );
- return d->primaryPid();
-}
-
-/**
- * Returns the username the primary instance is running as.
- * @return Returns the username the primary instance is running as.
- */
-QString SingleApplication::primaryUser() const
-{
- Q_D( const SingleApplication );
- return d->primaryUser();
-}
-
-/**
- * Returns the username the current instance is running as.
- * @return Returns the username the current instance is running as.
- */
-QString SingleApplication::currentUser() const
-{
- return SingleApplicationPrivate::getUsername();
-}
-
-/**
- * Sends message to the Primary Instance.
- * @param message The message to send.
- * @param timeout the maximum timeout in milliseconds for blocking functions.
- * @return true if the message was sent successfuly, false otherwise.
- */
-bool SingleApplication::sendMessage( const QByteArray &message, int timeout )
-{
- Q_D( SingleApplication );
-
- // Nobody to connect to
- if( isPrimary() ) return false;
-
- // Make sure the socket is connected
- if( ! d->connectToPrimary( timeout, SingleApplicationPrivate::Reconnect ) )
- return false;
-
- d->socket->write( message );
- bool dataWritten = d->socket->waitForBytesWritten( timeout );
- d->socket->flush();
- return dataWritten;
-}
-
-/**
- * Cleans up the shared memory block and exits with a failure.
- * This function halts program execution.
- */
-void SingleApplication::abortSafely()
-{
- Q_D( SingleApplication );
-
- qCritical() << "SingleApplication: " << d->memory->error() << d->memory->errorString();
- delete d;
- ::exit( EXIT_FAILURE );
-}
-
-QStringList SingleApplication::userData() const
-{
- Q_D( const SingleApplication );
- return d->appData();
-}
diff --git a/client/3rd/SingleApplication/singleapplication.h b/client/3rd/SingleApplication/singleapplication.h
deleted file mode 100644
index 400c88ac..00000000
--- a/client/3rd/SingleApplication/singleapplication.h
+++ /dev/null
@@ -1,154 +0,0 @@
-// The MIT License (MIT)
-//
-// Copyright (c) Itay Grudev 2015 - 2018
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-#ifndef SINGLE_APPLICATION_H
-#define SINGLE_APPLICATION_H
-
-#include
-#include
-
-#ifndef QAPPLICATION_CLASS
- #define QAPPLICATION_CLASS QApplication
-#endif
-
-#include QT_STRINGIFY(QAPPLICATION_CLASS)
-
-class SingleApplicationPrivate;
-
-/**
- * @brief The SingleApplication class handles multiple instances of the same
- * Application
- * @see QCoreApplication
- */
-class SingleApplication : public QAPPLICATION_CLASS
-{
- Q_OBJECT
-
- using app_t = QAPPLICATION_CLASS;
-
-public:
- /**
- * @brief Mode of operation of SingleApplication.
- * Whether the block should be user-wide or system-wide and whether the
- * primary instance should be notified when a secondary instance had been
- * started.
- * @note Operating system can restrict the shared memory blocks to the same
- * user, in which case the User/System modes will have no effect and the
- * block will be user wide.
- * @enum
- */
- enum Mode {
- User = 1 << 0,
- System = 1 << 1,
- SecondaryNotification = 1 << 2,
- ExcludeAppVersion = 1 << 3,
- ExcludeAppPath = 1 << 4
- };
- Q_DECLARE_FLAGS(Options, Mode)
-
- /**
- * @brief Intitializes a SingleApplication instance with argc command line
- * arguments in argv
- * @arg {int &} argc - Number of arguments in argv
- * @arg {const char *[]} argv - Supplied command line arguments
- * @arg {bool} allowSecondary - Whether to start the instance as secondary
- * if there is already a primary instance.
- * @arg {Mode} mode - Whether for the SingleApplication block to be applied
- * User wide or System wide.
- * @arg {int} timeout - Timeout to wait in milliseconds.
- * @note argc and argv may be changed as Qt removes arguments that it
- * recognizes
- * @note Mode::SecondaryNotification only works if set on both the primary
- * instance and the secondary instance.
- * @note The timeout is just a hint for the maximum time of blocking
- * operations. It does not guarantee that the SingleApplication
- * initialisation will be completed in given time, though is a good hint.
- * Usually 4*timeout would be the worst case (fail) scenario.
- * @see See the corresponding QAPPLICATION_CLASS constructor for reference
- */
- explicit SingleApplication( int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 1000, const QString &userData = {} );
- ~SingleApplication() override;
-
- /**
- * @brief Returns if the instance is the primary instance
- * @returns {bool}
- */
- bool isPrimary() const;
-
- /**
- * @brief Returns if the instance is a secondary instance
- * @returns {bool}
- */
- bool isSecondary() const;
-
- /**
- * @brief Returns a unique identifier for the current instance
- * @returns {qint32}
- */
- quint32 instanceId() const;
-
- /**
- * @brief Returns the process ID (PID) of the primary instance
- * @returns {qint64}
- */
- qint64 primaryPid() const;
-
- /**
- * @brief Returns the username of the user running the primary instance
- * @returns {QString}
- */
- QString primaryUser() const;
-
- /**
- * @brief Returns the username of the current user
- * @returns {QString}
- */
- QString currentUser() const;
-
- /**
- * @brief Sends a message to the primary instance. Returns true on success.
- * @param {int} timeout - Timeout for connecting
- * @returns {bool}
- * @note sendMessage() will return false if invoked from the primary
- * instance.
- */
- bool sendMessage( const QByteArray &message, int timeout = 100 );
-
- /**
- * @brief Get the set user data.
- * @returns {QStringList}
- */
- QStringList userData() const;
-
-Q_SIGNALS:
- void instanceStarted();
- void receivedMessage( quint32 instanceId, QByteArray message );
-
-private:
- SingleApplicationPrivate *d_ptr;
- Q_DECLARE_PRIVATE(SingleApplication)
- void abortSafely();
-};
-
-Q_DECLARE_OPERATORS_FOR_FLAGS(SingleApplication::Options)
-
-#endif // SINGLE_APPLICATION_H
diff --git a/client/3rd/SingleApplication/singleapplication.pri b/client/3rd/SingleApplication/singleapplication.pri
deleted file mode 100644
index 80283fc4..00000000
--- a/client/3rd/SingleApplication/singleapplication.pri
+++ /dev/null
@@ -1,15 +0,0 @@
-QT += core network
-CONFIG += c++11
-
-HEADERS += \
- $$PWD/singleapplication.h \
- $$PWD/singleapplication_p.h
-SOURCES += $$PWD/singleapplication.cpp \
- $$PWD/singleapplication_p.cpp
-
-INCLUDEPATH += $$PWD
-
-win32 {
- msvc:LIBS += Advapi32.lib
- gcc:LIBS += -ladvapi32
-}
diff --git a/client/3rd/SingleApplication/singleapplication_p.cpp b/client/3rd/SingleApplication/singleapplication_p.cpp
deleted file mode 100644
index e65bd955..00000000
--- a/client/3rd/SingleApplication/singleapplication_p.cpp
+++ /dev/null
@@ -1,486 +0,0 @@
-// The MIT License (MIT)
-//
-// Copyright (c) Itay Grudev 2015 - 2020
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-//
-// W A R N I N G !!!
-// -----------------
-//
-// This file is not part of the SingleApplication API. It is used purely as an
-// implementation detail. This header file may change from version to
-// version without notice, or may even be removed.
-//
-
-#include
-#include
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
-#include
-#else
-#include
-#endif
-
-#include "singleapplication.h"
-#include "singleapplication_p.h"
-
-#ifdef Q_OS_UNIX
- #include
- #include
- #include
-#endif
-
-#ifdef Q_OS_WIN
- #ifndef NOMINMAX
- #define NOMINMAX 1
- #endif
- #include
- #include
-#endif
-
-SingleApplicationPrivate::SingleApplicationPrivate( SingleApplication *q_ptr )
- : q_ptr( q_ptr )
-{
- server = nullptr;
- socket = nullptr;
- memory = nullptr;
- instanceNumber = 0;
-}
-
-SingleApplicationPrivate::~SingleApplicationPrivate()
-{
- if( socket != nullptr ){
- socket->close();
- delete socket;
- }
-
- if( memory != nullptr ){
- memory->lock();
- auto *inst = static_cast(memory->data());
- if( server != nullptr ){
- server->close();
- delete server;
- inst->primary = false;
- inst->primaryPid = -1;
- inst->primaryUser[0] = '\0';
- inst->checksum = blockChecksum();
- }
- memory->unlock();
-
- delete memory;
- }
-}
-
-QString SingleApplicationPrivate::getUsername()
-{
-#ifdef Q_OS_WIN
- wchar_t username[UNLEN + 1];
- // Specifies size of the buffer on input
- DWORD usernameLength = UNLEN + 1;
- if( GetUserNameW( username, &usernameLength ) )
- return QString::fromWCharArray( username );
-#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
- return QString::fromLocal8Bit( qgetenv( "USERNAME" ) );
-#else
- return qEnvironmentVariable( "USERNAME" );
-#endif
-#endif
-#ifdef Q_OS_UNIX
- QString username;
- uid_t uid = geteuid();
- struct passwd *pw = getpwuid( uid );
- if( pw )
- username = QString::fromLocal8Bit( pw->pw_name );
- if ( username.isEmpty() ){
-#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
- username = QString::fromLocal8Bit( qgetenv( "USER" ) );
-#else
- username = qEnvironmentVariable( "USER" );
-#endif
- }
- return username;
-#endif
-}
-
-void SingleApplicationPrivate::genBlockServerName()
-{
- QCryptographicHash appData( QCryptographicHash::Sha256 );
- appData.addData( "SingleApplication", 17 );
- appData.addData( SingleApplication::app_t::applicationName().toUtf8() );
- appData.addData( SingleApplication::app_t::organizationName().toUtf8() );
- appData.addData( SingleApplication::app_t::organizationDomain().toUtf8() );
-
- if ( ! appDataList.isEmpty() )
- appData.addData( appDataList.join( "" ).toUtf8() );
-
- if( ! (options & SingleApplication::Mode::ExcludeAppVersion) ){
- appData.addData( SingleApplication::app_t::applicationVersion().toUtf8() );
- }
-
- if( ! (options & SingleApplication::Mode::ExcludeAppPath) ){
-#ifdef Q_OS_WIN
- appData.addData( SingleApplication::app_t::applicationFilePath().toLower().toUtf8() );
-#else
- appData.addData( SingleApplication::app_t::applicationFilePath().toUtf8() );
-#endif
- }
-
- // User level block requires a user specific data in the hash
- if( options & SingleApplication::Mode::User ){
- appData.addData( getUsername().toUtf8() );
- }
-
- // Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with
- // server naming requirements.
- blockServerName = appData.result().toBase64().replace("/", "_");
-}
-
-void SingleApplicationPrivate::initializeMemoryBlock() const
-{
- auto *inst = static_cast( memory->data() );
- inst->primary = false;
- inst->secondary = 0;
- inst->primaryPid = -1;
- inst->primaryUser[0] = '\0';
- inst->checksum = blockChecksum();
-}
-
-void SingleApplicationPrivate::startPrimary()
-{
- // Reset the number of connections
- auto *inst = static_cast ( memory->data() );
-
- inst->primary = true;
- inst->primaryPid = QCoreApplication::applicationPid();
- qstrncpy( inst->primaryUser, getUsername().toUtf8().data(), sizeof(inst->primaryUser) );
- inst->checksum = blockChecksum();
- instanceNumber = 0;
- // Successful creation means that no main process exists
- // So we start a QLocalServer to listen for connections
- QLocalServer::removeServer( blockServerName );
- server = new QLocalServer();
-
- // Restrict access to the socket according to the
- // SingleApplication::Mode::User flag on User level or no restrictions
- if( options & SingleApplication::Mode::User ){
- server->setSocketOptions( QLocalServer::UserAccessOption );
- } else {
- server->setSocketOptions( QLocalServer::WorldAccessOption );
- }
-
- server->listen( blockServerName );
- QObject::connect(
- server,
- &QLocalServer::newConnection,
- this,
- &SingleApplicationPrivate::slotConnectionEstablished
- );
-}
-
-void SingleApplicationPrivate::startSecondary()
-{
- auto *inst = static_cast ( memory->data() );
-
- inst->secondary += 1;
- inst->checksum = blockChecksum();
- instanceNumber = inst->secondary;
-}
-
-bool SingleApplicationPrivate::connectToPrimary( int msecs, ConnectionType connectionType )
-{
- QElapsedTimer time;
- time.start();
-
- // Connect to the Local Server of the Primary Instance if not already
- // connected.
- if( socket == nullptr ){
- socket = new QLocalSocket();
- }
-
- if( socket->state() == QLocalSocket::ConnectedState ) return true;
-
- if( socket->state() != QLocalSocket::ConnectedState ){
-
- while( true ){
- randomSleep();
-
- if( socket->state() != QLocalSocket::ConnectingState )
- socket->connectToServer( blockServerName );
-
- if( socket->state() == QLocalSocket::ConnectingState ){
- socket->waitForConnected( static_cast(msecs - time.elapsed()) );
- }
-
- // If connected break out of the loop
- if( socket->state() == QLocalSocket::ConnectedState ) break;
-
- // If elapsed time since start is longer than the method timeout return
- if( time.elapsed() >= msecs ) return false;
- }
- }
-
- // Initialisation message according to the SingleApplication protocol
- QByteArray initMsg;
- QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
-
-#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
- writeStream.setVersion(QDataStream::Qt_5_6);
-#endif
-
- writeStream << blockServerName.toLatin1();
- writeStream << static_cast(connectionType);
- writeStream << instanceNumber;
-#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
- quint16 checksum = qChecksum(QByteArray(initMsg, static_cast(initMsg.length())));
-#else
- quint16 checksum = qChecksum(initMsg.constData(), static_cast(initMsg.length()));
-#endif
- writeStream << checksum;
-
- // The header indicates the message length that follows
- QByteArray header;
- QDataStream headerStream(&header, QIODevice::WriteOnly);
-
-#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
- headerStream.setVersion(QDataStream::Qt_5_6);
-#endif
- headerStream << static_cast ( initMsg.length() );
-
- socket->write( header );
- socket->write( initMsg );
- bool result = socket->waitForBytesWritten( static_cast(msecs - time.elapsed()) );
- socket->flush();
- return result;
-}
-
-quint16 SingleApplicationPrivate::blockChecksum() const
-{
-#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
- quint16 checksum = qChecksum(QByteArray(static_cast(memory->constData()), offsetof(InstancesInfo, checksum)));
-#else
- quint16 checksum = qChecksum(static_cast(memory->constData()), offsetof(InstancesInfo, checksum));
-#endif
- return checksum;
-}
-
-qint64 SingleApplicationPrivate::primaryPid() const
-{
- qint64 pid;
-
- memory->lock();
- auto *inst = static_cast( memory->data() );
- pid = inst->primaryPid;
- memory->unlock();
-
- return pid;
-}
-
-QString SingleApplicationPrivate::primaryUser() const
-{
- QByteArray username;
-
- memory->lock();
- auto *inst = static_cast( memory->data() );
- username = inst->primaryUser;
- memory->unlock();
-
- return QString::fromUtf8( username );
-}
-
-/**
- * @brief Executed when a connection has been made to the LocalServer
- */
-void SingleApplicationPrivate::slotConnectionEstablished()
-{
- QLocalSocket *nextConnSocket = server->nextPendingConnection();
- connectionMap.insert(nextConnSocket, ConnectionInfo());
-
- QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose,
- [nextConnSocket, this](){
- auto &info = connectionMap[nextConnSocket];
- Q_EMIT this->slotClientConnectionClosed( nextConnSocket, info.instanceId );
- }
- );
-
- QObject::connect(nextConnSocket, &QLocalSocket::disconnected, nextConnSocket, &QLocalSocket::deleteLater);
-
- QObject::connect(nextConnSocket, &QLocalSocket::destroyed,
- [nextConnSocket, this](){
- connectionMap.remove(nextConnSocket);
- }
- );
-
- QObject::connect(nextConnSocket, &QLocalSocket::readyRead,
- [nextConnSocket, this](){
- auto &info = connectionMap[nextConnSocket];
- switch(info.stage){
- case StageHeader:
- readInitMessageHeader(nextConnSocket);
- break;
- case StageBody:
- readInitMessageBody(nextConnSocket);
- break;
- case StageConnected:
- Q_EMIT this->slotDataAvailable( nextConnSocket, info.instanceId );
- break;
- default:
- break;
- };
- }
- );
-}
-
-void SingleApplicationPrivate::readInitMessageHeader( QLocalSocket *sock )
-{
- if (!connectionMap.contains( sock )){
- return;
- }
-
- if( sock->bytesAvailable() < ( qint64 )sizeof( quint64 ) ){
- return;
- }
-
- QDataStream headerStream( sock );
-
-#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
- headerStream.setVersion( QDataStream::Qt_5_6 );
-#endif
-
- // Read the header to know the message length
- quint64 msgLen = 0;
- headerStream >> msgLen;
- ConnectionInfo &info = connectionMap[sock];
- info.stage = StageBody;
- info.msgLen = msgLen;
-
- if ( sock->bytesAvailable() >= (qint64) msgLen ){
- readInitMessageBody( sock );
- }
-}
-
-void SingleApplicationPrivate::readInitMessageBody( QLocalSocket *sock )
-{
- Q_Q(SingleApplication);
-
- if (!connectionMap.contains( sock )){
- return;
- }
-
- ConnectionInfo &info = connectionMap[sock];
- if( sock->bytesAvailable() < ( qint64 )info.msgLen ){
- return;
- }
-
- // Read the message body
- QByteArray msgBytes = sock->read(info.msgLen);
- QDataStream readStream(msgBytes);
-
-#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
- readStream.setVersion( QDataStream::Qt_5_6 );
-#endif
-
- // server name
- QByteArray latin1Name;
- readStream >> latin1Name;
-
- // connection type
- ConnectionType connectionType = InvalidConnection;
- quint8 connTypeVal = InvalidConnection;
- readStream >> connTypeVal;
- connectionType = static_cast ( connTypeVal );
-
- // instance id
- quint32 instanceId = 0;
- readStream >> instanceId;
-
- // checksum
- quint16 msgChecksum = 0;
- readStream >> msgChecksum;
-
-#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
- const quint16 actualChecksum = qChecksum(QByteArray(msgBytes, static_cast(msgBytes.length() - sizeof(quint16))));
-#else
- const quint16 actualChecksum = qChecksum(msgBytes.constData(), static_cast(msgBytes.length() - sizeof(quint16)));
-#endif
-
- bool isValid = readStream.status() == QDataStream::Ok &&
- QLatin1String(latin1Name) == blockServerName &&
- msgChecksum == actualChecksum;
-
- if( !isValid ){
- sock->close();
- return;
- }
-
- info.instanceId = instanceId;
- info.stage = StageConnected;
-
- if( connectionType == NewInstance ||
- ( connectionType == SecondaryInstance &&
- options & SingleApplication::Mode::SecondaryNotification ) )
- {
- Q_EMIT q->instanceStarted();
- }
-
- if (sock->bytesAvailable() > 0){
- Q_EMIT this->slotDataAvailable( sock, instanceId );
- }
-}
-
-void SingleApplicationPrivate::slotDataAvailable( QLocalSocket *dataSocket, quint32 instanceId )
-{
- Q_Q(SingleApplication);
- Q_EMIT q->receivedMessage( instanceId, dataSocket->readAll() );
-}
-
-void SingleApplicationPrivate::slotClientConnectionClosed( QLocalSocket *closedSocket, quint32 instanceId )
-{
- if( closedSocket->bytesAvailable() > 0 )
- Q_EMIT slotDataAvailable( closedSocket, instanceId );
-}
-
-void SingleApplicationPrivate::randomSleep()
-{
-#if QT_VERSION >= QT_VERSION_CHECK( 5, 10, 0 )
- QThread::msleep( QRandomGenerator::global()->bounded( 8u, 18u ));
-#else
- qsrand( QDateTime::currentMSecsSinceEpoch() % std::numeric_limits::max() );
- QThread::msleep( 8 + static_cast ( static_cast ( qrand() ) / RAND_MAX * 10 ));
-#endif
-}
-
-void SingleApplicationPrivate::addAppData(const QString &data)
-{
- appDataList.push_back(data);
-}
-
-QStringList SingleApplicationPrivate::appData() const
-{
- return appDataList;
-}
diff --git a/client/3rd/SingleApplication/singleapplication_p.h b/client/3rd/SingleApplication/singleapplication_p.h
deleted file mode 100644
index c49a46dd..00000000
--- a/client/3rd/SingleApplication/singleapplication_p.h
+++ /dev/null
@@ -1,104 +0,0 @@
-// The MIT License (MIT)
-//
-// Copyright (c) Itay Grudev 2015 - 2020
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to deal
-// in the Software without restriction, including without limitation the rights
-// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-// THE SOFTWARE.
-
-//
-// W A R N I N G !!!
-// -----------------
-//
-// This file is not part of the SingleApplication API. It is used purely as an
-// implementation detail. This header file may change from version to
-// version without notice, or may even be removed.
-//
-
-#ifndef SINGLEAPPLICATION_P_H
-#define SINGLEAPPLICATION_P_H
-
-#include
-#include
-#include
-#include "singleapplication.h"
-
-struct InstancesInfo {
- bool primary;
- quint32 secondary;
- qint64 primaryPid;
- char primaryUser[128];
- quint16 checksum; // Must be the last field
-};
-
-struct ConnectionInfo {
- qint64 msgLen = 0;
- quint32 instanceId = 0;
- quint8 stage = 0;
-};
-
-class SingleApplicationPrivate : public QObject {
-Q_OBJECT
-public:
- enum ConnectionType : quint8 {
- InvalidConnection = 0,
- NewInstance = 1,
- SecondaryInstance = 2,
- Reconnect = 3
- };
- enum ConnectionStage : quint8 {
- StageHeader = 0,
- StageBody = 1,
- StageConnected = 2,
- };
- Q_DECLARE_PUBLIC(SingleApplication)
-
- SingleApplicationPrivate( SingleApplication *q_ptr );
- ~SingleApplicationPrivate() override;
-
- static QString getUsername();
- void genBlockServerName();
- void initializeMemoryBlock() const;
- void startPrimary();
- void startSecondary();
- bool connectToPrimary( int msecs, ConnectionType connectionType );
- quint16 blockChecksum() const;
- qint64 primaryPid() const;
- QString primaryUser() const;
- void readInitMessageHeader(QLocalSocket *socket);
- void readInitMessageBody(QLocalSocket *socket);
- static void randomSleep();
- void addAppData(const QString &data);
- QStringList appData() const;
-
- SingleApplication *q_ptr;
- QSharedMemory *memory;
- QLocalSocket *socket;
- QLocalServer *server;
- quint32 instanceNumber;
- QString blockServerName;
- SingleApplication::Options options;
- QMap connectionMap;
- QStringList appDataList;
-
-public Q_SLOTS:
- void slotConnectionEstablished();
- void slotDataAvailable( QLocalSocket*, quint32 );
- void slotClientConnectionClosed( QLocalSocket*, quint32 );
-};
-
-#endif // SINGLEAPPLICATION_P_H
diff --git a/client/3rd/amneziawg-apple b/client/3rd/amneziawg-apple
index 76e7db55..811af0a8 160000
--- a/client/3rd/amneziawg-apple
+++ b/client/3rd/amneziawg-apple
@@ -1 +1 @@
-Subproject commit 76e7db556a6d7e2582f9481df91db188a46c009c
+Subproject commit 811af0a83b3faeade89a9093a588595666d32066
diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt
index 4d0c1c2a..a454142d 100644
--- a/client/CMakeLists.txt
+++ b/client/CMakeLists.txt
@@ -25,14 +25,14 @@ execute_process(
add_definitions(-DGIT_COMMIT_HASH="${GIT_COMMIT_HASH}")
add_definitions(-DPROD_AGW_PUBLIC_KEY="$ENV{PROD_AGW_PUBLIC_KEY}")
-add_definitions(-DPROD_PROXY_STORAGE_KEY="$ENV{PROD_PROXY_STORAGE_KEY}")
+add_definitions(-DPROD_S3_ENDPOINT="$ENV{PROD_S3_ENDPOINT}")
add_definitions(-DDEV_AGW_PUBLIC_KEY="$ENV{DEV_AGW_PUBLIC_KEY}")
add_definitions(-DDEV_AGW_ENDPOINT="$ENV{DEV_AGW_ENDPOINT}")
+add_definitions(-DDEV_S3_ENDPOINT="$ENV{DEV_S3_ENDPOINT}")
-if(IOS)
- set(PACKAGES ${PACKAGES} Multimedia)
-endif()
+add_definitions(-DFREE_V2_ENDPOINT="$ENV{FREE_V2_ENDPOINT}")
+add_definitions(-DPREM_V1_ENDPOINT="$ENV{PREM_V1_ENDPOINT}")
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
set(PACKAGES ${PACKAGES} Widgets)
@@ -47,10 +47,6 @@ set(LIBS ${LIBS}
Qt6::Core5Compat Qt6::Concurrent
)
-if(IOS)
- set(LIBS ${LIBS} Qt6::Multimedia)
-endif()
-
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
set(LIBS ${LIBS} Qt6::Widgets)
endif()
@@ -61,6 +57,7 @@ qt_add_executable(${PROJECT} MANUAL_FINALIZATION)
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep)
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_interface.rep)
+ qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_tun2socks.rep)
endif()
qt6_add_resources(QRC ${QRC} ${CMAKE_CURRENT_LIST_DIR}/resources.qrc)
@@ -94,11 +91,6 @@ configure_file(${CMAKE_CURRENT_LIST_DIR}/translations/translations.qrc.in ${CMAK
qt6_add_resources(QRC ${I18NQRC} ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc)
# -- i18n end
-if(IOS)
- execute_process(COMMAND bash ${CMAKE_CURRENT_LIST_DIR}/ios/scripts/openvpn.sh args
- WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
-endif()
-
set(IS_CI ${CI})
if(IS_CI)
message("Detected CI env")
@@ -108,8 +100,8 @@ if(IS_CI)
endif()
endif()
-
include(${CMAKE_CURRENT_LIST_DIR}/cmake/3rdparty.cmake)
+include(${CMAKE_CURRENT_LIST_DIR}/cmake/sources.cmake)
include_directories(
${CMAKE_CURRENT_LIST_DIR}/../ipc
@@ -118,165 +110,22 @@ include_directories(
${CMAKE_CURRENT_BINARY_DIR}
)
-configure_file(${CMAKE_CURRENT_LIST_DIR}/../version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h)
-
-set(HEADERS ${HEADERS}
- ${CMAKE_CURRENT_LIST_DIR}/migrations.h
- ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc.h
- ${CMAKE_CURRENT_LIST_DIR}/amnezia_application.h
- ${CMAKE_CURRENT_LIST_DIR}/containers/containers_defs.h
- ${CMAKE_CURRENT_LIST_DIR}/core/defs.h
- ${CMAKE_CURRENT_LIST_DIR}/core/errorstrings.h
- ${CMAKE_CURRENT_LIST_DIR}/core/scripts_registry.h
- ${CMAKE_CURRENT_LIST_DIR}/core/server_defs.h
- ${CMAKE_CURRENT_LIST_DIR}/core/controllers/apiController.h
- ${CMAKE_CURRENT_LIST_DIR}/core/controllers/serverController.h
- ${CMAKE_CURRENT_LIST_DIR}/core/controllers/vpnConfigurationController.h
- ${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.h
- ${CMAKE_CURRENT_LIST_DIR}/protocols/qml_register_protocols.h
- ${CMAKE_CURRENT_LIST_DIR}/ui/pages.h
- ${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.h
- ${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.h
- ${CMAKE_CURRENT_BINARY_DIR}/version.h
- ${CMAKE_CURRENT_LIST_DIR}/core/sshclient.h
- ${CMAKE_CURRENT_LIST_DIR}/core/networkUtilities.h
- ${CMAKE_CURRENT_LIST_DIR}/core/serialization/serialization.h
- ${CMAKE_CURRENT_LIST_DIR}/core/serialization/transfer.h
- ${CMAKE_CURRENT_LIST_DIR}/core/enums/apiEnums.h
- ${CMAKE_CURRENT_LIST_DIR}/../common/logger/logger.h
-)
-
-# Mozilla headres
-set(HEADERS ${HEADERS}
- ${CMAKE_CURRENT_LIST_DIR}/mozilla/models/server.h
- ${CMAKE_CURRENT_LIST_DIR}/mozilla/shared/ipaddress.h
- ${CMAKE_CURRENT_LIST_DIR}/mozilla/shared/leakdetector.h
- ${CMAKE_CURRENT_LIST_DIR}/mozilla/controllerimpl.h
- ${CMAKE_CURRENT_LIST_DIR}/mozilla/localsocketcontroller.h
-)
-
include_directories(mozilla)
include_directories(mozilla/shared)
include_directories(mozilla/models)
-if(NOT IOS)
- set(HEADERS ${HEADERS}
- ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.h
- )
-endif()
-
-if(NOT ANDROID)
- set(HEADERS ${HEADERS}
- ${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.h
- )
-endif()
-
-set(SOURCES ${SOURCES}
- ${CMAKE_CURRENT_LIST_DIR}/migrations.cpp
- ${CMAKE_CURRENT_LIST_DIR}/amnezia_application.cpp
- ${CMAKE_CURRENT_LIST_DIR}/containers/containers_defs.cpp
- ${CMAKE_CURRENT_LIST_DIR}/core/errorstrings.cpp
- ${CMAKE_CURRENT_LIST_DIR}/core/scripts_registry.cpp
- ${CMAKE_CURRENT_LIST_DIR}/core/server_defs.cpp
- ${CMAKE_CURRENT_LIST_DIR}/core/controllers/apiController.cpp
- ${CMAKE_CURRENT_LIST_DIR}/core/controllers/serverController.cpp
- ${CMAKE_CURRENT_LIST_DIR}/core/controllers/vpnConfigurationController.cpp
- ${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.cpp
- ${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.cpp
- ${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.cpp
- ${CMAKE_CURRENT_LIST_DIR}/core/sshclient.cpp
- ${CMAKE_CURRENT_LIST_DIR}/core/networkUtilities.cpp
- ${CMAKE_CURRENT_LIST_DIR}/core/serialization/outbound.cpp
- ${CMAKE_CURRENT_LIST_DIR}/core/serialization/inbound.cpp
- ${CMAKE_CURRENT_LIST_DIR}/core/serialization/ss.cpp
- ${CMAKE_CURRENT_LIST_DIR}/core/serialization/ssd.cpp
- ${CMAKE_CURRENT_LIST_DIR}/core/serialization/vless.cpp
- ${CMAKE_CURRENT_LIST_DIR}/core/serialization/trojan.cpp
- ${CMAKE_CURRENT_LIST_DIR}/core/serialization/vmess.cpp
- ${CMAKE_CURRENT_LIST_DIR}/core/serialization/vmess_new.cpp
- ${CMAKE_CURRENT_LIST_DIR}/../common/logger/logger.cpp
-)
-
-# Mozilla sources
-set(SOURCES ${SOURCES}
- ${CMAKE_CURRENT_LIST_DIR}/mozilla/models/server.cpp
- ${CMAKE_CURRENT_LIST_DIR}/mozilla/shared/ipaddress.cpp
- ${CMAKE_CURRENT_LIST_DIR}/mozilla/shared/leakdetector.cpp
- ${CMAKE_CURRENT_LIST_DIR}/mozilla/localsocketcontroller.cpp
-)
+configure_file(${CMAKE_CURRENT_LIST_DIR}/../version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_definitions(${PROJECT} PRIVATE "MZ_DEBUG")
endif()
-if(NOT IOS)
- set(SOURCES ${SOURCES}
- ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.cpp
- )
-endif()
-
-if(NOT ANDROID)
- set(SOURCES ${SOURCES}
- ${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.cpp
- )
-endif()
-
-file(GLOB COMMON_FILES_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/*.h)
-file(GLOB COMMON_FILES_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/*.cpp)
-
-file(GLOB_RECURSE PAGE_LOGIC_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/pages_logic/*.h)
-file(GLOB_RECURSE PAGE_LOGIC_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/pages_logic/*.cpp)
-
-file(GLOB CONFIGURATORS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/configurators/*.h)
-file(GLOB CONFIGURATORS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/configurators/*.cpp)
-
-file(GLOB UI_MODELS_H CONFIGURE_DEPENDS
- ${CMAKE_CURRENT_LIST_DIR}/ui/models/*.h
- ${CMAKE_CURRENT_LIST_DIR}/ui/models/protocols/*.h
- ${CMAKE_CURRENT_LIST_DIR}/ui/models/services/*.h
-)
-file(GLOB UI_MODELS_CPP CONFIGURE_DEPENDS
- ${CMAKE_CURRENT_LIST_DIR}/ui/models/*.cpp
- ${CMAKE_CURRENT_LIST_DIR}/ui/models/protocols/*.cpp
- ${CMAKE_CURRENT_LIST_DIR}/ui/models/services/*.cpp
-)
-
-file(GLOB UI_CONTROLLERS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/controllers/*.h)
-file(GLOB UI_CONTROLLERS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/controllers/*.cpp)
-
-set(HEADERS ${HEADERS}
- ${COMMON_FILES_H}
- ${PAGE_LOGIC_H}
- ${CONFIGURATORS_H}
- ${UI_MODELS_H}
- ${UI_CONTROLLERS_H}
-)
-set(SOURCES ${SOURCES}
- ${COMMON_FILES_CPP}
- ${PAGE_LOGIC_CPP}
- ${CONFIGURATORS_CPP}
- ${UI_MODELS_CPP}
- ${UI_CONTROLLERS_CPP}
-)
-
if(WIN32)
configure_file(
${CMAKE_CURRENT_LIST_DIR}/platforms/windows/amneziavpn.rc.in
${CMAKE_CURRENT_BINARY_DIR}/amneziavpn.rc
)
- set(HEADERS ${HEADERS}
- ${CMAKE_CURRENT_LIST_DIR}/protocols/ikev2_vpn_protocol_windows.h
- )
-
- set(SOURCES ${SOURCES}
- ${CMAKE_CURRENT_LIST_DIR}/protocols/ikev2_vpn_protocol_windows.cpp
- )
-
- set(RESOURCES ${RESOURCES}
- ${CMAKE_CURRENT_BINARY_DIR}/amneziavpn.rc
- )
-
set(LIBS ${LIBS}
user32
rasapi32
@@ -320,30 +169,6 @@ endif()
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
message("Client desktop build")
add_compile_definitions(AMNEZIA_DESKTOP)
-
- set(HEADERS ${HEADERS}
- ${CMAKE_CURRENT_LIST_DIR}/core/ipcclient.h
- ${CMAKE_CURRENT_LIST_DIR}/core/privileged_process.h
- ${CMAKE_CURRENT_LIST_DIR}/ui/systemtray_notificationhandler.h
- ${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnprotocol.h
- ${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.h
- ${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.h
- ${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.h
- ${CMAKE_CURRENT_LIST_DIR}/protocols/xrayprotocol.h
- ${CMAKE_CURRENT_LIST_DIR}/protocols/awgprotocol.h
- )
-
- set(SOURCES ${SOURCES}
- ${CMAKE_CURRENT_LIST_DIR}/core/ipcclient.cpp
- ${CMAKE_CURRENT_LIST_DIR}/core/privileged_process.cpp
- ${CMAKE_CURRENT_LIST_DIR}/ui/systemtray_notificationhandler.cpp
- ${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnprotocol.cpp
- ${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.cpp
- ${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.cpp
- ${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.cpp
- ${CMAKE_CURRENT_LIST_DIR}/protocols/xrayprotocol.cpp
- ${CMAKE_CURRENT_LIST_DIR}/protocols/awgprotocol.cpp
- )
endif()
if(ANDROID)
diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp
index 526b9fa9..f32d525a 100644
--- a/client/amnezia_application.cpp
+++ b/client/amnezia_application.cpp
@@ -2,6 +2,8 @@
#include
#include
+#include
+#include
#include
#include
#include
@@ -12,29 +14,15 @@
#include
#include "logger.h"
+#include "ui/controllers/pageController.h"
#include "ui/models/installedAppsModel.h"
#include "version.h"
#include "platforms/ios/QRCodeReaderBase.h"
-#if defined(Q_OS_ANDROID)
- #include "core/installedAppsImageProvider.h"
- #include "platforms/android/android_controller.h"
-#endif
#include "protocols/qml_register_protocols.h"
-#if defined(Q_OS_IOS)
- #include "platforms/ios/ios_controller.h"
- #include
-#endif
-
-#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv)
-#else
-AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecondary, SingleApplication::Options options, int timeout,
- const QString &userData)
- : SingleApplication(argc, argv, allowSecondary, options, timeout, userData)
-#endif
{
setQuitOnLastWindowClosed(false);
@@ -88,78 +76,12 @@ void AmneziaApplication::init()
m_vpnConnection->moveToThread(&m_vpnConnectionThread);
m_vpnConnectionThread.start();
- initModels();
- loadTranslator();
- initControllers();
-
-#ifdef Q_OS_ANDROID
- if (!AndroidController::initLogging()) {
- qFatal("Android logging initialization failed");
- }
- AndroidController::instance()->setSaveLogs(m_settings->isSaveLogs());
- 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, [this](Vpn::ConnectionState state) {
- m_connectionController->onConnectionStateChanged(state);
- if (m_vpnConnection)
- m_vpnConnection->restoreConnection();
- });
- if (!AndroidController::instance()->initialize()) {
- qFatal("Android controller initialization failed");
- }
-
- connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, [this](QString data) {
- m_pageController->goToPageHome();
- m_importController->extractConfigFromData(data);
- m_pageController->goToPageViewConfig();
- });
-
- m_engine->addImageProvider(QLatin1String("installedAppImage"), new InstalledAppsImageProvider);
-#endif
-
-#ifdef Q_OS_IOS
- IosController::Instance()->initialize();
- connect(IosController::Instance(), &IosController::importConfigFromOutside, [this](QString data) {
- m_pageController->goToPageHome();
- m_importController->extractConfigFromData(data);
- m_pageController->goToPageViewConfig();
- });
-
- connect(IosController::Instance(), &IosController::importBackupFromOutside, [this](QString filePath) {
- m_pageController->goToPageHome();
- m_pageController->goToPageSettingsBackup();
- 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
-
-#ifndef Q_OS_ANDROID
- m_notificationHandler.reset(NotificationHandler::create(nullptr));
-
- connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(),
- &NotificationHandler::setConnectionState);
-
- connect(m_notificationHandler.get(), &NotificationHandler::raiseRequested, m_pageController.get(), &PageController::raiseMainWindow);
- connect(m_notificationHandler.get(), &NotificationHandler::connectRequested, m_connectionController.get(),
- static_cast(&ConnectionController::openConnection));
- connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(),
- &ConnectionController::closeConnection);
- connect(this, &AmneziaApplication::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated);
-#endif
+ m_coreController.reset(new CoreController(m_vpnConnection, m_settings, m_engine));
m_engine->addImportPath("qrc:/ui/qml/Modules/");
m_engine->load(url);
- m_systemController->setQmlRoot(m_engine->rootObjects().value(0));
+
+ m_coreController->setQmlRoot();
bool enabled = m_settings->isSaveLogs();
#ifndef Q_OS_ANDROID
@@ -171,23 +93,13 @@ void AmneziaApplication::init()
#endif
Logger::setServiceLogsEnabled(enabled);
-#ifdef Q_OS_WIN
+#ifdef Q_OS_WIN //TODO
if (m_parser.isSet("a"))
- m_pageController->showOnStartup();
+ m_coreController->pageController()->showOnStartup();
else
- emit m_pageController->raiseMainWindow();
+ emit m_coreController->pageController()->raiseMainWindow();
#else
- m_pageController->showOnStartup();
-#endif
-
- // TODO - fix
-#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
- if (isPrimary()) {
- QObject::connect(this, &SingleApplication::instanceStarted, m_pageController.get(), [this]() {
- qDebug() << "Secondary instance started, showing this window instead";
- emit m_pageController->raiseMainWindow();
- });
- }
+ m_coreController->pageController()->showOnStartup();
#endif
// Android TextArea clipboard workaround
@@ -244,33 +156,6 @@ void AmneziaApplication::loadFonts()
QFontDatabase::addApplicationFont(":/fonts/pt-root-ui_vf.ttf");
}
-void AmneziaApplication::loadTranslator()
-{
- auto locale = m_settings->getAppLanguage();
- m_translator.reset(new QTranslator());
- updateTranslator(locale);
-}
-
-void AmneziaApplication::updateTranslator(const QLocale &locale)
-{
- if (!m_translator->isEmpty()) {
- QCoreApplication::removeTranslator(m_translator.get());
- }
-
- QString strFileName = QString(":/translations/amneziavpn") + QLatin1String("_") + locale.name() + ".qm";
- if (m_translator->load(strFileName)) {
- if (QCoreApplication::installTranslator(m_translator.get())) {
- m_settings->setAppLanguage(locale);
- }
- } else {
- m_settings->setAppLanguage(QLocale::English);
- }
-
- m_engine->retranslate();
-
- emit translationsUpdated();
-}
-
bool AmneziaApplication::parseCommands()
{
m_parser.setApplicationDescription(APPLICATION_NAME);
@@ -294,165 +179,36 @@ bool AmneziaApplication::parseCommands()
return true;
}
+#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
+void AmneziaApplication::startLocalServer()
+{
+ const QString serverName("AmneziaVPNInstance");
+ QLocalServer::removeServer(serverName);
+
+ QLocalServer *server = new QLocalServer(this);
+ server->listen(serverName);
+
+ QObject::connect(server, &QLocalServer::newConnection, this, [server, this]() {
+ if (server) {
+ QLocalSocket *clientConnection = server->nextPendingConnection();
+ clientConnection->deleteLater();
+ }
+ emit m_coreController->pageController()->raiseMainWindow(); //TODO
+ });
+}
+#endif
+
QQmlApplicationEngine *AmneziaApplication::qmlEngine() const
{
return m_engine;
}
-void AmneziaApplication::initModels()
+QNetworkAccessManager *AmneziaApplication::networkManager()
{
- m_containersModel.reset(new ContainersModel(this));
- m_engine->rootContext()->setContextProperty("ContainersModel", m_containersModel.get());
-
- m_defaultServerContainersModel.reset(new ContainersModel(this));
- m_engine->rootContext()->setContextProperty("DefaultServerContainersModel", m_defaultServerContainersModel.get());
-
- m_serversModel.reset(new ServersModel(m_settings, this));
- m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get());
- connect(m_serversModel.get(), &ServersModel::containersUpdated, m_containersModel.get(), &ContainersModel::updateModel);
- connect(m_serversModel.get(), &ServersModel::defaultServerContainersUpdated, m_defaultServerContainersModel.get(),
- &ContainersModel::updateModel);
- m_serversModel->resetModel();
-
- m_languageModel.reset(new LanguageModel(m_settings, this));
- m_engine->rootContext()->setContextProperty("LanguageModel", m_languageModel.get());
- connect(m_languageModel.get(), &LanguageModel::updateTranslations, this, &AmneziaApplication::updateTranslator);
- connect(this, &AmneziaApplication::translationsUpdated, m_languageModel.get(), &LanguageModel::translationsUpdated);
-
- m_sitesModel.reset(new SitesModel(m_settings, this));
- m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get());
-
- m_appSplitTunnelingModel.reset(new AppSplitTunnelingModel(m_settings, this));
- m_engine->rootContext()->setContextProperty("AppSplitTunnelingModel", m_appSplitTunnelingModel.get());
-
- m_protocolsModel.reset(new ProtocolsModel(m_settings, this));
- m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get());
-
- m_openVpnConfigModel.reset(new OpenVpnConfigModel(this));
- m_engine->rootContext()->setContextProperty("OpenVpnConfigModel", m_openVpnConfigModel.get());
-
- m_shadowSocksConfigModel.reset(new ShadowSocksConfigModel(this));
- m_engine->rootContext()->setContextProperty("ShadowSocksConfigModel", m_shadowSocksConfigModel.get());
-
- m_cloakConfigModel.reset(new CloakConfigModel(this));
- m_engine->rootContext()->setContextProperty("CloakConfigModel", m_cloakConfigModel.get());
-
- m_wireGuardConfigModel.reset(new WireGuardConfigModel(this));
- m_engine->rootContext()->setContextProperty("WireGuardConfigModel", m_wireGuardConfigModel.get());
-
- m_awgConfigModel.reset(new AwgConfigModel(this));
- m_engine->rootContext()->setContextProperty("AwgConfigModel", m_awgConfigModel.get());
-
- m_xrayConfigModel.reset(new XrayConfigModel(this));
- m_engine->rootContext()->setContextProperty("XrayConfigModel", m_xrayConfigModel.get());
-
-#ifdef Q_OS_WINDOWS
- m_ikev2ConfigModel.reset(new Ikev2ConfigModel(this));
- m_engine->rootContext()->setContextProperty("Ikev2ConfigModel", m_ikev2ConfigModel.get());
-#endif
-
- m_sftpConfigModel.reset(new SftpConfigModel(this));
- m_engine->rootContext()->setContextProperty("SftpConfigModel", m_sftpConfigModel.get());
-
- m_socks5ConfigModel.reset(new Socks5ProxyConfigModel(this));
- m_engine->rootContext()->setContextProperty("Socks5ProxyConfigModel", m_socks5ConfigModel.get());
-
- m_clientManagementModel.reset(new ClientManagementModel(m_settings, this));
- m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get());
- connect(m_clientManagementModel.get(), &ClientManagementModel::adminConfigRevoked, m_serversModel.get(),
- &ServersModel::clearCachedProfile);
-
- m_apiServicesModel.reset(new ApiServicesModel(this));
- m_engine->rootContext()->setContextProperty("ApiServicesModel", m_apiServicesModel.get());
-
- m_apiCountryModel.reset(new ApiCountryModel(this));
- m_engine->rootContext()->setContextProperty("ApiCountryModel", m_apiCountryModel.get());
- connect(m_serversModel.get(), &ServersModel::updateApiLanguageModel, this, [this]() {
- m_apiCountryModel->updateModel(m_serversModel->getProcessedServerData("apiAvailableCountries").toJsonArray(),
- m_serversModel->getProcessedServerData("apiServerCountryCode").toString());
- });
- connect(m_serversModel.get(), &ServersModel::updateApiServicesModel, this,
- [this]() { m_apiServicesModel->updateModel(m_serversModel->getProcessedServerData("apiConfig").toJsonObject()); });
+ return m_nam;
}
-void AmneziaApplication::initControllers()
+QClipboard *AmneziaApplication::getClipboard()
{
- m_connectionController.reset(
- new ConnectionController(m_serversModel, m_containersModel, m_clientManagementModel, m_vpnConnection, m_settings));
- m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get());
-
- connect(m_connectionController.get(), qOverload(&ConnectionController::connectionErrorOccurred), this,
- [this](const QString &errorMessage) {
- emit m_pageController->showErrorMessage(errorMessage);
- emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
- });
-
- connect(m_connectionController.get(), qOverload(&ConnectionController::connectionErrorOccurred), this,
- [this](ErrorCode errorCode) {
- emit m_pageController->showErrorMessage(errorCode);
- emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
- });
-
- connect(m_connectionController.get(), &ConnectionController::connectButtonClicked, m_connectionController.get(),
- &ConnectionController::toggleConnection, Qt::QueuedConnection);
-
- m_pageController.reset(new PageController(m_serversModel, m_settings));
- m_engine->rootContext()->setContextProperty("PageController", m_pageController.get());
-
- m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_clientManagementModel,
- m_apiServicesModel, m_settings));
- m_engine->rootContext()->setContextProperty("InstallController", m_installController.get());
- connect(m_installController.get(), &InstallController::passphraseRequestStarted, m_pageController.get(),
- &PageController::showPassphraseRequestDrawer);
- connect(m_pageController.get(), &PageController::passphraseRequestDrawerClosed, m_installController.get(),
- &InstallController::setEncryptedPassphrase);
- connect(m_installController.get(), &InstallController::currentContainerUpdated, m_connectionController.get(),
- &ConnectionController::onCurrentContainerUpdated);
-
- connect(m_installController.get(), &InstallController::updateServerFromApiFinished, this, [this]() {
- disconnect(m_reloadConfigErrorOccurredConnection);
- emit m_connectionController->configFromApiUpdated();
- });
-
- connect(m_connectionController.get(), &ConnectionController::updateApiConfigFromGateway, this, [this]() {
- m_reloadConfigErrorOccurredConnection = connect(
- m_installController.get(), qOverload(&InstallController::installationErrorOccurred), this,
- [this]() { emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); },
- static_cast(Qt::AutoConnection || Qt::SingleShotConnection));
- m_installController->updateServiceFromApi(m_serversModel->getDefaultServerIndex(), "", "");
- });
-
- connect(m_connectionController.get(), &ConnectionController::updateApiConfigFromTelegram, this, [this]() {
- m_reloadConfigErrorOccurredConnection = connect(
- m_installController.get(), qOverload(&InstallController::installationErrorOccurred), this,
- [this]() { emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); },
- static_cast(Qt::AutoConnection || Qt::SingleShotConnection));
- m_serversModel->removeApiConfig(m_serversModel->getDefaultServerIndex());
- m_installController->updateServiceFromTelegram(m_serversModel->getDefaultServerIndex());
- });
-
- connect(this, &AmneziaApplication::translationsUpdated, m_connectionController.get(), &ConnectionController::onTranslationsUpdated);
-
- m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings));
- m_engine->rootContext()->setContextProperty("ImportController", m_importController.get());
-
- m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_clientManagementModel, m_settings));
- m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get());
-
- m_settingsController.reset(
- new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, m_appSplitTunnelingModel, m_settings));
- m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get());
- if (m_settingsController->isAutoConnectEnabled() && m_serversModel->getDefaultServerIndex() >= 0) {
- QTimer::singleShot(1000, this, [this]() { m_connectionController->openConnection(); });
- }
- connect(m_settingsController.get(), &SettingsController::amneziaDnsToggled, m_serversModel.get(), &ServersModel::toggleAmneziaDns);
-
- m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel));
- m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get());
-
- m_appSplitTunnelingController.reset(new AppSplitTunnelingController(m_settings, m_appSplitTunnelingModel));
- m_engine->rootContext()->setContextProperty("AppSplitTunnelingController", m_appSplitTunnelingController.get());
-
- m_systemController.reset(new SystemController(m_settings));
- m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get());
+ return this->clipboard();
}
diff --git a/client/amnezia_application.h b/client/amnezia_application.h
index 6fb61f44..ea5f6f52 100644
--- a/client/amnezia_application.h
+++ b/client/amnezia_application.h
@@ -11,135 +11,55 @@
#else
#include
#endif
+#include
+#include "core/controllers/coreController.h"
#include "settings.h"
#include "vpnconnection.h"
-#include "ui/controllers/connectionController.h"
-#include "ui/controllers/exportController.h"
-#include "ui/controllers/importController.h"
-#include "ui/controllers/installController.h"
-#include "ui/controllers/pageController.h"
-#include "ui/controllers/settingsController.h"
-#include "ui/controllers/sitesController.h"
-#include "ui/controllers/systemController.h"
-#include "ui/controllers/appSplitTunnelingController.h"
-#include "ui/models/containers_model.h"
-#include "ui/models/languageModel.h"
-#include "ui/models/protocols/cloakConfigModel.h"
-#ifndef Q_OS_ANDROID
- #include "ui/notificationhandler.h"
-#endif
-#ifdef Q_OS_WINDOWS
- #include "ui/models/protocols/ikev2ConfigModel.h"
-#endif
-#include "ui/models/protocols/awgConfigModel.h"
-#include "ui/models/protocols/openvpnConfigModel.h"
-#include "ui/models/protocols/shadowsocksConfigModel.h"
-#include "ui/models/protocols/wireguardConfigModel.h"
-#include "ui/models/protocols/xrayConfigModel.h"
-#include "ui/models/protocols_model.h"
-#include "ui/models/servers_model.h"
-#include "ui/models/services/sftpConfigModel.h"
-#include "ui/models/services/socks5ProxyConfigModel.h"
-#include "ui/models/sites_model.h"
-#include "ui/models/clientManagementModel.h"
-#include "ui/models/appSplitTunnelingModel.h"
-#include "ui/models/apiServicesModel.h"
-#include "ui/models/apiCountryModel.h"
-
#define amnApp (static_cast(QCoreApplication::instance()))
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#define AMNEZIA_BASE_CLASS QGuiApplication
#else
- #define AMNEZIA_BASE_CLASS SingleApplication
- #define QAPPLICATION_CLASS QApplication
- #include "singleapplication.h"
+ #define AMNEZIA_BASE_CLASS QApplication
#endif
class AmneziaApplication : public AMNEZIA_BASE_CLASS
{
Q_OBJECT
public:
-#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
AmneziaApplication(int &argc, char *argv[]);
-#else
- AmneziaApplication(int &argc, char *argv[], bool allowSecondary = false,
- SingleApplication::Options options = SingleApplication::User, int timeout = 1000,
- const QString &userData = {});
-#endif
virtual ~AmneziaApplication();
void init();
void registerTypes();
void loadFonts();
- void loadTranslator();
- void updateTranslator(const QLocale &locale);
bool parseCommands();
- QQmlApplicationEngine *qmlEngine() const;
- QNetworkAccessManager *manager() { return m_nam; }
+#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
+ void startLocalServer();
+#endif
-signals:
- void translationsUpdated();
+ QQmlApplicationEngine *qmlEngine() const;
+ QNetworkAccessManager *networkManager();
+ QClipboard *getClipboard();
private:
- void initModels();
- void initControllers();
-
QQmlApplicationEngine *m_engine {};
std::shared_ptr m_settings;
+ QScopedPointer m_coreController;
+
QSharedPointer m_containerProps;
QSharedPointer m_protocolProps;
- QSharedPointer m_translator;
QCommandLineParser m_parser;
- QSharedPointer m_containersModel;
- QSharedPointer m_defaultServerContainersModel;
- QSharedPointer m_serversModel;
- QSharedPointer m_languageModel;
- QSharedPointer m_protocolsModel;
- QSharedPointer m_sitesModel;
- QSharedPointer m_appSplitTunnelingModel;
- QSharedPointer m_clientManagementModel;
- QSharedPointer m_apiServicesModel;
- QSharedPointer m_apiCountryModel;
-
- QScopedPointer m_openVpnConfigModel;
- QScopedPointer m_shadowSocksConfigModel;
- QScopedPointer m_cloakConfigModel;
- QScopedPointer m_xrayConfigModel;
- QScopedPointer m_wireGuardConfigModel;
- QScopedPointer m_awgConfigModel;
-#ifdef Q_OS_WINDOWS
- QScopedPointer m_ikev2ConfigModel;
-#endif
-
- QScopedPointer m_sftpConfigModel;
- QScopedPointer m_socks5ConfigModel;
-
QSharedPointer m_vpnConnection;
QThread m_vpnConnectionThread;
-#ifndef Q_OS_ANDROID
- QScopedPointer m_notificationHandler;
-#endif
-
- QScopedPointer m_connectionController;
- QScopedPointer m_pageController;
- QScopedPointer m_installController;
- QScopedPointer m_importController;
- QScopedPointer m_exportController;
- QScopedPointer m_settingsController;
- QScopedPointer m_sitesController;
- QScopedPointer m_systemController;
- QScopedPointer m_appSplitTunnelingController;
QNetworkAccessManager *m_nam;
-
- QMetaObject::Connection m_reloadConfigErrorOccurredConnection;
};
#endif // AMNEZIA_APPLICATION_H
diff --git a/client/android/AndroidManifest.xml b/client/android/AndroidManifest.xml
index 179def86..b28f754b 100644
--- a/client/android/AndroidManifest.xml
+++ b/client/android/AndroidManifest.xml
@@ -20,7 +20,7 @@
-
+
@@ -91,6 +91,13 @@
android:exported="false"
android:theme="@style/Translucent" />
+
+
Unit): AwgConfig = Builder().apply(block).build()
- }
-}
diff --git a/client/android/cloak/src/main/kotlin/Cloak.kt b/client/android/cloak/src/main/kotlin/Cloak.kt
index 18a5e6c7..d408fb19 100644
--- a/client/android/cloak/src/main/kotlin/Cloak.kt
+++ b/client/android/cloak/src/main/kotlin/Cloak.kt
@@ -3,10 +3,16 @@ package org.amnezia.vpn.protocol.cloak
import android.util.Base64
import net.openvpn.ovpn3.ClientAPI_Config
import org.amnezia.vpn.protocol.openvpn.OpenVpn
+import org.amnezia.vpn.util.LibraryLoader.loadSharedLibrary
import org.json.JSONObject
class Cloak : OpenVpn() {
+ override fun internalInit() {
+ super.internalInit()
+ if (!isInitialized) loadSharedLibrary(context, "ck-ovpn-plugin")
+ }
+
override fun parseConfig(config: JSONObject): ClientAPI_Config {
val openVpnConfig = ClientAPI_Config()
diff --git a/client/android/gradle.properties b/client/android/gradle.properties
index 5a27838c..ce651e1c 100644
--- a/client/android/gradle.properties
+++ b/client/android/gradle.properties
@@ -33,7 +33,7 @@ android.library.defaults.buildfeatures.androidresources=false
# For development copy and set local values for these parameters in local.properties
#androidCompileSdkVersion=android-34
#androidBuildToolsVersion=34.0.0
-#qtMinSdkVersion=24
+#qtMinSdkVersion=26
#qtTargetSdkVersion=34
#androidNdkVersion=26.1.10909125
#qtTargetAbiList=x86_64
diff --git a/client/android/openvpn/src/main/kotlin/org/amnezia/vpn/protocol/openvpn/OpenVpn.kt b/client/android/openvpn/src/main/kotlin/org/amnezia/vpn/protocol/openvpn/OpenVpn.kt
index fa0e19f1..22fe35cd 100644
--- a/client/android/openvpn/src/main/kotlin/org/amnezia/vpn/protocol/openvpn/OpenVpn.kt
+++ b/client/android/openvpn/src/main/kotlin/org/amnezia/vpn/protocol/openvpn/OpenVpn.kt
@@ -11,6 +11,7 @@ import org.amnezia.vpn.protocol.Protocol
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
import org.amnezia.vpn.protocol.Statistics
import org.amnezia.vpn.protocol.VpnStartException
+import org.amnezia.vpn.util.LibraryLoader.loadSharedLibrary
import org.amnezia.vpn.util.net.InetNetwork
import org.amnezia.vpn.util.net.getLocalNetworks
import org.amnezia.vpn.util.net.parseInetAddress
@@ -34,7 +35,10 @@ open class OpenVpn : Protocol() {
}
override fun internalInit() {
- if (!isInitialized) loadSharedLibrary(context, "ovpn3")
+ if (!isInitialized) {
+ loadSharedLibrary(context, "ovpn3")
+ loadSharedLibrary(context, "ovpnutil")
+ }
if (this::scope.isInitialized) {
scope.cancel()
}
diff --git a/client/android/protocolApi/src/main/kotlin/Exceptions.kt b/client/android/protocolApi/src/main/kotlin/Exceptions.kt
index 739a327c..b80648b0 100644
--- a/client/android/protocolApi/src/main/kotlin/Exceptions.kt
+++ b/client/android/protocolApi/src/main/kotlin/Exceptions.kt
@@ -2,7 +2,6 @@ package org.amnezia.vpn.protocol
sealed class ProtocolException(message: String? = null, cause: Throwable? = null) : Exception(message, cause)
-class LoadLibraryException(message: String? = null, cause: Throwable? = null) : ProtocolException(message, cause)
class BadConfigException(message: String? = null, cause: Throwable? = null) : ProtocolException(message, cause)
class VpnStartException(message: String? = null, cause: Throwable? = null) : ProtocolException(message, cause)
diff --git a/client/android/protocolApi/src/main/kotlin/Protocol.kt b/client/android/protocolApi/src/main/kotlin/Protocol.kt
index 24cbc595..6e682aa4 100644
--- a/client/android/protocolApi/src/main/kotlin/Protocol.kt
+++ b/client/android/protocolApi/src/main/kotlin/Protocol.kt
@@ -1,6 +1,5 @@
package org.amnezia.vpn.protocol
-import android.annotation.SuppressLint
import android.content.Context
import android.net.IpPrefix
import android.net.VpnService
@@ -8,9 +7,6 @@ import android.net.VpnService.Builder
import android.os.Build
import android.system.OsConstants
import androidx.annotation.RequiresApi
-import java.io.File
-import java.io.FileOutputStream
-import java.util.zip.ZipFile
import kotlinx.coroutines.flow.MutableStateFlow
import org.amnezia.vpn.util.Log
import org.amnezia.vpn.util.net.InetNetwork
@@ -158,60 +154,6 @@ abstract class Protocol {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
vpnBuilder.setMetered(false)
}
-
- companion object {
- private fun extractLibrary(context: Context, libraryName: String, destination: File): Boolean {
- Log.d(TAG, "Extracting library: $libraryName")
- val apks = hashSetOf()
- context.applicationInfo.run {
- sourceDir?.let { apks += it }
- splitSourceDirs?.let { apks += it }
- }
- for (abi in Build.SUPPORTED_ABIS) {
- for (apk in apks) {
- ZipFile(File(apk), ZipFile.OPEN_READ).use { zipFile ->
- val mappedName = System.mapLibraryName(libraryName)
- val libraryZipPath = listOf("lib", abi, mappedName).joinToString(File.separator)
- val zipEntry = zipFile.getEntry(libraryZipPath)
- zipEntry?.let {
- Log.d(TAG, "Extracting apk:/$libraryZipPath to ${destination.absolutePath}")
- FileOutputStream(destination).use { outStream ->
- zipFile.getInputStream(zipEntry).use { inStream ->
- inStream.copyTo(outStream, 32 * 1024)
- outStream.fd.sync()
- }
- }
- }
- return true
- }
- }
- }
- return false
- }
-
- @SuppressLint("UnsafeDynamicallyLoadedCode")
- fun loadSharedLibrary(context: Context, libraryName: String) {
- Log.d(TAG, "Loading library: $libraryName")
- try {
- System.loadLibrary(libraryName)
- return
- } catch (_: UnsatisfiedLinkError) {
- Log.d(TAG, "Failed to load library, try to extract it from apk")
- }
- var tempFile: File? = null
- try {
- tempFile = File.createTempFile("lib", ".so", context.codeCacheDir)
- if (extractLibrary(context, libraryName, tempFile)) {
- System.load(tempFile.absolutePath)
- return
- }
- } catch (e: Exception) {
- throw LoadLibraryException("Failed to load library apk: $libraryName", e)
- } finally {
- tempFile?.delete()
- }
- }
- }
}
private fun VpnService.Builder.addAddress(addr: InetNetwork) = addAddress(addr.address, addr.mask)
diff --git a/client/android/res/mipmap-anydpi-v26/ic_banner.xml b/client/android/res/mipmap-anydpi-v26/ic_banner.xml
deleted file mode 100644
index cf3108b3..00000000
--- a/client/android/res/mipmap-anydpi-v26/ic_banner.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/client/android/res/mipmap-hdpi/ic_banner.png b/client/android/res/mipmap-hdpi/ic_banner.png
new file mode 100644
index 00000000..a444777f
Binary files /dev/null and b/client/android/res/mipmap-hdpi/ic_banner.png differ
diff --git a/client/android/res/mipmap-mdpi/ic_banner.png b/client/android/res/mipmap-mdpi/ic_banner.png
new file mode 100644
index 00000000..b9ad1db7
Binary files /dev/null and b/client/android/res/mipmap-mdpi/ic_banner.png differ
diff --git a/client/android/res/mipmap-xhdpi/ic_banner_foreground.png b/client/android/res/mipmap-xhdpi/ic_banner_foreground.png
deleted file mode 100644
index 1c21902e..00000000
Binary files a/client/android/res/mipmap-xhdpi/ic_banner_foreground.png and /dev/null differ
diff --git a/client/android/res/values-ru/strings.xml b/client/android/res/values-ru/strings.xml
index 8bdabfc0..5e35bba5 100644
--- a/client/android/res/values-ru/strings.xml
+++ b/client/android/res/values-ru/strings.xml
@@ -23,4 +23,6 @@
Настройки уведомлений
Для показа уведомлений необходимо включить уведомления в системных настройках
Открыть настройки уведомлений
+
+ Пожалуйста, установите приложение для просмотра файлов
\ No newline at end of file
diff --git a/client/android/res/values/ic_banner_background.xml b/client/android/res/values/ic_banner_background.xml
deleted file mode 100644
index fa6f91c7..00000000
--- a/client/android/res/values/ic_banner_background.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
- #1E1E1F
-
\ No newline at end of file
diff --git a/client/android/res/values/libs.xml b/client/android/res/values/libs.xml
index fe63866f..3ccf1d80 100644
--- a/client/android/res/values/libs.xml
+++ b/client/android/res/values/libs.xml
@@ -3,7 +3,6 @@
-
diff --git a/client/android/res/values/strings.xml b/client/android/res/values/strings.xml
index 5251403b..bf8d76d1 100644
--- a/client/android/res/values/strings.xml
+++ b/client/android/res/values/strings.xml
@@ -23,4 +23,6 @@
Notification settings
To show notifications, you must enable notifications in the system settings
Open notification settings
+
+ Please install a file management utility to browse files
\ No newline at end of file
diff --git a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt
index 9d1c31cb..c6db5e29 100644
--- a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt
+++ b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt
@@ -4,6 +4,7 @@ import android.Manifest
import android.annotation.SuppressLint
import android.app.AlertDialog
import android.app.NotificationManager
+import android.content.ActivityNotFoundException
import android.content.BroadcastReceiver
import android.content.ComponentName
import android.content.Intent
@@ -12,6 +13,7 @@ import android.content.Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
import android.content.ServiceConnection
import android.content.pm.PackageManager
import android.graphics.Bitmap
+import android.net.Uri
import android.net.VpnService
import android.os.Build
import android.os.Bundle
@@ -20,7 +22,13 @@ import android.os.IBinder
import android.os.Looper
import android.os.Message
import android.os.Messenger
+import android.os.ParcelFileDescriptor
+import android.os.SystemClock
+import android.provider.OpenableColumns
import android.provider.Settings
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewGroup
import android.view.WindowManager.LayoutParams
import android.webkit.MimeTypeMap
import android.widget.Toast
@@ -29,6 +37,7 @@ import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import java.io.IOException
import kotlin.LazyThreadSafetyMode.NONE
+import kotlin.coroutines.CoroutineContext
import kotlin.text.RegexOption.IGNORE_CASE
import AppListProvider
import kotlinx.coroutines.CompletableDeferred
@@ -43,6 +52,7 @@ import kotlinx.coroutines.withContext
import org.amnezia.vpn.protocol.getStatistics
import org.amnezia.vpn.protocol.getStatus
import org.amnezia.vpn.qt.QtAndroidController
+import org.amnezia.vpn.util.LibraryLoader.loadSharedLibrary
import org.amnezia.vpn.util.Log
import org.amnezia.vpn.util.Prefs
import org.json.JSONException
@@ -69,6 +79,7 @@ class AmneziaActivity : QtActivity() {
private var isInBoundState = false
private var notificationStateReceiver: BroadcastReceiver? = null
private lateinit var vpnServiceMessenger: IpcMessenger
+ private var pfd: ParcelFileDescriptor? = null
private val actionResultHandlers = mutableMapOf()
private val permissionRequestHandlers = mutableMapOf()
@@ -157,7 +168,8 @@ class AmneziaActivity : QtActivity() {
*/
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- Log.d(TAG, "Create Amnezia activity: $intent")
+ Log.d(TAG, "Create Amnezia activity")
+ loadLibs()
window.apply {
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
statusBarColor = getColor(R.color.black)
@@ -179,6 +191,17 @@ class AmneziaActivity : QtActivity() {
runBlocking { vpnProto = proto.await() }
}
+ private fun loadLibs() {
+ listOf(
+ "rsapss",
+ "crypto_3",
+ "ssl_3",
+ "ssh"
+ ).forEach {
+ loadSharedLibrary(this.applicationContext, it)
+ }
+ }
+
private fun registerBroadcastReceivers() {
notificationStateReceiver = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
registerBroadcastReceiver(
@@ -187,7 +210,7 @@ class AmneziaActivity : QtActivity() {
NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED
)
) {
- Log.d(
+ Log.v(
TAG, "Notification state changed: ${it?.action}, blocked = " +
"${it?.getBooleanExtra(NotificationManager.EXTRA_BLOCKED_STATE, false)}"
)
@@ -201,7 +224,7 @@ class AmneziaActivity : QtActivity() {
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
- Log.d(TAG, "onNewIntent: $intent")
+ Log.v(TAG, "onNewIntent: $intent")
intent?.let(::processIntent)
}
@@ -390,7 +413,7 @@ class AmneziaActivity : QtActivity() {
@MainThread
private fun startVpn(vpnConfig: String) {
getVpnProto(vpnConfig)?.let { proto ->
- Log.d(TAG, "Proto from config: $proto, current proto: $vpnProto")
+ Log.v(TAG, "Proto from config: $proto, current proto: $vpnProto")
if (isServiceConnected) {
if (proto.serviceClass == vpnProto?.serviceClass) {
vpnProto = proto
@@ -500,21 +523,25 @@ class AmneziaActivity : QtActivity() {
type = "text/*"
putExtra(Intent.EXTRA_TITLE, fileName)
}.also {
- startActivityForResult(it, CREATE_FILE_ACTION_CODE, ActivityResultHandler(
- onSuccess = {
- it?.data?.let { uri ->
- Log.d(TAG, "Save file to $uri")
- try {
- contentResolver.openOutputStream(uri)?.use { os ->
- os.bufferedWriter().use { it.write(data) }
+ try {
+ startActivityForResult(it, CREATE_FILE_ACTION_CODE, ActivityResultHandler(
+ onSuccess = {
+ it?.data?.let { uri ->
+ Log.v(TAG, "Save file to $uri")
+ try {
+ contentResolver.openOutputStream(uri)?.use { os ->
+ os.bufferedWriter().use { it.write(data) }
+ }
+ } catch (e: IOException) {
+ Log.e(TAG, "Failed to save file $uri: $e")
+ // todo: send error to Qt
}
- } catch (e: IOException) {
- Log.e(TAG, "Failed to save file $uri: $e")
- // todo: send error to Qt
}
}
- }
- ))
+ ))
+ } catch (_: ActivityNotFoundException) {
+ Toast.makeText(this@AmneziaActivity, "Unsupported", Toast.LENGTH_LONG).show()
+ }
}
}
}
@@ -523,46 +550,115 @@ class AmneziaActivity : QtActivity() {
fun openFile(filter: String?) {
Log.v(TAG, "Open file with filter: $filter")
mainScope.launch {
- val mimeTypes = if (!filter.isNullOrEmpty()) {
- val extensionRegex = "\\*\\.([a-z0-9]+)".toRegex(IGNORE_CASE)
- val mime = MimeTypeMap.getSingleton()
- extensionRegex.findAll(filter).map {
- it.groups[1]?.value?.let { mime.getMimeTypeFromExtension(it) } ?: "*/*"
- }.toSet()
- } else emptySet()
+ val intent = if (!isOnTv()) {
+ val mimeTypes = if (!filter.isNullOrEmpty()) {
+ val extensionRegex = "\\*\\.([a-z0-9]+)".toRegex(IGNORE_CASE)
+ val mime = MimeTypeMap.getSingleton()
+ extensionRegex.findAll(filter).map {
+ it.groups[1]?.value?.let { mime.getMimeTypeFromExtension(it) } ?: "*/*"
+ }.toSet()
+ } else emptySet()
- Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
- addCategory(Intent.CATEGORY_OPENABLE)
- Log.v(TAG, "File mimyType filter: $mimeTypes")
- if ("*/*" in mimeTypes) {
- type = "*/*"
- } else {
- when (mimeTypes.size) {
- 1 -> type = mimeTypes.first()
+ Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
+ addCategory(Intent.CATEGORY_OPENABLE)
+ Log.v(TAG, "File mimyType filter: $mimeTypes")
+ if ("*/*" in mimeTypes) {
+ type = "*/*"
+ } else {
+ when (mimeTypes.size) {
+ 1 -> type = mimeTypes.first()
- in 2..Int.MAX_VALUE -> {
- type = "*/*"
- putExtra(EXTRA_MIME_TYPES, mimeTypes.toTypedArray())
+ in 2..Int.MAX_VALUE -> {
+ type = "*/*"
+ putExtra(EXTRA_MIME_TYPES, mimeTypes.toTypedArray())
+ }
+
+ else -> type = "*/*"
}
-
- else -> type = "*/*"
}
}
- }.also {
- startActivityForResult(it, OPEN_FILE_ACTION_CODE, ActivityResultHandler(
+ } else {
+ Intent(this@AmneziaActivity, TvFilePicker::class.java)
+ }
+
+ try {
+ startActivityForResult(intent, OPEN_FILE_ACTION_CODE, ActivityResultHandler(
onAny = {
- val uri = it?.data?.toString() ?: ""
- Log.d(TAG, "Open file: $uri")
+ if (isOnTv() && it?.hasExtra("activityNotFound") == true) {
+ showNoFileBrowserAlertDialog()
+ }
+ val uri = it?.data?.apply {
+ grantUriPermission(packageName, this, Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ }?.toString() ?: ""
+ Log.v(TAG, "Open file: $uri")
mainScope.launch {
qtInitialized.await()
QtAndroidController.onFileOpened(uri)
}
}
))
+ } catch (_: ActivityNotFoundException) {
+ showNoFileBrowserAlertDialog()
+ mainScope.launch {
+ qtInitialized.await()
+ QtAndroidController.onFileOpened("")
+ }
}
}
}
+ private fun showNoFileBrowserAlertDialog() {
+ AlertDialog.Builder(this)
+ .setMessage(R.string.tvNoFileBrowser)
+ .setCancelable(false)
+ .setPositiveButton(android.R.string.ok) { _, _ ->
+ try {
+ startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("market://webstoreredirect")))
+ } catch (_: Throwable) {}
+ }
+ .show()
+ }
+
+ @Suppress("unused")
+ fun getFd(fileName: String): Int {
+ Log.v(TAG, "Get fd for $fileName")
+ return blockingCall {
+ try {
+ pfd = contentResolver.openFileDescriptor(Uri.parse(fileName), "r")
+ pfd?.fd ?: -1
+ } catch (e: Exception) {
+ Log.e(TAG, "Failed to get fd: $e")
+ -1
+ }
+ }
+ }
+
+ @Suppress("unused")
+ fun closeFd() {
+ Log.v(TAG, "Close fd")
+ mainScope.launch {
+ pfd?.close()
+ pfd = null
+ }
+ }
+
+ @Suppress("unused")
+ fun getFileName(uri: String): String {
+ Log.v(TAG, "Get file name for uri: $uri")
+ return blockingCall {
+ try {
+ contentResolver.query(Uri.parse(uri), arrayOf(OpenableColumns.DISPLAY_NAME), null, null, null)?.use { cursor ->
+ if (cursor.moveToFirst() && !cursor.isNull(0)) {
+ return@blockingCall cursor.getString(0) ?: ""
+ }
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "Failed to get file name: $e")
+ }
+ ""
+ }
+ }
+
@Suppress("unused")
@SuppressLint("UnsupportedChromeOsCameraSystemFeature")
fun isCameraPresent(): Boolean = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)
@@ -707,9 +803,121 @@ class AmneziaActivity : QtActivity() {
}
}
+ // method to workaround Qt's problem with calling the keyboard on TVs
+ @Suppress("unused")
+ fun sendTouch(x: Float, y: Float) {
+ Log.v(TAG, "Send touch: $x, $y")
+ blockingCall {
+ findQtWindow(window.decorView)?.let {
+ Log.v(TAG, "Send touch to $it")
+ it.dispatchTouchEvent(createEvent(x, y, SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN))
+ it.dispatchTouchEvent(createEvent(x, y, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP))
+ }
+ }
+ }
+
+ private fun findQtWindow(view: View): View? {
+ Log.v(TAG, "findQtWindow: process $view")
+ if (view::class.simpleName == "QtWindow") return view
+ else if (view is ViewGroup) {
+ for (i in 0 until view.childCount) {
+ val result = findQtWindow(view.getChildAt(i))
+ if (result != null) return result
+ }
+ return null
+ } else return null
+ }
+
+ private fun createEvent(x: Float, y: Float, eventTime: Long, action: Int): MotionEvent =
+ MotionEvent.obtain(
+ eventTime,
+ eventTime,
+ action,
+ 1,
+ arrayOf(MotionEvent.PointerProperties().apply {
+ id = 0
+ toolType = MotionEvent.TOOL_TYPE_FINGER
+ }),
+ arrayOf(MotionEvent.PointerCoords().apply {
+ this.x = x
+ this.y = y
+ pressure = 1f
+ size = 1f
+ }),
+ 0, 0, 1.0f, 1.0f, 0, 0, 0,0
+ )
+
+ // workaround for a bug in Qt that causes the mouse click event not to be handled
+ // also disable right-click, as it causes the application to crash
+ private var lastButtonState = 0
+ private fun MotionEvent.fixCopy(): MotionEvent = MotionEvent.obtain(
+ downTime,
+ eventTime,
+ action,
+ pointerCount,
+ (0 until pointerCount).map { i ->
+ MotionEvent.PointerProperties().apply {
+ getPointerProperties(i, this)
+ }
+ }.toTypedArray(),
+ (0 until pointerCount).map { i ->
+ MotionEvent.PointerCoords().apply {
+ getPointerCoords(i, this)
+ }
+ }.toTypedArray(),
+ metaState,
+ MotionEvent.BUTTON_PRIMARY,
+ xPrecision,
+ yPrecision,
+ deviceId,
+ edgeFlags,
+ source,
+ flags
+ )
+
+ private fun handleMouseEvent(ev: MotionEvent, superDispatch: (MotionEvent?) -> Boolean): Boolean {
+ when (ev.action) {
+ MotionEvent.ACTION_DOWN -> {
+ lastButtonState = ev.buttonState
+ if (ev.buttonState == MotionEvent.BUTTON_SECONDARY) return true
+ }
+
+ MotionEvent.ACTION_UP -> {
+ when (lastButtonState) {
+ MotionEvent.BUTTON_SECONDARY -> return true
+ MotionEvent.BUTTON_PRIMARY -> {
+ val modEvent = ev.fixCopy()
+ return superDispatch(modEvent).apply { modEvent.recycle() }
+ }
+ }
+ }
+ }
+ return superDispatch(ev)
+ }
+
+ override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
+ Log.v(TAG, "dispatchTouch: $ev")
+ if (ev != null && ev.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) {
+ return handleMouseEvent(ev) { super.dispatchTouchEvent(it) }
+ }
+ return super.dispatchTouchEvent(ev)
+ }
+
+ override fun dispatchTrackballEvent(ev: MotionEvent?): Boolean {
+ ev?.let { return handleMouseEvent(ev) { super.dispatchTrackballEvent(it) }}
+ return super.dispatchTrackballEvent(ev)
+ }
+
/**
* Utils methods
*/
+ private fun blockingCall(
+ context: CoroutineContext = Dispatchers.Main.immediate,
+ block: suspend () -> T
+ ) = runBlocking {
+ mainScope.async(context) { block() }.await()
+ }
+
companion object {
private fun actionCodeToString(actionCode: Int): String =
when (actionCode) {
diff --git a/client/android/src/org/amnezia/vpn/AmneziaVpnService.kt b/client/android/src/org/amnezia/vpn/AmneziaVpnService.kt
index 54330861..8d108bc3 100644
--- a/client/android/src/org/amnezia/vpn/AmneziaVpnService.kt
+++ b/client/android/src/org/amnezia/vpn/AmneziaVpnService.kt
@@ -22,6 +22,7 @@ import androidx.annotation.MainThread
import androidx.core.app.ServiceCompat
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
+import java.net.UnknownHostException
import java.util.concurrent.ConcurrentHashMap
import kotlin.LazyThreadSafetyMode.NONE
import kotlinx.coroutines.CoroutineExceptionHandler
@@ -40,7 +41,6 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
import org.amnezia.vpn.protocol.BadConfigException
-import org.amnezia.vpn.protocol.LoadLibraryException
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
import org.amnezia.vpn.protocol.ProtocolState.CONNECTING
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
@@ -50,6 +50,7 @@ import org.amnezia.vpn.protocol.ProtocolState.UNKNOWN
import org.amnezia.vpn.protocol.VpnException
import org.amnezia.vpn.protocol.VpnStartException
import org.amnezia.vpn.protocol.putStatus
+import org.amnezia.vpn.util.LoadLibraryException
import org.amnezia.vpn.util.Log
import org.amnezia.vpn.util.Prefs
import org.amnezia.vpn.util.net.NetworkState
@@ -127,6 +128,8 @@ open class AmneziaVpnService : VpnService() {
is LoadLibraryException -> onError("${e.message}. Caused: ${e.cause?.message}")
+ is UnknownHostException -> onError("Unknown host")
+
else -> throw e
}
}
@@ -297,7 +300,7 @@ open class AmneziaVpnService : VpnService() {
arrayOf(ACTION_CONNECT, ACTION_DISCONNECT), ContextCompat.RECEIVER_NOT_EXPORTED
) {
it?.action?.let { action ->
- Log.d(TAG, "Broadcast request received: $action")
+ Log.v(TAG, "Broadcast request received: $action")
when (action) {
ACTION_CONNECT -> connect()
ACTION_DISCONNECT -> disconnect()
@@ -314,7 +317,7 @@ open class AmneziaVpnService : VpnService() {
)
) {
val state = it?.getBooleanExtra(NotificationManager.EXTRA_BLOCKED_STATE, false)
- Log.d(TAG, "Notification state changed: ${it?.action}, blocked = $state")
+ Log.v(TAG, "Notification state changed: ${it?.action}, blocked = $state")
if (state == false) {
enableNotification()
} else {
@@ -447,7 +450,7 @@ open class AmneziaVpnService : VpnService() {
serviceNotification.isNotificationEnabled() &&
getSystemService()?.isInteractive != false
) {
- Log.d(TAG, "Launch traffic stats update")
+ Log.v(TAG, "Launch traffic stats update")
trafficStats.reset()
startTrafficStatsUpdateJob()
}
diff --git a/client/android/src/org/amnezia/vpn/AuthActivity.kt b/client/android/src/org/amnezia/vpn/AuthActivity.kt
index 2593315c..46401548 100644
--- a/client/android/src/org/amnezia/vpn/AuthActivity.kt
+++ b/client/android/src/org/amnezia/vpn/AuthActivity.kt
@@ -66,7 +66,7 @@ class AuthActivity : FragmentActivity() {
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(result: AuthenticationResult) {
super.onAuthenticationSucceeded(result)
- Log.d(TAG, "Authentication succeeded")
+ Log.v(TAG, "Authentication succeeded")
QtAndroidController.onAuthResult(true)
finish()
}
diff --git a/client/android/src/org/amnezia/vpn/ImportConfigActivity.kt b/client/android/src/org/amnezia/vpn/ImportConfigActivity.kt
index 9faa30d0..49823a36 100644
--- a/client/android/src/org/amnezia/vpn/ImportConfigActivity.kt
+++ b/client/android/src/org/amnezia/vpn/ImportConfigActivity.kt
@@ -29,20 +29,20 @@ class ImportConfigActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- Log.d(TAG, "Create Import Config Activity: $intent")
+ Log.v(TAG, "Create Import Config Activity: $intent")
intent?.let(::readConfig)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
- Log.d(TAG, "onNewIntent: $intent")
+ Log.v(TAG, "onNewIntent: $intent")
intent.let(::readConfig)
}
private fun readConfig(intent: Intent) {
when (intent.action) {
ACTION_SEND -> {
- Log.d(TAG, "Process SEND action, type: ${intent.type}")
+ Log.v(TAG, "Process SEND action, type: ${intent.type}")
when (intent.type) {
"application/octet-stream" -> {
intent.getUriCompat()?.let { uri ->
@@ -60,7 +60,7 @@ class ImportConfigActivity : ComponentActivity() {
}
ACTION_VIEW -> {
- Log.d(TAG, "Process VIEW action, scheme: ${intent.scheme}")
+ Log.v(TAG, "Process VIEW action, scheme: ${intent.scheme}")
when (intent.scheme) {
"file", "content" -> {
intent.data?.let { uri ->
diff --git a/client/android/src/org/amnezia/vpn/ServiceNotification.kt b/client/android/src/org/amnezia/vpn/ServiceNotification.kt
index f4707731..47e8f263 100644
--- a/client/android/src/org/amnezia/vpn/ServiceNotification.kt
+++ b/client/android/src/org/amnezia/vpn/ServiceNotification.kt
@@ -62,7 +62,7 @@ class ServiceNotification(private val context: Context) {
fun buildNotification(serverName: String?, protocol: String?, state: ProtocolState): Notification {
val speedString = if (state == CONNECTED) zeroSpeed else null
- Log.d(TAG, "Build notification: $serverName, $state")
+ Log.v(TAG, "Build notification: $serverName, $state")
return notificationBuilder
.setSmallIcon(R.drawable.ic_amnezia_round)
@@ -88,17 +88,15 @@ class ServiceNotification(private val context: Context) {
fun isNotificationEnabled(): Boolean {
if (!context.isNotificationPermissionGranted()) return false
if (!notificationManager.areNotificationsEnabled()) return false
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- return notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID)
- ?.let { it.importance != NotificationManager.IMPORTANCE_NONE } ?: true
- }
- return true
+ return notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID)?.let {
+ it.importance != NotificationManager.IMPORTANCE_NONE
+ } ?: true
}
@SuppressLint("MissingPermission")
fun updateNotification(serverName: String?, protocol: String?, state: ProtocolState) {
if (context.isNotificationPermissionGranted()) {
- Log.d(TAG, "Update notification: $serverName, $state")
+ Log.v(TAG, "Update notification: $serverName, $state")
notificationManager.notify(NOTIFICATION_ID, buildNotification(serverName, protocol, state))
}
}
diff --git a/client/android/src/org/amnezia/vpn/TvFilePicker.kt b/client/android/src/org/amnezia/vpn/TvFilePicker.kt
new file mode 100644
index 00000000..1ac275eb
--- /dev/null
+++ b/client/android/src/org/amnezia/vpn/TvFilePicker.kt
@@ -0,0 +1,45 @@
+package org.amnezia.vpn
+
+import android.content.ActivityNotFoundException
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.result.contract.ActivityResultContracts
+import org.amnezia.vpn.util.Log
+
+private const val TAG = "TvFilePicker"
+
+class TvFilePicker : ComponentActivity() {
+
+ private val fileChooseResultLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) {
+ setResult(RESULT_OK, Intent().apply { data = it })
+ finish()
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ Log.v(TAG, "onCreate")
+ getFile()
+ }
+
+ override fun onNewIntent(intent: Intent) {
+ super.onNewIntent(intent)
+ Log.v(TAG, "onNewIntent")
+ getFile()
+ }
+
+ private fun getFile() {
+ try {
+ Log.v(TAG, "getFile")
+ fileChooseResultLauncher.launch("*/*")
+ } catch (_: ActivityNotFoundException) {
+ Log.w(TAG, "Activity not found")
+ setResult(RESULT_CANCELED, Intent().apply { putExtra("activityNotFound", true) })
+ finish()
+ } catch (e: Exception) {
+ Log.e(TAG, "Failed to get file: $e")
+ setResult(RESULT_CANCELED)
+ finish()
+ }
+ }
+}
diff --git a/client/android/utils/src/main/kotlin/LibraryLoader.kt b/client/android/utils/src/main/kotlin/LibraryLoader.kt
new file mode 100644
index 00000000..8def18d0
--- /dev/null
+++ b/client/android/utils/src/main/kotlin/LibraryLoader.kt
@@ -0,0 +1,66 @@
+package org.amnezia.vpn.util
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.os.Build
+import java.io.File
+import java.io.FileOutputStream
+import java.util.zip.ZipFile
+
+private const val TAG = "LibraryLoader"
+
+object LibraryLoader {
+ private fun extractLibrary(context: Context, libraryName: String, destination: File): Boolean {
+ Log.d(TAG, "Extracting library: $libraryName")
+ val apks = hashSetOf()
+ context.applicationInfo.run {
+ sourceDir?.let { apks += it }
+ splitSourceDirs?.let { apks += it }
+ }
+ for (abi in Build.SUPPORTED_ABIS) {
+ for (apk in apks) {
+ ZipFile(File(apk), ZipFile.OPEN_READ).use { zipFile ->
+ val mappedName = System.mapLibraryName(libraryName)
+ val libraryZipPath = listOf("lib", abi, mappedName).joinToString(File.separator)
+ val zipEntry = zipFile.getEntry(libraryZipPath)
+ zipEntry?.let {
+ Log.d(TAG, "Extracting apk:/$libraryZipPath to ${destination.absolutePath}")
+ FileOutputStream(destination).use { outStream ->
+ zipFile.getInputStream(zipEntry).use { inStream ->
+ inStream.copyTo(outStream, 32 * 1024)
+ outStream.fd.sync()
+ }
+ }
+ }
+ return true
+ }
+ }
+ }
+ return false
+ }
+
+ @SuppressLint("UnsafeDynamicallyLoadedCode")
+ fun loadSharedLibrary(context: Context, libraryName: String) {
+ Log.d(TAG, "Loading library: $libraryName")
+ try {
+ System.loadLibrary(libraryName)
+ return
+ } catch (_: UnsatisfiedLinkError) {
+ Log.w(TAG, "Failed to load library, try to extract it from apk")
+ }
+ var tempFile: File? = null
+ try {
+ tempFile = File.createTempFile("lib", ".so", context.codeCacheDir)
+ if (extractLibrary(context, libraryName, tempFile)) {
+ System.load(tempFile.absolutePath)
+ return
+ }
+ } catch (e: Exception) {
+ throw LoadLibraryException("Failed to load library apk: $libraryName", e)
+ } finally {
+ tempFile?.delete()
+ }
+ }
+}
+
+class LoadLibraryException(message: String? = null, cause: Throwable? = null) : Exception(message, cause)
diff --git a/client/android/utils/src/main/kotlin/Log.kt b/client/android/utils/src/main/kotlin/Log.kt
index a656b9ea..da11c200 100644
--- a/client/android/utils/src/main/kotlin/Log.kt
+++ b/client/android/utils/src/main/kotlin/Log.kt
@@ -1,8 +1,6 @@
package org.amnezia.vpn.util
import android.content.Context
-import android.icu.text.DateFormat
-import android.icu.text.SimpleDateFormat
import android.os.Build
import android.os.Process
import java.io.File
@@ -12,8 +10,6 @@ import java.nio.channels.FileChannel
import java.nio.channels.FileLock
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
-import java.util.Date
-import java.util.Locale
import java.util.concurrent.locks.ReentrantLock
import org.amnezia.vpn.util.Log.Priority.D
import org.amnezia.vpn.util.Log.Priority.E
@@ -41,11 +37,7 @@ private const val LOG_MAX_FILE_SIZE = 1024 * 1024
* | | | create a report and/or terminate the process |
*/
object Log {
- private val dateTimeFormat: Any =
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) DateTimeFormatter.ofPattern(DATE_TIME_PATTERN)
- else object : ThreadLocal() {
- override fun initialValue(): DateFormat = SimpleDateFormat(DATE_TIME_PATTERN, Locale.US)
- }
+ private val dateTimeFormat: DateTimeFormatter = DateTimeFormatter.ofPattern(DATE_TIME_PATTERN)
private lateinit var logDir: File
private val logFile: File by lazy { File(logDir, LOG_FILE_NAME) }
@@ -143,12 +135,7 @@ object Log {
}
private fun formatLogMsg(tag: String, msg: String, priority: Priority): String {
- val date = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- LocalDateTime.now().format(dateTimeFormat as DateTimeFormatter)
- } else {
- @Suppress("UNCHECKED_CAST")
- (dateTimeFormat as ThreadLocal).get()?.format(Date())
- }
+ val date = LocalDateTime.now().format(dateTimeFormat)
return "$date ${Process.myPid()} ${Process.myTid()} $priority [${Thread.currentThread().name}] " +
"$tag: $msg\n"
}
diff --git a/client/android/utils/src/main/kotlin/net/NetworkState.kt b/client/android/utils/src/main/kotlin/net/NetworkState.kt
index b71bf393..1cab5535 100644
--- a/client/android/utils/src/main/kotlin/net/NetworkState.kt
+++ b/client/android/utils/src/main/kotlin/net/NetworkState.kt
@@ -42,18 +42,12 @@ class NetworkState(
private val networkCallback: NetworkCallback by lazy(NONE) {
object : NetworkCallback() {
override fun onAvailable(network: Network) {
- Log.d(TAG, "onAvailable: $network")
+ Log.v(TAG, "onAvailable: $network")
}
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
- Log.d(TAG, "onCapabilitiesChanged: $network, $networkCapabilities")
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- checkNetworkState(network, networkCapabilities)
- } else {
- handler.post {
- checkNetworkState(network, networkCapabilities)
- }
- }
+ Log.v(TAG, "onCapabilitiesChanged: $network, $networkCapabilities")
+ checkNetworkState(network, networkCapabilities)
}
private fun checkNetworkState(network: Network, networkCapabilities: NetworkCapabilities) {
@@ -73,11 +67,11 @@ class NetworkState(
}
override fun onBlockedStatusChanged(network: Network, blocked: Boolean) {
- Log.d(TAG, "onBlockedStatusChanged: $network, $blocked")
+ Log.v(TAG, "onBlockedStatusChanged: $network, $blocked")
}
override fun onLost(network: Network) {
- Log.d(TAG, "onLost: $network")
+ Log.v(TAG, "onLost: $network")
}
}
}
@@ -87,7 +81,7 @@ class NetworkState(
Log.d(TAG, "Bind network listener")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
connectivityManager.registerBestMatchingNetworkCallback(networkRequest, networkCallback, handler)
- } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ } else {
val numberAttempts = 300
var attemptCount = 0
while(true) {
@@ -108,8 +102,6 @@ class NetworkState(
}
}
}
- } else {
- connectivityManager.requestNetwork(networkRequest, networkCallback)
}
isListenerBound = true
}
diff --git a/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/Wireguard.kt b/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/Wireguard.kt
index c32ab8c2..42a27de4 100644
--- a/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/Wireguard.kt
+++ b/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/Wireguard.kt
@@ -1,18 +1,19 @@
package org.amnezia.vpn.protocol.wireguard
import android.net.VpnService.Builder
-import java.io.IOException
-import java.util.Locale
-import java.util.TreeMap
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
-import kotlinx.coroutines.withContext
+import kotlinx.coroutines.launch
import org.amnezia.awg.GoBackend
import org.amnezia.vpn.protocol.Protocol
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
import org.amnezia.vpn.protocol.Statistics
import org.amnezia.vpn.protocol.VpnStartException
+import org.amnezia.vpn.util.LibraryLoader.loadSharedLibrary
import org.amnezia.vpn.util.Log
import org.amnezia.vpn.util.asSequence
import org.amnezia.vpn.util.net.InetEndpoint
@@ -27,6 +28,8 @@ open class Wireguard : Protocol() {
private var tunnelHandle: Int = -1
protected open val ifName: String = "amn0"
+ private lateinit var scope: CoroutineScope
+ private var statusJob: Job? = null
override val statistics: Statistics
get() {
@@ -49,46 +52,17 @@ open class Wireguard : Protocol() {
override fun internalInit() {
if (!isInitialized) loadSharedLibrary(context, "wg-go")
+ if (this::scope.isInitialized) {
+ scope.cancel()
+ }
+ scope = CoroutineScope(Dispatchers.IO)
}
override suspend fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) {
val wireguardConfig = parseConfig(config)
- val startTime = System.currentTimeMillis()
start(wireguardConfig, vpnBuilder, protect)
- waitForConnection(startTime)
- state.value = CONNECTED
}
- private suspend fun waitForConnection(startTime: Long) {
- Log.d(TAG, "Waiting for connection")
- withContext(Dispatchers.IO) {
- val time = String.format(Locale.ROOT,"%.3f", startTime / 1000.0)
- try {
- delay(1000)
- var log = getLogcat(time)
- Log.d(TAG, "First waiting log: $log")
- // check that there is a connection log,
- // to avoid infinite connection
- if (!log.contains("Attaching to interface")) {
- Log.w(TAG, "Logs do not contain a connection log")
- return@withContext
- }
- while (!log.contains("Received handshake response")) {
- delay(1000)
- log = getLogcat(time)
- }
- } catch (e: IOException) {
- Log.e(TAG, "Failed to get logcat: $e")
- }
- }
- }
-
- private fun getLogcat(time: String): String =
- ProcessBuilder("logcat", "--buffer=main", "--format=raw", "*:S AmneziaWG/awg0", "-t", time)
- .redirectErrorStream(true)
- .start()
- .inputStream.reader().readText()
-
protected open fun parseConfig(config: JSONObject): WireguardConfig {
val configData = config.getJSONObject("wireguard_config_data")
return WireguardConfig.build {
@@ -129,12 +103,40 @@ open class Wireguard : Protocol() {
val port = configData.getInt("port")
setEndpoint(InetEndpoint(host, port))
+ if (configData.optBoolean("isObfuscationEnabled")) {
+ setUseProtocolExtension(true)
+ configExtensionParameters(configData)
+ }
+
configData.optStringOrNull("persistent_keep_alive")?.let { setPersistentKeepalive(it.toInt()) }
configData.getString("client_priv_key").let { setPrivateKeyHex(it.base64ToHex()) }
configData.getString("server_pub_key").let { setPublicKeyHex(it.base64ToHex()) }
configData.optStringOrNull("psk_key")?.let { setPreSharedKeyHex(it.base64ToHex()) }
}
+ protected fun WireguardConfig.Builder.configExtensionParameters(configData: JSONObject) {
+ configData.optStringOrNull("Jc")?.let { setJc(it.toInt()) }
+ configData.optStringOrNull("Jmin")?.let { setJmin(it.toInt()) }
+ configData.optStringOrNull("Jmax")?.let { setJmax(it.toInt()) }
+ configData.optStringOrNull("S1")?.let { setS1(it.toInt()) }
+ configData.optStringOrNull("S2")?.let { setS2(it.toInt()) }
+ configData.optStringOrNull("S3")?.let { setS3(it.toInt()) }
+ configData.optStringOrNull("S4")?.let { setS4(it.toInt()) }
+ configData.optStringOrNull("H1")?.let { setH1(it.toLong()) }
+ configData.optStringOrNull("H2")?.let { setH2(it.toLong()) }
+ configData.optStringOrNull("H3")?.let { setH3(it.toLong()) }
+ configData.optStringOrNull("H4")?.let { setH4(it.toLong()) }
+ configData.optStringOrNull("I1")?.let { setI1(it) }
+ configData.optStringOrNull("I2")?.let { setI2(it) }
+ configData.optStringOrNull("I3")?.let { setI3(it) }
+ configData.optStringOrNull("I4")?.let { setI4(it) }
+ configData.optStringOrNull("I5")?.let { setI5(it) }
+ configData.optStringOrNull("J1")?.let { setJ1(it) }
+ configData.optStringOrNull("J2")?.let { setJ2(it) }
+ configData.optStringOrNull("J3")?.let { setJ3(it) }
+ configData.optStringOrNull("Itime")?.let { setItime(it.toInt()) }
+ }
+
private fun start(config: WireguardConfig, vpnBuilder: Builder, protect: (Int) -> Boolean) {
if (tunnelHandle != -1) {
Log.w(TAG, "Tunnel already up")
@@ -161,6 +163,43 @@ open class Wireguard : Protocol() {
tunnelHandle = -1
throw VpnStartException("Protect VPN interface: permission not granted or revoked")
}
+ launchStatusJob()
+ }
+
+ private fun launchStatusJob() {
+ Log.d(TAG, "Launch status job")
+ statusJob = scope.launch {
+ while (true) {
+ val lastHandshake = getLastHandshake()
+ Log.v(TAG, "lastHandshake=$lastHandshake")
+ if (lastHandshake == 0L) {
+ delay(1000)
+ continue
+ }
+ if (lastHandshake == -2L || lastHandshake > 0L) state.value = CONNECTED
+ else if (lastHandshake == -1L) state.value = DISCONNECTED
+ statusJob = null
+ break
+ }
+ }
+ }
+
+ private fun getLastHandshake(): Long {
+ if (tunnelHandle == -1) {
+ Log.e(TAG, "Trying to get config of a non-existent tunnel")
+ return -1
+ }
+ val config = GoBackend.awgGetConfig(tunnelHandle)
+ if (config == null) {
+ Log.e(TAG, "Failed to get tunnel config")
+ return -2
+ }
+ val lastHandshake = config.lines().find { it.startsWith("last_handshake_time_sec=") }?.substring(24)?.toLong()
+ if (lastHandshake == null) {
+ Log.e(TAG, "Failed to get last_handshake_time_sec")
+ return -2
+ }
+ return lastHandshake
}
override fun stopVpn() {
@@ -168,6 +207,8 @@ open class Wireguard : Protocol() {
Log.w(TAG, "Tunnel already down")
return
}
+ statusJob?.cancel()
+ statusJob = null
val handleToClose = tunnelHandle
tunnelHandle = -1
GoBackend.awgTurnOff(handleToClose)
diff --git a/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/WireguardConfig.kt b/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/WireguardConfig.kt
index 09269f54..2dfbbae8 100644
--- a/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/WireguardConfig.kt
+++ b/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/WireguardConfig.kt
@@ -1,6 +1,7 @@
package org.amnezia.vpn.protocol.wireguard
import android.util.Base64
+import org.amnezia.vpn.protocol.BadConfigException
import org.amnezia.vpn.protocol.ProtocolConfig
import org.amnezia.vpn.util.net.InetEndpoint
@@ -12,7 +13,28 @@ open class WireguardConfig protected constructor(
val persistentKeepalive: Int,
val publicKeyHex: String,
val preSharedKeyHex: String?,
- val privateKeyHex: String
+ val privateKeyHex: String,
+ val useProtocolExtension: Boolean,
+ val jc: Int?,
+ val jmin: Int?,
+ val jmax: Int?,
+ val s1: Int?,
+ val s2: Int?,
+ val s3: Int?,
+ val s4: Int?,
+ val h1: Long?,
+ val h2: Long?,
+ val h3: Long?,
+ val h4: Long?,
+ var i1: String?,
+ var i2: String?,
+ var i3: String?,
+ var i4: String?,
+ var i5: String?,
+ var j1: String?,
+ var j2: String?,
+ var j3: String?,
+ var itime: Int?
) : ProtocolConfig(protocolConfigBuilder) {
protected constructor(builder: Builder) : this(
@@ -21,7 +43,28 @@ open class WireguardConfig protected constructor(
builder.persistentKeepalive,
builder.publicKeyHex,
builder.preSharedKeyHex,
- builder.privateKeyHex
+ builder.privateKeyHex,
+ builder.useProtocolExtension,
+ builder.jc,
+ builder.jmin,
+ builder.jmax,
+ builder.s1,
+ builder.s2,
+ builder.s3,
+ builder.s4,
+ builder.h1,
+ builder.h2,
+ builder.h3,
+ builder.h4,
+ builder.i1,
+ builder.i2,
+ builder.i3,
+ builder.i4,
+ builder.i5,
+ builder.j1,
+ builder.j2,
+ builder.j3,
+ builder.itime
)
fun toWgUserspaceString(): String = with(StringBuilder()) {
@@ -33,6 +76,41 @@ open class WireguardConfig protected constructor(
open fun appendDeviceLine(sb: StringBuilder) = with(sb) {
appendLine("private_key=$privateKeyHex")
+ if (useProtocolExtension) {
+ validateProtocolExtensionParameters()
+ appendLine("jc=$jc")
+ appendLine("jmin=$jmin")
+ appendLine("jmax=$jmax")
+ appendLine("s1=$s1")
+ appendLine("s2=$s2")
+ s3?.let { appendLine("s3=$it") }
+ s4?.let { appendLine("s4=$it") }
+ appendLine("h1=$h1")
+ appendLine("h2=$h2")
+ appendLine("h3=$h3")
+ appendLine("h4=$h4")
+ i1?.let { appendLine("i1=$it") }
+ i2?.let { appendLine("i2=$it") }
+ i3?.let { appendLine("i3=$it") }
+ i4?.let { appendLine("i4=$it") }
+ i5?.let { appendLine("i5=$it") }
+ j1?.let { appendLine("j1=$it") }
+ j2?.let { appendLine("j2=$it") }
+ j3?.let { appendLine("j3=$it") }
+ itime?.let { appendLine("itime=$it") }
+ }
+ }
+
+ private fun validateProtocolExtensionParameters() {
+ if (jc == null) throw BadConfigException("Parameter jc is undefined")
+ if (jmin == null) throw BadConfigException("Parameter jmin is undefined")
+ if (jmax == null) throw BadConfigException("Parameter jmax is undefined")
+ if (s1 == null) throw BadConfigException("Parameter s1 is undefined")
+ if (s2 == null) throw BadConfigException("Parameter s2 is undefined")
+ if (h1 == null) throw BadConfigException("Parameter h1 is undefined")
+ if (h2 == null) throw BadConfigException("Parameter h2 is undefined")
+ if (h3 == null) throw BadConfigException("Parameter h3 is undefined")
+ if (h4 == null) throw BadConfigException("Parameter h4 is undefined")
}
open fun appendPeerLine(sb: StringBuilder) = with(sb) {
@@ -65,6 +143,29 @@ open class WireguardConfig protected constructor(
override var mtu: Int = WIREGUARD_DEFAULT_MTU
+ internal var useProtocolExtension: Boolean = false
+
+ internal var jc: Int? = null
+ internal var jmin: Int? = null
+ internal var jmax: Int? = null
+ internal var s1: Int? = null
+ internal var s2: Int? = null
+ internal var s3: Int? = null
+ internal var s4: Int? = null
+ internal var h1: Long? = null
+ internal var h2: Long? = null
+ internal var h3: Long? = null
+ internal var h4: Long? = null
+ internal var i1: String? = null
+ internal var i2: String? = null
+ internal var i3: String? = null
+ internal var i4: String? = null
+ internal var i5: String? = null
+ internal var j1: String? = null
+ internal var j2: String? = null
+ internal var j3: String? = null
+ internal var itime: Int? = null
+
fun setEndpoint(endpoint: InetEndpoint) = apply { this.endpoint = endpoint }
fun setPersistentKeepalive(persistentKeepalive: Int) = apply { this.persistentKeepalive = persistentKeepalive }
@@ -75,6 +176,29 @@ open class WireguardConfig protected constructor(
fun setPrivateKeyHex(privateKeyHex: String) = apply { this.privateKeyHex = privateKeyHex }
+ fun setUseProtocolExtension(useProtocolExtension: Boolean) = apply { this.useProtocolExtension = useProtocolExtension }
+
+ fun setJc(jc: Int) = apply { this.jc = jc }
+ fun setJmin(jmin: Int) = apply { this.jmin = jmin }
+ fun setJmax(jmax: Int) = apply { this.jmax = jmax }
+ fun setS1(s1: Int) = apply { this.s1 = s1 }
+ fun setS2(s2: Int) = apply { this.s2 = s2 }
+ fun setS3(s3: Int) = apply { this.s3 = s3 }
+ fun setS4(s4: Int) = apply { this.s4 = s4 }
+ fun setH1(h1: Long) = apply { this.h1 = h1 }
+ fun setH2(h2: Long) = apply { this.h2 = h2 }
+ fun setH3(h3: Long) = apply { this.h3 = h3 }
+ fun setH4(h4: Long) = apply { this.h4 = h4 }
+ fun setI1(i1: String) = apply { this.i1 = i1 }
+ fun setI2(i2: String) = apply { this.i2 = i2 }
+ fun setI3(i3: String) = apply { this.i3 = i3 }
+ fun setI4(i4: String) = apply { this.i4 = i4 }
+ fun setI5(i5: String) = apply { this.i5 = i5 }
+ fun setJ1(j1: String) = apply { this.j1 = j1 }
+ fun setJ2(j2: String) = apply { this.j2 = j2 }
+ fun setJ3(j3: String) = apply { this.j3 = j3 }
+ fun setItime(itime: Int) = apply { this.itime = itime }
+
override fun build(): WireguardConfig = configBuild().run { WireguardConfig(this@Builder) }
}
diff --git a/client/android/xray/src/main/kotlin/Xray.kt b/client/android/xray/src/main/kotlin/Xray.kt
index 6e37c9c2..08242525 100644
--- a/client/android/xray/src/main/kotlin/Xray.kt
+++ b/client/android/xray/src/main/kotlin/Xray.kt
@@ -130,8 +130,8 @@ class Xray : Protocol() {
LibXray.initXray(assetsPath)
val geoDir = File(assetsPath, "geo").absolutePath
val configPath = File(context.cacheDir, "config.json")
- Log.d(TAG, "xray.location.asset: $geoDir")
- Log.d(TAG, "config: $configPath")
+ Log.v(TAG, "xray.location.asset: $geoDir")
+ Log.v(TAG, "config: $configPath")
try {
configPath.writeText(configJson)
} catch (e: IOException) {
diff --git a/client/cmake/3rdparty.cmake b/client/cmake/3rdparty.cmake
index 087f4961..2b5036c5 100644
--- a/client/cmake/3rdparty.cmake
+++ b/client/cmake/3rdparty.cmake
@@ -2,10 +2,6 @@ set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/..)
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/Modules;${CMAKE_MODULE_PATH}")
-if(NOT IOS AND NOT ANDROID)
- include(${CLIENT_ROOT_DIR}/3rd/SingleApplication/singleapplication.cmake)
-endif()
-
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/SortFilterProxyModel)
set(LIBS ${LIBS} SortFilterProxyModel)
include(${CLIENT_ROOT_DIR}/cmake/QSimpleCrypto.cmake)
diff --git a/client/cmake/android.cmake b/client/cmake/android.cmake
index c96d9ab8..34ca5bff 100644
--- a/client/cmake/android.cmake
+++ b/client/cmake/android.cmake
@@ -1,6 +1,6 @@
message("Client android ${CMAKE_ANDROID_ARCH_ABI} build")
-set(APP_ANDROID_MIN_SDK 24)
+set(APP_ANDROID_MIN_SDK 26)
set(ANDROID_PLATFORM "android-${APP_ANDROID_MIN_SDK}" CACHE STRING
"The minimum API level supported by the application or library" FORCE)
diff --git a/client/cmake/ios.cmake b/client/cmake/ios.cmake
index 5fda3506..a498a5b1 100644
--- a/client/cmake/ios.cmake
+++ b/client/cmake/ios.cmake
@@ -76,12 +76,22 @@ set_target_properties(${PROJECT} PROPERTIES
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/Frameworks"
XCODE_EMBED_APP_EXTENSIONS networkextension
- XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
- XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
- XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
- XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "match AppStore org.amnezia.AmneziaVPN"
- XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "match Development org.amnezia.AmneziaVPN"
)
+
+if(DEFINED DEPLOY)
+ set_target_properties(${PROJECT} PROPERTIES
+ XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
+ XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
+ XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
+ XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "distr ios.org.amnezia.AmneziaVPN"
+ XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "dev ios.org.amnezia.AmneziaVPN"
+ )
+else()
+ set_target_properties(${PROJECT} PROPERTIES
+ XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
+ )
+endif()
+
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES"
@@ -126,9 +136,9 @@ add_subdirectory(ios/networkextension)
add_dependencies(${PROJECT} networkextension)
set_property(TARGET ${PROJECT} PROPERTY XCODE_EMBED_FRAMEWORKS
- "${CMAKE_CURRENT_SOURCE_DIR}/3rd/OpenVPNAdapter/build/Release-iphoneos/OpenVPNAdapter.framework"
+ "${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/OpenVPNAdapter.framework"
)
-set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/3rd/OpenVPNAdapter/build/Release-iphoneos)
-target_link_libraries("networkextension" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/3rd/OpenVPNAdapter/build/Release-iphoneos/OpenVPNAdapter.framework")
+set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/)
+target_link_libraries("networkextension" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/OpenVPNAdapter.framework")
diff --git a/client/cmake/sources.cmake b/client/cmake/sources.cmake
new file mode 100644
index 00000000..c3af531a
--- /dev/null
+++ b/client/cmake/sources.cmake
@@ -0,0 +1,191 @@
+set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/..)
+
+set(HEADERS ${HEADERS}
+ ${CLIENT_ROOT_DIR}/migrations.h
+ ${CLIENT_ROOT_DIR}/../ipc/ipc.h
+ ${CLIENT_ROOT_DIR}/amnezia_application.h
+ ${CLIENT_ROOT_DIR}/containers/containers_defs.h
+ ${CLIENT_ROOT_DIR}/core/defs.h
+ ${CLIENT_ROOT_DIR}/core/errorstrings.h
+ ${CLIENT_ROOT_DIR}/core/scripts_registry.h
+ ${CLIENT_ROOT_DIR}/core/server_defs.h
+ ${CLIENT_ROOT_DIR}/core/api/apiDefs.h
+ ${CLIENT_ROOT_DIR}/core/qrCodeUtils.h
+ ${CLIENT_ROOT_DIR}/core/controllers/coreController.h
+ ${CLIENT_ROOT_DIR}/core/controllers/gatewayController.h
+ ${CLIENT_ROOT_DIR}/core/controllers/serverController.h
+ ${CLIENT_ROOT_DIR}/core/controllers/vpnConfigurationController.h
+ ${CLIENT_ROOT_DIR}/protocols/protocols_defs.h
+ ${CLIENT_ROOT_DIR}/protocols/qml_register_protocols.h
+ ${CLIENT_ROOT_DIR}/ui/pages.h
+ ${CLIENT_ROOT_DIR}/ui/qautostart.h
+ ${CLIENT_ROOT_DIR}/protocols/vpnprotocol.h
+ ${CMAKE_CURRENT_BINARY_DIR}/version.h
+ ${CLIENT_ROOT_DIR}/core/sshclient.h
+ ${CLIENT_ROOT_DIR}/core/networkUtilities.h
+ ${CLIENT_ROOT_DIR}/core/serialization/serialization.h
+ ${CLIENT_ROOT_DIR}/core/serialization/transfer.h
+ ${CLIENT_ROOT_DIR}/../common/logger/logger.h
+ ${CLIENT_ROOT_DIR}/utils/qmlUtils.h
+ ${CLIENT_ROOT_DIR}/core/api/apiUtils.h
+)
+
+# Mozilla headres
+set(HEADERS ${HEADERS}
+ ${CLIENT_ROOT_DIR}/mozilla/models/server.h
+ ${CLIENT_ROOT_DIR}/mozilla/shared/ipaddress.h
+ ${CLIENT_ROOT_DIR}/mozilla/shared/leakdetector.h
+ ${CLIENT_ROOT_DIR}/mozilla/controllerimpl.h
+ ${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.h
+)
+
+if(NOT IOS)
+ set(HEADERS ${HEADERS}
+ ${CLIENT_ROOT_DIR}/platforms/ios/QRCodeReaderBase.h
+ )
+endif()
+
+if(NOT ANDROID)
+ set(HEADERS ${HEADERS}
+ ${CLIENT_ROOT_DIR}/ui/notificationhandler.h
+ )
+endif()
+
+set(SOURCES ${SOURCES}
+ ${CLIENT_ROOT_DIR}/migrations.cpp
+ ${CLIENT_ROOT_DIR}/amnezia_application.cpp
+ ${CLIENT_ROOT_DIR}/containers/containers_defs.cpp
+ ${CLIENT_ROOT_DIR}/core/errorstrings.cpp
+ ${CLIENT_ROOT_DIR}/core/scripts_registry.cpp
+ ${CLIENT_ROOT_DIR}/core/server_defs.cpp
+ ${CLIENT_ROOT_DIR}/core/qrCodeUtils.cpp
+ ${CLIENT_ROOT_DIR}/core/controllers/coreController.cpp
+ ${CLIENT_ROOT_DIR}/core/controllers/gatewayController.cpp
+ ${CLIENT_ROOT_DIR}/core/controllers/serverController.cpp
+ ${CLIENT_ROOT_DIR}/core/controllers/vpnConfigurationController.cpp
+ ${CLIENT_ROOT_DIR}/protocols/protocols_defs.cpp
+ ${CLIENT_ROOT_DIR}/ui/qautostart.cpp
+ ${CLIENT_ROOT_DIR}/protocols/vpnprotocol.cpp
+ ${CLIENT_ROOT_DIR}/core/sshclient.cpp
+ ${CLIENT_ROOT_DIR}/core/networkUtilities.cpp
+ ${CLIENT_ROOT_DIR}/core/serialization/outbound.cpp
+ ${CLIENT_ROOT_DIR}/core/serialization/inbound.cpp
+ ${CLIENT_ROOT_DIR}/core/serialization/ss.cpp
+ ${CLIENT_ROOT_DIR}/core/serialization/ssd.cpp
+ ${CLIENT_ROOT_DIR}/core/serialization/vless.cpp
+ ${CLIENT_ROOT_DIR}/core/serialization/trojan.cpp
+ ${CLIENT_ROOT_DIR}/core/serialization/vmess.cpp
+ ${CLIENT_ROOT_DIR}/core/serialization/vmess_new.cpp
+ ${CLIENT_ROOT_DIR}/../common/logger/logger.cpp
+ ${CLIENT_ROOT_DIR}/utils/qmlUtils.cpp
+ ${CLIENT_ROOT_DIR}/core/api/apiUtils.cpp
+)
+
+# Mozilla sources
+set(SOURCES ${SOURCES}
+ ${CLIENT_ROOT_DIR}/mozilla/models/server.cpp
+ ${CLIENT_ROOT_DIR}/mozilla/shared/ipaddress.cpp
+ ${CLIENT_ROOT_DIR}/mozilla/shared/leakdetector.cpp
+ ${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.cpp
+)
+
+if(NOT IOS)
+ set(SOURCES ${SOURCES}
+ ${CLIENT_ROOT_DIR}/platforms/ios/QRCodeReaderBase.cpp
+ )
+endif()
+
+if(NOT ANDROID)
+ set(SOURCES ${SOURCES}
+ ${CLIENT_ROOT_DIR}/ui/notificationhandler.cpp
+ )
+endif()
+
+file(GLOB COMMON_FILES_H CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/*.h)
+file(GLOB COMMON_FILES_CPP CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/*.cpp)
+
+file(GLOB_RECURSE PAGE_LOGIC_H CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/ui/pages_logic/*.h)
+file(GLOB_RECURSE PAGE_LOGIC_CPP CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/ui/pages_logic/*.cpp)
+
+file(GLOB CONFIGURATORS_H CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/configurators/*.h)
+file(GLOB CONFIGURATORS_CPP CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/configurators/*.cpp)
+
+file(GLOB UI_MODELS_H CONFIGURE_DEPENDS
+ ${CLIENT_ROOT_DIR}/ui/models/*.h
+ ${CLIENT_ROOT_DIR}/ui/models/protocols/*.h
+ ${CLIENT_ROOT_DIR}/ui/models/services/*.h
+ ${CLIENT_ROOT_DIR}/ui/models/api/*.h
+)
+file(GLOB UI_MODELS_CPP CONFIGURE_DEPENDS
+ ${CLIENT_ROOT_DIR}/ui/models/*.cpp
+ ${CLIENT_ROOT_DIR}/ui/models/protocols/*.cpp
+ ${CLIENT_ROOT_DIR}/ui/models/services/*.cpp
+ ${CLIENT_ROOT_DIR}/ui/models/api/*.cpp
+)
+
+file(GLOB UI_CONTROLLERS_H CONFIGURE_DEPENDS
+ ${CLIENT_ROOT_DIR}/ui/controllers/*.h
+ ${CLIENT_ROOT_DIR}/ui/controllers/api/*.h
+)
+file(GLOB UI_CONTROLLERS_CPP CONFIGURE_DEPENDS
+ ${CLIENT_ROOT_DIR}/ui/controllers/*.cpp
+ ${CLIENT_ROOT_DIR}/ui/controllers/api/*.cpp
+)
+
+set(HEADERS ${HEADERS}
+ ${COMMON_FILES_H}
+ ${PAGE_LOGIC_H}
+ ${CONFIGURATORS_H}
+ ${UI_MODELS_H}
+ ${UI_CONTROLLERS_H}
+)
+set(SOURCES ${SOURCES}
+ ${COMMON_FILES_CPP}
+ ${PAGE_LOGIC_CPP}
+ ${CONFIGURATORS_CPP}
+ ${UI_MODELS_CPP}
+ ${UI_CONTROLLERS_CPP}
+)
+
+if(WIN32)
+ set(HEADERS ${HEADERS}
+ ${CLIENT_ROOT_DIR}/protocols/ikev2_vpn_protocol_windows.h
+ )
+
+ set(SOURCES ${SOURCES}
+ ${CLIENT_ROOT_DIR}/protocols/ikev2_vpn_protocol_windows.cpp
+ )
+
+ set(RESOURCES ${RESOURCES}
+ ${CMAKE_CURRENT_BINARY_DIR}/amneziavpn.rc
+ )
+endif()
+
+if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
+ message("Client desktop build")
+ add_compile_definitions(AMNEZIA_DESKTOP)
+
+ set(HEADERS ${HEADERS}
+ ${CLIENT_ROOT_DIR}/core/ipcclient.h
+ ${CLIENT_ROOT_DIR}/core/privileged_process.h
+ ${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.h
+ ${CLIENT_ROOT_DIR}/protocols/openvpnprotocol.h
+ ${CLIENT_ROOT_DIR}/protocols/openvpnovercloakprotocol.h
+ ${CLIENT_ROOT_DIR}/protocols/shadowsocksvpnprotocol.h
+ ${CLIENT_ROOT_DIR}/protocols/wireguardprotocol.h
+ ${CLIENT_ROOT_DIR}/protocols/xrayprotocol.h
+ ${CLIENT_ROOT_DIR}/protocols/awgprotocol.h
+ )
+
+ set(SOURCES ${SOURCES}
+ ${CLIENT_ROOT_DIR}/core/ipcclient.cpp
+ ${CLIENT_ROOT_DIR}/core/privileged_process.cpp
+ ${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.cpp
+ ${CLIENT_ROOT_DIR}/protocols/openvpnprotocol.cpp
+ ${CLIENT_ROOT_DIR}/protocols/openvpnovercloakprotocol.cpp
+ ${CLIENT_ROOT_DIR}/protocols/shadowsocksvpnprotocol.cpp
+ ${CLIENT_ROOT_DIR}/protocols/wireguardprotocol.cpp
+ ${CLIENT_ROOT_DIR}/protocols/xrayprotocol.cpp
+ ${CLIENT_ROOT_DIR}/protocols/awgprotocol.cpp
+ )
+endif()
diff --git a/client/configurators/awg_configurator.cpp b/client/configurators/awg_configurator.cpp
index 21b61ba4..f83acb19 100644
--- a/client/configurators/awg_configurator.cpp
+++ b/client/configurators/awg_configurator.cpp
@@ -1,4 +1,5 @@
#include "awg_configurator.h"
+#include "protocols/protocols_defs.h"
#include
#include
@@ -39,6 +40,20 @@ QString AwgConfigurator::createConfig(const ServerCredentials &credentials, Dock
jsonConfig[config_key::responsePacketMagicHeader] = configMap.value(config_key::responsePacketMagicHeader);
jsonConfig[config_key::underloadPacketMagicHeader] = configMap.value(config_key::underloadPacketMagicHeader);
jsonConfig[config_key::transportPacketMagicHeader] = configMap.value(config_key::transportPacketMagicHeader);
+
+ // jsonConfig[config_key::cookieReplyPacketJunkSize] = configMap.value(config_key::cookieReplyPacketJunkSize);
+ // jsonConfig[config_key::transportPacketJunkSize] = configMap.value(config_key::transportPacketJunkSize);
+
+ // jsonConfig[config_key::specialJunk1] = configMap.value(amnezia::config_key::specialJunk1);
+ // jsonConfig[config_key::specialJunk2] = configMap.value(amnezia::config_key::specialJunk2);
+ // jsonConfig[config_key::specialJunk3] = configMap.value(amnezia::config_key::specialJunk3);
+ // jsonConfig[config_key::specialJunk4] = configMap.value(amnezia::config_key::specialJunk4);
+ // jsonConfig[config_key::specialJunk5] = configMap.value(amnezia::config_key::specialJunk5);
+ // jsonConfig[config_key::controlledJunk1] = configMap.value(amnezia::config_key::controlledJunk1);
+ // jsonConfig[config_key::controlledJunk2] = configMap.value(amnezia::config_key::controlledJunk2);
+ // jsonConfig[config_key::controlledJunk3] = configMap.value(amnezia::config_key::controlledJunk3);
+ // jsonConfig[config_key::specialHandshakeTimeout] = configMap.value(amnezia::config_key::specialHandshakeTimeout);
+
jsonConfig[config_key::mtu] =
containerConfig.value(ProtocolProps::protoToString(Proto::Awg)).toObject().value(config_key::mtu).toString(protocols::awg::defaultMtu);
diff --git a/client/configurators/openvpn_configurator.cpp b/client/configurators/openvpn_configurator.cpp
index fafb7c2b..f6996320 100644
--- a/client/configurators/openvpn_configurator.cpp
+++ b/client/configurators/openvpn_configurator.cpp
@@ -13,10 +13,10 @@
#include
#endif
+#include "core/networkUtilities.h"
#include "containers/containers_defs.h"
#include "core/controllers/serverController.h"
#include "core/scripts_registry.h"
-#include "core/server_defs.h"
#include "settings.h"
#include "utilities.h"
@@ -24,6 +24,7 @@
#include
#include
+
OpenVpnConfigurator::OpenVpnConfigurator(std::shared_ptr settings, const QSharedPointer &serverController,
QObject *parent)
: ConfiguratorBase(settings, serverController, parent)
@@ -117,22 +118,22 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPairisSitesSplitTunnelingEnabled()) {
config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n");
-
-#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
- // Prevent ipv6 leak
- config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
-#endif
config.append("block-ipv6\n");
} else if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
- // no redirect-gateway
+ // no redirect-gateway
} else if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n");
// Prevent ipv6 leak
- config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
#endif
config.append("block-ipv6\n");
}
@@ -166,10 +167,15 @@ QString OpenVpnConfigurator::processConfigWithExportSettings(const QPair
#include
#include
+#include
#include
#include
#include
@@ -19,13 +20,17 @@
#include "settings.h"
#include "utilities.h"
-WireguardConfigurator::WireguardConfigurator(std::shared_ptr settings, const QSharedPointer &serverController,
- bool isAwg, QObject *parent)
+WireguardConfigurator::WireguardConfigurator(std::shared_ptr settings,
+ const QSharedPointer &serverController, bool isAwg,
+ QObject *parent)
: ConfiguratorBase(settings, serverController, parent), m_isAwg(isAwg)
{
- m_serverConfigPath = m_isAwg ? amnezia::protocols::awg::serverConfigPath : amnezia::protocols::wireguard::serverConfigPath;
- m_serverPublicKeyPath = m_isAwg ? amnezia::protocols::awg::serverPublicKeyPath : amnezia::protocols::wireguard::serverPublicKeyPath;
- m_serverPskKeyPath = m_isAwg ? amnezia::protocols::awg::serverPskKeyPath : amnezia::protocols::wireguard::serverPskKeyPath;
+ m_serverConfigPath =
+ m_isAwg ? amnezia::protocols::awg::serverConfigPath : amnezia::protocols::wireguard::serverConfigPath;
+ m_serverPublicKeyPath =
+ m_isAwg ? amnezia::protocols::awg::serverPublicKeyPath : amnezia::protocols::wireguard::serverPublicKeyPath;
+ m_serverPskKeyPath =
+ m_isAwg ? amnezia::protocols::awg::serverPskKeyPath : amnezia::protocols::wireguard::serverPskKeyPath;
m_configTemplate = m_isAwg ? ProtocolScriptType::awg_template : ProtocolScriptType::wireguard_template;
m_protocolName = m_isAwg ? config_key::awg : config_key::wireguard;
@@ -63,9 +68,31 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::genClientKeys()
return connData;
}
+QList WireguardConfigurator::getIpsFromConf(const QString &input)
+{
+ QRegularExpression regex("AllowedIPs = (\\d+\\.\\d+\\.\\d+\\.\\d+)");
+ QRegularExpressionMatchIterator matchIterator = regex.globalMatch(input);
+
+ QList ips;
+
+ while (matchIterator.hasNext()) {
+ QRegularExpressionMatch match = matchIterator.next();
+ const QString address_string { match.captured(1) };
+ const QHostAddress address { address_string };
+ if (address.isNull()) {
+ qWarning() << "Couldn't recognize the ip address: " << address_string;
+ } else {
+ ips << address;
+ }
+ }
+
+ return ips;
+}
+
WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardConfig(const ServerCredentials &credentials,
DockerContainer container,
- const QJsonObject &containerConfig, ErrorCode &errorCode)
+ const QJsonObject &containerConfig,
+ ErrorCode &errorCode)
{
WireguardConfigurator::ConnectionData connData = WireguardConfigurator::genClientKeys();
connData.host = credentials.hostName;
@@ -76,65 +103,45 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
return connData;
}
- // Get list of already created clients (only IP addresses)
- QString nextIpNumber;
- {
- QString script = QString("cat %1 | grep AllowedIPs").arg(m_serverConfigPath);
- QString stdOut;
- auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
- stdOut += data + "\n";
- return ErrorCode::NoError;
- };
+ QString getIpsScript = QString("cat %1 | grep AllowedIPs").arg(m_serverConfigPath);
+ QString stdOut;
+ auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
+ stdOut += data + "\n";
+ return ErrorCode::NoError;
+ };
- errorCode = m_serverController->runContainerScript(credentials, container, script, cbReadStdOut);
- if (errorCode != ErrorCode::NoError) {
- return connData;
- }
+ errorCode = m_serverController->runContainerScript(credentials, container, getIpsScript, cbReadStdOut);
+ if (errorCode != ErrorCode::NoError) {
+ return connData;
+ }
+ auto ips = getIpsFromConf(stdOut);
- stdOut.replace("AllowedIPs = ", "");
- stdOut.replace("/32", "");
- QStringList ips = stdOut.split("\n", Qt::SkipEmptyParts);
-
- // remove extra IPs from each line for case when user manually edited the wg0.conf
- // and added there more IPs for route his itnernal networks, like:
- // ...
- // AllowedIPs = 10.8.1.6/32, 192.168.1.0/24, 192.168.2.0/24, ...
- // ...
- // without this code - next IP would be 1 if last item in 'ips' has format above
- QStringList vpnIps;
- for (const auto &ip : ips) {
- vpnIps.append(ip.split(",", Qt::SkipEmptyParts).first().trimmed());
- }
- ips = vpnIps;
-
- // Calc next IP address
- if (ips.isEmpty()) {
- nextIpNumber = "2";
+ QHostAddress nextIp = [&] {
+ QHostAddress result;
+ QHostAddress lastIp;
+ if (ips.empty()) {
+ lastIp.setAddress(containerConfig.value(m_protocolName)
+ .toObject()
+ .value(config_key::subnet_address)
+ .toString(protocols::wireguard::defaultSubnetAddress));
} else {
- int next = ips.last().split(".").last().toInt() + 1;
- if (next > 254) {
- errorCode = ErrorCode::AddressPoolError;
- return connData;
- }
- nextIpNumber = QString::number(next);
+ lastIp = ips.last();
}
- }
-
- QString subnetIp = containerConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress);
- {
- QStringList l = subnetIp.split(".", Qt::SkipEmptyParts);
- if (l.isEmpty()) {
- errorCode = ErrorCode::AddressPoolError;
- return connData;
+ quint8 lastOctet = static_cast(lastIp.toIPv4Address());
+ switch (lastOctet) {
+ case 254: result.setAddress(lastIp.toIPv4Address() + 3); break;
+ case 255: result.setAddress(lastIp.toIPv4Address() + 2); break;
+ default: result.setAddress(lastIp.toIPv4Address() + 1); break;
}
- l.removeLast();
- l.append(nextIpNumber);
- connData.clientIP = l.join(".");
- }
+ return result;
+ }();
+
+ connData.clientIP = nextIp.toString();
// Get keys
- connData.serverPubKey = m_serverController->getTextFileFromContainer(container, credentials, m_serverPublicKeyPath, errorCode);
+ connData.serverPubKey =
+ m_serverController->getTextFileFromContainer(container, credentials, m_serverPublicKeyPath, errorCode);
connData.serverPubKey.replace("\n", "");
if (errorCode != ErrorCode::NoError) {
return connData;
@@ -161,10 +168,12 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
return connData;
}
- QString script = QString("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip %1)'").arg(m_serverConfigPath);
+ QString script = QString("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip %1)'")
+ .arg(m_serverConfigPath);
errorCode = m_serverController->runScript(
- credentials, m_serverController->replaceVars(script, m_serverController->genVarsForScript(credentials, container)));
+ credentials,
+ m_serverController->replaceVars(script, m_serverController->genVarsForScript(credentials, container)));
return connData;
}
@@ -173,8 +182,8 @@ QString WireguardConfigurator::createConfig(const ServerCredentials &credentials
const QJsonObject &containerConfig, ErrorCode &errorCode)
{
QString scriptData = amnezia::scriptData(m_configTemplate, container);
- QString config =
- m_serverController->replaceVars(scriptData, m_serverController->genVarsForScript(credentials, container, containerConfig));
+ QString config = m_serverController->replaceVars(
+ scriptData, m_serverController->genVarsForScript(credentials, container, containerConfig));
ConnectionData connData = prepareWireguardConfig(credentials, container, containerConfig, errorCode);
if (errorCode != ErrorCode::NoError) {
@@ -199,7 +208,7 @@ QString WireguardConfigurator::createConfig(const ServerCredentials &credentials
jConfig[config_key::server_pub_key] = connData.serverPubKey;
jConfig[config_key::mtu] = wireguarConfig.value(config_key::mtu).toString(protocols::wireguard::defaultMtu);
- jConfig[config_key::persistent_keep_alive] = 25;
+ jConfig[config_key::persistent_keep_alive] = "25";
QJsonArray allowedIps { "0.0.0.0/0", "::/0" };
jConfig[config_key::allowed_ips] = allowedIps;
@@ -208,16 +217,16 @@ QString WireguardConfigurator::createConfig(const ServerCredentials &credentials
return QJsonDocument(jConfig).toJson();
}
-QString WireguardConfigurator::processConfigWithLocalSettings(const QPair &dns, const bool isApiConfig,
- QString &protocolConfigString)
+QString WireguardConfigurator::processConfigWithLocalSettings(const QPair &dns,
+ const bool isApiConfig, QString &protocolConfigString)
{
processConfigWithDnsSettings(dns, protocolConfigString);
return protocolConfigString;
}
-QString WireguardConfigurator::processConfigWithExportSettings(const QPair &dns, const bool isApiConfig,
- QString &protocolConfigString)
+QString WireguardConfigurator::processConfigWithExportSettings(const QPair &dns,
+ const bool isApiConfig, QString &protocolConfigString)
{
processConfigWithDnsSettings(dns, protocolConfigString);
diff --git a/client/configurators/wireguard_configurator.h b/client/configurators/wireguard_configurator.h
index 22e8a8be..a4302e3e 100644
--- a/client/configurators/wireguard_configurator.h
+++ b/client/configurators/wireguard_configurator.h
@@ -1,6 +1,7 @@
#ifndef WIREGUARD_CONFIGURATOR_H
#define WIREGUARD_CONFIGURATOR_H
+#include
#include
#include
@@ -12,8 +13,8 @@ class WireguardConfigurator : public ConfiguratorBase
{
Q_OBJECT
public:
- WireguardConfigurator(std::shared_ptr settings, const QSharedPointer &serverController, bool isAwg,
- QObject *parent = nullptr);
+ WireguardConfigurator(std::shared_ptr settings, const QSharedPointer &serverController,
+ bool isAwg, QObject *parent = nullptr);
struct ConnectionData
{
@@ -26,15 +27,18 @@ public:
QString port;
};
- QString createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
- ErrorCode &errorCode);
+ QString createConfig(const ServerCredentials &credentials, DockerContainer container,
+ const QJsonObject &containerConfig, ErrorCode &errorCode);
- QString processConfigWithLocalSettings(const QPair &dns, const bool isApiConfig, QString &protocolConfigString);
- QString processConfigWithExportSettings(const QPair &dns, const bool isApiConfig, QString &protocolConfigString);
+ QString processConfigWithLocalSettings(const QPair &dns, const bool isApiConfig,
+ QString &protocolConfigString);
+ QString processConfigWithExportSettings(const QPair &dns, const bool isApiConfig,
+ QString &protocolConfigString);
static ConnectionData genClientKeys();
private:
+ QList getIpsFromConf(const QString &input);
ConnectionData prepareWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode &errorCode);
diff --git a/client/configurators/xray_configurator.cpp b/client/configurators/xray_configurator.cpp
index 786da47c..514aa821 100644
--- a/client/configurators/xray_configurator.cpp
+++ b/client/configurators/xray_configurator.cpp
@@ -3,38 +3,169 @@
#include
#include
#include
+#include
+#include "logger.h"
#include "containers/containers_defs.h"
#include "core/controllers/serverController.h"
#include "core/scripts_registry.h"
+namespace {
+Logger logger("XrayConfigurator");
+}
+
XrayConfigurator::XrayConfigurator(std::shared_ptr settings, const QSharedPointer &serverController, QObject *parent)
: ConfiguratorBase(settings, serverController, parent)
{
}
-QString XrayConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
- ErrorCode &errorCode)
+QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentials, DockerContainer container,
+ const QJsonObject &containerConfig, ErrorCode &errorCode)
{
- QString config = m_serverController->replaceVars(amnezia::scriptData(ProtocolScriptType::xray_template, container),
- m_serverController->genVarsForScript(credentials, container, containerConfig));
-
- QString xrayPublicKey =
- m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::PublicKeyPath, errorCode);
- xrayPublicKey.replace("\n", "");
-
- QString xrayUuid = m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::uuidPath, errorCode);
- xrayUuid.replace("\n", "");
-
- QString xrayShortId =
- m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::shortidPath, errorCode);
- xrayShortId.replace("\n", "");
-
+ // Generate new UUID for client
+ QString clientId = QUuid::createUuid().toString(QUuid::WithoutBraces);
+
+ // Get current server config
+ QString currentConfig = m_serverController->getTextFileFromContainer(
+ container, credentials, amnezia::protocols::xray::serverConfigPath, errorCode);
+
if (errorCode != ErrorCode::NoError) {
+ logger.error() << "Failed to get server config file";
return "";
}
- config.replace("$XRAY_CLIENT_ID", xrayUuid);
+ // Parse current config as JSON
+ QJsonDocument doc = QJsonDocument::fromJson(currentConfig.toUtf8());
+ if (doc.isNull() || !doc.isObject()) {
+ logger.error() << "Failed to parse server config JSON";
+ errorCode = ErrorCode::InternalError;
+ return "";
+ }
+
+ QJsonObject serverConfig = doc.object();
+
+ // Validate server config structure
+ if (!serverConfig.contains("inbounds")) {
+ logger.error() << "Server config missing 'inbounds' field";
+ errorCode = ErrorCode::InternalError;
+ return "";
+ }
+
+ QJsonArray inbounds = serverConfig["inbounds"].toArray();
+ if (inbounds.isEmpty()) {
+ logger.error() << "Server config has empty 'inbounds' array";
+ errorCode = ErrorCode::InternalError;
+ return "";
+ }
+
+ QJsonObject inbound = inbounds[0].toObject();
+ if (!inbound.contains("settings")) {
+ logger.error() << "Inbound missing 'settings' field";
+ errorCode = ErrorCode::InternalError;
+ return "";
+ }
+
+ QJsonObject settings = inbound["settings"].toObject();
+ if (!settings.contains("clients")) {
+ logger.error() << "Settings missing 'clients' field";
+ errorCode = ErrorCode::InternalError;
+ return "";
+ }
+
+ QJsonArray clients = settings["clients"].toArray();
+
+ // Create configuration for new client
+ QJsonObject clientConfig {
+ {"id", clientId},
+ {"flow", "xtls-rprx-vision"}
+ };
+
+ clients.append(clientConfig);
+
+ // Update config
+ settings["clients"] = clients;
+ inbound["settings"] = settings;
+ inbounds[0] = inbound;
+ serverConfig["inbounds"] = inbounds;
+
+ // Save updated config to server
+ QString updatedConfig = QJsonDocument(serverConfig).toJson();
+ errorCode = m_serverController->uploadTextFileToContainer(
+ container,
+ credentials,
+ updatedConfig,
+ amnezia::protocols::xray::serverConfigPath,
+ libssh::ScpOverwriteMode::ScpOverwriteExisting
+ );
+ if (errorCode != ErrorCode::NoError) {
+ logger.error() << "Failed to upload updated config";
+ return "";
+ }
+
+ // Restart container
+ QString restartScript = QString("sudo docker restart $CONTAINER_NAME");
+ errorCode = m_serverController->runScript(
+ credentials,
+ m_serverController->replaceVars(restartScript, m_serverController->genVarsForScript(credentials, container))
+ );
+
+ if (errorCode != ErrorCode::NoError) {
+ logger.error() << "Failed to restart container";
+ return "";
+ }
+
+ return clientId;
+}
+
+QString XrayConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
+ const QJsonObject &containerConfig, ErrorCode &errorCode)
+{
+ // Get client ID from prepareServerConfig
+ QString xrayClientId = prepareServerConfig(credentials, container, containerConfig, errorCode);
+ if (errorCode != ErrorCode::NoError || xrayClientId.isEmpty()) {
+ logger.error() << "Failed to prepare server config";
+ errorCode = ErrorCode::InternalError;
+ return "";
+ }
+
+ QString config = m_serverController->replaceVars(amnezia::scriptData(ProtocolScriptType::xray_template, container),
+ m_serverController->genVarsForScript(credentials, container, containerConfig));
+
+ if (config.isEmpty()) {
+ logger.error() << "Failed to get config template";
+ errorCode = ErrorCode::InternalError;
+ return "";
+ }
+
+ QString xrayPublicKey =
+ m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::PublicKeyPath, errorCode);
+ if (errorCode != ErrorCode::NoError || xrayPublicKey.isEmpty()) {
+ logger.error() << "Failed to get public key";
+ errorCode = ErrorCode::InternalError;
+ return "";
+ }
+ xrayPublicKey.replace("\n", "");
+
+ QString xrayShortId =
+ m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::shortidPath, errorCode);
+ if (errorCode != ErrorCode::NoError || xrayShortId.isEmpty()) {
+ logger.error() << "Failed to get short ID";
+ errorCode = ErrorCode::InternalError;
+ return "";
+ }
+ xrayShortId.replace("\n", "");
+
+ // Validate all required variables are present
+ if (!config.contains("$XRAY_CLIENT_ID") || !config.contains("$XRAY_PUBLIC_KEY") || !config.contains("$XRAY_SHORT_ID")) {
+ logger.error() << "Config template missing required variables:"
+ << "XRAY_CLIENT_ID:" << !config.contains("$XRAY_CLIENT_ID")
+ << "XRAY_PUBLIC_KEY:" << !config.contains("$XRAY_PUBLIC_KEY")
+ << "XRAY_SHORT_ID:" << !config.contains("$XRAY_SHORT_ID");
+ errorCode = ErrorCode::InternalError;
+ return "";
+ }
+
+ config.replace("$XRAY_CLIENT_ID", xrayClientId);
config.replace("$XRAY_PUBLIC_KEY", xrayPublicKey);
config.replace("$XRAY_SHORT_ID", xrayShortId);
diff --git a/client/configurators/xray_configurator.h b/client/configurators/xray_configurator.h
index 2acfdf71..8ed4e775 100644
--- a/client/configurators/xray_configurator.h
+++ b/client/configurators/xray_configurator.h
@@ -14,6 +14,10 @@ public:
QString createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
ErrorCode &errorCode);
+
+private:
+ QString prepareServerConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
+ ErrorCode &errorCode);
};
#endif // XRAY_CONFIGURATOR_H
diff --git a/client/containers/containers_defs.cpp b/client/containers/containers_defs.cpp
index ce673a85..214e2a51 100644
--- a/client/containers/containers_defs.cpp
+++ b/client/containers/containers_defs.cpp
@@ -110,22 +110,19 @@ QMap ContainerProps::containerDescriptions()
QObject::tr("OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its "
"own security protocol with SSL/TLS for key exchange.") },
{ DockerContainer::ShadowSocks,
- QObject::tr("Shadowsocks - masks VPN traffic, making it similar to normal web traffic, but it "
- "may be recognized by analysis systems in some highly censored regions.") },
+ QObject::tr("Shadowsocks masks VPN traffic, making it resemble normal web traffic, but it may still be detected by certain analysis systems.") },
{ DockerContainer::Cloak,
QObject::tr("OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against "
- "active-probing detection. Ideal for bypassing blocking in regions with the highest levels "
- "of censorship.") },
+ "active-probing detection. It is very resistant to detection, but offers low speed.") },
{ DockerContainer::WireGuard,
- QObject::tr("WireGuard - New popular VPN protocol with high performance, high speed and low power "
- "consumption. Recommended for regions with low levels of censorship.") },
+ QObject::tr("WireGuard - popular VPN protocol with high performance, high speed and low power "
+ "consumption.") },
{ DockerContainer::Awg,
- QObject::tr("AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, "
- "but very resistant to blockages. "
- "Recommended for regions with high levels of censorship.") },
+ QObject::tr("AmneziaWG is a special protocol from Amnezia based on WireGuard. "
+ "It provides high connection speed and ensures stable operation even in the most challenging network conditions.") },
{ DockerContainer::Xray,
- QObject::tr("XRay with REALITY - Suitable for countries with the highest level of internet censorship. "
- "Traffic masking as web traffic at the TLS level, and protection against detection by active probing methods.") },
+ QObject::tr("XRay with REALITY masks VPN traffic as web traffic and protects against active probing. "
+ "It is highly resistant to detection and offers high speed.") },
{ DockerContainer::Ipsec,
QObject::tr("IKEv2/IPsec - Modern stable protocol, a bit faster than others, restores connection after "
"signal loss. It has native support on the latest versions of Android and iOS.") },
@@ -143,100 +140,83 @@ QMap ContainerProps::containerDetailedDescriptions()
{
return {
{ DockerContainer::OpenVpn,
- QObject::tr(
- "OpenVPN stands as one of the most popular and time-tested VPN protocols available.\n"
- "It employs its unique security protocol, "
- "leveraging the strength of SSL/TLS for encryption and key exchange. "
- "Furthermore, OpenVPN's support for a multitude of authentication methods makes it versatile and adaptable, "
- "catering to a wide range of devices and operating systems. "
- "Due to its open-source nature, OpenVPN benefits from extensive scrutiny by the global community, "
- "which continually reinforces its security. "
- "With a strong balance of performance, security, and compatibility, "
- "OpenVPN remains a top choice for privacy-conscious individuals and businesses alike.\n\n"
- "* Available in the AmneziaVPN across all platforms\n"
- "* Normal power consumption on mobile devices\n"
- "* Flexible customisation to suit user needs to work with different operating systems and devices\n"
- "* Recognised by DPI analysis systems and therefore susceptible to blocking\n"
- "* Can operate over both TCP and UDP network protocols.") },
+ QObject::tr("OpenVPN is one of the most popular and reliable VPN protocols. "
+ "It uses SSL/TLS encryption, supports a wide variety of devices and operating systems, "
+ "and is continuously improved by the community due to its open-source nature. "
+ "It provides a good balance between speed and security but is easily recognized by DPI systems, "
+ "making it susceptible to blocking.\n"
+ "\nFeatures:\n"
+ "* Available on all AmneziaVPN platforms\n"
+ "* Normal battery consumption on mobile devices\n"
+ "* Flexible customization for various devices and OS\n"
+ "* Operates over both TCP and UDP protocols") },
{ DockerContainer::ShadowSocks,
- QObject::tr("Shadowsocks, inspired by the SOCKS5 protocol, safeguards the connection using the AEAD cipher. "
- "Although Shadowsocks is designed to be discreet and challenging to identify, it isn't identical to a standard HTTPS connection."
- "However, certain traffic analysis systems might still detect a Shadowsocks connection. "
- "Due to limited support in Amnezia, it's recommended to use AmneziaWG protocol.\n\n"
- "* Available in the AmneziaVPN only on desktop platforms\n"
- "* Configurable encryption protocol\n"
+ QObject::tr("Shadowsocks is based on the SOCKS5 protocol and encrypts connections using AEAD cipher. "
+ "Although designed to be discreet, it doesn't mimic a standard HTTPS connection and can be detected by some DPI systems. "
+ "Due to limited support in Amnezia, we recommend using the AmneziaWG protocol.\n"
+ "\nFeatures:\n"
+ "* Available in AmneziaVPN only on desktop platforms\n"
+ "* Customizable encryption protocol\n"
"* Detectable by some DPI systems\n"
- "* Works over TCP network protocol.") },
+ "* Operates over TCP protocol\n") },
{ DockerContainer::Cloak,
- QObject::tr("This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for "
- "protecting against blocking.\n\n"
- "OpenVPN provides a secure VPN connection by encrypting all internet traffic between the client "
- "and the server.\n\n"
- "Cloak protects OpenVPN from detection and blocking. \n\n"
- "Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, "
- "and also protects the VPN from detection by Active Probing. This makes it very resistant to "
- "being detected\n\n"
- "Immediately after receiving the first data packet, Cloak authenticates the incoming connection. "
- "If authentication fails, the plugin masks the server as a fake website and your VPN becomes "
- "invisible to analysis systems.\n\n"
- "If there is a extreme level of Internet censorship in your region, we advise you to use only "
- "OpenVPN over Cloak from the first connection\n\n"
- "* Available in the AmneziaVPN across all platforms\n"
+ QObject::tr("This combination includes the OpenVPN protocol and the Cloak plugin, specifically designed to protect against blocking.\n"
+ "\nOpenVPN securely encrypts all internet traffic between your device and the server.\n"
+ "\nThe Cloak plugin further protects the connection from DPI detection. "
+ "It modifies traffic metadata to disguise VPN traffic as regular web traffic and prevents detection through active probing. "
+ "If an incoming connection fails authentication, Cloak serves a fake website, making your VPN invisible to traffic analysis systems.\n"
+ "\nIn regions with heavy internet censorship, we strongly recommend using OpenVPN with Cloak from your first connection.\n"
+ "\nFeatures:\n"
+ "* Available on all AmneziaVPN platforms\n"
"* High power consumption on mobile devices\n"
- "* Flexible settings\n"
- "* Not recognised by DPI analysis systems\n"
- "* Works over TCP network protocol, 443 port.\n") },
+ "* Flexible configuration options\n"
+ "* Undetectable by DPI systems\n"
+ "* Operates over TCP protocol on port 443") },
{ DockerContainer::WireGuard,
- QObject::tr("A relatively new popular VPN protocol with a simplified architecture.\n"
- "WireGuard provides stable VPN connection and high performance on all devices. It uses hard-coded encryption "
- "settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput.\n"
- "WireGuard is very susceptible to blocking due to its distinct packet signatures. "
- "Unlike some other VPN protocols that employ obfuscation techniques, "
- "the consistent signature patterns of WireGuard packets can be more easily identified and "
- "thus blocked by advanced Deep Packet Inspection (DPI) systems and other network monitoring tools.\n\n"
- "* Available in the AmneziaVPN across all platforms\n"
- "* Low power consumption\n"
- "* Minimum number of settings\n"
- "* Easily recognised by DPI analysis systems, susceptible to blocking\n"
- "* Works over UDP network protocol.") },
+ QObject::tr("WireGuard is a modern, streamlined VPN protocol offering stable connectivity and excellent performance across all devices. "
+ "It uses fixed encryption settings, delivering lower latency and higher data transfer speeds compared to OpenVPN. "
+ "However, WireGuard is easily identifiable by DPI systems due to its distinctive packet signatures, making it susceptible to blocking.\n"
+ "\nFeatures:\n"
+ "* Available on all AmneziaVPN platforms\n"
+ "* Low power consumption on mobile devices\n"
+ "* Minimal configuration required\n"
+ "* Easily detected by DPI systems (susceptible to blocking)\n"
+ "* Operates over UDP protocol") },
{ DockerContainer::Awg,
- QObject::tr("A modern iteration of the popular VPN protocol, "
- "AmneziaWG builds upon the foundation set by WireGuard, "
- "retaining its simplified architecture and high-performance capabilities across devices.\n"
- "While WireGuard is known for its efficiency, "
- "it had issues with being easily detected due to its distinct packet signatures. "
- "AmneziaWG solves this problem by using better obfuscation methods, "
- "making its traffic blend in with regular internet traffic.\n"
- "This means that AmneziaWG keeps the fast performance of the original "
- "while adding an extra layer of stealth, "
- "making it a great choice for those wanting a fast and discreet VPN connection.\n\n"
- "* Available in the AmneziaVPN across all platforms\n"
- "* Low power consumption\n"
- "* Minimum number of settings\n"
- "* Not recognised by DPI analysis systems, resistant to blocking\n"
- "* Works over UDP network protocol.") },
+ QObject::tr("AmneziaWG is a modern VPN protocol based on WireGuard, "
+ "combining simplified architecture with high performance across all devices. "
+ "It addresses WireGuard's main vulnerability (easy detection by DPI systems) through advanced obfuscation techniques, "
+ "making VPN traffic indistinguishable from regular internet traffic.\n"
+ "\nAmneziaWG is an excellent choice for those seeking a fast, stealthy VPN connection.\n"
+ "\nFeatures:\n"
+ "* Available on all AmneziaVPN platforms\n"
+ "* Low battery consumption on mobile devices\n"
+ "* Minimal settings required\n"
+ "* Undetectable by traffic analysis systems (DPI)\n"
+ "* Operates over UDP protocol") },
{ DockerContainer::Xray,
- QObject::tr("The REALITY protocol, a pioneering development by the creators of XRay, "
- "is specifically designed to counteract the highest levels of internet censorship through its novel approach to evasion.\n"
- "It uniquely identifies censors during the TLS handshake phase, seamlessly operating as a proxy for legitimate clients while diverting censors to genuine websites like google.com, "
- "thus presenting an authentic TLS certificate and data. \n"
- "This advanced capability differentiates REALITY from similar technologies by its ability to disguise web traffic as coming from random, "
- "legitimate sites without the need for specific configurations. \n"
- "Unlike older protocols such as VMess, VLESS, and the XTLS-Vision transport, "
- "REALITY's innovative \"friend or foe\" recognition at the TLS handshake enhances security and circumvents detection by sophisticated DPI systems employing active probing techniques. "
- "This makes REALITY a robust solution for maintaining internet freedom in environments with stringent censorship.")
- },
+ QObject::tr("REALITY is an innovative protocol developed by the creators of XRay, designed specifically to combat high levels of internet censorship. "
+ "REALITY identifies censorship systems during the TLS handshake, "
+ "redirecting suspicious traffic seamlessly to legitimate websites like google.com while providing genuine TLS certificates. "
+ "This allows VPN traffic to blend indistinguishably with regular web traffic without special configuration."
+ "\nUnlike older protocols such as VMess, VLESS, and XTLS-Vision, REALITY incorporates an advanced built-in \"friend-or-foe\" detection mechanism, "
+ "effectively protecting against DPI and other traffic analysis methods.\n"
+ "\nFeatures:\n"
+ "* Resistant to active probing and DPI detection\n"
+ "* No special configuration required to disguise traffic\n"
+ "* Highly effective in heavily censored regions\n"
+ "* Minimal battery consumption on devices\n"
+ "* Operates over TCP protocol") },
{ DockerContainer::Ipsec,
- QObject::tr("IKEv2, paired with the IPSec encryption layer, stands as a modern and stable VPN protocol.\n"
- "One of its distinguishing features is its ability to swiftly switch between networks and devices, "
- "making it particularly adaptive in dynamic network environments. \n"
- "While it offers a blend of security, stability, and speed, "
- "it's essential to note that IKEv2 can be easily detected and is susceptible to blocking.\n\n"
- "* Available in the AmneziaVPN only on Windows\n"
- "* Low power consumption, on mobile devices\n"
- "* Minimal configuration\n"
- "* Recognised by DPI analysis systems\n"
- "* Works over UDP network protocol, ports 500 and 4500.") },
+ QObject::tr("IKEv2, combined with IPSec encryption, is a modern and reliable VPN protocol. "
+ "It reconnects quickly when switching networks or devices, making it ideal for dynamic network environments. "
+ "While it provides good security and speed, it's easily recognized by DPI systems and susceptible to blocking.\n"
+ "\nFeatures:\n"
+ "* Available in AmneziaVPN only on Windows\n"
+ "* Low battery consumption on mobile devices\n"
+ "* Minimal configuration required\n"
+ "* Detectable by DPI analysis systems(easily blocked)\n"
+ "* Operates over UDP protocol(ports 500 and 4500)") },
{ DockerContainer::TorWebSite, QObject::tr("Website in Tor network") },
{ DockerContainer::Dns, QObject::tr("DNS Service") },
@@ -332,9 +312,7 @@ QStringList ContainerProps::fixedPortsForContainer(DockerContainer c)
bool ContainerProps::isEasySetupContainer(DockerContainer container)
{
switch (container) {
- case DockerContainer::WireGuard: return true;
case DockerContainer::Awg: return true;
- // case DockerContainer::Cloak: return true;
default: return false;
}
}
@@ -342,9 +320,7 @@ bool ContainerProps::isEasySetupContainer(DockerContainer container)
QString ContainerProps::easySetupHeader(DockerContainer container)
{
switch (container) {
- case DockerContainer::WireGuard: return tr("Low");
- case DockerContainer::Awg: return tr("High");
- // case DockerContainer::Cloak: return tr("Extreme");
+ case DockerContainer::Awg: return tr("Automatic");
default: return "";
}
}
@@ -352,10 +328,8 @@ QString ContainerProps::easySetupHeader(DockerContainer container)
QString ContainerProps::easySetupDescription(DockerContainer container)
{
switch (container) {
- case DockerContainer::WireGuard: return tr("I just want to increase the level of my privacy.");
- case DockerContainer::Awg: return tr("I want to bypass censorship. This option recommended in most cases.");
- // case DockerContainer::Cloak:
- // return tr("Most VPN protocols are blocked. Recommended if other options are not working.");
+ case DockerContainer::Awg: return tr("AmneziaWG protocol will be installed. "
+ "It provides high connection speed and ensures stable operation even in the most challenging network conditions.");
default: return "";
}
}
@@ -363,9 +337,7 @@ QString ContainerProps::easySetupDescription(DockerContainer container)
int ContainerProps::easySetupOrder(DockerContainer container)
{
switch (container) {
- case DockerContainer::WireGuard: return 3;
- case DockerContainer::Awg: return 2;
- // case DockerContainer::Cloak: return 1;
+ case DockerContainer::Awg: return 1;
default: return 0;
}
}
@@ -384,9 +356,9 @@ bool ContainerProps::isShareable(DockerContainer container)
QJsonObject ContainerProps::getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig)
{
QString protocolConfigString = containerConfig.value(ProtocolProps::protoToString(protocol))
- .toObject()
- .value(config_key::last_config)
- .toString();
+ .toObject()
+ .value(config_key::last_config)
+ .toString();
return QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
}
diff --git a/client/core/api/apiDefs.h b/client/core/api/apiDefs.h
new file mode 100644
index 00000000..12c8051f
--- /dev/null
+++ b/client/core/api/apiDefs.h
@@ -0,0 +1,72 @@
+#ifndef APIDEFS_H
+#define APIDEFS_H
+
+#include
+
+namespace apiDefs
+{
+ enum ConfigType {
+ AmneziaFreeV2 = 0,
+ AmneziaFreeV3,
+ AmneziaPremiumV1,
+ AmneziaPremiumV2,
+ SelfHosted,
+ ExternalPremium
+ };
+
+ enum ConfigSource {
+ Telegram = 1,
+ AmneziaGateway
+ };
+
+ namespace key
+ {
+ constexpr QLatin1String configVersion("config_version");
+ constexpr QLatin1String apiEndpoint("api_endpoint");
+ constexpr QLatin1String apiKey("api_key");
+ constexpr QLatin1String description("description");
+ constexpr QLatin1String name("name");
+ constexpr QLatin1String protocol("protocol");
+
+ constexpr QLatin1String apiConfig("api_config");
+ constexpr QLatin1String stackType("stack_type");
+ constexpr QLatin1String serviceType("service_type");
+ constexpr QLatin1String cliVersion("cli_version");
+ constexpr QLatin1String supportedProtocols("supported_protocols");
+
+ constexpr QLatin1String vpnKey("vpn_key");
+ constexpr QLatin1String config("config");
+ constexpr QLatin1String configs("configs");
+
+ constexpr QLatin1String installationUuid("installation_uuid");
+ constexpr QLatin1String workerLastUpdated("worker_last_updated");
+ constexpr QLatin1String lastDownloaded("last_downloaded");
+ constexpr QLatin1String sourceType("source_type");
+
+ constexpr QLatin1String serverCountryCode("server_country_code");
+ constexpr QLatin1String serverCountryName("server_country_name");
+
+ constexpr QLatin1String osVersion("os_version");
+
+ constexpr QLatin1String availableCountries("available_countries");
+ constexpr QLatin1String activeDeviceCount("active_device_count");
+ constexpr QLatin1String maxDeviceCount("max_device_count");
+ constexpr QLatin1String subscriptionEndDate("subscription_end_date");
+ constexpr QLatin1String issuedConfigs("issued_configs");
+
+ constexpr QLatin1String supportInfo("support_info");
+ constexpr QLatin1String email("email");
+ constexpr QLatin1String billingEmail("billing_email");
+ constexpr QLatin1String website("website");
+ constexpr QLatin1String websiteName("website_name");
+ constexpr QLatin1String telegram("telegram");
+
+ constexpr QLatin1String id("id");
+ constexpr QLatin1String orderId("order_id");
+ constexpr QLatin1String migrationCode("migration_code");
+ }
+
+ const int requestTimeoutMsecs = 12 * 1000; // 12 secs
+}
+
+#endif // APIDEFS_H
diff --git a/client/core/api/apiUtils.cpp b/client/core/api/apiUtils.cpp
new file mode 100644
index 00000000..7f3e6db3
--- /dev/null
+++ b/client/core/api/apiUtils.cpp
@@ -0,0 +1,164 @@
+#include "apiUtils.h"
+
+#include
+#include
+
+namespace
+{
+ const QByteArray AMNEZIA_CONFIG_SIGNATURE = QByteArray::fromHex("000000ff");
+
+ QString escapeUnicode(const QString &input)
+ {
+ QString output;
+ for (QChar c : input) {
+ if (c.unicode() < 0x20 || c.unicode() > 0x7E) {
+ output += QString("\\u%1").arg(QString::number(c.unicode(), 16).rightJustified(4, '0'));
+ } else {
+ output += c;
+ }
+ }
+ return output;
+ }
+}
+
+bool apiUtils::isSubscriptionExpired(const QString &subscriptionEndDate)
+{
+ QDateTime now = QDateTime::currentDateTime();
+ QDateTime endDate = QDateTime::fromString(subscriptionEndDate, Qt::ISODateWithMs);
+ return endDate < now;
+}
+
+bool apiUtils::isServerFromApi(const QJsonObject &serverConfigObject)
+{
+ auto configVersion = serverConfigObject.value(apiDefs::key::configVersion).toInt();
+ switch (configVersion) {
+ case apiDefs::ConfigSource::Telegram: return true;
+ case apiDefs::ConfigSource::AmneziaGateway: return true;
+ default: return false;
+ }
+}
+
+apiDefs::ConfigType apiUtils::getConfigType(const QJsonObject &serverConfigObject)
+{
+ auto configVersion = serverConfigObject.value(apiDefs::key::configVersion).toInt();
+
+ switch (configVersion) {
+ case apiDefs::ConfigSource::Telegram: {
+ constexpr QLatin1String freeV2Endpoint(FREE_V2_ENDPOINT);
+ constexpr QLatin1String premiumV1Endpoint(PREM_V1_ENDPOINT);
+
+ auto apiEndpoint = serverConfigObject.value(apiDefs::key::apiEndpoint).toString();
+
+ if (apiEndpoint.contains(premiumV1Endpoint)) {
+ return apiDefs::ConfigType::AmneziaPremiumV1;
+ } else if (apiEndpoint.contains(freeV2Endpoint)) {
+ return apiDefs::ConfigType::AmneziaFreeV2;
+ }
+ };
+ case apiDefs::ConfigSource::AmneziaGateway: {
+ constexpr QLatin1String servicePremium("amnezia-premium");
+ constexpr QLatin1String serviceFree("amnezia-free");
+ constexpr QLatin1String serviceExternalPremium("external-premium");
+
+ auto apiConfigObject = serverConfigObject.value(apiDefs::key::apiConfig).toObject();
+ auto serviceType = apiConfigObject.value(apiDefs::key::serviceType).toString();
+
+ if (serviceType == servicePremium) {
+ return apiDefs::ConfigType::AmneziaPremiumV2;
+ } else if (serviceType == serviceFree) {
+ return apiDefs::ConfigType::AmneziaFreeV3;
+ } else if (serviceType == serviceExternalPremium) {
+ return apiDefs::ConfigType::ExternalPremium;
+ }
+ }
+ default: {
+ return apiDefs::ConfigType::SelfHosted;
+ }
+ };
+}
+
+apiDefs::ConfigSource apiUtils::getConfigSource(const QJsonObject &serverConfigObject)
+{
+ return static_cast(serverConfigObject.value(apiDefs::key::configVersion).toInt());
+}
+
+amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList &sslErrors, QNetworkReply *reply)
+{
+ const int httpStatusCodeConflict = 409;
+ const int httpStatusCodeNotFound = 404;
+
+ if (!sslErrors.empty()) {
+ qDebug().noquote() << sslErrors;
+ return amnezia::ErrorCode::ApiConfigSslError;
+ } else if (reply->error() == QNetworkReply::NoError) {
+ return amnezia::ErrorCode::NoError;
+ } else if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
+ || reply->error() == QNetworkReply::NetworkError::TimeoutError) {
+ qDebug() << reply->error();
+ return amnezia::ErrorCode::ApiConfigTimeoutError;
+ } else if (reply->error() == QNetworkReply::NetworkError::OperationNotImplementedError) {
+ qDebug() << reply->error();
+ return amnezia::ErrorCode::ApiUpdateRequestError;
+ } else {
+ QString err = reply->errorString();
+ int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
+ qDebug() << QString::fromUtf8(reply->readAll());
+ qDebug() << reply->error();
+ qDebug() << err;
+ qDebug() << httpStatusCode;
+ if (httpStatusCode == httpStatusCodeConflict) {
+ return amnezia::ErrorCode::ApiConfigLimitError;
+ } else if (httpStatusCode == httpStatusCodeNotFound) {
+ return amnezia::ErrorCode::ApiNotFoundError;
+ }
+ return amnezia::ErrorCode::ApiConfigDownloadError;
+ }
+
+ qDebug() << "something went wrong";
+ return amnezia::ErrorCode::InternalError;
+}
+
+bool apiUtils::isPremiumServer(const QJsonObject &serverConfigObject)
+{
+ static const QSet premiumTypes = { apiDefs::ConfigType::AmneziaPremiumV1, apiDefs::ConfigType::AmneziaPremiumV2,
+ apiDefs::ConfigType::ExternalPremium };
+ return premiumTypes.contains(getConfigType(serverConfigObject));
+}
+
+QString apiUtils::getPremiumV1VpnKey(const QJsonObject &serverConfigObject)
+{
+ if (apiUtils::getConfigType(serverConfigObject) != apiDefs::ConfigType::AmneziaPremiumV1) {
+ return {};
+ }
+
+ QList> orderedFields;
+ orderedFields.append(qMakePair(apiDefs::key::name, serverConfigObject[apiDefs::key::name].toString()));
+ orderedFields.append(qMakePair(apiDefs::key::description, serverConfigObject[apiDefs::key::description].toString()));
+ orderedFields.append(qMakePair(apiDefs::key::configVersion, serverConfigObject[apiDefs::key::configVersion].toDouble()));
+ orderedFields.append(qMakePair(apiDefs::key::protocol, serverConfigObject[apiDefs::key::protocol].toString()));
+ orderedFields.append(qMakePair(apiDefs::key::apiEndpoint, serverConfigObject[apiDefs::key::apiEndpoint].toString()));
+ orderedFields.append(qMakePair(apiDefs::key::apiKey, serverConfigObject[apiDefs::key::apiKey].toString()));
+
+ QString vpnKeyStr = "{";
+ for (int i = 0; i < orderedFields.size(); ++i) {
+ const auto &pair = orderedFields[i];
+ if (pair.second.typeId() == QMetaType::Type::QString) {
+ vpnKeyStr += "\"" + pair.first + "\": \"" + pair.second.toString() + "\"";
+ } else if (pair.second.typeId() == QMetaType::Type::Double || pair.second.typeId() == QMetaType::Type::Int) {
+ vpnKeyStr += "\"" + pair.first + "\": " + QString::number(pair.second.toDouble(), 'f', 1);
+ }
+
+ if (i < orderedFields.size() - 1) {
+ vpnKeyStr += ", ";
+ }
+ }
+ vpnKeyStr += "}";
+
+ QByteArray vpnKeyCompressed = escapeUnicode(vpnKeyStr).toUtf8();
+ vpnKeyCompressed = qCompress(vpnKeyCompressed, 6);
+ vpnKeyCompressed = vpnKeyCompressed.mid(4);
+
+ QByteArray signedData = AMNEZIA_CONFIG_SIGNATURE + vpnKeyCompressed;
+
+ return QString("vpn://%1").arg(QString(signedData.toBase64(QByteArray::Base64UrlEncoding)));
+}
diff --git a/client/core/api/apiUtils.h b/client/core/api/apiUtils.h
new file mode 100644
index 00000000..45eaf2de
--- /dev/null
+++ b/client/core/api/apiUtils.h
@@ -0,0 +1,26 @@
+#ifndef APIUTILS_H
+#define APIUTILS_H
+
+#include
+#include
+
+#include "apiDefs.h"
+#include "core/defs.h"
+
+namespace apiUtils
+{
+ bool isServerFromApi(const QJsonObject &serverConfigObject);
+
+ bool isSubscriptionExpired(const QString &subscriptionEndDate);
+
+ bool isPremiumServer(const QJsonObject &serverConfigObject);
+
+ apiDefs::ConfigType getConfigType(const QJsonObject &serverConfigObject);
+ apiDefs::ConfigSource getConfigSource(const QJsonObject &serverConfigObject);
+
+ amnezia::ErrorCode checkNetworkReplyErrors(const QList &sslErrors, QNetworkReply *reply);
+
+ QString getPremiumV1VpnKey(const QJsonObject &serverConfigObject);
+}
+
+#endif // APIUTILS_H
diff --git a/client/core/controllers/apiController.cpp b/client/core/controllers/apiController.cpp
deleted file mode 100644
index 3f8684e0..00000000
--- a/client/core/controllers/apiController.cpp
+++ /dev/null
@@ -1,423 +0,0 @@
-#include "apiController.h"
-
-#include
-#include
-#include
-#include
-
-#include "QBlockCipher.h"
-#include "QRsa.h"
-
-#include "amnezia_application.h"
-#include "configurators/wireguard_configurator.h"
-#include "core/enums/apiEnums.h"
-#include "version.h"
-
-namespace
-{
- namespace configKey
- {
- constexpr char cloak[] = "cloak";
- constexpr char awg[] = "awg";
-
- constexpr char apiEdnpoint[] = "api_endpoint";
- constexpr char accessToken[] = "api_key";
- constexpr char certificate[] = "certificate";
- constexpr char publicKey[] = "public_key";
- constexpr char protocol[] = "protocol";
-
- constexpr char uuid[] = "installation_uuid";
- constexpr char osVersion[] = "os_version";
- constexpr char appVersion[] = "app_version";
-
- constexpr char userCountryCode[] = "user_country_code";
- constexpr char serverCountryCode[] = "server_country_code";
- constexpr char serviceType[] = "service_type";
-
- constexpr char aesKey[] = "aes_key";
- constexpr char aesIv[] = "aes_iv";
- constexpr char aesSalt[] = "aes_salt";
-
- constexpr char apiPayload[] = "api_payload";
- constexpr char keyPayload[] = "key_payload";
- }
-
- const QStringList proxyStorageUrl = { "" };
-
- ErrorCode checkErrors(const QList &sslErrors, QNetworkReply *reply)
- {
- if (!sslErrors.empty()) {
- qDebug().noquote() << sslErrors;
- return ErrorCode::ApiConfigSslError;
- } else if (reply->error() == QNetworkReply::NoError) {
- return ErrorCode::NoError;
- } else if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
- || reply->error() == QNetworkReply::NetworkError::TimeoutError) {
- return ErrorCode::ApiConfigTimeoutError;
- } else {
- QString err = reply->errorString();
- qDebug() << QString::fromUtf8(reply->readAll());
- qDebug() << reply->error();
- qDebug() << err;
- qDebug() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
- return ErrorCode::ApiConfigDownloadError;
- }
- }
-}
-
-ApiController::ApiController(const QString &gatewayEndpoint, bool isDevEnvironment, QObject *parent)
- : QObject(parent), m_gatewayEndpoint(gatewayEndpoint), m_isDevEnvironment(isDevEnvironment)
-{
-}
-
-void ApiController::fillServerConfig(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData,
- const QByteArray &apiResponseBody, QJsonObject &serverConfig)
-{
- QString data = QJsonDocument::fromJson(apiResponseBody).object().value(config_key::config).toString();
-
- data.replace("vpn://", "");
- QByteArray ba = QByteArray::fromBase64(data.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
-
- if (ba.isEmpty()) {
- emit errorOccurred(ErrorCode::ApiConfigEmptyError);
- return;
- }
-
- QByteArray ba_uncompressed = qUncompress(ba);
- if (!ba_uncompressed.isEmpty()) {
- ba = ba_uncompressed;
- }
-
- QString configStr = ba;
- if (protocol == configKey::cloak) {
- configStr.replace("", "\n");
- configStr.replace("$OPENVPN_PRIV_KEY", apiPayloadData.certRequest.privKey);
- } else if (protocol == configKey::awg) {
- configStr.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", apiPayloadData.wireGuardClientPrivKey);
- auto serverConfig = QJsonDocument::fromJson(configStr.toUtf8()).object();
- auto containers = serverConfig.value(config_key::containers).toArray();
- if (containers.isEmpty()) {
- return; // todo process error
- }
- auto container = containers.at(0).toObject();
- QString containerName = ContainerProps::containerTypeToString(DockerContainer::Awg);
- auto containerConfig = container.value(containerName).toObject();
- auto protocolConfig = QJsonDocument::fromJson(containerConfig.value(config_key::last_config).toString().toUtf8()).object();
- containerConfig[config_key::junkPacketCount] = protocolConfig.value(config_key::junkPacketCount);
- containerConfig[config_key::junkPacketMinSize] = protocolConfig.value(config_key::junkPacketMinSize);
- containerConfig[config_key::junkPacketMaxSize] = protocolConfig.value(config_key::junkPacketMaxSize);
- containerConfig[config_key::initPacketJunkSize] = protocolConfig.value(config_key::initPacketJunkSize);
- containerConfig[config_key::responsePacketJunkSize] = protocolConfig.value(config_key::responsePacketJunkSize);
- containerConfig[config_key::initPacketMagicHeader] = protocolConfig.value(config_key::initPacketMagicHeader);
- containerConfig[config_key::responsePacketMagicHeader] = protocolConfig.value(config_key::responsePacketMagicHeader);
- containerConfig[config_key::underloadPacketMagicHeader] = protocolConfig.value(config_key::underloadPacketMagicHeader);
- containerConfig[config_key::transportPacketMagicHeader] = protocolConfig.value(config_key::transportPacketMagicHeader);
- container[containerName] = containerConfig;
- containers.replace(0, container);
- serverConfig[config_key::containers] = containers;
- configStr = QString(QJsonDocument(serverConfig).toJson());
- }
-
- QJsonObject apiConfig = QJsonDocument::fromJson(configStr.toUtf8()).object();
- serverConfig[config_key::dns1] = apiConfig.value(config_key::dns1);
- serverConfig[config_key::dns2] = apiConfig.value(config_key::dns2);
- serverConfig[config_key::containers] = apiConfig.value(config_key::containers);
- serverConfig[config_key::hostName] = apiConfig.value(config_key::hostName);
-
- if (apiConfig.value(config_key::configVersion).toInt() == ApiConfigSources::AmneziaGateway) {
- serverConfig[config_key::configVersion] = apiConfig.value(config_key::configVersion);
- serverConfig[config_key::description] = apiConfig.value(config_key::description);
- serverConfig[config_key::name] = apiConfig.value(config_key::name);
- }
-
- auto defaultContainer = apiConfig.value(config_key::defaultContainer).toString();
- serverConfig[config_key::defaultContainer] = defaultContainer;
-
- return;
-}
-
-QStringList ApiController::getProxyUrls()
-{
- QNetworkRequest request;
- request.setTransferTimeout(7000);
- request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
-
- QEventLoop wait;
- QList sslErrors;
- QNetworkReply *reply;
-
- for (const auto &proxyStorageUrl : proxyStorageUrl) {
- request.setUrl(proxyStorageUrl);
- reply = amnApp->manager()->get(request);
-
- connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
- connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList &errors) { sslErrors = errors; });
- wait.exec();
-
- if (reply->error() == QNetworkReply::NetworkError::NoError) {
- break;
- }
- reply->deleteLater();
- }
-
- auto encryptedResponseBody = reply->readAll();
- reply->deleteLater();
-
- EVP_PKEY *privateKey = nullptr;
- QByteArray responseBody;
- try {
- QByteArray key = PROD_PROXY_STORAGE_KEY;
- QSimpleCrypto::QRsa rsa;
- privateKey = rsa.getPrivateKeyFromByteArray(key, "");
- responseBody = rsa.decrypt(encryptedResponseBody, privateKey, RSA_PKCS1_PADDING);
- } catch (...) {
- qCritical() << "error loading private key from environment variables or decrypting payload";
- return {};
- }
-
- auto endpointsArray = QJsonDocument::fromJson(responseBody).array();
-
- QStringList endpoints;
- for (const auto &endpoint : endpointsArray) {
- endpoints.push_back(endpoint.toString());
- }
- return endpoints;
-}
-
-ApiController::ApiPayloadData ApiController::generateApiPayloadData(const QString &protocol)
-{
- ApiController::ApiPayloadData apiPayload;
- if (protocol == configKey::cloak) {
- apiPayload.certRequest = OpenVpnConfigurator::createCertRequest();
- } else if (protocol == configKey::awg) {
- auto connData = WireguardConfigurator::genClientKeys();
- apiPayload.wireGuardClientPubKey = connData.clientPubKey;
- apiPayload.wireGuardClientPrivKey = connData.clientPrivKey;
- }
- return apiPayload;
-}
-
-QJsonObject ApiController::fillApiPayload(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData)
-{
- QJsonObject obj;
- if (protocol == configKey::cloak) {
- obj[configKey::certificate] = apiPayloadData.certRequest.request;
- } else if (protocol == configKey::awg) {
- obj[configKey::publicKey] = apiPayloadData.wireGuardClientPubKey;
- }
-
- obj[configKey::osVersion] = QSysInfo::productType();
- obj[configKey::appVersion] = QString(APP_VERSION);
-
- return obj;
-}
-
-void ApiController::updateServerConfigFromApi(const QString &installationUuid, const int serverIndex, QJsonObject serverConfig)
-{
-#ifdef Q_OS_IOS
- IosController::Instance()->requestInetAccess();
- QThread::msleep(10);
-#endif
-
- if (serverConfig.value(config_key::configVersion).toInt()) {
- QNetworkRequest request;
- request.setTransferTimeout(7000);
- request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
- request.setRawHeader("Authorization", "Api-Key " + serverConfig.value(configKey::accessToken).toString().toUtf8());
- QString endpoint = serverConfig.value(configKey::apiEdnpoint).toString();
- request.setUrl(endpoint);
-
- QString protocol = serverConfig.value(configKey::protocol).toString();
-
- ApiPayloadData apiPayloadData = generateApiPayloadData(protocol);
-
- QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData);
- apiPayload[configKey::uuid] = installationUuid;
-
- QByteArray requestBody = QJsonDocument(apiPayload).toJson();
-
- QNetworkReply *reply = amnApp->manager()->post(request, requestBody);
-
- QObject::connect(reply, &QNetworkReply::finished, [this, reply, protocol, apiPayloadData, serverIndex, serverConfig]() mutable {
- if (reply->error() == QNetworkReply::NoError) {
- auto apiResponseBody = reply->readAll();
- fillServerConfig(protocol, apiPayloadData, apiResponseBody, serverConfig);
- emit finished(serverConfig, serverIndex);
- } else {
- if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
- || reply->error() == QNetworkReply::NetworkError::TimeoutError) {
- emit errorOccurred(ErrorCode::ApiConfigTimeoutError);
- } else {
- QString err = reply->errorString();
- qDebug() << QString::fromUtf8(reply->readAll());
- qDebug() << reply->error();
- qDebug() << err;
- qDebug() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
- emit errorOccurred(ErrorCode::ApiConfigDownloadError);
- }
- }
-
- reply->deleteLater();
- });
-
- QObject::connect(reply, &QNetworkReply::errorOccurred,
- [this, reply](QNetworkReply::NetworkError error) { qDebug() << reply->errorString() << error; });
- connect(reply, &QNetworkReply::sslErrors, [this, reply](const QList &errors) {
- qDebug().noquote() << errors;
- emit errorOccurred(ErrorCode::ApiConfigSslError);
- });
- }
-}
-
-ErrorCode ApiController::getServicesList(QByteArray &responseBody)
-{
-#ifdef Q_OS_IOS
- IosController::Instance()->requestInetAccess();
- QThread::msleep(10);
-#endif
-
- QNetworkRequest request;
- request.setTransferTimeout(7000);
- request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
-
- request.setUrl(QString("%1v1/services").arg(m_gatewayEndpoint));
-
- QNetworkReply *reply;
- reply = amnApp->manager()->get(request);
-
- QEventLoop wait;
- QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
-
- QList sslErrors;
- connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList &errors) { sslErrors = errors; });
- wait.exec();
-
- if (reply->error() == QNetworkReply::NetworkError::TimeoutError || reply->error() == QNetworkReply::NetworkError::OperationCanceledError) {
- m_proxyUrls = getProxyUrls();
- for (const QString &proxyUrl : m_proxyUrls) {
- request.setUrl(QString("%1v1/services").arg(proxyUrl));
- reply = amnApp->manager()->get(request);
-
- QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
- connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList &errors) { sslErrors = errors; });
- wait.exec();
- if (reply->error() != QNetworkReply::NetworkError::TimeoutError
- && reply->error() != QNetworkReply::NetworkError::OperationCanceledError) {
- break;
- }
- reply->deleteLater();
- }
- }
-
- responseBody = reply->readAll();
- auto errorCode = checkErrors(sslErrors, reply);
- reply->deleteLater();
- return errorCode;
-}
-
-ErrorCode ApiController::getConfigForService(const QString &installationUuid, const QString &userCountryCode, const QString &serviceType,
- const QString &protocol, const QString &serverCountryCode, QJsonObject &serverConfig)
-{
-#ifdef Q_OS_IOS
- IosController::Instance()->requestInetAccess();
- QThread::msleep(10);
-#endif
-
- QNetworkAccessManager manager;
- QNetworkRequest request;
- request.setTransferTimeout(7000);
- request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
-
- request.setUrl(QString("%1v1/config").arg(m_gatewayEndpoint));
-
- ApiPayloadData apiPayloadData = generateApiPayloadData(protocol);
-
- QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData);
- apiPayload[configKey::userCountryCode] = userCountryCode;
- if (!serverCountryCode.isEmpty()) {
- apiPayload[configKey::serverCountryCode] = serverCountryCode;
- }
- apiPayload[configKey::serviceType] = serviceType;
- apiPayload[configKey::uuid] = installationUuid;
-
- QSimpleCrypto::QBlockCipher blockCipher;
- QByteArray key = blockCipher.generatePrivateSalt(32);
- QByteArray iv = blockCipher.generatePrivateSalt(32);
- QByteArray salt = blockCipher.generatePrivateSalt(8);
-
- QJsonObject keyPayload;
- keyPayload[configKey::aesKey] = QString(key.toBase64());
- keyPayload[configKey::aesIv] = QString(iv.toBase64());
- keyPayload[configKey::aesSalt] = QString(salt.toBase64());
-
- QByteArray encryptedKeyPayload;
- QByteArray encryptedApiPayload;
- try {
- QSimpleCrypto::QRsa rsa;
-
- EVP_PKEY *publicKey = nullptr;
- try {
- QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
- QSimpleCrypto::QRsa rsa;
- publicKey = rsa.getPublicKeyFromByteArray(key);
- } catch (...) {
- qCritical() << "error loading public key from environment variables";
- return ErrorCode::ApiMissingAgwPublicKey;
- }
-
- encryptedKeyPayload = rsa.encrypt(QJsonDocument(keyPayload).toJson(), publicKey, RSA_PKCS1_PADDING);
- EVP_PKEY_free(publicKey);
-
- encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), key, iv, "", salt);
- } catch (...) { // todo change error handling in QSimpleCrypto?
- qCritical() << "error when encrypting the request body";
- }
-
- QJsonObject requestBody;
- requestBody[configKey::keyPayload] = QString(encryptedKeyPayload.toBase64());
- requestBody[configKey::apiPayload] = QString(encryptedApiPayload.toBase64());
-
- QNetworkReply *reply = manager.post(request, QJsonDocument(requestBody).toJson());
-
- QEventLoop wait;
- connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
-
- QList sslErrors;
- connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList &errors) { sslErrors = errors; });
- wait.exec();
-
- if (reply->error() == QNetworkReply::NetworkError::TimeoutError || reply->error() == QNetworkReply::NetworkError::OperationCanceledError) {
- if (m_proxyUrls.isEmpty()) {
- m_proxyUrls = getProxyUrls();
- }
- for (const QString &proxyUrl : m_proxyUrls) {
- request.setUrl(QString("%1v1/config").arg(proxyUrl));
- reply = manager.post(request, QJsonDocument(requestBody).toJson());
-
- QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
- connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList &errors) { sslErrors = errors; });
- wait.exec();
- if (reply->error() != QNetworkReply::NetworkError::TimeoutError
- && reply->error() != QNetworkReply::NetworkError::OperationCanceledError) {
- break;
- }
- reply->deleteLater();
- }
- }
-
- auto errorCode = checkErrors(sslErrors, reply);
- if (errorCode) {
- return errorCode;
- }
-
- auto encryptedResponseBody = reply->readAll();
- reply->deleteLater();
- try {
- auto responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt);
- fillServerConfig(protocol, apiPayloadData, responseBody, serverConfig);
- } catch (...) { // todo change error handling in QSimpleCrypto?
- qCritical() << "error when decrypting the request body";
- }
-
- return errorCode;
-}
diff --git a/client/core/controllers/apiController.h b/client/core/controllers/apiController.h
deleted file mode 100644
index 1f811498..00000000
--- a/client/core/controllers/apiController.h
+++ /dev/null
@@ -1,50 +0,0 @@
-#ifndef APICONTROLLER_H
-#define APICONTROLLER_H
-
-#include
-
-#include "configurators/openvpn_configurator.h"
-
-#ifdef Q_OS_IOS
- #include "platforms/ios/ios_controller.h"
-#endif
-
-class ApiController : public QObject
-{
- Q_OBJECT
-
-public:
- explicit ApiController(const QString &gatewayEndpoint, bool isDevEnvironment, QObject *parent = nullptr);
-
-public slots:
- void updateServerConfigFromApi(const QString &installationUuid, const int serverIndex, QJsonObject serverConfig);
-
- ErrorCode getServicesList(QByteArray &responseBody);
- ErrorCode getConfigForService(const QString &installationUuid, const QString &userCountryCode, const QString &serviceType,
- const QString &protocol, const QString &serverCountryCode, QJsonObject &serverConfig);
-
-signals:
- void errorOccurred(ErrorCode errorCode);
- void finished(const QJsonObject &config, const int serverIndex);
-
-private:
- struct ApiPayloadData
- {
- OpenVpnConfigurator::ConnectionData certRequest;
-
- QString wireGuardClientPrivKey;
- QString wireGuardClientPubKey;
- };
-
- ApiPayloadData generateApiPayloadData(const QString &protocol);
- QJsonObject fillApiPayload(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData);
- void fillServerConfig(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData, const QByteArray &apiResponseBody,
- QJsonObject &serverConfig);
- QStringList getProxyUrls();
-
- QString m_gatewayEndpoint;
- QStringList m_proxyUrls;
- bool m_isDevEnvironment = false;
-};
-
-#endif // APICONTROLLER_H
diff --git a/client/core/controllers/coreController.cpp b/client/core/controllers/coreController.cpp
new file mode 100644
index 00000000..0e72ef1a
--- /dev/null
+++ b/client/core/controllers/coreController.cpp
@@ -0,0 +1,399 @@
+#include "coreController.h"
+
+#include
+#include
+
+#if defined(Q_OS_ANDROID)
+ #include "core/installedAppsImageProvider.h"
+ #include "platforms/android/android_controller.h"
+#endif
+
+#if defined(Q_OS_IOS)
+ #include "platforms/ios/ios_controller.h"
+ #include
+#endif
+
+CoreController::CoreController(const QSharedPointer &vpnConnection, const std::shared_ptr &settings,
+ QQmlApplicationEngine *engine, QObject *parent)
+ : QObject(parent), m_vpnConnection(vpnConnection), m_settings(settings), m_engine(engine)
+{
+ initModels();
+ initControllers();
+ initSignalHandlers();
+
+ initAndroidController();
+ initAppleController();
+
+ initNotificationHandler();
+
+ auto locale = m_settings->getAppLanguage();
+ m_translator.reset(new QTranslator());
+ updateTranslator(locale);
+}
+
+void CoreController::initModels()
+{
+ m_containersModel.reset(new ContainersModel(this));
+ m_engine->rootContext()->setContextProperty("ContainersModel", m_containersModel.get());
+
+ m_defaultServerContainersModel.reset(new ContainersModel(this));
+ m_engine->rootContext()->setContextProperty("DefaultServerContainersModel", m_defaultServerContainersModel.get());
+
+ m_serversModel.reset(new ServersModel(m_settings, this));
+ m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get());
+
+ m_languageModel.reset(new LanguageModel(m_settings, this));
+ m_engine->rootContext()->setContextProperty("LanguageModel", m_languageModel.get());
+
+ m_sitesModel.reset(new SitesModel(m_settings, this));
+ m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get());
+
+ m_allowedDnsModel.reset(new AllowedDnsModel(m_settings, this));
+ m_engine->rootContext()->setContextProperty("AllowedDnsModel", m_allowedDnsModel.get());
+
+ m_appSplitTunnelingModel.reset(new AppSplitTunnelingModel(m_settings, this));
+ m_engine->rootContext()->setContextProperty("AppSplitTunnelingModel", m_appSplitTunnelingModel.get());
+
+ m_protocolsModel.reset(new ProtocolsModel(m_settings, this));
+ m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get());
+
+ m_openVpnConfigModel.reset(new OpenVpnConfigModel(this));
+ m_engine->rootContext()->setContextProperty("OpenVpnConfigModel", m_openVpnConfigModel.get());
+
+ m_shadowSocksConfigModel.reset(new ShadowSocksConfigModel(this));
+ m_engine->rootContext()->setContextProperty("ShadowSocksConfigModel", m_shadowSocksConfigModel.get());
+
+ m_cloakConfigModel.reset(new CloakConfigModel(this));
+ m_engine->rootContext()->setContextProperty("CloakConfigModel", m_cloakConfigModel.get());
+
+ m_wireGuardConfigModel.reset(new WireGuardConfigModel(this));
+ m_engine->rootContext()->setContextProperty("WireGuardConfigModel", m_wireGuardConfigModel.get());
+
+ m_awgConfigModel.reset(new AwgConfigModel(this));
+ m_engine->rootContext()->setContextProperty("AwgConfigModel", m_awgConfigModel.get());
+
+ m_xrayConfigModel.reset(new XrayConfigModel(this));
+ m_engine->rootContext()->setContextProperty("XrayConfigModel", m_xrayConfigModel.get());
+
+#ifdef Q_OS_WINDOWS
+ m_ikev2ConfigModel.reset(new Ikev2ConfigModel(this));
+ m_engine->rootContext()->setContextProperty("Ikev2ConfigModel", m_ikev2ConfigModel.get());
+#endif
+
+ m_sftpConfigModel.reset(new SftpConfigModel(this));
+ m_engine->rootContext()->setContextProperty("SftpConfigModel", m_sftpConfigModel.get());
+
+ m_socks5ConfigModel.reset(new Socks5ProxyConfigModel(this));
+ m_engine->rootContext()->setContextProperty("Socks5ProxyConfigModel", m_socks5ConfigModel.get());
+
+ m_clientManagementModel.reset(new ClientManagementModel(m_settings, this));
+ m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get());
+
+ m_apiServicesModel.reset(new ApiServicesModel(this));
+ m_engine->rootContext()->setContextProperty("ApiServicesModel", m_apiServicesModel.get());
+
+ m_apiCountryModel.reset(new ApiCountryModel(this));
+ m_engine->rootContext()->setContextProperty("ApiCountryModel", m_apiCountryModel.get());
+
+ m_apiAccountInfoModel.reset(new ApiAccountInfoModel(this));
+ m_engine->rootContext()->setContextProperty("ApiAccountInfoModel", m_apiAccountInfoModel.get());
+
+ m_apiDevicesModel.reset(new ApiDevicesModel(m_settings, this));
+ m_engine->rootContext()->setContextProperty("ApiDevicesModel", m_apiDevicesModel.get());
+}
+
+void CoreController::initControllers()
+{
+ m_connectionController.reset(
+ new ConnectionController(m_serversModel, m_containersModel, m_clientManagementModel, m_vpnConnection, m_settings));
+ m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get());
+
+ m_pageController.reset(new PageController(m_serversModel, m_settings));
+ m_engine->rootContext()->setContextProperty("PageController", m_pageController.get());
+
+ m_focusController.reset(new FocusController(m_engine, this));
+ m_engine->rootContext()->setContextProperty("FocusController", m_focusController.get());
+
+ m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_clientManagementModel, m_settings));
+ m_engine->rootContext()->setContextProperty("InstallController", m_installController.get());
+
+ connect(m_installController.get(), &InstallController::currentContainerUpdated, m_connectionController.get(),
+ &ConnectionController::onCurrentContainerUpdated); // TODO remove this
+
+ m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings));
+ m_engine->rootContext()->setContextProperty("ImportController", m_importController.get());
+
+ m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_clientManagementModel, m_settings));
+ m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get());
+
+ m_settingsController.reset(
+ new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, m_appSplitTunnelingModel, m_settings));
+ m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get());
+
+ m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel));
+ m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get());
+
+ m_allowedDnsController.reset(new AllowedDnsController(m_settings, m_allowedDnsModel));
+ m_engine->rootContext()->setContextProperty("AllowedDnsController", m_allowedDnsController.get());
+
+ m_appSplitTunnelingController.reset(new AppSplitTunnelingController(m_settings, m_appSplitTunnelingModel));
+ m_engine->rootContext()->setContextProperty("AppSplitTunnelingController", m_appSplitTunnelingController.get());
+
+ m_systemController.reset(new SystemController(m_settings));
+ m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get());
+
+ m_apiSettingsController.reset(
+ new ApiSettingsController(m_serversModel, m_apiAccountInfoModel, m_apiCountryModel, m_apiDevicesModel, m_settings));
+ m_engine->rootContext()->setContextProperty("ApiSettingsController", m_apiSettingsController.get());
+
+ m_apiConfigsController.reset(new ApiConfigsController(m_serversModel, m_apiServicesModel, m_settings));
+ m_engine->rootContext()->setContextProperty("ApiConfigsController", m_apiConfigsController.get());
+
+ m_apiPremV1MigrationController.reset(new ApiPremV1MigrationController(m_serversModel, m_settings, this));
+ m_engine->rootContext()->setContextProperty("ApiPremV1MigrationController", m_apiPremV1MigrationController.get());
+}
+
+void CoreController::initAndroidController()
+{
+#ifdef Q_OS_ANDROID
+ if (!AndroidController::initLogging()) {
+ qFatal("Android logging initialization failed");
+ }
+ AndroidController::instance()->setSaveLogs(m_settings->isSaveLogs());
+ 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, [this](Vpn::ConnectionState state) {
+ m_connectionController->onConnectionStateChanged(state);
+ if (m_vpnConnection)
+ m_vpnConnection->restoreConnection();
+ });
+ if (!AndroidController::instance()->initialize()) {
+ qFatal("Android controller initialization failed");
+ }
+
+ connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, this, [this](QString data) {
+ emit m_pageController->goToPageHome();
+ m_importController->extractConfigFromData(data);
+ data.clear();
+ emit m_pageController->goToPageViewConfig();
+ });
+
+ m_engine->addImageProvider(QLatin1String("installedAppImage"), new InstalledAppsImageProvider);
+#endif
+}
+
+void CoreController::initAppleController()
+{
+#ifdef Q_OS_IOS
+ IosController::Instance()->initialize();
+ connect(IosController::Instance(), &IosController::importConfigFromOutside, this, [this](QString data) {
+ emit m_pageController->goToPageHome();
+ m_importController->extractConfigFromData(data);
+ emit m_pageController->goToPageViewConfig();
+ });
+
+ connect(IosController::Instance(), &IosController::importBackupFromOutside, this, [this](QString filePath) {
+ emit m_pageController->goToPageHome();
+ m_pageController->goToPageSettingsBackup();
+ emit 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
+}
+
+void CoreController::initSignalHandlers()
+{
+ initErrorMessagesHandler();
+
+ initApiCountryModelUpdateHandler();
+ initContainerModelUpdateHandler();
+ initAdminConfigRevokedHandler();
+ initPassphraseRequestHandler();
+ initTranslationsUpdatedHandler();
+ initAutoConnectHandler();
+ initAmneziaDnsToggledHandler();
+ initPrepareConfigHandler();
+ initImportPremiumV2VpnKeyHandler();
+ initShowMigrationDrawerHandler();
+ initStrictKillSwitchHandler();
+}
+
+void CoreController::initNotificationHandler()
+{
+#ifndef Q_OS_ANDROID
+ m_notificationHandler.reset(NotificationHandler::create(nullptr));
+
+ connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(),
+ &NotificationHandler::setConnectionState);
+
+ connect(m_notificationHandler.get(), &NotificationHandler::raiseRequested, m_pageController.get(), &PageController::raiseMainWindow);
+ connect(m_notificationHandler.get(), &NotificationHandler::connectRequested, m_connectionController.get(),
+ static_cast(&ConnectionController::openConnection));
+ connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(),
+ &ConnectionController::closeConnection);
+ connect(this, &CoreController::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated);
+#endif
+}
+
+void CoreController::updateTranslator(const QLocale &locale)
+{
+ if (!m_translator->isEmpty()) {
+ QCoreApplication::removeTranslator(m_translator.get());
+ }
+
+ QStringList availableTranslations;
+ QDirIterator it(":/translations", QStringList("amneziavpn_*.qm"), QDir::Files);
+ while (it.hasNext()) {
+ availableTranslations << it.next();
+ }
+
+ // This code allow to load translation for the language only, without country code
+ const QString lang = locale.name().split("_").first();
+ const QString translationFilePrefix = QString(":/translations/amneziavpn_") + lang;
+ QString strFileName = QString(":/translations/amneziavpn_%1.qm").arg(locale.name());
+ for (const QString &translation : availableTranslations) {
+ if (translation.contains(translationFilePrefix)) {
+ strFileName = translation;
+ break;
+ }
+ }
+
+ if (m_translator->load(strFileName)) {
+ if (QCoreApplication::installTranslator(m_translator.get())) {
+ m_settings->setAppLanguage(locale);
+ }
+ } else {
+ m_settings->setAppLanguage(QLocale::English);
+ }
+
+ m_engine->retranslate();
+
+ emit translationsUpdated();
+}
+
+void CoreController::initErrorMessagesHandler()
+{
+ connect(m_connectionController.get(), &ConnectionController::connectionErrorOccurred, this, [this](ErrorCode errorCode) {
+ emit m_pageController->showErrorMessage(errorCode);
+ emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
+ });
+
+ connect(m_apiConfigsController.get(), &ApiConfigsController::errorOccurred, m_pageController.get(),
+ qOverload(&PageController::showErrorMessage));
+}
+
+void CoreController::setQmlRoot()
+{
+ m_systemController->setQmlRoot(m_engine->rootObjects().value(0));
+}
+
+void CoreController::initApiCountryModelUpdateHandler()
+{
+ // TODO
+ connect(m_serversModel.get(), &ServersModel::updateApiCountryModel, this, [this]() {
+ m_apiCountryModel->updateModel(m_serversModel->getProcessedServerData("apiAvailableCountries").toJsonArray(),
+ m_serversModel->getProcessedServerData("apiServerCountryCode").toString());
+ });
+ connect(m_serversModel.get(), &ServersModel::updateApiServicesModel, this,
+ [this]() { m_apiServicesModel->updateModel(m_serversModel->getProcessedServerData("apiConfig").toJsonObject()); });
+}
+
+void CoreController::initContainerModelUpdateHandler()
+{
+ connect(m_serversModel.get(), &ServersModel::containersUpdated, m_containersModel.get(), &ContainersModel::updateModel);
+ connect(m_serversModel.get(), &ServersModel::defaultServerContainersUpdated, m_defaultServerContainersModel.get(),
+ &ContainersModel::updateModel);
+ m_serversModel->resetModel();
+}
+
+void CoreController::initAdminConfigRevokedHandler()
+{
+ connect(m_clientManagementModel.get(), &ClientManagementModel::adminConfigRevoked, m_serversModel.get(),
+ &ServersModel::clearCachedProfile);
+}
+
+void CoreController::initPassphraseRequestHandler()
+{
+ connect(m_installController.get(), &InstallController::passphraseRequestStarted, m_pageController.get(),
+ &PageController::showPassphraseRequestDrawer);
+ connect(m_pageController.get(), &PageController::passphraseRequestDrawerClosed, m_installController.get(),
+ &InstallController::setEncryptedPassphrase);
+}
+
+void CoreController::initTranslationsUpdatedHandler()
+{
+ connect(m_languageModel.get(), &LanguageModel::updateTranslations, this, &CoreController::updateTranslator);
+ connect(this, &CoreController::translationsUpdated, m_languageModel.get(), &LanguageModel::translationsUpdated);
+ connect(this, &CoreController::translationsUpdated, m_connectionController.get(), &ConnectionController::onTranslationsUpdated);
+}
+
+void CoreController::initAutoConnectHandler()
+{
+ if (m_settingsController->isAutoConnectEnabled() && m_serversModel->getDefaultServerIndex() >= 0) {
+ QTimer::singleShot(1000, this, [this]() { m_connectionController->openConnection(); });
+ }
+}
+
+void CoreController::initAmneziaDnsToggledHandler()
+{
+ connect(m_settingsController.get(), &SettingsController::amneziaDnsToggled, m_serversModel.get(), &ServersModel::toggleAmneziaDns);
+}
+
+void CoreController::initPrepareConfigHandler()
+{
+ connect(m_connectionController.get(), &ConnectionController::prepareConfig, this, [this]() {
+ emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Preparing);
+
+ if (!m_apiConfigsController->isConfigValid()) {
+ emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
+ return;
+ }
+
+ if (!m_installController->isConfigValid()) {
+ emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
+ return;
+ }
+
+ m_connectionController->openConnection();
+ });
+}
+
+void CoreController::initImportPremiumV2VpnKeyHandler()
+{
+ connect(m_apiPremV1MigrationController.get(), &ApiPremV1MigrationController::importPremiumV2VpnKey, this, [this](const QString &vpnKey) {
+ m_importController->extractConfigFromData(vpnKey);
+ m_importController->importConfig();
+
+ emit m_apiPremV1MigrationController->migrationFinished();
+ });
+}
+
+void CoreController::initShowMigrationDrawerHandler()
+{
+ QTimer::singleShot(1000, this, [this]() {
+ if (m_apiPremV1MigrationController->isPremV1MigrationReminderActive() && m_apiPremV1MigrationController->hasConfigsToMigration()) {
+ m_apiPremV1MigrationController->showMigrationDrawer();
+ }
+ });
+}
+
+void CoreController::initStrictKillSwitchHandler()
+{
+ connect(m_settingsController.get(), &SettingsController::strictKillSwitchEnabledChanged, m_vpnConnection.get(),
+ &VpnConnection::onKillSwitchModeChanged);
+}
+
+QSharedPointer CoreController::pageController() const
+{
+ return m_pageController;
+}
diff --git a/client/core/controllers/coreController.h b/client/core/controllers/coreController.h
new file mode 100644
index 00000000..9ae53562
--- /dev/null
+++ b/client/core/controllers/coreController.h
@@ -0,0 +1,145 @@
+#ifndef CORECONTROLLER_H
+#define CORECONTROLLER_H
+
+#include
+#include
+#include
+
+#include "ui/controllers/api/apiConfigsController.h"
+#include "ui/controllers/api/apiSettingsController.h"
+#include "ui/controllers/api/apiPremV1MigrationController.h"
+#include "ui/controllers/appSplitTunnelingController.h"
+#include "ui/controllers/allowedDnsController.h"
+#include "ui/controllers/connectionController.h"
+#include "ui/controllers/exportController.h"
+#include "ui/controllers/focusController.h"
+#include "ui/controllers/importController.h"
+#include "ui/controllers/installController.h"
+#include "ui/controllers/pageController.h"
+#include "ui/controllers/settingsController.h"
+#include "ui/controllers/sitesController.h"
+#include "ui/controllers/systemController.h"
+
+#include "ui/models/allowed_dns_model.h"
+#include "ui/models/containers_model.h"
+#include "ui/models/languageModel.h"
+#include "ui/models/protocols/cloakConfigModel.h"
+#ifdef Q_OS_WINDOWS
+ #include "ui/models/protocols/ikev2ConfigModel.h"
+#endif
+#include "ui/models/api/apiAccountInfoModel.h"
+#include "ui/models/api/apiCountryModel.h"
+#include "ui/models/api/apiDevicesModel.h"
+#include "ui/models/api/apiServicesModel.h"
+#include "ui/models/appSplitTunnelingModel.h"
+#include "ui/models/clientManagementModel.h"
+#include "ui/models/protocols/awgConfigModel.h"
+#include "ui/models/protocols/openvpnConfigModel.h"
+#include "ui/models/protocols/shadowsocksConfigModel.h"
+#include "ui/models/protocols/wireguardConfigModel.h"
+#include "ui/models/protocols/xrayConfigModel.h"
+#include "ui/models/protocols_model.h"
+#include "ui/models/servers_model.h"
+#include "ui/models/services/sftpConfigModel.h"
+#include "ui/models/services/socks5ProxyConfigModel.h"
+#include "ui/models/sites_model.h"
+
+#ifndef Q_OS_ANDROID
+ #include "ui/notificationhandler.h"
+#endif
+
+class CoreController : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit CoreController(const QSharedPointer &vpnConnection, const std::shared_ptr &settings,
+ QQmlApplicationEngine *engine, QObject *parent = nullptr);
+
+ QSharedPointer pageController() const;
+ void setQmlRoot();
+
+signals:
+ void translationsUpdated();
+
+private:
+ void initModels();
+ void initControllers();
+ void initAndroidController();
+ void initAppleController();
+ void initSignalHandlers();
+
+ void initNotificationHandler();
+
+ void updateTranslator(const QLocale &locale);
+
+ void initErrorMessagesHandler();
+
+ void initApiCountryModelUpdateHandler();
+ void initContainerModelUpdateHandler();
+ void initAdminConfigRevokedHandler();
+ void initPassphraseRequestHandler();
+ void initTranslationsUpdatedHandler();
+ void initAutoConnectHandler();
+ void initAmneziaDnsToggledHandler();
+ void initPrepareConfigHandler();
+ void initImportPremiumV2VpnKeyHandler();
+ void initShowMigrationDrawerHandler();
+ void initStrictKillSwitchHandler();
+
+ QQmlApplicationEngine *m_engine {}; // TODO use parent child system here?
+ std::shared_ptr m_settings;
+ QSharedPointer m_vpnConnection;
+ QSharedPointer m_translator;
+
+#ifndef Q_OS_ANDROID
+ QScopedPointer m_notificationHandler;
+#endif
+
+ QMetaObject::Connection m_reloadConfigErrorOccurredConnection;
+
+ QScopedPointer m_connectionController;
+ QScopedPointer m_focusController;
+ QSharedPointer m_pageController; // TODO
+ QScopedPointer m_installController;
+ QScopedPointer m_importController;
+ QScopedPointer m_exportController;
+ QScopedPointer m_settingsController;
+ QScopedPointer m_sitesController;
+ QScopedPointer m_systemController;
+ QScopedPointer m_appSplitTunnelingController;
+ QScopedPointer m_allowedDnsController;
+
+ QScopedPointer m_apiSettingsController;
+ QScopedPointer m_apiConfigsController;
+ QScopedPointer m_apiPremV1MigrationController;
+
+ QSharedPointer m_containersModel;
+ QSharedPointer m_defaultServerContainersModel;
+ QSharedPointer m_serversModel;
+ QSharedPointer m_languageModel;
+ QSharedPointer m_protocolsModel;
+ QSharedPointer m_sitesModel;
+ QSharedPointer m_allowedDnsModel;
+ QSharedPointer m_appSplitTunnelingModel;
+ QSharedPointer m_clientManagementModel;
+
+ QSharedPointer m_apiServicesModel;
+ QSharedPointer m_apiCountryModel;
+ QSharedPointer m_apiAccountInfoModel;
+ QSharedPointer m_apiDevicesModel;
+
+ QScopedPointer m_openVpnConfigModel;
+ QScopedPointer m_shadowSocksConfigModel;
+ QScopedPointer m_cloakConfigModel;
+ QScopedPointer m_xrayConfigModel;
+ QScopedPointer m_wireGuardConfigModel;
+ QScopedPointer m_awgConfigModel;
+#ifdef Q_OS_WINDOWS
+ QScopedPointer m_ikev2ConfigModel;
+#endif
+ QScopedPointer m_sftpConfigModel;
+ QScopedPointer m_socks5ConfigModel;
+};
+
+#endif // CORECONTROLLER_H
diff --git a/client/core/controllers/gatewayController.cpp b/client/core/controllers/gatewayController.cpp
new file mode 100644
index 00000000..26855ae6
--- /dev/null
+++ b/client/core/controllers/gatewayController.cpp
@@ -0,0 +1,364 @@
+#include "gatewayController.h"
+
+#include
+#include
+
+#include
+#include