Compare commits

..

5 Commits

Author SHA1 Message Date
Mitternacht822 3e719aac56 fix: fixed incorrect lines 2025-12-03 15:23:57 +04:00
Mitternacht822 f87925c819 fix: fixed empty spaces 2025-12-03 14:59:59 +04:00
Stageddat 63c6d7a77b fix: fix grammar 2025-12-03 09:46:45 +01:00
Stageddat 90ef4ec3de feat: add spanish translations to client 2025-12-03 09:43:59 +01:00
Stageddat eb7bfc2815 feat: add spanish translation 2025-12-03 09:32:58 +01:00
284 changed files with 15469 additions and 18580 deletions
-56
View File
@@ -1,56 +0,0 @@
# .github/actions/setup-keychain/action.yml
name: Setup apple keychain
description: Creates and configures a temporary build keychain
inputs:
keychain-path:
description: Name of the keychain
required: true
keychain-password:
description: Temporary keychain password
required: true
app-cert-base64:
description: Base64-encoded P12 app certificate
required: true
app-cert-password:
description: Application certificate password
required: true
installer-cert-base64:
description: Base64-encoded P12 installer certificate
required: true
installer-cert-password:
description: Installer certificate password
required: true
runs:
using: composite
steps:
- name: Create keychain
shell: bash
env:
KEYCHAIN_PATH: ${{ inputs.keychain-path }}
KEYCHAIN_PASSWORD: ${{ inputs.keychain-password }}
APP_CERT_BASE64: ${{ inputs.app-cert-base64 }}
APP_CERT_PASSWORD: ${{ inputs.app-cert-password }}
INSTALLER_CERT_BASE64: ${{ inputs.installer-cert-base64 }}
INSTALLER_CERT_PASSWORD: ${{ inputs.installer-cert-password }}
run: |
set -e
APP_CERT_PATH=$RUNNER_TEMP/DeveloperIdApplicationCertificate.p12
INSTALLER_CERT_PATH=$RUNNER_TEMP/DeveloperIdInstallerCertificate.p12
echo -n "$APP_CERT_BASE64" | base64 --decode -o "$APP_CERT_PATH"
echo -n "$INSTALLER_CERT_BASE64" | base64 --decode -o "$INSTALLER_CERT_PATH"
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security default-keychain -s "$KEYCHAIN_PATH"
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security import "${{ github.action_path }}/DeveloperIDG2CA.cer" -k "$KEYCHAIN_PATH" -A
security import "$APP_CERT_PATH" -k "$KEYCHAIN_PATH" -P "$APP_CERT_PASSWORD" -A -t cert -f pkcs12
security import "$INSTALLER_CERT_PATH" -k "$KEYCHAIN_PATH" -P "$INSTALLER_CERT_PASSWORD" -A -t cert -f pkcs12
security set-key-partition-list -S apple-tool:,apple:,codesign: -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
security list-keychain -d user -s "$KEYCHAIN_PATH"
+219 -145
View File
@@ -10,10 +10,10 @@ env:
jobs:
Build-Linux-Ubuntu:
runs-on: android-runner
runs-on: ubuntu-22.04
env:
QT_VERSION: 6.10.1
QT_VERSION: 6.6.2
QIF_VERSION: 4.7
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
@@ -30,26 +30,13 @@ jobs:
version: ${{ env.QT_VERSION }}
host: 'linux'
target: 'desktop'
arch: 'linux_gcc_64'
arch: 'gcc_64'
modules: 'qtremoteobjects qt5compat qtshadertools'
dir: ${{ runner.temp }}
setup-python: 'true'
tools: 'tools_ifw'
set-env: 'true'
aqtversion: '==3.3.0'
py7zrversion: '==0.22.*'
extra: '--base ${{ env.QT_MIRROR }}'
- name: 'Setup python'
uses: actions/setup-python@v6
with:
python-version: 3.14
- name: 'Install conan'
run: pip install "conan==2.26.2"
- name: 'Install system packages'
run: sudo apt-get install libxkbcommon-x11-0 libsecret-1-dev
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
- name: 'Get sources'
uses: actions/checkout@v4
@@ -57,17 +44,38 @@ jobs:
submodules: 'true'
fetch-depth: 10
- name: 'Get version from CMakeLists.txt'
id: get_version
run: |
VERSION=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\)/\1/')
echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "Version: $VERSION"
- name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2
- name: 'Build project'
shell: bash
env:
QT_INSTALL_DIR: ${{ runner.temp }}
run: ./deploy/build.sh
run: |
sudo apt-get install libxkbcommon-x11-0
export QT_BIN_DIR=${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/gcc_64/bin
export QIF_BIN_DIR=${{ runner.temp }}/Qt/Tools/QtInstallerFramework/${{ env.QIF_VERSION }}/bin
bash deploy/build_linux.sh
- name: 'Pack installer'
run: cd deploy && tar -cf AmneziaVPN_Linux_Installer.tar AmneziaVPN_Linux_Installer.bin && zip AmneziaVPN_${VERSION}_linux_x64.tar.zip AmneziaVPN_Linux_Installer.tar
- name: 'Upload installer artifact'
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v4
with:
path: deploy/build/AmneziaVPN-*-Linux.run
archive: false
name: AmneziaVPN_${{ env.VERSION }}_linux_x64.tar.zip
path: deploy/AmneziaVPN_${{ env.VERSION }}_linux_x64.tar.zip
retention-days: 7
- name: 'Upload unpacked artifact'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_Linux_unpacked
path: deploy/AppDir
retention-days: 7
- name: 'Upload translations artifact'
@@ -83,8 +91,9 @@ jobs:
runs-on: windows-latest
env:
QT_VERSION: 6.10.1
QT_VERSION: 6.6.2
QIF_VERSION: 4.7
BUILD_ARCH: 64
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
@@ -100,79 +109,70 @@ jobs:
submodules: 'true'
fetch-depth: 10
- name: 'Get version from CMakeLists.txt'
id: get_version
shell: bash
run: |
VERSION=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\)/\1/')
echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "Version: $VERSION"
- name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2
- name: 'Install Qt'
uses: jurplel/install-qt-action@v3
with:
version: ${{ env.QT_VERSION }}
host: 'windows'
target: 'desktop'
arch: 'win64_msvc2022_64'
arch: 'win64_msvc2019_64'
modules: 'qtremoteobjects qt5compat qtshadertools'
dir: ${{ runner.temp }}
setup-python: 'true'
tools: 'tools_ifw'
set-env: 'true'
aqtversion: '==3.3.0'
py7zrversion: '==0.22.*'
extra: '--base ${{ env.QT_MIRROR }}'
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
- name: 'Setup mvsc'
uses: ilammy/msvc-dev-cmd@v1
with:
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: 'Setup python'
uses: actions/setup-python@v6
with:
python-version: 3.14
- name: 'Install conan'
run: pip install "conan==2.26.2"
- name: 'Build project'
shell: cmd
env:
QT_INSTALL_DIR: ${{ runner.temp }}
run: |
set WIX_ROOT_PATH="${{ env.USERPROFILE }}\.dotnet\tools"
deploy\build.bat --installer all
set BUILD_ARCH=${{ env.BUILD_ARCH }}
set QT_BIN_DIR="${{ runner.temp }}\\Qt\\${{ env.QT_VERSION }}\\msvc2019_64\\bin"
set QIF_BIN_DIR="${{ runner.temp }}\\Qt\\Tools\\QtInstallerFramework\\${{ env.QIF_VERSION }}\\bin"
call deploy\\build_windows.bat
- name: 'Upload WIX installer artifact'
uses: actions/upload-artifact@v7
- name: 'Rename Windows installer'
shell: cmd
run: |
copy AmneziaVPN_x${{ env.BUILD_ARCH }}.exe AmneziaVPN_%VERSION%_x64.exe
- name: 'Upload installer artifact'
uses: actions/upload-artifact@v4
with:
path: deploy/build/AmneziaVPN-*-win64.msi
archive: false
name: AmneziaVPN_${{ env.VERSION }}_x64.exe
path: AmneziaVPN_${{ env.VERSION }}_x64.exe
retention-days: 7
- name: 'Upload IFW installer artifact'
uses: actions/upload-artifact@v7
- name: 'Upload unpacked artifact'
uses: actions/upload-artifact@v4
with:
path: deploy/build/AmneziaVPN-*-win64.exe
archive: false
name: AmneziaVPN_Windows_unpacked
path: deploy\\build_${{ env.BUILD_ARCH }}\\client\\Release
retention-days: 7
# ------------------------------------------------------
Build-iOS:
runs-on: macos-latest
runs-on: macos-13
env:
QT_VERSION: 6.10.1
QT_VERSION: 6.6.2
CC: cc
CXX: c++
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
@@ -187,7 +187,7 @@ jobs:
- name: 'Setup xcode'
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '26.1'
xcode-version: '15.2'
- name: 'Install desktop Qt'
uses: jurplel/install-qt-action@v3
@@ -231,13 +231,11 @@ jobs:
submodules: 'true'
fetch-depth: 10
- name: 'Setup python'
uses: actions/setup-python@v6
with:
python-version: 3.14
- name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2
- name: 'Install deps'
run: pip install "conan==2.26.2" jsonschema jinja2
- name: 'Install dependencies'
run: pip install jsonschema jinja2
- name: 'Build project'
run: |
@@ -260,15 +258,113 @@ jobs:
IOS_APP_PROVISIONING_PROFILE: ${{ secrets.IOS_APP_PROVISIONING_PROFILE }}
IOS_NE_PROVISIONING_PROFILE: ${{ secrets.IOS_NE_PROVISIONING_PROFILE }}
# - name: 'Upload appstore .ipa and dSYMs to artifacts'
# uses: actions/upload-artifact@v4
# with:
# name: app-store ipa & dsyms
# path: |
# ${{ github.workspace }}/AmneziaVPN-iOS.ipa
# ${{ github.workspace }}/*.app.dSYM.zip
# retention-days: 7
# ------------------------------------------------------
Build-MacOS-old:
runs-on: macos-latest
env:
# Keep compat with MacOS 10.15 aka Catalina by Qt 6.4
QT_VERSION: 6.4.3
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
MAC_APP_CERT_CERT: ${{ secrets.MAC_APP_CERT_CERT }}
MAC_SIGNER_ID: ${{ secrets.MAC_SIGNER_ID }}
MAC_APP_CERT_PW: ${{ secrets.MAC_APP_CERT_PW }}
MAC_INSTALLER_SIGNER_CERT: ${{ secrets.MAC_INSTALLER_SIGNER_CERT }}
MAC_INSTALLER_SIGNER_ID: ${{ secrets.MAC_INSTALLER_SIGNER_ID }}
MAC_INSTALL_CERT_PW: ${{ secrets.MAC_INSTALL_CERT_PW }}
APPLE_DEV_EMAIL: ${{ secrets.APPLE_DEV_EMAIL }}
APPLE_DEV_PASSWORD: ${{ secrets.APPLE_DEV_PASSWORD }}
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
steps:
- name: 'Setup xcode'
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '15.4.0'
- name: 'Install Qt'
uses: jurplel/install-qt-action@v3
with:
version: ${{ env.QT_VERSION }}
host: 'mac'
target: 'desktop'
arch: 'clang_64'
modules: 'qtremoteobjects qt5compat qtshadertools'
dir: ${{ runner.temp }}
setup-python: 'true'
set-env: 'true'
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
- name: 'Get sources'
uses: actions/checkout@v4
with:
submodules: 'true'
fetch-depth: 10
- name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2
- name: 'Build project'
run: |
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin"
bash deploy/build_macos.sh -n
- name: 'Upload installer artifact'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_MacOS_old_installer
path: deploy/build/pkg/AmneziaVPN.pkg
retention-days: 7
- name: 'Upload unpacked artifact'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_MacOS_old_unpacked
path: deploy/build/client/AmneziaVPN.app
retention-days: 7
# ------------------------------------------------------
Build-MacOS:
runs-on: macos-latest
env:
QT_VERSION: 6.10.1
KEYCHAIN_NAME: "build.keychain"
KEYCHAIN_PASSWORD: ""
QT_VERSION: 6.8.3
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
MAC_APP_CERT_CERT: ${{ secrets.MAC_APP_CERT_CERT }}
MAC_SIGNER_ID: ${{ secrets.MAC_SIGNER_ID }}
MAC_APP_CERT_PW: ${{ secrets.MAC_APP_CERT_PW }}
MAC_INSTALLER_SIGNER_CERT: ${{ secrets.MAC_INSTALLER_SIGNER_CERT }}
MAC_INSTALLER_SIGNER_ID: ${{ secrets.MAC_INSTALLER_SIGNER_ID }}
MAC_INSTALL_CERT_PW: ${{ secrets.MAC_INSTALL_CERT_PW }}
APPLE_DEV_EMAIL: ${{ secrets.APPLE_DEV_EMAIL }}
APPLE_DEV_PASSWORD: ${{ secrets.APPLE_DEV_PASSWORD }}
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
@@ -293,19 +389,15 @@ jobs:
arch: 'clang_64'
modules: 'qtremoteobjects qt5compat qtshadertools'
dir: ${{ runner.temp }}
#setup-python: 'true'
#set-env: 'true'
#extra: '--external 7z --base ${{ env.QT_MIRROR }}'
setup-python: 'true'
set-env: 'true'
aqtversion: '==3.3.0'
py7zrversion: '==0.22.*'
extra: '--base ${{ env.QT_MIRROR }}'
- name: 'Setup python'
uses: actions/setup-python@v6
with:
python-version: 3.14
- name: 'Install conan'
run: pip install "conan==2.26.2"
extra: '--base ${{ env.QT_MIRROR }}'
cache: 'true'
- name: 'Get sources'
uses: actions/checkout@v4
@@ -313,41 +405,46 @@ jobs:
submodules: 'true'
fetch-depth: 10
- name: 'Install certs'
uses: ./.github/actions/setup-keychain
with:
keychain-path: ${{ env.KEYCHAIN_NAME }}
keychain-password: ${{ env.KEYCHAIN_PASSWORD }}
app-cert-base64: ${{ secrets.MAC_APP_CERT_CERT }}
app-cert-password: ${{ secrets.MAC_APP_CERT_PW }}
installer-cert-base64: ${{ secrets.MAC_INSTALLER_SIGNER_CERT }}
installer-cert-password: ${{ secrets.MAC_INSTALL_CERT_PW }}
- name: 'Get version from CMakeLists.txt'
id: get_version
run: |
VERSION=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\)/\1/')
echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "Version: $VERSION"
- name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2
- name: 'Build project'
env:
QT_INSTALL_DIR: ${{ runner.temp }}
CODESIGN_KEYCHAIN: ${{ env.KEYCHAIN_NAME }}
CODESIGN_SIGNATURE: ${{ secrets.MAC_SIGNER_ID }}
CODESIGN_INSTALLER_KEYCHAIN: ${{ env.KEYCHAIN_NAME }}
CODESIGN_INSTALLER_SIGNATURE: ${{ secrets.MAC_INSTALLER_SIGNER_ID }}
NOTARYTOOL_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
NOTARYTOOL_EMAIL: ${{ secrets.APPLE_DEV_EMAIL }}
NOTARYTOOL_PASSWORD: ${{ secrets.APPLE_DEV_PASSWORD }}
shell: bash
run: deploy/build.sh
run: |
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin"
bash deploy/build_macos.sh -n
- name: 'Pack macOS installer'
run: |
cd deploy/build/pkg
zip -r ../../AmneziaVPN_${VERSION}_macos.zip AmneziaVPN.pkg
cd ../../..
- name: 'Upload installer artifact'
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@v4
with:
path: deploy/build/AmneziaVPN-*-Darwin.pkg
archive: false
name: AmneziaVPN_${{ env.VERSION }}_macos.zip
path: deploy/AmneziaVPN_${{ env.VERSION }}_macos.zip
retention-days: 7
- name: 'Upload unpacked artifact'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_MacOS_unpacked
path: deploy/build/client/AmneziaVPN.app
retention-days: 7
Build-MacOS-NE:
runs-on: macos-latest
env:
QT_VERSION: 6.10.1
QT_VERSION: 6.8.3
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
@@ -367,31 +464,21 @@ jobs:
- name: 'Setup xcode'
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '26.1'
xcode-version: '16.2.0'
- name: 'Install desktop Qt'
- name: 'Install Qt'
uses: jurplel/install-qt-action@v3
with:
version: ${{ env.QT_VERSION }}
host: 'mac'
target: 'desktop'
modules: 'qtremoteobjects qt5compat qtshadertools qtmultimedia'
arch: 'clang_64'
modules: 'qtremoteobjects qt5compat qtshadertools'
dir: ${{ runner.temp }}
setup-python: 'true'
set-env: 'true'
extra: '--base ${{ env.QT_MIRROR }}'
extra: '--external 7z --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'
uses: actions/checkout@v4
@@ -399,13 +486,8 @@ jobs:
submodules: 'true'
fetch-depth: 10
- name: 'Setup python'
uses: actions/setup-python@v6
with:
python-version: 3.14
- name: 'Install conan'
run: pip install "conan==2.26.2"
- name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2
- name: 'Build project'
run: |
@@ -422,11 +504,11 @@ jobs:
# ------------------------------------------------------
Build-Android:
runs-on: android-runner
runs-on: ubuntu-latest
env:
ANDROID_BUILD_PLATFORM: android-36
QT_VERSION: 6.10.1
QT_VERSION: 6.8.3
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
@@ -514,15 +596,15 @@ jobs:
echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "Version: $VERSION"
# - name: 'Setup ccache'
# uses: hendrikmuhs/ccache-action@v1.2
- name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2
- name: 'Setup Java'
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
# cache: 'gradle'
cache: 'gradle'
- name: 'Setup Android NDK'
id: setup-ndk
@@ -537,14 +619,6 @@ jobs:
run: |
echo $KEYSTORE_BASE64 | base64 --decode > android.keystore
- name: 'Setup python'
uses: actions/setup-python@v6
with:
python-version: 3.14
- name: 'Install conan'
run: pip install "conan==2.26.2"
- name: 'Build project'
env:
ANDROID_NDK_ROOT: ${{ steps.setup-ndk.outputs.ndk-path }}
+1 -1
View File
@@ -24,7 +24,7 @@ jobs:
- name: Verify git tag
run: |
TAG_NAME=${{ inputs.RELEASE_VERSION }}
CMAKE_TAG=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+).*/\1/')
CMAKE_TAG=$(grep 'project.*VERSION' CMakeLists.txt | sed -E 's/.* ([0-9]+.[0-9]+.[0-9]+.[0-9]+)$/\1/')
if [[ "$TAG_NAME" == "$CMAKE_TAG" ]]; then
echo "Git tag ($TAG_NAME) matches CMakeLists.txt version ($CMAKE_TAG)."
else
-3
View File
@@ -140,6 +140,3 @@ ios-ne-build.sh
macos-ne-build.sh
macos-signed-build.sh
macos-with-sign-build.sh
DeveloperIdApplicationCertificate.p12
DeveloperIdInstallerCertificate.p12
-4
View File
@@ -14,7 +14,3 @@
[submodule "client/3rd/QSimpleCrypto"]
path = client/3rd/QSimpleCrypto
url = https://github.com/amnezia-vpn/QSimpleCrypto.git
[submodule "client/3rd/qtgamepad"]
path = client/3rd/qtgamepad
url = https://github.com/amnezia-vpn/qtgamepad.git
branch = 6.6
+5 -16
View File
@@ -1,14 +1,7 @@
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN)
set(AMNEZIAVPN_VERSION 4.8.13.1)
set(QT_CREATOR_SKIP_PACKAGE_MANAGER_SETUP ON CACHE BOOL "" FORCE)
set(CMAKE_PROJECT_TOP_LEVEL_INCLUDES
${CMAKE_SOURCE_DIR}/cmake/platform_settings.cmake
${CMAKE_SOURCE_DIR}/cmake/recipes_bootstrap.cmake
${CMAKE_SOURCE_DIR}/cmake/conan_provider.cmake
CACHE STRING "" FORCE)
set(AMNEZIAVPN_VERSION 4.8.11.5)
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
DESCRIPTION "AmneziaVPN"
@@ -19,7 +12,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
set(RELEASE_DATE "${CURRENT_DATE}")
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
set(APP_ANDROID_VERSION_CODE 2107)
set(APP_ANDROID_VERSION_CODE 2100)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux")
@@ -31,8 +24,6 @@ elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Android")
set(MZ_PLATFORM_NAME "android")
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "iOS")
set(MZ_PLATFORM_NAME "ios")
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "tvOS")
set(MZ_PLATFORM_NAME "ios")
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Emscripten")
set(MZ_PLATFORM_NAME "wasm")
endif()
@@ -42,7 +33,7 @@ set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(APPLE)
if(IOS OR CMAKE_SYSTEM_NAME STREQUAL "tvOS")
if(IOS)
set(CMAKE_OSX_ARCHITECTURES "arm64")
elseif(MACOS_NE)
set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64")
@@ -53,10 +44,8 @@ endif()
add_subdirectory(client)
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE AND NOT CMAKE_SYSTEM_NAME STREQUAL "tvOS")
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
add_subdirectory(service)
endif()
if ((LINUX AND NOT ANDROID) OR (APPLE AND NOT IOS AND NOT MACOS_NE AND NOT CMAKE_SYSTEM_NAME STREQUAL "tvOS") OR (WIN32))
include(${CMAKE_SOURCE_DIR}/cmake/CPack.cmake)
include(${CMAKE_SOURCE_DIR}/deploy/installer/config.cmake)
endif()
-340
View File
@@ -1,340 +0,0 @@
# AmneziaVPN Apple TV Build
This document describes how to build the current branch for Apple TV from the repository root.
The pipeline is:
1. Use a separately built static Qt 6.9.2 for `tvOS`.
2. Let Conan build/provide C/C++ dependencies.
3. Generate an Xcode project with `qt-cmake`.
4. Build the `.app` and embedded Network Extension with `xcodebuild`.
Important:
- Run the project commands from the repository root.
- This is a device build for `appletvos`, not a simulator build.
- `xcodebuild build` produces `.app`.
- `.ipa` is produced later via `archive` and `-exportArchive`.
- The current tvOS Network Extension scope is WireGuard-only.
- The temporary tvOS WireGuard bridge prebuilt is opt-in. The Conan recipe does not contain machine-specific fallback paths.
- Do not initialize or update submodules just for this build. If a clean checkout has empty `client/3rd` folders, pass `AMNEZIA_THIRDPARTY_ROOT` to an already initialized read-only `client/3rd` tree.
## 1. Environment
Set these paths for your machine:
```bash
export REPO_ROOT="$PWD"
export QT_DESKTOP_PREFIX="$HOME/Qt/6.9.2/macos"
export QT_TVOS_SRC="$HOME/Qt_tv/qt-6.9.2-tvos-src"
export QT_TVOS_PREFIX="$HOME/Qt_tv/6.9.2/tvos-device"
export BUILD_DIR="$REPO_ROOT/build-tvos-device-conan"
```
If this checkout does not have initialized `client/3rd` sources, point CMake at an initialized tree:
```bash
export AMNEZIA_THIRDPARTY_ROOT="/path/to/initialized/amnezia/client/3rd"
```
If you are using a temporary prebuilt tvOS WireGuard bridge, point Conan at it explicitly:
```bash
export AMNEZIA_TVOS_AWG_PREBUILT_DIR="/path/to/WireGuardKitGo-appletvos"
export AMNEZIA_TVOS_AWG_VERSION_HEADER_DIR="/path/to/directory/with/wireguard-go-version.h"
```
`AMNEZIA_TVOS_AWG_PREBUILT_DIR` must contain `libwg-go.a`.
`AMNEZIA_TVOS_AWG_VERSION_HEADER_DIR` is optional when `wireguard-go-version.h` lives in the same directory as `libwg-go.a`.
If the env vars are not set, the recipe uses the normal source build path. Rebuilding and publishing the tvOS WireGuard bridge through the registry is a separate task.
## 2. Required Local Tools
Conan must be available:
```bash
uv tool install conan
export PATH="$HOME/.local/bin:$PATH"
conan --version
```
Validated version:
```text
Conan version 2.27.1
```
The build uses Xcode's AppleTVOS SDK:
```bash
xcrun --sdk appletvos --show-sdk-path
```
## 3. Prepare Qt Sources
Do not edit the installed Qt sources in place. Copy them into a separate tvOS fork:
```bash
mkdir -p "$HOME/Qt_tv"
rsync -a "$HOME/Qt/6.9.2/Src/" "$QT_TVOS_SRC/"
```
Recommended for reproducibility:
```bash
cd "$QT_TVOS_SRC"
git init
git add .
git commit -m "Qt 6.9.2 source snapshot"
```
## 4. Apply the Qt tvOS Patchset
Apply the local Qt tvOS patchset to `$QT_TVOS_SRC`.
If you need to recreate the patchset from a fresh copy, compare these files against `$HOME/Qt/6.9.2/Src` and reapply the same changes:
- `qtbase/cmake/QtBaseGlobalTargets.cmake`
- `qtbase/cmake/QtBaseHelpers.cmake`
- `qtbase/cmake/QtBuildPathsHelpers.cmake`
- `qtbase/cmake/QtMkspecHelpers.cmake`
- `qtbase/cmake/QtConfig.cmake.in`
- `qtbase/mkspecs/unsupported/macx-tvos-clang/qplatformdefs.h`
- `qtbase/src/corelib/CMakeLists.txt`
- `qtbase/src/corelib/platform/darwin/qdarwinpermissionplugin_location.mm`
- `qtbase/src/gui/CMakeLists.txt`
- `qtbase/src/widgets/CMakeLists.txt`
- `qtbase/src/network/kernel/qnetworkproxy_darwin.cpp`
- `qtbase/src/testlib/qtestcrashhandler.cpp`
- `qtbase/src/plugins/platforms/ios/qiosapplicationdelegate.mm`
- `qtbase/src/plugins/platforms/ios/qiosscreen.mm`
- `qtbase/src/plugins/platforms/ios/qiostheme.mm`
- `qtbase/src/plugins/platforms/ios/quiview.mm`
- `qtbase/src/plugins/platforms/ios/qiosclipboard.mm`
Recommended after patching:
```bash
git -C "$QT_TVOS_SRC" diff > "$HOME/Qt_tv/qt-6.9.2-tvos.patch"
```
Do not use `QT_APPLE_SDK=appletvos`. The working path is `CMAKE_SYSTEM_NAME=tvOS` with `CMAKE_OSX_SYSROOT=appletvos`.
## 5. Build Qt 6.9.2 for Apple TV
Only the modules required by this project are built.
```bash
mkdir -p /private/tmp/qt6.9.2-tvos-device-build
cd /private/tmp/qt6.9.2-tvos-device-build
"$QT_TVOS_SRC/configure" \
-release -static -appstore-compliant \
-nomake tests -nomake examples \
-submodules qtbase,qtdeclarative,qtshadertools,qtremoteobjects,qtsvg,qt5compat,qttools \
-qt-host-path "$QT_DESKTOP_PREFIX" \
-prefix "$QT_TVOS_PREFIX" \
-- \
-G Ninja \
-DQT_QMAKE_TARGET_MKSPEC=macx-tvos-clang \
-DCMAKE_SYSTEM_NAME=tvOS \
-DCMAKE_OSX_SYSROOT=appletvos \
-DCMAKE_OSX_ARCHITECTURES=arm64 \
-DCMAKE_OSX_DEPLOYMENT_TARGET=17.0 \
-DBUILD_SHARED_LIBS=OFF \
-DQT_NO_APPLE_SDK_MAX_VERSION_CHECK=ON
cmake --build . --parallel 8
cmake --install .
```
Sanity checks:
```bash
"$QT_TVOS_PREFIX/bin/qt-cmake" --version
"$QT_TVOS_PREFIX/bin/qmake" -query QMAKE_XSPEC
```
Expected `QMAKE_XSPEC`:
```text
macx-tvos-clang
```
Return to the repository root after building Qt:
```bash
cd "$REPO_ROOT"
```
## 6. Conan Dependency Behavior
For `CMAKE_SYSTEM_NAME=tvOS`, the project-level Conan graph is intentionally reduced:
- included: `awg-apple/2.0.1`
- included: `libssh/0.11.3@amnezia`
- included: `openssl/3.6.1` with `no_apps=True`
- excluded: `openvpnadapter`
- excluded: `hev-socks5-tunnel`
This keeps the current Apple TV target in the same practical scope as before: app plus WireGuard-only Network Extension.
`libssh` is built with `WITH_EXEC=OFF` on tvOS because tvOS does not provide `fork()` or `execv()`.
## 7. Configure the Project
From the repository root:
```bash
cd "$REPO_ROOT"
"$QT_TVOS_PREFIX/bin/qt-cmake" \
-B"$BUILD_DIR" \
-GXcode \
-DQT_HOST_PATH="$QT_DESKTOP_PREFIX" \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_SYSTEM_NAME=tvOS \
-DCMAKE_OSX_SYSROOT=appletvos
```
If you need to provide an external initialized `client/3rd` tree:
```bash
"$QT_TVOS_PREFIX/bin/qt-cmake" \
-B"$BUILD_DIR" \
-GXcode \
-DQT_HOST_PATH="$QT_DESKTOP_PREFIX" \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_SYSTEM_NAME=tvOS \
-DCMAKE_OSX_SYSROOT=appletvos \
-DAMNEZIA_THIRDPARTY_ROOT="$AMNEZIA_THIRDPARTY_ROOT"
```
Expected non-fatal configure warnings:
```text
Warning: plug-in QIOSIntegrationPlugin is not known to the current Qt installation.
Warning: plug-in QJpegPlugin is not known to the current Qt installation.
...
```
In this repo those warnings are tolerated because `client/cmake/ios.cmake` also links the static plugin targets explicitly when available.
## 8. Build the Apple TV App
```bash
xcodebuild -quiet \
-project "$BUILD_DIR/AmneziaVPN.xcodeproj" \
-scheme AmneziaVPN \
-configuration RelWithDebInfo \
-sdk appletvos \
CODE_SIGNING_ALLOWED=NO \
build
```
Outputs:
- `$BUILD_DIR/client/RelWithDebInfo-appletvos/AmneziaVPN.app`
- `$BUILD_DIR/client/RelWithDebInfo-appletvos/AmneziaVPN.app/PlugIns/AmneziaVPNNetworkExtension.appex`
Verification:
```bash
file "$BUILD_DIR/client/RelWithDebInfo-appletvos/AmneziaVPN.app/AmneziaVPN"
file "$BUILD_DIR/client/RelWithDebInfo-appletvos/AmneziaVPN.app/PlugIns/AmneziaVPNNetworkExtension.appex/AmneziaVPNNetworkExtension"
lipo -info "$BUILD_DIR/client/RelWithDebInfo-appletvos/AmneziaVPN.app/AmneziaVPN"
lipo -info "$BUILD_DIR/client/RelWithDebInfo-appletvos/AmneziaVPN.app/PlugIns/AmneziaVPNNetworkExtension.appex/AmneziaVPNNetworkExtension"
```
Expected:
```text
Mach-O 64-bit executable arm64
Non-fat file: ... is architecture: arm64
```
Useful plist checks:
```bash
plutil -p "$BUILD_DIR/client/RelWithDebInfo-appletvos/AmneziaVPN.app/Info.plist" | rg 'CFBundleIdentifier|DTPlatformName|UIDeviceFamily|MinimumOSVersion' -C 1
plutil -p "$BUILD_DIR/client/RelWithDebInfo-appletvos/AmneziaVPN.app/PlugIns/AmneziaVPNNetworkExtension.appex/Info.plist" | rg 'CFBundleIdentifier|NSExtension|DTPlatformName|MinimumOSVersion' -C 1
```
Expected:
- `DTPlatformName => appletvos`
- `UIDeviceFamily => 3`
- `MinimumOSVersion => 17.0`
- extension point `com.apple.networkextension.packet-tunnel`
## 9. `.app` vs `.ipa`
This is the normal sequence:
1. `xcodebuild build` -> `.app`
2. `xcodebuild archive` -> `.xcarchive`
3. `xcodebuild -exportArchive` -> `.ipa`
So seeing `.app` after a successful `build` is correct.
## 10. Optional Archive and Export
The commands below are the next step for packaging, but signing and provisioning must be configured first.
Archive:
```bash
xcodebuild \
-project "$BUILD_DIR/AmneziaVPN.xcodeproj" \
-scheme AmneziaVPN \
-configuration RelWithDebInfo \
-sdk appletvos \
-archivePath "$BUILD_DIR/AmneziaVPN-tvos.xcarchive" \
archive
```
Export:
```bash
xcodebuild -exportArchive \
-archivePath "$BUILD_DIR/AmneziaVPN-tvos.xcarchive" \
-exportPath "$BUILD_DIR/export-tvos" \
-exportOptionsPlist /absolute/path/to/ExportOptions.plist
```
The resulting `.ipa` should appear under:
```text
$BUILD_DIR/export-tvos
```
## 11. Known Non-Fatal Warnings
The validated `xcodebuild` still prints warnings that do not break the build:
- missing Swift search path under the active Xcode Metal toolchain
- `SDKROOT[sdk=...]` target-level warnings generated by Xcode project export
- Swift conditional compilation flag warnings such as `GROUP_ID="..."`
- asset catalog warnings because the current icon set is still iOS-shaped, not a full tvOS Top Shelf asset set
- Go/WireGuard umbrella-header warnings from the temporary local `libwg-go.a` bridge
- deprecated libssh SCP API warnings in existing app code
- `qt_import_plugins()` warnings shown during configure
If the static platform plugin is not linked correctly, the typical failure is:
- `_OBJC_CLASS_$_QIOSApplicationDelegate`
- `_qt_main_wrapper`
Those are cleanup tasks, not blockers for the current build proof.
## 12. Fast Rebuild Checklist
If everything is already built once:
1. Reuse `$QT_TVOS_PREFIX`
2. Reuse Conan cache under `$HOME/.conan2`
3. Reuse or pass an initialized `AMNEZIA_THIRDPARTY_ROOT`
4. Re-run `qt-cmake` into `$BUILD_DIR`
5. Re-run `xcodebuild -quiet ... build`
Submodule client/3rd/qtgamepad deleted from f72b3e0c62
+34 -42
View File
@@ -3,9 +3,6 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN)
project(${PROJECT})
set(AMNEZIA_THIRDPARTY_ROOT "${CMAKE_CURRENT_LIST_DIR}/3rd" CACHE PATH "Path to Amnezia client/3rd sources")
get_filename_component(AMNEZIA_THIRDPARTY_CLIENT_ROOT "${AMNEZIA_THIRDPARTY_ROOT}/.." ABSOLUTE)
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set_property(GLOBAL PROPERTY AUTOGEN_TARGETS_FOLDER "Autogen")
set_property(GLOBAL PROPERTY AUTOMOC_TARGETS_FOLDER "Autogen")
@@ -36,7 +33,7 @@ add_definitions(-DDEV_S3_ENDPOINT="$ENV{DEV_S3_ENDPOINT}")
add_definitions(-DFREE_V2_ENDPOINT="$ENV{FREE_V2_ENDPOINT}")
add_definitions(-DPREM_V1_ENDPOINT="$ENV{PREM_V1_ENDPOINT}")
if(WIN32 OR (APPLE AND NOT IOS AND NOT CMAKE_SYSTEM_NAME STREQUAL "tvOS") OR (LINUX AND NOT ANDROID))
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
set(PACKAGES ${PACKAGES} Widgets)
endif()
@@ -49,7 +46,7 @@ set(LIBS ${LIBS}
Qt6::Core5Compat Qt6::Concurrent
)
if(WIN32 OR (APPLE AND NOT IOS AND NOT CMAKE_SYSTEM_NAME STREQUAL "tvOS") OR (LINUX AND NOT ANDROID))
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
set(LIBS ${LIBS} Qt6::Widgets)
endif()
@@ -59,9 +56,10 @@ target_include_directories(${PROJECT} PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
)
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE AND NOT CMAKE_SYSTEM_NAME STREQUAL "tvOS") OR (LINUX AND NOT ANDROID))
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep)
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_interface.rep)
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_tun2socks.rep)
endif()
qt6_add_resources(QRC ${QRC} ${CMAKE_CURRENT_LIST_DIR}/resources.qrc)
@@ -78,6 +76,7 @@ set(AMNEZIAVPN_TS_FILES
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_uk_UA.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_es_ES.ts
)
file(GLOB_RECURSE AMNEZIAVPN_TS_SOURCES *.qrc *.cpp *.h *.ui)
@@ -111,7 +110,6 @@ include_directories(
${CMAKE_CURRENT_LIST_DIR}/../ipc
${CMAKE_CURRENT_LIST_DIR}/../common/logger
${CMAKE_CURRENT_LIST_DIR}
${AMNEZIA_THIRDPARTY_CLIENT_ROOT}
${CMAKE_CURRENT_BINARY_DIR}
)
@@ -179,7 +177,7 @@ if(LINUX AND NOT ANDROID)
link_directories(${CMAKE_CURRENT_LIST_DIR}/platforms/linux)
endif()
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE AND NOT CMAKE_SYSTEM_NAME STREQUAL "tvOS") OR (LINUX AND NOT ANDROID))
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
add_compile_definitions(AMNEZIA_DESKTOP)
endif()
@@ -187,8 +185,7 @@ if(ANDROID)
include(cmake/android.cmake)
endif()
if(IOS OR CMAKE_SYSTEM_NAME STREQUAL "tvOS")
option(AMNEZIA_IOS_ENABLE_APPLETV_TARGET "Enable Apple TV target settings for iOS/Xcode projects" OFF)
if(IOS)
include(cmake/ios.cmake)
include(cmake/ios-arch-fixup.cmake)
elseif(APPLE AND MACOS_NE)
@@ -201,40 +198,35 @@ endif()
target_link_libraries(${PROJECT} PRIVATE ${LIBS})
target_compile_definitions(${PROJECT} PRIVATE "MZ_$<UPPER_CASE:${MZ_PLATFORM_NAME}>")
target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC} ${I18NQRC})
# Finalize the executable so Qt can gather/deploy QML modules and plugins correctly (Android needs this).
if(COMMAND qt_import_qml_plugins)
qt_import_qml_plugins(${PROJECT})
endif()
if(COMMAND qt_finalize_executable)
qt_finalize_executable(${PROJECT})
else()
qt_finalize_target(${PROJECT})
endif()
if(NOT IOS AND NOT CMAKE_SYSTEM_NAME STREQUAL "tvOS")
install(TARGETS ${PROJECT}
DESTINATION ${CMAKE_INSTALL_BINDIR}
COMPONENT AmneziaVPN
)
install(FILES $<TARGET_RUNTIME_DLLS:${PROJECT}>
DESTINATION ${CMAKE_INSTALL_BINDIR}
COMPONENT AmneziaVPN
)
set(deploy_tool_options "")
if(WIN32)
set(deploy_tool_options "--force-openssl --force")
# deploy artifacts required to run the application to the debug build folder
if(WIN32)
if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8")
set(DEPLOY_PLATFORM_PATH "windows/x64")
else()
set(DEPLOY_PLATFORM_PATH "windows/x32")
endif()
elseif(LINUX)
set(DEPLOY_PLATFORM_PATH "linux/client")
elseif(APPLE AND NOT IOS)
set(DEPLOY_PLATFORM_PATH "macos")
endif()
qt_generate_deploy_qml_app_script(
TARGET ${PROJECT}
OUTPUT_SCRIPT QT_DEPLOY_SCRIPT
NO_UNSUPPORTED_PLATFORM_ERROR
DEPLOY_TOOL_OPTIONS ${deploy_tool_options}
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
add_custom_command(
TARGET ${PROJECT} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E $<IF:$<CONFIG:Debug>,copy_directory,true>
${CMAKE_SOURCE_DIR}/deploy/data/${DEPLOY_PLATFORM_PATH}
$<TARGET_FILE_DIR:${PROJECT}>
COMMAND_EXPAND_LISTS
)
install(SCRIPT ${QT_DEPLOY_SCRIPT}
COMPONENT AmneziaVPN
add_custom_command(
TARGET ${PROJECT} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E $<IF:$<CONFIG:Debug>,copy_directory,true>
${CMAKE_SOURCE_DIR}/client/3rd-prebuilt/deploy-prebuilt/${DEPLOY_PLATFORM_PATH}
$<TARGET_FILE_DIR:${PROJECT}>
COMMAND_EXPAND_LISTS
)
endif()
target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC} ${I18NQRC})
qt_finalize_target(${PROJECT})
+13 -50
View File
@@ -27,15 +27,10 @@
#include <QtQuick/QQuickWindow> // for QQuickWindow
#include <QWindow> // for qobject_cast<QWindow*>
bool AmneziaApplication::m_forceQuit = false;
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv),
m_optAutostart({QStringLiteral("a"), QStringLiteral("autostart")}, QStringLiteral("System autostart")),
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"))
m_optCleanup ({QStringLiteral("c"), QStringLiteral("cleanup")}, QStringLiteral("Cleanup logs"))
{
setDesktopFileName(QStringLiteral(APPLICATION_NAME));
setQuitOnLastWindowClosed(false);
// Fix config file permissions
@@ -60,13 +55,11 @@ AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_C
AmneziaApplication::~AmneziaApplication()
{
#ifdef AMNEZIA_DESKTOP
if (m_vpnConnection && m_vpnConnectionThread.isRunning()) {
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectSlots", Qt::BlockingQueuedConnection);
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectFromVpn", Qt::BlockingQueuedConnection);
if (m_vpnConnection) {
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectSlots", Qt::QueuedConnection);
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectFromVpn", Qt::QueuedConnection);
QThread::msleep(2000);
}
#endif
m_vpnConnectionThread.requestInterruption();
m_vpnConnectionThread.quit();
@@ -77,6 +70,7 @@ AmneziaApplication::~AmneziaApplication()
}
if (m_engine) {
QObject::disconnect(m_engine, 0, 0, 0);
delete m_engine;
}
}
@@ -96,6 +90,9 @@ namespace {
void AmneziaApplication::init()
{
#ifdef Q_OS_ANDROID
clearQtCaches();
#endif
m_engine = new QQmlApplicationEngine;
const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml"));
@@ -129,16 +126,6 @@ void AmneziaApplication::init()
m_coreController.reset(new CoreController(m_vpnConnection, m_settings, m_engine));
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_coreController->setQmlRoot();
@@ -178,18 +165,6 @@ void AmneziaApplication::init()
}
});
#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()
@@ -236,8 +211,6 @@ bool AmneziaApplication::parseCommands()
m_parser.addOption(m_optAutostart);
m_parser.addOption(m_optCleanup);
m_parser.addOption(m_optConnect);
m_parser.addOption(m_optImport);
m_parser.process(*this);
@@ -250,7 +223,7 @@ bool AmneziaApplication::parseCommands()
return true;
}
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(Q_OS_TVOS) && !defined(MACOS_NE)
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
void AmneziaApplication::startLocalServer() {
const QString serverName("AmneziaVPNInstance");
QLocalServer::removeServer(serverName);
@@ -271,15 +244,11 @@ void AmneziaApplication::startLocalServer() {
bool AmneziaApplication::eventFilter(QObject *watched, QEvent *event)
{
if (event->type() == QEvent::Close) {
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(Q_OS_TVOS)
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
quit();
#else
if (m_forceQuit) {
quit();
} else {
if (m_coreController && m_coreController->pageController()) {
m_coreController->pageController()->hideMainWindow();
}
if (m_coreController && m_coreController->pageController()) {
m_coreController->pageController()->hideMainWindow();
}
#endif
return true; // eat the close
@@ -288,12 +257,6 @@ bool AmneziaApplication::eventFilter(QObject *watched, QEvent *event)
return QObject::eventFilter(watched, event);
}
void AmneziaApplication::forceQuit()
{
m_forceQuit = true;
quit();
}
QQmlApplicationEngine *AmneziaApplication::qmlEngine() const
{
return m_engine;
+3 -9
View File
@@ -6,7 +6,7 @@
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QThread>
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(Q_OS_TVOS)
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#include <QGuiApplication>
#else
#include <QApplication>
@@ -19,7 +19,7 @@
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(Q_OS_TVOS)
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#define AMNEZIA_BASE_CLASS QGuiApplication
#else
#define AMNEZIA_BASE_CLASS QApplication
@@ -37,7 +37,7 @@ public:
void loadFonts();
bool parseCommands();
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(Q_OS_TVOS) && !defined(MACOS_NE)
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
void startLocalServer();
#endif
@@ -45,11 +45,7 @@ public:
QNetworkAccessManager *networkManager();
QClipboard *getClipboard();
public slots:
void forceQuit();
private:
static bool m_forceQuit;
QQmlApplicationEngine *m_engine {};
std::shared_ptr<Settings> m_settings;
@@ -62,8 +58,6 @@ private:
QCommandLineOption m_optAutostart;
QCommandLineOption m_optCleanup;
QCommandLineOption m_optConnect;
QCommandLineOption m_optImport;
QSharedPointer<VpnConnection> m_vpnConnection;
QThread m_vpnConnectionThread;
+2 -2
View File
@@ -1,11 +1,11 @@
[versions]
agp = "8.6.1"
agp = "8.5.2"
kotlin = "1.9.24"
androidx-core = "1.13.1"
androidx-activity = "1.9.1"
androidx-annotation = "1.8.2"
androidx-biometric = "1.2.0-alpha05"
androidx-camera = "1.5.3"
androidx-camera = "1.3.4"
androidx-fragment = "1.8.2"
androidx-security-crypto = "1.1.0-alpha06"
androidx-datastore = "1.1.1"
@@ -93,7 +93,7 @@ open class OpenVpn : Protocol() {
openVpnClient = null
}
override fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean) {
override fun reconnectVpn(vpnBuilder: Builder) {
openVpnClient?.let {
it.establish = makeEstablish(vpnBuilder)
it.reconnect(0)
@@ -42,7 +42,7 @@ abstract class Protocol {
abstract fun stopVpn()
abstract fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean)
abstract fun reconnectVpn(vpnBuilder: Builder)
protected fun ProtocolConfig.Builder.configSplitTunneling(config: JSONObject) {
if (!allowSplitTunneling) {
@@ -26,8 +26,6 @@ import android.os.ParcelFileDescriptor
import android.os.SystemClock
import android.provider.OpenableColumns
import android.provider.Settings
import android.view.InputDevice
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
@@ -90,10 +88,6 @@ class AmneziaActivity : QtActivity() {
private val actionResultHandlers = mutableMapOf<Int, ActivityResultHandler>()
private val permissionRequestHandlers = mutableMapOf<Int, PermissionRequestHandler>()
private var isActivityResumed = false
private var hasWindowFocus = false
private val resumeHandler = Handler(Looper.getMainLooper())
private val vpnServiceEventHandler: Handler by lazy(NONE) {
object : Handler(Looper.getMainLooper()) {
@@ -203,7 +197,10 @@ class AmneziaActivity : QtActivity() {
private fun loadLibs() {
listOf(
"rsapss"
"rsapss",
"crypto_3",
"ssl_3",
"ssh"
).forEach {
loadSharedLibrary(this.applicationContext, it)
}
@@ -263,10 +260,6 @@ class AmneziaActivity : QtActivity() {
}
override fun onStop() {
isActivityResumed = false
hasWindowFocus = false
// Cancel all pending operations when activity stops
resumeHandler.removeCallbacksAndMessages(null)
Log.d(TAG, "Stop Amnezia activity")
doUnbindService()
mainScope.launch {
@@ -276,90 +269,23 @@ class AmneziaActivity : QtActivity() {
super.onStop()
}
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
hasWindowFocus = hasFocus
Log.d(TAG, "Window focus changed: hasFocus=$hasFocus")
// Cancel pending operations if window loses focus
if (!hasFocus) {
resumeHandler.removeCallbacksAndMessages(null)
}
}
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
val deviceId = event.deviceId
val keyCode = event.keyCode
val pressed = event.action == KeyEvent.ACTION_DOWN
val source = event.source
if (deviceId < 0 && pressed) {
when (keyCode) {
KeyEvent.KEYCODE_BUTTON_A,
KeyEvent.KEYCODE_BUTTON_B,
KeyEvent.KEYCODE_BUTTON_X,
KeyEvent.KEYCODE_BUTTON_Y,
KeyEvent.KEYCODE_BUTTON_START,
KeyEvent.KEYCODE_BUTTON_SELECT,
KeyEvent.KEYCODE_DPAD_CENTER -> {
nativeGamepadKeyEvent(0, keyCode, true)
nativeGamepadKeyEvent(0, keyCode, false)
return true
}
}
}
// Real gamepad events (deviceId >= 0)
if (deviceId >= 0) {
val isGamepad = (source and InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD
val isJoystick = (source and InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK
val isDpad = (source and InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD
if (isGamepad || isJoystick || isDpad) {
nativeGamepadKeyEvent(deviceId, keyCode, pressed)
return true
}
}
return super.dispatchKeyEvent(event)
}
private external fun nativeGamepadKeyEvent(deviceId: Int, keyCode: Int, pressed: Boolean)
override fun onPause() {
super.onPause()
isActivityResumed = false
// Cancel all pending operations when activity pauses
resumeHandler.removeCallbacksAndMessages(null)
Log.d(TAG, "Pause Amnezia activity")
}
override fun onResume() {
super.onResume()
isActivityResumed = true
Log.d(TAG, "Resume Amnezia activity")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
window.decorView.apply {
invalidate()
resumeHandler.postDelayed({
// Check if activity is still resumed and has focus before executing
if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
sendTouch(1f, 1f)
}
postDelayed({
sendTouch(1f, 1f)
}, 100)
resumeHandler.postDelayed({
if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
sendTouch(2f, 2f)
}
postDelayed({
sendTouch(2f, 2f)
}, 200)
resumeHandler.postDelayed({
if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
requestLayout()
invalidate()
}
postDelayed({
requestLayout()
invalidate()
}, 250)
}
}
@@ -388,11 +314,6 @@ class AmneziaActivity : QtActivity() {
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
statusBarColor = getColor(R.color.black)
}
WindowInsetsControllerCompat(window, window.decorView).apply {
isAppearanceLightStatusBars = false
isAppearanceLightNavigationBars = false
}
}
}
@@ -425,10 +346,6 @@ class AmneziaActivity : QtActivity() {
}
override fun onDestroy() {
isActivityResumed = false
hasWindowFocus = false
// Cancel all pending operations when activity is destroyed
resumeHandler.removeCallbacksAndMessages(null)
Log.d(TAG, "Destroy Amnezia activity")
unregisterBroadcastReceiver(notificationStateReceiver)
notificationStateReceiver = null
@@ -565,7 +565,7 @@ open class AmneziaVpnService : VpnService() {
protocolState.value = RECONNECTING
connectionJob = connectionScope.launch {
vpnProto?.protocol?.reconnectVpn(Builder(), ::protect)
vpnProto?.protocol?.reconnectVpn(Builder())
}
}
@@ -1,10 +1,7 @@
package org.amnezia.vpn
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.result.contract.ActivityResultContracts
@@ -14,25 +11,7 @@ private const val TAG = "TvFilePicker"
class TvFilePicker : ComponentActivity() {
private val fileChooseResultLauncher = registerForActivityResult(object : ActivityResultContracts.OpenDocument() {
override fun createIntent(context: Context, input: Array<String>): Intent {
val intent = super.createIntent(context, input)
val activitiesToResolveIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.packageManager.queryIntentActivities(intent, PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong()))
} else {
@Suppress("DEPRECATION")
context.packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
}
if (activitiesToResolveIntent.all {
val name = it.activityInfo.packageName
name.startsWith("com.google.android.tv.frameworkpackagestubs") || name.startsWith("com.android.tv.frameworkpackagestubs")
}) {
throw ActivityNotFoundException()
}
return intent
}
}) {
private val fileChooseResultLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) {
setResult(RESULT_OK, Intent().apply { data = it })
finish()
}
@@ -52,7 +31,7 @@ class TvFilePicker : ComponentActivity() {
private fun getFile() {
try {
Log.v(TAG, "getFile")
fileChooseResultLauncher.launch(arrayOf("*/*"))
fileChooseResultLauncher.launch("*/*")
} catch (_: ActivityNotFoundException) {
Log.w(TAG, "Activity not found")
setResult(RESULT_CANCELED, Intent().apply { putExtra("activityNotFound", true) })
@@ -12,7 +12,6 @@ import org.amnezia.vpn.protocol.Protocol
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
import org.amnezia.vpn.protocol.Statistics
import org.amnezia.vpn.protocol.VpnException
import org.amnezia.vpn.protocol.VpnStartException
import org.amnezia.vpn.util.LibraryLoader.loadSharedLibrary
import org.amnezia.vpn.util.Log
@@ -28,7 +27,6 @@ private const val TAG = "Wireguard"
open class Wireguard : Protocol() {
private var tunnelHandle: Int = -1
private var config: WireguardConfig? = null // save config for reconnect
protected open val ifName: String = "amn0"
private lateinit var scope: CoroutineScope
private var statusJob: Job? = null
@@ -63,7 +61,6 @@ open class Wireguard : Protocol() {
override suspend fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) {
val wireguardConfig = parseConfig(config)
start(wireguardConfig, vpnBuilder, protect)
this.config = wireguardConfig
}
protected open fun parseConfig(config: JSONObject): WireguardConfig {
@@ -125,24 +122,23 @@ open class Wireguard : Protocol() {
configData.optStringOrNull("S2")?.let { setS2(it.toInt()) }
configData.optStringOrNull("S3")?.let { setS3(it.toInt()) }
configData.optStringOrNull("S4")?.let { setS4(it.toInt()) }
configData.optStringOrNull("H1")?.trim()?.let { if (it.isNotEmpty()) setH1(it) }
configData.optStringOrNull("H2")?.trim()?.let { if (it.isNotEmpty()) setH2(it) }
configData.optStringOrNull("H3")?.trim()?.let { if (it.isNotEmpty()) setH3(it) }
configData.optStringOrNull("H4")?.trim()?.let { if (it.isNotEmpty()) setH4(it) }
configData.optStringOrNull("H1")?.let { setH1(it.toLong()) }
configData.optStringOrNull("H2")?.let { setH2(it.toLong()) }
configData.optStringOrNull("H3")?.let { setH3(it.toLong()) }
configData.optStringOrNull("H4")?.let { setH4(it.toLong()) }
configData.optStringOrNull("I1")?.let { setI1(it) }
configData.optStringOrNull("I2")?.let { setI2(it) }
configData.optStringOrNull("I3")?.let { setI3(it) }
configData.optStringOrNull("I4")?.let { setI4(it) }
configData.optStringOrNull("I5")?.let { setI5(it) }
configData.optStringOrNull("J1")?.let { setJ1(it) }
configData.optStringOrNull("J2")?.let { setJ2(it) }
configData.optStringOrNull("J3")?.let { setJ3(it) }
configData.optStringOrNull("Itime")?.let { setItime(it.toInt()) }
}
private fun start(
config: WireguardConfig,
vpnBuilder: Builder,
protect: (Int) -> Boolean,
stopExistingVpn: Boolean = false
) {
if (!stopExistingVpn && tunnelHandle != -1) {
private fun start(config: WireguardConfig, vpnBuilder: Builder, protect: (Int) -> Boolean) {
if (tunnelHandle != -1) {
Log.w(TAG, "Tunnel already up")
return
}
@@ -150,9 +146,6 @@ open class Wireguard : Protocol() {
buildVpnInterface(config, vpnBuilder)
vpnBuilder.establish().use { tunFd ->
if (stopExistingVpn && tunnelHandle != -1) {
turnOffVpn()
}
if (tunFd == null) {
throw VpnStartException("Create VPN interface: permission not granted or revoked")
}
@@ -209,25 +202,20 @@ open class Wireguard : Protocol() {
return lastHandshake
}
private fun turnOffVpn() {
statusJob?.cancel()
statusJob = null
val handleToClose = tunnelHandle
tunnelHandle = -1
GoBackend.awgTurnOff(handleToClose)
}
override fun stopVpn() {
if (tunnelHandle == -1) {
Log.w(TAG, "Tunnel already down")
return
}
turnOffVpn()
statusJob?.cancel()
statusJob = null
val handleToClose = tunnelHandle
tunnelHandle = -1
GoBackend.awgTurnOff(handleToClose)
state.value = DISCONNECTED
}
override fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean) {
val config = this.config ?: throw VpnException("Reconnect config is empty")
start(config, vpnBuilder, protect, true)
override fun reconnectVpn(vpnBuilder: Builder) {
state.value = CONNECTED
}
}
@@ -22,15 +22,19 @@ open class WireguardConfig protected constructor(
val s2: Int?,
val s3: Int?,
val s4: Int?,
val h1: String?,
val h2: String?,
val h3: String?,
val h4: String?,
val h1: Long?,
val h2: Long?,
val h3: Long?,
val h4: Long?,
var i1: String?,
var i2: String?,
var i3: String?,
var i4: String?,
var i5: String?,
var j1: String?,
var j2: String?,
var j3: String?,
var itime: Int?
) : ProtocolConfig(protocolConfigBuilder) {
protected constructor(builder: Builder) : this(
@@ -57,6 +61,10 @@ open class WireguardConfig protected constructor(
builder.i3,
builder.i4,
builder.i5,
builder.j1,
builder.j2,
builder.j3,
builder.itime
)
fun toWgUserspaceString(): String = with(StringBuilder()) {
@@ -86,6 +94,10 @@ open class WireguardConfig protected constructor(
i3?.let { appendLine("i3=$it") }
i4?.let { appendLine("i4=$it") }
i5?.let { appendLine("i5=$it") }
j1?.let { appendLine("j1=$it") }
j2?.let { appendLine("j2=$it") }
j3?.let { appendLine("j3=$it") }
itime?.let { appendLine("itime=$it") }
}
}
@@ -140,15 +152,19 @@ open class WireguardConfig protected constructor(
internal var s2: Int? = null
internal var s3: Int? = null
internal var s4: Int? = null
internal var h1: String? = null
internal var h2: String? = null
internal var h3: String? = null
internal var h4: String? = null
internal var h1: Long? = null
internal var h2: Long? = null
internal var h3: Long? = null
internal var h4: Long? = null
internal var i1: String? = null
internal var i2: String? = null
internal var i3: String? = null
internal var i4: String? = null
internal var i5: String? = null
internal var j1: String? = null
internal var j2: String? = null
internal var j3: String? = null
internal var itime: Int? = null
fun setEndpoint(endpoint: InetEndpoint) = apply { this.endpoint = endpoint }
@@ -169,15 +185,19 @@ open class WireguardConfig protected constructor(
fun setS2(s2: Int) = apply { this.s2 = s2 }
fun setS3(s3: Int) = apply { this.s3 = s3 }
fun setS4(s4: Int) = apply { this.s4 = s4 }
fun setH1(h1: String) = apply { this.h1 = h1 }
fun setH2(h2: String) = apply { this.h2 = h2 }
fun setH3(h3: String) = apply { this.h3 = h3 }
fun setH4(h4: String) = apply { this.h4 = h4 }
fun setH1(h1: Long) = apply { this.h1 = h1 }
fun setH2(h2: Long) = apply { this.h2 = h2 }
fun setH3(h3: Long) = apply { this.h3 = h3 }
fun setH4(h4: Long) = apply { this.h4 = h4 }
fun setI1(i1: String) = apply { this.i1 = i1 }
fun setI2(i2: String) = apply { this.i2 = i2 }
fun setI3(i3: String) = apply { this.i3 = i3 }
fun setI4(i4: String) = apply { this.i4 = i4 }
fun setI5(i5: String) = apply { this.i5 = i5 }
fun setJ1(j1: String) = apply { this.j1 = j1 }
fun setJ2(j2: String) = apply { this.j2 = j2 }
fun setJ3(j3: String) = apply { this.j3 = j3 }
fun setItime(itime: Int) = apply { this.itime = itime }
override fun build(): WireguardConfig = configBuild().run { WireguardConfig(this@Builder) }
}
+2 -2
View File
@@ -157,7 +157,7 @@ class Xray : Protocol() {
state.value = DISCONNECTED
}
override fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean) {
override fun reconnectVpn(vpnBuilder: Builder) {
state.value = CONNECTED
}
@@ -166,7 +166,7 @@ class Xray : Protocol() {
mtu = config.mtu.toLong()
proxy = "socks5://127.0.0.1:${config.socksPort}"
device = "fd://$fd"
logLevel = "warn"
logLevel = "warning"
}
LibXray.startTun2Socks(tun2SocksConfig, fd.toLong()).isNotNullOrBlank { err ->
throw VpnStartException("Failed to start tun2socks: $err")
+80 -32
View File
@@ -1,49 +1,97 @@
set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/..)
set(AMNEZIA_THIRDPARTY_ROOT "${CLIENT_ROOT_DIR}/3rd" CACHE PATH "Path to Amnezia client/3rd sources")
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/Modules;${CMAKE_MODULE_PATH}")
add_subdirectory(${AMNEZIA_THIRDPARTY_ROOT}/SortFilterProxyModel ${CMAKE_CURRENT_BINARY_DIR}/3rd/SortFilterProxyModel)
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/SortFilterProxyModel)
set(LIBS ${LIBS} SortFilterProxyModel)
include(${CLIENT_ROOT_DIR}/cmake/QSimpleCrypto.cmake)
include(${AMNEZIA_THIRDPARTY_ROOT}/qrcodegen/qrcodegen.cmake)
include(${CLIENT_ROOT_DIR}/3rd/qrcodegen/qrcodegen.cmake)
set(LIBSSH_ROOT_DIR "${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/libssh/")
set(OPENSSL_ROOT_DIR "${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/openssl/")
set(OPENSSL_LIBRARIES_DIR "${OPENSSL_ROOT_DIR}/lib")
if(WIN32)
set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/windows/include")
if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8")
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/windows/x86_64/ssh.lib")
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/windows/x86_64")
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/windows/win64/libssl.lib")
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/windows/win64/libcrypto.lib")
else()
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/windows/x86/ssh.lib")
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/windows/x86")
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/windows/win32/libssl.lib")
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/windows/win32/libcrypto.lib")
endif()
elseif(APPLE AND NOT IOS)
if(MACOS_NE)
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/universal2/libssh.a")
set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/universal2/libz.a")
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/macos/universal2")
else()
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libssh.a")
set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libz.a")
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/macos/x86_64")
endif()
set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/macos/include")
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libssl.a")
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libcrypto.a")
elseif(IOS)
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/ios/arm64")
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/ios/arm64/libssh.a")
set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/ios/arm64/libz.a")
set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/ios/iphone/include")
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/ios/iphone/lib/libssl.a")
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/ios/iphone/lib/libcrypto.a")
elseif(ANDROID)
set(abi ${CMAKE_ANDROID_ARCH_ABI})
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/android/${abi}")
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/android/${abi}/libssh.so")
set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/android/include")
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/android/${abi}/libssl.a")
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/android/${abi}/libcrypto.a")
set(OPENSSL_LIBRARIES_DIR "${OPENSSL_ROOT_DIR}/android/${abi}")
elseif(LINUX)
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/linux/x86_64")
set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/linux/x86_64/libz.a")
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/linux/x86_64/libssh.a")
set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/linux/include")
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")
endif()
file(COPY ${OPENSSL_LIB_SSL_PATH} ${OPENSSL_LIB_CRYPTO_PATH}
DESTINATION ${OPENSSL_LIBRARIES_DIR})
set(OPENSSL_USE_STATIC_LIBS TRUE)
set(LIBS ${LIBS}
${LIBSSH_LIB_PATH}
${ZLIB_LIB_PATH}
)
set(LIBS ${LIBS}
${OPENSSL_LIB_SSL_PATH}
${OPENSSL_LIB_CRYPTO_PATH}
)
add_compile_definitions(_WINSOCKAPI_)
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
set(BUILD_WITH_QT6 ON)
add_subdirectory(${AMNEZIA_THIRDPARTY_ROOT}/qtkeychain ${CMAKE_CURRENT_BINARY_DIR}/3rd/qtkeychain EXCLUDE_FROM_ALL)
if(ANDROID)
# Use qtgamepad from amnezia-vpn/qtgamepad repository
# Only if Qt6CorePrivate is available (required by qtgamepad)
find_package(Qt6CorePrivate CONFIG QUIET)
if(Qt6CorePrivate_FOUND)
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtgamepad)
# Link both the C++ module and QML plugin
if(TARGET GamepadLegacy)
target_link_libraries(${PROJECT} PRIVATE GamepadLegacy)
endif()
if(TARGET GamepadLegacyQuickPrivate)
target_link_libraries(${PROJECT} PRIVATE GamepadLegacyQuickPrivate)
endif()
message(STATUS "Gamepad support enabled for Android")
else()
message(STATUS "Qt6CorePrivate not found. Gamepad support disabled for Android.")
endif()
endif()
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtkeychain)
set(LIBS ${LIBS} qt6keychain)
include_directories(
${AMNEZIA_THIRDPARTY_ROOT}/QSimpleCrypto/src/include
${AMNEZIA_THIRDPARTY_ROOT}/qtkeychain/qtkeychain
${OPENSSL_INCLUDE_DIR}
${LIBSSH_INCLUDE_DIR}/include
${LIBSSH_ROOT_DIR}/include
${CLIENT_ROOT_DIR}/3rd/libssh/include
${CLIENT_ROOT_DIR}/3rd/QSimpleCrypto/src/include
${CLIENT_ROOT_DIR}/3rd/qtkeychain/qtkeychain
${CMAKE_CURRENT_BINARY_DIR}/3rd/qtkeychain
${CMAKE_CURRENT_BINARY_DIR}/3rd/libssh/include
)
find_package(OpenSSL REQUIRED)
list(APPEND LIBS OpenSSL::SSL OpenSSL::Crypto)
find_package(libssh REQUIRED)
list(APPEND LIBS ssh::ssh)
+1 -2
View File
@@ -1,6 +1,5 @@
set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/..)
set(AMNEZIA_THIRDPARTY_ROOT "${CLIENT_ROOT_DIR}/3rd" CACHE PATH "Path to Amnezia client/3rd sources")
set(QSIMPLECRYPTO_DIR ${AMNEZIA_THIRDPARTY_ROOT}/QSimpleCrypto/src)
set(QSIMPLECRYPTO_DIR ${CLIENT_ROOT_DIR}/3rd/QSimpleCrypto/src)
include_directories(${QSIMPLECRYPTO_DIR})
+15 -15
View File
@@ -20,11 +20,7 @@ set(QT_ANDROID_MULTI_ABI_FORWARD_VARS "QT_NO_GLOBAL_APK_TARGET_PART_OF_ALL;CMAKE
# We need to include qtprivate api's
# As QAndroidBinder is not yet implemented with a public api
# 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)
set(LIBS ${LIBS} Qt6::CorePrivate -ljnigraphics)
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/platforms/android)
@@ -42,14 +38,18 @@ set(SOURCES ${SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/core/installedAppsImageProvider.cpp
)
foreach(abi IN ITEMS ${QT_ANDROID_ABIS})
set_property(TARGET ${PROJECT} PROPERTY QT_ANDROID_EXTRA_LIBS
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/amneziawg/android/${abi}/libwg-go.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/libck-ovpn-plugin.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/libovpn3.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/libovpnutil.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/librsapss.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openssl/android/${abi}/libcrypto_3.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openssl/android/${abi}/libssl_3.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/libssh/android/${abi}/libssh.so
)
endforeach()
find_package(awg-android REQUIRED)
set(LIBS ${LIBS} amnezia::awg-android)
set_property(TARGET ${PROJECT} APPEND PROPERTY QT_ANDROID_EXTRA_LIBS ${AMNEZIA_ANDROID_LIBWG_PATH} ${AMNEZIA_ANDROID_LIBWG_QUICK_PATH})
find_package(amnezia-libxray REQUIRED)
file(COPY ${AMNEZIA_LIBXRAY_PATH} DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/android/xray/libXray)
find_package(openvpn-pt-android REQUIRED)
set(LIBS ${LIBS} amnezia::openvpn-pt-android)
set_property(TARGET ${PROJECT} APPEND PROPERTY QT_ANDROID_EXTRA_LIBS ${OPENVPN_PT_ANDROID_LIBCK_OVPN_PLUGIN_PATH})
file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/xray/android/libxray.aar
DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/android/xray/libXray)
+1 -3
View File
@@ -39,7 +39,5 @@ while(IOS_TARGETS)
set_target_properties(${TARGET_NAME} PROPERTIES
XCODE_ATTRIBUTE_ARCHS[sdk=iphoneos*] "arm64"
XCODE_ATTRIBUTE_ARCHS[sdk=iphonesimulator*] "x86_64"
XCODE_ATTRIBUTE_ARCHS[sdk=appletvos*] "arm64"
XCODE_ATTRIBUTE_ARCHS[sdk=appletvsimulator*] "arm64"
)
endwhile()
endwhile()
+31 -138
View File
@@ -1,19 +1,7 @@
message("Client iOS build")
set(CMAKE_OSX_DEPLOYMENT_TARGET 13.0)
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(OBJCXX)
@@ -22,23 +10,13 @@ enable_language(Swift)
find_package(Qt6 REQUIRED COMPONENTS ShaderTools)
set(LIBS ${LIBS} Qt6::ShaderTools)
if(AMNEZIA_IOS_APPLETV)
# Use framework linker flags directly for tvOS to avoid iPhoneOS SDK absolute paths.
set(FW_AUTHENTICATIONSERVICES "-framework AuthenticationServices")
set(FW_UIKIT "-framework UIKit")
set(FW_AVFOUNDATION "-framework AVFoundation")
set(FW_FOUNDATION "-framework Foundation")
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()
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)
set(LIBS ${LIBS}
${FW_AUTHENTICATIONSERVICES}
@@ -47,19 +25,15 @@ set(LIBS ${LIBS}
${FW_FOUNDATION}
${FW_STOREKIT}
${FW_USERNOTIFICATIONS}
${FW_NETWORKEXTENSION}
)
if(NOT AMNEZIA_IOS_APPLETV)
set(LIBS ${LIBS} ${FW_NETWORKEXTENSION})
endif()
set(HEADERS ${HEADERS}
${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/iosnotificationhandler.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
)
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h PROPERTIES OBJECTIVE_CPP_HEADER TRUE)
@@ -72,7 +46,6 @@ set(SOURCES ${SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.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
)
@@ -82,7 +55,7 @@ target_include_directories(${PROJECT} PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS})
set_target_properties(${PROJECT} PROPERTIES
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
MACOSX_BUNDLE_INFO_PLIST ${IOS_INFO_PLIST}
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Info.plist.in
MACOSX_BUNDLE_ICON_FILE "AppIcon"
MACOSX_BUNDLE_INFO_STRING "AmneziaVPN"
MACOSX_BUNDLE_BUNDLE_NAME "AmneziaVPN"
@@ -91,6 +64,7 @@ set_target_properties(${PROJECT} PROPERTIES
MACOSX_BUNDLE_LONG_VERSION_STRING "${APPLE_PROJECT_VERSION}-${CMAKE_PROJECT_VERSION_TWEAK}"
MACOSX_BUNDLE_SHORT_VERSION_STRING "${APPLE_PROJECT_VERSION}"
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_CURRENT_PROJECT_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
XCODE_ATTRIBUTE_PRODUCT_NAME "AmneziaVPN"
@@ -98,36 +72,13 @@ set_target_properties(${PROJECT} PROPERTIES
XCODE_GENERATE_SCHEME TRUE
XCODE_ATTRIBUTE_ENABLE_BITCODE "NO"
XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME "AppIcon"
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2"
XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY ON
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/Frameworks"
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)
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
@@ -158,61 +109,7 @@ target_compile_options(${PROJECT} PRIVATE
-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(AMNEZIA_THIRDPARTY_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/3rd" CACHE PATH "Path to Amnezia client/3rd sources")
set(WG_APPLE_SOURCE_DIR ${AMNEZIA_THIRDPARTY_ROOT}/amneziawg-apple/Sources)
set(WG_APPLE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rd/amneziawg-apple/Sources)
target_sources(${PROJECT} PRIVATE
# ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosvpnprotocol.swift
@@ -224,29 +121,25 @@ target_sources(${PROJECT} PRIVATE
${CLIENT_ROOT_DIR}/platforms/ios/VPNCController.swift
)
if(IOS_LAUNCHSCREEN_STORYBOARD)
target_sources(${PROJECT} PRIVATE
${IOS_LAUNCHSCREEN_STORYBOARD}
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Media.xcassets
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
)
target_sources(${PROJECT} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/AmneziaVPNLaunchScreen.storyboard
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Media.xcassets
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
)
set_property(TARGET ${PROJECT} APPEND PROPERTY RESOURCE
${IOS_LAUNCHSCREEN_STORYBOARD}
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Media.xcassets
${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()
set_property(TARGET ${PROJECT} APPEND PROPERTY RESOURCE
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/AmneziaVPNLaunchScreen.storyboard
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Media.xcassets
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
)
add_subdirectory(ios/networkextension)
add_dependencies(${PROJECT} networkextension)
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")
+3
View File
@@ -23,6 +23,9 @@ set_target_properties(${PROJECT} PROPERTIES
MACOSX_BUNDLE_SHORT_VERSION_STRING "${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}"
MACOSX_BUNDLE_BUNDLE_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
)
set(CMAKE_OSX_ARCHITECTURES "x86_64" CACHE INTERNAL "" FORCE)
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15)
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/ui/macos_util.h
+16 -4
View File
@@ -1,6 +1,7 @@
message("Client ==> MacOS NE build")
set_target_properties(${PROJECT} PROPERTIES MACOSX_BUNDLE TRUE)
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15)
set(APPLE_PROJECT_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
@@ -34,7 +35,6 @@ set(HEADERS ${HEADERS}
${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/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-C-Interface.h
)
@@ -45,7 +45,6 @@ set(SOURCES ${SOURCES}
${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/iosnotificationhandler.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
@@ -151,6 +150,19 @@ message(${QtCore_location})
get_filename_component(QT_BIN_DIR_DETECTED "${QtCore_location}/../../../../../bin" ABSOLUTE)
add_custom_command(TARGET ${PROJECT} POST_BUILD
COMMAND ${QT_BIN_DIR_DETECTED}/macdeployqt $<TARGET_BUNDLE_DIR:AmneziaVPN> -appstore-compliant -qmldir=${CMAKE_CURRENT_SOURCE_DIR}
set_property(TARGET ${PROJECT} PROPERTY XCODE_EMBED_FRAMEWORKS
"${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-macos/OpenVPNAdapter.framework"
)
set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-macos)
target_link_libraries("AmneziaVPNNetworkExtension" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-macos/OpenVPNAdapter.framework")
add_custom_command(TARGET ${PROJECT} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory
$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks
COMMAND /usr/bin/find "$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework" -name "*.sha256" -delete
COMMAND /usr/bin/codesign --force --sign "Apple Distribution"
"$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework/Versions/Current/OpenVPNAdapter"
COMMAND ${QT_BIN_DIR_DETECTED}/macdeployqt $<TARGET_BUNDLE_DIR:AmneziaVPN> -appstore-compliant -qmldir=${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Signing OpenVPNAdapter framework"
)
+6 -4
View File
@@ -39,7 +39,7 @@ set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/mozilla/controllerimpl.h
)
if(NOT IOS AND NOT MACOS_NE AND NOT CMAKE_SYSTEM_NAME STREQUAL "tvOS")
if(NOT IOS AND NOT MACOS_NE)
set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/platforms/ios/QRCodeReaderBase.h
)
@@ -89,14 +89,14 @@ set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/mozilla/shared/leakdetector.cpp
)
if(NOT IOS AND NOT MACOS_NE AND NOT CMAKE_SYSTEM_NAME STREQUAL "tvOS")
if(NOT IOS AND NOT MACOS_NE)
set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/platforms/ios/QRCodeReaderBase.cpp
)
endif()
# Include native macOS platform helpers (dock/status-item)
if(APPLE AND NOT IOS AND NOT CMAKE_SYSTEM_NAME STREQUAL "tvOS")
if(APPLE AND NOT IOS)
list(APPEND HEADERS
${CLIENT_ROOT_DIR}/platforms/macos/macosutils.h
${CLIENT_ROOT_DIR}/platforms/macos/macosstatusicon.h
@@ -175,12 +175,13 @@ if(WIN32)
)
endif()
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE AND NOT CMAKE_SYSTEM_NAME STREQUAL "tvOS") OR (LINUX AND NOT ANDROID))
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
message("Client desktop build")
add_compile_definitions(AMNEZIA_DESKTOP)
set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/core/ipcclient.h
${CLIENT_ROOT_DIR}/core/privileged_process.h
${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.h
${CLIENT_ROOT_DIR}/protocols/openvpnprotocol.h
${CLIENT_ROOT_DIR}/protocols/openvpnovercloakprotocol.h
@@ -193,6 +194,7 @@ if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE AND NOT CMAKE_SYSTEM_NAME STREQU
set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/core/ipcclient.cpp
${CLIENT_ROOT_DIR}/core/privileged_process.cpp
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.cpp
${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.cpp
${CLIENT_ROOT_DIR}/protocols/openvpnprotocol.cpp
+11 -9
View File
@@ -41,16 +41,18 @@ QString AwgConfigurator::createConfig(const ServerCredentials &credentials, Dock
jsonConfig[config_key::underloadPacketMagicHeader] = configMap.value(config_key::underloadPacketMagicHeader);
jsonConfig[config_key::transportPacketMagicHeader] = configMap.value(config_key::transportPacketMagicHeader);
if (container == DockerContainer::Awg2) {
jsonConfig[config_key::cookieReplyPacketJunkSize] = configMap.value(config_key::cookieReplyPacketJunkSize);
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::specialJunk2] = configMap.value(amnezia::config_key::specialJunk2);
jsonConfig[config_key::specialJunk3] = configMap.value(amnezia::config_key::specialJunk3);
jsonConfig[config_key::specialJunk4] = configMap.value(amnezia::config_key::specialJunk4);
jsonConfig[config_key::specialJunk5] = configMap.value(amnezia::config_key::specialJunk5);
// jsonConfig[config_key::specialJunk1] = configMap.value(amnezia::config_key::specialJunk1);
// jsonConfig[config_key::specialJunk2] = configMap.value(amnezia::config_key::specialJunk2);
// jsonConfig[config_key::specialJunk3] = configMap.value(amnezia::config_key::specialJunk3);
// jsonConfig[config_key::specialJunk4] = configMap.value(amnezia::config_key::specialJunk4);
// jsonConfig[config_key::specialJunk5] = configMap.value(amnezia::config_key::specialJunk5);
// jsonConfig[config_key::controlledJunk1] = configMap.value(amnezia::config_key::controlledJunk1);
// jsonConfig[config_key::controlledJunk2] = configMap.value(amnezia::config_key::controlledJunk2);
// jsonConfig[config_key::controlledJunk3] = configMap.value(amnezia::config_key::controlledJunk3);
// jsonConfig[config_key::specialHandshakeTimeout] = configMap.value(amnezia::config_key::specialHandshakeTimeout);
jsonConfig[config_key::mtu] =
containerConfig.value(ProtocolProps::protoToString(Proto::Awg)).toObject().value(config_key::mtu).toString(protocols::awg::defaultMtu);
@@ -1,13 +1,17 @@
#include "openvpn_configurator.h"
#include <QDebug>
#include <QCoreApplication>
#include <QJsonDocument>
#include <QJsonObject>
#include <QProcess>
#include <QString>
#include <QTemporaryDir>
#include <QTemporaryFile>
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#include <QGuiApplication>
#else
#include <QApplication>
#endif
#include "core/networkUtilities.h"
#include "containers/containers_defs.h"
@@ -161,7 +165,7 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPair<QString,
QString dnsConf = QString("\nscript-security 2\n"
"up %1/update-resolv-conf.sh\n"
"down %1/update-resolv-conf.sh\n")
.arg(QCoreApplication::applicationDirPath());
.arg(qApp->applicationDirPath());
config.append(dnsConf);
#endif
+11 -7
View File
@@ -1,7 +1,6 @@
#include "ssh_configurator.h"
#include <QDebug>
#include <QCoreApplication>
#include <QObject>
#include <QProcess>
#include <QString>
@@ -9,6 +8,11 @@
#include <QTemporaryFile>
#include <QThread>
#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 "utilities.h"
@@ -20,7 +24,7 @@ SshConfigurator::SshConfigurator(std::shared_ptr<Settings> settings, const QShar
QString SshConfigurator::convertOpenSShKey(const QString &key)
{
#if !defined(Q_OS_IOS) && !defined(Q_OS_TVOS) && !defined(MACOS_NE)
#if !defined(Q_OS_IOS) && !defined(MACOS_NE)
QProcess p;
p.setProcessChannelMode(QProcess::MergedChannels);
@@ -66,13 +70,13 @@ QString SshConfigurator::convertOpenSShKey(const QString &key)
// DEAD CODE.
void SshConfigurator::openSshTerminal(const ServerCredentials &credentials)
{
#if !defined(Q_OS_IOS) && !defined(Q_OS_TVOS) && !defined(MACOS_NE)
#if !defined(Q_OS_IOS) && !defined(MACOS_NE)
QProcess *p = new QProcess();
p->setProcessChannelMode(QProcess::SeparateChannels);
#ifdef Q_OS_WIN
p->setProcessEnvironment(prepareEnv());
p->setProgram(QCoreApplication::applicationDirPath() + "\\cygwin\\putty.exe");
p->setProgram(qApp->applicationDirPath() + "\\cygwin\\putty.exe");
if (credentials.secretData.contains("PRIVATE KEY")) {
// todo: connect by key
@@ -96,10 +100,10 @@ QProcessEnvironment SshConfigurator::prepareEnv()
#ifdef Q_OS_WIN
pathEnvVar.clear();
pathEnvVar.prepend(QDir::toNativeSeparators(QCoreApplication::applicationDirPath()) + "\\cygwin;");
pathEnvVar.prepend(QDir::toNativeSeparators(QCoreApplication::applicationDirPath()) + "\\openvpn;");
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\cygwin;");
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\openvpn;");
#elif defined(Q_OS_MACX) && !defined(MACOS_NE)
pathEnvVar.prepend(QDir::toNativeSeparators(QCoreApplication::applicationDirPath()) + "/Contents/MacOS");
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "/Contents/MacOS");
#endif
env.insert("PATH", pathEnvVar);
@@ -103,11 +103,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
return connData;
}
QString configPath = m_serverConfigPath;
if (container == DockerContainer::Awg) {
configPath = amnezia::protocols::awg::serverLegacyConfigPath;
}
QString getIpsScript = QString("cat %1 | grep AllowedIPs").arg(configPath);
QString getIpsScript = QString("cat %1 | grep AllowedIPs").arg(m_serverConfigPath);
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
@@ -165,18 +161,15 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
"AllowedIPs = %3/32\n\n")
.arg(connData.clientPubKey, connData.pskKey, connData.clientIP);
errorCode = m_serverController->uploadTextFileToContainer(container, credentials, configPart, configPath,
errorCode = m_serverController->uploadTextFileToContainer(container, credentials, configPart, m_serverConfigPath,
libssh::ScpOverwriteMode::ScpAppendToExisting);
if (errorCode != ErrorCode::NoError) {
return connData;
}
bool isAwg = (container == DockerContainer::Awg2);
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);
QString script = QString("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip %1)'")
.arg(m_serverConfigPath);
errorCode = m_serverController->runScript(
credentials,
+8 -39
View File
@@ -28,10 +28,7 @@ QString ContainerProps::containerToString(amnezia::DockerContainer c)
return "none";
if (c == DockerContainer::Cloak)
return "amnezia-openvpn-cloak";
if (c == DockerContainer::Awg)
return "amnezia-awg";
if (c == DockerContainer::Awg2)
return "amnezia-awg2";
QMetaEnum metaEnum = QMetaEnum::fromType<DockerContainer>();
QString containerKey = metaEnum.valueToKey(static_cast<int>(c));
@@ -44,10 +41,7 @@ QString ContainerProps::containerTypeToString(amnezia::DockerContainer c)
return "none";
if (c == DockerContainer::Ipsec)
return "ikev2";
if (c == DockerContainer::Awg)
return "awg";
if (c == DockerContainer::Awg2)
return "awg";
QMetaEnum metaEnum = QMetaEnum::fromType<DockerContainer>();
QString containerKey = metaEnum.valueToKey(static_cast<int>(c));
@@ -77,8 +71,6 @@ QVector<amnezia::Proto> ContainerProps::protocolsForContainer(amnezia::DockerCon
case DockerContainer::Socks5Proxy: return { Proto::Socks5Proxy };
case DockerContainer::Awg: return { Proto::Awg };
case DockerContainer::Awg2: return { Proto::Awg };
default: return { defaultProtocol(container) };
}
}
@@ -102,7 +94,6 @@ QMap<DockerContainer, QString> ContainerProps::containerHumanNames()
{ DockerContainer::Cloak, "OpenVPN over Cloak" },
{ DockerContainer::WireGuard, "WireGuard" },
{ DockerContainer::Awg, "AmneziaWG" },
{ DockerContainer::Awg2, "AmneziaWG" },
{ DockerContainer::Xray, "XRay" },
{ DockerContainer::Ipsec, QObject::tr("IPsec") },
{ DockerContainer::SSXray, "Shadowsocks"},
@@ -129,9 +120,6 @@ QMap<DockerContainer, QString> ContainerProps::containerDescriptions()
{ DockerContainer::Awg,
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::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,
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.") },
@@ -194,7 +182,7 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
"* Minimal configuration required\n"
"* Easily detected by DPI systems (susceptible to blocking)\n"
"* Operates over UDP protocol") },
{ DockerContainer::Awg2,
{ DockerContainer::Awg,
QObject::tr("AmneziaWG is a modern VPN protocol based on WireGuard, "
"combining simplified architecture with high performance across all devices. "
"It addresses WireGuard's main vulnerability (easy detection by DPI systems) through advanced obfuscation techniques, "
@@ -254,7 +242,6 @@ Proto ContainerProps::defaultProtocol(DockerContainer c)
case DockerContainer::Cloak: return Proto::Cloak;
case DockerContainer::ShadowSocks: return Proto::ShadowSocks;
case DockerContainer::WireGuard: return Proto::WireGuard;
case DockerContainer::Awg2: return Proto::Awg;
case DockerContainer::Awg: return Proto::Awg;
case DockerContainer::Xray: return Proto::Xray;
case DockerContainer::Ipsec: return Proto::Ikev2;
@@ -268,15 +255,6 @@ 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)
{
#ifdef Q_OS_WINDOWS
@@ -287,7 +265,6 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
switch (c) {
case DockerContainer::WireGuard: return true;
case DockerContainer::OpenVpn: return true;
case DockerContainer::Awg2: return true;
case DockerContainer::Awg: return true;
case DockerContainer::Xray: return true;
case DockerContainer::Cloak: return true;
@@ -301,7 +278,6 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
// macOS build using Network Extension hide OpenVPN-based containers
switch (c) {
case DockerContainer::WireGuard: return true;
case DockerContainer::Awg2: return true;
case DockerContainer::Awg: return true;
case DockerContainer::Xray: return true;
case DockerContainer::SSXray: return true;
@@ -324,7 +300,6 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
case DockerContainer::WireGuard: return true;
case DockerContainer::OpenVpn: return true;
case DockerContainer::ShadowSocks: return false;
case DockerContainer::Awg2: return true;
case DockerContainer::Awg: return true;
case DockerContainer::Cloak: return true;
case DockerContainer::Xray: return true;
@@ -354,7 +329,7 @@ QStringList ContainerProps::fixedPortsForContainer(DockerContainer c)
bool ContainerProps::isEasySetupContainer(DockerContainer container)
{
switch (container) {
case DockerContainer::Awg2: return true;
case DockerContainer::Awg: return true;
default: return false;
}
}
@@ -362,7 +337,7 @@ bool ContainerProps::isEasySetupContainer(DockerContainer container)
QString ContainerProps::easySetupHeader(DockerContainer container)
{
switch (container) {
case DockerContainer::Awg2: return tr("Automatic");
case DockerContainer::Awg: return tr("Automatic");
default: return "";
}
}
@@ -370,7 +345,7 @@ QString ContainerProps::easySetupHeader(DockerContainer container)
QString ContainerProps::easySetupDescription(DockerContainer container)
{
switch (container) {
case DockerContainer::Awg2: return tr("AmneziaWG protocol will be installed. "
case DockerContainer::Awg: return tr("AmneziaWG protocol will be installed. "
"It provides high connection speed and ensures stable operation even in the most challenging network conditions.");
default: return "";
}
@@ -379,7 +354,7 @@ QString ContainerProps::easySetupDescription(DockerContainer container)
int ContainerProps::easySetupOrder(DockerContainer container)
{
switch (container) {
case DockerContainer::Awg2: return 1;
case DockerContainer::Awg: return 1;
default: return 0;
}
}
@@ -395,12 +370,6 @@ 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)
{
QString protocolConfigString = containerConfig.value(ProtocolProps::protoToString(protocol))
@@ -418,7 +387,7 @@ int ContainerProps::installPageOrder(DockerContainer container)
case DockerContainer::Cloak: return 5;
case DockerContainer::ShadowSocks: return 6;
case DockerContainer::WireGuard: return 2;
case DockerContainer::Awg2: return 1;
case DockerContainer::Awg: return 1;
case DockerContainer::Xray: return 3;
case DockerContainer::Ipsec: return 7;
case DockerContainer::SSXray: return 8;
-5
View File
@@ -17,7 +17,6 @@ namespace amnezia
enum DockerContainer {
None = 0,
Awg,
Awg2,
WireGuard,
OpenVpn,
Cloak,
@@ -46,7 +45,6 @@ namespace amnezia
Q_INVOKABLE static amnezia::DockerContainer containerFromString(const QString &container);
Q_INVOKABLE static QString containerToString(amnezia::DockerContainer container);
Q_INVOKABLE static QString containerTypeToString(amnezia::DockerContainer c);
Q_INVOKABLE static QString containerTypeToProtocolString(amnezia::DockerContainer c);
Q_INVOKABLE static QList<amnezia::DockerContainer> allContainers();
@@ -73,9 +71,6 @@ namespace amnezia
static bool isShareable(amnezia::DockerContainer container);
static bool isAwgContainer(amnezia::DockerContainer container);
static QJsonObject getProtocolConfigFromContainer(const amnezia::Proto protocol, const QJsonObject &containerConfig);
static int installPageOrder(amnezia::DockerContainer container);
-1
View File
@@ -68,7 +68,6 @@ namespace apiDefs
constexpr QLatin1String migrationCode("migration_code");
constexpr QLatin1String transactionId("transaction_id");
constexpr QLatin1String isTestPurchase("is_test_purchase");
constexpr QLatin1String userCountryCode("user_country_code");
+2 -14
View File
@@ -1,7 +1,6 @@
#include "apiUtils.h"
#include <QDateTime>
#include <QJsonDocument>
#include <QJsonObject>
namespace
@@ -89,7 +88,6 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
{
const int httpStatusCodeConflict = 409;
const int httpStatusCodeNotFound = 404;
const int httpStatusCodeNotImplemented = 501;
if (!sslErrors.empty()) {
qDebug().noquote() << sslErrors;
@@ -108,20 +106,10 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
qDebug() << replyError;
qDebug() << replyErrorString;
qDebug() << httpStatusCode;
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) {
if (httpStatusCode == httpStatusCodeConflict) {
return amnezia::ErrorCode::ApiConfigLimitError;
} else if (httpStatusFromBody == httpStatusCodeNotFound) {
} else if (httpStatusCode == httpStatusCodeNotFound) {
return amnezia::ErrorCode::ApiNotFoundError;
} else if (httpStatusFromBody == httpStatusCodeNotImplemented) {
return amnezia::ErrorCode::ApiUpdateRequestError;
}
return amnezia::ErrorCode::ApiConfigDownloadError;
}
+28 -23
View File
@@ -8,7 +8,7 @@
#include "platforms/android/android_controller.h"
#endif
#if defined(Q_OS_IOS) || defined(Q_OS_TVOS)
#if defined(Q_OS_IOS)
#include "platforms/ios/ios_controller.h"
#include <AmneziaVPN-Swift.h>
#endif
@@ -154,6 +154,9 @@ void CoreController::initControllers()
m_apiConfigsController.reset(new ApiConfigsController(m_serversModel, m_apiServicesModel, m_settings));
m_engine->rootContext()->setContextProperty("ApiConfigsController", m_apiConfigsController.get());
m_apiPremV1MigrationController.reset(new ApiPremV1MigrationController(m_serversModel, m_settings, this));
m_engine->rootContext()->setContextProperty("ApiPremV1MigrationController", m_apiPremV1MigrationController.get());
m_apiNewsController.reset(new ApiNewsController(m_newsModel, m_settings, m_serversModel, this));
m_engine->rootContext()->setContextProperty("ApiNewsController", m_apiNewsController.get());
}
@@ -196,7 +199,7 @@ void CoreController::initAndroidController()
void CoreController::initAppleController()
{
#if defined(Q_OS_IOS) || defined(Q_OS_TVOS)
#ifdef Q_OS_IOS
IosController::Instance()->initialize();
connect(IosController::Instance(), &IosController::importConfigFromOutside, this, [this](QString data) {
emit m_pageController->goToPageHome();
@@ -228,12 +231,14 @@ void CoreController::initSignalHandlers()
initAutoConnectHandler();
initAmneziaDnsToggledHandler();
initPrepareConfigHandler();
initImportPremiumV2VpnKeyHandler();
initShowMigrationDrawerHandler();
initStrictKillSwitchHandler();
}
void CoreController::initNotificationHandler()
{
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(Q_OS_TVOS)
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
m_notificationHandler.reset(NotificationHandler::create(nullptr));
connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(),
@@ -248,7 +253,7 @@ void CoreController::initNotificationHandler()
auto* trayHandler = qobject_cast<SystemTrayNotificationHandler*>(m_notificationHandler.get());
connect(this, &CoreController::websiteUrlChanged, trayHandler, &SystemTrayNotificationHandler::updateWebsiteUrl);
#endif
#endif
}
void CoreController::updateTranslator(const QLocale &locale)
@@ -377,6 +382,25 @@ 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()
{
connect(m_settingsController.get(), &SettingsController::strictKillSwitchEnabledChanged, m_vpnConnection.get(),
@@ -387,22 +411,3 @@ QSharedPointer<PageController> CoreController::pageController() const
{
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();
}
}
+7 -6
View File
@@ -5,12 +5,13 @@
#include <QQmlContext>
#include <QThread>
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(Q_OS_TVOS)
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#include "ui/systemtray_notificationhandler.h"
#endif
#include "ui/controllers/api/apiConfigsController.h"
#include "ui/controllers/api/apiSettingsController.h"
#include "ui/controllers/api/apiPremV1MigrationController.h"
#include "ui/controllers/api/apiNewsController.h"
#include "ui/controllers/appSplitTunnelingController.h"
#include "ui/controllers/allowedDnsController.h"
@@ -49,7 +50,7 @@
#include "ui/models/sites_model.h"
#include "ui/models/newsModel.h"
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(Q_OS_TVOS)
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#include "ui/notificationhandler.h"
#endif
@@ -64,9 +65,6 @@ public:
QSharedPointer<PageController> pageController() const;
void setQmlRoot();
void openConnectionByIndex(int serverIndex);
void importConfigFromData(const QString &data);
signals:
void translationsUpdated();
void websiteUrlChanged(const QString &newUrl);
@@ -92,6 +90,8 @@ private:
void initAutoConnectHandler();
void initAmneziaDnsToggledHandler();
void initPrepareConfigHandler();
void initImportPremiumV2VpnKeyHandler();
void initShowMigrationDrawerHandler();
void initStrictKillSwitchHandler();
QQmlApplicationEngine *m_engine {}; // TODO use parent child system here?
@@ -99,7 +99,7 @@ private:
QSharedPointer<VpnConnection> m_vpnConnection;
QSharedPointer<QTranslator> m_translator;
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(Q_OS_TVOS)
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
QScopedPointer<NotificationHandler> m_notificationHandler;
#endif
@@ -119,6 +119,7 @@ private:
QScopedPointer<ApiSettingsController> m_apiSettingsController;
QScopedPointer<ApiConfigsController> m_apiConfigsController;
QScopedPointer<ApiPremV1MigrationController> m_apiPremV1MigrationController;
QScopedPointer<ApiNewsController> m_apiNewsController;
QSharedPointer<ContainersModel> m_containersModel;
+47 -102
View File
@@ -41,11 +41,6 @@ namespace
constexpr QLatin1String errorResponsePattern3("Account not found.");
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,
@@ -79,11 +74,7 @@ GatewayController::EncryptedRequestData GatewayController::prepareRequest(const
QString host = QUrl(encRequestData.request.url()).host();
QString ip = NetworkUtilities::getIPAddress(host);
if (!ip.isEmpty()) {
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";
});
IpcClient::Interface()->addKillSwitchAllowedRange(QStringList { ip });
}
}
#endif
@@ -135,26 +126,6 @@ GatewayController::EncryptedRequestData GatewayController::prepareRequest(const
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)
{
EncryptedRequestData encRequestData = prepareRequest(endpoint, apiPayload);
@@ -178,27 +149,21 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
reply->deleteLater();
auto decryptionResult =
tryDecryptResponseBody(encryptedResponseBody, replyError, encRequestData.key, encRequestData.iv, encRequestData.salt);
if (sslErrors.isEmpty() && shouldBypassProxy(replyError, decryptionResult.decryptedBody, decryptionResult.isDecryptionSuccessful)) {
if (sslErrors.isEmpty()
&& shouldBypassProxy(replyError, encryptedResponseBody, true, encRequestData.key, encRequestData.iv, encRequestData.salt)) {
auto requestFunction = [&encRequestData, &encryptedResponseBody](const QString &url) {
encRequestData.request.setUrl(url);
return amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
};
auto replyProcessingFunction = [&encryptedResponseBody, &replyErrorString, &replyError, &httpStatusCode, &sslErrors, &encRequestData,
&decryptionResult, this](QNetworkReply *reply, const QList<QSslError> &nestedSslErrors) {
auto replyProcessingFunction = [&encryptedResponseBody, &replyErrorString, &replyError, &httpStatusCode, &sslErrors,
&encRequestData, this](QNetworkReply *reply, const QList<QSslError> &nestedSslErrors) {
encryptedResponseBody = reply->readAll();
replyErrorString = reply->errorString();
replyError = reply->error();
httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
decryptionResult =
tryDecryptResponseBody(encryptedResponseBody, replyError, encRequestData.key, encRequestData.iv, encRequestData.salt);
if (!sslErrors.isEmpty()
|| shouldBypassProxy(replyError, decryptionResult.decryptedBody, decryptionResult.isDecryptionSuccessful)) {
|| shouldBypassProxy(replyError, encryptedResponseBody, true, encRequestData.key, encRequestData.iv, encRequestData.salt)) {
sslErrors = nestedSslErrors;
return false;
}
@@ -210,19 +175,21 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
bypassProxy(endpoint, serviceType, userCountryCode, requestFunction, replyProcessingFunction);
}
auto errorCode =
apiUtils::checkNetworkReplyErrors(sslErrors, replyErrorString, replyError, httpStatusCode, decryptionResult.decryptedBody);
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, replyErrorString, replyError, httpStatusCode, encryptedResponseBody);
if (errorCode) {
return errorCode;
}
if (!decryptionResult.isDecryptionSuccessful) {
try {
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";
return ErrorCode::ApiConfigDecryptionError;
}
responseBody = decryptionResult.decryptedBody;
return ErrorCode::NoError;
}
QFuture<QPair<ErrorCode, QByteArray>> GatewayController::postAsync(const QString &endpoint, const QJsonObject apiPayload)
@@ -251,33 +218,32 @@ QFuture<QPair<ErrorCode, QByteArray>> GatewayController::postAsync(const QString
reply->deleteLater();
auto decryptionResult =
tryDecryptResponseBody(encryptedResponseBody, replyError, encRequestData.key, encRequestData.iv, encRequestData.salt);
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);
auto processResponse = [promise, encRequestData](const QByteArray &ecryptedResponseBody, const QList<QSslError> &sslErrors,
QNetworkReply::NetworkError replyError, const QString &replyErrorString,
int httpStatusCode) {
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, replyErrorString, replyError, httpStatusCode, ecryptedResponseBody);
if (errorCode) {
promise->addResult(qMakePair(errorCode, QByteArray()));
promise->finish();
return;
}
if (!decryptionResult.isDecryptionSuccessful) {
QSimpleCrypto::QBlockCipher blockCipher;
try {
QByteArray responseBody = blockCipher.decryptAesBlockCipher(ecryptedResponseBody, encRequestData.key, encRequestData.iv, "",
encRequestData.salt);
promise->addResult(qMakePair(ErrorCode::NoError, responseBody));
promise->finish();
} catch (...) {
Utils::logException();
qCritical() << "error when decrypting the request body";
promise->addResult(qMakePair(ErrorCode::ApiConfigDecryptionError, QByteArray()));
promise->finish();
return;
}
promise->addResult(qMakePair(ErrorCode::NoError, decryptionResult.decryptedBody));
promise->finish();
};
if (sslErrors->isEmpty() && shouldBypassProxy(replyError, decryptionResult.decryptedBody, decryptionResult.isDecryptionSuccessful)) {
if (sslErrors->isEmpty()
&& shouldBypassProxy(replyError, encryptedResponseBody, true, encRequestData.key, encRequestData.iv, encRequestData.salt)) {
auto serviceType = apiPayload.value(apiDefs::key::serviceType).toString("");
auto userCountryCode = apiPayload.value(apiDefs::key::userCountryCode).toString("");
@@ -300,21 +266,13 @@ QFuture<QPair<ErrorCode, QByteArray>> GatewayController::postAsync(const QString
proxyStorageUrls.push_back(baseUrl + "endpoints.json");
getProxyUrlsAsync(proxyStorageUrls, 0, [this, encRequestData, endpoint, processResponse](const QStringList &proxyUrls) {
getProxyUrlAsync(proxyUrls, 0, [this, encRequestData, endpoint, processResponse](const QString &proxyUrl) {
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);
});
getProxyUrlAsync(proxyUrls, 0, [this, encRequestData, endpoint, processResponse](const QString &proxyUrls) {
bypassProxyAsync(endpoint, proxyUrls, encRequestData, processResponse);
});
});
} else {
processResponse(decryptionResult, *sslErrors, replyError, replyErrorString, httpStatusCode);
processResponse(encryptedResponseBody, *sslErrors, replyError, replyErrorString, httpStatusCode);
}
});
@@ -407,23 +365,9 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
return {};
}
bool GatewayController::shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &decryptedResponseBody,
bool isDecryptionSuccessful)
bool GatewayController::shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &responseBody,
bool checkEncryption, const QByteArray &key, const QByteArray &iv, const QByteArray &salt)
{
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) {
qDebug() << "timeout occurred";
qDebug() << replyError;
@@ -431,7 +375,7 @@ bool GatewayController::shouldBypassProxy(const QNetworkReply::NetworkError &rep
} else if (responseBody.contains("html")) {
qDebug() << "the response contains an html tag";
return true;
} else if (httpStatus == httpStatusCodeNotFound) {
} else if (replyError == QNetworkReply::NetworkError::ContentNotFoundError) {
if (responseBody.contains(errorResponsePattern1) || responseBody.contains(errorResponsePattern2)
|| responseBody.contains(errorResponsePattern3)) {
return false;
@@ -439,18 +383,24 @@ bool GatewayController::shouldBypassProxy(const QNetworkReply::NetworkError &rep
qDebug() << replyError;
return true;
}
} else if (httpStatus == httpStatusCodeNotImplemented) {
} else if (replyError == QNetworkReply::NetworkError::OperationNotImplementedError) {
if (responseBody.contains(updateRequestResponsePattern)) {
return false;
} else {
qDebug() << replyError;
return true;
}
} else if (httpStatus == httpStatusCodeConflict) {
return false;
} else if (replyError != QNetworkReply::NetworkError::NoError) {
qDebug() << replyError;
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;
}
@@ -598,8 +548,7 @@ 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()) {
onComplete("");
@@ -633,11 +582,11 @@ void GatewayController::getProxyUrlAsync(const QStringList proxyUrls, const int
void GatewayController::bypassProxyAsync(
const QString &endpoint, const QString &proxyUrl, EncryptedRequestData encRequestData,
std::function<void(const QByteArray &, bool, const QList<QSslError> &, QNetworkReply::NetworkError, const QString &, int)> onComplete)
std::function<void(const QByteArray &, const QList<QSslError> &, QNetworkReply::NetworkError, const QString &, int)> onComplete)
{
auto sslErrors = QSharedPointer<QList<QSslError>>::create();
if (proxyUrl.isEmpty()) {
onComplete(QByteArray(), false, *sslErrors, QNetworkReply::InternalServerError, "empty proxy url", 0);
onComplete(QByteArray(), *sslErrors, QNetworkReply::InternalServerError, "empty proxy url", 0);
return;
}
@@ -648,7 +597,7 @@ void GatewayController::bypassProxyAsync(
connect(reply, &QNetworkReply::sslErrors, this, [sslErrors](const QList<QSslError> &errors) { *sslErrors = errors; });
connect(reply, &QNetworkReply::finished, this, [sslErrors, onComplete, encRequestData, reply, this]() {
connect(reply, &QNetworkReply::finished, this, [sslErrors, onComplete, reply]() {
QByteArray encryptedResponseBody = reply->readAll();
QString replyErrorString = reply->errorString();
auto replyError = reply->error();
@@ -656,10 +605,6 @@ void GatewayController::bypassProxyAsync(
reply->deleteLater();
auto decryptionResult =
tryDecryptResponseBody(encryptedResponseBody, replyError, encRequestData.key, encRequestData.iv, encRequestData.salt);
onComplete(decryptionResult.decryptedBody, decryptionResult.isDecryptionSuccessful, *sslErrors, replyError, replyErrorString,
httpStatusCode);
onComplete(encryptedResponseBody, *sslErrors, replyError, replyErrorString, httpStatusCode);
});
}
+3 -10
View File
@@ -36,18 +36,11 @@ private:
amnezia::ErrorCode errorCode;
};
struct DecryptionResult
{
QByteArray decryptedBody;
bool isDecryptionSuccessful;
};
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);
bool shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &decryptedResponseBody, bool isDecryptionSuccessful);
bool shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &responseBody, bool checkEncryption,
const QByteArray &key = "", const QByteArray &iv = "", const QByteArray &salt = "");
void bypassProxy(const QString &endpoint, const QString &serviceType, const QString &userCountryCode,
std::function<QNetworkReply *(const QString &url)> requestFunction,
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction);
@@ -57,7 +50,7 @@ private:
void getProxyUrlAsync(const QStringList proxyUrls, const int currentProxyIndex, std::function<void(const QString &)> onComplete);
void bypassProxyAsync(
const QString &endpoint, const QString &proxyUrl, EncryptedRequestData encRequestData,
std::function<void(const QByteArray &, bool, const QList<QSslError> &, QNetworkReply::NetworkError, const QString &, int)> onComplete);
std::function<void(const QByteArray &, const QList<QSslError> &, QNetworkReply::NetworkError, const QString &, int)> onComplete);
int m_requestTimeoutMsecs;
QString m_gatewayEndpoint;
+7 -25
View File
@@ -345,7 +345,7 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
return true;
}
if (ContainerProps::isAwgContainer(container)) {
if (container == DockerContainer::Awg) {
if ((oldProtoConfig.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)
@@ -367,11 +367,11 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
|| (oldProtoConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader)
!= newProtoConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader))
|| (oldProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader))
!= newProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader)
|| (oldProtoConfig.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)
!= newProtoConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize)))
!= newProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader))
// || (oldProtoConfig.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)
// != newProtoConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize))
return true;
}
@@ -419,18 +419,6 @@ ErrorCode ServerController::installDockerWorker(const ServerCredentials &credent
cbReadStdOut, cbReadStdErr);
qDebug().noquote() << "ServerController::installDockerWorker" << stdOut;
if (container == DockerContainer::Awg2) {
QRegularExpression regex(R"(Linux\s+(\d+)\.(\d+)[^\d]*)");
QRegularExpressionMatch match = regex.match(stdOut);
if (match.hasMatch()) {
int majorVersion = match.captured(1).toInt();
int minorVersion = match.captured(2).toInt();
if (majorVersion < 4 || (majorVersion == 4 && minorVersion < 14)) {
return ErrorCode::ServerLinuxKernelTooOld;
}
}
}
if (stdOut.contains("lock"))
return ErrorCode::ServerPacketManagerError;
if (stdOut.contains("command not found"))
@@ -660,11 +648,6 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
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({ { "$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
vars.append({ { "$SOCKS5_PROXY_PORT", socks5ProxyConfig.value(config_key::port).toString(protocols::socks5Proxy::defaultPort) } });
@@ -674,8 +657,7 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
vars.append({ { "$SOCKS5_USER", socks5user } });
vars.append({ { "$SOCKS5_AUTH_TYPE", socks5user.isEmpty() ? "none" : "strong" } });
QString serverIp = (!ContainerProps::isAwgContainer(container) &&
container != DockerContainer::WireGuard && container != DockerContainer::Xray)
QString serverIp = (container != DockerContainer::Awg && container != DockerContainer::WireGuard && container != DockerContainer::Xray)
? NetworkUtilities::getIPAddress(credentials.hostName)
: credentials.hostName;
if (!serverIp.isEmpty()) {
@@ -99,12 +99,11 @@ QJsonObject VpnConfigurationsController::createVpnConfiguration(const QPair<QStr
protocolConfigString = configurator->processConfigWithLocalSettings(dns, isApiConfig, protocolConfigString);
QJsonObject vpnConfigData = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
if (ContainerProps::isAwgContainer(container) || container == DockerContainer::WireGuard) {
if (container == DockerContainer::Awg || container == DockerContainer::WireGuard) {
// add mtu for old configs
if (vpnConfigData[config_key::mtu].toString().isEmpty()) {
vpnConfigData[config_key::mtu] =
ContainerProps::isAwgContainer(container) ? protocols::awg::defaultMtu :
protocols::wireguard::defaultMtu;
container == DockerContainer::Awg ? protocols::awg::defaultMtu : protocols::wireguard::defaultMtu;
}
}
-2
View File
@@ -61,7 +61,6 @@ namespace amnezia
ServerDockerOnCgroupsV2 = 211,
ServerCgroupMountpoint = 212,
DockerPullRateLimit = 213,
ServerLinuxKernelTooOld = 214,
// Ssh connection errors
SshRequestDeniedError = 300,
@@ -122,7 +121,6 @@ namespace amnezia
ApiMigrationError = 1110,
ApiUpdateRequestError = 1111,
ApiSubscriptionExpiredError = 1112,
ApiPurchaseError = 1113,
// QFile errors
OpenError = 1200,
-2
View File
@@ -29,7 +29,6 @@ QString errorString(ErrorCode code) {
case(ErrorCode::ServerDockerOnCgroupsV2): errorMessage = QObject::tr("Docker error: runc doesn't work on cgroups v2"); break;
case(ErrorCode::ServerCgroupMountpoint): errorMessage = QObject::tr("Server error: cgroup mountpoint does not exist"); break;
case(ErrorCode::DockerPullRateLimit): errorMessage = QObject::tr("Docker error: The pull rate limit has been reached"); break;
case(ErrorCode::ServerLinuxKernelTooOld): errorMessage = QObject::tr("Server error: Linux kernel is too old"); break;
// Libssh errors
case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break;
@@ -79,7 +78,6 @@ QString errorString(ErrorCode code) {
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::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
case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;
+135 -56
View File
@@ -1,74 +1,153 @@
#include "ipcclient.h"
#include "ipc.h"
#include <QRemoteObjectNode>
#include <QtNetwork/qlocalsocket.h>
IpcClient *IpcClient::m_instance = nullptr;
IpcClient::IpcClient(QObject *parent) : QObject(parent)
{
m_node.connectToNode(QUrl("local:" + amnezia::getIpcServiceUrl()));
m_interface.reset(m_node.acquire<IpcInterfaceReplica>());
}
IpcClient& IpcClient::Instance()
IpcClient::~IpcClient()
{
thread_local IpcClient ipcClient;
return ipcClient;
if (m_localSocket)
m_localSocket->close();
}
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> rep = Instance().m_interface;
if (rep.isNull()) {
qCritical() << "IpcClient::Interface(): Failed to acquire replica";
if (!Instance())
return nullptr;
}
if (!rep->waitForSource(1000)) {
qCritical() << "IpcClient::Interface(): Failed to initialize replica";
return nullptr;
}
if (!rep->isReplicaValid()) {
qWarning() << "IpcClient::Interface(): Replica is invalid";
}
return rep;
return Instance()->m_ipcClient;
}
QSharedPointer<IpcProcessInterfaceReplica> IpcClient::CreatePrivilegedProcess()
QSharedPointer<IpcProcessTun2SocksReplica> IpcClient::InterfaceTun2Socks()
{
return withInterface([](QSharedPointer<IpcInterfaceReplica> &iface) -> QSharedPointer<IpcProcessInterfaceReplica> {
auto createPrivilegedProcess = iface->createPrivilegedProcess();
if (!createPrivilegedProcess.waitForFinished()) {
qCritical() << "Failed to create privileged process";
return nullptr;
}
const int pid = createPrivilegedProcess.returnValue();
auto* node = new QRemoteObjectNode();
node->connectToNode(QUrl(QString("local:%1").arg(amnezia::getIpcProcessUrl(pid))));
QSharedPointer<IpcProcessInterfaceReplica> rep(
node->acquire<IpcProcessInterfaceReplica>(),
[node] (IpcProcessInterfaceReplica *ptr) {
delete ptr;
node->deleteLater();
}
);
if (rep.isNull()) {
qCritical() << "IpcClient::CreatePrivilegedProcess(): Failed to acquire replica";
return nullptr;
}
if (!rep->waitForSource()) {
qCritical() << "IpcClient::CreatePrivilegedProcess(): Failed to initialize replica";
return nullptr;
}
if (!rep->isReplicaValid()) {
qCritical() << "IpcClient::CreatePrivilegedProcess(): Replica is invalid";
return nullptr;
}
return rep;
},
[]() -> QSharedPointer<IpcProcessInterfaceReplica> {
if (!Instance())
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;
Instance()->m_localSocket = new QLocalSocket(Instance());
connect(Instance()->m_localSocket.data(), &QLocalSocket::connected, &Instance()->m_ClientNode, []() {
Instance()->m_ClientNode.addClientSideConnection(Instance()->m_localSocket.data());
auto cliNode = Instance()->m_ClientNode.acquire<IpcInterfaceReplica>();
cliNode->waitForSource(5000);
Instance()->m_ipcClient.reset(cliNode);
if (!Instance()->m_ipcClient) {
qWarning() << "IpcClient is not ready!";
}
Instance()->m_ipcClient->waitForSource(1000);
if (!Instance()->m_ipcClient->isReplicaValid()) {
qWarning() << "IpcClient replica is not connected!";
}
auto t2sNode = Instance()->m_ClientNode.acquire<IpcProcessTun2SocksReplica>();
t2sNode->waitForSource(5000);
Instance()->m_Tun2SocksClient.reset(t2sNode);
if (!Instance()->m_Tun2SocksClient) {
qWarning() << "IpcClient::m_Tun2SocksClient is not ready!";
}
Instance()->m_Tun2SocksClient->waitForSource(1000);
if (!Instance()->m_Tun2SocksClient->isReplicaValid()) {
qWarning() << "IpcClient::m_Tun2SocksClient replica is not connected!";
}
});
connect(Instance()->m_localSocket, &QLocalSocket::disconnected,
[instance]() { instance->m_isSocketConnected = false; });
Instance()->m_localSocket->connectToServer(amnezia::getIpcServiceUrl());
Instance()->m_localSocket->waitForConnected();
if (!Instance()->m_ipcClient) {
qDebug() << "IpcClient::init failed";
return false;
}
qDebug() << "IpcClient::init succeed";
instance->m_isSocketConnected = (Instance()->m_ipcClient->isReplicaValid() && Instance()->m_Tun2SocksClient->isReplicaValid());
return Instance()->isSocketConnected();
}
QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
{
if (!Instance()->m_ipcClient || !Instance()->m_ipcClient->isReplicaValid()) {
qWarning() << "IpcClient::createPrivilegedProcess : IpcClient IpcClient replica is not valid";
return nullptr;
}
QRemoteObjectPendingReply<int> futureResult = Instance()->m_ipcClient->createPrivilegedProcess();
futureResult.waitForFinished(5000);
int pid = futureResult.returnValue();
auto pd = QSharedPointer<ProcessDescriptor>(new ProcessDescriptor());
Instance()->m_processNodes.insert(pid, pd);
pd->localSocket.reset(new QLocalSocket(pd->replicaNode.data()));
connect(pd->localSocket.data(), &QLocalSocket::connected, pd->replicaNode.data(), [pd]() {
pd->replicaNode->addClientSideConnection(pd->localSocket.data());
IpcProcessInterfaceReplica *repl = pd->replicaNode->acquire<IpcProcessInterfaceReplica>();
PrivilegedProcess *priv = static_cast<PrivilegedProcess *>(repl);
pd->ipcProcess.reset(priv);
if (!pd->ipcProcess) {
qWarning() << "Acquire PrivilegedProcess failed";
} else {
pd->ipcProcess->waitForSource(1000);
if (!pd->ipcProcess->isReplicaValid()) {
qWarning() << "PrivilegedProcess replica is not connected!";
}
QObject::connect(pd->ipcProcess.data(), &PrivilegedProcess::destroyed, pd->ipcProcess.data(),
[pd]() { pd->replicaNode->deleteLater(); });
}
});
pd->localSocket->connectToServer(amnezia::getIpcProcessUrl(pid));
pd->localSocket->waitForConnected();
auto processReplica = QSharedPointer<PrivilegedProcess>(pd->ipcProcess);
return processReplica;
}
+36 -35
View File
@@ -4,53 +4,54 @@
#include <QLocalSocket>
#include <QObject>
#include "ipc.h"
#include "rep_ipc_interface_replica.h"
#include "rep_ipc_process_interface_replica.h"
#include "rep_ipc_process_tun2socks_replica.h"
#include "privileged_process.h"
class IpcClient : public QObject
{
Q_OBJECT
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();
static QSharedPointer<IpcInterfaceReplica> Interface();
static QSharedPointer<IpcProcessInterfaceReplica> CreatePrivilegedProcess();
bool isSocketConnected() const;
void closeAndResetInstance(bool deleteSelf = false);
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:
private:
QRemoteObjectNode m_node;
QSharedPointer<IpcInterfaceReplica> m_interface;
~IpcClient() override;
QRemoteObjectNode m_ClientNode;
QRemoteObjectNode m_Tun2SocksNode;
QSharedPointer<IpcInterfaceReplica> m_ipcClient;
QPointer<QLocalSocket> m_localSocket;
QPointer<QLocalSocket> m_tun2socksSocket;
QSharedPointer<IpcProcessTun2SocksReplica> m_Tun2SocksClient;
struct ProcessDescriptor {
ProcessDescriptor () {
replicaNode = QSharedPointer<QRemoteObjectNode>(new QRemoteObjectNode());
ipcProcess = QSharedPointer<PrivilegedProcess>();
localSocket = QSharedPointer<QLocalSocket>();
}
QSharedPointer<PrivilegedProcess> ipcProcess;
QSharedPointer<QRemoteObjectNode> replicaNode;
QSharedPointer<QLocalSocket> localSocket;
};
QMap<int, QSharedPointer<ProcessDescriptor>> m_processNodes;
bool m_isSocketConnected {false};
static IpcClient *m_instance;
};
#endif // IPCCLIENT_H
+17 -39
View File
@@ -1,12 +1,11 @@
#include "networkUtilities.h"
#include <QtNetwork/qnetworkinterface.h>
#include <cstddef>
#ifdef Q_OS_WIN
#include <windows.h>
#include <Ipexport.h>
#include <Ws2tcpip.h>
#include <ws2ipdef.h>
#include <stdint.h>
#include <Iphlpapi.h>
#include <Iptypes.h>
#include <WinSock2.h>
@@ -24,22 +23,13 @@
#include <sys/socket.h>
#include <unistd.h>
#endif
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS) && !defined(Q_OS_TVOS) && !defined(MACOS_NE)
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
#include <sys/param.h>
#include <sys/sysctl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.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
#include <QHostAddress>
@@ -249,14 +239,12 @@ DWORD GetAdaptersAddressesWrapper(const ULONG Family,
}
#endif
QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
QString NetworkUtilities::getGatewayAndIface()
{
#ifdef Q_OS_WIN
constexpr int BUFF_LEN = 100;
char buff[BUFF_LEN] = {'\0'};
QString resGateway;
int resIndex = -1;
QString result;
PIP_ADAPTER_ADDRESSES pAdapterAddresses = nullptr;
DWORD dwRetVal =
@@ -264,7 +252,7 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
if (dwRetVal != NO_ERROR) {
qDebug() << "ipv4 stack detect GetAdaptersAddresses failed.";
return {};
return "";
}
PIP_ADAPTER_ADDRESSES pCurAddress = pAdapterAddresses;
@@ -279,9 +267,7 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
struct sockaddr_in addr;
if (inet_pton(AF_INET, buff, &addr.sin_addr) == 1) {
qDebug() << "this is true v4 !";
resGateway = gw;
resIndex = pCurAddress->IfIndex;
result = gw;
}
}
}
@@ -289,7 +275,7 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
}
free(pAdapterAddresses);
return { resGateway, QNetworkInterface::interfaceFromIndex(resIndex) };
return result;
#endif
#ifdef Q_OS_LINUX
constexpr int BUFFER_SIZE = 100;
@@ -306,7 +292,7 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
if ((sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) {
perror("socket failed");
return {};
return "";
}
memset(msgbuf, 0, sizeof(msgbuf));
@@ -330,7 +316,7 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
/* send msg */
if (send(sock, nlmsg, nlmsg->nlmsg_len, 0) < 0) {
perror("send failed");
return {};
return "";
}
/* receive response */
@@ -339,7 +325,7 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
received_bytes = recv(sock, ptr, sizeof(buffer) - msg_len, 0);
if (received_bytes < 0) {
perror("Error in recv");
return {};
return "";
}
nlh = (struct nlmsghdr *) ptr;
@@ -349,7 +335,7 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
(nlmsg->nlmsg_type == NLMSG_ERROR))
{
perror("Error in received packet");
return {};
return "";
}
/* If we received all data break */
@@ -402,12 +388,10 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
}
}
close(sock);
return { gateway_address, QNetworkInterface::interfaceFromName(interface) };
return gateway_address;
#endif
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS) && !defined(Q_OS_TVOS) && !defined(MACOS_NE)
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
QString gateway;
int index = -1;
int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_FLAGS, RTF_GATEWAY};
int afinet_type[] = {AF_INET, AF_INET6};
@@ -417,17 +401,17 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
size_t needed = 0;
if (sysctl(mib, sizeof(mib) / sizeof(int), nullptr, &needed, nullptr, 0) < 0)
return {};
return "";
char* buf;
if ((buf = new char[needed]) == 0)
return {};
return "";
if (sysctl(mib, sizeof(mib) / sizeof(int), buf, &needed, nullptr, 0) < 0)
{
qDebug() << "sysctl: net.route.0.0.dump";
delete[] buf;
return {};
return gateway;
}
struct rt_msghdr* rt;
@@ -465,10 +449,7 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
&(reinterpret_cast<struct sockaddr_in*>(sa_tab[RTAX_GATEWAY]))->sin_addr,
sizeof(struct in_addr));
if (inet_ntop(AF_INET, srcStr4, dstStr4, INET_ADDRSTRLEN) != nullptr)
{
gateway = dstStr4;
index = rt->rtm_index;
}
break;
}
}
@@ -482,10 +463,7 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
&(reinterpret_cast<struct sockaddr_in6*>(sa_tab[RTAX_GATEWAY]))->sin6_addr,
sizeof(struct in6_addr));
if (inet_ntop(AF_INET6, srcStr6, dstStr6, INET6_ADDRSTRLEN) != nullptr)
{
gateway = dstStr6;
index = rt->rtm_index;
}
break;
}
}
@@ -494,6 +472,6 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
free(buf);
}
return { gateway, QNetworkInterface::interfaceFromIndex(index) };
return gateway;
#endif
}
+2 -2
View File
@@ -6,7 +6,7 @@
#include <QString>
#include <QHostAddress>
#include <QNetworkReply>
#include <QtNetwork/qnetworkinterface.h>
class NetworkUtilities : public QObject
{
@@ -17,7 +17,7 @@ public:
static bool checkIPv4Format(const QString &ip);
static bool checkIpSubnetFormat(const QString &ip);
static bool checkIpv6Enabled();
static QPair<QString, QNetworkInterface> getGatewayAndIface();
static QString getGatewayAndIface();
// Returns the Interface Index that could Route to dst
static int AdapterIndexTo(const QHostAddress& dst);
+27 -62
View File
@@ -1,11 +1,8 @@
#include "osSignalHandler.h"
#include <QCoreApplication>
#include <QMetaObject>
#include <QSocketNotifier>
#include "../amnezia_application.h"
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
#include <pthread.h>
#include <signal.h>
@@ -18,8 +15,7 @@
#endif
#ifdef Q_OS_WIN
#include <QAbstractNativeEventFilter>
#include <QMetaObject>
#include <windows.h>
#endif
@@ -29,30 +25,21 @@ namespace
static bool initialized = false;
#ifdef Q_OS_WIN
class WindowsCloseFilter : public QAbstractNativeEventFilter
static BOOL WINAPI consoleHandler(DWORD signal)
{
public:
bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) override
{
MSG *msg = static_cast<MSG *>(message);
switch (msg->message) {
case WM_CLOSE: {
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);
}
}
switch (signal) {
case CTRL_CLOSE_EVENT:
case CTRL_C_EVENT:
case CTRL_BREAK_EVENT:
case CTRL_LOGOFF_EVENT:
case CTRL_SHUTDOWN_EVENT:
if (QCoreApplication::instance()) {
QMetaObject::invokeMethod(QCoreApplication::instance(), "quit", Qt::QueuedConnection);
}
}
return false;
};
};
static WindowsCloseFilter *windowsFilter = nullptr;
return TRUE;
default: return FALSE;
}
}
#endif
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
@@ -83,16 +70,14 @@ namespace
}
});
}
#elif defined(Q_OS_MACOS)
#elif defined(Q_OS_MACX)
static int signalPipe[2] = { -1, -1 };
static QSocketNotifier *socketNotifier = nullptr;
static void macSignalHandler(int)
{
if (signalPipe[1] >= 0) {
const char ch = 1;
::write(signalPipe[1], &ch, sizeof(ch));
}
const char ch = 1;
::write(signalPipe[1], &ch, sizeof(ch));
}
static void setupUnixSignalHandler()
@@ -103,14 +88,6 @@ namespace
::fcntl(signalPipe[0], F_SETFL, O_NONBLOCK);
::fcntl(signalPipe[1], F_SETFL, O_NONBLOCK);
socketNotifier = new QSocketNotifier(signalPipe[0], QSocketNotifier::Read, QCoreApplication::instance());
QObject::connect(socketNotifier, &QSocketNotifier::activated, QCoreApplication::instance(), [](int) {
char buf[16];
::read(signalPipe[0], buf, sizeof(buf));
QCoreApplication::quit();
});
struct sigaction sa {};
sa.sa_handler = macSignalHandler;
sigemptyset(&sa.sa_mask);
@@ -118,6 +95,14 @@ namespace
sigaction(SIGINT, &sa, nullptr);
sigaction(SIGTERM, &sa, nullptr);
socketNotifier = new QSocketNotifier(signalPipe[0], QSocketNotifier::Read, QCoreApplication::instance());
QObject::connect(socketNotifier, &QSocketNotifier::activated, QCoreApplication::instance(), [](int) {
char buf[16];
::read(signalPipe[0], buf, sizeof(buf));
QCoreApplication::quit();
});
}
#endif
@@ -126,8 +111,6 @@ namespace
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
if (socketNotifier) {
socketNotifier->setEnabled(false);
socketNotifier->deleteLater();
socketNotifier = nullptr;
}
if (signalFd >= 0) {
@@ -136,17 +119,8 @@ namespace
}
#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) {
socketNotifier->setEnabled(false);
socketNotifier->deleteLater();
socketNotifier = nullptr;
}
if (signalPipe[0] >= 0) {
@@ -159,14 +133,6 @@ namespace
signalPipe[1] = -1;
}
#endif
#ifdef Q_OS_WIN
if (windowsFilter) {
QCoreApplication::instance()->removeNativeEventFilter(windowsFilter);
delete windowsFilter;
windowsFilter = nullptr;
}
#endif
}
}
@@ -181,13 +147,12 @@ void OsSignalHandler::setup()
initialized = true;
#if (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) || defined(Q_OS_MACOS)
#if (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) || defined(Q_OS_MACX)
setupUnixSignalHandler();
#endif
#ifdef Q_OS_WIN
windowsFilter = new WindowsCloseFilter();
QCoreApplication::instance()->installNativeEventFilter(windowsFilter);
SetConsoleCtrlHandler(consoleHandler, TRUE);
#endif
QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, [] { cleanupUnixSignalHandler(); });
+27
View File
@@ -0,0 +1,27 @@
#include "privileged_process.h"
PrivilegedProcess::PrivilegedProcess() :
IpcProcessInterfaceReplica()
{
}
PrivilegedProcess::~PrivilegedProcess()
{
qDebug() << "PrivilegedProcess::~PrivilegedProcess()";
}
void PrivilegedProcess::waitForFinished(int msecs)
{
QSharedPointer<QEventLoop> loop(new QEventLoop);
connect(this, &PrivilegedProcess::finished, this, [this, loop](int exitCode, QProcess::ExitStatus exitStatus) mutable{
loop->quit();
loop.clear();
});
QTimer::singleShot(msecs, this, [this, loop]() mutable {
loop->quit();
loop.clear();
});
loop->exec();
}
+24
View File
@@ -0,0 +1,24 @@
#ifndef PRIVILEGED_PROCESS_H
#define PRIVILEGED_PROCESS_H
#include <QObject>
#include "rep_ipc_process_interface_replica.h"
// This class is dangerous - instance of this class casted from base class,
// so it support only functions
// Do not add any members into it
//
class PrivilegedProcess : public IpcProcessInterfaceReplica
{
Q_OBJECT
public:
PrivilegedProcess();
~PrivilegedProcess() override;
void waitForFinished(int msecs);
};
#endif // PRIVILEGED_PROCESS_H
+1 -2
View File
@@ -11,8 +11,7 @@ QString amnezia::scriptFolder(amnezia::DockerContainer container)
case DockerContainer::Cloak: return QLatin1String("openvpn_cloak");
case DockerContainer::ShadowSocks: return QLatin1String("openvpn_shadowsocks");
case DockerContainer::WireGuard: return QLatin1String("wireguard");
case DockerContainer::Awg2: return QLatin1String("awg");
case DockerContainer::Awg: return QLatin1String("awg_legacy");
case DockerContainer::Awg: return QLatin1String("awg");
case DockerContainer::Ipsec: return QLatin1String("ipsec");
case DockerContainer::Xray: return QLatin1String("xray");
+12
View File
@@ -440,6 +440,18 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
if (!obj.value("I5").isNull()) {
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;
}
+6
View File
@@ -152,6 +152,12 @@ QString InterfaceConfig::toWgConf(const QMap<QString, QString>& extra) const {
for (const QString& key : m_specialJunk.keys()) {
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.
for (const QString& key : extra.keys()) {
+2
View File
@@ -57,6 +57,8 @@ class InterfaceConfig {
QString m_underloadPacketMagicHeader;
QString m_transportPacketMagicHeader;
QMap<QString, QString> m_specialJunk;
QMap<QString, QString> m_controlledJunk;
QString m_specialHandshakeTimeout;
QJsonObject toJson() const;
QString toWgConf(
-54
View File
@@ -1,54 +0,0 @@
<?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>
@@ -1,23 +0,0 @@
<?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>
+33 -90
View File
@@ -1,14 +1,6 @@
enable_language(Swift)
set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../..)
set(AMNEZIA_THIRDPARTY_ROOT "${CLIENT_ROOT_DIR}/3rd" CACHE PATH "Path to Amnezia client/3rd sources")
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)
set_target_properties(networkextension PROPERTIES
@@ -36,23 +28,6 @@ set_target_properties(networkextension PROPERTIES
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)
set_target_properties(networkextension PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
@@ -70,49 +45,38 @@ endif()
set_target_properties(networkextension PROPERTIES
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
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_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
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_LIBRESOLV libresolv.9.tbd)
if(NOT AMNEZIA_IOS_APPLETV)
target_link_libraries(networkextension PRIVATE ${FW_UI_KIT})
target_link_libraries(networkextension PRIVATE ${FW_LIBRESOLV})
else()
target_link_libraries(networkextension PRIVATE -lresolv)
endif()
target_link_libraries(networkextension PRIVATE ${FW_ASSETS_LIBRARY})
target_link_libraries(networkextension PRIVATE ${FW_MOBILE_CORE})
target_link_libraries(networkextension PRIVATE ${FW_UI_KIT})
target_link_libraries(networkextension PRIVATE ${FW_LIBRESOLV})
target_compile_options(networkextension PRIVATE -DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\")
target_compile_options(networkextension PRIVATE -DNETWORK_EXTENSION=1)
set(WG_APPLE_SOURCE_DIR ${AMNEZIA_THIRDPARTY_ROOT}/amneziawg-apple/Sources)
set(WG_APPLE_SOURCE_DIR ${CLIENT_ROOT_DIR}/3rd/amneziawg-apple/Sources)
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
target_sources(networkextension PRIVATE
${WG_APPLE_SOURCE_DIR}/WireGuardKit/WireGuardAdapter.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/PacketTunnelSettingsGenerator.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/DNSResolver.swift
${WG_APPLE_SOURCE_DIR}/WireGuardNetworkExtension/ErrorNotifier.swift
${WG_APPLE_SOURCE_DIR}/Shared/Keychain.swift
${WG_APPLE_SOURCE_DIR}/Shared/FileManager+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/NETunnelProviderProtocol+Extension.swift
${WG_APPLE_SOURCE_DIR}/Shared/Model/String+ArrayConversion.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/TunnelConfiguration.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/IPAddressRange.swift
@@ -120,50 +84,24 @@ set(NE_WIREGUARD_SOURCES
${WG_APPLE_SOURCE_DIR}/WireGuardKit/DNSServer.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/InterfaceConfiguration.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}/WireGuardKit/Array+ConcurrentMap.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/IPAddress+AddrInfo.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/PrivateKey.swift
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+WireGuard.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
)
set(NE_OPENVPN_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
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+WireGuard.swift
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+OpenVPN.swift
)
set(NE_APPLE_GLUE_SOURCES
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+Xray.swift
${CLIENT_ROOT_DIR}/platforms/ios/WGConfig.swift
${CLIENT_ROOT_DIR}/platforms/ios/XrayConfig.swift
${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
${CMAKE_CURRENT_SOURCE_DIR}/PrivacyInfo.xcprivacy
)
@@ -172,16 +110,21 @@ set_property(TARGET networkextension APPEND PROPERTY RESOURCE
${CMAKE_CURRENT_SOURCE_DIR}/PrivacyInfo.xcprivacy
)
## Build wireguard-go-version.h
execute_process(
COMMAND go list -m golang.zx2c4.com/wireguard
WORKING_DIRECTORY ${CLIENT_ROOT_DIR}/3rd/wireguard-apple/Sources/WireGuardKitGo
OUTPUT_VARIABLE WG_VERSION_FULL
)
string(REGEX REPLACE ".*v\([0-9.]*\).*" "\\1" WG_VERSION_STRING 1.1.1)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/wireguard-go-version.h.in
${CMAKE_CURRENT_BINARY_DIR}/wireguard-go-version.h)
target_sources(networkextension PRIVATE
${CMAKE_CURRENT_BINARY_DIR}/wireguard-go-version.h)
target_include_directories(networkextension PRIVATE ${CLIENT_ROOT_DIR})
target_include_directories(networkextension PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
find_package(awg-apple REQUIRED)
target_link_libraries(networkextension PRIVATE amnezia::awg-apple)
target_link_libraries(networkextension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/wireguard/ios/arm64/libwg-go.a)
if(NOT AMNEZIA_IOS_APPLETV)
find_package(openvpnadapter REQUIRED)
target_link_libraries(networkextension PRIVATE amnezia::openvpnadapter)
find_package(hev-socks5-tunnel REQUIRED)
target_link_libraries(networkextension PRIVATE heiher::hev-socks5-tunnel)
endif()
target_link_libraries(networkextension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/xray/HevSocks5Tunnel.xcframework)
@@ -0,0 +1,3 @@
#ifndef WIREGUARD_GO_VERSION
#define WIREGUARD_GO_VERSION "@WG_VERSION_STRING@"
#endif // WIREGUARD_GO_VERSION
+17 -6
View File
@@ -114,14 +114,25 @@ set_property(TARGET AmneziaVPNNetworkExtension APPEND PROPERTY RESOURCE
${CMAKE_CURRENT_SOURCE_DIR}/PrivacyInfo.xcprivacy
)
## Build wireguard-go-version.h
execute_process(
COMMAND go list -m golang.zx2c4.com/wireguard
WORKING_DIRECTORY ${CLIENT_ROOT_DIR}/3rd/wireguard-apple/Sources/WireGuardKitGo
OUTPUT_VARIABLE WG_VERSION_FULL
)
string(REGEX REPLACE ".*v\([0-9.]*\).*" "\\1" WG_VERSION_STRING 1.1.1)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/wireguard-go-version.h.in
${CMAKE_CURRENT_BINARY_DIR}/wireguard-go-version.h)
target_sources(AmneziaVPNNetworkExtension PRIVATE
${CMAKE_CURRENT_BINARY_DIR}/wireguard-go-version.h)
target_include_directories(AmneziaVPNNetworkExtension PRIVATE ${CLIENT_ROOT_DIR})
target_include_directories(AmneziaVPNNetworkExtension PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
find_package(openvpnadapter REQUIRED)
target_link_libraries(AmneziaVPNNetworkExtension PRIVATE amnezia::openvpnadapter)
target_link_libraries(AmneziaVPNNetworkExtension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/wireguard/macos/universal2/libwg-go.a)
find_package(awg-apple REQUIRED)
target_link_libraries(AmneziaVPNNetworkExtension PRIVATE amnezia::awg-apple)
message(${CLIENT_ROOT_DIR})
message(${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/xray/HevSocks5Tunnel.xcframework/macos-arm64_x86_64/libhev-socks5-tunnel.a)
target_link_libraries(AmneziaVPNNetworkExtension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/xray/HevSocks5Tunnel.xcframework/macos-arm64_x86_64/libhev-socks5-tunnel.a)
find_package(hev-socks5-tunnel REQUIRED)
target_link_libraries(AmneziaVPNNetworkExtension PRIVATE heiher::hev-socks5-tunnel)
target_include_directories(AmneziaVPNNetworkExtension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/xray/HevSocks5Tunnel.xcframework/macos-arm64_x86_64/Headers)
@@ -0,0 +1,3 @@
#ifndef WIREGUARD_GO_VERSION
#define WIREGUARD_GO_VERSION "@WG_VERSION_STRING@"
#endif // WIREGUARD_GO_VERSION
+4 -3
View File
@@ -12,11 +12,11 @@
#include "Windows.h"
#endif
#if defined(Q_OS_IOS) || defined(Q_OS_TVOS)
#if defined(Q_OS_IOS)
#include "platforms/ios/QtAppDelegate-C-Interface.h"
#endif
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(Q_OS_TVOS) && !defined(MACOS_NE)
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
bool isAnotherInstanceRunning()
{
QLocalSocket socket;
@@ -47,7 +47,7 @@ int main(int argc, char *argv[])
AmneziaApplication app(argc, argv);
OsSignalHandler::setup();
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(Q_OS_TVOS) && !defined(MACOS_NE)
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
if (isAnotherInstanceRunning()) {
QTimer::singleShot(1000, &app, [&]() { app.quit(); });
return app.exec();
@@ -75,6 +75,7 @@ int main(int argc, char *argv[])
qInfo().noquote() << QString("Started %1 version %2 %3").arg(APPLICATION_NAME, APP_VERSION, GIT_COMMIT_HASH);
qInfo().noquote() << QString("%1 (%2)").arg(QSysInfo::prettyProductName(), QSysInfo::currentCpuArchitecture());
qInfo().noquote() << QString("SSL backend: %1").arg(QSslSocket::sslLibraryVersionString());
return app.exec();
}
+27 -10
View File
@@ -260,33 +260,50 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
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::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()
&& !wgConfig.value(amnezia::config_key::junkPacketMinSize).isUndefined()
&& !wgConfig.value(amnezia::config_key::junkPacketMaxSize).isUndefined()
&& !wgConfig.value(amnezia::config_key::initPacketJunkSize).isUndefined()
&& !wgConfig.value(amnezia::config_key::responsePacketJunkSize).isUndefined()
&& !wgConfig.value(amnezia::config_key::cookieReplyPacketJunkSize).isUndefined()
&& !wgConfig.value(amnezia::config_key::transportPacketJunkSize).isUndefined()
// && !wgConfig.value(amnezia::config_key::cookieReplyPacketJunkSize).isUndefined()
// && !wgConfig.value(amnezia::config_key::transportPacketJunkSize).isUndefined()
&& !wgConfig.value(amnezia::config_key::initPacketMagicHeader).isUndefined()
&& !wgConfig.value(amnezia::config_key::responsePacketMagicHeader).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::specialJunk2).isUndefined()
&& !wgConfig.value(amnezia::config_key::specialJunk3).isUndefined()
&& !wgConfig.value(amnezia::config_key::specialJunk4).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::junkPacketMinSize, wgConfig.value(amnezia::config_key::junkPacketMinSize));
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::responsePacketJunkSize, wgConfig.value(amnezia::config_key::responsePacketJunkSize));
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::cookieReplyPacketJunkSize, wgConfig.value(amnezia::config_key::cookieReplyPacketJunkSize));
// 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::responsePacketMagicHeader, wgConfig.value(amnezia::config_key::responsePacketMagicHeader));
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::specialJunk1, wgConfig.value(amnezia::config_key::specialJunk1));
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::specialJunk4, wgConfig.value(amnezia::config_key::specialJunk4));
json.insert(amnezia::config_key::specialJunk5, wgConfig.value(amnezia::config_key::specialJunk5));
// 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::specialJunk3, wgConfig.value(amnezia::config_key::specialJunk3));
// 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::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);
+9 -3
View File
@@ -72,9 +72,9 @@ void NetworkWatcher::initialize() {
connect(m_impl, &NetworkWatcherImpl::unsecuredNetwork, this,
&NetworkWatcher::unsecuredNetwork);
connect(m_impl, &NetworkWatcherImpl::networkChanged, this,
&NetworkWatcher::networkChanged);
connect(m_impl, &NetworkWatcherImpl::wakeup, this,
&NetworkWatcher::wakeup);
&NetworkWatcher::networkChange);
connect(m_impl, &NetworkWatcherImpl::sleepMode, this,
&NetworkWatcher::onSleepMode);
m_impl->initialize();
// Enable sleep/wake monitoring for VPN auto-reconnection
@@ -97,6 +97,12 @@ void NetworkWatcher::settingsChanged() {
logger.debug() << "NetworkWatcher settings changed - keeping sleep monitoring active";
}
void NetworkWatcher::onSleepMode()
{
logger.debug() << "Resumed from sleep mode";
emit sleepMode();
}
void NetworkWatcher::unsecuredNetwork(const QString& networkName,
const QString& networkId) {
logger.debug() << "Unsecured network:" << logger.sensitive(networkName)
+4 -2
View File
@@ -29,11 +29,13 @@ public:
// false to restore.
void simulateDisconnection(bool simulatedDisconnection);
void onSleepMode();
QNetworkInformation::Reachability getReachability();
signals:
void networkChanged();
void wakeup();
void networkChange();
void sleepMode();
private:
void settingsChanged();
+1 -1
View File
@@ -41,7 +41,7 @@ signals:
// TODO: Only windows-networkwatcher has this, the other plattforms should
// too.
void networkChanged(QString newBSSID);
void wakeup();
void sleepMode();
private:
@@ -131,7 +131,7 @@ extension PacketTunnelProvider {
}
startHandler = completionHandler
ovpnAdapter?.connect(using: openVPNPacketFlow())
ovpnAdapter?.connect(using: packetFlow)
}
func handleOpenVPNStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
@@ -153,7 +153,7 @@ extension PacketTunnelProvider {
}
func stopOpenVPN(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
ovpnLog(.info, message: "Stopping tunnel: reason: \(reason.amneziaDescription)")
ovpnLog(.info, message: "Stopping tunnel: reason: \(reason.description)")
stopHandler = completionHandler
if vpnReachability.isTracking {
@@ -293,3 +293,5 @@ extension PacketTunnelProvider: OpenVPNAdapterDelegate {
ovpnLog(.info, message: logMessage)
}
}
extension NEPacketTunnelFlow: OpenVPNAdapterPacketFlow {}
@@ -94,24 +94,15 @@ extension PacketTunnelProvider {
}
} catch {
wg_log(.error, message: "Can't parse WG config: \(error.localizedDescription)")
errorNotifier.notify(PacketTunnelProviderError.savedProtocolConfigurationIsInvalid)
completionHandler(PacketTunnelProviderError.savedProtocolConfigurationIsInvalid)
completionHandler(nil)
return
}
}
func handleWireguardStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
guard let completionHandler = completionHandler else { return }
guard let wgAdapter = wgAdapter else {
completionHandler(nil)
return
}
wgAdapter.getRuntimeConfiguration { settings in
guard let settings = settings else {
completionHandler(nil)
return
}
let components = settings.components(separatedBy: "\n")
wgAdapter?.getRuntimeConfiguration { settings in
let components = settings!.components(separatedBy: "\n")
var settingsDictionary: [String: String] = [:]
for component in components {
@@ -140,7 +131,7 @@ extension PacketTunnelProvider {
}
}
func handleWireguardAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
private func handleWireguardAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
guard let completionHandler = completionHandler else { return }
if messageData.count == 1 && messageData[0] == 0 {
wgAdapter?.getRuntimeConfiguration { settings in
@@ -185,7 +176,7 @@ extension PacketTunnelProvider {
}
func stopWireguard(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
wg_log(.info, message: "Stopping tunnel: reason: \(reason.amneziaDescription)")
wg_log(.info, message: "Stopping tunnel: reason: \(reason.description)")
wgAdapter?.stop { error in
ErrorNotifier.removeLastErrorFile()
@@ -107,8 +107,6 @@ extension PacketTunnelProvider {
return
}
self?.updateActiveInterfaceIndexForCurrentPath()
// Launch xray
self?.setupAndStartXray(configData: updatedData) { xrayError in
if let xrayError {
@@ -135,15 +133,6 @@ extension PacketTunnelProvider {
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,
completionHandler: @escaping (Error?) -> Void) {
let path = Constants.cachesDirectory.appendingPathComponent("config.json", isDirectory: false).path
@@ -153,17 +142,6 @@ extension PacketTunnelProvider {
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,
path,
Int64.max)
+22 -208
View File
@@ -1,11 +1,8 @@
import Foundation
import NetworkExtension
import Network
import os
import Darwin
#if !os(tvOS)
import OpenVPNAdapter
#endif
enum TunnelProtoType: String {
case wireguard, openvpn, xray
@@ -40,112 +37,18 @@ struct Constants {
class PacketTunnelProvider: NEPacketTunnelProvider {
var wgAdapter: WireGuardAdapter?
#if !os(tvOS)
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 splitTunnelSites: [String]?
#if !os(tvOS)
let vpnReachability = OpenVPNReachability()
#endif
var startHandler: ((Error?) -> Void)?
var stopHandler: (() -> Void)?
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) {
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 {
if let completionHandler {
completionHandler(nil)
@@ -156,10 +59,6 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
neLog(.info, title: "App said: ", message: message)
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")
return
}
@@ -205,30 +104,15 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
return
}
didReceiveInitialPathUpdate = false
updateActiveInterfaceIndexForCurrentPath()
switch protoType {
case .wireguard:
startWireguard(activationAttemptId: activationAttemptId,
errorNotifier: errorNotifier,
completionHandler: completionHandler)
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)
#endif
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)
#endif
}
}
@@ -245,18 +129,10 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
stopWireguard(with: reason,
completionHandler: completionHandler)
case .openvpn:
#if os(tvOS)
completionHandler()
#else
stopOpenVPN(with: reason,
completionHandler: completionHandler)
#endif
case .xray:
#if os(tvOS)
completionHandler()
#else
stopXray(completionHandler: completionHandler)
#endif
}
}
@@ -270,11 +146,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
case .wireguard:
handleWireguardStatusMessage(messageData, completionHandler: completionHandler)
case .openvpn:
#if !os(tvOS)
handleOpenVPNStatusMessage(messageData, completionHandler: completionHandler)
#else
completionHandler?(nil)
#endif
case .xray:
break;
}
@@ -285,97 +157,41 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
of object: Any?,
change: [NSKeyValueChangeKey: Any]?,
context: UnsafeMutableRawPointer?) {
guard Constants.kDefaultPathKey == keyPath else {
guard Constants.kDefaultPathKey != keyPath else { return }
// 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
}
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: Network.NWPath, completion: @escaping (Error?) -> Void) {
updateActiveInterfaceIndex(for: changePath)
neLog(.info, message: "Tunnel restarted.")
private func handle(networkChange changePath: NWPath, completion: @escaping (Error?) -> Void) {
wg_log(.info, message: "Tunnel restarted.")
startTunnel(options: nil, completionHandler: completion)
}
}
private extension PacketTunnelProvider {
func pathSignature(for path: Network.NWPath) -> String {
var signatureComponents = [String(describing: path.status)]
signatureComponents.append(path.isExpensive ? "exp" : "noexp")
signatureComponents.append(path.isConstrained ? "con" : "nocon")
let preferredTypes: [NWInterface.InterfaceType] = [.wiredEthernet, .wifi, .cellular, .loopback, .other]
let sortedInterfaces = path.availableInterfaces.sorted { lhs, rhs in
if lhs.type == rhs.type {
return lhs.index < rhs.index
}
let lhsOrder = preferredTypes.firstIndex(of: lhs.type) ?? preferredTypes.count
let rhsOrder = preferredTypes.firstIndex(of: rhs.type) ?? preferredTypes.count
if lhsOrder == rhsOrder {
return lhs.index < rhs.index
}
return lhsOrder < rhsOrder
}
for interface in sortedInterfaces {
let typeName: String
switch interface.type {
case .wiredEthernet: typeName = "ethernet"
case .wifi: typeName = "wifi"
case .cellular: typeName = "cellular"
case .loopback: typeName = "loopback"
case .other: typeName = "other"
@unknown default: typeName = "unknown"
}
signatureComponents.append("\(typeName):\(interface.index)")
}
// Include currently used interface preference ordering
for type in preferredTypes {
let usesType = path.usesInterfaceType(type)
signatureComponents.append("uses-\(type):\(usesType)")
}
return signatureComponents.joined(separator: "|")
}
}
extension WireGuardLogLevel {
var osLogLevel: OSLogType {
switch self {
case .verbose:
return .debug
case .error:
return .error
}
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 {
extension NEProviderStopReason: CustomStringConvertible {
public var description: String {
switch self {
case .none:
return "No specific reason"
@@ -407,8 +223,6 @@ extension NEProviderStopReason {
return "The current console user changed"
case .connectionFailed:
return "The connection failed"
case .internalError:
return "The network extension reported an internal error"
case .sleep:
return "A stop reason indicating the VPNC enabled disconnect on sleep and the device went to sleep"
case .appUpdate:
+1 -1
View File
@@ -1,4 +1,4 @@
#if !MACOS_NE && !TARGET_OS_TV
#if !MACOS_NE
#include "QRCodeReaderBase.h"
#import <UIKit/UIKit.h>
+7 -40
View File
@@ -11,7 +11,13 @@ class ScreenProtection {
import UIKit
public func toggleScreenshots(_ isEnabled: Bool) {
ScreenProtection.shared.setScreenshotsEnabled(isEnabled)
let window = UIApplication.shared.keyWindows.first!
if isEnabled {
ScreenProtection.shared.disable(for: window.rootViewController!.view)
} else {
ScreenProtection.shared.enable(for: window.rootViewController!.view)
}
}
extension UIApplication {
@@ -39,45 +45,6 @@ class ScreenProtection {
private var blurView: UIVisualEffectView?
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) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
-39
View File
@@ -1,39 +0,0 @@
/* 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
@@ -1,264 +0,0 @@
/* 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
+38 -40
View File
@@ -6,6 +6,8 @@ struct WGConfig: Decodable {
let junkPacketCount, junkPacketMinSize, junkPacketMaxSize: String?
let initPacketJunkSize, responsePacketJunkSize, cookieReplyPacketJunkSize, transportPacketJunkSize: String?
let specialJunk1, specialJunk2, specialJunk3, specialJunk4, specialJunk5: String?
let controlledJunk1, controlledJunk2, controlledJunk3: String?
let specialHandshakeTimeout: String?
let dns1: String
let dns2: String
let mtu: String
@@ -26,6 +28,8 @@ struct WGConfig: Decodable {
case junkPacketCount = "Jc", junkPacketMinSize = "Jmin", junkPacketMaxSize = "Jmax"
case initPacketJunkSize = "S1", responsePacketJunkSize = "S2", cookieReplyPacketJunkSize = "S3", transportPacketJunkSize = "S4"
case specialJunk1 = "I1", specialJunk2 = "I2", specialJunk3 = "I3", specialJunk4 = "I4", specialJunk5 = "I5"
case controlledJunk1 = "J1", controlledJunk2 = "J2", controlledJunk3 = "J3"
case specialHandshakeTimeout = "Itime"
case dns1
case dns2
case mtu
@@ -42,64 +46,58 @@ struct WGConfig: Decodable {
}
var settings: String {
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 "" }
guard junkPacketCount != nil else { return "" }
var settingsLines: [String] = []
// Required parameters when junkPacketCount is present
settingsLines.append("Jc = \(junkPacketCount)")
settingsLines.append("Jmin = \(junkPacketMinSize)")
settingsLines.append("Jmax = \(junkPacketMaxSize)")
settingsLines.append("S1 = \(initPacketJunkSize)")
settingsLines.append("S2 = \(responsePacketJunkSize)")
settingsLines.append("H1 = \(initPacketMagicHeader)")
settingsLines.append("H2 = \(responsePacketMagicHeader)")
settingsLines.append("H3 = \(underloadPacketMagicHeader)")
settingsLines.append("H4 = \(transportPacketMagicHeader)")
settingsLines.append("Jc = \(junkPacketCount!)")
settingsLines.append("Jmin = \(junkPacketMinSize!)")
settingsLines.append("Jmax = \(junkPacketMaxSize!)")
settingsLines.append("S1 = \(initPacketJunkSize!)")
settingsLines.append("S2 = \(responsePacketJunkSize!)")
settingsLines.append("H1 = \(initPacketMagicHeader!)")
settingsLines.append("H2 = \(responsePacketMagicHeader!)")
settingsLines.append("H3 = \(underloadPacketMagicHeader!)")
settingsLines.append("H4 = \(transportPacketMagicHeader!)")
// Optional parameters - only add if not nil and not empty
if let s3 = trimmed(cookieReplyPacketJunkSize) {
if let s3 = cookieReplyPacketJunkSize, !s3.isEmpty {
settingsLines.append("S3 = \(s3)")
}
if let s4 = trimmed(transportPacketJunkSize) {
if let s4 = transportPacketJunkSize, !s4.isEmpty {
settingsLines.append("S4 = \(s4)")
}
if let i1 = trimmed(specialJunk1) {
if let i1 = specialJunk1, !i1.isEmpty {
settingsLines.append("I1 = \(i1)")
}
if let i2 = trimmed(specialJunk2) {
if let i2 = specialJunk2, !i2.isEmpty {
settingsLines.append("I2 = \(i2)")
}
if let i3 = trimmed(specialJunk3) {
if let i3 = specialJunk3, !i3.isEmpty {
settingsLines.append("I3 = \(i3)")
}
if let i4 = trimmed(specialJunk4) {
if let i4 = specialJunk4, !i4.isEmpty {
settingsLines.append("I4 = \(i4)")
}
if let i5 = trimmed(specialJunk5) {
if let i5 = specialJunk5, !i5.isEmpty {
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")
}
+2 -32
View File
@@ -2,13 +2,6 @@
#define IOS_CONTROLLER_H
#include "protocols/vpnprotocol.h"
#include <functional>
#include <QVariant>
#include <QVariantMap>
#include <QStringList>
#include <QList>
#include <QElapsedTimer>
#include <atomic>
#ifdef __OBJC__
#import <Foundation/Foundation.h>
@@ -62,24 +55,7 @@ public:
bool shareText(const QStringList &filesToSend);
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();
bool isTestFlight();
signals:
void connectionStateChanged(Vpn::ConnectionState state);
void bytesChanged(quint64 receivedBytes, quint64 sentBytes);
@@ -105,7 +81,6 @@ private:
bool startXray(const QString &jsonConfig);
void startTunnel();
void emitConnectionStateIfChanged(Vpn::ConnectionState state);
private:
void *m_iosControllerWrapper {};
@@ -119,13 +94,8 @@ private:
amnezia::Proto m_proto;
QJsonObject m_rawConfig;
QString m_tunnelId;
uint64_t m_txBytes = 0;
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 };
uint64_t m_txBytes;
uint64_t m_rxBytes;
};
#endif // IOS_CONTROLLER_H
+27 -278
View File
@@ -10,7 +10,6 @@
#include "../protocols/vpnprotocol.h"
#import "ios_controller_wrapper.h"
#import "StoreKitController.h"
const char* Action::start = "start";
const char* Action::restart = "restart";
@@ -93,48 +92,6 @@ 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 {
IosController* s_instance = nullptr;
}
@@ -144,9 +101,6 @@ IosController::IosController() : QObject()
s_instance = this;
m_iosControllerWrapper = [[IosControllerWrapper alloc] initWithCppController:this];
// Initialize StoreKitController early to start observing the payment queue
[StoreKitController sharedInstance];
[[NSNotificationCenter defaultCenter]
removeObserver: (__bridge NSObject *)m_iosControllerWrapper];
[[NSNotificationCenter defaultCenter]
@@ -156,15 +110,6 @@ IosController::IosController() : QObject()
}
void IosController::emitConnectionStateIfChanged(Vpn::ConnectionState state)
{
if (m_lastEmittedState == state) {
return;
}
m_lastEmittedState = state;
emit connectionStateChanged(state);
}
IosController* IosController::Instance() {
if (!s_instance) {
s_instance = new IosController();
@@ -331,65 +276,33 @@ void IosController::disconnectVpn()
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 *actionValue = [NSString stringWithUTF8String:Action::getStatus];
NSString *tunnelIdKey = [NSString stringWithUTF8String:MessageKey::tunnelId];
NSString *tunnelIdValue = !m_tunnelId.isEmpty() ? m_tunnelId.toNSString() : @"";
NSDictionary* message = @{actionKey: actionValue, tunnelIdKey: tunnelIdValue};
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sendVpnExtensionMessage(message, [&](NSDictionary* response){
if (!response) {
QMetaObject::invokeMethod(this, [this]() {
m_statusRequestInFlight = false;
}, Qt::QueuedConnection);
return;
uint64_t txBytes = [response[@"tx_bytes"] intValue];
uint64_t rxBytes = [response[@"rx_bytes"] intValue];
uint64_t last_handshake_time_sec = 0;
#if !MACOS_NE
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";
}
const uint64_t txBytes = uint64FromResponse(response, @"tx_bytes");
const uint64_t rxBytes = uint64FromResponse(response, @"rx_bytes");
const long long last_handshake_time_sec = int64FromResponse(response, @"last_handshake_time_sec");
if (last_handshake_time_sec < 0) {
disconnectVpn();
qDebug() << "Invalid handshake time, disconnecting VPN.";
}
#endif
QMetaObject::invokeMethod(this, [this, txBytes, rxBytes, last_handshake_time_sec]() {
if (isWireGuardBasedProto(m_proto) && m_handshakeAwaiting) {
const bool hasHandshakeData = (last_handshake_time_sec >= 0);
const bool hasFreshHandshake = hasHandshakeData &&
((last_handshake_time_sec > 0) ||
(rxBytes >= kHandshakeRxThreshold) ||
(txBytes >= kHandshakeRxThreshold));
if (hasFreshHandshake) {
m_handshakeConfirmed = true;
m_handshakeAwaiting = false;
m_handshakeTimer.invalidate();
qDebug() << "IosController::checkStatus : handshake confirmed";
emitConnectionStateIfChanged(Vpn::ConnectionState::Connected);
} else if (m_handshakeTimer.isValid() &&
m_handshakeTimer.elapsed() > kHandshakeTimeoutMs) {
m_handshakeTimer.restart();
qDebug() << "IosController::checkStatus : handshake timed out, keeping tunnel alive";
emitConnectionStateIfChanged(Vpn::ConnectionState::Reconnecting);
}
}
emit bytesChanged(rxBytes - m_rxBytes, txBytes - m_txBytes);
m_rxBytes = rxBytes;
m_txBytes = txBytes;
m_statusRequestInFlight = false;
}, Qt::QueuedConnection);
});
emit bytesChanged(rxBytes - m_rxBytes, txBytes - m_txBytes);
m_rxBytes = rxBytes;
m_txBytes = txBytes;
});
}
@@ -496,22 +409,7 @@ void IosController::vpnStatusDidChange(void *pNotification)
}
}
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);
emit connectionStateChanged(iosStatusToState(session.status));
}
}
@@ -772,6 +670,10 @@ bool IosController::setupAwg()
wgConfig.insert(config_key::specialJunk3, config[config_key::specialJunk3]);
wgConfig.insert(config_key::specialJunk4, config[config_key::specialJunk4]);
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);
QString wgConfigDocStr(wgConfigDoc.toJson(QJsonDocument::Compact));
@@ -897,9 +799,6 @@ void IosController::sendVpnExtensionMessage(NSDictionary* message, std::function
{
if (!m_currentTunnel) {
qDebug() << "Cannot set an extension callback without a tunnel manager";
if (callback) {
callback(nil);
}
return;
}
@@ -909,9 +808,6 @@ void IosController::sendVpnExtensionMessage(NSDictionary* message, std::function
if (!data || error) {
qDebug() << "Failed to serialize message to VpnExtension as JSON. Error:"
<< [error.localizedDescription UTF8String];
if (callback) {
callback(nil);
}
return;
}
@@ -942,27 +838,16 @@ void IosController::sendVpnExtensionMessage(NSDictionary* message, std::function
[session sendProviderMessage:data returnError:&sendError responseHandler:completionHandler];
} else {
qDebug() << "Method sendProviderMessage:responseHandler:error: does not exist";
if (callback) {
callback(nil);
}
return;
}
if (sendError) {
qDebug() << "Failed to send message to VpnExtension. Error:"
<< [sendError.localizedDescription UTF8String];
if (callback) {
callback(nil);
}
}
}
bool IosController::shareText(const QStringList& filesToSend) {
#if defined(Q_OS_TVOS)
Q_UNUSED(filesToSend)
return false;
#else
NSMutableArray *sharingItems = [NSMutableArray new];
for (int i = 0; i < filesToSend.size(); i++) {
@@ -971,7 +856,7 @@ bool IosController::shareText(const QStringList& filesToSend) {
}
#if !MACOS_NE
UIViewController *qtController = getViewController();
if (!qtController) return false;
if (!qtController) return;
UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:sharingItems applicationActivities:nil];
#endif
@@ -995,25 +880,23 @@ bool IosController::shareText(const QStringList& filesToSend) {
wait.exec();
return isAccepted;
#endif
}
QString IosController::openFile() {
#if defined(Q_OS_TVOS)
return QString();
#elif !MACOS_NE
#if !MACOS_NE
UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"public.item"] inMode:UIDocumentPickerModeOpen];
DocumentPickerDelegate *documentPickerDelegate = [[DocumentPickerDelegate alloc] init];
documentPicker.delegate = documentPickerDelegate;
UIViewController *qtController = getViewController();
if (!qtController) return QString();
if (!qtController) return;
[qtController presentViewController:documentPicker animated:YES completion:nil];
#endif
__block QString filePath;
#if !MACOS_NE && !defined(Q_OS_TVOS)
#if !MACOS_NE
documentPickerDelegate.documentPickerClosedCallback = ^(NSString *path) {
if (path) {
filePath = QString::fromUtf8(path.UTF8String);
@@ -1030,135 +913,6 @@ QString IosController::openFile() {
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() {
NSURL *url = [NSURL URLWithString:@"http://captive.apple.com/generate_204"];
if (!url) {
@@ -1177,8 +931,3 @@ void IosController::requestInetAccess() {
}];
[task resume];
}
bool IosController::isTestFlight() {
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
return receiptURL && [[receiptURL lastPathComponent] isEqualToString:@"sandboxReceipt"];
}
@@ -1,7 +1,6 @@
#import <NetworkExtension/NetworkExtension.h>
#import <NetworkExtension/NETunnelProviderSession.h>
#import <Foundation/Foundation.h>
#include <TargetConditionals.h>
#if !MACOS_NE
#include <UIKit/UIKit.h>
@@ -22,7 +21,7 @@ class IosController;
@end
typedef void (^DocumentPickerClosedCallback)(NSString *path);
#if !MACOS_NE && !TARGET_OS_TV
#if !MACOS_NE
@interface DocumentPickerDelegate : NSObject <UIDocumentPickerDelegate>
@property (nonatomic, copy) DocumentPickerClosedCallback documentPickerClosedCallback;
@@ -26,7 +26,7 @@
@end
#if !MACOS_NE && !TARGET_OS_TV
#if !MACOS_NE
@implementation DocumentPickerDelegate
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
@@ -7,24 +7,6 @@
#import <UserNotifications/UserNotifications.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
#import <UIKit/UIKit.h>
@@ -190,5 +172,3 @@ void IOSNotificationHandler::notify(NotificationHandler::Message type, const QSt
}];
}
#endif
#endif // Q_OS_TVOS
-6
View File
@@ -1,6 +0,0 @@
/*
* tvOS does not export these iOS runtime helpers used by Go cgo archives.
* WireGuardKitGo references them indirectly; provide no-op stubs for tvOS.
*/
void darwin_arm_init_mach_exception_handler(void) {}
void darwin_arm_init_thread_exception_port(void) {}
@@ -165,7 +165,7 @@ bool LinuxRouteMonitor::rtmSendRoute(int action, int flags, int type,
if (rtm->rtm_type == RTN_THROW) {
struct in_addr ip4;
inet_pton(AF_INET, NetworkUtilities::getGatewayAndIface().first.toUtf8(), &ip4);
inet_pton(AF_INET, NetworkUtilities::getGatewayAndIface().toUtf8(), &ip4);
nlmsg_append_attr(nlmsg, sizeof(buf), RTA_GATEWAY, &ip4, sizeof(ip4));
nlmsg_append_attr32(nlmsg, sizeof(buf), RTA_PRIORITY, 0);
rtm->rtm_type = RTN_UNICAST;
@@ -80,7 +80,7 @@ bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) {
QDir appPath(QCoreApplication::applicationDirPath());
QStringList wgArgs = {"-f", "amn0"};
m_tunnel.start(appPath.filePath("amneziawg-go"), wgArgs);
m_tunnel.start(appPath.filePath("../../client/bin/wireguard-go"), wgArgs);
if (!m_tunnel.waitForStarted(WG_TUN_PROC_TIMEOUT)) {
logger.error() << "Unable to start tunnel process due to timeout";
m_tunnel.kill();
@@ -143,6 +143,12 @@ bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) {
for (const QString& key : config.m_specialJunk.keys()) {
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));
if (err != 0) {
@@ -41,8 +41,8 @@ void LinuxNetworkWatcher::initialize() {
connect(m_worker, &LinuxNetworkWatcherWorker::unsecuredNetwork, this,
&LinuxNetworkWatcher::unsecuredNetwork);
connect(m_worker, &LinuxNetworkWatcherWorker::wakeup, this,
&NetworkWatcherImpl::wakeup);
connect(m_worker, &LinuxNetworkWatcherWorker::sleepMode, this,
&NetworkWatcherImpl::sleepMode);
// Let's wait a few seconds to allow the UI to be fully loaded and shown.
// This is not strictly needed, but it's better for user experience because
@@ -200,7 +200,7 @@ void LinuxNetworkWatcherWorker::checkDevices() {
void LinuxNetworkWatcherWorker::NMStateChanged(quint32 state)
{
if (state == NM_STATE_ASLEEP) {
emit wakeup();
emit sleepMode();
}
logger.debug() << "NMStateChanged " << state;
@@ -23,7 +23,7 @@ class LinuxNetworkWatcherWorker final : public QObject {
signals:
void unsecuredNetwork(const QString& networkName, const QString& networkId);
void wakeup();
void sleepMode();
public slots:
void initialize();
@@ -79,7 +79,7 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) {
QDir appPath(QCoreApplication::applicationDirPath());
QStringList wgArgs = {"-f", "utun"};
m_tunnel.start(appPath.filePath("amneziawg-go"), wgArgs);
m_tunnel.start(appPath.filePath("wireguard-go"), wgArgs);
if (!m_tunnel.waitForStarted(WG_TUN_PROC_TIMEOUT)) {
logger.error() << "Unable to start tunnel process due to timeout";
m_tunnel.kill();
@@ -141,6 +141,12 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) {
for (const QString& key : config.m_specialJunk.keys()) {
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));
if (err != 0) {
@@ -173,10 +173,10 @@ void PowerNotificationsListener::sleepWakeupCallBack(void *refParam, io_service_
case kIOMessageSystemHasPoweredOn:
/* Announces that the system and its devices have woken up. */
logger.debug() << "System has powered on - emitting wakeup signal from dedicated CFRunLoop thread";
logger.debug() << "System has powered on - emitting sleepMode signal from dedicated CFRunLoop thread";
if (listener->m_watcher) {
// Use QMetaObject::invokeMethod for thread-safe signal emission
QMetaObject::invokeMethod(listener->m_watcher, "wakeup", Qt::QueuedConnection);
QMetaObject::invokeMethod(listener->m_watcher, "sleepMode", Qt::QueuedConnection);
}
break;
@@ -62,9 +62,6 @@ void WindowsDaemon::prepareActivation(const InterfaceConfig& config, int inetAda
}
void WindowsDaemon::activateSplitTunnel(const InterfaceConfig& config, int vpnAdapterIndex) {
if (m_splitTunnelManager == nullptr)
return;
if (config.m_vpnDisabledApps.length() > 0) {
m_splitTunnelManager->start(m_inetAdapterIndex, vpnAdapterIndex);
m_splitTunnelManager->excludeApps(config.m_vpnDisabledApps);
@@ -41,7 +41,7 @@ LRESULT WindowsNetworkWatcher::PowerWndProcCallback(HWND hwnd, UINT uMsg, WPARAM
switch (uMsg) {
case WM_POWERBROADCAST:
if (wParam == PBT_APMRESUMESUSPEND) {
emit obj->wakeup();
emit obj->sleepMode();
}
break;
default:
@@ -6,7 +6,6 @@
#include <chrono>
#include "ipc.h"
#include "logger.h"
#include "ikev2_vpn_protocol_windows.h"
#include "utilities.h"
+43 -60
View File
@@ -7,7 +7,7 @@
#include <QNetworkInterface>
#include "core/networkUtilities.h"
#include "ipc.h"
#include "logger.h"
#include "openvpnprotocol.h"
#include "utilities.h"
#include "version.h"
@@ -56,12 +56,8 @@ void OpenVpnProtocol::stop()
}
#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
QRemoteObjectPendingReply<bool> reply = iface->disableKillSwitch();
if (!reply.waitForFinished(1000) && !reply.returnValue()) {
qWarning() << "OpenVpnProtocol::stop(): Failed to disable killswitch";
}
});
QRemoteObjectPendingReply<bool> disableKillSwitchResp = IpcClient::Interface()->disableKillSwitch();
disableKillSwitchResp.waitForFinished(1000);
#endif
setConnectionState(Vpn::ConnectionState::Disconnected);
@@ -69,24 +65,21 @@ void OpenVpnProtocol::stop()
ErrorCode OpenVpnProtocol::prepare()
{
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;
}, [] () {
if (!IpcClient::Interface()) {
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()
@@ -180,17 +173,8 @@ ErrorCode OpenVpnProtocol::start()
}
#ifdef AMNEZIA_DESKTOP
const ErrorCode res = IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
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;
}
IpcClient::Interface()->addKillSwitchAllowedRange(QStringList(NetworkUtilities::getIPAddress(
m_configData.value(amnezia::config_key::hostName).toString())));
#endif
// Detect default gateway
@@ -232,6 +216,12 @@ ErrorCode OpenVpnProtocol::start()
return ErrorCode::AmneziaServiceConnectionFailed;
}
m_openVpnProcess->waitForSource(5000);
if (!m_openVpnProcess->isInitialized()) {
qWarning() << "IpcProcess replica is not connected!";
setLastError(ErrorCode::AmneziaServiceConnectionFailed);
return ErrorCode::AmneziaServiceConnectionFailed;
}
m_openVpnProcess->setProgram(PermittedProcess::OpenVPN);
QStringList arguments({
"--config", configPath(), "--management", m_managementHost, QString::number(mgmtPort),
@@ -240,13 +230,13 @@ ErrorCode OpenVpnProtocol::start()
m_openVpnProcess->setArguments(arguments);
qDebug() << arguments.join(" ");
connect(m_openVpnProcess.data(), &IpcProcessInterfaceReplica::errorOccurred,
connect(m_openVpnProcess.data(), &PrivilegedProcess::errorOccurred,
[&](QProcess::ProcessError error) { qDebug() << "PrivilegedProcess errorOccurred" << error; });
connect(m_openVpnProcess.data(), &IpcProcessInterfaceReplica::stateChanged,
connect(m_openVpnProcess.data(), &PrivilegedProcess::stateChanged,
[&](QProcess::ProcessState newState) { qDebug() << "PrivilegedProcess stateChanged" << newState; });
connect(m_openVpnProcess.data(), &IpcProcessInterfaceReplica::finished, this,
connect(m_openVpnProcess.data(), &PrivilegedProcess::finished, this,
[&]() { setConnectionState(Vpn::ConnectionState::Disconnected); });
m_openVpnProcess->start();
@@ -347,37 +337,30 @@ void OpenVpnProtocol::updateVpnGateway(const QString &line)
m_vpnGateway = l.split(" ").at(2);
#ifdef Q_OS_WIN
QThread::msleep(300);
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
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()) {
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);
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",
NetworkUtilities::getIPAddress(m_configData.value(amnezia::config_key::hostName).toString()));
IpcClient::Interface()->enablePeerTraffic(m_configData);
}
}
});
}
#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",
NetworkUtilities::getIPAddress(m_configData.value(amnezia::config_key::hostName).toString()));
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";
}
});
IpcClient::Interface()->enableKillSwitch(m_configData, 0);
}
#endif
qDebug() << QString("Set vpn local address %1, gw %2").arg(m_vpnLocalAddress).arg(vpnGateway());
+1 -1
View File
@@ -53,7 +53,7 @@ private:
void updateRouteGateway(QString line);
void updateVpnGateway(const QString &line);
QSharedPointer<IpcProcessInterfaceReplica> m_openVpnProcess;
QSharedPointer<PrivilegedProcess> m_openVpnProcess;
};
#endif // OPENVPNPROTOCOL_H
-15
View File
@@ -1,7 +1,6 @@
#include "protocols_defs.h"
#include <QRandomGenerator>
#include <QJsonObject>
using namespace amnezia;
@@ -218,17 +217,3 @@ QString ProtocolProps::key_proto_config_path(Proto p)
{
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 "";
}
+12 -13
View File
@@ -83,8 +83,10 @@ namespace amnezia
constexpr char specialJunk3[] = "I3";
constexpr char specialJunk4[] = "I4";
constexpr char specialJunk5[] = "I5";
constexpr char protocolVersion[] = "protocol_version";
constexpr char controlledJunk1[] = "J1";
constexpr char controlledJunk2[] = "J2";
constexpr char controlledJunk3[] = "J3";
constexpr char specialHandshakeTimeout[] = "Itime";
constexpr char openvpn[] = "openvpn";
constexpr char wireguard[] = "wireguard";
@@ -190,7 +192,7 @@ namespace amnezia
constexpr char defaultPort[] = "51820";
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(Q_OS_TVOS) || defined(MACOS_NE)
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE)
constexpr char defaultMtu[] = "1280";
#else
constexpr char defaultMtu[] = "1376";
@@ -210,14 +212,13 @@ namespace amnezia
namespace awg
{
constexpr char defaultPort[] = "55424";
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(Q_OS_TVOS) || defined(MACOS_NE)
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE)
constexpr char defaultMtu[] = "1280";
#else
constexpr char defaultMtu[] = "1376";
#endif
constexpr char serverConfigPath[] = "/opt/amnezia/awg/awg0.conf";
constexpr char serverLegacyConfigPath[] = "/opt/amnezia/awg/wg0.conf";
constexpr char serverConfigPath[] = "/opt/amnezia/awg/wg0.conf";
constexpr char serverPublicKeyPath[] = "/opt/amnezia/awg/wireguard_server_public_key.key";
constexpr char serverPskKeyPath[] = "/opt/amnezia/awg/wireguard_psk.key";
@@ -233,14 +234,15 @@ namespace amnezia
constexpr char defaultResponsePacketMagicHeader[] = "3288052141";
constexpr char defaultTransportPacketMagicHeader[] = "2528465083";
constexpr char defaultUnderloadPacketMagicHeader[] = "1766607858";
constexpr char defaultSpecialJunk1[] = "<r 2><b 0x858000010001000000000669636c6f756403636f6d0000010001c00c000100010000105a00044d583737>";
constexpr char defaultSpecialJunk1[] = "";
constexpr char defaultSpecialJunk2[] = "";
constexpr char defaultSpecialJunk3[] = "";
constexpr char defaultSpecialJunk4[] = "";
constexpr char defaultSpecialJunk5[] = "";
constexpr char awgV1_5[] = "1.5";
constexpr char awgV2[] = "2";
constexpr char defaultControlledJunk1[] = "";
constexpr char defaultControlledJunk2[] = "";
constexpr char defaultControlledJunk3[] = "";
constexpr char defaultSpecialHandshakeTimeout[] = "";
}
namespace socks5Proxy
@@ -323,9 +325,6 @@ namespace amnezia
Q_INVOKABLE static QString key_proto_config_data(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
+2 -3
View File
@@ -4,7 +4,7 @@
#include "core/errorstrings.h"
#include "vpnprotocol.h"
#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))
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) and !defined MACOS_NE || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
#include "openvpnovercloakprotocol.h"
#include "openvpnprotocol.h"
#include "shadowsocksvpnprotocol.h"
@@ -114,12 +114,11 @@ VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject &
#if defined(Q_OS_WINDOWS)
case DockerContainer::Ipsec: return new Ikev2Protocol(configuration);
#endif
#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))
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) and !defined MACOS_NE || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
case DockerContainer::OpenVpn: return new OpenVpnProtocol(configuration);
case DockerContainer::Cloak: return new OpenVpnOverCloakProtocol(configuration);
case DockerContainer::ShadowSocks: return new ShadowSocksVpnProtocol(configuration);
case DockerContainer::WireGuard: return new WireguardProtocol(configuration);
case DockerContainer::Awg2: return new WireguardProtocol(configuration);
case DockerContainer::Awg: return new WireguardProtocol(configuration);
case DockerContainer::Xray: return new XrayProtocol(configuration);
case DockerContainer::SSXray: return new XrayProtocol(configuration);
+2 -2
View File
@@ -15,7 +15,7 @@ WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *
m_impl.reset(new LocalSocketController());
connect(m_impl.get(), &ControllerImpl::connected, this,
[this](const QString &pubkey, const QDateTime &connectionTimestamp) {
setConnectionState(Vpn::ConnectionState::Connected);
emit connectionStateChanged(Vpn::ConnectionState::Connected);
});
connect(m_impl.get(), &ControllerImpl::statusUpdated, this,
[this](const QString& serverIpv4Gateway,
@@ -38,7 +38,7 @@ WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *
});
connect(m_impl.get(), &ControllerImpl::disconnected, this,
[this]() { setConnectionState(Vpn::ConnectionState::Disconnected); });
[this]() { emit connectionStateChanged(Vpn::ConnectionState::Disconnected); });
m_impl->initialize(nullptr, nullptr);
}
+177 -207
View File
@@ -1,46 +1,20 @@
#include "xrayprotocol.h"
#include "core/ipcclient.h"
#include "ipc.h"
#include "utilities.h"
#include "core/networkUtilities.h"
#include <QCryptographicHash>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkInterface>
#include <QJsonDocument>
#include <QtCore/qlogging.h>
#include <QtCore/qobjectdefs.h>
#include <QtCore/qprocess.h>
#ifdef Q_OS_MACOS
static const QString tunName = "utun22";
#else
static const QString tunName = "tun2";
#endif
#include "core/networkUtilities.h"
#include "utilities.h"
XrayProtocol::XrayProtocol(const QJsonObject &configuration, QObject *parent) : VpnProtocol(configuration, parent)
{
readXrayConfiguration(configuration);
m_routeGateway = NetworkUtilities::getGatewayAndIface();
m_vpnGateway = amnezia::protocols::xray::defaultLocalAddr;
m_vpnLocalAddress = amnezia::protocols::xray::defaultLocalAddr;
m_routeGateway = NetworkUtilities::getGatewayAndIface().first;
m_routeMode = static_cast<Settings::RouteMode>(configuration.value(amnezia::config_key::splitTunnelType).toInt());
m_remoteAddress = NetworkUtilities::getIPAddress(m_rawConfig.value(amnezia::config_key::hostName).toString());
const QString primaryDns = configuration.value(amnezia::config_key::dns1).toString();
m_dnsServers.push_back(QHostAddress(primaryDns));
if (primaryDns != amnezia::protocols::dns::amneziaDnsIp) {
const QString secondaryDns = configuration.value(amnezia::config_key::dns2).toString();
m_dnsServers.push_back(QHostAddress(secondaryDns));
}
QJsonObject xrayConfiguration = configuration.value(ProtocolProps::key_proto_config_data(Proto::Xray)).toObject();
if (xrayConfiguration.isEmpty()) {
xrayConfiguration = configuration.value(ProtocolProps::key_proto_config_data(Proto::SSXray)).toObject();
}
m_xrayConfig = xrayConfiguration;
m_t2sProcess = IpcClient::InterfaceTun2Socks();
}
XrayProtocol::~XrayProtocol()
@@ -51,196 +25,192 @@ XrayProtocol::~XrayProtocol()
ErrorCode XrayProtocol::start()
{
qDebug() << "XrayProtocol::start()";
qDebug().noquote() << "XrayProtocol xrayExecPath():" << xrayExecPath();
return IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
auto xrayStart = iface->xrayStart(QJsonDocument(m_xrayConfig).toJson());
if (!xrayStart.waitForFinished() || !xrayStart.returnValue()) {
qCritical() << "Failed to start xray";
return ErrorCode::XrayExecutableCrashed;
}
return startTun2Socks();
}, [] () {
return ErrorCode::AmneziaServiceConnectionFailed;
if (!QFileInfo::exists(xrayExecPath())) {
setLastError(ErrorCode::XrayExecutableMissing);
return lastError();
}
#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
});
connect(&m_xrayProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this,
[this](int exitCode, QProcess::ExitStatus exitStatus) {
qDebug().noquote() << "XrayProtocol finished, exitCode, exitStatus" << exitCode << exitStatus;
setConnectionState(Vpn::ConnectionState::Disconnected);
if ((exitStatus != QProcess::NormalExit) || (exitCode != 0)) {
emit protocolError(amnezia::ErrorCode::XrayExecutableCrashed);
emit setConnectionState(Vpn::ConnectionState::Error);
}
});
m_xrayProcess.start();
m_xrayProcess.waitForStarted();
if (m_xrayProcess.state() == QProcess::ProcessState::Running) {
setConnectionState(Vpn::ConnectionState::Connecting);
QThread::msleep(1000);
return startTun2Sock();
} else
return ErrorCode::XrayExecutableMissing;
}
ErrorCode XrayProtocol::startTun2Sock()
{
m_t2sProcess->start();
#ifdef Q_OS_WIN
m_configData.insert("inetAdapterIndex", NetworkUtilities::AdapterIndexTo(QHostAddress(m_remoteAddress)));
#endif
connect(m_t2sProcess.data(), &IpcProcessTun2SocksReplica::stateChanged, this,
[&](QProcess::ProcessState newState) { qDebug() << "PrivilegedProcess stateChanged" << newState; });
connect(m_t2sProcess.data(), &IpcProcessTun2SocksReplica::setConnectionState, this, [&](int vpnState) {
qDebug() << "PrivilegedProcess setConnectionState " << vpnState;
if (vpnState == Vpn::ConnectionState::Connected) {
setConnectionState(Vpn::ConnectionState::Connecting);
QList<QHostAddress> dnsAddr;
dnsAddr.push_back(QHostAddress(m_configData.value(config_key::dns1).toString()));
// We don't use secondary DNS if primary DNS is AmneziaDNS
if (!m_configData.value(amnezia::config_key::dns1).toString().
contains(amnezia::protocols::dns::amneziaDnsIp)) {
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);
}
}
}
#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;
}
void XrayProtocol::stop()
{
qDebug() << "XrayProtocol::stop()";
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
auto disableKillSwitch = iface->disableKillSwitch();
if (!disableKillSwitch.waitForFinished() || !disableKillSwitch.returnValue())
qWarning() << "Failed to disable killswitch";
auto StartRoutingIpv6 = iface->StartRoutingIpv6();
if (!StartRoutingIpv6.waitForFinished() || !StartRoutingIpv6.returnValue())
qWarning() << "Failed to start routing ipv6";
auto restoreResolvers = iface->restoreResolvers();
if (!restoreResolvers.waitForFinished() || !restoreResolvers.returnValue())
qWarning() << "Failed to restore resolvers";
auto deleteTun = iface->deleteTun(tunName);
if (!deleteTun.waitForFinished() || !deleteTun.returnValue())
qWarning() << "Failed to delete tun";
auto xrayStop = iface->xrayStop();
if (!xrayStop.waitForFinished() || !xrayStop.returnValue())
qWarning() << "Failed to stop xray";
});
if (m_tun2socksProcess) {
m_tun2socksProcess->blockSignals(true);
#ifndef Q_OS_WIN
m_tun2socksProcess->terminate();
auto waitForFinished = m_tun2socksProcess->waitForFinished(1000);
if (!waitForFinished.waitForFinished() || !waitForFinished.returnValue()) {
qWarning() << "Failed to terminate tun2socks. Killing the process...";
m_tun2socksProcess->kill();
}
#else
// terminate does not do anything useful on Windows
// so just kill the process
m_tun2socksProcess->kill();
#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
m_tun2socksProcess->close();
m_tun2socksProcess.reset();
#endif
qDebug() << "XrayProtocol::stop()";
m_xrayProcess.disconnect();
m_xrayProcess.kill();
m_xrayProcess.waitForFinished(3000);
if (m_t2sProcess) {
m_t2sProcess->stop();
QThread::msleep(200);
}
setConnectionState(Vpn::ConnectionState::Disconnected);
}
ErrorCode XrayProtocol::startTun2Socks()
QString XrayProtocol::xrayExecPath()
{
m_tun2socksProcess = IpcClient::CreatePrivilegedProcess();
if (!m_tun2socksProcess->waitForSource()) {
return ErrorCode::AmneziaServiceConnectionFailed;
}
m_tun2socksProcess->setProgram(PermittedProcess::Tun2Socks);
m_tun2socksProcess->setArguments({"-device", QString("tun://%1").arg(tunName), "-proxy", "socks5://127.0.0.1:10808" });
connect(m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::readyReadStandardError, this, [this]() {
auto readAllStandardError = m_tun2socksProcess->readAllStandardError();
if (!readAllStandardError.waitForFinished()) {
qWarning() << "Failed to read output from tun2socks";
return;
}
const QString line = readAllStandardError.returnValue();
if (!line.contains("[TCP]") && !line.contains("[UDP]"))
qDebug() << "[tun2socks]:" << line;
if (line.contains("[STACK] tun://") && line.contains("<-> socks5://127.0.0.1")) {
disconnect(m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::readyReadStandardOutput, this, nullptr);
if (ErrorCode res = setupRouting(); res != ErrorCode::NoError) {
stop();
setLastError(res);
} else {
setConnectionState(Vpn::ConnectionState::Connected);
}
}
}, Qt::QueuedConnection);
connect(m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::finished, this, [this](int exitCode, QProcess::ExitStatus exitStatus) {
if (exitStatus == QProcess::ExitStatus::CrashExit) {
qCritical() << "Tun2socks process crashed!";
} else {
qCritical() << QString("Tun2socks process was closed with %1 exit code").arg(exitCode);
}
stop();
setLastError(ErrorCode::Tun2SockExecutableCrashed);
}, Qt::QueuedConnection);
m_tun2socksProcess->start();
return ErrorCode::NoError;
}
ErrorCode XrayProtocol::setupRouting() {
return IpcClient::withInterface([this](QSharedPointer<IpcInterfaceReplica> iface) -> ErrorCode {
#ifdef Q_OS_WIN
const int inetAdapterIndex = NetworkUtilities::AdapterIndexTo(QHostAddress(m_remoteAddress));
#endif
auto createTun = iface->createTun(tunName, amnezia::protocols::xray::defaultLocalAddr);
if (!createTun.waitForFinished() || !createTun.returnValue()) {
qCritical() << "Failed to assign IP address for TUN";
return ErrorCode::InternalError;
}
auto updateResolvers = iface->updateResolvers(tunName, m_dnsServers);
if (!updateResolvers.waitForFinished() || !updateResolvers.returnValue()) {
qCritical() << "Failed to set DNS resolvers for TUN";
return ErrorCode::InternalError;
}
#ifdef Q_OS_WIN
int vpnAdapterIndex = -1;
QList<QNetworkInterface> netInterfaces = QNetworkInterface::allInterfaces();
for (auto& netInterface : netInterfaces) {
for (auto& address : netInterface.addressEntries()) {
if (m_vpnLocalAddress == address.ip().toString())
vpnAdapterIndex = netInterface.index();
}
}
return Utils::executable(QString("xray/xray"), true);
#else
static const int vpnAdapterIndex = 0;
return Utils::executable(QString("xray"), true);
#endif
const bool killSwitchEnabled = QVariant(m_rawConfig.value(config_key::killSwitchOption).toString()).toBool();
if (killSwitchEnabled) {
if (vpnAdapterIndex != -1) {
QJsonObject config = m_rawConfig;
config.insert("vpnServer", m_remoteAddress);
auto enableKillSwitch = IpcClient::Interface()->enableKillSwitch(config, vpnAdapterIndex);
if (!enableKillSwitch.waitForFinished() || !enableKillSwitch.returnValue()) {
qCritical() << "Failed to enable killswitch";
return ErrorCode::InternalError;
}
} else
qWarning() << "Failed to get vpnAdapterIndex. Killswitch disabled";
}
if (m_routeMode == Settings::RouteMode::VpnAllSites) {
static const QStringList subnets = { "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" };
auto routeAddList = iface->routeAddList(m_vpnGateway, subnets);
if (!routeAddList.waitForFinished() || routeAddList.returnValue() != subnets.count()) {
qCritical() << "Failed to set routes for TUN";
return ErrorCode::InternalError;
}
}
auto StopRoutingIpv6 = iface->StopRoutingIpv6();
if (!StopRoutingIpv6.waitForFinished() || !StopRoutingIpv6.returnValue()) {
qCritical() << "Failed to disable IPv6 routing";
return ErrorCode::InternalError;
}
#ifdef Q_OS_WIN
if (inetAdapterIndex != -1 && vpnAdapterIndex != -1) {
QJsonObject config = m_rawConfig;
config.insert("inetAdapterIndex", inetAdapterIndex);
config.insert("vpnAdapterIndex", vpnAdapterIndex);
config.insert("vpnGateway", m_vpnGateway);
config.insert("vpnServer", m_remoteAddress);
auto enablePeerTraffic = iface->enablePeerTraffic(config);
if (!enablePeerTraffic.waitForFinished() || !enablePeerTraffic.returnValue()) {
qCritical() << "Failed to enable peer traffic";
return ErrorCode::InternalError;
}
} else
qWarning() << "Failed to get adapter indexes. Split-tunneling disabled";
#endif
return ErrorCode::NoError;
},
[] () {
return ErrorCode::AmneziaServiceConnectionFailed;
});
}
void XrayProtocol::readXrayConfiguration(const QJsonObject &configuration)
{
m_configData = configuration;
QJsonObject xrayConfiguration = configuration.value(ProtocolProps::key_proto_config_data(Proto::Xray)).toObject();
if (xrayConfiguration.isEmpty()) {
xrayConfiguration = configuration.value(ProtocolProps::key_proto_config_data(Proto::SSXray)).toObject();
}
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_primaryDNS = configuration.value(amnezia::config_key::dns1).toString();
m_secondaryDNS = configuration.value(amnezia::config_key::dns2).toString();
}

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