Compare commits

..

5 Commits

Author SHA1 Message Date
lunardunno
76dfad9462 Changing for Error 207
Changing description and title for Error 207
2025-03-19 21:38:41 +04:00
lunardunno
6da26057a1 Revert "Changing for Error 208"
This reverts commit f45624c023.
2025-03-19 21:33:16 +04:00
lunardunno
f45624c023 Changing for Error 208
Changing description and title for error 208
2025-03-19 16:20:10 +04:00
lunardunno
bfb8d3b45d Sudo check witch variable $HOME.
Direct use of the $HOME variable.
2025-03-19 16:04:07 +04:00
lunardunno
33f1658ade Direct use of the $HOME variable. 2025-03-19 15:53:36 +04:00
390 changed files with 13826 additions and 30094 deletions

View File

@@ -10,18 +10,16 @@ env:
jobs:
Build-Linux-Ubuntu:
runs-on: 4-core
runs-on: ubuntu-20.04
env:
QT_VERSION: 6.8.3
QT_VERSION: 6.6.2
QIF_VERSION: 4.7
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
steps:
- name: 'Install Qt'
@@ -30,15 +28,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 }}'
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
- name: 'Get sources'
uses: actions/checkout@v4
@@ -46,15 +42,8 @@ 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: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2
- name: 'Build project'
run: |
@@ -64,13 +53,13 @@ jobs:
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
run: cd deploy && tar -cf AmneziaVPN_Linux_Installer.tar AmneziaVPN_Linux_Installer.bin
- name: 'Upload installer artifact'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_${{ env.VERSION }}_linux_x64.tar.zip
path: deploy/AmneziaVPN_${{ env.VERSION }}_linux_x64.tar.zip
name: AmneziaVPN_Linux_installer.tar
path: deploy/AmneziaVPN_Linux_Installer.tar
retention-days: 7
- name: 'Upload unpacked artifact'
@@ -93,7 +82,7 @@ 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 }}
@@ -101,8 +90,6 @@ jobs:
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
steps:
- name: 'Get sources'
@@ -111,16 +98,8 @@ 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: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2
- name: 'Install Qt'
uses: jurplel/install-qt-action@v3
@@ -128,62 +107,32 @@ jobs:
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: 'Build project'
shell: cmd
run: |
set BUILD_ARCH=${{ env.BUILD_ARCH }}
set QT_BIN_DIR="${{ runner.temp }}\\Qt\\${{ env.QT_VERSION }}\\msvc2022_64\\bin"
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"
set WIX_BIN_DIR=%USERPROFILE%\.dotnet\tools
call deploy\\build_windows.bat
- 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:
name: AmneziaVPN_${{ env.VERSION }}_x64.exe
path: AmneziaVPN_${{ env.VERSION }}_x64.exe
retention-days: 7
- name: 'Upload MSI installer artifact'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_Windows_MSI_installer
path: AmneziaVPN_x${{ env.BUILD_ARCH }}.msi
name: AmneziaVPN_Windows_installer
path: AmneziaVPN_x${{ env.BUILD_ARCH }}.exe
retention-days: 7
- name: 'Upload unpacked artifact'
@@ -196,10 +145,10 @@ jobs:
# ------------------------------------------------------
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 }}
@@ -207,14 +156,12 @@ jobs:
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: '26.1'
xcode-version: '15.2'
- name: 'Install desktop Qt'
uses: jurplel/install-qt-action@v3
@@ -243,7 +190,7 @@ jobs:
- name: 'Install go'
uses: actions/setup-go@v5
with:
go-version: '1.24'
go-version: '1.22.1'
cache: false
- name: 'Setup gomobile'
@@ -258,8 +205,8 @@ jobs:
submodules: 'true'
fetch-depth: 10
# - name: 'Setup ccache'
# uses: hendrikmuhs/ccache-action@v1.2
- name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2
- name: 'Install dependencies'
run: pip install jsonschema jinja2
@@ -296,33 +243,18 @@ jobs:
# ------------------------------------------------------
Build-MacOS-old:
Build-MacOS:
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 }}
QIF_VERSION: 4.6
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
steps:
- name: 'Setup xcode'
@@ -343,6 +275,11 @@ jobs:
set-env: 'true'
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
- name: 'Install Qt Installer Framework ${{ env.QIF_VERSION }}'
run: |
mkdir -pv ${{ runner.temp }}/Qt/Tools/QtInstallerFramework
wget https://qt.amzsvc.com/tools/ifw/${{ env.QIF_VERSION }}.zip
unzip ${{ env.QIF_VERSION }}.zip -d ${{ runner.temp }}/Qt/Tools/QtInstallerFramework/
- name: 'Get sources'
uses: actions/checkout@v4
@@ -350,183 +287,22 @@ jobs:
submodules: 'true'
fetch-depth: 10
# - name: 'Setup ccache'
# uses: hendrikmuhs/ccache-action@v1.2
- 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
export QIF_BIN_DIR="${{ runner.temp }}/Qt/Tools/QtInstallerFramework/${{ env.QIF_VERSION }}/bin"
bash deploy/build_macos.sh
- name: 'Upload installer artifact'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_MacOS_old_installer
path: deploy/build/pkg/AmneziaVPN.pkg
name: AmneziaVPN_MacOS_installer
path: AmneziaVPN.dmg
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
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: '16.2.0'
- name: 'Install Qt'
uses: jurplel/install-qt-action@v4
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'
aqtversion: '==3.3.0'
py7zrversion: '==0.22.*'
extra: '--base ${{ env.QT_MIRROR }}'
- name: 'Get sources'
uses: actions/checkout@v4
with:
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'
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@v4
with:
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
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 }}
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: '26.1'
- name: 'Install desktop Qt'
uses: jurplel/install-qt-action@v3
with:
version: ${{ env.QT_VERSION }}
host: 'mac'
target: 'desktop'
modules: 'qtremoteobjects qt5compat qtshadertools qtmultimedia'
arch: 'clang_64'
dir: ${{ runner.temp }}
set-env: 'true'
extra: '--base ${{ env.QT_MIRROR }}'
- name: 'Install go'
uses: actions/setup-go@v5
with:
go-version: '1.24'
cache: false
- name: 'Setup gomobile'
run: |
export PATH=$PATH:~/go/bin
go install golang.org/x/mobile/cmd/gomobile@latest
gomobile init
- name: 'Get sources'
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_ne.sh
- name: 'Upload unpacked artifact'
uses: actions/upload-artifact@v4
with:
@@ -537,19 +313,17 @@ jobs:
# ------------------------------------------------------
Build-Android:
runs-on: 4-core
runs-on: ubuntu-latest
env:
ANDROID_BUILD_PLATFORM: android-36
QT_VERSION: 6.10.1
ANDROID_BUILD_PLATFORM: android-34
QT_VERSION: 6.7.3
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
steps:
- name: 'Install desktop Qt'
@@ -622,22 +396,15 @@ jobs:
with:
submodules: 'true'
- 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: '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
@@ -662,44 +429,35 @@ jobs:
shell: bash
run: ./deploy/build_android.sh --aab --apk all --build-platform ${{ env.ANDROID_BUILD_PLATFORM }}
- name: 'Rename Android APKs'
run: |
cd deploy/build
mv AmneziaVPN-x86_64-release.apk AmneziaVPN_${VERSION}_android9+_x86_64.apk
mv AmneziaVPN-x86-release.apk AmneziaVPN_${VERSION}_android9+_x86.apk
mv AmneziaVPN-arm64-v8a-release.apk AmneziaVPN_${VERSION}_android9+_arm64-v8a.apk
mv AmneziaVPN-armeabi-v7a-release.apk AmneziaVPN_${VERSION}_android9+_armeabi-v7a.apk
cd ../..
- name: 'Upload x86_64 apk'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_${{ env.VERSION }}_android9+_x86_64.apk
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_x86_64.apk
name: AmneziaVPN-android-x86_64
path: deploy/build/AmneziaVPN-x86_64-release.apk
compression-level: 0
retention-days: 7
- name: 'Upload x86 apk'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_${{ env.VERSION }}_android9+_x86.apk
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_x86.apk
name: AmneziaVPN-android-x86
path: deploy/build/AmneziaVPN-x86-release.apk
compression-level: 0
retention-days: 7
- name: 'Upload arm64-v8a apk'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_${{ env.VERSION }}_android9+_arm64-v8a.apk
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_arm64-v8a.apk
name: AmneziaVPN-android-arm64-v8a
path: deploy/build/AmneziaVPN-arm64-v8a-release.apk
compression-level: 0
retention-days: 7
- name: 'Upload armeabi-v7a apk'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_${{ env.VERSION }}_android9+_armeabi-v7a.apk
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_armeabi-v7a.apk
name: AmneziaVPN-android-armeabi-v7a
path: deploy/build/AmneziaVPN-armeabi-v7a-release.apk
compression-level: 0
retention-days: 7

View File

@@ -20,8 +20,6 @@ jobs:
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
steps:
- name: 'Install desktop Qt'

View File

@@ -1,41 +1,64 @@
name: 'Upload a new version'
on:
workflow_dispatch:
inputs:
RELEASE_VERSION:
description: 'Release version (e.g. 1.2.3.4)'
required: true
type: string
push:
tags:
- '[0-9]+.[0-9]+.[0-9]+.[0-9]+'
jobs:
Upload-S3:
upload:
runs-on: ubuntu-latest
name: upload
steps:
- name: Checkout
- name: Checkout CMakeLists.txt
uses: actions/checkout@v4
with:
ref: ${{ inputs.RELEASE_VERSION }}
ref: ${{ github.ref_name }}
sparse-checkout: |
CMakeLists.txt
deploy/deploy_s3.sh
sparse-checkout-cone-mode: false
- name: Verify git tag
run: |
TAG_NAME=${{ inputs.RELEASE_VERSION }}
GIT_TAG=${{ github.ref_name }}
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)."
if [[ "$GIT_TAG" == "$CMAKE_TAG" ]]; then
echo "Git tag ($GIT_TAG) and version in CMakeLists.txt ($CMAKE_TAG) are the same. Continuing..."
else
echo "::error::Mismatch: Git tag ($TAG_NAME) != CMakeLists.txt version ($CMAKE_TAG). Exiting with error..."
echo "Git tag ($GIT_TAG) and version in CMakeLists.txt ($CMAKE_TAG) are not the same! Cancelling..."
exit 1
fi
- name: Setup Rclone
uses: AnimMouse/setup-rclone@v1
- name: Download artifacts from the "${{ github.ref_name }}" tag
uses: robinraju/release-downloader@v1.8
with:
rclone_config: ${{ secrets.RCLONE_CONFIG }}
tag: ${{ github.ref_name }}
fileName: "AmneziaVPN_(Linux_|)${{ github.ref_name }}*"
out-file-path: ${{ github.ref_name }}
- name: Send dist to S3
run: bash deploy/deploy_s3.sh ${{ inputs.RELEASE_VERSION }}
- name: Upload beta version
uses: jakejarvis/s3-sync-action@master
if: contains(github.event.base_ref, 'dev')
with:
args: --include "AmneziaVPN*" --delete
env:
AWS_S3_BUCKET: updates
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
AWS_S3_ENDPOINT: https://${{ vars.CF_ACCOUNT_ID }}.r2.cloudflarestorage.com
SOURCE_DIR: ${{ github.ref_name }}
DEST_DIR: beta/${{ github.ref_name }}
- name: Upload stable version
uses: jakejarvis/s3-sync-action@master
if: contains(github.event.base_ref, 'master')
with:
args: --include "AmneziaVPN*" --delete
env:
AWS_S3_BUCKET: updates
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
AWS_S3_ENDPOINT: https://${{ vars.CF_ACCOUNT_ID }}.r2.cloudflarestorage.com
SOURCE_DIR: ${{ github.ref_name }}
DEST_DIR: stable/${{ github.ref_name }}

12
.gitignore vendored
View File

@@ -1,9 +1,5 @@
# User settings
*.user
# Gateway configs (contains sensitive endpoints)
gateway.json
client/gateway.json
macOSPackage/
AmneziaVPN.dmg
AmneziaVPN.exe
@@ -13,7 +9,6 @@ deploy/build_32/*
deploy/build_64/*
winbuild*.bat
.cache/
.vscode/
# Qt-es
@@ -138,9 +133,4 @@ client/3rd/ShadowSocks/ss_ios.xcconfig
out/
# CMake files
CMakeFiles/
ios-ne-build.sh
macos-ne-build.sh
macos-signed-build.sh
macos-with-sign-build.sh
CMakeFiles/

1
.gitmodules vendored
View File

@@ -7,7 +7,6 @@
[submodule "client/3rd-prebuilt"]
path = client/3rd-prebuilt
url = https://github.com/amnezia-vpn/3rd-prebuilt
branch = feature/special-handshake
[submodule "client/3rd/amneziawg-apple"]
path = client/3rd/amneziawg-apple
url = https://github.com/amnezia-vpn/amneziawg-apple

View File

@@ -1,9 +1,8 @@
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN)
set(AMNEZIAVPN_VERSION 4.8.12.8)
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
project(${PROJECT} VERSION 4.8.4.3
DESCRIPTION "AmneziaVPN"
HOMEPAGE_URL "https://amnezia.org/"
)
@@ -12,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
set(RELEASE_DATE "${CURRENT_DATE}")
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
set(APP_ANDROID_VERSION_CODE 2104)
set(APP_ANDROID_VERSION_CODE 2080)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux")
@@ -32,53 +31,14 @@ set(QT_BUILD_TOOLS_WHEN_CROSS_COMPILING ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(APPLE)
if(IOS)
set(CMAKE_OSX_ARCHITECTURES "arm64")
elseif(MACOS_NE)
set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64")
else()
set(CMAKE_OSX_ARCHITECTURES "x86_64")
endif()
if(APPLE AND NOT IOS)
set(CMAKE_OSX_ARCHITECTURES "x86_64")
endif()
add_subdirectory(client)
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
if(NOT IOS AND NOT ANDROID)
add_subdirectory(service)
include(${CMAKE_SOURCE_DIR}/deploy/installer/config.cmake)
endif()
set(AMNEZIA_STAGE_DIR "${CMAKE_BINARY_DIR}/stage")
if(WIN32 AND NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
file(TO_CMAKE_PATH "${AMNEZIA_STAGE_DIR}" AMNEZIA_STAGE_DIR_CMAKE)
set(CPACK_GENERATOR "WIX")
set(CPACK_WIX_VERSION 4)
set(CPACK_PACKAGE_NAME "AmneziaVPN")
set(CPACK_PACKAGE_VENDOR "AmneziaVPN")
set(CPACK_PACKAGE_VERSION ${AMNEZIAVPN_VERSION})
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "AmneziaVPN client")
set(CPACK_PACKAGE_INSTALL_DIRECTORY "AmneziaVPN")
set(CPACK_PACKAGE_DIRECTORY "${CMAKE_BINARY_DIR}")
set(CPACK_PACKAGE_EXECUTABLES "AmneziaVPN" "AmneziaVPN")
set(CPACK_WIX_UPGRADE_GUID "{2D55AC62-96D6-4692-8C05-0D85BBF95485}")
set(CPACK_WIX_PRODUCT_ICON "${CMAKE_SOURCE_DIR}/client/images/app.ico")
# WiX patches
set(_AMNEZIA_WIX_PATCH_SERVICE "${CMAKE_SOURCE_DIR}/deploy/installer/wix/service_install_patch.xml")
set(_AMNEZIA_WIX_PATCH_CLOSE_APP "${CMAKE_SOURCE_DIR}/deploy/installer/wix/close_client_patch.xml")
file(TO_CMAKE_PATH "${_AMNEZIA_WIX_PATCH_SERVICE}" _AMNEZIA_WIX_PATCH_SERVICE_CMAKE)
file(TO_CMAKE_PATH "${_AMNEZIA_WIX_PATCH_CLOSE_APP}" _AMNEZIA_WIX_PATCH_CLOSE_APP_CMAKE)
set(CPACK_WIX_PATCH_FILE "${_AMNEZIA_WIX_PATCH_SERVICE_CMAKE};${_AMNEZIA_WIX_PATCH_CLOSE_APP_CMAKE}")
# WiX v4 Util extension for CloseApplication + namespace for util
set(CPACK_WIX_EXTENSIONS "${CPACK_WIX_EXTENSIONS};WixToolset.Util.wixext")
set(CPACK_WIX_CUSTOM_XMLNS "util=http://wixtoolset.org/schemas/v4/wxs/util")
set(CPACK_INSTALLED_DIRECTORIES "${AMNEZIA_STAGE_DIR_CMAKE};/")
include(CPack)
endif()

View File

@@ -9,17 +9,17 @@
### [English]([https://github.com/amnezia-vpn/amnezia-client/blob/dev/README_RU.md](https://github.com/amnezia-vpn/amnezia-client/tree/dev?tab=readme-ov-file#)) | [Русский](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README_RU.md)
[Amnezia](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en) is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
[Amnezia](https://amnezia.org) is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
[![Image](https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/uipic4.png)](https://amnezia.org)
### [Website](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en) | [Alt website link](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en-mirror) | [Documentation](https://docs.amnezia.org) | [Troubleshooting](https://docs.amnezia.org/troubleshooting)
### [Website](https://amnezia.org) | [Alt website link](https://storage.googleapis.com/amnezia/amnezia.org) | [Documentation](https://docs.amnezia.org) | [Troubleshooting](https://docs.amnezia.org/troubleshooting)
> [!TIP]
> If the [Amnezia website](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en) is blocked in your region, you can use an [Alternative website link](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en-mirror).
> If the [Amnezia website](https://amnezia.org) is blocked in your region, you can use an [Alternative website link](https://storage.googleapis.com/amnezia/amnezia.org ).
<a href="https://amnezia.org/en/downloads?utm_source=github&utm_campaign=amnezia_button-readme-en"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
<a href="https://storage.googleapis.com/amnezia/amnezia.org?m-path=/en/downloads&utm_source=github&utm_campaign=amnezia_button-readme-en-mirrow"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-alt.svg" width="150" style="max-width: 100%;"></a>
<a href="https://amnezia.org/downloads"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
<a href="https://storage.googleapis.com/amnezia/q9p19109"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-alt.svg" width="150" style="max-width: 100%;"></a>
[All releases](https://github.com/amnezia-vpn/amnezia-client/releases)

View File

@@ -6,16 +6,16 @@
[![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/amnezia-vpn/amnezia-client)
### [English](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README.md) | Русский
[AmneziaVPN](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru) — это open source VPN-клиент, ключевая особенность которого заключается в возможности развернуть собственный VPN на вашем сервере.
[AmneziaVPN](https://amnezia.org) — это open source VPN-клиент, ключевая особенность которого заключается в возможности развернуть собственный VPN на вашем сервере.
[![Image](https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/uipic4.png)](https://amnezia.org)
### [Сайт](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru) | [Зеркало сайта](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru-mirror) | [Документация](https://docs.amnezia.org) | [Решение проблем](https://docs.amnezia.org/troubleshooting)
### [Сайт](https://amnezia.org) | [Зеркало сайта](https://storage.googleapis.com/amnezia/amnezia.org) | [Документация](https://docs.amnezia.org) | [Решение проблем](https://docs.amnezia.org/troubleshooting)
> [!TIP]
> Если [сайт Amnezia](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru-mirror).
> Если [сайт Amnezia](https://amnezia.org) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/amnezia/amnezia.org).
<a href="https://storage.googleapis.com/amnezia/amnezia.org?m-path=/ru/downloads&utm_source=github&utm_campaign=amnezia_button-readme-ru-mirror"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website-ru.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
<a href="https://storage.googleapis.com/amnezia/q9p19109"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website-ru.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
[Все релизы](https://github.com/amnezia-vpn/amnezia-client/releases)

View File

@@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN)
project(${PROJECT})
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set_property(GLOBAL PROPERTY AUTOGEN_TARGETS_FOLDER "Autogen")
set_property(GLOBAL PROPERTY AUTOMOC_TARGETS_FOLDER "Autogen")
@@ -30,24 +31,6 @@ add_definitions(-DDEV_AGW_PUBLIC_KEY="$ENV{DEV_AGW_PUBLIC_KEY}")
add_definitions(-DDEV_AGW_ENDPOINT="$ENV{DEV_AGW_ENDPOINT}")
add_definitions(-DDEV_S3_ENDPOINT="$ENV{DEV_S3_ENDPOINT}")
add_definitions(-DFREE_V2_ENDPOINT="$ENV{FREE_V2_ENDPOINT}")
add_definitions(-DPREM_V1_ENDPOINT="$ENV{PREM_V1_ENDPOINT}")
add_definitions(-DAGW_DNS_SERVER="$ENV{AGW_DNS_SERVER}")
add_definitions(-DAGW_DNS_DOMAIN="$ENV{AGW_DNS_DOMAIN}")
add_definitions(-DAGW_DNS_PRIMARY="$ENV{AGW_DNS_PRIMARY}")
add_definitions(-DAGW_DNS_PORT_UDP="$ENV{AGW_DNS_PORT_UDP}")
add_definitions(-DAGW_DNS_PORT_DOT="$ENV{AGW_DNS_PORT_DOT}")
add_definitions(-DAGW_DNS_PORT_DOH="$ENV{AGW_DNS_PORT_DOH}")
add_definitions(-DAGW_DNS_PORT_DOQ="$ENV{AGW_DNS_PORT_DOQ}")
add_definitions(-DAGW_DNS_DOH_PATH="$ENV{AGW_DNS_DOH_PATH}")
add_definitions(-DAGW_DNS_RETRY_COUNT="$ENV{AGW_DNS_RETRY_COUNT}")
add_definitions(-DAGW_DNS_TIMEOUT_MS="$ENV{AGW_DNS_TIMEOUT_MS}")
if(DEFINED ENV{AGW_INSECURE_SSL} AND NOT "$ENV{AGW_INSECURE_SSL}" STREQUAL "" AND NOT "$ENV{AGW_INSECURE_SSL}" STREQUAL "0")
add_definitions(-DAGW_INSECURE_SSL=1)
endif()
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
set(PACKAGES ${PACKAGES} Widgets)
endif()
@@ -67,11 +50,8 @@ endif()
qt_standard_project_setup()
qt_add_executable(${PROJECT} MANUAL_FINALIZATION)
target_include_directories(${PROJECT} PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
)
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
if(WIN32 OR (APPLE AND NOT IOS) 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)
@@ -127,15 +107,6 @@ include_directories(
${CMAKE_CURRENT_BINARY_DIR}
)
if(MACOS_NE)
message("MACOS_NE is ON")
add_definitions(-DQ_OS_MAC)
add_definitions(-DMACOS_NE)
message("Add macros for MacOS Network Extension")
else()
message("MACOS_NE is OFF")
endif()
include_directories(mozilla)
include_directories(mozilla/shared)
include_directories(mozilla/models)
@@ -165,7 +136,7 @@ if(WIN32)
endif()
if(APPLE)
cmake_policy(SET CMP0099 NEW)
cmake_policy(SET CMP0099 OLD)
cmake_policy(SET CMP0114 NEW)
if(NOT BUILD_OSX_APP_IDENTIFIER)
@@ -184,6 +155,7 @@ if(APPLE)
set(CMAKE_XCODE_GENERATE_SCHEME FALSE)
set(CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM ${BUILD_VPN_DEVELOPMENT_TEAM})
set(CMAKE_XCODE_ATTRIBUTE_GROUP_ID_IOS ${BUILD_IOS_GROUP_IDENTIFIER})
endif()
if(LINUX AND NOT ANDROID)
@@ -191,7 +163,8 @@ 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) OR (LINUX AND NOT ANDROID))
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
message("Client desktop build")
add_compile_definitions(AMNEZIA_DESKTOP)
endif()
@@ -202,9 +175,7 @@ endif()
if(IOS)
include(cmake/ios.cmake)
include(cmake/ios-arch-fixup.cmake)
elseif(APPLE AND MACOS_NE)
include(cmake/macos_ne.cmake)
elseif(APPLE)
elseif(APPLE AND NOT IOS)
include(cmake/osxtools.cmake)
include(cmake/macos.cmake)
endif()
@@ -225,7 +196,7 @@ elseif(APPLE AND NOT IOS)
set(DEPLOY_PLATFORM_PATH "macos")
endif()
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
if(NOT IOS AND NOT ANDROID)
add_custom_command(
TARGET ${PROJECT} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E $<IF:$<CONFIG:Debug>,copy_directory,true>
@@ -240,13 +211,8 @@ if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
$<TARGET_FILE_DIR:${PROJECT}>
COMMAND_EXPAND_LISTS
)
endif()
target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC} ${I18NQRC})
qt_finalize_target(${PROJECT})
option(BUILD_TESTS "Build transport integration tests" OFF)
if(BUILD_TESTS)
enable_testing()
add_subdirectory(tests)
endif()

View File

@@ -12,9 +12,6 @@
#include <QTextDocument>
#include <QTimer>
#include <QTranslator>
#include <QEvent>
#include <QDir>
#include <QSettings>
#include "logger.h"
#include "ui/controllers/pageController.h"
@@ -24,18 +21,9 @@
#include "platforms/ios/QRCodeReaderBase.h"
#include "protocols/qml_register_protocols.h"
#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"))
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv)
{
setDesktopFileName(QStringLiteral(APPLICATION_NAME));
setQuitOnLastWindowClosed(false);
// Fix config file permissions
@@ -60,68 +48,30 @@ 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);
}
#endif
m_vpnConnectionThread.requestInterruption();
m_vpnConnectionThread.quit();
if (!m_vpnConnectionThread.wait(3000)) {
m_vpnConnectionThread.terminate();
m_vpnConnectionThread.wait(500);
}
m_vpnConnectionThread.wait(3000);
if (m_engine) {
QObject::disconnect(m_engine, 0, 0, 0);
delete m_engine;
}
}
#ifdef Q_OS_ANDROID
namespace {
static void clearQtCaches()
{
const QString cacheRoot = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
if (!cacheRoot.isEmpty()) {
QDir(cacheRoot + "/QtShaderCache").removeRecursively();
QDir(cacheRoot + "/qmlcache").removeRecursively();
}
}
}
#endif
void AmneziaApplication::init()
{
m_engine = new QQmlApplicationEngine;
const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml"));
QObject::connect(
m_engine, &QQmlApplicationEngine::objectCreated, this,
[this, url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl) {
QCoreApplication::exit(-1);
return;
}
// install filter on main window
if (auto win = qobject_cast<QQuickWindow*>(obj)) {
win->installEventFilter(this);
win->show();
}
},
Qt::QueuedConnection);
m_engine, &QQmlApplicationEngine::objectCreated, this,
[url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
},
Qt::QueuedConnection);
m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance());
#ifdef MACOS_NE
m_engine->rootContext()->setContextProperty("IsMacOsNeBuild", true);
#else
m_engine->rootContext()->setContextProperty("IsMacOsNeBuild", false);
#endif
m_vpnConnection.reset(new VpnConnection(m_settings));
m_vpnConnection->moveToThread(&m_vpnConnectionThread);
m_vpnConnectionThread.start();
@@ -129,16 +79,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();
@@ -154,7 +94,7 @@ void AmneziaApplication::init()
Logger::setServiceLogsEnabled(enabled);
#ifdef Q_OS_WIN //TODO
if (m_parser.isSet(m_optAutostart))
if (m_parser.isSet("a"))
m_coreController->pageController()->showOnStartup();
else
emit m_coreController->pageController()->raiseMainWindow();
@@ -178,18 +118,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()
@@ -234,14 +162,15 @@ bool AmneziaApplication::parseCommands()
m_parser.addHelpOption();
m_parser.addVersionOption();
m_parser.addOption(m_optAutostart);
m_parser.addOption(m_optCleanup);
m_parser.addOption(m_optConnect);
m_parser.addOption(m_optImport);
QCommandLineOption c_autostart { { "a", "autostart" }, "System autostart" };
m_parser.addOption(c_autostart);
QCommandLineOption c_cleanup { { "c", "cleanup" }, "Cleanup logs" };
m_parser.addOption(c_cleanup);
m_parser.process(*this);
if (m_parser.isSet(m_optCleanup)) {
if (m_parser.isSet(c_cleanup)) {
Logger::cleanUp();
QTimer::singleShot(100, this, [this] { quit(); });
exec();
@@ -250,8 +179,9 @@ bool AmneziaApplication::parseCommands()
return true;
}
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
void AmneziaApplication::startLocalServer() {
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
void AmneziaApplication::startLocalServer()
{
const QString serverName("AmneziaVPNInstance");
QLocalServer::removeServer(serverName);
@@ -268,32 +198,6 @@ void AmneziaApplication::startLocalServer() {
}
#endif
bool AmneziaApplication::eventFilter(QObject *watched, QEvent *event)
{
if (event->type() == QEvent::Close) {
#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();
}
}
#endif
return true; // eat the close
}
// call base QObject::eventFilter
return QObject::eventFilter(watched, event);
}
void AmneziaApplication::forceQuit()
{
m_forceQuit = true;
quit();
}
QQmlApplicationEngine *AmneziaApplication::qmlEngine() const
{
return m_engine;

View File

@@ -7,9 +7,9 @@
#include <QQmlContext>
#include <QThread>
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#include <QGuiApplication>
#include <QGuiApplication>
#else
#include <QApplication>
#include <QApplication>
#endif
#include <QClipboard>
@@ -20,9 +20,9 @@
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#define AMNEZIA_BASE_CLASS QGuiApplication
#define AMNEZIA_BASE_CLASS QGuiApplication
#else
#define AMNEZIA_BASE_CLASS QApplication
#define AMNEZIA_BASE_CLASS QApplication
#endif
class AmneziaApplication : public AMNEZIA_BASE_CLASS
@@ -37,7 +37,7 @@ public:
void loadFonts();
bool parseCommands();
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
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;
@@ -60,17 +56,10 @@ private:
QCommandLineParser m_parser;
QCommandLineOption m_optAutostart;
QCommandLineOption m_optCleanup;
QCommandLineOption m_optConnect;
QCommandLineOption m_optImport;
QSharedPointer<VpnConnection> m_vpnConnection;
QThread m_vpnConnectionThread;
QNetworkAccessManager *m_nam;
protected:
bool eventFilter(QObject *watched, QEvent *event) override;
};
#endif // AMNEZIA_APPLICATION_H

View File

@@ -45,8 +45,7 @@
android:configChanges="uiMode|screenSize|smallestScreenSize|screenLayout|orientation|density
|fontScale|layoutDirection|locale|keyboard|keyboardHidden|navigation|mcc|mnc"
android:launchMode="singleInstance"
android:windowSoftInputMode="adjustResize|stateUnchanged"
android:enableOnBackInvokedCallback="false"
android:windowSoftInputMode="stateUnchanged|adjustResize"
android:exported="true">
<intent-filter>
@@ -215,4 +214,4 @@
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/qtprovider_paths" />
</provider>
</application>
</manifest>
</manifest>

View File

@@ -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)

View File

@@ -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) {

View File

@@ -6,9 +6,6 @@
<item name="android:colorBackground">@color/black</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:enforceNavigationBarContrast">false</item>
<item name="android:enforceStatusBarContrast">false</item>
</style>
<style name="Translucent" parent="NoActionBar">
<item name="android:windowBackground">@android:color/transparent</item>

View File

@@ -35,11 +35,6 @@ import android.widget.Toast
import androidx.annotation.MainThread
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import androidx.core.graphics.Insets
import androidx.core.view.OnApplyWindowInsetsListener
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import java.io.IOException
import kotlin.LazyThreadSafetyMode.NONE
import kotlin.coroutines.CoroutineContext
@@ -175,9 +170,10 @@ class AmneziaActivity : QtActivity() {
super.onCreate(savedInstanceState)
Log.d(TAG, "Create Amnezia activity")
loadLibs()
// Configure window for edge-to-edge display
configureWindowForEdgeToEdge()
window.apply {
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
statusBarColor = getColor(R.color.black)
}
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
val proto = mainScope.async(Dispatchers.IO) {
VpnStateStore.getVpnState().vpnProto
@@ -269,98 +265,6 @@ class AmneziaActivity : QtActivity() {
super.onStop()
}
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
Log.d(TAG, "Window focus changed: hasFocus=$hasFocus")
}
override fun onPause() {
super.onPause()
Log.d(TAG, "Pause Amnezia activity")
}
override fun onResume() {
super.onResume()
/* if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
window.decorView.apply {
invalidate()
postDelayed({
sendTouch(1f, 1f)
}, 100)
postDelayed({
sendTouch(2f, 2f)
}, 200)
postDelayed({
requestLayout()
invalidate()
}, 250)
}
} */
Log.d(TAG, "Resume Amnezia activity")
}
private fun configureWindowForEdgeToEdge() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
window.apply {
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
addFlags(LayoutParams.FLAG_LAYOUT_NO_LIMITS)
statusBarColor = android.graphics.Color.TRANSPARENT
navigationBarColor = android.graphics.Color.TRANSPARENT
}
WindowInsetsControllerCompat(window, window.decorView).apply {
isAppearanceLightStatusBars = false
isAppearanceLightNavigationBars = false
}
// Workaround for Android 14 (API 34+) IME adjustResize bug
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
setupImeInsetsListener()
}
} else {
window.apply {
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
statusBarColor = getColor(R.color.black)
}
WindowInsetsControllerCompat(window, window.decorView).apply {
isAppearanceLightStatusBars = false
isAppearanceLightNavigationBars = false
}
}
}
private fun setupImeInsetsListener() {
ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { view, windowInsets ->
val imeInsets = windowInsets.getInsets(WindowInsetsCompat.Type.ime())
val imeVisible = windowInsets.isVisible(WindowInsetsCompat.Type.ime())
val imeHeight = if (imeVisible) imeInsets.bottom else 0
val density = resources.displayMetrics.density
val imeHeightDp = (imeHeight / density).toInt()
// Also track system bars (navigation bar, status bar) changes
val systemBarsInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val navBarHeight = systemBarsInsets.bottom
val navBarHeightDp = (navBarHeight / density).toInt()
val statusBarHeight = systemBarsInsets.top
val statusBarHeightDp = (statusBarHeight / density).toInt()
mainScope.launch {
qtInitialized.await()
QtAndroidController.onImeInsetsChanged(imeHeightDp)
QtAndroidController.onSystemBarsInsetsChanged(navBarHeightDp, statusBarHeightDp)
}
// Return windowInsets instead of CONSUMED to allow proper handling
windowInsets
}
}
override fun onDestroy() {
Log.d(TAG, "Destroy Amnezia activity")
unregisterBroadcastReceiver(notificationStateReceiver)
@@ -762,43 +666,6 @@ class AmneziaActivity : QtActivity() {
@Suppress("unused")
fun isOnTv(): Boolean = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
@Suppress("unused")
fun isEdgeToEdgeEnabled(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
@Suppress("unused")
fun getStatusBarHeight(): Int {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) return 0
val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android")
val heightPx = if (resourceId > 0) {
resources.getDimensionPixelSize(resourceId)
} else {
0
}
// Convert physical pixels to device-independent pixels for QML
val density = resources.displayMetrics.density
val heightDp = (heightPx / density).toInt()
return heightDp
}
@Suppress("unused")
fun getNavigationBarHeight(): Int {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) return 0
val resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android")
val heightPx = if (resourceId > 0) {
resources.getDimensionPixelSize(resourceId)
} else {
0
}
// Convert physical pixels to device-independent pixels for QML
val density = resources.displayMetrics.density
val heightDp = (heightPx / density).toInt()
return heightDp
}
@Suppress("unused")
fun startQrCodeReader() {
Log.v(TAG, "Start camera")

View File

@@ -565,7 +565,7 @@ open class AmneziaVpnService : VpnService() {
protocolState.value = RECONNECTING
connectionJob = connectionScope.launch {
vpnProto?.protocol?.reconnectVpn(Builder(), ::protect)
vpnProto?.protocol?.reconnectVpn(Builder())
}
}

View File

@@ -38,15 +38,15 @@ object AppListProvider {
}
}
private class App(pi: PackageInfo, pm: PackageManager, ai: ApplicationInfo? = pi.applicationInfo) : Comparable<App> {
private class App(pi: PackageInfo, pm: PackageManager, ai: ApplicationInfo = pi.applicationInfo) : Comparable<App> {
val name: String?
val packageName: String = pi.packageName
val icon: Boolean = (ai?.icon ?: 0) != 0
val icon: Boolean = ai.icon != 0
val isLaunchable: Boolean = pm.getLaunchIntentForPackage(packageName) != null
init {
val name = ai?.loadLabel(pm)?.toString()
this.name = name?.takeIf { it != packageName }
val name = ai.loadLabel(pm).toString()
this.name = if (name != packageName) name else null
}
override fun compareTo(other: App): Int {

View File

@@ -28,7 +28,4 @@ object QtAndroidController {
external fun onAuthResult(result: Boolean)
external fun decodeQrCode(data: String): Boolean
external fun onImeInsetsChanged(heightDp: Int)
external fun onSystemBarsInsetsChanged(navBarHeightDp: Int, statusBarHeightDp: Int)
}

View File

@@ -10,8 +10,6 @@ import java.nio.channels.FileChannel
import java.nio.channels.FileLock
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.time.ZonedDateTime
import java.time.ZoneOffset
import java.util.concurrent.locks.ReentrantLock
import org.amnezia.vpn.util.Log.Priority.D
import org.amnezia.vpn.util.Log.Priority.E
@@ -137,8 +135,8 @@ object Log {
}
private fun formatLogMsg(tag: String, msg: String, priority: Priority): String {
val utcDate = ZonedDateTime.now(ZoneOffset.UTC).format(dateTimeFormat)
return "${utcDate}Z ${Process.myPid()} ${Process.myTid()} $priority [${Thread.currentThread().name}] " +
val date = LocalDateTime.now().format(dateTimeFormat)
return "$date ${Process.myPid()} ${Process.myTid()} $priority [${Thread.currentThread().name}] " +
"$tag: $msg\n"
}

View File

@@ -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 {
@@ -123,26 +120,14 @@ open class Wireguard : Protocol() {
configData.optStringOrNull("Jmax")?.let { setJmax(it.toInt()) }
configData.optStringOrNull("S1")?.let { setS1(it.toInt()) }
configData.optStringOrNull("S2")?.let { setS2(it.toInt()) }
configData.optStringOrNull("S3")?.let { setS3(it.toInt()) }
configData.optStringOrNull("S4")?.let { setS4(it.toInt()) }
configData.optStringOrNull("H1")?.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("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("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()) }
}
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 +135,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 +191,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
}
}

View File

@@ -20,17 +20,10 @@ open class WireguardConfig protected constructor(
val jmax: Int?,
val s1: Int?,
val s2: Int?,
val s3: Int?,
val s4: Int?,
val h1: String?,
val h2: String?,
val h3: String?,
val h4: String?,
var i1: String?,
var i2: String?,
var i3: String?,
var i4: String?,
var i5: String?,
val h1: Long?,
val h2: Long?,
val h3: Long?,
val h4: Long?
) : ProtocolConfig(protocolConfigBuilder) {
protected constructor(builder: Builder) : this(
@@ -46,17 +39,10 @@ open class WireguardConfig protected constructor(
builder.jmax,
builder.s1,
builder.s2,
builder.s3,
builder.s4,
builder.h1,
builder.h2,
builder.h3,
builder.h4,
builder.i1,
builder.i2,
builder.i3,
builder.i4,
builder.i5,
builder.h4
)
fun toWgUserspaceString(): String = with(StringBuilder()) {
@@ -75,17 +61,10 @@ open class WireguardConfig protected constructor(
appendLine("jmax=$jmax")
appendLine("s1=$s1")
appendLine("s2=$s2")
s3?.let { appendLine("s3=$it") }
s4?.let { appendLine("s4=$it") }
appendLine("h1=$h1")
appendLine("h2=$h2")
appendLine("h3=$h3")
appendLine("h4=$h4")
i1?.let { appendLine("i1=$it") }
i2?.let { appendLine("i2=$it") }
i3?.let { appendLine("i3=$it") }
i4?.let { appendLine("i4=$it") }
i5?.let { appendLine("i5=$it") }
}
}
@@ -138,17 +117,10 @@ open class WireguardConfig protected constructor(
internal var jmax: Int? = null
internal var s1: Int? = null
internal var s2: Int? = null
internal var s3: Int? = null
internal var s4: Int? = null
internal var h1: String? = null
internal var h2: String? = null
internal var h3: String? = null
internal var h4: String? = 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 h1: Long? = null
internal var h2: Long? = null
internal var h3: Long? = null
internal var h4: Long? = null
fun setEndpoint(endpoint: InetEndpoint) = apply { this.endpoint = endpoint }
@@ -167,17 +139,10 @@ open class WireguardConfig protected constructor(
fun setJmax(jmax: Int) = apply { this.jmax = jmax }
fun setS1(s1: Int) = apply { this.s1 = s1 }
fun setS2(s2: Int) = apply { this.s2 = s2 }
fun setS3(s3: Int) = apply { this.s3 = s3 }
fun setS4(s4: Int) = apply { this.s4 = s4 }
fun setH1(h1: 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 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 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 }
override fun build(): WireguardConfig = configBuild().run { WireguardConfig(this@Builder) }
}

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")

View File

@@ -27,18 +27,12 @@ if(WIN32)
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(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")
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")
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")
@@ -62,7 +56,7 @@ elseif(LINUX)
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})

View File

@@ -1,6 +1,6 @@
message("Client android ${CMAKE_ANDROID_ARCH_ABI} build")
set(APP_ANDROID_MIN_SDK 28)
set(APP_ANDROID_MIN_SDK 26)
set(ANDROID_PLATFORM "android-${APP_ANDROID_MIN_SDK}" CACHE STRING
"The minimum API level supported by the application or library" FORCE)
@@ -11,8 +11,8 @@ set_target_properties(${PROJECT} PROPERTIES
QT_ANDROID_VERSION_NAME ${CMAKE_PROJECT_VERSION}
QT_ANDROID_VERSION_CODE ${APP_ANDROID_VERSION_CODE}
QT_ANDROID_MIN_SDK_VERSION ${APP_ANDROID_MIN_SDK}
QT_ANDROID_TARGET_SDK_VERSION 36
QT_ANDROID_SDK_BUILD_TOOLS_REVISION 36.0.0
QT_ANDROID_TARGET_SDK_VERSION 34
QT_ANDROID_SDK_BUILD_TOOLS_REVISION 34.0.0
QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android
)
@@ -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)

View File

@@ -34,7 +34,6 @@ set(HEADERS ${HEADERS}
${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)
@@ -47,8 +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
)
@@ -79,22 +76,8 @@ set_target_properties(${PROJECT} PROPERTIES
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/Frameworks"
XCODE_EMBED_APP_EXTENSIONS networkextension
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
)
if(DEFINED DEPLOY)
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "distr ios.org.amnezia.AmneziaVPN"
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "dev ios.org.amnezia.AmneziaVPN"
)
else()
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
)
endif()
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES"

View File

@@ -14,15 +14,11 @@ set(LIBS ${LIBS}
${FW_SECURITY}
${FW_COREWLAN}
${FW_NETWORK}
${FW_USER_NOTIFICATIONS}
${FW_USERNOTIFICATIONS}
${FW_NETWORK_EXTENSION}
)
set_target_properties(${PROJECT} PROPERTIES
MACOSX_BUNDLE TRUE
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_target_properties(${PROJECT} PROPERTIES MACOSX_BUNDLE TRUE)
set(CMAKE_OSX_ARCHITECTURES "x86_64" CACHE INTERNAL "" FORCE)
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15)
@@ -35,8 +31,6 @@ set(SOURCES ${SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/ui/macos_util.mm
)
set(ICON_FILE ${CMAKE_CURRENT_SOURCE_DIR}/images/app.icns)
set(MACOSX_BUNDLE_ICON_FILE app.icns)
set_source_files_properties(${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
@@ -55,3 +49,4 @@ execute_process(
)
message("OSX_SDK_PATH is: ${OSX_SDK_PATH}")

View File

@@ -1,170 +0,0 @@
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})
enable_language(OBJC)
enable_language(Swift)
find_package(Qt6 REQUIRED COMPONENTS ShaderTools Widgets)
# Link Qt Widgets for QWidget, QMenu, QAction etc.
set(LIBS ${LIBS} Qt6::ShaderTools Qt6::Widgets)
find_library(FW_AUTHENTICATIONSERVICES AuthenticationServices)
find_library(FW_AVFOUNDATION AVFoundation)
find_library(FW_FOUNDATION Foundation)
find_library(FW_STOREKIT StoreKit)
find_library(FW_SERVICEMGMT ServiceManagement)
find_library(FW_USERNOTIFICATIONS UserNotifications)
find_library(FW_NETWORKEXTENSION NetworkExtension)
set(LIBS ${LIBS}
${FW_AUTHENTICATIONSERVICES}
${FW_AVFOUNDATION}
${FW_FOUNDATION}
${FW_STOREKIT}
${FW_SERVICEMGMT}
${FW_USERNOTIFICATIONS}
${FW_NETWORKEXTENSION}
)
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
)
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h PROPERTIES OBJECTIVE_CPP_HEADER TRUE)
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
)
set(ICON_FILE ${CMAKE_CURRENT_SOURCE_DIR}/images/app.icns)
set(MACOSX_BUNDLE_ICON_FILE app.icns)
set_source_files_properties(${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
set(SOURCES ${SOURCES} ${ICON_FILE})
target_include_directories(${PROJECT} PRIVATE
${Qt6Gui_PRIVATE_INCLUDE_DIRS}
${Qt6Widgets_PRIVATE_INCLUDE_DIRS}
)
set_target_properties(${PROJECT} PROPERTIES
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Info.plist.in
MACOSX_BUNDLE_ICON_FILE "AppIcon"
MACOSX_BUNDLE_INFO_STRING "AmneziaVPN"
MACOSX_BUNDLE_BUNDLE_NAME "AmneziaVPN"
MACOSX_BUNDLE_BUNDLE_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
MACOSX_BUNDLE_LONG_VERSION_STRING "${APPLE_PROJECT_VERSION}-${CMAKE_PROJECT_VERSION_TWEAK}"
MACOSX_BUNDLE_SHORT_VERSION_STRING "${APPLE_PROJECT_VERSION}"
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}"
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/macos/app/app.entitlements"
XCODE_ATTRIBUTE_MARKETING_VERSION "${APPLE_PROJECT_VERSION}"
XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
XCODE_ATTRIBUTE_PRODUCT_NAME "AmneziaVPN"
XCODE_ATTRIBUTE_BUNDLE_INFO_STRING "AmneziaVPN"
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 "NO"
XCODE_EMBED_FRAMEWORKS_REMOVE_HEADERS_ON_COPY "YES"
XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET "11.0"
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../Frameworks"
XCODE_EMBED_APP_EXTENSIONS AmneziaVPNNetworkExtension
)
if(DEPLOY)
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "distr macos.org.amnezia.AmneziaVPN"
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "dev macos.org.amnezia.AmneziaVPN"
)
else()
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
)
endif()
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES"
XCODE_ATTRIBUTE_SWIFT_PRECOMPILE_BRIDGING_HEADER "NO"
XCODE_ATTRIBUTE_SWIFT_OBJC_INTERFACE_HEADER_NAME "AmneziaVPN-Swift.h"
XCODE_ATTRIBUTE_SWIFT_OBJC_INTEROP_MODE "objcxx"
)
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "X7UJ388FXK"
)
target_include_directories(${PROJECT} PRIVATE ${CMAKE_CURRENT_LIST_DIR})
target_compile_options(${PROJECT} PRIVATE
-DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\"
-DVPN_NE_BUNDLEID=\"${BUILD_IOS_APP_IDENTIFIER}.network-extension\"
)
set(WG_APPLE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rd/amneziawg-apple/Sources)
target_sources(${PROJECT} PRIVATE
${WG_APPLE_SOURCE_DIR}/WireGuardKitC/x25519.c
${CLIENT_ROOT_DIR}/platforms/ios/LogController.swift
${CLIENT_ROOT_DIR}/platforms/ios/Log.swift
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
${CLIENT_ROOT_DIR}/platforms/ios/ScreenProtection.swift
${CLIENT_ROOT_DIR}/platforms/ios/VPNCController.swift
)
target_sources(${PROJECT} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Images.xcassets
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
)
set_property(TARGET ${PROJECT} APPEND PROPERTY RESOURCE
${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Images.xcassets
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
)
add_subdirectory(macos/networkextension)
add_dependencies(${PROJECT} AmneziaVPNNetworkExtension)
get_target_property(QtCore_location Qt6::Core LOCATION)
message("QtCore_location")
message(${QtCore_location})
get_filename_component(QT_BIN_DIR_DETECTED "${QtCore_location}/../../../../../bin" ABSOLUTE)
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"
)

View File

@@ -23,18 +23,11 @@ set(HEADERS ${HEADERS}
${CMAKE_CURRENT_BINARY_DIR}/version.h
${CLIENT_ROOT_DIR}/core/sshclient.h
${CLIENT_ROOT_DIR}/core/networkUtilities.h
${CLIENT_ROOT_DIR}/core/transport/igatewaytransport.h
${CLIENT_ROOT_DIR}/core/transport/httpGatewayTransport.h
${CLIENT_ROOT_DIR}/core/transport/dnsGatewayTransport.h
${CLIENT_ROOT_DIR}/core/transport/dns/dnsResolver.h
${CLIENT_ROOT_DIR}/core/transport/dns/dnsTunnel.h
${CLIENT_ROOT_DIR}/core/transport/dns/dnsPacket_p.h
${CLIENT_ROOT_DIR}/core/serialization/serialization.h
${CLIENT_ROOT_DIR}/core/serialization/transfer.h
${CLIENT_ROOT_DIR}/../common/logger/logger.h
${CLIENT_ROOT_DIR}/utils/qmlUtils.h
${CLIENT_ROOT_DIR}/core/api/apiUtils.h
${CLIENT_ROOT_DIR}/core/osSignalHandler.h
)
# Mozilla headres
@@ -43,9 +36,10 @@ set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/mozilla/shared/ipaddress.h
${CLIENT_ROOT_DIR}/mozilla/shared/leakdetector.h
${CLIENT_ROOT_DIR}/mozilla/controllerimpl.h
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.h
)
if(NOT IOS AND NOT MACOS_NE)
if(NOT IOS)
set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/platforms/ios/QRCodeReaderBase.h
)
@@ -74,11 +68,6 @@ set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/protocols/vpnprotocol.cpp
${CLIENT_ROOT_DIR}/core/sshclient.cpp
${CLIENT_ROOT_DIR}/core/networkUtilities.cpp
${CLIENT_ROOT_DIR}/core/transport/httpGatewayTransport.cpp
${CLIENT_ROOT_DIR}/core/transport/dnsGatewayTransport.cpp
${CLIENT_ROOT_DIR}/core/transport/dns/dnsResolver.cpp
${CLIENT_ROOT_DIR}/core/transport/dns/dnsTunnel.cpp
${CLIENT_ROOT_DIR}/core/transport/dns/dnsPacket.cpp
${CLIENT_ROOT_DIR}/core/serialization/outbound.cpp
${CLIENT_ROOT_DIR}/core/serialization/inbound.cpp
${CLIENT_ROOT_DIR}/core/serialization/ss.cpp
@@ -90,7 +79,6 @@ set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/../common/logger/logger.cpp
${CLIENT_ROOT_DIR}/utils/qmlUtils.cpp
${CLIENT_ROOT_DIR}/core/api/apiUtils.cpp
${CLIENT_ROOT_DIR}/core/osSignalHandler.cpp
)
# Mozilla sources
@@ -98,28 +86,15 @@ set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/mozilla/models/server.cpp
${CLIENT_ROOT_DIR}/mozilla/shared/ipaddress.cpp
${CLIENT_ROOT_DIR}/mozilla/shared/leakdetector.cpp
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.cpp
)
if(NOT IOS AND NOT MACOS_NE)
if(NOT IOS)
set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/platforms/ios/QRCodeReaderBase.cpp
)
endif()
# Include native macOS platform helpers (dock/status-item)
if(APPLE AND NOT IOS)
list(APPEND HEADERS
${CLIENT_ROOT_DIR}/platforms/macos/macosutils.h
${CLIENT_ROOT_DIR}/platforms/macos/macosstatusicon.h
${CLIENT_ROOT_DIR}/ui/macos_util.h
)
list(APPEND SOURCES
${CLIENT_ROOT_DIR}/platforms/macos/macosutils.mm
${CLIENT_ROOT_DIR}/platforms/macos/macosstatusicon.mm
${CLIENT_ROOT_DIR}/ui/macos_util.mm
)
endif()
if(NOT ANDROID)
set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/ui/notificationhandler.cpp
@@ -186,7 +161,7 @@ if(WIN32)
)
endif()
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
message("Client desktop build")
add_compile_definitions(AMNEZIA_DESKTOP)
@@ -200,13 +175,11 @@ if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
${CLIENT_ROOT_DIR}/protocols/wireguardprotocol.h
${CLIENT_ROOT_DIR}/protocols/xrayprotocol.h
${CLIENT_ROOT_DIR}/protocols/awgprotocol.h
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.h
)
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
${CLIENT_ROOT_DIR}/protocols/openvpnovercloakprotocol.cpp
@@ -216,14 +189,3 @@ if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
${CLIENT_ROOT_DIR}/protocols/awgprotocol.cpp
)
endif()
if(APPLE AND MACOS_NE)
# Include only the tray notification handler in NE builds
set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.h
)
set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.cpp
)
endif()

View File

@@ -1,5 +1,4 @@
#include "awg_configurator.h"
#include "protocols/protocols_defs.h"
#include <QJsonDocument>
#include <QJsonObject>
@@ -40,18 +39,6 @@ QString AwgConfigurator::createConfig(const ServerCredentials &credentials, Dock
jsonConfig[config_key::responsePacketMagicHeader] = configMap.value(config_key::responsePacketMagicHeader);
jsonConfig[config_key::underloadPacketMagicHeader] = configMap.value(config_key::underloadPacketMagicHeader);
jsonConfig[config_key::transportPacketMagicHeader] = configMap.value(config_key::transportPacketMagicHeader);
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::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::mtu] =
containerConfig.value(ProtocolProps::protoToString(Proto::Awg)).toObject().value(config_key::mtu).toString(protocols::awg::defaultMtu);

View File

@@ -13,10 +13,10 @@
#include <QApplication>
#endif
#include "core/networkUtilities.h"
#include "containers/containers_defs.h"
#include "core/controllers/serverController.h"
#include "core/scripts_registry.h"
#include "core/server_defs.h"
#include "settings.h"
#include "utilities.h"
@@ -24,7 +24,6 @@
#include <openssl/rsa.h>
#include <openssl/x509.h>
OpenVpnConfigurator::OpenVpnConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController,
QObject *parent)
: ConfiguratorBase(settings, serverController, parent)
@@ -83,30 +82,12 @@ QString OpenVpnConfigurator::createConfig(const ServerCredentials &credentials,
return "";
}
auto sanitizeStaticKey = [](const QString &key) {
QStringList lines = key.split('\n');
QStringList filtered;
filtered.reserve(lines.size());
for (const QString &line : lines) {
const QString trimmed = line.trimmed();
if (trimmed.startsWith('#')) {
continue;
}
filtered.append(line);
}
QString result = filtered.join('\n');
if (!result.endsWith('\n')) {
result.append('\n');
}
return result;
};
config.replace("$OPENVPN_CA_CERT", connData.caCert);
config.replace("$OPENVPN_CLIENT_CERT", connData.clientCert);
config.replace("$OPENVPN_PRIV_KEY", connData.privKey);
if (config.contains("$OPENVPN_TA_KEY")) {
config.replace("$OPENVPN_TA_KEY", sanitizeStaticKey(connData.taKey));
config.replace("$OPENVPN_TA_KEY", connData.taKey);
} else {
config.replace("<tls-auth>", "");
config.replace("</tls-auth>", "");
@@ -135,23 +116,23 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPair<QString,
if (!isApiConfig) {
QRegularExpression regex("redirect-gateway.*");
config.replace(regex, "");
// We don't use secondary DNS if primary DNS is AmneziaDNS
if (dns.first.contains(protocols::dns::amneziaDnsIp)) {
QRegularExpression dnsRegex("dhcp-option DNS " + dns.second);
config.replace(dnsRegex, "");
}
if (!m_settings->isSitesSplitTunnelingEnabled()) {
config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n");
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
// Prevent ipv6 leak
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
#endif
config.append("block-ipv6\n");
} else if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
// no redirect-gateway
// no redirect-gateway
} else if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n");
// Prevent ipv6 leak
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
#endif
config.append("block-ipv6\n");
}
@@ -185,15 +166,10 @@ QString OpenVpnConfigurator::processConfigWithExportSettings(const QPair<QString
QRegularExpression regex("redirect-gateway.*");
config.replace(regex, "");
// We don't use secondary DNS if primary DNS is AmneziaDNS
if (dns.first.contains(protocols::dns::amneziaDnsIp)) {
QRegularExpression dnsRegex("dhcp-option DNS " + dns.second);
config.replace(dnsRegex, "");
}
config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n");
// Prevent ipv6 leak
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
config.append("block-ipv6\n");
// remove block-outside-dns for all exported configs

View File

@@ -8,7 +8,7 @@
#include <QTemporaryFile>
#include <QThread>
#include <qtimer.h>
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE)
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#include <QGuiApplication>
#else
#include <QApplication>
@@ -24,7 +24,7 @@ SshConfigurator::SshConfigurator(std::shared_ptr<Settings> settings, const QShar
QString SshConfigurator::convertOpenSShKey(const QString &key)
{
#if !defined(Q_OS_IOS) && !defined(MACOS_NE)
#ifndef Q_OS_IOS
QProcess p;
p.setProcessChannelMode(QProcess::MergedChannels);
@@ -67,10 +67,9 @@ QString SshConfigurator::convertOpenSShKey(const QString &key)
#endif
}
// DEAD CODE.
void SshConfigurator::openSshTerminal(const ServerCredentials &credentials)
{
#if !defined(Q_OS_IOS) && !defined(MACOS_NE)
#ifndef Q_OS_IOS
QProcess *p = new QProcess();
p->setProcessChannelMode(QProcess::SeparateChannels);
@@ -102,7 +101,7 @@ QProcessEnvironment SshConfigurator::prepareEnv()
pathEnvVar.clear();
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\cygwin;");
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\openvpn;");
#elif defined(Q_OS_MACX) && !defined(MACOS_NE)
#elif defined(Q_OS_MACX)
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "/Contents/MacOS");
#endif

View File

@@ -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,

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.") },
@@ -152,83 +140,98 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
{
return {
{ DockerContainer::OpenVpn,
QObject::tr("OpenVPN is one of the most popular and reliable VPN protocols. "
"It uses SSL/TLS encryption, supports a wide variety of devices and operating systems, "
"and is continuously improved by the community due to its open-source nature. "
"It provides a good balance between speed and security but is easily recognized by DPI systems, "
"making it susceptible to blocking.\n"
"\nFeatures:\n"
"* Available on all AmneziaVPN platforms\n"
"* Normal battery consumption on mobile devices\n"
"* Flexible customization for various devices and OS\n"
"* Operates over both TCP and UDP protocols") },
QObject::tr(
"OpenVPN stands as one of the most popular and time-tested VPN protocols available.\n"
"It employs its unique security protocol, "
"leveraging the strength of SSL/TLS for encryption and key exchange. "
"Furthermore, OpenVPN's support for a multitude of authentication methods makes it versatile and adaptable, "
"catering to a wide range of devices and operating systems. "
"Due to its open-source nature, OpenVPN benefits from extensive scrutiny by the global community, "
"which continually reinforces its security. "
"With a strong balance of performance, security, and compatibility, "
"OpenVPN remains a top choice for privacy-conscious individuals and businesses alike.\n\n"
"* Available in the AmneziaVPN across all platforms\n"
"* Normal power consumption on mobile devices\n"
"* Flexible customisation to suit user needs to work with different operating systems and devices\n"
"* Recognised by DPI systems and therefore susceptible to blocking\n"
"* Can operate over both TCP and UDP network protocols.") },
{ DockerContainer::ShadowSocks,
QObject::tr("Shadowsocks is based on the SOCKS5 protocol and encrypts connections using AEAD cipher. "
"Although designed to be discreet, it doesn't mimic a standard HTTPS connection and can be detected by some DPI systems. "
"Due to limited support in Amnezia, we recommend using the AmneziaWG protocol.\n"
"\nFeatures:\n"
"* Available in AmneziaVPN only on desktop platforms\n"
"* Customizable encryption protocol\n"
QObject::tr("Shadowsocks, inspired by the SOCKS5 protocol, safeguards the connection using the AEAD cipher. "
"Although Shadowsocks is designed to be discreet and challenging to identify, it isn't identical to a standard HTTPS connection."
"However, certain traffic analysis systems might still detect a Shadowsocks connection. "
"Due to limited support in Amnezia, it's recommended to use AmneziaWG protocol.\n\n"
"* Available in the AmneziaVPN only on desktop platforms\n"
"* Configurable encryption protocol\n"
"* Detectable by some DPI systems\n"
"* Operates over TCP protocol\n") },
"* Works over TCP network protocol.") },
{ DockerContainer::Cloak,
QObject::tr("This combination includes the OpenVPN protocol and the Cloak plugin, specifically designed to protect against blocking.\n"
"\nOpenVPN securely encrypts all internet traffic between your device and the server.\n"
"\nThe Cloak plugin further protects the connection from DPI detection. "
"It modifies traffic metadata to disguise VPN traffic as regular web traffic and prevents detection through active probing. "
"If an incoming connection fails authentication, Cloak serves a fake website, making your VPN invisible to traffic analysis systems.\n"
"\nIn regions with heavy internet censorship, we strongly recommend using OpenVPN with Cloak from your first connection.\n"
"\nFeatures:\n"
"* Available on all AmneziaVPN platforms\n"
QObject::tr("This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for "
"protecting against detection.\n\n"
"OpenVPN provides a secure VPN connection by encrypting all internet traffic between the client "
"and the server.\n\n"
"Cloak protects OpenVPN from detection. \n\n"
"Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, "
"and also protects the VPN from detection by Active Probing. This makes it very resistant to "
"being detected\n\n"
"Immediately after receiving the first data packet, Cloak authenticates the incoming connection. "
"If authentication fails, the plugin masks the server as a fake website and your VPN becomes "
"invisible to analysis systems.\n\n"
"* Available in the AmneziaVPN across all platforms\n"
"* High power consumption on mobile devices\n"
"* Flexible configuration options\n"
"* Undetectable by DPI systems\n"
"* Operates over TCP protocol on port 443") },
"* Flexible settings\n"
"* Not recognised by detection systems\n"
"* Works over TCP network protocol, 443 port.\n") },
{ DockerContainer::WireGuard,
QObject::tr("WireGuard is a modern, streamlined VPN protocol offering stable connectivity and excellent performance across all devices. "
"It uses fixed encryption settings, delivering lower latency and higher data transfer speeds compared to OpenVPN. "
"However, WireGuard is easily identifiable by DPI systems due to its distinctive packet signatures, making it susceptible to blocking.\n"
"\nFeatures:\n"
"* Available on all AmneziaVPN platforms\n"
"* Low power consumption on mobile devices\n"
"* Minimal configuration required\n"
"* Easily detected by DPI systems (susceptible to blocking)\n"
"* Operates over UDP protocol") },
{ DockerContainer::Awg2,
QObject::tr("AmneziaWG is a modern VPN protocol based on WireGuard, "
"combining simplified architecture with high performance across all devices. "
"It addresses WireGuard's main vulnerability (easy detection by DPI systems) through advanced obfuscation techniques, "
"making VPN traffic indistinguishable from regular internet traffic.\n"
"\nAmneziaWG is an excellent choice for those seeking a fast, stealthy VPN connection.\n"
"\nFeatures:\n"
"* Available on all AmneziaVPN platforms\n"
"* Low battery consumption on mobile devices\n"
"* Minimal settings required\n"
"* Undetectable by traffic analysis systems (DPI)\n"
"* Operates over UDP protocol") },
QObject::tr("A relatively new popular VPN protocol with a simplified architecture.\n"
"WireGuard provides stable VPN connection and high performance on all devices. It uses hard-coded encryption "
"settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput.\n"
"WireGuard is very susceptible to detection and blocking due to its distinct packet signatures. "
"Unlike some other VPN protocols that employ obfuscation techniques, "
"the consistent signature patterns of WireGuard packets can be more easily identified and "
"thus blocked by advanced Deep Packet Inspection (DPI) systems and other network monitoring tools.\n\n"
"* Available in the AmneziaVPN across all platforms\n"
"* Low power consumption\n"
"* Minimum number of settings\n"
"* Easily recognised by DPI analysis systems, susceptible to blocking\n"
"* Works over UDP network protocol.") },
{ DockerContainer::Awg,
QObject::tr("A modern iteration of the popular VPN protocol, "
"AmneziaWG builds upon the foundation set by WireGuard, "
"retaining its simplified architecture and high-performance capabilities across devices.\n"
"While WireGuard is known for its efficiency, "
"it had issues with being easily detected due to its distinct packet signatures. "
"AmneziaWG solves this problem by using better obfuscation methods, "
"making its traffic blend in with regular internet traffic.\n"
"This means that AmneziaWG keeps the fast performance of the original "
"while adding an extra layer of stealth, "
"making it a great choice for those wanting a fast and discreet VPN connection.\n\n"
"* Available in the AmneziaVPN across all platforms\n"
"* Low power consumption\n"
"* Minimum number of settings\n"
"* Not recognised by traffic analysis systems\n"
"* Works over UDP network protocol.") },
{ DockerContainer::Xray,
QObject::tr("REALITY is an innovative protocol developed by the creators of XRay, designed specifically to combat high levels of internet censorship. "
"REALITY identifies censorship systems during the TLS handshake, "
"redirecting suspicious traffic seamlessly to legitimate websites like google.com while providing genuine TLS certificates. "
"This allows VPN traffic to blend indistinguishably with regular web traffic without special configuration."
"\nUnlike older protocols such as VMess, VLESS, and XTLS-Vision, REALITY incorporates an advanced built-in \"friend-or-foe\" detection mechanism, "
"effectively protecting against DPI and other traffic analysis methods.\n"
"\nFeatures:\n"
"* Resistant to active probing and DPI detection\n"
"* No special configuration required to disguise traffic\n"
"* Highly effective in heavily censored regions\n"
"* Minimal battery consumption on devices\n"
"* Operates over TCP protocol") },
QObject::tr("The REALITY protocol, a pioneering development by the creators of XRay, "
"is designed to provide the highest level of protection against detection through its innovative approach to security and privacy.\n"
"It uniquely identifies attackers during the TLS handshake phase, seamlessly operating as a proxy for legitimate clients while diverting attackers to genuine websites, "
"thus presenting an authentic TLS certificate and data. \n"
"This advanced capability differentiates REALITY from similar technologies by its ability to disguise web traffic as coming from random, "
"legitimate sites without the need for specific configurations. \n"
"Unlike older protocols such as VMess, VLESS, and the XTLS-Vision transport, "
"REALITY's innovative \"friend or foe\" recognition at the TLS handshake enhances security. "
"This makes REALITY a robust solution for maintaining internet freedom.")
},
{ DockerContainer::Ipsec,
QObject::tr("IKEv2, combined with IPSec encryption, is a modern and reliable VPN protocol. "
"It reconnects quickly when switching networks or devices, making it ideal for dynamic network environments. "
"While it provides good security and speed, it's easily recognized by DPI systems and susceptible to blocking.\n"
"\nFeatures:\n"
"* Available in AmneziaVPN only on Windows\n"
"* Low battery consumption on mobile devices\n"
"* Minimal configuration required\n"
"* Detectable by DPI analysis systems(easily blocked)\n"
"* Operates over UDP protocol(ports 500 and 4500)") },
QObject::tr("IKEv2, paired with the IPSec encryption layer, stands as a modern and stable VPN protocol.\n"
"One of its distinguishing features is its ability to swiftly switch between networks and devices, "
"making it particularly adaptive in dynamic network environments. \n"
"While it offers a blend of security, stability, and speed, "
"it's essential to note that IKEv2 can be easily detected and is susceptible to blocking.\n\n"
"* Available in the AmneziaVPN only on Windows\n"
"* Low power consumption, on mobile devices\n"
"* Minimal configuration\n"
"* Recognised by DPI analysis systems\n"
"* Works over UDP network protocol, ports 500 and 4500.") },
{ DockerContainer::TorWebSite, QObject::tr("Website in Tor network") },
{ DockerContainer::Dns, QObject::tr("DNS Service") },
@@ -254,7 +257,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,49 +270,21 @@ 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
return true;
#elif defined(Q_OS_IOS)
// Standard iOS build (without Network Extension limitations)
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;
case DockerContainer::SSXray: return true;
// case DockerContainer::ShadowSocks: return true;
default:
return false;
}
#elif defined(MACOS_NE)
// 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;
case DockerContainer::OpenVpn:
case DockerContainer::Cloak:
case DockerContainer::ShadowSocks:
return false;
default:
return false;
default: return false;
}
#elif defined(Q_OS_MAC)
switch (c) {
@@ -324,7 +298,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 +327,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 +335,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 +343,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 +352,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 +368,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 +385,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;

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);

View File

@@ -10,8 +10,7 @@ namespace apiDefs
AmneziaFreeV3,
AmneziaPremiumV1,
AmneziaPremiumV2,
SelfHosted,
ExternalPremium
SelfHosted
};
enum ConfigSource {
@@ -22,21 +21,12 @@ namespace apiDefs
namespace key
{
constexpr QLatin1String configVersion("config_version");
constexpr QLatin1String apiEndpoint("api_endpoint");
constexpr QLatin1String apiKey("api_key");
constexpr QLatin1String description("description");
constexpr QLatin1String name("name");
constexpr QLatin1String protocol("protocol");
constexpr QLatin1String apiConfig("api_config");
constexpr QLatin1String stackType("stack_type");
constexpr QLatin1String serviceType("service_type");
constexpr QLatin1String cliVersion("cli_version");
constexpr QLatin1String supportedProtocols("supported_protocols");
constexpr QLatin1String vpnKey("vpn_key");
constexpr QLatin1String config("config");
constexpr QLatin1String configs("configs");
constexpr QLatin1String installationUuid("installation_uuid");
constexpr QLatin1String workerLastUpdated("worker_last_updated");
@@ -47,39 +37,15 @@ namespace apiDefs
constexpr QLatin1String serverCountryName("server_country_name");
constexpr QLatin1String osVersion("os_version");
constexpr QLatin1String appLanguage("app_language");
constexpr QLatin1String availableCountries("available_countries");
constexpr QLatin1String activeDeviceCount("active_device_count");
constexpr QLatin1String maxDeviceCount("max_device_count");
constexpr QLatin1String subscriptionEndDate("subscription_end_date");
constexpr QLatin1String issuedConfigs("issued_configs");
constexpr QLatin1String subscriptionDescription("subscription_description");
constexpr QLatin1String supportInfo("support_info");
constexpr QLatin1String email("email");
constexpr QLatin1String billingEmail("billing_email");
constexpr QLatin1String website("website");
constexpr QLatin1String websiteName("website_name");
constexpr QLatin1String telegram("telegram");
constexpr QLatin1String id("id");
constexpr QLatin1String orderId("order_id");
constexpr QLatin1String migrationCode("migration_code");
constexpr QLatin1String transactionId("transaction_id");
constexpr QLatin1String isTestPurchase("is_test_purchase");
constexpr QLatin1String userCountryCode("user_country_code");
constexpr QLatin1String serviceInfo("service_info");
constexpr QLatin1String isAdVisible("is_ad_visible");
constexpr QLatin1String adHeader("ad_header");
constexpr QLatin1String adDescription("ad_description");
constexpr QLatin1String adEndpoint("ad_endpoint");
}
const int requestTimeoutMsecs = 30 * 1000; // 30 secs (increased for DNS transport testing)
const int requestTimeoutMsecs = 12 * 1000; // 12 secs
}
#endif // APIDEFS_H

View File

@@ -1,30 +1,11 @@
#include "apiUtils.h"
#include <QDateTime>
#include <QJsonDocument>
#include <QJsonObject>
namespace
{
const QByteArray AMNEZIA_CONFIG_SIGNATURE = QByteArray::fromHex("000000ff");
QString escapeUnicode(const QString &input)
{
QString output;
for (QChar c : input) {
if (c.unicode() < 0x20 || c.unicode() > 0x7E) {
output += QString("\\u%1").arg(QString::number(c.unicode(), 16).rightJustified(4, '0'));
} else {
output += c;
}
}
return output;
}
}
bool apiUtils::isSubscriptionExpired(const QString &subscriptionEndDate)
{
QDateTime now = QDateTime::currentDateTimeUtc();
QDateTime now = QDateTime::currentDateTime();
QDateTime endDate = QDateTime::fromString(subscriptionEndDate, Qt::ISODateWithMs);
return endDate < now;
}
@@ -42,34 +23,24 @@ bool apiUtils::isServerFromApi(const QJsonObject &serverConfigObject)
apiDefs::ConfigType apiUtils::getConfigType(const QJsonObject &serverConfigObject)
{
auto configVersion = serverConfigObject.value(apiDefs::key::configVersion).toInt();
switch (configVersion) {
case apiDefs::ConfigSource::Telegram: {
constexpr QLatin1String freeV2Endpoint(FREE_V2_ENDPOINT);
constexpr QLatin1String premiumV1Endpoint(PREM_V1_ENDPOINT);
auto apiEndpoint = serverConfigObject.value(apiDefs::key::apiEndpoint).toString();
if (apiEndpoint.contains(premiumV1Endpoint)) {
return apiDefs::ConfigType::AmneziaPremiumV1;
} else if (apiEndpoint.contains(freeV2Endpoint)) {
return apiDefs::ConfigType::AmneziaFreeV2;
}
};
case apiDefs::ConfigSource::AmneziaGateway: {
constexpr QLatin1String stackPremium("prem");
constexpr QLatin1String stackFree("free");
constexpr QLatin1String servicePremium("amnezia-premium");
constexpr QLatin1String serviceFree("amnezia-free");
constexpr QLatin1String serviceExternalPremium("external-premium");
auto apiConfigObject = serverConfigObject.value(apiDefs::key::apiConfig).toObject();
auto stackType = apiConfigObject.value(apiDefs::key::stackType).toString();
auto serviceType = apiConfigObject.value(apiDefs::key::serviceType).toString();
if (serviceType == servicePremium) {
if (serviceType == servicePremium || stackType == stackPremium) {
return apiDefs::ConfigType::AmneziaPremiumV2;
} else if (serviceType == serviceFree) {
} else if (serviceType == serviceFree || stackType == stackFree) {
return apiDefs::ConfigType::AmneziaFreeV3;
} else if (serviceType == serviceExternalPremium) {
return apiDefs::ConfigType::ExternalPremium;
}
}
default: {
@@ -83,45 +54,30 @@ apiDefs::ConfigSource apiUtils::getConfigSource(const QJsonObject &serverConfigO
return static_cast<apiDefs::ConfigSource>(serverConfigObject.value(apiDefs::key::configVersion).toInt());
}
amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &sslErrors, const QString &replyErrorString,
const QNetworkReply::NetworkError &replyError, const int httpStatusCode,
const QByteArray &responseBody)
amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &sslErrors, QNetworkReply *reply)
{
const int httpStatusCodeConflict = 409;
const int httpStatusCodeNotFound = 404;
const int httpStatusCodeNotImplemented = 501;
if (!sslErrors.empty()) {
qDebug().noquote() << sslErrors;
return amnezia::ErrorCode::ApiConfigSslError;
} else if (replyError == QNetworkReply::NoError) {
} else if (reply->error() == QNetworkReply::NoError) {
return amnezia::ErrorCode::NoError;
} else if (replyError == QNetworkReply::NetworkError::OperationCanceledError
|| replyError == QNetworkReply::NetworkError::TimeoutError) {
qDebug() << replyError;
} else if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
return amnezia::ErrorCode::ApiConfigTimeoutError;
} else if (replyError == QNetworkReply::NetworkError::OperationNotImplementedError) {
qDebug() << replyError;
return amnezia::ErrorCode::ApiUpdateRequestError;
} else {
qDebug() << QString::fromUtf8(responseBody);
qDebug() << replyError;
qDebug() << replyErrorString;
QString err = reply->errorString();
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
qDebug() << QString::fromUtf8(reply->readAll());
qDebug() << reply->error();
qDebug() << err;
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;
}
@@ -129,96 +85,3 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
qDebug() << "something went wrong";
return amnezia::ErrorCode::InternalError;
}
bool apiUtils::isPremiumServer(const QJsonObject &serverConfigObject)
{
static const QSet<apiDefs::ConfigType> premiumTypes = { apiDefs::ConfigType::AmneziaPremiumV1, apiDefs::ConfigType::AmneziaPremiumV2,
apiDefs::ConfigType::ExternalPremium };
return premiumTypes.contains(getConfigType(serverConfigObject));
}
QString apiUtils::getPremiumV1VpnKey(const QJsonObject &serverConfigObject)
{
if (apiUtils::getConfigType(serverConfigObject) != apiDefs::ConfigType::AmneziaPremiumV1) {
return {};
}
QList<QPair<QString, QVariant>> orderedFields;
orderedFields.append(qMakePair(apiDefs::key::name, serverConfigObject[apiDefs::key::name].toString()));
orderedFields.append(qMakePair(apiDefs::key::description, serverConfigObject[apiDefs::key::description].toString()));
orderedFields.append(qMakePair(apiDefs::key::configVersion, serverConfigObject[apiDefs::key::configVersion].toDouble()));
orderedFields.append(qMakePair(apiDefs::key::protocol, serverConfigObject[apiDefs::key::protocol].toString()));
orderedFields.append(qMakePair(apiDefs::key::apiEndpoint, serverConfigObject[apiDefs::key::apiEndpoint].toString()));
orderedFields.append(qMakePair(apiDefs::key::apiKey, serverConfigObject[apiDefs::key::apiKey].toString()));
QString vpnKeyStr = "{";
for (int i = 0; i < orderedFields.size(); ++i) {
const auto &pair = orderedFields[i];
if (pair.second.typeId() == QMetaType::Type::QString) {
vpnKeyStr += "\"" + pair.first + "\": \"" + pair.second.toString() + "\"";
} else if (pair.second.typeId() == QMetaType::Type::Double || pair.second.typeId() == QMetaType::Type::Int) {
vpnKeyStr += "\"" + pair.first + "\": " + QString::number(pair.second.toDouble(), 'f', 1);
}
if (i < orderedFields.size() - 1) {
vpnKeyStr += ", ";
}
}
vpnKeyStr += "}";
QByteArray vpnKeyCompressed = escapeUnicode(vpnKeyStr).toUtf8();
vpnKeyCompressed = qCompress(vpnKeyCompressed, 6);
vpnKeyCompressed = vpnKeyCompressed.mid(4);
QByteArray signedData = AMNEZIA_CONFIG_SIGNATURE + vpnKeyCompressed;
return QString("vpn://%1").arg(QString(signedData.toBase64(QByteArray::Base64UrlEncoding)));
}
QString apiUtils::getPremiumV2VpnKey(const QJsonObject &serverConfigObject)
{
if (apiUtils::getConfigType(serverConfigObject) != apiDefs::ConfigType::AmneziaPremiumV2) {
return {};
}
QString vpnKeyText = "";
auto apiConfig = serverConfigObject.value(apiDefs::key::apiConfig).toObject();
auto authData = serverConfigObject.value(QLatin1String("auth_data")).toObject();
const QString name = serverConfigObject.value(apiDefs::key::name).toString();
const QString description = serverConfigObject.value(apiDefs::key::description).toString();
const double configVersion = serverConfigObject.value(apiDefs::key::configVersion).toDouble();
const QString serviceType = apiConfig.value(apiDefs::key::serviceType).toString();
const QString serviceProtocol = apiConfig.value(QLatin1String("service_protocol")).toString();
const QString userCountryCode = apiConfig.value(QLatin1String("user_country_code")).toString();
const QString apiKey = authData.value(apiDefs::key::apiKey).toString();
QString vpnKeyStr = "{";
vpnKeyStr += "\"" + QString(apiDefs::key::name) + "\": \"" + name + "\", ";
vpnKeyStr += "\"" + QString(apiDefs::key::description) + "\": \"" + description + "\", ";
vpnKeyStr += "\"" + QString(apiDefs::key::configVersion) + "\": " + QString::number(static_cast<int>(configVersion)) + ", ";
vpnKeyStr += "\"" + QString(apiDefs::key::apiConfig) + "\": {";
vpnKeyStr += "\"" + QString(apiDefs::key::serviceType) + "\": \"" + serviceType + "\", ";
vpnKeyStr += "\"service_protocol\": \"" + serviceProtocol + "\", ";
vpnKeyStr += "\"user_country_code\": \"" + userCountryCode + "\"";
vpnKeyStr += "}, ";
vpnKeyStr += "\"auth_data\": {";
vpnKeyStr += "\"" + QString(apiDefs::key::apiKey) + "\": \"" + apiKey + "\"";
vpnKeyStr += "}";
vpnKeyStr += "}";
QByteArray vpnKeyCompressed = escapeUnicode(vpnKeyStr).toUtf8();
vpnKeyCompressed = qCompress(vpnKeyCompressed, 6);
vpnKeyCompressed = vpnKeyCompressed.mid(4);
QByteArray signedData = AMNEZIA_CONFIG_SIGNATURE + vpnKeyCompressed;
vpnKeyText = QString("vpn://%1").arg(QString(signedData.toBase64(QByteArray::Base64UrlEncoding)));
return vpnKeyText;
}

View File

@@ -13,17 +13,10 @@ namespace apiUtils
bool isSubscriptionExpired(const QString &subscriptionEndDate);
bool isPremiumServer(const QJsonObject &serverConfigObject);
apiDefs::ConfigType getConfigType(const QJsonObject &serverConfigObject);
apiDefs::ConfigSource getConfigSource(const QJsonObject &serverConfigObject);
amnezia::ErrorCode checkNetworkReplyErrors(const QList<QSslError> &sslErrors, const QString &replyErrorString,
const QNetworkReply::NetworkError &replyError, const int httpStatusCode,
const QByteArray &responseBody);
QString getPremiumV1VpnKey(const QJsonObject &serverConfigObject);
QString getPremiumV2VpnKey(const QJsonObject &serverConfigObject);
amnezia::ErrorCode checkNetworkReplyErrors(const QList<QSslError> &sslErrors, QNetworkReply *reply);
}
#endif // APIUTILS_H

View File

@@ -1,6 +1,5 @@
#include "coreController.h"
#include <QDirIterator>
#include <QTranslator>
#if defined(Q_OS_ANDROID)
@@ -26,8 +25,9 @@ CoreController::CoreController(const QSharedPointer<VpnConnection> &vpnConnectio
initNotificationHandler();
auto locale = m_settings->getAppLanguage();
m_translator.reset(new QTranslator());
updateTranslator(m_settings->getAppLanguage());
updateTranslator(locale);
}
void CoreController::initModels()
@@ -47,9 +47,6 @@ void CoreController::initModels()
m_sitesModel.reset(new SitesModel(m_settings, this));
m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get());
m_allowedDnsModel.reset(new AllowedDnsModel(m_settings, this));
m_engine->rootContext()->setContextProperty("AllowedDnsModel", m_allowedDnsModel.get());
m_appSplitTunnelingModel.reset(new AppSplitTunnelingModel(m_settings, this));
m_engine->rootContext()->setContextProperty("AppSplitTunnelingModel", m_appSplitTunnelingModel.get());
@@ -99,9 +96,6 @@ void CoreController::initModels()
m_apiDevicesModel.reset(new ApiDevicesModel(m_settings, this));
m_engine->rootContext()->setContextProperty("ApiDevicesModel", m_apiDevicesModel.get());
m_newsModel.reset(new NewsModel(m_settings, this));
m_engine->rootContext()->setContextProperty("NewsModel", m_newsModel.get());
}
void CoreController::initControllers()
@@ -122,9 +116,6 @@ void CoreController::initControllers()
connect(m_installController.get(), &InstallController::currentContainerUpdated, m_connectionController.get(),
&ConnectionController::onCurrentContainerUpdated); // TODO remove this
connect(m_installController.get(), &InstallController::profileCleared,
m_protocolsModel.get(), &ProtocolsModel::updateModel);
m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings));
m_engine->rootContext()->setContextProperty("ImportController", m_importController.get());
@@ -138,9 +129,6 @@ void CoreController::initControllers()
m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel));
m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get());
m_allowedDnsController.reset(new AllowedDnsController(m_settings, m_allowedDnsModel));
m_engine->rootContext()->setContextProperty("AllowedDnsController", m_allowedDnsController.get());
m_appSplitTunnelingController.reset(new AppSplitTunnelingController(m_settings, m_appSplitTunnelingModel));
m_engine->rootContext()->setContextProperty("AppSplitTunnelingController", m_appSplitTunnelingController.get());
@@ -153,9 +141,6 @@ void CoreController::initControllers()
m_apiConfigsController.reset(new ApiConfigsController(m_serversModel, m_apiServicesModel, m_settings));
m_engine->rootContext()->setContextProperty("ApiConfigsController", m_apiConfigsController.get());
m_apiNewsController.reset(new ApiNewsController(m_newsModel, m_settings, m_serversModel, this));
m_engine->rootContext()->setContextProperty("ApiNewsController", m_apiNewsController.get());
}
void CoreController::initAndroidController()
@@ -228,12 +213,11 @@ void CoreController::initSignalHandlers()
initAutoConnectHandler();
initAmneziaDnsToggledHandler();
initPrepareConfigHandler();
initStrictKillSwitchHandler();
}
void CoreController::initNotificationHandler()
{
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#ifndef Q_OS_ANDROID
m_notificationHandler.reset(NotificationHandler::create(nullptr));
connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(),
@@ -245,10 +229,7 @@ void CoreController::initNotificationHandler()
connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(),
&ConnectionController::closeConnection);
connect(this, &CoreController::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated);
auto* trayHandler = qobject_cast<SystemTrayNotificationHandler*>(m_notificationHandler.get());
connect(this, &CoreController::websiteUrlChanged, trayHandler, &SystemTrayNotificationHandler::updateWebsiteUrl);
#endif
#endif
}
void CoreController::updateTranslator(const QLocale &locale)
@@ -257,23 +238,7 @@ void CoreController::updateTranslator(const QLocale &locale)
QCoreApplication::removeTranslator(m_translator.get());
}
QStringList availableTranslations;
QDirIterator it(":/translations", QStringList("amneziavpn_*.qm"), QDir::Files);
while (it.hasNext()) {
availableTranslations << it.next();
}
// This code allow to load translation for the language only, without country code
const QString lang = locale.name().split("_").first();
const QString translationFilePrefix = QString(":/translations/amneziavpn_") + lang;
QString strFileName = QString(":/translations/amneziavpn_%1.qm").arg(locale.name());
for (const QString &translation : availableTranslations) {
if (translation.contains(translationFilePrefix)) {
strFileName = translation;
break;
}
}
QString strFileName = QString(":/translations/amneziavpn") + QLatin1String("_") + locale.name() + ".qm";
if (m_translator->load(strFileName)) {
if (QCoreApplication::installTranslator(m_translator.get())) {
m_settings->setAppLanguage(locale);
@@ -285,7 +250,6 @@ void CoreController::updateTranslator(const QLocale &locale)
m_engine->retranslate();
emit translationsUpdated();
emit websiteUrlChanged(m_languageModel->getCurrentSiteUrl());
}
void CoreController::initErrorMessagesHandler()
@@ -306,10 +270,13 @@ void CoreController::setQmlRoot()
void CoreController::initApiCountryModelUpdateHandler()
{
// TODO
connect(m_serversModel.get(), &ServersModel::updateApiCountryModel, this, [this]() {
m_apiCountryModel->updateModel(m_serversModel->getProcessedServerData("apiAvailableCountries").toJsonArray(),
m_serversModel->getProcessedServerData("apiServerCountryCode").toString());
});
connect(m_serversModel.get(), &ServersModel::updateApiServicesModel, this,
[this]() { m_apiServicesModel->updateModel(m_serversModel->getProcessedServerData("apiConfig").toJsonObject()); });
}
void CoreController::initContainerModelUpdateHandler()
@@ -317,11 +284,6 @@ void CoreController::initContainerModelUpdateHandler()
connect(m_serversModel.get(), &ServersModel::containersUpdated, m_containersModel.get(), &ContainersModel::updateModel);
connect(m_serversModel.get(), &ServersModel::defaultServerContainersUpdated, m_defaultServerContainersModel.get(),
&ContainersModel::updateModel);
connect(m_serversModel.get(), &ServersModel::gatewayStacksExpanded, this, [this]() {
if (m_serversModel->hasServersFromGatewayApi()) {
m_apiNewsController->fetchNews(false);
}
});
m_serversModel->resetModel();
}
@@ -377,32 +339,7 @@ void CoreController::initPrepareConfigHandler()
});
}
void CoreController::initStrictKillSwitchHandler()
{
connect(m_settingsController.get(), &SettingsController::strictKillSwitchEnabledChanged, m_vpnConnection.get(),
&VpnConnection::onKillSwitchModeChanged);
}
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();
}
}

View File

@@ -5,15 +5,9 @@
#include <QQmlContext>
#include <QThread>
#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/apiNewsController.h"
#include "ui/controllers/appSplitTunnelingController.h"
#include "ui/controllers/allowedDnsController.h"
#include "ui/controllers/connectionController.h"
#include "ui/controllers/exportController.h"
#include "ui/controllers/focusController.h"
@@ -24,7 +18,6 @@
#include "ui/controllers/sitesController.h"
#include "ui/controllers/systemController.h"
#include "ui/models/allowed_dns_model.h"
#include "ui/models/containers_model.h"
#include "ui/models/languageModel.h"
#include "ui/models/protocols/cloakConfigModel.h"
@@ -47,9 +40,8 @@
#include "ui/models/services/sftpConfigModel.h"
#include "ui/models/services/socks5ProxyConfigModel.h"
#include "ui/models/sites_model.h"
#include "ui/models/newsModel.h"
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#ifndef Q_OS_ANDROID
#include "ui/notificationhandler.h"
#endif
@@ -64,12 +56,8 @@ public:
QSharedPointer<PageController> pageController() const;
void setQmlRoot();
void openConnectionByIndex(int serverIndex);
void importConfigFromData(const QString &data);
signals:
void translationsUpdated();
void websiteUrlChanged(const QString &newUrl);
private:
void initModels();
@@ -92,14 +80,13 @@ private:
void initAutoConnectHandler();
void initAmneziaDnsToggledHandler();
void initPrepareConfigHandler();
void initStrictKillSwitchHandler();
QQmlApplicationEngine *m_engine {}; // TODO use parent child system here?
std::shared_ptr<Settings> m_settings;
QSharedPointer<VpnConnection> m_vpnConnection;
QSharedPointer<QTranslator> m_translator;
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#ifndef Q_OS_ANDROID
QScopedPointer<NotificationHandler> m_notificationHandler;
#endif
@@ -115,11 +102,9 @@ private:
QScopedPointer<SitesController> m_sitesController;
QScopedPointer<SystemController> m_systemController;
QScopedPointer<AppSplitTunnelingController> m_appSplitTunnelingController;
QScopedPointer<AllowedDnsController> m_allowedDnsController;
QScopedPointer<ApiSettingsController> m_apiSettingsController;
QScopedPointer<ApiConfigsController> m_apiConfigsController;
QScopedPointer<ApiNewsController> m_apiNewsController;
QSharedPointer<ContainersModel> m_containersModel;
QSharedPointer<ContainersModel> m_defaultServerContainersModel;
@@ -127,8 +112,6 @@ private:
QSharedPointer<LanguageModel> m_languageModel;
QSharedPointer<ProtocolsModel> m_protocolsModel;
QSharedPointer<SitesModel> m_sitesModel;
QSharedPointer<NewsModel> m_newsModel;
QSharedPointer<AllowedDnsModel> m_allowedDnsModel;
QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel;
QSharedPointer<ClientManagementModel> m_clientManagementModel;

View File

@@ -1,20 +1,18 @@
#include "gatewayController.h"
#include <QDebug>
#include <algorithm>
#include <random>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QMutexLocker>
#include <QSharedPointer>
#include <QThread>
#include <QtConcurrent>
#include <QNetworkReply>
#include "QBlockCipher.h"
#include "QRsa.h"
#include "amnezia_application.h"
#include "core/transport/dnsGatewayTransport.h"
#include "core/transport/httpGatewayTransport.h"
#include "core/api/apiUtils.h"
#include "utilities.h"
namespace
@@ -29,330 +27,290 @@ namespace
constexpr char keyPayload[] = "key_payload";
}
amnezia::transport::dns::DnsProtocol dnsProtocolFromPrimary(PrimaryTransport p)
{
switch (p) {
case PrimaryTransport::DnsUdp: return amnezia::transport::dns::DnsProtocol::Udp;
case PrimaryTransport::DnsTcp: return amnezia::transport::dns::DnsProtocol::Tcp;
case PrimaryTransport::DnsDot: return amnezia::transport::dns::DnsProtocol::Tls;
case PrimaryTransport::DnsDoh: return amnezia::transport::dns::DnsProtocol::Https;
case PrimaryTransport::DnsDoq: return amnezia::transport::dns::DnsProtocol::Quic;
default: return amnezia::transport::dns::DnsProtocol::Udp;
}
}
} // namespace
constexpr QLatin1String errorResponsePattern1("No active configuration found for");
constexpr QLatin1String errorResponsePattern2("No non-revoked public key found for");
constexpr QLatin1String errorResponsePattern3("Account not found.");
}
TransportsConfig TransportsConfig::fromJson(const QJsonObject &json)
GatewayController::GatewayController(const QString &gatewayEndpoint, bool isDevEnvironment, int requestTimeoutMsecs, QObject *parent)
: QObject(parent), m_gatewayEndpoint(gatewayEndpoint), m_isDevEnvironment(isDevEnvironment), m_requestTimeoutMsecs(requestTimeoutMsecs)
{
using amnezia::transport::dns::DnsProtocol;
}
TransportsConfig config;
ErrorCode GatewayController::get(const QString &endpoint, QByteArray &responseBody)
{
#ifdef Q_OS_IOS
IosController::Instance()->requestInetAccess();
QThread::msleep(10);
#endif
QString primaryStr = json.value("primary").toString("http").toLower();
if (primaryStr == "http") {
config.primary = PrimaryTransport::Http;
} else if (primaryStr == "dns_udp" || primaryStr == "udp") {
config.primary = PrimaryTransport::DnsUdp;
} else if (primaryStr == "dns_tcp" || primaryStr == "tcp") {
config.primary = PrimaryTransport::DnsTcp;
} else if (primaryStr == "dns_dot" || primaryStr == "dot") {
config.primary = PrimaryTransport::DnsDot;
} else if (primaryStr == "dns_doh" || primaryStr == "doh") {
config.primary = PrimaryTransport::DnsDoh;
} else if (primaryStr == "dns_doq" || primaryStr == "doq") {
config.primary = PrimaryTransport::DnsDoq;
}
QNetworkRequest request;
request.setTransferTimeout(m_requestTimeoutMsecs);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
config.retryCount = json.value("retry_count").toInt(3);
config.timeoutMs = json.value("timeout_ms").toInt(10000);
request.setUrl(QString(endpoint).arg(m_gatewayEndpoint));
if (json.contains("http")) {
QJsonObject httpObj = json["http"].toObject();
config.httpEnabled = httpObj.value("enabled").toBool(true);
config.httpEndpoint = httpObj.value("endpoint").toString();
}
QNetworkReply *reply;
reply = amnApp->networkManager()->get(request);
if (json.contains("dns_transports")) {
QJsonArray transportsArray = json["dns_transports"].toArray();
for (const auto &transportVal : transportsArray) {
QJsonObject transportObj = transportVal.toObject();
DnsTransportEntry entry;
QEventLoop wait;
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
entry.server = transportObj.value("server").toString();
entry.domain = transportObj.value("domain").toString();
entry.port = static_cast<quint16>(transportObj.value("port").toInt(15353));
entry.dohPath = transportObj.value("path").toString("/dns-query");
QList<QSslError> sslErrors;
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
wait.exec();
QString typeStr = transportObj.value("type").toString().toLower();
if (typeStr == "udp") {
entry.type = DnsProtocol::Udp;
} else if (typeStr == "tcp") {
entry.type = DnsProtocol::Tcp;
} else if (typeStr == "dot" || typeStr == "tls") {
entry.type = DnsProtocol::Tls;
if (!transportObj.contains("port")) entry.port = 8853;
} else if (typeStr == "doh" || typeStr == "https") {
entry.type = DnsProtocol::Https;
if (!transportObj.contains("port")) entry.port = 443;
} else if (typeStr == "doq" || typeStr == "quic") {
entry.type = DnsProtocol::Quic;
if (!transportObj.contains("port")) entry.port = 8853;
} else {
continue;
responseBody = reply->readAll();
if (sslErrors.isEmpty() && shouldBypassProxy(reply, responseBody, false)) {
auto requestFunction = [&request, &responseBody](const QString &url) {
request.setUrl(url);
return amnApp->networkManager()->get(request);
};
auto replyProcessingFunction = [&responseBody, &reply, &sslErrors, this](QNetworkReply *nestedReply,
const QList<QSslError> &nestedSslErrors) {
responseBody = nestedReply->readAll();
if (!sslErrors.isEmpty() || !shouldBypassProxy(nestedReply, responseBody, false)) {
sslErrors = nestedSslErrors;
reply = nestedReply;
return true;
}
return false;
};
if (entry.isValid()) {
config.dnsTransports.append(entry);
}
}
bypassProxy(endpoint, reply, requestFunction, replyProcessingFunction);
}
return config;
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, reply);
reply->deleteLater();
return errorCode;
}
GatewayController::GatewayController(const QString &gatewayEndpoint,
const bool isDevEnvironment,
const int requestTimeoutMsecs,
const bool isStrictKillSwitchEnabled,
QObject *parent)
: QObject(parent),
m_requestTimeoutMsecs(requestTimeoutMsecs),
m_gatewayEndpoint(gatewayEndpoint),
m_isDevEnvironment(isDevEnvironment),
m_isStrictKillSwitchEnabled(isStrictKillSwitchEnabled)
ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody)
{
auto httpTransport = std::make_shared<amnezia::transport::HttpGatewayTransport>(
m_gatewayEndpoint, m_isDevEnvironment, m_requestTimeoutMsecs, m_isStrictKillSwitchEnabled);
{
QMutexLocker lock(&m_transportMutex);
m_transport = std::move(httpTransport);
}
}
#ifdef Q_OS_IOS
IosController::Instance()->requestInetAccess();
QThread::msleep(10);
#endif
std::shared_ptr<amnezia::transport::IGatewayTransport> GatewayController::buildTransport(
const TransportsConfig &config, int requestTimeoutMsecs, bool isDevEnvironment, bool isStrictKillSwitchEnabled)
{
using namespace amnezia::transport;
QNetworkRequest request;
request.setTransferTimeout(m_requestTimeoutMsecs);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
auto makeHttp = [&](const QString &httpEndpoint) {
return std::make_shared<HttpGatewayTransport>(
httpEndpoint, isDevEnvironment, requestTimeoutMsecs, isStrictKillSwitchEnabled);
};
if (config.primary == PrimaryTransport::Http) {
return makeHttp(config.httpEndpoint);
}
const auto wantedProtocol = dnsProtocolFromPrimary(config.primary);
for (const auto &entry : config.dnsTransports) {
if (entry.type == wantedProtocol && entry.isValid()) {
return std::make_shared<DnsGatewayTransport>(
entry.type, entry.server, entry.domain, entry.port,
requestTimeoutMsecs, isStrictKillSwitchEnabled, entry.dohPath);
}
}
return makeHttp(config.httpEndpoint);
}
void GatewayController::setTransportsConfig(const TransportsConfig &config)
{
if (config.timeoutMs > 0) {
m_requestTimeoutMsecs = config.timeoutMs;
}
if (!config.httpEndpoint.isEmpty()) {
m_gatewayEndpoint = config.httpEndpoint;
}
TransportsConfig effective = config;
if (effective.httpEndpoint.isEmpty()) {
effective.httpEndpoint = m_gatewayEndpoint;
}
auto newTransport = buildTransport(effective, m_requestTimeoutMsecs, m_isDevEnvironment, m_isStrictKillSwitchEnabled);
QString activeName;
{
QMutexLocker lock(&m_transportMutex);
m_transport = std::move(newTransport);
activeName = m_transport ? m_transport->name() : QStringLiteral("none");
}
qDebug() << "[Transport] Active transport set to" << activeName;
}
TransportsConfig GatewayController::buildTransportsConfig()
{
using amnezia::transport::dns::DnsProtocol;
TransportsConfig config;
QString server = QString(AGW_DNS_SERVER).trimmed();
QString domain = QString(AGW_DNS_DOMAIN).trimmed();
if (server.isEmpty() || domain.isEmpty()) {
qDebug() << "[Transport] DNS server/domain not configured, HTTP only";
return config;
}
QString primaryStr = QString(AGW_DNS_PRIMARY).trimmed().toLower();
if (primaryStr == "udp" || primaryStr == "dns_udp") {
config.primary = PrimaryTransport::DnsUdp;
} else if (primaryStr == "tcp" || primaryStr == "dns_tcp") {
config.primary = PrimaryTransport::DnsTcp;
} else if (primaryStr == "dot" || primaryStr == "dns_dot") {
config.primary = PrimaryTransport::DnsDot;
} else if (primaryStr == "doh" || primaryStr == "dns_doh") {
config.primary = PrimaryTransport::DnsDoh;
} else if (primaryStr == "doq" || primaryStr == "dns_doq") {
config.primary = PrimaryTransport::DnsDoq;
} else {
config.primary = PrimaryTransport::Http;
}
int retryCount = QString(AGW_DNS_RETRY_COUNT).trimmed().toInt();
config.retryCount = (retryCount > 0) ? retryCount : 3;
int timeoutMs = QString(AGW_DNS_TIMEOUT_MS).trimmed().toInt();
config.timeoutMs = (timeoutMs > 0) ? timeoutMs : 10000;
config.httpEnabled = true;
auto addTransport = [&](DnsProtocol type, const char *portDefine, quint16 defaultPort,
const QString &dohPath = QString()) {
DnsTransportEntry entry;
entry.type = type;
entry.server = server;
entry.domain = domain;
quint16 port = QString(portDefine).trimmed().toUShort();
entry.port = (port > 0) ? port : defaultPort;
if (!dohPath.isEmpty()) entry.dohPath = dohPath;
config.dnsTransports.append(entry);
};
addTransport(DnsProtocol::Udp, AGW_DNS_PORT_UDP, 5353);
addTransport(DnsProtocol::Tcp, AGW_DNS_PORT_UDP, 5353);
addTransport(DnsProtocol::Tls, AGW_DNS_PORT_DOT, 853);
QString dohPath = QString(AGW_DNS_DOH_PATH).trimmed();
if (dohPath.isEmpty()) dohPath = "/dns-query";
addTransport(DnsProtocol::Https, AGW_DNS_PORT_DOH, 443, dohPath);
addTransport(DnsProtocol::Quic, AGW_DNS_PORT_DOQ, 8853);
qDebug() << "[Transport] Built config from env: server=" << server << "domain=" << domain
<< "transports=" << config.dnsTransports.size() << "primary=" << static_cast<int>(config.primary);
return config;
}
GatewayController::EncryptedRequest GatewayController::encryptRequest(const QJsonObject &apiPayload)
{
EncryptedRequest result;
result.errorCode = amnezia::ErrorCode::NoError;
request.setUrl(endpoint.arg(m_gatewayEndpoint));
QSimpleCrypto::QBlockCipher blockCipher;
result.key = blockCipher.generatePrivateSalt(32);
result.iv = blockCipher.generatePrivateSalt(16);
result.salt = blockCipher.generatePrivateSalt(8);
QByteArray key = blockCipher.generatePrivateSalt(32);
QByteArray iv = blockCipher.generatePrivateSalt(32);
QByteArray salt = blockCipher.generatePrivateSalt(8);
QJsonObject keyPayload;
keyPayload[configKey::aesKey] = QString(result.key.toBase64());
keyPayload[configKey::aesIv] = QString(result.iv.toBase64());
keyPayload[configKey::aesSalt] = QString(result.salt.toBase64());
keyPayload[configKey::aesKey] = QString(key.toBase64());
keyPayload[configKey::aesIv] = QString(iv.toBase64());
keyPayload[configKey::aesSalt] = QString(salt.toBase64());
QByteArray encryptedKeyPayload;
QByteArray encryptedApiPayload;
try {
QSimpleCrypto::QRsa rsa;
EVP_PKEY *publicKey = nullptr;
try {
QByteArray rsaKey = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
rsaKey = rsaKey.trimmed();
rsaKey.replace("\\n", "\n");
QSimpleCrypto::QRsa rsa;
publicKey = rsa.getPublicKeyFromByteArray(rsaKey);
} catch (...) {
Utils::logException();
qCritical() << "error loading public key from environment variables";
result.errorCode = amnezia::ErrorCode::ApiMissingAgwPublicKey;
return result;
return ErrorCode::ApiMissingAgwPublicKey;
}
encryptedKeyPayload = rsa.encrypt(QJsonDocument(keyPayload).toJson(QJsonDocument::Compact),
publicKey, RSA_PKCS1_PADDING);
encryptedKeyPayload = rsa.encrypt(QJsonDocument(keyPayload).toJson(), publicKey, RSA_PKCS1_PADDING);
EVP_PKEY_free(publicKey);
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(QJsonDocument::Compact),
result.key, result.iv, "", result.salt);
} catch (...) {
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), key, iv, "", salt);
} catch (...) { // todo change error handling in QSimpleCrypto?
Utils::logException();
qCritical() << "error when encrypting the request body";
result.errorCode = amnezia::ErrorCode::ApiConfigDecryptionError;
return result;
return ErrorCode::ApiConfigDecryptionError;
}
QJsonObject requestBody;
requestBody[configKey::keyPayload] = QString(encryptedKeyPayload.toBase64());
requestBody[configKey::apiPayload] = QString(encryptedApiPayload.toBase64());
result.body = QJsonDocument(requestBody).toJson(QJsonDocument::Compact);
return result;
}
QNetworkReply *reply = amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson());
amnezia::transport::DecryptionResult GatewayController::decryptResponse(const QByteArray &encryptedResponseBody,
const QByteArray &key,
const QByteArray &iv,
const QByteArray &salt) const
{
amnezia::transport::DecryptionResult result;
result.decrypted = encryptedResponseBody;
result.isOk = false;
QEventLoop wait;
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
if (encryptedResponseBody.isEmpty()) {
return result;
QList<QSslError> sslErrors;
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
wait.exec();
QByteArray encryptedResponseBody = reply->readAll();
if (sslErrors.isEmpty() && shouldBypassProxy(reply, encryptedResponseBody, true, key, iv, salt)) {
auto requestFunction = [&request, &encryptedResponseBody, &requestBody](const QString &url) {
request.setUrl(url);
return amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson());
};
auto replyProcessingFunction = [&encryptedResponseBody, &reply, &sslErrors, &key, &iv, &salt,
this](QNetworkReply *nestedReply, const QList<QSslError> &nestedSslErrors) {
encryptedResponseBody = nestedReply->readAll();
reply = nestedReply;
if (!sslErrors.isEmpty() || shouldBypassProxy(nestedReply, encryptedResponseBody, true, key, iv, salt)) {
sslErrors = nestedSslErrors;
return false;
}
return true;
};
bypassProxy(endpoint, reply, requestFunction, replyProcessingFunction);
}
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, reply);
reply->deleteLater();
if (errorCode) {
return errorCode;
}
try {
QSimpleCrypto::QBlockCipher blockCipher;
result.decrypted = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt);
result.isOk = true;
} catch (...) {
result.decrypted = encryptedResponseBody;
result.isOk = false;
responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt);
return ErrorCode::NoError;
} catch (...) { // todo change error handling in QSimpleCrypto?
Utils::logException();
qCritical() << "error when decrypting the request body";
return ErrorCode::ApiConfigDecryptionError;
}
}
QStringList GatewayController::getProxyUrls()
{
QNetworkRequest request;
request.setTransferTimeout(m_requestTimeoutMsecs);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QEventLoop wait;
QList<QSslError> sslErrors;
QNetworkReply *reply;
QStringList proxyStorageUrls;
if (m_isDevEnvironment) {
proxyStorageUrls = QString(DEV_S3_ENDPOINT).split(", ");
} else {
proxyStorageUrls = QString(PROD_S3_ENDPOINT).split(", ");
}
return result;
}
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
std::shared_ptr<amnezia::transport::IGatewayTransport> GatewayController::currentTransport() const
{
QMutexLocker lock(&m_transportMutex);
return m_transport;
}
for (const auto &proxyStorageUrl : proxyStorageUrls) {
request.setUrl(proxyStorageUrl);
reply = amnApp->networkManager()->get(request);
amnezia::ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody)
{
EncryptedRequest enc = encryptRequest(apiPayload);
if (enc.errorCode != amnezia::ErrorCode::NoError) {
return enc.errorCode;
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
wait.exec();
if (reply->error() == QNetworkReply::NetworkError::NoError) {
auto encryptedResponseBody = reply->readAll();
reply->deleteLater();
EVP_PKEY *privateKey = nullptr;
QByteArray responseBody;
try {
if (!m_isDevEnvironment) {
QCryptographicHash hash(QCryptographicHash::Sha512);
hash.addData(key);
QByteArray hashResult = hash.result().toHex();
QByteArray key = QByteArray::fromHex(hashResult.left(64));
QByteArray iv = QByteArray::fromHex(hashResult.mid(64, 32));
QByteArray ba = QByteArray::fromBase64(encryptedResponseBody);
QSimpleCrypto::QBlockCipher blockCipher;
responseBody = blockCipher.decryptAesBlockCipher(ba, key, iv);
} else {
responseBody = encryptedResponseBody;
}
} catch (...) {
Utils::logException();
qCritical() << "error loading private key from environment variables or decrypting payload" << encryptedResponseBody;
continue;
}
auto endpointsArray = QJsonDocument::fromJson(responseBody).array();
QStringList endpoints;
for (const auto &endpoint : endpointsArray) {
endpoints.push_back(endpoint.toString());
}
return endpoints;
} else {
reply->deleteLater();
}
}
auto transport = currentTransport();
if (!transport) {
return amnezia::ErrorCode::AmneziaServiceConnectionFailed;
}
auto decryptionHook = [this, key = enc.key, iv = enc.iv, salt = enc.salt](const QByteArray &encrypted) {
return decryptResponse(encrypted, key, iv, salt);
};
return transport->send(endpoint, enc.body, responseBody, decryptionHook);
return {};
}
QFuture<QPair<amnezia::ErrorCode, QByteArray>> GatewayController::postAsync(const QString &endpoint, const QJsonObject apiPayload)
bool GatewayController::shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key,
const QByteArray &iv, const QByteArray &salt)
{
return QtConcurrent::run([this, endpoint, apiPayload]() {
QByteArray responseBody;
amnezia::ErrorCode errorCode = post(endpoint, apiPayload, responseBody);
return qMakePair(errorCode, responseBody);
});
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError || reply->error() == QNetworkReply::NetworkError::TimeoutError) {
qDebug() << "Timeout occurred";
return true;
} else if (responseBody.contains("html")) {
qDebug() << "The response contains an html tag";
return true;
} else if (reply->error() == QNetworkReply::NetworkError::ContentNotFoundError) {
if (responseBody.contains(errorResponsePattern1) || responseBody.contains(errorResponsePattern2)
|| responseBody.contains(errorResponsePattern3)) {
return false;
} else {
return true;
}
} else if (reply->error() != QNetworkReply::NetworkError::NoError) {
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;
}
void GatewayController::bypassProxy(const QString &endpoint, QNetworkReply *reply,
std::function<QNetworkReply *(const QString &url)> requestFunction,
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction)
{
QStringList proxyUrls = getProxyUrls();
std::random_device randomDevice;
std::mt19937 generator(randomDevice());
std::shuffle(proxyUrls.begin(), proxyUrls.end(), generator);
QEventLoop wait;
QList<QSslError> sslErrors;
QByteArray responseBody;
for (const QString &proxyUrl : proxyUrls) {
qDebug() << "Go to the next endpoint";
reply->deleteLater(); // delete the previous reply
reply = requestFunction(endpoint.arg(proxyUrl));
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
wait.exec();
if (replyProcessingFunction(reply, sslErrors)) {
break;
}
}
}

View File

@@ -1,88 +1,35 @@
#ifndef GATEWAYCONTROLLER_H
#define GATEWAYCONTROLLER_H
#include <QFuture>
#include <QJsonArray>
#include <QJsonObject>
#include <QMutex>
#include <QNetworkReply>
#include <QObject>
#include <QPair>
#include <memory>
#include "core/defs.h"
#include "core/transport/dns/dnsResolver.h"
#include "core/transport/igatewaytransport.h"
struct DnsTransportEntry
{
amnezia::transport::dns::DnsProtocol type = amnezia::transport::dns::DnsProtocol::Udp;
QString server;
QString domain;
quint16 port = 15353;
QString dohPath = "/dns-query";
bool isValid() const { return !server.isEmpty() && !domain.isEmpty(); }
};
enum class PrimaryTransport { Http, DnsUdp, DnsTcp, DnsDot, DnsDoh, DnsDoq };
struct TransportsConfig
{
PrimaryTransport primary = PrimaryTransport::Http;
bool httpEnabled = true;
QString httpEndpoint;
QList<DnsTransportEntry> dnsTransports;
int retryCount = 3;
int timeoutMs = 10000;
bool isValid() const { return httpEnabled || !dnsTransports.isEmpty(); }
static TransportsConfig fromJson(const QJsonObject &json);
};
#ifdef Q_OS_IOS
#include "platforms/ios/ios_controller.h"
#endif
class GatewayController : public QObject
{
Q_OBJECT
public:
explicit GatewayController(const QString &gatewayEndpoint,
const bool isDevEnvironment,
const int requestTimeoutMsecs,
const bool isStrictKillSwitchEnabled,
QObject *parent = nullptr);
explicit GatewayController(const QString &gatewayEndpoint, bool isDevEnvironment, int requestTimeoutMsecs, QObject *parent = nullptr);
amnezia::ErrorCode get(const QString &endpoint, QByteArray &responseBody);
amnezia::ErrorCode post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody);
QFuture<QPair<amnezia::ErrorCode, QByteArray>> postAsync(const QString &endpoint, const QJsonObject apiPayload);
static TransportsConfig buildTransportsConfig();
void setTransportsConfig(const TransportsConfig &config);
private:
struct EncryptedRequest
{
QByteArray body;
QByteArray key;
QByteArray iv;
QByteArray salt;
amnezia::ErrorCode errorCode = amnezia::ErrorCode::NoError;
};
EncryptedRequest encryptRequest(const QJsonObject &apiPayload);
amnezia::transport::DecryptionResult decryptResponse(const QByteArray &encryptedResponseBody,
const QByteArray &key,
const QByteArray &iv,
const QByteArray &salt) const;
std::shared_ptr<amnezia::transport::IGatewayTransport> currentTransport() const;
static std::shared_ptr<amnezia::transport::IGatewayTransport> buildTransport(
const TransportsConfig &config, int requestTimeoutMsecs, bool isDevEnvironment, bool isStrictKillSwitchEnabled);
QStringList getProxyUrls();
bool shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key = "",
const QByteArray &iv = "", const QByteArray &salt = "");
void bypassProxy(const QString &endpoint, QNetworkReply *reply, std::function<QNetworkReply *(const QString &url)> requestFunction,
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction);
int m_requestTimeoutMsecs;
QString m_gatewayEndpoint;
bool m_isDevEnvironment = false;
bool m_isStrictKillSwitchEnabled = false;
mutable QMutex m_transportMutex;
std::shared_ptr<amnezia::transport::IGatewayTransport> m_transport;
};
#endif // GATEWAYCONTROLLER_H

View File

@@ -138,7 +138,7 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
if (overwriteMode == libssh::ScpOverwriteMode::ScpOverwriteExisting) {
e = runScript(credentials,
replaceVars(QStringLiteral("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName, path),
replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(path),
genVarsForScript(credentials, container)),
cbReadStd, cbReadStd);
@@ -146,7 +146,7 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
return e;
} else if (overwriteMode == libssh::ScpOverwriteMode::ScpAppendToExisting) {
e = runScript(credentials,
replaceVars(QStringLiteral("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName, tmpFileName),
replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(tmpFileName),
genVarsForScript(credentials, container)),
cbReadStd, cbReadStd);
@@ -154,7 +154,7 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
return e;
e = runScript(credentials,
replaceVars(QStringLiteral("sudo docker exec -i $CONTAINER_NAME sh -c \"cat %1 >> %2\"").arg(tmpFileName, path),
replaceVars(QString("sudo docker exec -i $CONTAINER_NAME sh -c \"cat %1 >> %2\"").arg(tmpFileName).arg(path),
genVarsForScript(credentials, container)),
cbReadStd, cbReadStd);
@@ -177,7 +177,7 @@ QByteArray ServerController::getTextFileFromContainer(DockerContainer container,
errorCode = ErrorCode::NoError;
QString script = QStringLiteral("sudo docker exec -i %1 sh -c \"xxd -p '%2'\"").arg(ContainerProps::containerToString(container), path);
QString script = QString("sudo docker exec -i %1 sh -c \"xxd -p \'%2\'\"").arg(ContainerProps::containerToString(container)).arg(path);
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
@@ -345,11 +345,11 @@ 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)
!= newProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort))
!= newProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort))
|| (oldProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount)
!= newProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount))
|| (oldProtoConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize)
@@ -366,13 +366,8 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
!= newProtoConfig.value(config_key::responsePacketMagicHeader).toString(protocols::awg::defaultResponsePacketMagicHeader))
|| (oldProtoConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader)
!= newProtoConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader))
|| (oldProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader))
!= newProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader)
|| (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)))
|| (oldProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader)
!= newProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader)))
return true;
}
@@ -380,7 +375,7 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
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::wireguard::defaultPort)
!= newProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort)))
!= newProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort)))
return true;
}
@@ -388,13 +383,6 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
return true;
}
if (container == DockerContainer::Xray) {
if (oldProtoConfig.value(config_key::port).toString(protocols::xray::defaultPort)
!= newProtoConfig.value(config_key::port).toString(protocols::xray::defaultPort)) {
return true;
}
}
return false;
}
@@ -451,24 +439,15 @@ ErrorCode ServerController::buildContainerWorker(const ServerCredentials &creden
stdOut += data + "\n";
return ErrorCode::NoError;
};
auto cbReadStdErr = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};
ErrorCode error =
errorCode =
runScript(credentials,
replaceVars(amnezia::scriptData(SharedScriptType::build_container), genVarsForScript(credentials, container, config)),
cbReadStdOut, cbReadStdErr);
cbReadStdOut);
if (errorCode)
return errorCode;
if (stdOut.contains("doesn't work on cgroups v2"))
return ErrorCode::ServerDockerOnCgroupsV2;
if (stdOut.contains("cgroup mountpoint does not exist"))
return ErrorCode::ServerCgroupMountpoint;
if (stdOut.contains("have reached") && stdOut.contains("pull rate limit"))
return ErrorCode::DockerPullRateLimit;
return error;
return errorCode;
}
ErrorCode ServerController::runContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config)
@@ -646,14 +625,6 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
vars.append({ { "$UNDERLOAD_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::underloadPacketMagicHeader).toString() } });
vars.append({ { "$TRANSPORT_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::transportPacketMagicHeader).toString() } });
vars.append({ { "$COOKIE_REPLY_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::cookieReplyPacketJunkSize).toString() } });
vars.append({ { "$TRANSPORT_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::transportPacketJunkSize).toString() } });
vars.append({ { "$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) } });
auto username = socks5ProxyConfig.value(config_key::userName).toString();
@@ -662,8 +633,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()) {
@@ -739,7 +709,7 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential
QString transportProto = containerConfig.value(config_key::transport_proto).toString(defaultTransportProto);
// TODO reimplement with netstat
QString script = QString("which lsof > /dev/null 2>&1 || true && sudo lsof -i -P -n 2>/dev/null | grep -E ':%1 ").arg(port);
QString script = QString("which lsof &>/dev/null || true && sudo lsof -i -P -n 2>/dev/null | grep -E ':%1 ").arg(port);
for (auto &port : fixedPorts) {
script = script.append("|:%1").arg(port);
}
@@ -841,7 +811,7 @@ ErrorCode ServerController::isServerDpkgBusy(const ServerCredentials &credential
if (stdOut.contains("Packet manager not found"))
return ErrorCode::ServerPacketManagerError;
if (stdOut.contains("fuser not installed") || stdOut.contains("cat not installed"))
if (stdOut.contains("fuser not installed"))
return ErrorCode::NoError;
if (stdOut.isEmpty()) {

View File

@@ -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;
}
}

View File

@@ -58,9 +58,6 @@ namespace amnezia
ServerUserDirectoryNotAccessible = 208,
ServerUserNotAllowedInSudoers = 209,
ServerUserPasswordRequired = 210,
ServerDockerOnCgroupsV2 = 211,
ServerCgroupMountpoint = 212,
DockerPullRateLimit = 213,
// Ssh connection errors
SshRequestDeniedError = 300,
@@ -118,10 +115,6 @@ namespace amnezia
ApiServicesMissingError = 1107,
ApiConfigLimitError = 1108,
ApiNotFoundError = 1109,
ApiMigrationError = 1110,
ApiUpdateRequestError = 1111,
ApiSubscriptionExpiredError = 1112,
ApiPurchaseError = 1113,
// QFile errors
OpenError = 1200,

View File

@@ -26,9 +26,6 @@ QString errorString(ErrorCode code) {
case(ErrorCode::ServerUserDirectoryNotAccessible): errorMessage = QObject::tr("The server user's home directory is not accessible"); break;
case(ErrorCode::ServerUserNotAllowedInSudoers): errorMessage = QObject::tr("Action not allowed in sudoers"); break;
case(ErrorCode::ServerUserPasswordRequired): errorMessage = QObject::tr("The user's password is required"); break;
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;
// Libssh errors
case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break;
@@ -75,10 +72,6 @@ QString errorString(ErrorCode code) {
case (ErrorCode::ApiServicesMissingError): errorMessage = QObject::tr("Missing list of available services"); break;
case (ErrorCode::ApiConfigLimitError): errorMessage = QObject::tr("The limit of allowed configurations per subscription has been exceeded"); break;
case (ErrorCode::ApiNotFoundError): errorMessage = QObject::tr("Error when retrieving configuration from API"); break;
case (ErrorCode::ApiMigrationError): errorMessage = QObject::tr("A migration error has occurred. Please contact our technical support"); break;
case (ErrorCode::ApiUpdateRequestError): errorMessage = QObject::tr("Please update the application to use this feature"); break;
case (ErrorCode::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;

View File

@@ -1,71 +1,108 @@
#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>());
m_tun2socks.reset(m_node.acquire<IpcProcessTun2SocksReplica>());
}
IpcClient& IpcClient::Instance()
IpcClient::~IpcClient()
{
thread_local IpcClient ipcClient;
return ipcClient;
if (m_localSocket)
m_localSocket->close();
}
bool IpcClient::isSocketConnected() const
{
return m_isSocketConnected;
}
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<IpcProcessTun2SocksReplica> IpcClient::InterfaceTun2Socks()
{
QSharedPointer<IpcProcessTun2SocksReplica> rep = Instance().m_tun2socks;
if (rep.isNull()) {
qCritical() << "IpcClient::InterfaceTun2Socks: Replica is undefined";
if (!Instance())
return nullptr;
return Instance()->m_Tun2SocksClient;
}
bool IpcClient::init(IpcClient *instance)
{
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;
}
if (!rep->waitForSource(1000)) {
qCritical() << "IpcClient::InterfaceTun2Socks: Failed to initialize replica";
return nullptr;
}
if (!rep->isReplicaValid()) {
qWarning() << "IpcClient::InterfaceTun2Socks(): Replica is invalid";
}
return rep;
qDebug() << "IpcClient::init succeed";
return (Instance()->m_ipcClient->isReplicaValid() && Instance()->m_Tun2SocksClient->isReplicaValid());
}
QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
{
QSharedPointer<IpcInterfaceReplica> rep = Interface();
if (!rep) {
qCritical() << "IpcClient::createPrivilegedProcess: Replica is invalid";
if (!Instance()->m_ipcClient || !Instance()->m_ipcClient->isReplicaValid()) {
qWarning() << "IpcClient::createPrivilegedProcess : IpcClient IpcClient replica is not valid";
return nullptr;
}
QRemoteObjectPendingReply<int> pidReply = rep->createPrivilegedProcess();
if (!pidReply.waitForFinished(5000)){
qCritical() << "IpcClient::createPrivilegedProcess: Failed to execute RO createPrivilegedProcess call";
return nullptr;
}
QRemoteObjectPendingReply<int> futureResult = Instance()->m_ipcClient->createPrivilegedProcess();
futureResult.waitForFinished(5000);
int pid = pidReply.returnValue();
QSharedPointer<ProcessDescriptor> pd(new ProcessDescriptor());
int pid = futureResult.returnValue();
auto pd = QSharedPointer<ProcessDescriptor>(new ProcessDescriptor());
Instance()->m_processNodes.insert(pid, pd);
pd->localSocket.reset(new QLocalSocket(pd->replicaNode.data()));
@@ -73,7 +110,6 @@ QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
pd->replicaNode->addClientSideConnection(pd->localSocket.data());
IpcProcessInterfaceReplica *repl = pd->replicaNode->acquire<IpcProcessInterfaceReplica>();
// TODO: rework the unsafe cast below
PrivilegedProcess *priv = static_cast<PrivilegedProcess *>(repl);
pd->ipcProcess.reset(priv);
if (!pd->ipcProcess) {
@@ -88,12 +124,8 @@ QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
[pd]() { pd->replicaNode->deleteLater(); });
}
});
pd->localSocket->connectToServer(amnezia::getIpcProcessUrl(pid));
if (!pd->localSocket->waitForConnected()) {
qCritical() << "IpcClient::createPrivilegedProcess: Failed to connect to process' socket";
return nullptr;
}
pd->localSocket->waitForConnected();
auto processReplica = QSharedPointer<PrivilegedProcess>(pd->ipcProcess);
return processReplica;

View File

@@ -4,6 +4,7 @@
#include <QLocalSocket>
#include <QObject>
#include "ipc.h"
#include "rep_ipc_interface_replica.h"
#include "rep_ipc_process_tun2socks_replica.h"
@@ -13,48 +14,27 @@ 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<IpcProcessTun2SocksReplica> InterfaceTun2Socks();
static QSharedPointer<PrivilegedProcess> CreatePrivilegedProcess();
bool isSocketConnected() const;
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;
QSharedPointer<IpcProcessTun2SocksReplica> m_tun2socks;
~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 () {
@@ -66,6 +46,11 @@ private:
QSharedPointer<QRemoteObjectNode> replicaNode;
QSharedPointer<QLocalSocket> localSocket;
};
QMap<int, QSharedPointer<ProcessDescriptor>> m_processNodes;
bool m_isSocketConnected {false};
static IpcClient *m_instance;
};
#endif // IPCCLIENT_H

View File

@@ -1,19 +1,17 @@
#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>
#include <winsock.h>
#include <QNetworkInterface>
#include "qendian.h"
#include <QSettings>
#endif
#ifdef Q_OS_LINUX
#include <arpa/inet.h>
@@ -24,27 +22,17 @@
#include <sys/socket.h>
#include <unistd.h>
#endif
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS)
#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>
#include <QHostInfo>
#include <QDebug>
QRegularExpression NetworkUtilities::ipAddressRegExp()
{
@@ -181,7 +169,7 @@ int NetworkUtilities::AdapterIndexTo(const QHostAddress& dst) {
#ifdef Q_OS_WIN
qDebug() << "Getting Current Internet Adapter that routes to"
<< dst.toString();
quint32 ipBigEndian;
quint32_be ipBigEndian;
quint32 ip = dst.toIPv4Address();
qToBigEndian(ip, &ipBigEndian);
_MIB_IPFORWARDROW routeInfo;
@@ -197,17 +185,6 @@ int NetworkUtilities::AdapterIndexTo(const QHostAddress& dst) {
return 0;
}
bool NetworkUtilities::checkIpv6Enabled() {
#ifdef Q_OS_WIN
QSettings RegHLM("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Tcpip6\\Parameters",
QSettings::NativeFormat);
int ret = RegHLM.value("DisabledComponents", 0).toInt();
qDebug() << "Check for Windows disabled IPv6 return " << ret;
return (ret != 255);
#endif
return true;
}
#ifdef Q_OS_WIN
DWORD GetAdaptersAddressesWrapper(const ULONG Family,
const ULONG Flags,
@@ -250,14 +227,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 =
@@ -265,7 +240,7 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
if (dwRetVal != NO_ERROR) {
qDebug() << "ipv4 stack detect GetAdaptersAddresses failed.";
return {};
return "";
}
PIP_ADAPTER_ADDRESSES pCurAddress = pAdapterAddresses;
@@ -280,9 +255,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;
}
}
}
@@ -290,7 +263,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;
@@ -307,7 +280,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));
@@ -331,7 +304,7 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
/* send msg */
if (send(sock, nlmsg, nlmsg->nlmsg_len, 0) < 0) {
perror("send failed");
return {};
return "";
}
/* receive response */
@@ -340,7 +313,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;
@@ -350,7 +323,7 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
(nlmsg->nlmsg_type == NLMSG_ERROR))
{
perror("Error in received packet");
return {};
return "";
}
/* If we received all data break */
@@ -403,12 +376,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(MACOS_NE)
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS)
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};
@@ -418,17 +389,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;
@@ -466,10 +437,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;
}
}
@@ -483,10 +451,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;
}
}
@@ -495,6 +460,6 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
free(buf);
}
return { gateway, QNetworkInterface::interfaceFromIndex(index) };
return gateway;
#endif
}

View File

@@ -6,7 +6,7 @@
#include <QString>
#include <QHostAddress>
#include <QNetworkReply>
#include <QtNetwork/qnetworkinterface.h>
class NetworkUtilities : public QObject
{
@@ -16,8 +16,7 @@ public:
static QString getStringBetween(const QString &s, const QString &a, const QString &b);
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);
@@ -30,6 +29,7 @@ public:
static QString netMaskFromIpWithSubnet(const QString ip);
static QString ipAddressFromIpWithSubnet(const QString ip);
static QStringList summarizeRoutes(const QStringList &ips, const QString cidr);
};

View File

@@ -1,194 +0,0 @@
#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>
#include <sys/signalfd.h>
#include <unistd.h>
#elif defined(Q_OS_MACOS)
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#endif
#ifdef Q_OS_WIN
#include <QAbstractNativeEventFilter>
#include <windows.h>
#endif
namespace
{
static bool initialized = false;
#ifdef Q_OS_WIN
class WindowsCloseFilter : public QAbstractNativeEventFilter
{
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);
}
}
}
}
return false;
};
};
static WindowsCloseFilter *windowsFilter = nullptr;
#endif
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
static int signalFd = -1;
static QSocketNotifier *socketNotifier = nullptr;
static void setupUnixSignalHandler()
{
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGINT);
sigaddset(&set, SIGTERM);
pthread_sigmask(SIG_BLOCK, &set, nullptr);
signalFd = signalfd(-1, &set, SFD_NONBLOCK | SFD_CLOEXEC);
if (signalFd < 0)
return;
socketNotifier = new QSocketNotifier(signalFd, QSocketNotifier::Read, QCoreApplication::instance());
QObject::connect(socketNotifier, &QSocketNotifier::activated, QCoreApplication::instance(), [](int) {
signalfd_siginfo fdsi;
::read(signalFd, &fdsi, sizeof(fdsi));
if (fdsi.ssi_signo == SIGINT || fdsi.ssi_signo == SIGTERM) {
QCoreApplication::quit();
}
});
}
#elif defined(Q_OS_MACOS)
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));
}
}
static void setupUnixSignalHandler()
{
if (::pipe(signalPipe) != 0)
return;
::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);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, nullptr);
sigaction(SIGTERM, &sa, nullptr);
}
#endif
static void cleanupUnixSignalHandler()
{
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
if (socketNotifier) {
socketNotifier->setEnabled(false);
socketNotifier->deleteLater();
socketNotifier = nullptr;
}
if (signalFd >= 0) {
::close(signalFd);
signalFd = -1;
}
#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) {
::close(signalPipe[0]);
signalPipe[0] = -1;
}
if (signalPipe[1] >= 0) {
::close(signalPipe[1]);
signalPipe[1] = -1;
}
#endif
#ifdef Q_OS_WIN
if (windowsFilter) {
QCoreApplication::instance()->removeNativeEventFilter(windowsFilter);
delete windowsFilter;
windowsFilter = nullptr;
}
#endif
}
}
OsSignalHandler::OsSignalHandler(QObject *parent) : QObject(parent)
{
}
void OsSignalHandler::setup()
{
if (initialized)
return;
initialized = true;
#if (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) || defined(Q_OS_MACOS)
setupUnixSignalHandler();
#endif
#ifdef Q_OS_WIN
windowsFilter = new WindowsCloseFilter();
QCoreApplication::instance()->installNativeEventFilter(windowsFilter);
#endif
QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, [] { cleanupUnixSignalHandler(); });
}

View File

@@ -1,17 +0,0 @@
#ifndef OSSIGNALHANDLER_H
#define OSSIGNALHANDLER_H
#include <QObject>
class OsSignalHandler : public QObject
{
Q_OBJECT
public:
static void setup();
private:
explicit OsSignalHandler(QObject *parent = nullptr);
static void handleSignal(int signal);
};
#endif // OSSIGNALHANDLER_H

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");

View File

@@ -21,7 +21,6 @@ namespace amnezia::serialization
namespace vless
{
QJsonObject Deserialize(const QString &vless, QString *alias, QString *errMessage);
const QString Serialize(const VlessServerObject &server, const QString &alias);
} // namespace vless
namespace ss

View File

@@ -42,25 +42,6 @@ struct VMessServerObject
};
struct VlessServerObject
{
QString address;
QString id; // UUID
int port;
QString flow = "xtls-rprx-vision";
QString encryption = "none";
QString network = "tcp";
QString security = "reality";
QString serverName; // SNI
QString publicKey;
QString shortId;
QString fingerprint = "chrome";
QString spiderX = "";
JSONSTRUCT_COMPARE(VlessServerObject, address, id, port, flow, encryption)
JSONSTRUCT_REGISTER(VlessServerObject, F(address, id, port, flow, encryption, network, security, serverName, publicKey, shortId, fingerprint, spiderX))
};
namespace transfer
{

View File

@@ -252,65 +252,5 @@ QJsonObject Deserialize(const QString &str, QString *alias, QString *errMessage)
root["inbounds"] = QJsonArray { inbound };
return root;
}
const QString Serialize(const VlessServerObject &server, const QString &alias)
{
QUrl url;
// Set basic URL components
url.setScheme("vless");
url.setUserInfo(server.id);
url.setHost(server.address);
url.setPort(server.port);
QUrlQuery query;
if (!server.network.isEmpty() && server.network != "tcp") {
query.addQueryItem("type", server.network);
}
if (!server.encryption.isEmpty()) {
query.addQueryItem("encryption", server.encryption);
}
if (!server.security.isEmpty() && server.security != "none") {
query.addQueryItem("security", server.security);
}
if (!server.flow.isEmpty() && (server.security == "xtls" || server.security == "reality")) {
query.addQueryItem("flow", server.flow);
}
if (!server.serverName.isEmpty()) {
query.addQueryItem("sni", server.serverName);
}
if (server.security == "reality") {
if (!server.fingerprint.isEmpty()) {
query.addQueryItem("fp", server.fingerprint);
}
if (!server.publicKey.isEmpty()) {
query.addQueryItem("pbk", server.publicKey);
}
if (!server.shortId.isEmpty()) {
query.addQueryItem("sid", server.shortId);
}
if (!server.spiderX.isEmpty()) {
query.addQueryItem("spiderX", server.spiderX);
}
}
url.setQuery(query);
if (!alias.isEmpty()) {
url.setFragment(alias);
}
return url.toString(QUrl::ComponentFormattingOption::FullyEncoded);
}
}
} // namespace amnezia::serialization::vless

View File

@@ -1,153 +0,0 @@
#include "dnsPacket_p.h"
#include <QHostInfo>
#include <cstring>
namespace amnezia::transport::dns::detail
{
QHostAddress resolveHostAddress(const QString &host)
{
QHostAddress addr(host);
if (!addr.isNull()) return addr;
QHostInfo info = QHostInfo::fromName(host);
if (!info.addresses().isEmpty()) return info.addresses().first();
return QHostAddress();
}
QByteArray encodeDnsName(const QString &hostname)
{
QByteArray result;
const QStringList parts = hostname.split('.');
for (const QString &part : parts) {
if (part.length() > 63) {
return QByteArray();
}
result.append(static_cast<char>(part.length()));
result.append(part.toUtf8());
}
result.append(static_cast<char>(0));
return result;
}
QByteArray buildDnsQuery(const QString &hostname, quint16 transactionId)
{
QByteArray packet;
DnsHeader header;
header.id = qToBigEndian(transactionId);
header.flags = qToBigEndian<quint16>(0x0100);
header.qdcount = qToBigEndian<quint16>(1);
header.ancount = 0;
header.nscount = 0;
header.arcount = 0;
packet.append(reinterpret_cast<const char *>(&header), sizeof(DnsHeader));
const QByteArray qname = encodeDnsName(hostname);
if (qname.isEmpty()) {
return QByteArray();
}
packet.append(qname);
quint16 qtype = qToBigEndian<quint16>(DNS_TYPE_A);
packet.append(reinterpret_cast<const char *>(&qtype), sizeof(quint16));
quint16 qclass = qToBigEndian<quint16>(DNS_CLASS_IN);
packet.append(reinterpret_cast<const char *>(&qclass), sizeof(quint16));
return packet;
}
QString parseDnsResponse(const QByteArray &response, bool isTcp)
{
if (response.size() < static_cast<int>(sizeof(DnsHeader))) {
return QString();
}
int offset = isTcp ? 2 : 0;
if (response.size() < offset + static_cast<int>(sizeof(DnsHeader))) {
return QString();
}
DnsHeader header;
std::memcpy(&header, response.constData() + offset, sizeof(DnsHeader));
offset += sizeof(DnsHeader);
const quint16 flags = qFromBigEndian(header.flags);
const quint16 ancount = qFromBigEndian(header.ancount);
if ((flags & 0x8000) == 0 || (flags & 0x000F) != 0) {
return QString();
}
if (ancount == 0) {
return QString();
}
while (offset < response.size() && response.at(offset) != 0) {
const quint8 length = static_cast<quint8>(response.at(offset));
if (length > 63) {
return QString();
}
offset += length + 1;
}
if (offset >= response.size()) {
return QString();
}
offset++;
offset += 4;
for (int i = 0; i < ancount && offset < response.size(); ++i) {
if (offset >= response.size()) {
break;
}
const quint8 nameByte = static_cast<quint8>(response.at(offset));
if ((nameByte & 0xC0) == 0xC0) {
offset += 2;
} else {
while (offset < response.size() && response.at(offset) != 0) {
const quint8 length = static_cast<quint8>(response.at(offset));
if (length > 63) {
return QString();
}
offset += length + 1;
}
offset++;
}
if (offset + 10 > response.size()) {
break;
}
const quint16 type =
qFromBigEndian<quint16>(*reinterpret_cast<const quint16 *>(response.constData() + offset));
offset += 2;
offset += 2;
offset += 4;
const quint16 rdlength =
qFromBigEndian<quint16>(*reinterpret_cast<const quint16 *>(response.constData() + offset));
offset += 2;
if (type == DNS_TYPE_A && rdlength == 4) {
if (offset + 4 > response.size()) {
break;
}
QHostAddress ip;
ip.setAddress(
qFromBigEndian<quint32>(*reinterpret_cast<const quint32 *>(response.constData() + offset)));
return ip.toString();
}
offset += rdlength;
}
return QString();
}
} // namespace amnezia::transport::dns::detail

View File

@@ -1,38 +0,0 @@
#ifndef DNSPACKET_P_H
#define DNSPACKET_P_H
#include <QByteArray>
#include <QHostAddress>
#include <QString>
#include <QtEndian>
namespace amnezia::transport::dns::detail
{
constexpr quint16 DNS_PORT = 53;
constexpr quint16 DNS_TYPE_A = 1;
constexpr quint16 DNS_CLASS_IN = 1;
#pragma pack(push, 1)
struct DnsHeader
{
quint16 id;
quint16 flags;
quint16 qdcount;
quint16 ancount;
quint16 nscount;
quint16 arcount;
};
#pragma pack(pop)
QHostAddress resolveHostAddress(const QString &host);
QByteArray encodeDnsName(const QString &hostname);
QByteArray buildDnsQuery(const QString &hostname, quint16 transactionId);
QString parseDnsResponse(const QByteArray &response, bool isTcp);
} // namespace amnezia::transport::dns::detail
#endif // DNSPACKET_P_H

View File

@@ -1,354 +0,0 @@
#include "dnsResolver.h"
#include "dnsPacket_p.h"
#include <QDateTime>
#include <QEventLoop>
#include <QHostAddress>
#include <QNetworkAccessManager>
#include <QNetworkDatagram>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QSslSocket>
#include <QTcpSocket>
#include <QTimer>
#include <QUdpSocket>
#include <QUrl>
namespace amnezia::transport::dns::DnsResolver
{
using detail::buildDnsQuery;
using detail::parseDnsResponse;
using detail::resolveHostAddress;
QString resolve(const QString &hostname,
const QString &dnsServer,
DnsProtocol protocol,
quint16 port,
int timeoutMsecs,
const QString &dohEndpoint)
{
switch (protocol) {
case DnsProtocol::Udp:
return resolveOverUdp(hostname, dnsServer, port, timeoutMsecs);
case DnsProtocol::Tcp:
return resolveOverTcp(hostname, dnsServer, port, timeoutMsecs);
case DnsProtocol::Tls:
return resolveOverTls(hostname, dnsServer, port, timeoutMsecs);
case DnsProtocol::Https:
return resolveOverHttps(hostname, dnsServer, dohEndpoint, timeoutMsecs);
case DnsProtocol::Quic:
return resolveOverQuic(hostname, dnsServer, port, timeoutMsecs);
}
return QString();
}
QString resolveOverUdp(const QString &hostname, const QString &dnsServer, quint16 port, int timeoutMsecs)
{
QUdpSocket socket;
const quint16 transactionId = static_cast<quint16>(QDateTime::currentMSecsSinceEpoch() & 0xFFFF);
const QByteArray query = buildDnsQuery(hostname, transactionId);
if (query.isEmpty()) {
return QString();
}
const QHostAddress dnsAddress = resolveHostAddress(dnsServer);
if (dnsAddress.isNull()) {
return QString();
}
const qint64 bytesWritten = socket.writeDatagram(query, dnsAddress, port);
if (bytesWritten != query.size()) {
return QString();
}
QEventLoop loop;
QTimer timer;
timer.setSingleShot(true);
timer.setInterval(timeoutMsecs);
QByteArray response;
bool responseReceived = false;
QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
QObject::connect(&socket, &QUdpSocket::readyRead, [&]() {
while (socket.hasPendingDatagrams()) {
QNetworkDatagram datagram = socket.receiveDatagram();
if (datagram.isValid()) {
response = datagram.data();
responseReceived = true;
loop.quit();
}
}
});
timer.start();
loop.exec();
timer.stop();
if (!responseReceived || response.isEmpty()) {
return QString();
}
return parseDnsResponse(response, false);
}
QString resolveOverTcp(const QString &hostname, const QString &dnsServer, quint16 port, int timeoutMsecs)
{
QTcpSocket socket;
const QHostAddress dnsAddress = resolveHostAddress(dnsServer);
if (dnsAddress.isNull()) {
return QString();
}
socket.connectToHost(dnsAddress, port);
if (!socket.waitForConnected(timeoutMsecs)) {
return QString();
}
const quint16 transactionId = static_cast<quint16>(QDateTime::currentMSecsSinceEpoch() & 0xFFFF);
const QByteArray query = buildDnsQuery(hostname, transactionId);
if (query.isEmpty()) {
socket.close();
return QString();
}
quint16 length = qToBigEndian<quint16>(static_cast<quint16>(query.size()));
QByteArray tcpQuery;
tcpQuery.append(reinterpret_cast<const char *>(&length), sizeof(quint16));
tcpQuery.append(query);
const qint64 bytesWritten = socket.write(tcpQuery);
if (bytesWritten != tcpQuery.size() || !socket.waitForBytesWritten(timeoutMsecs)) {
socket.close();
return QString();
}
QEventLoop loop;
QTimer timer;
timer.setSingleShot(true);
timer.setInterval(timeoutMsecs);
QByteArray response;
bool responseReceived = false;
QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
QObject::connect(&socket, &QTcpSocket::readyRead, [&]() {
if (socket.bytesAvailable() >= 2 && response.isEmpty()) {
QByteArray lengthBytes = socket.read(2);
if (lengthBytes.size() == 2) {
const quint16 responseLength =
qFromBigEndian<quint16>(*reinterpret_cast<const quint16 *>(lengthBytes.constData()));
while (socket.bytesAvailable() < responseLength) {
if (!socket.waitForReadyRead(timeoutMsecs / 2)) {
break;
}
}
if (socket.bytesAvailable() >= responseLength) {
response = socket.read(responseLength);
responseReceived = true;
loop.quit();
}
}
}
});
timer.start();
loop.exec();
timer.stop();
socket.close();
if (!responseReceived || response.isEmpty()) {
return QString();
}
return parseDnsResponse(response, true);
}
QString resolveOverTls(const QString &hostname, const QString &dnsServer, quint16 port, int timeoutMsecs)
{
QSslSocket socket;
const QHostAddress dnsAddress = resolveHostAddress(dnsServer);
if (dnsAddress.isNull()) {
return QString();
}
socket.setPeerVerifyMode(QSslSocket::QueryPeer);
socket.connectToHostEncrypted(dnsAddress.toString(), port);
if (!socket.waitForConnected(timeoutMsecs)) {
return QString();
}
if (!socket.waitForEncrypted(timeoutMsecs)) {
socket.close();
return QString();
}
const quint16 transactionId = static_cast<quint16>(QDateTime::currentMSecsSinceEpoch() & 0xFFFF);
const QByteArray query = buildDnsQuery(hostname, transactionId);
if (query.isEmpty()) {
socket.close();
return QString();
}
quint16 length = qToBigEndian<quint16>(static_cast<quint16>(query.size()));
QByteArray tlsQuery;
tlsQuery.append(reinterpret_cast<const char *>(&length), sizeof(quint16));
tlsQuery.append(query);
const qint64 bytesWritten = socket.write(tlsQuery);
if (bytesWritten != tlsQuery.size() || !socket.waitForBytesWritten(timeoutMsecs)) {
socket.close();
return QString();
}
QEventLoop loop;
QTimer timer;
timer.setSingleShot(true);
timer.setInterval(timeoutMsecs);
QByteArray response;
bool responseReceived = false;
QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
QObject::connect(&socket, &QSslSocket::readyRead, [&]() {
if (socket.bytesAvailable() >= 2 && response.isEmpty()) {
QByteArray lengthBytes = socket.read(2);
if (lengthBytes.size() == 2) {
const quint16 responseLength =
qFromBigEndian<quint16>(*reinterpret_cast<const quint16 *>(lengthBytes.constData()));
while (socket.bytesAvailable() < responseLength) {
if (!socket.waitForReadyRead(timeoutMsecs / 2)) {
break;
}
}
if (socket.bytesAvailable() >= responseLength) {
response = socket.read(responseLength);
responseReceived = true;
loop.quit();
}
}
}
});
timer.start();
loop.exec();
timer.stop();
socket.close();
if (!responseReceived || response.isEmpty()) {
return QString();
}
return parseDnsResponse(response, true);
}
QString resolveOverHttps(const QString &hostname, const QString &dnsServer, const QString &endpoint, int timeoutMsecs)
{
const QString dohUrl = QStringLiteral("https://%1%2").arg(dnsServer, endpoint);
const quint16 transactionId = static_cast<quint16>(QDateTime::currentMSecsSinceEpoch() & 0xFFFF);
const QByteArray query = buildDnsQuery(hostname, transactionId);
if (query.isEmpty()) {
return QString();
}
QNetworkRequest request;
request.setUrl(QUrl(dohUrl));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/dns-message");
request.setRawHeader("Accept", "application/dns-message");
request.setTransferTimeout(timeoutMsecs);
QNetworkAccessManager nam;
QNetworkReply *reply = nam.post(request, query);
QEventLoop loop;
QTimer timer;
timer.setSingleShot(true);
timer.setInterval(timeoutMsecs);
QByteArray response;
bool responseReceived = false;
QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
QObject::connect(reply, &QNetworkReply::finished, [&]() {
if (reply->error() == QNetworkReply::NoError) {
response = reply->readAll();
responseReceived = true;
}
loop.quit();
});
timer.start();
loop.exec();
timer.stop();
reply->deleteLater();
if (!responseReceived || response.isEmpty()) {
return QString();
}
return parseDnsResponse(response, false);
}
QString resolveOverQuic(const QString &hostname, const QString &dnsServer, quint16 port, int timeoutMsecs)
{
// QUIC требует специальной библиотеки — пока используем UDP fallback
QUdpSocket socket;
const QHostAddress dnsAddress = resolveHostAddress(dnsServer);
if (dnsAddress.isNull()) {
return QString();
}
const quint16 transactionId = static_cast<quint16>(QDateTime::currentMSecsSinceEpoch() & 0xFFFF);
const QByteArray query = buildDnsQuery(hostname, transactionId);
if (query.isEmpty()) {
return QString();
}
const qint64 bytesWritten = socket.writeDatagram(query, dnsAddress, port);
if (bytesWritten != query.size()) {
return QString();
}
QEventLoop loop;
QTimer timer;
timer.setSingleShot(true);
timer.setInterval(timeoutMsecs);
QByteArray response;
bool responseReceived = false;
QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
QObject::connect(&socket, &QUdpSocket::readyRead, [&]() {
while (socket.hasPendingDatagrams()) {
QNetworkDatagram datagram = socket.receiveDatagram();
if (datagram.isValid()) {
response = datagram.data();
responseReceived = true;
loop.quit();
}
}
});
timer.start();
loop.exec();
timer.stop();
if (!responseReceived || response.isEmpty()) {
return QString();
}
return parseDnsResponse(response, false);
}
} // namespace amnezia::transport::dns::DnsResolver

View File

@@ -1,29 +0,0 @@
#ifndef DNSRESOLVER_H
#define DNSRESOLVER_H
#include <QString>
namespace amnezia::transport::dns
{
enum class DnsProtocol { Udp, Tcp, Tls, Https, Quic };
namespace DnsResolver
{
QString resolve(const QString &hostname,
const QString &dnsServer,
DnsProtocol protocol,
quint16 port,
int timeoutMsecs = 3000,
const QString &dohEndpoint = QStringLiteral("/dns-query"));
QString resolveOverUdp(const QString &hostname, const QString &dnsServer, quint16 port, int timeoutMsecs = 3000);
QString resolveOverTcp(const QString &hostname, const QString &dnsServer, quint16 port, int timeoutMsecs = 3000);
QString resolveOverTls(const QString &hostname, const QString &dnsServer, quint16 port, int timeoutMsecs = 3000);
QString resolveOverHttps(const QString &hostname, const QString &dnsServer, const QString &endpoint, int timeoutMsecs = 3000);
QString resolveOverQuic(const QString &hostname, const QString &dnsServer, quint16 port, int timeoutMsecs = 3000);
} // namespace DnsResolver
} // namespace amnezia::transport::dns
#endif // DNSRESOLVER_H

View File

@@ -1,817 +0,0 @@
#include "dnsTunnel.h"
#include "dnsPacket_p.h"
#include <QDateTime>
#include <QDebug>
#include <QElapsedTimer>
#include <QEventLoop>
#include <QHostAddress>
#include <QList>
#include <QMap>
#include <QNetworkAccessManager>
#include <QNetworkDatagram>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QSharedPointer>
#include <QSslError>
#include <QSslSocket>
#include <QStringList>
#include <QTcpSocket>
#include <QThread>
#include <QTimer>
#include <QUdpSocket>
#include <QUrl>
namespace amnezia::transport::dns::DnsTunnel
{
using detail::resolveHostAddress;
namespace
{
constexpr quint16 EDNS0_PAYLOAD_OPTION_CODE = 65001;
constexpr quint16 EDNS0_CHUNK_REQUEST_CODE = 65002;
constexpr quint16 EDNS0_CHUNK_RESPONSE_CODE = 65003;
struct ChunkMeta
{
QByteArray chunkId;
quint16 totalChunks = 0;
quint16 chunkIndex = 0;
quint32 totalSize = 0;
};
void appendUint16BE(QByteArray &data, quint16 value)
{
data.append(static_cast<char>((value >> 8) & 0xFF));
data.append(static_cast<char>(value & 0xFF));
}
QByteArray buildDnsChunkRequest(const QString &queryName, quint16 transactionId,
const QByteArray &chunkId, quint16 chunkIndex)
{
QByteArray query;
appendUint16BE(query, transactionId);
appendUint16BE(query, 0x0100);
appendUint16BE(query, 1);
appendUint16BE(query, 0);
appendUint16BE(query, 0);
appendUint16BE(query, 1);
const QStringList labels = queryName.split('.');
for (const QString &label : labels) {
QByteArray labelBytes = label.toUtf8();
query.append(static_cast<char>(labelBytes.size()));
query.append(labelBytes);
}
query.append(static_cast<char>(0));
appendUint16BE(query, 16);
appendUint16BE(query, 1);
const quint16 optionDataLen = 4 + 18;
query.append(static_cast<char>(0));
appendUint16BE(query, 41);
appendUint16BE(query, 4096);
query.append(static_cast<char>(0));
query.append(static_cast<char>(0));
appendUint16BE(query, 0);
appendUint16BE(query, optionDataLen);
appendUint16BE(query, EDNS0_CHUNK_REQUEST_CODE);
appendUint16BE(query, 18);
query.append(chunkId.left(16).leftJustified(16, '\0'));
appendUint16BE(query, chunkIndex);
return query;
}
ChunkMeta parseChunkMeta(const QByteArray &response)
{
ChunkMeta meta;
if (response.size() < 12) return meta;
const quint8 *data = reinterpret_cast<const quint8 *>(response.constData());
const quint16 qdCount = (data[4] << 8) | data[5];
const quint16 anCount = (data[6] << 8) | data[7];
const quint16 nsCount = (data[8] << 8) | data[9];
const quint16 arCount = (data[10] << 8) | data[11];
int pos = 12;
auto skipDnsName = [&]() -> bool {
int maxLabels = 128;
while (pos < response.size() && data[pos] != 0 && maxLabels-- > 0) {
if ((data[pos] & 0xC0) == 0xC0) {
pos += 2;
return pos <= response.size();
}
const int labelLen = data[pos];
if (pos + 1 + labelLen > response.size()) return false;
pos += labelLen + 1;
}
if (pos < response.size() && data[pos] == 0) pos++;
return pos <= response.size();
};
for (int i = 0; i < qdCount && pos < response.size(); ++i) {
if (!skipDnsName()) return meta;
if (pos + 4 > response.size()) return meta;
pos += 4;
}
for (int i = 0; i < anCount && pos < response.size(); ++i) {
if (!skipDnsName()) return meta;
if (pos + 10 > response.size()) return meta;
const quint16 rdlen = (data[pos + 8] << 8) | data[pos + 9];
if (pos + 10 + rdlen > response.size()) return meta;
pos += 10 + rdlen;
}
for (int i = 0; i < nsCount && pos < response.size(); ++i) {
if (!skipDnsName()) return meta;
if (pos + 10 > response.size()) return meta;
const quint16 rdlen = (data[pos + 8] << 8) | data[pos + 9];
if (pos + 10 + rdlen > response.size()) return meta;
pos += 10 + rdlen;
}
for (int i = 0; i < arCount && pos < response.size(); ++i) {
if (pos < response.size() && data[pos] == 0) {
pos++;
} else {
if (!skipDnsName()) return meta;
}
if (pos + 10 > response.size()) return meta;
const quint16 rtype = (data[pos] << 8) | data[pos + 1];
const quint16 rdlen = (data[pos + 8] << 8) | data[pos + 9];
if (pos + 10 + rdlen > response.size()) return meta;
pos += 10;
if (rtype == 41 && rdlen > 0) {
const int optEnd = pos + rdlen;
while (pos + 4 <= optEnd) {
const quint16 optCode = (data[pos] << 8) | data[pos + 1];
const quint16 optLen = (data[pos + 2] << 8) | data[pos + 3];
pos += 4;
if (optCode == EDNS0_CHUNK_RESPONSE_CODE && optLen >= 24) {
meta.chunkId = QByteArray(reinterpret_cast<const char *>(data + pos), 16);
meta.totalChunks = (data[pos + 16] << 8) | data[pos + 17];
meta.chunkIndex = (data[pos + 18] << 8) | data[pos + 19];
meta.totalSize = (static_cast<quint32>(data[pos + 20]) << 24)
| (static_cast<quint32>(data[pos + 21]) << 16)
| (static_cast<quint32>(data[pos + 22]) << 8) | data[pos + 23];
return meta;
}
pos += optLen;
}
} else {
pos += rdlen;
}
}
return meta;
}
QByteArray buildDnsTxtQueryWithPayload(const QString &queryName, quint16 transactionId, const QByteArray &payload)
{
QByteArray query;
appendUint16BE(query, transactionId);
appendUint16BE(query, 0x0100);
appendUint16BE(query, 1);
appendUint16BE(query, 0);
appendUint16BE(query, 0);
appendUint16BE(query, 1);
const QStringList labels = queryName.split('.');
for (const QString &label : labels) {
QByteArray labelBytes = label.toUtf8();
query.append(static_cast<char>(labelBytes.size()));
query.append(labelBytes);
}
query.append(static_cast<char>(0));
appendUint16BE(query, 16);
appendUint16BE(query, 1);
const QByteArray payloadBase64 = payload.toBase64();
const quint16 optionDataLen = 4 + payloadBase64.size();
query.append(static_cast<char>(0));
appendUint16BE(query, 41);
appendUint16BE(query, 4096);
query.append(static_cast<char>(0));
query.append(static_cast<char>(0));
appendUint16BE(query, 0);
appendUint16BE(query, optionDataLen);
appendUint16BE(query, EDNS0_PAYLOAD_OPTION_CODE);
appendUint16BE(query, payloadBase64.size());
query.append(payloadBase64);
return query;
}
QByteArray parseDnsTxtResponse(const QByteArray &response)
{
if (response.size() < 12) {
return QByteArray();
}
const uchar *data = reinterpret_cast<const uchar *>(response.constData());
int pos = 0;
pos += 2;
const quint16 flags = (data[pos] << 8) | data[pos + 1]; pos += 2;
const quint16 qdCount = (data[pos] << 8) | data[pos + 1]; pos += 2;
const quint16 anCount = (data[pos] << 8) | data[pos + 1]; pos += 2;
pos += 2;
pos += 2;
if ((flags & 0x8000) == 0) {
return QByteArray();
}
if (anCount > 100 || qdCount > 10) {
return QByteArray();
}
auto skipDnsName = [&]() -> bool {
int maxLabels = 128;
while (pos < response.size() && data[pos] != 0 && maxLabels-- > 0) {
if ((data[pos] & 0xC0) == 0xC0) {
pos += 2;
return pos <= response.size();
}
const int labelLen = data[pos];
if (pos + 1 + labelLen > response.size()) return false;
pos += labelLen + 1;
}
if (pos < response.size() && data[pos] == 0) pos++;
return pos <= response.size();
};
for (int i = 0; i < qdCount && pos < response.size(); ++i) {
if (!skipDnsName()) {
return QByteArray();
}
if (pos + 4 > response.size()) return QByteArray();
pos += 4;
}
QByteArray combinedTxt;
for (int i = 0; i < anCount && pos < response.size(); ++i) {
if (!skipDnsName()) {
break;
}
if (pos + 10 > response.size()) {
break;
}
const quint16 rtype = (data[pos] << 8) | data[pos + 1]; pos += 2;
pos += 2; // class
pos += 4; // ttl
const quint16 rdlength = (data[pos] << 8) | data[pos + 1]; pos += 2;
if (pos + rdlength > response.size()) {
break;
}
if (rtype == 16) {
const int rdEnd = pos + rdlength;
while (pos < rdEnd && pos < response.size()) {
const quint8 txtLen = data[pos++];
if (txtLen > 0 && pos + txtLen <= rdEnd && pos + txtLen <= response.size()) {
combinedTxt.append(reinterpret_cast<const char *>(data + pos), txtLen);
pos += txtLen;
} else {
break;
}
}
} else {
pos += rdlength;
}
}
if (combinedTxt.isEmpty()) {
return QByteArray();
}
return QByteArray::fromBase64(combinedTxt);
}
} // namespace
QByteArray send(const QByteArray &payload,
const QString &endpointName,
const QString &baseDomain,
const QString &dnsServer,
DnsProtocol protocol,
quint16 port,
int timeoutMsecs,
const QString &dohEndpoint)
{
const QString queryName = QStringLiteral("%1.%2").arg(endpointName, baseDomain);
switch (protocol) {
case DnsProtocol::Udp:
return sendOverUdpChunked(payload, queryName, dnsServer, port, timeoutMsecs);
case DnsProtocol::Tcp:
return sendOverTcp(payload, queryName, dnsServer, port, timeoutMsecs);
case DnsProtocol::Tls:
return sendOverTls(payload, queryName, dnsServer, port, timeoutMsecs);
case DnsProtocol::Https:
return sendOverHttps(payload, queryName, dnsServer, port, dohEndpoint, timeoutMsecs);
case DnsProtocol::Quic:
return QByteArray();
}
return QByteArray();
}
QByteArray sendOverUdp(const QByteArray &payload, const QString &queryName,
const QString &dnsServer, quint16 port, int timeoutMsecs)
{
QUdpSocket socket;
const quint16 transactionId = static_cast<quint16>(QDateTime::currentMSecsSinceEpoch() & 0xFFFF);
const QByteArray query = buildDnsTxtQueryWithPayload(queryName, transactionId, payload);
if (query.isEmpty()) {
return QByteArray();
}
const QHostAddress dnsAddress = resolveHostAddress(dnsServer);
if (dnsAddress.isNull()) {
return QByteArray();
}
const qint64 bytesWritten = socket.writeDatagram(query, dnsAddress, port);
if (bytesWritten != query.size()) {
return QByteArray();
}
QElapsedTimer timer;
timer.start();
while (timer.elapsed() < timeoutMsecs) {
if (socket.waitForReadyRead(qMax(1, timeoutMsecs - static_cast<int>(timer.elapsed())))) {
while (socket.hasPendingDatagrams()) {
QNetworkDatagram datagram = socket.receiveDatagram();
if (datagram.isValid()) {
return parseDnsTxtResponse(datagram.data());
}
}
}
}
return QByteArray();
}
QByteArray sendOverTcp(const QByteArray &payload, const QString &queryName,
const QString &dnsServer, quint16 port, int timeoutMsecs)
{
qDebug() << "[DNS-TCP] start: queryName=" << queryName << "server=" << dnsServer
<< "port=" << port << "payloadBytes=" << payload.size();
QTcpSocket socket;
const QHostAddress dnsAddress = resolveHostAddress(dnsServer);
if (dnsAddress.isNull()) {
qWarning() << "[DNS-TCP] failed to resolve" << dnsServer;
return QByteArray();
}
socket.connectToHost(dnsAddress, port);
if (!socket.waitForConnected(timeoutMsecs)) {
qWarning() << "[DNS-TCP] connect failed:" << socket.errorString();
return QByteArray();
}
qDebug() << "[DNS-TCP] connected";
const quint16 transactionId = static_cast<quint16>(QDateTime::currentMSecsSinceEpoch() & 0xFFFF);
const QByteArray query = buildDnsTxtQueryWithPayload(queryName, transactionId, payload);
if (query.isEmpty()) {
qWarning() << "[DNS-TCP] failed to build DNS query";
socket.close();
return QByteArray();
}
qDebug() << "[DNS-TCP] built DNS query bytes=" << query.size() << "txid=" << transactionId;
quint16 length = qToBigEndian<quint16>(static_cast<quint16>(query.size()));
QByteArray tcpQuery;
tcpQuery.append(reinterpret_cast<const char *>(&length), sizeof(quint16));
tcpQuery.append(query);
const qint64 bytesWritten = socket.write(tcpQuery);
qDebug() << "[DNS-TCP] wrote bytes=" << bytesWritten << "/ expected=" << tcpQuery.size();
if (bytesWritten != tcpQuery.size() || !socket.waitForBytesWritten(timeoutMsecs)) {
qWarning() << "[DNS-TCP] write failed:" << socket.errorString();
socket.close();
return QByteArray();
}
QElapsedTimer timer;
timer.start();
while (socket.bytesAvailable() < 2) {
const int remaining = timeoutMsecs - timer.elapsed();
if (remaining <= 0 || !socket.waitForReadyRead(remaining)) {
qWarning() << "[DNS-TCP] timeout waiting for response length, socketState="
<< socket.state() << "err=" << socket.errorString()
<< "bytesAvailable=" << socket.bytesAvailable();
socket.close();
return QByteArray();
}
}
QByteArray lengthBytes = socket.read(2);
if (lengthBytes.size() != 2) {
qWarning() << "[DNS-TCP] could not read length prefix";
socket.close();
return QByteArray();
}
const quint16 responseLength =
qFromBigEndian<quint16>(*reinterpret_cast<const quint16 *>(lengthBytes.constData()));
qDebug() << "[DNS-TCP] response length prefix=" << responseLength;
QByteArray response;
while (response.size() < responseLength) {
const int remaining = timeoutMsecs - timer.elapsed();
if (remaining <= 0) {
qWarning() << "[DNS-TCP] timeout reading body, got" << response.size() << "/" << responseLength;
socket.close();
return QByteArray();
}
if (socket.bytesAvailable() > 0) {
response.append(socket.read(responseLength - response.size()));
} else if (!socket.waitForReadyRead(remaining)) {
qWarning() << "[DNS-TCP] timeout in waitForReadyRead, got" << response.size() << "/" << responseLength;
socket.close();
return QByteArray();
}
}
qDebug() << "[DNS-TCP] full response read, bytes=" << response.size();
socket.close();
QByteArray parsed = parseDnsTxtResponse(response);
qDebug() << "[DNS-TCP] parsed TXT payload bytes=" << parsed.size();
return parsed;
}
QByteArray sendOverTls(const QByteArray &payload, const QString &queryName,
const QString &dnsServer, quint16 port, int timeoutMsecs)
{
QSslSocket socket;
#ifdef AGW_INSECURE_SSL
socket.setPeerVerifyMode(QSslSocket::VerifyNone);
QObject::connect(&socket, QOverload<const QList<QSslError> &>::of(&QSslSocket::sslErrors),
&socket, [&socket](const QList<QSslError> &errs) {
qWarning() << "[DoT] sslErrors (ignored, AGW_INSECURE_SSL=1):" << errs;
socket.ignoreSslErrors();
});
#else
socket.setPeerVerifyMode(QSslSocket::VerifyPeer);
#endif
const QHostAddress dnsAddress = resolveHostAddress(dnsServer);
if (dnsAddress.isNull()) {
qWarning() << "[DoT] failed to resolve" << dnsServer;
return QByteArray();
}
socket.connectToHostEncrypted(dnsServer, port);
if (!socket.waitForEncrypted(timeoutMsecs)) {
qWarning() << "[DoT] handshake failed:" << socket.errorString();
return QByteArray();
}
const quint16 transactionId = static_cast<quint16>(QDateTime::currentMSecsSinceEpoch() & 0xFFFF);
const QByteArray query = buildDnsTxtQueryWithPayload(queryName, transactionId, payload);
if (query.isEmpty()) {
socket.close();
return QByteArray();
}
quint16 length = qToBigEndian<quint16>(static_cast<quint16>(query.size()));
QByteArray tcpQuery;
tcpQuery.append(reinterpret_cast<const char *>(&length), sizeof(quint16));
tcpQuery.append(query);
const qint64 bytesWritten = socket.write(tcpQuery);
if (bytesWritten != tcpQuery.size() || !socket.waitForBytesWritten(timeoutMsecs)) {
socket.close();
return QByteArray();
}
QElapsedTimer timer;
timer.start();
while (socket.bytesAvailable() < 2) {
const int remaining = timeoutMsecs - timer.elapsed();
if (remaining <= 0 || !socket.waitForReadyRead(remaining)) {
socket.close();
return QByteArray();
}
}
QByteArray lengthBytes = socket.read(2);
if (lengthBytes.size() != 2) {
socket.close();
return QByteArray();
}
const quint16 responseLength =
qFromBigEndian<quint16>(*reinterpret_cast<const quint16 *>(lengthBytes.constData()));
QByteArray response;
while (response.size() < responseLength) {
const int remaining = timeoutMsecs - timer.elapsed();
if (remaining <= 0) {
socket.close();
return QByteArray();
}
if (socket.bytesAvailable() > 0) {
response.append(socket.read(responseLength - response.size()));
} else if (!socket.waitForReadyRead(remaining)) {
socket.close();
return QByteArray();
}
}
socket.close();
return parseDnsTxtResponse(response);
}
QByteArray sendOverHttps(const QByteArray &payload, const QString &queryName,
const QString &dnsServer, quint16 port, const QString &endpoint, int timeoutMsecs)
{
const quint16 transactionId = static_cast<quint16>(QDateTime::currentMSecsSinceEpoch() & 0xFFFF);
const QByteArray dnsQuery = buildDnsTxtQueryWithPayload(queryName, transactionId, payload);
qDebug() << "[DoH] queryName=" << queryName << "payloadBytes=" << payload.size()
<< "dnsQueryBytes=" << dnsQuery.size() << "txid=" << transactionId;
if (dnsQuery.isEmpty()) {
qWarning() << "[DoH] failed to build DNS query (payload too big or queryName invalid)";
return QByteArray();
}
const QString scheme = (port == 443) ? QStringLiteral("https") : QStringLiteral("http");
const QString url = QStringLiteral("%1://%2:%3%4").arg(scheme).arg(dnsServer).arg(port).arg(endpoint);
qDebug() << "[DoH] POST" << url << "timeoutMs=" << timeoutMsecs;
QNetworkRequest request((QUrl(url)));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/dns-message");
request.setRawHeader("Accept", "application/dns-message");
request.setTransferTimeout(timeoutMsecs);
QNetworkAccessManager manager;
QNetworkReply *reply = manager.post(request, dnsQuery);
QObject::connect(reply, &QNetworkReply::sslErrors, reply,
[reply](const QList<QSslError> &errs) {
qWarning() << "[DoH] sslErrors:" << errs;
#ifdef AGW_INSECURE_SSL
qWarning() << "[DoH] AGW_INSECURE_SSL=1, ignoring SSL errors";
reply->ignoreSslErrors();
#endif
});
QEventLoop loop;
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
QTimer::singleShot(timeoutMsecs, &loop, &QEventLoop::quit);
loop.exec();
if (!reply->isFinished()) {
qWarning() << "[DoH] timeout after" << timeoutMsecs << "ms, aborting";
reply->abort();
reply->deleteLater();
return QByteArray();
}
if (reply->error() != QNetworkReply::NoError) {
qWarning() << "[DoH] reply error:" << reply->error() << reply->errorString()
<< "httpStatus=" << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
reply->deleteLater();
return QByteArray();
}
QByteArray response = reply->readAll();
qDebug() << "[DoH] raw HTTP response bytes=" << response.size()
<< "httpStatus=" << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
reply->deleteLater();
if (response.isEmpty()) {
qWarning() << "[DoH] empty HTTP response body";
return QByteArray();
}
QByteArray parsed = parseDnsTxtResponse(response);
qDebug() << "[DoH] parsed TXT payload bytes=" << parsed.size();
return parsed;
}
QByteArray sendOverUdpChunked(const QByteArray &payload, const QString &queryName,
const QString &dnsServer, quint16 port, int timeoutMsecs)
{
qDebug() << "[DNS-UDP] start: queryName=" << queryName << "server=" << dnsServer
<< "port=" << port << "payloadBytes=" << payload.size() << "timeoutMs=" << timeoutMsecs;
const QHostAddress dnsAddress = resolveHostAddress(dnsServer);
if (dnsAddress.isNull()) {
qWarning() << "[DNS-UDP] failed to resolve" << dnsServer;
return QByteArray();
}
qDebug() << "[DNS-UDP] resolved to" << dnsAddress.toString();
constexpr int MAX_INITIAL_RETRIES = 3;
constexpr int MAX_CHUNK_RETRIES = 2;
constexpr int MAX_CONCURRENT_REQUESTS = 5;
constexpr int BASE_TIMEOUT_MS = 2000;
auto sendUdpRequestWithTimeout = [&](const QByteArray &query, int requestTimeoutMs) -> QByteArray {
QUdpSocket socket;
const qint64 written = socket.writeDatagram(query, dnsAddress, port);
if (written != query.size()) {
return QByteArray();
}
QElapsedTimer timer;
timer.start();
while (timer.elapsed() < requestTimeoutMs) {
if (socket.waitForReadyRead(qMax(1, requestTimeoutMs - static_cast<int>(timer.elapsed())))) {
while (socket.hasPendingDatagrams()) {
QNetworkDatagram datagram = socket.receiveDatagram();
if (datagram.isValid()) {
return datagram.data();
}
}
}
}
return QByteArray();
};
auto sendWithRetry = [&](const QByteArray &query, int maxRetries) -> QByteArray {
for (int attempt = 0; attempt < maxRetries; ++attempt) {
const int timeout = BASE_TIMEOUT_MS * (attempt + 1);
QByteArray response = sendUdpRequestWithTimeout(query, timeout);
if (!response.isEmpty()) {
return response;
}
if (attempt < maxRetries - 1) {
QThread::msleep(timeout / 2);
}
}
return QByteArray();
};
const quint16 transactionId = static_cast<quint16>(QDateTime::currentMSecsSinceEpoch() & 0xFFFF);
const QByteArray initialQuery = buildDnsTxtQueryWithPayload(queryName, transactionId, payload);
qDebug() << "[DNS-UDP] initialQuery size=" << initialQuery.size() << "txid=" << transactionId;
if (initialQuery.isEmpty()) {
qWarning() << "[DNS-UDP] failed to build initial query (payload too big or queryName invalid)";
return QByteArray();
}
const QByteArray firstResponse = sendWithRetry(initialQuery, MAX_INITIAL_RETRIES);
qDebug() << "[DNS-UDP] first response size=" << firstResponse.size();
if (firstResponse.isEmpty()) {
qWarning() << "[DNS-UDP] no response from server after" << MAX_INITIAL_RETRIES << "retries";
return QByteArray();
}
const ChunkMeta meta = parseChunkMeta(firstResponse);
const QByteArray firstTxtData = parseDnsTxtResponse(firstResponse);
qDebug() << "[DNS-UDP] meta totalChunks=" << meta.totalChunks
<< "chunkId=" << meta.chunkId << "firstTxtData size=" << firstTxtData.size();
if (firstTxtData.isEmpty()) {
qWarning() << "[DNS-UDP] failed to parse TXT data from first response";
return QByteArray();
}
if (meta.totalChunks <= 1) {
qDebug() << "[DNS-UDP] single chunk, returning" << firstTxtData.size() << "bytes";
return firstTxtData;
}
QMap<int, QByteArray> chunks;
chunks[0] = firstTxtData;
auto requestChunksBatch = [&](const QList<int> &chunkIndices, int batchTimeout) {
if (chunkIndices.isEmpty()) return;
QList<QSharedPointer<QUdpSocket>> sockets;
QMap<QUdpSocket *, int> socketToIndex;
for (int idx : chunkIndices) {
if (chunks.contains(idx)) continue;
const quint16 chunkTxId =
static_cast<quint16>((QDateTime::currentMSecsSinceEpoch() + idx) & 0xFFFF);
const QByteArray chunkQuery =
buildDnsChunkRequest(queryName, chunkTxId, meta.chunkId, idx);
if (chunkQuery.isEmpty()) {
continue;
}
auto socket = QSharedPointer<QUdpSocket>::create();
socket->writeDatagram(chunkQuery, dnsAddress, port);
socketToIndex[socket.data()] = idx;
sockets.append(socket);
}
if (sockets.isEmpty()) return;
QElapsedTimer deadline;
deadline.start();
int receivedCount = 0;
const int expectedCount = sockets.size();
while (deadline.elapsed() < batchTimeout && receivedCount < expectedCount
&& chunks.size() < meta.totalChunks) {
for (auto &socket : sockets) {
if (socket->waitForReadyRead(50)) {
while (socket->hasPendingDatagrams()) {
QNetworkDatagram datagram = socket->receiveDatagram();
if (datagram.isValid()) {
const QByteArray chunkTxtData = parseDnsTxtResponse(datagram.data());
if (!chunkTxtData.isEmpty()) {
const ChunkMeta chunkMeta = parseChunkMeta(datagram.data());
const int idx = (chunkMeta.totalChunks > 0)
? chunkMeta.chunkIndex
: socketToIndex.value(socket.data(), -1);
if (idx >= 0 && !chunks.contains(idx)) {
chunks[idx] = chunkTxtData;
receivedCount++;
}
}
}
}
}
}
}
};
const int totalTimeout = qMax(timeoutMsecs / 2, 5000);
const int batchTimeout = totalTimeout / (MAX_CHUNK_RETRIES + 1);
for (int retryRound = 0; retryRound <= MAX_CHUNK_RETRIES; ++retryRound) {
QList<int> missing;
for (int i = 1; i < meta.totalChunks; ++i) {
if (!chunks.contains(i)) {
missing.append(i);
}
}
if (missing.isEmpty()) {
break;
}
for (int batchStart = 0; batchStart < missing.size(); batchStart += MAX_CONCURRENT_REQUESTS) {
const QList<int> batch = missing.mid(batchStart, MAX_CONCURRENT_REQUESTS);
requestChunksBatch(batch, batchTimeout);
}
}
QList<int> finalMissing;
for (int i = 0; i < meta.totalChunks; ++i) {
if (!chunks.contains(i)) {
finalMissing.append(i);
}
}
if (!finalMissing.isEmpty()) {
return QByteArray();
}
QByteArray combined;
combined.reserve(meta.totalSize > 0 ? meta.totalSize : meta.totalChunks * 500);
for (int i = 0; i < meta.totalChunks; ++i) {
combined.append(chunks[i]);
}
return combined;
}
} // namespace amnezia::transport::dns::DnsTunnel

View File

@@ -1,35 +0,0 @@
#ifndef DNSTUNNEL_H
#define DNSTUNNEL_H
#include <QByteArray>
#include <QString>
#include "dnsResolver.h"
namespace amnezia::transport::dns::DnsTunnel
{
QByteArray send(const QByteArray &payload,
const QString &endpointName,
const QString &baseDomain,
const QString &dnsServer,
DnsProtocol protocol,
quint16 port,
int timeoutMsecs = 30000,
const QString &dohEndpoint = QStringLiteral("/dns-query"));
QByteArray sendOverUdp(const QByteArray &payload, const QString &queryName,
const QString &dnsServer, quint16 port, int timeoutMsecs);
QByteArray sendOverTcp(const QByteArray &payload, const QString &queryName,
const QString &dnsServer, quint16 port, int timeoutMsecs);
QByteArray sendOverTls(const QByteArray &payload, const QString &queryName,
const QString &dnsServer, quint16 port, int timeoutMsecs);
QByteArray sendOverHttps(const QByteArray &payload, const QString &queryName,
const QString &dnsServer, quint16 port, const QString &endpoint, int timeoutMsecs);
QByteArray sendOverUdpChunked(const QByteArray &payload, const QString &queryName,
const QString &dnsServer, quint16 port, int timeoutMsecs);
} // namespace amnezia::transport::dns::DnsTunnel
#endif // DNSTUNNEL_H

View File

@@ -1,157 +0,0 @@
#include "dnsGatewayTransport.h"
#include <QDebug>
#include <QHostAddress>
#include <QHostInfo>
#include <QSharedPointer>
#include <QStringList>
#include "dns/dnsTunnel.h"
#include "core/networkUtilities.h"
#ifdef AMNEZIA_DESKTOP
#include "core/ipcclient.h"
#endif
namespace amnezia::transport
{
DnsGatewayTransport::DnsGatewayTransport(dns::DnsProtocol protocol,
const QString &dnsServer,
const QString &baseDomain,
quint16 port,
int timeoutMsecs,
bool isStrictKillSwitchEnabled,
const QString &dohEndpoint)
: m_protocol(protocol),
m_dnsServer(dnsServer),
m_baseDomain(baseDomain),
m_port(port),
m_timeoutMsecs(timeoutMsecs),
m_isStrictKillSwitchEnabled(isStrictKillSwitchEnabled),
m_dohEndpoint(dohEndpoint)
{
}
QString DnsGatewayTransport::name() const
{
switch (m_protocol) {
case dns::DnsProtocol::Udp: return QStringLiteral("DNS-UDP");
case dns::DnsProtocol::Tcp: return QStringLiteral("DNS-TCP");
case dns::DnsProtocol::Tls: return QStringLiteral("DNS-DoT");
case dns::DnsProtocol::Https: return QStringLiteral("DNS-DoH");
case dns::DnsProtocol::Quic: return QStringLiteral("DNS-DoQ");
}
return QStringLiteral("DNS");
}
QString DnsGatewayTransport::resolveServerOnce()
{
if (m_resolved.load()) {
return m_resolvedServerIp;
}
QHostAddress addr(m_dnsServer);
if (!addr.isNull()) {
m_resolvedServerIp = m_dnsServer;
} else {
QHostInfo info = QHostInfo::fromName(m_dnsServer);
if (!info.addresses().isEmpty()) {
m_resolvedServerIp = info.addresses().first().toString();
} else {
m_resolvedServerIp = m_dnsServer;
}
}
m_resolved.store(true);
return m_resolvedServerIp;
}
void DnsGatewayTransport::applyKillSwitchAllowlist(const QString &ip)
{
#ifdef AMNEZIA_DESKTOP
if (!m_isStrictKillSwitchEnabled || ip.isEmpty()) {
return;
}
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
QRemoteObjectPendingReply<bool> reply = iface->addKillSwitchAllowedRange(QStringList { ip });
if (!reply.waitForFinished(1000) || !reply.returnValue()) {
qWarning() << "DnsGatewayTransport: addKillSwitchAllowedRange failed for" << ip;
}
});
#else
Q_UNUSED(ip)
#endif
}
amnezia::ErrorCode DnsGatewayTransport::send(const QString &endpointTemplate,
const QByteArray &requestBody,
QByteArray &decryptedResponse,
const DecryptionHook &decryptionHook)
{
QString endpointName = endpointTemplate;
endpointName.remove("%1");
if (endpointName.startsWith(QLatin1String("v1/"))) {
endpointName = endpointName.mid(3);
}
while (endpointName.endsWith(QLatin1Char('/'))) {
endpointName.chop(1);
}
while (endpointName.startsWith(QLatin1Char('/'))) {
endpointName = endpointName.mid(1);
}
qDebug() << "[DNS-Transport]" << name() << "send() endpointTemplate=" << endpointTemplate
<< "endpointName=" << endpointName << "baseDomain=" << m_baseDomain
<< "server=" << m_dnsServer << "port=" << m_port
<< "dohPath=" << m_dohEndpoint << "timeoutMs=" << m_timeoutMsecs
<< "requestBodyBytes=" << requestBody.size();
if (endpointName.isEmpty() || m_baseDomain.isEmpty() || m_dnsServer.isEmpty()) {
qWarning() << "[DNS-Transport] ABORT: empty endpoint/baseDomain/server";
return amnezia::ErrorCode::AmneziaServiceConnectionFailed;
}
const bool needsHostname = (m_protocol == dns::DnsProtocol::Tls
|| m_protocol == dns::DnsProtocol::Https);
QString serverIp = resolveServerOnce();
QString serverForRequest = needsHostname ? m_dnsServer : serverIp;
qDebug() << "[DNS-Transport] resolved server IP=" << serverIp
<< "serverForRequest=" << serverForRequest
<< "needsHostname=" << needsHostname;
applyKillSwitchAllowlist(serverIp);
const QByteArray encrypted = dns::DnsTunnel::send(requestBody,
endpointName,
m_baseDomain,
serverForRequest,
m_protocol,
m_port,
m_timeoutMsecs,
m_dohEndpoint);
qDebug() << "[DNS-Transport] DnsTunnel::send returned" << encrypted.size() << "bytes";
if (encrypted.isEmpty()) {
qWarning() << "[DNS-Transport] DnsTunnel returned empty payload, treat as connection failure";
return amnezia::ErrorCode::AmneziaServiceConnectionFailed;
}
if (!decryptionHook) {
qCritical() << "[DNS-Transport] decryption hook is null";
return amnezia::ErrorCode::ApiConfigDecryptionError;
}
DecryptionResult decrypted = decryptionHook(encrypted);
if (!decrypted.isOk) {
qCritical() << "[DNS-Transport] response decryption failed (encrypted bytes="
<< encrypted.size() << ")";
return amnezia::ErrorCode::ApiConfigDecryptionError;
}
qDebug() << "[DNS-Transport] success, decrypted response bytes=" << decrypted.decrypted.size();
decryptedResponse = decrypted.decrypted;
return amnezia::ErrorCode::NoError;
}
} // namespace amnezia::transport

View File

@@ -1,49 +0,0 @@
#ifndef DNSGATEWAYTRANSPORT_H
#define DNSGATEWAYTRANSPORT_H
#include <QString>
#include <atomic>
#include "dns/dnsResolver.h"
#include "igatewaytransport.h"
namespace amnezia::transport
{
class DnsGatewayTransport : public IGatewayTransport
{
public:
DnsGatewayTransport(dns::DnsProtocol protocol,
const QString &dnsServer,
const QString &baseDomain,
quint16 port,
int timeoutMsecs,
bool isStrictKillSwitchEnabled,
const QString &dohEndpoint = QStringLiteral("/dns-query"));
QString name() const override;
amnezia::ErrorCode send(const QString &endpointTemplate,
const QByteArray &requestBody,
QByteArray &decryptedResponse,
const DecryptionHook &decryptionHook) override;
private:
QString resolveServerOnce();
void applyKillSwitchAllowlist(const QString &ip);
dns::DnsProtocol m_protocol;
QString m_dnsServer;
QString m_baseDomain;
quint16 m_port;
int m_timeoutMsecs;
bool m_isStrictKillSwitchEnabled;
QString m_dohEndpoint;
std::atomic_bool m_resolved{ false };
QString m_resolvedServerIp;
};
} // namespace amnezia::transport
#endif // DNSGATEWAYTRANSPORT_H

View File

@@ -1,345 +0,0 @@
#include "httpGatewayTransport.h"
#include <algorithm>
#include <random>
#include <QCryptographicHash>
#include <QDebug>
#include <QEventLoop>
#include <QHostAddress>
#include <QJsonArray>
#include <QJsonDocument>
#include <QMutexLocker>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QSharedPointer>
#include <QThread>
#include <QUrl>
#include <QUuid>
#include "QBlockCipher.h"
#include "amnezia_application.h"
#include "core/api/apiUtils.h"
#include "core/networkUtilities.h"
#include "utilities.h"
#ifdef AMNEZIA_DESKTOP
#include "core/ipcclient.h"
#endif
#ifdef Q_OS_IOS
#include "platforms/ios/ios_controller.h"
#endif
namespace amnezia::transport
{
QMutex HttpGatewayTransport::s_proxyMutex;
QString HttpGatewayTransport::s_proxyUrl;
namespace
{
constexpr int kProxyHealthTimeoutMsecs = 1000;
constexpr int httpStatusCodeNotFound = 404;
constexpr int httpStatusCodeConflict = 409;
constexpr int httpStatusCodeNotImplemented = 501;
constexpr QLatin1String errorResponsePattern1("No active configuration found for");
constexpr QLatin1String errorResponsePattern2("No non-revoked public key found for");
constexpr QLatin1String errorResponsePattern3("Account not found.");
constexpr QLatin1String updateRequestResponsePattern("client version update is required");
} // namespace
HttpGatewayTransport::HttpGatewayTransport(const QString &endpoint,
bool isDevEnvironment,
int requestTimeoutMsecs,
bool isStrictKillSwitchEnabled)
: m_endpoint(endpoint),
m_isDevEnvironment(isDevEnvironment),
m_requestTimeoutMsecs(requestTimeoutMsecs),
m_isStrictKillSwitchEnabled(isStrictKillSwitchEnabled)
{
}
void HttpGatewayTransport::applyKillSwitchAllowlist(const QString &host)
{
#ifdef AMNEZIA_DESKTOP
if (!m_isStrictKillSwitchEnabled || host.isEmpty()) {
return;
}
const QString ip = NetworkUtilities::getIPAddress(host);
if (ip.isEmpty()) {
return;
}
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
QRemoteObjectPendingReply<bool> reply = iface->addKillSwitchAllowedRange(QStringList { ip });
if (!reply.waitForFinished(1000) || !reply.returnValue()) {
qWarning() << "HttpGatewayTransport: addKillSwitchAllowedRange failed for" << ip;
}
});
#else
Q_UNUSED(host)
#endif
}
HttpGatewayTransport::ReplyOutcome HttpGatewayTransport::doPost(const QString &fullUrl, const QByteArray &requestBody)
{
ReplyOutcome outcome;
#ifdef Q_OS_IOS
IosController::Instance()->requestInetAccess();
QThread::msleep(10);
#endif
QNetworkRequest request;
request.setTransferTimeout(m_requestTimeoutMsecs);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setRawHeader("X-Client-Request-ID",
QUuid::createUuid().toString(QUuid::WithoutBraces).toUtf8());
request.setUrl(fullUrl);
applyKillSwitchAllowlist(QUrl(fullUrl).host());
QNetworkReply *reply = amnApp->networkManager()->post(request, requestBody);
QEventLoop wait;
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
QObject::connect(reply, &QNetworkReply::sslErrors, [&, reply](const QList<QSslError> &errors) {
outcome.sslErrors = errors;
#ifdef AGW_INSECURE_SSL
qWarning() << "[HTTP] sslErrors (ignored, AGW_INSECURE_SSL=1):" << errors;
reply->ignoreSslErrors();
outcome.sslErrors.clear();
#endif
});
wait.exec(QEventLoop::ExcludeUserInputEvents);
outcome.encryptedBody = reply->readAll();
outcome.errorString = reply->errorString();
outcome.networkError = reply->error();
outcome.httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
reply->deleteLater();
return outcome;
}
bool HttpGatewayTransport::shouldBypass(const ReplyOutcome &outcome, const DecryptionResult &decrypted) const
{
if (!outcome.sslErrors.isEmpty()) {
return false;
}
if (!decrypted.isOk) {
return true;
}
int apiHttpStatus = -1;
QJsonDocument jsonDoc = QJsonDocument::fromJson(decrypted.decrypted);
if (jsonDoc.isObject()) {
apiHttpStatus = jsonDoc.object().value("http_status").toInt(-1);
}
if (outcome.networkError == QNetworkReply::NetworkError::OperationCanceledError
|| outcome.networkError == QNetworkReply::NetworkError::TimeoutError) {
return true;
}
if (decrypted.decrypted.contains("html")) {
return true;
}
if (apiHttpStatus == httpStatusCodeNotFound) {
if (decrypted.decrypted.contains(errorResponsePattern1)
|| decrypted.decrypted.contains(errorResponsePattern2)
|| decrypted.decrypted.contains(errorResponsePattern3)) {
return false;
}
return true;
}
if (apiHttpStatus == httpStatusCodeNotImplemented) {
if (decrypted.decrypted.contains(updateRequestResponsePattern)) {
return false;
}
return true;
}
if (apiHttpStatus == httpStatusCodeConflict) {
return false;
}
if (outcome.networkError != QNetworkReply::NetworkError::NoError) {
return true;
}
return false;
}
QStringList HttpGatewayTransport::fetchProxyUrls(const QByteArray &/*serviceHint*/)
{
QStringList baseUrls = m_isDevEnvironment
? QString(DEV_S3_ENDPOINT).split(", ")
: QString(PROD_S3_ENDPOINT).split(", ");
QByteArray rsaKey = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
QStringList proxyStorageUrls;
for (const auto &baseUrl : baseUrls) {
proxyStorageUrls.push_back(baseUrl + "endpoints.json");
}
QNetworkRequest request;
request.setTransferTimeout(m_requestTimeoutMsecs);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
for (const auto &proxyStorageUrl : proxyStorageUrls) {
request.setUrl(proxyStorageUrl);
QNetworkReply *reply = amnApp->networkManager()->get(request);
QEventLoop wait;
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
wait.exec(QEventLoop::ExcludeUserInputEvents);
if (reply->error() != QNetworkReply::NoError) {
reply->deleteLater();
continue;
}
QByteArray encryptedResponseBody = reply->readAll();
reply->deleteLater();
QByteArray responseBody;
try {
if (!m_isDevEnvironment) {
QCryptographicHash hash(QCryptographicHash::Sha512);
hash.addData(rsaKey);
QByteArray hashResult = hash.result().toHex();
QByteArray key = QByteArray::fromHex(hashResult.left(64));
QByteArray iv = QByteArray::fromHex(hashResult.mid(64, 32));
QSimpleCrypto::QBlockCipher blockCipher;
responseBody = blockCipher.decryptAesBlockCipher(QByteArray::fromBase64(encryptedResponseBody), key, iv);
} else {
responseBody = encryptedResponseBody;
}
} catch (...) {
Utils::logException();
qCritical() << "HttpGatewayTransport: error decrypting proxy storage payload";
continue;
}
QJsonArray endpointsArray = QJsonDocument::fromJson(responseBody).array();
QStringList endpoints;
endpoints.reserve(endpointsArray.size());
for (const QJsonValue &endpoint : endpointsArray) {
endpoints.push_back(endpoint.toString());
}
return endpoints;
}
return {};
}
amnezia::ErrorCode HttpGatewayTransport::send(const QString &endpointTemplate,
const QByteArray &requestBody,
QByteArray &decryptedResponse,
const DecryptionHook &decryptionHook)
{
auto buildOutcome = [&](const QString &gatewayBase) {
return doPost(endpointTemplate.arg(gatewayBase), requestBody);
};
auto tryDecrypt = [&](const QByteArray &encrypted) -> DecryptionResult {
if (!decryptionHook) {
DecryptionResult r;
r.decrypted = encrypted;
r.isOk = false;
return r;
}
return decryptionHook(encrypted);
};
QString cachedProxy;
{
QMutexLocker lock(&s_proxyMutex);
cachedProxy = s_proxyUrl;
}
const QString primaryBase = cachedProxy.isEmpty() ? m_endpoint : cachedProxy;
ReplyOutcome outcome = buildOutcome(primaryBase);
DecryptionResult decrypted = tryDecrypt(outcome.encryptedBody);
if (outcome.sslErrors.isEmpty() && shouldBypass(outcome, decrypted)) {
QStringList proxyUrls = fetchProxyUrls(QByteArray());
std::random_device randomDevice;
std::mt19937 generator(randomDevice());
std::shuffle(proxyUrls.begin(), proxyUrls.end(), generator);
bool bypassResolved = false;
if (cachedProxy.isEmpty()) {
QNetworkRequest healthRequest;
healthRequest.setTransferTimeout(kProxyHealthTimeoutMsecs);
healthRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
for (const QString &proxyUrl : std::as_const(proxyUrls)) {
healthRequest.setUrl(proxyUrl + "lmbd-health");
QNetworkReply *reply = amnApp->networkManager()->get(healthRequest);
QEventLoop wait;
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
wait.exec(QEventLoop::ExcludeUserInputEvents);
const auto err = reply->error();
reply->deleteLater();
if (err == QNetworkReply::NoError) {
QMutexLocker lock(&s_proxyMutex);
s_proxyUrl = proxyUrl;
cachedProxy = proxyUrl;
break;
}
}
}
if (!cachedProxy.isEmpty()) {
ReplyOutcome retry = buildOutcome(cachedProxy);
DecryptionResult retryDecrypted = tryDecrypt(retry.encryptedBody);
if (retry.sslErrors.isEmpty() && !shouldBypass(retry, retryDecrypted)) {
outcome = retry;
decrypted = retryDecrypted;
bypassResolved = true;
}
}
if (!bypassResolved) {
for (const QString &proxyUrl : std::as_const(proxyUrls)) {
ReplyOutcome retry = buildOutcome(proxyUrl);
DecryptionResult retryDecrypted = tryDecrypt(retry.encryptedBody);
if (retry.sslErrors.isEmpty() && !shouldBypass(retry, retryDecrypted)) {
{
QMutexLocker lock(&s_proxyMutex);
s_proxyUrl = proxyUrl;
}
outcome = retry;
decrypted = retryDecrypted;
bypassResolved = true;
break;
}
}
}
}
auto errorCode = apiUtils::checkNetworkReplyErrors(outcome.sslErrors,
outcome.errorString,
outcome.networkError,
outcome.httpStatusCode,
decrypted.decrypted);
if (errorCode != amnezia::ErrorCode::NoError) {
return errorCode;
}
if (!decrypted.isOk) {
qCritical() << "HttpGatewayTransport: response decryption failed";
return amnezia::ErrorCode::ApiConfigDecryptionError;
}
decryptedResponse = decrypted.decrypted;
return amnezia::ErrorCode::NoError;
}
} // namespace amnezia::transport

View File

@@ -1,58 +0,0 @@
#ifndef HTTPGATEWAYTRANSPORT_H
#define HTTPGATEWAYTRANSPORT_H
#include <QByteArray>
#include <QList>
#include <QMutex>
#include <QNetworkReply>
#include <QSslError>
#include <QString>
#include <QStringList>
#include "igatewaytransport.h"
namespace amnezia::transport
{
class HttpGatewayTransport : public IGatewayTransport
{
public:
HttpGatewayTransport(const QString &endpoint,
bool isDevEnvironment,
int requestTimeoutMsecs,
bool isStrictKillSwitchEnabled);
QString name() const override { return QStringLiteral("HTTP"); }
amnezia::ErrorCode send(const QString &endpointTemplate,
const QByteArray &requestBody,
QByteArray &decryptedResponse,
const DecryptionHook &decryptionHook) override;
private:
struct ReplyOutcome
{
QByteArray encryptedBody;
QList<QSslError> sslErrors;
QNetworkReply::NetworkError networkError = QNetworkReply::NoError;
QString errorString;
int httpStatusCode = 0;
};
ReplyOutcome doPost(const QString &fullUrl, const QByteArray &requestBody);
void applyKillSwitchAllowlist(const QString &host);
QStringList fetchProxyUrls(const QByteArray &serviceHint);
bool shouldBypass(const ReplyOutcome &outcome, const DecryptionResult &decrypted) const;
QString m_endpoint;
bool m_isDevEnvironment;
int m_requestTimeoutMsecs;
bool m_isStrictKillSwitchEnabled;
static QMutex s_proxyMutex;
static QString s_proxyUrl;
};
} // namespace amnezia::transport
#endif // HTTPGATEWAYTRANSPORT_H

View File

@@ -1,36 +0,0 @@
#ifndef IGATEWAYTRANSPORT_H
#define IGATEWAYTRANSPORT_H
#include <QByteArray>
#include <QString>
#include <functional>
#include "core/defs.h"
namespace amnezia::transport
{
struct DecryptionResult
{
QByteArray decrypted;
bool isOk = false;
};
using DecryptionHook = std::function<DecryptionResult(const QByteArray &encrypted)>;
class IGatewayTransport
{
public:
virtual ~IGatewayTransport() = default;
virtual QString name() const = 0;
virtual amnezia::ErrorCode send(const QString &endpointTemplate,
const QByteArray &requestBody,
QByteArray &decryptedResponse,
const DecryptionHook &decryptionHook) = 0;
};
} // namespace amnezia::transport
#endif // IGATEWAYTRANSPORT_H

View File

@@ -149,7 +149,8 @@ bool Daemon::activate(const InterfaceConfig& config) {
// set routing
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
if (!wgutils()->updateRoutePrefix(ip)) {
logger.debug() << "Routing configuration failed for" << ip.toString();
logger.debug() << "Routing configuration failed for"
<< logger.sensitive(ip.toString());
return false;
}
}
@@ -169,14 +170,11 @@ bool Daemon::maybeUpdateResolvers(const InterfaceConfig& config) {
if ((config.m_hopType == InterfaceConfig::MultiHopExit) ||
(config.m_hopType == InterfaceConfig::SingleHop)) {
QList<QHostAddress> resolvers;
resolvers.append(QHostAddress(config.m_primaryDnsServer));
if (!config.m_secondaryDnsServer.isEmpty()) {
resolvers.append(QHostAddress(config.m_secondaryDnsServer));
}
resolvers.append(QHostAddress(config.m_dnsServer));
// If the DNS is not the Gateway, it's a user defined DNS
// thus, not add any other :)
if (config.m_primaryDnsServer == config.m_serverIpv4Gateway) {
if (config.m_dnsServer == config.m_serverIpv4Gateway) {
resolvers.append(QHostAddress(config.m_serverIpv6Gateway));
}
@@ -282,26 +280,15 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
config.m_serverIpv4Gateway = obj.value("serverIpv4Gateway").toString();
config.m_serverIpv6Gateway = obj.value("serverIpv6Gateway").toString();
if (!obj.contains("primaryDnsServer")) {
config.m_primaryDnsServer = QString();
if (!obj.contains("dnsServer")) {
config.m_dnsServer = QString();
} else {
QJsonValue value = obj.value("primaryDnsServer");
QJsonValue value = obj.value("dnsServer");
if (!value.isString()) {
logger.error() << "dnsServer is not a string";
return false;
}
config.m_primaryDnsServer = value.toString();
}
if (!obj.contains("secondaryDnsServer")) {
config.m_secondaryDnsServer = QString();
} else {
QJsonValue value = obj.value("secondaryDnsServer");
if (!value.isString()) {
logger.error() << "dnsServer is not a string";
return false;
}
config.m_secondaryDnsServer = value.toString();
config.m_dnsServer = value.toString();
}
if (!obj.contains("hopType")) {
@@ -384,9 +371,6 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
if (!parseStringList(obj, "vpnDisabledApps", config.m_vpnDisabledApps)) {
return false;
}
if (!parseStringList(obj, "allowedDnsServers", config.m_allowedDnsServers)) {
return false;
}
config.m_killSwitchEnabled = QVariant(obj.value("killSwitchOption").toString()).toBool();
@@ -405,13 +389,6 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
if (!obj.value("S2").isNull()) {
config.m_responsePacketJunkSize = obj.value("S2").toString();
}
if (!obj.value("S3").isNull()) {
config.m_cookieReplyPacketJunkSize = obj.value("S3").toString();
}
if (!obj.value("S4").isNull()) {
config.m_transportPacketJunkSize = obj.value("S4").toString();
}
if (!obj.value("H1").isNull()) {
config.m_initPacketMagicHeader = obj.value("H1").toString();
}
@@ -425,22 +402,6 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
config.m_transportPacketMagicHeader = obj.value("H4").toString();
}
if (!obj.value("I1").isNull()) {
config.m_specialJunk["I1"] = obj.value("I1").toString();
}
if (!obj.value("I2").isNull()) {
config.m_specialJunk["I2"] = obj.value("I2").toString();
}
if (!obj.value("I3").isNull()) {
config.m_specialJunk["I3"] = obj.value("I3").toString();
}
if (!obj.value("I4").isNull()) {
config.m_specialJunk["I4"] = obj.value("I4").toString();
}
if (!obj.value("I5").isNull()) {
config.m_specialJunk["I5"] = obj.value("I5").toString();
}
return true;
}
@@ -483,7 +444,7 @@ bool Daemon::deactivate(bool emitSignals) {
m_connections.clear();
// Delete the interface
return wgutils()->deleteInterface();
return wgutils()->deleteInterface();
}
QString Daemon::logs() {

View File

@@ -28,8 +28,7 @@ QJsonObject InterfaceConfig::toJson() const {
(m_hopType == InterfaceConfig::SingleHop)) {
json.insert("serverIpv4Gateway", QJsonValue(m_serverIpv4Gateway));
json.insert("serverIpv6Gateway", QJsonValue(m_serverIpv6Gateway));
json.insert("primaryDnsServer", QJsonValue(m_primaryDnsServer));
json.insert("secondaryDnsServer", QJsonValue(m_secondaryDnsServer));
json.insert("dnsServer", QJsonValue(m_dnsServer));
}
QJsonArray allowedIPAddesses;
@@ -49,13 +48,6 @@ QJsonObject InterfaceConfig::toJson() const {
}
json.insert("excludedAddresses", jsExcludedAddresses);
QJsonArray jsAllowedDnsServers;
for (const QString& i : m_allowedDnsServers) {
jsAllowedDnsServers.append(QJsonValue(i));
}
json.insert("allowedDnsServers", jsAllowedDnsServers);
QJsonArray disabledApps;
for (const QString& i : m_vpnDisabledApps) {
disabledApps.append(QJsonValue(i));
@@ -101,15 +93,11 @@ QString InterfaceConfig::toWgConf(const QMap<QString, QString>& extra) const {
out << "MTU = " << m_deviceMTU << "\n";
}
if (!m_primaryDnsServer.isEmpty()) {
QStringList dnsServers;
dnsServers.append(m_primaryDnsServer);
if (!m_secondaryDnsServer.isEmpty()) {
dnsServers.append(m_secondaryDnsServer);
}
if (!m_dnsServer.isNull()) {
QStringList dnsServers(m_dnsServer);
// If the DNS is not the Gateway, it's a user defined DNS
// thus, not add any other :)
if (m_primaryDnsServer == m_serverIpv4Gateway) {
if (m_dnsServer == m_serverIpv4Gateway) {
dnsServers.append(m_serverIpv6Gateway);
}
out << "DNS = " << dnsServers.join(", ") << "\n";
@@ -130,12 +118,6 @@ QString InterfaceConfig::toWgConf(const QMap<QString, QString>& extra) const {
if (!m_responsePacketJunkSize.isNull()) {
out << "S2 = " << m_responsePacketJunkSize << "\n";
}
if (!m_cookieReplyPacketJunkSize.isNull()) {
out << "S3 = " << m_cookieReplyPacketJunkSize << "\n";
}
if (!m_transportPacketJunkSize.isNull()) {
out << "S4 = " << m_transportPacketJunkSize << "\n";
}
if (!m_initPacketMagicHeader.isNull()) {
out << "H1 = " << m_initPacketMagicHeader << "\n";
}
@@ -149,10 +131,6 @@ QString InterfaceConfig::toWgConf(const QMap<QString, QString>& extra) const {
out << "H4 = " << m_transportPacketMagicHeader << "\n";
}
for (const QString& key : m_specialJunk.keys()) {
out << key << " = " << m_specialJunk[key] << "\n";
}
// If any extra config was provided, append it now.
for (const QString& key : extra.keys()) {
out << key << " = " << extra[key] << "\n";

View File

@@ -6,9 +6,8 @@
#define INTERFACECONFIG_H
#include <QList>
#include <QMap>
#include <QString>
#include <QMap>
#include "ipaddress.h"
class QJsonObject;
@@ -32,14 +31,12 @@ class InterfaceConfig {
QString m_serverIpv4AddrIn;
QString m_serverPskKey;
QString m_serverIpv6AddrIn;
QString m_primaryDnsServer;
QString m_secondaryDnsServer;
QString m_dnsServer;
int m_serverPort = 0;
int m_deviceMTU = 1420;
QList<IPAddress> m_allowedIPAddressRanges;
QStringList m_excludedAddresses;
QStringList m_vpnDisabledApps;
QStringList m_allowedDnsServers;
bool m_killSwitchEnabled;
#if defined(MZ_ANDROID) || defined(MZ_IOS)
QString m_installationId;
@@ -50,13 +47,10 @@ class InterfaceConfig {
QString m_junkPacketMaxSize;
QString m_initPacketJunkSize;
QString m_responsePacketJunkSize;
QString m_cookieReplyPacketJunkSize;
QString m_transportPacketJunkSize;
QString m_initPacketMagicHeader;
QString m_responsePacketMagicHeader;
QString m_underloadPacketMagicHeader;
QString m_transportPacketMagicHeader;
QMap<QString, QString> m_specialJunk;
QJsonObject toJson() const;
QString toWgConf(

View File

@@ -1,44 +0,0 @@
{
"primary": "http",
"retry_count": 3,
"timeout_ms": 10000,
"http": {
"enabled": true,
"endpoint": "https://your-gateway.example.com/"
},
"dns_transports": [
{
"type": "udp",
"server": "your-gateway.example.com",
"domain": "gateway.example.com",
"port": 5453
},
{
"type": "tcp",
"server": "your-gateway.example.com",
"domain": "gateway.example.com",
"port": 5453
},
{
"type": "dot",
"server": "your-gateway.example.com",
"domain": "gateway.example.com",
"port": 8853
},
{
"type": "doh",
"server": "your-gateway.example.com",
"domain": "gateway.example.com",
"port": 443,
"path": "/dns-query"
},
{
"type": "doq",
"server": "your-gateway.example.com",
"domain": "gateway.example.com",
"port": 8854
}
]
}

View File

@@ -1,14 +0,0 @@
<svg width="24" height="24" viewBox="0 0 74 74" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_4_34)">
<path d="M55.5 12.3333H18.5C15.0942 12.3333 12.3333 15.0943 12.3333 18.5V55.5C12.3333 58.9058 15.0942 61.6667 18.5 61.6667H55.5C58.9057 61.6667 61.6666 58.9058 61.6666 55.5V18.5C61.6666 15.0943 58.9057 12.3333 55.5 12.3333Z" stroke="#CBCAC8" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M21.5833 24.6667H52.4167" stroke="#CBCAC8" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M21.5833 37H52.4167" stroke="#CBCAC8" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M21.5833 49.3333H40.0833" stroke="#CBCAC8" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="61.5" cy="12.5" r="15" fill="#FBB36B" stroke="#1C1D21" stroke-width="5"/>
</g>
<defs>
<clipPath id="clip0_4_34">
<rect width="74" height="74" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 982 B

View File

@@ -1,8 +0,0 @@
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="#CBCAC8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<!-- Основа газеты -->
<rect x="4" y="4" width="16" height="16" rx="2"/>
<!-- Линии текста -->
<line x1="7" y1="8" x2="17" y2="8"/>
<line x1="7" y1="12" x2="17" y2="12"/>
<line x1="7" y1="16" x2="13" y2="16"/>
</svg>

Before

Width:  |  Height:  |  Size: 410 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.9 KiB

View File

@@ -1,3 +0,0 @@
<svg width="16" height="16" viewBox="0 0 35 35" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="17.5" cy="17.5" r="15" fill="#FBB36B" stroke="#1C1D21" stroke-width="5"/>
</svg>

Before

Width:  |  Height:  |  Size: 188 B

View File

@@ -32,41 +32,17 @@
<false/>
<key>UILaunchStoryboardName</key>
<string>AmneziaVPNLaunchScreen</string>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<true/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneClassName</key>
<string>UIWindowScene</string>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>QIOSWindowSceneDelegate</string>
</dict>
</array>
</dict>
</dict>
<key>UIRequiredDeviceCapabilities</key>
<array/>
<key>UIRequiresFullScreen</key>
<false/>
<true/>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationPortrait</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<array/>
<key>UIUserInterfaceStyle</key>
<string>Light</string>
<key>com.wireguard.ios.app_group_id</key>

View File

@@ -26,21 +26,9 @@ set_target_properties(networkextension PROPERTIES
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2"
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../../Frameworks"
)
if(DEPLOY)
set_target_properties(networkextension PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "distr ios.org.amnezia.AmneziaVPN"
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "dev ios.org.amnezia.AmneziaVPN"
)
else()
set_target_properties(networkextension PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
)
endif()
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
)
set_target_properties(networkextension PROPERTIES
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 682 B

After

Width:  |  Height:  |  Size: 336 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 593 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 593 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 340 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -1,68 +1,6 @@
{
"images": [
{
"idiom": "mac",
"size": "16x16",
"scale": "1x",
"filename": "16.png"
},
{
"idiom": "mac",
"size": "16x16",
"scale": "2x",
"filename": "16@2x.png"
},
{
"idiom": "mac",
"size": "32x32",
"scale": "1x",
"filename": "32.png"
},
{
"idiom": "mac",
"size": "32x32",
"scale": "2x",
"filename": "32@2x.png"
},
{
"idiom": "mac",
"size": "128x128",
"scale": "1x",
"filename": "128.png"
},
{
"idiom": "mac",
"size": "128x128",
"scale": "2x",
"filename": "128@2x.png"
},
{
"idiom": "mac",
"size": "256x256",
"scale": "1x",
"filename": "256.png"
},
{
"idiom": "mac",
"size": "256x256",
"scale": "2x",
"filename": "256@2x.png"
},
{
"idiom": "mac",
"size": "512x512",
"scale": "1x",
"filename": "512.png"
},
{
"idiom": "mac",
"size": "512x512",
"scale": "2x",
"filename": "512@2x.png"
}
],
"info": {
"version": 1,
"author": "xcode"
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleAllowMixedLocalizations</key>
<true/>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSMinimumSystemVersion</key>
<string>${MACOSX_DEPLOYMENT_TARGET}</string>
<key>LSMultipleInstancesProhibited</key>
<true/>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSSupportsAutomaticGraphicsSwitching</key>
<true/>
</dict>
</plist>

View File

@@ -1,172 +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>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSMinimumSystemVersion</key>
<string>${MACOSX_DEPLOYMENT_TARGET}</string>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<key>com.wireguard.ios.app_group_id</key>
<string>group.org.amnezia.AmneziaVPN</string>
<key>NSCameraUsageDescription</key>
<string>Amnezia VPN needs access to the camera for reading QR-codes.</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<false/>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
<key>CFBundleIcons</key>
<dict/>
<key>UTImportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>Amnezia VPN config</string>
<key>UTTypeIconFiles</key>
<array/>
<key>UTTypeIdentifier</key>
<string>org.amnezia.AmneziaVPN.amnezia-config</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>vpn</string>
</array>
<key>public.mime-type</key>
<array>
<string>text/plain</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>WireGuard config</string>
<key>UTTypeIconFiles</key>
<array/>
<key>UTTypeIdentifier</key>
<string>org.amnezia.AmneziaVPN.wireguard-config</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>conf</string>
<string>cfg</string>
</array>
<key>public.mime-type</key>
<array>
<string>text/plain</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>OpenVPN config</string>
<key>UTTypeIconFiles</key>
<array/>
<key>UTTypeIdentifier</key>
<string>org.amnezia.AmneziaVPN.openvpn-config</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>ovpn</string>
</array>
<key>public.mime-type</key>
<array>
<string>text/plain</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>AmneziaVPN backup file</string>
<key>UTTypeIconFiles</key>
<array/>
<key>UTTypeIdentifier</key>
<string>org.amnezia.AmneziaVPN.backup-config</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>backup</string>
</array>
<key>public.mime-type</key>
<array>
<string>text/plain</string>
</array>
</dict>
</dict>
</array>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeName</key>
<string>Amnezia VPN config</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
<key>LSItemContentTypes</key>
<array>
<string>org.amnezia.AmneziaVPN.amnezia-config</string>
<string>org.amnezia.AmneziaVPN.wireguard-config</string>
<string>org.amnezia.AmneziaVPN.openvpn-config</string>
<string>org.amnezia.AmneziaVPN.backup-config</string>
</array>
</dict>
</array>
<key>NSExtensions</key>
<array>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.networkextension.packet-tunnel</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).PacketTunnelProvider</string>
</dict>
</array>
</dict>
</plist>

View File

@@ -2,40 +2,34 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.networking.custom-protocol</key>
<true/>
<key>com.apple.application-identifier</key>
<string>$(DEVELOPMENT_TEAM).$(APP_ID_MACOS)</string>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>app-proxy-provider</string>
<string>packet-tunnel-provider</string>
<string>dns-settings</string>
<string>relay</string>
<string>content-filter-provider</string>
<string>dns-proxy</string>
</array>
<key>com.apple.developer.system-extension.install</key>
<true/>
<key>com.apple.developer.networking.vpn.api</key>
<array>
<string>allow-vpn</string>
</array>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.org.amnezia.AmneziaVPN</string>
</array>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>$(DEVELOPMENT_TEAM).*</string>
</array>
<key>com.apple.developer.team-identifier</key>
<string>$(DEVELOPMENT_TEAM)</string>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>$(DEVELOPMENT_TEAM).$(GROUP_ID_MACOS)</string>
</array>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
</dict>
</plist>

View File

@@ -2,30 +2,41 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.networking.custom-protocol</key>
<true/>
<key>com.apple.application-identifier</key>
<string>$(DEVELOPMENT_TEAM).$(NETEXT_ID_MACOS)</string>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>dns-settings</string>
<string>relay</string>
<string>packet-tunnel-provider</string>
<string>content-filter-provider</string>
<string>dns-proxy</string>
<string>app-proxy-provider</string>
</array>
<key>com.apple.developer.networking.vpn.api</key>
<key>keychain-access-groups</key>
<array>
<string>allow-vpn</string>
<string>$(DEVELOPMENT_TEAM).*</string>
</array>
<key>com.apple.developer.team-identifier</key>
<string>$(DEVELOPMENT_TEAM)</string>
<key>com.apple.developer.system-extension.install</key>
<true/>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.org.amnezia.AmneziaVPN</string>
<string>$(DEVELOPMENT_TEAM).$(GROUP_ID_MACOS)</string>
</array>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.private.network.socket-delegate</key>
<true/>
</dict>
</plist>

View File

@@ -1,138 +0,0 @@
enable_language(Swift)
message("Client message >> macos build >> AmneziaVPNNetworkExtension")
set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../..)
add_executable(AmneziaVPNNetworkExtension)
message("executable_path is: @executable_path/../../Frameworks")
set_target_properties(AmneziaVPNNetworkExtension PROPERTIES
XCODE_PRODUCT_TYPE com.apple.product-type.app-extension
# MACOSX_BUNDLE YES
BUNDLE_EXTENSION appex
MACOSX_BUNDLE_SHORT_VERSION_STRING "${APPLE_PROJECT_VERSION}"
MACOSX_BUNDLE_INFO_STRING "AmneziaVPNNetworkExtension"
MACOSX_BUNDLE_BUNDLE_NAME "AmneziaVPNNetworkExtension"
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}.network-extension"
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_NAME "${BUILD_IOS_APP_IDENTIFIER}.network-extension"
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS ${CMAKE_CURRENT_SOURCE_DIR}/AmneziaVPNNetworkExtension.entitlements
XCODE_ATTRIBUTE_MARKETING_VERSION "${APP_MAJOR_VERSION}"
XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION "${BUILD_ID}"
XCODE_ATTRIBUTE_PRODUCT_NAME "AmneziaVPNNetworkExtension"
XCODE_ATTRIBUTE_APPLICATION_EXTENSION_API_ONLY "YES"
XCODE_ATTRIBUTE_ENABLE_BITCODE "NO"
XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET "11.0"
XCODE_ATTRIBUTE_INFOPLIST_FILE ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../../../../Frameworks @loader_path/../../../../Frameworks"
)
if(DEPLOY)
message("DEPLOY is ON")
set_target_properties(AmneziaVPNNetworkExtension PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "distr macos.org.amnezia.amneziaVPN.NE"
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "dev macos.org.amnezia.amneziaVPN.NE"
)
else()
set_target_properties(AmneziaVPNNetworkExtension PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
)
endif()
set_target_properties(AmneziaVPNNetworkExtension 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("AmneziaVPNNetworkExtension" 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)
# Set the root directory
set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../..)
target_link_libraries(AmneziaVPNNetworkExtension PRIVATE ${FW_LIBRESOLV})
target_compile_options(AmneziaVPNNetworkExtension PRIVATE -DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\")
target_compile_options(AmneziaVPNNetworkExtension PRIVATE -DNETWORK_EXTENSION=1)
set(WG_APPLE_SOURCE_DIR ${CLIENT_ROOT_DIR}/3rd/amneziawg-apple/Sources)
message("WG_APPLE_SOURCE_DIR is: ${WG_APPLE_SOURCE_DIR}")
message("CLIENT_ROOT_DIR is: ${CLIENT_ROOT_DIR}")
target_sources(AmneziaVPNNetworkExtension 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/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
${WG_APPLE_SOURCE_DIR}/WireGuardKit/Endpoint.swift
${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/HevSocksTunnel.swift
${CLIENT_ROOT_DIR}/platforms/ios/NELogController.swift
${CLIENT_ROOT_DIR}/platforms/ios/Log.swift
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider.swift
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+WireGuard.swift
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+OpenVPN.swift
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+Xray.swift
${CLIENT_ROOT_DIR}/platforms/ios/WGConfig.swift
${CLIENT_ROOT_DIR}/platforms/ios/iosglue.mm
${CLIENT_ROOT_DIR}/platforms/ios/XrayConfig.swift
)
target_sources(AmneziaVPNNetworkExtension PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/PrivacyInfo.xcprivacy
)
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})
target_link_libraries(AmneziaVPNNetworkExtension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/wireguard/macos/universal2/libwg-go.a)
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)
target_include_directories(AmneziaVPNNetworkExtension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/xray/HevSocks5Tunnel.xcframework/macos-arm64_x86_64/Headers)

View File

@@ -3,32 +3,27 @@
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>AmneziaVPNNetworkExtension</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>org.amnezia.AmneziaVPN.network-extension</string>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>AmneziaVPNNetworkExtension</string>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>${APPLE_PROJECT_VERSION}</string>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>${CMAKE_PROJECT_VERSION_TWEAK}</string>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSMinimumSystemVersion</key>
<string>${CMAKE_OSX_DEPLOYMENT_TARGET}</string>
<key>CFBundleDisplayName</key>
<string>AmneziaVPNNetworkExtension</string>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
@@ -36,11 +31,5 @@
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).PacketTunnelProvider</string>
</dict>
<key>com.wireguard.ios.app_group_id</key>
<string>group.org.amnezia.AmneziaVPN</string>
<key>com.wireguard.macos.app_group_id</key>
<string>${BUILD_VPN_DEVELOPMENT_TEAM}.group.org.amnezia.AmneziaVPN</string>
</dict>
</plist>

View File

@@ -1,25 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>1C8F.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
</array>
</dict>
</plist>

View File

@@ -1,10 +1,10 @@
/* 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/. */
#include "macos/gobridge/wireguard.h"
#include "wireguard-go-version.h"
#include "3rd/amneziawg-apple/Sources/WireGuardKitGo/wireguard.h"
#include "3rd/amneziawg-apple/Sources/WireGuardKitC/WireGuardKitC.h"
#include "3rd/awg-apple/Sources/WireGuardKitC/WireGuardKitC.h"
#include <stdbool.h>
#include <stdint.h>
@@ -23,8 +23,3 @@ bool key_from_hex(uint8_t key[WG_KEY_LEN], const char* hex);
bool key_eq(const uint8_t key1[WG_KEY_LEN], const uint8_t key2[WG_KEY_LEN]);
void write_msg_to_log(const char* tag, const char* msg);
// init function definition in C
void hev_socks5_tunnel_quit(void);
// Updated function definition in C
int hev_socks5_tunnel_main(const char* configFile, int fd);

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