mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-23 02:00:20 +07:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8c5f5b2f23 | |||
| ce37146edf | |||
| fa3dd7d553 | |||
| b9021d6581 |
+54
-141
@@ -10,10 +10,10 @@ env:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
Build-Linux-Ubuntu:
|
Build-Linux-Ubuntu:
|
||||||
runs-on: 4-core
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
env:
|
env:
|
||||||
QT_VERSION: 6.8.3
|
QT_VERSION: 6.6.2
|
||||||
QIF_VERSION: 4.7
|
QIF_VERSION: 4.7
|
||||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||||
@@ -30,15 +30,13 @@ jobs:
|
|||||||
version: ${{ env.QT_VERSION }}
|
version: ${{ env.QT_VERSION }}
|
||||||
host: 'linux'
|
host: 'linux'
|
||||||
target: 'desktop'
|
target: 'desktop'
|
||||||
arch: 'linux_gcc_64'
|
arch: 'gcc_64'
|
||||||
modules: 'qtremoteobjects qt5compat qtshadertools'
|
modules: 'qtremoteobjects qt5compat qtshadertools'
|
||||||
dir: ${{ runner.temp }}
|
dir: ${{ runner.temp }}
|
||||||
setup-python: 'true'
|
setup-python: 'true'
|
||||||
tools: 'tools_ifw'
|
tools: 'tools_ifw'
|
||||||
set-env: 'true'
|
set-env: 'true'
|
||||||
aqtversion: '==3.3.0'
|
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||||
py7zrversion: '==0.22.*'
|
|
||||||
extra: '--base ${{ env.QT_MIRROR }}'
|
|
||||||
|
|
||||||
- name: 'Get sources'
|
- name: 'Get sources'
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -46,15 +44,8 @@ jobs:
|
|||||||
submodules: 'true'
|
submodules: 'true'
|
||||||
fetch-depth: 10
|
fetch-depth: 10
|
||||||
|
|
||||||
- name: 'Get version from CMakeLists.txt'
|
- name: 'Setup ccache'
|
||||||
id: get_version
|
uses: hendrikmuhs/ccache-action@v1.2
|
||||||
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'
|
- name: 'Build project'
|
||||||
run: |
|
run: |
|
||||||
@@ -64,13 +55,13 @@ jobs:
|
|||||||
bash deploy/build_linux.sh
|
bash deploy/build_linux.sh
|
||||||
|
|
||||||
- name: 'Pack installer'
|
- 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'
|
- name: 'Upload installer artifact'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: AmneziaVPN_${{ env.VERSION }}_linux_x64.tar.zip
|
name: AmneziaVPN_Linux_installer.tar
|
||||||
path: deploy/AmneziaVPN_${{ env.VERSION }}_linux_x64.tar.zip
|
path: deploy/AmneziaVPN_Linux_Installer.tar
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|
||||||
- name: 'Upload unpacked artifact'
|
- name: 'Upload unpacked artifact'
|
||||||
@@ -93,7 +84,7 @@ jobs:
|
|||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
|
|
||||||
env:
|
env:
|
||||||
QT_VERSION: 6.10.1
|
QT_VERSION: 6.6.2
|
||||||
QIF_VERSION: 4.7
|
QIF_VERSION: 4.7
|
||||||
BUILD_ARCH: 64
|
BUILD_ARCH: 64
|
||||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||||
@@ -111,16 +102,8 @@ jobs:
|
|||||||
submodules: 'true'
|
submodules: 'true'
|
||||||
fetch-depth: 10
|
fetch-depth: 10
|
||||||
|
|
||||||
- name: 'Get version from CMakeLists.txt'
|
- name: 'Setup ccache'
|
||||||
id: get_version
|
uses: hendrikmuhs/ccache-action@v1.2
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
VERSION=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\)/\1/')
|
|
||||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
|
||||||
echo "Version: $VERSION"
|
|
||||||
|
|
||||||
# - name: 'Setup ccache'
|
|
||||||
# uses: hendrikmuhs/ccache-action@v1.2
|
|
||||||
|
|
||||||
- name: 'Install Qt'
|
- name: 'Install Qt'
|
||||||
uses: jurplel/install-qt-action@v3
|
uses: jurplel/install-qt-action@v3
|
||||||
@@ -128,62 +111,32 @@ jobs:
|
|||||||
version: ${{ env.QT_VERSION }}
|
version: ${{ env.QT_VERSION }}
|
||||||
host: 'windows'
|
host: 'windows'
|
||||||
target: 'desktop'
|
target: 'desktop'
|
||||||
arch: 'win64_msvc2022_64'
|
arch: 'win64_msvc2019_64'
|
||||||
modules: 'qtremoteobjects qt5compat qtshadertools'
|
modules: 'qtremoteobjects qt5compat qtshadertools'
|
||||||
dir: ${{ runner.temp }}
|
dir: ${{ runner.temp }}
|
||||||
setup-python: 'true'
|
setup-python: 'true'
|
||||||
tools: 'tools_ifw'
|
tools: 'tools_ifw'
|
||||||
set-env: 'true'
|
set-env: 'true'
|
||||||
aqtversion: '==3.3.0'
|
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||||
py7zrversion: '==0.22.*'
|
|
||||||
extra: '--base ${{ env.QT_MIRROR }}'
|
|
||||||
|
|
||||||
- name: 'Setup mvsc'
|
- name: 'Setup mvsc'
|
||||||
uses: ilammy/msvc-dev-cmd@v1
|
uses: ilammy/msvc-dev-cmd@v1
|
||||||
with:
|
with:
|
||||||
arch: 'x64'
|
arch: 'x64'
|
||||||
|
|
||||||
- name: 'Setup .NET SDK'
|
|
||||||
uses: actions/setup-dotnet@v4
|
|
||||||
with:
|
|
||||||
dotnet-version: '8.0.x'
|
|
||||||
|
|
||||||
- name: 'Install WiX Toolset'
|
|
||||||
shell: powershell
|
|
||||||
run: |
|
|
||||||
dotnet tool install --global wix --version 4.0.6
|
|
||||||
wix extension add -g WixToolset.UI.wixext/4.0.6
|
|
||||||
wix extension add -g WixToolset.Util.wixext/4.0.6
|
|
||||||
wix extension list -g
|
|
||||||
$wixBinDir = Join-Path $env:USERPROFILE ".dotnet\tools"
|
|
||||||
echo "WIX_BIN_DIR=$wixBinDir" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
|
||||||
|
|
||||||
- name: 'Build project'
|
- name: 'Build project'
|
||||||
shell: cmd
|
shell: cmd
|
||||||
run: |
|
run: |
|
||||||
set BUILD_ARCH=${{ env.BUILD_ARCH }}
|
set BUILD_ARCH=${{ env.BUILD_ARCH }}
|
||||||
set QT_BIN_DIR="${{ runner.temp }}\\Qt\\${{ env.QT_VERSION }}\\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 QIF_BIN_DIR="${{ runner.temp }}\\Qt\\Tools\\QtInstallerFramework\\${{ env.QIF_VERSION }}\\bin"
|
||||||
set WIX_BIN_DIR=%USERPROFILE%\.dotnet\tools
|
|
||||||
call deploy\\build_windows.bat
|
call deploy\\build_windows.bat
|
||||||
|
|
||||||
- name: 'Rename Windows installer'
|
|
||||||
shell: cmd
|
|
||||||
run: |
|
|
||||||
copy AmneziaVPN_x${{ env.BUILD_ARCH }}.exe AmneziaVPN_%VERSION%_x64.exe
|
|
||||||
|
|
||||||
- name: 'Upload installer artifact'
|
- name: 'Upload installer artifact'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: AmneziaVPN_${{ env.VERSION }}_x64.exe
|
name: AmneziaVPN_Windows_installer
|
||||||
path: AmneziaVPN_${{ env.VERSION }}_x64.exe
|
path: AmneziaVPN_x${{ env.BUILD_ARCH }}.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
|
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|
||||||
- name: 'Upload unpacked artifact'
|
- name: 'Upload unpacked artifact'
|
||||||
@@ -196,10 +149,10 @@ jobs:
|
|||||||
# ------------------------------------------------------
|
# ------------------------------------------------------
|
||||||
|
|
||||||
Build-iOS:
|
Build-iOS:
|
||||||
runs-on: macos-latest
|
runs-on: macos-13
|
||||||
|
|
||||||
env:
|
env:
|
||||||
QT_VERSION: 6.10.1
|
QT_VERSION: 6.6.2
|
||||||
CC: cc
|
CC: cc
|
||||||
CXX: c++
|
CXX: c++
|
||||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||||
@@ -214,7 +167,7 @@ jobs:
|
|||||||
- name: 'Setup xcode'
|
- name: 'Setup xcode'
|
||||||
uses: maxim-lobanov/setup-xcode@v1
|
uses: maxim-lobanov/setup-xcode@v1
|
||||||
with:
|
with:
|
||||||
xcode-version: '26.1'
|
xcode-version: '15.2'
|
||||||
|
|
||||||
- name: 'Install desktop Qt'
|
- name: 'Install desktop Qt'
|
||||||
uses: jurplel/install-qt-action@v3
|
uses: jurplel/install-qt-action@v3
|
||||||
@@ -258,8 +211,8 @@ jobs:
|
|||||||
submodules: 'true'
|
submodules: 'true'
|
||||||
fetch-depth: 10
|
fetch-depth: 10
|
||||||
|
|
||||||
# - name: 'Setup ccache'
|
- name: 'Setup ccache'
|
||||||
# uses: hendrikmuhs/ccache-action@v1.2
|
uses: hendrikmuhs/ccache-action@v1.2
|
||||||
|
|
||||||
- name: 'Install dependencies'
|
- name: 'Install dependencies'
|
||||||
run: pip install jsonschema jinja2
|
run: pip install jsonschema jinja2
|
||||||
@@ -350,8 +303,8 @@ jobs:
|
|||||||
submodules: 'true'
|
submodules: 'true'
|
||||||
fetch-depth: 10
|
fetch-depth: 10
|
||||||
|
|
||||||
# - name: 'Setup ccache'
|
- name: 'Setup ccache'
|
||||||
# uses: hendrikmuhs/ccache-action@v1.2
|
uses: hendrikmuhs/ccache-action@v1.2
|
||||||
|
|
||||||
- name: 'Build project'
|
- name: 'Build project'
|
||||||
run: |
|
run: |
|
||||||
@@ -378,7 +331,7 @@ jobs:
|
|||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
|
|
||||||
env:
|
env:
|
||||||
QT_VERSION: 6.10.1
|
QT_VERSION: 6.8.0
|
||||||
|
|
||||||
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
|
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
|
||||||
|
|
||||||
@@ -408,7 +361,7 @@ jobs:
|
|||||||
xcode-version: '16.2.0'
|
xcode-version: '16.2.0'
|
||||||
|
|
||||||
- name: 'Install Qt'
|
- name: 'Install Qt'
|
||||||
uses: jurplel/install-qt-action@v4
|
uses: jurplel/install-qt-action@v3
|
||||||
with:
|
with:
|
||||||
version: ${{ env.QT_VERSION }}
|
version: ${{ env.QT_VERSION }}
|
||||||
host: 'mac'
|
host: 'mac'
|
||||||
@@ -418,9 +371,8 @@ jobs:
|
|||||||
dir: ${{ runner.temp }}
|
dir: ${{ runner.temp }}
|
||||||
setup-python: 'true'
|
setup-python: 'true'
|
||||||
set-env: 'true'
|
set-env: 'true'
|
||||||
aqtversion: '==3.3.0'
|
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||||
py7zrversion: '==0.22.*'
|
|
||||||
extra: '--base ${{ env.QT_MIRROR }}'
|
|
||||||
|
|
||||||
- name: 'Get sources'
|
- name: 'Get sources'
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -428,32 +380,19 @@ jobs:
|
|||||||
submodules: 'true'
|
submodules: 'true'
|
||||||
fetch-depth: 10
|
fetch-depth: 10
|
||||||
|
|
||||||
- name: 'Get version from CMakeLists.txt'
|
- name: 'Setup ccache'
|
||||||
id: get_version
|
uses: hendrikmuhs/ccache-action@v1.2
|
||||||
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'
|
- name: 'Build project'
|
||||||
run: |
|
run: |
|
||||||
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin"
|
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin"
|
||||||
bash deploy/build_macos.sh -n
|
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'
|
- name: 'Upload installer artifact'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: AmneziaVPN_${{ env.VERSION }}_macos.zip
|
name: AmneziaVPN_MacOS_installer
|
||||||
path: deploy/AmneziaVPN_${{ env.VERSION }}_macos.zip
|
path: deploy/build/pkg/AmneziaVPN.pkg
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|
||||||
- name: 'Upload unpacked artifact'
|
- name: 'Upload unpacked artifact'
|
||||||
@@ -467,7 +406,7 @@ jobs:
|
|||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
|
|
||||||
env:
|
env:
|
||||||
QT_VERSION: 6.10.1
|
QT_VERSION: 6.8.3
|
||||||
|
|
||||||
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
|
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
|
||||||
|
|
||||||
@@ -487,31 +426,21 @@ jobs:
|
|||||||
- name: 'Setup xcode'
|
- name: 'Setup xcode'
|
||||||
uses: maxim-lobanov/setup-xcode@v1
|
uses: maxim-lobanov/setup-xcode@v1
|
||||||
with:
|
with:
|
||||||
xcode-version: '26.1'
|
xcode-version: '16.2.0'
|
||||||
|
|
||||||
- name: 'Install desktop Qt'
|
- name: 'Install Qt'
|
||||||
uses: jurplel/install-qt-action@v3
|
uses: jurplel/install-qt-action@v3
|
||||||
with:
|
with:
|
||||||
version: ${{ env.QT_VERSION }}
|
version: ${{ env.QT_VERSION }}
|
||||||
host: 'mac'
|
host: 'mac'
|
||||||
target: 'desktop'
|
target: 'desktop'
|
||||||
modules: 'qtremoteobjects qt5compat qtshadertools qtmultimedia'
|
|
||||||
arch: 'clang_64'
|
arch: 'clang_64'
|
||||||
|
modules: 'qtremoteobjects qt5compat qtshadertools'
|
||||||
dir: ${{ runner.temp }}
|
dir: ${{ runner.temp }}
|
||||||
|
setup-python: 'true'
|
||||||
set-env: 'true'
|
set-env: 'true'
|
||||||
extra: '--base ${{ env.QT_MIRROR }}'
|
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||||
|
|
||||||
- name: 'Install go'
|
|
||||||
uses: actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
go-version: '1.24'
|
|
||||||
cache: false
|
|
||||||
|
|
||||||
- name: 'Setup gomobile'
|
|
||||||
run: |
|
|
||||||
export PATH=$PATH:~/go/bin
|
|
||||||
go install golang.org/x/mobile/cmd/gomobile@latest
|
|
||||||
gomobile init
|
|
||||||
|
|
||||||
- name: 'Get sources'
|
- name: 'Get sources'
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -519,8 +448,8 @@ jobs:
|
|||||||
submodules: 'true'
|
submodules: 'true'
|
||||||
fetch-depth: 10
|
fetch-depth: 10
|
||||||
|
|
||||||
# - name: 'Setup ccache'
|
- name: 'Setup ccache'
|
||||||
# uses: hendrikmuhs/ccache-action@v1.2
|
uses: hendrikmuhs/ccache-action@v1.2
|
||||||
|
|
||||||
- name: 'Build project'
|
- name: 'Build project'
|
||||||
run: |
|
run: |
|
||||||
@@ -537,11 +466,11 @@ jobs:
|
|||||||
# ------------------------------------------------------
|
# ------------------------------------------------------
|
||||||
|
|
||||||
Build-Android:
|
Build-Android:
|
||||||
runs-on: 4-core
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
env:
|
env:
|
||||||
ANDROID_BUILD_PLATFORM: android-36
|
ANDROID_BUILD_PLATFORM: android-34
|
||||||
QT_VERSION: 6.10.1
|
QT_VERSION: 6.7.3
|
||||||
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
||||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||||
@@ -622,22 +551,15 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
submodules: 'true'
|
submodules: 'true'
|
||||||
|
|
||||||
- name: 'Get version from CMakeLists.txt'
|
- name: 'Setup ccache'
|
||||||
id: get_version
|
uses: hendrikmuhs/ccache-action@v1.2
|
||||||
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 Java'
|
- name: 'Setup Java'
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin'
|
distribution: 'temurin'
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
# cache: 'gradle'
|
cache: 'gradle'
|
||||||
|
|
||||||
- name: 'Setup Android NDK'
|
- name: 'Setup Android NDK'
|
||||||
id: setup-ndk
|
id: setup-ndk
|
||||||
@@ -662,44 +584,35 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: ./deploy/build_android.sh --aab --apk all --build-platform ${{ env.ANDROID_BUILD_PLATFORM }}
|
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'
|
- name: 'Upload x86_64 apk'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: AmneziaVPN_${{ env.VERSION }}_android9+_x86_64.apk
|
name: AmneziaVPN-android-x86_64
|
||||||
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_x86_64.apk
|
path: deploy/build/AmneziaVPN-x86_64-release.apk
|
||||||
compression-level: 0
|
compression-level: 0
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|
||||||
- name: 'Upload x86 apk'
|
- name: 'Upload x86 apk'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: AmneziaVPN_${{ env.VERSION }}_android9+_x86.apk
|
name: AmneziaVPN-android-x86
|
||||||
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_x86.apk
|
path: deploy/build/AmneziaVPN-x86-release.apk
|
||||||
compression-level: 0
|
compression-level: 0
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|
||||||
- name: 'Upload arm64-v8a apk'
|
- name: 'Upload arm64-v8a apk'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: AmneziaVPN_${{ env.VERSION }}_android9+_arm64-v8a.apk
|
name: AmneziaVPN-android-arm64-v8a
|
||||||
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_arm64-v8a.apk
|
path: deploy/build/AmneziaVPN-arm64-v8a-release.apk
|
||||||
compression-level: 0
|
compression-level: 0
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|
||||||
- name: 'Upload armeabi-v7a apk'
|
- name: 'Upload armeabi-v7a apk'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: AmneziaVPN_${{ env.VERSION }}_android9+_armeabi-v7a.apk
|
name: AmneziaVPN-android-armeabi-v7a
|
||||||
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_armeabi-v7a.apk
|
path: deploy/build/AmneziaVPN-armeabi-v7a-release.apk
|
||||||
compression-level: 0
|
compression-level: 0
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
# User settings
|
# User settings
|
||||||
*.user
|
*.user
|
||||||
|
|
||||||
# Gateway configs (contains sensitive endpoints)
|
|
||||||
gateway.json
|
|
||||||
client/gateway.json
|
|
||||||
macOSPackage/
|
macOSPackage/
|
||||||
AmneziaVPN.dmg
|
AmneziaVPN.dmg
|
||||||
AmneziaVPN.exe
|
AmneziaVPN.exe
|
||||||
@@ -13,7 +9,6 @@ deploy/build_32/*
|
|||||||
deploy/build_64/*
|
deploy/build_64/*
|
||||||
winbuild*.bat
|
winbuild*.bat
|
||||||
.cache/
|
.cache/
|
||||||
.vscode/
|
|
||||||
|
|
||||||
|
|
||||||
# Qt-es
|
# Qt-es
|
||||||
|
|||||||
+2
-35
@@ -1,7 +1,7 @@
|
|||||||
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||||
|
|
||||||
set(PROJECT AmneziaVPN)
|
set(PROJECT AmneziaVPN)
|
||||||
set(AMNEZIAVPN_VERSION 4.8.12.8)
|
set(AMNEZIAVPN_VERSION 4.8.10.0)
|
||||||
|
|
||||||
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
|
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
|
||||||
DESCRIPTION "AmneziaVPN"
|
DESCRIPTION "AmneziaVPN"
|
||||||
@@ -12,7 +12,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
|
|||||||
set(RELEASE_DATE "${CURRENT_DATE}")
|
set(RELEASE_DATE "${CURRENT_DATE}")
|
||||||
|
|
||||||
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||||
set(APP_ANDROID_VERSION_CODE 2104)
|
set(APP_ANDROID_VERSION_CODE 2093)
|
||||||
|
|
||||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
set(MZ_PLATFORM_NAME "linux")
|
set(MZ_PLATFORM_NAME "linux")
|
||||||
@@ -49,36 +49,3 @@ if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
|
|||||||
|
|
||||||
include(${CMAKE_SOURCE_DIR}/deploy/installer/config.cmake)
|
include(${CMAKE_SOURCE_DIR}/deploy/installer/config.cmake)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(AMNEZIA_STAGE_DIR "${CMAKE_BINARY_DIR}/stage")
|
|
||||||
|
|
||||||
if(WIN32 AND NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
|
|
||||||
file(TO_CMAKE_PATH "${AMNEZIA_STAGE_DIR}" AMNEZIA_STAGE_DIR_CMAKE)
|
|
||||||
|
|
||||||
set(CPACK_GENERATOR "WIX")
|
|
||||||
set(CPACK_WIX_VERSION 4)
|
|
||||||
set(CPACK_PACKAGE_NAME "AmneziaVPN")
|
|
||||||
set(CPACK_PACKAGE_VENDOR "AmneziaVPN")
|
|
||||||
set(CPACK_PACKAGE_VERSION ${AMNEZIAVPN_VERSION})
|
|
||||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "AmneziaVPN client")
|
|
||||||
set(CPACK_PACKAGE_INSTALL_DIRECTORY "AmneziaVPN")
|
|
||||||
set(CPACK_PACKAGE_DIRECTORY "${CMAKE_BINARY_DIR}")
|
|
||||||
set(CPACK_PACKAGE_EXECUTABLES "AmneziaVPN" "AmneziaVPN")
|
|
||||||
set(CPACK_WIX_UPGRADE_GUID "{2D55AC62-96D6-4692-8C05-0D85BBF95485}")
|
|
||||||
set(CPACK_WIX_PRODUCT_ICON "${CMAKE_SOURCE_DIR}/client/images/app.ico")
|
|
||||||
|
|
||||||
# WiX patches
|
|
||||||
set(_AMNEZIA_WIX_PATCH_SERVICE "${CMAKE_SOURCE_DIR}/deploy/installer/wix/service_install_patch.xml")
|
|
||||||
set(_AMNEZIA_WIX_PATCH_CLOSE_APP "${CMAKE_SOURCE_DIR}/deploy/installer/wix/close_client_patch.xml")
|
|
||||||
file(TO_CMAKE_PATH "${_AMNEZIA_WIX_PATCH_SERVICE}" _AMNEZIA_WIX_PATCH_SERVICE_CMAKE)
|
|
||||||
file(TO_CMAKE_PATH "${_AMNEZIA_WIX_PATCH_CLOSE_APP}" _AMNEZIA_WIX_PATCH_CLOSE_APP_CMAKE)
|
|
||||||
set(CPACK_WIX_PATCH_FILE "${_AMNEZIA_WIX_PATCH_SERVICE_CMAKE};${_AMNEZIA_WIX_PATCH_CLOSE_APP_CMAKE}")
|
|
||||||
|
|
||||||
# WiX v4 Util extension for CloseApplication + namespace for util
|
|
||||||
set(CPACK_WIX_EXTENSIONS "${CPACK_WIX_EXTENSIONS};WixToolset.Util.wixext")
|
|
||||||
set(CPACK_WIX_CUSTOM_XMLNS "util=http://wixtoolset.org/schemas/v4/wxs/util")
|
|
||||||
|
|
||||||
set(CPACK_INSTALLED_DIRECTORIES "${AMNEZIA_STAGE_DIR_CMAKE};/")
|
|
||||||
|
|
||||||
include(CPack)
|
|
||||||
endif()
|
|
||||||
|
|||||||
+1
-1
Submodule client/3rd-prebuilt updated: 51bb4703a4...02a00ee00d
Vendored
+1
-1
Submodule client/3rd/amneziawg-apple updated: cf63135331...811af0a83b
+1
-22
@@ -33,21 +33,6 @@ add_definitions(-DDEV_S3_ENDPOINT="$ENV{DEV_S3_ENDPOINT}")
|
|||||||
add_definitions(-DFREE_V2_ENDPOINT="$ENV{FREE_V2_ENDPOINT}")
|
add_definitions(-DFREE_V2_ENDPOINT="$ENV{FREE_V2_ENDPOINT}")
|
||||||
add_definitions(-DPREM_V1_ENDPOINT="$ENV{PREM_V1_ENDPOINT}")
|
add_definitions(-DPREM_V1_ENDPOINT="$ENV{PREM_V1_ENDPOINT}")
|
||||||
|
|
||||||
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))
|
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||||
set(PACKAGES ${PACKAGES} Widgets)
|
set(PACKAGES ${PACKAGES} Widgets)
|
||||||
endif()
|
endif()
|
||||||
@@ -71,7 +56,7 @@ target_include_directories(${PROJECT} PUBLIC
|
|||||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
|
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
|
||||||
)
|
)
|
||||||
|
|
||||||
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
|
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||||
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep)
|
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep)
|
||||||
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_interface.rep)
|
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_interface.rep)
|
||||||
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_tun2socks.rep)
|
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_tun2socks.rep)
|
||||||
@@ -244,9 +229,3 @@ endif()
|
|||||||
|
|
||||||
target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC} ${I18NQRC})
|
target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC} ${I18NQRC})
|
||||||
qt_finalize_target(${PROJECT})
|
qt_finalize_target(${PROJECT})
|
||||||
|
|
||||||
option(BUILD_TESTS "Build transport integration tests" OFF)
|
|
||||||
if(BUILD_TESTS)
|
|
||||||
enable_testing()
|
|
||||||
add_subdirectory(tests)
|
|
||||||
endif()
|
|
||||||
|
|||||||
@@ -13,8 +13,6 @@
|
|||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QTranslator>
|
#include <QTranslator>
|
||||||
#include <QEvent>
|
#include <QEvent>
|
||||||
#include <QDir>
|
|
||||||
#include <QSettings>
|
|
||||||
|
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "ui/controllers/pageController.h"
|
#include "ui/controllers/pageController.h"
|
||||||
@@ -27,15 +25,10 @@
|
|||||||
#include <QtQuick/QQuickWindow> // for QQuickWindow
|
#include <QtQuick/QQuickWindow> // for QQuickWindow
|
||||||
#include <QWindow> // for qobject_cast<QWindow*>
|
#include <QWindow> // for qobject_cast<QWindow*>
|
||||||
|
|
||||||
bool AmneziaApplication::m_forceQuit = false;
|
|
||||||
|
|
||||||
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv),
|
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv),
|
||||||
m_optAutostart({QStringLiteral("a"), QStringLiteral("autostart")}, QStringLiteral("System autostart")),
|
m_optAutostart({QStringLiteral("a"), QStringLiteral("autostart")}, QStringLiteral("System autostart")),
|
||||||
m_optCleanup ({QStringLiteral("c"), QStringLiteral("cleanup")}, QStringLiteral("Cleanup logs")),
|
m_optCleanup ({QStringLiteral("c"), QStringLiteral("cleanup")}, QStringLiteral("Cleanup logs"))
|
||||||
m_optConnect ({QStringLiteral("connect")}, QStringLiteral("Connect to server by index on startup"), QStringLiteral("index")),
|
|
||||||
m_optImport ({QStringLiteral("import")}, QStringLiteral("Import configuration from data string"), QStringLiteral("data"))
|
|
||||||
{
|
{
|
||||||
setDesktopFileName(QStringLiteral(APPLICATION_NAME));
|
|
||||||
setQuitOnLastWindowClosed(false);
|
setQuitOnLastWindowClosed(false);
|
||||||
|
|
||||||
// Fix config file permissions
|
// Fix config file permissions
|
||||||
@@ -60,40 +53,24 @@ AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_C
|
|||||||
|
|
||||||
AmneziaApplication::~AmneziaApplication()
|
AmneziaApplication::~AmneziaApplication()
|
||||||
{
|
{
|
||||||
#ifdef AMNEZIA_DESKTOP
|
if (m_vpnConnection) {
|
||||||
if (m_vpnConnection && m_vpnConnectionThread.isRunning()) {
|
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectFromVpn", Qt::QueuedConnection);
|
||||||
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectSlots", Qt::BlockingQueuedConnection);
|
QMetaObject::invokeMethod(m_vpnConnection.get(), "deleteLater", Qt::QueuedConnection);
|
||||||
|
|
||||||
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectFromVpn", Qt::BlockingQueuedConnection);
|
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
m_vpnConnectionThread.requestInterruption();
|
|
||||||
m_vpnConnectionThread.quit();
|
m_vpnConnectionThread.quit();
|
||||||
|
|
||||||
if (!m_vpnConnectionThread.wait(3000)) {
|
if (!m_vpnConnectionThread.wait(4000)) {
|
||||||
m_vpnConnectionThread.terminate();
|
m_vpnConnectionThread.terminate();
|
||||||
m_vpnConnectionThread.wait(500);
|
m_vpnConnectionThread.wait(1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_engine) {
|
if (m_engine) {
|
||||||
|
QObject::disconnect(m_engine, 0, 0, 0);
|
||||||
delete m_engine;
|
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()
|
void AmneziaApplication::init()
|
||||||
{
|
{
|
||||||
m_engine = new QQmlApplicationEngine;
|
m_engine = new QQmlApplicationEngine;
|
||||||
@@ -129,16 +106,6 @@ void AmneziaApplication::init()
|
|||||||
m_coreController.reset(new CoreController(m_vpnConnection, m_settings, m_engine));
|
m_coreController.reset(new CoreController(m_vpnConnection, m_settings, m_engine));
|
||||||
|
|
||||||
m_engine->addImportPath("qrc:/ui/qml/Modules/");
|
m_engine->addImportPath("qrc:/ui/qml/Modules/");
|
||||||
|
|
||||||
if (m_parser.isSet(m_optImport)) {
|
|
||||||
const QString data = m_parser.value(m_optImport);
|
|
||||||
if (!data.isEmpty()) {
|
|
||||||
if (m_coreController) {
|
|
||||||
m_coreController->importConfigFromData(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_engine->load(url);
|
m_engine->load(url);
|
||||||
|
|
||||||
m_coreController->setQmlRoot();
|
m_coreController->setQmlRoot();
|
||||||
@@ -178,18 +145,6 @@ void AmneziaApplication::init()
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (m_parser.isSet(m_optConnect)) {
|
|
||||||
bool ok = false;
|
|
||||||
int idx = m_parser.value(m_optConnect).toInt(&ok);
|
|
||||||
if (ok) {
|
|
||||||
QTimer::singleShot(0, this, [this, idx]() {
|
|
||||||
if (m_coreController) {
|
|
||||||
m_coreController->openConnectionByIndex(idx);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AmneziaApplication::registerTypes()
|
void AmneziaApplication::registerTypes()
|
||||||
@@ -236,8 +191,6 @@ bool AmneziaApplication::parseCommands()
|
|||||||
|
|
||||||
m_parser.addOption(m_optAutostart);
|
m_parser.addOption(m_optAutostart);
|
||||||
m_parser.addOption(m_optCleanup);
|
m_parser.addOption(m_optCleanup);
|
||||||
m_parser.addOption(m_optConnect);
|
|
||||||
m_parser.addOption(m_optImport);
|
|
||||||
|
|
||||||
m_parser.process(*this);
|
m_parser.process(*this);
|
||||||
|
|
||||||
@@ -274,13 +227,9 @@ bool AmneziaApplication::eventFilter(QObject *watched, QEvent *event)
|
|||||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||||
quit();
|
quit();
|
||||||
#else
|
#else
|
||||||
if (m_forceQuit) {
|
|
||||||
quit();
|
|
||||||
} else {
|
|
||||||
if (m_coreController && m_coreController->pageController()) {
|
if (m_coreController && m_coreController->pageController()) {
|
||||||
m_coreController->pageController()->hideMainWindow();
|
m_coreController->pageController()->hideMainWindow();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
return true; // eat the close
|
return true; // eat the close
|
||||||
}
|
}
|
||||||
@@ -288,12 +237,6 @@ bool AmneziaApplication::eventFilter(QObject *watched, QEvent *event)
|
|||||||
return QObject::eventFilter(watched, event);
|
return QObject::eventFilter(watched, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AmneziaApplication::forceQuit()
|
|
||||||
{
|
|
||||||
m_forceQuit = true;
|
|
||||||
quit();
|
|
||||||
}
|
|
||||||
|
|
||||||
QQmlApplicationEngine *AmneziaApplication::qmlEngine() const
|
QQmlApplicationEngine *AmneziaApplication::qmlEngine() const
|
||||||
{
|
{
|
||||||
return m_engine;
|
return m_engine;
|
||||||
|
|||||||
@@ -45,11 +45,7 @@ public:
|
|||||||
QNetworkAccessManager *networkManager();
|
QNetworkAccessManager *networkManager();
|
||||||
QClipboard *getClipboard();
|
QClipboard *getClipboard();
|
||||||
|
|
||||||
public slots:
|
|
||||||
void forceQuit();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static bool m_forceQuit;
|
|
||||||
QQmlApplicationEngine *m_engine {};
|
QQmlApplicationEngine *m_engine {};
|
||||||
std::shared_ptr<Settings> m_settings;
|
std::shared_ptr<Settings> m_settings;
|
||||||
|
|
||||||
@@ -62,8 +58,6 @@ private:
|
|||||||
|
|
||||||
QCommandLineOption m_optAutostart;
|
QCommandLineOption m_optAutostart;
|
||||||
QCommandLineOption m_optCleanup;
|
QCommandLineOption m_optCleanup;
|
||||||
QCommandLineOption m_optConnect;
|
|
||||||
QCommandLineOption m_optImport;
|
|
||||||
|
|
||||||
QSharedPointer<VpnConnection> m_vpnConnection;
|
QSharedPointer<VpnConnection> m_vpnConnection;
|
||||||
QThread m_vpnConnectionThread;
|
QThread m_vpnConnectionThread;
|
||||||
|
|||||||
@@ -45,8 +45,7 @@
|
|||||||
android:configChanges="uiMode|screenSize|smallestScreenSize|screenLayout|orientation|density
|
android:configChanges="uiMode|screenSize|smallestScreenSize|screenLayout|orientation|density
|
||||||
|fontScale|layoutDirection|locale|keyboard|keyboardHidden|navigation|mcc|mnc"
|
|fontScale|layoutDirection|locale|keyboard|keyboardHidden|navigation|mcc|mnc"
|
||||||
android:launchMode="singleInstance"
|
android:launchMode="singleInstance"
|
||||||
android:windowSoftInputMode="adjustResize|stateUnchanged"
|
android:windowSoftInputMode="stateUnchanged|adjustResize"
|
||||||
android:enableOnBackInvokedCallback="false"
|
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ open class OpenVpn : Protocol() {
|
|||||||
openVpnClient = null
|
openVpnClient = null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
override fun reconnectVpn(vpnBuilder: Builder) {
|
||||||
openVpnClient?.let {
|
openVpnClient?.let {
|
||||||
it.establish = makeEstablish(vpnBuilder)
|
it.establish = makeEstablish(vpnBuilder)
|
||||||
it.reconnect(0)
|
it.reconnect(0)
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ abstract class Protocol {
|
|||||||
|
|
||||||
abstract fun stopVpn()
|
abstract fun stopVpn()
|
||||||
|
|
||||||
abstract fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean)
|
abstract fun reconnectVpn(vpnBuilder: Builder)
|
||||||
|
|
||||||
protected fun ProtocolConfig.Builder.configSplitTunneling(config: JSONObject) {
|
protected fun ProtocolConfig.Builder.configSplitTunneling(config: JSONObject) {
|
||||||
if (!allowSplitTunneling) {
|
if (!allowSplitTunneling) {
|
||||||
|
|||||||
@@ -6,9 +6,6 @@
|
|||||||
<item name="android:colorBackground">@color/black</item>
|
<item name="android:colorBackground">@color/black</item>
|
||||||
<item name="android:windowActionBar">false</item>
|
<item name="android:windowActionBar">false</item>
|
||||||
<item name="android:windowNoTitle">true</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>
|
||||||
<style name="Translucent" parent="NoActionBar">
|
<style name="Translucent" parent="NoActionBar">
|
||||||
<item name="android:windowBackground">@android:color/transparent</item>
|
<item name="android:windowBackground">@android:color/transparent</item>
|
||||||
|
|||||||
@@ -35,11 +35,6 @@ import android.widget.Toast
|
|||||||
import androidx.annotation.MainThread
|
import androidx.annotation.MainThread
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.core.content.ContextCompat
|
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 java.io.IOException
|
||||||
import kotlin.LazyThreadSafetyMode.NONE
|
import kotlin.LazyThreadSafetyMode.NONE
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
@@ -175,9 +170,10 @@ class AmneziaActivity : QtActivity() {
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
Log.d(TAG, "Create Amnezia activity")
|
Log.d(TAG, "Create Amnezia activity")
|
||||||
loadLibs()
|
loadLibs()
|
||||||
|
window.apply {
|
||||||
// Configure window for edge-to-edge display
|
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||||||
configureWindowForEdgeToEdge()
|
statusBarColor = getColor(R.color.black)
|
||||||
|
}
|
||||||
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
||||||
val proto = mainScope.async(Dispatchers.IO) {
|
val proto = mainScope.async(Dispatchers.IO) {
|
||||||
VpnStateStore.getVpnState().vpnProto
|
VpnStateStore.getVpnState().vpnProto
|
||||||
@@ -269,98 +265,6 @@ class AmneziaActivity : QtActivity() {
|
|||||||
super.onStop()
|
super.onStop()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
|
||||||
super.onWindowFocusChanged(hasFocus)
|
|
||||||
Log.d(TAG, "Window focus changed: hasFocus=$hasFocus")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onPause() {
|
|
||||||
super.onPause()
|
|
||||||
Log.d(TAG, "Pause Amnezia activity")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
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() {
|
override fun onDestroy() {
|
||||||
Log.d(TAG, "Destroy Amnezia activity")
|
Log.d(TAG, "Destroy Amnezia activity")
|
||||||
unregisterBroadcastReceiver(notificationStateReceiver)
|
unregisterBroadcastReceiver(notificationStateReceiver)
|
||||||
@@ -762,43 +666,6 @@ class AmneziaActivity : QtActivity() {
|
|||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun isOnTv(): Boolean = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
|
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")
|
@Suppress("unused")
|
||||||
fun startQrCodeReader() {
|
fun startQrCodeReader() {
|
||||||
Log.v(TAG, "Start camera")
|
Log.v(TAG, "Start camera")
|
||||||
|
|||||||
@@ -565,7 +565,7 @@ open class AmneziaVpnService : VpnService() {
|
|||||||
protocolState.value = RECONNECTING
|
protocolState.value = RECONNECTING
|
||||||
|
|
||||||
connectionJob = connectionScope.launch {
|
connectionJob = connectionScope.launch {
|
||||||
vpnProto?.protocol?.reconnectVpn(Builder(), ::protect)
|
vpnProto?.protocol?.reconnectVpn(Builder())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 name: String?
|
||||||
val packageName: String = pi.packageName
|
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
|
val isLaunchable: Boolean = pm.getLaunchIntentForPackage(packageName) != null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val name = ai?.loadLabel(pm)?.toString()
|
val name = ai.loadLabel(pm).toString()
|
||||||
this.name = name?.takeIf { it != packageName }
|
this.name = if (name != packageName) name else null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun compareTo(other: App): Int {
|
override fun compareTo(other: App): Int {
|
||||||
|
|||||||
@@ -28,7 +28,4 @@ object QtAndroidController {
|
|||||||
external fun onAuthResult(result: Boolean)
|
external fun onAuthResult(result: Boolean)
|
||||||
|
|
||||||
external fun decodeQrCode(data: String): Boolean
|
external fun decodeQrCode(data: String): Boolean
|
||||||
|
|
||||||
external fun onImeInsetsChanged(heightDp: Int)
|
|
||||||
external fun onSystemBarsInsetsChanged(navBarHeightDp: Int, statusBarHeightDp: Int)
|
|
||||||
}
|
}
|
||||||
+17
-29
@@ -12,7 +12,6 @@ import org.amnezia.vpn.protocol.Protocol
|
|||||||
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
|
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
|
||||||
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
||||||
import org.amnezia.vpn.protocol.Statistics
|
import org.amnezia.vpn.protocol.Statistics
|
||||||
import org.amnezia.vpn.protocol.VpnException
|
|
||||||
import org.amnezia.vpn.protocol.VpnStartException
|
import org.amnezia.vpn.protocol.VpnStartException
|
||||||
import org.amnezia.vpn.util.LibraryLoader.loadSharedLibrary
|
import org.amnezia.vpn.util.LibraryLoader.loadSharedLibrary
|
||||||
import org.amnezia.vpn.util.Log
|
import org.amnezia.vpn.util.Log
|
||||||
@@ -28,7 +27,6 @@ private const val TAG = "Wireguard"
|
|||||||
open class Wireguard : Protocol() {
|
open class Wireguard : Protocol() {
|
||||||
|
|
||||||
private var tunnelHandle: Int = -1
|
private var tunnelHandle: Int = -1
|
||||||
private var config: WireguardConfig? = null // save config for reconnect
|
|
||||||
protected open val ifName: String = "amn0"
|
protected open val ifName: String = "amn0"
|
||||||
private lateinit var scope: CoroutineScope
|
private lateinit var scope: CoroutineScope
|
||||||
private var statusJob: Job? = null
|
private var statusJob: Job? = null
|
||||||
@@ -63,7 +61,6 @@ open class Wireguard : Protocol() {
|
|||||||
override suspend fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
override suspend fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
||||||
val wireguardConfig = parseConfig(config)
|
val wireguardConfig = parseConfig(config)
|
||||||
start(wireguardConfig, vpnBuilder, protect)
|
start(wireguardConfig, vpnBuilder, protect)
|
||||||
this.config = wireguardConfig
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun parseConfig(config: JSONObject): WireguardConfig {
|
protected open fun parseConfig(config: JSONObject): WireguardConfig {
|
||||||
@@ -125,24 +122,23 @@ open class Wireguard : Protocol() {
|
|||||||
configData.optStringOrNull("S2")?.let { setS2(it.toInt()) }
|
configData.optStringOrNull("S2")?.let { setS2(it.toInt()) }
|
||||||
configData.optStringOrNull("S3")?.let { setS3(it.toInt()) }
|
configData.optStringOrNull("S3")?.let { setS3(it.toInt()) }
|
||||||
configData.optStringOrNull("S4")?.let { setS4(it.toInt()) }
|
configData.optStringOrNull("S4")?.let { setS4(it.toInt()) }
|
||||||
configData.optStringOrNull("H1")?.trim()?.let { if (it.isNotEmpty()) setH1(it) }
|
configData.optStringOrNull("H1")?.let { setH1(it.toLong()) }
|
||||||
configData.optStringOrNull("H2")?.trim()?.let { if (it.isNotEmpty()) setH2(it) }
|
configData.optStringOrNull("H2")?.let { setH2(it.toLong()) }
|
||||||
configData.optStringOrNull("H3")?.trim()?.let { if (it.isNotEmpty()) setH3(it) }
|
configData.optStringOrNull("H3")?.let { setH3(it.toLong()) }
|
||||||
configData.optStringOrNull("H4")?.trim()?.let { if (it.isNotEmpty()) setH4(it) }
|
configData.optStringOrNull("H4")?.let { setH4(it.toLong()) }
|
||||||
configData.optStringOrNull("I1")?.let { setI1(it) }
|
configData.optStringOrNull("I1")?.let { setI1(it) }
|
||||||
configData.optStringOrNull("I2")?.let { setI2(it) }
|
configData.optStringOrNull("I2")?.let { setI2(it) }
|
||||||
configData.optStringOrNull("I3")?.let { setI3(it) }
|
configData.optStringOrNull("I3")?.let { setI3(it) }
|
||||||
configData.optStringOrNull("I4")?.let { setI4(it) }
|
configData.optStringOrNull("I4")?.let { setI4(it) }
|
||||||
configData.optStringOrNull("I5")?.let { setI5(it) }
|
configData.optStringOrNull("I5")?.let { setI5(it) }
|
||||||
|
configData.optStringOrNull("J1")?.let { setJ1(it) }
|
||||||
|
configData.optStringOrNull("J2")?.let { setJ2(it) }
|
||||||
|
configData.optStringOrNull("J3")?.let { setJ3(it) }
|
||||||
|
configData.optStringOrNull("Itime")?.let { setItime(it.toInt()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun start(
|
private fun start(config: WireguardConfig, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
||||||
config: WireguardConfig,
|
if (tunnelHandle != -1) {
|
||||||
vpnBuilder: Builder,
|
|
||||||
protect: (Int) -> Boolean,
|
|
||||||
stopExistingVpn: Boolean = false
|
|
||||||
) {
|
|
||||||
if (!stopExistingVpn && tunnelHandle != -1) {
|
|
||||||
Log.w(TAG, "Tunnel already up")
|
Log.w(TAG, "Tunnel already up")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -150,9 +146,6 @@ open class Wireguard : Protocol() {
|
|||||||
buildVpnInterface(config, vpnBuilder)
|
buildVpnInterface(config, vpnBuilder)
|
||||||
|
|
||||||
vpnBuilder.establish().use { tunFd ->
|
vpnBuilder.establish().use { tunFd ->
|
||||||
if (stopExistingVpn && tunnelHandle != -1) {
|
|
||||||
turnOffVpn()
|
|
||||||
}
|
|
||||||
if (tunFd == null) {
|
if (tunFd == null) {
|
||||||
throw VpnStartException("Create VPN interface: permission not granted or revoked")
|
throw VpnStartException("Create VPN interface: permission not granted or revoked")
|
||||||
}
|
}
|
||||||
@@ -209,25 +202,20 @@ open class Wireguard : Protocol() {
|
|||||||
return lastHandshake
|
return lastHandshake
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun turnOffVpn() {
|
|
||||||
statusJob?.cancel()
|
|
||||||
statusJob = null
|
|
||||||
val handleToClose = tunnelHandle
|
|
||||||
tunnelHandle = -1
|
|
||||||
GoBackend.awgTurnOff(handleToClose)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun stopVpn() {
|
override fun stopVpn() {
|
||||||
if (tunnelHandle == -1) {
|
if (tunnelHandle == -1) {
|
||||||
Log.w(TAG, "Tunnel already down")
|
Log.w(TAG, "Tunnel already down")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
turnOffVpn()
|
statusJob?.cancel()
|
||||||
|
statusJob = null
|
||||||
|
val handleToClose = tunnelHandle
|
||||||
|
tunnelHandle = -1
|
||||||
|
GoBackend.awgTurnOff(handleToClose)
|
||||||
state.value = DISCONNECTED
|
state.value = DISCONNECTED
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
override fun reconnectVpn(vpnBuilder: Builder) {
|
||||||
val config = this.config ?: throw VpnException("Reconnect config is empty")
|
state.value = CONNECTED
|
||||||
start(config, vpnBuilder, protect, true)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-12
@@ -22,15 +22,19 @@ open class WireguardConfig protected constructor(
|
|||||||
val s2: Int?,
|
val s2: Int?,
|
||||||
val s3: Int?,
|
val s3: Int?,
|
||||||
val s4: Int?,
|
val s4: Int?,
|
||||||
val h1: String?,
|
val h1: Long?,
|
||||||
val h2: String?,
|
val h2: Long?,
|
||||||
val h3: String?,
|
val h3: Long?,
|
||||||
val h4: String?,
|
val h4: Long?,
|
||||||
var i1: String?,
|
var i1: String?,
|
||||||
var i2: String?,
|
var i2: String?,
|
||||||
var i3: String?,
|
var i3: String?,
|
||||||
var i4: String?,
|
var i4: String?,
|
||||||
var i5: String?,
|
var i5: String?,
|
||||||
|
var j1: String?,
|
||||||
|
var j2: String?,
|
||||||
|
var j3: String?,
|
||||||
|
var itime: Int?
|
||||||
) : ProtocolConfig(protocolConfigBuilder) {
|
) : ProtocolConfig(protocolConfigBuilder) {
|
||||||
|
|
||||||
protected constructor(builder: Builder) : this(
|
protected constructor(builder: Builder) : this(
|
||||||
@@ -57,6 +61,10 @@ open class WireguardConfig protected constructor(
|
|||||||
builder.i3,
|
builder.i3,
|
||||||
builder.i4,
|
builder.i4,
|
||||||
builder.i5,
|
builder.i5,
|
||||||
|
builder.j1,
|
||||||
|
builder.j2,
|
||||||
|
builder.j3,
|
||||||
|
builder.itime
|
||||||
)
|
)
|
||||||
|
|
||||||
fun toWgUserspaceString(): String = with(StringBuilder()) {
|
fun toWgUserspaceString(): String = with(StringBuilder()) {
|
||||||
@@ -86,6 +94,10 @@ open class WireguardConfig protected constructor(
|
|||||||
i3?.let { appendLine("i3=$it") }
|
i3?.let { appendLine("i3=$it") }
|
||||||
i4?.let { appendLine("i4=$it") }
|
i4?.let { appendLine("i4=$it") }
|
||||||
i5?.let { appendLine("i5=$it") }
|
i5?.let { appendLine("i5=$it") }
|
||||||
|
j1?.let { appendLine("j1=$it") }
|
||||||
|
j2?.let { appendLine("j2=$it") }
|
||||||
|
j3?.let { appendLine("j3=$it") }
|
||||||
|
itime?.let { appendLine("itime=$it") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,15 +152,19 @@ open class WireguardConfig protected constructor(
|
|||||||
internal var s2: Int? = null
|
internal var s2: Int? = null
|
||||||
internal var s3: Int? = null
|
internal var s3: Int? = null
|
||||||
internal var s4: Int? = null
|
internal var s4: Int? = null
|
||||||
internal var h1: String? = null
|
internal var h1: Long? = null
|
||||||
internal var h2: String? = null
|
internal var h2: Long? = null
|
||||||
internal var h3: String? = null
|
internal var h3: Long? = null
|
||||||
internal var h4: String? = null
|
internal var h4: Long? = null
|
||||||
internal var i1: String? = null
|
internal var i1: String? = null
|
||||||
internal var i2: String? = null
|
internal var i2: String? = null
|
||||||
internal var i3: String? = null
|
internal var i3: String? = null
|
||||||
internal var i4: String? = null
|
internal var i4: String? = null
|
||||||
internal var i5: String? = null
|
internal var i5: String? = null
|
||||||
|
internal var j1: String? = null
|
||||||
|
internal var j2: String? = null
|
||||||
|
internal var j3: String? = null
|
||||||
|
internal var itime: Int? = null
|
||||||
|
|
||||||
fun setEndpoint(endpoint: InetEndpoint) = apply { this.endpoint = endpoint }
|
fun setEndpoint(endpoint: InetEndpoint) = apply { this.endpoint = endpoint }
|
||||||
|
|
||||||
@@ -169,15 +185,19 @@ open class WireguardConfig protected constructor(
|
|||||||
fun setS2(s2: Int) = apply { this.s2 = s2 }
|
fun setS2(s2: Int) = apply { this.s2 = s2 }
|
||||||
fun setS3(s3: Int) = apply { this.s3 = s3 }
|
fun setS3(s3: Int) = apply { this.s3 = s3 }
|
||||||
fun setS4(s4: Int) = apply { this.s4 = s4 }
|
fun setS4(s4: Int) = apply { this.s4 = s4 }
|
||||||
fun setH1(h1: String) = apply { this.h1 = h1 }
|
fun setH1(h1: Long) = apply { this.h1 = h1 }
|
||||||
fun setH2(h2: String) = apply { this.h2 = h2 }
|
fun setH2(h2: Long) = apply { this.h2 = h2 }
|
||||||
fun setH3(h3: String) = apply { this.h3 = h3 }
|
fun setH3(h3: Long) = apply { this.h3 = h3 }
|
||||||
fun setH4(h4: String) = apply { this.h4 = h4 }
|
fun setH4(h4: Long) = apply { this.h4 = h4 }
|
||||||
fun setI1(i1: String) = apply { this.i1 = i1 }
|
fun setI1(i1: String) = apply { this.i1 = i1 }
|
||||||
fun setI2(i2: String) = apply { this.i2 = i2 }
|
fun setI2(i2: String) = apply { this.i2 = i2 }
|
||||||
fun setI3(i3: String) = apply { this.i3 = i3 }
|
fun setI3(i3: String) = apply { this.i3 = i3 }
|
||||||
fun setI4(i4: String) = apply { this.i4 = i4 }
|
fun setI4(i4: String) = apply { this.i4 = i4 }
|
||||||
fun setI5(i5: String) = apply { this.i5 = i5 }
|
fun setI5(i5: String) = apply { this.i5 = i5 }
|
||||||
|
fun setJ1(j1: String) = apply { this.j1 = j1 }
|
||||||
|
fun setJ2(j2: String) = apply { this.j2 = j2 }
|
||||||
|
fun setJ3(j3: String) = apply { this.j3 = j3 }
|
||||||
|
fun setItime(itime: Int) = apply { this.itime = itime }
|
||||||
|
|
||||||
override fun build(): WireguardConfig = configBuild().run { WireguardConfig(this@Builder) }
|
override fun build(): WireguardConfig = configBuild().run { WireguardConfig(this@Builder) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ class Xray : Protocol() {
|
|||||||
state.value = DISCONNECTED
|
state.value = DISCONNECTED
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
override fun reconnectVpn(vpnBuilder: Builder) {
|
||||||
state.value = CONNECTED
|
state.value = CONNECTED
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,7 +166,7 @@ class Xray : Protocol() {
|
|||||||
mtu = config.mtu.toLong()
|
mtu = config.mtu.toLong()
|
||||||
proxy = "socks5://127.0.0.1:${config.socksPort}"
|
proxy = "socks5://127.0.0.1:${config.socksPort}"
|
||||||
device = "fd://$fd"
|
device = "fd://$fd"
|
||||||
logLevel = "warn"
|
logLevel = "warning"
|
||||||
}
|
}
|
||||||
LibXray.startTun2Socks(tun2SocksConfig, fd.toLong()).isNotNullOrBlank { err ->
|
LibXray.startTun2Socks(tun2SocksConfig, fd.toLong()).isNotNullOrBlank { err ->
|
||||||
throw VpnStartException("Failed to start tun2socks: $err")
|
throw VpnStartException("Failed to start tun2socks: $err")
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
message("Client android ${CMAKE_ANDROID_ARCH_ABI} build")
|
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
|
set(ANDROID_PLATFORM "android-${APP_ANDROID_MIN_SDK}" CACHE STRING
|
||||||
"The minimum API level supported by the application or library" FORCE)
|
"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_NAME ${CMAKE_PROJECT_VERSION}
|
||||||
QT_ANDROID_VERSION_CODE ${APP_ANDROID_VERSION_CODE}
|
QT_ANDROID_VERSION_CODE ${APP_ANDROID_VERSION_CODE}
|
||||||
QT_ANDROID_MIN_SDK_VERSION ${APP_ANDROID_MIN_SDK}
|
QT_ANDROID_MIN_SDK_VERSION ${APP_ANDROID_MIN_SDK}
|
||||||
QT_ANDROID_TARGET_SDK_VERSION 36
|
QT_ANDROID_TARGET_SDK_VERSION 34
|
||||||
QT_ANDROID_SDK_BUILD_TOOLS_REVISION 36.0.0
|
QT_ANDROID_SDK_BUILD_TOOLS_REVISION 34.0.0
|
||||||
QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android
|
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
|
# We need to include qtprivate api's
|
||||||
# As QAndroidBinder is not yet implemented with a public api
|
# As QAndroidBinder is not yet implemented with a public api
|
||||||
# Check if Qt6::CorePrivate is available (may not be in all Qt versions/configurations)
|
set(LIBS ${LIBS} Qt6::CorePrivate -ljnigraphics)
|
||||||
if(TARGET Qt6::CorePrivate)
|
|
||||||
set(LIBS ${LIBS} Qt6::CorePrivate)
|
|
||||||
endif()
|
|
||||||
set(LIBS ${LIBS} -ljnigraphics)
|
|
||||||
|
|
||||||
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/platforms/android)
|
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/platforms/android)
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ set(HEADERS ${HEADERS}
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.h
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.h
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.h
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.h
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate-C-Interface.h
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate-C-Interface.h
|
||||||
)
|
)
|
||||||
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h PROPERTIES OBJECTIVE_CPP_HEADER TRUE)
|
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h PROPERTIES OBJECTIVE_CPP_HEADER TRUE)
|
||||||
@@ -47,8 +46,6 @@ set(SOURCES ${SOURCES}
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.mm
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/AmneziaSceneDelegateHooks.mm
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ set(HEADERS ${HEADERS}
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.h
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.h
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.h
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.h
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate-C-Interface.h
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate-C-Interface.h
|
||||||
)
|
)
|
||||||
@@ -46,7 +45,6 @@ set(SOURCES ${SOURCES}
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.mm
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.mm
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.mm
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.mm
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.mm
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.mm
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.mm
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
|
||||||
|
|||||||
@@ -23,18 +23,11 @@ set(HEADERS ${HEADERS}
|
|||||||
${CMAKE_CURRENT_BINARY_DIR}/version.h
|
${CMAKE_CURRENT_BINARY_DIR}/version.h
|
||||||
${CLIENT_ROOT_DIR}/core/sshclient.h
|
${CLIENT_ROOT_DIR}/core/sshclient.h
|
||||||
${CLIENT_ROOT_DIR}/core/networkUtilities.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/serialization.h
|
||||||
${CLIENT_ROOT_DIR}/core/serialization/transfer.h
|
${CLIENT_ROOT_DIR}/core/serialization/transfer.h
|
||||||
${CLIENT_ROOT_DIR}/../common/logger/logger.h
|
${CLIENT_ROOT_DIR}/../common/logger/logger.h
|
||||||
${CLIENT_ROOT_DIR}/utils/qmlUtils.h
|
${CLIENT_ROOT_DIR}/utils/qmlUtils.h
|
||||||
${CLIENT_ROOT_DIR}/core/api/apiUtils.h
|
${CLIENT_ROOT_DIR}/core/api/apiUtils.h
|
||||||
${CLIENT_ROOT_DIR}/core/osSignalHandler.h
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Mozilla headres
|
# Mozilla headres
|
||||||
@@ -43,6 +36,7 @@ set(HEADERS ${HEADERS}
|
|||||||
${CLIENT_ROOT_DIR}/mozilla/shared/ipaddress.h
|
${CLIENT_ROOT_DIR}/mozilla/shared/ipaddress.h
|
||||||
${CLIENT_ROOT_DIR}/mozilla/shared/leakdetector.h
|
${CLIENT_ROOT_DIR}/mozilla/shared/leakdetector.h
|
||||||
${CLIENT_ROOT_DIR}/mozilla/controllerimpl.h
|
${CLIENT_ROOT_DIR}/mozilla/controllerimpl.h
|
||||||
|
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.h
|
||||||
)
|
)
|
||||||
|
|
||||||
if(NOT IOS AND NOT MACOS_NE)
|
if(NOT IOS AND NOT MACOS_NE)
|
||||||
@@ -74,11 +68,6 @@ set(SOURCES ${SOURCES}
|
|||||||
${CLIENT_ROOT_DIR}/protocols/vpnprotocol.cpp
|
${CLIENT_ROOT_DIR}/protocols/vpnprotocol.cpp
|
||||||
${CLIENT_ROOT_DIR}/core/sshclient.cpp
|
${CLIENT_ROOT_DIR}/core/sshclient.cpp
|
||||||
${CLIENT_ROOT_DIR}/core/networkUtilities.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/outbound.cpp
|
||||||
${CLIENT_ROOT_DIR}/core/serialization/inbound.cpp
|
${CLIENT_ROOT_DIR}/core/serialization/inbound.cpp
|
||||||
${CLIENT_ROOT_DIR}/core/serialization/ss.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}/../common/logger/logger.cpp
|
||||||
${CLIENT_ROOT_DIR}/utils/qmlUtils.cpp
|
${CLIENT_ROOT_DIR}/utils/qmlUtils.cpp
|
||||||
${CLIENT_ROOT_DIR}/core/api/apiUtils.cpp
|
${CLIENT_ROOT_DIR}/core/api/apiUtils.cpp
|
||||||
${CLIENT_ROOT_DIR}/core/osSignalHandler.cpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Mozilla sources
|
# Mozilla sources
|
||||||
@@ -98,6 +86,7 @@ set(SOURCES ${SOURCES}
|
|||||||
${CLIENT_ROOT_DIR}/mozilla/models/server.cpp
|
${CLIENT_ROOT_DIR}/mozilla/models/server.cpp
|
||||||
${CLIENT_ROOT_DIR}/mozilla/shared/ipaddress.cpp
|
${CLIENT_ROOT_DIR}/mozilla/shared/ipaddress.cpp
|
||||||
${CLIENT_ROOT_DIR}/mozilla/shared/leakdetector.cpp
|
${CLIENT_ROOT_DIR}/mozilla/shared/leakdetector.cpp
|
||||||
|
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
if(NOT IOS AND NOT MACOS_NE)
|
if(NOT IOS AND NOT MACOS_NE)
|
||||||
@@ -186,7 +175,7 @@ if(WIN32)
|
|||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
|
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||||
message("Client desktop build")
|
message("Client desktop build")
|
||||||
add_compile_definitions(AMNEZIA_DESKTOP)
|
add_compile_definitions(AMNEZIA_DESKTOP)
|
||||||
|
|
||||||
@@ -200,13 +189,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/wireguardprotocol.h
|
||||||
${CLIENT_ROOT_DIR}/protocols/xrayprotocol.h
|
${CLIENT_ROOT_DIR}/protocols/xrayprotocol.h
|
||||||
${CLIENT_ROOT_DIR}/protocols/awgprotocol.h
|
${CLIENT_ROOT_DIR}/protocols/awgprotocol.h
|
||||||
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.h
|
|
||||||
)
|
)
|
||||||
|
|
||||||
set(SOURCES ${SOURCES}
|
set(SOURCES ${SOURCES}
|
||||||
${CLIENT_ROOT_DIR}/core/ipcclient.cpp
|
${CLIENT_ROOT_DIR}/core/ipcclient.cpp
|
||||||
${CLIENT_ROOT_DIR}/core/privileged_process.cpp
|
${CLIENT_ROOT_DIR}/core/privileged_process.cpp
|
||||||
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.cpp
|
|
||||||
${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.cpp
|
${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.cpp
|
||||||
${CLIENT_ROOT_DIR}/protocols/openvpnprotocol.cpp
|
${CLIENT_ROOT_DIR}/protocols/openvpnprotocol.cpp
|
||||||
${CLIENT_ROOT_DIR}/protocols/openvpnovercloakprotocol.cpp
|
${CLIENT_ROOT_DIR}/protocols/openvpnovercloakprotocol.cpp
|
||||||
@@ -216,14 +203,3 @@ if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
|
|||||||
${CLIENT_ROOT_DIR}/protocols/awgprotocol.cpp
|
${CLIENT_ROOT_DIR}/protocols/awgprotocol.cpp
|
||||||
)
|
)
|
||||||
endif()
|
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()
|
|
||||||
|
|||||||
@@ -41,16 +41,18 @@ QString AwgConfigurator::createConfig(const ServerCredentials &credentials, Dock
|
|||||||
jsonConfig[config_key::underloadPacketMagicHeader] = configMap.value(config_key::underloadPacketMagicHeader);
|
jsonConfig[config_key::underloadPacketMagicHeader] = configMap.value(config_key::underloadPacketMagicHeader);
|
||||||
jsonConfig[config_key::transportPacketMagicHeader] = configMap.value(config_key::transportPacketMagicHeader);
|
jsonConfig[config_key::transportPacketMagicHeader] = configMap.value(config_key::transportPacketMagicHeader);
|
||||||
|
|
||||||
if (container == DockerContainer::Awg2) {
|
// jsonConfig[config_key::cookieReplyPacketJunkSize] = configMap.value(config_key::cookieReplyPacketJunkSize);
|
||||||
jsonConfig[config_key::cookieReplyPacketJunkSize] = configMap.value(config_key::cookieReplyPacketJunkSize);
|
// jsonConfig[config_key::transportPacketJunkSize] = configMap.value(config_key::transportPacketJunkSize);
|
||||||
jsonConfig[config_key::transportPacketJunkSize] = configMap.value(config_key::transportPacketJunkSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
jsonConfig[config_key::specialJunk1] = configMap.value(amnezia::config_key::specialJunk1);
|
// jsonConfig[config_key::specialJunk1] = configMap.value(amnezia::config_key::specialJunk1);
|
||||||
jsonConfig[config_key::specialJunk2] = configMap.value(amnezia::config_key::specialJunk2);
|
// jsonConfig[config_key::specialJunk2] = configMap.value(amnezia::config_key::specialJunk2);
|
||||||
jsonConfig[config_key::specialJunk3] = configMap.value(amnezia::config_key::specialJunk3);
|
// jsonConfig[config_key::specialJunk3] = configMap.value(amnezia::config_key::specialJunk3);
|
||||||
jsonConfig[config_key::specialJunk4] = configMap.value(amnezia::config_key::specialJunk4);
|
// jsonConfig[config_key::specialJunk4] = configMap.value(amnezia::config_key::specialJunk4);
|
||||||
jsonConfig[config_key::specialJunk5] = configMap.value(amnezia::config_key::specialJunk5);
|
// jsonConfig[config_key::specialJunk5] = configMap.value(amnezia::config_key::specialJunk5);
|
||||||
|
// jsonConfig[config_key::controlledJunk1] = configMap.value(amnezia::config_key::controlledJunk1);
|
||||||
|
// jsonConfig[config_key::controlledJunk2] = configMap.value(amnezia::config_key::controlledJunk2);
|
||||||
|
// jsonConfig[config_key::controlledJunk3] = configMap.value(amnezia::config_key::controlledJunk3);
|
||||||
|
// jsonConfig[config_key::specialHandshakeTimeout] = configMap.value(amnezia::config_key::specialHandshakeTimeout);
|
||||||
|
|
||||||
jsonConfig[config_key::mtu] =
|
jsonConfig[config_key::mtu] =
|
||||||
containerConfig.value(ProtocolProps::protoToString(Proto::Awg)).toObject().value(config_key::mtu).toString(protocols::awg::defaultMtu);
|
containerConfig.value(ProtocolProps::protoToString(Proto::Awg)).toObject().value(config_key::mtu).toString(protocols::awg::defaultMtu);
|
||||||
|
|||||||
@@ -83,30 +83,12 @@ QString OpenVpnConfigurator::createConfig(const ServerCredentials &credentials,
|
|||||||
return "";
|
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_CA_CERT", connData.caCert);
|
||||||
config.replace("$OPENVPN_CLIENT_CERT", connData.clientCert);
|
config.replace("$OPENVPN_CLIENT_CERT", connData.clientCert);
|
||||||
config.replace("$OPENVPN_PRIV_KEY", connData.privKey);
|
config.replace("$OPENVPN_PRIV_KEY", connData.privKey);
|
||||||
|
|
||||||
if (config.contains("$OPENVPN_TA_KEY")) {
|
if (config.contains("$OPENVPN_TA_KEY")) {
|
||||||
config.replace("$OPENVPN_TA_KEY", sanitizeStaticKey(connData.taKey));
|
config.replace("$OPENVPN_TA_KEY", connData.taKey);
|
||||||
} else {
|
} else {
|
||||||
config.replace("<tls-auth>", "");
|
config.replace("<tls-auth>", "");
|
||||||
config.replace("</tls-auth>", "");
|
config.replace("</tls-auth>", "");
|
||||||
|
|||||||
@@ -103,11 +103,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
|
|||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString configPath = m_serverConfigPath;
|
QString getIpsScript = QString("cat %1 | grep AllowedIPs").arg(m_serverConfigPath);
|
||||||
if (container == DockerContainer::Awg) {
|
|
||||||
configPath = amnezia::protocols::awg::serverLegacyConfigPath;
|
|
||||||
}
|
|
||||||
QString getIpsScript = QString("cat %1 | grep AllowedIPs").arg(configPath);
|
|
||||||
QString stdOut;
|
QString stdOut;
|
||||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||||
stdOut += data + "\n";
|
stdOut += data + "\n";
|
||||||
@@ -165,18 +161,15 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
|
|||||||
"AllowedIPs = %3/32\n\n")
|
"AllowedIPs = %3/32\n\n")
|
||||||
.arg(connData.clientPubKey, connData.pskKey, connData.clientIP);
|
.arg(connData.clientPubKey, connData.pskKey, connData.clientIP);
|
||||||
|
|
||||||
errorCode = m_serverController->uploadTextFileToContainer(container, credentials, configPart, configPath,
|
errorCode = m_serverController->uploadTextFileToContainer(container, credentials, configPart, m_serverConfigPath,
|
||||||
libssh::ScpOverwriteMode::ScpAppendToExisting);
|
libssh::ScpOverwriteMode::ScpAppendToExisting);
|
||||||
|
|
||||||
if (errorCode != ErrorCode::NoError) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isAwg = (container == DockerContainer::Awg2);
|
QString script = QString("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip %1)'")
|
||||||
QString bin = isAwg ? QStringLiteral("awg") : QStringLiteral("wg");
|
.arg(m_serverConfigPath);
|
||||||
QString iface = isAwg ? QStringLiteral("awg0") : QStringLiteral("wg0");
|
|
||||||
QString script = QString(
|
|
||||||
"sudo docker exec -i $CONTAINER_NAME bash -c '%1 syncconf %2 <(%1-quick strip %3)'").arg(bin, iface, configPath);
|
|
||||||
|
|
||||||
errorCode = m_serverController->runScript(
|
errorCode = m_serverController->runScript(
|
||||||
credentials,
|
credentials,
|
||||||
|
|||||||
@@ -28,10 +28,7 @@ QString ContainerProps::containerToString(amnezia::DockerContainer c)
|
|||||||
return "none";
|
return "none";
|
||||||
if (c == DockerContainer::Cloak)
|
if (c == DockerContainer::Cloak)
|
||||||
return "amnezia-openvpn-cloak";
|
return "amnezia-openvpn-cloak";
|
||||||
if (c == DockerContainer::Awg)
|
|
||||||
return "amnezia-awg";
|
|
||||||
if (c == DockerContainer::Awg2)
|
|
||||||
return "amnezia-awg2";
|
|
||||||
QMetaEnum metaEnum = QMetaEnum::fromType<DockerContainer>();
|
QMetaEnum metaEnum = QMetaEnum::fromType<DockerContainer>();
|
||||||
QString containerKey = metaEnum.valueToKey(static_cast<int>(c));
|
QString containerKey = metaEnum.valueToKey(static_cast<int>(c));
|
||||||
|
|
||||||
@@ -44,10 +41,7 @@ QString ContainerProps::containerTypeToString(amnezia::DockerContainer c)
|
|||||||
return "none";
|
return "none";
|
||||||
if (c == DockerContainer::Ipsec)
|
if (c == DockerContainer::Ipsec)
|
||||||
return "ikev2";
|
return "ikev2";
|
||||||
if (c == DockerContainer::Awg)
|
|
||||||
return "awg";
|
|
||||||
if (c == DockerContainer::Awg2)
|
|
||||||
return "awg";
|
|
||||||
QMetaEnum metaEnum = QMetaEnum::fromType<DockerContainer>();
|
QMetaEnum metaEnum = QMetaEnum::fromType<DockerContainer>();
|
||||||
QString containerKey = metaEnum.valueToKey(static_cast<int>(c));
|
QString containerKey = metaEnum.valueToKey(static_cast<int>(c));
|
||||||
|
|
||||||
@@ -77,8 +71,6 @@ QVector<amnezia::Proto> ContainerProps::protocolsForContainer(amnezia::DockerCon
|
|||||||
|
|
||||||
case DockerContainer::Socks5Proxy: return { Proto::Socks5Proxy };
|
case DockerContainer::Socks5Proxy: return { Proto::Socks5Proxy };
|
||||||
|
|
||||||
case DockerContainer::Awg: return { Proto::Awg };
|
|
||||||
case DockerContainer::Awg2: return { Proto::Awg };
|
|
||||||
default: return { defaultProtocol(container) };
|
default: return { defaultProtocol(container) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -102,7 +94,6 @@ QMap<DockerContainer, QString> ContainerProps::containerHumanNames()
|
|||||||
{ DockerContainer::Cloak, "OpenVPN over Cloak" },
|
{ DockerContainer::Cloak, "OpenVPN over Cloak" },
|
||||||
{ DockerContainer::WireGuard, "WireGuard" },
|
{ DockerContainer::WireGuard, "WireGuard" },
|
||||||
{ DockerContainer::Awg, "AmneziaWG" },
|
{ DockerContainer::Awg, "AmneziaWG" },
|
||||||
{ DockerContainer::Awg2, "AmneziaWG" },
|
|
||||||
{ DockerContainer::Xray, "XRay" },
|
{ DockerContainer::Xray, "XRay" },
|
||||||
{ DockerContainer::Ipsec, QObject::tr("IPsec") },
|
{ DockerContainer::Ipsec, QObject::tr("IPsec") },
|
||||||
{ DockerContainer::SSXray, "Shadowsocks"},
|
{ DockerContainer::SSXray, "Shadowsocks"},
|
||||||
@@ -129,9 +120,6 @@ QMap<DockerContainer, QString> ContainerProps::containerDescriptions()
|
|||||||
{ DockerContainer::Awg,
|
{ DockerContainer::Awg,
|
||||||
QObject::tr("AmneziaWG is a special protocol from Amnezia based on WireGuard. "
|
QObject::tr("AmneziaWG is a special protocol from Amnezia based on WireGuard. "
|
||||||
"It provides high connection speed and ensures stable operation even in the most challenging network conditions.") },
|
"It provides high connection speed and ensures stable operation even in the most challenging network conditions.") },
|
||||||
{ DockerContainer::Awg2,
|
|
||||||
QObject::tr("AmneziaWG is a special protocol from Amnezia based on WireGuard. "
|
|
||||||
"It provides high connection speed and ensures stable operation even in the most challenging network conditions.") },
|
|
||||||
{ DockerContainer::Xray,
|
{ DockerContainer::Xray,
|
||||||
QObject::tr("XRay with REALITY masks VPN traffic as web traffic and protects against active probing. "
|
QObject::tr("XRay with REALITY masks VPN traffic as web traffic and protects against active probing. "
|
||||||
"It is highly resistant to detection and offers high speed.") },
|
"It is highly resistant to detection and offers high speed.") },
|
||||||
@@ -194,7 +182,7 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
|
|||||||
"* Minimal configuration required\n"
|
"* Minimal configuration required\n"
|
||||||
"* Easily detected by DPI systems (susceptible to blocking)\n"
|
"* Easily detected by DPI systems (susceptible to blocking)\n"
|
||||||
"* Operates over UDP protocol") },
|
"* Operates over UDP protocol") },
|
||||||
{ DockerContainer::Awg2,
|
{ DockerContainer::Awg,
|
||||||
QObject::tr("AmneziaWG is a modern VPN protocol based on WireGuard, "
|
QObject::tr("AmneziaWG is a modern VPN protocol based on WireGuard, "
|
||||||
"combining simplified architecture with high performance across all devices. "
|
"combining simplified architecture with high performance across all devices. "
|
||||||
"It addresses WireGuard's main vulnerability (easy detection by DPI systems) through advanced obfuscation techniques, "
|
"It addresses WireGuard's main vulnerability (easy detection by DPI systems) through advanced obfuscation techniques, "
|
||||||
@@ -254,7 +242,6 @@ Proto ContainerProps::defaultProtocol(DockerContainer c)
|
|||||||
case DockerContainer::Cloak: return Proto::Cloak;
|
case DockerContainer::Cloak: return Proto::Cloak;
|
||||||
case DockerContainer::ShadowSocks: return Proto::ShadowSocks;
|
case DockerContainer::ShadowSocks: return Proto::ShadowSocks;
|
||||||
case DockerContainer::WireGuard: return Proto::WireGuard;
|
case DockerContainer::WireGuard: return Proto::WireGuard;
|
||||||
case DockerContainer::Awg2: return Proto::Awg;
|
|
||||||
case DockerContainer::Awg: return Proto::Awg;
|
case DockerContainer::Awg: return Proto::Awg;
|
||||||
case DockerContainer::Xray: return Proto::Xray;
|
case DockerContainer::Xray: return Proto::Xray;
|
||||||
case DockerContainer::Ipsec: return Proto::Ikev2;
|
case DockerContainer::Ipsec: return Proto::Ikev2;
|
||||||
@@ -268,15 +255,6 @@ Proto ContainerProps::defaultProtocol(DockerContainer c)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ContainerProps::containerTypeToProtocolString(DockerContainer c)
|
|
||||||
{
|
|
||||||
if (c == DockerContainer::None)
|
|
||||||
return "none";
|
|
||||||
|
|
||||||
Proto p = defaultProtocol(c);
|
|
||||||
return ProtocolProps::protoToString(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
|
bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
|
||||||
{
|
{
|
||||||
#ifdef Q_OS_WINDOWS
|
#ifdef Q_OS_WINDOWS
|
||||||
@@ -287,7 +265,6 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
|
|||||||
switch (c) {
|
switch (c) {
|
||||||
case DockerContainer::WireGuard: return true;
|
case DockerContainer::WireGuard: return true;
|
||||||
case DockerContainer::OpenVpn: return true;
|
case DockerContainer::OpenVpn: return true;
|
||||||
case DockerContainer::Awg2: return true;
|
|
||||||
case DockerContainer::Awg: return true;
|
case DockerContainer::Awg: return true;
|
||||||
case DockerContainer::Xray: return true;
|
case DockerContainer::Xray: return true;
|
||||||
case DockerContainer::Cloak: return true;
|
case DockerContainer::Cloak: return true;
|
||||||
@@ -301,7 +278,6 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
|
|||||||
// macOS build using Network Extension – hide OpenVPN-based containers
|
// macOS build using Network Extension – hide OpenVPN-based containers
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case DockerContainer::WireGuard: return true;
|
case DockerContainer::WireGuard: return true;
|
||||||
case DockerContainer::Awg2: return true;
|
|
||||||
case DockerContainer::Awg: return true;
|
case DockerContainer::Awg: return true;
|
||||||
case DockerContainer::Xray: return true;
|
case DockerContainer::Xray: return true;
|
||||||
case DockerContainer::SSXray: return true;
|
case DockerContainer::SSXray: return true;
|
||||||
@@ -324,7 +300,6 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
|
|||||||
case DockerContainer::WireGuard: return true;
|
case DockerContainer::WireGuard: return true;
|
||||||
case DockerContainer::OpenVpn: return true;
|
case DockerContainer::OpenVpn: return true;
|
||||||
case DockerContainer::ShadowSocks: return false;
|
case DockerContainer::ShadowSocks: return false;
|
||||||
case DockerContainer::Awg2: return true;
|
|
||||||
case DockerContainer::Awg: return true;
|
case DockerContainer::Awg: return true;
|
||||||
case DockerContainer::Cloak: return true;
|
case DockerContainer::Cloak: return true;
|
||||||
case DockerContainer::Xray: return true;
|
case DockerContainer::Xray: return true;
|
||||||
@@ -354,7 +329,7 @@ QStringList ContainerProps::fixedPortsForContainer(DockerContainer c)
|
|||||||
bool ContainerProps::isEasySetupContainer(DockerContainer container)
|
bool ContainerProps::isEasySetupContainer(DockerContainer container)
|
||||||
{
|
{
|
||||||
switch (container) {
|
switch (container) {
|
||||||
case DockerContainer::Awg2: return true;
|
case DockerContainer::Awg: return true;
|
||||||
default: return false;
|
default: return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -362,7 +337,7 @@ bool ContainerProps::isEasySetupContainer(DockerContainer container)
|
|||||||
QString ContainerProps::easySetupHeader(DockerContainer container)
|
QString ContainerProps::easySetupHeader(DockerContainer container)
|
||||||
{
|
{
|
||||||
switch (container) {
|
switch (container) {
|
||||||
case DockerContainer::Awg2: return tr("Automatic");
|
case DockerContainer::Awg: return tr("Automatic");
|
||||||
default: return "";
|
default: return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -370,7 +345,7 @@ QString ContainerProps::easySetupHeader(DockerContainer container)
|
|||||||
QString ContainerProps::easySetupDescription(DockerContainer container)
|
QString ContainerProps::easySetupDescription(DockerContainer container)
|
||||||
{
|
{
|
||||||
switch (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.");
|
"It provides high connection speed and ensures stable operation even in the most challenging network conditions.");
|
||||||
default: return "";
|
default: return "";
|
||||||
}
|
}
|
||||||
@@ -379,7 +354,7 @@ QString ContainerProps::easySetupDescription(DockerContainer container)
|
|||||||
int ContainerProps::easySetupOrder(DockerContainer container)
|
int ContainerProps::easySetupOrder(DockerContainer container)
|
||||||
{
|
{
|
||||||
switch (container) {
|
switch (container) {
|
||||||
case DockerContainer::Awg2: return 1;
|
case DockerContainer::Awg: return 1;
|
||||||
default: return 0;
|
default: return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -395,12 +370,6 @@ bool ContainerProps::isShareable(DockerContainer container)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ContainerProps::isAwgContainer(DockerContainer container)
|
|
||||||
{
|
|
||||||
return container == DockerContainer::Awg || container == DockerContainer::Awg2;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
QJsonObject ContainerProps::getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig)
|
QJsonObject ContainerProps::getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig)
|
||||||
{
|
{
|
||||||
QString protocolConfigString = containerConfig.value(ProtocolProps::protoToString(protocol))
|
QString protocolConfigString = containerConfig.value(ProtocolProps::protoToString(protocol))
|
||||||
@@ -418,7 +387,7 @@ int ContainerProps::installPageOrder(DockerContainer container)
|
|||||||
case DockerContainer::Cloak: return 5;
|
case DockerContainer::Cloak: return 5;
|
||||||
case DockerContainer::ShadowSocks: return 6;
|
case DockerContainer::ShadowSocks: return 6;
|
||||||
case DockerContainer::WireGuard: return 2;
|
case DockerContainer::WireGuard: return 2;
|
||||||
case DockerContainer::Awg2: return 1;
|
case DockerContainer::Awg: return 1;
|
||||||
case DockerContainer::Xray: return 3;
|
case DockerContainer::Xray: return 3;
|
||||||
case DockerContainer::Ipsec: return 7;
|
case DockerContainer::Ipsec: return 7;
|
||||||
case DockerContainer::SSXray: return 8;
|
case DockerContainer::SSXray: return 8;
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ namespace amnezia
|
|||||||
enum DockerContainer {
|
enum DockerContainer {
|
||||||
None = 0,
|
None = 0,
|
||||||
Awg,
|
Awg,
|
||||||
Awg2,
|
|
||||||
WireGuard,
|
WireGuard,
|
||||||
OpenVpn,
|
OpenVpn,
|
||||||
Cloak,
|
Cloak,
|
||||||
@@ -46,7 +45,6 @@ namespace amnezia
|
|||||||
Q_INVOKABLE static amnezia::DockerContainer containerFromString(const QString &container);
|
Q_INVOKABLE static amnezia::DockerContainer containerFromString(const QString &container);
|
||||||
Q_INVOKABLE static QString containerToString(amnezia::DockerContainer container);
|
Q_INVOKABLE static QString containerToString(amnezia::DockerContainer container);
|
||||||
Q_INVOKABLE static QString containerTypeToString(amnezia::DockerContainer c);
|
Q_INVOKABLE static QString containerTypeToString(amnezia::DockerContainer c);
|
||||||
Q_INVOKABLE static QString containerTypeToProtocolString(amnezia::DockerContainer c);
|
|
||||||
|
|
||||||
Q_INVOKABLE static QList<amnezia::DockerContainer> allContainers();
|
Q_INVOKABLE static QList<amnezia::DockerContainer> allContainers();
|
||||||
|
|
||||||
@@ -73,9 +71,6 @@ namespace amnezia
|
|||||||
|
|
||||||
static bool isShareable(amnezia::DockerContainer container);
|
static bool isShareable(amnezia::DockerContainer container);
|
||||||
|
|
||||||
static bool isAwgContainer(amnezia::DockerContainer container);
|
|
||||||
|
|
||||||
|
|
||||||
static QJsonObject getProtocolConfigFromContainer(const amnezia::Proto protocol, const QJsonObject &containerConfig);
|
static QJsonObject getProtocolConfigFromContainer(const amnezia::Proto protocol, const QJsonObject &containerConfig);
|
||||||
|
|
||||||
static int installPageOrder(amnezia::DockerContainer container);
|
static int installPageOrder(amnezia::DockerContainer container);
|
||||||
|
|||||||
@@ -47,14 +47,12 @@ namespace apiDefs
|
|||||||
constexpr QLatin1String serverCountryName("server_country_name");
|
constexpr QLatin1String serverCountryName("server_country_name");
|
||||||
|
|
||||||
constexpr QLatin1String osVersion("os_version");
|
constexpr QLatin1String osVersion("os_version");
|
||||||
constexpr QLatin1String appLanguage("app_language");
|
|
||||||
|
|
||||||
constexpr QLatin1String availableCountries("available_countries");
|
constexpr QLatin1String availableCountries("available_countries");
|
||||||
constexpr QLatin1String activeDeviceCount("active_device_count");
|
constexpr QLatin1String activeDeviceCount("active_device_count");
|
||||||
constexpr QLatin1String maxDeviceCount("max_device_count");
|
constexpr QLatin1String maxDeviceCount("max_device_count");
|
||||||
constexpr QLatin1String subscriptionEndDate("subscription_end_date");
|
constexpr QLatin1String subscriptionEndDate("subscription_end_date");
|
||||||
constexpr QLatin1String issuedConfigs("issued_configs");
|
constexpr QLatin1String issuedConfigs("issued_configs");
|
||||||
constexpr QLatin1String subscriptionDescription("subscription_description");
|
|
||||||
|
|
||||||
constexpr QLatin1String supportInfo("support_info");
|
constexpr QLatin1String supportInfo("support_info");
|
||||||
constexpr QLatin1String email("email");
|
constexpr QLatin1String email("email");
|
||||||
@@ -66,20 +64,9 @@ namespace apiDefs
|
|||||||
constexpr QLatin1String id("id");
|
constexpr QLatin1String id("id");
|
||||||
constexpr QLatin1String orderId("order_id");
|
constexpr QLatin1String orderId("order_id");
|
||||||
constexpr QLatin1String migrationCode("migration_code");
|
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
|
#endif // APIDEFS_H
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#include "apiUtils.h"
|
#include "apiUtils.h"
|
||||||
|
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QJsonDocument>
|
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
@@ -83,45 +82,34 @@ apiDefs::ConfigSource apiUtils::getConfigSource(const QJsonObject &serverConfigO
|
|||||||
return static_cast<apiDefs::ConfigSource>(serverConfigObject.value(apiDefs::key::configVersion).toInt());
|
return static_cast<apiDefs::ConfigSource>(serverConfigObject.value(apiDefs::key::configVersion).toInt());
|
||||||
}
|
}
|
||||||
|
|
||||||
amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &sslErrors, const QString &replyErrorString,
|
amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &sslErrors, QNetworkReply *reply)
|
||||||
const QNetworkReply::NetworkError &replyError, const int httpStatusCode,
|
|
||||||
const QByteArray &responseBody)
|
|
||||||
{
|
{
|
||||||
const int httpStatusCodeConflict = 409;
|
const int httpStatusCodeConflict = 409;
|
||||||
const int httpStatusCodeNotFound = 404;
|
const int httpStatusCodeNotFound = 404;
|
||||||
const int httpStatusCodeNotImplemented = 501;
|
|
||||||
|
|
||||||
if (!sslErrors.empty()) {
|
if (!sslErrors.empty()) {
|
||||||
qDebug().noquote() << sslErrors;
|
qDebug().noquote() << sslErrors;
|
||||||
return amnezia::ErrorCode::ApiConfigSslError;
|
return amnezia::ErrorCode::ApiConfigSslError;
|
||||||
} else if (replyError == QNetworkReply::NoError) {
|
} else if (reply->error() == QNetworkReply::NoError) {
|
||||||
return amnezia::ErrorCode::NoError;
|
return amnezia::ErrorCode::NoError;
|
||||||
} else if (replyError == QNetworkReply::NetworkError::OperationCanceledError
|
} else if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
||||||
|| replyError == QNetworkReply::NetworkError::TimeoutError) {
|
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||||
qDebug() << replyError;
|
qDebug() << reply->error();
|
||||||
return amnezia::ErrorCode::ApiConfigTimeoutError;
|
return amnezia::ErrorCode::ApiConfigTimeoutError;
|
||||||
} else if (replyError == QNetworkReply::NetworkError::OperationNotImplementedError) {
|
} else if (reply->error() == QNetworkReply::NetworkError::OperationNotImplementedError) {
|
||||||
qDebug() << replyError;
|
qDebug() << reply->error();
|
||||||
return amnezia::ErrorCode::ApiUpdateRequestError;
|
return amnezia::ErrorCode::ApiUpdateRequestError;
|
||||||
} else {
|
} else {
|
||||||
qDebug() << QString::fromUtf8(responseBody);
|
QString err = reply->errorString();
|
||||||
qDebug() << replyError;
|
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
qDebug() << replyErrorString;
|
qDebug() << QString::fromUtf8(reply->readAll());
|
||||||
|
qDebug() << reply->error();
|
||||||
|
qDebug() << err;
|
||||||
qDebug() << httpStatusCode;
|
qDebug() << httpStatusCode;
|
||||||
|
if (httpStatusCode == httpStatusCodeConflict) {
|
||||||
int httpStatusFromBody = -1;
|
|
||||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(responseBody);
|
|
||||||
if (jsonDoc.isObject()) {
|
|
||||||
QJsonObject jsonObj = jsonDoc.object();
|
|
||||||
httpStatusFromBody = jsonObj.value("http_status").toInt(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (httpStatusFromBody == httpStatusCodeConflict) {
|
|
||||||
return amnezia::ErrorCode::ApiConfigLimitError;
|
return amnezia::ErrorCode::ApiConfigLimitError;
|
||||||
} else if (httpStatusFromBody == httpStatusCodeNotFound) {
|
} else if (httpStatusCode == httpStatusCodeNotFound) {
|
||||||
return amnezia::ErrorCode::ApiNotFoundError;
|
return amnezia::ErrorCode::ApiNotFoundError;
|
||||||
} else if (httpStatusFromBody == httpStatusCodeNotImplemented) {
|
|
||||||
return amnezia::ErrorCode::ApiUpdateRequestError;
|
|
||||||
}
|
}
|
||||||
return amnezia::ErrorCode::ApiConfigDownloadError;
|
return amnezia::ErrorCode::ApiConfigDownloadError;
|
||||||
}
|
}
|
||||||
@@ -174,51 +162,3 @@ QString apiUtils::getPremiumV1VpnKey(const QJsonObject &serverConfigObject)
|
|||||||
|
|
||||||
return QString("vpn://%1").arg(QString(signedData.toBase64(QByteArray::Base64UrlEncoding)));
|
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;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -18,12 +18,9 @@ namespace apiUtils
|
|||||||
apiDefs::ConfigType getConfigType(const QJsonObject &serverConfigObject);
|
apiDefs::ConfigType getConfigType(const QJsonObject &serverConfigObject);
|
||||||
apiDefs::ConfigSource getConfigSource(const QJsonObject &serverConfigObject);
|
apiDefs::ConfigSource getConfigSource(const QJsonObject &serverConfigObject);
|
||||||
|
|
||||||
amnezia::ErrorCode checkNetworkReplyErrors(const QList<QSslError> &sslErrors, const QString &replyErrorString,
|
amnezia::ErrorCode checkNetworkReplyErrors(const QList<QSslError> &sslErrors, QNetworkReply *reply);
|
||||||
const QNetworkReply::NetworkError &replyError, const int httpStatusCode,
|
|
||||||
const QByteArray &responseBody);
|
|
||||||
|
|
||||||
QString getPremiumV1VpnKey(const QJsonObject &serverConfigObject);
|
QString getPremiumV1VpnKey(const QJsonObject &serverConfigObject);
|
||||||
QString getPremiumV2VpnKey(const QJsonObject &serverConfigObject);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // APIUTILS_H
|
#endif // APIUTILS_H
|
||||||
|
|||||||
@@ -26,8 +26,9 @@ CoreController::CoreController(const QSharedPointer<VpnConnection> &vpnConnectio
|
|||||||
|
|
||||||
initNotificationHandler();
|
initNotificationHandler();
|
||||||
|
|
||||||
|
auto locale = m_settings->getAppLanguage();
|
||||||
m_translator.reset(new QTranslator());
|
m_translator.reset(new QTranslator());
|
||||||
updateTranslator(m_settings->getAppLanguage());
|
updateTranslator(locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoreController::initModels()
|
void CoreController::initModels()
|
||||||
@@ -99,9 +100,6 @@ void CoreController::initModels()
|
|||||||
|
|
||||||
m_apiDevicesModel.reset(new ApiDevicesModel(m_settings, this));
|
m_apiDevicesModel.reset(new ApiDevicesModel(m_settings, this));
|
||||||
m_engine->rootContext()->setContextProperty("ApiDevicesModel", m_apiDevicesModel.get());
|
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()
|
void CoreController::initControllers()
|
||||||
@@ -154,8 +152,8 @@ void CoreController::initControllers()
|
|||||||
m_apiConfigsController.reset(new ApiConfigsController(m_serversModel, m_apiServicesModel, m_settings));
|
m_apiConfigsController.reset(new ApiConfigsController(m_serversModel, m_apiServicesModel, m_settings));
|
||||||
m_engine->rootContext()->setContextProperty("ApiConfigsController", m_apiConfigsController.get());
|
m_engine->rootContext()->setContextProperty("ApiConfigsController", m_apiConfigsController.get());
|
||||||
|
|
||||||
m_apiNewsController.reset(new ApiNewsController(m_newsModel, m_settings, m_serversModel, this));
|
m_apiPremV1MigrationController.reset(new ApiPremV1MigrationController(m_serversModel, m_settings, this));
|
||||||
m_engine->rootContext()->setContextProperty("ApiNewsController", m_apiNewsController.get());
|
m_engine->rootContext()->setContextProperty("ApiPremV1MigrationController", m_apiPremV1MigrationController.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoreController::initAndroidController()
|
void CoreController::initAndroidController()
|
||||||
@@ -228,6 +226,8 @@ void CoreController::initSignalHandlers()
|
|||||||
initAutoConnectHandler();
|
initAutoConnectHandler();
|
||||||
initAmneziaDnsToggledHandler();
|
initAmneziaDnsToggledHandler();
|
||||||
initPrepareConfigHandler();
|
initPrepareConfigHandler();
|
||||||
|
initImportPremiumV2VpnKeyHandler();
|
||||||
|
initShowMigrationDrawerHandler();
|
||||||
initStrictKillSwitchHandler();
|
initStrictKillSwitchHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,11 +317,6 @@ void CoreController::initContainerModelUpdateHandler()
|
|||||||
connect(m_serversModel.get(), &ServersModel::containersUpdated, m_containersModel.get(), &ContainersModel::updateModel);
|
connect(m_serversModel.get(), &ServersModel::containersUpdated, m_containersModel.get(), &ContainersModel::updateModel);
|
||||||
connect(m_serversModel.get(), &ServersModel::defaultServerContainersUpdated, m_defaultServerContainersModel.get(),
|
connect(m_serversModel.get(), &ServersModel::defaultServerContainersUpdated, m_defaultServerContainersModel.get(),
|
||||||
&ContainersModel::updateModel);
|
&ContainersModel::updateModel);
|
||||||
connect(m_serversModel.get(), &ServersModel::gatewayStacksExpanded, this, [this]() {
|
|
||||||
if (m_serversModel->hasServersFromGatewayApi()) {
|
|
||||||
m_apiNewsController->fetchNews(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
m_serversModel->resetModel();
|
m_serversModel->resetModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -377,6 +372,25 @@ void CoreController::initPrepareConfigHandler()
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CoreController::initImportPremiumV2VpnKeyHandler()
|
||||||
|
{
|
||||||
|
connect(m_apiPremV1MigrationController.get(), &ApiPremV1MigrationController::importPremiumV2VpnKey, this, [this](const QString &vpnKey) {
|
||||||
|
m_importController->extractConfigFromData(vpnKey);
|
||||||
|
m_importController->importConfig();
|
||||||
|
|
||||||
|
emit m_apiPremV1MigrationController->migrationFinished();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void CoreController::initShowMigrationDrawerHandler()
|
||||||
|
{
|
||||||
|
QTimer::singleShot(1000, this, [this]() {
|
||||||
|
if (m_apiPremV1MigrationController->isPremV1MigrationReminderActive() && m_apiPremV1MigrationController->hasConfigsToMigration()) {
|
||||||
|
m_apiPremV1MigrationController->showMigrationDrawer();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void CoreController::initStrictKillSwitchHandler()
|
void CoreController::initStrictKillSwitchHandler()
|
||||||
{
|
{
|
||||||
connect(m_settingsController.get(), &SettingsController::strictKillSwitchEnabledChanged, m_vpnConnection.get(),
|
connect(m_settingsController.get(), &SettingsController::strictKillSwitchEnabledChanged, m_vpnConnection.get(),
|
||||||
@@ -387,22 +401,3 @@ QSharedPointer<PageController> CoreController::pageController() const
|
|||||||
{
|
{
|
||||||
return m_pageController;
|
return m_pageController;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoreController::openConnectionByIndex(int serverIndex)
|
|
||||||
{
|
|
||||||
if (m_serversModel) {
|
|
||||||
m_serversModel->setProcessedServerIndex(serverIndex);
|
|
||||||
m_serversModel->setDefaultServerIndex(serverIndex);
|
|
||||||
}
|
|
||||||
m_connectionController->toggleConnection();
|
|
||||||
}
|
|
||||||
|
|
||||||
void CoreController::importConfigFromData(const QString &data)
|
|
||||||
{
|
|
||||||
if (!m_importController)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (m_importController->extractConfigFromData(data)) {
|
|
||||||
m_importController->importConfig();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
#include "ui/controllers/api/apiConfigsController.h"
|
#include "ui/controllers/api/apiConfigsController.h"
|
||||||
#include "ui/controllers/api/apiSettingsController.h"
|
#include "ui/controllers/api/apiSettingsController.h"
|
||||||
#include "ui/controllers/api/apiNewsController.h"
|
#include "ui/controllers/api/apiPremV1MigrationController.h"
|
||||||
#include "ui/controllers/appSplitTunnelingController.h"
|
#include "ui/controllers/appSplitTunnelingController.h"
|
||||||
#include "ui/controllers/allowedDnsController.h"
|
#include "ui/controllers/allowedDnsController.h"
|
||||||
#include "ui/controllers/connectionController.h"
|
#include "ui/controllers/connectionController.h"
|
||||||
@@ -47,7 +47,6 @@
|
|||||||
#include "ui/models/services/sftpConfigModel.h"
|
#include "ui/models/services/sftpConfigModel.h"
|
||||||
#include "ui/models/services/socks5ProxyConfigModel.h"
|
#include "ui/models/services/socks5ProxyConfigModel.h"
|
||||||
#include "ui/models/sites_model.h"
|
#include "ui/models/sites_model.h"
|
||||||
#include "ui/models/newsModel.h"
|
|
||||||
|
|
||||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||||
#include "ui/notificationhandler.h"
|
#include "ui/notificationhandler.h"
|
||||||
@@ -64,9 +63,6 @@ public:
|
|||||||
QSharedPointer<PageController> pageController() const;
|
QSharedPointer<PageController> pageController() const;
|
||||||
void setQmlRoot();
|
void setQmlRoot();
|
||||||
|
|
||||||
void openConnectionByIndex(int serverIndex);
|
|
||||||
void importConfigFromData(const QString &data);
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void translationsUpdated();
|
void translationsUpdated();
|
||||||
void websiteUrlChanged(const QString &newUrl);
|
void websiteUrlChanged(const QString &newUrl);
|
||||||
@@ -92,6 +88,8 @@ private:
|
|||||||
void initAutoConnectHandler();
|
void initAutoConnectHandler();
|
||||||
void initAmneziaDnsToggledHandler();
|
void initAmneziaDnsToggledHandler();
|
||||||
void initPrepareConfigHandler();
|
void initPrepareConfigHandler();
|
||||||
|
void initImportPremiumV2VpnKeyHandler();
|
||||||
|
void initShowMigrationDrawerHandler();
|
||||||
void initStrictKillSwitchHandler();
|
void initStrictKillSwitchHandler();
|
||||||
|
|
||||||
QQmlApplicationEngine *m_engine {}; // TODO use parent child system here?
|
QQmlApplicationEngine *m_engine {}; // TODO use parent child system here?
|
||||||
@@ -119,7 +117,7 @@ private:
|
|||||||
|
|
||||||
QScopedPointer<ApiSettingsController> m_apiSettingsController;
|
QScopedPointer<ApiSettingsController> m_apiSettingsController;
|
||||||
QScopedPointer<ApiConfigsController> m_apiConfigsController;
|
QScopedPointer<ApiConfigsController> m_apiConfigsController;
|
||||||
QScopedPointer<ApiNewsController> m_apiNewsController;
|
QScopedPointer<ApiPremV1MigrationController> m_apiPremV1MigrationController;
|
||||||
|
|
||||||
QSharedPointer<ContainersModel> m_containersModel;
|
QSharedPointer<ContainersModel> m_containersModel;
|
||||||
QSharedPointer<ContainersModel> m_defaultServerContainersModel;
|
QSharedPointer<ContainersModel> m_defaultServerContainersModel;
|
||||||
@@ -127,7 +125,6 @@ private:
|
|||||||
QSharedPointer<LanguageModel> m_languageModel;
|
QSharedPointer<LanguageModel> m_languageModel;
|
||||||
QSharedPointer<ProtocolsModel> m_protocolsModel;
|
QSharedPointer<ProtocolsModel> m_protocolsModel;
|
||||||
QSharedPointer<SitesModel> m_sitesModel;
|
QSharedPointer<SitesModel> m_sitesModel;
|
||||||
QSharedPointer<NewsModel> m_newsModel;
|
|
||||||
QSharedPointer<AllowedDnsModel> m_allowedDnsModel;
|
QSharedPointer<AllowedDnsModel> m_allowedDnsModel;
|
||||||
QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel;
|
QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel;
|
||||||
QSharedPointer<ClientManagementModel> m_clientManagementModel;
|
QSharedPointer<ClientManagementModel> m_clientManagementModel;
|
||||||
|
|||||||
@@ -1,22 +1,26 @@
|
|||||||
#include "gatewayController.h"
|
#include "gatewayController.h"
|
||||||
|
|
||||||
#include <QDebug>
|
#include <algorithm>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QMutexLocker>
|
#include <QNetworkReply>
|
||||||
#include <QSharedPointer>
|
#include <QUrl>
|
||||||
#include <QThread>
|
|
||||||
#include <QtConcurrent>
|
|
||||||
|
|
||||||
#include "QBlockCipher.h"
|
#include "QBlockCipher.h"
|
||||||
#include "QRsa.h"
|
#include "QRsa.h"
|
||||||
|
|
||||||
#include "amnezia_application.h"
|
#include "amnezia_application.h"
|
||||||
#include "core/transport/dnsGatewayTransport.h"
|
#include "core/api/apiUtils.h"
|
||||||
#include "core/transport/httpGatewayTransport.h"
|
#include "core/networkUtilities.h"
|
||||||
#include "utilities.h"
|
#include "utilities.h"
|
||||||
|
|
||||||
|
#ifdef AMNEZIA_DESKTOP
|
||||||
|
#include "core/ipcclient.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
namespace configKey
|
namespace configKey
|
||||||
@@ -29,330 +33,381 @@ namespace
|
|||||||
constexpr char keyPayload[] = "key_payload";
|
constexpr char keyPayload[] = "key_payload";
|
||||||
}
|
}
|
||||||
|
|
||||||
amnezia::transport::dns::DnsProtocol dnsProtocolFromPrimary(PrimaryTransport p)
|
constexpr QLatin1String errorResponsePattern1("No active configuration found for");
|
||||||
{
|
constexpr QLatin1String errorResponsePattern2("No non-revoked public key found for");
|
||||||
switch (p) {
|
constexpr QLatin1String errorResponsePattern3("Account not found.");
|
||||||
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
|
|
||||||
|
|
||||||
TransportsConfig TransportsConfig::fromJson(const QJsonObject &json)
|
constexpr QLatin1String updateRequestResponsePattern("client version update is required");
|
||||||
{
|
|
||||||
using amnezia::transport::dns::DnsProtocol;
|
|
||||||
|
|
||||||
TransportsConfig config;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
config.retryCount = json.value("retry_count").toInt(3);
|
|
||||||
config.timeoutMs = json.value("timeout_ms").toInt(10000);
|
|
||||||
|
|
||||||
if (json.contains("http")) {
|
|
||||||
QJsonObject httpObj = json["http"].toObject();
|
|
||||||
config.httpEnabled = httpObj.value("enabled").toBool(true);
|
|
||||||
config.httpEndpoint = httpObj.value("endpoint").toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (json.contains("dns_transports")) {
|
|
||||||
QJsonArray transportsArray = json["dns_transports"].toArray();
|
|
||||||
for (const auto &transportVal : transportsArray) {
|
|
||||||
QJsonObject transportObj = transportVal.toObject();
|
|
||||||
DnsTransportEntry entry;
|
|
||||||
|
|
||||||
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");
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entry.isValid()) {
|
|
||||||
config.dnsTransports.append(entry);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return config;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GatewayController::GatewayController(const QString &gatewayEndpoint,
|
GatewayController::GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
|
||||||
const bool isDevEnvironment,
|
const bool isStrictKillSwitchEnabled, QObject *parent)
|
||||||
const int requestTimeoutMsecs,
|
|
||||||
const bool isStrictKillSwitchEnabled,
|
|
||||||
QObject *parent)
|
|
||||||
: QObject(parent),
|
: QObject(parent),
|
||||||
m_requestTimeoutMsecs(requestTimeoutMsecs),
|
|
||||||
m_gatewayEndpoint(gatewayEndpoint),
|
m_gatewayEndpoint(gatewayEndpoint),
|
||||||
m_isDevEnvironment(isDevEnvironment),
|
m_isDevEnvironment(isDevEnvironment),
|
||||||
|
m_requestTimeoutMsecs(requestTimeoutMsecs),
|
||||||
m_isStrictKillSwitchEnabled(isStrictKillSwitchEnabled)
|
m_isStrictKillSwitchEnabled(isStrictKillSwitchEnabled)
|
||||||
{
|
{
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<amnezia::transport::IGatewayTransport> GatewayController::buildTransport(
|
ErrorCode GatewayController::get(const QString &endpoint, QByteArray &responseBody)
|
||||||
const TransportsConfig &config, int requestTimeoutMsecs, bool isDevEnvironment, bool isStrictKillSwitchEnabled)
|
|
||||||
{
|
{
|
||||||
using namespace amnezia::transport;
|
#ifdef Q_OS_IOS
|
||||||
|
IosController::Instance()->requestInetAccess();
|
||||||
|
QThread::msleep(10);
|
||||||
|
#endif
|
||||||
|
|
||||||
auto makeHttp = [&](const QString &httpEndpoint) {
|
QNetworkRequest request;
|
||||||
return std::make_shared<HttpGatewayTransport>(
|
request.setTransferTimeout(m_requestTimeoutMsecs);
|
||||||
httpEndpoint, isDevEnvironment, requestTimeoutMsecs, isStrictKillSwitchEnabled);
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
|
request.setRawHeader(QString("X-Client-Request-ID").toUtf8(), QUuid::createUuid().toString(QUuid::WithoutBraces).toUtf8());
|
||||||
|
|
||||||
|
request.setUrl(QString(endpoint).arg(m_proxyUrl.isEmpty() ? m_gatewayEndpoint : m_proxyUrl));
|
||||||
|
|
||||||
|
// bypass killSwitch exceptions for API-gateway
|
||||||
|
#ifdef AMNEZIA_DESKTOP
|
||||||
|
if (m_isStrictKillSwitchEnabled) {
|
||||||
|
QString host = QUrl(request.url()).host();
|
||||||
|
QString ip = NetworkUtilities::getIPAddress(host);
|
||||||
|
if (!ip.isEmpty()) {
|
||||||
|
IpcClient::Interface()->addKillSwitchAllowedRange(QStringList { ip });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QNetworkReply *reply;
|
||||||
|
reply = amnApp->networkManager()->get(request);
|
||||||
|
|
||||||
|
QEventLoop wait;
|
||||||
|
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||||
|
|
||||||
|
QList<QSslError> sslErrors;
|
||||||
|
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||||
|
wait.exec();
|
||||||
|
|
||||||
|
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);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (config.primary == PrimaryTransport::Http) {
|
auto replyProcessingFunction = [&responseBody, &reply, &sslErrors, this](QNetworkReply *nestedReply,
|
||||||
return makeHttp(config.httpEndpoint);
|
const QList<QSslError> &nestedSslErrors) {
|
||||||
|
responseBody = nestedReply->readAll();
|
||||||
|
if (!sslErrors.isEmpty() || !shouldBypassProxy(nestedReply, responseBody, false)) {
|
||||||
|
sslErrors = nestedSslErrors;
|
||||||
|
reply = nestedReply;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
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);
|
bypassProxy(endpoint, reply, requestFunction, replyProcessingFunction);
|
||||||
addTransport(DnsProtocol::Tcp, AGW_DNS_PORT_UDP, 5353);
|
}
|
||||||
addTransport(DnsProtocol::Tls, AGW_DNS_PORT_DOT, 853);
|
|
||||||
|
|
||||||
QString dohPath = QString(AGW_DNS_DOH_PATH).trimmed();
|
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, reply);
|
||||||
if (dohPath.isEmpty()) dohPath = "/dns-query";
|
reply->deleteLater();
|
||||||
addTransport(DnsProtocol::Https, AGW_DNS_PORT_DOH, 443, dohPath);
|
|
||||||
|
|
||||||
addTransport(DnsProtocol::Quic, AGW_DNS_PORT_DOQ, 8853);
|
return errorCode;
|
||||||
|
|
||||||
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)
|
ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody)
|
||||||
{
|
{
|
||||||
EncryptedRequest result;
|
#ifdef Q_OS_IOS
|
||||||
result.errorCode = amnezia::ErrorCode::NoError;
|
IosController::Instance()->requestInetAccess();
|
||||||
|
QThread::msleep(10);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QNetworkRequest request;
|
||||||
|
request.setTransferTimeout(m_requestTimeoutMsecs);
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
|
request.setRawHeader(QString("X-Client-Request-ID").toUtf8(), QUuid::createUuid().toString(QUuid::WithoutBraces).toUtf8());
|
||||||
|
|
||||||
|
request.setUrl(endpoint.arg(m_proxyUrl.isEmpty() ? m_gatewayEndpoint : m_proxyUrl));
|
||||||
|
|
||||||
|
// bypass killSwitch exceptions for API-gateway
|
||||||
|
#ifdef AMNEZIA_DESKTOP
|
||||||
|
if (m_isStrictKillSwitchEnabled) {
|
||||||
|
QString host = QUrl(request.url()).host();
|
||||||
|
QString ip = NetworkUtilities::getIPAddress(host);
|
||||||
|
if (!ip.isEmpty()) {
|
||||||
|
IpcClient::Interface()->addKillSwitchAllowedRange(QStringList { ip });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
QSimpleCrypto::QBlockCipher blockCipher;
|
QSimpleCrypto::QBlockCipher blockCipher;
|
||||||
result.key = blockCipher.generatePrivateSalt(32);
|
QByteArray key = blockCipher.generatePrivateSalt(32);
|
||||||
result.iv = blockCipher.generatePrivateSalt(16);
|
QByteArray iv = blockCipher.generatePrivateSalt(32);
|
||||||
result.salt = blockCipher.generatePrivateSalt(8);
|
QByteArray salt = blockCipher.generatePrivateSalt(8);
|
||||||
|
|
||||||
QJsonObject keyPayload;
|
QJsonObject keyPayload;
|
||||||
keyPayload[configKey::aesKey] = QString(result.key.toBase64());
|
keyPayload[configKey::aesKey] = QString(key.toBase64());
|
||||||
keyPayload[configKey::aesIv] = QString(result.iv.toBase64());
|
keyPayload[configKey::aesIv] = QString(iv.toBase64());
|
||||||
keyPayload[configKey::aesSalt] = QString(result.salt.toBase64());
|
keyPayload[configKey::aesSalt] = QString(salt.toBase64());
|
||||||
|
|
||||||
QByteArray encryptedKeyPayload;
|
QByteArray encryptedKeyPayload;
|
||||||
QByteArray encryptedApiPayload;
|
QByteArray encryptedApiPayload;
|
||||||
try {
|
try {
|
||||||
QSimpleCrypto::QRsa rsa;
|
QSimpleCrypto::QRsa rsa;
|
||||||
|
|
||||||
EVP_PKEY *publicKey = nullptr;
|
EVP_PKEY *publicKey = nullptr;
|
||||||
try {
|
try {
|
||||||
QByteArray rsaKey = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
QByteArray rsaKey = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
||||||
rsaKey = rsaKey.trimmed();
|
QSimpleCrypto::QRsa rsa;
|
||||||
rsaKey.replace("\\n", "\n");
|
|
||||||
publicKey = rsa.getPublicKeyFromByteArray(rsaKey);
|
publicKey = rsa.getPublicKeyFromByteArray(rsaKey);
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
Utils::logException();
|
Utils::logException();
|
||||||
qCritical() << "error loading public key from environment variables";
|
qCritical() << "error loading public key from environment variables";
|
||||||
result.errorCode = amnezia::ErrorCode::ApiMissingAgwPublicKey;
|
return ErrorCode::ApiMissingAgwPublicKey;
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
encryptedKeyPayload = rsa.encrypt(QJsonDocument(keyPayload).toJson(QJsonDocument::Compact),
|
encryptedKeyPayload = rsa.encrypt(QJsonDocument(keyPayload).toJson(), publicKey, RSA_PKCS1_PADDING);
|
||||||
publicKey, RSA_PKCS1_PADDING);
|
|
||||||
EVP_PKEY_free(publicKey);
|
EVP_PKEY_free(publicKey);
|
||||||
|
|
||||||
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(QJsonDocument::Compact),
|
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), key, iv, "", salt);
|
||||||
result.key, result.iv, "", result.salt);
|
} catch (...) { // todo change error handling in QSimpleCrypto?
|
||||||
} catch (...) {
|
|
||||||
Utils::logException();
|
Utils::logException();
|
||||||
qCritical() << "error when encrypting the request body";
|
qCritical() << "error when encrypting the request body";
|
||||||
result.errorCode = amnezia::ErrorCode::ApiConfigDecryptionError;
|
return ErrorCode::ApiConfigDecryptionError;
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonObject requestBody;
|
QJsonObject requestBody;
|
||||||
requestBody[configKey::keyPayload] = QString(encryptedKeyPayload.toBase64());
|
requestBody[configKey::keyPayload] = QString(encryptedKeyPayload.toBase64());
|
||||||
requestBody[configKey::apiPayload] = QString(encryptedApiPayload.toBase64());
|
requestBody[configKey::apiPayload] = QString(encryptedApiPayload.toBase64());
|
||||||
|
|
||||||
result.body = QJsonDocument(requestBody).toJson(QJsonDocument::Compact);
|
QNetworkReply *reply = amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson());
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
amnezia::transport::DecryptionResult GatewayController::decryptResponse(const QByteArray &encryptedResponseBody,
|
QEventLoop wait;
|
||||||
const QByteArray &key,
|
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||||
const QByteArray &iv,
|
|
||||||
const QByteArray &salt) const
|
|
||||||
{
|
|
||||||
amnezia::transport::DecryptionResult result;
|
|
||||||
result.decrypted = encryptedResponseBody;
|
|
||||||
result.isOk = false;
|
|
||||||
|
|
||||||
if (encryptedResponseBody.isEmpty()) {
|
QList<QSslError> sslErrors;
|
||||||
return result;
|
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 {
|
try {
|
||||||
|
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(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
||||||
|
|
||||||
|
for (const auto &proxyStorageUrl : proxyStorageUrls) {
|
||||||
|
request.setUrl(proxyStorageUrl);
|
||||||
|
reply = amnApp->networkManager()->get(request);
|
||||||
|
|
||||||
|
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;
|
QSimpleCrypto::QBlockCipher blockCipher;
|
||||||
result.decrypted = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt);
|
responseBody = blockCipher.decryptAesBlockCipher(ba, key, iv);
|
||||||
result.isOk = true;
|
} else {
|
||||||
|
responseBody = encryptedResponseBody;
|
||||||
|
}
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
result.decrypted = encryptedResponseBody;
|
Utils::logException();
|
||||||
result.isOk = false;
|
qCritical() << "error loading private key from environment variables or decrypting payload" << encryptedResponseBody;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
auto endpointsArray = QJsonDocument::fromJson(responseBody).array();
|
||||||
|
|
||||||
|
QStringList endpoints;
|
||||||
|
for (const auto &endpoint : endpointsArray) {
|
||||||
|
endpoints.push_back(endpoint.toString());
|
||||||
|
}
|
||||||
|
return endpoints;
|
||||||
|
} else {
|
||||||
|
apiUtils::checkNetworkReplyErrors(sslErrors, reply);
|
||||||
|
qDebug() << "go to the next storage endpoint";
|
||||||
|
|
||||||
|
reply->deleteLater();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<amnezia::transport::IGatewayTransport> GatewayController::currentTransport() const
|
bool GatewayController::shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key,
|
||||||
|
const QByteArray &iv, const QByteArray &salt)
|
||||||
{
|
{
|
||||||
QMutexLocker lock(&m_transportMutex);
|
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError || reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||||
return m_transport;
|
qDebug() << "timeout occurred";
|
||||||
|
qDebug() << reply->error();
|
||||||
|
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 {
|
||||||
|
qDebug() << reply->error();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else if (reply->error() == QNetworkReply::NetworkError::OperationNotImplementedError) {
|
||||||
|
if (responseBody.contains(updateRequestResponsePattern)) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
qDebug() << reply->error();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else if (reply->error() != QNetworkReply::NetworkError::NoError) {
|
||||||
|
qDebug() << reply->error();
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
amnezia::ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody)
|
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)
|
||||||
{
|
{
|
||||||
EncryptedRequest enc = encryptRequest(apiPayload);
|
QStringList proxyUrls = getProxyUrls();
|
||||||
if (enc.errorCode != amnezia::ErrorCode::NoError) {
|
std::random_device randomDevice;
|
||||||
return enc.errorCode;
|
std::mt19937 generator(randomDevice());
|
||||||
}
|
std::shuffle(proxyUrls.begin(), proxyUrls.end(), generator);
|
||||||
|
|
||||||
auto transport = currentTransport();
|
QByteArray responseBody;
|
||||||
if (!transport) {
|
|
||||||
return amnezia::ErrorCode::AmneziaServiceConnectionFailed;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto decryptionHook = [this, key = enc.key, iv = enc.iv, salt = enc.salt](const QByteArray &encrypted) {
|
auto bypassFunction = [this](const QString &endpoint, const QString &proxyUrl, QNetworkReply *reply,
|
||||||
return decryptResponse(encrypted, key, iv, salt);
|
std::function<QNetworkReply *(const QString &url)> requestFunction,
|
||||||
|
std::function<bool(QNetworkReply * reply, const QList<QSslError> &sslErrors)> replyProcessingFunction) {
|
||||||
|
QEventLoop wait;
|
||||||
|
QList<QSslError> sslErrors;
|
||||||
|
|
||||||
|
qDebug() << "go to the next proxy 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)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
return transport->send(endpoint, enc.body, responseBody, decryptionHook);
|
if (m_proxyUrl.isEmpty()) {
|
||||||
}
|
QNetworkRequest request;
|
||||||
|
request.setTransferTimeout(1000);
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
|
|
||||||
QFuture<QPair<amnezia::ErrorCode, QByteArray>> GatewayController::postAsync(const QString &endpoint, const QJsonObject apiPayload)
|
QEventLoop wait;
|
||||||
{
|
QList<QSslError> sslErrors;
|
||||||
return QtConcurrent::run([this, endpoint, apiPayload]() {
|
QNetworkReply *reply;
|
||||||
QByteArray responseBody;
|
|
||||||
amnezia::ErrorCode errorCode = post(endpoint, apiPayload, responseBody);
|
for (const QString &proxyUrl : proxyUrls) {
|
||||||
return qMakePair(errorCode, responseBody);
|
request.setUrl(proxyUrl + "lmbd-health");
|
||||||
});
|
reply = amnApp->networkManager()->get(request);
|
||||||
|
|
||||||
|
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) {
|
||||||
|
reply->deleteLater();
|
||||||
|
|
||||||
|
m_proxyUrl = proxyUrl;
|
||||||
|
if (!m_proxyUrl.isEmpty()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reply->deleteLater();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_proxyUrl.isEmpty()) {
|
||||||
|
if (bypassFunction(endpoint, m_proxyUrl, reply, requestFunction, replyProcessingFunction)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const QString &proxyUrl : proxyUrls) {
|
||||||
|
if (bypassFunction(endpoint, proxyUrl, reply, requestFunction, replyProcessingFunction)) {
|
||||||
|
m_proxyUrl = proxyUrl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,88 +1,39 @@
|
|||||||
#ifndef GATEWAYCONTROLLER_H
|
#ifndef GATEWAYCONTROLLER_H
|
||||||
#define GATEWAYCONTROLLER_H
|
#define GATEWAYCONTROLLER_H
|
||||||
|
|
||||||
#include <QFuture>
|
#include <QNetworkReply>
|
||||||
#include <QJsonArray>
|
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QMutex>
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QPair>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include "core/defs.h"
|
#include "core/defs.h"
|
||||||
#include "core/transport/dns/dnsResolver.h"
|
|
||||||
#include "core/transport/igatewaytransport.h"
|
|
||||||
|
|
||||||
struct DnsTransportEntry
|
#ifdef Q_OS_IOS
|
||||||
{
|
#include "platforms/ios/ios_controller.h"
|
||||||
amnezia::transport::dns::DnsProtocol type = amnezia::transport::dns::DnsProtocol::Udp;
|
#endif
|
||||||
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);
|
|
||||||
};
|
|
||||||
|
|
||||||
class GatewayController : public QObject
|
class GatewayController : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit GatewayController(const QString &gatewayEndpoint,
|
explicit GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
|
||||||
const bool isDevEnvironment,
|
const bool isStrictKillSwitchEnabled, QObject *parent = nullptr);
|
||||||
const int requestTimeoutMsecs,
|
|
||||||
const bool isStrictKillSwitchEnabled,
|
|
||||||
QObject *parent = nullptr);
|
|
||||||
|
|
||||||
|
amnezia::ErrorCode get(const QString &endpoint, QByteArray &responseBody);
|
||||||
amnezia::ErrorCode post(const QString &endpoint, const QJsonObject apiPayload, 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:
|
private:
|
||||||
struct EncryptedRequest
|
QStringList getProxyUrls();
|
||||||
{
|
bool shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key = "",
|
||||||
QByteArray body;
|
const QByteArray &iv = "", const QByteArray &salt = "");
|
||||||
QByteArray key;
|
void bypassProxy(const QString &endpoint, QNetworkReply *reply, std::function<QNetworkReply *(const QString &url)> requestFunction,
|
||||||
QByteArray iv;
|
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction);
|
||||||
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);
|
|
||||||
|
|
||||||
int m_requestTimeoutMsecs;
|
int m_requestTimeoutMsecs;
|
||||||
QString m_gatewayEndpoint;
|
QString m_gatewayEndpoint;
|
||||||
bool m_isDevEnvironment = false;
|
bool m_isDevEnvironment = false;
|
||||||
bool m_isStrictKillSwitchEnabled = false;
|
bool m_isStrictKillSwitchEnabled = false;
|
||||||
|
|
||||||
mutable QMutex m_transportMutex;
|
inline static QString m_proxyUrl;
|
||||||
std::shared_ptr<amnezia::transport::IGatewayTransport> m_transport;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // GATEWAYCONTROLLER_H
|
#endif // GATEWAYCONTROLLER_H
|
||||||
|
|||||||
@@ -345,7 +345,7 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ContainerProps::isAwgContainer(container)) {
|
if (container == DockerContainer::Awg) {
|
||||||
if ((oldProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress)
|
if ((oldProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress)
|
||||||
!= newProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress))
|
!= newProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress))
|
||||||
|| (oldProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort)
|
|| (oldProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort)
|
||||||
@@ -367,11 +367,11 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
|
|||||||
|| (oldProtoConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader)
|
|| (oldProtoConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader)
|
||||||
!= newProtoConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader))
|
!= newProtoConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader))
|
||||||
|| (oldProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader))
|
|| (oldProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader))
|
||||||
!= newProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader)
|
!= newProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader))
|
||||||
|| (oldProtoConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize)
|
// || (oldProtoConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize)
|
||||||
!= newProtoConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize))
|
// != newProtoConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize))
|
||||||
|| (oldProtoConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize)
|
// || (oldProtoConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize)
|
||||||
!= newProtoConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize)))
|
// != newProtoConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize))
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -648,11 +648,6 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
|
|||||||
|
|
||||||
vars.append({ { "$COOKIE_REPLY_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::cookieReplyPacketJunkSize).toString() } });
|
vars.append({ { "$COOKIE_REPLY_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::cookieReplyPacketJunkSize).toString() } });
|
||||||
vars.append({ { "$TRANSPORT_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::transportPacketJunkSize).toString() } });
|
vars.append({ { "$TRANSPORT_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::transportPacketJunkSize).toString() } });
|
||||||
vars.append({ { "$SPECIAL_JUNK_1", amneziaWireguarConfig.value(config_key::specialJunk1).toString() } });
|
|
||||||
vars.append({ { "$SPECIAL_JUNK_2", amneziaWireguarConfig.value(config_key::specialJunk2).toString() } });
|
|
||||||
vars.append({ { "$SPECIAL_JUNK_3", amneziaWireguarConfig.value(config_key::specialJunk3).toString() } });
|
|
||||||
vars.append({ { "$SPECIAL_JUNK_4", amneziaWireguarConfig.value(config_key::specialJunk4).toString() } });
|
|
||||||
vars.append({ { "$SPECIAL_JUNK_5", amneziaWireguarConfig.value(config_key::specialJunk5).toString() } });
|
|
||||||
|
|
||||||
// Socks5 proxy vars
|
// Socks5 proxy vars
|
||||||
vars.append({ { "$SOCKS5_PROXY_PORT", socks5ProxyConfig.value(config_key::port).toString(protocols::socks5Proxy::defaultPort) } });
|
vars.append({ { "$SOCKS5_PROXY_PORT", socks5ProxyConfig.value(config_key::port).toString(protocols::socks5Proxy::defaultPort) } });
|
||||||
@@ -662,8 +657,7 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
|
|||||||
vars.append({ { "$SOCKS5_USER", socks5user } });
|
vars.append({ { "$SOCKS5_USER", socks5user } });
|
||||||
vars.append({ { "$SOCKS5_AUTH_TYPE", socks5user.isEmpty() ? "none" : "strong" } });
|
vars.append({ { "$SOCKS5_AUTH_TYPE", socks5user.isEmpty() ? "none" : "strong" } });
|
||||||
|
|
||||||
QString serverIp = (!ContainerProps::isAwgContainer(container) &&
|
QString serverIp = (container != DockerContainer::Awg && container != DockerContainer::WireGuard && container != DockerContainer::Xray)
|
||||||
container != DockerContainer::WireGuard && container != DockerContainer::Xray)
|
|
||||||
? NetworkUtilities::getIPAddress(credentials.hostName)
|
? NetworkUtilities::getIPAddress(credentials.hostName)
|
||||||
: credentials.hostName;
|
: credentials.hostName;
|
||||||
if (!serverIp.isEmpty()) {
|
if (!serverIp.isEmpty()) {
|
||||||
|
|||||||
@@ -99,12 +99,11 @@ QJsonObject VpnConfigurationsController::createVpnConfiguration(const QPair<QStr
|
|||||||
protocolConfigString = configurator->processConfigWithLocalSettings(dns, isApiConfig, protocolConfigString);
|
protocolConfigString = configurator->processConfigWithLocalSettings(dns, isApiConfig, protocolConfigString);
|
||||||
|
|
||||||
QJsonObject vpnConfigData = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
|
QJsonObject vpnConfigData = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
|
||||||
if (ContainerProps::isAwgContainer(container) || container == DockerContainer::WireGuard) {
|
if (container == DockerContainer::Awg || container == DockerContainer::WireGuard) {
|
||||||
// add mtu for old configs
|
// add mtu for old configs
|
||||||
if (vpnConfigData[config_key::mtu].toString().isEmpty()) {
|
if (vpnConfigData[config_key::mtu].toString().isEmpty()) {
|
||||||
vpnConfigData[config_key::mtu] =
|
vpnConfigData[config_key::mtu] =
|
||||||
ContainerProps::isAwgContainer(container) ? protocols::awg::defaultMtu :
|
container == DockerContainer::Awg ? protocols::awg::defaultMtu : protocols::wireguard::defaultMtu;
|
||||||
protocols::wireguard::defaultMtu;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -121,7 +121,6 @@ namespace amnezia
|
|||||||
ApiMigrationError = 1110,
|
ApiMigrationError = 1110,
|
||||||
ApiUpdateRequestError = 1111,
|
ApiUpdateRequestError = 1111,
|
||||||
ApiSubscriptionExpiredError = 1112,
|
ApiSubscriptionExpiredError = 1112,
|
||||||
ApiPurchaseError = 1113,
|
|
||||||
|
|
||||||
// QFile errors
|
// QFile errors
|
||||||
OpenError = 1200,
|
OpenError = 1200,
|
||||||
|
|||||||
@@ -78,7 +78,6 @@ QString errorString(ErrorCode code) {
|
|||||||
case (ErrorCode::ApiMigrationError): errorMessage = QObject::tr("A migration error has occurred. Please contact our technical support"); break;
|
case (ErrorCode::ApiMigrationError): errorMessage = QObject::tr("A migration error has occurred. Please contact our technical support"); break;
|
||||||
case (ErrorCode::ApiUpdateRequestError): errorMessage = QObject::tr("Please update the application to use this feature"); break;
|
case (ErrorCode::ApiUpdateRequestError): errorMessage = QObject::tr("Please update the application to use this feature"); break;
|
||||||
case (ErrorCode::ApiSubscriptionExpiredError): errorMessage = QObject::tr("Your Amnezia Premium subscription has expired.\n Please check your email for renewal instructions.\n If you haven't received an email, please contact our support."); break;
|
case (ErrorCode::ApiSubscriptionExpiredError): errorMessage = QObject::tr("Your Amnezia Premium subscription has expired.\n Please check your email for renewal instructions.\n If you haven't received an email, please contact our support."); break;
|
||||||
case (ErrorCode::ApiPurchaseError): errorMessage = QObject::tr("Unable to process purchase"); break;
|
|
||||||
|
|
||||||
// QFile errors
|
// QFile errors
|
||||||
case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;
|
case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;
|
||||||
|
|||||||
+77
-45
@@ -1,71 +1,108 @@
|
|||||||
#include "ipcclient.h"
|
#include "ipcclient.h"
|
||||||
#include "ipc.h"
|
|
||||||
#include <QRemoteObjectNode>
|
#include <QRemoteObjectNode>
|
||||||
#include <QtNetwork/qlocalsocket.h>
|
|
||||||
|
IpcClient *IpcClient::m_instance = nullptr;
|
||||||
|
|
||||||
IpcClient::IpcClient(QObject *parent) : QObject(parent)
|
IpcClient::IpcClient(QObject *parent) : QObject(parent)
|
||||||
{
|
{
|
||||||
m_node.connectToNode(QUrl("local:" + amnezia::getIpcServiceUrl()));
|
|
||||||
m_interface.reset(m_node.acquire<IpcInterfaceReplica>());
|
|
||||||
m_tun2socks.reset(m_node.acquire<IpcProcessTun2SocksReplica>());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IpcClient& IpcClient::Instance()
|
IpcClient::~IpcClient()
|
||||||
{
|
{
|
||||||
thread_local IpcClient ipcClient;
|
if (m_localSocket)
|
||||||
return ipcClient;
|
m_localSocket->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IpcClient::isSocketConnected() const
|
||||||
|
{
|
||||||
|
return m_isSocketConnected;
|
||||||
|
}
|
||||||
|
|
||||||
|
IpcClient *IpcClient::Instance()
|
||||||
|
{
|
||||||
|
return m_instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
QSharedPointer<IpcInterfaceReplica> IpcClient::Interface()
|
QSharedPointer<IpcInterfaceReplica> IpcClient::Interface()
|
||||||
{
|
{
|
||||||
QSharedPointer<IpcInterfaceReplica> rep = Instance().m_interface;
|
if (!Instance())
|
||||||
if (rep.isNull()) {
|
|
||||||
qCritical() << "IpcClient::Interface(): Failed to acquire replica";
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
return Instance()->m_ipcClient;
|
||||||
if (!rep->waitForSource(1000)) {
|
|
||||||
qCritical() << "IpcClient::Interface(): Failed to initialize replica";
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
if (!rep->isReplicaValid()) {
|
|
||||||
qWarning() << "IpcClient::Interface(): Replica is invalid";
|
|
||||||
}
|
|
||||||
return rep;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QSharedPointer<IpcProcessTun2SocksReplica> IpcClient::InterfaceTun2Socks()
|
QSharedPointer<IpcProcessTun2SocksReplica> IpcClient::InterfaceTun2Socks()
|
||||||
{
|
{
|
||||||
QSharedPointer<IpcProcessTun2SocksReplica> rep = Instance().m_tun2socks;
|
if (!Instance())
|
||||||
if (rep.isNull()) {
|
|
||||||
qCritical() << "IpcClient::InterfaceTun2Socks: Replica is undefined";
|
|
||||||
return nullptr;
|
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!";
|
||||||
}
|
}
|
||||||
if (!rep->waitForSource(1000)) {
|
|
||||||
qCritical() << "IpcClient::InterfaceTun2Socks: Failed to initialize replica";
|
Instance()->m_ipcClient->waitForSource(1000);
|
||||||
return nullptr;
|
|
||||||
|
if (!Instance()->m_ipcClient->isReplicaValid()) {
|
||||||
|
qWarning() << "IpcClient replica is not connected!";
|
||||||
}
|
}
|
||||||
if (!rep->isReplicaValid()) {
|
|
||||||
qWarning() << "IpcClient::InterfaceTun2Socks(): Replica is invalid";
|
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!";
|
||||||
}
|
}
|
||||||
return rep;
|
|
||||||
|
Instance()->m_Tun2SocksClient->waitForSource(1000);
|
||||||
|
|
||||||
|
if (!Instance()->m_Tun2SocksClient->isReplicaValid()) {
|
||||||
|
qWarning() << "IpcClient::m_Tun2SocksClient replica is not connected!";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(Instance()->m_localSocket, &QLocalSocket::disconnected,
|
||||||
|
[instance]() { instance->m_isSocketConnected = false; });
|
||||||
|
|
||||||
|
Instance()->m_localSocket->connectToServer(amnezia::getIpcServiceUrl());
|
||||||
|
Instance()->m_localSocket->waitForConnected();
|
||||||
|
|
||||||
|
if (!Instance()->m_ipcClient) {
|
||||||
|
qDebug() << "IpcClient::init failed";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "IpcClient::init succeed";
|
||||||
|
|
||||||
|
return (Instance()->m_ipcClient->isReplicaValid() && Instance()->m_Tun2SocksClient->isReplicaValid());
|
||||||
}
|
}
|
||||||
|
|
||||||
QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
|
QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
|
||||||
{
|
{
|
||||||
QSharedPointer<IpcInterfaceReplica> rep = Interface();
|
if (!Instance()->m_ipcClient || !Instance()->m_ipcClient->isReplicaValid()) {
|
||||||
if (!rep) {
|
qWarning() << "IpcClient::createPrivilegedProcess : IpcClient IpcClient replica is not valid";
|
||||||
qCritical() << "IpcClient::createPrivilegedProcess: Replica is invalid";
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
QRemoteObjectPendingReply<int> pidReply = rep->createPrivilegedProcess();
|
QRemoteObjectPendingReply<int> futureResult = Instance()->m_ipcClient->createPrivilegedProcess();
|
||||||
if (!pidReply.waitForFinished(5000)){
|
futureResult.waitForFinished(5000);
|
||||||
qCritical() << "IpcClient::createPrivilegedProcess: Failed to execute RO createPrivilegedProcess call";
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
int pid = pidReply.returnValue();
|
int pid = futureResult.returnValue();
|
||||||
QSharedPointer<ProcessDescriptor> pd(new ProcessDescriptor());
|
|
||||||
|
auto pd = QSharedPointer<ProcessDescriptor>(new ProcessDescriptor());
|
||||||
|
Instance()->m_processNodes.insert(pid, pd);
|
||||||
|
|
||||||
pd->localSocket.reset(new QLocalSocket(pd->replicaNode.data()));
|
pd->localSocket.reset(new QLocalSocket(pd->replicaNode.data()));
|
||||||
|
|
||||||
@@ -73,7 +110,6 @@ QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
|
|||||||
pd->replicaNode->addClientSideConnection(pd->localSocket.data());
|
pd->replicaNode->addClientSideConnection(pd->localSocket.data());
|
||||||
|
|
||||||
IpcProcessInterfaceReplica *repl = pd->replicaNode->acquire<IpcProcessInterfaceReplica>();
|
IpcProcessInterfaceReplica *repl = pd->replicaNode->acquire<IpcProcessInterfaceReplica>();
|
||||||
// TODO: rework the unsafe cast below
|
|
||||||
PrivilegedProcess *priv = static_cast<PrivilegedProcess *>(repl);
|
PrivilegedProcess *priv = static_cast<PrivilegedProcess *>(repl);
|
||||||
pd->ipcProcess.reset(priv);
|
pd->ipcProcess.reset(priv);
|
||||||
if (!pd->ipcProcess) {
|
if (!pd->ipcProcess) {
|
||||||
@@ -88,12 +124,8 @@ QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
|
|||||||
[pd]() { pd->replicaNode->deleteLater(); });
|
[pd]() { pd->replicaNode->deleteLater(); });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
pd->localSocket->connectToServer(amnezia::getIpcProcessUrl(pid));
|
pd->localSocket->connectToServer(amnezia::getIpcProcessUrl(pid));
|
||||||
if (!pd->localSocket->waitForConnected()) {
|
pd->localSocket->waitForConnected();
|
||||||
qCritical() << "IpcClient::createPrivilegedProcess: Failed to connect to process' socket";
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto processReplica = QSharedPointer<PrivilegedProcess>(pd->ipcProcess);
|
auto processReplica = QSharedPointer<PrivilegedProcess>(pd->ipcProcess);
|
||||||
return processReplica;
|
return processReplica;
|
||||||
|
|||||||
+17
-32
@@ -4,6 +4,7 @@
|
|||||||
#include <QLocalSocket>
|
#include <QLocalSocket>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
|
#include "ipc.h"
|
||||||
#include "rep_ipc_interface_replica.h"
|
#include "rep_ipc_interface_replica.h"
|
||||||
#include "rep_ipc_process_tun2socks_replica.h"
|
#include "rep_ipc_process_tun2socks_replica.h"
|
||||||
|
|
||||||
@@ -15,46 +16,25 @@ class IpcClient : public QObject
|
|||||||
public:
|
public:
|
||||||
explicit IpcClient(QObject *parent = nullptr);
|
explicit IpcClient(QObject *parent = nullptr);
|
||||||
|
|
||||||
static IpcClient& Instance();
|
static IpcClient *Instance();
|
||||||
|
static bool init(IpcClient *instance);
|
||||||
static QSharedPointer<IpcInterfaceReplica> Interface();
|
static QSharedPointer<IpcInterfaceReplica> Interface();
|
||||||
static QSharedPointer<IpcProcessTun2SocksReplica> InterfaceTun2Socks();
|
static QSharedPointer<IpcProcessTun2SocksReplica> InterfaceTun2Socks();
|
||||||
static QSharedPointer<PrivilegedProcess> CreatePrivilegedProcess();
|
static QSharedPointer<PrivilegedProcess> CreatePrivilegedProcess();
|
||||||
|
|
||||||
template <typename Func>
|
bool isSocketConnected() const;
|
||||||
static auto withInterface(Func func)
|
|
||||||
{
|
|
||||||
QSharedPointer<IpcInterfaceReplica> iface = Instance().m_interface;
|
|
||||||
using ReturnType = decltype(func(std::declval<QSharedPointer<IpcInterfaceReplica>>()));
|
|
||||||
|
|
||||||
if (iface.isNull() || !iface->waitForSource(1000) || !iface->isReplicaValid()) {
|
|
||||||
qWarning() << "IpcClient::withInterface(): Service is not running";
|
|
||||||
|
|
||||||
if constexpr (std::is_void_v<ReturnType>)
|
|
||||||
return;
|
|
||||||
else
|
|
||||||
return ReturnType{};
|
|
||||||
}
|
|
||||||
|
|
||||||
return func(iface);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename OnSuccess, typename OnFailure>
|
|
||||||
static auto withInterface(OnSuccess onSuccess, OnFailure onFailure)
|
|
||||||
{
|
|
||||||
QSharedPointer<IpcInterfaceReplica> iface = Instance().m_interface;
|
|
||||||
if (iface.isNull() || !iface->waitForSource(1000) || !iface->isReplicaValid()) {
|
|
||||||
return onFailure();
|
|
||||||
}
|
|
||||||
|
|
||||||
return onSuccess(iface);
|
|
||||||
}
|
|
||||||
signals:
|
signals:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QRemoteObjectNode m_node;
|
~IpcClient() override;
|
||||||
QSharedPointer<IpcInterfaceReplica> m_interface;
|
|
||||||
QSharedPointer<IpcProcessTun2SocksReplica> m_tun2socks;
|
QRemoteObjectNode m_ClientNode;
|
||||||
|
QRemoteObjectNode m_Tun2SocksNode;
|
||||||
|
QSharedPointer<IpcInterfaceReplica> m_ipcClient;
|
||||||
|
QPointer<QLocalSocket> m_localSocket;
|
||||||
|
QPointer<QLocalSocket> m_tun2socksSocket;
|
||||||
|
QSharedPointer<IpcProcessTun2SocksReplica> m_Tun2SocksClient;
|
||||||
|
|
||||||
struct ProcessDescriptor {
|
struct ProcessDescriptor {
|
||||||
ProcessDescriptor () {
|
ProcessDescriptor () {
|
||||||
@@ -66,6 +46,11 @@ private:
|
|||||||
QSharedPointer<QRemoteObjectNode> replicaNode;
|
QSharedPointer<QRemoteObjectNode> replicaNode;
|
||||||
QSharedPointer<QLocalSocket> localSocket;
|
QSharedPointer<QLocalSocket> localSocket;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
QMap<int, QSharedPointer<ProcessDescriptor>> m_processNodes;
|
||||||
|
bool m_isSocketConnected {false};
|
||||||
|
|
||||||
|
static IpcClient *m_instance;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // IPCCLIENT_H
|
#endif // IPCCLIENT_H
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
#include "networkUtilities.h"
|
#include "networkUtilities.h"
|
||||||
#include <QtNetwork/qnetworkinterface.h>
|
|
||||||
#include <cstddef>
|
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <Ipexport.h>
|
#include <Ipexport.h>
|
||||||
#include <Ws2tcpip.h>
|
#include <Ws2tcpip.h>
|
||||||
#include <ws2ipdef.h>
|
#include <ws2ipdef.h>
|
||||||
|
#include <stdint.h>
|
||||||
#include <Iphlpapi.h>
|
#include <Iphlpapi.h>
|
||||||
#include <Iptypes.h>
|
#include <Iptypes.h>
|
||||||
#include <WinSock2.h>
|
#include <WinSock2.h>
|
||||||
@@ -31,20 +30,10 @@
|
|||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
#include <net/route.h>
|
#include <net/route.h>
|
||||||
#include <ifaddrs.h>
|
|
||||||
#include <net/if.h>
|
|
||||||
#include <net/if_dl.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <arpa/inet.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <ifaddrs.h>
|
|
||||||
#include <net/if.h>
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <QHostAddress>
|
#include <QHostAddress>
|
||||||
#include <QHostInfo>
|
#include <QHostInfo>
|
||||||
#include <QDebug>
|
|
||||||
|
|
||||||
QRegularExpression NetworkUtilities::ipAddressRegExp()
|
QRegularExpression NetworkUtilities::ipAddressRegExp()
|
||||||
{
|
{
|
||||||
@@ -181,7 +170,7 @@ int NetworkUtilities::AdapterIndexTo(const QHostAddress& dst) {
|
|||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
qDebug() << "Getting Current Internet Adapter that routes to"
|
qDebug() << "Getting Current Internet Adapter that routes to"
|
||||||
<< dst.toString();
|
<< dst.toString();
|
||||||
quint32 ipBigEndian;
|
quint32_be ipBigEndian;
|
||||||
quint32 ip = dst.toIPv4Address();
|
quint32 ip = dst.toIPv4Address();
|
||||||
qToBigEndian(ip, &ipBigEndian);
|
qToBigEndian(ip, &ipBigEndian);
|
||||||
_MIB_IPFORWARDROW routeInfo;
|
_MIB_IPFORWARDROW routeInfo;
|
||||||
@@ -250,14 +239,12 @@ DWORD GetAdaptersAddressesWrapper(const ULONG Family,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
QString NetworkUtilities::getGatewayAndIface()
|
||||||
{
|
{
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
constexpr int BUFF_LEN = 100;
|
constexpr int BUFF_LEN = 100;
|
||||||
char buff[BUFF_LEN] = {'\0'};
|
char buff[BUFF_LEN] = {'\0'};
|
||||||
|
QString result;
|
||||||
QString resGateway;
|
|
||||||
int resIndex = -1;
|
|
||||||
|
|
||||||
PIP_ADAPTER_ADDRESSES pAdapterAddresses = nullptr;
|
PIP_ADAPTER_ADDRESSES pAdapterAddresses = nullptr;
|
||||||
DWORD dwRetVal =
|
DWORD dwRetVal =
|
||||||
@@ -265,7 +252,7 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
|||||||
|
|
||||||
if (dwRetVal != NO_ERROR) {
|
if (dwRetVal != NO_ERROR) {
|
||||||
qDebug() << "ipv4 stack detect GetAdaptersAddresses failed.";
|
qDebug() << "ipv4 stack detect GetAdaptersAddresses failed.";
|
||||||
return {};
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
PIP_ADAPTER_ADDRESSES pCurAddress = pAdapterAddresses;
|
PIP_ADAPTER_ADDRESSES pCurAddress = pAdapterAddresses;
|
||||||
@@ -280,9 +267,7 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
|||||||
struct sockaddr_in addr;
|
struct sockaddr_in addr;
|
||||||
if (inet_pton(AF_INET, buff, &addr.sin_addr) == 1) {
|
if (inet_pton(AF_INET, buff, &addr.sin_addr) == 1) {
|
||||||
qDebug() << "this is true v4 !";
|
qDebug() << "this is true v4 !";
|
||||||
|
result = gw;
|
||||||
resGateway = gw;
|
|
||||||
resIndex = pCurAddress->IfIndex;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -290,7 +275,7 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
|||||||
}
|
}
|
||||||
|
|
||||||
free(pAdapterAddresses);
|
free(pAdapterAddresses);
|
||||||
return { resGateway, QNetworkInterface::interfaceFromIndex(resIndex) };
|
return result;
|
||||||
#endif
|
#endif
|
||||||
#ifdef Q_OS_LINUX
|
#ifdef Q_OS_LINUX
|
||||||
constexpr int BUFFER_SIZE = 100;
|
constexpr int BUFFER_SIZE = 100;
|
||||||
@@ -307,7 +292,7 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
|||||||
|
|
||||||
if ((sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) {
|
if ((sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) {
|
||||||
perror("socket failed");
|
perror("socket failed");
|
||||||
return {};
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
memset(msgbuf, 0, sizeof(msgbuf));
|
memset(msgbuf, 0, sizeof(msgbuf));
|
||||||
@@ -331,7 +316,7 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
|||||||
/* send msg */
|
/* send msg */
|
||||||
if (send(sock, nlmsg, nlmsg->nlmsg_len, 0) < 0) {
|
if (send(sock, nlmsg, nlmsg->nlmsg_len, 0) < 0) {
|
||||||
perror("send failed");
|
perror("send failed");
|
||||||
return {};
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
/* receive response */
|
/* receive response */
|
||||||
@@ -340,7 +325,7 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
|||||||
received_bytes = recv(sock, ptr, sizeof(buffer) - msg_len, 0);
|
received_bytes = recv(sock, ptr, sizeof(buffer) - msg_len, 0);
|
||||||
if (received_bytes < 0) {
|
if (received_bytes < 0) {
|
||||||
perror("Error in recv");
|
perror("Error in recv");
|
||||||
return {};
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
nlh = (struct nlmsghdr *) ptr;
|
nlh = (struct nlmsghdr *) ptr;
|
||||||
@@ -350,7 +335,7 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
|||||||
(nlmsg->nlmsg_type == NLMSG_ERROR))
|
(nlmsg->nlmsg_type == NLMSG_ERROR))
|
||||||
{
|
{
|
||||||
perror("Error in received packet");
|
perror("Error in received packet");
|
||||||
return {};
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If we received all data break */
|
/* If we received all data break */
|
||||||
@@ -403,12 +388,10 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
close(sock);
|
close(sock);
|
||||||
return { gateway_address, QNetworkInterface::interfaceFromName(interface) };
|
return gateway_address;
|
||||||
#endif
|
#endif
|
||||||
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||||
QString gateway;
|
QString gateway;
|
||||||
int index = -1;
|
|
||||||
|
|
||||||
int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_FLAGS, RTF_GATEWAY};
|
int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_FLAGS, RTF_GATEWAY};
|
||||||
int afinet_type[] = {AF_INET, AF_INET6};
|
int afinet_type[] = {AF_INET, AF_INET6};
|
||||||
|
|
||||||
@@ -418,17 +401,17 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
|||||||
|
|
||||||
size_t needed = 0;
|
size_t needed = 0;
|
||||||
if (sysctl(mib, sizeof(mib) / sizeof(int), nullptr, &needed, nullptr, 0) < 0)
|
if (sysctl(mib, sizeof(mib) / sizeof(int), nullptr, &needed, nullptr, 0) < 0)
|
||||||
return {};
|
return "";
|
||||||
|
|
||||||
char* buf;
|
char* buf;
|
||||||
if ((buf = new char[needed]) == 0)
|
if ((buf = new char[needed]) == 0)
|
||||||
return {};
|
return "";
|
||||||
|
|
||||||
if (sysctl(mib, sizeof(mib) / sizeof(int), buf, &needed, nullptr, 0) < 0)
|
if (sysctl(mib, sizeof(mib) / sizeof(int), buf, &needed, nullptr, 0) < 0)
|
||||||
{
|
{
|
||||||
qDebug() << "sysctl: net.route.0.0.dump";
|
qDebug() << "sysctl: net.route.0.0.dump";
|
||||||
delete[] buf;
|
delete[] buf;
|
||||||
return {};
|
return gateway;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct rt_msghdr* rt;
|
struct rt_msghdr* rt;
|
||||||
@@ -466,10 +449,7 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
|||||||
&(reinterpret_cast<struct sockaddr_in*>(sa_tab[RTAX_GATEWAY]))->sin_addr,
|
&(reinterpret_cast<struct sockaddr_in*>(sa_tab[RTAX_GATEWAY]))->sin_addr,
|
||||||
sizeof(struct in_addr));
|
sizeof(struct in_addr));
|
||||||
if (inet_ntop(AF_INET, srcStr4, dstStr4, INET_ADDRSTRLEN) != nullptr)
|
if (inet_ntop(AF_INET, srcStr4, dstStr4, INET_ADDRSTRLEN) != nullptr)
|
||||||
{
|
|
||||||
gateway = dstStr4;
|
gateway = dstStr4;
|
||||||
index = rt->rtm_index;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -483,10 +463,7 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
|||||||
&(reinterpret_cast<struct sockaddr_in6*>(sa_tab[RTAX_GATEWAY]))->sin6_addr,
|
&(reinterpret_cast<struct sockaddr_in6*>(sa_tab[RTAX_GATEWAY]))->sin6_addr,
|
||||||
sizeof(struct in6_addr));
|
sizeof(struct in6_addr));
|
||||||
if (inet_ntop(AF_INET6, srcStr6, dstStr6, INET6_ADDRSTRLEN) != nullptr)
|
if (inet_ntop(AF_INET6, srcStr6, dstStr6, INET6_ADDRSTRLEN) != nullptr)
|
||||||
{
|
|
||||||
gateway = dstStr6;
|
gateway = dstStr6;
|
||||||
index = rt->rtm_index;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -495,6 +472,6 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
|||||||
free(buf);
|
free(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { gateway, QNetworkInterface::interfaceFromIndex(index) };
|
return gateway;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QHostAddress>
|
#include <QHostAddress>
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
#include <QtNetwork/qnetworkinterface.h>
|
|
||||||
|
|
||||||
class NetworkUtilities : public QObject
|
class NetworkUtilities : public QObject
|
||||||
{
|
{
|
||||||
@@ -17,7 +17,7 @@ public:
|
|||||||
static bool checkIPv4Format(const QString &ip);
|
static bool checkIPv4Format(const QString &ip);
|
||||||
static bool checkIpSubnetFormat(const QString &ip);
|
static bool checkIpSubnetFormat(const QString &ip);
|
||||||
static bool checkIpv6Enabled();
|
static bool checkIpv6Enabled();
|
||||||
static QPair<QString, QNetworkInterface> getGatewayAndIface();
|
static QString getGatewayAndIface();
|
||||||
// Returns the Interface Index that could Route to dst
|
// Returns the Interface Index that could Route to dst
|
||||||
static int AdapterIndexTo(const QHostAddress& dst);
|
static int AdapterIndexTo(const QHostAddress& dst);
|
||||||
|
|
||||||
|
|||||||
@@ -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(); });
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
@@ -11,8 +11,7 @@ QString amnezia::scriptFolder(amnezia::DockerContainer container)
|
|||||||
case DockerContainer::Cloak: return QLatin1String("openvpn_cloak");
|
case DockerContainer::Cloak: return QLatin1String("openvpn_cloak");
|
||||||
case DockerContainer::ShadowSocks: return QLatin1String("openvpn_shadowsocks");
|
case DockerContainer::ShadowSocks: return QLatin1String("openvpn_shadowsocks");
|
||||||
case DockerContainer::WireGuard: return QLatin1String("wireguard");
|
case DockerContainer::WireGuard: return QLatin1String("wireguard");
|
||||||
case DockerContainer::Awg2: return QLatin1String("awg");
|
case DockerContainer::Awg: return QLatin1String("awg");
|
||||||
case DockerContainer::Awg: return QLatin1String("awg_legacy");
|
|
||||||
case DockerContainer::Ipsec: return QLatin1String("ipsec");
|
case DockerContainer::Ipsec: return QLatin1String("ipsec");
|
||||||
case DockerContainer::Xray: return QLatin1String("xray");
|
case DockerContainer::Xray: return QLatin1String("xray");
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ namespace amnezia::serialization
|
|||||||
namespace vless
|
namespace vless
|
||||||
{
|
{
|
||||||
QJsonObject Deserialize(const QString &vless, QString *alias, QString *errMessage);
|
QJsonObject Deserialize(const QString &vless, QString *alias, QString *errMessage);
|
||||||
const QString Serialize(const VlessServerObject &server, const QString &alias);
|
|
||||||
} // namespace vless
|
} // namespace vless
|
||||||
|
|
||||||
namespace ss
|
namespace ss
|
||||||
|
|||||||
@@ -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
|
namespace transfer
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|||||||
@@ -252,65 +252,5 @@ QJsonObject Deserialize(const QString &str, QString *alias, QString *errMessage)
|
|||||||
root["inbounds"] = QJsonArray { inbound };
|
root["inbounds"] = QJsonArray { inbound };
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
} // namespace amnezia::serialization::vless
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -440,6 +440,18 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
|
|||||||
if (!obj.value("I5").isNull()) {
|
if (!obj.value("I5").isNull()) {
|
||||||
config.m_specialJunk["I5"] = obj.value("I5").toString();
|
config.m_specialJunk["I5"] = obj.value("I5").toString();
|
||||||
}
|
}
|
||||||
|
if (!obj.value("J1").isNull()) {
|
||||||
|
config.m_controlledJunk["J1"] = obj.value("J1").toString();
|
||||||
|
}
|
||||||
|
if (!obj.value("J2").isNull()) {
|
||||||
|
config.m_controlledJunk["J2"] = obj.value("J2").toString();
|
||||||
|
}
|
||||||
|
if (!obj.value("J3").isNull()) {
|
||||||
|
config.m_controlledJunk["J3"] = obj.value("J3").toString();
|
||||||
|
}
|
||||||
|
if (!obj.value("Itime").isNull()) {
|
||||||
|
config.m_specialHandshakeTimeout = obj.value("Itime").toString();
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,6 +152,12 @@ QString InterfaceConfig::toWgConf(const QMap<QString, QString>& extra) const {
|
|||||||
for (const QString& key : m_specialJunk.keys()) {
|
for (const QString& key : m_specialJunk.keys()) {
|
||||||
out << key << " = " << m_specialJunk[key] << "\n";
|
out << key << " = " << m_specialJunk[key] << "\n";
|
||||||
}
|
}
|
||||||
|
for (const QString& key : m_controlledJunk.keys()) {
|
||||||
|
out << key << " = " << m_controlledJunk[key] << "\n";
|
||||||
|
}
|
||||||
|
if (!m_specialHandshakeTimeout.isNull()) {
|
||||||
|
out << "Itime = " << m_specialHandshakeTimeout << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
// If any extra config was provided, append it now.
|
// If any extra config was provided, append it now.
|
||||||
for (const QString& key : extra.keys()) {
|
for (const QString& key : extra.keys()) {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QMap>
|
|
||||||
#include "ipaddress.h"
|
#include "ipaddress.h"
|
||||||
|
|
||||||
class QJsonObject;
|
class QJsonObject;
|
||||||
@@ -57,6 +57,8 @@ class InterfaceConfig {
|
|||||||
QString m_underloadPacketMagicHeader;
|
QString m_underloadPacketMagicHeader;
|
||||||
QString m_transportPacketMagicHeader;
|
QString m_transportPacketMagicHeader;
|
||||||
QMap<QString, QString> m_specialJunk;
|
QMap<QString, QString> m_specialJunk;
|
||||||
|
QMap<QString, QString> m_controlledJunk;
|
||||||
|
QString m_specialHandshakeTimeout;
|
||||||
|
|
||||||
QJsonObject toJson() const;
|
QJsonObject toJson() const;
|
||||||
QString toWgConf(
|
QString toWgConf(
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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 |
@@ -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 |
@@ -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 |
@@ -32,41 +32,17 @@
|
|||||||
<false/>
|
<false/>
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>AmneziaVPNLaunchScreen</string>
|
<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>
|
<key>UIRequiredDeviceCapabilities</key>
|
||||||
<array/>
|
<array/>
|
||||||
<key>UIRequiresFullScreen</key>
|
<key>UIRequiresFullScreen</key>
|
||||||
<false/>
|
<true/>
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
<array>
|
<array>
|
||||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
<array>
|
<array/>
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
|
||||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
|
||||||
</array>
|
|
||||||
<key>UIUserInterfaceStyle</key>
|
<key>UIUserInterfaceStyle</key>
|
||||||
<string>Light</string>
|
<string>Light</string>
|
||||||
<key>com.wireguard.ios.app_group_id</key>
|
<key>com.wireguard.ios.app_group_id</key>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
|
|
||||||
#include "amnezia_application.h"
|
#include "amnezia_application.h"
|
||||||
#include "core/osSignalHandler.h"
|
|
||||||
#include "migrations.h"
|
#include "migrations.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
|
|
||||||
@@ -45,7 +44,6 @@ int main(int argc, char *argv[])
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
AmneziaApplication app(argc, argv);
|
AmneziaApplication app(argc, argv);
|
||||||
OsSignalHandler::setup();
|
|
||||||
|
|
||||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||||
if (isAnotherInstanceRunning()) {
|
if (isAnotherInstanceRunning()) {
|
||||||
|
|||||||
@@ -5,9 +5,6 @@
|
|||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#include <QCoreApplication>
|
|
||||||
#include <QDateTime>
|
|
||||||
#include <QDebug>
|
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QHostAddress>
|
#include <QHostAddress>
|
||||||
@@ -15,13 +12,12 @@
|
|||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QJsonValue>
|
#include <QJsonValue>
|
||||||
#include <QLocalSocket>
|
|
||||||
#include <QObject>
|
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
#include <QTimer>
|
|
||||||
|
|
||||||
|
#include "ipaddress.h"
|
||||||
#include "leakdetector.h"
|
#include "leakdetector.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
|
#include "models/server.h"
|
||||||
#include "daemon/daemonerrors.h"
|
#include "daemon/daemonerrors.h"
|
||||||
|
|
||||||
#include "protocols/protocols_defs.h"
|
#include "protocols/protocols_defs.h"
|
||||||
@@ -119,6 +115,7 @@ void LocalSocketController::daemonConnected() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
||||||
|
|
||||||
QString protocolName = rawConfig.value("protocol").toString();
|
QString protocolName = rawConfig.value("protocol").toString();
|
||||||
|
|
||||||
int splitTunnelType = rawConfig.value("splitTunnelType").toInt();
|
int splitTunnelType = rawConfig.value("splitTunnelType").toInt();
|
||||||
@@ -135,16 +132,13 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
|||||||
// json.insert("hopindex", QJsonValue((double)hop.m_hopindex));
|
// json.insert("hopindex", QJsonValue((double)hop.m_hopindex));
|
||||||
json.insert("privateKey", wgConfig.value(amnezia::config_key::client_priv_key));
|
json.insert("privateKey", wgConfig.value(amnezia::config_key::client_priv_key));
|
||||||
json.insert("deviceIpv4Address", wgConfig.value(amnezia::config_key::client_ip));
|
json.insert("deviceIpv4Address", wgConfig.value(amnezia::config_key::client_ip));
|
||||||
m_deviceIpv4 = wgConfig.value(amnezia::config_key::client_ip).toString();
|
|
||||||
|
|
||||||
// set up IPv6 unique-local-address, ULA, with "fd00::/8" prefix, not globally routable.
|
// set up IPv6 unique-local-address, ULA, with "fd00::/8" prefix, not globally routable.
|
||||||
// this will be default IPv6 gateway, OS recognizes that IPv6 link is local and switches to IPv4.
|
// this will be default IPv6 gateway, OS recognizes that IPv6 link is local and switches to IPv4.
|
||||||
// Otherwise some OSes (Linux) try IPv6 forever and hang.
|
// Otherwise some OSes (Linux) try IPv6 forever and hang.
|
||||||
// https://en.wikipedia.org/wiki/Unique_local_address (RFC 4193)
|
// https://en.wikipedia.org/wiki/Unique_local_address (RFC 4193)
|
||||||
// https://man7.org/linux/man-pages/man5/gai.conf.5.html
|
// https://man7.org/linux/man-pages/man5/gai.conf.5.html
|
||||||
|
json.insert("deviceIpv6Address", "fd58:baa6:dead::1"); // simply "dead::1" is globally-routable, don't use it
|
||||||
// simply "dead::1" is globally-routable, don't use it
|
|
||||||
json.insert("deviceIpv6Address", "fd58:baa6:dead::1");
|
|
||||||
|
|
||||||
json.insert("serverPublicKey", wgConfig.value(amnezia::config_key::server_pub_key));
|
json.insert("serverPublicKey", wgConfig.value(amnezia::config_key::server_pub_key));
|
||||||
json.insert("serverPskKey", wgConfig.value(amnezia::config_key::psk_key));
|
json.insert("serverPskKey", wgConfig.value(amnezia::config_key::psk_key));
|
||||||
@@ -226,6 +220,7 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
|||||||
|
|
||||||
json.insert("allowedIPAddressRanges", jsAllowedIPAddesses);
|
json.insert("allowedIPAddressRanges", jsAllowedIPAddesses);
|
||||||
|
|
||||||
|
|
||||||
QJsonArray jsExcludedAddresses;
|
QJsonArray jsExcludedAddresses;
|
||||||
jsExcludedAddresses.append(wgConfig.value(amnezia::config_key::hostName));
|
jsExcludedAddresses.append(wgConfig.value(amnezia::config_key::hostName));
|
||||||
if (splitTunnelType == 2) {
|
if (splitTunnelType == 2) {
|
||||||
@@ -260,6 +255,10 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
|||||||
json.insert(amnezia::config_key::specialJunk3, wgConfig.value(amnezia::config_key::specialJunk3));
|
json.insert(amnezia::config_key::specialJunk3, wgConfig.value(amnezia::config_key::specialJunk3));
|
||||||
json.insert(amnezia::config_key::specialJunk4, wgConfig.value(amnezia::config_key::specialJunk4));
|
json.insert(amnezia::config_key::specialJunk4, wgConfig.value(amnezia::config_key::specialJunk4));
|
||||||
json.insert(amnezia::config_key::specialJunk5, wgConfig.value(amnezia::config_key::specialJunk5));
|
json.insert(amnezia::config_key::specialJunk5, wgConfig.value(amnezia::config_key::specialJunk5));
|
||||||
|
json.insert(amnezia::config_key::controlledJunk1, wgConfig.value(amnezia::config_key::controlledJunk1));
|
||||||
|
json.insert(amnezia::config_key::controlledJunk2, wgConfig.value(amnezia::config_key::controlledJunk2));
|
||||||
|
json.insert(amnezia::config_key::controlledJunk3, wgConfig.value(amnezia::config_key::controlledJunk3));
|
||||||
|
json.insert(amnezia::config_key::specialHandshakeTimeout, wgConfig.value(amnezia::config_key::specialHandshakeTimeout));
|
||||||
} else if (!wgConfig.value(amnezia::config_key::junkPacketCount).isUndefined()
|
} else if (!wgConfig.value(amnezia::config_key::junkPacketCount).isUndefined()
|
||||||
&& !wgConfig.value(amnezia::config_key::junkPacketMinSize).isUndefined()
|
&& !wgConfig.value(amnezia::config_key::junkPacketMinSize).isUndefined()
|
||||||
&& !wgConfig.value(amnezia::config_key::junkPacketMaxSize).isUndefined()
|
&& !wgConfig.value(amnezia::config_key::junkPacketMaxSize).isUndefined()
|
||||||
@@ -275,7 +274,11 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
|||||||
&& !wgConfig.value(amnezia::config_key::specialJunk2).isUndefined()
|
&& !wgConfig.value(amnezia::config_key::specialJunk2).isUndefined()
|
||||||
&& !wgConfig.value(amnezia::config_key::specialJunk3).isUndefined()
|
&& !wgConfig.value(amnezia::config_key::specialJunk3).isUndefined()
|
||||||
&& !wgConfig.value(amnezia::config_key::specialJunk4).isUndefined()
|
&& !wgConfig.value(amnezia::config_key::specialJunk4).isUndefined()
|
||||||
&& !wgConfig.value(amnezia::config_key::specialJunk5).isUndefined()) {
|
&& !wgConfig.value(amnezia::config_key::specialJunk5).isUndefined()
|
||||||
|
&& !wgConfig.value(amnezia::config_key::controlledJunk1).isUndefined()
|
||||||
|
&& !wgConfig.value(amnezia::config_key::controlledJunk2).isUndefined()
|
||||||
|
&& !wgConfig.value(amnezia::config_key::controlledJunk3).isUndefined()
|
||||||
|
&& !wgConfig.value(amnezia::config_key::specialHandshakeTimeout).isUndefined()) {
|
||||||
json.insert(amnezia::config_key::junkPacketCount, wgConfig.value(amnezia::config_key::junkPacketCount));
|
json.insert(amnezia::config_key::junkPacketCount, wgConfig.value(amnezia::config_key::junkPacketCount));
|
||||||
json.insert(amnezia::config_key::junkPacketMinSize, wgConfig.value(amnezia::config_key::junkPacketMinSize));
|
json.insert(amnezia::config_key::junkPacketMinSize, wgConfig.value(amnezia::config_key::junkPacketMinSize));
|
||||||
json.insert(amnezia::config_key::junkPacketMaxSize, wgConfig.value(amnezia::config_key::junkPacketMaxSize));
|
json.insert(amnezia::config_key::junkPacketMaxSize, wgConfig.value(amnezia::config_key::junkPacketMaxSize));
|
||||||
@@ -292,6 +295,10 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
|||||||
json.insert(amnezia::config_key::specialJunk3, wgConfig.value(amnezia::config_key::specialJunk3));
|
json.insert(amnezia::config_key::specialJunk3, wgConfig.value(amnezia::config_key::specialJunk3));
|
||||||
json.insert(amnezia::config_key::specialJunk4, wgConfig.value(amnezia::config_key::specialJunk4));
|
json.insert(amnezia::config_key::specialJunk4, wgConfig.value(amnezia::config_key::specialJunk4));
|
||||||
json.insert(amnezia::config_key::specialJunk5, wgConfig.value(amnezia::config_key::specialJunk5));
|
json.insert(amnezia::config_key::specialJunk5, wgConfig.value(amnezia::config_key::specialJunk5));
|
||||||
|
json.insert(amnezia::config_key::controlledJunk1, wgConfig.value(amnezia::config_key::controlledJunk1));
|
||||||
|
json.insert(amnezia::config_key::controlledJunk2, wgConfig.value(amnezia::config_key::controlledJunk2));
|
||||||
|
json.insert(amnezia::config_key::controlledJunk3, wgConfig.value(amnezia::config_key::controlledJunk3));
|
||||||
|
json.insert(amnezia::config_key::specialHandshakeTimeout, wgConfig.value(amnezia::config_key::specialHandshakeTimeout));
|
||||||
}
|
}
|
||||||
|
|
||||||
write(json);
|
write(json);
|
||||||
@@ -442,7 +449,6 @@ void LocalSocketController::parseCommand(const QByteArray& command) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (type == "status") {
|
if (type == "status") {
|
||||||
|
|
||||||
QJsonValue serverIpv4Gateway = obj.value("serverIpv4Gateway");
|
QJsonValue serverIpv4Gateway = obj.value("serverIpv4Gateway");
|
||||||
if (!serverIpv4Gateway.isString()) {
|
if (!serverIpv4Gateway.isString()) {
|
||||||
logger.error() << "Unexpected serverIpv4Gateway value";
|
logger.error() << "Unexpected serverIpv4Gateway value";
|
||||||
@@ -487,11 +493,6 @@ void LocalSocketController::parseCommand(const QByteArray& command) {
|
|||||||
|
|
||||||
logger.debug() << "Handshake completed with:"
|
logger.debug() << "Handshake completed with:"
|
||||||
<< pubkey.toString();
|
<< pubkey.toString();
|
||||||
|
|
||||||
checkStatus();
|
|
||||||
|
|
||||||
emit statusUpdated("", m_deviceIpv4, 0, 0);
|
|
||||||
|
|
||||||
emit connected(pubkey.toString());
|
emit connected(pubkey.toString());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
|
|
||||||
#include "controllerimpl.h"
|
#include "controllerimpl.h"
|
||||||
|
|
||||||
|
|
||||||
class QJsonObject;
|
class QJsonObject;
|
||||||
|
|
||||||
class LocalSocketController final : public ControllerImpl {
|
class LocalSocketController final : public ControllerImpl {
|
||||||
@@ -59,7 +58,6 @@ class LocalSocketController final : public ControllerImpl {
|
|||||||
|
|
||||||
QByteArray m_buffer;
|
QByteArray m_buffer;
|
||||||
|
|
||||||
QString m_deviceIpv4;
|
|
||||||
std::function<void(const QString&)> m_logCallback = nullptr;
|
std::function<void(const QString&)> m_logCallback = nullptr;
|
||||||
|
|
||||||
QTimer m_initializingTimer;
|
QTimer m_initializingTimer;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
//#include "mozillavpn.h"
|
//#include "mozillavpn.h"
|
||||||
#include "networkwatcherimpl.h"
|
#include "networkwatcherimpl.h"
|
||||||
|
#include "platforms/dummy/dummynetworkwatcher.h"
|
||||||
//#include "settingsholder.h"
|
//#include "settingsholder.h"
|
||||||
|
|
||||||
#ifdef MZ_WINDOWS
|
#ifdef MZ_WINDOWS
|
||||||
@@ -50,7 +51,7 @@ NetworkWatcher::NetworkWatcher() { MZ_COUNT_CTOR(NetworkWatcher); }
|
|||||||
NetworkWatcher::~NetworkWatcher() { MZ_COUNT_DTOR(NetworkWatcher); }
|
NetworkWatcher::~NetworkWatcher() { MZ_COUNT_DTOR(NetworkWatcher); }
|
||||||
|
|
||||||
void NetworkWatcher::initialize() {
|
void NetworkWatcher::initialize() {
|
||||||
logger.debug() << "Initialize NetworkWatcher";
|
logger.debug() << "Initialize";
|
||||||
|
|
||||||
#if defined(MZ_WINDOWS)
|
#if defined(MZ_WINDOWS)
|
||||||
m_impl = new WindowsNetworkWatcher(this);
|
m_impl = new WindowsNetworkWatcher(this);
|
||||||
@@ -68,45 +69,59 @@ void NetworkWatcher::initialize() {
|
|||||||
m_impl = new DummyNetworkWatcher(this);
|
m_impl = new DummyNetworkWatcher(this);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
connect(m_impl, &NetworkWatcherImpl::unsecuredNetwork, this,
|
connect(m_impl, &NetworkWatcherImpl::unsecuredNetwork, this,
|
||||||
&NetworkWatcher::unsecuredNetwork);
|
&NetworkWatcher::unsecuredNetwork);
|
||||||
connect(m_impl, &NetworkWatcherImpl::networkChanged, this,
|
connect(m_impl, &NetworkWatcherImpl::networkChanged, this,
|
||||||
&NetworkWatcher::networkChange);
|
&NetworkWatcher::networkChange);
|
||||||
connect(m_impl, &NetworkWatcherImpl::sleepMode, this,
|
|
||||||
&NetworkWatcher::onSleepMode);
|
|
||||||
m_impl->initialize();
|
m_impl->initialize();
|
||||||
|
|
||||||
// Enable sleep/wake monitoring for VPN auto-reconnection
|
|
||||||
logger.debug() << "Starting NetworkWatcher for sleep/wake monitoring";
|
// TODO: IMPL FOR AMNEZIA
|
||||||
logger.debug() << "About to call m_impl->start()";
|
#if 0
|
||||||
try {
|
SettingsHolder* settingsHolder = SettingsHolder::instance();
|
||||||
|
Q_ASSERT(settingsHolder);
|
||||||
|
|
||||||
|
m_active = settingsHolder->unsecuredNetworkAlert() ||
|
||||||
|
settingsHolder->captivePortalAlert();
|
||||||
|
m_reportUnsecuredNetwork = settingsHolder->unsecuredNetworkAlert();
|
||||||
|
if (m_active) {
|
||||||
m_impl->start();
|
m_impl->start();
|
||||||
logger.debug() << "m_impl->start() completed successfully";
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
logger.error() << "Exception in m_impl->start():" << e.what();
|
|
||||||
} catch (...) {
|
|
||||||
logger.error() << "Unknown exception in m_impl->start()";
|
|
||||||
}
|
}
|
||||||
m_active = true;
|
|
||||||
m_reportUnsecuredNetwork = false; // Disable unsecured network alerts for Amnezia
|
connect(settingsHolder, &SettingsHolder::unsecuredNetworkAlertChanged, this,
|
||||||
|
&NetworkWatcher::settingsChanged);
|
||||||
|
connect(settingsHolder, &SettingsHolder::captivePortalAlertChanged, this,
|
||||||
|
&NetworkWatcher::settingsChanged);
|
||||||
|
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkWatcher::settingsChanged() {
|
void NetworkWatcher::settingsChanged() {
|
||||||
// For Amnezia: Keep NetworkWatcher always active for sleep/wake monitoring
|
// TODO: IMPL FOR AMNEZIA
|
||||||
logger.debug() << "NetworkWatcher settings changed - keeping sleep monitoring active";
|
#if 0
|
||||||
}
|
SettingsHolder* settingsHolder = SettingsHolder::instance();
|
||||||
|
m_active = settingsHolder->unsecuredNetworkAlert() ||
|
||||||
|
settingsHolder->captivePortalAlert();
|
||||||
|
m_reportUnsecuredNetwork = settingsHolder->unsecuredNetworkAlert();
|
||||||
|
|
||||||
void NetworkWatcher::onSleepMode()
|
if (m_active) {
|
||||||
{
|
logger.debug()
|
||||||
logger.debug() << "Resumed from sleep mode";
|
<< "Starting Network Watcher; Reporting of Unsecured Networks: "
|
||||||
emit sleepMode();
|
<< m_reportUnsecuredNetwork;
|
||||||
|
m_impl->start();
|
||||||
|
} else {
|
||||||
|
logger.debug() << "Stopping Network Watcher";
|
||||||
|
m_impl->stop();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkWatcher::unsecuredNetwork(const QString& networkName,
|
void NetworkWatcher::unsecuredNetwork(const QString& networkName,
|
||||||
const QString& networkId) {
|
const QString& networkId) {
|
||||||
logger.debug() << "Unsecured network:" << logger.sensitive(networkName)
|
logger.debug() << "Unsecured network:" << logger.sensitive(networkName)
|
||||||
<< "id:" << logger.sensitive(networkId);
|
<< "id:" << logger.sensitive(networkId);
|
||||||
|
|
||||||
#ifndef UNIT_TEST
|
#ifndef UNIT_TEST
|
||||||
if (!m_reportUnsecuredNetwork) {
|
if (!m_reportUnsecuredNetwork) {
|
||||||
logger.debug() << "Disabled. Ignoring unsecured network";
|
logger.debug() << "Disabled. Ignoring unsecured network";
|
||||||
|
|||||||
@@ -29,13 +29,10 @@ public:
|
|||||||
// false to restore.
|
// false to restore.
|
||||||
void simulateDisconnection(bool simulatedDisconnection);
|
void simulateDisconnection(bool simulatedDisconnection);
|
||||||
|
|
||||||
void onSleepMode();
|
|
||||||
|
|
||||||
QNetworkInformation::Reachability getReachability();
|
QNetworkInformation::Reachability getReachability();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void networkChange();
|
void networkChange();
|
||||||
void sleepMode();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void settingsChanged();
|
void settingsChanged();
|
||||||
|
|||||||
@@ -41,8 +41,6 @@ signals:
|
|||||||
// TODO: Only windows-networkwatcher has this, the other plattforms should
|
// TODO: Only windows-networkwatcher has this, the other plattforms should
|
||||||
// too.
|
// too.
|
||||||
void networkChanged(QString newBSSID);
|
void networkChanged(QString newBSSID);
|
||||||
void sleepMode();
|
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool m_active = false;
|
bool m_active = false;
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ void PingHelper::start(const QString& serverIpv4Gateway,
|
|||||||
|
|
||||||
m_gateway = QHostAddress(serverIpv4Gateway);
|
m_gateway = QHostAddress(serverIpv4Gateway);
|
||||||
m_source = QHostAddress(deviceIpv4Address.section('/', 0, 0));
|
m_source = QHostAddress(deviceIpv4Address.section('/', 0, 0));
|
||||||
|
|
||||||
m_pingSender = PingSenderFactory::create(m_source, this);
|
m_pingSender = PingSenderFactory::create(m_source, this);
|
||||||
|
|
||||||
// Some platforms require root access to send and receive ICMP pings. If
|
// Some platforms require root access to send and receive ICMP pings. If
|
||||||
@@ -54,10 +53,8 @@ void PingHelper::start(const QString& serverIpv4Gateway,
|
|||||||
|
|
||||||
connect(m_pingSender, &PingSender::recvPing, this, &PingHelper::pingReceived,
|
connect(m_pingSender, &PingSender::recvPing, this, &PingHelper::pingReceived,
|
||||||
Qt::QueuedConnection);
|
Qt::QueuedConnection);
|
||||||
connect(m_pingSender, &PingSender::criticalPingError, this, [this]() {
|
connect(m_pingSender, &PingSender::criticalPingError, this,
|
||||||
logger.info() << "Encountered Unrecoverable ping error";
|
[]() { logger.info() << "Encountered Unrecoverable ping error"; });
|
||||||
emit connectionLose();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Reset the ping statistics
|
// Reset the ping statistics
|
||||||
m_sequence = 0;
|
m_sequence = 0;
|
||||||
|
|||||||
@@ -33,8 +33,6 @@ class PingHelper final : public QObject {
|
|||||||
|
|
||||||
signals:
|
signals:
|
||||||
void pingSentAndReceived(qint64 msec);
|
void pingSentAndReceived(qint64 msec);
|
||||||
void connectionLose();
|
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void nextPing();
|
void nextPing();
|
||||||
|
|||||||
@@ -5,21 +5,22 @@
|
|||||||
#include "pingsenderfactory.h"
|
#include "pingsenderfactory.h"
|
||||||
|
|
||||||
#if defined(MZ_LINUX) || defined(MZ_ANDROID)
|
#if defined(MZ_LINUX) || defined(MZ_ANDROID)
|
||||||
# include "platforms/linux/linuxpingsender.h"
|
//# include "platforms/linux/linuxpingsender.h"
|
||||||
#elif defined(MZ_MACOS) || defined(MZ_IOS)
|
#elif defined(MZ_MACOS) || defined(MZ_IOS)
|
||||||
# include "platforms/macos/macospingsender.h"
|
# include "platforms/macos/macospingsender.h"
|
||||||
#elif defined(MZ_WINDOWS)
|
#elif defined(MZ_WINDOWS)
|
||||||
# include "platforms/windows/windowspingsender.h"
|
# include "platforms/windows/windowspingsender.h"
|
||||||
#elif defined(MZ_WASM) || defined(UNIT_TEST)
|
#elif defined(MZ_DUMMY) || defined(UNIT_TEST)
|
||||||
# include "platforms/dummy/dummypingsender.h"
|
# include "platforms/dummy/dummypingsender.h"
|
||||||
#else
|
#else
|
||||||
# error "Unsupported platform"
|
# error "Unsupported platform"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
PingSender* PingSenderFactory::create(const QHostAddress& source,
|
PingSender* PingSenderFactory::create(const QHostAddress& source,
|
||||||
QObject* parent) {
|
QObject* parent) {
|
||||||
#if defined(MZ_LINUX) || defined(MZ_ANDROID)
|
#if defined(MZ_LINUX) || defined(MZ_ANDROID)
|
||||||
return new LinuxPingSender(source, parent);
|
return nullptr;
|
||||||
|
// return new LinuxPingSender(source, parent);
|
||||||
#elif defined(MZ_MACOS) || defined(MZ_IOS)
|
#elif defined(MZ_MACOS) || defined(MZ_IOS)
|
||||||
return new MacOSPingSender(source, parent);
|
return new MacOSPingSender(source, parent);
|
||||||
#elif defined(MZ_WINDOWS)
|
#elif defined(MZ_WINDOWS)
|
||||||
|
|||||||
@@ -10,10 +10,9 @@ class QHostAddress;
|
|||||||
class QObject;
|
class QObject;
|
||||||
|
|
||||||
class PingSenderFactory final {
|
class PingSenderFactory final {
|
||||||
public:
|
public:
|
||||||
PingSenderFactory() = delete;
|
PingSenderFactory() = delete;
|
||||||
static PingSender* create(const QHostAddress& source, QObject* parent);
|
static PingSender* create(const QHostAddress& source, QObject* parent);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
#endif // PINGSENDERFACTORY_H
|
#endif // PINGSENDERFACTORY_H
|
||||||
|
|||||||
@@ -99,9 +99,7 @@ bool AndroidController::initialize()
|
|||||||
{"onFileOpened", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onFileOpened)},
|
{"onFileOpened", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onFileOpened)},
|
||||||
{"onConfigImported", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onConfigImported)},
|
{"onConfigImported", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onConfigImported)},
|
||||||
{"onAuthResult", "(Z)V", reinterpret_cast<void *>(onAuthResult)},
|
{"onAuthResult", "(Z)V", reinterpret_cast<void *>(onAuthResult)},
|
||||||
{"decodeQrCode", "(Ljava/lang/String;)Z", reinterpret_cast<bool *>(decodeQrCode)},
|
{"decodeQrCode", "(Ljava/lang/String;)Z", reinterpret_cast<bool *>(decodeQrCode)}
|
||||||
{"onImeInsetsChanged", "(I)V", reinterpret_cast<void *>(onImeInsetsChanged)},
|
|
||||||
{"onSystemBarsInsetsChanged", "(II)V", reinterpret_cast<void *>(onSystemBarsInsetsChanged)}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
QJniEnvironment env;
|
QJniEnvironment env;
|
||||||
@@ -204,21 +202,6 @@ bool AndroidController::isOnTv()
|
|||||||
return callActivityMethod<jboolean>("isOnTv", "()Z");
|
return callActivityMethod<jboolean>("isOnTv", "()Z");
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AndroidController::isEdgeToEdgeEnabled()
|
|
||||||
{
|
|
||||||
return callActivityMethod<jboolean>("isEdgeToEdgeEnabled", "()Z");
|
|
||||||
}
|
|
||||||
|
|
||||||
int AndroidController::getStatusBarHeight()
|
|
||||||
{
|
|
||||||
return callActivityMethod<jint>("getStatusBarHeight", "()I");
|
|
||||||
}
|
|
||||||
|
|
||||||
int AndroidController::getNavigationBarHeight()
|
|
||||||
{
|
|
||||||
return callActivityMethod<jint>("getNavigationBarHeight", "()I");
|
|
||||||
}
|
|
||||||
|
|
||||||
void AndroidController::startQrReaderActivity()
|
void AndroidController::startQrReaderActivity()
|
||||||
{
|
{
|
||||||
callActivityMethod("startQrCodeReader", "()V");
|
callActivityMethod("startQrCodeReader", "()V");
|
||||||
@@ -538,23 +521,3 @@ bool AndroidController::decodeQrCode(JNIEnv *env, jobject thiz, jstring data)
|
|||||||
|
|
||||||
return ImportController::decodeQrCode(AndroidUtils::convertJString(env, data));
|
return ImportController::decodeQrCode(AndroidUtils::convertJString(env, data));
|
||||||
}
|
}
|
||||||
// static
|
|
||||||
void AndroidController::onImeInsetsChanged(JNIEnv *env, jobject thiz, jint heightDp)
|
|
||||||
{
|
|
||||||
Q_UNUSED(env);
|
|
||||||
Q_UNUSED(thiz);
|
|
||||||
|
|
||||||
qDebug() << "Android IME insets changed: height =" << heightDp << "dp";
|
|
||||||
emit AndroidController::instance()->imeInsetsChanged(heightDp);
|
|
||||||
}
|
|
||||||
|
|
||||||
// static
|
|
||||||
void AndroidController::onSystemBarsInsetsChanged(JNIEnv *env, jobject thiz, jint navBarHeightDp, jint statusBarHeightDp)
|
|
||||||
{
|
|
||||||
Q_UNUSED(env);
|
|
||||||
Q_UNUSED(thiz);
|
|
||||||
|
|
||||||
qDebug() << "Android system bars insets changed: nav bar =" << navBarHeightDp << "dp, status bar =" << statusBarHeightDp << "dp";
|
|
||||||
emit AndroidController::instance()->systemBarsInsetsChanged(navBarHeightDp, statusBarHeightDp);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -39,9 +39,6 @@ public:
|
|||||||
QString getFileName(const QString &uri);
|
QString getFileName(const QString &uri);
|
||||||
bool isCameraPresent();
|
bool isCameraPresent();
|
||||||
bool isOnTv();
|
bool isOnTv();
|
||||||
bool isEdgeToEdgeEnabled();
|
|
||||||
int getStatusBarHeight();
|
|
||||||
int getNavigationBarHeight();
|
|
||||||
void startQrReaderActivity();
|
void startQrReaderActivity();
|
||||||
void setSaveLogs(bool enabled);
|
void setSaveLogs(bool enabled);
|
||||||
void exportLogsFile(const QString &fileName);
|
void exportLogsFile(const QString &fileName);
|
||||||
@@ -73,8 +70,6 @@ signals:
|
|||||||
void importConfigFromOutside(QString config);
|
void importConfigFromOutside(QString config);
|
||||||
void initConnectionState(Vpn::ConnectionState state);
|
void initConnectionState(Vpn::ConnectionState state);
|
||||||
void authenticationResult(bool result);
|
void authenticationResult(bool result);
|
||||||
void imeInsetsChanged(int heightDp);
|
|
||||||
void systemBarsInsetsChanged(int navBarHeightDp, int statusBarHeightDp);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool isWaitingStatus = true;
|
bool isWaitingStatus = true;
|
||||||
@@ -103,8 +98,6 @@ private:
|
|||||||
static void onFileOpened(JNIEnv *env, jobject thiz, jstring uri);
|
static void onFileOpened(JNIEnv *env, jobject thiz, jstring uri);
|
||||||
static void onAuthResult(JNIEnv *env, jobject thiz, jboolean result);
|
static void onAuthResult(JNIEnv *env, jobject thiz, jboolean result);
|
||||||
static bool decodeQrCode(JNIEnv *env, jobject thiz, jstring data);
|
static bool decodeQrCode(JNIEnv *env, jobject thiz, jstring data);
|
||||||
static void onImeInsetsChanged(JNIEnv *env, jobject thiz, jint heightDp);
|
|
||||||
static void onSystemBarsInsetsChanged(JNIEnv *env, jobject thiz, jint navBarHeightDp, jint statusBarHeightDp);
|
|
||||||
|
|
||||||
template <typename Ret, typename ...Args>
|
template <typename Ret, typename ...Args>
|
||||||
static auto callActivityMethod(const char *methodName, const char *signature, Args &&...args);
|
static auto callActivityMethod(const char *methodName, const char *signature, Args &&...args);
|
||||||
|
|||||||
@@ -1,82 +0,0 @@
|
|||||||
#import <UIKit/UIKit.h>
|
|
||||||
#import <objc/runtime.h>
|
|
||||||
#include <dispatch/dispatch.h>
|
|
||||||
|
|
||||||
#include <QByteArray>
|
|
||||||
#include <QFile>
|
|
||||||
#include <QString>
|
|
||||||
|
|
||||||
#include "ios_controller.h"
|
|
||||||
|
|
||||||
using SceneOpenURLContexts = void (*)(id, SEL, UIScene *, NSSet<UIOpenURLContext *> *);
|
|
||||||
|
|
||||||
static SceneOpenURLContexts g_originalSceneOpenURLContexts = nullptr;
|
|
||||||
|
|
||||||
static void amnezia_handleURL(NSURL *url)
|
|
||||||
{
|
|
||||||
if (!url || !url.isFileURL) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString filePath(url.path.UTF8String);
|
|
||||||
if (filePath.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
||||||
if (filePath.contains("backup")) {
|
|
||||||
IosController::Instance()->importBackupFromOutside(filePath);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QFile file(filePath);
|
|
||||||
if (!file.open(QIODevice::ReadOnly)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QByteArray data = file.readAll();
|
|
||||||
IosController::Instance()->importConfigFromOutside(QString::fromUtf8(data));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static void amnezia_scene_openURLContexts(id self, SEL _cmd, UIScene *scene, NSSet<UIOpenURLContext *> *contexts)
|
|
||||||
{
|
|
||||||
if (g_originalSceneOpenURLContexts) {
|
|
||||||
g_originalSceneOpenURLContexts(self, _cmd, scene, contexts);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!contexts || contexts.count == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (@available(iOS 13.0, *)) {
|
|
||||||
for (UIOpenURLContext *context in contexts) {
|
|
||||||
amnezia_handleURL(context.URL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@interface AmneziaSceneDelegateHooks : NSObject
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation AmneziaSceneDelegateHooks
|
|
||||||
|
|
||||||
+ (void)load
|
|
||||||
{
|
|
||||||
Class cls = objc_getClass("QIOSWindowSceneDelegate");
|
|
||||||
if (!cls) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SEL selector = @selector(scene:openURLContexts:);
|
|
||||||
Method method = class_getInstanceMethod(cls, selector);
|
|
||||||
if (method) {
|
|
||||||
g_originalSceneOpenURLContexts = reinterpret_cast<SceneOpenURLContexts>(method_getImplementation(method));
|
|
||||||
method_setImplementation(method, reinterpret_cast<IMP>(amnezia_scene_openURLContexts));
|
|
||||||
} else {
|
|
||||||
const char *types = "v@:@@";
|
|
||||||
class_addMethod(cls, selector, reinterpret_cast<IMP>(amnezia_scene_openURLContexts), types);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
@@ -2,8 +2,7 @@ import Foundation
|
|||||||
import os.log
|
import os.log
|
||||||
|
|
||||||
struct Log {
|
struct Log {
|
||||||
private static let subsystemIdentifier = Bundle.main.bundleIdentifier ?? "org.amnezia.AmneziaVPN"
|
static let osLog = Logger()
|
||||||
static let osLog = Logger(subsystem: subsystemIdentifier, category: "App")
|
|
||||||
|
|
||||||
private static let IsLoggingEnabledKey = "IsLoggingEnabled"
|
private static let IsLoggingEnabledKey = "IsLoggingEnabled"
|
||||||
static var isLoggingEnabled: Bool {
|
static var isLoggingEnabled: Bool {
|
||||||
@@ -78,41 +77,10 @@ struct Log {
|
|||||||
static func log(_ type: OSLogType, title: String = "", message: String, url: URL = neLogURL) {
|
static func log(_ type: OSLogType, title: String = "", message: String, url: URL = neLogURL) {
|
||||||
NSLog("\(title) \(message)")
|
NSLog("\(title) \(message)")
|
||||||
|
|
||||||
switch type {
|
|
||||||
case .debug:
|
|
||||||
if title.isEmpty {
|
|
||||||
osLog.debug("\(message, privacy: .public)")
|
|
||||||
} else {
|
|
||||||
osLog.debug("\(title, privacy: .public) \(message, privacy: .public)")
|
|
||||||
}
|
|
||||||
case .info:
|
|
||||||
if title.isEmpty {
|
|
||||||
osLog.info("\(message, privacy: .public)")
|
|
||||||
} else {
|
|
||||||
osLog.info("\(title, privacy: .public) \(message, privacy: .public)")
|
|
||||||
}
|
|
||||||
case .error:
|
|
||||||
if title.isEmpty {
|
|
||||||
osLog.error("\(message, privacy: .public)")
|
|
||||||
} else {
|
|
||||||
osLog.error("\(title, privacy: .public) \(message, privacy: .public)")
|
|
||||||
}
|
|
||||||
case .fault:
|
|
||||||
if title.isEmpty {
|
|
||||||
osLog.fault("\(message, privacy: .public)")
|
|
||||||
} else {
|
|
||||||
osLog.fault("\(title, privacy: .public) \(message, privacy: .public)")
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
if title.isEmpty {
|
|
||||||
osLog.log("\(message, privacy: .public)")
|
|
||||||
} else {
|
|
||||||
osLog.log("\(title, privacy: .public) \(message, privacy: .public)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
guard isLoggingEnabled else { return }
|
guard isLoggingEnabled else { return }
|
||||||
|
|
||||||
|
osLog.log(level: type, "\(title) \(message)")
|
||||||
|
|
||||||
let date = Date()
|
let date = Date()
|
||||||
let level = Record.Level(from: type)
|
let level = Record.Level(from: type)
|
||||||
let messages = message.split(whereSeparator: \.isNewline)
|
let messages = message.split(whereSeparator: \.isNewline)
|
||||||
|
|||||||
@@ -1,76 +1,22 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import os.log
|
import os.log
|
||||||
|
|
||||||
private let subsystemIdentifier = Bundle.main.bundleIdentifier ?? "org.amnezia.AmneziaVPN"
|
|
||||||
private let wireGuardSystemLogger = Logger(subsystem: subsystemIdentifier, category: "WireGuard")
|
|
||||||
private let openVPNSystemLogger = Logger(subsystem: subsystemIdentifier, category: "OpenVPN")
|
|
||||||
private let xraySystemLogger = Logger(subsystem: subsystemIdentifier, category: "Xray")
|
|
||||||
private let networkExtensionLogger = Logger(subsystem: subsystemIdentifier, category: "NetworkExtension")
|
|
||||||
|
|
||||||
private func logToSystem(_ logger: Logger, type: OSLogType, prefix: String, title: String, message: String) {
|
|
||||||
let combinedTitle: String
|
|
||||||
if title.isEmpty {
|
|
||||||
combinedTitle = prefix
|
|
||||||
} else {
|
|
||||||
combinedTitle = "\(prefix): \(title)"
|
|
||||||
}
|
|
||||||
|
|
||||||
switch type {
|
|
||||||
case .debug:
|
|
||||||
if combinedTitle.isEmpty {
|
|
||||||
logger.debug("\(message, privacy: .public)")
|
|
||||||
} else {
|
|
||||||
logger.debug("\(combinedTitle, privacy: .public) \(message, privacy: .public)")
|
|
||||||
}
|
|
||||||
case .info:
|
|
||||||
if combinedTitle.isEmpty {
|
|
||||||
logger.info("\(message, privacy: .public)")
|
|
||||||
} else {
|
|
||||||
logger.info("\(combinedTitle, privacy: .public) \(message, privacy: .public)")
|
|
||||||
}
|
|
||||||
case .error:
|
|
||||||
if combinedTitle.isEmpty {
|
|
||||||
logger.error("\(message, privacy: .public)")
|
|
||||||
} else {
|
|
||||||
logger.error("\(combinedTitle, privacy: .public) \(message, privacy: .public)")
|
|
||||||
}
|
|
||||||
case .fault:
|
|
||||||
if combinedTitle.isEmpty {
|
|
||||||
logger.fault("\(message, privacy: .public)")
|
|
||||||
} else {
|
|
||||||
logger.fault("\(combinedTitle, privacy: .public) \(message, privacy: .public)")
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
if combinedTitle.isEmpty {
|
|
||||||
logger.log("\(message, privacy: .public)")
|
|
||||||
} else {
|
|
||||||
logger.log("\(combinedTitle, privacy: .public) \(message, privacy: .public)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func wg_log(_ type: OSLogType, title: String = "", staticMessage: StaticString) {
|
public func wg_log(_ type: OSLogType, title: String = "", staticMessage: StaticString) {
|
||||||
let stringMessage = String(describing: staticMessage)
|
neLog(type, title: "WG: \(title)", message: "\(staticMessage)")
|
||||||
logToSystem(wireGuardSystemLogger, type: type, prefix: "WG", title: title, message: stringMessage)
|
|
||||||
neLog(type, title: "WG: \(title)", message: stringMessage)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func wg_log(_ type: OSLogType, title: String = "", message: String) {
|
public func wg_log(_ type: OSLogType, title: String = "", message: String) {
|
||||||
logToSystem(wireGuardSystemLogger, type: type, prefix: "WG", title: title, message: message)
|
|
||||||
neLog(type, title: "WG: \(title)", message: message)
|
neLog(type, title: "WG: \(title)", message: message)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func ovpnLog(_ type: OSLogType, title: String = "", message: String) {
|
public func ovpnLog(_ type: OSLogType, title: String = "", message: String) {
|
||||||
logToSystem(openVPNSystemLogger, type: type, prefix: "OVPN", title: title, message: message)
|
|
||||||
neLog(type, title: "OVPN: \(title)", message: message)
|
neLog(type, title: "OVPN: \(title)", message: message)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func xrayLog(_ type: OSLogType, title: String = "", message: String) {
|
public func xrayLog(_ type: OSLogType, title: String = "", message: String) {
|
||||||
logToSystem(xraySystemLogger, type: type, prefix: "XRAY", title: title, message: message)
|
|
||||||
neLog(type, title: "XRAY: \(title)", message: message)
|
neLog(type, title: "XRAY: \(title)", message: message)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func neLog(_ type: OSLogType, title: String = "", message: String) {
|
public func neLog(_ type: OSLogType, title: String = "", message: String) {
|
||||||
logToSystem(networkExtensionLogger, type: type, prefix: "NE", title: title, message: message)
|
|
||||||
Log.log(type, title: "NE: \(title)", message: message)
|
Log.log(type, title: "NE: \(title)", message: message)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import NetworkExtension
|
import NetworkExtension
|
||||||
import OpenVPNAdapter
|
import OpenVPNAdapter
|
||||||
import CryptoKit
|
|
||||||
|
|
||||||
struct OpenVPNConfig: Decodable {
|
struct OpenVPNConfig: Decodable {
|
||||||
let config: String
|
let config: String
|
||||||
@@ -28,83 +27,26 @@ extension PacketTunnelProvider {
|
|||||||
let ovpnConfiguration = Data(openVPNConfig.config.utf8)
|
let ovpnConfiguration = Data(openVPNConfig.config.utf8)
|
||||||
setupAndlaunchOpenVPN(withConfig: ovpnConfiguration, completionHandler: completionHandler)
|
setupAndlaunchOpenVPN(withConfig: ovpnConfiguration, completionHandler: completionHandler)
|
||||||
} catch {
|
} catch {
|
||||||
ovpnLog(.error, message: "Can't parse OpenVPN config: \(error.localizedDescription)")
|
ovpnLog(.error, message: "Can't parse config: \(error.localizedDescription)")
|
||||||
|
|
||||||
|
if let underlyingError = (error as NSError).userInfo[NSUnderlyingErrorKey] as? NSError {
|
||||||
|
ovpnLog(.error, message: "Can't parse config: \(underlyingError.localizedDescription)")
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func logOpenVPNError(_ error: NSError) {
|
|
||||||
let fatalFlag = (error.userInfo[OpenVPNAdapterErrorFatalKey] as? Bool) ?? false
|
|
||||||
var lines: [String] = []
|
|
||||||
lines.append("domain=\(error.domain) code=\(error.code) fatal=\(fatalFlag)")
|
|
||||||
|
|
||||||
if let adapterMessage = error.userInfo[OpenVPNAdapterErrorMessageKey] as? String, !adapterMessage.isEmpty {
|
|
||||||
lines.append("message=\(adapterMessage)")
|
|
||||||
}
|
|
||||||
|
|
||||||
let userInfoKeys = error.userInfo.keys.map { String(describing: $0) }.sorted()
|
|
||||||
if !userInfoKeys.isEmpty {
|
|
||||||
lines.append("userInfoKeys=[\(userInfoKeys.joined(separator: ","))]")
|
|
||||||
}
|
|
||||||
|
|
||||||
if let underlying = error.userInfo[NSUnderlyingErrorKey] as? NSError {
|
|
||||||
lines.append("underlying=\(underlying.domain)#\(underlying.code) fatal=\((underlying.userInfo[OpenVPNAdapterErrorFatalKey] as? Bool) ?? false)")
|
|
||||||
if let underlyingMessage = underlying.userInfo[OpenVPNAdapterErrorMessageKey] as? String, !underlyingMessage.isEmpty {
|
|
||||||
lines.append("underlyingMessage=\(underlyingMessage)")
|
|
||||||
} else if !underlying.localizedDescription.isEmpty {
|
|
||||||
lines.append("underlyingLocalized=\(underlying.localizedDescription)")
|
|
||||||
}
|
|
||||||
} else if let underlying = error.userInfo[NSUnderlyingErrorKey] {
|
|
||||||
lines.append("underlyingRaw=\(underlying)")
|
|
||||||
}
|
|
||||||
|
|
||||||
let formatted = lines.joined(separator: "\n ")
|
|
||||||
ovpnLog(.error, title: "Error", message: formatted)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func setupAndlaunchOpenVPN(withConfig ovpnConfiguration: Data,
|
private func setupAndlaunchOpenVPN(withConfig ovpnConfiguration: Data,
|
||||||
withShadowSocks viaSS: Bool = false,
|
withShadowSocks viaSS: Bool = false,
|
||||||
completionHandler: @escaping (Error?) -> Void) {
|
completionHandler: @escaping (Error?) -> Void) {
|
||||||
ovpnLog(.info, message: "Setup and launch")
|
ovpnLog(.info, message: "Setup and launch")
|
||||||
|
|
||||||
var configString = String(decoding: ovpnConfiguration, as: UTF8.self)
|
let str = String(decoding: ovpnConfiguration, as: UTF8.self)
|
||||||
|
|
||||||
let digest = SHA256.hash(data: ovpnConfiguration)
|
|
||||||
let digestString = digest.map { String(format: "%02x", $0) }.joined()
|
|
||||||
ovpnLog(.info, title: "ConfigDigest", message: digestString)
|
|
||||||
|
|
||||||
let hasTlsAuthOpen = configString.contains("<tls-auth>")
|
|
||||||
let hasTlsAuthClose = configString.contains("</tls-auth>")
|
|
||||||
ovpnLog(.info, title: "ConfigFlags", message: "tls-auth open=\(hasTlsAuthOpen) close=\(hasTlsAuthClose)")
|
|
||||||
|
|
||||||
let lines = configString.split(separator: "\n")
|
|
||||||
let head = lines.prefix(10).joined(separator: "\n")
|
|
||||||
let tail = lines.suffix(10).joined(separator: "\n")
|
|
||||||
ovpnLog(.debug, title: "ConfigHead", message: head)
|
|
||||||
ovpnLog(.debug, title: "ConfigTail", message: tail)
|
|
||||||
|
|
||||||
if let start = configString.range(of: "<tls-auth>"),
|
|
||||||
let end = configString.range(of: "</tls-auth>", range: start.upperBound..<configString.endIndex) {
|
|
||||||
let keyBody = String(configString[start.upperBound..<end.lowerBound])
|
|
||||||
ovpnLog(.debug, title: "TLSAuthInline", message: keyBody)
|
|
||||||
let sanitizedLines = keyBody
|
|
||||||
.split(whereSeparator: { $0.isNewline })
|
|
||||||
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
|
|
||||||
.filter { !$0.isEmpty }
|
|
||||||
.filter { !$0.hasPrefix("#") }
|
|
||||||
|
|
||||||
let sanitizedKey = sanitizedLines.joined(separator: "\n")
|
|
||||||
ovpnLog(.debug, title: "TLSAuthSanitized", message: sanitizedKey)
|
|
||||||
let sanitizedBlock = "<tls-auth>\n\(sanitizedKey)\n</tls-auth>"
|
|
||||||
configString.replaceSubrange(start.lowerBound..<end.upperBound, with: sanitizedBlock)
|
|
||||||
}
|
|
||||||
|
|
||||||
let normalizedConfig = configString.replacingOccurrences(of: "\r\n", with: "\n")
|
|
||||||
let sanitizedData = Data(normalizedConfig.utf8)
|
|
||||||
|
|
||||||
let configuration = OpenVPNConfiguration()
|
let configuration = OpenVPNConfiguration()
|
||||||
configuration.fileContent = sanitizedData
|
configuration.fileContent = ovpnConfiguration
|
||||||
if configString.contains("cloak") {
|
if str.contains("cloak") {
|
||||||
configuration.setPTCloak()
|
configuration.setPTCloak()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,8 +57,6 @@ extension PacketTunnelProvider {
|
|||||||
evaluation = try ovpnAdapter?.apply(configuration: configuration)
|
evaluation = try ovpnAdapter?.apply(configuration: configuration)
|
||||||
|
|
||||||
} catch {
|
} catch {
|
||||||
let nsError = error as NSError
|
|
||||||
ovpnLog(.error, title: "ApplyConfig", message: "domain=\(nsError.domain) code=\(nsError.code) info=\(nsError.userInfo)")
|
|
||||||
completionHandler(error)
|
completionHandler(error)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -131,7 +71,7 @@ extension PacketTunnelProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
startHandler = completionHandler
|
startHandler = completionHandler
|
||||||
ovpnAdapter?.connect(using: openVPNPacketFlow())
|
ovpnAdapter?.connect(using: packetFlow)
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleOpenVPNStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
func handleOpenVPNStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||||
@@ -153,7 +93,7 @@ extension PacketTunnelProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func stopOpenVPN(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
func stopOpenVPN(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
||||||
ovpnLog(.info, message: "Stopping tunnel: reason: \(reason.amneziaDescription)")
|
ovpnLog(.info, message: "Stopping tunnel: reason: \(reason.description)")
|
||||||
|
|
||||||
stopHandler = completionHandler
|
stopHandler = completionHandler
|
||||||
if vpnReachability.isTracking {
|
if vpnReachability.isTracking {
|
||||||
@@ -268,11 +208,8 @@ extension PacketTunnelProvider: OpenVPNAdapterDelegate {
|
|||||||
|
|
||||||
// Handle errors thrown by the OpenVPN library
|
// Handle errors thrown by the OpenVPN library
|
||||||
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleError error: Error) {
|
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleError error: Error) {
|
||||||
let nsError = error as NSError
|
|
||||||
logOpenVPNError(nsError)
|
|
||||||
|
|
||||||
// Handle only fatal errors
|
// Handle only fatal errors
|
||||||
guard let fatal = nsError.userInfo[OpenVPNAdapterErrorFatalKey] as? Bool,
|
guard let fatal = (error as NSError).userInfo[OpenVPNAdapterErrorFatalKey] as? Bool,
|
||||||
fatal == true else { return }
|
fatal == true else { return }
|
||||||
|
|
||||||
if vpnReachability.isTracking {
|
if vpnReachability.isTracking {
|
||||||
@@ -293,3 +230,5 @@ extension PacketTunnelProvider: OpenVPNAdapterDelegate {
|
|||||||
ovpnLog(.info, message: logMessage)
|
ovpnLog(.info, message: logMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension NEPacketTunnelFlow: OpenVPNAdapterPacketFlow {}
|
||||||
|
|||||||
@@ -94,24 +94,15 @@ extension PacketTunnelProvider {
|
|||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
wg_log(.error, message: "Can't parse WG config: \(error.localizedDescription)")
|
wg_log(.error, message: "Can't parse WG config: \(error.localizedDescription)")
|
||||||
errorNotifier.notify(PacketTunnelProviderError.savedProtocolConfigurationIsInvalid)
|
completionHandler(nil)
|
||||||
completionHandler(PacketTunnelProviderError.savedProtocolConfigurationIsInvalid)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleWireguardStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
func handleWireguardStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||||
guard let completionHandler = completionHandler else { return }
|
guard let completionHandler = completionHandler else { return }
|
||||||
guard let wgAdapter = wgAdapter else {
|
wgAdapter?.getRuntimeConfiguration { settings in
|
||||||
completionHandler(nil)
|
let components = settings!.components(separatedBy: "\n")
|
||||||
return
|
|
||||||
}
|
|
||||||
wgAdapter.getRuntimeConfiguration { settings in
|
|
||||||
guard let settings = settings else {
|
|
||||||
completionHandler(nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let components = settings.components(separatedBy: "\n")
|
|
||||||
|
|
||||||
var settingsDictionary: [String: String] = [:]
|
var settingsDictionary: [String: String] = [:]
|
||||||
for component in components {
|
for component in components {
|
||||||
@@ -140,7 +131,7 @@ extension PacketTunnelProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleWireguardAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
private func handleWireguardAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||||
guard let completionHandler = completionHandler else { return }
|
guard let completionHandler = completionHandler else { return }
|
||||||
if messageData.count == 1 && messageData[0] == 0 {
|
if messageData.count == 1 && messageData[0] == 0 {
|
||||||
wgAdapter?.getRuntimeConfiguration { settings in
|
wgAdapter?.getRuntimeConfiguration { settings in
|
||||||
@@ -185,7 +176,7 @@ extension PacketTunnelProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func stopWireguard(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
func stopWireguard(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
||||||
wg_log(.info, message: "Stopping tunnel: reason: \(reason.amneziaDescription)")
|
wg_log(.info, message: "Stopping tunnel: reason: \(reason.description)")
|
||||||
|
|
||||||
wgAdapter?.stop { error in
|
wgAdapter?.stop { error in
|
||||||
ErrorNotifier.removeLastErrorFile()
|
ErrorNotifier.removeLastErrorFile()
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import NetworkExtension
|
import NetworkExtension
|
||||||
|
import WireGuardKitGo
|
||||||
|
|
||||||
enum XrayErrors: Error {
|
enum XrayErrors: Error {
|
||||||
case noXrayConfig
|
case noXrayConfig
|
||||||
@@ -107,8 +108,6 @@ extension PacketTunnelProvider {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
self?.updateActiveInterfaceIndexForCurrentPath()
|
|
||||||
|
|
||||||
// Launch xray
|
// Launch xray
|
||||||
self?.setupAndStartXray(configData: updatedData) { xrayError in
|
self?.setupAndStartXray(configData: updatedData) { xrayError in
|
||||||
if let xrayError {
|
if let xrayError {
|
||||||
@@ -135,15 +134,6 @@ extension PacketTunnelProvider {
|
|||||||
completionHandler()
|
completionHandler()
|
||||||
}
|
}
|
||||||
|
|
||||||
func sockCallback(fd: uintptr_t) {
|
|
||||||
if activeIfaceIdx != 0 {
|
|
||||||
withUnsafePointer(to: activeIfaceIdx) { ptr in
|
|
||||||
setsockopt(Int32(fd), IPPROTO_IP, IP_BOUND_IF, ptr, socklen_t(MemoryLayout<UInt32>.size))
|
|
||||||
setsockopt(Int32(fd), IPPROTO_IPV6, IPV6_BOUND_IF, ptr, socklen_t(MemoryLayout<UInt32>.size))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func setupAndStartXray(configData: Data,
|
private func setupAndStartXray(configData: Data,
|
||||||
completionHandler: @escaping (Error?) -> Void) {
|
completionHandler: @escaping (Error?) -> Void) {
|
||||||
let path = Constants.cachesDirectory.appendingPathComponent("config.json", isDirectory: false).path
|
let path = Constants.cachesDirectory.appendingPathComponent("config.json", isDirectory: false).path
|
||||||
@@ -153,17 +143,6 @@ extension PacketTunnelProvider {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
updateActiveInterfaceIndexForCurrentPath()
|
|
||||||
|
|
||||||
let ctx = Unmanaged.passUnretained(self).toOpaque()
|
|
||||||
let cb: libxray_sockcallback = { (fd, ctx) in
|
|
||||||
guard let ctx = ctx else { return }
|
|
||||||
let instance = Unmanaged<PacketTunnelProvider>.fromOpaque(ctx).takeUnretainedValue()
|
|
||||||
|
|
||||||
instance.sockCallback(fd: fd)
|
|
||||||
}
|
|
||||||
LibXraySetSockCallback(cb, ctx)
|
|
||||||
|
|
||||||
LibXrayRunXray(nil,
|
LibXrayRunXray(nil,
|
||||||
path,
|
path,
|
||||||
Int64.max)
|
Int64.max)
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import NetworkExtension
|
import NetworkExtension
|
||||||
import Network
|
|
||||||
import os
|
import os
|
||||||
import Darwin
|
import Darwin
|
||||||
import OpenVPNAdapter
|
import OpenVPNAdapter
|
||||||
@@ -39,12 +38,6 @@ struct Constants {
|
|||||||
class PacketTunnelProvider: NEPacketTunnelProvider {
|
class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
var wgAdapter: WireGuardAdapter?
|
var wgAdapter: WireGuardAdapter?
|
||||||
var ovpnAdapter: OpenVPNAdapter?
|
var ovpnAdapter: OpenVPNAdapter?
|
||||||
private lazy var openVPNPacketFlowAdapter = PacketTunnelFlowAdapter(flow: packetFlow)
|
|
||||||
private let pathMonitorQueue = DispatchQueue(label: Constants.processQueueName + ".path-monitor")
|
|
||||||
private let pathMonitor = NWPathMonitor()
|
|
||||||
private var didReceiveInitialPathUpdate = false
|
|
||||||
private var currentPath: Network.NWPath?
|
|
||||||
private var currentPathSignature: String?
|
|
||||||
|
|
||||||
var splitTunnelType: Int?
|
var splitTunnelType: Int?
|
||||||
var splitTunnelSites: [String]?
|
var splitTunnelSites: [String]?
|
||||||
@@ -55,89 +48,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||||||
var stopHandler: (() -> Void)?
|
var stopHandler: (() -> Void)?
|
||||||
var protoType: TunnelProtoType?
|
var protoType: TunnelProtoType?
|
||||||
|
|
||||||
var activeIfaceIdx: UInt32 = 0
|
|
||||||
|
|
||||||
func openVPNPacketFlow() -> OpenVPNAdapterPacketFlow {
|
|
||||||
openVPNPacketFlowAdapter
|
|
||||||
}
|
|
||||||
|
|
||||||
override init() {
|
|
||||||
super.init()
|
|
||||||
pathMonitor.pathUpdateHandler = { [weak self] path in
|
|
||||||
guard let self else { return }
|
|
||||||
self.currentPath = path
|
|
||||||
let signature = self.pathSignature(for: path)
|
|
||||||
let hasMeaningfulChange = self.currentPathSignature != signature
|
|
||||||
self.currentPathSignature = signature
|
|
||||||
self.updateActiveInterfaceIndex(for: path)
|
|
||||||
|
|
||||||
guard self.didReceiveInitialPathUpdate else {
|
|
||||||
self.didReceiveInitialPathUpdate = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard hasMeaningfulChange, let proto = self.protoType else { return }
|
|
||||||
|
|
||||||
// WireGuard/AWG manages network changes internally; avoid restarting the tunnel here.
|
|
||||||
if proto == .wireguard {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.handle(networkChange: path) { _ in }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pathMonitor.start(queue: pathMonitorQueue)
|
|
||||||
|
|
||||||
currentPath = pathMonitor.currentPath
|
|
||||||
currentPathSignature = pathSignature(for: pathMonitor.currentPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateActiveInterfaceIndex(for path: Network.NWPath?) {
|
|
||||||
guard let path else {
|
|
||||||
activeIfaceIdx = 0
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let preferredTypes: [NWInterface.InterfaceType] = [.wiredEthernet, .wifi, .cellular, .other]
|
|
||||||
|
|
||||||
let nonLoopbackInterfaces = path.availableInterfaces.filter { $0.type != .loopback }
|
|
||||||
let activeInterfaces = nonLoopbackInterfaces.filter { path.usesInterfaceType($0.type) }
|
|
||||||
|
|
||||||
let candidate = preferredTypes.compactMap { type in
|
|
||||||
activeInterfaces.first { $0.type == type }
|
|
||||||
}.first ?? activeInterfaces.first ?? nonLoopbackInterfaces.first
|
|
||||||
|
|
||||||
if let candidate {
|
|
||||||
activeIfaceIdx = UInt32(candidate.index)
|
|
||||||
} else {
|
|
||||||
activeIfaceIdx = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func updateActiveInterfaceIndexForCurrentPath() {
|
|
||||||
if let currentPath {
|
|
||||||
currentPathSignature = pathSignature(for: currentPath)
|
|
||||||
updateActiveInterfaceIndex(for: currentPath)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
currentPath = pathMonitor.currentPath
|
|
||||||
currentPathSignature = pathSignature(for: pathMonitor.currentPath)
|
|
||||||
updateActiveInterfaceIndex(for: pathMonitor.currentPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||||
if messageData.count == 1 && messageData[0] == 0 {
|
|
||||||
guard let completionHandler else { return }
|
|
||||||
if protoType == .wireguard {
|
|
||||||
handleWireguardAppMessage(messageData, completionHandler: completionHandler)
|
|
||||||
} else {
|
|
||||||
completionHandler(nil)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
guard let message = String(data: messageData, encoding: .utf8) else {
|
guard let message = String(data: messageData, encoding: .utf8) else {
|
||||||
if let completionHandler {
|
if let completionHandler {
|
||||||
completionHandler(nil)
|
completionHandler(nil)
|
||||||
@@ -148,10 +59,6 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||||||
neLog(.info, title: "App said: ", message: message)
|
neLog(.info, title: "App said: ", message: message)
|
||||||
|
|
||||||
guard let message = try? JSONSerialization.jsonObject(with: messageData, options: []) as? [String: Any] else {
|
guard let message = try? JSONSerialization.jsonObject(with: messageData, options: []) as? [String: Any] else {
|
||||||
if protoType == .wireguard {
|
|
||||||
handleWireguardAppMessage(messageData, completionHandler: completionHandler)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
neLog(.error, message: "Failed to serialize message from app")
|
neLog(.error, message: "Failed to serialize message from app")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -197,9 +104,6 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
didReceiveInitialPathUpdate = false
|
|
||||||
updateActiveInterfaceIndexForCurrentPath()
|
|
||||||
|
|
||||||
switch protoType {
|
switch protoType {
|
||||||
case .wireguard:
|
case .wireguard:
|
||||||
startWireguard(activationAttemptId: activationAttemptId,
|
startWireguard(activationAttemptId: activationAttemptId,
|
||||||
@@ -253,63 +157,28 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||||||
of object: Any?,
|
of object: Any?,
|
||||||
change: [NSKeyValueChangeKey: Any]?,
|
change: [NSKeyValueChangeKey: Any]?,
|
||||||
context: UnsafeMutableRawPointer?) {
|
context: UnsafeMutableRawPointer?) {
|
||||||
guard Constants.kDefaultPathKey == keyPath else {
|
guard Constants.kDefaultPathKey != keyPath else { return }
|
||||||
|
// Since iOS 11, we have observed that this KVO event fires repeatedly when connecting over Wifi,
|
||||||
|
// even though the underlying network has not changed (i.e. `isEqualToPath` returns false),
|
||||||
|
// leading to "wakeup crashes" due to excessive network activity. Guard against false positives by
|
||||||
|
// comparing the paths' string description, which includes properties not exposed by the class
|
||||||
|
guard let lastPath: NWPath = change?[.oldKey] as? NWPath,
|
||||||
|
let defPath = defaultPath,
|
||||||
|
lastPath != defPath || lastPath.description != defPath.description else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
DispatchQueue.main.async { [weak self] in
|
||||||
|
guard let self, self.defaultPath != nil else { return }
|
||||||
|
self.handle(networkChange: self.defaultPath!) { _ in }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handle(networkChange changePath: Network.NWPath, completion: @escaping (Error?) -> Void) {
|
private func handle(networkChange changePath: NWPath, completion: @escaping (Error?) -> Void) {
|
||||||
updateActiveInterfaceIndex(for: changePath)
|
|
||||||
wg_log(.info, message: "Tunnel restarted.")
|
wg_log(.info, message: "Tunnel restarted.")
|
||||||
startTunnel(options: nil, completionHandler: completion)
|
startTunnel(options: nil, completionHandler: completion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private extension PacketTunnelProvider {
|
|
||||||
func pathSignature(for path: Network.NWPath) -> String {
|
|
||||||
var signatureComponents = [String(describing: path.status)]
|
|
||||||
signatureComponents.append(path.isExpensive ? "exp" : "noexp")
|
|
||||||
signatureComponents.append(path.isConstrained ? "con" : "nocon")
|
|
||||||
|
|
||||||
let preferredTypes: [NWInterface.InterfaceType] = [.wiredEthernet, .wifi, .cellular, .loopback, .other]
|
|
||||||
let sortedInterfaces = path.availableInterfaces.sorted { lhs, rhs in
|
|
||||||
if lhs.type == rhs.type {
|
|
||||||
return lhs.index < rhs.index
|
|
||||||
}
|
|
||||||
|
|
||||||
let lhsOrder = preferredTypes.firstIndex(of: lhs.type) ?? preferredTypes.count
|
|
||||||
let rhsOrder = preferredTypes.firstIndex(of: rhs.type) ?? preferredTypes.count
|
|
||||||
|
|
||||||
if lhsOrder == rhsOrder {
|
|
||||||
return lhs.index < rhs.index
|
|
||||||
}
|
|
||||||
|
|
||||||
return lhsOrder < rhsOrder
|
|
||||||
}
|
|
||||||
|
|
||||||
for interface in sortedInterfaces {
|
|
||||||
let typeName: String
|
|
||||||
switch interface.type {
|
|
||||||
case .wiredEthernet: typeName = "ethernet"
|
|
||||||
case .wifi: typeName = "wifi"
|
|
||||||
case .cellular: typeName = "cellular"
|
|
||||||
case .loopback: typeName = "loopback"
|
|
||||||
case .other: typeName = "other"
|
|
||||||
@unknown default: typeName = "unknown"
|
|
||||||
}
|
|
||||||
signatureComponents.append("\(typeName):\(interface.index)")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Include currently used interface preference ordering
|
|
||||||
for type in preferredTypes {
|
|
||||||
let usesType = path.usesInterfaceType(type)
|
|
||||||
signatureComponents.append("uses-\(type):\(usesType)")
|
|
||||||
}
|
|
||||||
|
|
||||||
return signatureComponents.joined(separator: "|")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension WireGuardLogLevel {
|
extension WireGuardLogLevel {
|
||||||
var osLogLevel: OSLogType {
|
var osLogLevel: OSLogType {
|
||||||
switch self {
|
switch self {
|
||||||
@@ -321,27 +190,8 @@ extension WireGuardLogLevel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class PacketTunnelFlowAdapter: NSObject, OpenVPNAdapterPacketFlow {
|
extension NEProviderStopReason: CustomStringConvertible {
|
||||||
private let flow: NEPacketTunnelFlow
|
public var description: String {
|
||||||
|
|
||||||
init(flow: NEPacketTunnelFlow) {
|
|
||||||
self.flow = flow
|
|
||||||
super.init()
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc(readPacketsWithCompletionHandler:)
|
|
||||||
func readPackets(completionHandler: @escaping ([Data], [NSNumber]) -> Void) {
|
|
||||||
flow.readPackets(completionHandler: completionHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
@objc(writePackets:withProtocols:)
|
|
||||||
func writePackets(_ packets: [Data], withProtocols protocols: [NSNumber]) -> Bool {
|
|
||||||
flow.writePackets(packets, withProtocols: protocols)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension NEProviderStopReason {
|
|
||||||
var amneziaDescription: String {
|
|
||||||
switch self {
|
switch self {
|
||||||
case .none:
|
case .none:
|
||||||
return "No specific reason"
|
return "No specific reason"
|
||||||
@@ -373,8 +223,6 @@ extension NEProviderStopReason {
|
|||||||
return "The current console user changed"
|
return "The current console user changed"
|
||||||
case .connectionFailed:
|
case .connectionFailed:
|
||||||
return "The connection failed"
|
return "The connection failed"
|
||||||
case .internalError:
|
|
||||||
return "The network extension reported an internal error"
|
|
||||||
case .sleep:
|
case .sleep:
|
||||||
return "A stop reason indicating the VPNC enabled disconnect on sleep and the device went to sleep"
|
return "A stop reason indicating the VPNC enabled disconnect on sleep and the device went to sleep"
|
||||||
case .appUpdate:
|
case .appUpdate:
|
||||||
|
|||||||
@@ -11,7 +11,13 @@ class ScreenProtection {
|
|||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
public func toggleScreenshots(_ isEnabled: Bool) {
|
public func toggleScreenshots(_ isEnabled: Bool) {
|
||||||
ScreenProtection.shared.setScreenshotsEnabled(isEnabled)
|
let window = UIApplication.shared.keyWindows.first!
|
||||||
|
|
||||||
|
if isEnabled {
|
||||||
|
ScreenProtection.shared.disable(for: window.rootViewController!.view)
|
||||||
|
} else {
|
||||||
|
ScreenProtection.shared.enable(for: window.rootViewController!.view)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension UIApplication {
|
extension UIApplication {
|
||||||
@@ -39,45 +45,6 @@ class ScreenProtection {
|
|||||||
|
|
||||||
private var blurView: UIVisualEffectView?
|
private var blurView: UIVisualEffectView?
|
||||||
private var recordingObservation: NSKeyValueObservation?
|
private var recordingObservation: NSKeyValueObservation?
|
||||||
private var desiredScreenshotsEnabled: Bool?
|
|
||||||
private var retryCount = 0
|
|
||||||
private var retryWorkItem: DispatchWorkItem?
|
|
||||||
|
|
||||||
public func setScreenshotsEnabled(_ isEnabled: Bool) {
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
self.desiredScreenshotsEnabled = isEnabled
|
|
||||||
self.applyScreenshotsSettingOrRetry()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func applyScreenshotsSettingOrRetry() {
|
|
||||||
assert(Thread.isMainThread)
|
|
||||||
|
|
||||||
guard let desiredScreenshotsEnabled else { return }
|
|
||||||
guard let window = UIApplication.shared.keyWindows.first,
|
|
||||||
let rootView = window.rootViewController?.view else {
|
|
||||||
retryCount += 1
|
|
||||||
guard retryCount <= 50 else { return } // ~5s total
|
|
||||||
|
|
||||||
retryWorkItem?.cancel()
|
|
||||||
let item = DispatchWorkItem { [weak self] in
|
|
||||||
self?.applyScreenshotsSettingOrRetry()
|
|
||||||
}
|
|
||||||
retryWorkItem = item
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: item)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
retryWorkItem?.cancel()
|
|
||||||
retryWorkItem = nil
|
|
||||||
retryCount = 0
|
|
||||||
|
|
||||||
if desiredScreenshotsEnabled {
|
|
||||||
disable(for: rootView)
|
|
||||||
} else {
|
|
||||||
enable(for: rootView)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public func enable(for view: UIView) {
|
public func enable(for view: UIView) {
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
|
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
#ifndef STOREKITCONTROLLER_H
|
|
||||||
#define STOREKITCONTROLLER_H
|
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
#import <StoreKit/StoreKit.h>
|
|
||||||
|
|
||||||
@class Product;
|
|
||||||
@class Transaction;
|
|
||||||
@class VerificationResult;
|
|
||||||
|
|
||||||
API_AVAILABLE(ios(15.0), macos(12.0))
|
|
||||||
@interface StoreKitController : NSObject
|
|
||||||
|
|
||||||
+ (instancetype)sharedInstance;
|
|
||||||
|
|
||||||
- (void)purchaseProduct:(NSString *)productIdentifier
|
|
||||||
completion:(void (^)(BOOL success,
|
|
||||||
NSString *_Nullable transactionId,
|
|
||||||
NSString *_Nullable productId,
|
|
||||||
NSString *_Nullable originalTransactionId,
|
|
||||||
NSError *_Nullable error))completion;
|
|
||||||
|
|
||||||
- (void)restorePurchasesWithCompletion:(void (^)(BOOL success,
|
|
||||||
NSArray<NSDictionary *> *_Nullable restoredTransactions,
|
|
||||||
NSError *_Nullable error))completion;
|
|
||||||
|
|
||||||
// Fetch product information for a set of identifiers without initiating a purchase
|
|
||||||
- (void)fetchProductsWithIdentifiers:(NSSet<NSString *> *)productIdentifiers
|
|
||||||
completion:(void (^)(NSArray<NSDictionary *> *products,
|
|
||||||
NSArray<NSString *> *invalidIdentifiers,
|
|
||||||
NSError *_Nullable error))completion;
|
|
||||||
|
|
||||||
@end
|
|
||||||
|
|
||||||
#endif // STOREKITCONTROLLER_H
|
|
||||||
@@ -1,264 +0,0 @@
|
|||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
||||||
|
|
||||||
#import "StoreKitController.h"
|
|
||||||
#import <StoreKit/StoreKit.h>
|
|
||||||
|
|
||||||
#include <QtCore/QDebug>
|
|
||||||
#include <QtCore/QString>
|
|
||||||
|
|
||||||
API_AVAILABLE(ios(15.0), macos(12.0))
|
|
||||||
@interface StoreKitController () <SKProductsRequestDelegate, SKPaymentTransactionObserver>
|
|
||||||
@property (nonatomic, copy) void (^purchaseCompletion)(BOOL success,
|
|
||||||
NSString *_Nullable transactionId,
|
|
||||||
NSString *_Nullable productId,
|
|
||||||
NSString *_Nullable originalTransactionId,
|
|
||||||
NSError *_Nullable error);
|
|
||||||
@property (nonatomic, copy) void (^restoreCompletion)(BOOL success,
|
|
||||||
NSArray<NSDictionary *> *_Nullable restoredTransactions,
|
|
||||||
NSError *_Nullable error);
|
|
||||||
@property (nonatomic, copy) void (^productsFetchCompletion)(NSArray<NSDictionary *> *products,
|
|
||||||
NSArray<NSString *> *invalidIdentifiers,
|
|
||||||
NSError *_Nullable error);
|
|
||||||
@property (nonatomic, strong) SKProductsRequest *productsRequest;
|
|
||||||
@property (nonatomic, strong) NSMutableArray<NSDictionary *> *restoredTransactions;
|
|
||||||
@end
|
|
||||||
|
|
||||||
@implementation StoreKitController
|
|
||||||
|
|
||||||
+ (instancetype)sharedInstance
|
|
||||||
{
|
|
||||||
static dispatch_once_t onceToken;
|
|
||||||
static StoreKitController *instance;
|
|
||||||
dispatch_once(&onceToken, ^{
|
|
||||||
if (@available(iOS 15.0, macOS 12.0, *)) {
|
|
||||||
instance = [[StoreKitController alloc] init];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (instancetype)init API_AVAILABLE(ios(15.0), macos(12.0))
|
|
||||||
{
|
|
||||||
self = [super init];
|
|
||||||
if (self) {
|
|
||||||
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
|
|
||||||
}
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)dealloc
|
|
||||||
{
|
|
||||||
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)purchaseProduct:(NSString *)productIdentifier
|
|
||||||
completion:(void (^)(BOOL success,
|
|
||||||
NSString *_Nullable transactionId,
|
|
||||||
NSString *_Nullable productId,
|
|
||||||
NSString *_Nullable originalTransactionId,
|
|
||||||
NSError *_Nullable error))completion API_AVAILABLE(ios(15.0), macos(12.0))
|
|
||||||
{
|
|
||||||
self.purchaseCompletion = completion;
|
|
||||||
|
|
||||||
qInfo().noquote() << "[IAP][StoreKit] Starting purchase for" << QString::fromUtf8(productIdentifier.UTF8String);
|
|
||||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
|
||||||
[self performPurchaseAsync:productIdentifier];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)performPurchaseAsync:(NSString *)productIdentifier API_AVAILABLE(ios(15.0), macos(12.0))
|
|
||||||
{
|
|
||||||
dispatch_async(dispatch_get_main_queue(), ^{
|
|
||||||
@try {
|
|
||||||
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:productIdentifier]];
|
|
||||||
request.delegate = self;
|
|
||||||
[request start];
|
|
||||||
|
|
||||||
} @catch (NSException *exception) {
|
|
||||||
NSError *error = [NSError errorWithDomain:@"StoreKitController"
|
|
||||||
code:1
|
|
||||||
userInfo:@{ NSLocalizedDescriptionKey : exception.reason ?: @"Purchase failed" }];
|
|
||||||
if (self.purchaseCompletion) {
|
|
||||||
self.purchaseCompletion(NO, nil, nil, nil, error);
|
|
||||||
self.purchaseCompletion = nil;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)restorePurchasesWithCompletion:(void (^)(BOOL success,
|
|
||||||
NSArray<NSDictionary *> *_Nullable restoredTransactions,
|
|
||||||
NSError *_Nullable error))completion API_AVAILABLE(ios(15.0), macos(12.0))
|
|
||||||
{
|
|
||||||
self.restoreCompletion = completion;
|
|
||||||
self.restoredTransactions = [NSMutableArray array];
|
|
||||||
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)fetchProductsWithIdentifiers:(NSSet<NSString *> *)productIdentifiers
|
|
||||||
completion:(void (^)(NSArray<NSDictionary *> *products,
|
|
||||||
NSArray<NSString *> *invalidIdentifiers,
|
|
||||||
NSError *_Nullable error))completion API_AVAILABLE(ios(15.0), macos(12.0))
|
|
||||||
{
|
|
||||||
self.productsFetchCompletion = completion;
|
|
||||||
self.productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
|
|
||||||
self.productsRequest.delegate = self;
|
|
||||||
[self.productsRequest start];
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - SKProductsRequestDelegate / SKRequestDelegate
|
|
||||||
|
|
||||||
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
|
|
||||||
{
|
|
||||||
if (self.purchaseCompletion) {
|
|
||||||
SKProduct *product = response.products.firstObject;
|
|
||||||
if (!product) {
|
|
||||||
NSError *error = [NSError errorWithDomain:@"StoreKitController"
|
|
||||||
code:0
|
|
||||||
userInfo:@{ NSLocalizedDescriptionKey : @"Product not found" }];
|
|
||||||
self.purchaseCompletion(NO, nil, nil, nil, error);
|
|
||||||
self.purchaseCompletion = nil;
|
|
||||||
self.productsRequest = nil;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
NSString *currencyCode = [product.priceLocale objectForKey:NSLocaleCurrencyCode] ?: @"";
|
|
||||||
NSString *priceString = [product.price stringValue] ?: @"";
|
|
||||||
qInfo().noquote() << "[IAP][StoreKit] Received product" << QString::fromUtf8(product.productIdentifier.UTF8String)
|
|
||||||
<< "price=" << QString::fromUtf8(priceString.UTF8String)
|
|
||||||
<< "currency=" << QString::fromUtf8(currencyCode.UTF8String);
|
|
||||||
SKPayment *payment = [SKPayment paymentWithProduct:product];
|
|
||||||
[[SKPaymentQueue defaultQueue] addPayment:payment];
|
|
||||||
self.productsRequest = nil;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self.productsFetchCompletion) {
|
|
||||||
NSMutableArray<NSDictionary *> *productDicts = [NSMutableArray array];
|
|
||||||
for (SKProduct *p in response.products) {
|
|
||||||
NSDictionary *productDict = @{
|
|
||||||
@"productId": p.productIdentifier,
|
|
||||||
@"title": p.localizedTitle,
|
|
||||||
@"description": p.localizedDescription,
|
|
||||||
@"price": p.price.stringValue,
|
|
||||||
@"currencyCode": [p.priceLocale objectForKey:NSLocaleCurrencyCode] ?: @""
|
|
||||||
};
|
|
||||||
[productDicts addObject:productDict];
|
|
||||||
NSString *productCurrency = [p.priceLocale objectForKey:NSLocaleCurrencyCode] ?: @"";
|
|
||||||
NSString *productPrice = [p.price stringValue] ?: @"";
|
|
||||||
qInfo().noquote() << "[IAP][StoreKit] Fetched product info" << QString::fromUtf8(p.productIdentifier.UTF8String)
|
|
||||||
<< "price=" << QString::fromUtf8(productPrice.UTF8String)
|
|
||||||
<< "currency=" << QString::fromUtf8(productCurrency.UTF8String);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.productsFetchCompletion(productDicts, response.invalidProductIdentifiers, nil);
|
|
||||||
self.productsFetchCompletion = nil;
|
|
||||||
self.productsRequest = nil;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error
|
|
||||||
{
|
|
||||||
if (self.purchaseCompletion) {
|
|
||||||
self.purchaseCompletion(NO, nil, nil, nil, error);
|
|
||||||
self.purchaseCompletion = nil;
|
|
||||||
}
|
|
||||||
if (self.productsFetchCompletion) {
|
|
||||||
self.productsFetchCompletion(@[], @[], error);
|
|
||||||
self.productsFetchCompletion = nil;
|
|
||||||
}
|
|
||||||
self.productsRequest = nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pragma mark - SKPaymentTransactionObserver
|
|
||||||
|
|
||||||
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions
|
|
||||||
{
|
|
||||||
for (SKPaymentTransaction *transaction in transactions) {
|
|
||||||
switch (transaction.transactionState) {
|
|
||||||
case SKPaymentTransactionStatePurchased: {
|
|
||||||
NSString *originalTransactionId = transaction.originalTransaction.transactionIdentifier ?: transaction.transactionIdentifier;
|
|
||||||
qInfo().noquote() << "[IAP][StoreKit] Transaction purchased" << QString::fromUtf8(transaction.transactionIdentifier.UTF8String)
|
|
||||||
<< "original=" << QString::fromUtf8((originalTransactionId ?: @"").UTF8String)
|
|
||||||
<< "product=" << QString::fromUtf8(transaction.payment.productIdentifier.UTF8String);
|
|
||||||
|
|
||||||
if (self.purchaseCompletion) {
|
|
||||||
self.purchaseCompletion(YES,
|
|
||||||
transaction.transactionIdentifier,
|
|
||||||
transaction.payment.productIdentifier,
|
|
||||||
originalTransactionId,
|
|
||||||
nil);
|
|
||||||
self.purchaseCompletion = nil;
|
|
||||||
}
|
|
||||||
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case SKPaymentTransactionStateFailed:
|
|
||||||
qInfo().noquote() << "[IAP][StoreKit] Transaction failed" << QString::fromUtf8(transaction.transactionIdentifier.UTF8String)
|
|
||||||
<< "product=" << QString::fromUtf8(transaction.payment.productIdentifier.UTF8String)
|
|
||||||
<< "error=" << QString::fromUtf8(transaction.error.localizedDescription.UTF8String);
|
|
||||||
if (self.purchaseCompletion) {
|
|
||||||
self.purchaseCompletion(NO,
|
|
||||||
transaction.transactionIdentifier,
|
|
||||||
transaction.payment.productIdentifier,
|
|
||||||
nil,
|
|
||||||
transaction.error);
|
|
||||||
self.purchaseCompletion = nil;
|
|
||||||
}
|
|
||||||
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
|
|
||||||
break;
|
|
||||||
case SKPaymentTransactionStateRestored: {
|
|
||||||
if (self.restoreCompletion) {
|
|
||||||
NSString *transactionId = transaction.transactionIdentifier ?: @"";
|
|
||||||
NSString *originalTransactionId = transaction.originalTransaction.transactionIdentifier ?: transactionId;
|
|
||||||
NSString *productId = transaction.payment.productIdentifier ?: @"";
|
|
||||||
|
|
||||||
qInfo().noquote() << "[IAP][StoreKit] Transaction restored"
|
|
||||||
<< QString::fromUtf8(transactionId.UTF8String)
|
|
||||||
<< "original="
|
|
||||||
<< QString::fromUtf8((originalTransactionId ?: @"").UTF8String)
|
|
||||||
<< "product="
|
|
||||||
<< QString::fromUtf8((productId ?: @"").UTF8String);
|
|
||||||
|
|
||||||
NSDictionary *info = @{
|
|
||||||
@"transactionId": transactionId,
|
|
||||||
@"originalTransactionId": originalTransactionId ?: @"",
|
|
||||||
@"productId": productId ?: @""
|
|
||||||
};
|
|
||||||
if (!self.restoredTransactions) {
|
|
||||||
self.restoredTransactions = [NSMutableArray array];
|
|
||||||
}
|
|
||||||
[self.restoredTransactions addObject:info];
|
|
||||||
}
|
|
||||||
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case SKPaymentTransactionStatePurchasing:
|
|
||||||
case SKPaymentTransactionStateDeferred:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
|
|
||||||
{
|
|
||||||
if (self.restoreCompletion) {
|
|
||||||
NSArray<NSDictionary *> *transactions = [self.restoredTransactions copy];
|
|
||||||
self.restoreCompletion(YES, transactions, nil);
|
|
||||||
self.restoreCompletion = nil;
|
|
||||||
self.restoredTransactions = nil;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error
|
|
||||||
{
|
|
||||||
if (self.restoreCompletion) {
|
|
||||||
self.restoreCompletion(NO, nil, error);
|
|
||||||
self.restoreCompletion = nil;
|
|
||||||
self.restoredTransactions = nil;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
|
||||||
@@ -6,6 +6,8 @@ struct WGConfig: Decodable {
|
|||||||
let junkPacketCount, junkPacketMinSize, junkPacketMaxSize: String?
|
let junkPacketCount, junkPacketMinSize, junkPacketMaxSize: String?
|
||||||
let initPacketJunkSize, responsePacketJunkSize, cookieReplyPacketJunkSize, transportPacketJunkSize: String?
|
let initPacketJunkSize, responsePacketJunkSize, cookieReplyPacketJunkSize, transportPacketJunkSize: String?
|
||||||
let specialJunk1, specialJunk2, specialJunk3, specialJunk4, specialJunk5: String?
|
let specialJunk1, specialJunk2, specialJunk3, specialJunk4, specialJunk5: String?
|
||||||
|
let controlledJunk1, controlledJunk2, controlledJunk3: String?
|
||||||
|
let specialHandshakeTimeout: String?
|
||||||
let dns1: String
|
let dns1: String
|
||||||
let dns2: String
|
let dns2: String
|
||||||
let mtu: String
|
let mtu: String
|
||||||
@@ -26,6 +28,8 @@ struct WGConfig: Decodable {
|
|||||||
case junkPacketCount = "Jc", junkPacketMinSize = "Jmin", junkPacketMaxSize = "Jmax"
|
case junkPacketCount = "Jc", junkPacketMinSize = "Jmin", junkPacketMaxSize = "Jmax"
|
||||||
case initPacketJunkSize = "S1", responsePacketJunkSize = "S2", cookieReplyPacketJunkSize = "S3", transportPacketJunkSize = "S4"
|
case initPacketJunkSize = "S1", responsePacketJunkSize = "S2", cookieReplyPacketJunkSize = "S3", transportPacketJunkSize = "S4"
|
||||||
case specialJunk1 = "I1", specialJunk2 = "I2", specialJunk3 = "I3", specialJunk4 = "I4", specialJunk5 = "I5"
|
case specialJunk1 = "I1", specialJunk2 = "I2", specialJunk3 = "I3", specialJunk4 = "I4", specialJunk5 = "I5"
|
||||||
|
case controlledJunk1 = "J1", controlledJunk2 = "J2", controlledJunk3 = "J3"
|
||||||
|
case specialHandshakeTimeout = "Itime"
|
||||||
case dns1
|
case dns1
|
||||||
case dns2
|
case dns2
|
||||||
case mtu
|
case mtu
|
||||||
@@ -42,63 +46,57 @@ struct WGConfig: Decodable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var settings: String {
|
var settings: String {
|
||||||
func trimmed(_ value: String?) -> String? {
|
guard junkPacketCount != nil else { return "" }
|
||||||
guard let value = value?.trimmingCharacters(in: .whitespacesAndNewlines),
|
|
||||||
!value.isEmpty else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
guard
|
|
||||||
let junkPacketCount = trimmed(junkPacketCount),
|
|
||||||
let junkPacketMinSize = trimmed(junkPacketMinSize),
|
|
||||||
let junkPacketMaxSize = trimmed(junkPacketMaxSize),
|
|
||||||
let initPacketJunkSize = trimmed(initPacketJunkSize),
|
|
||||||
let responsePacketJunkSize = trimmed(responsePacketJunkSize),
|
|
||||||
let initPacketMagicHeader = trimmed(initPacketMagicHeader),
|
|
||||||
let responsePacketMagicHeader = trimmed(responsePacketMagicHeader),
|
|
||||||
let underloadPacketMagicHeader = trimmed(underloadPacketMagicHeader),
|
|
||||||
let transportPacketMagicHeader = trimmed(transportPacketMagicHeader)
|
|
||||||
else { return "" }
|
|
||||||
|
|
||||||
var settingsLines: [String] = []
|
var settingsLines: [String] = []
|
||||||
|
|
||||||
// Required parameters when junkPacketCount is present
|
// Required parameters when junkPacketCount is present
|
||||||
settingsLines.append("Jc = \(junkPacketCount)")
|
settingsLines.append("Jc = \(junkPacketCount!)")
|
||||||
settingsLines.append("Jmin = \(junkPacketMinSize)")
|
settingsLines.append("Jmin = \(junkPacketMinSize!)")
|
||||||
settingsLines.append("Jmax = \(junkPacketMaxSize)")
|
settingsLines.append("Jmax = \(junkPacketMaxSize!)")
|
||||||
settingsLines.append("S1 = \(initPacketJunkSize)")
|
settingsLines.append("S1 = \(initPacketJunkSize!)")
|
||||||
settingsLines.append("S2 = \(responsePacketJunkSize)")
|
settingsLines.append("S2 = \(responsePacketJunkSize!)")
|
||||||
|
|
||||||
settingsLines.append("H1 = \(initPacketMagicHeader)")
|
settingsLines.append("H1 = \(initPacketMagicHeader!)")
|
||||||
settingsLines.append("H2 = \(responsePacketMagicHeader)")
|
settingsLines.append("H2 = \(responsePacketMagicHeader!)")
|
||||||
settingsLines.append("H3 = \(underloadPacketMagicHeader)")
|
settingsLines.append("H3 = \(underloadPacketMagicHeader!)")
|
||||||
settingsLines.append("H4 = \(transportPacketMagicHeader)")
|
settingsLines.append("H4 = \(transportPacketMagicHeader!)")
|
||||||
|
|
||||||
// Optional parameters - only add if not nil and not empty
|
// Optional parameters - only add if not nil and not empty
|
||||||
if let s3 = trimmed(cookieReplyPacketJunkSize) {
|
if let s3 = cookieReplyPacketJunkSize, !s3.isEmpty {
|
||||||
settingsLines.append("S3 = \(s3)")
|
settingsLines.append("S3 = \(s3)")
|
||||||
}
|
}
|
||||||
if let s4 = trimmed(transportPacketJunkSize) {
|
if let s4 = transportPacketJunkSize, !s4.isEmpty {
|
||||||
settingsLines.append("S4 = \(s4)")
|
settingsLines.append("S4 = \(s4)")
|
||||||
}
|
}
|
||||||
|
|
||||||
if let i1 = trimmed(specialJunk1) {
|
if let i1 = specialJunk1, !i1.isEmpty {
|
||||||
settingsLines.append("I1 = \(i1)")
|
settingsLines.append("I1 = \(i1)")
|
||||||
}
|
}
|
||||||
if let i2 = trimmed(specialJunk2) {
|
if let i2 = specialJunk2, !i2.isEmpty {
|
||||||
settingsLines.append("I2 = \(i2)")
|
settingsLines.append("I2 = \(i2)")
|
||||||
}
|
}
|
||||||
if let i3 = trimmed(specialJunk3) {
|
if let i3 = specialJunk3, !i3.isEmpty {
|
||||||
settingsLines.append("I3 = \(i3)")
|
settingsLines.append("I3 = \(i3)")
|
||||||
}
|
}
|
||||||
if let i4 = trimmed(specialJunk4) {
|
if let i4 = specialJunk4, !i4.isEmpty {
|
||||||
settingsLines.append("I4 = \(i4)")
|
settingsLines.append("I4 = \(i4)")
|
||||||
}
|
}
|
||||||
if let i5 = trimmed(specialJunk5) {
|
if let i5 = specialJunk5, !i5.isEmpty {
|
||||||
settingsLines.append("I5 = \(i5)")
|
settingsLines.append("I5 = \(i5)")
|
||||||
}
|
}
|
||||||
|
if let j1 = controlledJunk1, !j1.isEmpty {
|
||||||
|
settingsLines.append("J1 = \(j1)")
|
||||||
|
}
|
||||||
|
if let j2 = controlledJunk2, !j2.isEmpty {
|
||||||
|
settingsLines.append("J2 = \(j2)")
|
||||||
|
}
|
||||||
|
if let j3 = controlledJunk3, !j3.isEmpty {
|
||||||
|
settingsLines.append("J3 = \(j3)")
|
||||||
|
}
|
||||||
|
if let itime = specialHandshakeTimeout, !itime.isEmpty {
|
||||||
|
settingsLines.append("Itime = \(itime)")
|
||||||
|
}
|
||||||
|
|
||||||
return settingsLines.joined(separator: "\n")
|
return settingsLines.joined(separator: "\n")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,13 +2,6 @@
|
|||||||
#define IOS_CONTROLLER_H
|
#define IOS_CONTROLLER_H
|
||||||
|
|
||||||
#include "protocols/vpnprotocol.h"
|
#include "protocols/vpnprotocol.h"
|
||||||
#include <functional>
|
|
||||||
#include <QVariant>
|
|
||||||
#include <QVariantMap>
|
|
||||||
#include <QStringList>
|
|
||||||
#include <QList>
|
|
||||||
#include <QElapsedTimer>
|
|
||||||
#include <atomic>
|
|
||||||
|
|
||||||
#ifdef __OBJC__
|
#ifdef __OBJC__
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
@@ -62,24 +55,7 @@ public:
|
|||||||
bool shareText(const QStringList &filesToSend);
|
bool shareText(const QStringList &filesToSend);
|
||||||
QString openFile();
|
QString openFile();
|
||||||
|
|
||||||
void purchaseProduct(const QString &productId,
|
|
||||||
std::function<void(bool success,
|
|
||||||
const QString &transactionId,
|
|
||||||
const QString &purchasedProductId,
|
|
||||||
const QString &originalTransactionId,
|
|
||||||
const QString &errorString)> &&callback);
|
|
||||||
void restorePurchases(std::function<void(bool success,
|
|
||||||
const QList<QVariantMap> &transactions,
|
|
||||||
const QString &errorString)> &&callback);
|
|
||||||
|
|
||||||
// Fetch product info for given product identifiers and return basic fields for logging
|
|
||||||
void fetchProducts(const QStringList &productIds,
|
|
||||||
std::function<void(const QList<QVariantMap> &products,
|
|
||||||
const QStringList &invalidIds,
|
|
||||||
const QString &errorString)> &&callback);
|
|
||||||
|
|
||||||
void requestInetAccess();
|
void requestInetAccess();
|
||||||
bool isTestFlight();
|
|
||||||
signals:
|
signals:
|
||||||
void connectionStateChanged(Vpn::ConnectionState state);
|
void connectionStateChanged(Vpn::ConnectionState state);
|
||||||
void bytesChanged(quint64 receivedBytes, quint64 sentBytes);
|
void bytesChanged(quint64 receivedBytes, quint64 sentBytes);
|
||||||
@@ -105,7 +81,6 @@ private:
|
|||||||
bool startXray(const QString &jsonConfig);
|
bool startXray(const QString &jsonConfig);
|
||||||
|
|
||||||
void startTunnel();
|
void startTunnel();
|
||||||
void emitConnectionStateIfChanged(Vpn::ConnectionState state);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void *m_iosControllerWrapper {};
|
void *m_iosControllerWrapper {};
|
||||||
@@ -119,13 +94,8 @@ private:
|
|||||||
amnezia::Proto m_proto;
|
amnezia::Proto m_proto;
|
||||||
QJsonObject m_rawConfig;
|
QJsonObject m_rawConfig;
|
||||||
QString m_tunnelId;
|
QString m_tunnelId;
|
||||||
uint64_t m_txBytes = 0;
|
uint64_t m_txBytes;
|
||||||
uint64_t m_rxBytes = 0;
|
uint64_t m_rxBytes;
|
||||||
bool m_handshakeAwaiting = false;
|
|
||||||
bool m_handshakeConfirmed = false;
|
|
||||||
QElapsedTimer m_handshakeTimer;
|
|
||||||
Vpn::ConnectionState m_lastEmittedState = Vpn::ConnectionState::Unknown;
|
|
||||||
std::atomic_bool m_statusRequestInFlight { false };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // IOS_CONTROLLER_H
|
#endif // IOS_CONTROLLER_H
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
|
|
||||||
#include "../protocols/vpnprotocol.h"
|
#include "../protocols/vpnprotocol.h"
|
||||||
#import "ios_controller_wrapper.h"
|
#import "ios_controller_wrapper.h"
|
||||||
#import "StoreKitController.h"
|
|
||||||
|
|
||||||
const char* Action::start = "start";
|
const char* Action::start = "start";
|
||||||
const char* Action::restart = "restart";
|
const char* Action::restart = "restart";
|
||||||
@@ -30,46 +29,12 @@ const char* MessageKey::SplitTunnelSites = "SplitTunnelSites";
|
|||||||
|
|
||||||
#if !MACOS_NE
|
#if !MACOS_NE
|
||||||
static UIViewController* getViewController() {
|
static UIViewController* getViewController() {
|
||||||
UIApplication *application = [UIApplication sharedApplication];
|
NSArray *windows = [[UIApplication sharedApplication]windows];
|
||||||
|
for (UIWindow *window in windows) {
|
||||||
if (@available(iOS 13.0, *)) {
|
if (window.isKeyWindow) {
|
||||||
for (UIScene *scene in application.connectedScenes) {
|
|
||||||
if (scene.activationState != UISceneActivationStateForegroundActive) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (![scene isKindOfClass:[UIWindowScene class]]) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
UIWindowScene *windowScene = (UIWindowScene *)scene;
|
|
||||||
|
|
||||||
for (UIWindow *window in windowScene.windows) {
|
|
||||||
if (window.isKeyWindow && window.rootViewController) {
|
|
||||||
return window.rootViewController;
|
return window.rootViewController;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (UIWindow *window in windowScene.windows) {
|
|
||||||
if (!window.isHidden && window.rootViewController) {
|
|
||||||
return window.rootViewController;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (UIWindow *window in application.windows) {
|
|
||||||
if (window.isKeyWindow && window.rootViewController) {
|
|
||||||
return window.rootViewController;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (UIWindow *window in application.windows) {
|
|
||||||
if (window.rootViewController) {
|
|
||||||
return window.rootViewController;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil;
|
return nil;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -93,48 +58,6 @@ Vpn::ConnectionState iosStatusToState(NEVPNStatus status) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
|
||||||
constexpr int kHandshakeTimeoutMs = 12000;
|
|
||||||
constexpr uint64_t kHandshakeRxThreshold = 4096;
|
|
||||||
bool isWireGuardBasedProto(amnezia::Proto proto) {
|
|
||||||
return proto == amnezia::Proto::WireGuard || proto == amnezia::Proto::Awg;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t uint64FromResponse(NSDictionary *response, NSString *key, uint64_t fallback = 0) {
|
|
||||||
id value = response[key];
|
|
||||||
if (!value || value == [NSNull null]) {
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
if ([value isKindOfClass:[NSNumber class]]) {
|
|
||||||
return [(NSNumber *)value unsignedLongLongValue];
|
|
||||||
}
|
|
||||||
if ([value isKindOfClass:[NSString class]]) {
|
|
||||||
const char *str = [(NSString *)value UTF8String];
|
|
||||||
if (str && *str) {
|
|
||||||
return strtoull(str, nullptr, 10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
long long int64FromResponse(NSDictionary *response, NSString *key, long long fallback = 0) {
|
|
||||||
id value = response[key];
|
|
||||||
if (!value || value == [NSNull null]) {
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
if ([value isKindOfClass:[NSNumber class]]) {
|
|
||||||
return [(NSNumber *)value longLongValue];
|
|
||||||
}
|
|
||||||
if ([value isKindOfClass:[NSString class]]) {
|
|
||||||
const char *str = [(NSString *)value UTF8String];
|
|
||||||
if (str && *str) {
|
|
||||||
return strtoll(str, nullptr, 10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
IosController* s_instance = nullptr;
|
IosController* s_instance = nullptr;
|
||||||
}
|
}
|
||||||
@@ -144,9 +67,6 @@ IosController::IosController() : QObject()
|
|||||||
s_instance = this;
|
s_instance = this;
|
||||||
m_iosControllerWrapper = [[IosControllerWrapper alloc] initWithCppController:this];
|
m_iosControllerWrapper = [[IosControllerWrapper alloc] initWithCppController:this];
|
||||||
|
|
||||||
// Initialize StoreKitController early to start observing the payment queue
|
|
||||||
[StoreKitController sharedInstance];
|
|
||||||
|
|
||||||
[[NSNotificationCenter defaultCenter]
|
[[NSNotificationCenter defaultCenter]
|
||||||
removeObserver: (__bridge NSObject *)m_iosControllerWrapper];
|
removeObserver: (__bridge NSObject *)m_iosControllerWrapper];
|
||||||
[[NSNotificationCenter defaultCenter]
|
[[NSNotificationCenter defaultCenter]
|
||||||
@@ -156,15 +76,6 @@ IosController::IosController() : QObject()
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void IosController::emitConnectionStateIfChanged(Vpn::ConnectionState state)
|
|
||||||
{
|
|
||||||
if (m_lastEmittedState == state) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
m_lastEmittedState = state;
|
|
||||||
emit connectionStateChanged(state);
|
|
||||||
}
|
|
||||||
|
|
||||||
IosController* IosController::Instance() {
|
IosController* IosController::Instance() {
|
||||||
if (!s_instance) {
|
if (!s_instance) {
|
||||||
s_instance = new IosController();
|
s_instance = new IosController();
|
||||||
@@ -331,65 +242,33 @@ void IosController::disconnectVpn()
|
|||||||
|
|
||||||
void IosController::checkStatus()
|
void IosController::checkStatus()
|
||||||
{
|
{
|
||||||
if (!m_currentTunnel) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_currentTunnel.connection.status != NEVPNStatusConnected) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_statusRequestInFlight.exchange(true)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
NSString *actionKey = [NSString stringWithUTF8String:MessageKey::action];
|
NSString *actionKey = [NSString stringWithUTF8String:MessageKey::action];
|
||||||
NSString *actionValue = [NSString stringWithUTF8String:Action::getStatus];
|
NSString *actionValue = [NSString stringWithUTF8String:Action::getStatus];
|
||||||
NSString *tunnelIdKey = [NSString stringWithUTF8String:MessageKey::tunnelId];
|
NSString *tunnelIdKey = [NSString stringWithUTF8String:MessageKey::tunnelId];
|
||||||
NSString *tunnelIdValue = !m_tunnelId.isEmpty() ? m_tunnelId.toNSString() : @"";
|
NSString *tunnelIdValue = !m_tunnelId.isEmpty() ? m_tunnelId.toNSString() : @"";
|
||||||
|
|
||||||
NSDictionary* message = @{actionKey: actionValue, tunnelIdKey: tunnelIdValue};
|
NSDictionary* message = @{actionKey: actionValue, tunnelIdKey: tunnelIdValue};
|
||||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
|
||||||
sendVpnExtensionMessage(message, [&](NSDictionary* response){
|
sendVpnExtensionMessage(message, [&](NSDictionary* response){
|
||||||
if (!response) {
|
uint64_t txBytes = [response[@"tx_bytes"] intValue];
|
||||||
QMetaObject::invokeMethod(this, [this]() {
|
uint64_t rxBytes = [response[@"rx_bytes"] intValue];
|
||||||
m_statusRequestInFlight = false;
|
|
||||||
}, Qt::QueuedConnection);
|
uint64_t last_handshake_time_sec = 0;
|
||||||
return;
|
#if !MACOS_NE
|
||||||
|
if (response[@"last_handshake_time_sec"] && ![response[@"last_handshake_time_sec"] isKindOfClass:[NSNull class]]) {
|
||||||
|
last_handshake_time_sec = [response[@"last_handshake_time_sec"] intValue];
|
||||||
|
} else {
|
||||||
|
qDebug() << "Key last_handshake_time_sec is missing or null";
|
||||||
}
|
}
|
||||||
|
|
||||||
const uint64_t txBytes = uint64FromResponse(response, @"tx_bytes");
|
if (last_handshake_time_sec < 0) {
|
||||||
const uint64_t rxBytes = uint64FromResponse(response, @"rx_bytes");
|
disconnectVpn();
|
||||||
const long long last_handshake_time_sec = int64FromResponse(response, @"last_handshake_time_sec");
|
qDebug() << "Invalid handshake time, disconnecting VPN.";
|
||||||
|
|
||||||
QMetaObject::invokeMethod(this, [this, txBytes, rxBytes, last_handshake_time_sec]() {
|
|
||||||
if (isWireGuardBasedProto(m_proto) && m_handshakeAwaiting) {
|
|
||||||
const bool hasHandshakeData = (last_handshake_time_sec >= 0);
|
|
||||||
const bool hasFreshHandshake = hasHandshakeData &&
|
|
||||||
((last_handshake_time_sec > 0) ||
|
|
||||||
(rxBytes >= kHandshakeRxThreshold) ||
|
|
||||||
(txBytes >= kHandshakeRxThreshold));
|
|
||||||
|
|
||||||
if (hasFreshHandshake) {
|
|
||||||
m_handshakeConfirmed = true;
|
|
||||||
m_handshakeAwaiting = false;
|
|
||||||
m_handshakeTimer.invalidate();
|
|
||||||
qDebug() << "IosController::checkStatus : handshake confirmed";
|
|
||||||
emitConnectionStateIfChanged(Vpn::ConnectionState::Connected);
|
|
||||||
} else if (m_handshakeTimer.isValid() &&
|
|
||||||
m_handshakeTimer.elapsed() > kHandshakeTimeoutMs) {
|
|
||||||
m_handshakeTimer.restart();
|
|
||||||
qDebug() << "IosController::checkStatus : handshake timed out, keeping tunnel alive";
|
|
||||||
emitConnectionStateIfChanged(Vpn::ConnectionState::Reconnecting);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
emit bytesChanged(rxBytes - m_rxBytes, txBytes - m_txBytes);
|
emit bytesChanged(rxBytes - m_rxBytes, txBytes - m_txBytes);
|
||||||
m_rxBytes = rxBytes;
|
m_rxBytes = rxBytes;
|
||||||
m_txBytes = txBytes;
|
m_txBytes = txBytes;
|
||||||
m_statusRequestInFlight = false;
|
|
||||||
}, Qt::QueuedConnection);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -496,22 +375,7 @@ void IosController::vpnStatusDidChange(void *pNotification)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Vpn::ConnectionState nextState = iosStatusToState(session.status);
|
emit connectionStateChanged(iosStatusToState(session.status));
|
||||||
if (session.status == NEVPNStatusConnected && isWireGuardBasedProto(m_proto)) {
|
|
||||||
if (!m_handshakeConfirmed) {
|
|
||||||
nextState = Vpn::ConnectionState::Connecting;
|
|
||||||
if (!m_handshakeAwaiting) {
|
|
||||||
m_handshakeAwaiting = true;
|
|
||||||
m_handshakeTimer.restart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (session.status != NEVPNStatusConnected) {
|
|
||||||
m_handshakeAwaiting = false;
|
|
||||||
m_handshakeConfirmed = false;
|
|
||||||
m_handshakeTimer.invalidate();
|
|
||||||
m_statusRequestInFlight = false;
|
|
||||||
}
|
|
||||||
emitConnectionStateIfChanged(nextState);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -772,6 +636,10 @@ bool IosController::setupAwg()
|
|||||||
wgConfig.insert(config_key::specialJunk3, config[config_key::specialJunk3]);
|
wgConfig.insert(config_key::specialJunk3, config[config_key::specialJunk3]);
|
||||||
wgConfig.insert(config_key::specialJunk4, config[config_key::specialJunk4]);
|
wgConfig.insert(config_key::specialJunk4, config[config_key::specialJunk4]);
|
||||||
wgConfig.insert(config_key::specialJunk5, config[config_key::specialJunk5]);
|
wgConfig.insert(config_key::specialJunk5, config[config_key::specialJunk5]);
|
||||||
|
wgConfig.insert(config_key::controlledJunk1, config[config_key::controlledJunk1]);
|
||||||
|
wgConfig.insert(config_key::controlledJunk2, config[config_key::controlledJunk2]);
|
||||||
|
wgConfig.insert(config_key::controlledJunk3, config[config_key::controlledJunk3]);
|
||||||
|
wgConfig.insert(config_key::specialHandshakeTimeout, config[config_key::specialHandshakeTimeout]);
|
||||||
|
|
||||||
QJsonDocument wgConfigDoc(wgConfig);
|
QJsonDocument wgConfigDoc(wgConfig);
|
||||||
QString wgConfigDocStr(wgConfigDoc.toJson(QJsonDocument::Compact));
|
QString wgConfigDocStr(wgConfigDoc.toJson(QJsonDocument::Compact));
|
||||||
@@ -897,9 +765,6 @@ void IosController::sendVpnExtensionMessage(NSDictionary* message, std::function
|
|||||||
{
|
{
|
||||||
if (!m_currentTunnel) {
|
if (!m_currentTunnel) {
|
||||||
qDebug() << "Cannot set an extension callback without a tunnel manager";
|
qDebug() << "Cannot set an extension callback without a tunnel manager";
|
||||||
if (callback) {
|
|
||||||
callback(nil);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -909,9 +774,6 @@ void IosController::sendVpnExtensionMessage(NSDictionary* message, std::function
|
|||||||
if (!data || error) {
|
if (!data || error) {
|
||||||
qDebug() << "Failed to serialize message to VpnExtension as JSON. Error:"
|
qDebug() << "Failed to serialize message to VpnExtension as JSON. Error:"
|
||||||
<< [error.localizedDescription UTF8String];
|
<< [error.localizedDescription UTF8String];
|
||||||
if (callback) {
|
|
||||||
callback(nil);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -942,18 +804,11 @@ void IosController::sendVpnExtensionMessage(NSDictionary* message, std::function
|
|||||||
[session sendProviderMessage:data returnError:&sendError responseHandler:completionHandler];
|
[session sendProviderMessage:data returnError:&sendError responseHandler:completionHandler];
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "Method sendProviderMessage:responseHandler:error: does not exist";
|
qDebug() << "Method sendProviderMessage:responseHandler:error: does not exist";
|
||||||
if (callback) {
|
|
||||||
callback(nil);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sendError) {
|
if (sendError) {
|
||||||
qDebug() << "Failed to send message to VpnExtension. Error:"
|
qDebug() << "Failed to send message to VpnExtension. Error:"
|
||||||
<< [sendError.localizedDescription UTF8String];
|
<< [sendError.localizedDescription UTF8String];
|
||||||
if (callback) {
|
|
||||||
callback(nil);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1024,135 +879,6 @@ QString IosController::openFile() {
|
|||||||
return filePath;
|
return filePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
void IosController::purchaseProduct(const QString &productId,
|
|
||||||
std::function<void(bool success,
|
|
||||||
const QString &transactionId,
|
|
||||||
const QString &purchasedProductId,
|
|
||||||
const QString &originalTransactionId,
|
|
||||||
const QString &errorString)> &&callback)
|
|
||||||
{
|
|
||||||
qInfo().noquote() << "[IAP][IosController] purchaseProduct called" << productId;
|
|
||||||
if (@available(iOS 15.0, macOS 12.0, *)) {
|
|
||||||
StoreKitController *controller = [StoreKitController sharedInstance];
|
|
||||||
__block auto cb = std::move(callback);
|
|
||||||
[controller purchaseProduct:productId.toNSString() completion:^(BOOL s,
|
|
||||||
NSString * _Nullable transactionId,
|
|
||||||
NSString * _Nullable prodId,
|
|
||||||
NSString * _Nullable originalTxId,
|
|
||||||
NSError * _Nullable error) {
|
|
||||||
const QString txId = QString::fromUtf8((transactionId ?: @"").UTF8String);
|
|
||||||
const QString pId = QString::fromUtf8((prodId ?: @"").UTF8String);
|
|
||||||
const QString origTxId = QString::fromUtf8((originalTxId ?: @"").UTF8String);
|
|
||||||
const QString err = QString::fromUtf8((error.localizedDescription ?: @"").UTF8String);
|
|
||||||
|
|
||||||
qInfo().noquote() << "[IAP][IosController] purchase completion" << "success=" << s
|
|
||||||
<< "transactionId=" << txId << "originalTransactionId=" << origTxId
|
|
||||||
<< "productId=" << pId << "error=" << err;
|
|
||||||
|
|
||||||
if (cb) {
|
|
||||||
cb(s, txId, pId, origTxId, err);
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
} else {
|
|
||||||
if (callback) {
|
|
||||||
callback(false, QString(), QString(), QString(), "StoreKit 2 requires iOS 15.0 or later");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void IosController::restorePurchases(std::function<void(bool success,
|
|
||||||
const QList<QVariantMap> &transactions,
|
|
||||||
const QString &errorString)> &&callback)
|
|
||||||
{
|
|
||||||
if (@available(iOS 15.0, macOS 12.0, *)) {
|
|
||||||
StoreKitController *controller = [StoreKitController sharedInstance];
|
|
||||||
__block auto cb = std::move(callback);
|
|
||||||
[controller restorePurchasesWithCompletion:^(BOOL s,
|
|
||||||
NSArray<NSDictionary *> * _Nullable restoredTransactions,
|
|
||||||
NSError * _Nullable error) {
|
|
||||||
QString err;
|
|
||||||
if (error) {
|
|
||||||
err = QString::fromUtf8(error.localizedDescription.UTF8String);
|
|
||||||
}
|
|
||||||
QList<QVariantMap> transactions;
|
|
||||||
for (NSDictionary *dict in restoredTransactions ?: @[]) {
|
|
||||||
QVariantMap transaction;
|
|
||||||
NSString *transactionId = dict[@"transactionId"];
|
|
||||||
NSString *productId = dict[@"productId"];
|
|
||||||
NSString *originalTransactionId = dict[@"originalTransactionId"];
|
|
||||||
|
|
||||||
if (transactionId) {
|
|
||||||
transaction.insert(QStringLiteral("transactionId"), QString::fromUtf8(transactionId.UTF8String));
|
|
||||||
}
|
|
||||||
if (productId) {
|
|
||||||
transaction.insert(QStringLiteral("productId"), QString::fromUtf8(productId.UTF8String));
|
|
||||||
}
|
|
||||||
if (originalTransactionId) {
|
|
||||||
transaction.insert(QStringLiteral("originalTransactionId"),
|
|
||||||
QString::fromUtf8(originalTransactionId.UTF8String));
|
|
||||||
}
|
|
||||||
transactions.push_back(transaction);
|
|
||||||
}
|
|
||||||
if (cb) {
|
|
||||||
cb(s, transactions, err);
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
} else {
|
|
||||||
if (callback) {
|
|
||||||
callback(false, QList<QVariantMap>(), "StoreKit 2 requires iOS 15.0 or later");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void IosController::fetchProducts(const QStringList &productIds,
|
|
||||||
std::function<void(const QList<QVariantMap> &products,
|
|
||||||
const QStringList &invalidIds,
|
|
||||||
const QString &errorString)> &&callback)
|
|
||||||
{
|
|
||||||
if (@available(iOS 15.0, macOS 12.0, *)) {
|
|
||||||
StoreKitController *controller = [StoreKitController sharedInstance];
|
|
||||||
NSMutableSet<NSString *> *ids = [NSMutableSet setWithCapacity:productIds.size()];
|
|
||||||
for (const auto &pid : productIds) {
|
|
||||||
[ids addObject:pid.toNSString()];
|
|
||||||
}
|
|
||||||
__block auto cb = std::move(callback);
|
|
||||||
|
|
||||||
[controller fetchProductsWithIdentifiers:ids
|
|
||||||
completion:^(NSArray<NSDictionary *> * _Nonnull products,
|
|
||||||
NSArray<NSString *> * _Nonnull invalidIdentifiers,
|
|
||||||
NSError * _Nullable error) {
|
|
||||||
QList<QVariantMap> outProducts;
|
|
||||||
for (NSDictionary *p in products) {
|
|
||||||
QVariantMap m;
|
|
||||||
m["productId"] = QString::fromUtf8([p[@"productId"] UTF8String]);
|
|
||||||
m["title"] = QString::fromUtf8([p[@"title"] UTF8String]);
|
|
||||||
m["description"] = QString::fromUtf8([p[@"description"] UTF8String]);
|
|
||||||
m["price"] = QString::fromUtf8([p[@"price"] UTF8String]);
|
|
||||||
m["currencyCode"] = QString::fromUtf8([p[@"currencyCode"] UTF8String]);
|
|
||||||
outProducts.push_back(m);
|
|
||||||
}
|
|
||||||
|
|
||||||
QStringList invalid;
|
|
||||||
for (NSString *inv in invalidIdentifiers) {
|
|
||||||
invalid.push_back(QString::fromUtf8(inv.UTF8String));
|
|
||||||
}
|
|
||||||
|
|
||||||
QString err;
|
|
||||||
if (error) {
|
|
||||||
err = QString::fromUtf8(error.localizedDescription.UTF8String);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cb) {
|
|
||||||
cb(outProducts, invalid, err);
|
|
||||||
}
|
|
||||||
}];
|
|
||||||
} else {
|
|
||||||
if (callback) {
|
|
||||||
callback(QList<QVariantMap>(), QStringList(), "StoreKit 2 requires iOS 15.0 or later");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void IosController::requestInetAccess() {
|
void IosController::requestInetAccess() {
|
||||||
NSURL *url = [NSURL URLWithString:@"http://captive.apple.com/generate_204"];
|
NSURL *url = [NSURL URLWithString:@"http://captive.apple.com/generate_204"];
|
||||||
if (!url) {
|
if (!url) {
|
||||||
@@ -1171,8 +897,3 @@ void IosController::requestInetAccess() {
|
|||||||
}];
|
}];
|
||||||
[task resume];
|
[task resume];
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IosController::isTestFlight() {
|
|
||||||
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
|
|
||||||
return receiptURL && [[receiptURL lastPathComponent] isEqualToString:@"sandboxReceipt"];
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -34,9 +34,6 @@ void IOSNetworkWatcher::initialize() {
|
|||||||
});
|
});
|
||||||
nw_path_monitor_start(m_networkMonitor);
|
nw_path_monitor_start(m_networkMonitor);
|
||||||
|
|
||||||
// Call start() to initialize sleep/wake monitoring (will call MacOSNetworkWatcher::start() if this is macOS)
|
|
||||||
this->start();
|
|
||||||
|
|
||||||
//TODO IMPL FOR AMNEZIA
|
//TODO IMPL FOR AMNEZIA
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ bool LinuxRouteMonitor::rtmSendRoute(int action, int flags, int type,
|
|||||||
|
|
||||||
if (rtm->rtm_type == RTN_THROW) {
|
if (rtm->rtm_type == RTN_THROW) {
|
||||||
struct in_addr ip4;
|
struct in_addr ip4;
|
||||||
inet_pton(AF_INET, NetworkUtilities::getGatewayAndIface().first.toUtf8(), &ip4);
|
inet_pton(AF_INET, NetworkUtilities::getGatewayAndIface().toUtf8(), &ip4);
|
||||||
nlmsg_append_attr(nlmsg, sizeof(buf), RTA_GATEWAY, &ip4, sizeof(ip4));
|
nlmsg_append_attr(nlmsg, sizeof(buf), RTA_GATEWAY, &ip4, sizeof(ip4));
|
||||||
nlmsg_append_attr32(nlmsg, sizeof(buf), RTA_PRIORITY, 0);
|
nlmsg_append_attr32(nlmsg, sizeof(buf), RTA_PRIORITY, 0);
|
||||||
rtm->rtm_type = RTN_UNICAST;
|
rtm->rtm_type = RTN_UNICAST;
|
||||||
|
|||||||
@@ -143,6 +143,12 @@ bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) {
|
|||||||
for (const QString& key : config.m_specialJunk.keys()) {
|
for (const QString& key : config.m_specialJunk.keys()) {
|
||||||
out << key.toLower() << "=" << config.m_specialJunk.value(key) << "\n";
|
out << key.toLower() << "=" << config.m_specialJunk.value(key) << "\n";
|
||||||
}
|
}
|
||||||
|
for (const QString& key : config.m_controlledJunk.keys()) {
|
||||||
|
out << key.toLower() << "=" << config.m_controlledJunk.value(key) << "\n";
|
||||||
|
}
|
||||||
|
if (!config.m_specialHandshakeTimeout.isEmpty()) {
|
||||||
|
out << "itime=" << config.m_specialHandshakeTimeout << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
int err = uapiErrno(uapiCommand(message));
|
int err = uapiErrno(uapiCommand(message));
|
||||||
if (err != 0) {
|
if (err != 0) {
|
||||||
|
|||||||
@@ -41,9 +41,6 @@ void LinuxNetworkWatcher::initialize() {
|
|||||||
connect(m_worker, &LinuxNetworkWatcherWorker::unsecuredNetwork, this,
|
connect(m_worker, &LinuxNetworkWatcherWorker::unsecuredNetwork, this,
|
||||||
&LinuxNetworkWatcher::unsecuredNetwork);
|
&LinuxNetworkWatcher::unsecuredNetwork);
|
||||||
|
|
||||||
connect(m_worker, &LinuxNetworkWatcherWorker::sleepMode, this,
|
|
||||||
&NetworkWatcherImpl::sleepMode);
|
|
||||||
|
|
||||||
// Let's wait a few seconds to allow the UI to be fully loaded and shown.
|
// Let's wait a few seconds to allow the UI to be fully loaded and shown.
|
||||||
// This is not strictly needed, but it's better for user experience because
|
// This is not strictly needed, but it's better for user experience because
|
||||||
// it makes the UI faster to appear, plus it gives a bit of delay between the
|
// it makes the UI faster to appear, plus it gives a bit of delay between the
|
||||||
|
|||||||
@@ -33,21 +33,7 @@
|
|||||||
#define NM_802_11_AP_SEC_WEAK_CRYPTO \
|
#define NM_802_11_AP_SEC_WEAK_CRYPTO \
|
||||||
(NM_802_11_AP_SEC_PAIR_WEP40 | NM_802_11_AP_SEC_PAIR_WEP104)
|
(NM_802_11_AP_SEC_PAIR_WEP40 | NM_802_11_AP_SEC_PAIR_WEP104)
|
||||||
|
|
||||||
|
|
||||||
enum NMState {
|
|
||||||
NM_STATE_UNKNOWN = 0,
|
|
||||||
NM_STATE_ASLEEP = 10,
|
|
||||||
NM_STATE_DISCONNECTED = 20,
|
|
||||||
NM_STATE_DISCONNECTING = 30,
|
|
||||||
NM_STATE_CONNECTING = 40,
|
|
||||||
NM_STATE_CONNECTED_LOCAL = 50,
|
|
||||||
NM_STATE_CONNECTED_SITE = 60,
|
|
||||||
NM_STATE_CONNECTED_GLOBAL = 70
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
constexpr const char* DBUS_NETWORKMANAGER = "org.freedesktop.NetworkManager";
|
constexpr const char* DBUS_NETWORKMANAGER = "org.freedesktop.NetworkManager";
|
||||||
constexpr const char* DBUS_NETWORKMANAGER_PATH = "/org/freedesktop/NetworkManager";
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
Logger logger("LinuxNetworkWatcherWorker");
|
Logger logger("LinuxNetworkWatcherWorker");
|
||||||
@@ -87,7 +73,7 @@ void LinuxNetworkWatcherWorker::initialize() {
|
|||||||
// documentation:
|
// documentation:
|
||||||
// https://developer.gnome.org/NetworkManager/stable/gdbus-org.freedesktop.NetworkManager.html
|
// https://developer.gnome.org/NetworkManager/stable/gdbus-org.freedesktop.NetworkManager.html
|
||||||
|
|
||||||
QDBusInterface nm(DBUS_NETWORKMANAGER, DBUS_NETWORKMANAGER_PATH,
|
QDBusInterface nm(DBUS_NETWORKMANAGER, "/org/freedesktop/NetworkManager",
|
||||||
DBUS_NETWORKMANAGER, QDBusConnection::systemBus());
|
DBUS_NETWORKMANAGER, QDBusConnection::systemBus());
|
||||||
if (!nm.isValid()) {
|
if (!nm.isValid()) {
|
||||||
logger.error()
|
logger.error()
|
||||||
@@ -122,12 +108,6 @@ void LinuxNetworkWatcherWorker::initialize() {
|
|||||||
SLOT(propertyChanged(QString, QVariantMap, QStringList)));
|
SLOT(propertyChanged(QString, QVariantMap, QStringList)));
|
||||||
}
|
}
|
||||||
|
|
||||||
QDBusConnection::systemBus().connect(DBUS_NETWORKMANAGER,
|
|
||||||
DBUS_NETWORKMANAGER_PATH,
|
|
||||||
DBUS_NETWORKMANAGER,
|
|
||||||
"StateChanged",
|
|
||||||
this, SLOT(NMStateChanged(quint32)));
|
|
||||||
|
|
||||||
if (m_devicePaths.isEmpty()) {
|
if (m_devicePaths.isEmpty()) {
|
||||||
logger.warning() << "No wifi devices found";
|
logger.warning() << "No wifi devices found";
|
||||||
return;
|
return;
|
||||||
@@ -193,16 +173,5 @@ void LinuxNetworkWatcherWorker::checkDevices() {
|
|||||||
emit unsecuredNetwork(ssid, bssid);
|
emit unsecuredNetwork(ssid, bssid);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LinuxNetworkWatcherWorker::NMStateChanged(quint32 state)
|
|
||||||
{
|
|
||||||
if (state == NM_STATE_ASLEEP) {
|
|
||||||
emit sleepMode();
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug() << "NMStateChanged " << state;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ class LinuxNetworkWatcherWorker final : public QObject {
|
|||||||
|
|
||||||
signals:
|
signals:
|
||||||
void unsecuredNetwork(const QString& networkName, const QString& networkId);
|
void unsecuredNetwork(const QString& networkName, const QString& networkId);
|
||||||
void sleepMode();
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void initialize();
|
void initialize();
|
||||||
@@ -31,7 +30,6 @@ class LinuxNetworkWatcherWorker final : public QObject {
|
|||||||
private slots:
|
private slots:
|
||||||
void propertyChanged(QString interface, QVariantMap properties,
|
void propertyChanged(QString interface, QVariantMap properties,
|
||||||
QStringList list);
|
QStringList list);
|
||||||
void NMStateChanged(quint32 state);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// We collect the list of DBus wifi network device paths during the
|
// We collect the list of DBus wifi network device paths during the
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user