Compare commits

..

31 Commits

Author SHA1 Message Date
spectrum 40cc856ece feat: add support for tvOS platform 2026-04-14 16:34:04 +03:00
spectrum c551d759bb refactor: separate network extension sources for different protocols and platforms 2026-02-16 15:15:52 +02:00
spectrum d00d409cda feat: add Apple TV support for network extension and refactor ios cmake scripts 2026-02-13 16:51:44 +02:00
spectrum c897963bac feat: add support for Apple TV target settings for iOS/Xcode projects 2026-02-13 00:17:19 +02: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
160 changed files with 4181 additions and 7591 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
+39 -4
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")
@@ -24,6 +24,8 @@ elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Android")
set(MZ_PLATFORM_NAME "android") set(MZ_PLATFORM_NAME "android")
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "iOS") elseif(${CMAKE_SYSTEM_NAME} STREQUAL "iOS")
set(MZ_PLATFORM_NAME "ios") set(MZ_PLATFORM_NAME "ios")
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "tvOS")
set(MZ_PLATFORM_NAME "ios")
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Emscripten") elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Emscripten")
set(MZ_PLATFORM_NAME "wasm") set(MZ_PLATFORM_NAME "wasm")
endif() endif()
@@ -33,7 +35,7 @@ set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(APPLE) if(APPLE)
if(IOS) if(IOS OR CMAKE_SYSTEM_NAME STREQUAL "tvOS")
set(CMAKE_OSX_ARCHITECTURES "arm64") set(CMAKE_OSX_ARCHITECTURES "arm64")
elseif(MACOS_NE) elseif(MACOS_NE)
set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64") set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64")
@@ -44,8 +46,41 @@ endif()
add_subdirectory(client) add_subdirectory(client)
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE) if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE AND NOT CMAKE_SYSTEM_NAME STREQUAL "tvOS")
add_subdirectory(service) add_subdirectory(service)
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 AND NOT CMAKE_SYSTEM_NAME STREQUAL "tvOS")
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()
+8 -8
View File
@@ -33,7 +33,7 @@ add_definitions(-DDEV_S3_ENDPOINT="$ENV{DEV_S3_ENDPOINT}")
add_definitions(-DFREE_V2_ENDPOINT="$ENV{FREE_V2_ENDPOINT}") add_definitions(-DFREE_V2_ENDPOINT="$ENV{FREE_V2_ENDPOINT}")
add_definitions(-DPREM_V1_ENDPOINT="$ENV{PREM_V1_ENDPOINT}") add_definitions(-DPREM_V1_ENDPOINT="$ENV{PREM_V1_ENDPOINT}")
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) if(WIN32 OR (APPLE AND NOT IOS AND NOT CMAKE_SYSTEM_NAME STREQUAL "tvOS") OR (LINUX AND NOT ANDROID))
set(PACKAGES ${PACKAGES} Widgets) set(PACKAGES ${PACKAGES} Widgets)
endif() endif()
@@ -46,7 +46,7 @@ set(LIBS ${LIBS}
Qt6::Core5Compat Qt6::Concurrent Qt6::Core5Compat Qt6::Concurrent
) )
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) if(WIN32 OR (APPLE AND NOT IOS AND NOT CMAKE_SYSTEM_NAME STREQUAL "tvOS") OR (LINUX AND NOT ANDROID))
set(LIBS ${LIBS} Qt6::Widgets) set(LIBS ${LIBS} Qt6::Widgets)
endif() endif()
@@ -56,7 +56,7 @@ target_include_directories(${PROJECT} PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}> $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
) )
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID)) if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE AND NOT CMAKE_SYSTEM_NAME STREQUAL "tvOS") OR (LINUX AND NOT ANDROID))
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep) qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep)
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_interface.rep) qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_interface.rep)
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_tun2socks.rep) qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_tun2socks.rep)
@@ -76,7 +76,6 @@ set(AMNEZIAVPN_TS_FILES
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_uk_UA.ts ${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_uk_UA.ts
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ur_PK.ts ${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ur_PK.ts
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_hi_IN.ts ${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_hi_IN.ts
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_es_ES.ts
) )
file(GLOB_RECURSE AMNEZIAVPN_TS_SOURCES *.qrc *.cpp *.h *.ui) file(GLOB_RECURSE AMNEZIAVPN_TS_SOURCES *.qrc *.cpp *.h *.ui)
@@ -177,7 +176,7 @@ if(LINUX AND NOT ANDROID)
link_directories(${CMAKE_CURRENT_LIST_DIR}/platforms/linux) link_directories(${CMAKE_CURRENT_LIST_DIR}/platforms/linux)
endif() endif()
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID)) if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE AND NOT CMAKE_SYSTEM_NAME STREQUAL "tvOS") OR (LINUX AND NOT ANDROID))
add_compile_definitions(AMNEZIA_DESKTOP) add_compile_definitions(AMNEZIA_DESKTOP)
endif() endif()
@@ -185,7 +184,8 @@ if(ANDROID)
include(cmake/android.cmake) include(cmake/android.cmake)
endif() endif()
if(IOS) if(IOS OR CMAKE_SYSTEM_NAME STREQUAL "tvOS")
option(AMNEZIA_IOS_ENABLE_APPLETV_TARGET "Enable Apple TV target settings for iOS/Xcode projects" OFF)
include(cmake/ios.cmake) include(cmake/ios.cmake)
include(cmake/ios-arch-fixup.cmake) include(cmake/ios-arch-fixup.cmake)
elseif(APPLE AND MACOS_NE) elseif(APPLE AND MACOS_NE)
@@ -207,11 +207,11 @@ if(WIN32)
endif() endif()
elseif(LINUX) elseif(LINUX)
set(DEPLOY_PLATFORM_PATH "linux/client") set(DEPLOY_PLATFORM_PATH "linux/client")
elseif(APPLE AND NOT IOS) elseif(APPLE AND NOT IOS AND NOT CMAKE_SYSTEM_NAME STREQUAL "tvOS")
set(DEPLOY_PLATFORM_PATH "macos") set(DEPLOY_PLATFORM_PATH "macos")
endif() endif()
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE) if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE AND NOT CMAKE_SYSTEM_NAME STREQUAL "tvOS")
add_custom_command( add_custom_command(
TARGET ${PROJECT} POST_BUILD TARGET ${PROJECT} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E $<IF:$<CONFIG:Debug>,copy_directory,true> COMMAND ${CMAKE_COMMAND} -E $<IF:$<CONFIG:Debug>,copy_directory,true>
+50 -13
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);
@@ -223,7 +250,7 @@ bool AmneziaApplication::parseCommands()
return true; return true;
} }
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE) #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(Q_OS_TVOS) && !defined(MACOS_NE)
void AmneziaApplication::startLocalServer() { void AmneziaApplication::startLocalServer() {
const QString serverName("AmneziaVPNInstance"); const QString serverName("AmneziaVPNInstance");
QLocalServer::removeServer(serverName); QLocalServer::removeServer(serverName);
@@ -244,11 +271,15 @@ void AmneziaApplication::startLocalServer() {
bool AmneziaApplication::eventFilter(QObject *watched, QEvent *event) bool AmneziaApplication::eventFilter(QObject *watched, QEvent *event)
{ {
if (event->type() == QEvent::Close) { if (event->type() == QEvent::Close) {
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(Q_OS_TVOS)
quit(); quit();
#else #else
if (m_coreController && m_coreController->pageController()) { if (m_forceQuit) {
m_coreController->pageController()->hideMainWindow(); quit();
} else {
if (m_coreController && m_coreController->pageController()) {
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;
+9 -3
View File
@@ -6,7 +6,7 @@
#include <QQmlApplicationEngine> #include <QQmlApplicationEngine>
#include <QQmlContext> #include <QQmlContext>
#include <QThread> #include <QThread>
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(Q_OS_TVOS)
#include <QGuiApplication> #include <QGuiApplication>
#else #else
#include <QApplication> #include <QApplication>
@@ -19,7 +19,7 @@
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance())) #define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(Q_OS_TVOS)
#define AMNEZIA_BASE_CLASS QGuiApplication #define AMNEZIA_BASE_CLASS QGuiApplication
#else #else
#define AMNEZIA_BASE_CLASS QApplication #define AMNEZIA_BASE_CLASS QApplication
@@ -37,7 +37,7 @@ public:
void loadFonts(); void loadFonts();
bool parseCommands(); bool parseCommands();
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE) #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(Q_OS_TVOS) && !defined(MACOS_NE)
void startLocalServer(); void startLocalServer();
#endif #endif
@@ -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")
+25 -5
View File
@@ -26,7 +26,7 @@ if(WIN32)
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/windows/win32/libssl.lib") set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/windows/win32/libssl.lib")
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/windows/win32/libcrypto.lib") set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/windows/win32/libcrypto.lib")
endif() endif()
elseif(APPLE AND NOT IOS) elseif(APPLE AND NOT IOS AND NOT CMAKE_SYSTEM_NAME STREQUAL "tvOS")
if(MACOS_NE) if(MACOS_NE)
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/universal2/libssh.a") set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/universal2/libssh.a")
set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/universal2/libz.a") set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/universal2/libz.a")
@@ -38,7 +38,25 @@ elseif(APPLE AND NOT IOS)
endif() endif()
set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/macos/include") set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/macos/include")
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libssl.a") set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libssl.a")
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libcrypto.a") set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libcrypto.a")
elseif(CMAKE_SYSTEM_NAME STREQUAL "tvOS")
set(TVOS_3RD_ROOT "$ENV{HOME}/Qt_tv/3rd-tvos")
execute_process(
COMMAND xcrun --sdk appletvos --show-sdk-path
OUTPUT_VARIABLE TVOS_SDK_PATH
OUTPUT_STRIP_TRAILING_WHITESPACE
)
set(LIBSSH_ROOT_DIR "${TVOS_3RD_ROOT}/libssh/0.10.6/appletvos-arm64")
set(OPENSSL_ROOT_DIR "${TVOS_3RD_ROOT}/openssl/3.0.13/appletvos-arm64")
set(OPENSSL_LIBRARIES_DIR "${OPENSSL_ROOT_DIR}/lib")
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}")
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/lib/libssh.a")
set(ZLIB_LIB_PATH "${TVOS_SDK_PATH}/usr/lib/libz.tbd")
set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/include")
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/lib/libssl.a")
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/lib/libcrypto.a")
elseif(IOS) elseif(IOS)
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/ios/arm64") set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/ios/arm64")
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/ios/arm64/libssh.a") set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/ios/arm64/libssh.a")
@@ -62,9 +80,11 @@ elseif(LINUX)
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/linux/x86_64/libssl.a") set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/linux/x86_64/libssl.a")
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/linux/x86_64/libcrypto.a") set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/linux/x86_64/libcrypto.a")
endif() endif()
file(COPY ${OPENSSL_LIB_SSL_PATH} ${OPENSSL_LIB_CRYPTO_PATH} if(NOT CMAKE_SYSTEM_NAME STREQUAL "tvOS")
DESTINATION ${OPENSSL_LIBRARIES_DIR}) file(COPY ${OPENSSL_LIB_SSL_PATH} ${OPENSSL_LIB_CRYPTO_PATH}
DESTINATION ${OPENSSL_LIBRARIES_DIR})
endif()
set(OPENSSL_USE_STATIC_LIBS TRUE) set(OPENSSL_USE_STATIC_LIBS TRUE)
+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)
+3 -1
View File
@@ -39,5 +39,7 @@ while(IOS_TARGETS)
set_target_properties(${TARGET_NAME} PROPERTIES set_target_properties(${TARGET_NAME} PROPERTIES
XCODE_ATTRIBUTE_ARCHS[sdk=iphoneos*] "arm64" XCODE_ATTRIBUTE_ARCHS[sdk=iphoneos*] "arm64"
XCODE_ATTRIBUTE_ARCHS[sdk=iphonesimulator*] "x86_64" XCODE_ATTRIBUTE_ARCHS[sdk=iphonesimulator*] "x86_64"
XCODE_ATTRIBUTE_ARCHS[sdk=appletvos*] "arm64"
XCODE_ATTRIBUTE_ARCHS[sdk=appletvsimulator*] "arm64"
) )
endwhile() endwhile()
+144 -27
View File
@@ -1,6 +1,20 @@
message("Client iOS build") message("Client iOS build")
set(CMAKE_OSX_DEPLOYMENT_TARGET 13.0) set(CMAKE_OSX_DEPLOYMENT_TARGET 13.0)
set(APPLE_PROJECT_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) set(APPLE_PROJECT_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
set(AMNEZIA_IOS_APPLETV ${AMNEZIA_IOS_ENABLE_APPLETV_TARGET})
if(AMNEZIA_IOS_APPLETV)
message("Apple TV target mode is ON")
set(CMAKE_OSX_DEPLOYMENT_TARGET 17.0)
set(QT_NO_SET_DEFAULT_IOS_LAUNCH_SCREEN TRUE)
set(QT_NO_ADD_IOS_LAUNCH_SCREEN_TO_BUNDLE TRUE)
set(IOS_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Info-tvOS.plist.in)
set(IOS_LAUNCHSCREEN_STORYBOARD ${CMAKE_CURRENT_SOURCE_DIR}/ios/app/tvOS/AmneziaVPNLaunchScreen.storyboard)
else()
message("Apple TV target mode is OFF")
set(IOS_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Info.plist.in)
set(IOS_LAUNCHSCREEN_STORYBOARD ${CMAKE_CURRENT_SOURCE_DIR}/ios/app/AmneziaVPNLaunchScreen.storyboard)
endif()
enable_language(OBJC) enable_language(OBJC)
@@ -10,13 +24,23 @@ enable_language(Swift)
find_package(Qt6 REQUIRED COMPONENTS ShaderTools) find_package(Qt6 REQUIRED COMPONENTS ShaderTools)
set(LIBS ${LIBS} Qt6::ShaderTools) set(LIBS ${LIBS} Qt6::ShaderTools)
find_library(FW_AUTHENTICATIONSERVICES AuthenticationServices) if(AMNEZIA_IOS_APPLETV)
find_library(FW_UIKIT UIKit) # Use framework linker flags directly for tvOS to avoid iPhoneOS SDK absolute paths.
find_library(FW_AVFOUNDATION AVFoundation) set(FW_AUTHENTICATIONSERVICES "-framework AuthenticationServices")
find_library(FW_FOUNDATION Foundation) set(FW_UIKIT "-framework UIKit")
find_library(FW_STOREKIT StoreKit) set(FW_AVFOUNDATION "-framework AVFoundation")
find_library(FW_USERNOTIFICATIONS UserNotifications) set(FW_FOUNDATION "-framework Foundation")
find_library(FW_NETWORKEXTENSION NetworkExtension) set(FW_STOREKIT "-framework StoreKit")
set(FW_USERNOTIFICATIONS "-framework UserNotifications")
else()
find_library(FW_AUTHENTICATIONSERVICES AuthenticationServices)
find_library(FW_UIKIT UIKit)
find_library(FW_AVFOUNDATION AVFoundation)
find_library(FW_FOUNDATION Foundation)
find_library(FW_STOREKIT StoreKit)
find_library(FW_USERNOTIFICATIONS UserNotifications)
find_library(FW_NETWORKEXTENSION NetworkExtension)
endif()
set(LIBS ${LIBS} set(LIBS ${LIBS}
${FW_AUTHENTICATIONSERVICES} ${FW_AUTHENTICATIONSERVICES}
@@ -25,15 +49,19 @@ set(LIBS ${LIBS}
${FW_FOUNDATION} ${FW_FOUNDATION}
${FW_STOREKIT} ${FW_STOREKIT}
${FW_USERNOTIFICATIONS} ${FW_USERNOTIFICATIONS}
${FW_NETWORKEXTENSION}
) )
if(NOT AMNEZIA_IOS_APPLETV)
set(LIBS ${LIBS} ${FW_NETWORKEXTENSION})
endif()
set(HEADERS ${HEADERS} 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/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 +74,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
) )
@@ -55,7 +84,7 @@ target_include_directories(${PROJECT} PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS})
set_target_properties(${PROJECT} PROPERTIES set_target_properties(${PROJECT} PROPERTIES
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Info.plist.in MACOSX_BUNDLE_INFO_PLIST ${IOS_INFO_PLIST}
MACOSX_BUNDLE_ICON_FILE "AppIcon" MACOSX_BUNDLE_ICON_FILE "AppIcon"
MACOSX_BUNDLE_INFO_STRING "AmneziaVPN" MACOSX_BUNDLE_INFO_STRING "AmneziaVPN"
MACOSX_BUNDLE_BUNDLE_NAME "AmneziaVPN" MACOSX_BUNDLE_BUNDLE_NAME "AmneziaVPN"
@@ -64,7 +93,6 @@ set_target_properties(${PROJECT} PROPERTIES
MACOSX_BUNDLE_LONG_VERSION_STRING "${APPLE_PROJECT_VERSION}-${CMAKE_PROJECT_VERSION_TWEAK}" MACOSX_BUNDLE_LONG_VERSION_STRING "${APPLE_PROJECT_VERSION}-${CMAKE_PROJECT_VERSION_TWEAK}"
MACOSX_BUNDLE_SHORT_VERSION_STRING "${APPLE_PROJECT_VERSION}" MACOSX_BUNDLE_SHORT_VERSION_STRING "${APPLE_PROJECT_VERSION}"
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}" XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}"
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/ios/app/main.entitlements"
XCODE_ATTRIBUTE_MARKETING_VERSION "${APPLE_PROJECT_VERSION}" XCODE_ATTRIBUTE_MARKETING_VERSION "${APPLE_PROJECT_VERSION}"
XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}" XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
XCODE_ATTRIBUTE_PRODUCT_NAME "AmneziaVPN" XCODE_ATTRIBUTE_PRODUCT_NAME "AmneziaVPN"
@@ -72,13 +100,36 @@ set_target_properties(${PROJECT} PROPERTIES
XCODE_GENERATE_SCHEME TRUE XCODE_GENERATE_SCHEME TRUE
XCODE_ATTRIBUTE_ENABLE_BITCODE "NO" XCODE_ATTRIBUTE_ENABLE_BITCODE "NO"
XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME "AppIcon" XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME "AppIcon"
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2"
XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY ON XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY ON
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/Frameworks" XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/Frameworks"
XCODE_EMBED_APP_EXTENSIONS networkextension XCODE_EMBED_APP_EXTENSIONS networkextension
) )
if(AMNEZIA_IOS_APPLETV)
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_SUPPORTED_PLATFORMS "appletvos appletvsimulator"
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "3"
XCODE_ATTRIBUTE_TVOS_DEPLOYMENT_TARGET "${CMAKE_OSX_DEPLOYMENT_TARGET}"
XCODE_ATTRIBUTE_SDKROOT "appletvos"
XCODE_ATTRIBUTE_SDKROOT[sdk=appletvos*] "appletvos"
XCODE_ATTRIBUTE_SDKROOT[sdk=appletvsimulator*] "appletvsimulator"
XCODE_ATTRIBUTE_LIBRARY_SEARCH_PATHS "$(inherited) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)"
XCODE_ATTRIBUTE_LIBRARY_SEARCH_PATHS[sdk=appletvos*] "$(inherited) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)"
XCODE_ATTRIBUTE_LIBRARY_SEARCH_PATHS[sdk=appletvsimulator*] "$(inherited) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)"
XCODE_ATTRIBUTE_EXCLUDED_LIBRARY_SEARCH_PATHS "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS*.sdk/usr/lib/swift"
XCODE_ATTRIBUTE_EXCLUDED_FRAMEWORK_SEARCH_PATHS "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS*.sdk/System/Library/Frameworks"
)
set_target_properties(${PROJECT} PROPERTIES
QT_IOS_PERMISSIONS ""
)
else()
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/ios/app/main.entitlements"
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2"
)
endif()
if(DEFINED DEPLOY) if(DEFINED DEPLOY)
set_target_properties(${PROJECT} PROPERTIES set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution" XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
@@ -109,6 +160,59 @@ target_compile_options(${PROJECT} PRIVATE
-DVPN_NE_BUNDLEID=\"${BUILD_IOS_APP_IDENTIFIER}.network-extension\" -DVPN_NE_BUNDLEID=\"${BUILD_IOS_APP_IDENTIFIER}.network-extension\"
) )
if(AMNEZIA_IOS_APPLETV)
# qscnetworkreachability plugin links IOKit, which is unavailable on tvOS.
qt_import_plugins(${PROJECT}
NO_DEFAULT
INCLUDE
QIOSIntegrationPlugin
QJpegPlugin
QSvgPlugin
QGifPlugin
QICOPlugin
QSvgIconPlugin
QSecureTransportBackendPlugin
EXCLUDE
QSCNetworkReachabilityNetworkInformationPlugin
QDarwinCameraPermissionPlugin
)
# Static tvOS Qt build doesn't auto-link these plugin archives into the
# Xcode target, but the app entry point lives in QIOSIntegrationPlugin.
set(_amnezia_tvos_static_plugins
Qt6::QIOSIntegrationPlugin
Qt6::QIOSIntegrationPlugin_init
Qt6::QJpegPlugin
Qt6::QJpegPlugin_init
Qt6::QSvgPlugin
Qt6::QSvgPlugin_init
Qt6::QGifPlugin
Qt6::QGifPlugin_init
Qt6::QICOPlugin
Qt6::QICOPlugin_init
Qt6::QSvgIconPlugin
Qt6::QSvgIconPlugin_init
Qt6::QSecureTransportBackendPlugin
Qt6::QSecureTransportBackendPlugin_init
)
foreach(_amnezia_tvos_static_plugin IN LISTS _amnezia_tvos_static_plugins)
if(TARGET ${_amnezia_tvos_static_plugin})
target_link_libraries(${PROJECT} PRIVATE ${_amnezia_tvos_static_plugin})
endif()
endforeach()
unset(_amnezia_tvos_static_plugin)
unset(_amnezia_tvos_static_plugins)
# Qt 6.9.2 iOS package links IOKit via Qt6::Core interface, but tvOS SDK
# does not provide IOKit. Strip this single framework for Apple TV builds.
get_target_property(_qtcore_iface_libs Qt6::Core INTERFACE_LINK_LIBRARIES)
if(_qtcore_iface_libs)
string(REPLACE "-framework IOKit;" "" _qtcore_iface_libs "${_qtcore_iface_libs}")
string(REPLACE ";-framework IOKit" "" _qtcore_iface_libs "${_qtcore_iface_libs}")
set_property(TARGET Qt6::Core PROPERTY INTERFACE_LINK_LIBRARIES "${_qtcore_iface_libs}")
endif()
endif()
set(WG_APPLE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rd/amneziawg-apple/Sources) set(WG_APPLE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rd/amneziawg-apple/Sources)
target_sources(${PROJECT} PRIVATE target_sources(${PROJECT} PRIVATE
@@ -121,25 +225,38 @@ target_sources(${PROJECT} PRIVATE
${CLIENT_ROOT_DIR}/platforms/ios/VPNCController.swift ${CLIENT_ROOT_DIR}/platforms/ios/VPNCController.swift
) )
target_sources(${PROJECT} PRIVATE if(IOS_LAUNCHSCREEN_STORYBOARD)
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/AmneziaVPNLaunchScreen.storyboard target_sources(${PROJECT} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Media.xcassets ${IOS_LAUNCHSCREEN_STORYBOARD}
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy ${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Media.xcassets
) ${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
)
set_property(TARGET ${PROJECT} APPEND PROPERTY RESOURCE set_property(TARGET ${PROJECT} APPEND PROPERTY RESOURCE
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/AmneziaVPNLaunchScreen.storyboard ${IOS_LAUNCHSCREEN_STORYBOARD}
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Media.xcassets ${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Media.xcassets
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy ${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
) )
else()
target_sources(${PROJECT} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Media.xcassets
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
)
set_property(TARGET ${PROJECT} APPEND PROPERTY RESOURCE
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Media.xcassets
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
)
endif()
add_subdirectory(ios/networkextension) add_subdirectory(ios/networkextension)
add_dependencies(${PROJECT} networkextension) add_dependencies(${PROJECT} networkextension)
set_property(TARGET ${PROJECT} PROPERTY XCODE_EMBED_FRAMEWORKS if(NOT AMNEZIA_IOS_APPLETV)
"${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/OpenVPNAdapter.framework" set_property(TARGET ${PROJECT} PROPERTY XCODE_EMBED_FRAMEWORKS
) "${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/OpenVPNAdapter.framework"
)
set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/)
target_link_libraries("networkextension" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/OpenVPNAdapter.framework")
set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/)
target_link_libraries("networkextension" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/OpenVPNAdapter.framework")
endif()
+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
+4 -4
View File
@@ -39,7 +39,7 @@ set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/mozilla/controllerimpl.h ${CLIENT_ROOT_DIR}/mozilla/controllerimpl.h
) )
if(NOT IOS AND NOT MACOS_NE) if(NOT IOS AND NOT MACOS_NE AND NOT CMAKE_SYSTEM_NAME STREQUAL "tvOS")
set(HEADERS ${HEADERS} set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/platforms/ios/QRCodeReaderBase.h ${CLIENT_ROOT_DIR}/platforms/ios/QRCodeReaderBase.h
) )
@@ -89,14 +89,14 @@ set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/mozilla/shared/leakdetector.cpp ${CLIENT_ROOT_DIR}/mozilla/shared/leakdetector.cpp
) )
if(NOT IOS AND NOT MACOS_NE) if(NOT IOS AND NOT MACOS_NE AND NOT CMAKE_SYSTEM_NAME STREQUAL "tvOS")
set(SOURCES ${SOURCES} set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/platforms/ios/QRCodeReaderBase.cpp ${CLIENT_ROOT_DIR}/platforms/ios/QRCodeReaderBase.cpp
) )
endif() endif()
# Include native macOS platform helpers (dock/status-item) # Include native macOS platform helpers (dock/status-item)
if(APPLE AND NOT IOS) if(APPLE AND NOT IOS AND NOT CMAKE_SYSTEM_NAME STREQUAL "tvOS")
list(APPEND HEADERS list(APPEND HEADERS
${CLIENT_ROOT_DIR}/platforms/macos/macosutils.h ${CLIENT_ROOT_DIR}/platforms/macos/macosutils.h
${CLIENT_ROOT_DIR}/platforms/macos/macosstatusicon.h ${CLIENT_ROOT_DIR}/platforms/macos/macosstatusicon.h
@@ -175,7 +175,7 @@ if(WIN32)
) )
endif() endif()
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID)) if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE AND NOT CMAKE_SYSTEM_NAME STREQUAL "tvOS") OR (LINUX AND NOT ANDROID))
message("Client desktop build") message("Client desktop build")
add_compile_definitions(AMNEZIA_DESKTOP) add_compile_definitions(AMNEZIA_DESKTOP)
+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);
@@ -1,17 +1,13 @@
#include "openvpn_configurator.h" #include "openvpn_configurator.h"
#include <QDebug> #include <QDebug>
#include <QCoreApplication>
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
#include <QProcess> #include <QProcess>
#include <QString> #include <QString>
#include <QTemporaryDir> #include <QTemporaryDir>
#include <QTemporaryFile> #include <QTemporaryFile>
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#include <QGuiApplication>
#else
#include <QApplication>
#endif
#include "core/networkUtilities.h" #include "core/networkUtilities.h"
#include "containers/containers_defs.h" #include "containers/containers_defs.h"
@@ -165,7 +161,7 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPair<QString,
QString dnsConf = QString("\nscript-security 2\n" QString dnsConf = QString("\nscript-security 2\n"
"up %1/update-resolv-conf.sh\n" "up %1/update-resolv-conf.sh\n"
"down %1/update-resolv-conf.sh\n") "down %1/update-resolv-conf.sh\n")
.arg(qApp->applicationDirPath()); .arg(QCoreApplication::applicationDirPath());
config.append(dnsConf); config.append(dnsConf);
#endif #endif
+7 -11
View File
@@ -1,6 +1,7 @@
#include "ssh_configurator.h" #include "ssh_configurator.h"
#include <QDebug> #include <QDebug>
#include <QCoreApplication>
#include <QObject> #include <QObject>
#include <QProcess> #include <QProcess>
#include <QString> #include <QString>
@@ -8,11 +9,6 @@
#include <QTemporaryFile> #include <QTemporaryFile>
#include <QThread> #include <QThread>
#include <qtimer.h> #include <qtimer.h>
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE)
#include <QGuiApplication>
#else
#include <QApplication>
#endif
#include "core/server_defs.h" #include "core/server_defs.h"
#include "utilities.h" #include "utilities.h"
@@ -24,7 +20,7 @@ SshConfigurator::SshConfigurator(std::shared_ptr<Settings> settings, const QShar
QString SshConfigurator::convertOpenSShKey(const QString &key) QString SshConfigurator::convertOpenSShKey(const QString &key)
{ {
#if !defined(Q_OS_IOS) && !defined(MACOS_NE) #if !defined(Q_OS_IOS) && !defined(Q_OS_TVOS) && !defined(MACOS_NE)
QProcess p; QProcess p;
p.setProcessChannelMode(QProcess::MergedChannels); p.setProcessChannelMode(QProcess::MergedChannels);
@@ -70,13 +66,13 @@ QString SshConfigurator::convertOpenSShKey(const QString &key)
// DEAD CODE. // DEAD CODE.
void SshConfigurator::openSshTerminal(const ServerCredentials &credentials) void SshConfigurator::openSshTerminal(const ServerCredentials &credentials)
{ {
#if !defined(Q_OS_IOS) && !defined(MACOS_NE) #if !defined(Q_OS_IOS) && !defined(Q_OS_TVOS) && !defined(MACOS_NE)
QProcess *p = new QProcess(); QProcess *p = new QProcess();
p->setProcessChannelMode(QProcess::SeparateChannels); p->setProcessChannelMode(QProcess::SeparateChannels);
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
p->setProcessEnvironment(prepareEnv()); p->setProcessEnvironment(prepareEnv());
p->setProgram(qApp->applicationDirPath() + "\\cygwin\\putty.exe"); p->setProgram(QCoreApplication::applicationDirPath() + "\\cygwin\\putty.exe");
if (credentials.secretData.contains("PRIVATE KEY")) { if (credentials.secretData.contains("PRIVATE KEY")) {
// todo: connect by key // todo: connect by key
@@ -100,10 +96,10 @@ QProcessEnvironment SshConfigurator::prepareEnv()
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
pathEnvVar.clear(); pathEnvVar.clear();
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\cygwin;"); pathEnvVar.prepend(QDir::toNativeSeparators(QCoreApplication::applicationDirPath()) + "\\cygwin;");
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\openvpn;"); pathEnvVar.prepend(QDir::toNativeSeparators(QCoreApplication::applicationDirPath()) + "\\openvpn;");
#elif defined(Q_OS_MACX) && !defined(MACOS_NE) #elif defined(Q_OS_MACX) && !defined(MACOS_NE)
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "/Contents/MacOS"); pathEnvVar.prepend(QDir::toNativeSeparators(QCoreApplication::applicationDirPath()) + "/Contents/MacOS");
#endif #endif
env.insert("PATH", pathEnvVar); env.insert("PATH", pathEnvVar);
@@ -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;
} }
+23 -28
View File
@@ -8,7 +8,7 @@
#include "platforms/android/android_controller.h" #include "platforms/android/android_controller.h"
#endif #endif
#if defined(Q_OS_IOS) #if defined(Q_OS_IOS) || defined(Q_OS_TVOS)
#include "platforms/ios/ios_controller.h" #include "platforms/ios/ios_controller.h"
#include <AmneziaVPN-Swift.h> #include <AmneziaVPN-Swift.h>
#endif #endif
@@ -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());
} }
@@ -199,7 +196,7 @@ void CoreController::initAndroidController()
void CoreController::initAppleController() void CoreController::initAppleController()
{ {
#ifdef Q_OS_IOS #if defined(Q_OS_IOS) || defined(Q_OS_TVOS)
IosController::Instance()->initialize(); IosController::Instance()->initialize();
connect(IosController::Instance(), &IosController::importConfigFromOutside, this, [this](QString data) { connect(IosController::Instance(), &IosController::importConfigFromOutside, this, [this](QString data) {
emit m_pageController->goToPageHome(); emit m_pageController->goToPageHome();
@@ -231,14 +228,12 @@ void CoreController::initSignalHandlers()
initAutoConnectHandler(); initAutoConnectHandler();
initAmneziaDnsToggledHandler(); initAmneziaDnsToggledHandler();
initPrepareConfigHandler(); initPrepareConfigHandler();
initImportPremiumV2VpnKeyHandler();
initShowMigrationDrawerHandler();
initStrictKillSwitchHandler(); initStrictKillSwitchHandler();
} }
void CoreController::initNotificationHandler() void CoreController::initNotificationHandler()
{ {
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(Q_OS_TVOS)
m_notificationHandler.reset(NotificationHandler::create(nullptr)); m_notificationHandler.reset(NotificationHandler::create(nullptr));
connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(), connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(),
@@ -253,7 +248,7 @@ void CoreController::initNotificationHandler()
auto* trayHandler = qobject_cast<SystemTrayNotificationHandler*>(m_notificationHandler.get()); auto* trayHandler = qobject_cast<SystemTrayNotificationHandler*>(m_notificationHandler.get());
connect(this, &CoreController::websiteUrlChanged, trayHandler, &SystemTrayNotificationHandler::updateWebsiteUrl); connect(this, &CoreController::websiteUrlChanged, trayHandler, &SystemTrayNotificationHandler::updateWebsiteUrl);
#endif #endif
} }
void CoreController::updateTranslator(const QLocale &locale) void CoreController::updateTranslator(const QLocale &locale)
@@ -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();
}
}
+6 -7
View File
@@ -5,13 +5,12 @@
#include <QQmlContext> #include <QQmlContext>
#include <QThread> #include <QThread>
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(Q_OS_TVOS)
#include "ui/systemtray_notificationhandler.h" #include "ui/systemtray_notificationhandler.h"
#endif #endif
#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"
@@ -50,7 +49,7 @@
#include "ui/models/sites_model.h" #include "ui/models/sites_model.h"
#include "ui/models/newsModel.h" #include "ui/models/newsModel.h"
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(Q_OS_TVOS)
#include "ui/notificationhandler.h" #include "ui/notificationhandler.h"
#endif #endif
@@ -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?
@@ -99,7 +99,7 @@ private:
QSharedPointer<VpnConnection> m_vpnConnection; QSharedPointer<VpnConnection> m_vpnConnection;
QSharedPointer<QTranslator> m_translator; QSharedPointer<QTranslator> m_translator;
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(Q_OS_TVOS)
QScopedPointer<NotificationHandler> m_notificationHandler; QScopedPointer<NotificationHandler> m_notificationHandler;
#endif #endif
@@ -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;
+46 -99
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;
}
bool IpcClient::init(IpcClient *instance)
{
if (m_instance && m_instance != instance) {
m_instance->closeAndResetInstance(false);
m_instance->deleteLater();
} }
m_instance = instance; if (!rep->waitForSource(1000)) {
qCritical() << "IpcClient::InterfaceTun2Socks: Failed to initialize replica";
Instance()->m_localSocket = new QLocalSocket(Instance()); return nullptr;
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!";
}
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;
} }
if (!rep->isReplicaValid()) {
qDebug() << "IpcClient::init succeed"; qWarning() << "IpcClient::InterfaceTun2Socks(): Replica is invalid";
instance->m_isSocketConnected = (Instance()->m_ipcClient->isReplicaValid() && Instance()->m_Tun2SocksClient->isReplicaValid()); }
return rep;
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;
+36 -22
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"
@@ -14,28 +13,48 @@ class IpcClient : public QObject
{ {
Q_OBJECT Q_OBJECT
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<IpcProcessTun2SocksReplica> InterfaceTun2Socks();
static QSharedPointer<PrivilegedProcess> CreatePrivilegedProcess();
bool isSocketConnected() const; static QSharedPointer<IpcInterfaceReplica> Interface();
void closeAndResetInstance(bool deleteSelf = false); static QSharedPointer<IpcProcessTun2SocksReplica> InterfaceTun2Socks();
static QSharedPointer<PrivilegedProcess> CreatePrivilegedProcess();
template <typename Func>
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
+39 -17
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>
@@ -23,13 +24,22 @@
#include <sys/socket.h> #include <sys/socket.h>
#include <unistd.h> #include <unistd.h>
#endif #endif
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS) && !defined(MACOS_NE) #if defined(Q_OS_MAC) && !defined(Q_OS_IOS) && !defined(Q_OS_TVOS) && !defined(MACOS_NE)
#include <sys/param.h> #include <sys/param.h>
#include <sys/sysctl.h> #include <sys/sysctl.h>
#include <sys/socket.h> #include <sys/socket.h>
#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(Q_OS_TVOS) && !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);
+62 -27
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;
if (active != self) {
AmneziaApplication *app = qobject_cast<AmneziaApplication *>(QCoreApplication::instance());
if (app) {
QMetaObject::invokeMethod(app, "forceQuit", Qt::QueuedConnection);
}
}
} }
return TRUE; }
default: return FALSE; 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,14 +83,16 @@ 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)
{ {
const char ch = 1; if (signalPipe[1] >= 0) {
::write(signalPipe[1], &ch, sizeof(ch)); const char ch = 1;
::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(
+54
View File
@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleAllowMixedLocalizations</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>${QT_INTERNAL_DOLLAR_VAR}{PRODUCT_NAME}</string>
<key>CFBundleExecutable</key>
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
<key>CFBundleVersion</key>
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
<key>NSHumanReadableCopyright</key>
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>UIRequiredDeviceCapabilities</key>
<array/>
<key>UIRequiresFullScreen</key>
<true/>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UILaunchStoryboardName</key>
<string>AmneziaVPNLaunchScreen</string>
<key>UIUserInterfaceStyle</key>
<string>Light</string>
<key>com.wireguard.ios.app_group_id</key>
<string>group.org.amnezia.AmneziaVPN</string>
<key>UIViewControllerBasedStatusBarAppearance</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<false/>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
</dict>
</plist>
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder.AppleTV.Storyboard" version="3.0" toolsVersion="13122.16" targetRuntime="AppleTV" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="1920" height="1080"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<viewLayoutGuide key="safeArea" id="wu6-TO-1qx"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>
+119 -21
View File
@@ -1,6 +1,13 @@
enable_language(Swift) enable_language(Swift)
set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../..) set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../..)
set(AMNEZIA_IOS_APPLETV ${AMNEZIA_IOS_ENABLE_APPLETV_TARGET})
if(AMNEZIA_IOS_APPLETV)
message("Network Extension tvOS mode is ON")
else()
message("Network Extension tvOS mode is OFF")
endif()
add_executable(networkextension) add_executable(networkextension)
set_target_properties(networkextension PROPERTIES set_target_properties(networkextension PROPERTIES
@@ -28,6 +35,23 @@ set_target_properties(networkextension PROPERTIES
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../../Frameworks" XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../../Frameworks"
) )
if(AMNEZIA_IOS_APPLETV)
set_target_properties(networkextension PROPERTIES
XCODE_ATTRIBUTE_SUPPORTED_PLATFORMS "appletvos appletvsimulator"
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "3"
XCODE_ATTRIBUTE_TVOS_DEPLOYMENT_TARGET "${CMAKE_OSX_DEPLOYMENT_TARGET}"
XCODE_ATTRIBUTE_SDKROOT "appletvos"
XCODE_ATTRIBUTE_SDKROOT[sdk=appletvos*] "appletvos"
XCODE_ATTRIBUTE_SDKROOT[sdk=appletvsimulator*] "appletvsimulator"
XCODE_ATTRIBUTE_LIBRARY_SEARCH_PATHS "$(inherited) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)"
XCODE_ATTRIBUTE_LIBRARY_SEARCH_PATHS[sdk=appletvos*] "$(inherited) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)"
XCODE_ATTRIBUTE_LIBRARY_SEARCH_PATHS[sdk=appletvsimulator*] "$(inherited) $(SDKROOT)/usr/lib/swift $(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)"
XCODE_ATTRIBUTE_EXCLUDED_LIBRARY_SEARCH_PATHS "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS*.sdk/usr/lib/swift"
XCODE_ATTRIBUTE_EXCLUDED_FRAMEWORK_SEARCH_PATHS "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS*.sdk/System/Library/Frameworks"
LINKER_LANGUAGE Swift
)
endif()
if(DEPLOY) if(DEPLOY)
set_target_properties(networkextension PROPERTIES set_target_properties(networkextension PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution" XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
@@ -45,38 +69,49 @@ endif()
set_target_properties(networkextension PROPERTIES set_target_properties(networkextension PROPERTIES
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0" XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES" XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES"
XCODE_ATTRIBUTE_SWIFT_OBJC_BRIDGING_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/WireGuardNetworkExtension-Bridging-Header.h"
XCODE_ATTRIBUTE_SWIFT_OPTIMIZATION_LEVEL "-Onone" XCODE_ATTRIBUTE_SWIFT_OPTIMIZATION_LEVEL "-Onone"
XCODE_ATTRIBUTE_SWIFT_PRECOMPILE_BRIDGING_HEADER "NO" XCODE_ATTRIBUTE_SWIFT_PRECOMPILE_BRIDGING_HEADER "NO"
) )
set_target_properties(networkextension PROPERTIES
XCODE_ATTRIBUTE_SWIFT_OBJC_BRIDGING_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/WireGuardNetworkExtension-Bridging-Header.h"
)
set_target_properties("networkextension" PROPERTIES set_target_properties("networkextension" PROPERTIES
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "X7UJ388FXK" XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "X7UJ388FXK"
) )
find_library(FW_ASSETS_LIBRARY AssetsLibrary)
find_library(FW_MOBILE_CORE MobileCoreServices)
find_library(FW_UI_KIT UIKit) find_library(FW_UI_KIT UIKit)
find_library(FW_LIBRESOLV libresolv.9.tbd) find_library(FW_LIBRESOLV libresolv.9.tbd)
target_link_libraries(networkextension PRIVATE ${FW_ASSETS_LIBRARY}) if(NOT AMNEZIA_IOS_APPLETV)
target_link_libraries(networkextension PRIVATE ${FW_MOBILE_CORE}) target_link_libraries(networkextension PRIVATE ${FW_UI_KIT})
target_link_libraries(networkextension PRIVATE ${FW_UI_KIT}) target_link_libraries(networkextension PRIVATE ${FW_LIBRESOLV})
target_link_libraries(networkextension PRIVATE ${FW_LIBRESOLV}) else()
target_link_libraries(networkextension PRIVATE -lresolv)
endif()
target_compile_options(networkextension PRIVATE -DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\") target_compile_options(networkextension PRIVATE -DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\")
target_compile_options(networkextension PRIVATE -DNETWORK_EXTENSION=1) target_compile_options(networkextension PRIVATE -DNETWORK_EXTENSION=1)
set(WG_APPLE_SOURCE_DIR ${CLIENT_ROOT_DIR}/3rd/amneziawg-apple/Sources) set(WG_APPLE_SOURCE_DIR ${CLIENT_ROOT_DIR}/3rd/amneziawg-apple/Sources)
target_sources(networkextension PRIVATE set(NE_COMMON_SOURCES
${CLIENT_ROOT_DIR}/platforms/ios/NELogController.swift
${CLIENT_ROOT_DIR}/platforms/ios/Log.swift
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider.swift
)
set(NE_WIREGUARD_SOURCES
${WG_APPLE_SOURCE_DIR}/WireGuardKit/WireGuardAdapter.swift ${WG_APPLE_SOURCE_DIR}/WireGuardKit/WireGuardAdapter.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/PacketTunnelSettingsGenerator.swift ${WG_APPLE_SOURCE_DIR}/WireGuardKit/PacketTunnelSettingsGenerator.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/DNSResolver.swift ${WG_APPLE_SOURCE_DIR}/WireGuardKit/DNSResolver.swift
${WG_APPLE_SOURCE_DIR}/WireGuardNetworkExtension/ErrorNotifier.swift ${WG_APPLE_SOURCE_DIR}/WireGuardNetworkExtension/ErrorNotifier.swift
${WG_APPLE_SOURCE_DIR}/Shared/Keychain.swift ${WG_APPLE_SOURCE_DIR}/Shared/Keychain.swift
${WG_APPLE_SOURCE_DIR}/Shared/Model/TunnelConfiguration+WgQuickConfig.swift ${WG_APPLE_SOURCE_DIR}/Shared/FileManager+Extension.swift
${WG_APPLE_SOURCE_DIR}/Shared/Model/NETunnelProviderProtocol+Extension.swift ${WG_APPLE_SOURCE_DIR}/Shared/Model/NETunnelProviderProtocol+Extension.swift
${WG_APPLE_SOURCE_DIR}/Shared/Model/TunnelConfiguration+WgQuickConfig.swift
${WG_APPLE_SOURCE_DIR}/Shared/Model/String+ArrayConversion.swift ${WG_APPLE_SOURCE_DIR}/Shared/Model/String+ArrayConversion.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/TunnelConfiguration.swift ${WG_APPLE_SOURCE_DIR}/WireGuardKit/TunnelConfiguration.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/IPAddressRange.swift ${WG_APPLE_SOURCE_DIR}/WireGuardKit/IPAddressRange.swift
@@ -84,24 +119,50 @@ target_sources(networkextension PRIVATE
${WG_APPLE_SOURCE_DIR}/WireGuardKit/DNSServer.swift ${WG_APPLE_SOURCE_DIR}/WireGuardKit/DNSServer.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/InterfaceConfiguration.swift ${WG_APPLE_SOURCE_DIR}/WireGuardKit/InterfaceConfiguration.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/PeerConfiguration.swift ${WG_APPLE_SOURCE_DIR}/WireGuardKit/PeerConfiguration.swift
${WG_APPLE_SOURCE_DIR}/Shared/FileManager+Extension.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKitC/x25519.c ${WG_APPLE_SOURCE_DIR}/WireGuardKitC/x25519.c
${WG_APPLE_SOURCE_DIR}/WireGuardKit/Array+ConcurrentMap.swift ${WG_APPLE_SOURCE_DIR}/WireGuardKit/Array+ConcurrentMap.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/IPAddress+AddrInfo.swift ${WG_APPLE_SOURCE_DIR}/WireGuardKit/IPAddress+AddrInfo.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/PrivateKey.swift ${WG_APPLE_SOURCE_DIR}/WireGuardKit/PrivateKey.swift
${CLIENT_ROOT_DIR}/platforms/ios/HevSocksTunnel.swift
${CLIENT_ROOT_DIR}/platforms/ios/NELogController.swift
${CLIENT_ROOT_DIR}/platforms/ios/Log.swift
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider.swift
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+WireGuard.swift ${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+WireGuard.swift
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+OpenVPN.swift
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+Xray.swift
${CLIENT_ROOT_DIR}/platforms/ios/WGConfig.swift ${CLIENT_ROOT_DIR}/platforms/ios/WGConfig.swift
)
set(NE_XRAY_SOURCES
${CLIENT_ROOT_DIR}/platforms/ios/HevSocksTunnel.swift
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+Xray.swift
${CLIENT_ROOT_DIR}/platforms/ios/XrayConfig.swift ${CLIENT_ROOT_DIR}/platforms/ios/XrayConfig.swift
)
set(NE_OPENVPN_SOURCES
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+OpenVPN.swift
)
set(NE_APPLE_GLUE_SOURCES
${CLIENT_ROOT_DIR}/platforms/ios/iosglue.mm ${CLIENT_ROOT_DIR}/platforms/ios/iosglue.mm
) )
if(AMNEZIA_IOS_APPLETV)
list(APPEND NE_APPLE_GLUE_SOURCES
${CLIENT_ROOT_DIR}/platforms/ios/tvos_cgo_stubs.c
)
endif()
target_sources(networkextension PRIVATE ${NE_COMMON_SOURCES})
if(NOT AMNEZIA_IOS_APPLETV)
target_sources(networkextension PRIVATE
${NE_WIREGUARD_SOURCES}
${NE_OPENVPN_SOURCES}
${NE_XRAY_SOURCES}
${NE_APPLE_GLUE_SOURCES}
)
else()
target_sources(networkextension PRIVATE
${NE_WIREGUARD_SOURCES}
${NE_APPLE_GLUE_SOURCES}
)
endif()
target_sources(networkextension PRIVATE target_sources(networkextension PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/PrivacyInfo.xcprivacy ${CMAKE_CURRENT_SOURCE_DIR}/PrivacyInfo.xcprivacy
) )
@@ -113,7 +174,7 @@ set_property(TARGET networkextension APPEND PROPERTY RESOURCE
## Build wireguard-go-version.h ## Build wireguard-go-version.h
execute_process( execute_process(
COMMAND go list -m golang.zx2c4.com/wireguard COMMAND go list -m golang.zx2c4.com/wireguard
WORKING_DIRECTORY ${CLIENT_ROOT_DIR}/3rd/wireguard-apple/Sources/WireGuardKitGo WORKING_DIRECTORY ${WG_APPLE_SOURCE_DIR}/WireGuardKitGo
OUTPUT_VARIABLE WG_VERSION_FULL OUTPUT_VARIABLE WG_VERSION_FULL
) )
string(REGEX REPLACE ".*v\([0-9.]*\).*" "\\1" WG_VERSION_STRING 1.1.1) string(REGEX REPLACE ".*v\([0-9.]*\).*" "\\1" WG_VERSION_STRING 1.1.1)
@@ -122,9 +183,46 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/wireguard-go-version.h.in
target_sources(networkextension PRIVATE target_sources(networkextension PRIVATE
${CMAKE_CURRENT_BINARY_DIR}/wireguard-go-version.h) ${CMAKE_CURRENT_BINARY_DIR}/wireguard-go-version.h)
if(AMNEZIA_IOS_APPLETV)
set(WG_TVOS_LIB_DIR ${CMAKE_CURRENT_BINARY_DIR}/WireGuardKitGo-appletvos)
set(WG_TVOS_TMP_DIR ${CMAKE_CURRENT_BINARY_DIR}/WireGuardKitGo-appletvos-tmp)
execute_process(
COMMAND make clean
WORKING_DIRECTORY ${WG_APPLE_SOURCE_DIR}/WireGuardKitGo
OUTPUT_QUIET
ERROR_QUIET
)
execute_process(
COMMAND make build
PLATFORM_NAME=appletvos
GOOS_appletvos=ios
GOFLAGS=-tags=netgo
ARCHS=arm64
DEPLOYMENT_TARGET_CLANG_FLAG_NAME=mtvos-version-min
DEPLOYMENT_TARGET_CLANG_ENV_NAME=TVOS_DEPLOYMENT_TARGET
TVOS_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET}
CONFIGURATION_BUILD_DIR=${WG_TVOS_LIB_DIR}
CONFIGURATION_TEMP_DIR=${WG_TVOS_TMP_DIR}
WORKING_DIRECTORY ${WG_APPLE_SOURCE_DIR}/WireGuardKitGo
RESULT_VARIABLE WG_TVOS_BUILD_RESULT
)
if(NOT WG_TVOS_BUILD_RESULT EQUAL 0)
message(FATAL_ERROR "Failed to build tvOS WireGuard Go bridge (libwg-go.a)")
endif()
endif()
target_include_directories(networkextension PRIVATE ${CLIENT_ROOT_DIR}) target_include_directories(networkextension PRIVATE ${CLIENT_ROOT_DIR})
target_include_directories(networkextension PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) target_include_directories(networkextension PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(networkextension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/wireguard/ios/arm64/libwg-go.a) if(NOT AMNEZIA_IOS_APPLETV)
target_link_directories(networkextension PRIVATE
target_link_libraries(networkextension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/xray/HevSocks5Tunnel.xcframework) ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/wireguard/ios/arm64
)
target_link_libraries(networkextension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/wireguard/ios/arm64/libwg-go.a)
target_link_libraries(networkextension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/xray/HevSocks5Tunnel.xcframework)
else()
target_link_directories(networkextension PRIVATE
${WG_TVOS_LIB_DIR}
)
target_link_libraries(networkextension PRIVATE ${WG_TVOS_LIB_DIR}/libwg-go.a)
endif()
+3 -3
View File
@@ -12,11 +12,11 @@
#include "Windows.h" #include "Windows.h"
#endif #endif
#if defined(Q_OS_IOS) #if defined(Q_OS_IOS) || defined(Q_OS_TVOS)
#include "platforms/ios/QtAppDelegate-C-Interface.h" #include "platforms/ios/QtAppDelegate-C-Interface.h"
#endif #endif
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE) #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(Q_OS_TVOS) && !defined(MACOS_NE)
bool isAnotherInstanceRunning() bool isAnotherInstanceRunning()
{ {
QLocalSocket socket; QLocalSocket socket;
@@ -47,7 +47,7 @@ int main(int argc, char *argv[])
AmneziaApplication app(argc, argv); AmneziaApplication app(argc, argv);
OsSignalHandler::setup(); OsSignalHandler::setup();
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE) #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(Q_OS_TVOS) && !defined(MACOS_NE)
if (isAnotherInstanceRunning()) { if (isAnotherInstanceRunning()) {
QTimer::singleShot(1000, &app, [&]() { app.quit(); }); QTimer::singleShot(1000, &app, [&]() { app.quit(); });
return app.exec(); return app.exec();
+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)
+210 -24
View File
@@ -1,8 +1,11 @@
import Foundation import Foundation
import NetworkExtension import NetworkExtension
import Network
import os import os
import Darwin import Darwin
#if !os(tvOS)
import OpenVPNAdapter import OpenVPNAdapter
#endif
enum TunnelProtoType: String { enum TunnelProtoType: String {
case wireguard, openvpn, xray case wireguard, openvpn, xray
@@ -37,18 +40,112 @@ struct Constants {
class PacketTunnelProvider: NEPacketTunnelProvider { class PacketTunnelProvider: NEPacketTunnelProvider {
var wgAdapter: WireGuardAdapter? var wgAdapter: WireGuardAdapter?
#if !os(tvOS)
var ovpnAdapter: OpenVPNAdapter? var ovpnAdapter: OpenVPNAdapter?
private lazy var openVPNPacketFlowAdapter = PacketTunnelFlowAdapter(flow: packetFlow)
#endif
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]?
#if !os(tvOS)
let vpnReachability = OpenVPNReachability() let vpnReachability = OpenVPNReachability()
#endif
var startHandler: ((Error?) -> Void)? var startHandler: ((Error?) -> Void)?
var stopHandler: (() -> Void)? var stopHandler: (() -> Void)?
var protoType: TunnelProtoType? var protoType: TunnelProtoType?
var activeIfaceIdx: UInt32 = 0
#if !os(tvOS)
func openVPNPacketFlow() -> OpenVPNAdapterPacketFlow {
openVPNPacketFlowAdapter
}
#endif
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 +156,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,15 +205,30 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
return return
} }
didReceiveInitialPathUpdate = false
updateActiveInterfaceIndexForCurrentPath()
switch protoType { switch protoType {
case .wireguard: case .wireguard:
startWireguard(activationAttemptId: activationAttemptId, startWireguard(activationAttemptId: activationAttemptId,
errorNotifier: errorNotifier, errorNotifier: errorNotifier,
completionHandler: completionHandler) completionHandler: completionHandler)
case .openvpn: case .openvpn:
#if os(tvOS)
completionHandler(NSError(domain: "org.amnezia.ne",
code: -1002,
userInfo: [NSLocalizedDescriptionKey: "OpenVPN backend is not available for tvOS in this build"]))
#else
startOpenVPN(completionHandler: completionHandler) startOpenVPN(completionHandler: completionHandler)
#endif
case .xray: case .xray:
#if os(tvOS)
completionHandler(NSError(domain: "org.amnezia.ne",
code: -1003,
userInfo: [NSLocalizedDescriptionKey: "Xray backend is not available for tvOS in this build"]))
#else
startXray(completionHandler: completionHandler) startXray(completionHandler: completionHandler)
#endif
} }
} }
@@ -129,10 +245,18 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
stopWireguard(with: reason, stopWireguard(with: reason,
completionHandler: completionHandler) completionHandler: completionHandler)
case .openvpn: case .openvpn:
#if os(tvOS)
completionHandler()
#else
stopOpenVPN(with: reason, stopOpenVPN(with: reason,
completionHandler: completionHandler) completionHandler: completionHandler)
#endif
case .xray: case .xray:
#if os(tvOS)
completionHandler()
#else
stopXray(completionHandler: completionHandler) stopXray(completionHandler: completionHandler)
#endif
} }
} }
@@ -146,7 +270,11 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
case .wireguard: case .wireguard:
handleWireguardStatusMessage(messageData, completionHandler: completionHandler) handleWireguardStatusMessage(messageData, completionHandler: completionHandler)
case .openvpn: case .openvpn:
#if !os(tvOS)
handleOpenVPNStatusMessage(messageData, completionHandler: completionHandler) handleOpenVPNStatusMessage(messageData, completionHandler: completionHandler)
#else
completionHandler?(nil)
#endif
case .xray: case .xray:
break; break;
} }
@@ -157,41 +285,97 @@ 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) {
wg_log(.info, message: "Tunnel restarted.") updateActiveInterfaceIndex(for: changePath)
neLog(.info, message: "Tunnel restarted.")
startTunnel(options: nil, completionHandler: completion) startTunnel(options: nil, completionHandler: completion)
} }
} }
extension WireGuardLogLevel { private extension PacketTunnelProvider {
var osLogLevel: OSLogType { func pathSignature(for path: Network.NWPath) -> String {
switch self { var signatureComponents = [String(describing: path.status)]
case .verbose: signatureComponents.append(path.isExpensive ? "exp" : "noexp")
return .debug signatureComponents.append(path.isConstrained ? "con" : "nocon")
case .error:
return .error 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 NEProviderStopReason: CustomStringConvertible { extension WireGuardLogLevel {
public var description: String { var osLogLevel: OSLogType {
switch self {
case .verbose:
return .debug
case .error:
return .error
}
}
}
#if !os(tvOS)
final class PacketTunnelFlowAdapter: NSObject, OpenVPNAdapterPacketFlow {
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)
}
}
#endif
extension NEProviderStopReason {
var amneziaDescription: String {
switch self { switch self {
case .none: case .none:
return "No specific reason" return "No specific reason"
@@ -223,6 +407,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:
+1 -1
View File
@@ -1,4 +1,4 @@
#if !MACOS_NE #if !MACOS_NE && !TARGET_OS_TV
#include "QRCodeReaderBase.h" #include "QRCodeReaderBase.h"
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
+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
+40 -38
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,58 +42,64 @@ 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
+278 -27
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");
}
#endif
emit bytesChanged(rxBytes - m_rxBytes, txBytes - m_txBytes); QMetaObject::invokeMethod(this, [this, txBytes, rxBytes, last_handshake_time_sec]() {
m_rxBytes = rxBytes; if (isWireGuardBasedProto(m_proto) && m_handshakeAwaiting) {
m_txBytes = txBytes; 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);
}
}
emit bytesChanged(rxBytes - m_rxBytes, txBytes - m_txBytes);
m_rxBytes = rxBytes;
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,16 +942,27 @@ 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);
}
} }
} }
bool IosController::shareText(const QStringList& filesToSend) { bool IosController::shareText(const QStringList& filesToSend) {
#if defined(Q_OS_TVOS)
Q_UNUSED(filesToSend)
return false;
#else
NSMutableArray *sharingItems = [NSMutableArray new]; NSMutableArray *sharingItems = [NSMutableArray new];
for (int i = 0; i < filesToSend.size(); i++) { for (int i = 0; i < filesToSend.size(); i++) {
@@ -856,7 +971,7 @@ bool IosController::shareText(const QStringList& filesToSend) {
} }
#if !MACOS_NE #if !MACOS_NE
UIViewController *qtController = getViewController(); UIViewController *qtController = getViewController();
if (!qtController) return; if (!qtController) return false;
UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:sharingItems applicationActivities:nil]; UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:sharingItems applicationActivities:nil];
#endif #endif
@@ -880,23 +995,25 @@ bool IosController::shareText(const QStringList& filesToSend) {
wait.exec(); wait.exec();
return isAccepted; return isAccepted;
#endif
} }
QString IosController::openFile() { QString IosController::openFile() {
#if !MACOS_NE #if defined(Q_OS_TVOS)
return QString();
#elif !MACOS_NE
UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"public.item"] inMode:UIDocumentPickerModeOpen]; UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"public.item"] inMode:UIDocumentPickerModeOpen];
DocumentPickerDelegate *documentPickerDelegate = [[DocumentPickerDelegate alloc] init]; DocumentPickerDelegate *documentPickerDelegate = [[DocumentPickerDelegate alloc] init];
documentPicker.delegate = documentPickerDelegate; documentPicker.delegate = documentPickerDelegate;
UIViewController *qtController = getViewController(); UIViewController *qtController = getViewController();
if (!qtController) return; if (!qtController) return QString();
[qtController presentViewController:documentPicker animated:YES completion:nil]; [qtController presentViewController:documentPicker animated:YES completion:nil];
#endif #endif
__block QString filePath; __block QString filePath;
#if !MACOS_NE #if !MACOS_NE && !defined(Q_OS_TVOS)
documentPickerDelegate.documentPickerClosedCallback = ^(NSString *path) { documentPickerDelegate.documentPickerClosedCallback = ^(NSString *path) {
if (path) { if (path) {
filePath = QString::fromUtf8(path.UTF8String); filePath = QString::fromUtf8(path.UTF8String);
@@ -913,6 +1030,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 +1177,8 @@ void IosController::requestInetAccess() {
}]; }];
[task resume]; [task resume];
} }
bool IosController::isTestFlight() {
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
return receiptURL && [[receiptURL lastPathComponent] isEqualToString:@"sandboxReceipt"];
}
@@ -1,6 +1,7 @@
#import <NetworkExtension/NetworkExtension.h> #import <NetworkExtension/NetworkExtension.h>
#import <NetworkExtension/NETunnelProviderSession.h> #import <NetworkExtension/NETunnelProviderSession.h>
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#include <TargetConditionals.h>
#if !MACOS_NE #if !MACOS_NE
#include <UIKit/UIKit.h> #include <UIKit/UIKit.h>
@@ -21,7 +22,7 @@ class IosController;
@end @end
typedef void (^DocumentPickerClosedCallback)(NSString *path); typedef void (^DocumentPickerClosedCallback)(NSString *path);
#if !MACOS_NE #if !MACOS_NE && !TARGET_OS_TV
@interface DocumentPickerDelegate : NSObject <UIDocumentPickerDelegate> @interface DocumentPickerDelegate : NSObject <UIDocumentPickerDelegate>
@property (nonatomic, copy) DocumentPickerClosedCallback documentPickerClosedCallback; @property (nonatomic, copy) DocumentPickerClosedCallback documentPickerClosedCallback;
@@ -26,7 +26,7 @@
@end @end
#if !MACOS_NE #if !MACOS_NE && !TARGET_OS_TV
@implementation DocumentPickerDelegate @implementation DocumentPickerDelegate
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls { - (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
@@ -7,6 +7,24 @@
#import <UserNotifications/UserNotifications.h> #import <UserNotifications/UserNotifications.h>
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#if defined(Q_OS_TVOS)
IOSNotificationHandler::IOSNotificationHandler(QObject* parent) : NotificationHandler(parent) {}
IOSNotificationHandler::~IOSNotificationHandler() {}
void IOSNotificationHandler::notify(NotificationHandler::Message type,
const QString& title,
const QString& message,
int timerMsec) {
Q_UNUSED(type)
Q_UNUSED(title)
Q_UNUSED(message)
Q_UNUSED(timerMsec)
}
#else
#if !MACOS_NE #if !MACOS_NE
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
@@ -172,3 +190,5 @@ void IOSNotificationHandler::notify(NotificationHandler::Message type, const QSt
}]; }];
} }
#endif #endif
#endif // Q_OS_TVOS
@@ -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"
+57 -34
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) {
QRemoteObjectPendingReply<QStringList> listReply = iface->getTapList();
if (!listReply.waitForFinished(1000)) {
return ErrorCode::InternalError;
}
QStringList list = listReply.returnValue();
if (list.empty()) {
QRemoteObjectPendingReply<bool> installReply = iface->checkAndInstallDriver();
if (!installReply.waitForFinished() || !installReply.returnValue()) {
return ErrorCode::OpenVpnTapAdapterError;
}
}
return ErrorCode::NoError;
}, [] () {
return ErrorCode::AmneziaServiceConnectionFailed; return ErrorCode::AmneziaServiceConnectionFailed;
} });
QRemoteObjectPendingReply<QStringList> resultCheck = IpcClient::Interface()->getTapList();
resultCheck.waitForFinished();
if (resultCheck.returnValue().isEmpty()) {
QRemoteObjectPendingReply<bool> resultInstall = IpcClient::Interface()->checkAndInstallDriver();
resultInstall.waitForFinished();
if (!resultInstall.returnValue())
return ErrorCode::OpenVpnTapAdapterError;
}
return ErrorCode::NoError;
} }
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,30 +353,37 @@ 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);
QList<QNetworkInterface> netInterfaces = QNetworkInterface::allInterfaces(); IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
for (int i = 0; i < netInterfaces.size(); i++) { QList<QNetworkInterface> netInterfaces = QNetworkInterface::allInterfaces();
for (int j=0; j < netInterfaces.at(i).addressEntries().size(); j++) 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()) { // killSwitch toggle
if (QVariant(m_configData.value(config_key::killSwitchOption).toString()).toBool()) { if (m_vpnLocalAddress == netInterfaces.at(i).addressEntries().at(j).ip().toString()) {
IpcClient::Interface()->enableKillSwitch(m_configData, netInterfaces.at(i).index()); if (QVariant(m_configData.value(config_key::killSwitchOption).toString()).toBool()) {
iface->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",
NetworkUtilities::getIPAddress(m_configData.value(amnezia::config_key::hostName).toString()));
iface->enablePeerTraffic(m_configData);
} }
m_configData.insert("vpnAdapterIndex", netInterfaces.at(i).index());
m_configData.insert("vpnGateway", m_vpnGateway);
m_configData.insert("vpnServer",
NetworkUtilities::getIPAddress(m_configData.value(amnezia::config_key::hostName).toString()));
IpcClient::Interface()->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 "";
}
+13 -12
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";
@@ -192,7 +190,7 @@ namespace amnezia
constexpr char defaultPort[] = "51820"; constexpr char defaultPort[] = "51820";
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE) #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(Q_OS_TVOS) || defined(MACOS_NE)
constexpr char defaultMtu[] = "1280"; constexpr char defaultMtu[] = "1280";
#else #else
constexpr char defaultMtu[] = "1376"; constexpr char defaultMtu[] = "1376";
@@ -212,13 +210,14 @@ namespace amnezia
namespace awg namespace awg
{ {
constexpr char defaultPort[] = "55424"; constexpr char defaultPort[] = "55424";
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE) #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(Q_OS_TVOS) || defined(MACOS_NE)
constexpr char defaultMtu[] = "1280"; constexpr char defaultMtu[] = "1280";
#else #else
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
+3 -2
View File
@@ -4,7 +4,7 @@
#include "core/errorstrings.h" #include "core/errorstrings.h"
#include "vpnprotocol.h" #include "vpnprotocol.h"
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) and !defined MACOS_NE || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) #if defined(Q_OS_WINDOWS) || (defined(Q_OS_MACX) && !defined(Q_OS_IOS) && !defined(Q_OS_TVOS) && !defined(MACOS_NE)) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
#include "openvpnovercloakprotocol.h" #include "openvpnovercloakprotocol.h"
#include "openvpnprotocol.h" #include "openvpnprotocol.h"
#include "shadowsocksvpnprotocol.h" #include "shadowsocksvpnprotocol.h"
@@ -114,11 +114,12 @@ VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject &
#if defined(Q_OS_WINDOWS) #if defined(Q_OS_WINDOWS)
case DockerContainer::Ipsec: return new Ikev2Protocol(configuration); case DockerContainer::Ipsec: return new Ikev2Protocol(configuration);
#endif #endif
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) and !defined MACOS_NE || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) #if defined(Q_OS_WINDOWS) || (defined(Q_OS_MACX) && !defined(Q_OS_IOS) && !defined(Q_OS_TVOS) && !defined(MACOS_NE)) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
case DockerContainer::OpenVpn: return new OpenVpnProtocol(configuration); case DockerContainer::OpenVpn: return new OpenVpnProtocol(configuration);
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);
+76 -148
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,140 +27,71 @@ 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, setConnectionState(Vpn::ConnectionState::Connecting);
[this](int exitCode, QProcess::ExitStatus exitStatus) { return startTun2Sock();
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);
QThread::msleep(1000);
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;
if (vpnState == Vpn::ConnectionState::Connected) { IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
setConnectionState(Vpn::ConnectionState::Connecting); if (vpnState == Vpn::ConnectionState::Connected) {
QList<QHostAddress> dnsAddr; setConnectionState(Vpn::ConnectionState::Connecting);
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
QThread::msleep(8000);
#endif
#ifdef Q_OS_MACOS
QThread::msleep(5000);
IpcClient::Interface()->createTun("utun22", amnezia::protocols::xray::defaultLocalAddr);
IpcClient::Interface()->updateResolvers("utun22", dnsAddr);
#endif
#ifdef Q_OS_LINUX
QThread::msleep(1000);
IpcClient::Interface()->createTun("tun2", amnezia::protocols::xray::defaultLocalAddr);
IpcClient::Interface()->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
if (m_routeMode == Settings::RouteMode::VpnAllSites) {
IpcClient::Interface()->routeAddList(m_vpnGateway, QStringList() << "0.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();
#ifdef Q_OS_WIN
IpcClient::Interface()->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);
}
} }
#ifdef Q_OS_WIN
QThread::msleep(8000);
#endif
#ifdef Q_OS_MACOS
QThread::msleep(5000);
iface->createTun("utun22", amnezia::protocols::xray::defaultLocalAddr);
iface->updateResolvers("utun22", dnsAddr);
#endif
#ifdef Q_OS_LINUX
QThread::msleep(1000);
iface->createTun("tun2", amnezia::protocols::xray::defaultLocalAddr);
iface->updateResolvers("tun2", dnsAddr);
#endif
if (m_routeMode == Settings::RouteMode::VpnAllSites) {
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");
}
iface->StopRoutingIpv6();
#ifdef Q_OS_WIN
iface->updateResolvers("tun2", dnsAddr);
#endif
setConnectionState(Vpn::ConnectionState::Connected);
}
#if !defined(Q_OS_MACOS)
if (vpnState == Vpn::ConnectionState::Disconnected) {
setConnectionState(Vpn::ConnectionState::Disconnected);
iface->deleteTun("tun2");
iface->StartRoutingIpv6();
iface->clearSavedRoutes();
} }
#endif #endif
setConnectionState(Vpn::ConnectionState::Connected); });
}
#if !defined(Q_OS_MACOS)
if (vpnState == Vpn::ConnectionState::Disconnected) {
setConnectionState(Vpn::ConnectionState::Disconnected);
IpcClient::Interface()->deleteTun("tun2");
IpcClient::Interface()->StartRoutingIpv6();
IpcClient::Interface()->clearSavedRoutes();
}
#endif
}); });
return ErrorCode::NoError; return ErrorCode::NoError;
@@ -166,22 +99,30 @@ ErrorCode XrayProtocol::startTun2Sock()
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();
+4 -17
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>
+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>
File diff suppressed because it is too large Load Diff
+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

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