Compare commits

...

28 Commits

Author SHA1 Message Date
aiamnezia 238d149593 fix: enhance encryption handling in SecureQSettings
* Updated encryptText to return std::optional<QByteArray> for better error handling.
* Added fallback to plaintext storage if encryption fails in setValue method.
* Improved logging for encryption errors and empty key scenarios.
2026-01-20 14:48:17 +04:00
vkamn b591dd7445 fix: minor fixes (#2137)
* refactor: removed premv1 migration code

* fix: i1-i5 parsing when scaning server

* chore: bump version
2026-01-19 14:03:54 +08:00
vkamn a45bb5ea4f chore: bump version (#2108)
* chore: bump version

* chore: fix deploy.yml

* chore: return jurplel/install-qt-action@v3

* chore: bump qt version

* chore: disable cache

* chore: fix qt bin folder path

* chore: downgraded qt version for linux

* chore: disable gradle cache

* chore: use large runner for linux and android

* chore: change runner name for android and linux

* fix: change github runner label

* fix: set github runner specific os version in label

* chore: add self-hosted runner ubuntu-24.04-4cores

* fix: changed label to self-hosted for github runners

* fix: changed label to 4-core for github runners

* fix: fixed app closing delay

* fix: fixed awg description

* chore: bump version

---------

Co-authored-by: irvinklause <ik@amnezia.org>
2026-01-15 15:48:48 +08:00
yyy-amnezia d859b111ca feat: awg connection states (#2091)
* Submodule amneziawg-apple updated

* feat: add support for controlled junk and special handshake timeout in AWG configurator

* refactor: improve AWG configurator and iOS controller logic

* awg_configurator.cpp reverted
2025-12-30 10:45:32 +08:00
Artyom Titov 52031efc48 fix(): set desktopFileName for Wayland (#2104) 2025-12-29 19:18:44 +08:00
vkamn d78202c612 chore: is-test-flight processing (#2093)
* fix: context menu fixes for qt6.9

* chore: is-test-flight porcessing

* chore: bump version and minor build fixes

* refactor: moved test purchase processing on client side

* fix: fixed free import on ios

* chore: bump qt version in deploy.yml

* fix: minor fixes
2025-12-29 19:18:03 +08:00
yyy-amnezia 6bac948633 refactor: move iOS/macOS NE specific disconnect logic to the top of disconnectFromVpn method (#2100) 2025-12-27 11:09:11 +08:00
vkamn a4c4ef71fb fix: minor fixes (#2099)
* fix: fixed saving i1-i5 fields

* fix: fixed default value for s4

* fix: fixed server name when sharing admin config
2025-12-26 22:55:57 +08:00
Yaroslav Gurov 127f85f4f0 fix: replace arm64 macos awg blob with amd64 one (#2096) 2025-12-24 22:28:31 +08:00
MrMirDan 13d4ddd292 chore: ru translation (#2086) 2025-12-23 20:17:27 +08:00
lunardunno 7265e09c85 chore: improved retrieving of images list (#2084)
Improved retrieving list of images named amnezia for Docker Engine 29.1.3 cleanup.
2025-12-23 12:20:44 +08:00
Yaroslav Gurov 2e629b6dac chore: bump awg version (#2088) 2025-12-19 23:40:48 +08:00
Yaroslav Gurov 92aba49705 fix: cannot connect to IPC on Windows (#2083)
* fix: replace localsocket by QtRO-embedded one

* fix: make IpcClient initialization lazy
2025-12-19 22:44:42 +08:00
vkamn bec06b3a5e chore: bump version (#2080) 2025-12-19 11:46:10 +08:00
Yaroslav Gurov 91cd9474ea fix: safe IpcClient calls (#2076)
* fix: safe IpcClient calls

* fix: double free by specifying parent

* fix: windows includes for ikev2
2025-12-19 11:09:50 +08:00
Yaroslav 6178b05643 feat: ios in-app purchase methods (#1652)
* Add in-app purchase methods

* fix: init StoreKit controller on startup

* fix: Add transaction details to StoreKit callbacks

* nullpointer access fixed

* feat: in app purchase for ios

* feat: add IAP product fetching and logging for iOS platform

* feat: iOS Simulator building pipeline made

* feat: add support for multiple IAP product IDs and attempt purchase of the first valid one

* feat: add support for retrieving Base64-encoded app receipt after successful IAP purchase

* refactor: inapp-purchase code cleanup

* feat: iap processing

* refactor: move to storekit 2

* feat: add request to billing

* chore: add ios ifdef

* feat: remove iOS simulator specific code and exclusions

* refactor: remove unused StoreKit 2 transaction observer and simplify IAP product fetching logic

* feat: implement StoreKit 2 for iOS and macOS, add restore purchases functionality

* fix: Restore Purchases button appearance updated

* feat: enhance error handling and duplicate config detection in ApiConfigsController

* feat: add support for Mac OS NE in-app purchases and StoreKitController

* ci-cd fix

* Revert "ci-cd fix"

This reverts commit f22fd7a13b.

---------

Co-authored-by: vladimir.kuznetsov <nethiuswork@gmail.com>
Co-authored-by: vkamn <vk@amnezia.org>
Co-authored-by: spectrum <yyy@amnezia.org>
2025-12-18 22:36:12 +08:00
vkamn 46ce22b85c fix: fixed awg2 container processing (#2067) 2025-12-18 22:25:20 +08:00
NickVs2015 36edafb985 feat: add qt 6.10.1 support (#2065)
* feat: switch to qt 6.10.1

* feat: switch to qt 6.10.1 remove touch
2025-12-18 20:18:32 +08:00
Yaroslav Gurov d77eaba500 fix: make ipc client thread-safe (#2075) 2025-12-18 20:18:11 +08:00
yyy-amnezia 6a3d43fbb0 fix: iPad startup crash fix (#2071) 2025-12-17 21:54:27 +08:00
yyy-amnezia 4975955bbe feat: update GitHub workflow to use latest macOS, Xcode, and Qt versions, and add Go installation and gomobile setup (#2073) 2025-12-17 21:53:12 +08:00
Yaroslav Gurov 8f508783e3 fix: make ipc connection a singleton (#2069) 2025-12-16 23:05:31 +08:00
NickVs2015 f50817c43c feat: switch to qt 6.10.1 (#2057)
* feat: switch to qt 6.10.1

* feat: switch to qt 6.10.1 remove touch
2025-12-15 21:56:36 +08:00
Yaroslav Gurov 54f67b3d82 feat: native split-tunneling for xray (#1899)
* feat: integrated xray as a library and added split-tunneling

* fix: added copying amnezia_xray.dll to build dir

* fix: changed path on darwin

* chore: clean up getting default device

* chore: removed WSAGetLastError from sockopt logging

* fix: get rid of debug logs in xray handlers

* fix: minor fixes and xray debugging capabilities

* fix: macos default interface fix

* fix: roll-back ipv6 sockopt for mac

* fix: bind IPv6 on Windows

* fix: (win) better IPv6 handling and router fixes

* feat: prebuilts uploaded

* fix: removed redundant cmake definitions

* feat: moved xray to service process, reworked errors

* fix: return values in networkUtilities

* fix: macos build fixes

* fix: (windows) cmake fixes

* fix: (windows) compilation fix

* fix: (windows) changed location of amnezia_xray.dll

* feat: xray logs added to system service

* chore: bump xray&tun2socks versions for android

* chore: cleanup of XrayProtocol class
* removed killswitch
* removed redundant members and basic cleanup

* feat: support split-tunneling in iOS and macOS NE

* chore: update active interface index based on network path and available interfaces

* refactor: update network path handling and logging in PacketTunnelProvider

* chore: bump xray deps

---------

Co-authored-by: Yaroslav Yashin <yaroslav.yashin@gmail.com>
2025-12-15 21:54:34 +08:00
vkamn d669adb707 feat: msi installer and cli command (#2020)
* feat: Add msi quite installer

* chore: update code for new wix

* feat: add cpack wix installer

* feat: add gihub workflow for msi

* chore: fix deploy script

* chore: add wix logs

* chore: fix msi build

* chore: fix msi build

* chore: add wix exts log

* chore: add cpackwixpatch for registering the service

* chore: fix build script

* chore: fix wix fragment

* feat: add closing app with reinstalling

* chore: update version for test

* chore: fix build script

* feat: added cli commands --connect and --import (#1967)

* fix: delete unused file and disable rollback after unsuccessful service start in msi installer

* fix: Add deps to msi

* fix: msi deps

* feat: added os signal handler

* fix: incorrect import at the empty client start (#2024)

* chore: add force quit for os signal handler

* feat: os signal handler improvements

* fix: fixed --connection command

---------

Co-authored-by: Mykola Baibuz <mykola.baibuz@gmail.com>
Co-authored-by: aiamnezia <ai@amnezia.org>
Co-authored-by: Mitternacht822 <sb@amnezia.org>
2025-12-11 18:54:24 +08:00
albexk 5103bc640e feat: implement reconnection in AWG by turning the VPN off and on (#2046) 2025-12-11 18:51:19 +08:00
vkamn 3e6f0c0342 feat: add timestamp to news list page (#2050) 2025-12-11 18:51:01 +08:00
vkamn 40950b92ee feat: awg 2 support (#1836)
* Add updated awg container

* add missing files

* Hide uninstalled AwgLegacy container

* Fix resources file

* Add role for allowed for installation containers

* Add native config sharing for new Awg container

* Fix not opening awg settings

* Remove AwgLegacy from wizard manual installation page

* Fix AmneziaWG settings

* chore: update link to submodule

* refactor: remove j1-j3 and itime

* chore: return s3 s4 fields to ui

* fix: awg2 native config compatability

* chore: update packet size validation

* feat: add awg2 support in self-hosted containers

* fix: delete parameters from server config

* feat: add H-parameters  validation as a strings

* chore: update link to submodule

* chore: add containers type for awg 1.5 and awg 2

* chore: fixed s3/s4 visibility for awg 1

---------

Co-authored-by: aiamnezia <ai@amnezia.org>
2025-12-11 15:18:36 +08:00
138 changed files with 3673 additions and 2306 deletions
+72 -39
View File
@@ -10,10 +10,10 @@ env:
jobs: jobs:
Build-Linux-Ubuntu: Build-Linux-Ubuntu:
runs-on: ubuntu-22.04 runs-on: 4-core
env: env:
QT_VERSION: 6.6.2 QT_VERSION: 6.8.3
QIF_VERSION: 4.7 QIF_VERSION: 4.7
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }} PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }} PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
@@ -30,13 +30,15 @@ jobs:
version: ${{ env.QT_VERSION }} version: ${{ env.QT_VERSION }}
host: 'linux' host: 'linux'
target: 'desktop' target: 'desktop'
arch: 'gcc_64' arch: 'linux_gcc_64'
modules: 'qtremoteobjects qt5compat qtshadertools' modules: 'qtremoteobjects qt5compat qtshadertools'
dir: ${{ runner.temp }} dir: ${{ runner.temp }}
setup-python: 'true' setup-python: 'true'
tools: 'tools_ifw' tools: 'tools_ifw'
set-env: 'true' set-env: 'true'
extra: '--external 7z --base ${{ env.QT_MIRROR }}' aqtversion: '==3.3.0'
py7zrversion: '==0.22.*'
extra: '--base ${{ env.QT_MIRROR }}'
- name: 'Get sources' - name: 'Get sources'
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -51,8 +53,8 @@ jobs:
echo "VERSION=$VERSION" >> $GITHUB_ENV echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "Version: $VERSION" echo "Version: $VERSION"
- name: 'Setup ccache' # - name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2 # uses: hendrikmuhs/ccache-action@v1.2
- name: 'Build project' - name: 'Build project'
run: | run: |
@@ -91,7 +93,7 @@ jobs:
runs-on: windows-latest runs-on: windows-latest
env: env:
QT_VERSION: 6.6.2 QT_VERSION: 6.10.1
QIF_VERSION: 4.7 QIF_VERSION: 4.7
BUILD_ARCH: 64 BUILD_ARCH: 64
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }} PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
@@ -117,8 +119,8 @@ jobs:
echo "VERSION=$VERSION" >> $GITHUB_ENV echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "Version: $VERSION" echo "Version: $VERSION"
- name: 'Setup ccache' # - name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2 # uses: hendrikmuhs/ccache-action@v1.2
- name: 'Install Qt' - name: 'Install Qt'
uses: jurplel/install-qt-action@v3 uses: jurplel/install-qt-action@v3
@@ -126,25 +128,43 @@ jobs:
version: ${{ env.QT_VERSION }} version: ${{ env.QT_VERSION }}
host: 'windows' host: 'windows'
target: 'desktop' target: 'desktop'
arch: 'win64_msvc2019_64' arch: 'win64_msvc2022_64'
modules: 'qtremoteobjects qt5compat qtshadertools' modules: 'qtremoteobjects qt5compat qtshadertools'
dir: ${{ runner.temp }} dir: ${{ runner.temp }}
setup-python: 'true' setup-python: 'true'
tools: 'tools_ifw' tools: 'tools_ifw'
set-env: 'true' set-env: 'true'
extra: '--external 7z --base ${{ env.QT_MIRROR }}' aqtversion: '==3.3.0'
py7zrversion: '==0.22.*'
extra: '--base ${{ env.QT_MIRROR }}'
- name: 'Setup mvsc' - name: 'Setup mvsc'
uses: ilammy/msvc-dev-cmd@v1 uses: ilammy/msvc-dev-cmd@v1
with: with:
arch: 'x64' arch: 'x64'
- name: 'Setup .NET SDK'
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: 'Install WiX Toolset'
shell: powershell
run: |
dotnet tool install --global wix --version 4.0.6
wix extension add -g WixToolset.UI.wixext/4.0.6
wix extension add -g WixToolset.Util.wixext/4.0.6
wix extension list -g
$wixBinDir = Join-Path $env:USERPROFILE ".dotnet\tools"
echo "WIX_BIN_DIR=$wixBinDir" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
- name: 'Build project' - name: 'Build project'
shell: cmd shell: cmd
run: | run: |
set BUILD_ARCH=${{ env.BUILD_ARCH }} set BUILD_ARCH=${{ env.BUILD_ARCH }}
set QT_BIN_DIR="${{ runner.temp }}\\Qt\\${{ env.QT_VERSION }}\\msvc2019_64\\bin" set QT_BIN_DIR="${{ runner.temp }}\\Qt\\${{ env.QT_VERSION }}\\msvc2022_64\\bin"
set QIF_BIN_DIR="${{ runner.temp }}\\Qt\\Tools\\QtInstallerFramework\\${{ env.QIF_VERSION }}\\bin" set QIF_BIN_DIR="${{ runner.temp }}\\Qt\\Tools\\QtInstallerFramework\\${{ env.QIF_VERSION }}\\bin"
set WIX_BIN_DIR=%USERPROFILE%\.dotnet\tools
call deploy\\build_windows.bat call deploy\\build_windows.bat
- name: 'Rename Windows installer' - name: 'Rename Windows installer'
@@ -159,6 +179,13 @@ jobs:
path: AmneziaVPN_${{ env.VERSION }}_x64.exe path: AmneziaVPN_${{ env.VERSION }}_x64.exe
retention-days: 7 retention-days: 7
- name: 'Upload MSI installer artifact'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_Windows_MSI_installer
path: AmneziaVPN_x${{ env.BUILD_ARCH }}.msi
retention-days: 7
- name: 'Upload unpacked artifact' - name: 'Upload unpacked artifact'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
@@ -169,10 +196,10 @@ jobs:
# ------------------------------------------------------ # ------------------------------------------------------
Build-iOS: Build-iOS:
runs-on: macos-13 runs-on: macos-latest
env: env:
QT_VERSION: 6.6.2 QT_VERSION: 6.10.1
CC: cc CC: cc
CXX: c++ CXX: c++
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }} PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
@@ -187,7 +214,7 @@ jobs:
- name: 'Setup xcode' - name: 'Setup xcode'
uses: maxim-lobanov/setup-xcode@v1 uses: maxim-lobanov/setup-xcode@v1
with: with:
xcode-version: '15.2' xcode-version: '26.1'
- name: 'Install desktop Qt' - name: 'Install desktop Qt'
uses: jurplel/install-qt-action@v3 uses: jurplel/install-qt-action@v3
@@ -231,8 +258,8 @@ jobs:
submodules: 'true' submodules: 'true'
fetch-depth: 10 fetch-depth: 10
- name: 'Setup ccache' # - name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2 # uses: hendrikmuhs/ccache-action@v1.2
- name: 'Install dependencies' - name: 'Install dependencies'
run: pip install jsonschema jinja2 run: pip install jsonschema jinja2
@@ -323,8 +350,8 @@ jobs:
submodules: 'true' submodules: 'true'
fetch-depth: 10 fetch-depth: 10
- name: 'Setup ccache' # - name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2 # uses: hendrikmuhs/ccache-action@v1.2
- name: 'Build project' - name: 'Build project'
run: | run: |
@@ -351,7 +378,7 @@ jobs:
runs-on: macos-latest runs-on: macos-latest
env: env:
QT_VERSION: 6.8.3 QT_VERSION: 6.10.1
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }} MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
@@ -389,15 +416,11 @@ jobs:
arch: 'clang_64' arch: 'clang_64'
modules: 'qtremoteobjects qt5compat qtshadertools' modules: 'qtremoteobjects qt5compat qtshadertools'
dir: ${{ runner.temp }} dir: ${{ runner.temp }}
#setup-python: 'true'
#set-env: 'true'
#extra: '--external 7z --base ${{ env.QT_MIRROR }}'
setup-python: 'true' setup-python: 'true'
set-env: 'true' set-env: 'true'
aqtversion: '==3.3.0' aqtversion: '==3.3.0'
py7zrversion: '==0.22.*' py7zrversion: '==0.22.*'
extra: '--base ${{ env.QT_MIRROR }}' extra: '--base ${{ env.QT_MIRROR }}'
cache: 'true'
- name: 'Get sources' - name: 'Get sources'
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -412,8 +435,8 @@ jobs:
echo "VERSION=$VERSION" >> $GITHUB_ENV echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "Version: $VERSION" echo "Version: $VERSION"
- name: 'Setup ccache' # - name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2 # uses: hendrikmuhs/ccache-action@v1.2
- name: 'Build project' - name: 'Build project'
run: | run: |
@@ -444,7 +467,7 @@ jobs:
runs-on: macos-latest runs-on: macos-latest
env: env:
QT_VERSION: 6.8.3 QT_VERSION: 6.10.1
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }} MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
@@ -464,21 +487,31 @@ jobs:
- name: 'Setup xcode' - name: 'Setup xcode'
uses: maxim-lobanov/setup-xcode@v1 uses: maxim-lobanov/setup-xcode@v1
with: with:
xcode-version: '16.2.0' xcode-version: '26.1'
- name: 'Install Qt' - name: 'Install desktop Qt'
uses: jurplel/install-qt-action@v3 uses: jurplel/install-qt-action@v3
with: with:
version: ${{ env.QT_VERSION }} version: ${{ env.QT_VERSION }}
host: 'mac' host: 'mac'
target: 'desktop' target: 'desktop'
modules: 'qtremoteobjects qt5compat qtshadertools qtmultimedia'
arch: 'clang_64' arch: 'clang_64'
modules: 'qtremoteobjects qt5compat qtshadertools'
dir: ${{ runner.temp }} dir: ${{ runner.temp }}
setup-python: 'true'
set-env: 'true' set-env: 'true'
extra: '--external 7z --base ${{ env.QT_MIRROR }}' extra: '--base ${{ env.QT_MIRROR }}'
- name: 'Install go'
uses: actions/setup-go@v5
with:
go-version: '1.24'
cache: false
- name: 'Setup gomobile'
run: |
export PATH=$PATH:~/go/bin
go install golang.org/x/mobile/cmd/gomobile@latest
gomobile init
- name: 'Get sources' - name: 'Get sources'
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -486,8 +519,8 @@ jobs:
submodules: 'true' submodules: 'true'
fetch-depth: 10 fetch-depth: 10
- name: 'Setup ccache' # - name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2 # uses: hendrikmuhs/ccache-action@v1.2
- name: 'Build project' - name: 'Build project'
run: | run: |
@@ -504,11 +537,11 @@ jobs:
# ------------------------------------------------------ # ------------------------------------------------------
Build-Android: Build-Android:
runs-on: ubuntu-latest runs-on: 4-core
env: env:
ANDROID_BUILD_PLATFORM: android-36 ANDROID_BUILD_PLATFORM: android-36
QT_VERSION: 6.8.3 QT_VERSION: 6.10.1
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools' QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }} PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }} PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
@@ -596,15 +629,15 @@ jobs:
echo "VERSION=$VERSION" >> $GITHUB_ENV echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "Version: $VERSION" echo "Version: $VERSION"
- name: 'Setup ccache' # - name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2 # uses: hendrikmuhs/ccache-action@v1.2
- name: 'Setup Java' - name: 'Setup Java'
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:
distribution: 'temurin' distribution: 'temurin'
java-version: '17' java-version: '17'
cache: 'gradle' # cache: 'gradle'
- name: 'Setup Android NDK' - name: 'Setup Android NDK'
id: setup-ndk id: setup-ndk
+35 -2
View File
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN) set(PROJECT AmneziaVPN)
set(AMNEZIAVPN_VERSION 4.8.11.5) set(AMNEZIAVPN_VERSION 4.8.12.8)
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION} project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
DESCRIPTION "AmneziaVPN" DESCRIPTION "AmneziaVPN"
@@ -12,7 +12,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
set(RELEASE_DATE "${CURRENT_DATE}") set(RELEASE_DATE "${CURRENT_DATE}")
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
set(APP_ANDROID_VERSION_CODE 2100) set(APP_ANDROID_VERSION_CODE 2104)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux") set(MZ_PLATFORM_NAME "linux")
@@ -49,3 +49,36 @@ if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
include(${CMAKE_SOURCE_DIR}/deploy/installer/config.cmake) include(${CMAKE_SOURCE_DIR}/deploy/installer/config.cmake)
endif() endif()
set(AMNEZIA_STAGE_DIR "${CMAKE_BINARY_DIR}/stage")
if(WIN32 AND NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
file(TO_CMAKE_PATH "${AMNEZIA_STAGE_DIR}" AMNEZIA_STAGE_DIR_CMAKE)
set(CPACK_GENERATOR "WIX")
set(CPACK_WIX_VERSION 4)
set(CPACK_PACKAGE_NAME "AmneziaVPN")
set(CPACK_PACKAGE_VENDOR "AmneziaVPN")
set(CPACK_PACKAGE_VERSION ${AMNEZIAVPN_VERSION})
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "AmneziaVPN client")
set(CPACK_PACKAGE_INSTALL_DIRECTORY "AmneziaVPN")
set(CPACK_PACKAGE_DIRECTORY "${CMAKE_BINARY_DIR}")
set(CPACK_PACKAGE_EXECUTABLES "AmneziaVPN" "AmneziaVPN")
set(CPACK_WIX_UPGRADE_GUID "{2D55AC62-96D6-4692-8C05-0D85BBF95485}")
set(CPACK_WIX_PRODUCT_ICON "${CMAKE_SOURCE_DIR}/client/images/app.ico")
# WiX patches
set(_AMNEZIA_WIX_PATCH_SERVICE "${CMAKE_SOURCE_DIR}/deploy/installer/wix/service_install_patch.xml")
set(_AMNEZIA_WIX_PATCH_CLOSE_APP "${CMAKE_SOURCE_DIR}/deploy/installer/wix/close_client_patch.xml")
file(TO_CMAKE_PATH "${_AMNEZIA_WIX_PATCH_SERVICE}" _AMNEZIA_WIX_PATCH_SERVICE_CMAKE)
file(TO_CMAKE_PATH "${_AMNEZIA_WIX_PATCH_CLOSE_APP}" _AMNEZIA_WIX_PATCH_CLOSE_APP_CMAKE)
set(CPACK_WIX_PATCH_FILE "${_AMNEZIA_WIX_PATCH_SERVICE_CMAKE};${_AMNEZIA_WIX_PATCH_CLOSE_APP_CMAKE}")
# WiX v4 Util extension for CloseApplication + namespace for util
set(CPACK_WIX_EXTENSIONS "${CPACK_WIX_EXTENSIONS};WixToolset.Util.wixext")
set(CPACK_WIX_CUSTOM_XMLNS "util=http://wixtoolset.org/schemas/v4/wxs/util")
set(CPACK_INSTALLED_DIRECTORIES "${AMNEZIA_STAGE_DIR_CMAKE};/")
include(CPack)
endif()
+46 -9
View File
@@ -27,10 +27,15 @@
#include <QtQuick/QQuickWindow> // for QQuickWindow #include <QtQuick/QQuickWindow> // for QQuickWindow
#include <QWindow> // for qobject_cast<QWindow*> #include <QWindow> // for qobject_cast<QWindow*>
bool AmneziaApplication::m_forceQuit = false;
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv), AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv),
m_optAutostart({QStringLiteral("a"), QStringLiteral("autostart")}, QStringLiteral("System autostart")), m_optAutostart({QStringLiteral("a"), QStringLiteral("autostart")}, QStringLiteral("System autostart")),
m_optCleanup ({QStringLiteral("c"), QStringLiteral("cleanup")}, QStringLiteral("Cleanup logs")) m_optCleanup ({QStringLiteral("c"), QStringLiteral("cleanup")}, QStringLiteral("Cleanup logs")),
m_optConnect ({QStringLiteral("connect")}, QStringLiteral("Connect to server by index on startup"), QStringLiteral("index")),
m_optImport ({QStringLiteral("import")}, QStringLiteral("Import configuration from data string"), QStringLiteral("data"))
{ {
setDesktopFileName(QStringLiteral(APPLICATION_NAME));
setQuitOnLastWindowClosed(false); setQuitOnLastWindowClosed(false);
// Fix config file permissions // Fix config file permissions
@@ -55,11 +60,13 @@ AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_C
AmneziaApplication::~AmneziaApplication() AmneziaApplication::~AmneziaApplication()
{ {
if (m_vpnConnection) { #ifdef AMNEZIA_DESKTOP
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectSlots", Qt::QueuedConnection); if (m_vpnConnection && m_vpnConnectionThread.isRunning()) {
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectFromVpn", Qt::QueuedConnection); QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectSlots", Qt::BlockingQueuedConnection);
QThread::msleep(2000);
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectFromVpn", Qt::BlockingQueuedConnection);
} }
#endif
m_vpnConnectionThread.requestInterruption(); m_vpnConnectionThread.requestInterruption();
m_vpnConnectionThread.quit(); m_vpnConnectionThread.quit();
@@ -70,7 +77,6 @@ AmneziaApplication::~AmneziaApplication()
} }
if (m_engine) { if (m_engine) {
QObject::disconnect(m_engine, 0, 0, 0);
delete m_engine; delete m_engine;
} }
} }
@@ -90,9 +96,6 @@ namespace {
void AmneziaApplication::init() void AmneziaApplication::init()
{ {
#ifdef Q_OS_ANDROID
clearQtCaches();
#endif
m_engine = new QQmlApplicationEngine; m_engine = new QQmlApplicationEngine;
const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml")); const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml"));
@@ -126,6 +129,16 @@ void AmneziaApplication::init()
m_coreController.reset(new CoreController(m_vpnConnection, m_settings, m_engine)); m_coreController.reset(new CoreController(m_vpnConnection, m_settings, m_engine));
m_engine->addImportPath("qrc:/ui/qml/Modules/"); m_engine->addImportPath("qrc:/ui/qml/Modules/");
if (m_parser.isSet(m_optImport)) {
const QString data = m_parser.value(m_optImport);
if (!data.isEmpty()) {
if (m_coreController) {
m_coreController->importConfigFromData(data);
}
}
}
m_engine->load(url); m_engine->load(url);
m_coreController->setQmlRoot(); m_coreController->setQmlRoot();
@@ -165,6 +178,18 @@ void AmneziaApplication::init()
} }
}); });
#endif #endif
if (m_parser.isSet(m_optConnect)) {
bool ok = false;
int idx = m_parser.value(m_optConnect).toInt(&ok);
if (ok) {
QTimer::singleShot(0, this, [this, idx]() {
if (m_coreController) {
m_coreController->openConnectionByIndex(idx);
}
});
}
}
} }
void AmneziaApplication::registerTypes() void AmneziaApplication::registerTypes()
@@ -211,6 +236,8 @@ bool AmneziaApplication::parseCommands()
m_parser.addOption(m_optAutostart); m_parser.addOption(m_optAutostart);
m_parser.addOption(m_optCleanup); m_parser.addOption(m_optCleanup);
m_parser.addOption(m_optConnect);
m_parser.addOption(m_optImport);
m_parser.process(*this); m_parser.process(*this);
@@ -247,9 +274,13 @@ bool AmneziaApplication::eventFilter(QObject *watched, QEvent *event)
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
quit(); quit();
#else #else
if (m_forceQuit) {
quit();
} else {
if (m_coreController && m_coreController->pageController()) { if (m_coreController && m_coreController->pageController()) {
m_coreController->pageController()->hideMainWindow(); m_coreController->pageController()->hideMainWindow();
} }
}
#endif #endif
return true; // eat the close return true; // eat the close
} }
@@ -257,6 +288,12 @@ bool AmneziaApplication::eventFilter(QObject *watched, QEvent *event)
return QObject::eventFilter(watched, event); return QObject::eventFilter(watched, event);
} }
void AmneziaApplication::forceQuit()
{
m_forceQuit = true;
quit();
}
QQmlApplicationEngine *AmneziaApplication::qmlEngine() const QQmlApplicationEngine *AmneziaApplication::qmlEngine() const
{ {
return m_engine; return m_engine;
+6
View File
@@ -45,7 +45,11 @@ public:
QNetworkAccessManager *networkManager(); QNetworkAccessManager *networkManager();
QClipboard *getClipboard(); QClipboard *getClipboard();
public slots:
void forceQuit();
private: private:
static bool m_forceQuit;
QQmlApplicationEngine *m_engine {}; QQmlApplicationEngine *m_engine {};
std::shared_ptr<Settings> m_settings; std::shared_ptr<Settings> m_settings;
@@ -58,6 +62,8 @@ private:
QCommandLineOption m_optAutostart; QCommandLineOption m_optAutostart;
QCommandLineOption m_optCleanup; QCommandLineOption m_optCleanup;
QCommandLineOption m_optConnect;
QCommandLineOption m_optImport;
QSharedPointer<VpnConnection> m_vpnConnection; QSharedPointer<VpnConnection> m_vpnConnection;
QThread m_vpnConnectionThread; QThread m_vpnConnectionThread;
@@ -93,7 +93,7 @@ open class OpenVpn : Protocol() {
openVpnClient = null openVpnClient = null
} }
override fun reconnectVpn(vpnBuilder: Builder) { override fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean) {
openVpnClient?.let { openVpnClient?.let {
it.establish = makeEstablish(vpnBuilder) it.establish = makeEstablish(vpnBuilder)
it.reconnect(0) it.reconnect(0)
@@ -42,7 +42,7 @@ abstract class Protocol {
abstract fun stopVpn() abstract fun stopVpn()
abstract fun reconnectVpn(vpnBuilder: Builder) abstract fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean)
protected fun ProtocolConfig.Builder.configSplitTunneling(config: JSONObject) { protected fun ProtocolConfig.Builder.configSplitTunneling(config: JSONObject) {
if (!allowSplitTunneling) { if (!allowSplitTunneling) {
@@ -269,9 +269,19 @@ class AmneziaActivity : QtActivity() {
super.onStop() super.onStop()
} }
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
Log.d(TAG, "Window focus changed: hasFocus=$hasFocus")
}
override fun onPause() {
super.onPause()
Log.d(TAG, "Pause Amnezia activity")
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { /* if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
window.decorView.apply { window.decorView.apply {
invalidate() invalidate()
@@ -288,7 +298,8 @@ class AmneziaActivity : QtActivity() {
invalidate() invalidate()
}, 250) }, 250)
} }
} } */
Log.d(TAG, "Resume Amnezia activity")
} }
private fun configureWindowForEdgeToEdge() { private fun configureWindowForEdgeToEdge() {
@@ -314,6 +325,11 @@ class AmneziaActivity : QtActivity() {
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
statusBarColor = getColor(R.color.black) statusBarColor = getColor(R.color.black)
} }
WindowInsetsControllerCompat(window, window.decorView).apply {
isAppearanceLightStatusBars = false
isAppearanceLightNavigationBars = false
}
} }
} }
@@ -565,7 +565,7 @@ open class AmneziaVpnService : VpnService() {
protocolState.value = RECONNECTING protocolState.value = RECONNECTING
connectionJob = connectionScope.launch { connectionJob = connectionScope.launch {
vpnProto?.protocol?.reconnectVpn(Builder()) vpnProto?.protocol?.reconnectVpn(Builder(), ::protect)
} }
} }
@@ -12,6 +12,7 @@ import org.amnezia.vpn.protocol.Protocol
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
import org.amnezia.vpn.protocol.Statistics import org.amnezia.vpn.protocol.Statistics
import org.amnezia.vpn.protocol.VpnException
import org.amnezia.vpn.protocol.VpnStartException import org.amnezia.vpn.protocol.VpnStartException
import org.amnezia.vpn.util.LibraryLoader.loadSharedLibrary import org.amnezia.vpn.util.LibraryLoader.loadSharedLibrary
import org.amnezia.vpn.util.Log import org.amnezia.vpn.util.Log
@@ -27,6 +28,7 @@ private const val TAG = "Wireguard"
open class Wireguard : Protocol() { open class Wireguard : Protocol() {
private var tunnelHandle: Int = -1 private var tunnelHandle: Int = -1
private var config: WireguardConfig? = null // save config for reconnect
protected open val ifName: String = "amn0" protected open val ifName: String = "amn0"
private lateinit var scope: CoroutineScope private lateinit var scope: CoroutineScope
private var statusJob: Job? = null private var statusJob: Job? = null
@@ -61,6 +63,7 @@ open class Wireguard : Protocol() {
override suspend fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) { override suspend fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) {
val wireguardConfig = parseConfig(config) val wireguardConfig = parseConfig(config)
start(wireguardConfig, vpnBuilder, protect) start(wireguardConfig, vpnBuilder, protect)
this.config = wireguardConfig
} }
protected open fun parseConfig(config: JSONObject): WireguardConfig { protected open fun parseConfig(config: JSONObject): WireguardConfig {
@@ -122,23 +125,24 @@ open class Wireguard : Protocol() {
configData.optStringOrNull("S2")?.let { setS2(it.toInt()) } configData.optStringOrNull("S2")?.let { setS2(it.toInt()) }
configData.optStringOrNull("S3")?.let { setS3(it.toInt()) } configData.optStringOrNull("S3")?.let { setS3(it.toInt()) }
configData.optStringOrNull("S4")?.let { setS4(it.toInt()) } configData.optStringOrNull("S4")?.let { setS4(it.toInt()) }
configData.optStringOrNull("H1")?.let { setH1(it.toLong()) } configData.optStringOrNull("H1")?.trim()?.let { if (it.isNotEmpty()) setH1(it) }
configData.optStringOrNull("H2")?.let { setH2(it.toLong()) } configData.optStringOrNull("H2")?.trim()?.let { if (it.isNotEmpty()) setH2(it) }
configData.optStringOrNull("H3")?.let { setH3(it.toLong()) } configData.optStringOrNull("H3")?.trim()?.let { if (it.isNotEmpty()) setH3(it) }
configData.optStringOrNull("H4")?.let { setH4(it.toLong()) } configData.optStringOrNull("H4")?.trim()?.let { if (it.isNotEmpty()) setH4(it) }
configData.optStringOrNull("I1")?.let { setI1(it) } configData.optStringOrNull("I1")?.let { setI1(it) }
configData.optStringOrNull("I2")?.let { setI2(it) } configData.optStringOrNull("I2")?.let { setI2(it) }
configData.optStringOrNull("I3")?.let { setI3(it) } configData.optStringOrNull("I3")?.let { setI3(it) }
configData.optStringOrNull("I4")?.let { setI4(it) } configData.optStringOrNull("I4")?.let { setI4(it) }
configData.optStringOrNull("I5")?.let { setI5(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) { private fun start(
if (tunnelHandle != -1) { config: WireguardConfig,
vpnBuilder: Builder,
protect: (Int) -> Boolean,
stopExistingVpn: Boolean = false
) {
if (!stopExistingVpn && tunnelHandle != -1) {
Log.w(TAG, "Tunnel already up") Log.w(TAG, "Tunnel already up")
return return
} }
@@ -146,6 +150,9 @@ open class Wireguard : Protocol() {
buildVpnInterface(config, vpnBuilder) buildVpnInterface(config, vpnBuilder)
vpnBuilder.establish().use { tunFd -> vpnBuilder.establish().use { tunFd ->
if (stopExistingVpn && tunnelHandle != -1) {
turnOffVpn()
}
if (tunFd == null) { if (tunFd == null) {
throw VpnStartException("Create VPN interface: permission not granted or revoked") throw VpnStartException("Create VPN interface: permission not granted or revoked")
} }
@@ -202,20 +209,25 @@ open class Wireguard : Protocol() {
return lastHandshake return lastHandshake
} }
override fun stopVpn() { private fun turnOffVpn() {
if (tunnelHandle == -1) {
Log.w(TAG, "Tunnel already down")
return
}
statusJob?.cancel() statusJob?.cancel()
statusJob = null statusJob = null
val handleToClose = tunnelHandle val handleToClose = tunnelHandle
tunnelHandle = -1 tunnelHandle = -1
GoBackend.awgTurnOff(handleToClose) GoBackend.awgTurnOff(handleToClose)
}
override fun stopVpn() {
if (tunnelHandle == -1) {
Log.w(TAG, "Tunnel already down")
return
}
turnOffVpn()
state.value = DISCONNECTED state.value = DISCONNECTED
} }
override fun reconnectVpn(vpnBuilder: Builder) { override fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean) {
state.value = CONNECTED val config = this.config ?: throw VpnException("Reconnect config is empty")
start(config, vpnBuilder, protect, true)
} }
} }
@@ -22,19 +22,15 @@ open class WireguardConfig protected constructor(
val s2: Int?, val s2: Int?,
val s3: Int?, val s3: Int?,
val s4: Int?, val s4: Int?,
val h1: Long?, val h1: String?,
val h2: Long?, val h2: String?,
val h3: Long?, val h3: String?,
val h4: Long?, val h4: String?,
var i1: String?, var i1: String?,
var i2: String?, var i2: String?,
var i3: String?, var i3: String?,
var i4: String?, var i4: String?,
var i5: String?, var i5: String?,
var j1: String?,
var j2: String?,
var j3: String?,
var itime: Int?
) : ProtocolConfig(protocolConfigBuilder) { ) : ProtocolConfig(protocolConfigBuilder) {
protected constructor(builder: Builder) : this( protected constructor(builder: Builder) : this(
@@ -61,10 +57,6 @@ open class WireguardConfig protected constructor(
builder.i3, builder.i3,
builder.i4, builder.i4,
builder.i5, builder.i5,
builder.j1,
builder.j2,
builder.j3,
builder.itime
) )
fun toWgUserspaceString(): String = with(StringBuilder()) { fun toWgUserspaceString(): String = with(StringBuilder()) {
@@ -94,10 +86,6 @@ open class WireguardConfig protected constructor(
i3?.let { appendLine("i3=$it") } i3?.let { appendLine("i3=$it") }
i4?.let { appendLine("i4=$it") } i4?.let { appendLine("i4=$it") }
i5?.let { appendLine("i5=$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") }
} }
} }
@@ -152,19 +140,15 @@ open class WireguardConfig protected constructor(
internal var s2: Int? = null internal var s2: Int? = null
internal var s3: Int? = null internal var s3: Int? = null
internal var s4: Int? = null internal var s4: Int? = null
internal var h1: Long? = null internal var h1: String? = null
internal var h2: Long? = null internal var h2: String? = null
internal var h3: Long? = null internal var h3: String? = null
internal var h4: Long? = null internal var h4: String? = null
internal var i1: String? = null internal var i1: String? = null
internal var i2: String? = null internal var i2: String? = null
internal var i3: String? = null internal var i3: String? = null
internal var i4: String? = null internal var i4: String? = null
internal var i5: 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 setEndpoint(endpoint: InetEndpoint) = apply { this.endpoint = endpoint }
@@ -185,19 +169,15 @@ open class WireguardConfig protected constructor(
fun setS2(s2: Int) = apply { this.s2 = s2 } fun setS2(s2: Int) = apply { this.s2 = s2 }
fun setS3(s3: Int) = apply { this.s3 = s3 } fun setS3(s3: Int) = apply { this.s3 = s3 }
fun setS4(s4: Int) = apply { this.s4 = s4 } fun setS4(s4: Int) = apply { this.s4 = s4 }
fun setH1(h1: Long) = apply { this.h1 = h1 } fun setH1(h1: String) = apply { this.h1 = h1 }
fun setH2(h2: Long) = apply { this.h2 = h2 } fun setH2(h2: String) = apply { this.h2 = h2 }
fun setH3(h3: Long) = apply { this.h3 = h3 } fun setH3(h3: String) = apply { this.h3 = h3 }
fun setH4(h4: Long) = apply { this.h4 = h4 } fun setH4(h4: String) = apply { this.h4 = h4 }
fun setI1(i1: String) = apply { this.i1 = i1 } fun setI1(i1: String) = apply { this.i1 = i1 }
fun setI2(i2: String) = apply { this.i2 = i2 } fun setI2(i2: String) = apply { this.i2 = i2 }
fun setI3(i3: String) = apply { this.i3 = i3 } fun setI3(i3: String) = apply { this.i3 = i3 }
fun setI4(i4: String) = apply { this.i4 = i4 } fun setI4(i4: String) = apply { this.i4 = i4 }
fun setI5(i5: String) = apply { this.i5 = i5 } 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) } override fun build(): WireguardConfig = configBuild().run { WireguardConfig(this@Builder) }
} }
+2 -2
View File
@@ -157,7 +157,7 @@ class Xray : Protocol() {
state.value = DISCONNECTED state.value = DISCONNECTED
} }
override fun reconnectVpn(vpnBuilder: Builder) { override fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean) {
state.value = CONNECTED state.value = CONNECTED
} }
@@ -166,7 +166,7 @@ class Xray : Protocol() {
mtu = config.mtu.toLong() mtu = config.mtu.toLong()
proxy = "socks5://127.0.0.1:${config.socksPort}" proxy = "socks5://127.0.0.1:${config.socksPort}"
device = "fd://$fd" device = "fd://$fd"
logLevel = "warning" logLevel = "warn"
} }
LibXray.startTun2Socks(tun2SocksConfig, fd.toLong()).isNotNullOrBlank { err -> LibXray.startTun2Socks(tun2SocksConfig, fd.toLong()).isNotNullOrBlank { err ->
throw VpnStartException("Failed to start tun2socks: $err") throw VpnStartException("Failed to start tun2socks: $err")
+5 -1
View File
@@ -20,7 +20,11 @@ set(QT_ANDROID_MULTI_ABI_FORWARD_VARS "QT_NO_GLOBAL_APK_TARGET_PART_OF_ALL;CMAKE
# We need to include qtprivate api's # We need to include qtprivate api's
# As QAndroidBinder is not yet implemented with a public api # As QAndroidBinder is not yet implemented with a public api
set(LIBS ${LIBS} Qt6::CorePrivate -ljnigraphics) # Check if Qt6::CorePrivate is available (may not be in all Qt versions/configurations)
if(TARGET Qt6::CorePrivate)
set(LIBS ${LIBS} Qt6::CorePrivate)
endif()
set(LIBS ${LIBS} -ljnigraphics)
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/platforms/android) link_directories(${CMAKE_CURRENT_SOURCE_DIR}/platforms/android)
+2
View File
@@ -34,6 +34,7 @@ set(HEADERS ${HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate-C-Interface.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate-C-Interface.h
) )
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h PROPERTIES OBJECTIVE_CPP_HEADER TRUE) set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h PROPERTIES OBJECTIVE_CPP_HEADER TRUE)
@@ -46,6 +47,7 @@ set(SOURCES ${SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/AmneziaSceneDelegateHooks.mm ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/AmneziaSceneDelegateHooks.mm
) )
+2
View File
@@ -35,6 +35,7 @@ set(HEADERS ${HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate-C-Interface.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate-C-Interface.h
) )
@@ -45,6 +46,7 @@ set(SOURCES ${SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.mm ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.mm ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.mm ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
+9 -11
View File
@@ -41,18 +41,16 @@ QString AwgConfigurator::createConfig(const ServerCredentials &credentials, Dock
jsonConfig[config_key::underloadPacketMagicHeader] = configMap.value(config_key::underloadPacketMagicHeader); jsonConfig[config_key::underloadPacketMagicHeader] = configMap.value(config_key::underloadPacketMagicHeader);
jsonConfig[config_key::transportPacketMagicHeader] = configMap.value(config_key::transportPacketMagicHeader); jsonConfig[config_key::transportPacketMagicHeader] = configMap.value(config_key::transportPacketMagicHeader);
// jsonConfig[config_key::cookieReplyPacketJunkSize] = configMap.value(config_key::cookieReplyPacketJunkSize); if (container == DockerContainer::Awg2) {
// jsonConfig[config_key::transportPacketJunkSize] = configMap.value(config_key::transportPacketJunkSize); 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::specialJunk1] = configMap.value(amnezia::config_key::specialJunk1);
// jsonConfig[config_key::specialJunk2] = configMap.value(amnezia::config_key::specialJunk2); jsonConfig[config_key::specialJunk2] = configMap.value(amnezia::config_key::specialJunk2);
// jsonConfig[config_key::specialJunk3] = configMap.value(amnezia::config_key::specialJunk3); jsonConfig[config_key::specialJunk3] = configMap.value(amnezia::config_key::specialJunk3);
// jsonConfig[config_key::specialJunk4] = configMap.value(amnezia::config_key::specialJunk4); jsonConfig[config_key::specialJunk4] = configMap.value(amnezia::config_key::specialJunk4);
// jsonConfig[config_key::specialJunk5] = configMap.value(amnezia::config_key::specialJunk5); 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] = jsonConfig[config_key::mtu] =
containerConfig.value(ProtocolProps::protoToString(Proto::Awg)).toObject().value(config_key::mtu).toString(protocols::awg::defaultMtu); containerConfig.value(ProtocolProps::protoToString(Proto::Awg)).toObject().value(config_key::mtu).toString(protocols::awg::defaultMtu);
@@ -103,7 +103,11 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
return connData; return connData;
} }
QString getIpsScript = QString("cat %1 | grep AllowedIPs").arg(m_serverConfigPath); QString configPath = m_serverConfigPath;
if (container == DockerContainer::Awg) {
configPath = amnezia::protocols::awg::serverLegacyConfigPath;
}
QString getIpsScript = QString("cat %1 | grep AllowedIPs").arg(configPath);
QString stdOut; QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) { auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n"; stdOut += data + "\n";
@@ -161,15 +165,18 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
"AllowedIPs = %3/32\n\n") "AllowedIPs = %3/32\n\n")
.arg(connData.clientPubKey, connData.pskKey, connData.clientIP); .arg(connData.clientPubKey, connData.pskKey, connData.clientIP);
errorCode = m_serverController->uploadTextFileToContainer(container, credentials, configPart, m_serverConfigPath, errorCode = m_serverController->uploadTextFileToContainer(container, credentials, configPart, configPath,
libssh::ScpOverwriteMode::ScpAppendToExisting); libssh::ScpOverwriteMode::ScpAppendToExisting);
if (errorCode != ErrorCode::NoError) { if (errorCode != ErrorCode::NoError) {
return connData; return connData;
} }
QString script = QString("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip %1)'") bool isAwg = (container == DockerContainer::Awg2);
.arg(m_serverConfigPath); QString bin = isAwg ? QStringLiteral("awg") : QStringLiteral("wg");
QString iface = isAwg ? QStringLiteral("awg0") : QStringLiteral("wg0");
QString script = QString(
"sudo docker exec -i $CONTAINER_NAME bash -c '%1 syncconf %2 <(%1-quick strip %3)'").arg(bin, iface, configPath);
errorCode = m_serverController->runScript( errorCode = m_serverController->runScript(
credentials, credentials,
+39 -8
View File
@@ -28,7 +28,10 @@ QString ContainerProps::containerToString(amnezia::DockerContainer c)
return "none"; return "none";
if (c == DockerContainer::Cloak) if (c == DockerContainer::Cloak)
return "amnezia-openvpn-cloak"; return "amnezia-openvpn-cloak";
if (c == DockerContainer::Awg)
return "amnezia-awg";
if (c == DockerContainer::Awg2)
return "amnezia-awg2";
QMetaEnum metaEnum = QMetaEnum::fromType<DockerContainer>(); QMetaEnum metaEnum = QMetaEnum::fromType<DockerContainer>();
QString containerKey = metaEnum.valueToKey(static_cast<int>(c)); QString containerKey = metaEnum.valueToKey(static_cast<int>(c));
@@ -41,7 +44,10 @@ QString ContainerProps::containerTypeToString(amnezia::DockerContainer c)
return "none"; return "none";
if (c == DockerContainer::Ipsec) if (c == DockerContainer::Ipsec)
return "ikev2"; return "ikev2";
if (c == DockerContainer::Awg)
return "awg";
if (c == DockerContainer::Awg2)
return "awg";
QMetaEnum metaEnum = QMetaEnum::fromType<DockerContainer>(); QMetaEnum metaEnum = QMetaEnum::fromType<DockerContainer>();
QString containerKey = metaEnum.valueToKey(static_cast<int>(c)); QString containerKey = metaEnum.valueToKey(static_cast<int>(c));
@@ -71,6 +77,8 @@ QVector<amnezia::Proto> ContainerProps::protocolsForContainer(amnezia::DockerCon
case DockerContainer::Socks5Proxy: return { Proto::Socks5Proxy }; case DockerContainer::Socks5Proxy: return { Proto::Socks5Proxy };
case DockerContainer::Awg: return { Proto::Awg };
case DockerContainer::Awg2: return { Proto::Awg };
default: return { defaultProtocol(container) }; default: return { defaultProtocol(container) };
} }
} }
@@ -94,6 +102,7 @@ QMap<DockerContainer, QString> ContainerProps::containerHumanNames()
{ DockerContainer::Cloak, "OpenVPN over Cloak" }, { DockerContainer::Cloak, "OpenVPN over Cloak" },
{ DockerContainer::WireGuard, "WireGuard" }, { DockerContainer::WireGuard, "WireGuard" },
{ DockerContainer::Awg, "AmneziaWG" }, { DockerContainer::Awg, "AmneziaWG" },
{ DockerContainer::Awg2, "AmneziaWG" },
{ DockerContainer::Xray, "XRay" }, { DockerContainer::Xray, "XRay" },
{ DockerContainer::Ipsec, QObject::tr("IPsec") }, { DockerContainer::Ipsec, QObject::tr("IPsec") },
{ DockerContainer::SSXray, "Shadowsocks"}, { DockerContainer::SSXray, "Shadowsocks"},
@@ -120,6 +129,9 @@ QMap<DockerContainer, QString> ContainerProps::containerDescriptions()
{ DockerContainer::Awg, { DockerContainer::Awg,
QObject::tr("AmneziaWG is a special protocol from Amnezia based on WireGuard. " 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.") }, "It provides high connection speed and ensures stable operation even in the most challenging network conditions.") },
{ DockerContainer::Awg2,
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, { DockerContainer::Xray,
QObject::tr("XRay with REALITY masks VPN traffic as web traffic and protects against active probing. " 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.") }, "It is highly resistant to detection and offers high speed.") },
@@ -182,7 +194,7 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
"* Minimal configuration required\n" "* Minimal configuration required\n"
"* Easily detected by DPI systems (susceptible to blocking)\n" "* Easily detected by DPI systems (susceptible to blocking)\n"
"* Operates over UDP protocol") }, "* Operates over UDP protocol") },
{ DockerContainer::Awg, { DockerContainer::Awg2,
QObject::tr("AmneziaWG is a modern VPN protocol based on WireGuard, " QObject::tr("AmneziaWG is a modern VPN protocol based on WireGuard, "
"combining simplified architecture with high performance across all devices. " "combining simplified architecture with high performance across all devices. "
"It addresses WireGuard's main vulnerability (easy detection by DPI systems) through advanced obfuscation techniques, " "It addresses WireGuard's main vulnerability (easy detection by DPI systems) through advanced obfuscation techniques, "
@@ -242,6 +254,7 @@ Proto ContainerProps::defaultProtocol(DockerContainer c)
case DockerContainer::Cloak: return Proto::Cloak; case DockerContainer::Cloak: return Proto::Cloak;
case DockerContainer::ShadowSocks: return Proto::ShadowSocks; case DockerContainer::ShadowSocks: return Proto::ShadowSocks;
case DockerContainer::WireGuard: return Proto::WireGuard; case DockerContainer::WireGuard: return Proto::WireGuard;
case DockerContainer::Awg2: return Proto::Awg;
case DockerContainer::Awg: return Proto::Awg; case DockerContainer::Awg: return Proto::Awg;
case DockerContainer::Xray: return Proto::Xray; case DockerContainer::Xray: return Proto::Xray;
case DockerContainer::Ipsec: return Proto::Ikev2; case DockerContainer::Ipsec: return Proto::Ikev2;
@@ -255,6 +268,15 @@ Proto ContainerProps::defaultProtocol(DockerContainer c)
} }
} }
QString ContainerProps::containerTypeToProtocolString(DockerContainer c)
{
if (c == DockerContainer::None)
return "none";
Proto p = defaultProtocol(c);
return ProtocolProps::protoToString(p);
}
bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c) bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
{ {
#ifdef Q_OS_WINDOWS #ifdef Q_OS_WINDOWS
@@ -265,6 +287,7 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
switch (c) { switch (c) {
case DockerContainer::WireGuard: return true; case DockerContainer::WireGuard: return true;
case DockerContainer::OpenVpn: return true; case DockerContainer::OpenVpn: return true;
case DockerContainer::Awg2: return true;
case DockerContainer::Awg: return true; case DockerContainer::Awg: return true;
case DockerContainer::Xray: return true; case DockerContainer::Xray: return true;
case DockerContainer::Cloak: return true; case DockerContainer::Cloak: return true;
@@ -278,6 +301,7 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
// macOS build using Network Extension hide OpenVPN-based containers // macOS build using Network Extension hide OpenVPN-based containers
switch (c) { switch (c) {
case DockerContainer::WireGuard: return true; case DockerContainer::WireGuard: return true;
case DockerContainer::Awg2: return true;
case DockerContainer::Awg: return true; case DockerContainer::Awg: return true;
case DockerContainer::Xray: return true; case DockerContainer::Xray: return true;
case DockerContainer::SSXray: return true; case DockerContainer::SSXray: return true;
@@ -300,6 +324,7 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
case DockerContainer::WireGuard: return true; case DockerContainer::WireGuard: return true;
case DockerContainer::OpenVpn: return true; case DockerContainer::OpenVpn: return true;
case DockerContainer::ShadowSocks: return false; case DockerContainer::ShadowSocks: return false;
case DockerContainer::Awg2: return true;
case DockerContainer::Awg: return true; case DockerContainer::Awg: return true;
case DockerContainer::Cloak: return true; case DockerContainer::Cloak: return true;
case DockerContainer::Xray: return true; case DockerContainer::Xray: return true;
@@ -329,7 +354,7 @@ QStringList ContainerProps::fixedPortsForContainer(DockerContainer c)
bool ContainerProps::isEasySetupContainer(DockerContainer container) bool ContainerProps::isEasySetupContainer(DockerContainer container)
{ {
switch (container) { switch (container) {
case DockerContainer::Awg: return true; case DockerContainer::Awg2: return true;
default: return false; default: return false;
} }
} }
@@ -337,7 +362,7 @@ bool ContainerProps::isEasySetupContainer(DockerContainer container)
QString ContainerProps::easySetupHeader(DockerContainer container) QString ContainerProps::easySetupHeader(DockerContainer container)
{ {
switch (container) { switch (container) {
case DockerContainer::Awg: return tr("Automatic"); case DockerContainer::Awg2: return tr("Automatic");
default: return ""; default: return "";
} }
} }
@@ -345,7 +370,7 @@ QString ContainerProps::easySetupHeader(DockerContainer container)
QString ContainerProps::easySetupDescription(DockerContainer container) QString ContainerProps::easySetupDescription(DockerContainer container)
{ {
switch (container) { switch (container) {
case DockerContainer::Awg: return tr("AmneziaWG protocol will be installed. " case DockerContainer::Awg2: return tr("AmneziaWG protocol will be installed. "
"It provides high connection speed and ensures stable operation even in the most challenging network conditions."); "It provides high connection speed and ensures stable operation even in the most challenging network conditions.");
default: return ""; default: return "";
} }
@@ -354,7 +379,7 @@ QString ContainerProps::easySetupDescription(DockerContainer container)
int ContainerProps::easySetupOrder(DockerContainer container) int ContainerProps::easySetupOrder(DockerContainer container)
{ {
switch (container) { switch (container) {
case DockerContainer::Awg: return 1; case DockerContainer::Awg2: return 1;
default: return 0; default: return 0;
} }
} }
@@ -370,6 +395,12 @@ bool ContainerProps::isShareable(DockerContainer container)
} }
} }
bool ContainerProps::isAwgContainer(DockerContainer container)
{
return container == DockerContainer::Awg || container == DockerContainer::Awg2;
}
QJsonObject ContainerProps::getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig) QJsonObject ContainerProps::getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig)
{ {
QString protocolConfigString = containerConfig.value(ProtocolProps::protoToString(protocol)) QString protocolConfigString = containerConfig.value(ProtocolProps::protoToString(protocol))
@@ -387,7 +418,7 @@ int ContainerProps::installPageOrder(DockerContainer container)
case DockerContainer::Cloak: return 5; case DockerContainer::Cloak: return 5;
case DockerContainer::ShadowSocks: return 6; case DockerContainer::ShadowSocks: return 6;
case DockerContainer::WireGuard: return 2; case DockerContainer::WireGuard: return 2;
case DockerContainer::Awg: return 1; case DockerContainer::Awg2: return 1;
case DockerContainer::Xray: return 3; case DockerContainer::Xray: return 3;
case DockerContainer::Ipsec: return 7; case DockerContainer::Ipsec: return 7;
case DockerContainer::SSXray: return 8; case DockerContainer::SSXray: return 8;
+5
View File
@@ -17,6 +17,7 @@ namespace amnezia
enum DockerContainer { enum DockerContainer {
None = 0, None = 0,
Awg, Awg,
Awg2,
WireGuard, WireGuard,
OpenVpn, OpenVpn,
Cloak, Cloak,
@@ -45,6 +46,7 @@ namespace amnezia
Q_INVOKABLE static amnezia::DockerContainer containerFromString(const QString &container); Q_INVOKABLE static amnezia::DockerContainer containerFromString(const QString &container);
Q_INVOKABLE static QString containerToString(amnezia::DockerContainer container); Q_INVOKABLE static QString containerToString(amnezia::DockerContainer container);
Q_INVOKABLE static QString containerTypeToString(amnezia::DockerContainer c); Q_INVOKABLE static QString containerTypeToString(amnezia::DockerContainer c);
Q_INVOKABLE static QString containerTypeToProtocolString(amnezia::DockerContainer c);
Q_INVOKABLE static QList<amnezia::DockerContainer> allContainers(); Q_INVOKABLE static QList<amnezia::DockerContainer> allContainers();
@@ -71,6 +73,9 @@ namespace amnezia
static bool isShareable(amnezia::DockerContainer container); static bool isShareable(amnezia::DockerContainer container);
static bool isAwgContainer(amnezia::DockerContainer container);
static QJsonObject getProtocolConfigFromContainer(const amnezia::Proto protocol, const QJsonObject &containerConfig); static QJsonObject getProtocolConfigFromContainer(const amnezia::Proto protocol, const QJsonObject &containerConfig);
static int installPageOrder(amnezia::DockerContainer container); static int installPageOrder(amnezia::DockerContainer container);
+1
View File
@@ -68,6 +68,7 @@ namespace apiDefs
constexpr QLatin1String migrationCode("migration_code"); constexpr QLatin1String migrationCode("migration_code");
constexpr QLatin1String transactionId("transaction_id"); constexpr QLatin1String transactionId("transaction_id");
constexpr QLatin1String isTestPurchase("is_test_purchase");
constexpr QLatin1String userCountryCode("user_country_code"); constexpr QLatin1String userCountryCode("user_country_code");
+14 -2
View File
@@ -1,6 +1,7 @@
#include "apiUtils.h" #include "apiUtils.h"
#include <QDateTime> #include <QDateTime>
#include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
namespace namespace
@@ -88,6 +89,7 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
{ {
const int httpStatusCodeConflict = 409; const int httpStatusCodeConflict = 409;
const int httpStatusCodeNotFound = 404; const int httpStatusCodeNotFound = 404;
const int httpStatusCodeNotImplemented = 501;
if (!sslErrors.empty()) { if (!sslErrors.empty()) {
qDebug().noquote() << sslErrors; qDebug().noquote() << sslErrors;
@@ -106,10 +108,20 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
qDebug() << replyError; qDebug() << replyError;
qDebug() << replyErrorString; qDebug() << replyErrorString;
qDebug() << httpStatusCode; qDebug() << httpStatusCode;
if (httpStatusCode == httpStatusCodeConflict) {
int httpStatusFromBody = -1;
QJsonDocument jsonDoc = QJsonDocument::fromJson(responseBody);
if (jsonDoc.isObject()) {
QJsonObject jsonObj = jsonDoc.object();
httpStatusFromBody = jsonObj.value("http_status").toInt(-1);
}
if (httpStatusFromBody == httpStatusCodeConflict) {
return amnezia::ErrorCode::ApiConfigLimitError; return amnezia::ErrorCode::ApiConfigLimitError;
} else if (httpStatusCode == httpStatusCodeNotFound) { } else if (httpStatusFromBody == httpStatusCodeNotFound) {
return amnezia::ErrorCode::ApiNotFoundError; return amnezia::ErrorCode::ApiNotFoundError;
} else if (httpStatusFromBody == httpStatusCodeNotImplemented) {
return amnezia::ErrorCode::ApiUpdateRequestError;
} }
return amnezia::ErrorCode::ApiConfigDownloadError; return amnezia::ErrorCode::ApiConfigDownloadError;
} }
+19 -24
View File
@@ -154,9 +154,6 @@ void CoreController::initControllers()
m_apiConfigsController.reset(new ApiConfigsController(m_serversModel, m_apiServicesModel, m_settings)); m_apiConfigsController.reset(new ApiConfigsController(m_serversModel, m_apiServicesModel, m_settings));
m_engine->rootContext()->setContextProperty("ApiConfigsController", m_apiConfigsController.get()); m_engine->rootContext()->setContextProperty("ApiConfigsController", m_apiConfigsController.get());
m_apiPremV1MigrationController.reset(new ApiPremV1MigrationController(m_serversModel, m_settings, this));
m_engine->rootContext()->setContextProperty("ApiPremV1MigrationController", m_apiPremV1MigrationController.get());
m_apiNewsController.reset(new ApiNewsController(m_newsModel, m_settings, m_serversModel, this)); m_apiNewsController.reset(new ApiNewsController(m_newsModel, m_settings, m_serversModel, this));
m_engine->rootContext()->setContextProperty("ApiNewsController", m_apiNewsController.get()); m_engine->rootContext()->setContextProperty("ApiNewsController", m_apiNewsController.get());
} }
@@ -231,8 +228,6 @@ void CoreController::initSignalHandlers()
initAutoConnectHandler(); initAutoConnectHandler();
initAmneziaDnsToggledHandler(); initAmneziaDnsToggledHandler();
initPrepareConfigHandler(); initPrepareConfigHandler();
initImportPremiumV2VpnKeyHandler();
initShowMigrationDrawerHandler();
initStrictKillSwitchHandler(); initStrictKillSwitchHandler();
} }
@@ -382,25 +377,6 @@ void CoreController::initPrepareConfigHandler()
}); });
} }
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() void CoreController::initStrictKillSwitchHandler()
{ {
connect(m_settingsController.get(), &SettingsController::strictKillSwitchEnabledChanged, m_vpnConnection.get(), connect(m_settingsController.get(), &SettingsController::strictKillSwitchEnabledChanged, m_vpnConnection.get(),
@@ -411,3 +387,22 @@ QSharedPointer<PageController> CoreController::pageController() const
{ {
return m_pageController; return m_pageController;
} }
void CoreController::openConnectionByIndex(int serverIndex)
{
if (m_serversModel) {
m_serversModel->setProcessedServerIndex(serverIndex);
m_serversModel->setDefaultServerIndex(serverIndex);
}
m_connectionController->toggleConnection();
}
void CoreController::importConfigFromData(const QString &data)
{
if (!m_importController)
return;
if (m_importController->extractConfigFromData(data)) {
m_importController->importConfig();
}
}
+3 -4
View File
@@ -11,7 +11,6 @@
#include "ui/controllers/api/apiConfigsController.h" #include "ui/controllers/api/apiConfigsController.h"
#include "ui/controllers/api/apiSettingsController.h" #include "ui/controllers/api/apiSettingsController.h"
#include "ui/controllers/api/apiPremV1MigrationController.h"
#include "ui/controllers/api/apiNewsController.h" #include "ui/controllers/api/apiNewsController.h"
#include "ui/controllers/appSplitTunnelingController.h" #include "ui/controllers/appSplitTunnelingController.h"
#include "ui/controllers/allowedDnsController.h" #include "ui/controllers/allowedDnsController.h"
@@ -65,6 +64,9 @@ public:
QSharedPointer<PageController> pageController() const; QSharedPointer<PageController> pageController() const;
void setQmlRoot(); void setQmlRoot();
void openConnectionByIndex(int serverIndex);
void importConfigFromData(const QString &data);
signals: signals:
void translationsUpdated(); void translationsUpdated();
void websiteUrlChanged(const QString &newUrl); void websiteUrlChanged(const QString &newUrl);
@@ -90,8 +92,6 @@ private:
void initAutoConnectHandler(); void initAutoConnectHandler();
void initAmneziaDnsToggledHandler(); void initAmneziaDnsToggledHandler();
void initPrepareConfigHandler(); void initPrepareConfigHandler();
void initImportPremiumV2VpnKeyHandler();
void initShowMigrationDrawerHandler();
void initStrictKillSwitchHandler(); void initStrictKillSwitchHandler();
QQmlApplicationEngine *m_engine {}; // TODO use parent child system here? QQmlApplicationEngine *m_engine {}; // TODO use parent child system here?
@@ -119,7 +119,6 @@ private:
QScopedPointer<ApiSettingsController> m_apiSettingsController; QScopedPointer<ApiSettingsController> m_apiSettingsController;
QScopedPointer<ApiConfigsController> m_apiConfigsController; QScopedPointer<ApiConfigsController> m_apiConfigsController;
QScopedPointer<ApiPremV1MigrationController> m_apiPremV1MigrationController;
QScopedPointer<ApiNewsController> m_apiNewsController; QScopedPointer<ApiNewsController> m_apiNewsController;
QSharedPointer<ContainersModel> m_containersModel; QSharedPointer<ContainersModel> m_containersModel;
+102 -47
View File
@@ -41,6 +41,11 @@ namespace
constexpr QLatin1String errorResponsePattern3("Account not found."); constexpr QLatin1String errorResponsePattern3("Account not found.");
constexpr QLatin1String updateRequestResponsePattern("client version update is required"); constexpr QLatin1String updateRequestResponsePattern("client version update is required");
constexpr int httpStatusCodeNotFound = 404;
constexpr int httpStatusCodeConflict = 409;
constexpr int httpStatusCodeNotImplemented = 501;
} }
GatewayController::GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs, GatewayController::GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
@@ -74,7 +79,11 @@ GatewayController::EncryptedRequestData GatewayController::prepareRequest(const
QString host = QUrl(encRequestData.request.url()).host(); QString host = QUrl(encRequestData.request.url()).host();
QString ip = NetworkUtilities::getIPAddress(host); QString ip = NetworkUtilities::getIPAddress(host);
if (!ip.isEmpty()) { if (!ip.isEmpty()) {
IpcClient::Interface()->addKillSwitchAllowedRange(QStringList { ip }); IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
QRemoteObjectPendingReply<bool> reply = iface->addKillSwitchAllowedRange(QStringList { ip });
if (!reply.waitForFinished(1000) || !reply.returnValue())
qWarning() << "GatewayController::prepareRequest(): Failed to execute remote addKillSwitchAllowedRange call";
});
} }
} }
#endif #endif
@@ -126,6 +135,26 @@ GatewayController::EncryptedRequestData GatewayController::prepareRequest(const
return encRequestData; return encRequestData;
} }
GatewayController::DecryptionResult GatewayController::tryDecryptResponseBody(const QByteArray &encryptedResponseBody,
QNetworkReply::NetworkError replyError, const QByteArray &key,
const QByteArray &iv, const QByteArray &salt)
{
DecryptionResult result;
result.decryptedBody = encryptedResponseBody;
result.isDecryptionSuccessful = false;
try {
QSimpleCrypto::QBlockCipher blockCipher;
result.decryptedBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt);
result.isDecryptionSuccessful = true;
} catch (...) {
result.decryptedBody = encryptedResponseBody;
result.isDecryptionSuccessful = false;
}
return result;
}
ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody) ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody)
{ {
EncryptedRequestData encRequestData = prepareRequest(endpoint, apiPayload); EncryptedRequestData encRequestData = prepareRequest(endpoint, apiPayload);
@@ -149,21 +178,27 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
reply->deleteLater(); reply->deleteLater();
if (sslErrors.isEmpty() auto decryptionResult =
&& shouldBypassProxy(replyError, encryptedResponseBody, true, encRequestData.key, encRequestData.iv, encRequestData.salt)) { tryDecryptResponseBody(encryptedResponseBody, replyError, encRequestData.key, encRequestData.iv, encRequestData.salt);
if (sslErrors.isEmpty() && shouldBypassProxy(replyError, decryptionResult.decryptedBody, decryptionResult.isDecryptionSuccessful)) {
auto requestFunction = [&encRequestData, &encryptedResponseBody](const QString &url) { auto requestFunction = [&encRequestData, &encryptedResponseBody](const QString &url) {
encRequestData.request.setUrl(url); encRequestData.request.setUrl(url);
return amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody); return amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
}; };
auto replyProcessingFunction = [&encryptedResponseBody, &replyErrorString, &replyError, &httpStatusCode, &sslErrors, auto replyProcessingFunction = [&encryptedResponseBody, &replyErrorString, &replyError, &httpStatusCode, &sslErrors, &encRequestData,
&encRequestData, this](QNetworkReply *reply, const QList<QSslError> &nestedSslErrors) { &decryptionResult, this](QNetworkReply *reply, const QList<QSslError> &nestedSslErrors) {
encryptedResponseBody = reply->readAll(); encryptedResponseBody = reply->readAll();
replyErrorString = reply->errorString(); replyErrorString = reply->errorString();
replyError = reply->error(); replyError = reply->error();
httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
decryptionResult =
tryDecryptResponseBody(encryptedResponseBody, replyError, encRequestData.key, encRequestData.iv, encRequestData.salt);
if (!sslErrors.isEmpty() if (!sslErrors.isEmpty()
|| shouldBypassProxy(replyError, encryptedResponseBody, true, encRequestData.key, encRequestData.iv, encRequestData.salt)) { || shouldBypassProxy(replyError, decryptionResult.decryptedBody, decryptionResult.isDecryptionSuccessful)) {
sslErrors = nestedSslErrors; sslErrors = nestedSslErrors;
return false; return false;
} }
@@ -175,21 +210,19 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
bypassProxy(endpoint, serviceType, userCountryCode, requestFunction, replyProcessingFunction); bypassProxy(endpoint, serviceType, userCountryCode, requestFunction, replyProcessingFunction);
} }
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, replyErrorString, replyError, httpStatusCode, encryptedResponseBody); auto errorCode =
apiUtils::checkNetworkReplyErrors(sslErrors, replyErrorString, replyError, httpStatusCode, decryptionResult.decryptedBody);
if (errorCode) { if (errorCode) {
return errorCode; return errorCode;
} }
try { if (!decryptionResult.isDecryptionSuccessful) {
QSimpleCrypto::QBlockCipher blockCipher;
responseBody =
blockCipher.decryptAesBlockCipher(encryptedResponseBody, encRequestData.key, encRequestData.iv, "", encRequestData.salt);
return ErrorCode::NoError;
} catch (...) { // todo change error handling in QSimpleCrypto?
Utils::logException();
qCritical() << "error when decrypting the request body"; qCritical() << "error when decrypting the request body";
return ErrorCode::ApiConfigDecryptionError; return ErrorCode::ApiConfigDecryptionError;
} }
responseBody = decryptionResult.decryptedBody;
return ErrorCode::NoError;
} }
QFuture<QPair<ErrorCode, QByteArray>> GatewayController::postAsync(const QString &endpoint, const QJsonObject apiPayload) QFuture<QPair<ErrorCode, QByteArray>> GatewayController::postAsync(const QString &endpoint, const QJsonObject apiPayload)
@@ -218,32 +251,33 @@ QFuture<QPair<ErrorCode, QByteArray>> GatewayController::postAsync(const QString
reply->deleteLater(); reply->deleteLater();
auto processResponse = [promise, encRequestData](const QByteArray &ecryptedResponseBody, const QList<QSslError> &sslErrors, auto decryptionResult =
QNetworkReply::NetworkError replyError, const QString &replyErrorString, tryDecryptResponseBody(encryptedResponseBody, replyError, encRequestData.key, encRequestData.iv, encRequestData.salt);
int httpStatusCode) {
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, replyErrorString, replyError, httpStatusCode, ecryptedResponseBody); auto processResponse = [promise, encRequestData](const GatewayController::DecryptionResult &decryptionResult,
const QList<QSslError> &sslErrors, QNetworkReply::NetworkError replyError,
const QString &replyErrorString, int httpStatusCode) {
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, replyErrorString, replyError, httpStatusCode,
decryptionResult.decryptedBody);
if (errorCode) { if (errorCode) {
promise->addResult(qMakePair(errorCode, QByteArray())); promise->addResult(qMakePair(errorCode, QByteArray()));
promise->finish(); promise->finish();
return; return;
} }
QSimpleCrypto::QBlockCipher blockCipher; if (!decryptionResult.isDecryptionSuccessful) {
try {
QByteArray responseBody = blockCipher.decryptAesBlockCipher(ecryptedResponseBody, encRequestData.key, encRequestData.iv, "",
encRequestData.salt);
promise->addResult(qMakePair(ErrorCode::NoError, responseBody));
promise->finish();
} catch (...) {
Utils::logException(); Utils::logException();
qCritical() << "error when decrypting the request body"; qCritical() << "error when decrypting the request body";
promise->addResult(qMakePair(ErrorCode::ApiConfigDecryptionError, QByteArray())); promise->addResult(qMakePair(ErrorCode::ApiConfigDecryptionError, QByteArray()));
promise->finish(); promise->finish();
return;
} }
promise->addResult(qMakePair(ErrorCode::NoError, decryptionResult.decryptedBody));
promise->finish();
}; };
if (sslErrors->isEmpty() if (sslErrors->isEmpty() && shouldBypassProxy(replyError, decryptionResult.decryptedBody, decryptionResult.isDecryptionSuccessful)) {
&& shouldBypassProxy(replyError, encryptedResponseBody, true, encRequestData.key, encRequestData.iv, encRequestData.salt)) {
auto serviceType = apiPayload.value(apiDefs::key::serviceType).toString(""); auto serviceType = apiPayload.value(apiDefs::key::serviceType).toString("");
auto userCountryCode = apiPayload.value(apiDefs::key::userCountryCode).toString(""); auto userCountryCode = apiPayload.value(apiDefs::key::userCountryCode).toString("");
@@ -266,13 +300,21 @@ QFuture<QPair<ErrorCode, QByteArray>> GatewayController::postAsync(const QString
proxyStorageUrls.push_back(baseUrl + "endpoints.json"); proxyStorageUrls.push_back(baseUrl + "endpoints.json");
getProxyUrlsAsync(proxyStorageUrls, 0, [this, encRequestData, endpoint, processResponse](const QStringList &proxyUrls) { getProxyUrlsAsync(proxyStorageUrls, 0, [this, encRequestData, endpoint, processResponse](const QStringList &proxyUrls) {
getProxyUrlAsync(proxyUrls, 0, [this, encRequestData, endpoint, processResponse](const QString &proxyUrls) { getProxyUrlAsync(proxyUrls, 0, [this, encRequestData, endpoint, processResponse](const QString &proxyUrl) {
bypassProxyAsync(endpoint, proxyUrls, encRequestData, processResponse); bypassProxyAsync(endpoint, proxyUrl, encRequestData,
[processResponse, this](const QByteArray &decryptedBody, bool isDecryptionSuccessful,
const QList<QSslError> &sslErrors, QNetworkReply::NetworkError replyError,
const QString &replyErrorString, int httpStatusCode) {
GatewayController::DecryptionResult result;
result.decryptedBody = decryptedBody;
result.isDecryptionSuccessful = isDecryptionSuccessful;
processResponse(result, sslErrors, replyError, replyErrorString, httpStatusCode);
});
}); });
}); });
} else { } else {
processResponse(encryptedResponseBody, *sslErrors, replyError, replyErrorString, httpStatusCode); processResponse(decryptionResult, *sslErrors, replyError, replyErrorString, httpStatusCode);
} }
}); });
@@ -365,9 +407,23 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
return {}; return {};
} }
bool GatewayController::shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &responseBody, bool GatewayController::shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &decryptedResponseBody,
bool checkEncryption, const QByteArray &key, const QByteArray &iv, const QByteArray &salt) bool isDecryptionSuccessful)
{ {
const QByteArray &responseBody = decryptedResponseBody;
int httpStatus = -1;
if (isDecryptionSuccessful) {
QJsonDocument jsonDoc = QJsonDocument::fromJson(responseBody);
if (jsonDoc.isObject()) {
QJsonObject jsonObj = jsonDoc.object();
httpStatus = jsonObj.value("http_status").toInt(-1);
}
} else {
qDebug() << "failed to decrypt the data";
return true;
}
if (replyError == QNetworkReply::NetworkError::OperationCanceledError || replyError == QNetworkReply::NetworkError::TimeoutError) { if (replyError == QNetworkReply::NetworkError::OperationCanceledError || replyError == QNetworkReply::NetworkError::TimeoutError) {
qDebug() << "timeout occurred"; qDebug() << "timeout occurred";
qDebug() << replyError; qDebug() << replyError;
@@ -375,7 +431,7 @@ bool GatewayController::shouldBypassProxy(const QNetworkReply::NetworkError &rep
} else if (responseBody.contains("html")) { } else if (responseBody.contains("html")) {
qDebug() << "the response contains an html tag"; qDebug() << "the response contains an html tag";
return true; return true;
} else if (replyError == QNetworkReply::NetworkError::ContentNotFoundError) { } else if (httpStatus == httpStatusCodeNotFound) {
if (responseBody.contains(errorResponsePattern1) || responseBody.contains(errorResponsePattern2) if (responseBody.contains(errorResponsePattern1) || responseBody.contains(errorResponsePattern2)
|| responseBody.contains(errorResponsePattern3)) { || responseBody.contains(errorResponsePattern3)) {
return false; return false;
@@ -383,24 +439,18 @@ bool GatewayController::shouldBypassProxy(const QNetworkReply::NetworkError &rep
qDebug() << replyError; qDebug() << replyError;
return true; return true;
} }
} else if (replyError == QNetworkReply::NetworkError::OperationNotImplementedError) { } else if (httpStatus == httpStatusCodeNotImplemented) {
if (responseBody.contains(updateRequestResponsePattern)) { if (responseBody.contains(updateRequestResponsePattern)) {
return false; return false;
} else { } else {
qDebug() << replyError; qDebug() << replyError;
return true; return true;
} }
} else if (httpStatus == httpStatusCodeConflict) {
return false;
} else if (replyError != QNetworkReply::NetworkError::NoError) { } else if (replyError != QNetworkReply::NetworkError::NoError) {
qDebug() << replyError; qDebug() << replyError;
return true; return true;
} else if (checkEncryption) {
try {
QSimpleCrypto::QBlockCipher blockCipher;
static_cast<void>(blockCipher.decryptAesBlockCipher(responseBody, key, iv, "", salt));
} catch (...) {
qDebug() << "failed to decrypt the data";
return true;
}
} }
return false; return false;
} }
@@ -548,7 +598,8 @@ void GatewayController::getProxyUrlsAsync(const QStringList proxyStorageUrls, co
}); });
} }
void GatewayController::getProxyUrlAsync(const QStringList proxyUrls, const int currentProxyIndex, std::function<void(const QString &)> onComplete) void GatewayController::getProxyUrlAsync(const QStringList proxyUrls, const int currentProxyIndex,
std::function<void(const QString &)> onComplete)
{ {
if (currentProxyIndex >= proxyUrls.size()) { if (currentProxyIndex >= proxyUrls.size()) {
onComplete(""); onComplete("");
@@ -582,11 +633,11 @@ void GatewayController::getProxyUrlAsync(const QStringList proxyUrls, const int
void GatewayController::bypassProxyAsync( void GatewayController::bypassProxyAsync(
const QString &endpoint, const QString &proxyUrl, EncryptedRequestData encRequestData, const QString &endpoint, const QString &proxyUrl, EncryptedRequestData encRequestData,
std::function<void(const QByteArray &, const QList<QSslError> &, QNetworkReply::NetworkError, const QString &, int)> onComplete) std::function<void(const QByteArray &, bool, const QList<QSslError> &, QNetworkReply::NetworkError, const QString &, int)> onComplete)
{ {
auto sslErrors = QSharedPointer<QList<QSslError>>::create(); auto sslErrors = QSharedPointer<QList<QSslError>>::create();
if (proxyUrl.isEmpty()) { if (proxyUrl.isEmpty()) {
onComplete(QByteArray(), *sslErrors, QNetworkReply::InternalServerError, "empty proxy url", 0); onComplete(QByteArray(), false, *sslErrors, QNetworkReply::InternalServerError, "empty proxy url", 0);
return; return;
} }
@@ -597,7 +648,7 @@ void GatewayController::bypassProxyAsync(
connect(reply, &QNetworkReply::sslErrors, this, [sslErrors](const QList<QSslError> &errors) { *sslErrors = errors; }); connect(reply, &QNetworkReply::sslErrors, this, [sslErrors](const QList<QSslError> &errors) { *sslErrors = errors; });
connect(reply, &QNetworkReply::finished, this, [sslErrors, onComplete, reply]() { connect(reply, &QNetworkReply::finished, this, [sslErrors, onComplete, encRequestData, reply, this]() {
QByteArray encryptedResponseBody = reply->readAll(); QByteArray encryptedResponseBody = reply->readAll();
QString replyErrorString = reply->errorString(); QString replyErrorString = reply->errorString();
auto replyError = reply->error(); auto replyError = reply->error();
@@ -605,6 +656,10 @@ void GatewayController::bypassProxyAsync(
reply->deleteLater(); reply->deleteLater();
onComplete(encryptedResponseBody, *sslErrors, replyError, replyErrorString, httpStatusCode); auto decryptionResult =
tryDecryptResponseBody(encryptedResponseBody, replyError, encRequestData.key, encRequestData.iv, encRequestData.salt);
onComplete(decryptionResult.decryptedBody, decryptionResult.isDecryptionSuccessful, *sslErrors, replyError, replyErrorString,
httpStatusCode);
}); });
} }
+10 -3
View File
@@ -36,11 +36,18 @@ private:
amnezia::ErrorCode errorCode; amnezia::ErrorCode errorCode;
}; };
struct DecryptionResult
{
QByteArray decryptedBody;
bool isDecryptionSuccessful;
};
EncryptedRequestData prepareRequest(const QString &endpoint, const QJsonObject &apiPayload); EncryptedRequestData prepareRequest(const QString &endpoint, const QJsonObject &apiPayload);
DecryptionResult tryDecryptResponseBody(const QByteArray &encryptedResponseBody, QNetworkReply::NetworkError replyError,
const QByteArray &key, const QByteArray &iv, const QByteArray &salt);
QStringList getProxyUrls(const QString &serviceType, const QString &userCountryCode); QStringList getProxyUrls(const QString &serviceType, const QString &userCountryCode);
bool shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &responseBody, bool checkEncryption, bool shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &decryptedResponseBody, bool isDecryptionSuccessful);
const QByteArray &key = "", const QByteArray &iv = "", const QByteArray &salt = "");
void bypassProxy(const QString &endpoint, const QString &serviceType, const QString &userCountryCode, void bypassProxy(const QString &endpoint, const QString &serviceType, const QString &userCountryCode,
std::function<QNetworkReply *(const QString &url)> requestFunction, std::function<QNetworkReply *(const QString &url)> requestFunction,
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction); std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction);
@@ -50,7 +57,7 @@ private:
void getProxyUrlAsync(const QStringList proxyUrls, const int currentProxyIndex, std::function<void(const QString &)> onComplete); void getProxyUrlAsync(const QStringList proxyUrls, const int currentProxyIndex, std::function<void(const QString &)> onComplete);
void bypassProxyAsync( void bypassProxyAsync(
const QString &endpoint, const QString &proxyUrl, EncryptedRequestData encRequestData, const QString &endpoint, const QString &proxyUrl, EncryptedRequestData encRequestData,
std::function<void(const QByteArray &, const QList<QSslError> &, QNetworkReply::NetworkError, const QString &, int)> onComplete); std::function<void(const QByteArray &, bool, const QList<QSslError> &, QNetworkReply::NetworkError, const QString &, int)> onComplete);
int m_requestTimeoutMsecs; int m_requestTimeoutMsecs;
QString m_gatewayEndpoint; QString m_gatewayEndpoint;
+13 -7
View File
@@ -345,7 +345,7 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
return true; return true;
} }
if (container == DockerContainer::Awg) { if (ContainerProps::isAwgContainer(container)) {
if ((oldProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress) if ((oldProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress)
!= newProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress)) != newProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress))
|| (oldProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort) || (oldProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort)
@@ -367,11 +367,11 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
|| (oldProtoConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader) || (oldProtoConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader)
!= newProtoConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader)) != newProtoConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader))
|| (oldProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader)) || (oldProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader))
!= newProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader)) != newProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader)
// || (oldProtoConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize) || (oldProtoConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize)
// != newProtoConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize)) != newProtoConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize))
// || (oldProtoConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize) || (oldProtoConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize)
// != newProtoConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize)) != newProtoConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize)))
return true; return true;
} }
@@ -648,6 +648,11 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
vars.append({ { "$COOKIE_REPLY_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::cookieReplyPacketJunkSize).toString() } }); vars.append({ { "$COOKIE_REPLY_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::cookieReplyPacketJunkSize).toString() } });
vars.append({ { "$TRANSPORT_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::transportPacketJunkSize).toString() } }); vars.append({ { "$TRANSPORT_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::transportPacketJunkSize).toString() } });
vars.append({ { "$SPECIAL_JUNK_1", amneziaWireguarConfig.value(config_key::specialJunk1).toString() } });
vars.append({ { "$SPECIAL_JUNK_2", amneziaWireguarConfig.value(config_key::specialJunk2).toString() } });
vars.append({ { "$SPECIAL_JUNK_3", amneziaWireguarConfig.value(config_key::specialJunk3).toString() } });
vars.append({ { "$SPECIAL_JUNK_4", amneziaWireguarConfig.value(config_key::specialJunk4).toString() } });
vars.append({ { "$SPECIAL_JUNK_5", amneziaWireguarConfig.value(config_key::specialJunk5).toString() } });
// Socks5 proxy vars // Socks5 proxy vars
vars.append({ { "$SOCKS5_PROXY_PORT", socks5ProxyConfig.value(config_key::port).toString(protocols::socks5Proxy::defaultPort) } }); vars.append({ { "$SOCKS5_PROXY_PORT", socks5ProxyConfig.value(config_key::port).toString(protocols::socks5Proxy::defaultPort) } });
@@ -657,7 +662,8 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
vars.append({ { "$SOCKS5_USER", socks5user } }); vars.append({ { "$SOCKS5_USER", socks5user } });
vars.append({ { "$SOCKS5_AUTH_TYPE", socks5user.isEmpty() ? "none" : "strong" } }); vars.append({ { "$SOCKS5_AUTH_TYPE", socks5user.isEmpty() ? "none" : "strong" } });
QString serverIp = (container != DockerContainer::Awg && container != DockerContainer::WireGuard && container != DockerContainer::Xray) QString serverIp = (!ContainerProps::isAwgContainer(container) &&
container != DockerContainer::WireGuard && container != DockerContainer::Xray)
? NetworkUtilities::getIPAddress(credentials.hostName) ? NetworkUtilities::getIPAddress(credentials.hostName)
: credentials.hostName; : credentials.hostName;
if (!serverIp.isEmpty()) { if (!serverIp.isEmpty()) {
@@ -99,11 +99,12 @@ QJsonObject VpnConfigurationsController::createVpnConfiguration(const QPair<QStr
protocolConfigString = configurator->processConfigWithLocalSettings(dns, isApiConfig, protocolConfigString); protocolConfigString = configurator->processConfigWithLocalSettings(dns, isApiConfig, protocolConfigString);
QJsonObject vpnConfigData = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object(); QJsonObject vpnConfigData = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
if (container == DockerContainer::Awg || container == DockerContainer::WireGuard) { if (ContainerProps::isAwgContainer(container) || container == DockerContainer::WireGuard) {
// add mtu for old configs // add mtu for old configs
if (vpnConfigData[config_key::mtu].toString().isEmpty()) { if (vpnConfigData[config_key::mtu].toString().isEmpty()) {
vpnConfigData[config_key::mtu] = vpnConfigData[config_key::mtu] =
container == DockerContainer::Awg ? protocols::awg::defaultMtu : protocols::wireguard::defaultMtu; ContainerProps::isAwgContainer(container) ? protocols::awg::defaultMtu :
protocols::wireguard::defaultMtu;
} }
} }
+1
View File
@@ -121,6 +121,7 @@ namespace amnezia
ApiMigrationError = 1110, ApiMigrationError = 1110,
ApiUpdateRequestError = 1111, ApiUpdateRequestError = 1111,
ApiSubscriptionExpiredError = 1112, ApiSubscriptionExpiredError = 1112,
ApiPurchaseError = 1113,
// QFile errors // QFile errors
OpenError = 1200, OpenError = 1200,
+1
View File
@@ -78,6 +78,7 @@ QString errorString(ErrorCode code) {
case (ErrorCode::ApiMigrationError): errorMessage = QObject::tr("A migration error has occurred. Please contact our technical support"); break; case (ErrorCode::ApiMigrationError): errorMessage = QObject::tr("A migration error has occurred. Please contact our technical support"); break;
case (ErrorCode::ApiUpdateRequestError): errorMessage = QObject::tr("Please update the application to use this feature"); break; case (ErrorCode::ApiUpdateRequestError): errorMessage = QObject::tr("Please update the application to use this feature"); break;
case (ErrorCode::ApiSubscriptionExpiredError): errorMessage = QObject::tr("Your Amnezia Premium subscription has expired.\n Please check your email for renewal instructions.\n If you haven't received an email, please contact our support."); break; case (ErrorCode::ApiSubscriptionExpiredError): errorMessage = QObject::tr("Your Amnezia Premium subscription has expired.\n Please check your email for renewal instructions.\n If you haven't received an email, please contact our support."); break;
case (ErrorCode::ApiPurchaseError): errorMessage = QObject::tr("Unable to process purchase"); break;
// QFile errors // QFile errors
case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break; case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;
+45 -98
View File
@@ -1,129 +1,71 @@
#include "ipcclient.h" #include "ipcclient.h"
#include "ipc.h"
#include <QRemoteObjectNode> #include <QRemoteObjectNode>
#include <QtNetwork/qlocalsocket.h>
IpcClient *IpcClient::m_instance = nullptr;
IpcClient::IpcClient(QObject *parent) : QObject(parent) IpcClient::IpcClient(QObject *parent) : QObject(parent)
{ {
m_node.connectToNode(QUrl("local:" + amnezia::getIpcServiceUrl()));
m_interface.reset(m_node.acquire<IpcInterfaceReplica>());
m_tun2socks.reset(m_node.acquire<IpcProcessTun2SocksReplica>());
} }
IpcClient::~IpcClient() IpcClient& IpcClient::Instance()
{ {
if (m_localSocket) thread_local IpcClient ipcClient;
m_localSocket->close(); return ipcClient;
}
bool IpcClient::isSocketConnected() const
{
return m_isSocketConnected;
}
void IpcClient::closeAndResetInstance(bool deleteSelf)
{
if (m_localSocket)
{
m_localSocket->disconnectFromServer();
m_localSocket->deleteLater();
m_localSocket.clear();
}
m_ipcClient.reset();
m_Tun2SocksClient.reset();
m_isSocketConnected = false;
if (deleteSelf) {
m_instance = nullptr;
}
}
IpcClient *IpcClient::Instance()
{
return m_instance;
} }
QSharedPointer<IpcInterfaceReplica> IpcClient::Interface() QSharedPointer<IpcInterfaceReplica> IpcClient::Interface()
{ {
if (!Instance()) QSharedPointer<IpcInterfaceReplica> rep = Instance().m_interface;
if (rep.isNull()) {
qCritical() << "IpcClient::Interface(): Failed to acquire replica";
return nullptr; return nullptr;
return Instance()->m_ipcClient; }
if (!rep->waitForSource(1000)) {
qCritical() << "IpcClient::Interface(): Failed to initialize replica";
return nullptr;
}
if (!rep->isReplicaValid()) {
qWarning() << "IpcClient::Interface(): Replica is invalid";
}
return rep;
} }
QSharedPointer<IpcProcessTun2SocksReplica> IpcClient::InterfaceTun2Socks() QSharedPointer<IpcProcessTun2SocksReplica> IpcClient::InterfaceTun2Socks()
{ {
if (!Instance()) QSharedPointer<IpcProcessTun2SocksReplica> rep = Instance().m_tun2socks;
if (rep.isNull()) {
qCritical() << "IpcClient::InterfaceTun2Socks: Replica is undefined";
return nullptr; return nullptr;
return Instance()->m_Tun2SocksClient;
} }
if (!rep->waitForSource(1000)) {
bool IpcClient::init(IpcClient *instance) qCritical() << "IpcClient::InterfaceTun2Socks: Failed to initialize replica";
{ return nullptr;
if (m_instance && m_instance != instance) {
m_instance->closeAndResetInstance(false);
m_instance->deleteLater();
} }
m_instance = instance; if (!rep->isReplicaValid()) {
qWarning() << "IpcClient::InterfaceTun2Socks(): Replica is invalid";
Instance()->m_localSocket = new QLocalSocket(Instance());
connect(Instance()->m_localSocket.data(), &QLocalSocket::connected, &Instance()->m_ClientNode, []() {
Instance()->m_ClientNode.addClientSideConnection(Instance()->m_localSocket.data());
auto cliNode = Instance()->m_ClientNode.acquire<IpcInterfaceReplica>();
cliNode->waitForSource(5000);
Instance()->m_ipcClient.reset(cliNode);
if (!Instance()->m_ipcClient) {
qWarning() << "IpcClient is not ready!";
} }
return rep;
Instance()->m_ipcClient->waitForSource(1000);
if (!Instance()->m_ipcClient->isReplicaValid()) {
qWarning() << "IpcClient replica is not connected!";
}
auto t2sNode = Instance()->m_ClientNode.acquire<IpcProcessTun2SocksReplica>();
t2sNode->waitForSource(5000);
Instance()->m_Tun2SocksClient.reset(t2sNode);
if (!Instance()->m_Tun2SocksClient) {
qWarning() << "IpcClient::m_Tun2SocksClient is not ready!";
}
Instance()->m_Tun2SocksClient->waitForSource(1000);
if (!Instance()->m_Tun2SocksClient->isReplicaValid()) {
qWarning() << "IpcClient::m_Tun2SocksClient replica is not connected!";
}
});
connect(Instance()->m_localSocket, &QLocalSocket::disconnected,
[instance]() { instance->m_isSocketConnected = false; });
Instance()->m_localSocket->connectToServer(amnezia::getIpcServiceUrl());
Instance()->m_localSocket->waitForConnected();
if (!Instance()->m_ipcClient) {
qDebug() << "IpcClient::init failed";
return false;
}
qDebug() << "IpcClient::init succeed";
instance->m_isSocketConnected = (Instance()->m_ipcClient->isReplicaValid() && Instance()->m_Tun2SocksClient->isReplicaValid());
return Instance()->isSocketConnected();
} }
QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess() QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
{ {
if (!Instance()->m_ipcClient || !Instance()->m_ipcClient->isReplicaValid()) { QSharedPointer<IpcInterfaceReplica> rep = Interface();
qWarning() << "IpcClient::createPrivilegedProcess : IpcClient IpcClient replica is not valid"; if (!rep) {
qCritical() << "IpcClient::createPrivilegedProcess: Replica is invalid";
return nullptr; return nullptr;
} }
QRemoteObjectPendingReply<int> futureResult = Instance()->m_ipcClient->createPrivilegedProcess(); QRemoteObjectPendingReply<int> pidReply = rep->createPrivilegedProcess();
futureResult.waitForFinished(5000); if (!pidReply.waitForFinished(5000)){
qCritical() << "IpcClient::createPrivilegedProcess: Failed to execute RO createPrivilegedProcess call";
return nullptr;
}
int pid = futureResult.returnValue(); int pid = pidReply.returnValue();
QSharedPointer<ProcessDescriptor> pd(new ProcessDescriptor());
auto pd = QSharedPointer<ProcessDescriptor>(new ProcessDescriptor());
Instance()->m_processNodes.insert(pid, pd);
pd->localSocket.reset(new QLocalSocket(pd->replicaNode.data())); pd->localSocket.reset(new QLocalSocket(pd->replicaNode.data()));
@@ -131,6 +73,7 @@ QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
pd->replicaNode->addClientSideConnection(pd->localSocket.data()); pd->replicaNode->addClientSideConnection(pd->localSocket.data());
IpcProcessInterfaceReplica *repl = pd->replicaNode->acquire<IpcProcessInterfaceReplica>(); IpcProcessInterfaceReplica *repl = pd->replicaNode->acquire<IpcProcessInterfaceReplica>();
// TODO: rework the unsafe cast below
PrivilegedProcess *priv = static_cast<PrivilegedProcess *>(repl); PrivilegedProcess *priv = static_cast<PrivilegedProcess *>(repl);
pd->ipcProcess.reset(priv); pd->ipcProcess.reset(priv);
if (!pd->ipcProcess) { if (!pd->ipcProcess) {
@@ -145,8 +88,12 @@ QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
[pd]() { pd->replicaNode->deleteLater(); }); [pd]() { pd->replicaNode->deleteLater(); });
} }
}); });
pd->localSocket->connectToServer(amnezia::getIpcProcessUrl(pid)); pd->localSocket->connectToServer(amnezia::getIpcProcessUrl(pid));
pd->localSocket->waitForConnected(); if (!pd->localSocket->waitForConnected()) {
qCritical() << "IpcClient::createPrivilegedProcess: Failed to connect to process' socket";
return nullptr;
}
auto processReplica = QSharedPointer<PrivilegedProcess>(pd->ipcProcess); auto processReplica = QSharedPointer<PrivilegedProcess>(pd->ipcProcess);
return processReplica; return processReplica;
+32 -18
View File
@@ -4,7 +4,6 @@
#include <QLocalSocket> #include <QLocalSocket>
#include <QObject> #include <QObject>
#include "ipc.h"
#include "rep_ipc_interface_replica.h" #include "rep_ipc_interface_replica.h"
#include "rep_ipc_process_tun2socks_replica.h" #include "rep_ipc_process_tun2socks_replica.h"
@@ -16,26 +15,46 @@ class IpcClient : public QObject
public: public:
explicit IpcClient(QObject *parent = nullptr); explicit IpcClient(QObject *parent = nullptr);
static IpcClient *Instance(); static IpcClient& Instance();
static bool init(IpcClient *instance);
static QSharedPointer<IpcInterfaceReplica> Interface(); static QSharedPointer<IpcInterfaceReplica> Interface();
static QSharedPointer<IpcProcessTun2SocksReplica> InterfaceTun2Socks(); static QSharedPointer<IpcProcessTun2SocksReplica> InterfaceTun2Socks();
static QSharedPointer<PrivilegedProcess> CreatePrivilegedProcess(); static QSharedPointer<PrivilegedProcess> CreatePrivilegedProcess();
bool isSocketConnected() const; template <typename Func>
void closeAndResetInstance(bool deleteSelf = false); static auto withInterface(Func func)
{
QSharedPointer<IpcInterfaceReplica> iface = Instance().m_interface;
using ReturnType = decltype(func(std::declval<QSharedPointer<IpcInterfaceReplica>>()));
if (iface.isNull() || !iface->waitForSource(1000) || !iface->isReplicaValid()) {
qWarning() << "IpcClient::withInterface(): Service is not running";
if constexpr (std::is_void_v<ReturnType>)
return;
else
return ReturnType{};
}
return func(iface);
}
template <typename OnSuccess, typename OnFailure>
static auto withInterface(OnSuccess onSuccess, OnFailure onFailure)
{
QSharedPointer<IpcInterfaceReplica> iface = Instance().m_interface;
if (iface.isNull() || !iface->waitForSource(1000) || !iface->isReplicaValid()) {
return onFailure();
}
return onSuccess(iface);
}
signals: signals:
private: private:
~IpcClient() override; QRemoteObjectNode m_node;
QSharedPointer<IpcInterfaceReplica> m_interface;
QRemoteObjectNode m_ClientNode; QSharedPointer<IpcProcessTun2SocksReplica> m_tun2socks;
QRemoteObjectNode m_Tun2SocksNode;
QSharedPointer<IpcInterfaceReplica> m_ipcClient;
QPointer<QLocalSocket> m_localSocket;
QPointer<QLocalSocket> m_tun2socksSocket;
QSharedPointer<IpcProcessTun2SocksReplica> m_Tun2SocksClient;
struct ProcessDescriptor { struct ProcessDescriptor {
ProcessDescriptor () { ProcessDescriptor () {
@@ -47,11 +66,6 @@ private:
QSharedPointer<QRemoteObjectNode> replicaNode; QSharedPointer<QRemoteObjectNode> replicaNode;
QSharedPointer<QLocalSocket> localSocket; QSharedPointer<QLocalSocket> localSocket;
}; };
QMap<int, QSharedPointer<ProcessDescriptor>> m_processNodes;
bool m_isSocketConnected {false};
static IpcClient *m_instance;
}; };
#endif // IPCCLIENT_H #endif // IPCCLIENT_H
+37 -15
View File
@@ -1,11 +1,12 @@
#include "networkUtilities.h" #include "networkUtilities.h"
#include <QtNetwork/qnetworkinterface.h>
#include <cstddef>
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
#include <windows.h> #include <windows.h>
#include <Ipexport.h> #include <Ipexport.h>
#include <Ws2tcpip.h> #include <Ws2tcpip.h>
#include <ws2ipdef.h> #include <ws2ipdef.h>
#include <stdint.h>
#include <Iphlpapi.h> #include <Iphlpapi.h>
#include <Iptypes.h> #include <Iptypes.h>
#include <WinSock2.h> #include <WinSock2.h>
@@ -30,6 +31,15 @@
#include <netinet/in.h> #include <netinet/in.h>
#include <arpa/inet.h> #include <arpa/inet.h>
#include <net/route.h> #include <net/route.h>
#include <ifaddrs.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <ifaddrs.h>
#include <net/if.h>
#endif #endif
#include <QHostAddress> #include <QHostAddress>
@@ -239,12 +249,14 @@ DWORD GetAdaptersAddressesWrapper(const ULONG Family,
} }
#endif #endif
QString NetworkUtilities::getGatewayAndIface() QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
{ {
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
constexpr int BUFF_LEN = 100; constexpr int BUFF_LEN = 100;
char buff[BUFF_LEN] = {'\0'}; char buff[BUFF_LEN] = {'\0'};
QString result;
QString resGateway;
int resIndex = -1;
PIP_ADAPTER_ADDRESSES pAdapterAddresses = nullptr; PIP_ADAPTER_ADDRESSES pAdapterAddresses = nullptr;
DWORD dwRetVal = DWORD dwRetVal =
@@ -252,7 +264,7 @@ QString NetworkUtilities::getGatewayAndIface()
if (dwRetVal != NO_ERROR) { if (dwRetVal != NO_ERROR) {
qDebug() << "ipv4 stack detect GetAdaptersAddresses failed."; qDebug() << "ipv4 stack detect GetAdaptersAddresses failed.";
return ""; return {};
} }
PIP_ADAPTER_ADDRESSES pCurAddress = pAdapterAddresses; PIP_ADAPTER_ADDRESSES pCurAddress = pAdapterAddresses;
@@ -267,7 +279,9 @@ QString NetworkUtilities::getGatewayAndIface()
struct sockaddr_in addr; struct sockaddr_in addr;
if (inet_pton(AF_INET, buff, &addr.sin_addr) == 1) { if (inet_pton(AF_INET, buff, &addr.sin_addr) == 1) {
qDebug() << "this is true v4 !"; qDebug() << "this is true v4 !";
result = gw;
resGateway = gw;
resIndex = pCurAddress->IfIndex;
} }
} }
} }
@@ -275,7 +289,7 @@ QString NetworkUtilities::getGatewayAndIface()
} }
free(pAdapterAddresses); free(pAdapterAddresses);
return result; return { resGateway, QNetworkInterface::interfaceFromIndex(resIndex) };
#endif #endif
#ifdef Q_OS_LINUX #ifdef Q_OS_LINUX
constexpr int BUFFER_SIZE = 100; constexpr int BUFFER_SIZE = 100;
@@ -292,7 +306,7 @@ QString NetworkUtilities::getGatewayAndIface()
if ((sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) { if ((sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) {
perror("socket failed"); perror("socket failed");
return ""; return {};
} }
memset(msgbuf, 0, sizeof(msgbuf)); memset(msgbuf, 0, sizeof(msgbuf));
@@ -316,7 +330,7 @@ QString NetworkUtilities::getGatewayAndIface()
/* send msg */ /* send msg */
if (send(sock, nlmsg, nlmsg->nlmsg_len, 0) < 0) { if (send(sock, nlmsg, nlmsg->nlmsg_len, 0) < 0) {
perror("send failed"); perror("send failed");
return ""; return {};
} }
/* receive response */ /* receive response */
@@ -325,7 +339,7 @@ QString NetworkUtilities::getGatewayAndIface()
received_bytes = recv(sock, ptr, sizeof(buffer) - msg_len, 0); received_bytes = recv(sock, ptr, sizeof(buffer) - msg_len, 0);
if (received_bytes < 0) { if (received_bytes < 0) {
perror("Error in recv"); perror("Error in recv");
return ""; return {};
} }
nlh = (struct nlmsghdr *) ptr; nlh = (struct nlmsghdr *) ptr;
@@ -335,7 +349,7 @@ QString NetworkUtilities::getGatewayAndIface()
(nlmsg->nlmsg_type == NLMSG_ERROR)) (nlmsg->nlmsg_type == NLMSG_ERROR))
{ {
perror("Error in received packet"); perror("Error in received packet");
return ""; return {};
} }
/* If we received all data break */ /* If we received all data break */
@@ -388,10 +402,12 @@ QString NetworkUtilities::getGatewayAndIface()
} }
} }
close(sock); close(sock);
return gateway_address; return { gateway_address, QNetworkInterface::interfaceFromName(interface) };
#endif #endif
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS) && !defined(MACOS_NE) #if defined(Q_OS_MAC) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
QString gateway; QString gateway;
int index = -1;
int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_FLAGS, RTF_GATEWAY}; int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_FLAGS, RTF_GATEWAY};
int afinet_type[] = {AF_INET, AF_INET6}; int afinet_type[] = {AF_INET, AF_INET6};
@@ -401,17 +417,17 @@ QString NetworkUtilities::getGatewayAndIface()
size_t needed = 0; size_t needed = 0;
if (sysctl(mib, sizeof(mib) / sizeof(int), nullptr, &needed, nullptr, 0) < 0) if (sysctl(mib, sizeof(mib) / sizeof(int), nullptr, &needed, nullptr, 0) < 0)
return ""; return {};
char* buf; char* buf;
if ((buf = new char[needed]) == 0) if ((buf = new char[needed]) == 0)
return ""; return {};
if (sysctl(mib, sizeof(mib) / sizeof(int), buf, &needed, nullptr, 0) < 0) if (sysctl(mib, sizeof(mib) / sizeof(int), buf, &needed, nullptr, 0) < 0)
{ {
qDebug() << "sysctl: net.route.0.0.dump"; qDebug() << "sysctl: net.route.0.0.dump";
delete[] buf; delete[] buf;
return gateway; return {};
} }
struct rt_msghdr* rt; struct rt_msghdr* rt;
@@ -449,7 +465,10 @@ QString NetworkUtilities::getGatewayAndIface()
&(reinterpret_cast<struct sockaddr_in*>(sa_tab[RTAX_GATEWAY]))->sin_addr, &(reinterpret_cast<struct sockaddr_in*>(sa_tab[RTAX_GATEWAY]))->sin_addr,
sizeof(struct in_addr)); sizeof(struct in_addr));
if (inet_ntop(AF_INET, srcStr4, dstStr4, INET_ADDRSTRLEN) != nullptr) if (inet_ntop(AF_INET, srcStr4, dstStr4, INET_ADDRSTRLEN) != nullptr)
{
gateway = dstStr4; gateway = dstStr4;
index = rt->rtm_index;
}
break; break;
} }
} }
@@ -463,7 +482,10 @@ QString NetworkUtilities::getGatewayAndIface()
&(reinterpret_cast<struct sockaddr_in6*>(sa_tab[RTAX_GATEWAY]))->sin6_addr, &(reinterpret_cast<struct sockaddr_in6*>(sa_tab[RTAX_GATEWAY]))->sin6_addr,
sizeof(struct in6_addr)); sizeof(struct in6_addr));
if (inet_ntop(AF_INET6, srcStr6, dstStr6, INET6_ADDRSTRLEN) != nullptr) if (inet_ntop(AF_INET6, srcStr6, dstStr6, INET6_ADDRSTRLEN) != nullptr)
{
gateway = dstStr6; gateway = dstStr6;
index = rt->rtm_index;
}
break; break;
} }
} }
@@ -472,6 +494,6 @@ QString NetworkUtilities::getGatewayAndIface()
free(buf); free(buf);
} }
return gateway; return { gateway, QNetworkInterface::interfaceFromIndex(index) };
#endif #endif
} }
+2 -2
View File
@@ -6,7 +6,7 @@
#include <QString> #include <QString>
#include <QHostAddress> #include <QHostAddress>
#include <QNetworkReply> #include <QNetworkReply>
#include <QtNetwork/qnetworkinterface.h>
class NetworkUtilities : public QObject class NetworkUtilities : public QObject
{ {
@@ -17,7 +17,7 @@ public:
static bool checkIPv4Format(const QString &ip); static bool checkIPv4Format(const QString &ip);
static bool checkIpSubnetFormat(const QString &ip); static bool checkIpSubnetFormat(const QString &ip);
static bool checkIpv6Enabled(); static bool checkIpv6Enabled();
static QString getGatewayAndIface(); static QPair<QString, QNetworkInterface> getGatewayAndIface();
// Returns the Interface Index that could Route to dst // Returns the Interface Index that could Route to dst
static int AdapterIndexTo(const QHostAddress& dst); static int AdapterIndexTo(const QHostAddress& dst);
+59 -24
View File
@@ -1,8 +1,11 @@
#include "osSignalHandler.h" #include "osSignalHandler.h"
#include <QCoreApplication> #include <QCoreApplication>
#include <QMetaObject>
#include <QSocketNotifier> #include <QSocketNotifier>
#include "../amnezia_application.h"
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) #if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
#include <pthread.h> #include <pthread.h>
#include <signal.h> #include <signal.h>
@@ -15,7 +18,8 @@
#endif #endif
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
#include <QMetaObject> #include <QAbstractNativeEventFilter>
#include <windows.h> #include <windows.h>
#endif #endif
@@ -25,21 +29,30 @@ namespace
static bool initialized = false; static bool initialized = false;
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
static BOOL WINAPI consoleHandler(DWORD signal) class WindowsCloseFilter : public QAbstractNativeEventFilter
{ {
switch (signal) { public:
case CTRL_CLOSE_EVENT: bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) override
case CTRL_C_EVENT: {
case CTRL_BREAK_EVENT: MSG *msg = static_cast<MSG *>(message);
case CTRL_LOGOFF_EVENT:
case CTRL_SHUTDOWN_EVENT: switch (msg->message) {
if (QCoreApplication::instance()) { case WM_CLOSE: {
QMetaObject::invokeMethod(QCoreApplication::instance(), "quit", Qt::QueuedConnection); const HWND active = GetActiveWindow();
} const HWND self = msg->hwnd;
return TRUE; if (active != self) {
default: return FALSE; AmneziaApplication *app = qobject_cast<AmneziaApplication *>(QCoreApplication::instance());
if (app) {
QMetaObject::invokeMethod(app, "forceQuit", Qt::QueuedConnection);
} }
} }
}
}
return false;
};
};
static WindowsCloseFilter *windowsFilter = nullptr;
#endif #endif
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) #if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
@@ -70,15 +83,17 @@ namespace
} }
}); });
} }
#elif defined(Q_OS_MACX) #elif defined(Q_OS_MACOS)
static int signalPipe[2] = { -1, -1 }; static int signalPipe[2] = { -1, -1 };
static QSocketNotifier *socketNotifier = nullptr; static QSocketNotifier *socketNotifier = nullptr;
static void macSignalHandler(int) static void macSignalHandler(int)
{ {
if (signalPipe[1] >= 0) {
const char ch = 1; const char ch = 1;
::write(signalPipe[1], &ch, sizeof(ch)); ::write(signalPipe[1], &ch, sizeof(ch));
} }
}
static void setupUnixSignalHandler() static void setupUnixSignalHandler()
{ {
@@ -88,14 +103,6 @@ namespace
::fcntl(signalPipe[0], F_SETFL, O_NONBLOCK); ::fcntl(signalPipe[0], F_SETFL, O_NONBLOCK);
::fcntl(signalPipe[1], F_SETFL, O_NONBLOCK); ::fcntl(signalPipe[1], F_SETFL, O_NONBLOCK);
struct sigaction sa {};
sa.sa_handler = macSignalHandler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, nullptr);
sigaction(SIGTERM, &sa, nullptr);
socketNotifier = new QSocketNotifier(signalPipe[0], QSocketNotifier::Read, QCoreApplication::instance()); socketNotifier = new QSocketNotifier(signalPipe[0], QSocketNotifier::Read, QCoreApplication::instance());
QObject::connect(socketNotifier, &QSocketNotifier::activated, QCoreApplication::instance(), [](int) { QObject::connect(socketNotifier, &QSocketNotifier::activated, QCoreApplication::instance(), [](int) {
@@ -103,6 +110,14 @@ namespace
::read(signalPipe[0], buf, sizeof(buf)); ::read(signalPipe[0], buf, sizeof(buf));
QCoreApplication::quit(); QCoreApplication::quit();
}); });
struct sigaction sa {};
sa.sa_handler = macSignalHandler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, nullptr);
sigaction(SIGTERM, &sa, nullptr);
} }
#endif #endif
@@ -111,6 +126,8 @@ namespace
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) #if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
if (socketNotifier) { if (socketNotifier) {
socketNotifier->setEnabled(false); socketNotifier->setEnabled(false);
socketNotifier->deleteLater();
socketNotifier = nullptr;
} }
if (signalFd >= 0) { if (signalFd >= 0) {
@@ -119,8 +136,17 @@ namespace
} }
#elif defined(Q_OS_MACOS) #elif defined(Q_OS_MACOS)
struct sigaction sa {};
sa.sa_handler = SIG_DFL;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, nullptr);
sigaction(SIGTERM, &sa, nullptr);
if (socketNotifier) { if (socketNotifier) {
socketNotifier->setEnabled(false); socketNotifier->setEnabled(false);
socketNotifier->deleteLater();
socketNotifier = nullptr;
} }
if (signalPipe[0] >= 0) { if (signalPipe[0] >= 0) {
@@ -133,6 +159,14 @@ namespace
signalPipe[1] = -1; signalPipe[1] = -1;
} }
#endif #endif
#ifdef Q_OS_WIN
if (windowsFilter) {
QCoreApplication::instance()->removeNativeEventFilter(windowsFilter);
delete windowsFilter;
windowsFilter = nullptr;
}
#endif
} }
} }
@@ -147,12 +181,13 @@ void OsSignalHandler::setup()
initialized = true; initialized = true;
#if (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) || defined(Q_OS_MACX) #if (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) || defined(Q_OS_MACOS)
setupUnixSignalHandler(); setupUnixSignalHandler();
#endif #endif
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
SetConsoleCtrlHandler(consoleHandler, TRUE); windowsFilter = new WindowsCloseFilter();
QCoreApplication::instance()->installNativeEventFilter(windowsFilter);
#endif #endif
QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, [] { cleanupUnixSignalHandler(); }); QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, [] { cleanupUnixSignalHandler(); });
+2 -1
View File
@@ -11,7 +11,8 @@ QString amnezia::scriptFolder(amnezia::DockerContainer container)
case DockerContainer::Cloak: return QLatin1String("openvpn_cloak"); case DockerContainer::Cloak: return QLatin1String("openvpn_cloak");
case DockerContainer::ShadowSocks: return QLatin1String("openvpn_shadowsocks"); case DockerContainer::ShadowSocks: return QLatin1String("openvpn_shadowsocks");
case DockerContainer::WireGuard: return QLatin1String("wireguard"); case DockerContainer::WireGuard: return QLatin1String("wireguard");
case DockerContainer::Awg: return QLatin1String("awg"); case DockerContainer::Awg2: return QLatin1String("awg");
case DockerContainer::Awg: return QLatin1String("awg_legacy");
case DockerContainer::Ipsec: return QLatin1String("ipsec"); case DockerContainer::Ipsec: return QLatin1String("ipsec");
case DockerContainer::Xray: return QLatin1String("xray"); case DockerContainer::Xray: return QLatin1String("xray");
-12
View File
@@ -440,18 +440,6 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
if (!obj.value("I5").isNull()) { if (!obj.value("I5").isNull()) {
config.m_specialJunk["I5"] = obj.value("I5").toString(); config.m_specialJunk["I5"] = obj.value("I5").toString();
} }
if (!obj.value("J1").isNull()) {
config.m_controlledJunk["J1"] = obj.value("J1").toString();
}
if (!obj.value("J2").isNull()) {
config.m_controlledJunk["J2"] = obj.value("J2").toString();
}
if (!obj.value("J3").isNull()) {
config.m_controlledJunk["J3"] = obj.value("J3").toString();
}
if (!obj.value("Itime").isNull()) {
config.m_specialHandshakeTimeout = obj.value("Itime").toString();
}
return true; return true;
} }
-6
View File
@@ -152,12 +152,6 @@ QString InterfaceConfig::toWgConf(const QMap<QString, QString>& extra) const {
for (const QString& key : m_specialJunk.keys()) { for (const QString& key : m_specialJunk.keys()) {
out << key << " = " << m_specialJunk[key] << "\n"; out << key << " = " << m_specialJunk[key] << "\n";
} }
for (const QString& key : m_controlledJunk.keys()) {
out << key << " = " << m_controlledJunk[key] << "\n";
}
if (!m_specialHandshakeTimeout.isNull()) {
out << "Itime = " << m_specialHandshakeTimeout << "\n";
}
// If any extra config was provided, append it now. // If any extra config was provided, append it now.
for (const QString& key : extra.keys()) { for (const QString& key : extra.keys()) {
-2
View File
@@ -57,8 +57,6 @@ class InterfaceConfig {
QString m_underloadPacketMagicHeader; QString m_underloadPacketMagicHeader;
QString m_transportPacketMagicHeader; QString m_transportPacketMagicHeader;
QMap<QString, QString> m_specialJunk; QMap<QString, QString> m_specialJunk;
QMap<QString, QString> m_controlledJunk;
QString m_specialHandshakeTimeout;
QJsonObject toJson() const; QJsonObject toJson() const;
QString toWgConf( QString toWgConf(
+11 -23
View File
@@ -260,50 +260,38 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
json.insert(amnezia::config_key::specialJunk3, wgConfig.value(amnezia::config_key::specialJunk3)); json.insert(amnezia::config_key::specialJunk3, wgConfig.value(amnezia::config_key::specialJunk3));
json.insert(amnezia::config_key::specialJunk4, wgConfig.value(amnezia::config_key::specialJunk4)); json.insert(amnezia::config_key::specialJunk4, wgConfig.value(amnezia::config_key::specialJunk4));
json.insert(amnezia::config_key::specialJunk5, wgConfig.value(amnezia::config_key::specialJunk5)); json.insert(amnezia::config_key::specialJunk5, wgConfig.value(amnezia::config_key::specialJunk5));
json.insert(amnezia::config_key::controlledJunk1, wgConfig.value(amnezia::config_key::controlledJunk1));
json.insert(amnezia::config_key::controlledJunk2, wgConfig.value(amnezia::config_key::controlledJunk2));
json.insert(amnezia::config_key::controlledJunk3, wgConfig.value(amnezia::config_key::controlledJunk3));
json.insert(amnezia::config_key::specialHandshakeTimeout, wgConfig.value(amnezia::config_key::specialHandshakeTimeout));
} else if (!wgConfig.value(amnezia::config_key::junkPacketCount).isUndefined() } else if (!wgConfig.value(amnezia::config_key::junkPacketCount).isUndefined()
&& !wgConfig.value(amnezia::config_key::junkPacketMinSize).isUndefined() && !wgConfig.value(amnezia::config_key::junkPacketMinSize).isUndefined()
&& !wgConfig.value(amnezia::config_key::junkPacketMaxSize).isUndefined() && !wgConfig.value(amnezia::config_key::junkPacketMaxSize).isUndefined()
&& !wgConfig.value(amnezia::config_key::initPacketJunkSize).isUndefined() && !wgConfig.value(amnezia::config_key::initPacketJunkSize).isUndefined()
&& !wgConfig.value(amnezia::config_key::responsePacketJunkSize).isUndefined() && !wgConfig.value(amnezia::config_key::responsePacketJunkSize).isUndefined()
// && !wgConfig.value(amnezia::config_key::cookieReplyPacketJunkSize).isUndefined() && !wgConfig.value(amnezia::config_key::cookieReplyPacketJunkSize).isUndefined()
// && !wgConfig.value(amnezia::config_key::transportPacketJunkSize).isUndefined() && !wgConfig.value(amnezia::config_key::transportPacketJunkSize).isUndefined()
&& !wgConfig.value(amnezia::config_key::initPacketMagicHeader).isUndefined() && !wgConfig.value(amnezia::config_key::initPacketMagicHeader).isUndefined()
&& !wgConfig.value(amnezia::config_key::responsePacketMagicHeader).isUndefined() && !wgConfig.value(amnezia::config_key::responsePacketMagicHeader).isUndefined()
&& !wgConfig.value(amnezia::config_key::underloadPacketMagicHeader).isUndefined() && !wgConfig.value(amnezia::config_key::underloadPacketMagicHeader).isUndefined()
&& !wgConfig.value(amnezia::config_key::transportPacketMagicHeader).isUndefined() && !wgConfig.value(amnezia::config_key::transportPacketMagicHeader).isUndefined()
/* && !wgConfig.value(amnezia::config_key::specialJunk1).isUndefined() && !wgConfig.value(amnezia::config_key::specialJunk1).isUndefined()
&& !wgConfig.value(amnezia::config_key::specialJunk2).isUndefined() && !wgConfig.value(amnezia::config_key::specialJunk2).isUndefined()
&& !wgConfig.value(amnezia::config_key::specialJunk3).isUndefined() && !wgConfig.value(amnezia::config_key::specialJunk3).isUndefined()
&& !wgConfig.value(amnezia::config_key::specialJunk4).isUndefined() && !wgConfig.value(amnezia::config_key::specialJunk4).isUndefined()
&& !wgConfig.value(amnezia::config_key::specialJunk5).isUndefined() && !wgConfig.value(amnezia::config_key::specialJunk5).isUndefined()) {
&& !wgConfig.value(amnezia::config_key::controlledJunk1).isUndefined()
&& !wgConfig.value(amnezia::config_key::controlledJunk2).isUndefined()
&& !wgConfig.value(amnezia::config_key::controlledJunk3).isUndefined()
&& !wgConfig.value(amnezia::config_key::specialHandshakeTimeout).isUndefined()*/) {
json.insert(amnezia::config_key::junkPacketCount, wgConfig.value(amnezia::config_key::junkPacketCount)); json.insert(amnezia::config_key::junkPacketCount, wgConfig.value(amnezia::config_key::junkPacketCount));
json.insert(amnezia::config_key::junkPacketMinSize, wgConfig.value(amnezia::config_key::junkPacketMinSize)); json.insert(amnezia::config_key::junkPacketMinSize, wgConfig.value(amnezia::config_key::junkPacketMinSize));
json.insert(amnezia::config_key::junkPacketMaxSize, wgConfig.value(amnezia::config_key::junkPacketMaxSize)); json.insert(amnezia::config_key::junkPacketMaxSize, wgConfig.value(amnezia::config_key::junkPacketMaxSize));
json.insert(amnezia::config_key::initPacketJunkSize, wgConfig.value(amnezia::config_key::initPacketJunkSize)); json.insert(amnezia::config_key::initPacketJunkSize, wgConfig.value(amnezia::config_key::initPacketJunkSize));
json.insert(amnezia::config_key::responsePacketJunkSize, wgConfig.value(amnezia::config_key::responsePacketJunkSize)); json.insert(amnezia::config_key::responsePacketJunkSize, wgConfig.value(amnezia::config_key::responsePacketJunkSize));
// json.insert(amnezia::config_key::cookieReplyPacketJunkSize, wgConfig.value(amnezia::config_key::cookieReplyPacketJunkSize)); json.insert(amnezia::config_key::cookieReplyPacketJunkSize, wgConfig.value(amnezia::config_key::cookieReplyPacketJunkSize));
// json.insert(amnezia::config_key::transportPacketJunkSize, wgConfig.value(amnezia::config_key::transportPacketJunkSize)); json.insert(amnezia::config_key::transportPacketJunkSize, wgConfig.value(amnezia::config_key::transportPacketJunkSize));
json.insert(amnezia::config_key::initPacketMagicHeader, wgConfig.value(amnezia::config_key::initPacketMagicHeader)); json.insert(amnezia::config_key::initPacketMagicHeader, wgConfig.value(amnezia::config_key::initPacketMagicHeader));
json.insert(amnezia::config_key::responsePacketMagicHeader, wgConfig.value(amnezia::config_key::responsePacketMagicHeader)); json.insert(amnezia::config_key::responsePacketMagicHeader, wgConfig.value(amnezia::config_key::responsePacketMagicHeader));
json.insert(amnezia::config_key::underloadPacketMagicHeader, wgConfig.value(amnezia::config_key::underloadPacketMagicHeader)); json.insert(amnezia::config_key::underloadPacketMagicHeader, wgConfig.value(amnezia::config_key::underloadPacketMagicHeader));
json.insert(amnezia::config_key::transportPacketMagicHeader, wgConfig.value(amnezia::config_key::transportPacketMagicHeader)); json.insert(amnezia::config_key::transportPacketMagicHeader, wgConfig.value(amnezia::config_key::transportPacketMagicHeader));
// json.insert(amnezia::config_key::specialJunk1, wgConfig.value(amnezia::config_key::specialJunk1)); json.insert(amnezia::config_key::specialJunk1, wgConfig.value(amnezia::config_key::specialJunk1));
// json.insert(amnezia::config_key::specialJunk2, wgConfig.value(amnezia::config_key::specialJunk2)); json.insert(amnezia::config_key::specialJunk2, wgConfig.value(amnezia::config_key::specialJunk2));
// json.insert(amnezia::config_key::specialJunk3, wgConfig.value(amnezia::config_key::specialJunk3)); json.insert(amnezia::config_key::specialJunk3, wgConfig.value(amnezia::config_key::specialJunk3));
// json.insert(amnezia::config_key::specialJunk4, wgConfig.value(amnezia::config_key::specialJunk4)); json.insert(amnezia::config_key::specialJunk4, wgConfig.value(amnezia::config_key::specialJunk4));
// json.insert(amnezia::config_key::specialJunk5, wgConfig.value(amnezia::config_key::specialJunk5)); json.insert(amnezia::config_key::specialJunk5, wgConfig.value(amnezia::config_key::specialJunk5));
// json.insert(amnezia::config_key::controlledJunk1, wgConfig.value(amnezia::config_key::controlledJunk1));
// json.insert(amnezia::config_key::controlledJunk2, wgConfig.value(amnezia::config_key::controlledJunk2));
// json.insert(amnezia::config_key::controlledJunk3, wgConfig.value(amnezia::config_key::controlledJunk3));
// json.insert(amnezia::config_key::specialHandshakeTimeout, wgConfig.value(amnezia::config_key::specialHandshakeTimeout));
} }
write(json); write(json);
@@ -131,7 +131,7 @@ extension PacketTunnelProvider {
} }
startHandler = completionHandler startHandler = completionHandler
ovpnAdapter?.connect(using: packetFlow) ovpnAdapter?.connect(using: openVPNPacketFlow())
} }
func handleOpenVPNStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) { func handleOpenVPNStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
@@ -153,7 +153,7 @@ extension PacketTunnelProvider {
} }
func stopOpenVPN(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { func stopOpenVPN(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
ovpnLog(.info, message: "Stopping tunnel: reason: \(reason.description)") ovpnLog(.info, message: "Stopping tunnel: reason: \(reason.amneziaDescription)")
stopHandler = completionHandler stopHandler = completionHandler
if vpnReachability.isTracking { if vpnReachability.isTracking {
@@ -293,5 +293,3 @@ extension PacketTunnelProvider: OpenVPNAdapterDelegate {
ovpnLog(.info, message: logMessage) ovpnLog(.info, message: logMessage)
} }
} }
extension NEPacketTunnelFlow: OpenVPNAdapterPacketFlow {}
@@ -94,15 +94,24 @@ extension PacketTunnelProvider {
} }
} catch { } catch {
wg_log(.error, message: "Can't parse WG config: \(error.localizedDescription)") wg_log(.error, message: "Can't parse WG config: \(error.localizedDescription)")
completionHandler(nil) errorNotifier.notify(PacketTunnelProviderError.savedProtocolConfigurationIsInvalid)
completionHandler(PacketTunnelProviderError.savedProtocolConfigurationIsInvalid)
return return
} }
} }
func handleWireguardStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) { func handleWireguardStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
guard let completionHandler = completionHandler else { return } guard let completionHandler = completionHandler else { return }
wgAdapter?.getRuntimeConfiguration { settings in guard let wgAdapter = wgAdapter else {
let components = settings!.components(separatedBy: "\n") completionHandler(nil)
return
}
wgAdapter.getRuntimeConfiguration { settings in
guard let settings = settings else {
completionHandler(nil)
return
}
let components = settings.components(separatedBy: "\n")
var settingsDictionary: [String: String] = [:] var settingsDictionary: [String: String] = [:]
for component in components { for component in components {
@@ -131,7 +140,7 @@ extension PacketTunnelProvider {
} }
} }
private func handleWireguardAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) { func handleWireguardAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
guard let completionHandler = completionHandler else { return } guard let completionHandler = completionHandler else { return }
if messageData.count == 1 && messageData[0] == 0 { if messageData.count == 1 && messageData[0] == 0 {
wgAdapter?.getRuntimeConfiguration { settings in wgAdapter?.getRuntimeConfiguration { settings in
@@ -176,7 +185,7 @@ extension PacketTunnelProvider {
} }
func stopWireguard(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { func stopWireguard(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
wg_log(.info, message: "Stopping tunnel: reason: \(reason.description)") wg_log(.info, message: "Stopping tunnel: reason: \(reason.amneziaDescription)")
wgAdapter?.stop { error in wgAdapter?.stop { error in
ErrorNotifier.removeLastErrorFile() ErrorNotifier.removeLastErrorFile()
@@ -107,6 +107,8 @@ extension PacketTunnelProvider {
return return
} }
self?.updateActiveInterfaceIndexForCurrentPath()
// Launch xray // Launch xray
self?.setupAndStartXray(configData: updatedData) { xrayError in self?.setupAndStartXray(configData: updatedData) { xrayError in
if let xrayError { if let xrayError {
@@ -133,6 +135,15 @@ extension PacketTunnelProvider {
completionHandler() completionHandler()
} }
func sockCallback(fd: uintptr_t) {
if activeIfaceIdx != 0 {
withUnsafePointer(to: activeIfaceIdx) { ptr in
setsockopt(Int32(fd), IPPROTO_IP, IP_BOUND_IF, ptr, socklen_t(MemoryLayout<UInt32>.size))
setsockopt(Int32(fd), IPPROTO_IPV6, IPV6_BOUND_IF, ptr, socklen_t(MemoryLayout<UInt32>.size))
}
}
}
private func setupAndStartXray(configData: Data, private func setupAndStartXray(configData: Data,
completionHandler: @escaping (Error?) -> Void) { completionHandler: @escaping (Error?) -> Void) {
let path = Constants.cachesDirectory.appendingPathComponent("config.json", isDirectory: false).path let path = Constants.cachesDirectory.appendingPathComponent("config.json", isDirectory: false).path
@@ -142,6 +153,17 @@ extension PacketTunnelProvider {
return return
} }
updateActiveInterfaceIndexForCurrentPath()
let ctx = Unmanaged.passUnretained(self).toOpaque()
let cb: libxray_sockcallback = { (fd, ctx) in
guard let ctx = ctx else { return }
let instance = Unmanaged<PacketTunnelProvider>.fromOpaque(ctx).takeUnretainedValue()
instance.sockCallback(fd: fd)
}
LibXraySetSockCallback(cb, ctx)
LibXrayRunXray(nil, LibXrayRunXray(nil,
path, path,
Int64.max) Int64.max)
+167 -15
View File
@@ -1,5 +1,6 @@
import Foundation import Foundation
import NetworkExtension import NetworkExtension
import Network
import os import os
import Darwin import Darwin
import OpenVPNAdapter import OpenVPNAdapter
@@ -38,6 +39,12 @@ struct Constants {
class PacketTunnelProvider: NEPacketTunnelProvider { class PacketTunnelProvider: NEPacketTunnelProvider {
var wgAdapter: WireGuardAdapter? var wgAdapter: WireGuardAdapter?
var ovpnAdapter: OpenVPNAdapter? var ovpnAdapter: OpenVPNAdapter?
private lazy var openVPNPacketFlowAdapter = PacketTunnelFlowAdapter(flow: packetFlow)
private let pathMonitorQueue = DispatchQueue(label: Constants.processQueueName + ".path-monitor")
private let pathMonitor = NWPathMonitor()
private var didReceiveInitialPathUpdate = false
private var currentPath: Network.NWPath?
private var currentPathSignature: String?
var splitTunnelType: Int? var splitTunnelType: Int?
var splitTunnelSites: [String]? var splitTunnelSites: [String]?
@@ -48,7 +55,89 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
var stopHandler: (() -> Void)? var stopHandler: (() -> Void)?
var protoType: TunnelProtoType? var protoType: TunnelProtoType?
var activeIfaceIdx: UInt32 = 0
func openVPNPacketFlow() -> OpenVPNAdapterPacketFlow {
openVPNPacketFlowAdapter
}
override init() {
super.init()
pathMonitor.pathUpdateHandler = { [weak self] path in
guard let self else { return }
self.currentPath = path
let signature = self.pathSignature(for: path)
let hasMeaningfulChange = self.currentPathSignature != signature
self.currentPathSignature = signature
self.updateActiveInterfaceIndex(for: path)
guard self.didReceiveInitialPathUpdate else {
self.didReceiveInitialPathUpdate = true
return
}
guard hasMeaningfulChange, let proto = self.protoType else { return }
// WireGuard/AWG manages network changes internally; avoid restarting the tunnel here.
if proto == .wireguard {
return
}
DispatchQueue.main.async {
self.handle(networkChange: path) { _ in }
}
}
pathMonitor.start(queue: pathMonitorQueue)
currentPath = pathMonitor.currentPath
currentPathSignature = pathSignature(for: pathMonitor.currentPath)
}
func updateActiveInterfaceIndex(for path: Network.NWPath?) {
guard let path else {
activeIfaceIdx = 0
return
}
let preferredTypes: [NWInterface.InterfaceType] = [.wiredEthernet, .wifi, .cellular, .other]
let nonLoopbackInterfaces = path.availableInterfaces.filter { $0.type != .loopback }
let activeInterfaces = nonLoopbackInterfaces.filter { path.usesInterfaceType($0.type) }
let candidate = preferredTypes.compactMap { type in
activeInterfaces.first { $0.type == type }
}.first ?? activeInterfaces.first ?? nonLoopbackInterfaces.first
if let candidate {
activeIfaceIdx = UInt32(candidate.index)
} else {
activeIfaceIdx = 0
}
}
func updateActiveInterfaceIndexForCurrentPath() {
if let currentPath {
currentPathSignature = pathSignature(for: currentPath)
updateActiveInterfaceIndex(for: currentPath)
return
}
currentPath = pathMonitor.currentPath
currentPathSignature = pathSignature(for: pathMonitor.currentPath)
updateActiveInterfaceIndex(for: pathMonitor.currentPath)
}
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) { override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
if messageData.count == 1 && messageData[0] == 0 {
guard let completionHandler else { return }
if protoType == .wireguard {
handleWireguardAppMessage(messageData, completionHandler: completionHandler)
} else {
completionHandler(nil)
}
return
}
guard let message = String(data: messageData, encoding: .utf8) else { guard let message = String(data: messageData, encoding: .utf8) else {
if let completionHandler { if let completionHandler {
completionHandler(nil) completionHandler(nil)
@@ -59,6 +148,10 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
neLog(.info, title: "App said: ", message: message) neLog(.info, title: "App said: ", message: message)
guard let message = try? JSONSerialization.jsonObject(with: messageData, options: []) as? [String: Any] else { guard let message = try? JSONSerialization.jsonObject(with: messageData, options: []) as? [String: Any] else {
if protoType == .wireguard {
handleWireguardAppMessage(messageData, completionHandler: completionHandler)
return
}
neLog(.error, message: "Failed to serialize message from app") neLog(.error, message: "Failed to serialize message from app")
return return
} }
@@ -104,6 +197,9 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
return return
} }
didReceiveInitialPathUpdate = false
updateActiveInterfaceIndexForCurrentPath()
switch protoType { switch protoType {
case .wireguard: case .wireguard:
startWireguard(activationAttemptId: activationAttemptId, startWireguard(activationAttemptId: activationAttemptId,
@@ -157,28 +253,63 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
of object: Any?, of object: Any?,
change: [NSKeyValueChangeKey: Any]?, change: [NSKeyValueChangeKey: Any]?,
context: UnsafeMutableRawPointer?) { context: UnsafeMutableRawPointer?) {
guard Constants.kDefaultPathKey != keyPath else { return } guard Constants.kDefaultPathKey == keyPath else {
// Since iOS 11, we have observed that this KVO event fires repeatedly when connecting over Wifi,
// even though the underlying network has not changed (i.e. `isEqualToPath` returns false),
// leading to "wakeup crashes" due to excessive network activity. Guard against false positives by
// comparing the paths' string description, which includes properties not exposed by the class
guard let lastPath: NWPath = change?[.oldKey] as? NWPath,
let defPath = defaultPath,
lastPath != defPath || lastPath.description != defPath.description else {
return return
} }
DispatchQueue.main.async { [weak self] in
guard let self, self.defaultPath != nil else { return }
self.handle(networkChange: self.defaultPath!) { _ in }
}
} }
private func handle(networkChange changePath: NWPath, completion: @escaping (Error?) -> Void) { private func handle(networkChange changePath: Network.NWPath, completion: @escaping (Error?) -> Void) {
updateActiveInterfaceIndex(for: changePath)
wg_log(.info, message: "Tunnel restarted.") wg_log(.info, message: "Tunnel restarted.")
startTunnel(options: nil, completionHandler: completion) startTunnel(options: nil, completionHandler: completion)
} }
} }
private extension PacketTunnelProvider {
func pathSignature(for path: Network.NWPath) -> String {
var signatureComponents = [String(describing: path.status)]
signatureComponents.append(path.isExpensive ? "exp" : "noexp")
signatureComponents.append(path.isConstrained ? "con" : "nocon")
let preferredTypes: [NWInterface.InterfaceType] = [.wiredEthernet, .wifi, .cellular, .loopback, .other]
let sortedInterfaces = path.availableInterfaces.sorted { lhs, rhs in
if lhs.type == rhs.type {
return lhs.index < rhs.index
}
let lhsOrder = preferredTypes.firstIndex(of: lhs.type) ?? preferredTypes.count
let rhsOrder = preferredTypes.firstIndex(of: rhs.type) ?? preferredTypes.count
if lhsOrder == rhsOrder {
return lhs.index < rhs.index
}
return lhsOrder < rhsOrder
}
for interface in sortedInterfaces {
let typeName: String
switch interface.type {
case .wiredEthernet: typeName = "ethernet"
case .wifi: typeName = "wifi"
case .cellular: typeName = "cellular"
case .loopback: typeName = "loopback"
case .other: typeName = "other"
@unknown default: typeName = "unknown"
}
signatureComponents.append("\(typeName):\(interface.index)")
}
// Include currently used interface preference ordering
for type in preferredTypes {
let usesType = path.usesInterfaceType(type)
signatureComponents.append("uses-\(type):\(usesType)")
}
return signatureComponents.joined(separator: "|")
}
}
extension WireGuardLogLevel { extension WireGuardLogLevel {
var osLogLevel: OSLogType { var osLogLevel: OSLogType {
switch self { switch self {
@@ -190,8 +321,27 @@ extension WireGuardLogLevel {
} }
} }
extension NEProviderStopReason: CustomStringConvertible { final class PacketTunnelFlowAdapter: NSObject, OpenVPNAdapterPacketFlow {
public var description: String { private let flow: NEPacketTunnelFlow
init(flow: NEPacketTunnelFlow) {
self.flow = flow
super.init()
}
@objc(readPacketsWithCompletionHandler:)
func readPackets(completionHandler: @escaping ([Data], [NSNumber]) -> Void) {
flow.readPackets(completionHandler: completionHandler)
}
@objc(writePackets:withProtocols:)
func writePackets(_ packets: [Data], withProtocols protocols: [NSNumber]) -> Bool {
flow.writePackets(packets, withProtocols: protocols)
}
}
extension NEProviderStopReason {
var amneziaDescription: String {
switch self { switch self {
case .none: case .none:
return "No specific reason" return "No specific reason"
@@ -223,6 +373,8 @@ extension NEProviderStopReason: CustomStringConvertible {
return "The current console user changed" return "The current console user changed"
case .connectionFailed: case .connectionFailed:
return "The connection failed" return "The connection failed"
case .internalError:
return "The network extension reported an internal error"
case .sleep: case .sleep:
return "A stop reason indicating the VPNC enabled disconnect on sleep and the device went to sleep" return "A stop reason indicating the VPNC enabled disconnect on sleep and the device went to sleep"
case .appUpdate: case .appUpdate:
+40 -7
View File
@@ -11,13 +11,7 @@ class ScreenProtection {
import UIKit import UIKit
public func toggleScreenshots(_ isEnabled: Bool) { public func toggleScreenshots(_ isEnabled: Bool) {
let window = UIApplication.shared.keyWindows.first! ScreenProtection.shared.setScreenshotsEnabled(isEnabled)
if isEnabled {
ScreenProtection.shared.disable(for: window.rootViewController!.view)
} else {
ScreenProtection.shared.enable(for: window.rootViewController!.view)
}
} }
extension UIApplication { extension UIApplication {
@@ -45,6 +39,45 @@ class ScreenProtection {
private var blurView: UIVisualEffectView? private var blurView: UIVisualEffectView?
private var recordingObservation: NSKeyValueObservation? private var recordingObservation: NSKeyValueObservation?
private var desiredScreenshotsEnabled: Bool?
private var retryCount = 0
private var retryWorkItem: DispatchWorkItem?
public func setScreenshotsEnabled(_ isEnabled: Bool) {
DispatchQueue.main.async {
self.desiredScreenshotsEnabled = isEnabled
self.applyScreenshotsSettingOrRetry()
}
}
private func applyScreenshotsSettingOrRetry() {
assert(Thread.isMainThread)
guard let desiredScreenshotsEnabled else { return }
guard let window = UIApplication.shared.keyWindows.first,
let rootView = window.rootViewController?.view else {
retryCount += 1
guard retryCount <= 50 else { return } // ~5s total
retryWorkItem?.cancel()
let item = DispatchWorkItem { [weak self] in
self?.applyScreenshotsSettingOrRetry()
}
retryWorkItem = item
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: item)
return
}
retryWorkItem?.cancel()
retryWorkItem = nil
retryCount = 0
if desiredScreenshotsEnabled {
disable(for: rootView)
} else {
enable(for: rootView)
}
}
public func enable(for view: UIView) { public func enable(for view: UIView) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
+39
View File
@@ -0,0 +1,39 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef STOREKITCONTROLLER_H
#define STOREKITCONTROLLER_H
#import <Foundation/Foundation.h>
#import <StoreKit/StoreKit.h>
@class Product;
@class Transaction;
@class VerificationResult;
API_AVAILABLE(ios(15.0), macos(12.0))
@interface StoreKitController : NSObject
+ (instancetype)sharedInstance;
- (void)purchaseProduct:(NSString *)productIdentifier
completion:(void (^)(BOOL success,
NSString *_Nullable transactionId,
NSString *_Nullable productId,
NSString *_Nullable originalTransactionId,
NSError *_Nullable error))completion;
- (void)restorePurchasesWithCompletion:(void (^)(BOOL success,
NSArray<NSDictionary *> *_Nullable restoredTransactions,
NSError *_Nullable error))completion;
// Fetch product information for a set of identifiers without initiating a purchase
- (void)fetchProductsWithIdentifiers:(NSSet<NSString *> *)productIdentifiers
completion:(void (^)(NSArray<NSDictionary *> *products,
NSArray<NSString *> *invalidIdentifiers,
NSError *_Nullable error))completion;
@end
#endif // STOREKITCONTROLLER_H
+264
View File
@@ -0,0 +1,264 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#import "StoreKitController.h"
#import <StoreKit/StoreKit.h>
#include <QtCore/QDebug>
#include <QtCore/QString>
API_AVAILABLE(ios(15.0), macos(12.0))
@interface StoreKitController () <SKProductsRequestDelegate, SKPaymentTransactionObserver>
@property (nonatomic, copy) void (^purchaseCompletion)(BOOL success,
NSString *_Nullable transactionId,
NSString *_Nullable productId,
NSString *_Nullable originalTransactionId,
NSError *_Nullable error);
@property (nonatomic, copy) void (^restoreCompletion)(BOOL success,
NSArray<NSDictionary *> *_Nullable restoredTransactions,
NSError *_Nullable error);
@property (nonatomic, copy) void (^productsFetchCompletion)(NSArray<NSDictionary *> *products,
NSArray<NSString *> *invalidIdentifiers,
NSError *_Nullable error);
@property (nonatomic, strong) SKProductsRequest *productsRequest;
@property (nonatomic, strong) NSMutableArray<NSDictionary *> *restoredTransactions;
@end
@implementation StoreKitController
+ (instancetype)sharedInstance
{
static dispatch_once_t onceToken;
static StoreKitController *instance;
dispatch_once(&onceToken, ^{
if (@available(iOS 15.0, macOS 12.0, *)) {
instance = [[StoreKitController alloc] init];
}
});
return instance;
}
- (instancetype)init API_AVAILABLE(ios(15.0), macos(12.0))
{
self = [super init];
if (self) {
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}
return self;
}
- (void)dealloc
{
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
}
- (void)purchaseProduct:(NSString *)productIdentifier
completion:(void (^)(BOOL success,
NSString *_Nullable transactionId,
NSString *_Nullable productId,
NSString *_Nullable originalTransactionId,
NSError *_Nullable error))completion API_AVAILABLE(ios(15.0), macos(12.0))
{
self.purchaseCompletion = completion;
qInfo().noquote() << "[IAP][StoreKit] Starting purchase for" << QString::fromUtf8(productIdentifier.UTF8String);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self performPurchaseAsync:productIdentifier];
});
}
- (void)performPurchaseAsync:(NSString *)productIdentifier API_AVAILABLE(ios(15.0), macos(12.0))
{
dispatch_async(dispatch_get_main_queue(), ^{
@try {
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:productIdentifier]];
request.delegate = self;
[request start];
} @catch (NSException *exception) {
NSError *error = [NSError errorWithDomain:@"StoreKitController"
code:1
userInfo:@{ NSLocalizedDescriptionKey : exception.reason ?: @"Purchase failed" }];
if (self.purchaseCompletion) {
self.purchaseCompletion(NO, nil, nil, nil, error);
self.purchaseCompletion = nil;
}
}
});
}
- (void)restorePurchasesWithCompletion:(void (^)(BOOL success,
NSArray<NSDictionary *> *_Nullable restoredTransactions,
NSError *_Nullable error))completion API_AVAILABLE(ios(15.0), macos(12.0))
{
self.restoreCompletion = completion;
self.restoredTransactions = [NSMutableArray array];
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}
- (void)fetchProductsWithIdentifiers:(NSSet<NSString *> *)productIdentifiers
completion:(void (^)(NSArray<NSDictionary *> *products,
NSArray<NSString *> *invalidIdentifiers,
NSError *_Nullable error))completion API_AVAILABLE(ios(15.0), macos(12.0))
{
self.productsFetchCompletion = completion;
self.productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
self.productsRequest.delegate = self;
[self.productsRequest start];
}
#pragma mark - SKProductsRequestDelegate / SKRequestDelegate
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
if (self.purchaseCompletion) {
SKProduct *product = response.products.firstObject;
if (!product) {
NSError *error = [NSError errorWithDomain:@"StoreKitController"
code:0
userInfo:@{ NSLocalizedDescriptionKey : @"Product not found" }];
self.purchaseCompletion(NO, nil, nil, nil, error);
self.purchaseCompletion = nil;
self.productsRequest = nil;
return;
}
NSString *currencyCode = [product.priceLocale objectForKey:NSLocaleCurrencyCode] ?: @"";
NSString *priceString = [product.price stringValue] ?: @"";
qInfo().noquote() << "[IAP][StoreKit] Received product" << QString::fromUtf8(product.productIdentifier.UTF8String)
<< "price=" << QString::fromUtf8(priceString.UTF8String)
<< "currency=" << QString::fromUtf8(currencyCode.UTF8String);
SKPayment *payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addPayment:payment];
self.productsRequest = nil;
return;
}
if (self.productsFetchCompletion) {
NSMutableArray<NSDictionary *> *productDicts = [NSMutableArray array];
for (SKProduct *p in response.products) {
NSDictionary *productDict = @{
@"productId": p.productIdentifier,
@"title": p.localizedTitle,
@"description": p.localizedDescription,
@"price": p.price.stringValue,
@"currencyCode": [p.priceLocale objectForKey:NSLocaleCurrencyCode] ?: @""
};
[productDicts addObject:productDict];
NSString *productCurrency = [p.priceLocale objectForKey:NSLocaleCurrencyCode] ?: @"";
NSString *productPrice = [p.price stringValue] ?: @"";
qInfo().noquote() << "[IAP][StoreKit] Fetched product info" << QString::fromUtf8(p.productIdentifier.UTF8String)
<< "price=" << QString::fromUtf8(productPrice.UTF8String)
<< "currency=" << QString::fromUtf8(productCurrency.UTF8String);
}
self.productsFetchCompletion(productDicts, response.invalidProductIdentifiers, nil);
self.productsFetchCompletion = nil;
self.productsRequest = nil;
return;
}
}
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error
{
if (self.purchaseCompletion) {
self.purchaseCompletion(NO, nil, nil, nil, error);
self.purchaseCompletion = nil;
}
if (self.productsFetchCompletion) {
self.productsFetchCompletion(@[], @[], error);
self.productsFetchCompletion = nil;
}
self.productsRequest = nil;
}
#pragma mark - SKPaymentTransactionObserver
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions
{
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased: {
NSString *originalTransactionId = transaction.originalTransaction.transactionIdentifier ?: transaction.transactionIdentifier;
qInfo().noquote() << "[IAP][StoreKit] Transaction purchased" << QString::fromUtf8(transaction.transactionIdentifier.UTF8String)
<< "original=" << QString::fromUtf8((originalTransactionId ?: @"").UTF8String)
<< "product=" << QString::fromUtf8(transaction.payment.productIdentifier.UTF8String);
if (self.purchaseCompletion) {
self.purchaseCompletion(YES,
transaction.transactionIdentifier,
transaction.payment.productIdentifier,
originalTransactionId,
nil);
self.purchaseCompletion = nil;
}
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
}
case SKPaymentTransactionStateFailed:
qInfo().noquote() << "[IAP][StoreKit] Transaction failed" << QString::fromUtf8(transaction.transactionIdentifier.UTF8String)
<< "product=" << QString::fromUtf8(transaction.payment.productIdentifier.UTF8String)
<< "error=" << QString::fromUtf8(transaction.error.localizedDescription.UTF8String);
if (self.purchaseCompletion) {
self.purchaseCompletion(NO,
transaction.transactionIdentifier,
transaction.payment.productIdentifier,
nil,
transaction.error);
self.purchaseCompletion = nil;
}
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateRestored: {
if (self.restoreCompletion) {
NSString *transactionId = transaction.transactionIdentifier ?: @"";
NSString *originalTransactionId = transaction.originalTransaction.transactionIdentifier ?: transactionId;
NSString *productId = transaction.payment.productIdentifier ?: @"";
qInfo().noquote() << "[IAP][StoreKit] Transaction restored"
<< QString::fromUtf8(transactionId.UTF8String)
<< "original="
<< QString::fromUtf8((originalTransactionId ?: @"").UTF8String)
<< "product="
<< QString::fromUtf8((productId ?: @"").UTF8String);
NSDictionary *info = @{
@"transactionId": transactionId,
@"originalTransactionId": originalTransactionId ?: @"",
@"productId": productId ?: @""
};
if (!self.restoredTransactions) {
self.restoredTransactions = [NSMutableArray array];
}
[self.restoredTransactions addObject:info];
}
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
}
case SKPaymentTransactionStatePurchasing:
case SKPaymentTransactionStateDeferred:
break;
}
}
}
- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
{
if (self.restoreCompletion) {
NSArray<NSDictionary *> *transactions = [self.restoredTransactions copy];
self.restoreCompletion(YES, transactions, nil);
self.restoreCompletion = nil;
self.restoredTransactions = nil;
}
}
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error
{
if (self.restoreCompletion) {
self.restoreCompletion(NO, nil, error);
self.restoreCompletion = nil;
self.restoredTransactions = nil;
}
}
@end
+35 -33
View File
@@ -6,8 +6,6 @@ struct WGConfig: Decodable {
let junkPacketCount, junkPacketMinSize, junkPacketMaxSize: String? let junkPacketCount, junkPacketMinSize, junkPacketMaxSize: String?
let initPacketJunkSize, responsePacketJunkSize, cookieReplyPacketJunkSize, transportPacketJunkSize: String? let initPacketJunkSize, responsePacketJunkSize, cookieReplyPacketJunkSize, transportPacketJunkSize: String?
let specialJunk1, specialJunk2, specialJunk3, specialJunk4, specialJunk5: String? let specialJunk1, specialJunk2, specialJunk3, specialJunk4, specialJunk5: String?
let controlledJunk1, controlledJunk2, controlledJunk3: String?
let specialHandshakeTimeout: String?
let dns1: String let dns1: String
let dns2: String let dns2: String
let mtu: String let mtu: String
@@ -28,8 +26,6 @@ struct WGConfig: Decodable {
case junkPacketCount = "Jc", junkPacketMinSize = "Jmin", junkPacketMaxSize = "Jmax" case junkPacketCount = "Jc", junkPacketMinSize = "Jmin", junkPacketMaxSize = "Jmax"
case initPacketJunkSize = "S1", responsePacketJunkSize = "S2", cookieReplyPacketJunkSize = "S3", transportPacketJunkSize = "S4" case initPacketJunkSize = "S1", responsePacketJunkSize = "S2", cookieReplyPacketJunkSize = "S3", transportPacketJunkSize = "S4"
case specialJunk1 = "I1", specialJunk2 = "I2", specialJunk3 = "I3", specialJunk4 = "I4", specialJunk5 = "I5" case specialJunk1 = "I1", specialJunk2 = "I2", specialJunk3 = "I3", specialJunk4 = "I4", specialJunk5 = "I5"
case controlledJunk1 = "J1", controlledJunk2 = "J2", controlledJunk3 = "J3"
case specialHandshakeTimeout = "Itime"
case dns1 case dns1
case dns2 case dns2
case mtu case mtu
@@ -46,57 +42,63 @@ struct WGConfig: Decodable {
} }
var settings: String { var settings: String {
guard junkPacketCount != nil else { return "" } func trimmed(_ value: String?) -> String? {
guard let value = value?.trimmingCharacters(in: .whitespacesAndNewlines),
!value.isEmpty else {
return nil
}
return value
}
guard
let junkPacketCount = trimmed(junkPacketCount),
let junkPacketMinSize = trimmed(junkPacketMinSize),
let junkPacketMaxSize = trimmed(junkPacketMaxSize),
let initPacketJunkSize = trimmed(initPacketJunkSize),
let responsePacketJunkSize = trimmed(responsePacketJunkSize),
let initPacketMagicHeader = trimmed(initPacketMagicHeader),
let responsePacketMagicHeader = trimmed(responsePacketMagicHeader),
let underloadPacketMagicHeader = trimmed(underloadPacketMagicHeader),
let transportPacketMagicHeader = trimmed(transportPacketMagicHeader)
else { return "" }
var settingsLines: [String] = [] var settingsLines: [String] = []
// Required parameters when junkPacketCount is present // Required parameters when junkPacketCount is present
settingsLines.append("Jc = \(junkPacketCount!)") settingsLines.append("Jc = \(junkPacketCount)")
settingsLines.append("Jmin = \(junkPacketMinSize!)") settingsLines.append("Jmin = \(junkPacketMinSize)")
settingsLines.append("Jmax = \(junkPacketMaxSize!)") settingsLines.append("Jmax = \(junkPacketMaxSize)")
settingsLines.append("S1 = \(initPacketJunkSize!)") settingsLines.append("S1 = \(initPacketJunkSize)")
settingsLines.append("S2 = \(responsePacketJunkSize!)") settingsLines.append("S2 = \(responsePacketJunkSize)")
settingsLines.append("H1 = \(initPacketMagicHeader!)") settingsLines.append("H1 = \(initPacketMagicHeader)")
settingsLines.append("H2 = \(responsePacketMagicHeader!)") settingsLines.append("H2 = \(responsePacketMagicHeader)")
settingsLines.append("H3 = \(underloadPacketMagicHeader!)") settingsLines.append("H3 = \(underloadPacketMagicHeader)")
settingsLines.append("H4 = \(transportPacketMagicHeader!)") settingsLines.append("H4 = \(transportPacketMagicHeader)")
// Optional parameters - only add if not nil and not empty // Optional parameters - only add if not nil and not empty
if let s3 = cookieReplyPacketJunkSize, !s3.isEmpty { if let s3 = trimmed(cookieReplyPacketJunkSize) {
settingsLines.append("S3 = \(s3)") settingsLines.append("S3 = \(s3)")
} }
if let s4 = transportPacketJunkSize, !s4.isEmpty { if let s4 = trimmed(transportPacketJunkSize) {
settingsLines.append("S4 = \(s4)") settingsLines.append("S4 = \(s4)")
} }
if let i1 = specialJunk1, !i1.isEmpty { if let i1 = trimmed(specialJunk1) {
settingsLines.append("I1 = \(i1)") settingsLines.append("I1 = \(i1)")
} }
if let i2 = specialJunk2, !i2.isEmpty { if let i2 = trimmed(specialJunk2) {
settingsLines.append("I2 = \(i2)") settingsLines.append("I2 = \(i2)")
} }
if let i3 = specialJunk3, !i3.isEmpty { if let i3 = trimmed(specialJunk3) {
settingsLines.append("I3 = \(i3)") settingsLines.append("I3 = \(i3)")
} }
if let i4 = specialJunk4, !i4.isEmpty { if let i4 = trimmed(specialJunk4) {
settingsLines.append("I4 = \(i4)") settingsLines.append("I4 = \(i4)")
} }
if let i5 = specialJunk5, !i5.isEmpty { if let i5 = trimmed(specialJunk5) {
settingsLines.append("I5 = \(i5)") settingsLines.append("I5 = \(i5)")
} }
if let j1 = controlledJunk1, !j1.isEmpty {
settingsLines.append("J1 = \(j1)")
}
if let j2 = controlledJunk2, !j2.isEmpty {
settingsLines.append("J2 = \(j2)")
}
if let j3 = controlledJunk3, !j3.isEmpty {
settingsLines.append("J3 = \(j3)")
}
if let itime = specialHandshakeTimeout, !itime.isEmpty {
settingsLines.append("Itime = \(itime)")
}
return settingsLines.joined(separator: "\n") return settingsLines.joined(separator: "\n")
} }
+32 -2
View File
@@ -2,6 +2,13 @@
#define IOS_CONTROLLER_H #define IOS_CONTROLLER_H
#include "protocols/vpnprotocol.h" #include "protocols/vpnprotocol.h"
#include <functional>
#include <QVariant>
#include <QVariantMap>
#include <QStringList>
#include <QList>
#include <QElapsedTimer>
#include <atomic>
#ifdef __OBJC__ #ifdef __OBJC__
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
@@ -55,7 +62,24 @@ public:
bool shareText(const QStringList &filesToSend); bool shareText(const QStringList &filesToSend);
QString openFile(); QString openFile();
void purchaseProduct(const QString &productId,
std::function<void(bool success,
const QString &transactionId,
const QString &purchasedProductId,
const QString &originalTransactionId,
const QString &errorString)> &&callback);
void restorePurchases(std::function<void(bool success,
const QList<QVariantMap> &transactions,
const QString &errorString)> &&callback);
// Fetch product info for given product identifiers and return basic fields for logging
void fetchProducts(const QStringList &productIds,
std::function<void(const QList<QVariantMap> &products,
const QStringList &invalidIds,
const QString &errorString)> &&callback);
void requestInetAccess(); void requestInetAccess();
bool isTestFlight();
signals: signals:
void connectionStateChanged(Vpn::ConnectionState state); void connectionStateChanged(Vpn::ConnectionState state);
void bytesChanged(quint64 receivedBytes, quint64 sentBytes); void bytesChanged(quint64 receivedBytes, quint64 sentBytes);
@@ -81,6 +105,7 @@ private:
bool startXray(const QString &jsonConfig); bool startXray(const QString &jsonConfig);
void startTunnel(); void startTunnel();
void emitConnectionStateIfChanged(Vpn::ConnectionState state);
private: private:
void *m_iosControllerWrapper {}; void *m_iosControllerWrapper {};
@@ -94,8 +119,13 @@ private:
amnezia::Proto m_proto; amnezia::Proto m_proto;
QJsonObject m_rawConfig; QJsonObject m_rawConfig;
QString m_tunnelId; QString m_tunnelId;
uint64_t m_txBytes; uint64_t m_txBytes = 0;
uint64_t m_rxBytes; uint64_t m_rxBytes = 0;
bool m_handshakeAwaiting = false;
bool m_handshakeConfirmed = false;
QElapsedTimer m_handshakeTimer;
Vpn::ConnectionState m_lastEmittedState = Vpn::ConnectionState::Unknown;
std::atomic_bool m_statusRequestInFlight { false };
}; };
#endif // IOS_CONTROLLER_H #endif // IOS_CONTROLLER_H
+263 -18
View File
@@ -10,6 +10,7 @@
#include "../protocols/vpnprotocol.h" #include "../protocols/vpnprotocol.h"
#import "ios_controller_wrapper.h" #import "ios_controller_wrapper.h"
#import "StoreKitController.h"
const char* Action::start = "start"; const char* Action::start = "start";
const char* Action::restart = "restart"; const char* Action::restart = "restart";
@@ -92,6 +93,48 @@ Vpn::ConnectionState iosStatusToState(NEVPNStatus status) {
} }
} }
namespace {
constexpr int kHandshakeTimeoutMs = 12000;
constexpr uint64_t kHandshakeRxThreshold = 4096;
bool isWireGuardBasedProto(amnezia::Proto proto) {
return proto == amnezia::Proto::WireGuard || proto == amnezia::Proto::Awg;
}
uint64_t uint64FromResponse(NSDictionary *response, NSString *key, uint64_t fallback = 0) {
id value = response[key];
if (!value || value == [NSNull null]) {
return fallback;
}
if ([value isKindOfClass:[NSNumber class]]) {
return [(NSNumber *)value unsignedLongLongValue];
}
if ([value isKindOfClass:[NSString class]]) {
const char *str = [(NSString *)value UTF8String];
if (str && *str) {
return strtoull(str, nullptr, 10);
}
}
return fallback;
}
long long int64FromResponse(NSDictionary *response, NSString *key, long long fallback = 0) {
id value = response[key];
if (!value || value == [NSNull null]) {
return fallback;
}
if ([value isKindOfClass:[NSNumber class]]) {
return [(NSNumber *)value longLongValue];
}
if ([value isKindOfClass:[NSString class]]) {
const char *str = [(NSString *)value UTF8String];
if (str && *str) {
return strtoll(str, nullptr, 10);
}
}
return fallback;
}
}
namespace { namespace {
IosController* s_instance = nullptr; IosController* s_instance = nullptr;
} }
@@ -101,6 +144,9 @@ IosController::IosController() : QObject()
s_instance = this; s_instance = this;
m_iosControllerWrapper = [[IosControllerWrapper alloc] initWithCppController:this]; m_iosControllerWrapper = [[IosControllerWrapper alloc] initWithCppController:this];
// Initialize StoreKitController early to start observing the payment queue
[StoreKitController sharedInstance];
[[NSNotificationCenter defaultCenter] [[NSNotificationCenter defaultCenter]
removeObserver: (__bridge NSObject *)m_iosControllerWrapper]; removeObserver: (__bridge NSObject *)m_iosControllerWrapper];
[[NSNotificationCenter defaultCenter] [[NSNotificationCenter defaultCenter]
@@ -110,6 +156,15 @@ IosController::IosController() : QObject()
} }
void IosController::emitConnectionStateIfChanged(Vpn::ConnectionState state)
{
if (m_lastEmittedState == state) {
return;
}
m_lastEmittedState = state;
emit connectionStateChanged(state);
}
IosController* IosController::Instance() { IosController* IosController::Instance() {
if (!s_instance) { if (!s_instance) {
s_instance = new IosController(); s_instance = new IosController();
@@ -276,33 +331,65 @@ void IosController::disconnectVpn()
void IosController::checkStatus() void IosController::checkStatus()
{ {
if (!m_currentTunnel) {
return;
}
if (m_currentTunnel.connection.status != NEVPNStatusConnected) {
return;
}
if (m_statusRequestInFlight.exchange(true)) {
return;
}
NSString *actionKey = [NSString stringWithUTF8String:MessageKey::action]; NSString *actionKey = [NSString stringWithUTF8String:MessageKey::action];
NSString *actionValue = [NSString stringWithUTF8String:Action::getStatus]; NSString *actionValue = [NSString stringWithUTF8String:Action::getStatus];
NSString *tunnelIdKey = [NSString stringWithUTF8String:MessageKey::tunnelId]; NSString *tunnelIdKey = [NSString stringWithUTF8String:MessageKey::tunnelId];
NSString *tunnelIdValue = !m_tunnelId.isEmpty() ? m_tunnelId.toNSString() : @""; NSString *tunnelIdValue = !m_tunnelId.isEmpty() ? m_tunnelId.toNSString() : @"";
NSDictionary* message = @{actionKey: actionValue, tunnelIdKey: tunnelIdValue}; NSDictionary* message = @{actionKey: actionValue, tunnelIdKey: tunnelIdValue};
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sendVpnExtensionMessage(message, [&](NSDictionary* response){ sendVpnExtensionMessage(message, [&](NSDictionary* response){
uint64_t txBytes = [response[@"tx_bytes"] intValue]; if (!response) {
uint64_t rxBytes = [response[@"rx_bytes"] intValue]; QMetaObject::invokeMethod(this, [this]() {
m_statusRequestInFlight = false;
uint64_t last_handshake_time_sec = 0; }, Qt::QueuedConnection);
#if !MACOS_NE return;
if (response[@"last_handshake_time_sec"] && ![response[@"last_handshake_time_sec"] isKindOfClass:[NSNull class]]) {
last_handshake_time_sec = [response[@"last_handshake_time_sec"] intValue];
} else {
qDebug() << "Key last_handshake_time_sec is missing or null";
} }
if (last_handshake_time_sec < 0) { const uint64_t txBytes = uint64FromResponse(response, @"tx_bytes");
disconnectVpn(); const uint64_t rxBytes = uint64FromResponse(response, @"rx_bytes");
qDebug() << "Invalid handshake time, disconnecting VPN."; const long long last_handshake_time_sec = int64FromResponse(response, @"last_handshake_time_sec");
QMetaObject::invokeMethod(this, [this, txBytes, rxBytes, last_handshake_time_sec]() {
if (isWireGuardBasedProto(m_proto) && m_handshakeAwaiting) {
const bool hasHandshakeData = (last_handshake_time_sec >= 0);
const bool hasFreshHandshake = hasHandshakeData &&
((last_handshake_time_sec > 0) ||
(rxBytes >= kHandshakeRxThreshold) ||
(txBytes >= kHandshakeRxThreshold));
if (hasFreshHandshake) {
m_handshakeConfirmed = true;
m_handshakeAwaiting = false;
m_handshakeTimer.invalidate();
qDebug() << "IosController::checkStatus : handshake confirmed";
emitConnectionStateIfChanged(Vpn::ConnectionState::Connected);
} else if (m_handshakeTimer.isValid() &&
m_handshakeTimer.elapsed() > kHandshakeTimeoutMs) {
m_handshakeTimer.restart();
qDebug() << "IosController::checkStatus : handshake timed out, keeping tunnel alive";
emitConnectionStateIfChanged(Vpn::ConnectionState::Reconnecting);
}
} }
#endif
emit bytesChanged(rxBytes - m_rxBytes, txBytes - m_txBytes); emit bytesChanged(rxBytes - m_rxBytes, txBytes - m_txBytes);
m_rxBytes = rxBytes; m_rxBytes = rxBytes;
m_txBytes = txBytes; m_txBytes = txBytes;
m_statusRequestInFlight = false;
}, Qt::QueuedConnection);
});
}); });
} }
@@ -409,7 +496,22 @@ void IosController::vpnStatusDidChange(void *pNotification)
} }
} }
emit connectionStateChanged(iosStatusToState(session.status)); Vpn::ConnectionState nextState = iosStatusToState(session.status);
if (session.status == NEVPNStatusConnected && isWireGuardBasedProto(m_proto)) {
if (!m_handshakeConfirmed) {
nextState = Vpn::ConnectionState::Connecting;
if (!m_handshakeAwaiting) {
m_handshakeAwaiting = true;
m_handshakeTimer.restart();
}
}
} else if (session.status != NEVPNStatusConnected) {
m_handshakeAwaiting = false;
m_handshakeConfirmed = false;
m_handshakeTimer.invalidate();
m_statusRequestInFlight = false;
}
emitConnectionStateIfChanged(nextState);
} }
} }
@@ -670,10 +772,6 @@ bool IosController::setupAwg()
wgConfig.insert(config_key::specialJunk3, config[config_key::specialJunk3]); wgConfig.insert(config_key::specialJunk3, config[config_key::specialJunk3]);
wgConfig.insert(config_key::specialJunk4, config[config_key::specialJunk4]); wgConfig.insert(config_key::specialJunk4, config[config_key::specialJunk4]);
wgConfig.insert(config_key::specialJunk5, config[config_key::specialJunk5]); wgConfig.insert(config_key::specialJunk5, config[config_key::specialJunk5]);
wgConfig.insert(config_key::controlledJunk1, config[config_key::controlledJunk1]);
wgConfig.insert(config_key::controlledJunk2, config[config_key::controlledJunk2]);
wgConfig.insert(config_key::controlledJunk3, config[config_key::controlledJunk3]);
wgConfig.insert(config_key::specialHandshakeTimeout, config[config_key::specialHandshakeTimeout]);
QJsonDocument wgConfigDoc(wgConfig); QJsonDocument wgConfigDoc(wgConfig);
QString wgConfigDocStr(wgConfigDoc.toJson(QJsonDocument::Compact)); QString wgConfigDocStr(wgConfigDoc.toJson(QJsonDocument::Compact));
@@ -799,6 +897,9 @@ void IosController::sendVpnExtensionMessage(NSDictionary* message, std::function
{ {
if (!m_currentTunnel) { if (!m_currentTunnel) {
qDebug() << "Cannot set an extension callback without a tunnel manager"; qDebug() << "Cannot set an extension callback without a tunnel manager";
if (callback) {
callback(nil);
}
return; return;
} }
@@ -808,6 +909,9 @@ void IosController::sendVpnExtensionMessage(NSDictionary* message, std::function
if (!data || error) { if (!data || error) {
qDebug() << "Failed to serialize message to VpnExtension as JSON. Error:" qDebug() << "Failed to serialize message to VpnExtension as JSON. Error:"
<< [error.localizedDescription UTF8String]; << [error.localizedDescription UTF8String];
if (callback) {
callback(nil);
}
return; return;
} }
@@ -838,11 +942,18 @@ void IosController::sendVpnExtensionMessage(NSDictionary* message, std::function
[session sendProviderMessage:data returnError:&sendError responseHandler:completionHandler]; [session sendProviderMessage:data returnError:&sendError responseHandler:completionHandler];
} else { } else {
qDebug() << "Method sendProviderMessage:responseHandler:error: does not exist"; qDebug() << "Method sendProviderMessage:responseHandler:error: does not exist";
if (callback) {
callback(nil);
}
return;
} }
if (sendError) { if (sendError) {
qDebug() << "Failed to send message to VpnExtension. Error:" qDebug() << "Failed to send message to VpnExtension. Error:"
<< [sendError.localizedDescription UTF8String]; << [sendError.localizedDescription UTF8String];
if (callback) {
callback(nil);
}
} }
} }
@@ -913,6 +1024,135 @@ QString IosController::openFile() {
return filePath; return filePath;
} }
void IosController::purchaseProduct(const QString &productId,
std::function<void(bool success,
const QString &transactionId,
const QString &purchasedProductId,
const QString &originalTransactionId,
const QString &errorString)> &&callback)
{
qInfo().noquote() << "[IAP][IosController] purchaseProduct called" << productId;
if (@available(iOS 15.0, macOS 12.0, *)) {
StoreKitController *controller = [StoreKitController sharedInstance];
__block auto cb = std::move(callback);
[controller purchaseProduct:productId.toNSString() completion:^(BOOL s,
NSString * _Nullable transactionId,
NSString * _Nullable prodId,
NSString * _Nullable originalTxId,
NSError * _Nullable error) {
const QString txId = QString::fromUtf8((transactionId ?: @"").UTF8String);
const QString pId = QString::fromUtf8((prodId ?: @"").UTF8String);
const QString origTxId = QString::fromUtf8((originalTxId ?: @"").UTF8String);
const QString err = QString::fromUtf8((error.localizedDescription ?: @"").UTF8String);
qInfo().noquote() << "[IAP][IosController] purchase completion" << "success=" << s
<< "transactionId=" << txId << "originalTransactionId=" << origTxId
<< "productId=" << pId << "error=" << err;
if (cb) {
cb(s, txId, pId, origTxId, err);
}
}];
} else {
if (callback) {
callback(false, QString(), QString(), QString(), "StoreKit 2 requires iOS 15.0 or later");
}
}
}
void IosController::restorePurchases(std::function<void(bool success,
const QList<QVariantMap> &transactions,
const QString &errorString)> &&callback)
{
if (@available(iOS 15.0, macOS 12.0, *)) {
StoreKitController *controller = [StoreKitController sharedInstance];
__block auto cb = std::move(callback);
[controller restorePurchasesWithCompletion:^(BOOL s,
NSArray<NSDictionary *> * _Nullable restoredTransactions,
NSError * _Nullable error) {
QString err;
if (error) {
err = QString::fromUtf8(error.localizedDescription.UTF8String);
}
QList<QVariantMap> transactions;
for (NSDictionary *dict in restoredTransactions ?: @[]) {
QVariantMap transaction;
NSString *transactionId = dict[@"transactionId"];
NSString *productId = dict[@"productId"];
NSString *originalTransactionId = dict[@"originalTransactionId"];
if (transactionId) {
transaction.insert(QStringLiteral("transactionId"), QString::fromUtf8(transactionId.UTF8String));
}
if (productId) {
transaction.insert(QStringLiteral("productId"), QString::fromUtf8(productId.UTF8String));
}
if (originalTransactionId) {
transaction.insert(QStringLiteral("originalTransactionId"),
QString::fromUtf8(originalTransactionId.UTF8String));
}
transactions.push_back(transaction);
}
if (cb) {
cb(s, transactions, err);
}
}];
} else {
if (callback) {
callback(false, QList<QVariantMap>(), "StoreKit 2 requires iOS 15.0 or later");
}
}
}
void IosController::fetchProducts(const QStringList &productIds,
std::function<void(const QList<QVariantMap> &products,
const QStringList &invalidIds,
const QString &errorString)> &&callback)
{
if (@available(iOS 15.0, macOS 12.0, *)) {
StoreKitController *controller = [StoreKitController sharedInstance];
NSMutableSet<NSString *> *ids = [NSMutableSet setWithCapacity:productIds.size()];
for (const auto &pid : productIds) {
[ids addObject:pid.toNSString()];
}
__block auto cb = std::move(callback);
[controller fetchProductsWithIdentifiers:ids
completion:^(NSArray<NSDictionary *> * _Nonnull products,
NSArray<NSString *> * _Nonnull invalidIdentifiers,
NSError * _Nullable error) {
QList<QVariantMap> outProducts;
for (NSDictionary *p in products) {
QVariantMap m;
m["productId"] = QString::fromUtf8([p[@"productId"] UTF8String]);
m["title"] = QString::fromUtf8([p[@"title"] UTF8String]);
m["description"] = QString::fromUtf8([p[@"description"] UTF8String]);
m["price"] = QString::fromUtf8([p[@"price"] UTF8String]);
m["currencyCode"] = QString::fromUtf8([p[@"currencyCode"] UTF8String]);
outProducts.push_back(m);
}
QStringList invalid;
for (NSString *inv in invalidIdentifiers) {
invalid.push_back(QString::fromUtf8(inv.UTF8String));
}
QString err;
if (error) {
err = QString::fromUtf8(error.localizedDescription.UTF8String);
}
if (cb) {
cb(outProducts, invalid, err);
}
}];
} else {
if (callback) {
callback(QList<QVariantMap>(), QStringList(), "StoreKit 2 requires iOS 15.0 or later");
}
}
}
void IosController::requestInetAccess() { void IosController::requestInetAccess() {
NSURL *url = [NSURL URLWithString:@"http://captive.apple.com/generate_204"]; NSURL *url = [NSURL URLWithString:@"http://captive.apple.com/generate_204"];
if (!url) { if (!url) {
@@ -931,3 +1171,8 @@ void IosController::requestInetAccess() {
}]; }];
[task resume]; [task resume];
} }
bool IosController::isTestFlight() {
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
return receiptURL && [[receiptURL lastPathComponent] isEqualToString:@"sandboxReceipt"];
}
@@ -165,7 +165,7 @@ bool LinuxRouteMonitor::rtmSendRoute(int action, int flags, int type,
if (rtm->rtm_type == RTN_THROW) { if (rtm->rtm_type == RTN_THROW) {
struct in_addr ip4; struct in_addr ip4;
inet_pton(AF_INET, NetworkUtilities::getGatewayAndIface().toUtf8(), &ip4); inet_pton(AF_INET, NetworkUtilities::getGatewayAndIface().first.toUtf8(), &ip4);
nlmsg_append_attr(nlmsg, sizeof(buf), RTA_GATEWAY, &ip4, sizeof(ip4)); nlmsg_append_attr(nlmsg, sizeof(buf), RTA_GATEWAY, &ip4, sizeof(ip4));
nlmsg_append_attr32(nlmsg, sizeof(buf), RTA_PRIORITY, 0); nlmsg_append_attr32(nlmsg, sizeof(buf), RTA_PRIORITY, 0);
rtm->rtm_type = RTN_UNICAST; rtm->rtm_type = RTN_UNICAST;
@@ -143,12 +143,6 @@ bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) {
for (const QString& key : config.m_specialJunk.keys()) { for (const QString& key : config.m_specialJunk.keys()) {
out << key.toLower() << "=" << config.m_specialJunk.value(key) << "\n"; out << key.toLower() << "=" << config.m_specialJunk.value(key) << "\n";
} }
for (const QString& key : config.m_controlledJunk.keys()) {
out << key.toLower() << "=" << config.m_controlledJunk.value(key) << "\n";
}
if (!config.m_specialHandshakeTimeout.isEmpty()) {
out << "itime=" << config.m_specialHandshakeTimeout << "\n";
}
int err = uapiErrno(uapiCommand(message)); int err = uapiErrno(uapiCommand(message));
if (err != 0) { if (err != 0) {
@@ -141,12 +141,6 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) {
for (const QString& key : config.m_specialJunk.keys()) { for (const QString& key : config.m_specialJunk.keys()) {
out << key.toLower() << "=" << config.m_specialJunk.value(key) << "\n"; out << key.toLower() << "=" << config.m_specialJunk.value(key) << "\n";
} }
for (const QString& key : config.m_controlledJunk.keys()) {
out << key.toLower() << "=" << config.m_controlledJunk.value(key) << "\n";
}
if (!config.m_specialHandshakeTimeout.isEmpty()) {
out << "itime=" << config.m_specialHandshakeTimeout << "\n";
}
int err = uapiErrno(uapiCommand(message)); int err = uapiErrno(uapiCommand(message));
if (err != 0) { if (err != 0) {
@@ -6,6 +6,7 @@
#include <chrono> #include <chrono>
#include "ipc.h"
#include "logger.h" #include "logger.h"
#include "ikev2_vpn_protocol_windows.h" #include "ikev2_vpn_protocol_windows.h"
#include "utilities.h" #include "utilities.h"
+41 -18
View File
@@ -7,7 +7,7 @@
#include <QNetworkInterface> #include <QNetworkInterface>
#include "core/networkUtilities.h" #include "core/networkUtilities.h"
#include "logger.h" #include "ipc.h"
#include "openvpnprotocol.h" #include "openvpnprotocol.h"
#include "utilities.h" #include "utilities.h"
#include "version.h" #include "version.h"
@@ -56,8 +56,12 @@ void OpenVpnProtocol::stop()
} }
#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS) #if defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
QRemoteObjectPendingReply<bool> disableKillSwitchResp = IpcClient::Interface()->disableKillSwitch(); IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
disableKillSwitchResp.waitForFinished(1000); QRemoteObjectPendingReply<bool> reply = iface->disableKillSwitch();
if (!reply.waitForFinished(1000) && !reply.returnValue()) {
qWarning() << "OpenVpnProtocol::stop(): Failed to disable killswitch";
}
});
#endif #endif
setConnectionState(Vpn::ConnectionState::Disconnected); setConnectionState(Vpn::ConnectionState::Disconnected);
@@ -65,21 +69,24 @@ void OpenVpnProtocol::stop()
ErrorCode OpenVpnProtocol::prepare() ErrorCode OpenVpnProtocol::prepare()
{ {
if (!IpcClient::Interface()) { return IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
return ErrorCode::AmneziaServiceConnectionFailed; QRemoteObjectPendingReply<QStringList> listReply = iface->getTapList();
if (!listReply.waitForFinished(1000)) {
return ErrorCode::InternalError;
} }
QRemoteObjectPendingReply<QStringList> resultCheck = IpcClient::Interface()->getTapList(); QStringList list = listReply.returnValue();
resultCheck.waitForFinished(); if (list.empty()) {
QRemoteObjectPendingReply<bool> installReply = iface->checkAndInstallDriver();
if (resultCheck.returnValue().isEmpty()) { if (!installReply.waitForFinished() || !installReply.returnValue()) {
QRemoteObjectPendingReply<bool> resultInstall = IpcClient::Interface()->checkAndInstallDriver();
resultInstall.waitForFinished();
if (!resultInstall.returnValue())
return ErrorCode::OpenVpnTapAdapterError; return ErrorCode::OpenVpnTapAdapterError;
} }
}
return ErrorCode::NoError; return ErrorCode::NoError;
}, [] () {
return ErrorCode::AmneziaServiceConnectionFailed;
});
} }
void OpenVpnProtocol::killOpenVpnProcess() void OpenVpnProtocol::killOpenVpnProcess()
@@ -173,8 +180,17 @@ ErrorCode OpenVpnProtocol::start()
} }
#ifdef AMNEZIA_DESKTOP #ifdef AMNEZIA_DESKTOP
IpcClient::Interface()->addKillSwitchAllowedRange(QStringList(NetworkUtilities::getIPAddress( const ErrorCode res = IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
m_configData.value(amnezia::config_key::hostName).toString()))); QString ip = NetworkUtilities::getIPAddress(m_configData.value(amnezia::config_key::hostName).toString());
QRemoteObjectPendingReply<bool> reply = iface->addKillSwitchAllowedRange(QStringList(ip));
if (!reply.waitForFinished(1000) || !reply.returnValue()) {
return ErrorCode::AmneziaServiceConnectionFailed;
}
return ErrorCode::NoError;
});
if (res != ErrorCode::NoError) {
return res;
}
#endif #endif
// Detect default gateway // Detect default gateway
@@ -337,6 +353,7 @@ void OpenVpnProtocol::updateVpnGateway(const QString &line)
m_vpnGateway = l.split(" ").at(2); m_vpnGateway = l.split(" ").at(2);
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
QThread::msleep(300); QThread::msleep(300);
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
QList<QNetworkInterface> netInterfaces = QNetworkInterface::allInterfaces(); QList<QNetworkInterface> netInterfaces = QNetworkInterface::allInterfaces();
for (int i = 0; i < netInterfaces.size(); i++) { for (int i = 0; i < netInterfaces.size(); i++) {
for (int j=0; j < netInterfaces.at(i).addressEntries().size(); j++) for (int j=0; j < netInterfaces.at(i).addressEntries().size(); j++)
@@ -344,23 +361,29 @@ void OpenVpnProtocol::updateVpnGateway(const QString &line)
// killSwitch toggle // killSwitch toggle
if (m_vpnLocalAddress == netInterfaces.at(i).addressEntries().at(j).ip().toString()) { if (m_vpnLocalAddress == netInterfaces.at(i).addressEntries().at(j).ip().toString()) {
if (QVariant(m_configData.value(config_key::killSwitchOption).toString()).toBool()) { if (QVariant(m_configData.value(config_key::killSwitchOption).toString()).toBool()) {
IpcClient::Interface()->enableKillSwitch(m_configData, netInterfaces.at(i).index()); iface->enableKillSwitch(m_configData, netInterfaces.at(i).index());
} }
m_configData.insert("vpnAdapterIndex", netInterfaces.at(i).index()); m_configData.insert("vpnAdapterIndex", netInterfaces.at(i).index());
m_configData.insert("vpnGateway", m_vpnGateway); m_configData.insert("vpnGateway", m_vpnGateway);
m_configData.insert("vpnServer", m_configData.insert("vpnServer",
NetworkUtilities::getIPAddress(m_configData.value(amnezia::config_key::hostName).toString())); NetworkUtilities::getIPAddress(m_configData.value(amnezia::config_key::hostName).toString()));
IpcClient::Interface()->enablePeerTraffic(m_configData); iface->enablePeerTraffic(m_configData);
} }
} }
} }
});
#endif #endif
#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS) #if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
// killSwitch toggle // killSwitch toggle
if (QVariant(m_configData.value(config_key::killSwitchOption).toString()).toBool()) { if (QVariant(m_configData.value(config_key::killSwitchOption).toString()).toBool()) {
m_configData.insert("vpnServer", m_configData.insert("vpnServer",
NetworkUtilities::getIPAddress(m_configData.value(amnezia::config_key::hostName).toString())); NetworkUtilities::getIPAddress(m_configData.value(amnezia::config_key::hostName).toString()));
IpcClient::Interface()->enableKillSwitch(m_configData, 0); IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
QRemoteObjectPendingReply<bool> reply = iface->enableKillSwitch(m_configData, 0);
if (!reply.waitForFinished(1000) || !reply.returnValue()) {
qWarning() << "OpenVpnProtocol::updateVpnGateway(): Failed to enable killswitch";
}
});
} }
#endif #endif
qDebug() << QString("Set vpn local address %1, gw %2").arg(m_vpnLocalAddress).arg(vpnGateway()); qDebug() << QString("Set vpn local address %1, gw %2").arg(m_vpnLocalAddress).arg(vpnGateway());
+15
View File
@@ -1,6 +1,7 @@
#include "protocols_defs.h" #include "protocols_defs.h"
#include <QRandomGenerator> #include <QRandomGenerator>
#include <QJsonObject>
using namespace amnezia; using namespace amnezia;
@@ -217,3 +218,17 @@ QString ProtocolProps::key_proto_config_path(Proto p)
{ {
return protoToString(p) + "_config_path"; return protoToString(p) + "_config_path";
} }
QString ProtocolProps::getProtocolVersion(const QJsonObject &protocolConfig)
{
return protocolConfig.value(config_key::protocolVersion).toString();
}
QString ProtocolProps::getProtocolVersionString(const QJsonObject &protocolConfig)
{
auto version = getProtocolVersion(protocolConfig);
if (version == protocols::awg::awgV2) return QObject::tr(" (version 2)");
if (version == protocols::awg::awgV1_5) return QObject::tr(" (version 1.5)");
return "";
}
+11 -10
View File
@@ -83,10 +83,8 @@ namespace amnezia
constexpr char specialJunk3[] = "I3"; constexpr char specialJunk3[] = "I3";
constexpr char specialJunk4[] = "I4"; constexpr char specialJunk4[] = "I4";
constexpr char specialJunk5[] = "I5"; constexpr char specialJunk5[] = "I5";
constexpr char controlledJunk1[] = "J1";
constexpr char controlledJunk2[] = "J2"; constexpr char protocolVersion[] = "protocol_version";
constexpr char controlledJunk3[] = "J3";
constexpr char specialHandshakeTimeout[] = "Itime";
constexpr char openvpn[] = "openvpn"; constexpr char openvpn[] = "openvpn";
constexpr char wireguard[] = "wireguard"; constexpr char wireguard[] = "wireguard";
@@ -218,7 +216,8 @@ namespace amnezia
constexpr char defaultMtu[] = "1376"; constexpr char defaultMtu[] = "1376";
#endif #endif
constexpr char serverConfigPath[] = "/opt/amnezia/awg/wg0.conf"; constexpr char serverConfigPath[] = "/opt/amnezia/awg/awg0.conf";
constexpr char serverLegacyConfigPath[] = "/opt/amnezia/awg/wg0.conf";
constexpr char serverPublicKeyPath[] = "/opt/amnezia/awg/wireguard_server_public_key.key"; constexpr char serverPublicKeyPath[] = "/opt/amnezia/awg/wireguard_server_public_key.key";
constexpr char serverPskKeyPath[] = "/opt/amnezia/awg/wireguard_psk.key"; constexpr char serverPskKeyPath[] = "/opt/amnezia/awg/wireguard_psk.key";
@@ -234,15 +233,14 @@ namespace amnezia
constexpr char defaultResponsePacketMagicHeader[] = "3288052141"; constexpr char defaultResponsePacketMagicHeader[] = "3288052141";
constexpr char defaultTransportPacketMagicHeader[] = "2528465083"; constexpr char defaultTransportPacketMagicHeader[] = "2528465083";
constexpr char defaultUnderloadPacketMagicHeader[] = "1766607858"; constexpr char defaultUnderloadPacketMagicHeader[] = "1766607858";
constexpr char defaultSpecialJunk1[] = ""; constexpr char defaultSpecialJunk1[] = "<b 0x084481800001000300000000077469636b65747306776964676574096b696e6f706f69736b0272750000010001c00c0005000100000039001806776964676574077469636b6574730679616e646578c025c0390005000100000039002b1765787465726e616c2d7469636b6574732d776964676574066166697368610679616e646578036e657400c05d000100010000001c000457fafe25>";
constexpr char defaultSpecialJunk2[] = ""; constexpr char defaultSpecialJunk2[] = "";
constexpr char defaultSpecialJunk3[] = ""; constexpr char defaultSpecialJunk3[] = "";
constexpr char defaultSpecialJunk4[] = ""; constexpr char defaultSpecialJunk4[] = "";
constexpr char defaultSpecialJunk5[] = ""; constexpr char defaultSpecialJunk5[] = "";
constexpr char defaultControlledJunk1[] = "";
constexpr char defaultControlledJunk2[] = ""; constexpr char awgV1_5[] = "1.5";
constexpr char defaultControlledJunk3[] = ""; constexpr char awgV2[] = "2";
constexpr char defaultSpecialHandshakeTimeout[] = "";
} }
namespace socks5Proxy namespace socks5Proxy
@@ -325,6 +323,9 @@ namespace amnezia
Q_INVOKABLE static QString key_proto_config_data(Proto p); Q_INVOKABLE static QString key_proto_config_data(Proto p);
Q_INVOKABLE static QString key_proto_config_path(Proto p); Q_INVOKABLE static QString key_proto_config_path(Proto p);
static QString getProtocolVersion(const QJsonObject &protocolConfig);
static QString getProtocolVersionString(const QJsonObject &protocolConfig);
}; };
} // namespace amnezia } // namespace amnezia
+1
View File
@@ -119,6 +119,7 @@ VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject &
case DockerContainer::Cloak: return new OpenVpnOverCloakProtocol(configuration); case DockerContainer::Cloak: return new OpenVpnOverCloakProtocol(configuration);
case DockerContainer::ShadowSocks: return new ShadowSocksVpnProtocol(configuration); case DockerContainer::ShadowSocks: return new ShadowSocksVpnProtocol(configuration);
case DockerContainer::WireGuard: return new WireguardProtocol(configuration); case DockerContainer::WireGuard: return new WireguardProtocol(configuration);
case DockerContainer::Awg2: return new WireguardProtocol(configuration);
case DockerContainer::Awg: return new WireguardProtocol(configuration); case DockerContainer::Awg: return new WireguardProtocol(configuration);
case DockerContainer::Xray: return new XrayProtocol(configuration); case DockerContainer::Xray: return new XrayProtocol(configuration);
case DockerContainer::SSXray: return new XrayProtocol(configuration); case DockerContainer::SSXray: return new XrayProtocol(configuration);
+52 -124
View File
@@ -1,17 +1,19 @@
#include "xrayprotocol.h" #include "xrayprotocol.h"
#include "core/ipcclient.h"
#include "utilities.h"
#include "core/networkUtilities.h"
#include <QCryptographicHash> #include <QCryptographicHash>
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
#include <QNetworkInterface> #include <QNetworkInterface>
#include <QJsonDocument>
#include "core/networkUtilities.h"
#include "utilities.h"
XrayProtocol::XrayProtocol(const QJsonObject &configuration, QObject *parent) : VpnProtocol(configuration, parent) XrayProtocol::XrayProtocol(const QJsonObject &configuration, QObject *parent) : VpnProtocol(configuration, parent)
{ {
readXrayConfiguration(configuration); readXrayConfiguration(configuration);
m_routeGateway = NetworkUtilities::getGatewayAndIface(); m_routeGateway = NetworkUtilities::getGatewayAndIface().first;
m_vpnGateway = amnezia::protocols::xray::defaultLocalAddr; m_vpnGateway = amnezia::protocols::xray::defaultLocalAddr;
m_vpnLocalAddress = amnezia::protocols::xray::defaultLocalAddr; m_vpnLocalAddress = amnezia::protocols::xray::defaultLocalAddr;
m_t2sProcess = IpcClient::InterfaceTun2Socks(); m_t2sProcess = IpcClient::InterfaceTun2Socks();
@@ -25,163 +27,102 @@ XrayProtocol::~XrayProtocol()
ErrorCode XrayProtocol::start() ErrorCode XrayProtocol::start()
{ {
qDebug().noquote() << "XrayProtocol xrayExecPath():" << xrayExecPath(); qDebug() << "XrayProtocol::start()";
if (!QFileInfo::exists(xrayExecPath())) { const ErrorCode err = IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
setLastError(ErrorCode::XrayExecutableMissing); iface->xrayStart(QJsonDocument(m_xrayConfig).toJson());
return lastError(); return ErrorCode::NoError;
} }, [] () {
return ErrorCode::AmneziaServiceConnectionFailed;
#ifdef QT_DEBUG
m_xrayCfgFile.setAutoRemove(false);
#endif
m_xrayCfgFile.open();
QString config = QJsonDocument(m_xrayConfig).toJson();
config.replace(m_remoteHost, m_remoteAddress);
m_xrayCfgFile.write(config.toUtf8());
m_xrayCfgFile.close();
QStringList args = QStringList() << "-c" << m_xrayCfgFile.fileName() << "-format=json";
qDebug().noquote() << "XrayProtocol::start()" << xrayExecPath() << args.join(" ");
m_xrayProcess.setProcessChannelMode(QProcess::MergedChannels);
m_xrayProcess.setProgram(xrayExecPath());
if (Utils::processIsRunning(Utils::executable("xray", false))) {
qDebug().noquote() << "kill previos xray";
Utils::killProcessByName(Utils::executable("xray", false));
}
m_xrayProcess.setArguments(args);
connect(&m_xrayProcess, &QProcess::readyReadStandardOutput, this, [this]() {
#ifdef QT_DEBUG
qDebug().noquote() << "xray:" << m_xrayProcess.readAllStandardOutput();
#endif
}); });
if (err != ErrorCode::NoError)
return err;
connect(&m_xrayProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this,
[this](int exitCode, QProcess::ExitStatus exitStatus) {
qDebug().noquote() << "XrayProtocol finished, exitCode, exitStatus" << exitCode << exitStatus;
setConnectionState(Vpn::ConnectionState::Disconnected);
if ((exitStatus != QProcess::NormalExit) || (exitCode != 0)) {
emit protocolError(amnezia::ErrorCode::XrayExecutableCrashed);
emit setConnectionState(Vpn::ConnectionState::Error);
}
});
m_xrayProcess.start();
m_xrayProcess.waitForStarted();
if (m_xrayProcess.state() == QProcess::ProcessState::Running) {
setConnectionState(Vpn::ConnectionState::Connecting); setConnectionState(Vpn::ConnectionState::Connecting);
QThread::msleep(1000);
return startTun2Sock(); return startTun2Sock();
} else
return ErrorCode::XrayExecutableMissing;
} }
ErrorCode XrayProtocol::startTun2Sock() ErrorCode XrayProtocol::startTun2Sock()
{ {
m_t2sProcess->start(); m_t2sProcess->start();
#ifdef Q_OS_WIN
m_configData.insert("inetAdapterIndex", NetworkUtilities::AdapterIndexTo(QHostAddress(m_remoteAddress)));
#endif
connect(m_t2sProcess.data(), &IpcProcessTun2SocksReplica::stateChanged, this, connect(m_t2sProcess.data(), &IpcProcessTun2SocksReplica::stateChanged, this,
[&](QProcess::ProcessState newState) { qDebug() << "PrivilegedProcess stateChanged" << newState; }); [&](QProcess::ProcessState newState) { qDebug() << "PrivilegedProcess stateChanged" << newState; });
connect(m_t2sProcess.data(), &IpcProcessTun2SocksReplica::setConnectionState, this, [&](int vpnState) { connect(m_t2sProcess.data(), &IpcProcessTun2SocksReplica::setConnectionState, this, [&](int vpnState) {
qDebug() << "PrivilegedProcess setConnectionState " << vpnState; qDebug() << "PrivilegedProcess setConnectionState " << vpnState;
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
if (vpnState == Vpn::ConnectionState::Connected) { if (vpnState == Vpn::ConnectionState::Connected) {
setConnectionState(Vpn::ConnectionState::Connecting); setConnectionState(Vpn::ConnectionState::Connecting);
QList<QHostAddress> dnsAddr; QList<QHostAddress> dnsAddr;
dnsAddr.push_back(QHostAddress(m_configData.value(config_key::dns1).toString())); dnsAddr.push_back(QHostAddress(m_primaryDNS));
// We don't use secondary DNS if primary DNS is AmneziaDNS // We don't use secondary DNS if primary DNS is AmneziaDNS
if (!m_configData.value(amnezia::config_key::dns1).toString(). if (!m_primaryDNS.contains(amnezia::protocols::dns::amneziaDnsIp)) {
contains(amnezia::protocols::dns::amneziaDnsIp)) { dnsAddr.push_back(QHostAddress(m_secondaryDNS));
dnsAddr.push_back(QHostAddress(m_configData.value(config_key::dns2).toString()));
} }
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
QThread::msleep(8000); QThread::msleep(8000);
#endif #endif
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
QThread::msleep(5000); QThread::msleep(5000);
IpcClient::Interface()->createTun("utun22", amnezia::protocols::xray::defaultLocalAddr); iface->createTun("utun22", amnezia::protocols::xray::defaultLocalAddr);
IpcClient::Interface()->updateResolvers("utun22", dnsAddr); iface->updateResolvers("utun22", dnsAddr);
#endif #endif
#ifdef Q_OS_LINUX #ifdef Q_OS_LINUX
QThread::msleep(1000); QThread::msleep(1000);
IpcClient::Interface()->createTun("tun2", amnezia::protocols::xray::defaultLocalAddr); iface->createTun("tun2", amnezia::protocols::xray::defaultLocalAddr);
IpcClient::Interface()->updateResolvers("tun2", dnsAddr); iface->updateResolvers("tun2", dnsAddr);
#endif
#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
// killSwitch toggle
if (QVariant(m_configData.value(config_key::killSwitchOption).toString()).toBool()) {
m_configData.insert("vpnServer", m_remoteAddress);
IpcClient::Interface()->enableKillSwitch(m_configData, 0);
}
#endif #endif
if (m_routeMode == Settings::RouteMode::VpnAllSites) { if (m_routeMode == Settings::RouteMode::VpnAllSites) {
IpcClient::Interface()->routeAddList(m_vpnGateway, QStringList() << "0.0.0.0/1"); iface->routeAddList(m_vpnGateway, QStringList() << "1.0.0.0/8" << "2.0.0.0/7" << "4.0.0.0/6" << "8.0.0.0/5" << "16.0.0.0/4" << "32.0.0.0/3" << "64.0.0.0/2" << "128.0.0.0/1");
IpcClient::Interface()->routeAddList(m_vpnGateway, QStringList() << "128.0.0.0/1");
IpcClient::Interface()->routeAddList(m_routeGateway, QStringList() << m_remoteAddress);
} }
IpcClient::Interface()->StopRoutingIpv6(); iface->StopRoutingIpv6();
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
IpcClient::Interface()->updateResolvers("tun2", dnsAddr); iface->updateResolvers("tun2", dnsAddr);
QList<QNetworkInterface> netInterfaces = QNetworkInterface::allInterfaces();
for (int i = 0; i < netInterfaces.size(); i++) {
for (int j = 0; j < netInterfaces.at(i).addressEntries().size(); j++) {
// killSwitch toggle
if (m_vpnLocalAddress == netInterfaces.at(i).addressEntries().at(j).ip().toString()) {
if (QVariant(m_configData.value(config_key::killSwitchOption).toString()).toBool()) {
IpcClient::Interface()->enableKillSwitch(m_configData, netInterfaces.at(i).index());
}
m_configData.insert("vpnAdapterIndex", netInterfaces.at(i).index());
m_configData.insert("vpnGateway", m_vpnGateway);
m_configData.insert("vpnServer", m_remoteAddress);
IpcClient::Interface()->enablePeerTraffic(m_configData);
}
}
}
#endif #endif
setConnectionState(Vpn::ConnectionState::Connected); setConnectionState(Vpn::ConnectionState::Connected);
} }
#if !defined(Q_OS_MACOS) #if !defined(Q_OS_MACOS)
if (vpnState == Vpn::ConnectionState::Disconnected) { if (vpnState == Vpn::ConnectionState::Disconnected) {
setConnectionState(Vpn::ConnectionState::Disconnected); setConnectionState(Vpn::ConnectionState::Disconnected);
IpcClient::Interface()->deleteTun("tun2"); iface->deleteTun("tun2");
IpcClient::Interface()->StartRoutingIpv6(); iface->StartRoutingIpv6();
IpcClient::Interface()->clearSavedRoutes(); iface->clearSavedRoutes();
} }
#endif #endif
}); });
});
return ErrorCode::NoError; return ErrorCode::NoError;
} }
void XrayProtocol::stop() void XrayProtocol::stop()
{ {
#ifdef AMNEZIA_DESKTOP
QRemoteObjectPendingReply<bool> disableKillSwitchResp = IpcClient::Interface()->disableKillSwitch();
disableKillSwitchResp.waitForFinished(1000);
QRemoteObjectPendingReply<bool> StartRoutingIpv6Resp = IpcClient::Interface()->StartRoutingIpv6();
StartRoutingIpv6Resp.waitForFinished(1000);
QRemoteObjectPendingReply<bool> restoreResolvers = IpcClient::Interface()->restoreResolvers();
restoreResolvers.waitForFinished(1000);
#if !defined(Q_OS_MACOS)
QRemoteObjectPendingReply<bool> deleteTunResp = IpcClient::Interface()->deleteTun("tun2");
deleteTunResp.waitForFinished(1000);
#endif
#endif
qDebug() << "XrayProtocol::stop()"; qDebug() << "XrayProtocol::stop()";
m_xrayProcess.disconnect();
m_xrayProcess.kill(); IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
m_xrayProcess.waitForFinished(3000); #ifdef AMNEZIA_DESKTOP
QRemoteObjectPendingReply<bool> StartRoutingIpv6Resp = iface->StartRoutingIpv6();
if (!StartRoutingIpv6Resp.waitForFinished(1000)) {
qWarning() << "XrayProtocol::stop(): Failed to start routing ipv6";
}
QRemoteObjectPendingReply<bool> restoreResolvers = iface->restoreResolvers();
if (!restoreResolvers.waitForFinished(1000)) {
qWarning() << "XrayProtocol::stop(): Failed to restore resolvers";
}
#if !defined(Q_OS_MACOS)
QRemoteObjectPendingReply<bool> deleteTunResp = iface->deleteTun("tun2");
if (!deleteTunResp.waitForFinished(1000)) {
qWarning() << "XrayProtocol::stop(): Failed to delete tun";
}
#endif
#endif
iface->xrayStop();
});
if (m_t2sProcess) { if (m_t2sProcess) {
m_t2sProcess->stop(); m_t2sProcess->stop();
QThread::msleep(200); QThread::msleep(200);
@@ -190,26 +131,13 @@ void XrayProtocol::stop()
setConnectionState(Vpn::ConnectionState::Disconnected); setConnectionState(Vpn::ConnectionState::Disconnected);
} }
QString XrayProtocol::xrayExecPath()
{
#ifdef Q_OS_WIN
return Utils::executable(QString("xray/xray"), true);
#else
return Utils::executable(QString("xray"), true);
#endif
}
void XrayProtocol::readXrayConfiguration(const QJsonObject &configuration) void XrayProtocol::readXrayConfiguration(const QJsonObject &configuration)
{ {
m_configData = configuration;
QJsonObject xrayConfiguration = configuration.value(ProtocolProps::key_proto_config_data(Proto::Xray)).toObject(); QJsonObject xrayConfiguration = configuration.value(ProtocolProps::key_proto_config_data(Proto::Xray)).toObject();
if (xrayConfiguration.isEmpty()) { if (xrayConfiguration.isEmpty()) {
xrayConfiguration = configuration.value(ProtocolProps::key_proto_config_data(Proto::SSXray)).toObject(); xrayConfiguration = configuration.value(ProtocolProps::key_proto_config_data(Proto::SSXray)).toObject();
} }
m_xrayConfig = xrayConfiguration; m_xrayConfig = xrayConfiguration;
m_localPort = QString(amnezia::protocols::xray::defaultLocalProxyPort).toInt();
m_remoteHost = configuration.value(amnezia::config_key::hostName).toString();
m_remoteAddress = NetworkUtilities::getIPAddress(m_remoteHost);
m_routeMode = static_cast<Settings::RouteMode>(configuration.value(amnezia::config_key::splitTunnelType).toInt()); m_routeMode = static_cast<Settings::RouteMode>(configuration.value(amnezia::config_key::splitTunnelType).toInt());
m_primaryDNS = configuration.value(amnezia::config_key::dns1).toString(); m_primaryDNS = configuration.value(amnezia::config_key::dns1).toString();
m_secondaryDNS = configuration.value(amnezia::config_key::dns2).toString(); m_secondaryDNS = configuration.value(amnezia::config_key::dns2).toString();
+3 -16
View File
@@ -3,8 +3,8 @@
#include "QProcess" #include "QProcess"
#include "containers/containers_defs.h" #include "core/ipcclient.h"
#include "openvpnprotocol.h" #include "vpnprotocol.h"
#include "settings.h" #include "settings.h"
class XrayProtocol : public VpnProtocol class XrayProtocol : public VpnProtocol
@@ -17,29 +17,16 @@ public:
ErrorCode startTun2Sock(); ErrorCode startTun2Sock();
void stop() override; void stop() override;
protected: private:
void readXrayConfiguration(const QJsonObject &configuration); void readXrayConfiguration(const QJsonObject &configuration);
protected:
QJsonObject m_xrayConfig; QJsonObject m_xrayConfig;
private:
static QString xrayExecPath();
static QString tun2SocksExecPath();
private:
int m_localPort;
QString m_remoteHost;
QString m_remoteAddress;
Settings::RouteMode m_routeMode; Settings::RouteMode m_routeMode;
QJsonObject m_configData;
QString m_primaryDNS; QString m_primaryDNS;
QString m_secondaryDNS; QString m_secondaryDNS;
#ifndef Q_OS_IOS #ifndef Q_OS_IOS
QProcess m_xrayProcess;
QSharedPointer<IpcProcessTun2SocksReplica> m_t2sProcess; QSharedPointer<IpcProcessTun2SocksReplica> m_t2sProcess;
#endif #endif
QTemporaryFile m_xrayCfgFile;
}; };
#endif // XRAYPROTOCOL_H #endif // XRAYPROTOCOL_H
+5 -3
View File
@@ -68,6 +68,11 @@
<file>server_scripts/awg/run_container.sh</file> <file>server_scripts/awg/run_container.sh</file>
<file>server_scripts/awg/start.sh</file> <file>server_scripts/awg/start.sh</file>
<file>server_scripts/awg/template.conf</file> <file>server_scripts/awg/template.conf</file>
<file>server_scripts/awg_legacy/configure_container.sh</file>
<file>server_scripts/awg_legacy/Dockerfile</file>
<file>server_scripts/awg_legacy/run_container.sh</file>
<file>server_scripts/awg_legacy/start.sh</file>
<file>server_scripts/awg_legacy/template.conf</file>
<file>server_scripts/build_container.sh</file> <file>server_scripts/build_container.sh</file>
<file>server_scripts/check_connection.sh</file> <file>server_scripts/check_connection.sh</file>
<file>server_scripts/check_server_is_busy.sh</file> <file>server_scripts/check_server_is_busy.sh</file>
@@ -242,9 +247,6 @@
<file>ui/qml/Pages2/PageSettingsApiNativeConfigs.qml</file> <file>ui/qml/Pages2/PageSettingsApiNativeConfigs.qml</file>
<file>ui/qml/Pages2/PageSettingsApiDevices.qml</file> <file>ui/qml/Pages2/PageSettingsApiDevices.qml</file>
<file>images/controls/monitor.svg</file> <file>images/controls/monitor.svg</file>
<file>ui/qml/Components/ApiPremV1MigrationDrawer.qml</file>
<file>ui/qml/Components/ApiPremV1SubListDrawer.qml</file>
<file>ui/qml/Components/OtpCodeDrawer.qml</file>
<file>ui/qml/Components/AwgTextField.qml</file> <file>ui/qml/Components/AwgTextField.qml</file>
<file>ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml</file> <file>ui/qml/Pages2/PageSettingsApiSubscriptionKey.qml</file>
<file>ui/qml/Components/SmartScroll.qml</file> <file>ui/qml/Components/SmartScroll.qml</file>
+18 -9
View File
@@ -95,13 +95,17 @@ void SecureQSettings::setValue(const QString &key, const QVariant &value)
ds << value; ds << value;
} }
QByteArray encryptedValue = encryptText(decryptedValue); const auto encryptedValue = encryptText(decryptedValue);
m_settings.setValue(key, magicString + encryptedValue); if (encryptedValue.has_value()) {
m_settings.setValue(key, magicString + *encryptedValue);
} else { } else {
qCritical() << "SecureQSettings::setValue Encryption required, but key is empty"; qCritical() << "SecureQSettings::setValue encryption failed, plaintext fallback";
return; m_settings.setValue(key, value);
}
} else {
qCritical() << "SecureQSettings::setValue Encryption required, but key is empty. plaintext fallback";
m_settings.setValue(key, value);
} }
} else { } else {
m_settings.setValue(key, value); m_settings.setValue(key, value);
} }
@@ -177,16 +181,21 @@ bool SecureQSettings::restoreAppConfig(const QByteArray &json)
return true; return true;
} }
QByteArray SecureQSettings::encryptText(const QByteArray &value) const std::optional<QByteArray> SecureQSettings::encryptText(const QByteArray &value) const
{ {
QSimpleCrypto::QBlockCipher cipher; QSimpleCrypto::QBlockCipher cipher;
QByteArray result; QByteArray encrypted;
try { try {
result = cipher.encryptAesBlockCipher(value, getEncKey(), getEncIv()); encrypted = cipher.encryptAesBlockCipher(value, getEncKey(), getEncIv());
} catch (...) { // todo change error handling in QSimpleCrypto? } catch (...) { // todo change error handling in QSimpleCrypto?
qCritical() << "error when encrypting the settings value"; qCritical() << "error when encrypting the settings value";
return std::nullopt;
} }
return result; if (encrypted.isEmpty() && !value.isEmpty()) {
qCritical() << "error when encrypting the settings value: empty result";
return std::nullopt;
}
return encrypted;
} }
QByteArray SecureQSettings::decryptText(const QByteArray &ba) const QByteArray SecureQSettings::decryptText(const QByteArray &ba) const
+2 -1
View File
@@ -5,6 +5,7 @@
#include <QMutexLocker> #include <QMutexLocker>
#include <QObject> #include <QObject>
#include <QSettings> #include <QSettings>
#include <optional>
#include "../client/3rd/qtkeychain/qtkeychain/keychain.h" #include "../client/3rd/qtkeychain/qtkeychain/keychain.h"
@@ -24,7 +25,7 @@ public:
QByteArray backupAppConfig() const; QByteArray backupAppConfig() const;
bool restoreAppConfig(const QByteArray &json); bool restoreAppConfig(const QByteArray &json);
QByteArray encryptText(const QByteArray &value) const; std::optional<QByteArray> encryptText(const QByteArray &value) const;
QByteArray decryptText(const QByteArray &ba) const; QByteArray decryptText(const QByteArray &ba) const;
bool encryptionRequired() const; bool encryptionRequired() const;
+1 -1
View File
@@ -1,4 +1,4 @@
FROM amneziavpn/amnezia-wg:latest FROM amneziavpn/amneziawg-go:latest
LABEL maintainer="AmneziaVPN" LABEL maintainer="AmneziaVPN"
@@ -1,15 +1,15 @@
mkdir -p /opt/amnezia/awg mkdir -p /opt/amnezia/awg
cd /opt/amnezia/awg cd /opt/amnezia/awg
WIREGUARD_SERVER_PRIVATE_KEY=$(wg genkey) WIREGUARD_SERVER_PRIVATE_KEY=$(awg genkey)
echo $WIREGUARD_SERVER_PRIVATE_KEY > /opt/amnezia/awg/wireguard_server_private_key.key echo $WIREGUARD_SERVER_PRIVATE_KEY > /opt/amnezia/awg/wireguard_server_private_key.key
WIREGUARD_SERVER_PUBLIC_KEY=$(echo $WIREGUARD_SERVER_PRIVATE_KEY | wg pubkey) WIREGUARD_SERVER_PUBLIC_KEY=$(echo $WIREGUARD_SERVER_PRIVATE_KEY | awg pubkey)
echo $WIREGUARD_SERVER_PUBLIC_KEY > /opt/amnezia/awg/wireguard_server_public_key.key echo $WIREGUARD_SERVER_PUBLIC_KEY > /opt/amnezia/awg/wireguard_server_public_key.key
WIREGUARD_PSK=$(wg genpsk) WIREGUARD_PSK=$(awg genpsk)
echo $WIREGUARD_PSK > /opt/amnezia/awg/wireguard_psk.key echo $WIREGUARD_PSK > /opt/amnezia/awg/wireguard_psk.key
cat > /opt/amnezia/awg/wg0.conf <<EOF cat > /opt/amnezia/awg/awg0.conf <<EOF
[Interface] [Interface]
PrivateKey = $WIREGUARD_SERVER_PRIVATE_KEY PrivateKey = $WIREGUARD_SERVER_PRIVATE_KEY
Address = $AWG_SUBNET_IP/$WIREGUARD_SUBNET_CIDR Address = $AWG_SUBNET_IP/$WIREGUARD_SUBNET_CIDR
@@ -19,9 +19,15 @@ Jmin = $JUNK_PACKET_MIN_SIZE
Jmax = $JUNK_PACKET_MAX_SIZE Jmax = $JUNK_PACKET_MAX_SIZE
S1 = $INIT_PACKET_JUNK_SIZE S1 = $INIT_PACKET_JUNK_SIZE
S2 = $RESPONSE_PACKET_JUNK_SIZE S2 = $RESPONSE_PACKET_JUNK_SIZE
S3 = $COOKIE_REPLY_PACKET_JUNK_SIZE
S4 = $TRANSPORT_PACKET_JUNK_SIZE
H1 = $INIT_PACKET_MAGIC_HEADER H1 = $INIT_PACKET_MAGIC_HEADER
H2 = $RESPONSE_PACKET_MAGIC_HEADER H2 = $RESPONSE_PACKET_MAGIC_HEADER
H3 = $UNDERLOAD_PACKET_MAGIC_HEADER H3 = $UNDERLOAD_PACKET_MAGIC_HEADER
H4 = $TRANSPORT_PACKET_MAGIC_HEADER H4 = $TRANSPORT_PACKET_MAGIC_HEADER
# I1 = $SPECIAL_JUNK_1
# I2 = $SPECIAL_JUNK_2
# I3 = $SPECIAL_JUNK_3
# I4 = $SPECIAL_JUNK_4
# I5 = $SPECIAL_JUNK_5
EOF EOF
+7 -7
View File
@@ -6,19 +6,19 @@ echo "Container startup"
#ifconfig eth0:0 $SERVER_IP_ADDRESS netmask 255.255.255.255 up #ifconfig eth0:0 $SERVER_IP_ADDRESS netmask 255.255.255.255 up
# kill daemons in case of restart # kill daemons in case of restart
wg-quick down /opt/amnezia/awg/wg0.conf awg-quick down /opt/amnezia/awg/awg0.conf
# start daemons if configured # start daemons if configured
if [ -f /opt/amnezia/awg/wg0.conf ]; then (wg-quick up /opt/amnezia/awg/wg0.conf); fi if [ -f /opt/amnezia/awg/awg0.conf ]; then (awg-quick up /opt/amnezia/awg/awg0.conf); fi
# Allow traffic on the TUN interface. # Allow traffic on the TUN interface.
iptables -A INPUT -i wg0 -j ACCEPT iptables -A INPUT -i awg0 -j ACCEPT
iptables -A FORWARD -i wg0 -j ACCEPT iptables -A FORWARD -i awg0 -j ACCEPT
iptables -A OUTPUT -o wg0 -j ACCEPT iptables -A OUTPUT -o awg0 -j ACCEPT
# Allow forwarding traffic only from the VPN. # Allow forwarding traffic only from the VPN.
iptables -A FORWARD -i wg0 -o eth0 -s $AWG_SUBNET_IP/$WIREGUARD_SUBNET_CIDR -j ACCEPT iptables -A FORWARD -i awg0 -o eth0 -s $AWG_SUBNET_IP/$WIREGUARD_SUBNET_CIDR -j ACCEPT
iptables -A FORWARD -i wg0 -o eth1 -s $AWG_SUBNET_IP/$WIREGUARD_SUBNET_CIDR -j ACCEPT iptables -A FORWARD -i awg0 -o eth1 -s $AWG_SUBNET_IP/$WIREGUARD_SUBNET_CIDR -j ACCEPT
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
+7
View File
@@ -7,10 +7,17 @@ Jmin = $JUNK_PACKET_MIN_SIZE
Jmax = $JUNK_PACKET_MAX_SIZE Jmax = $JUNK_PACKET_MAX_SIZE
S1 = $INIT_PACKET_JUNK_SIZE S1 = $INIT_PACKET_JUNK_SIZE
S2 = $RESPONSE_PACKET_JUNK_SIZE S2 = $RESPONSE_PACKET_JUNK_SIZE
S3 = $COOKIE_REPLY_PACKET_JUNK_SIZE
S4 = $TRANSPORT_PACKET_JUNK_SIZE
H1 = $INIT_PACKET_MAGIC_HEADER H1 = $INIT_PACKET_MAGIC_HEADER
H2 = $RESPONSE_PACKET_MAGIC_HEADER H2 = $RESPONSE_PACKET_MAGIC_HEADER
H3 = $UNDERLOAD_PACKET_MAGIC_HEADER H3 = $UNDERLOAD_PACKET_MAGIC_HEADER
H4 = $TRANSPORT_PACKET_MAGIC_HEADER H4 = $TRANSPORT_PACKET_MAGIC_HEADER
I1 = $SPECIAL_JUNK_1
I2 = $SPECIAL_JUNK_2
I3 = $SPECIAL_JUNK_3
I4 = $SPECIAL_JUNK_4
I5 = $SPECIAL_JUNK_5
[Peer] [Peer]
PublicKey = $WIREGUARD_SERVER_PUBLIC_KEY PublicKey = $WIREGUARD_SERVER_PUBLIC_KEY
@@ -0,0 +1,46 @@
FROM amneziavpn/amnezia-wg:latest
LABEL maintainer="AmneziaVPN"
#Install required packages
RUN apk add --no-cache bash curl dumb-init
RUN apk --update upgrade --no-cache
RUN mkdir -p /opt/amnezia
RUN echo -e "#!/bin/bash\ntail -f /dev/null" > /opt/amnezia/start.sh
RUN chmod a+x /opt/amnezia/start.sh
# Tune network
RUN echo -e " \n\
fs.file-max = 51200 \n\
\n\
net.core.rmem_max = 67108864 \n\
net.core.wmem_max = 67108864 \n\
net.core.netdev_max_backlog = 250000 \n\
net.core.somaxconn = 4096 \n\
\n\
net.ipv4.tcp_syncookies = 1 \n\
net.ipv4.tcp_tw_reuse = 1 \n\
net.ipv4.tcp_tw_recycle = 0 \n\
net.ipv4.tcp_fin_timeout = 30 \n\
net.ipv4.tcp_keepalive_time = 1200 \n\
net.ipv4.ip_local_port_range = 10000 65000 \n\
net.ipv4.tcp_max_syn_backlog = 8192 \n\
net.ipv4.tcp_max_tw_buckets = 5000 \n\
net.ipv4.tcp_fastopen = 3 \n\
net.ipv4.tcp_mem = 25600 51200 102400 \n\
net.ipv4.tcp_rmem = 4096 87380 67108864 \n\
net.ipv4.tcp_wmem = 4096 65536 67108864 \n\
net.ipv4.tcp_mtu_probing = 1 \n\
net.ipv4.tcp_congestion_control = hybla \n\
# for low-latency network, use cubic instead \n\
# net.ipv4.tcp_congestion_control = cubic \n\
" | sed -e 's/^\s\+//g' | tee -a /etc/sysctl.conf && \
mkdir -p /etc/security && \
echo -e " \n\
* soft nofile 51200 \n\
* hard nofile 51200 \n\
" | sed -e 's/^\s\+//g' | tee -a /etc/security/limits.conf
ENTRYPOINT [ "dumb-init", "/opt/amnezia/start.sh" ]
CMD [ "" ]
@@ -0,0 +1,31 @@
mkdir -p /opt/amnezia/awg
cd /opt/amnezia/awg
WIREGUARD_SERVER_PRIVATE_KEY=$(wg genkey)
echo $WIREGUARD_SERVER_PRIVATE_KEY > /opt/amnezia/awg/wireguard_server_private_key.key
WIREGUARD_SERVER_PUBLIC_KEY=$(echo $WIREGUARD_SERVER_PRIVATE_KEY | wg pubkey)
echo $WIREGUARD_SERVER_PUBLIC_KEY > /opt/amnezia/awg/wireguard_server_public_key.key
WIREGUARD_PSK=$(wg genpsk)
echo $WIREGUARD_PSK > /opt/amnezia/awg/wireguard_psk.key
cat > /opt/amnezia/awg/wg0.conf <<EOF
[Interface]
PrivateKey = $WIREGUARD_SERVER_PRIVATE_KEY
Address = $AWG_SUBNET_IP/$WIREGUARD_SUBNET_CIDR
ListenPort = $AWG_SERVER_PORT
Jc = $JUNK_PACKET_COUNT
Jmin = $JUNK_PACKET_MIN_SIZE
Jmax = $JUNK_PACKET_MAX_SIZE
S1 = $INIT_PACKET_JUNK_SIZE
S2 = $RESPONSE_PACKET_JUNK_SIZE
H1 = $INIT_PACKET_MAGIC_HEADER
H2 = $RESPONSE_PACKET_MAGIC_HEADER
H3 = $UNDERLOAD_PACKET_MAGIC_HEADER
H4 = $TRANSPORT_PACKET_MAGIC_HEADER
# I1 = $SPECIAL_JUNK_1
# I2 = $SPECIAL_JUNK_2
# I3 = $SPECIAL_JUNK_3
# I4 = $SPECIAL_JUNK_4
# I5 = $SPECIAL_JUNK_5
EOF
@@ -0,0 +1,18 @@
# Run container
sudo docker run -d \
--log-driver none \
--restart always \
--privileged \
--cap-add=NET_ADMIN \
--cap-add=SYS_MODULE \
-p $AWG_SERVER_PORT:$AWG_SERVER_PORT/udp \
-v /lib/modules:/lib/modules \
--sysctl="net.ipv4.conf.all.src_valid_mark=1" \
--name $CONTAINER_NAME \
$CONTAINER_NAME
sudo docker network connect amnezia-dns-net $CONTAINER_NAME
# Prevent to route packets outside of the container in case if server behind of the NAT
#sudo docker exec -i $CONTAINER_NAME sh -c "ifconfig eth0:0 $SERVER_IP_ADDRESS netmask 255.255.255.255 up"
+28
View File
@@ -0,0 +1,28 @@
#!/bin/bash
# This scripts copied from Amnezia client to Docker container to /opt/amnezia and launched every time container starts
echo "Container startup"
#ifconfig eth0:0 $SERVER_IP_ADDRESS netmask 255.255.255.255 up
# kill daemons in case of restart
wg-quick down /opt/amnezia/awg/wg0.conf
# start daemons if configured
if [ -f /opt/amnezia/awg/wg0.conf ]; then (wg-quick up /opt/amnezia/awg/wg0.conf); fi
# Allow traffic on the TUN interface.
iptables -A INPUT -i wg0 -j ACCEPT
iptables -A FORWARD -i wg0 -j ACCEPT
iptables -A OUTPUT -o wg0 -j ACCEPT
# Allow forwarding traffic only from the VPN.
iptables -A FORWARD -i wg0 -o eth0 -s $AWG_SUBNET_IP/$WIREGUARD_SUBNET_CIDR -j ACCEPT
iptables -A FORWARD -i wg0 -o eth1 -s $AWG_SUBNET_IP/$WIREGUARD_SUBNET_CIDR -j ACCEPT
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -t nat -A POSTROUTING -s $AWG_SUBNET_IP/$WIREGUARD_SUBNET_CIDR -o eth0 -j MASQUERADE
iptables -t nat -A POSTROUTING -s $AWG_SUBNET_IP/$WIREGUARD_SUBNET_CIDR -o eth1 -j MASQUERADE
tail -f /dev/null
@@ -0,0 +1,25 @@
[Interface]
Address = $WIREGUARD_CLIENT_IP/32
DNS = $PRIMARY_DNS, $SECONDARY_DNS
PrivateKey = $WIREGUARD_CLIENT_PRIVATE_KEY
Jc = $JUNK_PACKET_COUNT
Jmin = $JUNK_PACKET_MIN_SIZE
Jmax = $JUNK_PACKET_MAX_SIZE
S1 = $INIT_PACKET_JUNK_SIZE
S2 = $RESPONSE_PACKET_JUNK_SIZE
H1 = $INIT_PACKET_MAGIC_HEADER
H2 = $RESPONSE_PACKET_MAGIC_HEADER
H3 = $UNDERLOAD_PACKET_MAGIC_HEADER
H4 = $TRANSPORT_PACKET_MAGIC_HEADER
I1 = $SPECIAL_JUNK_1
I2 = $SPECIAL_JUNK_2
I3 = $SPECIAL_JUNK_3
I4 = $SPECIAL_JUNK_4
I5 = $SPECIAL_JUNK_5
[Peer]
PublicKey = $WIREGUARD_SERVER_PUBLIC_KEY
PresharedKey = $WIREGUARD_PSK
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = $SERVER_IP_ADDRESS:$AWG_SERVER_PORT
PersistentKeepalive = 25
@@ -1,5 +1,5 @@
sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker stop;\ sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker stop;\
sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker rm -fv;\ sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker rm -fv;\
sudo docker images -a | grep amnezia | awk '{print $3}' | xargs sudo docker rmi;\ sudo docker images -a --format table | grep amnezia | awk '{print $3}' | xargs sudo docker rmi;\
sudo docker network ls | grep amnezia-dns-net | awk '{print $1}' | xargs sudo docker network rm;\ sudo docker network ls | grep amnezia-dns-net | awk '{print $1}' | xargs sudo docker network rm;\
sudo rm -frd /opt/amnezia sudo rm -frd /opt/amnezia
+4 -4
View File
@@ -534,14 +534,14 @@ void Settings::setDevGatewayEndpoint()
m_gatewayEndpoint = DEV_AGW_ENDPOINT; m_gatewayEndpoint = DEV_AGW_ENDPOINT;
} }
QString Settings::getGatewayEndpoint() QString Settings::getGatewayEndpoint(bool isTestPurchase)
{ {
return m_gatewayEndpoint; return isTestPurchase ? DEV_AGW_ENDPOINT : m_gatewayEndpoint;
} }
bool Settings::isDevGatewayEnv() bool Settings::isDevGatewayEnv(bool isTestPurchase)
{ {
return value("Conf/devGatewayEnv", false).toBool(); return isTestPurchase ? true : value("Conf/devGatewayEnv", false).toBool();
} }
void Settings::toggleDevGatewayEnv(bool enabled) void Settings::toggleDevGatewayEnv(bool enabled)
+2 -2
View File
@@ -223,8 +223,8 @@ public:
void resetGatewayEndpoint(); void resetGatewayEndpoint();
void setGatewayEndpoint(const QString &endpoint); void setGatewayEndpoint(const QString &endpoint);
void setDevGatewayEndpoint(); void setDevGatewayEndpoint();
QString getGatewayEndpoint(); QString getGatewayEndpoint(bool isTestPurchase = false);
bool isDevGatewayEnv(); bool isDevGatewayEnv(bool isTestPurchase = false);
void toggleDevGatewayEnv(bool enabled); void toggleDevGatewayEnv(bool enabled);
bool isHomeAdLabelVisible(); bool isHomeAdLabelVisible();
+5
View File
@@ -2849,6 +2849,11 @@ Already installed containers were found on the server. All installed containers
<source>Site Amnezia</source> <source>Site Amnezia</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="229"/>
<source>Restore purchases</source>
<translation>استعادة المشتريات</translation>
</message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="251"/> <location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="251"/>
<source>VPN by Amnezia</source> <source>VPN by Amnezia</source>
+5
View File
@@ -2973,6 +2973,11 @@ It&apos;s okay as long as it&apos;s from someone you trust.</source>
<source>Site Amnezia</source> <source>Site Amnezia</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="229"/>
<source>Restore purchases</source>
<translation>بازیابی خریدها</translation>
</message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="251"/> <location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="251"/>
<source>VPN by Amnezia</source> <source>VPN by Amnezia</source>
+5
View File
@@ -2865,6 +2865,11 @@ Already installed containers were found on the server. All installed containers
<source>Site Amnezia</source> <source>Site Amnezia</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="229"/>
<source>Restore purchases</source>
<translation> ि </translation>
</message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="251"/> <location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="251"/>
<source>VPN by Amnezia</source> <source>VPN by Amnezia</source>
+5
View File
@@ -2867,6 +2867,11 @@ Already installed containers were found on the server. All installed containers
<source>Site Amnezia</source> <source>Site Amnezia</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="229"/>
<source>Restore purchases</source>
<translation>က </translation>
</message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="251"/> <location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="251"/>
<source>VPN by Amnezia</source> <source>VPN by Amnezia</source>
File diff suppressed because it is too large Load Diff
+5
View File
@@ -3131,6 +3131,11 @@ It&apos;s okay as long as it&apos;s from someone you trust.</source>
<source>Site Amnezia</source> <source>Site Amnezia</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="229"/>
<source>Restore purchases</source>
<translation>Відновити покупки</translation>
</message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="251"/> <location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="251"/>
<source>VPN by Amnezia</source> <source>VPN by Amnezia</source>
+5
View File
@@ -2857,6 +2857,11 @@ Already installed containers were found on the server. All installed containers
<source>Site Amnezia</source> <source>Site Amnezia</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="229"/>
<source>Restore purchases</source>
<translation>خریداری بحال کریں</translation>
</message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="251"/> <location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="251"/>
<source>VPN by Amnezia</source> <source>VPN by Amnezia</source>
+5
View File
@@ -3008,6 +3008,11 @@ It&apos;s okay as long as it&apos;s from someone you trust.</source>
<source>Site Amnezia</source> <source>Site Amnezia</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="229"/>
<source>Restore purchases</source>
<translation></translation>
</message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="251"/> <location filename="../ui/qml/Pages2/PageSetupWizardConfigSource.qml" line="251"/>
<source>VPN by Amnezia</source> <source>VPN by Amnezia</source>
@@ -1,8 +1,5 @@
#include "apiConfigsController.h" #include "apiConfigsController.h"
#include <QClipboard>
#include <QEventLoop>
#include "amnezia_application.h" #include "amnezia_application.h"
#include "configurators/wireguard_configurator.h" #include "configurators/wireguard_configurator.h"
#include "core/api/apiDefs.h" #include "core/api/apiDefs.h"
@@ -11,6 +8,12 @@
#include "core/qrCodeUtils.h" #include "core/qrCodeUtils.h"
#include "ui/controllers/systemController.h" #include "ui/controllers/systemController.h"
#include "version.h" #include "version.h"
#include <QClipboard>
#include <QDebug>
#include <QEventLoop>
#include <QSet>
#include "platforms/ios/ios_controller.h"
namespace namespace
{ {
@@ -50,6 +53,12 @@ namespace
constexpr char isConnectEvent[] = "is_connect_event"; constexpr char isConnectEvent[] = "is_connect_event";
} }
namespace serviceType
{
constexpr char amneziaFree[] = "amnezia-free";
constexpr char amneziaPremium[] = "amnezia-premium";
}
struct ProtocolData struct ProtocolData
{ {
OpenVpnConfigurator::ConnectionData certRequest; OpenVpnConfigurator::ConnectionData certRequest;
@@ -166,9 +175,10 @@ namespace
qDebug() << "missing containers field"; qDebug() << "missing containers field";
return ErrorCode::ApiConfigEmptyError; return ErrorCode::ApiConfigEmptyError;
} }
auto container = containers.at(0).toObject(); auto containerObject = containers.at(0).toObject();
QString containerName = ContainerProps::containerTypeToString(DockerContainer::Awg); auto containerType = ContainerProps::containerFromString(containerObject.value(config_key::container).toString());
auto serverProtocolConfig = container.value(containerName).toObject(); QString containerName = ContainerProps::containerTypeToString(containerType);
auto serverProtocolConfig = containerObject.value(containerName).toObject();
auto clientProtocolConfig = auto clientProtocolConfig =
QJsonDocument::fromJson(serverProtocolConfig.value(config_key::last_config).toString().toUtf8()).object(); QJsonDocument::fromJson(serverProtocolConfig.value(config_key::last_config).toString().toUtf8()).object();
@@ -191,15 +201,11 @@ namespace
serverProtocolConfig[config_key::specialJunk3] = clientProtocolConfig.value(config_key::specialJunk3); serverProtocolConfig[config_key::specialJunk3] = clientProtocolConfig.value(config_key::specialJunk3);
serverProtocolConfig[config_key::specialJunk4] = clientProtocolConfig.value(config_key::specialJunk4); serverProtocolConfig[config_key::specialJunk4] = clientProtocolConfig.value(config_key::specialJunk4);
serverProtocolConfig[config_key::specialJunk5] = clientProtocolConfig.value(config_key::specialJunk5); serverProtocolConfig[config_key::specialJunk5] = clientProtocolConfig.value(config_key::specialJunk5);
serverProtocolConfig[config_key::controlledJunk1] = clientProtocolConfig.value(config_key::controlledJunk1);
serverProtocolConfig[config_key::controlledJunk2] = clientProtocolConfig.value(config_key::controlledJunk2);
serverProtocolConfig[config_key::controlledJunk3] = clientProtocolConfig.value(config_key::controlledJunk3);
serverProtocolConfig[config_key::specialHandshakeTimeout] = clientProtocolConfig.value(config_key::specialHandshakeTimeout);
// //
container[containerName] = serverProtocolConfig; containerObject[containerName] = serverProtocolConfig;
containers.replace(0, container); containers.replace(0, containerObject);
newServerConfig[config_key::containers] = containers; newServerConfig[config_key::containers] = containers;
configStr = QString(QJsonDocument(newServerConfig).toJson()); configStr = QString(QJsonDocument(newServerConfig).toJson());
} }
@@ -235,19 +241,6 @@ namespace
return ErrorCode::NoError; return ErrorCode::NoError;
} }
bool isSubscriptionExpired(const QJsonObject &apiConfig)
{
auto subscription = apiConfig.value(configKey::subscription).toObject();
if (subscription.isEmpty()) {
return false;
}
auto subscriptionEndDate = subscription.value(configKey::endDate).toString();
if (apiUtils::isSubscriptionExpired(subscriptionEndDate)) {
return true;
}
return false;
}
} }
ApiConfigsController::ApiConfigsController(const QSharedPointer<ServersModel> &serversModel, ApiConfigsController::ApiConfigsController(const QSharedPointer<ServersModel> &serversModel,
@@ -284,11 +277,6 @@ bool ApiConfigsController::exportNativeConfig(const QString &serverCountryCode,
auto serverConfigObject = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex()); auto serverConfigObject = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex());
auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject(); auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject();
if (isSubscriptionExpired(apiConfigObject)) {
emit errorOccurred(ErrorCode::ApiSubscriptionExpiredError);
return false;
}
GatewayRequestData gatewayRequestData { QSysInfo::productType(), GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION), QString(APP_VERSION),
m_settings->getAppLanguage().name().split("_").first(), m_settings->getAppLanguage().name().split("_").first(),
@@ -304,9 +292,9 @@ bool ApiConfigsController::exportNativeConfig(const QString &serverCountryCode,
QJsonObject apiPayload = gatewayRequestData.toJsonObject(); QJsonObject apiPayload = gatewayRequestData.toJsonObject();
appendProtocolDataToApiPayload(gatewayRequestData.serviceProtocol, protocolData, apiPayload); appendProtocolDataToApiPayload(gatewayRequestData.serviceProtocol, protocolData, apiPayload);
bool isTestPurchase = apiConfigObject.value(apiDefs::key::isTestPurchase).toBool(false);
QByteArray responseBody; QByteArray responseBody;
ErrorCode errorCode = executeRequest(QString("%1v1/native_config"), apiPayload, responseBody); ErrorCode errorCode = executeRequest(QString("%1v1/native_config"), apiPayload, responseBody, isTestPurchase);
if (errorCode != ErrorCode::NoError) { if (errorCode != ErrorCode::NoError) {
emit errorOccurred(errorCode); emit errorOccurred(errorCode);
return false; return false;
@@ -325,11 +313,6 @@ bool ApiConfigsController::revokeNativeConfig(const QString &serverCountryCode)
auto serverConfigObject = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex()); auto serverConfigObject = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex());
auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject(); auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject();
if (isSubscriptionExpired(apiConfigObject)) {
emit errorOccurred(ErrorCode::ApiSubscriptionExpiredError);
return false;
}
GatewayRequestData gatewayRequestData { QSysInfo::productType(), GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION), QString(APP_VERSION),
m_settings->getAppLanguage().name().split("_").first(), m_settings->getAppLanguage().name().split("_").first(),
@@ -341,9 +324,9 @@ bool ApiConfigsController::revokeNativeConfig(const QString &serverCountryCode)
serverConfigObject.value(configKey::authData).toObject() }; serverConfigObject.value(configKey::authData).toObject() };
QJsonObject apiPayload = gatewayRequestData.toJsonObject(); QJsonObject apiPayload = gatewayRequestData.toJsonObject();
bool isTestPurchase = apiConfigObject.value(apiDefs::key::isTestPurchase).toBool(false);
QByteArray responseBody; QByteArray responseBody;
ErrorCode errorCode = executeRequest(QString("%1v1/revoke_native_config"), apiPayload, responseBody); ErrorCode errorCode = executeRequest(QString("%1v1/revoke_native_config"), apiPayload, responseBody, isTestPurchase);
if (errorCode != ErrorCode::NoError && errorCode != ErrorCode::ApiNotFoundError) { if (errorCode != ErrorCode::NoError && errorCode != ErrorCode::ApiNotFoundError) {
emit errorOccurred(errorCode); emit errorOccurred(errorCode);
return false; return false;
@@ -400,6 +383,218 @@ bool ApiConfigsController::fillAvailableServices()
QJsonObject data = QJsonDocument::fromJson(responseBody).object(); QJsonObject data = QJsonDocument::fromJson(responseBody).object();
m_apiServicesModel->updateModel(data); m_apiServicesModel->updateModel(data);
if (m_apiServicesModel->rowCount() > 0) {
m_apiServicesModel->setServiceIndex(0);
}
return true;
}
bool ApiConfigsController::importService()
{
#if defined(Q_OS_IOS) || defined(MACOS_NE)
bool isIosOrMacOsNe = true;
#else
bool isIosOrMacOsNe = false;
#endif
if (m_apiServicesModel->getSelectedServiceType() == serviceType::amneziaPremium) {
if (isIosOrMacOsNe) {
importSerivceFromAppStore();
return true;
}
} else {
importServiceFromGateway();
return true;
}
return false;
}
bool ApiConfigsController::importSerivceFromAppStore()
{
#if defined(Q_OS_IOS) || defined(MACOS_NE)
bool purchaseOk = false;
QString originalTransactionId;
QString storeTransactionId;
QString storeProductId;
QString purchaseError;
QEventLoop waitPurchase;
IosController::Instance()->purchaseProduct(QStringLiteral("amnezia_premium_6_month"),
[&](bool success, const QString &txId, const QString &purchasedProductId,
const QString &originalTxId, const QString &errorString) {
purchaseOk = success;
originalTransactionId = originalTxId;
storeTransactionId = txId;
storeProductId = purchasedProductId;
purchaseError = errorString;
waitPurchase.quit();
});
waitPurchase.exec();
if (!purchaseOk || originalTransactionId.isEmpty()) {
qDebug() << "IAP purchase failed:" << purchaseError;
emit errorOccurred(ErrorCode::ApiPurchaseError);
return false;
}
qInfo().noquote() << "[IAP] Purchase success. transactionId =" << storeTransactionId
<< "originalTransactionId =" << originalTransactionId << "productId =" << storeProductId;
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION),
m_settings->getAppLanguage().name().split("_").first(),
m_settings->getInstallationUuid(true),
m_apiServicesModel->getCountryCode(),
"",
m_apiServicesModel->getSelectedServiceType(),
m_apiServicesModel->getSelectedServiceProtocol(),
QJsonObject() };
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
apiPayload[apiDefs::key::transactionId] = originalTransactionId;
auto isTestPurchase = IosController::Instance()->isTestFlight();
ErrorCode errorCode;
QByteArray responseBody;
errorCode = executeRequest(QString("%1v1/subscriptions"), apiPayload, responseBody, isTestPurchase);
if (errorCode != ErrorCode::NoError) {
emit errorOccurred(errorCode);
return false;
}
errorCode = importServiceFromBilling(responseBody, isTestPurchase);
if (errorCode != ErrorCode::NoError) {
emit errorOccurred(errorCode);
return false;
}
emit installServerFromApiFinished(tr("%1 installed successfully.").arg(m_apiServicesModel->getSelectedServiceName()));
#endif
return true;
}
bool ApiConfigsController::restoreSerivceFromAppStore()
{
#if defined(Q_OS_IOS) || defined(MACOS_NE)
const QString premiumServiceType = QStringLiteral("amnezia-premium");
if (!fillAvailableServices()) {
qWarning().noquote() << "[IAP] Unable to fetch services list before restore";
emit errorOccurred(ErrorCode::ApiServicesMissingError);
return false;
}
if (m_apiServicesModel->rowCount() <= 0) {
emit errorOccurred(ErrorCode::ApiServicesMissingError);
return false;
}
// Ensure we have a valid premium selection for gateway requests
bool premiumSelected = false;
for (int i = 0; i < m_apiServicesModel->rowCount(); ++i) {
m_apiServicesModel->setServiceIndex(i);
if (m_apiServicesModel->getSelectedServiceType() == premiumServiceType) {
premiumSelected = true;
break;
}
}
if (!premiumSelected) {
emit errorOccurred(ErrorCode::ApiServicesMissingError);
return false;
}
bool restoreSuccess = false;
QList<QVariantMap> restoredTransactions;
QString restoreError;
QEventLoop waitRestore;
IosController::Instance()->restorePurchases([&](bool success, const QList<QVariantMap> &transactions, const QString &errorString) {
restoreSuccess = success;
restoredTransactions = transactions;
restoreError = errorString;
waitRestore.quit();
});
waitRestore.exec();
if (!restoreSuccess) {
qWarning().noquote() << "[IAP] Restore failed:" << restoreError;
emit errorOccurred(ErrorCode::ApiPurchaseError);
return false;
}
if (restoredTransactions.isEmpty()) {
qInfo().noquote() << "[IAP] Restore completed, but no transactions were returned";
emit errorOccurred(ErrorCode::ApiPurchaseError);
return false;
}
bool hasInstalledConfig = false;
bool duplicateConfigAlreadyPresent = false;
int duplicateCount = 0;
QSet<QString> processedTransactions;
for (const QVariantMap &transaction : restoredTransactions) {
const QString originalTransactionId = transaction.value(QStringLiteral("originalTransactionId")).toString();
const QString transactionId = transaction.value(QStringLiteral("transactionId")).toString();
const QString productId = transaction.value(QStringLiteral("productId")).toString();
if (originalTransactionId.isEmpty()) {
qWarning().noquote() << "[IAP] Skipping restored transaction without originalTransactionId" << transactionId;
continue;
}
if (processedTransactions.contains(originalTransactionId)) {
duplicateCount++;
continue;
}
processedTransactions.insert(originalTransactionId);
qInfo().noquote() << "[IAP] Restoring subscription. transactionId =" << transactionId
<< "originalTransactionId =" << originalTransactionId << "productId =" << productId;
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION),
m_settings->getAppLanguage().name().split("_").first(),
m_settings->getInstallationUuid(true),
m_apiServicesModel->getCountryCode(),
"",
m_apiServicesModel->getSelectedServiceType(),
m_apiServicesModel->getSelectedServiceProtocol(),
QJsonObject() };
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
apiPayload[apiDefs::key::transactionId] = originalTransactionId;
auto isTestPurchase = IosController::Instance()->isTestFlight();
QByteArray responseBody;
ErrorCode errorCode = executeRequest(QString("%1v1/subscriptions"), apiPayload, responseBody, isTestPurchase);
if (errorCode != ErrorCode::NoError) {
qWarning().noquote() << "[IAP] Failed to restore transaction" << originalTransactionId
<< "errorCode =" << static_cast<int>(errorCode);
continue;
}
ErrorCode installError = importServiceFromBilling(responseBody, isTestPurchase);
if (errorCode == ErrorCode::ApiConfigAlreadyAdded) {
duplicateConfigAlreadyPresent = true;
qInfo().noquote() << "[IAP] Skipping restored transaction" << originalTransactionId
<< "because subscription config with the same vpn_key already exists";
} else if (errorCode != ErrorCode::NoError) {
qWarning().noquote() << "[IAP] Failed to process restored subscription response for transaction" << originalTransactionId;
} else {
hasInstalledConfig = true;
}
}
if (!hasInstalledConfig) {
const ErrorCode restoreError = duplicateConfigAlreadyPresent ? ErrorCode::ApiConfigAlreadyAdded : ErrorCode::ApiPurchaseError;
emit errorOccurred(restoreError);
return false;
}
emit installServerFromApiFinished(tr("Subscription restored successfully."));
if (duplicateCount > 0) {
qInfo().noquote() << "[IAP] Skipped" << duplicateCount
<< "duplicate restored transactions for original transaction IDs already processed";
}
#endif
return true; return true;
} }
@@ -426,8 +621,10 @@ bool ApiConfigsController::importServiceFromGateway()
QJsonObject apiPayload = gatewayRequestData.toJsonObject(); QJsonObject apiPayload = gatewayRequestData.toJsonObject();
appendProtocolDataToApiPayload(gatewayRequestData.serviceProtocol, protocolData, apiPayload); appendProtocolDataToApiPayload(gatewayRequestData.serviceProtocol, protocolData, apiPayload);
ErrorCode errorCode;
QByteArray responseBody; QByteArray responseBody;
ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody);
errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody);
QJsonObject serverConfig; QJsonObject serverConfig;
if (errorCode == ErrorCode::NoError) { if (errorCode == ErrorCode::NoError) {
@@ -459,11 +656,6 @@ bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const
auto serverConfig = m_serversModel->getServerConfig(serverIndex); auto serverConfig = m_serversModel->getServerConfig(serverIndex);
auto apiConfig = serverConfig.value(configKey::apiConfig).toObject(); auto apiConfig = serverConfig.value(configKey::apiConfig).toObject();
if (isSubscriptionExpired(apiConfig)) {
emit errorOccurred(ErrorCode::ApiSubscriptionExpiredError);
return false;
}
GatewayRequestData gatewayRequestData { QSysInfo::productType(), GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION), QString(APP_VERSION),
m_settings->getAppLanguage().name().split("_").first(), m_settings->getAppLanguage().name().split("_").first(),
@@ -483,8 +675,9 @@ bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const
apiPayload.insert(configKey::isConnectEvent, true); apiPayload.insert(configKey::isConnectEvent, true);
} }
bool isTestPurchase = apiConfig.value(apiDefs::key::isTestPurchase).toBool(false);
QByteArray responseBody; QByteArray responseBody;
ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody); ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody, isTestPurchase);
QJsonObject newServerConfig; QJsonObject newServerConfig;
if (errorCode == ErrorCode::NoError) { if (errorCode == ErrorCode::NoError) {
@@ -576,15 +769,6 @@ bool ApiConfigsController::deactivateDevice(const bool isRemoveEvent)
return true; return true;
} }
if (isSubscriptionExpired(apiConfigObject)) {
if (isRemoveEvent) {
return true;
} else {
emit errorOccurred(ErrorCode::ApiSubscriptionExpiredError);
return false;
}
}
GatewayRequestData gatewayRequestData { QSysInfo::productType(), GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION), QString(APP_VERSION),
m_settings->getAppLanguage().name().split("_").first(), m_settings->getAppLanguage().name().split("_").first(),
@@ -596,9 +780,10 @@ bool ApiConfigsController::deactivateDevice(const bool isRemoveEvent)
serverConfigObject.value(configKey::authData).toObject() }; serverConfigObject.value(configKey::authData).toObject() };
QJsonObject apiPayload = gatewayRequestData.toJsonObject(); QJsonObject apiPayload = gatewayRequestData.toJsonObject();
bool isTestPurchase = apiConfigObject.value(apiDefs::key::isTestPurchase).toBool(false);
QByteArray responseBody; QByteArray responseBody;
ErrorCode errorCode = executeRequest(QString("%1v1/revoke_config"), apiPayload, responseBody); ErrorCode errorCode = executeRequest(QString("%1v1/revoke_config"), apiPayload, responseBody, isTestPurchase);
if (errorCode != ErrorCode::NoError && errorCode != ErrorCode::ApiNotFoundError) { if (errorCode != ErrorCode::NoError && errorCode != ErrorCode::ApiNotFoundError) {
emit errorOccurred(errorCode); emit errorOccurred(errorCode);
return false; return false;
@@ -620,11 +805,6 @@ bool ApiConfigsController::deactivateExternalDevice(const QString &uuid, const Q
return true; return true;
} }
if (isSubscriptionExpired(apiConfigObject)) {
emit errorOccurred(ErrorCode::ApiSubscriptionExpiredError);
return false;
}
GatewayRequestData gatewayRequestData { QSysInfo::productType(), GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION), QString(APP_VERSION),
m_settings->getAppLanguage().name().split("_").first(), m_settings->getAppLanguage().name().split("_").first(),
@@ -636,9 +816,9 @@ bool ApiConfigsController::deactivateExternalDevice(const QString &uuid, const Q
serverConfigObject.value(configKey::authData).toObject() }; serverConfigObject.value(configKey::authData).toObject() };
QJsonObject apiPayload = gatewayRequestData.toJsonObject(); QJsonObject apiPayload = gatewayRequestData.toJsonObject();
bool isTestPurchase = apiConfigObject.value(apiDefs::key::isTestPurchase).toBool(false);
QByteArray responseBody; QByteArray responseBody;
ErrorCode errorCode = executeRequest(QString("%1v1/revoke_config"), apiPayload, responseBody); ErrorCode errorCode = executeRequest(QString("%1v1/revoke_config"), apiPayload, responseBody, isTestPurchase);
if (errorCode != ErrorCode::NoError && errorCode != ErrorCode::ApiNotFoundError) { if (errorCode != ErrorCode::NoError && errorCode != ErrorCode::ApiNotFoundError) {
emit errorOccurred(errorCode); emit errorOccurred(errorCode);
return false; return false;
@@ -709,7 +889,7 @@ QList<QString> ApiConfigsController::getQrCodes()
int ApiConfigsController::getQrCodesCount() int ApiConfigsController::getQrCodesCount()
{ {
return m_qrCodes.size(); return static_cast<int>(m_qrCodes.size());
} }
QString ApiConfigsController::getVpnKey() QString ApiConfigsController::getVpnKey()
@@ -717,9 +897,58 @@ QString ApiConfigsController::getVpnKey()
return m_vpnKey; return m_vpnKey;
} }
ErrorCode ApiConfigsController::executeRequest(const QString &endpoint, const QJsonObject &apiPayload, QByteArray &responseBody) ErrorCode ApiConfigsController::importServiceFromBilling(const QByteArray &responseBody, const bool isTestPurchase)
{ {
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs, #ifdef Q_OS_IOS
m_settings->isStrictKillSwitchEnabled()); QJsonObject responseObject = QJsonDocument::fromJson(responseBody).object();
QString key = responseObject.value(QStringLiteral("key")).toString();
if (key.isEmpty()) {
qWarning().noquote() << "[IAP] Subscription response does not contain a key field";
return ErrorCode::ApiPurchaseError;
}
if (m_serversModel->hasServerWithVpnKey(key)) {
qInfo().noquote() << "[IAP] Subscription config with the same vpn_key already exists";
return ErrorCode::ApiConfigAlreadyAdded;
}
QString normalizedKey = key;
normalizedKey.replace(QStringLiteral("vpn://"), QString());
QByteArray configString = QByteArray::fromBase64(normalizedKey.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
QByteArray configUncompressed = qUncompress(configString);
if (!configUncompressed.isEmpty()) {
configString = configUncompressed;
}
if (configString.isEmpty()) {
qWarning().noquote() << "[IAP] Subscription response config payload is empty";
return ErrorCode::ApiPurchaseError;
}
QJsonObject configObject = QJsonDocument::fromJson(configString).object();
quint16 crc = qChecksum(QJsonDocument(configObject).toJson());
auto apiConfig = configObject.value(apiDefs::key::apiConfig).toObject();
apiConfig[apiDefs::key::vpnKey] = normalizedKey;
apiConfig[apiDefs::key::isTestPurchase] = isTestPurchase;
configObject.insert(apiDefs::key::apiConfig, apiConfig);
configObject.insert(config_key::crc, crc);
m_serversModel->addServer(configObject);
return ErrorCode::NoError;
#else
Q_UNUSED(responseBody)
Q_UNUSED(isTestPurchase)
return ErrorCode::NoError;
#endif
}
ErrorCode ApiConfigsController::executeRequest(const QString &endpoint, const QJsonObject &apiPayload, QByteArray &responseBody,
bool isTestPurchase)
{
GatewayController gatewayController(m_settings->getGatewayEndpoint(isTestPurchase), m_settings->isDevGatewayEnv(isTestPurchase),
apiDefs::requestTimeoutMsecs, m_settings->isStrictKillSwitchEnabled());
return gatewayController.post(endpoint, apiPayload, responseBody); return gatewayController.post(endpoint, apiPayload, responseBody);
} }
@@ -26,6 +26,9 @@ public slots:
void copyVpnKeyToClipboard(); void copyVpnKeyToClipboard();
bool fillAvailableServices(); bool fillAvailableServices();
bool importService();
bool importSerivceFromAppStore();
bool restoreSerivceFromAppStore();
bool importServiceFromGateway(); bool importServiceFromGateway();
bool updateServiceFromGateway(const int serverIndex, const QString &newCountryCode, const QString &newCountryName, bool updateServiceFromGateway(const int serverIndex, const QString &newCountryCode, const QString &newCountryName,
bool reloadServiceConfig = false); bool reloadServiceConfig = false);
@@ -53,7 +56,8 @@ private:
int getQrCodesCount(); int getQrCodesCount();
QString getVpnKey(); QString getVpnKey();
ErrorCode executeRequest(const QString &endpoint, const QJsonObject &apiPayload, QByteArray &responseBody); ErrorCode executeRequest(const QString &endpoint, const QJsonObject &apiPayload, QByteArray &responseBody, bool isTestPurchase = false);
ErrorCode importServiceFromBilling(const QByteArray &responseBody, const bool isTestPurchase);
QList<QString> m_qrCodes; QList<QString> m_qrCodes;
QString m_vpnKey; QString m_vpnKey;
@@ -1,133 +0,0 @@
#include "apiPremV1MigrationController.h"
#include <QEventLoop>
#include <QTimer>
#include "core/api/apiDefs.h"
#include "core/api/apiUtils.h"
#include "core/controllers/gatewayController.h"
ApiPremV1MigrationController::ApiPremV1MigrationController(const QSharedPointer<ServersModel> &serversModel,
const std::shared_ptr<Settings> &settings, QObject *parent)
: QObject(parent), m_serversModel(serversModel), m_settings(settings)
{
}
bool ApiPremV1MigrationController::hasConfigsToMigration()
{
QJsonArray vpnKeys;
auto serversCount = m_serversModel->getServersCount();
for (size_t i = 0; i < serversCount; i++) {
auto serverConfigObject = m_serversModel->getServerConfig(i);
if (apiUtils::getConfigType(serverConfigObject) != apiDefs::ConfigType::AmneziaPremiumV1) {
continue;
}
QString vpnKey = apiUtils::getPremiumV1VpnKey(serverConfigObject);
vpnKeys.append(vpnKey);
}
if (!vpnKeys.isEmpty()) {
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
m_settings->isStrictKillSwitchEnabled());
QJsonObject apiPayload;
apiPayload["configs"] = vpnKeys;
QByteArray responseBody;
ErrorCode errorCode = gatewayController.post(QString("%1v1/prem-v1/is-active-subscription"), apiPayload, responseBody);
auto migrationsStatus = QJsonDocument::fromJson(responseBody).object();
for (const auto &migrationStatus : migrationsStatus) {
if (migrationStatus == "not_found") {
return true;
}
}
}
return false;
}
void ApiPremV1MigrationController::getSubscriptionList(const QString &email)
{
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
m_settings->isStrictKillSwitchEnabled());
QJsonObject apiPayload;
apiPayload[apiDefs::key::email] = email;
QByteArray responseBody;
ErrorCode errorCode = gatewayController.post(QString("%1v1/prem-v1/subscription-list"), apiPayload, responseBody);
if (errorCode == ErrorCode::NoError) {
m_email = email;
m_subscriptionsModel = QJsonDocument::fromJson(responseBody).array();
if (m_subscriptionsModel.isEmpty()) {
emit noSubscriptionToMigrate();
return;
}
emit subscriptionsModelChanged();
} else {
emit errorOccurred(ErrorCode::ApiMigrationError);
}
}
QJsonArray ApiPremV1MigrationController::getSubscriptionModel()
{
return m_subscriptionsModel;
}
void ApiPremV1MigrationController::sendMigrationCode(const int subscriptionIndex)
{
QEventLoop wait;
QTimer::singleShot(1000, &wait, &QEventLoop::quit);
wait.exec(QEventLoop::ExcludeUserInputEvents);
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
m_settings->isStrictKillSwitchEnabled());
QJsonObject apiPayload;
apiPayload[apiDefs::key::email] = m_email;
QByteArray responseBody;
ErrorCode errorCode = gatewayController.post(QString("%1v1/prem-v1/migration-code"), apiPayload, responseBody);
if (errorCode == ErrorCode::NoError) {
m_subscriptionIndex = subscriptionIndex;
emit otpSuccessfullySent();
} else {
emit errorOccurred(ErrorCode::ApiMigrationError);
}
}
void ApiPremV1MigrationController::migrate(const QString &migrationCode)
{
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
m_settings->isStrictKillSwitchEnabled());
QJsonObject apiPayload;
apiPayload[apiDefs::key::email] = m_email;
apiPayload[apiDefs::key::orderId] = m_subscriptionsModel.at(m_subscriptionIndex).toObject().value(apiDefs::key::id).toString();
apiPayload[apiDefs::key::migrationCode] = migrationCode;
QByteArray responseBody;
ErrorCode errorCode = gatewayController.post(QString("%1v1/prem-v1/migrate"), apiPayload, responseBody);
if (errorCode == ErrorCode::NoError) {
auto responseObject = QJsonDocument::fromJson(responseBody).object();
QString premiumV2VpnKey = responseObject.value(apiDefs::key::config).toString();
emit importPremiumV2VpnKey(premiumV2VpnKey);
} else {
emit errorOccurred(ErrorCode::ApiMigrationError);
}
}
bool ApiPremV1MigrationController::isPremV1MigrationReminderActive()
{
return m_settings->isPremV1MigrationReminderActive();
}
void ApiPremV1MigrationController::disablePremV1MigrationReminder()
{
m_settings->disablePremV1MigrationReminder();
}
@@ -1,50 +0,0 @@
#ifndef APIPREMV1MIGRATIONCONTROLLER_H
#define APIPREMV1MIGRATIONCONTROLLER_H
#include <QObject>
#include "ui/models/servers_model.h"
class ApiPremV1MigrationController : public QObject
{
Q_OBJECT
public:
ApiPremV1MigrationController(const QSharedPointer<ServersModel> &serversModel, const std::shared_ptr<Settings> &settings,
QObject *parent = nullptr);
Q_PROPERTY(QJsonArray subscriptionsModel READ getSubscriptionModel NOTIFY subscriptionsModelChanged)
public slots:
bool hasConfigsToMigration();
void getSubscriptionList(const QString &email);
QJsonArray getSubscriptionModel();
void sendMigrationCode(const int subscriptionIndex);
void migrate(const QString &migrationCode);
bool isPremV1MigrationReminderActive();
void disablePremV1MigrationReminder();
signals:
void subscriptionsModelChanged();
void otpSuccessfullySent();
void importPremiumV2VpnKey(const QString &vpnKey);
void errorOccurred(ErrorCode errorCode);
void showMigrationDrawer();
void migrationFinished();
void noSubscriptionToMigrate();
private:
QSharedPointer<ServersModel> m_serversModel;
std::shared_ptr<Settings> m_settings;
QJsonArray m_subscriptionsModel;
int m_subscriptionIndex;
QString m_email;
};
#endif // APIPREMV1MIGRATIONCONTROLLER_H
@@ -5,6 +5,7 @@
#include "core/api/apiUtils.h" #include "core/api/apiUtils.h"
#include "core/controllers/gatewayController.h" #include "core/controllers/gatewayController.h"
#include "platforms/ios/ios_controller.h"
#include "version.h" #include "version.h"
namespace namespace
@@ -49,14 +50,15 @@ bool ApiSettingsController::getAccountInfo(bool reload)
wait.exec(QEventLoop::ExcludeUserInputEvents); wait.exec(QEventLoop::ExcludeUserInputEvents);
} }
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), requestTimeoutMsecs,
m_settings->isStrictKillSwitchEnabled());
auto processedIndex = m_serversModel->getProcessedServerIndex(); auto processedIndex = m_serversModel->getProcessedServerIndex();
auto serverConfig = m_serversModel->getServerConfig(processedIndex); auto serverConfig = m_serversModel->getServerConfig(processedIndex);
auto apiConfig = serverConfig.value(configKey::apiConfig).toObject(); auto apiConfig = serverConfig.value(configKey::apiConfig).toObject();
auto authData = serverConfig.value(configKey::authData).toObject(); auto authData = serverConfig.value(configKey::authData).toObject();
bool isTestPurchase = apiConfig.value(apiDefs::key::isTestPurchase).toBool(false);
GatewayController gatewayController(m_settings->getGatewayEndpoint(isTestPurchase), m_settings->isDevGatewayEnv(isTestPurchase),
requestTimeoutMsecs, m_settings->isStrictKillSwitchEnabled());
QJsonObject apiPayload; QJsonObject apiPayload;
apiPayload[configKey::userCountryCode] = apiConfig.value(configKey::userCountryCode).toString(); apiPayload[configKey::userCountryCode] = apiConfig.value(configKey::userCountryCode).toString();
apiPayload[configKey::serviceType] = apiConfig.value(configKey::serviceType).toString(); apiPayload[configKey::serviceType] = apiConfig.value(configKey::serviceType).toString();
@@ -6,6 +6,7 @@
#include <QApplication> #include <QApplication>
#endif #endif
#include "utilities.h"
#include "core/controllers/vpnConfigurationController.h" #include "core/controllers/vpnConfigurationController.h"
#include "version.h" #include "version.h"
+2 -3
View File
@@ -179,7 +179,8 @@ void ExportController::generateWireGuardConfig(const QString &clientName)
void ExportController::generateAwgConfig(const QString &clientName) void ExportController::generateAwgConfig(const QString &clientName)
{ {
QJsonObject nativeConfig; QJsonObject nativeConfig;
ErrorCode errorCode = generateNativeConfig(DockerContainer::Awg, clientName, Proto::Awg, nativeConfig); ErrorCode errorCode = generateNativeConfig(static_cast<DockerContainer>(m_containersModel->getProcessedContainerIndex()), clientName,
Proto::Awg, nativeConfig);
if (errorCode) { if (errorCode) {
emit exportErrorOccurred(errorCode); emit exportErrorOccurred(errorCode);
return; return;
@@ -316,7 +317,6 @@ void ExportController::generateXrayConfig(const QString &clientName)
vlessServer.network = streamSettings.value("network").toString("tcp"); vlessServer.network = streamSettings.value("network").toString("tcp");
vlessServer.security = streamSettings.value("security").toString("reality"); vlessServer.security = streamSettings.value("security").toString("reality");
if (vlessServer.security == "reality") { if (vlessServer.security == "reality") {
QJsonObject realitySettings = streamSettings.value("realitySettings").toObject(); QJsonObject realitySettings = streamSettings.value("realitySettings").toObject();
vlessServer.serverName = realitySettings.value("serverName").toString(); vlessServer.serverName = realitySettings.value("serverName").toString();
@@ -328,7 +328,6 @@ void ExportController::generateXrayConfig(const QString &clientName)
m_nativeConfigString = amnezia::serialization::vless::Serialize(vlessServer, "AmneziaVPN"); m_nativeConfigString = amnezia::serialization::vless::Serialize(vlessServer, "AmneziaVPN");
emit exportConfigChanged(); emit exportConfigChanged();
} }
+30 -22
View File
@@ -272,7 +272,7 @@ void ImportController::processNativeWireGuardConfig()
auto containers = m_config.value(config_key::containers).toArray(); auto containers = m_config.value(config_key::containers).toArray();
if (!containers.isEmpty()) { if (!containers.isEmpty()) {
auto container = containers.at(0).toObject(); auto container = containers.at(0).toObject();
auto serverProtocolConfig = container.value(ContainerProps::containerTypeToString(DockerContainer::WireGuard)).toObject(); auto serverProtocolConfig = container.value(ContainerProps::containerTypeToProtocolString(DockerContainer::WireGuard)).toObject();
auto clientProtocolConfig = QJsonDocument::fromJson(serverProtocolConfig.value(config_key::last_config).toString().toUtf8()).object(); auto clientProtocolConfig = QJsonDocument::fromJson(serverProtocolConfig.value(config_key::last_config).toString().toUtf8()).object();
QString junkPacketCount = QString::number(QRandomGenerator::global()->bounded(4, 7)); QString junkPacketCount = QString::number(QRandomGenerator::global()->bounded(4, 7));
@@ -288,18 +288,8 @@ void ImportController::processNativeWireGuardConfig()
clientProtocolConfig[config_key::underloadPacketMagicHeader] = "3"; clientProtocolConfig[config_key::underloadPacketMagicHeader] = "3";
clientProtocolConfig[config_key::transportPacketMagicHeader] = "4"; clientProtocolConfig[config_key::transportPacketMagicHeader] = "4";
// clientProtocolConfig[config_key::cookieReplyPacketJunkSize] = "0"; clientProtocolConfig[config_key::cookieReplyPacketJunkSize] = "0";
// clientProtocolConfig[config_key::transportPacketJunkSize] = "0"; clientProtocolConfig[config_key::transportPacketJunkSize] = "0";
// clientProtocolConfig[config_key::specialJunk1] = "";
// clientProtocolConfig[config_key::specialJunk2] = "";
// clientProtocolConfig[config_key::specialJunk3] = "";
// clientProtocolConfig[config_key::specialJunk4] = "";
// clientProtocolConfig[config_key::specialJunk5] = "";
// clientProtocolConfig[config_key::controlledJunk1] = "";
// clientProtocolConfig[config_key::controlledJunk2] = "";
// clientProtocolConfig[config_key::controlledJunk3] = "";
// clientProtocolConfig[config_key::specialHandshakeTimeout] = "0";
clientProtocolConfig[config_key::isObfuscationEnabled] = true; clientProtocolConfig[config_key::isObfuscationEnabled] = true;
@@ -458,6 +448,7 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data)
lastConfig[config_key::allowed_ips] = allowedIpsJsonArray; lastConfig[config_key::allowed_ips] = allowedIpsJsonArray;
QString protocolName = "wireguard"; QString protocolName = "wireguard";
QString protocolVersion;
const QStringList requiredJunkFields = { config_key::junkPacketCount, config_key::junkPacketMinSize, const QStringList requiredJunkFields = { config_key::junkPacketCount, config_key::junkPacketMinSize,
config_key::junkPacketMaxSize, config_key::initPacketJunkSize, config_key::junkPacketMaxSize, config_key::initPacketJunkSize,
@@ -465,11 +456,10 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data)
config_key::responsePacketMagicHeader, config_key::underloadPacketMagicHeader, config_key::responsePacketMagicHeader, config_key::underloadPacketMagicHeader,
config_key::transportPacketMagicHeader }; config_key::transportPacketMagicHeader };
const QStringList optionalJunkFields = { // config_key::cookieReplyPacketJunkSize, const QStringList optionalJunkFields = { config_key::cookieReplyPacketJunkSize,
// config_key::transportPacketJunkSize, config_key::transportPacketJunkSize,
config_key::specialJunk1, config_key::specialJunk2, config_key::specialJunk3, config_key::specialJunk1, config_key::specialJunk2, config_key::specialJunk3,
config_key::specialJunk4, config_key::specialJunk5, config_key::controlledJunk1, config_key::specialJunk4, config_key::specialJunk5
config_key::controlledJunk2, config_key::controlledJunk3, config_key::specialHandshakeTimeout
}; };
bool hasAllRequiredFields = std::all_of(requiredJunkFields.begin(), requiredJunkFields.end(), bool hasAllRequiredFields = std::all_of(requiredJunkFields.begin(), requiredJunkFields.end(),
@@ -485,6 +475,19 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data)
} }
} }
bool hasCookieReplyPacketJunkSize = !configMap.value(config_key::cookieReplyPacketJunkSize).isEmpty();
bool hasTransportPacketJunkSize = !configMap.value(config_key::transportPacketJunkSize).isEmpty();
bool hasSpecialJunk = !configMap.value(config_key::specialJunk1).isEmpty() ||
!configMap.value(config_key::specialJunk2).isEmpty() ||
!configMap.value(config_key::specialJunk3).isEmpty() ||
!configMap.value(config_key::specialJunk4).isEmpty() ||
!configMap.value(config_key::specialJunk5).isEmpty();
if (hasCookieReplyPacketJunkSize && hasTransportPacketJunkSize) {
protocolVersion = "2";
} else if (hasSpecialJunk && !hasCookieReplyPacketJunkSize && !hasTransportPacketJunkSize) {
protocolVersion = "1.5";
}
protocolName = "awg"; protocolName = "awg";
m_configType = ConfigTypes::Awg; m_configType = ConfigTypes::Awg;
} }
@@ -492,7 +495,9 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data)
if (!configMap.value("MTU").isEmpty()) { if (!configMap.value("MTU").isEmpty()) {
lastConfig[config_key::mtu] = configMap.value("MTU"); lastConfig[config_key::mtu] = configMap.value("MTU");
} else { } else {
lastConfig[config_key::mtu] = protocolName == "awg" ? protocols::awg::defaultMtu : protocols::wireguard::defaultMtu; lastConfig[config_key::mtu] = (protocolName == "awg")
? protocols::awg::defaultMtu
: protocols::wireguard::defaultMtu;
} }
QJsonObject wireguardConfig; QJsonObject wireguardConfig;
@@ -500,6 +505,9 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data)
wireguardConfig[config_key::isThirdPartyConfig] = true; wireguardConfig[config_key::isThirdPartyConfig] = true;
wireguardConfig[config_key::port] = port; wireguardConfig[config_key::port] = port;
wireguardConfig[config_key::transport_proto] = "udp"; wireguardConfig[config_key::transport_proto] = "udp";
if (protocolName == "awg" && !protocolVersion.isEmpty()) {
wireguardConfig[config_key::protocolVersion] = protocolVersion;
}
QJsonObject containers; QJsonObject containers;
containers.insert(config_key::container, QJsonValue("amnezia-" + protocolName)); containers.insert(config_key::container, QJsonValue("amnezia-" + protocolName));
@@ -733,8 +741,8 @@ void ImportController::processAmneziaConfig(QJsonObject &config)
for (auto i = 0; i < containers.size(); i++) { for (auto i = 0; i < containers.size(); i++) {
auto container = containers.at(i).toObject(); auto container = containers.at(i).toObject();
auto dockerContainer = ContainerProps::containerFromString(container.value(config_key::container).toString()); auto dockerContainer = ContainerProps::containerFromString(container.value(config_key::container).toString());
if (dockerContainer == DockerContainer::Awg || dockerContainer == DockerContainer::WireGuard) { if (ContainerProps::isAwgContainer(dockerContainer) || dockerContainer == DockerContainer::WireGuard) {
auto containerConfig = container.value(ContainerProps::containerTypeToString(dockerContainer)).toObject(); auto containerConfig = container.value(ContainerProps::containerTypeToProtocolString(dockerContainer)).toObject();
auto protocolConfig = containerConfig.value(config_key::last_config).toString(); auto protocolConfig = containerConfig.value(config_key::last_config).toString();
if (protocolConfig.isEmpty()) { if (protocolConfig.isEmpty()) {
return; return;
@@ -742,11 +750,11 @@ void ImportController::processAmneziaConfig(QJsonObject &config)
QJsonObject jsonConfig = QJsonDocument::fromJson(protocolConfig.toUtf8()).object(); QJsonObject jsonConfig = QJsonDocument::fromJson(protocolConfig.toUtf8()).object();
jsonConfig[config_key::mtu] = jsonConfig[config_key::mtu] =
dockerContainer == DockerContainer::Awg ? protocols::awg::defaultMtu : protocols::wireguard::defaultMtu; ContainerProps::isAwgContainer(dockerContainer) ? protocols::awg::defaultMtu : protocols::wireguard::defaultMtu;
containerConfig[config_key::last_config] = QString(QJsonDocument(jsonConfig).toJson()); containerConfig[config_key::last_config] = QString(QJsonDocument(jsonConfig).toJson());
container[ContainerProps::containerTypeToString(dockerContainer)] = containerConfig; container[ContainerProps::containerTypeToProtocolString(dockerContainer)] = containerConfig;
containers.replace(i, container); containers.replace(i, container);
config.insert(config_key::containers, containers); config.insert(config_key::containers, containers);
} }
+56 -54
View File
@@ -4,6 +4,7 @@
#include <QDir> #include <QDir>
#include <QEventLoop> #include <QEventLoop>
#include <QJsonObject> #include <QJsonObject>
#include <QPair>
#include <QRandomGenerator> #include <QRandomGenerator>
#include <QStandardPaths> #include <QStandardPaths>
#include <QtConcurrent> #include <QtConcurrent>
@@ -71,16 +72,19 @@ void InstallController::install(DockerContainer container, int port, TransportPr
if (protocol == mainProto) { if (protocol == mainProto) {
containerConfig.insert(config_key::port, QString::number(port)); containerConfig.insert(config_key::port, QString::number(port));
containerConfig.insert(config_key::transport_proto, ProtocolProps::transportProtoToString(transportProto, protocol)); containerConfig.insert(config_key::transport_proto, ProtocolProps::transportProtoToString(transportProto, protocol));
containerConfig.insert(config_key::subnet_address, protocols::wireguard::defaultSubnetAddress);
if (container == DockerContainer::Awg2) {
containerConfig[config_key::protocolVersion] = "2";
if (container == DockerContainer::Awg) {
QString junkPacketCount = QString::number(QRandomGenerator::global()->bounded(4, 7)); QString junkPacketCount = QString::number(QRandomGenerator::global()->bounded(4, 7));
QString junkPacketMinSize = QString::number(10); QString junkPacketMinSize = QString::number(10);
QString junkPacketMaxSize = QString::number(50); QString junkPacketMaxSize = QString::number(50);
int s1 = QRandomGenerator::global()->bounded(15, 150); int s1 = QRandomGenerator::global()->bounded(15, 150);
int s2 = QRandomGenerator::global()->bounded(15, 150); int s2 = QRandomGenerator::global()->bounded(15, 150);
// int s3 = QRandomGenerator::global()->bounded(15, 150); int s3 = QRandomGenerator::global()->bounded(0, 64);
// int s4 = QRandomGenerator::global()->bounded(15, 150); int s4 = QRandomGenerator::global()->bounded(0, 20);
// Ensure all values are unique and don't create equal packet sizes // Ensure all values are unique and don't create equal packet sizes
QSet<int> usedValues; QSet<int> usedValues;
@@ -91,37 +95,36 @@ void InstallController::install(DockerContainer container, int port, TransportPr
} }
usedValues.insert(s2); usedValues.insert(s2);
// while (usedValues.contains(s3) while (usedValues.contains(s3) || s1 + AwgConstant::messageInitiationSize == s3 + AwgConstant::messageCookieReplySize
// || s1 + AwgConstant::messageInitiationSize == s3 + AwgConstant::messageCookieReplySize || s2 + AwgConstant::messageResponseSize == s3 + AwgConstant::messageCookieReplySize) {
// || s2 + AwgConstant::messageResponseSize == s3 + AwgConstant::messageCookieReplySize) { s3 = QRandomGenerator::global()->bounded(0, 64);
// s3 = QRandomGenerator::global()->bounded(15, 150); }
// } usedValues.insert(s3);
// usedValues.insert(s3);
// while (usedValues.contains(s4) while (usedValues.contains(s4)) {
// || s1 + AwgConstant::messageInitiationSize == s4 + AwgConstant::messageTransportSize s4 = QRandomGenerator::global()->bounded(0, 20);
// || s2 + AwgConstant::messageResponseSize == s4 + AwgConstant::messageTransportSize }
// || s3 + AwgConstant::messageCookieReplySize == s4 + AwgConstant::messageTransportSize) {
// s4 = QRandomGenerator::global()->bounded(15, 150);
// }
QString initPacketJunkSize = QString::number(s1); QString initPacketJunkSize = QString::number(s1);
QString responsePacketJunkSize = QString::number(s2); QString responsePacketJunkSize = QString::number(s2);
// QString cookieReplyPacketJunkSize = QString::number(s3); QString cookieReplyPacketJunkSize = QString::number(s3);
// QString transportPacketJunkSize = QString::number(s4); QString transportPacketJunkSize = QString::number(s4);
QSet<QString> headersValue; QVector<QPair<QString, QString>> headersValue;
while (headersValue.size() != 4) { int min = 5;
auto max = (std::numeric_limits<qint32>::max)(); auto max = (std::numeric_limits<qint32>::max)();
headersValue.insert(QString::number(QRandomGenerator::global()->bounded(5, max))); while (headersValue.size() != 4) {
auto first = QRandomGenerator::global()->bounded(min, max);
auto second = QRandomGenerator::global()->bounded(first, max);
min = second;
headersValue.push_back(QPair<QString, QString>(QString::number(first), QString::number(second)));
} }
auto headersValueList = headersValue.values(); QString initPacketMagicHeader = headersValue.at(0).first + "-" + headersValue.at(0).second;
QString responsePacketMagicHeader = headersValue.at(1).first + "-" + headersValue.at(1).second;
QString initPacketMagicHeader = headersValueList.at(0); QString underloadPacketMagicHeader = headersValue.at(2).first + "-" + headersValue.at(2).second;
QString responsePacketMagicHeader = headersValueList.at(1); QString transportPacketMagicHeader = headersValue.at(3).first + "-" + headersValue.at(3).second;
QString underloadPacketMagicHeader = headersValueList.at(2);
QString transportPacketMagicHeader = headersValueList.at(3);
containerConfig[config_key::junkPacketCount] = junkPacketCount; containerConfig[config_key::junkPacketCount] = junkPacketCount;
containerConfig[config_key::junkPacketMinSize] = junkPacketMinSize; containerConfig[config_key::junkPacketMinSize] = junkPacketMinSize;
@@ -133,19 +136,14 @@ void InstallController::install(DockerContainer container, int port, TransportPr
containerConfig[config_key::underloadPacketMagicHeader] = underloadPacketMagicHeader; containerConfig[config_key::underloadPacketMagicHeader] = underloadPacketMagicHeader;
containerConfig[config_key::transportPacketMagicHeader] = transportPacketMagicHeader; containerConfig[config_key::transportPacketMagicHeader] = transportPacketMagicHeader;
// TODO: containerConfig[config_key::cookieReplyPacketJunkSize] = cookieReplyPacketJunkSize;
// containerConfig[config_key::cookieReplyPacketJunkSize] = cookieReplyPacketJunkSize; containerConfig[config_key::transportPacketJunkSize] = transportPacketJunkSize;
// containerConfig[config_key::transportPacketJunkSize] = transportPacketJunkSize;
// containerConfig[config_key::specialJunk1] = specialJunk1; containerConfig[config_key::specialJunk1] = protocols::awg::defaultSpecialJunk1;
// containerConfig[config_key::specialJunk2] = specialJunk2; containerConfig[config_key::specialJunk2] = protocols::awg::defaultSpecialJunk2;
// containerConfig[config_key::specialJunk3] = specialJunk3; containerConfig[config_key::specialJunk3] = protocols::awg::defaultSpecialJunk3;
// containerConfig[config_key::specialJunk4] = specialJunk4; containerConfig[config_key::specialJunk4] = protocols::awg::defaultSpecialJunk4;
// containerConfig[config_key::specialJunk5] = specialJunk5; containerConfig[config_key::specialJunk5] = protocols::awg::defaultSpecialJunk5;
// containerConfig[config_key::controlledJunk1] = controlledJunk1;
// containerConfig[config_key::controlledJunk2] = controlledJunk2;
// containerConfig[config_key::controlledJunk3] = controlledJunk3;
// containerConfig[config_key::specialHandshakeTimeout] = specialHandshakeTimeout;
} else if (container == DockerContainer::Sftp) { } else if (container == DockerContainer::Sftp) {
containerConfig.insert(config_key::userName, protocols::sftp::defaultUserName); containerConfig.insert(config_key::userName, protocols::sftp::defaultUserName);
@@ -420,8 +418,11 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
containerConfig.insert(config_key::transport_proto, transportProto); containerConfig.insert(config_key::transport_proto, transportProto);
if (protocol == Proto::Awg) { if (protocol == Proto::Awg) {
QString serverConfig = serverController->getTextFileFromContainer(container, credentials, QString configPath = amnezia::protocols::awg::serverConfigPath;
protocols::awg::serverConfigPath, errorCode); if (container == DockerContainer::Awg) {
configPath = amnezia::protocols::awg::serverLegacyConfigPath;
}
QString serverConfig = serverController->getTextFileFromContainer(container, credentials, configPath, errorCode);
QMap<QString, QString> serverConfigMap; QMap<QString, QString> serverConfigMap;
auto serverConfigLines = serverConfig.split("\n"); auto serverConfigLines = serverConfig.split("\n");
@@ -450,18 +451,19 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
containerConfig[config_key::transportPacketMagicHeader] = containerConfig[config_key::transportPacketMagicHeader] =
serverConfigMap.value(config_key::transportPacketMagicHeader); serverConfigMap.value(config_key::transportPacketMagicHeader);
// containerConfig[config_key::cookieReplyPacketJunkSize] = serverConfigMap.value(config_key::cookieReplyPacketJunkSize); // hack to parse i1-i5 from commented lines in server config
// containerConfig[config_key::transportPacketJunkSize] = serverConfigMap.value(config_key::transportPacketJunkSize); containerConfig[config_key::specialJunk1] = serverConfigMap.value(QString("# ") + config_key::specialJunk1);
containerConfig[config_key::specialJunk2] = serverConfigMap.value(QString("# ") + config_key::specialJunk2);
containerConfig[config_key::specialJunk3] = serverConfigMap.value(QString("# ") + config_key::specialJunk3);
containerConfig[config_key::specialJunk4] = serverConfigMap.value(QString("# ") + config_key::specialJunk4);
containerConfig[config_key::specialJunk5] = serverConfigMap.value(QString("# ") + config_key::specialJunk5);
// containerConfig[config_key::specialJunk1] = serverConfigMap.value(config_key::specialJunk1); if (container == DockerContainer::Awg2) {
// containerConfig[config_key::specialJunk2] = serverConfigMap.value(config_key::specialJunk2); containerConfig[config_key::protocolVersion] = "2";
// containerConfig[config_key::specialJunk3] = serverConfigMap.value(config_key::specialJunk3); containerConfig[config_key::cookieReplyPacketJunkSize] =
// containerConfig[config_key::specialJunk4] = serverConfigMap.value(config_key::specialJunk4); serverConfigMap.value(config_key::cookieReplyPacketJunkSize);
// containerConfig[config_key::specialJunk5] = serverConfigMap.value(config_key::specialJunk5); containerConfig[config_key::transportPacketJunkSize] = serverConfigMap.value(config_key::transportPacketJunkSize);
// containerConfig[config_key::controlledJunk1] = serverConfigMap.value(config_key::controlledJunk1); }
// containerConfig[config_key::controlledJunk2] = serverConfigMap.value(config_key::controlledJunk2);
// containerConfig[config_key::controlledJunk3] = serverConfigMap.value(config_key::controlledJunk3);
// containerConfig[config_key::specialHandshakeTimeout] = serverConfigMap.value(config_key::specialHandshakeTimeout);
} else if (protocol == Proto::WireGuard) { } else if (protocol == Proto::WireGuard) {
QString serverConfig = serverController->getTextFileFromContainer(container, credentials, QString serverConfig = serverController->getTextFileFromContainer(container, credentials,
@@ -626,8 +628,8 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
} }
} else if (protocol == Proto::ShadowSocks) { } else if (protocol == Proto::ShadowSocks) {
QString shadowsocksConfig = serverController->getTextFileFromContainer(container, credentials, QString shadowsocksConfig = serverController->getTextFileFromContainer(
"/opt/amnezia/shadowsocks/ss-config.json", errorCode); container, credentials, "/opt/amnezia/shadowsocks/ss-config.json", errorCode);
QJsonDocument doc = QJsonDocument::fromJson(shadowsocksConfig.toUtf8()); QJsonDocument doc = QJsonDocument::fromJson(shadowsocksConfig.toUtf8());
@@ -1068,7 +1070,7 @@ bool InstallController::isUpdateDockerContainerRequired(const DockerContainer co
const QJsonObject &oldProtoConfig = oldConfig.value(ProtocolProps::protoToString(mainProto)).toObject(); const QJsonObject &oldProtoConfig = oldConfig.value(ProtocolProps::protoToString(mainProto)).toObject();
const QJsonObject &newProtoConfig = newConfig.value(ProtocolProps::protoToString(mainProto)).toObject(); const QJsonObject &newProtoConfig = newConfig.value(ProtocolProps::protoToString(mainProto)).toObject();
if (container == DockerContainer::Awg) { if (container == DockerContainer::Awg2) {
const AwgConfig oldConfig(oldProtoConfig); const AwgConfig oldConfig(oldProtoConfig);
const AwgConfig newConfig(newProtoConfig); const AwgConfig newConfig(newProtoConfig);
+40 -13
View File
@@ -104,7 +104,7 @@ ErrorCode ClientManagementModel::updateModel(const DockerContainer container, co
if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) { if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) {
error = getOpenVpnClients(container, credentials, serverController, count); error = getOpenVpnClients(container, credentials, serverController, count);
} else if (container == DockerContainer::WireGuard || container == DockerContainer::Awg) { } else if (container == DockerContainer::WireGuard || ContainerProps::isAwgContainer(container)) {
error = getWireGuardClients(container, credentials, serverController, count); error = getWireGuardClients(container, credentials, serverController, count);
} else if (container == DockerContainer::Xray) { } else if (container == DockerContainer::Xray) {
error = getXrayClients(container, credentials, serverController, count); error = getXrayClients(container, credentials, serverController, count);
@@ -209,8 +209,15 @@ ErrorCode ClientManagementModel::getWireGuardClients(const DockerContainer conta
{ {
ErrorCode error = ErrorCode::NoError; ErrorCode error = ErrorCode::NoError;
const QString wireGuardConfigFile = QString("opt/amnezia/%1/wg0.conf").arg(container == DockerContainer::WireGuard ? "wireguard" : "awg"); QString configPath;
const QString wireguardConfigString = serverController->getTextFileFromContainer(container, credentials, wireGuardConfigFile, error); if (container == DockerContainer::Awg) {
configPath = QString::fromLatin1(amnezia::protocols::awg::serverLegacyConfigPath);
} else if (container == DockerContainer::Awg2) {
configPath = QString::fromLatin1(amnezia::protocols::awg::serverConfigPath);
} else {
configPath = QString::fromLatin1(amnezia::protocols::wireguard::serverConfigPath);
}
const QString wireguardConfigString = serverController->getTextFileFromContainer(container, credentials, configPath, error);
if (error != ErrorCode::NoError) { if (error != ErrorCode::NoError) {
logger.error() << "Failed to get the wg conf file from the server"; logger.error() << "Failed to get the wg conf file from the server";
return error; return error;
@@ -307,7 +314,7 @@ ErrorCode ClientManagementModel::getXrayClients(const DockerContainer container,
ErrorCode ClientManagementModel::wgShow(const DockerContainer container, const ServerCredentials &credentials, ErrorCode ClientManagementModel::wgShow(const DockerContainer container, const ServerCredentials &credentials,
const QSharedPointer<ServerController> &serverController, std::vector<WgShowData> &data) const QSharedPointer<ServerController> &serverController, std::vector<WgShowData> &data)
{ {
if (container != DockerContainer::WireGuard && container != DockerContainer::Awg) { if (container != DockerContainer::WireGuard && !ContainerProps::isAwgContainer(container)) {
return ErrorCode::NoError; return ErrorCode::NoError;
} }
@@ -318,7 +325,11 @@ ErrorCode ClientManagementModel::wgShow(const DockerContainer container, const S
return ErrorCode::NoError; return ErrorCode::NoError;
}; };
const QString command = QString("sudo docker exec -i $CONTAINER_NAME bash -c '%1'").arg("wg show all"); QString showBin = (container == DockerContainer::Awg2)
? QStringLiteral("awg")
: QStringLiteral("wg");
const QString command = QString("sudo docker exec -i $CONTAINER_NAME bash -c '%1 show all'")
.arg(showBin);
QString script = serverController->replaceVars(command, serverController->genVarsForScript(credentials, container)); QString script = serverController->replaceVars(command, serverController->genVarsForScript(credentials, container));
error = serverController->runScript(credentials, script, cbReadStdOut); error = serverController->runScript(credentials, script, cbReadStdOut);
@@ -397,6 +408,7 @@ ErrorCode ClientManagementModel::appendClient(const DockerContainer container, c
break; break;
case DockerContainer::OpenVpn: case DockerContainer::OpenVpn:
case DockerContainer::WireGuard: case DockerContainer::WireGuard:
case DockerContainer::Awg2:
case DockerContainer::Awg: case DockerContainer::Awg:
case DockerContainer::Xray: case DockerContainer::Xray:
protocol = ContainerProps::defaultProtocol(container); protocol = ContainerProps::defaultProtocol(container);
@@ -547,6 +559,7 @@ ErrorCode ClientManagementModel::revokeClient(const int row, const DockerContain
break; break;
} }
case DockerContainer::WireGuard: case DockerContainer::WireGuard:
case DockerContainer::Awg2:
case DockerContainer::Awg: { case DockerContainer::Awg: {
errorCode = revokeWireGuard(row, container, credentials, serverController); errorCode = revokeWireGuard(row, container, credentials, serverController);
break; break;
@@ -606,6 +619,7 @@ ErrorCode ClientManagementModel::revokeClient(const QJsonObject &containerConfig
} }
case DockerContainer::OpenVpn: case DockerContainer::OpenVpn:
case DockerContainer::WireGuard: case DockerContainer::WireGuard:
case DockerContainer::Awg2:
case DockerContainer::Awg: case DockerContainer::Awg:
case DockerContainer::Xray: { case DockerContainer::Xray: {
protocol = ContainerProps::defaultProtocol(container); protocol = ContainerProps::defaultProtocol(container);
@@ -679,7 +693,8 @@ ErrorCode ClientManagementModel::revokeClient(const QJsonObject &containerConfig
break; break;
} }
case DockerContainer::WireGuard: case DockerContainer::WireGuard:
case DockerContainer::Awg: { case DockerContainer::Awg:
case DockerContainer::Awg2: {
errorCode = revokeWireGuard(row, container, credentials, serverController); errorCode = revokeWireGuard(row, container, credentials, serverController);
break; break;
} }
@@ -738,9 +753,15 @@ ErrorCode ClientManagementModel::revokeWireGuard(const int row, const DockerCont
{ {
ErrorCode error = ErrorCode::NoError; ErrorCode error = ErrorCode::NoError;
const QString wireGuardConfigFile = QString configPath;
QString("/opt/amnezia/%1/wg0.conf").arg(container == DockerContainer::WireGuard ? "wireguard" : "awg"); if (container == DockerContainer::Awg) {
const QString wireguardConfigString = serverController->getTextFileFromContainer(container, credentials, wireGuardConfigFile, error); configPath = QString::fromLatin1(amnezia::protocols::awg::serverLegacyConfigPath);
} else if (container == DockerContainer::Awg2) {
configPath = QString::fromLatin1(amnezia::protocols::awg::serverConfigPath);
} else {
configPath = QString::fromLatin1(amnezia::protocols::wireguard::serverConfigPath);
}
const QString wireguardConfigString = serverController->getTextFileFromContainer(container, credentials, configPath, error);
if (error != ErrorCode::NoError) { if (error != ErrorCode::NoError) {
logger.error() << "Failed to get the wg conf file from the server"; logger.error() << "Failed to get the wg conf file from the server";
return error; return error;
@@ -758,7 +779,7 @@ ErrorCode ClientManagementModel::revokeWireGuard(const int row, const DockerCont
} }
QString newWireGuardConfig = configSections.join("["); QString newWireGuardConfig = configSections.join("[");
newWireGuardConfig.insert(0, "["); newWireGuardConfig.insert(0, "[");
error = serverController->uploadTextFileToContainer(container, credentials, newWireGuardConfig, wireGuardConfigFile); error = serverController->uploadTextFileToContainer(container, credentials, newWireGuardConfig, configPath);
if (error != ErrorCode::NoError) { if (error != ErrorCode::NoError) {
logger.error() << "Failed to upload the wg conf file to the server"; logger.error() << "Failed to upload the wg conf file to the server";
return error; return error;
@@ -782,12 +803,18 @@ ErrorCode ClientManagementModel::revokeWireGuard(const int row, const DockerCont
return error; return error;
} }
const QString script = "sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip %1)'"; bool isAwg = (container == DockerContainer::Awg2);
QString command = isAwg ? QStringLiteral("awg") : QStringLiteral("wg");
QString iface = isAwg ? QStringLiteral("awg0") : QStringLiteral("wg0");
QString script = QString(
"sudo docker exec -i $CONTAINER_NAME bash -c '%1 syncconf %2 <(%1-quick strip %3)'"
).arg(command, iface, configPath);
error = serverController->runScript( error = serverController->runScript(
credentials, credentials,
serverController->replaceVars(script.arg(wireGuardConfigFile), serverController->genVarsForScript(credentials, container))); serverController->replaceVars(script, serverController->genVarsForScript(credentials, container))
);
if (error != ErrorCode::NoError) { if (error != ErrorCode::NoError) {
logger.error() << "Failed to execute the command 'wg syncconf' on the server"; logger.error() << QString("Failed to execute command '%1 syncconf %2' on the server").arg(command, iface);
return error; return error;
} }
+26 -7
View File
@@ -2,8 +2,7 @@
#include <QJsonArray> #include <QJsonArray>
ContainersModel::ContainersModel(QObject *parent) ContainersModel::ContainersModel(QObject *parent) : QAbstractListModel(parent)
: QAbstractListModel(parent)
{ {
} }
@@ -20,10 +19,23 @@ QVariant ContainersModel::data(const QModelIndex &index, int role) const
} }
DockerContainer container = ContainerProps::allContainers().at(index.row()); DockerContainer container = ContainerProps::allContainers().at(index.row());
QString protocolKey = ContainerProps::containerTypeToProtocolString(container);
auto isThirdPartyConfig = m_containers.value(container).value(protocolKey).toObject().value(config_key::isThirdPartyConfig).toBool();
switch (role) { switch (role) {
case NameRole: return ContainerProps::containerHumanNames().value(container); case NameRole: {
case DescriptionRole: return ContainerProps::containerDescriptions().value(container); if (container == DockerContainer::Awg && !isThirdPartyConfig) {
return "AmneziaWG Legacy";
}
return ContainerProps::containerHumanNames().value(container);
}
case DescriptionRole: {
if (container == DockerContainer::Awg && !isThirdPartyConfig) {
return QObject::tr("AmneziaWG Legacy is a outdated version of AmneziaWG protocol. To upgrade, install AmneziaWG and recreate users.");
}
return ContainerProps::containerDescriptions().value(container);
}
case DetailedDescriptionRole: return ContainerProps::containerDetailedDescriptions().value(container); case DetailedDescriptionRole: return ContainerProps::containerDetailedDescriptions().value(container);
case ConfigRole: { case ConfigRole: {
if (container == DockerContainer::None) { if (container == DockerContainer::None) {
@@ -31,12 +43,14 @@ QVariant ContainersModel::data(const QModelIndex &index, int role) const
} }
return m_containers.value(container); return m_containers.value(container);
} }
case IsThirdPartyConfigRole: return isThirdPartyConfig;
case ServiceTypeRole: return ContainerProps::containerService(container); case ServiceTypeRole: return ContainerProps::containerService(container);
case DockerContainerRole: return container; case DockerContainerRole: return container;
case IsEasySetupContainerRole: return ContainerProps::isEasySetupContainer(container); case IsEasySetupContainerRole: return ContainerProps::isEasySetupContainer(container);
case EasySetupHeaderRole: return ContainerProps::easySetupHeader(container); case EasySetupHeaderRole: return ContainerProps::easySetupHeader(container);
case EasySetupDescriptionRole: return ContainerProps::easySetupDescription(container); case EasySetupDescriptionRole: return ContainerProps::easySetupDescription(container);
case EasySetupOrderRole: return ContainerProps::easySetupOrder(container); case EasySetupOrderRole: return ContainerProps::easySetupOrder(container);
case IsInstallationAllowedRole: return ContainersModel::isInstallationAllowed(container);
case IsInstalledRole: return m_containers.contains(container); case IsInstalledRole: return m_containers.contains(container);
case IsCurrentlyProcessedRole: return container == static_cast<DockerContainer>(m_processedContainerIndex); case IsCurrentlyProcessedRole: return container == static_cast<DockerContainer>(m_processedContainerIndex);
case IsSupportedRole: return ContainerProps::isSupportedByCurrentPlatform(container); case IsSupportedRole: return ContainerProps::isSupportedByCurrentPlatform(container);
@@ -58,8 +72,7 @@ void ContainersModel::updateModel(const QJsonArray &containers)
beginResetModel(); beginResetModel();
m_containers.clear(); m_containers.clear();
for (const QJsonValue &val : containers) { for (const QJsonValue &val : containers) {
m_containers.insert(ContainerProps::containerFromString(val.toObject().value(config_key::container).toString()), m_containers.insert(ContainerProps::containerFromString(val.toObject().value(config_key::container).toString()), val.toObject());
val.toObject());
} }
endResetModel(); endResetModel();
} }
@@ -114,6 +127,11 @@ bool ContainersModel::hasInstalledProtocols()
return false; return false;
} }
bool ContainersModel::isInstallationAllowed(DockerContainer container)
{
return container != DockerContainer::Awg;
}
QHash<int, QByteArray> ContainersModel::roleNames() const QHash<int, QByteArray> ContainersModel::roleNames() const
{ {
QHash<int, QByteArray> roles; QHash<int, QByteArray> roles;
@@ -123,6 +141,7 @@ QHash<int, QByteArray> ContainersModel::roleNames() const
roles[ServiceTypeRole] = "serviceType"; roles[ServiceTypeRole] = "serviceType";
roles[DockerContainerRole] = "dockerContainer"; roles[DockerContainerRole] = "dockerContainer";
roles[ConfigRole] = "config"; roles[ConfigRole] = "config";
roles[IsThirdPartyConfigRole] = "isThirdPartyConfig";
roles[IsEasySetupContainerRole] = "isEasySetupContainer"; roles[IsEasySetupContainerRole] = "isEasySetupContainer";
roles[EasySetupHeaderRole] = "easySetupHeader"; roles[EasySetupHeaderRole] = "easySetupHeader";
@@ -133,7 +152,7 @@ QHash<int, QByteArray> ContainersModel::roleNames() const
roles[IsCurrentlyProcessedRole] = "isCurrentlyProcessed"; roles[IsCurrentlyProcessedRole] = "isCurrentlyProcessed";
roles[IsSupportedRole] = "isSupported"; roles[IsSupportedRole] = "isSupported";
roles[IsShareableRole] = "isShareable"; roles[IsShareableRole] = "isShareable";
roles[IsInstallationAllowedRole] = "isInstallationAllowed";
roles[InstallPageOrderRole] = "installPageOrder"; roles[InstallPageOrderRole] = "installPageOrder";
return roles; return roles;
} }
+4
View File
@@ -20,6 +20,7 @@ public:
DetailedDescriptionRole, DetailedDescriptionRole,
ServiceTypeRole, ServiceTypeRole,
ConfigRole, ConfigRole,
IsThirdPartyConfigRole,
DockerContainerRole, DockerContainerRole,
IsEasySetupContainerRole, IsEasySetupContainerRole,
@@ -27,6 +28,7 @@ public:
EasySetupDescriptionRole, EasySetupDescriptionRole,
EasySetupOrderRole, EasySetupOrderRole,
IsInstallationAllowedRole,
IsInstalledRole, IsInstalledRole,
IsCurrentlyProcessedRole, IsCurrentlyProcessedRole,
IsDefaultRole, IsDefaultRole,
@@ -57,6 +59,8 @@ public slots:
bool hasInstalledServices(); bool hasInstalledServices();
bool hasInstalledProtocols(); bool hasInstalledProtocols();
static bool isInstallationAllowed(DockerContainer container);
protected: protected:
QHash<int, QByteArray> roleNames() const override; QHash<int, QByteArray> roleNames() const override;
+1 -1
View File
@@ -30,7 +30,7 @@ QVariant NewsModel::data(const QModelIndex &index, int role) const
case IdRole: return item.id; case IdRole: return item.id;
case TitleRole: return item.title; case TitleRole: return item.title;
case ContentRole: return item.content; case ContentRole: return item.content;
case TimestampRole: return item.timestamp.toString(Qt::ISODate); case TimestampRole: return item.timestamp.toLocalTime().toString(Qt::ISODate);
case IsReadRole: return item.read; case IsReadRole: return item.read;
case IsProcessedRole: return index.row() == m_processedIndex; case IsProcessedRole: return index.row() == m_processedIndex;
default: return QVariant(); default: return QVariant();
+104 -71
View File
@@ -33,12 +33,6 @@ bool AwgConfigModel::setData(const QModelIndex &index, const QVariant &value, in
case Roles::ClientSpecialJunk3Role: m_clientProtocolConfig.insert(config_key::specialJunk3, value.toString()); break; case Roles::ClientSpecialJunk3Role: m_clientProtocolConfig.insert(config_key::specialJunk3, value.toString()); break;
case Roles::ClientSpecialJunk4Role: m_clientProtocolConfig.insert(config_key::specialJunk4, value.toString()); break; case Roles::ClientSpecialJunk4Role: m_clientProtocolConfig.insert(config_key::specialJunk4, value.toString()); break;
case Roles::ClientSpecialJunk5Role: m_clientProtocolConfig.insert(config_key::specialJunk5, value.toString()); break; case Roles::ClientSpecialJunk5Role: m_clientProtocolConfig.insert(config_key::specialJunk5, value.toString()); break;
case Roles::ClientControlledJunk1Role: m_clientProtocolConfig.insert(config_key::controlledJunk1, value.toString()); break;
case Roles::ClientControlledJunk2Role: m_clientProtocolConfig.insert(config_key::controlledJunk2, value.toString()); break;
case Roles::ClientControlledJunk3Role: m_clientProtocolConfig.insert(config_key::controlledJunk3, value.toString()); break;
case Roles::ClientSpecialHandshakeTimeoutRole:
m_clientProtocolConfig.insert(config_key::specialHandshakeTimeout, value.toString());
break;
case Roles::ServerJunkPacketCountRole: m_serverProtocolConfig.insert(config_key::junkPacketCount, value.toString()); break; case Roles::ServerJunkPacketCountRole: m_serverProtocolConfig.insert(config_key::junkPacketCount, value.toString()); break;
case Roles::ServerJunkPacketMinSizeRole: m_serverProtocolConfig.insert(config_key::junkPacketMinSize, value.toString()); break; case Roles::ServerJunkPacketMinSizeRole: m_serverProtocolConfig.insert(config_key::junkPacketMinSize, value.toString()); break;
case Roles::ServerJunkPacketMaxSizeRole: m_serverProtocolConfig.insert(config_key::junkPacketMaxSize, value.toString()); break; case Roles::ServerJunkPacketMaxSizeRole: m_serverProtocolConfig.insert(config_key::junkPacketMaxSize, value.toString()); break;
@@ -46,12 +40,12 @@ bool AwgConfigModel::setData(const QModelIndex &index, const QVariant &value, in
case Roles::ServerResponsePacketJunkSizeRole: case Roles::ServerResponsePacketJunkSizeRole:
m_serverProtocolConfig.insert(config_key::responsePacketJunkSize, value.toString()); m_serverProtocolConfig.insert(config_key::responsePacketJunkSize, value.toString());
break; break;
// case Roles::ServerCookieReplyPacketJunkSizeRole: case Roles::ServerCookieReplyPacketJunkSizeRole:
// m_serverProtocolConfig.insert(config_key::cookieReplyPacketJunkSize, value.toString()); m_serverProtocolConfig.insert(config_key::cookieReplyPacketJunkSize, value.toString());
// break; break;
// case Roles::ServerTransportPacketJunkSizeRole: case Roles::ServerTransportPacketJunkSizeRole:
// m_serverProtocolConfig.insert(config_key::transportPacketJunkSize, value.toString()); m_serverProtocolConfig.insert(config_key::transportPacketJunkSize, value.toString());
// break; break;
case Roles::ServerInitPacketMagicHeaderRole: m_serverProtocolConfig.insert(config_key::initPacketMagicHeader, value.toString()); break; case Roles::ServerInitPacketMagicHeaderRole: m_serverProtocolConfig.insert(config_key::initPacketMagicHeader, value.toString()); break;
case Roles::ServerResponsePacketMagicHeaderRole: case Roles::ServerResponsePacketMagicHeaderRole:
m_serverProtocolConfig.insert(config_key::responsePacketMagicHeader, value.toString()); m_serverProtocolConfig.insert(config_key::responsePacketMagicHeader, value.toString());
@@ -62,6 +56,11 @@ bool AwgConfigModel::setData(const QModelIndex &index, const QVariant &value, in
case Roles::ServerTransportPacketMagicHeaderRole: case Roles::ServerTransportPacketMagicHeaderRole:
m_serverProtocolConfig.insert(config_key::transportPacketMagicHeader, value.toString()); m_serverProtocolConfig.insert(config_key::transportPacketMagicHeader, value.toString());
break; break;
case Roles::ServerSpecialJunk1Role: m_serverProtocolConfig.insert(config_key::specialJunk1, value.toString()); break;
case Roles::ServerSpecialJunk2Role: m_serverProtocolConfig.insert(config_key::specialJunk2, value.toString()); break;
case Roles::ServerSpecialJunk3Role: m_serverProtocolConfig.insert(config_key::specialJunk3, value.toString()); break;
case Roles::ServerSpecialJunk4Role: m_serverProtocolConfig.insert(config_key::specialJunk4, value.toString()); break;
case Roles::ServerSpecialJunk5Role: m_serverProtocolConfig.insert(config_key::specialJunk5, value.toString()); break;
} }
emit dataChanged(index, index, QList { role }); emit dataChanged(index, index, QList { role });
@@ -87,22 +86,25 @@ QVariant AwgConfigModel::data(const QModelIndex &index, int role) const
case Roles::ClientSpecialJunk3Role: return m_clientProtocolConfig.value(config_key::specialJunk3); case Roles::ClientSpecialJunk3Role: return m_clientProtocolConfig.value(config_key::specialJunk3);
case Roles::ClientSpecialJunk4Role: return m_clientProtocolConfig.value(config_key::specialJunk4); case Roles::ClientSpecialJunk4Role: return m_clientProtocolConfig.value(config_key::specialJunk4);
case Roles::ClientSpecialJunk5Role: return m_clientProtocolConfig.value(config_key::specialJunk5); case Roles::ClientSpecialJunk5Role: return m_clientProtocolConfig.value(config_key::specialJunk5);
case Roles::ClientControlledJunk1Role: return m_clientProtocolConfig.value(config_key::controlledJunk1);
case Roles::ClientControlledJunk2Role: return m_clientProtocolConfig.value(config_key::controlledJunk2);
case Roles::ClientControlledJunk3Role: return m_clientProtocolConfig.value(config_key::controlledJunk3);
case Roles::ClientSpecialHandshakeTimeoutRole: return m_clientProtocolConfig.value(config_key::specialHandshakeTimeout);
case Roles::ServerJunkPacketCountRole: return m_serverProtocolConfig.value(config_key::junkPacketCount); case Roles::ServerJunkPacketCountRole: return m_serverProtocolConfig.value(config_key::junkPacketCount);
case Roles::ServerJunkPacketMinSizeRole: return m_serverProtocolConfig.value(config_key::junkPacketMinSize); case Roles::ServerJunkPacketMinSizeRole: return m_serverProtocolConfig.value(config_key::junkPacketMinSize);
case Roles::ServerJunkPacketMaxSizeRole: return m_serverProtocolConfig.value(config_key::junkPacketMaxSize); case Roles::ServerJunkPacketMaxSizeRole: return m_serverProtocolConfig.value(config_key::junkPacketMaxSize);
case Roles::ServerInitPacketJunkSizeRole: return m_serverProtocolConfig.value(config_key::initPacketJunkSize); case Roles::ServerInitPacketJunkSizeRole: return m_serverProtocolConfig.value(config_key::initPacketJunkSize);
case Roles::ServerResponsePacketJunkSizeRole: return m_serverProtocolConfig.value(config_key::responsePacketJunkSize); case Roles::ServerResponsePacketJunkSizeRole: return m_serverProtocolConfig.value(config_key::responsePacketJunkSize);
// case Roles::ServerCookieReplyPacketJunkSizeRole: return m_serverProtocolConfig.value(config_key::cookieReplyPacketJunkSize); case Roles::ServerCookieReplyPacketJunkSizeRole: return m_serverProtocolConfig.value(config_key::cookieReplyPacketJunkSize);
// case Roles::ServerTransportPacketJunkSizeRole: return m_serverProtocolConfig.value(config_key::transportPacketJunkSize); case Roles::ServerTransportPacketJunkSizeRole: return m_serverProtocolConfig.value(config_key::transportPacketJunkSize);
case Roles::ServerInitPacketMagicHeaderRole: return m_serverProtocolConfig.value(config_key::initPacketMagicHeader); case Roles::ServerInitPacketMagicHeaderRole: return m_serverProtocolConfig.value(config_key::initPacketMagicHeader);
case Roles::ServerResponsePacketMagicHeaderRole: return m_serverProtocolConfig.value(config_key::responsePacketMagicHeader); case Roles::ServerResponsePacketMagicHeaderRole: return m_serverProtocolConfig.value(config_key::responsePacketMagicHeader);
case Roles::ServerUnderloadPacketMagicHeaderRole: return m_serverProtocolConfig.value(config_key::underloadPacketMagicHeader); case Roles::ServerUnderloadPacketMagicHeaderRole: return m_serverProtocolConfig.value(config_key::underloadPacketMagicHeader);
case Roles::ServerTransportPacketMagicHeaderRole: return m_serverProtocolConfig.value(config_key::transportPacketMagicHeader); case Roles::ServerTransportPacketMagicHeaderRole: return m_serverProtocolConfig.value(config_key::transportPacketMagicHeader);
case Roles::ServerSpecialJunk1Role: return m_serverProtocolConfig.value(config_key::specialJunk1);
case Roles::ServerSpecialJunk2Role: return m_serverProtocolConfig.value(config_key::specialJunk2);
case Roles::ServerSpecialJunk3Role: return m_serverProtocolConfig.value(config_key::specialJunk3);
case Roles::ServerSpecialJunk4Role: return m_serverProtocolConfig.value(config_key::specialJunk4);
case Roles::ServerSpecialJunk5Role: return m_serverProtocolConfig.value(config_key::specialJunk5);
case Roles::IsAwg2Role: return ProtocolProps::getProtocolVersion(m_fullConfig.value(config_key::awg).toObject()) == protocols::awg::awgV2;
} }
return QVariant(); return QVariant();
@@ -117,6 +119,11 @@ void AwgConfigModel::updateModel(const QJsonObject &config)
QJsonObject serverProtocolConfig = config.value(config_key::awg).toObject(); QJsonObject serverProtocolConfig = config.value(config_key::awg).toObject();
auto protocolVersion = serverProtocolConfig.value(config_key::protocolVersion).toString();
if (!protocolVersion.isEmpty()) {
m_serverProtocolConfig[config_key::protocolVersion] = protocolVersion;
}
auto defaultTransportProto = ProtocolProps::transportProtoToString(ProtocolProps::defaultTransportProto(Proto::Awg), Proto::Awg); auto defaultTransportProto = ProtocolProps::transportProtoToString(ProtocolProps::defaultTransportProto(Proto::Awg), Proto::Awg);
m_serverProtocolConfig.insert(config_key::transport_proto, m_serverProtocolConfig.insert(config_key::transport_proto,
serverProtocolConfig.value(config_key::transport_proto).toString(defaultTransportProto)); serverProtocolConfig.value(config_key::transport_proto).toString(defaultTransportProto));
@@ -134,10 +141,10 @@ void AwgConfigModel::updateModel(const QJsonObject &config)
serverProtocolConfig.value(config_key::initPacketJunkSize).toString(protocols::awg::defaultInitPacketJunkSize); serverProtocolConfig.value(config_key::initPacketJunkSize).toString(protocols::awg::defaultInitPacketJunkSize);
m_serverProtocolConfig[config_key::responsePacketJunkSize] = m_serverProtocolConfig[config_key::responsePacketJunkSize] =
serverProtocolConfig.value(config_key::responsePacketJunkSize).toString(protocols::awg::defaultResponsePacketJunkSize); serverProtocolConfig.value(config_key::responsePacketJunkSize).toString(protocols::awg::defaultResponsePacketJunkSize);
// m_serverProtocolConfig[config_key::cookieReplyPacketJunkSize] = m_serverProtocolConfig[config_key::cookieReplyPacketJunkSize] =
// serverProtocolConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize); serverProtocolConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize);
// m_serverProtocolConfig[config_key::transportPacketJunkSize] = m_serverProtocolConfig[config_key::transportPacketJunkSize] =
// serverProtocolConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize); serverProtocolConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize);
m_serverProtocolConfig[config_key::initPacketMagicHeader] = m_serverProtocolConfig[config_key::initPacketMagicHeader] =
serverProtocolConfig.value(config_key::initPacketMagicHeader).toString(protocols::awg::defaultInitPacketMagicHeader); serverProtocolConfig.value(config_key::initPacketMagicHeader).toString(protocols::awg::defaultInitPacketMagicHeader);
m_serverProtocolConfig[config_key::responsePacketMagicHeader] = m_serverProtocolConfig[config_key::responsePacketMagicHeader] =
@@ -147,6 +154,17 @@ void AwgConfigModel::updateModel(const QJsonObject &config)
m_serverProtocolConfig[config_key::transportPacketMagicHeader] = m_serverProtocolConfig[config_key::transportPacketMagicHeader] =
serverProtocolConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader); serverProtocolConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader);
m_serverProtocolConfig[config_key::specialJunk1] =
serverProtocolConfig.value(config_key::specialJunk1).toString();
m_serverProtocolConfig[config_key::specialJunk2] =
serverProtocolConfig.value(config_key::specialJunk2).toString();
m_serverProtocolConfig[config_key::specialJunk3] =
serverProtocolConfig.value(config_key::specialJunk3).toString();
m_serverProtocolConfig[config_key::specialJunk4] =
serverProtocolConfig.value(config_key::specialJunk4).toString();
m_serverProtocolConfig[config_key::specialJunk5] =
serverProtocolConfig.value(config_key::specialJunk5).toString();
auto lastConfig = m_serverProtocolConfig.value(config_key::last_config).toString(); auto lastConfig = m_serverProtocolConfig.value(config_key::last_config).toString();
QJsonObject clientProtocolConfig = QJsonDocument::fromJson(lastConfig.toUtf8()).object(); QJsonObject clientProtocolConfig = QJsonDocument::fromJson(lastConfig.toUtf8()).object();
m_clientProtocolConfig[config_key::mtu] = clientProtocolConfig[config_key::mtu].toString(protocols::awg::defaultMtu); m_clientProtocolConfig[config_key::mtu] = clientProtocolConfig[config_key::mtu].toString(protocols::awg::defaultMtu);
@@ -157,23 +175,15 @@ void AwgConfigModel::updateModel(const QJsonObject &config)
m_clientProtocolConfig[config_key::junkPacketMaxSize] = m_clientProtocolConfig[config_key::junkPacketMaxSize] =
clientProtocolConfig.value(config_key::junkPacketMaxSize).toString(m_serverProtocolConfig[config_key::junkPacketMaxSize].toString()); clientProtocolConfig.value(config_key::junkPacketMaxSize).toString(m_serverProtocolConfig[config_key::junkPacketMaxSize].toString());
m_clientProtocolConfig[config_key::specialJunk1] = m_clientProtocolConfig[config_key::specialJunk1] =
clientProtocolConfig.value(config_key::specialJunk1).toString(protocols::awg::defaultSpecialJunk1); clientProtocolConfig.value(config_key::specialJunk1).toString();
m_clientProtocolConfig[config_key::specialJunk2] = m_clientProtocolConfig[config_key::specialJunk2] =
clientProtocolConfig.value(config_key::specialJunk2).toString(protocols::awg::defaultSpecialJunk2); clientProtocolConfig.value(config_key::specialJunk2).toString();
m_clientProtocolConfig[config_key::specialJunk3] = m_clientProtocolConfig[config_key::specialJunk3] =
clientProtocolConfig.value(config_key::specialJunk3).toString(protocols::awg::defaultSpecialJunk3); clientProtocolConfig.value(config_key::specialJunk3).toString();
m_clientProtocolConfig[config_key::specialJunk4] = m_clientProtocolConfig[config_key::specialJunk4] =
clientProtocolConfig.value(config_key::specialJunk4).toString(protocols::awg::defaultSpecialJunk4); clientProtocolConfig.value(config_key::specialJunk4).toString();
m_clientProtocolConfig[config_key::specialJunk5] = m_clientProtocolConfig[config_key::specialJunk5] =
clientProtocolConfig.value(config_key::specialJunk5).toString(protocols::awg::defaultSpecialJunk5); clientProtocolConfig.value(config_key::specialJunk5).toString();
m_clientProtocolConfig[config_key::controlledJunk1] =
clientProtocolConfig.value(config_key::controlledJunk1).toString(protocols::awg::defaultControlledJunk1);
m_clientProtocolConfig[config_key::controlledJunk2] =
clientProtocolConfig.value(config_key::controlledJunk2).toString(protocols::awg::defaultControlledJunk2);
m_clientProtocolConfig[config_key::controlledJunk3] =
clientProtocolConfig.value(config_key::controlledJunk3).toString(protocols::awg::defaultControlledJunk3);
m_clientProtocolConfig[config_key::specialHandshakeTimeout] =
clientProtocolConfig.value(config_key::specialHandshakeTimeout).toString(protocols::awg::defaultSpecialHandshakeTimeout);
endResetModel(); endResetModel();
} }
@@ -196,14 +206,26 @@ QJsonObject AwgConfigModel::getConfig()
jsonConfig[config_key::specialJunk3] = m_clientProtocolConfig[config_key::specialJunk3].toString().trimmed(); jsonConfig[config_key::specialJunk3] = m_clientProtocolConfig[config_key::specialJunk3].toString().trimmed();
jsonConfig[config_key::specialJunk4] = m_clientProtocolConfig[config_key::specialJunk4].toString().trimmed(); jsonConfig[config_key::specialJunk4] = m_clientProtocolConfig[config_key::specialJunk4].toString().trimmed();
jsonConfig[config_key::specialJunk5] = m_clientProtocolConfig[config_key::specialJunk5].toString().trimmed(); jsonConfig[config_key::specialJunk5] = m_clientProtocolConfig[config_key::specialJunk5].toString().trimmed();
jsonConfig[config_key::controlledJunk1] = m_clientProtocolConfig[config_key::controlledJunk1].toString().trimmed();
jsonConfig[config_key::controlledJunk2] = m_clientProtocolConfig[config_key::controlledJunk2].toString().trimmed();
jsonConfig[config_key::controlledJunk3] = m_clientProtocolConfig[config_key::controlledJunk3].toString().trimmed();
jsonConfig[config_key::specialHandshakeTimeout] = m_clientProtocolConfig[config_key::specialHandshakeTimeout];
m_serverProtocolConfig[config_key::last_config] = QString(QJsonDocument(jsonConfig).toJson()); m_serverProtocolConfig[config_key::last_config] = QString(QJsonDocument(jsonConfig).toJson());
} }
QString currentProtocolVersion = m_serverProtocolConfig.value(config_key::protocolVersion).toString();
if (currentProtocolVersion != protocols::awg::awgV2) {
bool hasSpecialJunk = !m_serverProtocolConfig.value(config_key::specialJunk1).toString().trimmed().isEmpty() ||
!m_serverProtocolConfig.value(config_key::specialJunk2).toString().trimmed().isEmpty() ||
!m_serverProtocolConfig.value(config_key::specialJunk3).toString().trimmed().isEmpty() ||
!m_serverProtocolConfig.value(config_key::specialJunk4).toString().trimmed().isEmpty() ||
!m_serverProtocolConfig.value(config_key::specialJunk5).toString().trimmed().isEmpty();
if (hasSpecialJunk) {
m_serverProtocolConfig[config_key::protocolVersion] = protocols::awg::awgV1_5;
} else {
m_serverProtocolConfig.remove(config_key::protocolVersion);
}
}
m_fullConfig.insert(config_key::awg, m_serverProtocolConfig); m_fullConfig.insert(config_key::awg, m_serverProtocolConfig);
return m_fullConfig; return m_fullConfig;
} }
@@ -213,22 +235,17 @@ bool AwgConfigModel::isHeadersEqual(const QString &h1, const QString &h2, const
return (h1 == h2) || (h1 == h3) || (h1 == h4) || (h2 == h3) || (h2 == h4) || (h3 == h4); return (h1 == h2) || (h1 == h3) || (h1 == h4) || (h2 == h3) || (h2 == h4) || (h3 == h4);
} }
bool AwgConfigModel::isPacketSizeEqual(const int s1, const int s2) bool AwgConfigModel::isPacketSizeEqual(const int s1, const int s2, const int s3, const int s4)
{ {
return (AwgConstant::messageInitiationSize + s1 == AwgConstant::messageResponseSize + s2); int initSize = AwgConstant::messageInitiationSize + s1;
int responseSize = AwgConstant::messageResponseSize + s2;
int cookieSize = AwgConstant::messageCookieReplySize + s3;
int transportSize = AwgConstant::messageTransportSize + s4;
return (initSize == responseSize || initSize == cookieSize || initSize == transportSize || responseSize == cookieSize
|| responseSize == transportSize || cookieSize == transportSize);
} }
// bool AwgConfigModel::isPacketSizeEqual(const int s1, const int s2, const int s3, const int s4)
// {
// int initSize = AwgConstant::messageInitiationSize + s1;
// int responseSize = AwgConstant::messageResponseSize + s2;
// int cookieSize = AwgConstant::messageCookieReplySize + s3;
// int transportSize = AwgConstant::messageTransportSize + s4;
// return (initSize == responseSize || initSize == cookieSize || initSize == transportSize || responseSize == cookieSize
// || responseSize == transportSize || cookieSize == transportSize);
// }
bool AwgConfigModel::isServerSettingsEqual() bool AwgConfigModel::isServerSettingsEqual()
{ {
const AwgConfig oldConfig(m_fullConfig.value(config_key::awg).toObject()); const AwgConfig oldConfig(m_fullConfig.value(config_key::awg).toObject());
@@ -253,10 +270,6 @@ QHash<int, QByteArray> AwgConfigModel::roleNames() const
roles[ClientSpecialJunk3Role] = "clientSpecialJunk3"; roles[ClientSpecialJunk3Role] = "clientSpecialJunk3";
roles[ClientSpecialJunk4Role] = "clientSpecialJunk4"; roles[ClientSpecialJunk4Role] = "clientSpecialJunk4";
roles[ClientSpecialJunk5Role] = "clientSpecialJunk5"; roles[ClientSpecialJunk5Role] = "clientSpecialJunk5";
roles[ClientControlledJunk1Role] = "clientControlledJunk1";
roles[ClientControlledJunk2Role] = "clientControlledJunk2";
roles[ClientControlledJunk3Role] = "clientControlledJunk3";
roles[ClientSpecialHandshakeTimeoutRole] = "clientSpecialHandshakeTimeout";
roles[ServerJunkPacketCountRole] = "serverJunkPacketCount"; roles[ServerJunkPacketCountRole] = "serverJunkPacketCount";
roles[ServerJunkPacketMinSizeRole] = "serverJunkPacketMinSize"; roles[ServerJunkPacketMinSizeRole] = "serverJunkPacketMinSize";
@@ -270,12 +283,21 @@ QHash<int, QByteArray> AwgConfigModel::roleNames() const
roles[ServerResponsePacketMagicHeaderRole] = "serverResponsePacketMagicHeader"; roles[ServerResponsePacketMagicHeaderRole] = "serverResponsePacketMagicHeader";
roles[ServerUnderloadPacketMagicHeaderRole] = "serverUnderloadPacketMagicHeader"; roles[ServerUnderloadPacketMagicHeaderRole] = "serverUnderloadPacketMagicHeader";
roles[ServerTransportPacketMagicHeaderRole] = "serverTransportPacketMagicHeader"; roles[ServerTransportPacketMagicHeaderRole] = "serverTransportPacketMagicHeader";
roles[ServerSpecialJunk1Role] = "serverSpecialJunk1";
roles[ServerSpecialJunk2Role] = "serverSpecialJunk2";
roles[ServerSpecialJunk3Role] = "serverSpecialJunk3";
roles[ServerSpecialJunk4Role] = "serverSpecialJunk4";
roles[ServerSpecialJunk5Role] = "serverSpecialJunk5";
roles[IsAwg2Role] = "isAwg2";
return roles; return roles;
} }
AwgConfig::AwgConfig(const QJsonObject &serverProtocolConfig) AwgConfig::AwgConfig(const QJsonObject &serverProtocolConfig)
{ {
m_isProtocolV2 = ProtocolProps::getProtocolVersion(serverProtocolConfig) == protocols::awg::awgV2;
auto lastConfig = serverProtocolConfig.value(config_key::last_config).toString(); auto lastConfig = serverProtocolConfig.value(config_key::last_config).toString();
QJsonObject clientProtocolConfig = QJsonDocument::fromJson(lastConfig.toUtf8()).object(); QJsonObject clientProtocolConfig = QJsonDocument::fromJson(lastConfig.toUtf8()).object();
clientMtu = clientProtocolConfig[config_key::mtu].toString(protocols::awg::defaultMtu); clientMtu = clientProtocolConfig[config_key::mtu].toString(protocols::awg::defaultMtu);
@@ -287,11 +309,6 @@ AwgConfig::AwgConfig(const QJsonObject &serverProtocolConfig)
clientSpecialJunk3 = clientProtocolConfig.value(config_key::specialJunk3).toString(protocols::awg::defaultSpecialJunk3); clientSpecialJunk3 = clientProtocolConfig.value(config_key::specialJunk3).toString(protocols::awg::defaultSpecialJunk3);
clientSpecialJunk4 = clientProtocolConfig.value(config_key::specialJunk4).toString(protocols::awg::defaultSpecialJunk4); clientSpecialJunk4 = clientProtocolConfig.value(config_key::specialJunk4).toString(protocols::awg::defaultSpecialJunk4);
clientSpecialJunk5 = clientProtocolConfig.value(config_key::specialJunk5).toString(protocols::awg::defaultSpecialJunk5); clientSpecialJunk5 = clientProtocolConfig.value(config_key::specialJunk5).toString(protocols::awg::defaultSpecialJunk5);
clientControlledJunk1 = clientProtocolConfig.value(config_key::controlledJunk1).toString(protocols::awg::defaultControlledJunk1);
clientControlledJunk2 = clientProtocolConfig.value(config_key::controlledJunk2).toString(protocols::awg::defaultControlledJunk2);
clientControlledJunk3 = clientProtocolConfig.value(config_key::controlledJunk3).toString(protocols::awg::defaultControlledJunk3);
clientSpecialHandshakeTimeout =
clientProtocolConfig.value(config_key::specialHandshakeTimeout).toString(protocols::awg::defaultSpecialHandshakeTimeout);
subnetAddress = serverProtocolConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress); subnetAddress = serverProtocolConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress);
port = serverProtocolConfig.value(config_key::port).toString(protocols::awg::defaultPort); port = serverProtocolConfig.value(config_key::port).toString(protocols::awg::defaultPort);
@@ -301,10 +318,14 @@ AwgConfig::AwgConfig(const QJsonObject &serverProtocolConfig)
serverInitPacketJunkSize = serverProtocolConfig.value(config_key::initPacketJunkSize).toString(protocols::awg::defaultInitPacketJunkSize); serverInitPacketJunkSize = serverProtocolConfig.value(config_key::initPacketJunkSize).toString(protocols::awg::defaultInitPacketJunkSize);
serverResponsePacketJunkSize = serverResponsePacketJunkSize =
serverProtocolConfig.value(config_key::responsePacketJunkSize).toString(protocols::awg::defaultResponsePacketJunkSize); serverProtocolConfig.value(config_key::responsePacketJunkSize).toString(protocols::awg::defaultResponsePacketJunkSize);
// serverCookieReplyPacketJunkSize =
// serverProtocolConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize); if (m_isProtocolV2) {
// serverTransportPacketJunkSize = serverCookieReplyPacketJunkSize =
// serverProtocolConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize); serverProtocolConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize);
serverTransportPacketJunkSize =
serverProtocolConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize);
}
serverInitPacketMagicHeader = serverInitPacketMagicHeader =
serverProtocolConfig.value(config_key::initPacketMagicHeader).toString(protocols::awg::defaultInitPacketMagicHeader); serverProtocolConfig.value(config_key::initPacketMagicHeader).toString(protocols::awg::defaultInitPacketMagicHeader);
serverResponsePacketMagicHeader = serverResponsePacketMagicHeader =
@@ -313,6 +334,11 @@ AwgConfig::AwgConfig(const QJsonObject &serverProtocolConfig)
serverProtocolConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader); serverProtocolConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader);
serverTransportPacketMagicHeader = serverTransportPacketMagicHeader =
serverProtocolConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader); serverProtocolConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader);
serverSpecialJunk1 = serverProtocolConfig.value(config_key::specialJunk1).toString(protocols::awg::defaultSpecialJunk1);
serverSpecialJunk2 = serverProtocolConfig.value(config_key::specialJunk2).toString(protocols::awg::defaultSpecialJunk2);
serverSpecialJunk3 = serverProtocolConfig.value(config_key::specialJunk3).toString(protocols::awg::defaultSpecialJunk3);
serverSpecialJunk4 = serverProtocolConfig.value(config_key::specialJunk4).toString(protocols::awg::defaultSpecialJunk4);
serverSpecialJunk5 = serverProtocolConfig.value(config_key::specialJunk5).toString(protocols::awg::defaultSpecialJunk5);
} }
bool AwgConfig::hasEqualServerSettings(const AwgConfig &other) const bool AwgConfig::hasEqualServerSettings(const AwgConfig &other) const
@@ -320,14 +346,23 @@ bool AwgConfig::hasEqualServerSettings(const AwgConfig &other) const
if (subnetAddress != other.subnetAddress || port != other.port || serverJunkPacketCount != other.serverJunkPacketCount if (subnetAddress != other.subnetAddress || port != other.port || serverJunkPacketCount != other.serverJunkPacketCount
|| serverJunkPacketMinSize != other.serverJunkPacketMinSize || serverJunkPacketMaxSize != other.serverJunkPacketMaxSize || serverJunkPacketMinSize != other.serverJunkPacketMinSize || serverJunkPacketMaxSize != other.serverJunkPacketMaxSize
|| serverInitPacketJunkSize != other.serverInitPacketJunkSize || serverResponsePacketJunkSize != other.serverResponsePacketJunkSize || serverInitPacketJunkSize != other.serverInitPacketJunkSize || serverResponsePacketJunkSize != other.serverResponsePacketJunkSize
// || serverCookieReplyPacketJunkSize != other.serverCookieReplyPacketJunkSize
// || serverTransportPacketJunkSize != other.serverTransportPacketJunkSize
|| serverInitPacketMagicHeader != other.serverInitPacketMagicHeader || serverInitPacketMagicHeader != other.serverInitPacketMagicHeader
|| serverResponsePacketMagicHeader != other.serverResponsePacketMagicHeader || serverResponsePacketMagicHeader != other.serverResponsePacketMagicHeader
|| serverUnderloadPacketMagicHeader != other.serverUnderloadPacketMagicHeader || serverUnderloadPacketMagicHeader != other.serverUnderloadPacketMagicHeader
|| serverTransportPacketMagicHeader != other.serverTransportPacketMagicHeader) { || serverTransportPacketMagicHeader != other.serverTransportPacketMagicHeader
|| serverSpecialJunk1 != other.serverSpecialJunk1 || serverSpecialJunk2 != other.serverSpecialJunk2
|| serverSpecialJunk3 != other.serverSpecialJunk3 || serverSpecialJunk4 != other.serverSpecialJunk4
|| serverSpecialJunk5 != other.serverSpecialJunk5) {
return false; return false;
} }
if (m_isProtocolV2) {
if (serverCookieReplyPacketJunkSize != other.serverCookieReplyPacketJunkSize
|| serverTransportPacketJunkSize != other.serverTransportPacketJunkSize) {
return false;
}
}
return true; return true;
} }
@@ -337,9 +372,7 @@ bool AwgConfig::hasEqualClientSettings(const AwgConfig &other) const
|| clientJunkPacketMinSize != other.clientJunkPacketMinSize || clientJunkPacketMaxSize != other.clientJunkPacketMaxSize || clientJunkPacketMinSize != other.clientJunkPacketMinSize || clientJunkPacketMaxSize != other.clientJunkPacketMaxSize
|| clientSpecialJunk1 != other.clientSpecialJunk1 || clientSpecialJunk2 != other.clientSpecialJunk2 || clientSpecialJunk1 != other.clientSpecialJunk1 || clientSpecialJunk2 != other.clientSpecialJunk2
|| clientSpecialJunk3 != other.clientSpecialJunk3 || clientSpecialJunk4 != other.clientSpecialJunk4 || clientSpecialJunk3 != other.clientSpecialJunk3 || clientSpecialJunk4 != other.clientSpecialJunk4
|| clientSpecialJunk5 != other.clientSpecialJunk5 || clientControlledJunk1 != other.clientControlledJunk1 || clientSpecialJunk5 != other.clientSpecialJunk5) {
|| clientControlledJunk2 != other.clientControlledJunk2 || clientControlledJunk3 != other.clientControlledJunk3
|| clientSpecialHandshakeTimeout != other.clientSpecialHandshakeTimeout) {
return false; return false;
} }
return true; return true;
+17 -10
View File
@@ -16,7 +16,7 @@ namespace AwgConstant
struct AwgConfig struct AwgConfig
{ {
AwgConfig(const QJsonObject &jsonConfig); AwgConfig(const QJsonObject &serverProtocolConfig);
QString subnetAddress; QString subnetAddress;
QString port; QString port;
@@ -30,10 +30,6 @@ struct AwgConfig
QString clientSpecialJunk3; QString clientSpecialJunk3;
QString clientSpecialJunk4; QString clientSpecialJunk4;
QString clientSpecialJunk5; QString clientSpecialJunk5;
QString clientControlledJunk1;
QString clientControlledJunk2;
QString clientControlledJunk3;
QString clientSpecialHandshakeTimeout;
QString serverJunkPacketCount; QString serverJunkPacketCount;
QString serverJunkPacketMinSize; QString serverJunkPacketMinSize;
@@ -46,9 +42,17 @@ struct AwgConfig
QString serverResponsePacketMagicHeader; QString serverResponsePacketMagicHeader;
QString serverUnderloadPacketMagicHeader; QString serverUnderloadPacketMagicHeader;
QString serverTransportPacketMagicHeader; QString serverTransportPacketMagicHeader;
QString serverSpecialJunk1;
QString serverSpecialJunk2;
QString serverSpecialJunk3;
QString serverSpecialJunk4;
QString serverSpecialJunk5;
bool hasEqualServerSettings(const AwgConfig &other) const; bool hasEqualServerSettings(const AwgConfig &other) const;
bool hasEqualClientSettings(const AwgConfig &other) const; bool hasEqualClientSettings(const AwgConfig &other) const;
private:
bool m_isProtocolV2;
}; };
class AwgConfigModel : public QAbstractListModel class AwgConfigModel : public QAbstractListModel
@@ -69,10 +73,6 @@ public:
ClientSpecialJunk3Role, ClientSpecialJunk3Role,
ClientSpecialJunk4Role, ClientSpecialJunk4Role,
ClientSpecialJunk5Role, ClientSpecialJunk5Role,
ClientControlledJunk1Role,
ClientControlledJunk2Role,
ClientControlledJunk3Role,
ClientSpecialHandshakeTimeoutRole,
ServerJunkPacketCountRole, ServerJunkPacketCountRole,
ServerJunkPacketMinSizeRole, ServerJunkPacketMinSizeRole,
@@ -86,6 +86,13 @@ public:
ServerResponsePacketMagicHeaderRole, ServerResponsePacketMagicHeaderRole,
ServerUnderloadPacketMagicHeaderRole, ServerUnderloadPacketMagicHeaderRole,
ServerTransportPacketMagicHeaderRole, ServerTransportPacketMagicHeaderRole,
ServerSpecialJunk1Role,
ServerSpecialJunk2Role,
ServerSpecialJunk3Role,
ServerSpecialJunk4Role,
ServerSpecialJunk5Role,
IsAwg2Role
}; };
explicit AwgConfigModel(QObject *parent = nullptr); explicit AwgConfigModel(QObject *parent = nullptr);
@@ -100,7 +107,7 @@ public slots:
QJsonObject getConfig(); QJsonObject getConfig();
bool isHeadersEqual(const QString &h1, const QString &h2, const QString &h3, const QString &h4); bool isHeadersEqual(const QString &h1, const QString &h2, const QString &h3, const QString &h4);
bool isPacketSizeEqual(const int s1, const int s2/*, const int s3, const int s4*/); bool isPacketSizeEqual(const int s1, const int s2, const int s3, const int s4);
bool isServerSettingsEqual(); bool isServerSettingsEqual();
+3 -2
View File
@@ -42,7 +42,7 @@ QVariant ProtocolsModel::data(const QModelIndex &index, int role) const
return static_cast<int>(clientProtocolPage(ProtocolProps::protoFromString(m_content.keys().at(index.row())))); return static_cast<int>(clientProtocolPage(ProtocolProps::protoFromString(m_content.keys().at(index.row()))));
case ProtocolIndexRole: return ProtocolProps::protoFromString(m_content.keys().at(index.row())); case ProtocolIndexRole: return ProtocolProps::protoFromString(m_content.keys().at(index.row()));
case RawConfigRole: { case RawConfigRole: {
auto protocolConfig = m_content.value(ContainerProps::containerTypeToString(m_container)).toObject(); auto protocolConfig = m_content.value(ContainerProps::containerTypeToProtocolString(m_container)).toObject();
auto lastConfigJsonDoc = auto lastConfigJsonDoc =
QJsonDocument::fromJson(protocolConfig.value(config_key::last_config).toString().toUtf8()); QJsonDocument::fromJson(protocolConfig.value(config_key::last_config).toString().toUtf8());
auto lastConfigJson = lastConfigJsonDoc.object(); auto lastConfigJson = lastConfigJsonDoc.object();
@@ -55,7 +55,8 @@ QVariant ProtocolsModel::data(const QModelIndex &index, int role) const
return rawConfig; return rawConfig;
} }
case IsClientProtocolExistsRole: { case IsClientProtocolExistsRole: {
auto protocolConfig = m_content.value(ContainerProps::containerTypeToString(m_container)).toObject(); QString protocolKey = ContainerProps::containerTypeToProtocolString(m_container);
auto protocolConfig = m_content.value(protocolKey).toObject();
auto lastConfigJsonDoc = auto lastConfigJsonDoc =
QJsonDocument::fromJson(protocolConfig.value(config_key::last_config).toString().toUtf8()); QJsonDocument::fromJson(protocolConfig.value(config_key::last_config).toString().toUtf8());
auto lastConfigJson = lastConfigJsonDoc.object(); auto lastConfigJson = lastConfigJsonDoc.object();
+63 -9
View File
@@ -26,6 +26,15 @@ namespace
constexpr char publicKeyInfo[] = "public_key"; constexpr char publicKeyInfo[] = "public_key";
constexpr char expiresAt[] = "expires_at"; constexpr char expiresAt[] = "expires_at";
} }
QString normalizeVpnKey(const QString &vpnKey)
{
QString normalized = vpnKey.trimmed();
if (normalized.startsWith(QStringLiteral("vpn://"), Qt::CaseInsensitive)) {
normalized = normalized.mid(QStringLiteral("vpn://").size());
}
return normalized;
}
} }
ServersModel::ServersModel(std::shared_ptr<Settings> settings, QObject *parent) : m_settings(settings), QAbstractListModel(parent) ServersModel::ServersModel(std::shared_ptr<Settings> settings, QObject *parent) : m_settings(settings), QAbstractListModel(parent)
@@ -234,16 +243,29 @@ QString ServersModel::getServerDescription(const QJsonObject &server, const int
const QString ServersModel::getDefaultServerDescriptionCollapsed() const QString ServersModel::getDefaultServerDescriptionCollapsed()
{ {
const QJsonObject server = m_servers.at(m_defaultServerIndex).toObject(); const QJsonObject serverConfig = m_servers.at(m_defaultServerIndex).toObject();
const auto configVersion = server.value(config_key::configVersion).toInt(); const auto configVersion = serverConfig.value(config_key::configVersion).toInt();
auto description = getServerDescription(server, m_defaultServerIndex); auto description = getServerDescription(serverConfig, m_defaultServerIndex);
if (configVersion) { if (configVersion) {
return description; return description;
} }
auto container = ContainerProps::containerFromString(server.value(config_key::defaultContainer).toString()); auto container = ContainerProps::containerFromString(serverConfig.value(config_key::defaultContainer).toString());
QString protocolVersion;
QString containerName = ContainerProps::containerHumanNames().value(container);
return description += ContainerProps::containerHumanNames().value(container) + " | " + server.value(config_key::hostName).toString(); if (ContainerProps::isAwgContainer(container)) {
QJsonObject containerConfig = m_settings->containerConfig(m_defaultServerIndex, container);
QJsonObject serverProtocolConfig = containerConfig.value(ContainerProps::containerTypeToProtocolString(container)).toObject();
protocolVersion = ProtocolProps::getProtocolVersionString(serverProtocolConfig);
auto isThirdPartyConfig = serverProtocolConfig.value(config_key::isThirdPartyConfig).toBool();
if (container == DockerContainer::Awg && !isThirdPartyConfig) {
containerName = "AmneziaWG Legacy";
}
}
return description += containerName + protocolVersion + " | " + serverConfig.value(config_key::hostName).toString();
} }
const QString ServersModel::getDefaultServerDescriptionExpanded() const QString ServersModel::getDefaultServerDescriptionExpanded()
@@ -522,7 +544,22 @@ void ServersModel::setDefaultContainer(const int serverIndex, const int containe
const QString ServersModel::getDefaultServerDefaultContainerName() const QString ServersModel::getDefaultServerDefaultContainerName()
{ {
auto defaultContainer = qvariant_cast<DockerContainer>(getDefaultServerData("defaultContainer")); auto defaultContainer = qvariant_cast<DockerContainer>(getDefaultServerData("defaultContainer"));
return ContainerProps::containerHumanNames().value(defaultContainer);
QString protocolVersion;
QString containerName = ContainerProps::containerHumanNames().value(defaultContainer);
if (ContainerProps::isAwgContainer(defaultContainer)) {
QJsonObject containerConfig = m_settings->containerConfig(m_defaultServerIndex, defaultContainer);
QJsonObject serverProtocolConfig = containerConfig.value(ContainerProps::containerTypeToProtocolString(defaultContainer)).toObject();
protocolVersion = ProtocolProps::getProtocolVersionString(serverProtocolConfig);
auto isThirdPartyConfig = serverProtocolConfig.value(config_key::isThirdPartyConfig).toBool();
if (defaultContainer == DockerContainer::Awg && !isThirdPartyConfig) {
containerName = "AmneziaWG Legacy";
}
}
return containerName + protocolVersion;
} }
ErrorCode ServersModel::removeAllContainers(const QSharedPointer<ServerController> &serverController) ErrorCode ServersModel::removeAllContainers(const QSharedPointer<ServerController> &serverController)
@@ -690,6 +727,23 @@ bool ServersModel::isServerFromApiAlreadyExists(const QString &userCountryCode,
return false; return false;
} }
bool ServersModel::hasServerWithVpnKey(const QString &vpnKey) const
{
const QString normalizedInput = normalizeVpnKey(vpnKey);
if (normalizedInput.isEmpty()) {
return false;
}
for (const auto &server : std::as_const(m_servers)) {
const auto apiConfig = server.toObject().value(configKey::apiConfig).toObject();
const QString existingKey = normalizeVpnKey(apiConfig.value(apiDefs::key::vpnKey).toString());
if (!existingKey.isEmpty() && existingKey == normalizedInput) {
return true;
}
}
return false;
}
bool ServersModel::serverHasInstalledContainers(const int serverIndex) const bool ServersModel::serverHasInstalledContainers(const int serverIndex) const
{ {
QJsonObject server = m_servers.at(serverIndex).toObject(); QJsonObject server = m_servers.at(serverIndex).toObject();
@@ -753,8 +807,8 @@ bool ServersModel::isDefaultServerDefaultContainerHasSplitTunneling()
if (container.value(config_key::container).toString() != ContainerProps::containerToString(defaultContainer)) { if (container.value(config_key::container).toString() != ContainerProps::containerToString(defaultContainer)) {
continue; continue;
} }
if (defaultContainer == DockerContainer::Awg || defaultContainer == DockerContainer::WireGuard) { if (ContainerProps::isAwgContainer(defaultContainer) || defaultContainer == DockerContainer::WireGuard) {
QJsonObject serverProtocolConfig = container.value(ContainerProps::containerTypeToString(defaultContainer)).toObject(); QJsonObject serverProtocolConfig = container.value(ContainerProps::containerTypeToProtocolString(defaultContainer)).toObject();
QString clientProtocolConfigString = serverProtocolConfig.value(config_key::last_config).toString(); QString clientProtocolConfigString = serverProtocolConfig.value(config_key::last_config).toString();
QJsonObject clientProtocolConfig = QJsonDocument::fromJson(clientProtocolConfigString.toUtf8()).object(); QJsonObject clientProtocolConfig = QJsonDocument::fromJson(clientProtocolConfigString.toUtf8()).object();
return (clientProtocolConfigString.contains("AllowedIPs") && !clientProtocolConfigString.contains("AllowedIPs = 0.0.0.0/0, ::/0")) return (clientProtocolConfigString.contains("AllowedIPs") && !clientProtocolConfigString.contains("AllowedIPs = 0.0.0.0/0, ::/0"))
@@ -762,7 +816,7 @@ bool ServersModel::isDefaultServerDefaultContainerHasSplitTunneling()
&& !clientProtocolConfig.value(config_key::allowed_ips).toArray().contains("0.0.0.0/0")); && !clientProtocolConfig.value(config_key::allowed_ips).toArray().contains("0.0.0.0/0"));
} else if (defaultContainer == DockerContainer::Cloak || defaultContainer == DockerContainer::OpenVpn } else if (defaultContainer == DockerContainer::Cloak || defaultContainer == DockerContainer::OpenVpn
|| defaultContainer == DockerContainer::ShadowSocks) { || defaultContainer == DockerContainer::ShadowSocks) {
auto serverProtocolConfig = container.value(ContainerProps::containerTypeToString(DockerContainer::OpenVpn)).toObject(); auto serverProtocolConfig = container.value(ContainerProps::containerTypeToProtocolString(DockerContainer::OpenVpn)).toObject();
QString clientProtocolConfigString = serverProtocolConfig.value(config_key::last_config).toString(); QString clientProtocolConfigString = serverProtocolConfig.value(config_key::last_config).toString();
return !clientProtocolConfigString.isEmpty() && !clientProtocolConfigString.contains("redirect-gateway"); return !clientProtocolConfigString.isEmpty() && !clientProtocolConfigString.contains("redirect-gateway");
} }

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