Compare commits
325 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 47832e888c | |||
| 650c1c6ebb | |||
| 8dbded1624 | |||
| cebfcc846e | |||
| 4c18ceaa50 | |||
| ebe3a5dac6 | |||
| 92deee5f67 | |||
| a75bd0cf5e | |||
| 46f5b3894b | |||
| 493ee22883 | |||
| ad14847eb5 | |||
| cd50e0b8a5 | |||
| 78f504e35c | |||
| bf3d11e5c4 | |||
| 9a0222aee3 | |||
| f0f0f7c5be | |||
| 36b1a863bf | |||
| 4103c5bbcf | |||
| fa69da6d56 | |||
| aaf2c9ddeb | |||
| dbbc7119ec | |||
| c57162c4cc | |||
| 40e39895c9 | |||
| ec3ab2a03c | |||
| ddecfcad26 | |||
| 67bd880cdf | |||
| 477afb9d85 | |||
| f969fcdbb8 | |||
| b0ca16d861 | |||
| 9963359948 | |||
| ca639d293d | |||
| 83d045af64 | |||
| aea8ff4961 | |||
| 1892db4375 | |||
| c86a641e05 | |||
| befb2bf19a | |||
| 7ad6bc340c | |||
| 9164e38c34 | |||
| 8f7559f01b | |||
| af56200735 | |||
| 3874050fae | |||
| 3087163e34 | |||
| 1fa152845c | |||
| 50e23ef233 | |||
| ea648466de | |||
| b782775016 | |||
| 89a7fe1081 | |||
| e8bb096025 | |||
| fd5c7c8322 | |||
| e798d0f503 | |||
| bbb0abb596 | |||
| 0925aec86a | |||
| b084c4c284 | |||
| 87288ebccd | |||
| fcd7eadf4c | |||
| 0373338fb7 | |||
| 42f070fe9d | |||
| 02be6dc5f9 | |||
| bfcf7f0305 | |||
| 2bce595ade | |||
| cd1e561fd4 | |||
| 9bd1e6a0f5 | |||
| 5058c9aa6f | |||
| d78416835c | |||
| 40e6c6aae3 | |||
| 911a999c64 | |||
| b4f4184aa6 | |||
| 5c6db4b7a4 | |||
| f6277cdbb2 | |||
| 99312e61d3 | |||
| 9f0ae75a2f | |||
| 7960d8015d | |||
| 5dcc64e5e5 | |||
| 964436ad43 | |||
| 4fc3900fd5 | |||
| 8f5e42dd61 | |||
| 24895752c1 | |||
| 87eccfb4ca | |||
| a983d0504e | |||
| d0b8535395 | |||
| f84480cf56 | |||
| de7a026ec1 | |||
| a128c7d247 | |||
| f316f0e25a | |||
| ea5242e29b | |||
| b31a62c55f | |||
| 02e3107a23 | |||
| 1862850108 | |||
| f73792844c | |||
| a7199ca6f5 | |||
| 5e757cdd3b | |||
| 92af1f3268 | |||
| aad9d6dae2 | |||
| 423fe3fd4f | |||
| b591dd7445 | |||
| a45bb5ea4f | |||
| d859b111ca | |||
| 52031efc48 | |||
| d78202c612 | |||
| 6bac948633 | |||
| a4c4ef71fb | |||
| 127f85f4f0 | |||
| 13d4ddd292 | |||
| 7265e09c85 | |||
| 2e629b6dac | |||
| 92aba49705 | |||
| bec06b3a5e | |||
| 91cd9474ea | |||
| 6178b05643 | |||
| 46ce22b85c | |||
| 36edafb985 | |||
| d77eaba500 | |||
| 6a3d43fbb0 | |||
| 4975955bbe | |||
| 8f508783e3 | |||
| f50817c43c | |||
| 54f67b3d82 | |||
| d669adb707 | |||
| 5103bc640e | |||
| 3e6f0c0342 | |||
| 40950b92ee | |||
| ac77b4ee75 | |||
| fbf652f818 | |||
| bbbf4891e6 | |||
| 20d005d66c | |||
| c81ae2b060 | |||
| 105c42db1c | |||
| 89818ff63d | |||
| 414c422177 | |||
| b39ac8556c | |||
| 5e1742262d | |||
| 5a07a1274f | |||
| 7b8ff1fd6e | |||
| c7221832e0 | |||
| eb7d031c7d | |||
| 3b3a0aaceb | |||
| 01ec79b7d5 | |||
| 3d6339e2dd | |||
| b4d78d865a | |||
| b53cdcff08 | |||
| 3cc18c5807 | |||
| 5fdce1e49e | |||
| 2ee61a040b | |||
| 741b5cc0f9 | |||
| aaf0e070dc | |||
| e0e126eda8 | |||
| 236daf6b3b | |||
| f1481b1b1f | |||
| f6e7d3ccf1 | |||
| a754a11913 | |||
| 4d25e3b6f6 | |||
| 1fac280497 | |||
| c886c5e6a7 | |||
| cd7f78b9ca | |||
| a587d3230f | |||
| 93e7b45136 | |||
| e024f71ce1 | |||
| 50d1be7b4a | |||
| 3ec6d8973b | |||
| 3ea47d31a9 | |||
| 30c8cc4548 | |||
| 98586d2dd9 | |||
| c66d8ecca0 | |||
| db535f7e7d | |||
| 89f30d8c31 | |||
| 8bce432824 | |||
| f3539b2632 | |||
| 7a96c212f3 | |||
| 2d5dc54e0f | |||
| cef4c262e9 | |||
| 34309261a8 | |||
| 657eeb40c7 | |||
| b4938c2cc9 | |||
| 524fefc5cb | |||
| 73f13404bb | |||
| 5fc68cca83 | |||
| fcb7b8fa8d | |||
| a81e32ff95 | |||
| c897052107 | |||
| 4d0efc7ea5 | |||
| a77842c9e3 | |||
| 0ded9db780 | |||
| 58d480fcb5 | |||
| 7154428d26 | |||
| 02a52d0169 | |||
| ec60764072 | |||
| 17d2fa5532 | |||
| 3ca8b534e8 | |||
| e88f7c5e46 | |||
| 3ac5d7bd1f | |||
| 19cad00a00 | |||
| 1ea716a163 | |||
| 4551659c2a | |||
| c568bf8c24 | |||
| a412d91105 | |||
| ad01f23bbe | |||
| 656070b132 | |||
| c907f5ca36 | |||
| 94a13b2b54 | |||
| 169f11d9c7 | |||
| 816dc3af95 | |||
| b802863de5 | |||
| 8dc2a4b76c | |||
| beb1c6dbf2 | |||
| 3eb06916c7 | |||
| 30d0f84a4f | |||
| 251f2aa5db | |||
| 16d92ddb7c | |||
| e9d4fd8482 | |||
| 9fdcf5ab13 | |||
| a6e6de33c8 | |||
| 53c7fd4d81 | |||
| 2608ea4367 | |||
| d20ed4ad01 | |||
| eae2936449 | |||
| da8ad1f6ba | |||
| 5472347969 | |||
| a43f7a6926 | |||
| 47f917de0b | |||
| dbeb7edd7a | |||
| 6cede712f5 | |||
| d328739192 | |||
| d15c0bd962 | |||
| d53c794936 | |||
| e5dcb25a4a | |||
| f9002b4f43 | |||
| 0531508a75 | |||
| 174e85a20a | |||
| e9abb6f1e2 | |||
| 5be44f9596 | |||
| 90efaaff92 | |||
| 99b554e7c3 | |||
| ac0ce8a6f6 | |||
| 9f9da885b7 | |||
| f51fd2bf3e | |||
| c8378fd32d | |||
| d767214f10 | |||
| e027c504ae | |||
| 669a95d975 | |||
| a96df5d518 | |||
| c5c81735a0 | |||
| c933745707 | |||
| 6710fd18b3 | |||
| 1b78a71529 | |||
| 1909d3c94e | |||
| 10a107716c | |||
| 5445e6637b | |||
| 2380cd5cfb | |||
| 42661618dc | |||
| 8a7e901d7a | |||
| f8bea71716 | |||
| efcc0b7efc | |||
| 4d17e913b5 | |||
| b341934863 | |||
| 127f8ed3bb | |||
| 9dca80de18 | |||
| b0a6bcc055 | |||
| f0626e2eca | |||
| 979ab42c5a | |||
| e152e84ddc | |||
| 2605978889 | |||
| a2d30efaab | |||
| d3715d00ae | |||
| c37662dbe2 | |||
| 768ca1e73d | |||
| a20516850c | |||
| 7a203868ec | |||
| 43c3ce9a6e | |||
| 369e08844f | |||
| 48a5452a65 | |||
| c2f9340db6 | |||
| a6508e642a | |||
| a3e73797c2 | |||
| df7bf204ea | |||
| e16243ff55 | |||
| e23cbe67ad | |||
| 7702f2f74c | |||
| b457ef9a3f | |||
| a28ed6a977 | |||
| 0c73682cfc | |||
| 7e380b6cfb | |||
| 63b5257986 | |||
| acc4485e81 | |||
| 2c44999a31 | |||
| e59a48f9f4 | |||
| b86356b0cc | |||
| f6d7552b58 | |||
| 5bd88ac2e9 | |||
| 94fa5b59f3 | |||
| 7169480999 | |||
| c44ce0d77c | |||
| 7fd71a8408 | |||
| 68db721089 | |||
| a180e12bdf | |||
| f3a4a1b1be | |||
| 6977a8ecbc | |||
| d00f64e6ad | |||
| d5b3da6ba3 | |||
| c245318339 | |||
| b3b0fec2e1 | |||
| 9d571a4c71 | |||
| f283858490 | |||
| 76fe203767 | |||
| b9a47f2f50 | |||
| 27cb17c640 | |||
| ef8fb89eb3 | |||
| f1b045f8a8 | |||
| 050066132b | |||
| 2a6e6a1e24 | |||
| 92689d084c | |||
| 00f314039d | |||
| fcb75e837d | |||
| 9fbea76b74 | |||
| b3ff120bcf | |||
| 9dea98f020 | |||
| c4701d4e7a | |||
| 48903ca3a1 | |||
| 0c9fd4aef4 | |||
| b2af2e46ac | |||
| efc76a0683 | |||
| c4a553c166 | |||
| 69a00b0252 | |||
| 4257c08b43 | |||
| c9e5b92f79 | |||
| 99818c2ad8 |
@@ -10,16 +10,19 @@ env:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
Build-Linux-Ubuntu:
|
Build-Linux-Ubuntu:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: android-runner
|
||||||
|
|
||||||
env:
|
env:
|
||||||
QT_VERSION: 6.6.2
|
QT_VERSION: 6.10.1
|
||||||
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 }}
|
||||||
|
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
|
||||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||||
|
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||||
|
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Install Qt'
|
- name: 'Install Qt'
|
||||||
@@ -28,13 +31,15 @@ jobs:
|
|||||||
version: ${{ env.QT_VERSION }}
|
version: ${{ env.QT_VERSION }}
|
||||||
host: 'linux'
|
host: 'linux'
|
||||||
target: 'desktop'
|
target: 'desktop'
|
||||||
arch: 'gcc_64'
|
arch: 'linux_gcc_64'
|
||||||
modules: 'qtremoteobjects qt5compat qtshadertools'
|
modules: 'qtremoteobjects qt5compat qtshadertools'
|
||||||
dir: ${{ runner.temp }}
|
dir: ${{ runner.temp }}
|
||||||
setup-python: 'true'
|
setup-python: 'true'
|
||||||
tools: 'tools_ifw'
|
tools: 'tools_ifw'
|
||||||
set-env: 'true'
|
set-env: 'true'
|
||||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
aqtversion: '==3.3.0'
|
||||||
|
py7zrversion: '==0.22.*'
|
||||||
|
extra: '--base ${{ env.QT_MIRROR }}'
|
||||||
|
|
||||||
- name: 'Get sources'
|
- name: 'Get sources'
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -42,24 +47,31 @@ jobs:
|
|||||||
submodules: 'true'
|
submodules: 'true'
|
||||||
fetch-depth: 10
|
fetch-depth: 10
|
||||||
|
|
||||||
- name: 'Setup ccache'
|
- name: 'Get version from CMakeLists.txt'
|
||||||
uses: hendrikmuhs/ccache-action@v1.2
|
id: get_version
|
||||||
|
run: |
|
||||||
|
VERSION=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\)/\1/')
|
||||||
|
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||||
|
echo "Version: $VERSION"
|
||||||
|
|
||||||
|
# - name: 'Setup ccache'
|
||||||
|
# uses: hendrikmuhs/ccache-action@v1.2
|
||||||
|
|
||||||
- name: 'Build project'
|
- name: 'Build project'
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get install libxkbcommon-x11-0
|
sudo apt-get install libxkbcommon-x11-0 libsecret-1-dev
|
||||||
export QT_BIN_DIR=${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/gcc_64/bin
|
export QT_BIN_DIR=${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/gcc_64/bin
|
||||||
export QIF_BIN_DIR=${{ runner.temp }}/Qt/Tools/QtInstallerFramework/${{ env.QIF_VERSION }}/bin
|
export QIF_BIN_DIR=${{ runner.temp }}/Qt/Tools/QtInstallerFramework/${{ env.QIF_VERSION }}/bin
|
||||||
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
|
run: cd deploy && tar -cf AmneziaVPN_Linux_Installer.tar AmneziaVPN_Linux_Installer.bin && zip AmneziaVPN_${VERSION}_linux_x64.tar.zip AmneziaVPN_Linux_Installer.tar
|
||||||
|
|
||||||
- name: 'Upload installer artifact'
|
- name: 'Upload installer artifact'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: AmneziaVPN_Linux_installer.tar
|
name: AmneziaVPN_${{ env.VERSION }}_linux_x64.tar.zip
|
||||||
path: deploy/AmneziaVPN_Linux_Installer.tar
|
path: deploy/AmneziaVPN_${{ env.VERSION }}_linux_x64.tar.zip
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|
||||||
- name: 'Upload unpacked artifact'
|
- name: 'Upload unpacked artifact'
|
||||||
@@ -82,14 +94,17 @@ jobs:
|
|||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
|
|
||||||
env:
|
env:
|
||||||
QT_VERSION: 6.6.2
|
QT_VERSION: 6.10.1
|
||||||
QIF_VERSION: 4.7
|
QIF_VERSION: 4.7
|
||||||
BUILD_ARCH: 64
|
BUILD_ARCH: 64
|
||||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||||
|
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
|
||||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||||
|
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||||
|
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Get sources'
|
- name: 'Get sources'
|
||||||
@@ -98,8 +113,16 @@ jobs:
|
|||||||
submodules: 'true'
|
submodules: 'true'
|
||||||
fetch-depth: 10
|
fetch-depth: 10
|
||||||
|
|
||||||
- name: 'Setup ccache'
|
- name: 'Get version from CMakeLists.txt'
|
||||||
uses: hendrikmuhs/ccache-action@v1.2
|
id: get_version
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
VERSION=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\)/\1/')
|
||||||
|
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||||
|
echo "Version: $VERSION"
|
||||||
|
|
||||||
|
# - name: 'Setup ccache'
|
||||||
|
# uses: hendrikmuhs/ccache-action@v1.2
|
||||||
|
|
||||||
- name: 'Install Qt'
|
- name: 'Install Qt'
|
||||||
uses: jurplel/install-qt-action@v3
|
uses: jurplel/install-qt-action@v3
|
||||||
@@ -107,32 +130,62 @@ jobs:
|
|||||||
version: ${{ env.QT_VERSION }}
|
version: ${{ env.QT_VERSION }}
|
||||||
host: 'windows'
|
host: 'windows'
|
||||||
target: 'desktop'
|
target: 'desktop'
|
||||||
arch: 'win64_msvc2019_64'
|
arch: 'win64_msvc2022_64'
|
||||||
modules: 'qtremoteobjects qt5compat qtshadertools'
|
modules: 'qtremoteobjects qt5compat qtshadertools'
|
||||||
dir: ${{ runner.temp }}
|
dir: ${{ runner.temp }}
|
||||||
setup-python: 'true'
|
setup-python: 'true'
|
||||||
tools: 'tools_ifw'
|
tools: 'tools_ifw'
|
||||||
set-env: 'true'
|
set-env: 'true'
|
||||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
aqtversion: '==3.3.0'
|
||||||
|
py7zrversion: '==0.22.*'
|
||||||
|
extra: '--base ${{ env.QT_MIRROR }}'
|
||||||
|
|
||||||
- name: 'Setup mvsc'
|
- name: 'Setup mvsc'
|
||||||
uses: ilammy/msvc-dev-cmd@v1
|
uses: ilammy/msvc-dev-cmd@v1
|
||||||
with:
|
with:
|
||||||
arch: 'x64'
|
arch: 'x64'
|
||||||
|
|
||||||
|
- name: 'Setup .NET SDK'
|
||||||
|
uses: actions/setup-dotnet@v4
|
||||||
|
with:
|
||||||
|
dotnet-version: '8.0.x'
|
||||||
|
|
||||||
|
- name: 'Install WiX Toolset'
|
||||||
|
shell: powershell
|
||||||
|
run: |
|
||||||
|
dotnet tool install --global wix --version 4.0.6
|
||||||
|
wix extension add -g WixToolset.UI.wixext/4.0.6
|
||||||
|
wix extension add -g WixToolset.Util.wixext/4.0.6
|
||||||
|
wix extension list -g
|
||||||
|
$wixBinDir = Join-Path $env:USERPROFILE ".dotnet\tools"
|
||||||
|
echo "WIX_BIN_DIR=$wixBinDir" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||||
|
|
||||||
- name: 'Build project'
|
- name: 'Build project'
|
||||||
shell: cmd
|
shell: cmd
|
||||||
run: |
|
run: |
|
||||||
set BUILD_ARCH=${{ env.BUILD_ARCH }}
|
set BUILD_ARCH=${{ env.BUILD_ARCH }}
|
||||||
set QT_BIN_DIR="${{ runner.temp }}\\Qt\\${{ env.QT_VERSION }}\\msvc2019_64\\bin"
|
set QT_BIN_DIR="${{ runner.temp }}\\Qt\\${{ env.QT_VERSION }}\\msvc2022_64\\bin"
|
||||||
set QIF_BIN_DIR="${{ runner.temp }}\\Qt\\Tools\\QtInstallerFramework\\${{ env.QIF_VERSION }}\\bin"
|
set QIF_BIN_DIR="${{ runner.temp }}\\Qt\\Tools\\QtInstallerFramework\\${{ env.QIF_VERSION }}\\bin"
|
||||||
|
set WIX_BIN_DIR=%USERPROFILE%\.dotnet\tools
|
||||||
call deploy\\build_windows.bat
|
call deploy\\build_windows.bat
|
||||||
|
|
||||||
|
- name: 'Rename Windows installer'
|
||||||
|
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_Windows_installer
|
name: AmneziaVPN_${{ env.VERSION }}_x64.exe
|
||||||
path: AmneziaVPN_x${{ env.BUILD_ARCH }}.exe
|
path: AmneziaVPN_${{ env.VERSION }}_x64.exe
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
- name: 'Upload MSI installer artifact'
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: AmneziaVPN_Windows_MSI_installer
|
||||||
|
path: AmneziaVPN_x${{ env.BUILD_ARCH }}.msi
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|
||||||
- name: 'Upload unpacked artifact'
|
- name: 'Upload unpacked artifact'
|
||||||
@@ -145,23 +198,26 @@ jobs:
|
|||||||
# ------------------------------------------------------
|
# ------------------------------------------------------
|
||||||
|
|
||||||
Build-iOS:
|
Build-iOS:
|
||||||
runs-on: macos-13
|
runs-on: macos-latest
|
||||||
|
|
||||||
env:
|
env:
|
||||||
QT_VERSION: 6.6.2
|
QT_VERSION: 6.10.1
|
||||||
CC: cc
|
CC: cc
|
||||||
CXX: c++
|
CXX: c++
|
||||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||||
|
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
|
||||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||||
|
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||||
|
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Setup xcode'
|
- name: 'Setup xcode'
|
||||||
uses: maxim-lobanov/setup-xcode@v1
|
uses: maxim-lobanov/setup-xcode@v1
|
||||||
with:
|
with:
|
||||||
xcode-version: '15.2'
|
xcode-version: '26.1'
|
||||||
|
|
||||||
- name: 'Install desktop Qt'
|
- name: 'Install desktop Qt'
|
||||||
uses: jurplel/install-qt-action@v3
|
uses: jurplel/install-qt-action@v3
|
||||||
@@ -190,7 +246,7 @@ jobs:
|
|||||||
- name: 'Install go'
|
- name: 'Install go'
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: '1.22.1'
|
go-version: '1.24'
|
||||||
cache: false
|
cache: false
|
||||||
|
|
||||||
- name: 'Setup gomobile'
|
- name: 'Setup gomobile'
|
||||||
@@ -205,8 +261,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
|
||||||
@@ -243,18 +299,34 @@ jobs:
|
|||||||
|
|
||||||
# ------------------------------------------------------
|
# ------------------------------------------------------
|
||||||
|
|
||||||
Build-MacOS:
|
Build-MacOS-old:
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
|
|
||||||
env:
|
env:
|
||||||
# Keep compat with MacOS 10.15 aka Catalina by Qt 6.4
|
# Keep compat with MacOS 10.15 aka Catalina by Qt 6.4
|
||||||
QT_VERSION: 6.4.3
|
QT_VERSION: 6.4.3
|
||||||
QIF_VERSION: 4.6
|
|
||||||
|
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
|
||||||
|
|
||||||
|
MAC_APP_CERT_CERT: ${{ secrets.MAC_APP_CERT_CERT }}
|
||||||
|
MAC_SIGNER_ID: ${{ secrets.MAC_SIGNER_ID }}
|
||||||
|
MAC_APP_CERT_PW: ${{ secrets.MAC_APP_CERT_PW }}
|
||||||
|
|
||||||
|
MAC_INSTALLER_SIGNER_CERT: ${{ secrets.MAC_INSTALLER_SIGNER_CERT }}
|
||||||
|
MAC_INSTALLER_SIGNER_ID: ${{ secrets.MAC_INSTALLER_SIGNER_ID }}
|
||||||
|
MAC_INSTALL_CERT_PW: ${{ secrets.MAC_INSTALL_CERT_PW }}
|
||||||
|
|
||||||
|
APPLE_DEV_EMAIL: ${{ secrets.APPLE_DEV_EMAIL }}
|
||||||
|
APPLE_DEV_PASSWORD: ${{ secrets.APPLE_DEV_PASSWORD }}
|
||||||
|
|
||||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||||
|
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
|
||||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||||
|
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||||
|
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Setup xcode'
|
- name: 'Setup xcode'
|
||||||
@@ -275,11 +347,6 @@ jobs:
|
|||||||
set-env: 'true'
|
set-env: 'true'
|
||||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||||
|
|
||||||
- name: 'Install Qt Installer Framework ${{ env.QIF_VERSION }}'
|
|
||||||
run: |
|
|
||||||
mkdir -pv ${{ runner.temp }}/Qt/Tools/QtInstallerFramework
|
|
||||||
wget https://qt.amzsvc.com/tools/ifw/${{ env.QIF_VERSION }}.zip
|
|
||||||
unzip ${{ env.QIF_VERSION }}.zip -d ${{ runner.temp }}/Qt/Tools/QtInstallerFramework/
|
|
||||||
|
|
||||||
- name: 'Get sources'
|
- name: 'Get sources'
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -287,22 +354,185 @@ 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: |
|
||||||
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin"
|
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin"
|
||||||
export QIF_BIN_DIR="${{ runner.temp }}/Qt/Tools/QtInstallerFramework/${{ env.QIF_VERSION }}/bin"
|
bash deploy/build_macos.sh -n
|
||||||
bash deploy/build_macos.sh
|
|
||||||
|
|
||||||
- name: 'Upload installer artifact'
|
- name: 'Upload installer artifact'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: AmneziaVPN_MacOS_installer
|
name: AmneziaVPN_MacOS_old_installer
|
||||||
path: AmneziaVPN.dmg
|
path: deploy/build/pkg/AmneziaVPN.pkg
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|
||||||
|
- name: 'Upload unpacked artifact'
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: AmneziaVPN_MacOS_old_unpacked
|
||||||
|
path: deploy/build/client/AmneziaVPN.app
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
# ------------------------------------------------------
|
||||||
|
|
||||||
|
Build-MacOS:
|
||||||
|
runs-on: macos-latest
|
||||||
|
|
||||||
|
env:
|
||||||
|
QT_VERSION: 6.10.1
|
||||||
|
|
||||||
|
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
|
||||||
|
|
||||||
|
MAC_APP_CERT_CERT: ${{ secrets.MAC_APP_CERT_CERT }}
|
||||||
|
MAC_SIGNER_ID: ${{ secrets.MAC_SIGNER_ID }}
|
||||||
|
MAC_APP_CERT_PW: ${{ secrets.MAC_APP_CERT_PW }}
|
||||||
|
|
||||||
|
MAC_INSTALLER_SIGNER_CERT: ${{ secrets.MAC_INSTALLER_SIGNER_CERT }}
|
||||||
|
MAC_INSTALLER_SIGNER_ID: ${{ secrets.MAC_INSTALLER_SIGNER_ID }}
|
||||||
|
MAC_INSTALL_CERT_PW: ${{ secrets.MAC_INSTALL_CERT_PW }}
|
||||||
|
|
||||||
|
APPLE_DEV_EMAIL: ${{ secrets.APPLE_DEV_EMAIL }}
|
||||||
|
APPLE_DEV_PASSWORD: ${{ secrets.APPLE_DEV_PASSWORD }}
|
||||||
|
|
||||||
|
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||||
|
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||||
|
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
|
||||||
|
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||||
|
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||||
|
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||||
|
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||||
|
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: 'Setup xcode'
|
||||||
|
uses: maxim-lobanov/setup-xcode@v1
|
||||||
|
with:
|
||||||
|
xcode-version: '16.2.0'
|
||||||
|
|
||||||
|
- name: 'Install Qt'
|
||||||
|
uses: jurplel/install-qt-action@v4
|
||||||
|
with:
|
||||||
|
version: ${{ env.QT_VERSION }}
|
||||||
|
host: 'mac'
|
||||||
|
target: 'desktop'
|
||||||
|
arch: 'clang_64'
|
||||||
|
modules: 'qtremoteobjects qt5compat qtshadertools'
|
||||||
|
dir: ${{ runner.temp }}
|
||||||
|
setup-python: 'true'
|
||||||
|
set-env: 'true'
|
||||||
|
aqtversion: '==3.3.0'
|
||||||
|
py7zrversion: '==0.22.*'
|
||||||
|
extra: '--base ${{ env.QT_MIRROR }}'
|
||||||
|
|
||||||
|
- name: 'Get sources'
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: 'true'
|
||||||
|
fetch-depth: 10
|
||||||
|
|
||||||
|
- name: 'Get version from CMakeLists.txt'
|
||||||
|
id: get_version
|
||||||
|
run: |
|
||||||
|
VERSION=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\)/\1/')
|
||||||
|
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||||
|
echo "Version: $VERSION"
|
||||||
|
|
||||||
|
# - name: 'Setup ccache'
|
||||||
|
# uses: hendrikmuhs/ccache-action@v1.2
|
||||||
|
|
||||||
|
- name: 'Build project'
|
||||||
|
run: |
|
||||||
|
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin"
|
||||||
|
bash deploy/build_macos.sh -n
|
||||||
|
|
||||||
|
- name: 'Pack macOS installer'
|
||||||
|
run: |
|
||||||
|
cd deploy/build/pkg
|
||||||
|
zip -r ../../AmneziaVPN_${VERSION}_macos.zip AmneziaVPN.pkg
|
||||||
|
cd ../../..
|
||||||
|
|
||||||
|
- name: 'Upload installer artifact'
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: AmneziaVPN_${{ env.VERSION }}_macos.zip
|
||||||
|
path: deploy/AmneziaVPN_${{ env.VERSION }}_macos.zip
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
- name: 'Upload unpacked artifact'
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: AmneziaVPN_MacOS_unpacked
|
||||||
|
path: deploy/build/client/AmneziaVPN.app
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
Build-MacOS-NE:
|
||||||
|
runs-on: macos-latest
|
||||||
|
|
||||||
|
env:
|
||||||
|
QT_VERSION: 6.10.1
|
||||||
|
|
||||||
|
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
|
||||||
|
|
||||||
|
MAC_APP_CERT_CERT: ${{ secrets.MAC_APP_CERT_CERT }}
|
||||||
|
MAC_SIGNER_ID: ${{ secrets.MAC_SIGNER_ID }}
|
||||||
|
MAC_APP_CERT_PW: ${{ secrets.MAC_APP_CERT_PW }}
|
||||||
|
|
||||||
|
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||||
|
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||||
|
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
|
||||||
|
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||||
|
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||||
|
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||||
|
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||||
|
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: 'Setup xcode'
|
||||||
|
uses: maxim-lobanov/setup-xcode@v1
|
||||||
|
with:
|
||||||
|
xcode-version: '26.1'
|
||||||
|
|
||||||
|
- name: 'Install desktop Qt'
|
||||||
|
uses: jurplel/install-qt-action@v3
|
||||||
|
with:
|
||||||
|
version: ${{ env.QT_VERSION }}
|
||||||
|
host: 'mac'
|
||||||
|
target: 'desktop'
|
||||||
|
modules: 'qtremoteobjects qt5compat qtshadertools qtmultimedia'
|
||||||
|
arch: 'clang_64'
|
||||||
|
dir: ${{ runner.temp }}
|
||||||
|
set-env: 'true'
|
||||||
|
extra: '--base ${{ env.QT_MIRROR }}'
|
||||||
|
|
||||||
|
- name: 'Install go'
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: '1.24'
|
||||||
|
cache: false
|
||||||
|
|
||||||
|
- name: 'Setup gomobile'
|
||||||
|
run: |
|
||||||
|
export PATH=$PATH:~/go/bin
|
||||||
|
go install golang.org/x/mobile/cmd/gomobile@latest
|
||||||
|
gomobile init
|
||||||
|
|
||||||
|
- name: 'Get sources'
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: 'true'
|
||||||
|
fetch-depth: 10
|
||||||
|
|
||||||
|
# - name: 'Setup ccache'
|
||||||
|
# uses: hendrikmuhs/ccache-action@v1.2
|
||||||
|
|
||||||
|
- name: 'Build project'
|
||||||
|
run: |
|
||||||
|
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin"
|
||||||
|
bash deploy/build_macos_ne.sh
|
||||||
|
|
||||||
- name: 'Upload unpacked artifact'
|
- name: 'Upload unpacked artifact'
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
@@ -313,17 +543,20 @@ jobs:
|
|||||||
# ------------------------------------------------------
|
# ------------------------------------------------------
|
||||||
|
|
||||||
Build-Android:
|
Build-Android:
|
||||||
runs-on: ubuntu-latest
|
runs-on: android-runner
|
||||||
|
|
||||||
env:
|
env:
|
||||||
ANDROID_BUILD_PLATFORM: android-34
|
ANDROID_BUILD_PLATFORM: android-36
|
||||||
QT_VERSION: 6.7.3
|
QT_VERSION: 6.10.1
|
||||||
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
||||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||||
|
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
|
||||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||||
|
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||||
|
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Install desktop Qt'
|
- name: 'Install desktop Qt'
|
||||||
@@ -396,15 +629,22 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
submodules: 'true'
|
submodules: 'true'
|
||||||
|
|
||||||
- name: 'Setup ccache'
|
- name: 'Get version from CMakeLists.txt'
|
||||||
uses: hendrikmuhs/ccache-action@v1.2
|
id: get_version
|
||||||
|
run: |
|
||||||
|
VERSION=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\)/\1/')
|
||||||
|
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||||
|
echo "Version: $VERSION"
|
||||||
|
|
||||||
|
# - name: 'Setup ccache'
|
||||||
|
# uses: hendrikmuhs/ccache-action@v1.2
|
||||||
|
|
||||||
- name: 'Setup 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
|
||||||
@@ -429,35 +669,44 @@ 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-android-x86_64
|
name: AmneziaVPN_${{ env.VERSION }}_android9+_x86_64.apk
|
||||||
path: deploy/build/AmneziaVPN-x86_64-release.apk
|
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_x86_64.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-android-x86
|
name: AmneziaVPN_${{ env.VERSION }}_android9+_x86.apk
|
||||||
path: deploy/build/AmneziaVPN-x86-release.apk
|
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_x86.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-android-arm64-v8a
|
name: AmneziaVPN_${{ env.VERSION }}_android9+_arm64-v8a.apk
|
||||||
path: deploy/build/AmneziaVPN-arm64-v8a-release.apk
|
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_arm64-v8a.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-android-armeabi-v7a
|
name: AmneziaVPN_${{ env.VERSION }}_android9+_armeabi-v7a.apk
|
||||||
path: deploy/build/AmneziaVPN-armeabi-v7a-release.apk
|
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_armeabi-v7a.apk
|
||||||
compression-level: 0
|
compression-level: 0
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|
||||||
|
|||||||
@@ -17,9 +17,12 @@ jobs:
|
|||||||
QIF_VERSION: 4.5
|
QIF_VERSION: 4.5
|
||||||
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 }}
|
||||||
|
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
|
||||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||||
|
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||||
|
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Install desktop Qt'
|
- name: 'Install desktop Qt'
|
||||||
|
|||||||
@@ -1,64 +1,41 @@
|
|||||||
name: 'Upload a new version'
|
name: 'Upload a new version'
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
workflow_dispatch:
|
||||||
tags:
|
inputs:
|
||||||
- '[0-9]+.[0-9]+.[0-9]+.[0-9]+'
|
RELEASE_VERSION:
|
||||||
|
description: 'Release version (e.g. 1.2.3.4)'
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
upload:
|
Upload-S3:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: upload
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout CMakeLists.txt
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.ref_name }}
|
ref: ${{ inputs.RELEASE_VERSION }}
|
||||||
sparse-checkout: |
|
sparse-checkout: |
|
||||||
CMakeLists.txt
|
CMakeLists.txt
|
||||||
|
deploy/deploy_s3.sh
|
||||||
sparse-checkout-cone-mode: false
|
sparse-checkout-cone-mode: false
|
||||||
|
|
||||||
- name: Verify git tag
|
- name: Verify git tag
|
||||||
run: |
|
run: |
|
||||||
GIT_TAG=${{ github.ref_name }}
|
TAG_NAME=${{ inputs.RELEASE_VERSION }}
|
||||||
CMAKE_TAG=$(grep 'project.*VERSION' CMakeLists.txt | sed -E 's/.* ([0-9]+.[0-9]+.[0-9]+.[0-9]+)$/\1/')
|
CMAKE_TAG=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+).*/\1/')
|
||||||
|
if [[ "$TAG_NAME" == "$CMAKE_TAG" ]]; then
|
||||||
if [[ "$GIT_TAG" == "$CMAKE_TAG" ]]; then
|
echo "Git tag ($TAG_NAME) matches CMakeLists.txt version ($CMAKE_TAG)."
|
||||||
echo "Git tag ($GIT_TAG) and version in CMakeLists.txt ($CMAKE_TAG) are the same. Continuing..."
|
|
||||||
else
|
else
|
||||||
echo "Git tag ($GIT_TAG) and version in CMakeLists.txt ($CMAKE_TAG) are not the same! Cancelling..."
|
echo "::error::Mismatch: Git tag ($TAG_NAME) != CMakeLists.txt version ($CMAKE_TAG). Exiting with error..."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Download artifacts from the "${{ github.ref_name }}" tag
|
- name: Setup Rclone
|
||||||
uses: robinraju/release-downloader@v1.8
|
uses: AnimMouse/setup-rclone@v1
|
||||||
with:
|
with:
|
||||||
tag: ${{ github.ref_name }}
|
rclone_config: ${{ secrets.RCLONE_CONFIG }}
|
||||||
fileName: "AmneziaVPN_(Linux_|)${{ github.ref_name }}*"
|
|
||||||
out-file-path: ${{ github.ref_name }}
|
|
||||||
|
|
||||||
- name: Upload beta version
|
- name: Send dist to S3
|
||||||
uses: jakejarvis/s3-sync-action@master
|
run: bash deploy/deploy_s3.sh ${{ inputs.RELEASE_VERSION }}
|
||||||
if: contains(github.event.base_ref, 'dev')
|
|
||||||
with:
|
|
||||||
args: --include "AmneziaVPN*" --delete
|
|
||||||
env:
|
|
||||||
AWS_S3_BUCKET: updates
|
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
|
|
||||||
AWS_S3_ENDPOINT: https://${{ vars.CF_ACCOUNT_ID }}.r2.cloudflarestorage.com
|
|
||||||
SOURCE_DIR: ${{ github.ref_name }}
|
|
||||||
DEST_DIR: beta/${{ github.ref_name }}
|
|
||||||
|
|
||||||
- name: Upload stable version
|
|
||||||
uses: jakejarvis/s3-sync-action@master
|
|
||||||
if: contains(github.event.base_ref, 'master')
|
|
||||||
with:
|
|
||||||
args: --include "AmneziaVPN*" --delete
|
|
||||||
env:
|
|
||||||
AWS_S3_BUCKET: updates
|
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
|
|
||||||
AWS_S3_ENDPOINT: https://${{ vars.CF_ACCOUNT_ID }}.r2.cloudflarestorage.com
|
|
||||||
SOURCE_DIR: ${{ github.ref_name }}
|
|
||||||
DEST_DIR: stable/${{ github.ref_name }}
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ deploy/build_32/*
|
|||||||
deploy/build_64/*
|
deploy/build_64/*
|
||||||
winbuild*.bat
|
winbuild*.bat
|
||||||
.cache/
|
.cache/
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
|
||||||
# Qt-es
|
# Qt-es
|
||||||
@@ -133,4 +134,12 @@ client/3rd/ShadowSocks/ss_ios.xcconfig
|
|||||||
out/
|
out/
|
||||||
|
|
||||||
# CMake files
|
# CMake files
|
||||||
CMakeFiles/
|
CMakeFiles/
|
||||||
|
|
||||||
|
ios-ne-build.sh
|
||||||
|
macos-ne-build.sh
|
||||||
|
macos-signed-build.sh
|
||||||
|
macos-with-sign-build.sh
|
||||||
|
DeveloperIdApplicationCertificate.p12
|
||||||
|
DeveloperIdInstallerCertificate.p12
|
||||||
|
|
||||||
|
|||||||
@@ -7,9 +7,14 @@
|
|||||||
[submodule "client/3rd-prebuilt"]
|
[submodule "client/3rd-prebuilt"]
|
||||||
path = client/3rd-prebuilt
|
path = client/3rd-prebuilt
|
||||||
url = https://github.com/amnezia-vpn/3rd-prebuilt
|
url = https://github.com/amnezia-vpn/3rd-prebuilt
|
||||||
|
branch = feature/special-handshake
|
||||||
[submodule "client/3rd/amneziawg-apple"]
|
[submodule "client/3rd/amneziawg-apple"]
|
||||||
path = client/3rd/amneziawg-apple
|
path = client/3rd/amneziawg-apple
|
||||||
url = https://github.com/amnezia-vpn/amneziawg-apple
|
url = https://github.com/amnezia-vpn/amneziawg-apple
|
||||||
[submodule "client/3rd/QSimpleCrypto"]
|
[submodule "client/3rd/QSimpleCrypto"]
|
||||||
path = client/3rd/QSimpleCrypto
|
path = client/3rd/QSimpleCrypto
|
||||||
url = https://github.com/amnezia-vpn/QSimpleCrypto.git
|
url = https://github.com/amnezia-vpn/QSimpleCrypto.git
|
||||||
|
[submodule "client/3rd/qtgamepad"]
|
||||||
|
path = client/3rd/qtgamepad
|
||||||
|
url = https://github.com/amnezia-vpn/qtgamepad.git
|
||||||
|
branch = 6.6
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
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.15.2)
|
||||||
|
|
||||||
project(${PROJECT} VERSION 4.8.4.3
|
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
|
||||||
DESCRIPTION "AmneziaVPN"
|
DESCRIPTION "AmneziaVPN"
|
||||||
HOMEPAGE_URL "https://amnezia.org/"
|
HOMEPAGE_URL "https://amnezia.org/"
|
||||||
)
|
)
|
||||||
@@ -11,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 2080)
|
set(APP_ANDROID_VERSION_CODE 2119)
|
||||||
|
|
||||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
set(MZ_PLATFORM_NAME "linux")
|
set(MZ_PLATFORM_NAME "linux")
|
||||||
@@ -31,14 +32,56 @@ set(QT_BUILD_TOOLS_WHEN_CROSS_COMPILING ON)
|
|||||||
set(CMAKE_CXX_STANDARD 17)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
if(APPLE AND NOT IOS)
|
if(APPLE)
|
||||||
set(CMAKE_OSX_ARCHITECTURES "x86_64")
|
if(IOS)
|
||||||
|
set(CMAKE_OSX_ARCHITECTURES "arm64")
|
||||||
|
elseif(MACOS_NE)
|
||||||
|
set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64")
|
||||||
|
else()
|
||||||
|
set(CMAKE_OSX_ARCHITECTURES "x86_64")
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
add_subdirectory(client)
|
add_subdirectory(client)
|
||||||
|
|
||||||
if(NOT IOS AND NOT ANDROID)
|
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
|
||||||
add_subdirectory(service)
|
add_subdirectory(service)
|
||||||
|
|
||||||
include(${CMAKE_SOURCE_DIR}/deploy/installer/config.cmake)
|
include(${CMAKE_SOURCE_DIR}/deploy/installer/config.cmake)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
set(AMNEZIA_STAGE_DIR "${CMAKE_BINARY_DIR}/stage")
|
||||||
|
|
||||||
|
if(WIN32 AND NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
|
||||||
|
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(AMNEZIA_LICENSE_TXT "${CMAKE_BINARY_DIR}/LICENSE.txt")
|
||||||
|
configure_file("${CMAKE_SOURCE_DIR}/LICENSE" "${AMNEZIA_LICENSE_TXT}" COPYONLY)
|
||||||
|
set(CPACK_RESOURCE_FILE_LICENSE "${AMNEZIA_LICENSE_TXT}")
|
||||||
|
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()
|
||||||
|
|||||||
@@ -9,17 +9,17 @@
|
|||||||
### [English]([https://github.com/amnezia-vpn/amnezia-client/blob/dev/README_RU.md](https://github.com/amnezia-vpn/amnezia-client/tree/dev?tab=readme-ov-file#)) | [Русский](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README_RU.md)
|
### [English]([https://github.com/amnezia-vpn/amnezia-client/blob/dev/README_RU.md](https://github.com/amnezia-vpn/amnezia-client/tree/dev?tab=readme-ov-file#)) | [Русский](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README_RU.md)
|
||||||
|
|
||||||
|
|
||||||
[Amnezia](https://amnezia.org) is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
|
[Amnezia](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en) is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
|
||||||
|
|
||||||
[](https://amnezia.org)
|
[](https://amnezia.org)
|
||||||
|
|
||||||
### [Website](https://amnezia.org) | [Alt website link](https://storage.googleapis.com/amnezia/amnezia.org) | [Documentation](https://docs.amnezia.org) | [Troubleshooting](https://docs.amnezia.org/troubleshooting)
|
### [Website](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en) | [Alt website link](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en-mirror) | [Documentation](https://docs.amnezia.org) | [Troubleshooting](https://docs.amnezia.org/troubleshooting)
|
||||||
|
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
> If the [Amnezia website](https://amnezia.org) is blocked in your region, you can use an [Alternative website link](https://storage.googleapis.com/amnezia/amnezia.org ).
|
> If the [Amnezia website](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en) is blocked in your region, you can use an [Alternative website link](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en-mirror).
|
||||||
|
|
||||||
<a href="https://amnezia.org/downloads"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
|
<a href="https://amnezia.org/en/downloads?utm_source=github&utm_campaign=amnezia_button-readme-en"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
|
||||||
<a href="https://storage.googleapis.com/amnezia/q9p19109"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-alt.svg" width="150" style="max-width: 100%;"></a>
|
<a href="https://storage.googleapis.com/amnezia/amnezia.org?m-path=/en/downloads&utm_source=github&utm_campaign=amnezia_button-readme-en-mirrow"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-alt.svg" width="150" style="max-width: 100%;"></a>
|
||||||
|
|
||||||
[All releases](https://github.com/amnezia-vpn/amnezia-client/releases)
|
[All releases](https://github.com/amnezia-vpn/amnezia-client/releases)
|
||||||
|
|
||||||
@@ -179,7 +179,7 @@ You may face compiling issues in QT Creator after you've worked in Android Studi
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
GPL v3.0
|
This project is licensed under the GNU General Public License v3.0 (see LICENSE) and also includes third-party components distributed under their own terms (see THIRD_PARTY_LICENSES.md).
|
||||||
|
|
||||||
## Donate
|
## Donate
|
||||||
|
|
||||||
|
|||||||
@@ -6,16 +6,16 @@
|
|||||||
[](https://gitpod.io/#https://github.com/amnezia-vpn/amnezia-client)
|
[](https://gitpod.io/#https://github.com/amnezia-vpn/amnezia-client)
|
||||||
|
|
||||||
### [English](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README.md) | Русский
|
### [English](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README.md) | Русский
|
||||||
[AmneziaVPN](https://amnezia.org) — это open sourse VPN-клиент, ключевая особенность которого заключается в возможности развернуть собственный VPN на вашем сервере.
|
[AmneziaVPN](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru) — это open source VPN-клиент, ключевая особенность которого заключается в возможности развернуть собственный VPN на вашем сервере.
|
||||||
|
|
||||||
[](https://amnezia.org)
|
[](https://amnezia.org)
|
||||||
|
|
||||||
### [Сайт](https://amnezia.org) | [Зеркало на сайт](https://storage.googleapis.com/amnezia/amnezia.org) | [Документация](https://docs.amnezia.org) | [Решение проблем](https://docs.amnezia.org/troubleshooting)
|
### [Сайт](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru) | [Зеркало сайта](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru-mirror) | [Документация](https://docs.amnezia.org) | [Решение проблем](https://docs.amnezia.org/troubleshooting)
|
||||||
|
|
||||||
> [!TIP]
|
> [!TIP]
|
||||||
> Если [сайт Amnezia](https://amnezia.org) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/amnezia/amnezia.org).
|
> Если [сайт Amnezia](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru-mirror).
|
||||||
|
|
||||||
<a href="https://storage.googleapis.com/amnezia/q9p19109"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website-ru.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
|
<a href="https://storage.googleapis.com/amnezia/amnezia.org?m-path=/ru/downloads&utm_source=github&utm_campaign=amnezia_button-readme-ru-mirror"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website-ru.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
|
||||||
|
|
||||||
|
|
||||||
[Все релизы](https://github.com/amnezia-vpn/amnezia-client/releases)
|
[Все релизы](https://github.com/amnezia-vpn/amnezia-client/releases)
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
- Классические VPN-протоколы: OpenVPN, WireGuard и IKEv2.
|
- Классические VPN-протоколы: OpenVPN, WireGuard и IKEv2.
|
||||||
- Протоколы с маскировкой трафика (обфускацией): OpenVPN с плагином [Cloak](https://github.com/cbeuw/Cloak), Shadowsocks (OpenVPN over Shadowsocks), [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/) and XRay.
|
- Протоколы с маскировкой трафика (обфускацией): OpenVPN с плагином [Cloak](https://github.com/cbeuw/Cloak), Shadowsocks (OpenVPN over Shadowsocks), [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/) and XRay.
|
||||||
- Поддержка Split Tunneling — добавляйте любые сайты или приложения в список, чтобы включить VPN только для них.
|
- Поддержка Split Tunneling — добавляйте любые сайты или приложения в список, чтобы включить VPN только для них.
|
||||||
- Поддерживает платформы: Windows, MacOS, Linux, Android, iOS.
|
- Поддерживает платформы: Windows, macOS, Linux, Android, iOS.
|
||||||
- Поддержка конфигурации протокола AmneziaWG на [бета-прошивке Keenetic](https://docs.keenetic.com/ua/air/kn-1611/en/6319-latest-development-release.html#UUID-186c4108-5afd-c10b-f38a-cdff6c17fab3_section-idm33192196168192-improved).
|
- Поддержка конфигурации протокола AmneziaWG на [бета-прошивке Keenetic](https://docs.keenetic.com/ua/air/kn-1611/en/6319-latest-development-release.html#UUID-186c4108-5afd-c10b-f38a-cdff6c17fab3_section-idm33192196168192-improved).
|
||||||
|
|
||||||
## Ссылки
|
## Ссылки
|
||||||
@@ -38,10 +38,10 @@
|
|||||||
- [https://amnezia.org](https://amnezia.org) - Веб-сайт проекта | [Альтернативная ссылка (зеркало)](https://storage.googleapis.com/kldscp/amnezia.org)
|
- [https://amnezia.org](https://amnezia.org) - Веб-сайт проекта | [Альтернативная ссылка (зеркало)](https://storage.googleapis.com/kldscp/amnezia.org)
|
||||||
- [https://docs.amnezia.org](https://docs.amnezia.org) - Документация
|
- [https://docs.amnezia.org](https://docs.amnezia.org) - Документация
|
||||||
- [https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit
|
- [https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit
|
||||||
- [https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Канал поддржки в Telegram (Английский)
|
- [https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Канал поддержки в Telegram (Английский)
|
||||||
- [https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Канал поддржки в Telegram (Фарси)
|
- [https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Канал поддержки в Telegram (Фарси)
|
||||||
- [https://t.me/amnezia_vpn_mm](https://t.me/amnezia_vpn_mm) - Канал поддржки в Telegram (Мьянма)
|
- [https://t.me/amnezia_vpn_mm](https://t.me/amnezia_vpn_mm) - Канал поддержки в Telegram (Мьянма)
|
||||||
- [https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - Канал поддржки в Telegram (Русский)
|
- [https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - Канал поддержки в Telegram (Русский)
|
||||||
- [https://vpnpay.io/en/amnezia-premium/](https://vpnpay.io/en/amnezia-premium/) - Amnezia Premium | [Зеркало](https://storage.googleapis.com/kldscp/vpnpay.io/ru/amnezia-premium\)
|
- [https://vpnpay.io/en/amnezia-premium/](https://vpnpay.io/en/amnezia-premium/) - Amnezia Premium | [Зеркало](https://storage.googleapis.com/kldscp/vpnpay.io/ru/amnezia-premium\)
|
||||||
|
|
||||||
## Технологии
|
## Технологии
|
||||||
@@ -80,8 +80,8 @@ git submodule update --init --recursive
|
|||||||
Проверьте папку deploy для скриптов сборки.
|
Проверьте папку deploy для скриптов сборки.
|
||||||
|
|
||||||
### Как собрать iOS-приложение из исходного кода на MacOS
|
### Как собрать iOS-приложение из исходного кода на MacOS
|
||||||
1. Убедитесь, что у вас установлен XCode версии 14 или выше.
|
1. Убедитесь, что у вас установлен Xcode версии 14 или выше.
|
||||||
2. Для генерации проекта XCode используется QT. Требуется версия QT 6.6.2. Установите QT для MacOS здесь или через QT Online Installer. Необходимые модули:
|
2. Для генерации проекта Xcode используется QT. Требуется версия QT 6.6.2. Установите QT для MacOS здесь или через QT Online Installer. Необходимые модули:
|
||||||
- MacOS
|
- MacOS
|
||||||
- iOS
|
- iOS
|
||||||
- Модуль совместимости с Qt 5
|
- Модуль совместимости с Qt 5
|
||||||
@@ -117,7 +117,7 @@ $QT_IOS_BIN/qt-cmake . -B build-ios -GXcode -DQT_HOST_PATH=$QT_MACOS_ROOT_DIR
|
|||||||
export PATH=$(PATH):/path/to/GOPATH/bin
|
export PATH=$(PATH):/path/to/GOPATH/bin
|
||||||
```
|
```
|
||||||
|
|
||||||
6. Откройте проект в XCode. Теперь вы можете тестировать, архивировать или публиковать приложение.
|
6. Откройте проект в Xcode. Теперь вы можете тестировать, архивировать или публиковать приложение.
|
||||||
|
|
||||||
Если сборка завершится с ошибкой:
|
Если сборка завершится с ошибкой:
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -0,0 +1,149 @@
|
|||||||
|
# Third-Party Licenses
|
||||||
|
|
||||||
|
This project is licensed under the GNU General Public License v3.0.
|
||||||
|
This file lists third-party software components used by this repository.
|
||||||
|
Each component is distributed under its own license as linked below.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## QtKeychain
|
||||||
|
|
||||||
|
- Source: https://github.com/frankosterfeld/qtkeychain
|
||||||
|
- License: BSD License
|
||||||
|
- License Text: https://www.gnu.org/licenses/license-list.html#ModifiedBSD
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## QSimpleCrypto
|
||||||
|
|
||||||
|
- Source: https://github.com/n1flh31mur/QSimpleCrypto
|
||||||
|
- License: Apache License 2.0
|
||||||
|
- License Text: https://github.com/n1flh31mur/QSimpleCrypto/blob/master/LICENSE
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## SortFilterProxyModel
|
||||||
|
|
||||||
|
- Source: https://github.com/oKcerG/SortFilterProxyModel
|
||||||
|
- License: MIT License
|
||||||
|
- License Text: https://github.com/oKcerG/SortFilterProxyModel/blob/master/LICENSE
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## QJsonStruct
|
||||||
|
|
||||||
|
- Source: https://github.com/Qv2ray/QJsonStruct
|
||||||
|
- License: MIT License
|
||||||
|
- License Text: https://github.com/Qv2ray/QJsonStruct/blob/master/LICENSE
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## QR Code Generator (qrcodegen)
|
||||||
|
|
||||||
|
- Source: https://github.com/nayuki/QR-Code-generator
|
||||||
|
- License: MIT License
|
||||||
|
- License Text: https://www.nayuki.io/page/qr-code-generator-library
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Qt Gamepad
|
||||||
|
|
||||||
|
- Source: https://github.com/qt/qtgamepad
|
||||||
|
- License: GNU General Public License v3.0 (GPL-3.0)
|
||||||
|
- License Text: https://www.gnu.org/licenses/gpl-3.0.en.html
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## AmneziaWG Apple (WireGuard)
|
||||||
|
|
||||||
|
- Source: https://github.com/amnezia-vpn/amneziawg-apple
|
||||||
|
- License: MIT License
|
||||||
|
- License Text: https://github.com/amnezia-vpn/amneziawg-apple/blob/master/COPYING
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## AmneziaWG Android
|
||||||
|
|
||||||
|
- Source: https://github.com/amnezia-vpn/amneziawg-go
|
||||||
|
- License: MIT License
|
||||||
|
- License Text: https://github.com/amnezia-vpn/amneziawg-go/blob/master/LICENSE
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Xray Core
|
||||||
|
|
||||||
|
- Source: https://github.com/XTLS/Xray-core
|
||||||
|
- License: Mozilla Public License 2.0 (MPL-2.0)
|
||||||
|
- License Text: https://github.com/XTLS/Xray-core/blob/main/LICENSE
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cloak
|
||||||
|
|
||||||
|
- Source: https://github.com/cbeuw/Cloak
|
||||||
|
- License: GNU General Public License v3.0 (GPL-3.0)
|
||||||
|
- License Text: https://github.com/cbeuw/Cloak/blob/master/LICENSE
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Shadowsocks
|
||||||
|
|
||||||
|
- Source: https://github.com/shadowsocks/shadowsocks-libev
|
||||||
|
- License: GPL-3.0-or-later
|
||||||
|
- License Text: http://www.gnu.org/licenses/
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## OpenSSL
|
||||||
|
|
||||||
|
- Source: https://github.com/openssl/openssl
|
||||||
|
- License: Apache License 2.0
|
||||||
|
- License Text: https://www.openssl.org/source/license.html
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## libssh
|
||||||
|
|
||||||
|
- Source: https://www.libssh.org/
|
||||||
|
- License: GNU Lesser General Public License (LGPL)
|
||||||
|
- License Text: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## OpenVPNAdapter
|
||||||
|
|
||||||
|
- Source: https://github.com/ss-abramchuk/OpenVPNAdapter
|
||||||
|
- License: GNU Affero General Public License v3.0 (AGPL-3.0)
|
||||||
|
- License Text: https://github.com/ss-abramchuk/OpenVPNAdapter/blob/master/LICENSE
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Wintun
|
||||||
|
|
||||||
|
- Source: https://www.wintun.net/
|
||||||
|
- License: Prebuilt Binaries License
|
||||||
|
- License Text: https://github.com/WireGuard/wintun/blob/master/prebuilt-binaries-license.txt
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Mullvad Split Tunnel Driver
|
||||||
|
|
||||||
|
- Source: https://github.com/mullvad/win-split-tunnel
|
||||||
|
- License: GNU General Public License v3.0 (GPL-3.0) and Mozilla Public License Version 2.0
|
||||||
|
- License Text: https://github.com/mullvad/win-split-tunnel/blob/master/LICENSE-GPL.md https://github.com/mullvad/win-split-tunnel/blob/master/LICENSE-MPL.txt
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## tun2socks
|
||||||
|
|
||||||
|
- Source: https://github.com/eycorsican/go-tun2socks
|
||||||
|
- License: MIT License
|
||||||
|
- License Text: https://github.com/eycorsican/go-tun2socks/blob/master/LICENSE
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TAP-Windows Driver
|
||||||
|
|
||||||
|
- Source: https://github.com/OpenVPN/tap-windows6
|
||||||
|
- License: tap-windows6 license
|
||||||
|
- License Text: https://github.com/OpenVPN/tap-windows6/blob/master/COPYING
|
||||||
@@ -3,7 +3,6 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
|||||||
set(PROJECT AmneziaVPN)
|
set(PROJECT AmneziaVPN)
|
||||||
project(${PROJECT})
|
project(${PROJECT})
|
||||||
|
|
||||||
|
|
||||||
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
|
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
|
||||||
set_property(GLOBAL PROPERTY AUTOGEN_TARGETS_FOLDER "Autogen")
|
set_property(GLOBAL PROPERTY AUTOGEN_TARGETS_FOLDER "Autogen")
|
||||||
set_property(GLOBAL PROPERTY AUTOMOC_TARGETS_FOLDER "Autogen")
|
set_property(GLOBAL PROPERTY AUTOMOC_TARGETS_FOLDER "Autogen")
|
||||||
@@ -26,14 +25,14 @@ add_definitions(-DGIT_COMMIT_HASH="${GIT_COMMIT_HASH}")
|
|||||||
|
|
||||||
add_definitions(-DPROD_AGW_PUBLIC_KEY="$ENV{PROD_AGW_PUBLIC_KEY}")
|
add_definitions(-DPROD_AGW_PUBLIC_KEY="$ENV{PROD_AGW_PUBLIC_KEY}")
|
||||||
add_definitions(-DPROD_S3_ENDPOINT="$ENV{PROD_S3_ENDPOINT}")
|
add_definitions(-DPROD_S3_ENDPOINT="$ENV{PROD_S3_ENDPOINT}")
|
||||||
|
add_definitions(-DFALLBACK_S3_ENDPOINT="$ENV{FALLBACK_S3_ENDPOINT}")
|
||||||
|
|
||||||
add_definitions(-DDEV_AGW_PUBLIC_KEY="$ENV{DEV_AGW_PUBLIC_KEY}")
|
add_definitions(-DDEV_AGW_PUBLIC_KEY="$ENV{DEV_AGW_PUBLIC_KEY}")
|
||||||
add_definitions(-DDEV_AGW_ENDPOINT="$ENV{DEV_AGW_ENDPOINT}")
|
add_definitions(-DDEV_AGW_ENDPOINT="$ENV{DEV_AGW_ENDPOINT}")
|
||||||
add_definitions(-DDEV_S3_ENDPOINT="$ENV{DEV_S3_ENDPOINT}")
|
add_definitions(-DDEV_S3_ENDPOINT="$ENV{DEV_S3_ENDPOINT}")
|
||||||
|
|
||||||
if(IOS)
|
add_definitions(-DFREE_V2_ENDPOINT="$ENV{FREE_V2_ENDPOINT}")
|
||||||
set(PACKAGES ${PACKAGES} Multimedia)
|
add_definitions(-DPREM_V1_ENDPOINT="$ENV{PREM_V1_ENDPOINT}")
|
||||||
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)
|
||||||
@@ -48,21 +47,19 @@ set(LIBS ${LIBS}
|
|||||||
Qt6::Core5Compat Qt6::Concurrent
|
Qt6::Core5Compat Qt6::Concurrent
|
||||||
)
|
)
|
||||||
|
|
||||||
if(IOS)
|
|
||||||
set(LIBS ${LIBS} Qt6::Multimedia)
|
|
||||||
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(LIBS ${LIBS} Qt6::Widgets)
|
set(LIBS ${LIBS} Qt6::Widgets)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
qt_standard_project_setup()
|
qt_standard_project_setup()
|
||||||
qt_add_executable(${PROJECT} MANUAL_FINALIZATION)
|
qt_add_executable(${PROJECT} MANUAL_FINALIZATION)
|
||||||
|
target_include_directories(${PROJECT} PUBLIC
|
||||||
|
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
|
||||||
|
)
|
||||||
|
|
||||||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
|
||||||
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep)
|
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_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)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
qt6_add_resources(QRC ${QRC} ${CMAKE_CURRENT_LIST_DIR}/resources.qrc)
|
qt6_add_resources(QRC ${QRC} ${CMAKE_CURRENT_LIST_DIR}/resources.qrc)
|
||||||
@@ -82,6 +79,7 @@ set(AMNEZIAVPN_TS_FILES
|
|||||||
)
|
)
|
||||||
|
|
||||||
file(GLOB_RECURSE AMNEZIAVPN_TS_SOURCES *.qrc *.cpp *.h *.ui)
|
file(GLOB_RECURSE AMNEZIAVPN_TS_SOURCES *.qrc *.cpp *.h *.ui)
|
||||||
|
list(FILTER AMNEZIAVPN_TS_SOURCES EXCLUDE REGEX "qtgamepad/examples")
|
||||||
|
|
||||||
qt_create_translation(AMNEZIAVPN_QM_FILES ${AMNEZIAVPN_TS_SOURCES} ${AMNEZIAVPN_TS_FILES})
|
qt_create_translation(AMNEZIAVPN_QM_FILES ${AMNEZIAVPN_TS_SOURCES} ${AMNEZIAVPN_TS_FILES})
|
||||||
|
|
||||||
@@ -115,6 +113,15 @@ include_directories(
|
|||||||
${CMAKE_CURRENT_BINARY_DIR}
|
${CMAKE_CURRENT_BINARY_DIR}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(MACOS_NE)
|
||||||
|
message("MACOS_NE is ON")
|
||||||
|
add_definitions(-DQ_OS_MAC)
|
||||||
|
add_definitions(-DMACOS_NE)
|
||||||
|
message("Add macros for MacOS Network Extension")
|
||||||
|
else()
|
||||||
|
message("MACOS_NE is OFF")
|
||||||
|
endif()
|
||||||
|
|
||||||
include_directories(mozilla)
|
include_directories(mozilla)
|
||||||
include_directories(mozilla/shared)
|
include_directories(mozilla/shared)
|
||||||
include_directories(mozilla/models)
|
include_directories(mozilla/models)
|
||||||
@@ -144,7 +151,7 @@ if(WIN32)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
cmake_policy(SET CMP0099 OLD)
|
cmake_policy(SET CMP0099 NEW)
|
||||||
cmake_policy(SET CMP0114 NEW)
|
cmake_policy(SET CMP0114 NEW)
|
||||||
|
|
||||||
if(NOT BUILD_OSX_APP_IDENTIFIER)
|
if(NOT BUILD_OSX_APP_IDENTIFIER)
|
||||||
@@ -163,7 +170,6 @@ if(APPLE)
|
|||||||
set(CMAKE_XCODE_GENERATE_SCHEME FALSE)
|
set(CMAKE_XCODE_GENERATE_SCHEME FALSE)
|
||||||
set(CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM ${BUILD_VPN_DEVELOPMENT_TEAM})
|
set(CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM ${BUILD_VPN_DEVELOPMENT_TEAM})
|
||||||
set(CMAKE_XCODE_ATTRIBUTE_GROUP_ID_IOS ${BUILD_IOS_GROUP_IDENTIFIER})
|
set(CMAKE_XCODE_ATTRIBUTE_GROUP_ID_IOS ${BUILD_IOS_GROUP_IDENTIFIER})
|
||||||
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(LINUX AND NOT ANDROID)
|
if(LINUX AND NOT ANDROID)
|
||||||
@@ -171,8 +177,7 @@ if(LINUX AND NOT ANDROID)
|
|||||||
link_directories(${CMAKE_CURRENT_LIST_DIR}/platforms/linux)
|
link_directories(${CMAKE_CURRENT_LIST_DIR}/platforms/linux)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
|
||||||
message("Client desktop build")
|
|
||||||
add_compile_definitions(AMNEZIA_DESKTOP)
|
add_compile_definitions(AMNEZIA_DESKTOP)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
@@ -183,7 +188,9 @@ endif()
|
|||||||
if(IOS)
|
if(IOS)
|
||||||
include(cmake/ios.cmake)
|
include(cmake/ios.cmake)
|
||||||
include(cmake/ios-arch-fixup.cmake)
|
include(cmake/ios-arch-fixup.cmake)
|
||||||
elseif(APPLE AND NOT IOS)
|
elseif(APPLE AND MACOS_NE)
|
||||||
|
include(cmake/macos_ne.cmake)
|
||||||
|
elseif(APPLE)
|
||||||
include(cmake/osxtools.cmake)
|
include(cmake/osxtools.cmake)
|
||||||
include(cmake/macos.cmake)
|
include(cmake/macos.cmake)
|
||||||
endif()
|
endif()
|
||||||
@@ -204,7 +211,7 @@ elseif(APPLE AND NOT IOS)
|
|||||||
set(DEPLOY_PLATFORM_PATH "macos")
|
set(DEPLOY_PLATFORM_PATH "macos")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NOT IOS AND NOT ANDROID)
|
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
|
||||||
add_custom_command(
|
add_custom_command(
|
||||||
TARGET ${PROJECT} POST_BUILD
|
TARGET ${PROJECT} POST_BUILD
|
||||||
COMMAND ${CMAKE_COMMAND} -E $<IF:$<CONFIG:Debug>,copy_directory,true>
|
COMMAND ${CMAKE_COMMAND} -E $<IF:$<CONFIG:Debug>,copy_directory,true>
|
||||||
@@ -219,8 +226,16 @@ if(NOT IOS AND NOT ANDROID)
|
|||||||
$<TARGET_FILE_DIR:${PROJECT}>
|
$<TARGET_FILE_DIR:${PROJECT}>
|
||||||
COMMAND_EXPAND_LISTS
|
COMMAND_EXPAND_LISTS
|
||||||
)
|
)
|
||||||
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC} ${I18NQRC})
|
target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC} ${I18NQRC})
|
||||||
qt_finalize_target(${PROJECT})
|
|
||||||
|
# Finalize the executable so Qt can gather/deploy QML modules and plugins correctly (Android needs this).
|
||||||
|
if(COMMAND qt_import_qml_plugins)
|
||||||
|
qt_import_qml_plugins(${PROJECT})
|
||||||
|
endif()
|
||||||
|
if(COMMAND qt_finalize_executable)
|
||||||
|
qt_finalize_executable(${PROJECT})
|
||||||
|
else()
|
||||||
|
qt_finalize_target(${PROJECT})
|
||||||
|
endif()
|
||||||
|
|||||||
@@ -12,6 +12,9 @@
|
|||||||
#include <QTextDocument>
|
#include <QTextDocument>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QTranslator>
|
#include <QTranslator>
|
||||||
|
#include <QEvent>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QSettings>
|
||||||
|
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "ui/controllers/pageController.h"
|
#include "ui/controllers/pageController.h"
|
||||||
@@ -21,9 +24,18 @@
|
|||||||
#include "platforms/ios/QRCodeReaderBase.h"
|
#include "platforms/ios/QRCodeReaderBase.h"
|
||||||
|
|
||||||
#include "protocols/qml_register_protocols.h"
|
#include "protocols/qml_register_protocols.h"
|
||||||
|
#include <QtQuick/QQuickWindow> // for QQuickWindow
|
||||||
|
#include <QWindow> // for qobject_cast<QWindow*>
|
||||||
|
|
||||||
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv)
|
bool AmneziaApplication::m_forceQuit = false;
|
||||||
|
|
||||||
|
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv),
|
||||||
|
m_optAutostart({QStringLiteral("a"), QStringLiteral("autostart")}, QStringLiteral("System autostart")),
|
||||||
|
m_optCleanup ({QStringLiteral("c"), QStringLiteral("cleanup")}, QStringLiteral("Cleanup logs")),
|
||||||
|
m_optConnect ({QStringLiteral("connect")}, QStringLiteral("Connect to server by index on startup"), QStringLiteral("index")),
|
||||||
|
m_optImport ({QStringLiteral("import")}, QStringLiteral("Import configuration from data string"), QStringLiteral("data"))
|
||||||
{
|
{
|
||||||
|
setDesktopFileName(QStringLiteral(APPLICATION_NAME));
|
||||||
setQuitOnLastWindowClosed(false);
|
setQuitOnLastWindowClosed(false);
|
||||||
|
|
||||||
// Fix config file permissions
|
// Fix config file permissions
|
||||||
@@ -48,30 +60,78 @@ AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_C
|
|||||||
|
|
||||||
AmneziaApplication::~AmneziaApplication()
|
AmneziaApplication::~AmneziaApplication()
|
||||||
{
|
{
|
||||||
|
#ifdef AMNEZIA_DESKTOP
|
||||||
|
if (m_vpnConnection && m_vpnConnectionThread.isRunning()) {
|
||||||
|
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectSlots", Qt::BlockingQueuedConnection);
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectFromVpn", Qt::BlockingQueuedConnection);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
m_vpnConnectionThread.requestInterruption();
|
||||||
m_vpnConnectionThread.quit();
|
m_vpnConnectionThread.quit();
|
||||||
m_vpnConnectionThread.wait(3000);
|
|
||||||
|
if (!m_vpnConnectionThread.wait(3000)) {
|
||||||
|
m_vpnConnectionThread.terminate();
|
||||||
|
m_vpnConnectionThread.wait(500);
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml"));
|
const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml"));
|
||||||
QObject::connect(
|
QObject::connect(
|
||||||
m_engine, &QQmlApplicationEngine::objectCreated, this,
|
m_engine, &QQmlApplicationEngine::objectCreated, this,
|
||||||
[url](QObject *obj, const QUrl &objUrl) {
|
[this, url](QObject *obj, const QUrl &objUrl) {
|
||||||
if (!obj && url == objUrl)
|
if (!obj && url == objUrl) {
|
||||||
QCoreApplication::exit(-1);
|
QCoreApplication::exit(-1);
|
||||||
},
|
return;
|
||||||
Qt::QueuedConnection);
|
}
|
||||||
|
// install filter on main window
|
||||||
|
if (auto win = qobject_cast<QQuickWindow*>(obj)) {
|
||||||
|
win->installEventFilter(this);
|
||||||
|
#ifdef Q_OS_ANDROID
|
||||||
|
QObject::connect(win, &QQuickWindow::sceneGraphError,
|
||||||
|
[](QQuickWindow::SceneGraphError, const QString &msg) {
|
||||||
|
qWarning() << "Scene graph error (suppressed):" << msg;
|
||||||
|
});
|
||||||
|
// Keep graphics context alive across hide/show cycles to avoid
|
||||||
|
// eglSwapBuffers/makeCurrent being called on a context Android has reclaimed.
|
||||||
|
win->setPersistentSceneGraph(true);
|
||||||
|
win->setPersistentGraphics(true);
|
||||||
|
#endif
|
||||||
|
win->show();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Qt::QueuedConnection);
|
||||||
|
|
||||||
m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance());
|
m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance());
|
||||||
|
|
||||||
|
#ifdef MACOS_NE
|
||||||
|
m_engine->rootContext()->setContextProperty("IsMacOsNeBuild", true);
|
||||||
|
#else
|
||||||
|
m_engine->rootContext()->setContextProperty("IsMacOsNeBuild", false);
|
||||||
|
#endif
|
||||||
|
|
||||||
m_vpnConnection.reset(new VpnConnection(m_settings));
|
m_vpnConnection.reset(new VpnConnection(m_settings));
|
||||||
m_vpnConnection->moveToThread(&m_vpnConnectionThread);
|
m_vpnConnection->moveToThread(&m_vpnConnectionThread);
|
||||||
m_vpnConnectionThread.start();
|
m_vpnConnectionThread.start();
|
||||||
@@ -79,6 +139,16 @@ void AmneziaApplication::init()
|
|||||||
m_coreController.reset(new CoreController(m_vpnConnection, m_settings, m_engine));
|
m_coreController.reset(new CoreController(m_vpnConnection, m_settings, m_engine));
|
||||||
|
|
||||||
m_engine->addImportPath("qrc:/ui/qml/Modules/");
|
m_engine->addImportPath("qrc:/ui/qml/Modules/");
|
||||||
|
|
||||||
|
if (m_parser.isSet(m_optImport)) {
|
||||||
|
const QString data = m_parser.value(m_optImport);
|
||||||
|
if (!data.isEmpty()) {
|
||||||
|
if (m_coreController) {
|
||||||
|
m_coreController->importConfigFromData(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
m_engine->load(url);
|
m_engine->load(url);
|
||||||
|
|
||||||
m_coreController->setQmlRoot();
|
m_coreController->setQmlRoot();
|
||||||
@@ -94,7 +164,7 @@ void AmneziaApplication::init()
|
|||||||
Logger::setServiceLogsEnabled(enabled);
|
Logger::setServiceLogsEnabled(enabled);
|
||||||
|
|
||||||
#ifdef Q_OS_WIN //TODO
|
#ifdef Q_OS_WIN //TODO
|
||||||
if (m_parser.isSet("a"))
|
if (m_parser.isSet(m_optAutostart))
|
||||||
m_coreController->pageController()->showOnStartup();
|
m_coreController->pageController()->showOnStartup();
|
||||||
else
|
else
|
||||||
emit m_coreController->pageController()->raiseMainWindow();
|
emit m_coreController->pageController()->raiseMainWindow();
|
||||||
@@ -118,6 +188,18 @@ void AmneziaApplication::init()
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if (m_parser.isSet(m_optConnect)) {
|
||||||
|
bool ok = false;
|
||||||
|
int idx = m_parser.value(m_optConnect).toInt(&ok);
|
||||||
|
if (ok) {
|
||||||
|
QTimer::singleShot(0, this, [this, idx]() {
|
||||||
|
if (m_coreController) {
|
||||||
|
m_coreController->openConnectionByIndex(idx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AmneziaApplication::registerTypes()
|
void AmneziaApplication::registerTypes()
|
||||||
@@ -162,15 +244,14 @@ bool AmneziaApplication::parseCommands()
|
|||||||
m_parser.addHelpOption();
|
m_parser.addHelpOption();
|
||||||
m_parser.addVersionOption();
|
m_parser.addVersionOption();
|
||||||
|
|
||||||
QCommandLineOption c_autostart { { "a", "autostart" }, "System autostart" };
|
m_parser.addOption(m_optAutostart);
|
||||||
m_parser.addOption(c_autostart);
|
m_parser.addOption(m_optCleanup);
|
||||||
|
m_parser.addOption(m_optConnect);
|
||||||
QCommandLineOption c_cleanup { { "c", "cleanup" }, "Cleanup logs" };
|
m_parser.addOption(m_optImport);
|
||||||
m_parser.addOption(c_cleanup);
|
|
||||||
|
|
||||||
m_parser.process(*this);
|
m_parser.process(*this);
|
||||||
|
|
||||||
if (m_parser.isSet(c_cleanup)) {
|
if (m_parser.isSet(m_optCleanup)) {
|
||||||
Logger::cleanUp();
|
Logger::cleanUp();
|
||||||
QTimer::singleShot(100, this, [this] { quit(); });
|
QTimer::singleShot(100, this, [this] { quit(); });
|
||||||
exec();
|
exec();
|
||||||
@@ -179,9 +260,8 @@ bool AmneziaApplication::parseCommands()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||||
void AmneziaApplication::startLocalServer()
|
void AmneziaApplication::startLocalServer() {
|
||||||
{
|
|
||||||
const QString serverName("AmneziaVPNInstance");
|
const QString serverName("AmneziaVPNInstance");
|
||||||
QLocalServer::removeServer(serverName);
|
QLocalServer::removeServer(serverName);
|
||||||
|
|
||||||
@@ -198,6 +278,32 @@ void AmneziaApplication::startLocalServer()
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
bool AmneziaApplication::eventFilter(QObject *watched, QEvent *event)
|
||||||
|
{
|
||||||
|
if (event->type() == QEvent::Close) {
|
||||||
|
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||||
|
quit();
|
||||||
|
#else
|
||||||
|
if (m_forceQuit) {
|
||||||
|
quit();
|
||||||
|
} else {
|
||||||
|
if (m_coreController && m_coreController->pageController()) {
|
||||||
|
m_coreController->pageController()->hideMainWindow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return true; // eat the close
|
||||||
|
}
|
||||||
|
// call base QObject::eventFilter
|
||||||
|
return QObject::eventFilter(watched, event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AmneziaApplication::forceQuit()
|
||||||
|
{
|
||||||
|
m_forceQuit = true;
|
||||||
|
quit();
|
||||||
|
}
|
||||||
|
|
||||||
QQmlApplicationEngine *AmneziaApplication::qmlEngine() const
|
QQmlApplicationEngine *AmneziaApplication::qmlEngine() const
|
||||||
{
|
{
|
||||||
return m_engine;
|
return m_engine;
|
||||||
|
|||||||
@@ -7,9 +7,9 @@
|
|||||||
#include <QQmlContext>
|
#include <QQmlContext>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
#else
|
#else
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#endif
|
#endif
|
||||||
#include <QClipboard>
|
#include <QClipboard>
|
||||||
|
|
||||||
@@ -20,9 +20,9 @@
|
|||||||
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
|
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
|
||||||
|
|
||||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||||
#define AMNEZIA_BASE_CLASS QGuiApplication
|
#define AMNEZIA_BASE_CLASS QGuiApplication
|
||||||
#else
|
#else
|
||||||
#define AMNEZIA_BASE_CLASS QApplication
|
#define AMNEZIA_BASE_CLASS QApplication
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class AmneziaApplication : public AMNEZIA_BASE_CLASS
|
class AmneziaApplication : public AMNEZIA_BASE_CLASS
|
||||||
@@ -37,7 +37,7 @@ public:
|
|||||||
void loadFonts();
|
void loadFonts();
|
||||||
bool parseCommands();
|
bool parseCommands();
|
||||||
|
|
||||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||||
void startLocalServer();
|
void startLocalServer();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -45,7 +45,11 @@ public:
|
|||||||
QNetworkAccessManager *networkManager();
|
QNetworkAccessManager *networkManager();
|
||||||
QClipboard *getClipboard();
|
QClipboard *getClipboard();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void forceQuit();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
static bool m_forceQuit;
|
||||||
QQmlApplicationEngine *m_engine {};
|
QQmlApplicationEngine *m_engine {};
|
||||||
std::shared_ptr<Settings> m_settings;
|
std::shared_ptr<Settings> m_settings;
|
||||||
|
|
||||||
@@ -56,10 +60,17 @@ private:
|
|||||||
|
|
||||||
QCommandLineParser m_parser;
|
QCommandLineParser m_parser;
|
||||||
|
|
||||||
|
QCommandLineOption m_optAutostart;
|
||||||
|
QCommandLineOption m_optCleanup;
|
||||||
|
QCommandLineOption m_optConnect;
|
||||||
|
QCommandLineOption m_optImport;
|
||||||
|
|
||||||
QSharedPointer<VpnConnection> m_vpnConnection;
|
QSharedPointer<VpnConnection> m_vpnConnection;
|
||||||
QThread m_vpnConnectionThread;
|
QThread m_vpnConnectionThread;
|
||||||
|
|
||||||
QNetworkAccessManager *m_nam;
|
QNetworkAccessManager *m_nam;
|
||||||
|
protected:
|
||||||
|
bool eventFilter(QObject *watched, QEvent *event) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // AMNEZIA_APPLICATION_H
|
#endif // AMNEZIA_APPLICATION_H
|
||||||
|
|||||||
@@ -45,7 +45,8 @@
|
|||||||
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="stateUnchanged|adjustResize"
|
android:windowSoftInputMode="adjustResize|stateUnchanged"
|
||||||
|
android:enableOnBackInvokedCallback="false"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
@@ -214,4 +215,4 @@
|
|||||||
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/qtprovider_paths" />
|
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/qtprovider_paths" />
|
||||||
</provider>
|
</provider>
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -93,7 +93,7 @@ open class OpenVpn : Protocol() {
|
|||||||
openVpnClient = null
|
openVpnClient = null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun reconnectVpn(vpnBuilder: Builder) {
|
override fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
||||||
openVpnClient?.let {
|
openVpnClient?.let {
|
||||||
it.establish = makeEstablish(vpnBuilder)
|
it.establish = makeEstablish(vpnBuilder)
|
||||||
it.reconnect(0)
|
it.reconnect(0)
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ abstract class Protocol {
|
|||||||
|
|
||||||
abstract fun stopVpn()
|
abstract fun stopVpn()
|
||||||
|
|
||||||
abstract fun reconnectVpn(vpnBuilder: Builder)
|
abstract fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean)
|
||||||
|
|
||||||
protected fun ProtocolConfig.Builder.configSplitTunneling(config: JSONObject) {
|
protected fun ProtocolConfig.Builder.configSplitTunneling(config: JSONObject) {
|
||||||
if (!allowSplitTunneling) {
|
if (!allowSplitTunneling) {
|
||||||
|
|||||||
@@ -6,6 +6,9 @@
|
|||||||
<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>
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ import android.os.ParcelFileDescriptor
|
|||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
import android.provider.OpenableColumns
|
import android.provider.OpenableColumns
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
|
import android.view.InputDevice
|
||||||
|
import android.view.KeyEvent
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@@ -35,6 +37,11 @@ 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
|
||||||
@@ -68,6 +75,8 @@ private const val OPEN_FILE_ACTION_CODE = 3
|
|||||||
private const val CHECK_NOTIFICATION_PERMISSION_ACTION_CODE = 4
|
private const val CHECK_NOTIFICATION_PERMISSION_ACTION_CODE = 4
|
||||||
|
|
||||||
private const val PREFS_NOTIFICATION_PERMISSION_ASKED = "NOTIFICATION_PERMISSION_ASKED"
|
private const val PREFS_NOTIFICATION_PERMISSION_ASKED = "NOTIFICATION_PERMISSION_ASKED"
|
||||||
|
private const val OPEN_FILE_AFTER_RESUME_DELAY_MS = 400L
|
||||||
|
private const val KEY_PENDING_OPEN_FILE_URI = "pending_open_file_uri"
|
||||||
|
|
||||||
class AmneziaActivity : QtActivity() {
|
class AmneziaActivity : QtActivity() {
|
||||||
|
|
||||||
@@ -84,6 +93,12 @@ class AmneziaActivity : QtActivity() {
|
|||||||
private val actionResultHandlers = mutableMapOf<Int, ActivityResultHandler>()
|
private val actionResultHandlers = mutableMapOf<Int, ActivityResultHandler>()
|
||||||
private val permissionRequestHandlers = mutableMapOf<Int, PermissionRequestHandler>()
|
private val permissionRequestHandlers = mutableMapOf<Int, PermissionRequestHandler>()
|
||||||
|
|
||||||
|
private var isActivityResumed = false
|
||||||
|
private var hasWindowFocus = false
|
||||||
|
private val resumeHandler = Handler(Looper.getMainLooper())
|
||||||
|
private var pendingOpenFileUri: String? = null
|
||||||
|
private var openFileDeliveryScheduled = false
|
||||||
|
|
||||||
private val vpnServiceEventHandler: Handler by lazy(NONE) {
|
private val vpnServiceEventHandler: Handler by lazy(NONE) {
|
||||||
object : Handler(Looper.getMainLooper()) {
|
object : Handler(Looper.getMainLooper()) {
|
||||||
override fun handleMessage(msg: Message) {
|
override fun handleMessage(msg: Message) {
|
||||||
@@ -170,10 +185,9 @@ 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 {
|
|
||||||
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
// Configure window for edge-to-edge display
|
||||||
statusBarColor = getColor(R.color.black)
|
configureWindowForEdgeToEdge()
|
||||||
}
|
|
||||||
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
|
||||||
@@ -186,11 +200,18 @@ class AmneziaActivity : QtActivity() {
|
|||||||
doBindService()
|
doBindService()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
pendingOpenFileUri = savedInstanceState?.getString(KEY_PENDING_OPEN_FILE_URI)
|
||||||
|
openFileDeliveryScheduled = false
|
||||||
registerBroadcastReceivers()
|
registerBroadcastReceivers()
|
||||||
intent?.let(::processIntent)
|
intent?.let(::processIntent)
|
||||||
runBlocking { vpnProto = proto.await() }
|
runBlocking { vpnProto = proto.await() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
|
super.onSaveInstanceState(outState)
|
||||||
|
pendingOpenFileUri?.let { outState.putString(KEY_PENDING_OPEN_FILE_URI, it) }
|
||||||
|
}
|
||||||
|
|
||||||
private fun loadLibs() {
|
private fun loadLibs() {
|
||||||
listOf(
|
listOf(
|
||||||
"rsapss",
|
"rsapss",
|
||||||
@@ -256,6 +277,11 @@ class AmneziaActivity : QtActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onStop() {
|
override fun onStop() {
|
||||||
|
isActivityResumed = false
|
||||||
|
hasWindowFocus = false
|
||||||
|
// Cancel all pending operations when activity stops
|
||||||
|
resumeHandler.removeCallbacksAndMessages(null)
|
||||||
|
openFileDeliveryScheduled = false
|
||||||
Log.d(TAG, "Stop Amnezia activity")
|
Log.d(TAG, "Stop Amnezia activity")
|
||||||
doUnbindService()
|
doUnbindService()
|
||||||
mainScope.launch {
|
mainScope.launch {
|
||||||
@@ -265,7 +291,197 @@ class AmneziaActivity : QtActivity() {
|
|||||||
super.onStop()
|
super.onStop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||||
|
super.onWindowFocusChanged(hasFocus)
|
||||||
|
hasWindowFocus = hasFocus
|
||||||
|
Log.d(TAG, "Window focus changed: hasFocus=$hasFocus")
|
||||||
|
|
||||||
|
if (!hasFocus) {
|
||||||
|
// Cancel pending operations if window loses focus
|
||||||
|
resumeHandler.removeCallbacksAndMessages(null)
|
||||||
|
} else if (isActivityResumed && Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||||
|
window.decorView.apply {
|
||||||
|
invalidate()
|
||||||
|
resumeHandler.postDelayed({
|
||||||
|
if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
|
||||||
|
sendTouch(1f, 1f)
|
||||||
|
}
|
||||||
|
}, 50)
|
||||||
|
resumeHandler.postDelayed({
|
||||||
|
if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
|
||||||
|
sendTouch(2f, 2f)
|
||||||
|
requestLayout()
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
}, 150)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||||
|
val keyCode = event.keyCode
|
||||||
|
val pressed = event.action == KeyEvent.ACTION_DOWN
|
||||||
|
|
||||||
|
when (keyCode) {
|
||||||
|
KeyEvent.KEYCODE_BUTTON_A,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_B,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_X,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_Y,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_START,
|
||||||
|
KeyEvent.KEYCODE_BUTTON_SELECT -> {
|
||||||
|
nativeGamepadKeyEvent(0, keyCode, pressed)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
KeyEvent.KEYCODE_DPAD_CENTER,
|
||||||
|
KeyEvent.KEYCODE_DPAD_UP,
|
||||||
|
KeyEvent.KEYCODE_DPAD_DOWN,
|
||||||
|
KeyEvent.KEYCODE_DPAD_LEFT,
|
||||||
|
KeyEvent.KEYCODE_DPAD_RIGHT -> {
|
||||||
|
val syntheticKeyCode = if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) KeyEvent.KEYCODE_ENTER else keyCode
|
||||||
|
val synthetic = KeyEvent(
|
||||||
|
event.downTime, event.eventTime, event.action, syntheticKeyCode,
|
||||||
|
event.repeatCount, event.metaState, -1, event.scanCode,
|
||||||
|
event.flags, InputDevice.SOURCE_KEYBOARD
|
||||||
|
)
|
||||||
|
return super.dispatchKeyEvent(synthetic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return super.dispatchKeyEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
private external fun nativeGamepadKeyEvent(deviceId: Int, keyCode: Int, pressed: Boolean)
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
// Notify Qt to stop rendering BEFORE super.onPause() destroys the EGL surface.
|
||||||
|
// Using a coroutine here would be too late — the surface is gone by the time
|
||||||
|
// the coroutine runs. A direct synchronous call gives Qt's render thread the
|
||||||
|
// best chance to process visible=false before surface destruction.
|
||||||
|
if (qtInitialized.isCompleted) {
|
||||||
|
QtAndroidController.onActivityPaused()
|
||||||
|
}
|
||||||
|
super.onPause()
|
||||||
|
isActivityResumed = false
|
||||||
|
// Cancel all pending operations when activity pauses
|
||||||
|
resumeHandler.removeCallbacksAndMessages(null)
|
||||||
|
openFileDeliveryScheduled = false
|
||||||
|
Log.d(TAG, "Pause Amnezia activity")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
isActivityResumed = true
|
||||||
|
Log.d(TAG, "Resume Amnezia activity")
|
||||||
|
if (qtInitialized.isCompleted) {
|
||||||
|
QtAndroidController.onActivityResumed()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pendingOpenFileUri != null && !openFileDeliveryScheduled) {
|
||||||
|
val uri = pendingOpenFileUri!!
|
||||||
|
openFileDeliveryScheduled = true
|
||||||
|
resumeHandler.postDelayed({
|
||||||
|
if (!isFinishing && !isDestroyed) {
|
||||||
|
pendingOpenFileUri = null
|
||||||
|
openFileDeliveryScheduled = false
|
||||||
|
mainScope.launch {
|
||||||
|
qtInitialized.await()
|
||||||
|
QtAndroidController.onFileOpened(uri)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, OPEN_FILE_AFTER_RESUME_DELAY_MS)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||||
|
window.decorView.apply {
|
||||||
|
invalidate()
|
||||||
|
|
||||||
|
resumeHandler.postDelayed({
|
||||||
|
// Check if activity is still resumed and has focus before executing
|
||||||
|
if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
|
||||||
|
sendTouch(1f, 1f)
|
||||||
|
}
|
||||||
|
}, 100)
|
||||||
|
|
||||||
|
resumeHandler.postDelayed({
|
||||||
|
if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
|
||||||
|
sendTouch(2f, 2f)
|
||||||
|
}
|
||||||
|
}, 200)
|
||||||
|
|
||||||
|
resumeHandler.postDelayed({
|
||||||
|
if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
|
||||||
|
requestLayout()
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
}, 250)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
||||||
|
isActivityResumed = false
|
||||||
|
hasWindowFocus = false
|
||||||
|
// Cancel all pending operations when activity is destroyed
|
||||||
|
resumeHandler.removeCallbacksAndMessages(null)
|
||||||
Log.d(TAG, "Destroy Amnezia activity")
|
Log.d(TAG, "Destroy Amnezia activity")
|
||||||
unregisterBroadcastReceiver(notificationStateReceiver)
|
unregisterBroadcastReceiver(notificationStateReceiver)
|
||||||
notificationStateReceiver = null
|
notificationStateReceiver = null
|
||||||
@@ -591,9 +807,13 @@ class AmneziaActivity : QtActivity() {
|
|||||||
grantUriPermission(packageName, this, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
grantUriPermission(packageName, this, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
}?.toString() ?: ""
|
}?.toString() ?: ""
|
||||||
Log.v(TAG, "Open file: $uri")
|
Log.v(TAG, "Open file: $uri")
|
||||||
mainScope.launch {
|
if (uri.isNotEmpty()) {
|
||||||
qtInitialized.await()
|
pendingOpenFileUri = uri
|
||||||
QtAndroidController.onFileOpened(uri)
|
} else {
|
||||||
|
mainScope.launch {
|
||||||
|
qtInitialized.await()
|
||||||
|
QtAndroidController.onFileOpened(uri)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
@@ -622,7 +842,7 @@ class AmneziaActivity : QtActivity() {
|
|||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun getFd(fileName: String): Int {
|
fun getFd(fileName: String): Int {
|
||||||
Log.v(TAG, "Get fd for $fileName")
|
Log.v(TAG, "Get fd for $fileName")
|
||||||
return blockingCall {
|
return blockingCall(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
pfd = contentResolver.openFileDescriptor(Uri.parse(fileName), "r")
|
pfd = contentResolver.openFileDescriptor(Uri.parse(fileName), "r")
|
||||||
pfd?.fd ?: -1
|
pfd?.fd ?: -1
|
||||||
@@ -666,6 +886,43 @@ 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())
|
vpnProto?.protocol?.reconnectVpn(Builder(), ::protect)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
val icon: Boolean = (ai?.icon ?: 0) != 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 = if (name != packageName) name else null
|
this.name = name?.takeIf { it != packageName }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun compareTo(other: App): Int {
|
override fun compareTo(other: App): Int {
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
package org.amnezia.vpn
|
package org.amnezia.vpn
|
||||||
|
|
||||||
import android.content.ActivityNotFoundException
|
import android.content.ActivityNotFoundException
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
@@ -11,8 +14,29 @@ private const val TAG = "TvFilePicker"
|
|||||||
|
|
||||||
class TvFilePicker : ComponentActivity() {
|
class TvFilePicker : ComponentActivity() {
|
||||||
|
|
||||||
private val fileChooseResultLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) {
|
private val fileChooseResultLauncher = registerForActivityResult(object : ActivityResultContracts.OpenDocument() {
|
||||||
setResult(RESULT_OK, Intent().apply { data = it })
|
override fun createIntent(context: Context, input: Array<String>): Intent {
|
||||||
|
val intent = super.createIntent(context, input)
|
||||||
|
|
||||||
|
val activitiesToResolveIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
|
context.packageManager.queryIntentActivities(intent, PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong()))
|
||||||
|
} else {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
context.packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
|
||||||
|
}
|
||||||
|
if (activitiesToResolveIntent.all {
|
||||||
|
val name = it.activityInfo.packageName
|
||||||
|
name.startsWith("com.google.android.tv.frameworkpackagestubs") || name.startsWith("com.android.tv.frameworkpackagestubs")
|
||||||
|
}) {
|
||||||
|
throw ActivityNotFoundException()
|
||||||
|
}
|
||||||
|
return intent
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
setResult(RESULT_OK, Intent().apply {
|
||||||
|
data = it
|
||||||
|
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
})
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,7 +55,7 @@ class TvFilePicker : ComponentActivity() {
|
|||||||
private fun getFile() {
|
private fun getFile() {
|
||||||
try {
|
try {
|
||||||
Log.v(TAG, "getFile")
|
Log.v(TAG, "getFile")
|
||||||
fileChooseResultLauncher.launch("*/*")
|
fileChooseResultLauncher.launch(arrayOf("*/*"))
|
||||||
} catch (_: ActivityNotFoundException) {
|
} catch (_: ActivityNotFoundException) {
|
||||||
Log.w(TAG, "Activity not found")
|
Log.w(TAG, "Activity not found")
|
||||||
setResult(RESULT_CANCELED, Intent().apply { putExtra("activityNotFound", true) })
|
setResult(RESULT_CANCELED, Intent().apply { putExtra("activityNotFound", true) })
|
||||||
|
|||||||
@@ -28,4 +28,10 @@ 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)
|
||||||
|
|
||||||
|
external fun onActivityPaused()
|
||||||
|
external fun onActivityResumed()
|
||||||
}
|
}
|
||||||
@@ -10,6 +10,8 @@ import java.nio.channels.FileChannel
|
|||||||
import java.nio.channels.FileLock
|
import java.nio.channels.FileLock
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
|
import java.time.ZonedDateTime
|
||||||
|
import java.time.ZoneOffset
|
||||||
import java.util.concurrent.locks.ReentrantLock
|
import java.util.concurrent.locks.ReentrantLock
|
||||||
import org.amnezia.vpn.util.Log.Priority.D
|
import org.amnezia.vpn.util.Log.Priority.D
|
||||||
import org.amnezia.vpn.util.Log.Priority.E
|
import org.amnezia.vpn.util.Log.Priority.E
|
||||||
@@ -135,8 +137,8 @@ object Log {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun formatLogMsg(tag: String, msg: String, priority: Priority): String {
|
private fun formatLogMsg(tag: String, msg: String, priority: Priority): String {
|
||||||
val date = LocalDateTime.now().format(dateTimeFormat)
|
val utcDate = ZonedDateTime.now(ZoneOffset.UTC).format(dateTimeFormat)
|
||||||
return "$date ${Process.myPid()} ${Process.myTid()} $priority [${Thread.currentThread().name}] " +
|
return "${utcDate}Z ${Process.myPid()} ${Process.myTid()} $priority [${Thread.currentThread().name}] " +
|
||||||
"$tag: $msg\n"
|
"$tag: $msg\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import org.amnezia.vpn.protocol.Protocol
|
|||||||
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
|
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
|
||||||
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
||||||
import org.amnezia.vpn.protocol.Statistics
|
import org.amnezia.vpn.protocol.Statistics
|
||||||
|
import org.amnezia.vpn.protocol.VpnException
|
||||||
import org.amnezia.vpn.protocol.VpnStartException
|
import org.amnezia.vpn.protocol.VpnStartException
|
||||||
import org.amnezia.vpn.util.LibraryLoader.loadSharedLibrary
|
import org.amnezia.vpn.util.LibraryLoader.loadSharedLibrary
|
||||||
import org.amnezia.vpn.util.Log
|
import org.amnezia.vpn.util.Log
|
||||||
@@ -27,6 +28,7 @@ private const val TAG = "Wireguard"
|
|||||||
open class Wireguard : Protocol() {
|
open class Wireguard : Protocol() {
|
||||||
|
|
||||||
private var tunnelHandle: Int = -1
|
private var tunnelHandle: Int = -1
|
||||||
|
private var config: WireguardConfig? = null // save config for reconnect
|
||||||
protected open val ifName: String = "amn0"
|
protected open val ifName: String = "amn0"
|
||||||
private lateinit var scope: CoroutineScope
|
private lateinit var scope: CoroutineScope
|
||||||
private var statusJob: Job? = null
|
private var statusJob: Job? = null
|
||||||
@@ -61,6 +63,7 @@ open class Wireguard : Protocol() {
|
|||||||
override suspend fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
override suspend fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
||||||
val wireguardConfig = parseConfig(config)
|
val wireguardConfig = parseConfig(config)
|
||||||
start(wireguardConfig, vpnBuilder, protect)
|
start(wireguardConfig, vpnBuilder, protect)
|
||||||
|
this.config = wireguardConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun parseConfig(config: JSONObject): WireguardConfig {
|
protected open fun parseConfig(config: JSONObject): WireguardConfig {
|
||||||
@@ -120,14 +123,26 @@ open class Wireguard : Protocol() {
|
|||||||
configData.optStringOrNull("Jmax")?.let { setJmax(it.toInt()) }
|
configData.optStringOrNull("Jmax")?.let { setJmax(it.toInt()) }
|
||||||
configData.optStringOrNull("S1")?.let { setS1(it.toInt()) }
|
configData.optStringOrNull("S1")?.let { setS1(it.toInt()) }
|
||||||
configData.optStringOrNull("S2")?.let { setS2(it.toInt()) }
|
configData.optStringOrNull("S2")?.let { setS2(it.toInt()) }
|
||||||
configData.optStringOrNull("H1")?.let { setH1(it.toLong()) }
|
configData.optStringOrNull("S3")?.let { setS3(it.toInt()) }
|
||||||
configData.optStringOrNull("H2")?.let { setH2(it.toLong()) }
|
configData.optStringOrNull("S4")?.let { setS4(it.toInt()) }
|
||||||
configData.optStringOrNull("H3")?.let { setH3(it.toLong()) }
|
configData.optStringOrNull("H1")?.trim()?.let { if (it.isNotEmpty()) setH1(it) }
|
||||||
configData.optStringOrNull("H4")?.let { setH4(it.toLong()) }
|
configData.optStringOrNull("H2")?.trim()?.let { if (it.isNotEmpty()) setH2(it) }
|
||||||
|
configData.optStringOrNull("H3")?.trim()?.let { if (it.isNotEmpty()) setH3(it) }
|
||||||
|
configData.optStringOrNull("H4")?.trim()?.let { if (it.isNotEmpty()) setH4(it) }
|
||||||
|
configData.optStringOrNull("I1")?.let { setI1(it) }
|
||||||
|
configData.optStringOrNull("I2")?.let { setI2(it) }
|
||||||
|
configData.optStringOrNull("I3")?.let { setI3(it) }
|
||||||
|
configData.optStringOrNull("I4")?.let { setI4(it) }
|
||||||
|
configData.optStringOrNull("I5")?.let { setI5(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun start(config: WireguardConfig, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
private fun start(
|
||||||
if (tunnelHandle != -1) {
|
config: WireguardConfig,
|
||||||
|
vpnBuilder: Builder,
|
||||||
|
protect: (Int) -> Boolean,
|
||||||
|
stopExistingVpn: Boolean = false
|
||||||
|
) {
|
||||||
|
if (!stopExistingVpn && tunnelHandle != -1) {
|
||||||
Log.w(TAG, "Tunnel already up")
|
Log.w(TAG, "Tunnel already up")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -135,6 +150,9 @@ open class Wireguard : Protocol() {
|
|||||||
buildVpnInterface(config, vpnBuilder)
|
buildVpnInterface(config, vpnBuilder)
|
||||||
|
|
||||||
vpnBuilder.establish().use { tunFd ->
|
vpnBuilder.establish().use { tunFd ->
|
||||||
|
if (stopExistingVpn && tunnelHandle != -1) {
|
||||||
|
turnOffVpn()
|
||||||
|
}
|
||||||
if (tunFd == null) {
|
if (tunFd == null) {
|
||||||
throw VpnStartException("Create VPN interface: permission not granted or revoked")
|
throw VpnStartException("Create VPN interface: permission not granted or revoked")
|
||||||
}
|
}
|
||||||
@@ -191,20 +209,25 @@ open class Wireguard : Protocol() {
|
|||||||
return lastHandshake
|
return lastHandshake
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun stopVpn() {
|
private fun turnOffVpn() {
|
||||||
if (tunnelHandle == -1) {
|
|
||||||
Log.w(TAG, "Tunnel already down")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
statusJob?.cancel()
|
statusJob?.cancel()
|
||||||
statusJob = null
|
statusJob = null
|
||||||
val handleToClose = tunnelHandle
|
val handleToClose = tunnelHandle
|
||||||
tunnelHandle = -1
|
tunnelHandle = -1
|
||||||
GoBackend.awgTurnOff(handleToClose)
|
GoBackend.awgTurnOff(handleToClose)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun stopVpn() {
|
||||||
|
if (tunnelHandle == -1) {
|
||||||
|
Log.w(TAG, "Tunnel already down")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
turnOffVpn()
|
||||||
state.value = DISCONNECTED
|
state.value = DISCONNECTED
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun reconnectVpn(vpnBuilder: Builder) {
|
override fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
||||||
state.value = CONNECTED
|
val config = this.config ?: throw VpnException("Reconnect config is empty")
|
||||||
|
start(config, vpnBuilder, protect, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,10 +20,17 @@ open class WireguardConfig protected constructor(
|
|||||||
val jmax: Int?,
|
val jmax: Int?,
|
||||||
val s1: Int?,
|
val s1: Int?,
|
||||||
val s2: Int?,
|
val s2: Int?,
|
||||||
val h1: Long?,
|
val s3: Int?,
|
||||||
val h2: Long?,
|
val s4: Int?,
|
||||||
val h3: Long?,
|
val h1: String?,
|
||||||
val h4: Long?
|
val h2: String?,
|
||||||
|
val h3: String?,
|
||||||
|
val h4: String?,
|
||||||
|
var i1: String?,
|
||||||
|
var i2: String?,
|
||||||
|
var i3: String?,
|
||||||
|
var i4: String?,
|
||||||
|
var i5: String?,
|
||||||
) : ProtocolConfig(protocolConfigBuilder) {
|
) : ProtocolConfig(protocolConfigBuilder) {
|
||||||
|
|
||||||
protected constructor(builder: Builder) : this(
|
protected constructor(builder: Builder) : this(
|
||||||
@@ -39,10 +46,17 @@ open class WireguardConfig protected constructor(
|
|||||||
builder.jmax,
|
builder.jmax,
|
||||||
builder.s1,
|
builder.s1,
|
||||||
builder.s2,
|
builder.s2,
|
||||||
|
builder.s3,
|
||||||
|
builder.s4,
|
||||||
builder.h1,
|
builder.h1,
|
||||||
builder.h2,
|
builder.h2,
|
||||||
builder.h3,
|
builder.h3,
|
||||||
builder.h4
|
builder.h4,
|
||||||
|
builder.i1,
|
||||||
|
builder.i2,
|
||||||
|
builder.i3,
|
||||||
|
builder.i4,
|
||||||
|
builder.i5,
|
||||||
)
|
)
|
||||||
|
|
||||||
fun toWgUserspaceString(): String = with(StringBuilder()) {
|
fun toWgUserspaceString(): String = with(StringBuilder()) {
|
||||||
@@ -61,10 +75,17 @@ open class WireguardConfig protected constructor(
|
|||||||
appendLine("jmax=$jmax")
|
appendLine("jmax=$jmax")
|
||||||
appendLine("s1=$s1")
|
appendLine("s1=$s1")
|
||||||
appendLine("s2=$s2")
|
appendLine("s2=$s2")
|
||||||
|
s3?.let { appendLine("s3=$it") }
|
||||||
|
s4?.let { appendLine("s4=$it") }
|
||||||
appendLine("h1=$h1")
|
appendLine("h1=$h1")
|
||||||
appendLine("h2=$h2")
|
appendLine("h2=$h2")
|
||||||
appendLine("h3=$h3")
|
appendLine("h3=$h3")
|
||||||
appendLine("h4=$h4")
|
appendLine("h4=$h4")
|
||||||
|
i1?.let { appendLine("i1=$it") }
|
||||||
|
i2?.let { appendLine("i2=$it") }
|
||||||
|
i3?.let { appendLine("i3=$it") }
|
||||||
|
i4?.let { appendLine("i4=$it") }
|
||||||
|
i5?.let { appendLine("i5=$it") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,10 +138,17 @@ open class WireguardConfig protected constructor(
|
|||||||
internal var jmax: Int? = null
|
internal var jmax: Int? = null
|
||||||
internal var s1: Int? = null
|
internal var s1: Int? = null
|
||||||
internal var s2: Int? = null
|
internal var s2: Int? = null
|
||||||
internal var h1: Long? = null
|
internal var s3: Int? = null
|
||||||
internal var h2: Long? = null
|
internal var s4: Int? = null
|
||||||
internal var h3: Long? = null
|
internal var h1: String? = null
|
||||||
internal var h4: Long? = null
|
internal var h2: String? = null
|
||||||
|
internal var h3: String? = null
|
||||||
|
internal var h4: String? = null
|
||||||
|
internal var i1: String? = null
|
||||||
|
internal var i2: String? = null
|
||||||
|
internal var i3: String? = null
|
||||||
|
internal var i4: String? = null
|
||||||
|
internal var i5: String? = null
|
||||||
|
|
||||||
fun setEndpoint(endpoint: InetEndpoint) = apply { this.endpoint = endpoint }
|
fun setEndpoint(endpoint: InetEndpoint) = apply { this.endpoint = endpoint }
|
||||||
|
|
||||||
@@ -139,10 +167,17 @@ open class WireguardConfig protected constructor(
|
|||||||
fun setJmax(jmax: Int) = apply { this.jmax = jmax }
|
fun setJmax(jmax: Int) = apply { this.jmax = jmax }
|
||||||
fun setS1(s1: Int) = apply { this.s1 = s1 }
|
fun setS1(s1: Int) = apply { this.s1 = s1 }
|
||||||
fun setS2(s2: Int) = apply { this.s2 = s2 }
|
fun setS2(s2: Int) = apply { this.s2 = s2 }
|
||||||
fun setH1(h1: Long) = apply { this.h1 = h1 }
|
fun setS3(s3: Int) = apply { this.s3 = s3 }
|
||||||
fun setH2(h2: Long) = apply { this.h2 = h2 }
|
fun setS4(s4: Int) = apply { this.s4 = s4 }
|
||||||
fun setH3(h3: Long) = apply { this.h3 = h3 }
|
fun setH1(h1: String) = apply { this.h1 = h1 }
|
||||||
fun setH4(h4: Long) = apply { this.h4 = h4 }
|
fun setH2(h2: String) = apply { this.h2 = h2 }
|
||||||
|
fun setH3(h3: String) = apply { this.h3 = h3 }
|
||||||
|
fun setH4(h4: String) = apply { this.h4 = h4 }
|
||||||
|
fun setI1(i1: String) = apply { this.i1 = i1 }
|
||||||
|
fun setI2(i2: String) = apply { this.i2 = i2 }
|
||||||
|
fun setI3(i3: String) = apply { this.i3 = i3 }
|
||||||
|
fun setI4(i4: String) = apply { this.i4 = i4 }
|
||||||
|
fun setI5(i5: String) = apply { this.i5 = i5 }
|
||||||
|
|
||||||
override fun build(): WireguardConfig = configBuild().run { WireguardConfig(this@Builder) }
|
override fun build(): WireguardConfig = configBuild().run { WireguardConfig(this@Builder) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import android.content.Context
|
|||||||
import android.net.VpnService.Builder
|
import android.net.VpnService.Builder
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import java.net.InetAddress
|
||||||
|
import java.net.ServerSocket
|
||||||
|
import java.util.UUID
|
||||||
import go.Seq
|
import go.Seq
|
||||||
import org.amnezia.vpn.protocol.BadConfigException
|
import org.amnezia.vpn.protocol.BadConfigException
|
||||||
import org.amnezia.vpn.protocol.Protocol
|
import org.amnezia.vpn.protocol.Protocol
|
||||||
@@ -19,11 +22,32 @@ import org.amnezia.vpn.util.Log
|
|||||||
import org.amnezia.vpn.util.net.InetNetwork
|
import org.amnezia.vpn.util.net.InetNetwork
|
||||||
import org.amnezia.vpn.util.net.ip
|
import org.amnezia.vpn.util.net.ip
|
||||||
import org.amnezia.vpn.util.net.parseInetAddress
|
import org.amnezia.vpn.util.net.parseInetAddress
|
||||||
|
import org.json.JSONArray
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
private const val TAG = "Xray"
|
private const val TAG = "Xray"
|
||||||
private const val LIBXRAY_TAG = "libXray"
|
private const val LIBXRAY_TAG = "libXray"
|
||||||
|
|
||||||
|
private fun findSocksInboundIndex(inbounds: JSONArray): Int {
|
||||||
|
for (i in 0 until inbounds.length()) {
|
||||||
|
val o = inbounds.optJSONObject(i) ?: continue
|
||||||
|
if (o.optString("protocol").equals("socks", ignoreCase = true)) {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun acquireFreeLocalPort(): Int {
|
||||||
|
try {
|
||||||
|
ServerSocket(0, 1, InetAddress.getByName("127.0.0.1")).use { return it.localPort }
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw VpnStartException(
|
||||||
|
"Failed to acquire free TCP port on 127.0.0.1 for SOCKS inbound: ${e.message}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class Xray : Protocol() {
|
class Xray : Protocol() {
|
||||||
|
|
||||||
private var isRunning: Boolean = false
|
private var isRunning: Boolean = false
|
||||||
@@ -56,6 +80,10 @@ class Xray : Protocol() {
|
|||||||
val xrayJsonConfig = config.optJSONObject("xray_config_data")
|
val xrayJsonConfig = config.optJSONObject("xray_config_data")
|
||||||
?: config.optJSONObject("ssxray_config_data")
|
?: config.optJSONObject("ssxray_config_data")
|
||||||
?: throw BadConfigException("config_data not found")
|
?: throw BadConfigException("config_data not found")
|
||||||
|
|
||||||
|
// Inject SOCKS5 auth before starting xray. Re-uses existing credentials if present.
|
||||||
|
ensureInboundAuth(xrayJsonConfig)
|
||||||
|
|
||||||
val xrayConfig = parseConfig(config, xrayJsonConfig)
|
val xrayConfig = parseConfig(config, xrayJsonConfig)
|
||||||
|
|
||||||
(xrayJsonConfig.optJSONObject("log") ?: JSONObject().also { xrayJsonConfig.put("log", it) })
|
(xrayJsonConfig.optJSONObject("log") ?: JSONObject().also { xrayJsonConfig.put("log", it) })
|
||||||
@@ -97,9 +125,22 @@ class Xray : Protocol() {
|
|||||||
if (it.isNotBlank()) setMtu(it.toInt())
|
if (it.isNotBlank()) setMtu(it.toInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
val socksConfig = xrayJsonConfig.getJSONArray("inbounds")[0] as JSONObject
|
val inbounds = xrayJsonConfig.getJSONArray("inbounds")
|
||||||
|
val socksIdx = findSocksInboundIndex(inbounds)
|
||||||
|
if (socksIdx < 0) {
|
||||||
|
throw BadConfigException("socks inbound not found")
|
||||||
|
}
|
||||||
|
val socksConfig = inbounds.getJSONObject(socksIdx)
|
||||||
socksConfig.getInt("port").let { setSocksPort(it) }
|
socksConfig.getInt("port").let { setSocksPort(it) }
|
||||||
|
|
||||||
|
val socksSettings = socksConfig.optJSONObject("settings")
|
||||||
|
val accounts = socksSettings?.optJSONArray("accounts")
|
||||||
|
if (accounts != null && accounts.length() > 0) {
|
||||||
|
val account = accounts.getJSONObject(0)
|
||||||
|
setSocksUser(account.optString("user"))
|
||||||
|
setSocksPass(account.optString("pass"))
|
||||||
|
}
|
||||||
|
|
||||||
configSplitTunneling(config)
|
configSplitTunneling(config)
|
||||||
configAppSplitTunneling(config)
|
configAppSplitTunneling(config)
|
||||||
}
|
}
|
||||||
@@ -157,22 +198,54 @@ class Xray : Protocol() {
|
|||||||
state.value = DISCONNECTED
|
state.value = DISCONNECTED
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun reconnectVpn(vpnBuilder: Builder) {
|
override fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
||||||
state.value = CONNECTED
|
state.value = CONNECTED
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun runTun2Socks(config: XrayConfig, fd: Int) {
|
private fun runTun2Socks(config: XrayConfig, fd: Int) {
|
||||||
|
val proxyUrl = "socks5://${config.socksUser}:${config.socksPass}@127.0.0.1:${config.socksPort}"
|
||||||
val tun2SocksConfig = Tun2SocksConfig().apply {
|
val tun2SocksConfig = Tun2SocksConfig().apply {
|
||||||
mtu = config.mtu.toLong()
|
mtu = config.mtu.toLong()
|
||||||
proxy = "socks5://127.0.0.1:${config.socksPort}"
|
proxy = proxyUrl
|
||||||
device = "fd://$fd"
|
device = "fd://$fd"
|
||||||
logLevel = "warning"
|
logLevel = "warn"
|
||||||
}
|
}
|
||||||
LibXray.startTun2Socks(tun2SocksConfig, fd.toLong()).isNotNullOrBlank { err ->
|
LibXray.startTun2Socks(tun2SocksConfig, fd.toLong()).isNotNullOrBlank { err ->
|
||||||
throw VpnStartException("Failed to start tun2socks: $err")
|
throw VpnStartException("Failed to start tun2socks: $err")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensures SOCKS5 auth is present on the socks inbound settings.
|
||||||
|
// Re-uses existing credentials if already configured; otherwise generates random ones.
|
||||||
|
private fun ensureInboundAuth(xrayConfig: JSONObject) {
|
||||||
|
val inbounds = xrayConfig.optJSONArray("inbounds") ?: return
|
||||||
|
val socksIdx = findSocksInboundIndex(inbounds)
|
||||||
|
if (socksIdx < 0) return
|
||||||
|
|
||||||
|
val inbound = inbounds.getJSONObject(socksIdx)
|
||||||
|
inbound.put("port", acquireFreeLocalPort())
|
||||||
|
val settings = inbound.optJSONObject("settings") ?: JSONObject().also { inbound.put("settings", it) }
|
||||||
|
val accounts = settings.optJSONArray("accounts")
|
||||||
|
if (accounts != null && accounts.length() > 0) {
|
||||||
|
val account = accounts.getJSONObject(0)
|
||||||
|
if (account.optString("user").isNotEmpty() && account.optString("pass").isNotEmpty()) {
|
||||||
|
// Ensure auth mode is enforced even for imported configs that had accounts
|
||||||
|
// but auth: "noauth" (or no auth field).
|
||||||
|
settings.put("auth", "password")
|
||||||
|
inbound.put("settings", settings)
|
||||||
|
inbounds.put(socksIdx, inbound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val user = UUID.randomUUID().toString().replace("-", "").substring(0, 16)
|
||||||
|
val pass = UUID.randomUUID().toString().replace("-", "")
|
||||||
|
settings.put("auth", "password")
|
||||||
|
settings.put("accounts", JSONArray().put(JSONObject().put("user", user).put("pass", pass)))
|
||||||
|
inbound.put("settings", settings)
|
||||||
|
inbounds.put(socksIdx, inbound)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val instance: Xray by lazy { Xray() }
|
val instance: Xray by lazy { Xray() }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,12 +9,16 @@ private const val XRAY_DEFAULT_MAX_MEMORY: Long = 50 shl 20 // 50 MB
|
|||||||
class XrayConfig protected constructor(
|
class XrayConfig protected constructor(
|
||||||
protocolConfigBuilder: ProtocolConfig.Builder,
|
protocolConfigBuilder: ProtocolConfig.Builder,
|
||||||
val socksPort: Int,
|
val socksPort: Int,
|
||||||
|
val socksUser: String,
|
||||||
|
val socksPass: String,
|
||||||
val maxMemory: Long,
|
val maxMemory: Long,
|
||||||
) : ProtocolConfig(protocolConfigBuilder) {
|
) : ProtocolConfig(protocolConfigBuilder) {
|
||||||
|
|
||||||
protected constructor(builder: Builder) : this(
|
protected constructor(builder: Builder) : this(
|
||||||
builder,
|
builder,
|
||||||
builder.socksPort,
|
builder.socksPort,
|
||||||
|
builder.socksUser,
|
||||||
|
builder.socksPass,
|
||||||
builder.maxMemory
|
builder.maxMemory
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -22,6 +26,12 @@ class XrayConfig protected constructor(
|
|||||||
internal var socksPort: Int = 0
|
internal var socksPort: Int = 0
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
internal var socksUser: String = ""
|
||||||
|
private set
|
||||||
|
|
||||||
|
internal var socksPass: String = ""
|
||||||
|
private set
|
||||||
|
|
||||||
internal var maxMemory: Long = XRAY_DEFAULT_MAX_MEMORY
|
internal var maxMemory: Long = XRAY_DEFAULT_MAX_MEMORY
|
||||||
private set
|
private set
|
||||||
|
|
||||||
@@ -29,6 +39,10 @@ class XrayConfig protected constructor(
|
|||||||
|
|
||||||
fun setSocksPort(port: Int) = apply { socksPort = port }
|
fun setSocksPort(port: Int) = apply { socksPort = port }
|
||||||
|
|
||||||
|
fun setSocksUser(user: String) = apply { socksUser = user }
|
||||||
|
|
||||||
|
fun setSocksPass(pass: String) = apply { socksPass = pass }
|
||||||
|
|
||||||
fun setMaxMemory(maxMemory: Long) = apply { this.maxMemory = maxMemory }
|
fun setMaxMemory(maxMemory: Long) = apply { this.maxMemory = maxMemory }
|
||||||
|
|
||||||
override fun build(): XrayConfig = configBuild().run { XrayConfig(this@Builder) }
|
override fun build(): XrayConfig = configBuild().run { XrayConfig(this@Builder) }
|
||||||
|
|||||||
@@ -27,12 +27,18 @@ if(WIN32)
|
|||||||
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/windows/win32/libcrypto.lib")
|
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/windows/win32/libcrypto.lib")
|
||||||
endif()
|
endif()
|
||||||
elseif(APPLE AND NOT IOS)
|
elseif(APPLE AND NOT IOS)
|
||||||
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libssh.a")
|
if(MACOS_NE)
|
||||||
set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libz.a")
|
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/universal2/libssh.a")
|
||||||
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/macos/x86_64")
|
set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/universal2/libz.a")
|
||||||
|
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/macos/universal2")
|
||||||
|
else()
|
||||||
|
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libssh.a")
|
||||||
|
set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libz.a")
|
||||||
|
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/macos/x86_64")
|
||||||
|
endif()
|
||||||
set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/macos/include")
|
set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/macos/include")
|
||||||
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libssl.a")
|
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libssl.a")
|
||||||
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libcrypto.a")
|
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libcrypto.a")
|
||||||
elseif(IOS)
|
elseif(IOS)
|
||||||
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/ios/arm64")
|
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/ios/arm64")
|
||||||
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/ios/arm64/libssh.a")
|
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/ios/arm64/libssh.a")
|
||||||
@@ -56,7 +62,7 @@ elseif(LINUX)
|
|||||||
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/linux/x86_64/libssl.a")
|
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/linux/x86_64/libssl.a")
|
||||||
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/linux/x86_64/libcrypto.a")
|
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/linux/x86_64/libcrypto.a")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
file(COPY ${OPENSSL_LIB_SSL_PATH} ${OPENSSL_LIB_CRYPTO_PATH}
|
file(COPY ${OPENSSL_LIB_SSL_PATH} ${OPENSSL_LIB_CRYPTO_PATH}
|
||||||
DESTINATION ${OPENSSL_LIBRARIES_DIR})
|
DESTINATION ${OPENSSL_LIBRARIES_DIR})
|
||||||
|
|
||||||
@@ -77,6 +83,26 @@ add_compile_definitions(_WINSOCKAPI_)
|
|||||||
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
|
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
|
||||||
set(BUILD_WITH_QT6 ON)
|
set(BUILD_WITH_QT6 ON)
|
||||||
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtkeychain)
|
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtkeychain)
|
||||||
|
|
||||||
|
if(ANDROID)
|
||||||
|
# Use qtgamepad from amnezia-vpn/qtgamepad repository
|
||||||
|
# Only if Qt6CorePrivate is available (required by qtgamepad)
|
||||||
|
find_package(Qt6CorePrivate CONFIG QUIET)
|
||||||
|
if(Qt6CorePrivate_FOUND)
|
||||||
|
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtgamepad)
|
||||||
|
# Link both the C++ module and QML plugin
|
||||||
|
if(TARGET GamepadLegacy)
|
||||||
|
target_link_libraries(${PROJECT} PRIVATE GamepadLegacy)
|
||||||
|
endif()
|
||||||
|
if(TARGET GamepadLegacyQuickPrivate)
|
||||||
|
target_link_libraries(${PROJECT} PRIVATE GamepadLegacyQuickPrivate)
|
||||||
|
endif()
|
||||||
|
message(STATUS "Gamepad support enabled for Android")
|
||||||
|
else()
|
||||||
|
message(STATUS "Qt6CorePrivate not found. Gamepad support disabled for Android.")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
set(LIBS ${LIBS} qt6keychain)
|
set(LIBS ${LIBS} qt6keychain)
|
||||||
|
|
||||||
include_directories(
|
include_directories(
|
||||||
|
|||||||
@@ -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 26)
|
set(APP_ANDROID_MIN_SDK 28)
|
||||||
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 34
|
QT_ANDROID_TARGET_SDK_VERSION 36
|
||||||
QT_ANDROID_SDK_BUILD_TOOLS_REVISION 34.0.0
|
QT_ANDROID_SDK_BUILD_TOOLS_REVISION 36.0.0
|
||||||
QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android
|
QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -20,7 +20,11 @@ set(QT_ANDROID_MULTI_ABI_FORWARD_VARS "QT_NO_GLOBAL_APK_TARGET_PART_OF_ALL;CMAKE
|
|||||||
|
|
||||||
# We need to include qtprivate api's
|
# We need to include qtprivate api's
|
||||||
# As QAndroidBinder is not yet implemented with a public api
|
# As QAndroidBinder is not yet implemented with a public api
|
||||||
set(LIBS ${LIBS} Qt6::CorePrivate -ljnigraphics)
|
# Check if Qt6::CorePrivate is available (may not be in all Qt versions/configurations)
|
||||||
|
if(TARGET Qt6::CorePrivate)
|
||||||
|
set(LIBS ${LIBS} Qt6::CorePrivate)
|
||||||
|
endif()
|
||||||
|
set(LIBS ${LIBS} -ljnigraphics)
|
||||||
|
|
||||||
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/platforms/android)
|
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/platforms/android)
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ set(HEADERS ${HEADERS}
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.h
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.h
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.h
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate-C-Interface.h
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate-C-Interface.h
|
||||||
)
|
)
|
||||||
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h PROPERTIES OBJECTIVE_CPP_HEADER TRUE)
|
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h PROPERTIES OBJECTIVE_CPP_HEADER TRUE)
|
||||||
@@ -46,6 +47,8 @@ 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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -76,8 +79,22 @@ set_target_properties(${PROJECT} PROPERTIES
|
|||||||
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
|
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
|
||||||
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/Frameworks"
|
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/Frameworks"
|
||||||
XCODE_EMBED_APP_EXTENSIONS networkextension
|
XCODE_EMBED_APP_EXTENSIONS networkextension
|
||||||
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(DEFINED DEPLOY)
|
||||||
|
set_target_properties(${PROJECT} PROPERTIES
|
||||||
|
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
|
||||||
|
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
|
||||||
|
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
|
||||||
|
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "distr ios.org.amnezia.AmneziaVPN"
|
||||||
|
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "dev ios.org.amnezia.AmneziaVPN"
|
||||||
|
)
|
||||||
|
else()
|
||||||
|
set_target_properties(${PROJECT} PROPERTIES
|
||||||
|
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
set_target_properties(${PROJECT} PROPERTIES
|
set_target_properties(${PROJECT} PROPERTIES
|
||||||
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
|
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
|
||||||
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES"
|
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES"
|
||||||
@@ -104,6 +121,7 @@ target_sources(${PROJECT} PRIVATE
|
|||||||
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
|
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
|
||||||
${CLIENT_ROOT_DIR}/platforms/ios/ScreenProtection.swift
|
${CLIENT_ROOT_DIR}/platforms/ios/ScreenProtection.swift
|
||||||
${CLIENT_ROOT_DIR}/platforms/ios/VPNCController.swift
|
${CLIENT_ROOT_DIR}/platforms/ios/VPNCController.swift
|
||||||
|
${CLIENT_ROOT_DIR}/platforms/ios/StoreKit2Helper.swift
|
||||||
)
|
)
|
||||||
|
|
||||||
target_sources(${PROJECT} PRIVATE
|
target_sources(${PROJECT} PRIVATE
|
||||||
|
|||||||
@@ -14,11 +14,15 @@ set(LIBS ${LIBS}
|
|||||||
${FW_SECURITY}
|
${FW_SECURITY}
|
||||||
${FW_COREWLAN}
|
${FW_COREWLAN}
|
||||||
${FW_NETWORK}
|
${FW_NETWORK}
|
||||||
${FW_USERNOTIFICATIONS}
|
${FW_USER_NOTIFICATIONS}
|
||||||
${FW_NETWORK_EXTENSION}
|
${FW_NETWORK_EXTENSION}
|
||||||
)
|
)
|
||||||
|
|
||||||
set_target_properties(${PROJECT} PROPERTIES MACOSX_BUNDLE TRUE)
|
set_target_properties(${PROJECT} PROPERTIES
|
||||||
|
MACOSX_BUNDLE TRUE
|
||||||
|
MACOSX_BUNDLE_SHORT_VERSION_STRING "${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}"
|
||||||
|
MACOSX_BUNDLE_BUNDLE_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
|
||||||
|
)
|
||||||
set(CMAKE_OSX_ARCHITECTURES "x86_64" CACHE INTERNAL "" FORCE)
|
set(CMAKE_OSX_ARCHITECTURES "x86_64" CACHE INTERNAL "" FORCE)
|
||||||
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15)
|
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15)
|
||||||
|
|
||||||
@@ -31,6 +35,8 @@ set(SOURCES ${SOURCES}
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/ui/macos_util.mm
|
${CMAKE_CURRENT_SOURCE_DIR}/ui/macos_util.mm
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
set(ICON_FILE ${CMAKE_CURRENT_SOURCE_DIR}/images/app.icns)
|
set(ICON_FILE ${CMAKE_CURRENT_SOURCE_DIR}/images/app.icns)
|
||||||
set(MACOSX_BUNDLE_ICON_FILE app.icns)
|
set(MACOSX_BUNDLE_ICON_FILE app.icns)
|
||||||
set_source_files_properties(${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
|
set_source_files_properties(${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
|
||||||
@@ -49,4 +55,3 @@ execute_process(
|
|||||||
)
|
)
|
||||||
message("OSX_SDK_PATH is: ${OSX_SDK_PATH}")
|
message("OSX_SDK_PATH is: ${OSX_SDK_PATH}")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,171 @@
|
|||||||
|
message("Client ==> MacOS NE build")
|
||||||
|
|
||||||
|
set_target_properties(${PROJECT} PROPERTIES MACOSX_BUNDLE TRUE)
|
||||||
|
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15)
|
||||||
|
|
||||||
|
set(APPLE_PROJECT_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||||
|
|
||||||
|
enable_language(OBJC)
|
||||||
|
enable_language(Swift)
|
||||||
|
|
||||||
|
find_package(Qt6 REQUIRED COMPONENTS ShaderTools Widgets)
|
||||||
|
# Link Qt Widgets for QWidget, QMenu, QAction etc.
|
||||||
|
set(LIBS ${LIBS} Qt6::ShaderTools Qt6::Widgets)
|
||||||
|
|
||||||
|
find_library(FW_AUTHENTICATIONSERVICES AuthenticationServices)
|
||||||
|
find_library(FW_AVFOUNDATION AVFoundation)
|
||||||
|
find_library(FW_FOUNDATION Foundation)
|
||||||
|
find_library(FW_STOREKIT StoreKit)
|
||||||
|
find_library(FW_SERVICEMGMT ServiceManagement)
|
||||||
|
find_library(FW_USERNOTIFICATIONS UserNotifications)
|
||||||
|
find_library(FW_NETWORKEXTENSION NetworkExtension)
|
||||||
|
|
||||||
|
set(LIBS ${LIBS}
|
||||||
|
${FW_AUTHENTICATIONSERVICES}
|
||||||
|
${FW_AVFOUNDATION}
|
||||||
|
${FW_FOUNDATION}
|
||||||
|
${FW_STOREKIT}
|
||||||
|
${FW_SERVICEMGMT}
|
||||||
|
${FW_USERNOTIFICATIONS}
|
||||||
|
${FW_NETWORKEXTENSION}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
set(HEADERS ${HEADERS}
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate-C-Interface.h
|
||||||
|
)
|
||||||
|
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h PROPERTIES OBJECTIVE_CPP_HEADER TRUE)
|
||||||
|
|
||||||
|
|
||||||
|
set(SOURCES ${SOURCES}
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.mm
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.mm
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.mm
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.mm
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
|
||||||
|
)
|
||||||
|
|
||||||
|
set(ICON_FILE ${CMAKE_CURRENT_SOURCE_DIR}/images/app.icns)
|
||||||
|
set(MACOSX_BUNDLE_ICON_FILE app.icns)
|
||||||
|
set_source_files_properties(${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
|
||||||
|
set(SOURCES ${SOURCES} ${ICON_FILE})
|
||||||
|
|
||||||
|
|
||||||
|
target_include_directories(${PROJECT} PRIVATE
|
||||||
|
${Qt6Gui_PRIVATE_INCLUDE_DIRS}
|
||||||
|
${Qt6Widgets_PRIVATE_INCLUDE_DIRS}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
set_target_properties(${PROJECT} PROPERTIES
|
||||||
|
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
|
||||||
|
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Info.plist.in
|
||||||
|
MACOSX_BUNDLE_ICON_FILE "AppIcon"
|
||||||
|
MACOSX_BUNDLE_INFO_STRING "AmneziaVPN"
|
||||||
|
MACOSX_BUNDLE_BUNDLE_NAME "AmneziaVPN"
|
||||||
|
MACOSX_BUNDLE_BUNDLE_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
|
||||||
|
MACOSX_BUNDLE_LONG_VERSION_STRING "${APPLE_PROJECT_VERSION}-${CMAKE_PROJECT_VERSION_TWEAK}"
|
||||||
|
MACOSX_BUNDLE_SHORT_VERSION_STRING "${APPLE_PROJECT_VERSION}"
|
||||||
|
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}"
|
||||||
|
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/macos/app/app.entitlements"
|
||||||
|
XCODE_ATTRIBUTE_MARKETING_VERSION "${APPLE_PROJECT_VERSION}"
|
||||||
|
XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
|
||||||
|
XCODE_ATTRIBUTE_PRODUCT_NAME "AmneziaVPN"
|
||||||
|
XCODE_ATTRIBUTE_BUNDLE_INFO_STRING "AmneziaVPN"
|
||||||
|
XCODE_GENERATE_SCHEME TRUE
|
||||||
|
XCODE_ATTRIBUTE_ENABLE_BITCODE "NO"
|
||||||
|
XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME "AppIcon"
|
||||||
|
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2"
|
||||||
|
XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY "NO"
|
||||||
|
XCODE_EMBED_FRAMEWORKS_REMOVE_HEADERS_ON_COPY "YES"
|
||||||
|
XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET "11.0"
|
||||||
|
|
||||||
|
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
|
||||||
|
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../Frameworks"
|
||||||
|
XCODE_EMBED_APP_EXTENSIONS AmneziaVPNNetworkExtension
|
||||||
|
)
|
||||||
|
|
||||||
|
if(DEPLOY)
|
||||||
|
set_target_properties(${PROJECT} PROPERTIES
|
||||||
|
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
|
||||||
|
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
|
||||||
|
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
|
||||||
|
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "distr macos.org.amnezia.AmneziaVPN"
|
||||||
|
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "dev macos.org.amnezia.AmneziaVPN"
|
||||||
|
)
|
||||||
|
else()
|
||||||
|
set_target_properties(${PROJECT} PROPERTIES
|
||||||
|
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set_target_properties(${PROJECT} PROPERTIES
|
||||||
|
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
|
||||||
|
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES"
|
||||||
|
XCODE_ATTRIBUTE_SWIFT_PRECOMPILE_BRIDGING_HEADER "NO"
|
||||||
|
XCODE_ATTRIBUTE_SWIFT_OBJC_INTERFACE_HEADER_NAME "AmneziaVPN-Swift.h"
|
||||||
|
XCODE_ATTRIBUTE_SWIFT_OBJC_INTEROP_MODE "objcxx"
|
||||||
|
)
|
||||||
|
set_target_properties(${PROJECT} PROPERTIES
|
||||||
|
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "X7UJ388FXK"
|
||||||
|
)
|
||||||
|
target_include_directories(${PROJECT} PRIVATE ${CMAKE_CURRENT_LIST_DIR})
|
||||||
|
target_compile_options(${PROJECT} PRIVATE
|
||||||
|
-DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\"
|
||||||
|
-DVPN_NE_BUNDLEID=\"${BUILD_IOS_APP_IDENTIFIER}.network-extension\"
|
||||||
|
)
|
||||||
|
|
||||||
|
set(WG_APPLE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rd/amneziawg-apple/Sources)
|
||||||
|
|
||||||
|
target_sources(${PROJECT} PRIVATE
|
||||||
|
${WG_APPLE_SOURCE_DIR}/WireGuardKitC/x25519.c
|
||||||
|
${CLIENT_ROOT_DIR}/platforms/ios/LogController.swift
|
||||||
|
${CLIENT_ROOT_DIR}/platforms/ios/Log.swift
|
||||||
|
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
|
||||||
|
${CLIENT_ROOT_DIR}/platforms/ios/ScreenProtection.swift
|
||||||
|
${CLIENT_ROOT_DIR}/platforms/ios/VPNCController.swift
|
||||||
|
${CLIENT_ROOT_DIR}/platforms/ios/StoreKit2Helper.swift
|
||||||
|
)
|
||||||
|
|
||||||
|
target_sources(${PROJECT} PRIVATE
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Images.xcassets
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
|
||||||
|
)
|
||||||
|
|
||||||
|
set_property(TARGET ${PROJECT} APPEND PROPERTY RESOURCE
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Images.xcassets
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
|
||||||
|
)
|
||||||
|
|
||||||
|
add_subdirectory(macos/networkextension)
|
||||||
|
add_dependencies(${PROJECT} AmneziaVPNNetworkExtension)
|
||||||
|
|
||||||
|
get_target_property(QtCore_location Qt6::Core LOCATION)
|
||||||
|
message("QtCore_location")
|
||||||
|
message(${QtCore_location})
|
||||||
|
|
||||||
|
get_filename_component(QT_BIN_DIR_DETECTED "${QtCore_location}/../../../../../bin" ABSOLUTE)
|
||||||
|
|
||||||
|
set_property(TARGET ${PROJECT} PROPERTY XCODE_EMBED_FRAMEWORKS
|
||||||
|
"${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-macos/OpenVPNAdapter.framework"
|
||||||
|
)
|
||||||
|
|
||||||
|
set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-macos)
|
||||||
|
target_link_libraries("AmneziaVPNNetworkExtension" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-macos/OpenVPNAdapter.framework")
|
||||||
|
|
||||||
|
add_custom_command(TARGET ${PROJECT} POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E make_directory
|
||||||
|
$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks
|
||||||
|
COMMAND /usr/bin/find "$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework" -name "*.sha256" -delete
|
||||||
|
COMMAND /usr/bin/codesign --force --sign "Apple Distribution: Privacy Technologies OU"
|
||||||
|
"$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework/Versions/Current/OpenVPNAdapter"
|
||||||
|
COMMAND ${QT_BIN_DIR_DETECTED}/macdeployqt $<TARGET_BUNDLE_DIR:AmneziaVPN> -appstore-compliant -qmldir=${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
COMMENT "Signing OpenVPNAdapter framework"
|
||||||
|
)
|
||||||
@@ -28,6 +28,7 @@ set(HEADERS ${HEADERS}
|
|||||||
${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
|
||||||
@@ -36,10 +37,9 @@ 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)
|
if(NOT IOS AND NOT MACOS_NE)
|
||||||
set(HEADERS ${HEADERS}
|
set(HEADERS ${HEADERS}
|
||||||
${CLIENT_ROOT_DIR}/platforms/ios/QRCodeReaderBase.h
|
${CLIENT_ROOT_DIR}/platforms/ios/QRCodeReaderBase.h
|
||||||
)
|
)
|
||||||
@@ -79,6 +79,7 @@ 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
|
||||||
@@ -86,15 +87,28 @@ 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)
|
if(NOT IOS AND NOT MACOS_NE)
|
||||||
set(SOURCES ${SOURCES}
|
set(SOURCES ${SOURCES}
|
||||||
${CLIENT_ROOT_DIR}/platforms/ios/QRCodeReaderBase.cpp
|
${CLIENT_ROOT_DIR}/platforms/ios/QRCodeReaderBase.cpp
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# Include native macOS platform helpers (dock/status-item)
|
||||||
|
if(APPLE AND NOT IOS)
|
||||||
|
list(APPEND HEADERS
|
||||||
|
${CLIENT_ROOT_DIR}/platforms/macos/macosutils.h
|
||||||
|
${CLIENT_ROOT_DIR}/platforms/macos/macosstatusicon.h
|
||||||
|
${CLIENT_ROOT_DIR}/ui/macos_util.h
|
||||||
|
)
|
||||||
|
list(APPEND SOURCES
|
||||||
|
${CLIENT_ROOT_DIR}/platforms/macos/macosutils.mm
|
||||||
|
${CLIENT_ROOT_DIR}/platforms/macos/macosstatusicon.mm
|
||||||
|
${CLIENT_ROOT_DIR}/ui/macos_util.mm
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
if(NOT ANDROID)
|
if(NOT ANDROID)
|
||||||
set(SOURCES ${SOURCES}
|
set(SOURCES ${SOURCES}
|
||||||
${CLIENT_ROOT_DIR}/ui/notificationhandler.cpp
|
${CLIENT_ROOT_DIR}/ui/notificationhandler.cpp
|
||||||
@@ -161,13 +175,12 @@ if(WIN32)
|
|||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
|
||||||
message("Client desktop build")
|
message("Client desktop build")
|
||||||
add_compile_definitions(AMNEZIA_DESKTOP)
|
add_compile_definitions(AMNEZIA_DESKTOP)
|
||||||
|
|
||||||
set(HEADERS ${HEADERS}
|
set(HEADERS ${HEADERS}
|
||||||
${CLIENT_ROOT_DIR}/core/ipcclient.h
|
${CLIENT_ROOT_DIR}/core/ipcclient.h
|
||||||
${CLIENT_ROOT_DIR}/core/privileged_process.h
|
|
||||||
${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.h
|
${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.h
|
||||||
${CLIENT_ROOT_DIR}/protocols/openvpnprotocol.h
|
${CLIENT_ROOT_DIR}/protocols/openvpnprotocol.h
|
||||||
${CLIENT_ROOT_DIR}/protocols/openvpnovercloakprotocol.h
|
${CLIENT_ROOT_DIR}/protocols/openvpnovercloakprotocol.h
|
||||||
@@ -175,11 +188,12 @@ if(WIN32 OR (APPLE AND NOT IOS) 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}/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
|
||||||
@@ -189,3 +203,14 @@ if(WIN32 OR (APPLE AND NOT IOS) 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()
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "awg_configurator.h"
|
#include "awg_configurator.h"
|
||||||
|
#include "protocols/protocols_defs.h"
|
||||||
|
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
@@ -39,6 +40,18 @@ QString AwgConfigurator::createConfig(const ServerCredentials &credentials, Dock
|
|||||||
jsonConfig[config_key::responsePacketMagicHeader] = configMap.value(config_key::responsePacketMagicHeader);
|
jsonConfig[config_key::responsePacketMagicHeader] = configMap.value(config_key::responsePacketMagicHeader);
|
||||||
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::transportPacketJunkSize] = configMap.value(config_key::transportPacketJunkSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonConfig[config_key::specialJunk1] = configMap.value(amnezia::config_key::specialJunk1);
|
||||||
|
jsonConfig[config_key::specialJunk2] = configMap.value(amnezia::config_key::specialJunk2);
|
||||||
|
jsonConfig[config_key::specialJunk3] = configMap.value(amnezia::config_key::specialJunk3);
|
||||||
|
jsonConfig[config_key::specialJunk4] = configMap.value(amnezia::config_key::specialJunk4);
|
||||||
|
jsonConfig[config_key::specialJunk5] = configMap.value(amnezia::config_key::specialJunk5);
|
||||||
|
|
||||||
jsonConfig[config_key::mtu] =
|
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);
|
||||||
|
|
||||||
|
|||||||
@@ -13,10 +13,10 @@
|
|||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "core/networkUtilities.h"
|
||||||
#include "containers/containers_defs.h"
|
#include "containers/containers_defs.h"
|
||||||
#include "core/controllers/serverController.h"
|
#include "core/controllers/serverController.h"
|
||||||
#include "core/scripts_registry.h"
|
#include "core/scripts_registry.h"
|
||||||
#include "core/server_defs.h"
|
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "utilities.h"
|
#include "utilities.h"
|
||||||
|
|
||||||
@@ -24,6 +24,7 @@
|
|||||||
#include <openssl/rsa.h>
|
#include <openssl/rsa.h>
|
||||||
#include <openssl/x509.h>
|
#include <openssl/x509.h>
|
||||||
|
|
||||||
|
|
||||||
OpenVpnConfigurator::OpenVpnConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController,
|
OpenVpnConfigurator::OpenVpnConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController,
|
||||||
QObject *parent)
|
QObject *parent)
|
||||||
: ConfiguratorBase(settings, serverController, parent)
|
: ConfiguratorBase(settings, serverController, parent)
|
||||||
@@ -82,12 +83,30 @@ 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", connData.taKey);
|
config.replace("$OPENVPN_TA_KEY", sanitizeStaticKey(connData.taKey));
|
||||||
} else {
|
} else {
|
||||||
config.replace("<tls-auth>", "");
|
config.replace("<tls-auth>", "");
|
||||||
config.replace("</tls-auth>", "");
|
config.replace("</tls-auth>", "");
|
||||||
@@ -116,23 +135,23 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPair<QString,
|
|||||||
if (!isApiConfig) {
|
if (!isApiConfig) {
|
||||||
QRegularExpression regex("redirect-gateway.*");
|
QRegularExpression regex("redirect-gateway.*");
|
||||||
config.replace(regex, "");
|
config.replace(regex, "");
|
||||||
|
|
||||||
|
// We don't use secondary DNS if primary DNS is AmneziaDNS
|
||||||
|
if (dns.first.contains(protocols::dns::amneziaDnsIp)) {
|
||||||
|
QRegularExpression dnsRegex("dhcp-option DNS " + dns.second);
|
||||||
|
config.replace(dnsRegex, "");
|
||||||
|
}
|
||||||
|
|
||||||
if (!m_settings->isSitesSplitTunnelingEnabled()) {
|
if (!m_settings->isSitesSplitTunnelingEnabled()) {
|
||||||
config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n");
|
config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n");
|
||||||
|
|
||||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
|
||||||
// Prevent ipv6 leak
|
|
||||||
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
|
|
||||||
#endif
|
|
||||||
config.append("block-ipv6\n");
|
config.append("block-ipv6\n");
|
||||||
} else if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
|
} else if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
|
||||||
|
|
||||||
// no redirect-gateway
|
// no redirect-gateway
|
||||||
} else if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
|
} else if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
|
||||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||||
config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n");
|
config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n");
|
||||||
// Prevent ipv6 leak
|
// Prevent ipv6 leak
|
||||||
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
|
|
||||||
#endif
|
#endif
|
||||||
config.append("block-ipv6\n");
|
config.append("block-ipv6\n");
|
||||||
}
|
}
|
||||||
@@ -166,10 +185,15 @@ QString OpenVpnConfigurator::processConfigWithExportSettings(const QPair<QString
|
|||||||
QRegularExpression regex("redirect-gateway.*");
|
QRegularExpression regex("redirect-gateway.*");
|
||||||
config.replace(regex, "");
|
config.replace(regex, "");
|
||||||
|
|
||||||
|
// We don't use secondary DNS if primary DNS is AmneziaDNS
|
||||||
|
if (dns.first.contains(protocols::dns::amneziaDnsIp)) {
|
||||||
|
QRegularExpression dnsRegex("dhcp-option DNS " + dns.second);
|
||||||
|
config.replace(dnsRegex, "");
|
||||||
|
}
|
||||||
|
|
||||||
config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n");
|
config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n");
|
||||||
|
|
||||||
// Prevent ipv6 leak
|
// Prevent ipv6 leak
|
||||||
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
|
|
||||||
config.append("block-ipv6\n");
|
config.append("block-ipv6\n");
|
||||||
|
|
||||||
// remove block-outside-dns for all exported configs
|
// remove block-outside-dns for all exported configs
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
#include <QTemporaryFile>
|
#include <QTemporaryFile>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include <qtimer.h>
|
#include <qtimer.h>
|
||||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE)
|
||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
#else
|
#else
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
@@ -24,7 +24,7 @@ SshConfigurator::SshConfigurator(std::shared_ptr<Settings> settings, const QShar
|
|||||||
|
|
||||||
QString SshConfigurator::convertOpenSShKey(const QString &key)
|
QString SshConfigurator::convertOpenSShKey(const QString &key)
|
||||||
{
|
{
|
||||||
#ifndef Q_OS_IOS
|
#if !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||||
QProcess p;
|
QProcess p;
|
||||||
p.setProcessChannelMode(QProcess::MergedChannels);
|
p.setProcessChannelMode(QProcess::MergedChannels);
|
||||||
|
|
||||||
@@ -67,9 +67,10 @@ QString SshConfigurator::convertOpenSShKey(const QString &key)
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DEAD CODE.
|
||||||
void SshConfigurator::openSshTerminal(const ServerCredentials &credentials)
|
void SshConfigurator::openSshTerminal(const ServerCredentials &credentials)
|
||||||
{
|
{
|
||||||
#ifndef Q_OS_IOS
|
#if !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||||
QProcess *p = new QProcess();
|
QProcess *p = new QProcess();
|
||||||
p->setProcessChannelMode(QProcess::SeparateChannels);
|
p->setProcessChannelMode(QProcess::SeparateChannels);
|
||||||
|
|
||||||
@@ -101,7 +102,7 @@ QProcessEnvironment SshConfigurator::prepareEnv()
|
|||||||
pathEnvVar.clear();
|
pathEnvVar.clear();
|
||||||
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\cygwin;");
|
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\cygwin;");
|
||||||
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\openvpn;");
|
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\openvpn;");
|
||||||
#elif defined(Q_OS_MACX)
|
#elif defined(Q_OS_MACX) && !defined(MACOS_NE)
|
||||||
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "/Contents/MacOS");
|
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "/Contents/MacOS");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
|
#include <QRegularExpression>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QTemporaryDir>
|
#include <QTemporaryDir>
|
||||||
#include <QTemporaryFile>
|
#include <QTemporaryFile>
|
||||||
@@ -19,13 +20,17 @@
|
|||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "utilities.h"
|
#include "utilities.h"
|
||||||
|
|
||||||
WireguardConfigurator::WireguardConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController,
|
WireguardConfigurator::WireguardConfigurator(std::shared_ptr<Settings> settings,
|
||||||
bool isAwg, QObject *parent)
|
const QSharedPointer<ServerController> &serverController, bool isAwg,
|
||||||
|
QObject *parent)
|
||||||
: ConfiguratorBase(settings, serverController, parent), m_isAwg(isAwg)
|
: ConfiguratorBase(settings, serverController, parent), m_isAwg(isAwg)
|
||||||
{
|
{
|
||||||
m_serverConfigPath = m_isAwg ? amnezia::protocols::awg::serverConfigPath : amnezia::protocols::wireguard::serverConfigPath;
|
m_serverConfigPath =
|
||||||
m_serverPublicKeyPath = m_isAwg ? amnezia::protocols::awg::serverPublicKeyPath : amnezia::protocols::wireguard::serverPublicKeyPath;
|
m_isAwg ? amnezia::protocols::awg::serverConfigPath : amnezia::protocols::wireguard::serverConfigPath;
|
||||||
m_serverPskKeyPath = m_isAwg ? amnezia::protocols::awg::serverPskKeyPath : amnezia::protocols::wireguard::serverPskKeyPath;
|
m_serverPublicKeyPath =
|
||||||
|
m_isAwg ? amnezia::protocols::awg::serverPublicKeyPath : amnezia::protocols::wireguard::serverPublicKeyPath;
|
||||||
|
m_serverPskKeyPath =
|
||||||
|
m_isAwg ? amnezia::protocols::awg::serverPskKeyPath : amnezia::protocols::wireguard::serverPskKeyPath;
|
||||||
m_configTemplate = m_isAwg ? ProtocolScriptType::awg_template : ProtocolScriptType::wireguard_template;
|
m_configTemplate = m_isAwg ? ProtocolScriptType::awg_template : ProtocolScriptType::wireguard_template;
|
||||||
|
|
||||||
m_protocolName = m_isAwg ? config_key::awg : config_key::wireguard;
|
m_protocolName = m_isAwg ? config_key::awg : config_key::wireguard;
|
||||||
@@ -63,9 +68,31 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::genClientKeys()
|
|||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QList<QHostAddress> WireguardConfigurator::getIpsFromConf(const QString &input)
|
||||||
|
{
|
||||||
|
QRegularExpression regex("AllowedIPs = (\\d+\\.\\d+\\.\\d+\\.\\d+)");
|
||||||
|
QRegularExpressionMatchIterator matchIterator = regex.globalMatch(input);
|
||||||
|
|
||||||
|
QList<QHostAddress> ips;
|
||||||
|
|
||||||
|
while (matchIterator.hasNext()) {
|
||||||
|
QRegularExpressionMatch match = matchIterator.next();
|
||||||
|
const QString address_string { match.captured(1) };
|
||||||
|
const QHostAddress address { address_string };
|
||||||
|
if (address.isNull()) {
|
||||||
|
qWarning() << "Couldn't recognize the ip address: " << address_string;
|
||||||
|
} else {
|
||||||
|
ips << address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ips;
|
||||||
|
}
|
||||||
|
|
||||||
WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardConfig(const ServerCredentials &credentials,
|
WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardConfig(const ServerCredentials &credentials,
|
||||||
DockerContainer container,
|
DockerContainer container,
|
||||||
const QJsonObject &containerConfig, ErrorCode &errorCode)
|
const QJsonObject &containerConfig,
|
||||||
|
ErrorCode &errorCode)
|
||||||
{
|
{
|
||||||
WireguardConfigurator::ConnectionData connData = WireguardConfigurator::genClientKeys();
|
WireguardConfigurator::ConnectionData connData = WireguardConfigurator::genClientKeys();
|
||||||
connData.host = credentials.hostName;
|
connData.host = credentials.hostName;
|
||||||
@@ -76,65 +103,49 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
|
|||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get list of already created clients (only IP addresses)
|
QString configPath = m_serverConfigPath;
|
||||||
QString nextIpNumber;
|
if (container == DockerContainer::Awg) {
|
||||||
{
|
configPath = amnezia::protocols::awg::serverLegacyConfigPath;
|
||||||
QString script = QString("cat %1 | grep AllowedIPs").arg(m_serverConfigPath);
|
}
|
||||||
QString stdOut;
|
QString getIpsScript = QString("cat %1 | grep AllowedIPs").arg(configPath);
|
||||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
QString stdOut;
|
||||||
stdOut += data + "\n";
|
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||||
return ErrorCode::NoError;
|
stdOut += data + "\n";
|
||||||
};
|
return ErrorCode::NoError;
|
||||||
|
};
|
||||||
|
|
||||||
errorCode = m_serverController->runContainerScript(credentials, container, script, cbReadStdOut);
|
errorCode = m_serverController->runContainerScript(credentials, container, getIpsScript, cbReadStdOut);
|
||||||
if (errorCode != ErrorCode::NoError) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
auto ips = getIpsFromConf(stdOut);
|
||||||
|
|
||||||
stdOut.replace("AllowedIPs = ", "");
|
QHostAddress nextIp = [&] {
|
||||||
stdOut.replace("/32", "");
|
QHostAddress result;
|
||||||
QStringList ips = stdOut.split("\n", Qt::SkipEmptyParts);
|
QHostAddress lastIp;
|
||||||
|
if (ips.empty()) {
|
||||||
// remove extra IPs from each line for case when user manually edited the wg0.conf
|
lastIp.setAddress(containerConfig.value(m_protocolName)
|
||||||
// and added there more IPs for route his itnernal networks, like:
|
.toObject()
|
||||||
// ...
|
.value(config_key::subnet_address)
|
||||||
// AllowedIPs = 10.8.1.6/32, 192.168.1.0/24, 192.168.2.0/24, ...
|
.toString(protocols::wireguard::defaultSubnetAddress));
|
||||||
// ...
|
|
||||||
// without this code - next IP would be 1 if last item in 'ips' has format above
|
|
||||||
QStringList vpnIps;
|
|
||||||
for (const auto &ip : ips) {
|
|
||||||
vpnIps.append(ip.split(",", Qt::SkipEmptyParts).first().trimmed());
|
|
||||||
}
|
|
||||||
ips = vpnIps;
|
|
||||||
|
|
||||||
// Calc next IP address
|
|
||||||
if (ips.isEmpty()) {
|
|
||||||
nextIpNumber = "2";
|
|
||||||
} else {
|
} else {
|
||||||
int next = ips.last().split(".").last().toInt() + 1;
|
lastIp = ips.last();
|
||||||
if (next > 254) {
|
|
||||||
errorCode = ErrorCode::AddressPoolError;
|
|
||||||
return connData;
|
|
||||||
}
|
|
||||||
nextIpNumber = QString::number(next);
|
|
||||||
}
|
}
|
||||||
}
|
quint8 lastOctet = static_cast<quint8>(lastIp.toIPv4Address());
|
||||||
|
switch (lastOctet) {
|
||||||
QString subnetIp = containerConfig.value(m_protocolName).toObject().value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress);
|
case 254: result.setAddress(lastIp.toIPv4Address() + 3); break;
|
||||||
{
|
case 255: result.setAddress(lastIp.toIPv4Address() + 2); break;
|
||||||
QStringList l = subnetIp.split(".", Qt::SkipEmptyParts);
|
default: result.setAddress(lastIp.toIPv4Address() + 1); break;
|
||||||
if (l.isEmpty()) {
|
|
||||||
errorCode = ErrorCode::AddressPoolError;
|
|
||||||
return connData;
|
|
||||||
}
|
}
|
||||||
l.removeLast();
|
|
||||||
l.append(nextIpNumber);
|
|
||||||
|
|
||||||
connData.clientIP = l.join(".");
|
return result;
|
||||||
}
|
}();
|
||||||
|
|
||||||
|
connData.clientIP = nextIp.toString();
|
||||||
|
|
||||||
// Get keys
|
// Get keys
|
||||||
connData.serverPubKey = m_serverController->getTextFileFromContainer(container, credentials, m_serverPublicKeyPath, errorCode);
|
connData.serverPubKey =
|
||||||
|
m_serverController->getTextFileFromContainer(container, credentials, m_serverPublicKeyPath, errorCode);
|
||||||
connData.serverPubKey.replace("\n", "");
|
connData.serverPubKey.replace("\n", "");
|
||||||
if (errorCode != ErrorCode::NoError) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
return connData;
|
return connData;
|
||||||
@@ -154,17 +165,22 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
|
|||||||
"AllowedIPs = %3/32\n\n")
|
"AllowedIPs = %3/32\n\n")
|
||||||
.arg(connData.clientPubKey, connData.pskKey, connData.clientIP);
|
.arg(connData.clientPubKey, connData.pskKey, connData.clientIP);
|
||||||
|
|
||||||
errorCode = m_serverController->uploadTextFileToContainer(container, credentials, configPart, m_serverConfigPath,
|
errorCode = m_serverController->uploadTextFileToContainer(container, credentials, configPart, configPath,
|
||||||
libssh::ScpOverwriteMode::ScpAppendToExisting);
|
libssh::ScpOverwriteMode::ScpAppendToExisting);
|
||||||
|
|
||||||
if (errorCode != ErrorCode::NoError) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString script = QString("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip %1)'").arg(m_serverConfigPath);
|
bool isAwg = (container == DockerContainer::Awg2);
|
||||||
|
QString bin = isAwg ? QStringLiteral("awg") : QStringLiteral("wg");
|
||||||
|
QString iface = isAwg ? QStringLiteral("awg0") : QStringLiteral("wg0");
|
||||||
|
QString script = QString(
|
||||||
|
"sudo docker exec -i $CONTAINER_NAME bash -c '%1 syncconf %2 <(%1-quick strip %3)'").arg(bin, iface, configPath);
|
||||||
|
|
||||||
errorCode = m_serverController->runScript(
|
errorCode = m_serverController->runScript(
|
||||||
credentials, m_serverController->replaceVars(script, m_serverController->genVarsForScript(credentials, container)));
|
credentials,
|
||||||
|
m_serverController->replaceVars(script, m_serverController->genVarsForScript(credentials, container)));
|
||||||
|
|
||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
@@ -173,8 +189,8 @@ QString WireguardConfigurator::createConfig(const ServerCredentials &credentials
|
|||||||
const QJsonObject &containerConfig, ErrorCode &errorCode)
|
const QJsonObject &containerConfig, ErrorCode &errorCode)
|
||||||
{
|
{
|
||||||
QString scriptData = amnezia::scriptData(m_configTemplate, container);
|
QString scriptData = amnezia::scriptData(m_configTemplate, container);
|
||||||
QString config =
|
QString config = m_serverController->replaceVars(
|
||||||
m_serverController->replaceVars(scriptData, m_serverController->genVarsForScript(credentials, container, containerConfig));
|
scriptData, m_serverController->genVarsForScript(credentials, container, containerConfig));
|
||||||
|
|
||||||
ConnectionData connData = prepareWireguardConfig(credentials, container, containerConfig, errorCode);
|
ConnectionData connData = prepareWireguardConfig(credentials, container, containerConfig, errorCode);
|
||||||
if (errorCode != ErrorCode::NoError) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
@@ -208,16 +224,16 @@ QString WireguardConfigurator::createConfig(const ServerCredentials &credentials
|
|||||||
return QJsonDocument(jConfig).toJson();
|
return QJsonDocument(jConfig).toJson();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString WireguardConfigurator::processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
QString WireguardConfigurator::processConfigWithLocalSettings(const QPair<QString, QString> &dns,
|
||||||
QString &protocolConfigString)
|
const bool isApiConfig, QString &protocolConfigString)
|
||||||
{
|
{
|
||||||
processConfigWithDnsSettings(dns, protocolConfigString);
|
processConfigWithDnsSettings(dns, protocolConfigString);
|
||||||
|
|
||||||
return protocolConfigString;
|
return protocolConfigString;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString WireguardConfigurator::processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
QString WireguardConfigurator::processConfigWithExportSettings(const QPair<QString, QString> &dns,
|
||||||
QString &protocolConfigString)
|
const bool isApiConfig, QString &protocolConfigString)
|
||||||
{
|
{
|
||||||
processConfigWithDnsSettings(dns, protocolConfigString);
|
processConfigWithDnsSettings(dns, protocolConfigString);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#ifndef WIREGUARD_CONFIGURATOR_H
|
#ifndef WIREGUARD_CONFIGURATOR_H
|
||||||
#define WIREGUARD_CONFIGURATOR_H
|
#define WIREGUARD_CONFIGURATOR_H
|
||||||
|
|
||||||
|
#include <QHostAddress>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QProcessEnvironment>
|
#include <QProcessEnvironment>
|
||||||
|
|
||||||
@@ -12,8 +13,8 @@ class WireguardConfigurator : public ConfiguratorBase
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
WireguardConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, bool isAwg,
|
WireguardConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController,
|
||||||
QObject *parent = nullptr);
|
bool isAwg, QObject *parent = nullptr);
|
||||||
|
|
||||||
struct ConnectionData
|
struct ConnectionData
|
||||||
{
|
{
|
||||||
@@ -26,15 +27,18 @@ public:
|
|||||||
QString port;
|
QString port;
|
||||||
};
|
};
|
||||||
|
|
||||||
QString createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
|
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||||
ErrorCode &errorCode);
|
const QJsonObject &containerConfig, ErrorCode &errorCode);
|
||||||
|
|
||||||
QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig, QString &protocolConfigString);
|
QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||||
QString processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig, QString &protocolConfigString);
|
QString &protocolConfigString);
|
||||||
|
QString processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||||
|
QString &protocolConfigString);
|
||||||
|
|
||||||
static ConnectionData genClientKeys();
|
static ConnectionData genClientKeys();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
QList<QHostAddress> getIpsFromConf(const QString &input);
|
||||||
ConnectionData prepareWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
|
ConnectionData prepareWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||||
const QJsonObject &containerConfig, ErrorCode &errorCode);
|
const QJsonObject &containerConfig, ErrorCode &errorCode);
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,10 @@ QString ContainerProps::containerToString(amnezia::DockerContainer c)
|
|||||||
return "none";
|
return "none";
|
||||||
if (c == DockerContainer::Cloak)
|
if (c == DockerContainer::Cloak)
|
||||||
return "amnezia-openvpn-cloak";
|
return "amnezia-openvpn-cloak";
|
||||||
|
if (c == DockerContainer::Awg)
|
||||||
|
return "amnezia-awg";
|
||||||
|
if (c == DockerContainer::Awg2)
|
||||||
|
return "amnezia-awg2";
|
||||||
QMetaEnum metaEnum = QMetaEnum::fromType<DockerContainer>();
|
QMetaEnum metaEnum = QMetaEnum::fromType<DockerContainer>();
|
||||||
QString containerKey = metaEnum.valueToKey(static_cast<int>(c));
|
QString containerKey = metaEnum.valueToKey(static_cast<int>(c));
|
||||||
|
|
||||||
@@ -41,7 +44,10 @@ QString ContainerProps::containerTypeToString(amnezia::DockerContainer c)
|
|||||||
return "none";
|
return "none";
|
||||||
if (c == DockerContainer::Ipsec)
|
if (c == DockerContainer::Ipsec)
|
||||||
return "ikev2";
|
return "ikev2";
|
||||||
|
if (c == DockerContainer::Awg)
|
||||||
|
return "awg";
|
||||||
|
if (c == DockerContainer::Awg2)
|
||||||
|
return "awg";
|
||||||
QMetaEnum metaEnum = QMetaEnum::fromType<DockerContainer>();
|
QMetaEnum metaEnum = QMetaEnum::fromType<DockerContainer>();
|
||||||
QString containerKey = metaEnum.valueToKey(static_cast<int>(c));
|
QString containerKey = metaEnum.valueToKey(static_cast<int>(c));
|
||||||
|
|
||||||
@@ -71,6 +77,8 @@ QVector<amnezia::Proto> ContainerProps::protocolsForContainer(amnezia::DockerCon
|
|||||||
|
|
||||||
case DockerContainer::Socks5Proxy: return { Proto::Socks5Proxy };
|
case DockerContainer::Socks5Proxy: return { Proto::Socks5Proxy };
|
||||||
|
|
||||||
|
case DockerContainer::Awg: return { Proto::Awg };
|
||||||
|
case DockerContainer::Awg2: return { Proto::Awg };
|
||||||
default: return { defaultProtocol(container) };
|
default: return { defaultProtocol(container) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -94,6 +102,7 @@ QMap<DockerContainer, QString> ContainerProps::containerHumanNames()
|
|||||||
{ DockerContainer::Cloak, "OpenVPN over Cloak" },
|
{ DockerContainer::Cloak, "OpenVPN over Cloak" },
|
||||||
{ DockerContainer::WireGuard, "WireGuard" },
|
{ DockerContainer::WireGuard, "WireGuard" },
|
||||||
{ DockerContainer::Awg, "AmneziaWG" },
|
{ DockerContainer::Awg, "AmneziaWG" },
|
||||||
|
{ DockerContainer::Awg2, "AmneziaWG" },
|
||||||
{ DockerContainer::Xray, "XRay" },
|
{ DockerContainer::Xray, "XRay" },
|
||||||
{ DockerContainer::Ipsec, QObject::tr("IPsec") },
|
{ DockerContainer::Ipsec, QObject::tr("IPsec") },
|
||||||
{ DockerContainer::SSXray, "Shadowsocks"},
|
{ DockerContainer::SSXray, "Shadowsocks"},
|
||||||
@@ -120,6 +129,9 @@ QMap<DockerContainer, QString> ContainerProps::containerDescriptions()
|
|||||||
{ DockerContainer::Awg,
|
{ DockerContainer::Awg,
|
||||||
QObject::tr("AmneziaWG is a special protocol from Amnezia based on WireGuard. "
|
QObject::tr("AmneziaWG is a special protocol from Amnezia based on WireGuard. "
|
||||||
"It provides high connection speed and ensures stable operation even in the most challenging network conditions.") },
|
"It provides high connection speed and ensures stable operation even in the most challenging network conditions.") },
|
||||||
|
{ DockerContainer::Awg2,
|
||||||
|
QObject::tr("AmneziaWG is a special protocol from Amnezia based on WireGuard. "
|
||||||
|
"It provides high connection speed and ensures stable operation even in the most challenging network conditions.") },
|
||||||
{ DockerContainer::Xray,
|
{ DockerContainer::Xray,
|
||||||
QObject::tr("XRay with REALITY masks VPN traffic as web traffic and protects against active probing. "
|
QObject::tr("XRay with REALITY masks VPN traffic as web traffic and protects against active probing. "
|
||||||
"It is highly resistant to detection and offers high speed.") },
|
"It is highly resistant to detection and offers high speed.") },
|
||||||
@@ -140,98 +152,83 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
|
|||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
{ DockerContainer::OpenVpn,
|
{ DockerContainer::OpenVpn,
|
||||||
QObject::tr(
|
QObject::tr("OpenVPN is one of the most popular and reliable VPN protocols. "
|
||||||
"OpenVPN stands as one of the most popular and time-tested VPN protocols available.\n"
|
"It uses SSL/TLS encryption, supports a wide variety of devices and operating systems, "
|
||||||
"It employs its unique security protocol, "
|
"and is continuously improved by the community due to its open-source nature. "
|
||||||
"leveraging the strength of SSL/TLS for encryption and key exchange. "
|
"It provides a good balance between speed and security but is easily recognized by DPI systems, "
|
||||||
"Furthermore, OpenVPN's support for a multitude of authentication methods makes it versatile and adaptable, "
|
"making it susceptible to blocking.\n"
|
||||||
"catering to a wide range of devices and operating systems. "
|
"\nFeatures:\n"
|
||||||
"Due to its open-source nature, OpenVPN benefits from extensive scrutiny by the global community, "
|
"* Available on all AmneziaVPN platforms\n"
|
||||||
"which continually reinforces its security. "
|
"* Normal battery consumption on mobile devices\n"
|
||||||
"With a strong balance of performance, security, and compatibility, "
|
"* Flexible customization for various devices and OS\n"
|
||||||
"OpenVPN remains a top choice for privacy-conscious individuals and businesses alike.\n\n"
|
"* Operates over both TCP and UDP protocols") },
|
||||||
"* Available in the AmneziaVPN across all platforms\n"
|
|
||||||
"* Normal power consumption on mobile devices\n"
|
|
||||||
"* Flexible customisation to suit user needs to work with different operating systems and devices\n"
|
|
||||||
"* Recognised by DPI systems and therefore susceptible to blocking\n"
|
|
||||||
"* Can operate over both TCP and UDP network protocols.") },
|
|
||||||
{ DockerContainer::ShadowSocks,
|
{ DockerContainer::ShadowSocks,
|
||||||
QObject::tr("Shadowsocks, inspired by the SOCKS5 protocol, safeguards the connection using the AEAD cipher. "
|
QObject::tr("Shadowsocks is based on the SOCKS5 protocol and encrypts connections using AEAD cipher. "
|
||||||
"Although Shadowsocks is designed to be discreet and challenging to identify, it isn't identical to a standard HTTPS connection."
|
"Although designed to be discreet, it doesn't mimic a standard HTTPS connection and can be detected by some DPI systems. "
|
||||||
"However, certain traffic analysis systems might still detect a Shadowsocks connection. "
|
"Due to limited support in Amnezia, we recommend using the AmneziaWG protocol.\n"
|
||||||
"Due to limited support in Amnezia, it's recommended to use AmneziaWG protocol.\n\n"
|
"\nFeatures:\n"
|
||||||
"* Available in the AmneziaVPN only on desktop platforms\n"
|
"* Available in AmneziaVPN only on desktop platforms\n"
|
||||||
"* Configurable encryption protocol\n"
|
"* Customizable encryption protocol\n"
|
||||||
"* Detectable by some DPI systems\n"
|
"* Detectable by some DPI systems\n"
|
||||||
"* Works over TCP network protocol.") },
|
"* Operates over TCP protocol\n") },
|
||||||
{ DockerContainer::Cloak,
|
{ DockerContainer::Cloak,
|
||||||
QObject::tr("This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for "
|
QObject::tr("This combination includes the OpenVPN protocol and the Cloak plugin, specifically designed to protect against blocking.\n"
|
||||||
"protecting against detection.\n\n"
|
"\nOpenVPN securely encrypts all internet traffic between your device and the server.\n"
|
||||||
"OpenVPN provides a secure VPN connection by encrypting all internet traffic between the client "
|
"\nThe Cloak plugin further protects the connection from DPI detection. "
|
||||||
"and the server.\n\n"
|
"It modifies traffic metadata to disguise VPN traffic as regular web traffic and prevents detection through active probing. "
|
||||||
"Cloak protects OpenVPN from detection. \n\n"
|
"If an incoming connection fails authentication, Cloak serves a fake website, making your VPN invisible to traffic analysis systems.\n"
|
||||||
"Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, "
|
"\nIn regions with heavy internet censorship, we strongly recommend using OpenVPN with Cloak from your first connection.\n"
|
||||||
"and also protects the VPN from detection by Active Probing. This makes it very resistant to "
|
"\nFeatures:\n"
|
||||||
"being detected\n\n"
|
"* Available on all AmneziaVPN platforms\n"
|
||||||
"Immediately after receiving the first data packet, Cloak authenticates the incoming connection. "
|
|
||||||
"If authentication fails, the plugin masks the server as a fake website and your VPN becomes "
|
|
||||||
"invisible to analysis systems.\n\n"
|
|
||||||
"* Available in the AmneziaVPN across all platforms\n"
|
|
||||||
"* High power consumption on mobile devices\n"
|
"* High power consumption on mobile devices\n"
|
||||||
"* Flexible settings\n"
|
"* Flexible configuration options\n"
|
||||||
"* Not recognised by detection systems\n"
|
"* Undetectable by DPI systems\n"
|
||||||
"* Works over TCP network protocol, 443 port.\n") },
|
"* Operates over TCP protocol on port 443") },
|
||||||
{ DockerContainer::WireGuard,
|
{ DockerContainer::WireGuard,
|
||||||
QObject::tr("A relatively new popular VPN protocol with a simplified architecture.\n"
|
QObject::tr("WireGuard is a modern, streamlined VPN protocol offering stable connectivity and excellent performance across all devices. "
|
||||||
"WireGuard provides stable VPN connection and high performance on all devices. It uses hard-coded encryption "
|
"It uses fixed encryption settings, delivering lower latency and higher data transfer speeds compared to OpenVPN. "
|
||||||
"settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput.\n"
|
"However, WireGuard is easily identifiable by DPI systems due to its distinctive packet signatures, making it susceptible to blocking.\n"
|
||||||
"WireGuard is very susceptible to detection and blocking due to its distinct packet signatures. "
|
"\nFeatures:\n"
|
||||||
"Unlike some other VPN protocols that employ obfuscation techniques, "
|
"* Available on all AmneziaVPN platforms\n"
|
||||||
"the consistent signature patterns of WireGuard packets can be more easily identified and "
|
"* Low power consumption on mobile devices\n"
|
||||||
"thus blocked by advanced Deep Packet Inspection (DPI) systems and other network monitoring tools.\n\n"
|
"* Minimal configuration required\n"
|
||||||
"* Available in the AmneziaVPN across all platforms\n"
|
"* Easily detected by DPI systems (susceptible to blocking)\n"
|
||||||
"* Low power consumption\n"
|
"* Operates over UDP protocol") },
|
||||||
"* Minimum number of settings\n"
|
{ DockerContainer::Awg2,
|
||||||
"* Easily recognised by DPI analysis systems, susceptible to blocking\n"
|
QObject::tr("AmneziaWG is a modern VPN protocol based on WireGuard, "
|
||||||
"* Works over UDP network protocol.") },
|
"combining simplified architecture with high performance across all devices. "
|
||||||
{ DockerContainer::Awg,
|
"It addresses WireGuard's main vulnerability (easy detection by DPI systems) through advanced obfuscation techniques, "
|
||||||
QObject::tr("A modern iteration of the popular VPN protocol, "
|
"making VPN traffic indistinguishable from regular internet traffic.\n"
|
||||||
"AmneziaWG builds upon the foundation set by WireGuard, "
|
"\nAmneziaWG is an excellent choice for those seeking a fast, stealthy VPN connection.\n"
|
||||||
"retaining its simplified architecture and high-performance capabilities across devices.\n"
|
"\nFeatures:\n"
|
||||||
"While WireGuard is known for its efficiency, "
|
"* Available on all AmneziaVPN platforms\n"
|
||||||
"it had issues with being easily detected due to its distinct packet signatures. "
|
"* Low battery consumption on mobile devices\n"
|
||||||
"AmneziaWG solves this problem by using better obfuscation methods, "
|
"* Minimal settings required\n"
|
||||||
"making its traffic blend in with regular internet traffic.\n"
|
"* Undetectable by traffic analysis systems (DPI)\n"
|
||||||
"This means that AmneziaWG keeps the fast performance of the original "
|
"* Operates over UDP protocol") },
|
||||||
"while adding an extra layer of stealth, "
|
|
||||||
"making it a great choice for those wanting a fast and discreet VPN connection.\n\n"
|
|
||||||
"* Available in the AmneziaVPN across all platforms\n"
|
|
||||||
"* Low power consumption\n"
|
|
||||||
"* Minimum number of settings\n"
|
|
||||||
"* Not recognised by traffic analysis systems\n"
|
|
||||||
"* Works over UDP network protocol.") },
|
|
||||||
{ DockerContainer::Xray,
|
{ DockerContainer::Xray,
|
||||||
QObject::tr("The REALITY protocol, a pioneering development by the creators of XRay, "
|
QObject::tr("REALITY is an innovative protocol developed by the creators of XRay, designed specifically to combat high levels of internet censorship. "
|
||||||
"is designed to provide the highest level of protection against detection through its innovative approach to security and privacy.\n"
|
"REALITY identifies censorship systems during the TLS handshake, "
|
||||||
"It uniquely identifies attackers during the TLS handshake phase, seamlessly operating as a proxy for legitimate clients while diverting attackers to genuine websites, "
|
"redirecting suspicious traffic seamlessly to legitimate websites like google.com while providing genuine TLS certificates. "
|
||||||
"thus presenting an authentic TLS certificate and data. \n"
|
"This allows VPN traffic to blend indistinguishably with regular web traffic without special configuration."
|
||||||
"This advanced capability differentiates REALITY from similar technologies by its ability to disguise web traffic as coming from random, "
|
"\nUnlike older protocols such as VMess, VLESS, and XTLS-Vision, REALITY incorporates an advanced built-in \"friend-or-foe\" detection mechanism, "
|
||||||
"legitimate sites without the need for specific configurations. \n"
|
"effectively protecting against DPI and other traffic analysis methods.\n"
|
||||||
"Unlike older protocols such as VMess, VLESS, and the XTLS-Vision transport, "
|
"\nFeatures:\n"
|
||||||
"REALITY's innovative \"friend or foe\" recognition at the TLS handshake enhances security. "
|
"* Resistant to active probing and DPI detection\n"
|
||||||
"This makes REALITY a robust solution for maintaining internet freedom.")
|
"* No special configuration required to disguise traffic\n"
|
||||||
},
|
"* Highly effective in heavily censored regions\n"
|
||||||
|
"* Minimal battery consumption on devices\n"
|
||||||
|
"* Operates over TCP protocol") },
|
||||||
{ DockerContainer::Ipsec,
|
{ DockerContainer::Ipsec,
|
||||||
QObject::tr("IKEv2, paired with the IPSec encryption layer, stands as a modern and stable VPN protocol.\n"
|
QObject::tr("IKEv2, combined with IPSec encryption, is a modern and reliable VPN protocol. "
|
||||||
"One of its distinguishing features is its ability to swiftly switch between networks and devices, "
|
"It reconnects quickly when switching networks or devices, making it ideal for dynamic network environments. "
|
||||||
"making it particularly adaptive in dynamic network environments. \n"
|
"While it provides good security and speed, it's easily recognized by DPI systems and susceptible to blocking.\n"
|
||||||
"While it offers a blend of security, stability, and speed, "
|
"\nFeatures:\n"
|
||||||
"it's essential to note that IKEv2 can be easily detected and is susceptible to blocking.\n\n"
|
"* Available in AmneziaVPN only on Windows\n"
|
||||||
"* Available in the AmneziaVPN only on Windows\n"
|
"* Low battery consumption on mobile devices\n"
|
||||||
"* Low power consumption, on mobile devices\n"
|
"* Minimal configuration required\n"
|
||||||
"* Minimal configuration\n"
|
"* Detectable by DPI analysis systems(easily blocked)\n"
|
||||||
"* Recognised by DPI analysis systems\n"
|
"* Operates over UDP protocol(ports 500 and 4500)") },
|
||||||
"* Works over UDP network protocol, ports 500 and 4500.") },
|
|
||||||
|
|
||||||
{ DockerContainer::TorWebSite, QObject::tr("Website in Tor network") },
|
{ DockerContainer::TorWebSite, QObject::tr("Website in Tor network") },
|
||||||
{ DockerContainer::Dns, QObject::tr("DNS Service") },
|
{ DockerContainer::Dns, QObject::tr("DNS Service") },
|
||||||
@@ -257,6 +254,7 @@ Proto ContainerProps::defaultProtocol(DockerContainer c)
|
|||||||
case DockerContainer::Cloak: return Proto::Cloak;
|
case DockerContainer::Cloak: return Proto::Cloak;
|
||||||
case DockerContainer::ShadowSocks: return Proto::ShadowSocks;
|
case DockerContainer::ShadowSocks: return Proto::ShadowSocks;
|
||||||
case DockerContainer::WireGuard: return Proto::WireGuard;
|
case DockerContainer::WireGuard: return Proto::WireGuard;
|
||||||
|
case DockerContainer::Awg2: return Proto::Awg;
|
||||||
case DockerContainer::Awg: return Proto::Awg;
|
case DockerContainer::Awg: return Proto::Awg;
|
||||||
case DockerContainer::Xray: return Proto::Xray;
|
case DockerContainer::Xray: return Proto::Xray;
|
||||||
case DockerContainer::Ipsec: return Proto::Ikev2;
|
case DockerContainer::Ipsec: return Proto::Ikev2;
|
||||||
@@ -270,21 +268,49 @@ 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
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
#elif defined(Q_OS_IOS)
|
#elif defined(Q_OS_IOS)
|
||||||
|
// Standard iOS build (without Network Extension limitations)
|
||||||
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;
|
||||||
case DockerContainer::SSXray: return true;
|
case DockerContainer::SSXray: return true;
|
||||||
// case DockerContainer::ShadowSocks: return true;
|
// case DockerContainer::ShadowSocks: return true;
|
||||||
default: return false;
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif defined(MACOS_NE)
|
||||||
|
// macOS build using Network Extension – allow OpenVPN for parity with iOS.
|
||||||
|
switch (c) {
|
||||||
|
case DockerContainer::OpenVpn: return true;
|
||||||
|
case DockerContainer::WireGuard: return true;
|
||||||
|
case DockerContainer::Awg2: return true;
|
||||||
|
case DockerContainer::Awg: return true;
|
||||||
|
case DockerContainer::Xray: return true;
|
||||||
|
case DockerContainer::SSXray: return true;
|
||||||
|
case DockerContainer::Cloak:
|
||||||
|
case DockerContainer::ShadowSocks:
|
||||||
|
return false;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
#elif defined(Q_OS_MAC)
|
#elif defined(Q_OS_MAC)
|
||||||
switch (c) {
|
switch (c) {
|
||||||
@@ -298,6 +324,7 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
|
|||||||
case DockerContainer::WireGuard: return true;
|
case DockerContainer::WireGuard: return true;
|
||||||
case DockerContainer::OpenVpn: return true;
|
case DockerContainer::OpenVpn: return true;
|
||||||
case DockerContainer::ShadowSocks: return false;
|
case DockerContainer::ShadowSocks: return false;
|
||||||
|
case DockerContainer::Awg2: return true;
|
||||||
case DockerContainer::Awg: return true;
|
case DockerContainer::Awg: return true;
|
||||||
case DockerContainer::Cloak: return true;
|
case DockerContainer::Cloak: return true;
|
||||||
case DockerContainer::Xray: return true;
|
case DockerContainer::Xray: return true;
|
||||||
@@ -327,7 +354,7 @@ QStringList ContainerProps::fixedPortsForContainer(DockerContainer c)
|
|||||||
bool ContainerProps::isEasySetupContainer(DockerContainer container)
|
bool ContainerProps::isEasySetupContainer(DockerContainer container)
|
||||||
{
|
{
|
||||||
switch (container) {
|
switch (container) {
|
||||||
case DockerContainer::Awg: return true;
|
case DockerContainer::Awg2: return true;
|
||||||
default: return false;
|
default: return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -335,7 +362,7 @@ bool ContainerProps::isEasySetupContainer(DockerContainer container)
|
|||||||
QString ContainerProps::easySetupHeader(DockerContainer container)
|
QString ContainerProps::easySetupHeader(DockerContainer container)
|
||||||
{
|
{
|
||||||
switch (container) {
|
switch (container) {
|
||||||
case DockerContainer::Awg: return tr("Automatic");
|
case DockerContainer::Awg2: return tr("Automatic");
|
||||||
default: return "";
|
default: return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -343,7 +370,7 @@ QString ContainerProps::easySetupHeader(DockerContainer container)
|
|||||||
QString ContainerProps::easySetupDescription(DockerContainer container)
|
QString ContainerProps::easySetupDescription(DockerContainer container)
|
||||||
{
|
{
|
||||||
switch (container) {
|
switch (container) {
|
||||||
case DockerContainer::Awg: return tr("AmneziaWG protocol will be installed. "
|
case DockerContainer::Awg2: return tr("AmneziaWG protocol will be installed. "
|
||||||
"It provides high connection speed and ensures stable operation even in the most challenging network conditions.");
|
"It provides high connection speed and ensures stable operation even in the most challenging network conditions.");
|
||||||
default: return "";
|
default: return "";
|
||||||
}
|
}
|
||||||
@@ -352,7 +379,7 @@ QString ContainerProps::easySetupDescription(DockerContainer container)
|
|||||||
int ContainerProps::easySetupOrder(DockerContainer container)
|
int ContainerProps::easySetupOrder(DockerContainer container)
|
||||||
{
|
{
|
||||||
switch (container) {
|
switch (container) {
|
||||||
case DockerContainer::Awg: return 1;
|
case DockerContainer::Awg2: return 1;
|
||||||
default: return 0;
|
default: return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -368,6 +395,12 @@ bool ContainerProps::isShareable(DockerContainer container)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ContainerProps::isAwgContainer(DockerContainer container)
|
||||||
|
{
|
||||||
|
return container == DockerContainer::Awg || container == DockerContainer::Awg2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
QJsonObject ContainerProps::getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig)
|
QJsonObject ContainerProps::getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig)
|
||||||
{
|
{
|
||||||
QString protocolConfigString = containerConfig.value(ProtocolProps::protoToString(protocol))
|
QString protocolConfigString = containerConfig.value(ProtocolProps::protoToString(protocol))
|
||||||
@@ -385,7 +418,7 @@ int ContainerProps::installPageOrder(DockerContainer container)
|
|||||||
case DockerContainer::Cloak: return 5;
|
case DockerContainer::Cloak: return 5;
|
||||||
case DockerContainer::ShadowSocks: return 6;
|
case DockerContainer::ShadowSocks: return 6;
|
||||||
case DockerContainer::WireGuard: return 2;
|
case DockerContainer::WireGuard: return 2;
|
||||||
case DockerContainer::Awg: return 1;
|
case DockerContainer::Awg2: return 1;
|
||||||
case DockerContainer::Xray: return 3;
|
case DockerContainer::Xray: return 3;
|
||||||
case DockerContainer::Ipsec: return 7;
|
case DockerContainer::Ipsec: return 7;
|
||||||
case DockerContainer::SSXray: return 8;
|
case DockerContainer::SSXray: return 8;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ namespace amnezia
|
|||||||
enum DockerContainer {
|
enum DockerContainer {
|
||||||
None = 0,
|
None = 0,
|
||||||
Awg,
|
Awg,
|
||||||
|
Awg2,
|
||||||
WireGuard,
|
WireGuard,
|
||||||
OpenVpn,
|
OpenVpn,
|
||||||
Cloak,
|
Cloak,
|
||||||
@@ -45,6 +46,7 @@ namespace amnezia
|
|||||||
Q_INVOKABLE static amnezia::DockerContainer containerFromString(const QString &container);
|
Q_INVOKABLE static amnezia::DockerContainer containerFromString(const QString &container);
|
||||||
Q_INVOKABLE static QString containerToString(amnezia::DockerContainer container);
|
Q_INVOKABLE static QString containerToString(amnezia::DockerContainer container);
|
||||||
Q_INVOKABLE static QString containerTypeToString(amnezia::DockerContainer c);
|
Q_INVOKABLE static QString containerTypeToString(amnezia::DockerContainer c);
|
||||||
|
Q_INVOKABLE static QString containerTypeToProtocolString(amnezia::DockerContainer c);
|
||||||
|
|
||||||
Q_INVOKABLE static QList<amnezia::DockerContainer> allContainers();
|
Q_INVOKABLE static QList<amnezia::DockerContainer> allContainers();
|
||||||
|
|
||||||
@@ -71,6 +73,9 @@ namespace amnezia
|
|||||||
|
|
||||||
static bool isShareable(amnezia::DockerContainer container);
|
static bool isShareable(amnezia::DockerContainer container);
|
||||||
|
|
||||||
|
static bool isAwgContainer(amnezia::DockerContainer container);
|
||||||
|
|
||||||
|
|
||||||
static QJsonObject getProtocolConfigFromContainer(const amnezia::Proto protocol, const QJsonObject &containerConfig);
|
static QJsonObject getProtocolConfigFromContainer(const amnezia::Proto protocol, const QJsonObject &containerConfig);
|
||||||
|
|
||||||
static int installPageOrder(amnezia::DockerContainer container);
|
static int installPageOrder(amnezia::DockerContainer container);
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ namespace apiDefs
|
|||||||
AmneziaFreeV3,
|
AmneziaFreeV3,
|
||||||
AmneziaPremiumV1,
|
AmneziaPremiumV1,
|
||||||
AmneziaPremiumV2,
|
AmneziaPremiumV2,
|
||||||
SelfHosted
|
SelfHosted,
|
||||||
|
ExternalPremium,
|
||||||
|
ExternalTrial
|
||||||
};
|
};
|
||||||
|
|
||||||
enum ConfigSource {
|
enum ConfigSource {
|
||||||
@@ -21,12 +23,22 @@ namespace apiDefs
|
|||||||
namespace key
|
namespace key
|
||||||
{
|
{
|
||||||
constexpr QLatin1String configVersion("config_version");
|
constexpr QLatin1String configVersion("config_version");
|
||||||
|
constexpr QLatin1String apiEndpoint("api_endpoint");
|
||||||
|
constexpr QLatin1String apiKey("api_key");
|
||||||
|
constexpr QLatin1String description("description");
|
||||||
|
constexpr QLatin1String name("name");
|
||||||
|
constexpr QLatin1String protocol("protocol");
|
||||||
|
|
||||||
constexpr QLatin1String apiConfig("api_config");
|
constexpr QLatin1String apiConfig("api_config");
|
||||||
constexpr QLatin1String stackType("stack_type");
|
constexpr QLatin1String stackType("stack_type");
|
||||||
constexpr QLatin1String serviceType("service_type");
|
constexpr QLatin1String serviceType("service_type");
|
||||||
|
constexpr QLatin1String cliVersion("cli_version");
|
||||||
|
constexpr QLatin1String cliName("cli_name");
|
||||||
|
constexpr QLatin1String supportedProtocols("supported_protocols");
|
||||||
|
|
||||||
constexpr QLatin1String vpnKey("vpn_key");
|
constexpr QLatin1String vpnKey("vpn_key");
|
||||||
|
constexpr QLatin1String config("config");
|
||||||
|
constexpr QLatin1String configs("configs");
|
||||||
|
|
||||||
constexpr QLatin1String installationUuid("installation_uuid");
|
constexpr QLatin1String installationUuid("installation_uuid");
|
||||||
constexpr QLatin1String workerLastUpdated("worker_last_updated");
|
constexpr QLatin1String workerLastUpdated("worker_last_updated");
|
||||||
@@ -37,12 +49,44 @@ 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 subscriptionExpiredByServer("subscription_expired_by_server");
|
||||||
|
constexpr QLatin1String subscriptionStatus("subscription_status");
|
||||||
|
constexpr QLatin1String subscription("subscription");
|
||||||
|
constexpr QLatin1String endDate("end_date");
|
||||||
constexpr QLatin1String issuedConfigs("issued_configs");
|
constexpr QLatin1String issuedConfigs("issued_configs");
|
||||||
|
constexpr QLatin1String subscriptionDescription("subscription_description");
|
||||||
|
constexpr QLatin1String termsOfUseUrl("terms_of_use_url");
|
||||||
|
constexpr QLatin1String privacyPolicyUrl("privacy_policy_url");
|
||||||
|
|
||||||
|
constexpr QLatin1String supportInfo("support_info");
|
||||||
|
constexpr QLatin1String email("email");
|
||||||
|
constexpr QLatin1String billingEmail("billing_email");
|
||||||
|
constexpr QLatin1String website("website");
|
||||||
|
constexpr QLatin1String websiteName("website_name");
|
||||||
|
constexpr QLatin1String telegram("telegram");
|
||||||
|
|
||||||
|
constexpr QLatin1String id("id");
|
||||||
|
constexpr QLatin1String orderId("order_id");
|
||||||
|
constexpr QLatin1String migrationCode("migration_code");
|
||||||
|
|
||||||
|
constexpr QLatin1String transactionId("transaction_id");
|
||||||
|
constexpr QLatin1String isTestPurchase("is_test_purchase");
|
||||||
|
constexpr QLatin1String isInAppPurchase("is_in_app_purchase");
|
||||||
|
|
||||||
|
constexpr QLatin1String userCountryCode("user_country_code");
|
||||||
|
|
||||||
|
constexpr QLatin1String serviceInfo("service_info");
|
||||||
|
constexpr QLatin1String isAdVisible("is_ad_visible");
|
||||||
|
constexpr QLatin1String isRenewalAvailable("is_renewal_available");
|
||||||
|
constexpr QLatin1String adHeader("ad_header");
|
||||||
|
constexpr QLatin1String adDescription("ad_description");
|
||||||
|
constexpr QLatin1String adEndpoint("ad_endpoint");
|
||||||
}
|
}
|
||||||
|
|
||||||
const int requestTimeoutMsecs = 12 * 1000; // 12 secs
|
const int requestTimeoutMsecs = 12 * 1000; // 12 secs
|
||||||
|
|||||||
@@ -1,13 +1,75 @@
|
|||||||
#include "apiUtils.h"
|
#include "apiUtils.h"
|
||||||
|
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
#include <QJsonValue>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
const QByteArray AMNEZIA_CONFIG_SIGNATURE = QByteArray::fromHex("000000ff");
|
||||||
|
|
||||||
|
constexpr QLatin1String unprocessableSubscriptionMessage("Failed to retrieve subscription information. Is it activated?");
|
||||||
|
constexpr QLatin1String trialAlreadyUsedMessage("trial subscription already used");
|
||||||
|
|
||||||
|
QDateTime subscriptionEndUtcFromString(const QString &subscriptionEndDate)
|
||||||
|
{
|
||||||
|
if (subscriptionEndDate.isEmpty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
QDateTime endDate = QDateTime::fromString(subscriptionEndDate, Qt::ISODateWithMs).toUTC();
|
||||||
|
if (!endDate.isValid()) {
|
||||||
|
endDate = QDateTime::fromString(subscriptionEndDate, Qt::ISODate).toUTC();
|
||||||
|
}
|
||||||
|
return endDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString apiErrorMessageFromJson(const QJsonObject &jsonObj)
|
||||||
|
{
|
||||||
|
const QJsonValue value = jsonObj.value(QStringLiteral("message"));
|
||||||
|
return value.isString() ? value.toString().trimmed() : QString();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString escapeUnicode(const QString &input)
|
||||||
|
{
|
||||||
|
QString output;
|
||||||
|
for (QChar c : input) {
|
||||||
|
if (c.unicode() < 0x20 || c.unicode() > 0x7E) {
|
||||||
|
output += QString("\\u%1").arg(QString::number(c.unicode(), 16).rightJustified(4, '0'));
|
||||||
|
} else {
|
||||||
|
output += c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool apiUtils::isSubscriptionExpired(const QString &subscriptionEndDate)
|
bool apiUtils::isSubscriptionExpired(const QString &subscriptionEndDate)
|
||||||
{
|
{
|
||||||
QDateTime now = QDateTime::currentDateTime();
|
if (subscriptionEndDate.isEmpty()) {
|
||||||
QDateTime endDate = QDateTime::fromString(subscriptionEndDate, Qt::ISODateWithMs);
|
return false;
|
||||||
return endDate < now;
|
}
|
||||||
|
const QDateTime endDate = subscriptionEndUtcFromString(subscriptionEndDate);
|
||||||
|
if (!endDate.isValid()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return endDate <= QDateTime::currentDateTimeUtc();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool apiUtils::isSubscriptionExpiringSoon(const QString &subscriptionEndDate, int withinDays)
|
||||||
|
{
|
||||||
|
if (subscriptionEndDate.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const QDateTime endDate = subscriptionEndUtcFromString(subscriptionEndDate);
|
||||||
|
if (!endDate.isValid()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const QDateTime nowUtc = QDateTime::currentDateTimeUtc();
|
||||||
|
if (endDate <= nowUtc) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return endDate <= nowUtc.addDays(withinDays);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool apiUtils::isServerFromApi(const QJsonObject &serverConfigObject)
|
bool apiUtils::isServerFromApi(const QJsonObject &serverConfigObject)
|
||||||
@@ -23,24 +85,37 @@ bool apiUtils::isServerFromApi(const QJsonObject &serverConfigObject)
|
|||||||
apiDefs::ConfigType apiUtils::getConfigType(const QJsonObject &serverConfigObject)
|
apiDefs::ConfigType apiUtils::getConfigType(const QJsonObject &serverConfigObject)
|
||||||
{
|
{
|
||||||
auto configVersion = serverConfigObject.value(apiDefs::key::configVersion).toInt();
|
auto configVersion = serverConfigObject.value(apiDefs::key::configVersion).toInt();
|
||||||
|
|
||||||
switch (configVersion) {
|
switch (configVersion) {
|
||||||
case apiDefs::ConfigSource::Telegram: {
|
case apiDefs::ConfigSource::Telegram: {
|
||||||
|
constexpr QLatin1String freeV2Endpoint(FREE_V2_ENDPOINT);
|
||||||
|
constexpr QLatin1String premiumV1Endpoint(PREM_V1_ENDPOINT);
|
||||||
|
|
||||||
|
auto apiEndpoint = serverConfigObject.value(apiDefs::key::apiEndpoint).toString();
|
||||||
|
|
||||||
|
if (apiEndpoint.contains(premiumV1Endpoint)) {
|
||||||
|
return apiDefs::ConfigType::AmneziaPremiumV1;
|
||||||
|
} else if (apiEndpoint.contains(freeV2Endpoint)) {
|
||||||
|
return apiDefs::ConfigType::AmneziaFreeV2;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
case apiDefs::ConfigSource::AmneziaGateway: {
|
case apiDefs::ConfigSource::AmneziaGateway: {
|
||||||
constexpr QLatin1String stackPremium("prem");
|
|
||||||
constexpr QLatin1String stackFree("free");
|
|
||||||
|
|
||||||
constexpr QLatin1String servicePremium("amnezia-premium");
|
constexpr QLatin1String servicePremium("amnezia-premium");
|
||||||
constexpr QLatin1String serviceFree("amnezia-free");
|
constexpr QLatin1String serviceFree("amnezia-free");
|
||||||
|
constexpr QLatin1String serviceExternalPremium("external-premium");
|
||||||
|
constexpr QLatin1String serviceExternalTrial("external-trial");
|
||||||
|
|
||||||
auto apiConfigObject = serverConfigObject.value(apiDefs::key::apiConfig).toObject();
|
auto apiConfigObject = serverConfigObject.value(apiDefs::key::apiConfig).toObject();
|
||||||
auto stackType = apiConfigObject.value(apiDefs::key::stackType).toString();
|
|
||||||
auto serviceType = apiConfigObject.value(apiDefs::key::serviceType).toString();
|
auto serviceType = apiConfigObject.value(apiDefs::key::serviceType).toString();
|
||||||
|
|
||||||
if (serviceType == servicePremium || stackType == stackPremium) {
|
if (serviceType == servicePremium) {
|
||||||
return apiDefs::ConfigType::AmneziaPremiumV2;
|
return apiDefs::ConfigType::AmneziaPremiumV2;
|
||||||
} else if (serviceType == serviceFree || stackType == stackFree) {
|
} else if (serviceType == serviceFree) {
|
||||||
return apiDefs::ConfigType::AmneziaFreeV3;
|
return apiDefs::ConfigType::AmneziaFreeV3;
|
||||||
|
} else if (serviceType == serviceExternalPremium) {
|
||||||
|
return apiDefs::ConfigType::ExternalPremium;
|
||||||
|
} else if (serviceType == serviceExternalTrial) {
|
||||||
|
return apiDefs::ConfigType::ExternalTrial;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
@@ -54,34 +129,160 @@ 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, QNetworkReply *reply)
|
amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &sslErrors, const QString &replyErrorString,
|
||||||
|
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;
|
||||||
|
const int httpStatusCodePaymentRequired = 402;
|
||||||
|
const int httpStatusCodeUnprocessableEntity = 422;
|
||||||
|
|
||||||
if (!sslErrors.empty()) {
|
if (!sslErrors.empty()) {
|
||||||
qDebug().noquote() << sslErrors;
|
qDebug().noquote() << sslErrors;
|
||||||
return amnezia::ErrorCode::ApiConfigSslError;
|
return amnezia::ErrorCode::ApiConfigSslError;
|
||||||
} else if (reply->error() == QNetworkReply::NoError) {
|
}
|
||||||
|
if (replyError == QNetworkReply::NoError) {
|
||||||
return amnezia::ErrorCode::NoError;
|
return amnezia::ErrorCode::NoError;
|
||||||
} else if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
}
|
||||||
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
if (replyError == QNetworkReply::NetworkError::OperationCanceledError
|
||||||
|
|| replyError == QNetworkReply::NetworkError::TimeoutError) {
|
||||||
|
qDebug() << replyError;
|
||||||
return amnezia::ErrorCode::ApiConfigTimeoutError;
|
return amnezia::ErrorCode::ApiConfigTimeoutError;
|
||||||
} else {
|
}
|
||||||
QString err = reply->errorString();
|
if (replyError == QNetworkReply::NetworkError::OperationNotImplementedError) {
|
||||||
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
qDebug() << replyError;
|
||||||
qDebug() << QString::fromUtf8(reply->readAll());
|
return amnezia::ErrorCode::ApiUpdateRequestError;
|
||||||
qDebug() << reply->error();
|
}
|
||||||
qDebug() << err;
|
|
||||||
qDebug() << httpStatusCode;
|
qDebug() << QString::fromUtf8(responseBody);
|
||||||
if (httpStatusCode == httpStatusCodeConflict) {
|
qDebug() << replyError;
|
||||||
|
qDebug() << httpStatusCode;
|
||||||
|
|
||||||
|
QJsonDocument jsonDoc = QJsonDocument::fromJson(responseBody);
|
||||||
|
if (jsonDoc.isObject()) {
|
||||||
|
QJsonObject jsonObj = jsonDoc.object();
|
||||||
|
const int httpStatusFromBody = jsonObj.value(QStringLiteral("http_status")).toInt(-1);
|
||||||
|
if (httpStatusFromBody == httpStatusCodeConflict) {
|
||||||
|
if (apiErrorMessageFromJson(jsonObj).contains(trialAlreadyUsedMessage, Qt::CaseInsensitive)) {
|
||||||
|
return amnezia::ErrorCode::ApiTrialAlreadyUsedError;
|
||||||
|
}
|
||||||
return amnezia::ErrorCode::ApiConfigLimitError;
|
return amnezia::ErrorCode::ApiConfigLimitError;
|
||||||
} else if (httpStatusCode == httpStatusCodeNotFound) {
|
}
|
||||||
|
if (httpStatusFromBody == httpStatusCodeNotFound) {
|
||||||
return amnezia::ErrorCode::ApiNotFoundError;
|
return amnezia::ErrorCode::ApiNotFoundError;
|
||||||
}
|
}
|
||||||
|
if (httpStatusFromBody == httpStatusCodeNotImplemented) {
|
||||||
|
return amnezia::ErrorCode::ApiUpdateRequestError;
|
||||||
|
}
|
||||||
|
if (httpStatusFromBody == httpStatusCodeUnprocessableEntity) {
|
||||||
|
if (apiErrorMessageFromJson(jsonObj) == unprocessableSubscriptionMessage) {
|
||||||
|
return amnezia::ErrorCode::ApiSubscriptionExpiredError;
|
||||||
|
}
|
||||||
|
return amnezia::ErrorCode::ApiConfigDownloadError;
|
||||||
|
}
|
||||||
|
if (httpStatusFromBody == httpStatusCodePaymentRequired) {
|
||||||
|
return amnezia::ErrorCode::ApiSubscriptionNotActiveError;
|
||||||
|
}
|
||||||
return amnezia::ErrorCode::ApiConfigDownloadError;
|
return amnezia::ErrorCode::ApiConfigDownloadError;
|
||||||
}
|
}
|
||||||
|
|
||||||
qDebug() << "something went wrong";
|
qDebug() << "something went wrong";
|
||||||
return amnezia::ErrorCode::InternalError;
|
return amnezia::ErrorCode::ApiConfigDownloadError;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool apiUtils::isPremiumServer(const QJsonObject &serverConfigObject)
|
||||||
|
{
|
||||||
|
static const QSet<apiDefs::ConfigType> premiumTypes = { apiDefs::ConfigType::AmneziaPremiumV1, apiDefs::ConfigType::AmneziaPremiumV2,
|
||||||
|
apiDefs::ConfigType::ExternalPremium, apiDefs::ConfigType::ExternalTrial };
|
||||||
|
return premiumTypes.contains(getConfigType(serverConfigObject));
|
||||||
|
}
|
||||||
|
|
||||||
|
QString apiUtils::getPremiumV1VpnKey(const QJsonObject &serverConfigObject)
|
||||||
|
{
|
||||||
|
if (apiUtils::getConfigType(serverConfigObject) != apiDefs::ConfigType::AmneziaPremiumV1) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QPair<QString, QVariant>> orderedFields;
|
||||||
|
orderedFields.append(qMakePair(apiDefs::key::name, serverConfigObject[apiDefs::key::name].toString()));
|
||||||
|
orderedFields.append(qMakePair(apiDefs::key::description, serverConfigObject[apiDefs::key::description].toString()));
|
||||||
|
orderedFields.append(qMakePair(apiDefs::key::configVersion, serverConfigObject[apiDefs::key::configVersion].toDouble()));
|
||||||
|
orderedFields.append(qMakePair(apiDefs::key::protocol, serverConfigObject[apiDefs::key::protocol].toString()));
|
||||||
|
orderedFields.append(qMakePair(apiDefs::key::apiEndpoint, serverConfigObject[apiDefs::key::apiEndpoint].toString()));
|
||||||
|
orderedFields.append(qMakePair(apiDefs::key::apiKey, serverConfigObject[apiDefs::key::apiKey].toString()));
|
||||||
|
|
||||||
|
QString vpnKeyStr = "{";
|
||||||
|
for (int i = 0; i < orderedFields.size(); ++i) {
|
||||||
|
const auto &pair = orderedFields[i];
|
||||||
|
if (pair.second.typeId() == QMetaType::Type::QString) {
|
||||||
|
vpnKeyStr += "\"" + pair.first + "\": \"" + pair.second.toString() + "\"";
|
||||||
|
} else if (pair.second.typeId() == QMetaType::Type::Double || pair.second.typeId() == QMetaType::Type::Int) {
|
||||||
|
vpnKeyStr += "\"" + pair.first + "\": " + QString::number(pair.second.toDouble(), 'f', 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i < orderedFields.size() - 1) {
|
||||||
|
vpnKeyStr += ", ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vpnKeyStr += "}";
|
||||||
|
|
||||||
|
QByteArray vpnKeyCompressed = escapeUnicode(vpnKeyStr).toUtf8();
|
||||||
|
vpnKeyCompressed = qCompress(vpnKeyCompressed, 6);
|
||||||
|
vpnKeyCompressed = vpnKeyCompressed.mid(4);
|
||||||
|
|
||||||
|
QByteArray signedData = AMNEZIA_CONFIG_SIGNATURE + vpnKeyCompressed;
|
||||||
|
|
||||||
|
return QString("vpn://%1").arg(QString(signedData.toBase64(QByteArray::Base64UrlEncoding)));
|
||||||
|
}
|
||||||
|
|
||||||
|
QString apiUtils::getPremiumV2VpnKey(const QJsonObject &serverConfigObject)
|
||||||
|
{
|
||||||
|
auto configType = apiUtils::getConfigType(serverConfigObject);
|
||||||
|
if (configType != apiDefs::ConfigType::AmneziaPremiumV2 && configType != apiDefs::ConfigType::ExternalPremium
|
||||||
|
&& configType != apiDefs::ConfigType::ExternalTrial) {
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,10 +13,19 @@ namespace apiUtils
|
|||||||
|
|
||||||
bool isSubscriptionExpired(const QString &subscriptionEndDate);
|
bool isSubscriptionExpired(const QString &subscriptionEndDate);
|
||||||
|
|
||||||
|
bool isSubscriptionExpiringSoon(const QString &subscriptionEndDate, int withinDays = 30);
|
||||||
|
|
||||||
|
bool isPremiumServer(const QJsonObject &serverConfigObject);
|
||||||
|
|
||||||
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, QNetworkReply *reply);
|
amnezia::ErrorCode checkNetworkReplyErrors(const QList<QSslError> &sslErrors, const QString &replyErrorString,
|
||||||
|
const QNetworkReply::NetworkError &replyError, const int httpStatusCode,
|
||||||
|
const QByteArray &responseBody);
|
||||||
|
|
||||||
|
QString getPremiumV1VpnKey(const QJsonObject &serverConfigObject);
|
||||||
|
QString getPremiumV2VpnKey(const QJsonObject &serverConfigObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // APIUTILS_H
|
#endif // APIUTILS_H
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "coreController.h"
|
#include "coreController.h"
|
||||||
|
|
||||||
|
#include <QDirIterator>
|
||||||
#include <QTranslator>
|
#include <QTranslator>
|
||||||
|
|
||||||
#if defined(Q_OS_ANDROID)
|
#if defined(Q_OS_ANDROID)
|
||||||
@@ -25,9 +26,8 @@ CoreController::CoreController(const QSharedPointer<VpnConnection> &vpnConnectio
|
|||||||
|
|
||||||
initNotificationHandler();
|
initNotificationHandler();
|
||||||
|
|
||||||
auto locale = m_settings->getAppLanguage();
|
|
||||||
m_translator.reset(new QTranslator());
|
m_translator.reset(new QTranslator());
|
||||||
updateTranslator(locale);
|
updateTranslator(m_settings->getAppLanguage());
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoreController::initModels()
|
void CoreController::initModels()
|
||||||
@@ -47,6 +47,9 @@ void CoreController::initModels()
|
|||||||
m_sitesModel.reset(new SitesModel(m_settings, this));
|
m_sitesModel.reset(new SitesModel(m_settings, this));
|
||||||
m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get());
|
m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get());
|
||||||
|
|
||||||
|
m_allowedDnsModel.reset(new AllowedDnsModel(m_settings, this));
|
||||||
|
m_engine->rootContext()->setContextProperty("AllowedDnsModel", m_allowedDnsModel.get());
|
||||||
|
|
||||||
m_appSplitTunnelingModel.reset(new AppSplitTunnelingModel(m_settings, this));
|
m_appSplitTunnelingModel.reset(new AppSplitTunnelingModel(m_settings, this));
|
||||||
m_engine->rootContext()->setContextProperty("AppSplitTunnelingModel", m_appSplitTunnelingModel.get());
|
m_engine->rootContext()->setContextProperty("AppSplitTunnelingModel", m_appSplitTunnelingModel.get());
|
||||||
|
|
||||||
@@ -88,6 +91,12 @@ void CoreController::initModels()
|
|||||||
m_apiServicesModel.reset(new ApiServicesModel(this));
|
m_apiServicesModel.reset(new ApiServicesModel(this));
|
||||||
m_engine->rootContext()->setContextProperty("ApiServicesModel", m_apiServicesModel.get());
|
m_engine->rootContext()->setContextProperty("ApiServicesModel", m_apiServicesModel.get());
|
||||||
|
|
||||||
|
m_apiSubscriptionPlansModel.reset(new ApiSubscriptionPlansModel(this));
|
||||||
|
m_engine->rootContext()->setContextProperty("ApiSubscriptionPlansModel", m_apiSubscriptionPlansModel.get());
|
||||||
|
|
||||||
|
m_apiBenefitsModel.reset(new ApiBenefitsModel(this));
|
||||||
|
m_engine->rootContext()->setContextProperty("ApiBenefitsModel", m_apiBenefitsModel.get());
|
||||||
|
|
||||||
m_apiCountryModel.reset(new ApiCountryModel(this));
|
m_apiCountryModel.reset(new ApiCountryModel(this));
|
||||||
m_engine->rootContext()->setContextProperty("ApiCountryModel", m_apiCountryModel.get());
|
m_engine->rootContext()->setContextProperty("ApiCountryModel", m_apiCountryModel.get());
|
||||||
|
|
||||||
@@ -96,6 +105,9 @@ 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()
|
||||||
@@ -116,6 +128,9 @@ void CoreController::initControllers()
|
|||||||
connect(m_installController.get(), &InstallController::currentContainerUpdated, m_connectionController.get(),
|
connect(m_installController.get(), &InstallController::currentContainerUpdated, m_connectionController.get(),
|
||||||
&ConnectionController::onCurrentContainerUpdated); // TODO remove this
|
&ConnectionController::onCurrentContainerUpdated); // TODO remove this
|
||||||
|
|
||||||
|
connect(m_installController.get(), &InstallController::profileCleared,
|
||||||
|
m_protocolsModel.get(), &ProtocolsModel::updateModel);
|
||||||
|
|
||||||
m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings));
|
m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings));
|
||||||
m_engine->rootContext()->setContextProperty("ImportController", m_importController.get());
|
m_engine->rootContext()->setContextProperty("ImportController", m_importController.get());
|
||||||
|
|
||||||
@@ -126,9 +141,12 @@ void CoreController::initControllers()
|
|||||||
new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, m_appSplitTunnelingModel, m_settings));
|
new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, m_appSplitTunnelingModel, m_settings));
|
||||||
m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get());
|
m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get());
|
||||||
|
|
||||||
m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel));
|
m_sitesController.reset(new SitesController(m_settings, m_sitesModel));
|
||||||
m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get());
|
m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get());
|
||||||
|
|
||||||
|
m_allowedDnsController.reset(new AllowedDnsController(m_settings, m_allowedDnsModel));
|
||||||
|
m_engine->rootContext()->setContextProperty("AllowedDnsController", m_allowedDnsController.get());
|
||||||
|
|
||||||
m_appSplitTunnelingController.reset(new AppSplitTunnelingController(m_settings, m_appSplitTunnelingModel));
|
m_appSplitTunnelingController.reset(new AppSplitTunnelingController(m_settings, m_appSplitTunnelingModel));
|
||||||
m_engine->rootContext()->setContextProperty("AppSplitTunnelingController", m_appSplitTunnelingController.get());
|
m_engine->rootContext()->setContextProperty("AppSplitTunnelingController", m_appSplitTunnelingController.get());
|
||||||
|
|
||||||
@@ -139,8 +157,14 @@ void CoreController::initControllers()
|
|||||||
new ApiSettingsController(m_serversModel, m_apiAccountInfoModel, m_apiCountryModel, m_apiDevicesModel, m_settings));
|
new ApiSettingsController(m_serversModel, m_apiAccountInfoModel, m_apiCountryModel, m_apiDevicesModel, m_settings));
|
||||||
m_engine->rootContext()->setContextProperty("ApiSettingsController", m_apiSettingsController.get());
|
m_engine->rootContext()->setContextProperty("ApiSettingsController", m_apiSettingsController.get());
|
||||||
|
|
||||||
m_apiConfigsController.reset(new ApiConfigsController(m_serversModel, m_apiServicesModel, m_settings));
|
m_apiConfigsController.reset(
|
||||||
|
new ApiConfigsController(m_serversModel, m_apiServicesModel, m_apiSubscriptionPlansModel, m_apiBenefitsModel, m_settings));
|
||||||
m_engine->rootContext()->setContextProperty("ApiConfigsController", m_apiConfigsController.get());
|
m_engine->rootContext()->setContextProperty("ApiConfigsController", m_apiConfigsController.get());
|
||||||
|
connect(m_apiConfigsController.get(), &ApiConfigsController::subscriptionRefreshNeeded,
|
||||||
|
this, [this]() { m_apiSettingsController->getAccountInfo(false); });
|
||||||
|
|
||||||
|
m_apiNewsController.reset(new ApiNewsController(m_newsModel, m_settings, m_serversModel, this));
|
||||||
|
m_engine->rootContext()->setContextProperty("ApiNewsController", m_apiNewsController.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoreController::initAndroidController()
|
void CoreController::initAndroidController()
|
||||||
@@ -213,11 +237,12 @@ void CoreController::initSignalHandlers()
|
|||||||
initAutoConnectHandler();
|
initAutoConnectHandler();
|
||||||
initAmneziaDnsToggledHandler();
|
initAmneziaDnsToggledHandler();
|
||||||
initPrepareConfigHandler();
|
initPrepareConfigHandler();
|
||||||
|
initStrictKillSwitchHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoreController::initNotificationHandler()
|
void CoreController::initNotificationHandler()
|
||||||
{
|
{
|
||||||
#ifndef Q_OS_ANDROID
|
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||||
m_notificationHandler.reset(NotificationHandler::create(nullptr));
|
m_notificationHandler.reset(NotificationHandler::create(nullptr));
|
||||||
|
|
||||||
connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(),
|
connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(),
|
||||||
@@ -229,7 +254,10 @@ void CoreController::initNotificationHandler()
|
|||||||
connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(),
|
connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(),
|
||||||
&ConnectionController::closeConnection);
|
&ConnectionController::closeConnection);
|
||||||
connect(this, &CoreController::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated);
|
connect(this, &CoreController::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated);
|
||||||
#endif
|
|
||||||
|
auto* trayHandler = qobject_cast<SystemTrayNotificationHandler*>(m_notificationHandler.get());
|
||||||
|
connect(this, &CoreController::websiteUrlChanged, trayHandler, &SystemTrayNotificationHandler::updateWebsiteUrl);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoreController::updateTranslator(const QLocale &locale)
|
void CoreController::updateTranslator(const QLocale &locale)
|
||||||
@@ -238,7 +266,23 @@ void CoreController::updateTranslator(const QLocale &locale)
|
|||||||
QCoreApplication::removeTranslator(m_translator.get());
|
QCoreApplication::removeTranslator(m_translator.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
QString strFileName = QString(":/translations/amneziavpn") + QLatin1String("_") + locale.name() + ".qm";
|
QStringList availableTranslations;
|
||||||
|
QDirIterator it(":/translations", QStringList("amneziavpn_*.qm"), QDir::Files);
|
||||||
|
while (it.hasNext()) {
|
||||||
|
availableTranslations << it.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This code allow to load translation for the language only, without country code
|
||||||
|
const QString lang = locale.name().split("_").first();
|
||||||
|
const QString translationFilePrefix = QString(":/translations/amneziavpn_") + lang;
|
||||||
|
QString strFileName = QString(":/translations/amneziavpn_%1.qm").arg(locale.name());
|
||||||
|
for (const QString &translation : availableTranslations) {
|
||||||
|
if (translation.contains(translationFilePrefix)) {
|
||||||
|
strFileName = translation;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (m_translator->load(strFileName)) {
|
if (m_translator->load(strFileName)) {
|
||||||
if (QCoreApplication::installTranslator(m_translator.get())) {
|
if (QCoreApplication::installTranslator(m_translator.get())) {
|
||||||
m_settings->setAppLanguage(locale);
|
m_settings->setAppLanguage(locale);
|
||||||
@@ -250,6 +294,7 @@ void CoreController::updateTranslator(const QLocale &locale)
|
|||||||
m_engine->retranslate();
|
m_engine->retranslate();
|
||||||
|
|
||||||
emit translationsUpdated();
|
emit translationsUpdated();
|
||||||
|
emit websiteUrlChanged(m_languageModel->getCurrentSiteUrl());
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoreController::initErrorMessagesHandler()
|
void CoreController::initErrorMessagesHandler()
|
||||||
@@ -270,13 +315,10 @@ void CoreController::setQmlRoot()
|
|||||||
|
|
||||||
void CoreController::initApiCountryModelUpdateHandler()
|
void CoreController::initApiCountryModelUpdateHandler()
|
||||||
{
|
{
|
||||||
// TODO
|
|
||||||
connect(m_serversModel.get(), &ServersModel::updateApiCountryModel, this, [this]() {
|
connect(m_serversModel.get(), &ServersModel::updateApiCountryModel, this, [this]() {
|
||||||
m_apiCountryModel->updateModel(m_serversModel->getProcessedServerData("apiAvailableCountries").toJsonArray(),
|
m_apiCountryModel->updateModel(m_serversModel->getProcessedServerData("apiAvailableCountries").toJsonArray(),
|
||||||
m_serversModel->getProcessedServerData("apiServerCountryCode").toString());
|
m_serversModel->getProcessedServerData("apiServerCountryCode").toString());
|
||||||
});
|
});
|
||||||
connect(m_serversModel.get(), &ServersModel::updateApiServicesModel, this,
|
|
||||||
[this]() { m_apiServicesModel->updateModel(m_serversModel->getProcessedServerData("apiConfig").toJsonObject()); });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CoreController::initContainerModelUpdateHandler()
|
void CoreController::initContainerModelUpdateHandler()
|
||||||
@@ -284,6 +326,11 @@ 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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,7 +377,11 @@ void CoreController::initPrepareConfigHandler()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_installController->isConfigValid()) {
|
m_installController->validateConfig();
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(m_installController.get(), &InstallController::configValidated, this, [this](bool isValid) {
|
||||||
|
if (!isValid) {
|
||||||
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -339,7 +390,32 @@ void CoreController::initPrepareConfigHandler()
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CoreController::initStrictKillSwitchHandler()
|
||||||
|
{
|
||||||
|
connect(m_settingsController.get(), &SettingsController::strictKillSwitchEnabledChanged, m_vpnConnection.get(),
|
||||||
|
&VpnConnection::onKillSwitchModeChanged);
|
||||||
|
}
|
||||||
|
|
||||||
QSharedPointer<PageController> CoreController::pageController() const
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,9 +5,15 @@
|
|||||||
#include <QQmlContext>
|
#include <QQmlContext>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
|
|
||||||
|
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||||
|
#include "ui/systemtray_notificationhandler.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "ui/controllers/api/apiConfigsController.h"
|
#include "ui/controllers/api/apiConfigsController.h"
|
||||||
#include "ui/controllers/api/apiSettingsController.h"
|
#include "ui/controllers/api/apiSettingsController.h"
|
||||||
|
#include "ui/controllers/api/apiNewsController.h"
|
||||||
#include "ui/controllers/appSplitTunnelingController.h"
|
#include "ui/controllers/appSplitTunnelingController.h"
|
||||||
|
#include "ui/controllers/allowedDnsController.h"
|
||||||
#include "ui/controllers/connectionController.h"
|
#include "ui/controllers/connectionController.h"
|
||||||
#include "ui/controllers/exportController.h"
|
#include "ui/controllers/exportController.h"
|
||||||
#include "ui/controllers/focusController.h"
|
#include "ui/controllers/focusController.h"
|
||||||
@@ -18,6 +24,7 @@
|
|||||||
#include "ui/controllers/sitesController.h"
|
#include "ui/controllers/sitesController.h"
|
||||||
#include "ui/controllers/systemController.h"
|
#include "ui/controllers/systemController.h"
|
||||||
|
|
||||||
|
#include "ui/models/allowed_dns_model.h"
|
||||||
#include "ui/models/containers_model.h"
|
#include "ui/models/containers_model.h"
|
||||||
#include "ui/models/languageModel.h"
|
#include "ui/models/languageModel.h"
|
||||||
#include "ui/models/protocols/cloakConfigModel.h"
|
#include "ui/models/protocols/cloakConfigModel.h"
|
||||||
@@ -25,9 +32,11 @@
|
|||||||
#include "ui/models/protocols/ikev2ConfigModel.h"
|
#include "ui/models/protocols/ikev2ConfigModel.h"
|
||||||
#endif
|
#endif
|
||||||
#include "ui/models/api/apiAccountInfoModel.h"
|
#include "ui/models/api/apiAccountInfoModel.h"
|
||||||
|
#include "ui/models/api/apiBenefitsModel.h"
|
||||||
#include "ui/models/api/apiCountryModel.h"
|
#include "ui/models/api/apiCountryModel.h"
|
||||||
#include "ui/models/api/apiDevicesModel.h"
|
#include "ui/models/api/apiDevicesModel.h"
|
||||||
#include "ui/models/api/apiServicesModel.h"
|
#include "ui/models/api/apiServicesModel.h"
|
||||||
|
#include "ui/models/api/apiSubscriptionPlansModel.h"
|
||||||
#include "ui/models/appSplitTunnelingModel.h"
|
#include "ui/models/appSplitTunnelingModel.h"
|
||||||
#include "ui/models/clientManagementModel.h"
|
#include "ui/models/clientManagementModel.h"
|
||||||
#include "ui/models/protocols/awgConfigModel.h"
|
#include "ui/models/protocols/awgConfigModel.h"
|
||||||
@@ -40,8 +49,9 @@
|
|||||||
#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"
|
||||||
|
|
||||||
#ifndef Q_OS_ANDROID
|
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||||
#include "ui/notificationhandler.h"
|
#include "ui/notificationhandler.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -56,8 +66,12 @@ 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);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void initModels();
|
void initModels();
|
||||||
@@ -80,13 +94,14 @@ private:
|
|||||||
void initAutoConnectHandler();
|
void initAutoConnectHandler();
|
||||||
void initAmneziaDnsToggledHandler();
|
void initAmneziaDnsToggledHandler();
|
||||||
void initPrepareConfigHandler();
|
void initPrepareConfigHandler();
|
||||||
|
void initStrictKillSwitchHandler();
|
||||||
|
|
||||||
QQmlApplicationEngine *m_engine {}; // TODO use parent child system here?
|
QQmlApplicationEngine *m_engine {}; // TODO use parent child system here?
|
||||||
std::shared_ptr<Settings> m_settings;
|
std::shared_ptr<Settings> m_settings;
|
||||||
QSharedPointer<VpnConnection> m_vpnConnection;
|
QSharedPointer<VpnConnection> m_vpnConnection;
|
||||||
QSharedPointer<QTranslator> m_translator;
|
QSharedPointer<QTranslator> m_translator;
|
||||||
|
|
||||||
#ifndef Q_OS_ANDROID
|
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||||
QScopedPointer<NotificationHandler> m_notificationHandler;
|
QScopedPointer<NotificationHandler> m_notificationHandler;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -102,9 +117,11 @@ private:
|
|||||||
QScopedPointer<SitesController> m_sitesController;
|
QScopedPointer<SitesController> m_sitesController;
|
||||||
QScopedPointer<SystemController> m_systemController;
|
QScopedPointer<SystemController> m_systemController;
|
||||||
QScopedPointer<AppSplitTunnelingController> m_appSplitTunnelingController;
|
QScopedPointer<AppSplitTunnelingController> m_appSplitTunnelingController;
|
||||||
|
QScopedPointer<AllowedDnsController> m_allowedDnsController;
|
||||||
|
|
||||||
QScopedPointer<ApiSettingsController> m_apiSettingsController;
|
QScopedPointer<ApiSettingsController> m_apiSettingsController;
|
||||||
QScopedPointer<ApiConfigsController> m_apiConfigsController;
|
QScopedPointer<ApiConfigsController> m_apiConfigsController;
|
||||||
|
QScopedPointer<ApiNewsController> m_apiNewsController;
|
||||||
|
|
||||||
QSharedPointer<ContainersModel> m_containersModel;
|
QSharedPointer<ContainersModel> m_containersModel;
|
||||||
QSharedPointer<ContainersModel> m_defaultServerContainersModel;
|
QSharedPointer<ContainersModel> m_defaultServerContainersModel;
|
||||||
@@ -112,10 +129,14 @@ 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<AppSplitTunnelingModel> m_appSplitTunnelingModel;
|
QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel;
|
||||||
QSharedPointer<ClientManagementModel> m_clientManagementModel;
|
QSharedPointer<ClientManagementModel> m_clientManagementModel;
|
||||||
|
|
||||||
QSharedPointer<ApiServicesModel> m_apiServicesModel;
|
QSharedPointer<ApiServicesModel> m_apiServicesModel;
|
||||||
|
QSharedPointer<ApiSubscriptionPlansModel> m_apiSubscriptionPlansModel;
|
||||||
|
QSharedPointer<ApiBenefitsModel> m_apiBenefitsModel;
|
||||||
QSharedPointer<ApiCountryModel> m_apiCountryModel;
|
QSharedPointer<ApiCountryModel> m_apiCountryModel;
|
||||||
QSharedPointer<ApiAccountInfoModel> m_apiAccountInfoModel;
|
QSharedPointer<ApiAccountInfoModel> m_apiAccountInfoModel;
|
||||||
QSharedPointer<ApiDevicesModel> m_apiDevicesModel;
|
QSharedPointer<ApiDevicesModel> m_apiDevicesModel;
|
||||||
|
|||||||
@@ -1,20 +1,29 @@
|
|||||||
#include "gatewayController.h"
|
#include "gatewayController.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <functional>
|
||||||
#include <random>
|
#include <random>
|
||||||
|
|
||||||
|
#include <QCryptographicHash>
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
|
#include <QPromise>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
#include "QBlockCipher.h"
|
#include "QBlockCipher.h"
|
||||||
#include "QRsa.h"
|
#include "QRsa.h"
|
||||||
|
|
||||||
#include "amnezia_application.h"
|
#include "amnezia_application.h"
|
||||||
#include "core/api/apiUtils.h"
|
#include "core/api/apiUtils.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
|
||||||
@@ -26,86 +35,73 @@ namespace
|
|||||||
constexpr char apiPayload[] = "api_payload";
|
constexpr char apiPayload[] = "api_payload";
|
||||||
constexpr char keyPayload[] = "key_payload";
|
constexpr char keyPayload[] = "key_payload";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
constexpr int httpStatusCodeNotFound = 404;
|
||||||
|
constexpr int httpStatusCodeConflict = 409;
|
||||||
|
constexpr int httpStatusCodeNotImplemented = 501;
|
||||||
|
constexpr int httpStatusCodePaymentRequired = 402;
|
||||||
|
constexpr int httpStatusCodeUnprocessableEntity = 422;
|
||||||
|
|
||||||
|
constexpr QLatin1String unprocessableSubscriptionMessage("Failed to retrieve subscription information. Is it activated?");
|
||||||
|
|
||||||
|
constexpr int proxyStorageRequestTimeoutMsecs = 3000;
|
||||||
}
|
}
|
||||||
|
|
||||||
GatewayController::GatewayController(const QString &gatewayEndpoint, bool isDevEnvironment, int requestTimeoutMsecs, QObject *parent)
|
GatewayController::GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
|
||||||
: QObject(parent), m_gatewayEndpoint(gatewayEndpoint), m_isDevEnvironment(isDevEnvironment), m_requestTimeoutMsecs(requestTimeoutMsecs)
|
const bool isStrictKillSwitchEnabled, QObject *parent)
|
||||||
|
: QObject(parent),
|
||||||
|
m_gatewayEndpoint(gatewayEndpoint),
|
||||||
|
m_isDevEnvironment(isDevEnvironment),
|
||||||
|
m_requestTimeoutMsecs(requestTimeoutMsecs),
|
||||||
|
m_isStrictKillSwitchEnabled(isStrictKillSwitchEnabled)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode GatewayController::get(const QString &endpoint, QByteArray &responseBody)
|
GatewayController::EncryptedRequestData GatewayController::prepareRequest(const QString &endpoint, const QJsonObject &apiPayload)
|
||||||
{
|
{
|
||||||
|
EncryptedRequestData encRequestData;
|
||||||
|
encRequestData.errorCode = ErrorCode::NoError;
|
||||||
|
|
||||||
#ifdef Q_OS_IOS
|
#ifdef Q_OS_IOS
|
||||||
IosController::Instance()->requestInetAccess();
|
IosController::Instance()->requestInetAccess();
|
||||||
QThread::msleep(10);
|
QThread::msleep(10);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
QNetworkRequest request;
|
encRequestData.request.setTransferTimeout(m_requestTimeoutMsecs);
|
||||||
request.setTransferTimeout(m_requestTimeoutMsecs);
|
encRequestData.request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
encRequestData.request.setRawHeader(QString("X-Client-Request-ID").toUtf8(), QUuid::createUuid().toString(QUuid::WithoutBraces).toUtf8());
|
||||||
|
encRequestData.request.setUrl(endpoint.arg(m_proxyUrl.isEmpty() ? m_gatewayEndpoint : m_proxyUrl));
|
||||||
|
|
||||||
request.setUrl(QString(endpoint).arg(m_gatewayEndpoint));
|
// bypass killSwitch exceptions for API-gateway
|
||||||
|
#ifdef AMNEZIA_DESKTOP
|
||||||
QNetworkReply *reply;
|
if (m_isStrictKillSwitchEnabled) {
|
||||||
reply = amnApp->networkManager()->get(request);
|
QString host = QUrl(encRequestData.request.url()).host();
|
||||||
|
QString ip = NetworkUtilities::getIPAddress(host);
|
||||||
QEventLoop wait;
|
if (!ip.isEmpty()) {
|
||||||
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||||
|
QRemoteObjectPendingReply<bool> reply = iface->addKillSwitchAllowedRange(QStringList { ip });
|
||||||
QList<QSslError> sslErrors;
|
if (!reply.waitForFinished(1000) || !reply.returnValue())
|
||||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
qWarning() << "GatewayController::prepareRequest(): Failed to execute remote addKillSwitchAllowedRange call";
|
||||||
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);
|
|
||||||
};
|
|
||||||
|
|
||||||
auto replyProcessingFunction = [&responseBody, &reply, &sslErrors, this](QNetworkReply *nestedReply,
|
|
||||||
const QList<QSslError> &nestedSslErrors) {
|
|
||||||
responseBody = nestedReply->readAll();
|
|
||||||
if (!sslErrors.isEmpty() || !shouldBypassProxy(nestedReply, responseBody, false)) {
|
|
||||||
sslErrors = nestedSslErrors;
|
|
||||||
reply = nestedReply;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
bypassProxy(endpoint, reply, requestFunction, replyProcessingFunction);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, reply);
|
|
||||||
reply->deleteLater();
|
|
||||||
|
|
||||||
return errorCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody)
|
|
||||||
{
|
|
||||||
#ifdef Q_OS_IOS
|
|
||||||
IosController::Instance()->requestInetAccess();
|
|
||||||
QThread::msleep(10);
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
QNetworkRequest request;
|
|
||||||
request.setTransferTimeout(m_requestTimeoutMsecs);
|
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
|
||||||
|
|
||||||
request.setUrl(endpoint.arg(m_gatewayEndpoint));
|
|
||||||
|
|
||||||
QSimpleCrypto::QBlockCipher blockCipher;
|
QSimpleCrypto::QBlockCipher blockCipher;
|
||||||
QByteArray key = blockCipher.generatePrivateSalt(32);
|
encRequestData.key = blockCipher.generatePrivateSalt(32);
|
||||||
QByteArray iv = blockCipher.generatePrivateSalt(32);
|
encRequestData.iv = blockCipher.generatePrivateSalt(32);
|
||||||
QByteArray salt = blockCipher.generatePrivateSalt(8);
|
encRequestData.salt = blockCipher.generatePrivateSalt(8);
|
||||||
|
|
||||||
QJsonObject keyPayload;
|
QJsonObject keyPayload;
|
||||||
keyPayload[configKey::aesKey] = QString(key.toBase64());
|
keyPayload[configKey::aesKey] = QString(encRequestData.key.toBase64());
|
||||||
keyPayload[configKey::aesIv] = QString(iv.toBase64());
|
keyPayload[configKey::aesIv] = QString(encRequestData.iv.toBase64());
|
||||||
keyPayload[configKey::aesSalt] = QString(salt.toBase64());
|
keyPayload[configKey::aesSalt] = QString(encRequestData.salt.toBase64());
|
||||||
|
|
||||||
QByteArray encryptedKeyPayload;
|
QByteArray encryptedKeyPayload;
|
||||||
QByteArray encryptedApiPayload;
|
QByteArray encryptedApiPayload;
|
||||||
@@ -120,96 +116,277 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
|
|||||||
} catch (...) {
|
} catch (...) {
|
||||||
Utils::logException();
|
Utils::logException();
|
||||||
qCritical() << "error loading public key from environment variables";
|
qCritical() << "error loading public key from environment variables";
|
||||||
return ErrorCode::ApiMissingAgwPublicKey;
|
encRequestData.errorCode = ErrorCode::ApiMissingAgwPublicKey;
|
||||||
|
return encRequestData;
|
||||||
}
|
}
|
||||||
|
|
||||||
encryptedKeyPayload = rsa.encrypt(QJsonDocument(keyPayload).toJson(), publicKey, RSA_PKCS1_PADDING);
|
encryptedKeyPayload = rsa.encrypt(QJsonDocument(keyPayload).toJson(), publicKey, RSA_PKCS1_PADDING);
|
||||||
EVP_PKEY_free(publicKey);
|
EVP_PKEY_free(publicKey);
|
||||||
|
|
||||||
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), key, iv, "", salt);
|
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), encRequestData.key, encRequestData.iv,
|
||||||
} catch (...) { // todo change error handling in QSimpleCrypto?
|
"", encRequestData.salt);
|
||||||
|
} catch (...) {
|
||||||
Utils::logException();
|
Utils::logException();
|
||||||
qCritical() << "error when encrypting the request body";
|
qCritical() << "error when encrypting the request body";
|
||||||
return ErrorCode::ApiConfigDecryptionError;
|
encRequestData.errorCode = ErrorCode::ApiConfigDecryptionError;
|
||||||
|
return encRequestData;
|
||||||
}
|
}
|
||||||
|
|
||||||
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());
|
||||||
|
|
||||||
QNetworkReply *reply = amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson());
|
encRequestData.requestBody = QJsonDocument(requestBody).toJson();
|
||||||
|
return encRequestData;
|
||||||
|
}
|
||||||
|
|
||||||
|
GatewayController::DecryptionResult GatewayController::tryDecryptResponseBody(const QByteArray &encryptedResponseBody,
|
||||||
|
QNetworkReply::NetworkError replyError, const QByteArray &key,
|
||||||
|
const QByteArray &iv, const QByteArray &salt)
|
||||||
|
{
|
||||||
|
DecryptionResult result;
|
||||||
|
result.decryptedBody = encryptedResponseBody;
|
||||||
|
result.isDecryptionSuccessful = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
QSimpleCrypto::QBlockCipher blockCipher;
|
||||||
|
result.decryptedBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt);
|
||||||
|
result.isDecryptionSuccessful = true;
|
||||||
|
} catch (...) {
|
||||||
|
result.decryptedBody = encryptedResponseBody;
|
||||||
|
result.isDecryptionSuccessful = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody)
|
||||||
|
{
|
||||||
|
EncryptedRequestData encRequestData = prepareRequest(endpoint, apiPayload);
|
||||||
|
if (encRequestData.errorCode != ErrorCode::NoError) {
|
||||||
|
return encRequestData.errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
QNetworkReply *reply = amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
|
||||||
|
|
||||||
QEventLoop wait;
|
QEventLoop wait;
|
||||||
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||||
|
|
||||||
QList<QSslError> sslErrors;
|
QList<QSslError> sslErrors;
|
||||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||||
wait.exec();
|
wait.exec(QEventLoop::ExcludeUserInputEvents);
|
||||||
|
|
||||||
QByteArray encryptedResponseBody = reply->readAll();
|
QByteArray encryptedResponseBody = reply->readAll();
|
||||||
|
QString replyErrorString = reply->errorString();
|
||||||
|
auto replyError = reply->error();
|
||||||
|
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
|
|
||||||
if (sslErrors.isEmpty() && shouldBypassProxy(reply, encryptedResponseBody, true, key, iv, salt)) {
|
reply->deleteLater();
|
||||||
auto requestFunction = [&request, &encryptedResponseBody, &requestBody](const QString &url) {
|
|
||||||
request.setUrl(url);
|
auto decryptionResult =
|
||||||
return amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson());
|
tryDecryptResponseBody(encryptedResponseBody, replyError, encRequestData.key, encRequestData.iv, encRequestData.salt);
|
||||||
|
|
||||||
|
if (sslErrors.isEmpty() && shouldBypassProxy(replyError, decryptionResult.decryptedBody, decryptionResult.isDecryptionSuccessful)) {
|
||||||
|
auto requestFunction = [&encRequestData, &encryptedResponseBody](const QString &url) {
|
||||||
|
encRequestData.request.setUrl(url);
|
||||||
|
return amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
|
||||||
};
|
};
|
||||||
|
|
||||||
auto replyProcessingFunction = [&encryptedResponseBody, &reply, &sslErrors, &key, &iv, &salt,
|
auto replyProcessingFunction = [&encryptedResponseBody, &replyErrorString, &replyError, &httpStatusCode, &sslErrors, &encRequestData,
|
||||||
this](QNetworkReply *nestedReply, const QList<QSslError> &nestedSslErrors) {
|
&decryptionResult, this](QNetworkReply *reply, const QList<QSslError> &nestedSslErrors) {
|
||||||
encryptedResponseBody = nestedReply->readAll();
|
encryptedResponseBody = reply->readAll();
|
||||||
reply = nestedReply;
|
replyErrorString = reply->errorString();
|
||||||
if (!sslErrors.isEmpty() || shouldBypassProxy(nestedReply, encryptedResponseBody, true, key, iv, salt)) {
|
replyError = reply->error();
|
||||||
|
httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
|
|
||||||
|
decryptionResult =
|
||||||
|
tryDecryptResponseBody(encryptedResponseBody, replyError, encRequestData.key, encRequestData.iv, encRequestData.salt);
|
||||||
|
|
||||||
|
if (!sslErrors.isEmpty()
|
||||||
|
|| shouldBypassProxy(replyError, decryptionResult.decryptedBody, decryptionResult.isDecryptionSuccessful)) {
|
||||||
sslErrors = nestedSslErrors;
|
sslErrors = nestedSslErrors;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
bypassProxy(endpoint, reply, requestFunction, replyProcessingFunction);
|
auto serviceType = apiPayload.value(apiDefs::key::serviceType).toString("");
|
||||||
|
auto userCountryCode = apiPayload.value(apiDefs::key::userCountryCode).toString("");
|
||||||
|
bypassProxy(endpoint, serviceType, userCountryCode, requestFunction, replyProcessingFunction);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, reply);
|
auto errorCode =
|
||||||
reply->deleteLater();
|
apiUtils::checkNetworkReplyErrors(sslErrors, replyErrorString, replyError, httpStatusCode, decryptionResult.decryptedBody);
|
||||||
if (errorCode) {
|
if (errorCode) {
|
||||||
return errorCode;
|
return errorCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
if (!decryptionResult.isDecryptionSuccessful) {
|
||||||
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";
|
qCritical() << "error when decrypting the request body";
|
||||||
return ErrorCode::ApiConfigDecryptionError;
|
return ErrorCode::ApiConfigDecryptionError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
responseBody = decryptionResult.decryptedBody;
|
||||||
|
return ErrorCode::NoError;
|
||||||
}
|
}
|
||||||
|
|
||||||
QStringList GatewayController::getProxyUrls()
|
QFuture<QPair<ErrorCode, QByteArray>> GatewayController::postAsync(const QString &endpoint, const QJsonObject apiPayload)
|
||||||
|
{
|
||||||
|
auto promise = QSharedPointer<QPromise<QPair<ErrorCode, QByteArray>>>::create();
|
||||||
|
promise->start();
|
||||||
|
|
||||||
|
EncryptedRequestData encRequestData = prepareRequest(endpoint, apiPayload);
|
||||||
|
if (encRequestData.errorCode != ErrorCode::NoError) {
|
||||||
|
promise->addResult(qMakePair(encRequestData.errorCode, QByteArray()));
|
||||||
|
promise->finish();
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
QNetworkReply *reply = amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
|
||||||
|
|
||||||
|
auto sslErrors = QSharedPointer<QList<QSslError>>::create();
|
||||||
|
|
||||||
|
connect(reply, &QNetworkReply::sslErrors, [sslErrors](const QList<QSslError> &errors) { *sslErrors = errors; });
|
||||||
|
|
||||||
|
connect(reply, &QNetworkReply::finished, reply, [promise, sslErrors, encRequestData, endpoint, apiPayload, reply, this]() mutable {
|
||||||
|
QByteArray encryptedResponseBody = reply->readAll();
|
||||||
|
QString replyErrorString = reply->errorString();
|
||||||
|
auto replyError = reply->error();
|
||||||
|
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
|
|
||||||
|
reply->deleteLater();
|
||||||
|
|
||||||
|
auto decryptionResult =
|
||||||
|
tryDecryptResponseBody(encryptedResponseBody, replyError, encRequestData.key, encRequestData.iv, encRequestData.salt);
|
||||||
|
|
||||||
|
auto processResponse = [promise, encRequestData](const GatewayController::DecryptionResult &decryptionResult,
|
||||||
|
const QList<QSslError> &sslErrors, QNetworkReply::NetworkError replyError,
|
||||||
|
const QString &replyErrorString, int httpStatusCode) {
|
||||||
|
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, replyErrorString, replyError, httpStatusCode,
|
||||||
|
decryptionResult.decryptedBody);
|
||||||
|
if (errorCode) {
|
||||||
|
promise->addResult(qMakePair(errorCode, QByteArray()));
|
||||||
|
promise->finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!decryptionResult.isDecryptionSuccessful) {
|
||||||
|
Utils::logException();
|
||||||
|
qCritical() << "error when decrypting the request body";
|
||||||
|
promise->addResult(qMakePair(ErrorCode::ApiConfigDecryptionError, QByteArray()));
|
||||||
|
promise->finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
promise->addResult(qMakePair(ErrorCode::NoError, decryptionResult.decryptedBody));
|
||||||
|
promise->finish();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (sslErrors->isEmpty() && shouldBypassProxy(replyError, decryptionResult.decryptedBody, decryptionResult.isDecryptionSuccessful)) {
|
||||||
|
auto serviceType = apiPayload.value(apiDefs::key::serviceType).toString("");
|
||||||
|
auto userCountryCode = apiPayload.value(apiDefs::key::userCountryCode).toString("");
|
||||||
|
|
||||||
|
QStringList primaryBaseUrls;
|
||||||
|
QStringList fallbackBaseUrls;
|
||||||
|
if (m_isDevEnvironment) {
|
||||||
|
primaryBaseUrls = QString(DEV_S3_ENDPOINT).split(", ", Qt::SkipEmptyParts);
|
||||||
|
} else {
|
||||||
|
primaryBaseUrls = QString(PROD_S3_ENDPOINT).split(", ", Qt::SkipEmptyParts);
|
||||||
|
fallbackBaseUrls = QString(FALLBACK_S3_ENDPOINT).split(", ", Qt::SkipEmptyParts);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto appendStorageUrls = [&serviceType, &userCountryCode](const QStringList &baseUrls, QStringList &target) {
|
||||||
|
if (!serviceType.isEmpty()) {
|
||||||
|
for (const auto &baseUrl : baseUrls) {
|
||||||
|
QByteArray path = ("endpoints-" + serviceType + "-" + userCountryCode).toUtf8();
|
||||||
|
target.push_back(baseUrl + path.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals) + ".json");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const auto &baseUrl : baseUrls) {
|
||||||
|
target.push_back(baseUrl + "endpoints.json");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
QStringList proxyStorageUrls;
|
||||||
|
appendStorageUrls(primaryBaseUrls, proxyStorageUrls);
|
||||||
|
appendStorageUrls(fallbackBaseUrls, proxyStorageUrls);
|
||||||
|
|
||||||
|
getProxyUrlsAsync(proxyStorageUrls, 0, [this, encRequestData, endpoint, processResponse](const QStringList &proxyUrls) {
|
||||||
|
getProxyUrlAsync(proxyUrls, 0, [this, encRequestData, endpoint, processResponse](const QString &proxyUrl) {
|
||||||
|
bypassProxyAsync(endpoint, proxyUrl, encRequestData,
|
||||||
|
[processResponse, this](const QByteArray &decryptedBody, bool isDecryptionSuccessful,
|
||||||
|
const QList<QSslError> &sslErrors, QNetworkReply::NetworkError replyError,
|
||||||
|
const QString &replyErrorString, int httpStatusCode) {
|
||||||
|
GatewayController::DecryptionResult result;
|
||||||
|
result.decryptedBody = decryptedBody;
|
||||||
|
result.isDecryptionSuccessful = isDecryptionSuccessful;
|
||||||
|
processResponse(result, sslErrors, replyError, replyErrorString, httpStatusCode);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
} else {
|
||||||
|
processResponse(decryptionResult, *sslErrors, replyError, replyErrorString, httpStatusCode);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return promise->future();
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList GatewayController::getProxyUrls(const QString &serviceType, const QString &userCountryCode)
|
||||||
{
|
{
|
||||||
QNetworkRequest request;
|
QNetworkRequest request;
|
||||||
request.setTransferTimeout(m_requestTimeoutMsecs);
|
request.setTransferTimeout(proxyStorageRequestTimeoutMsecs);
|
||||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
|
|
||||||
QEventLoop wait;
|
QEventLoop wait;
|
||||||
QList<QSslError> sslErrors;
|
QList<QSslError> sslErrors;
|
||||||
QNetworkReply *reply;
|
QNetworkReply *reply;
|
||||||
|
|
||||||
QStringList proxyStorageUrl;
|
QStringList primaryBaseUrls;
|
||||||
|
QStringList fallbackBaseUrls;
|
||||||
if (m_isDevEnvironment) {
|
if (m_isDevEnvironment) {
|
||||||
proxyStorageUrl = QStringList { DEV_S3_ENDPOINT };
|
primaryBaseUrls = QString(DEV_S3_ENDPOINT).split(", ", Qt::SkipEmptyParts);
|
||||||
} else {
|
} else {
|
||||||
proxyStorageUrl = QStringList { PROD_S3_ENDPOINT };
|
primaryBaseUrls = QString(PROD_S3_ENDPOINT).split(", ", Qt::SkipEmptyParts);
|
||||||
|
fallbackBaseUrls = QString(FALLBACK_S3_ENDPOINT).split(", ", Qt::SkipEmptyParts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::random_device randomDevice;
|
||||||
|
std::mt19937 generator(randomDevice());
|
||||||
|
std::shuffle(primaryBaseUrls.begin(), primaryBaseUrls.end(), generator);
|
||||||
|
std::shuffle(fallbackBaseUrls.begin(), fallbackBaseUrls.end(), generator);
|
||||||
|
|
||||||
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
||||||
|
|
||||||
for (const auto &proxyStorageUrl : proxyStorageUrl) {
|
auto appendStorageUrls = [&serviceType, &userCountryCode](const QStringList &baseUrls, QStringList &target) {
|
||||||
|
if (!serviceType.isEmpty()) {
|
||||||
|
for (const auto &baseUrl : baseUrls) {
|
||||||
|
QByteArray path = ("endpoints-" + serviceType + "-" + userCountryCode).toUtf8();
|
||||||
|
target.push_back(baseUrl + path.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals) + ".json");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const auto &baseUrl : baseUrls) {
|
||||||
|
target.push_back(baseUrl + "endpoints.json");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
QStringList proxyStorageUrls;
|
||||||
|
appendStorageUrls(primaryBaseUrls, proxyStorageUrls);
|
||||||
|
appendStorageUrls(fallbackBaseUrls, proxyStorageUrls);
|
||||||
|
|
||||||
|
if (proxyStorageUrls.empty()) {
|
||||||
|
qDebug() << "empty storage endpoint list";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto &proxyStorageUrl : proxyStorageUrls) {
|
||||||
request.setUrl(proxyStorageUrl);
|
request.setUrl(proxyStorageUrl);
|
||||||
reply = amnApp->networkManager()->get(request);
|
reply = amnApp->networkManager()->get(request);
|
||||||
|
|
||||||
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||||
wait.exec();
|
wait.exec(QEventLoop::ExcludeUserInputEvents);
|
||||||
|
|
||||||
if (reply->error() == QNetworkReply::NetworkError::NoError) {
|
if (reply->error() == QNetworkReply::NetworkError::NoError) {
|
||||||
auto encryptedResponseBody = reply->readAll();
|
auto encryptedResponseBody = reply->readAll();
|
||||||
@@ -247,57 +424,284 @@ QStringList GatewayController::getProxyUrls()
|
|||||||
}
|
}
|
||||||
return endpoints;
|
return endpoints;
|
||||||
} else {
|
} else {
|
||||||
|
auto replyError = reply->error();
|
||||||
|
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
|
qDebug() << replyError;
|
||||||
|
qDebug() << httpStatusCode;
|
||||||
|
qDebug() << "go to the next storage endpoint";
|
||||||
|
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GatewayController::shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key,
|
bool GatewayController::shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &decryptedResponseBody,
|
||||||
const QByteArray &iv, const QByteArray &salt)
|
bool isDecryptionSuccessful)
|
||||||
{
|
{
|
||||||
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError || reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
const QByteArray &responseBody = decryptedResponseBody;
|
||||||
qDebug() << "Timeout occurred";
|
|
||||||
|
int apiHttpStatus = -1;
|
||||||
|
QString apiErrorMessage;
|
||||||
|
if (isDecryptionSuccessful) {
|
||||||
|
QJsonDocument jsonDoc = QJsonDocument::fromJson(responseBody);
|
||||||
|
if (jsonDoc.isObject()) {
|
||||||
|
QJsonObject jsonObj = jsonDoc.object();
|
||||||
|
apiHttpStatus = jsonObj.value("http_status").toInt(-1);
|
||||||
|
apiErrorMessage = jsonObj.value(QStringLiteral("message")).toString().trimmed();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
qDebug() << "failed to decrypt the data";
|
||||||
return true;
|
return true;
|
||||||
} else if (responseBody.contains("html")) {
|
}
|
||||||
qDebug() << "The response contains an html tag";
|
|
||||||
|
if (replyError == QNetworkReply::NetworkError::OperationCanceledError || replyError == QNetworkReply::NetworkError::TimeoutError) {
|
||||||
|
qDebug() << "timeout occurred";
|
||||||
|
qDebug() << replyError;
|
||||||
return true;
|
return true;
|
||||||
} else if (reply->error() == QNetworkReply::NetworkError::NoError && checkEncryption) {
|
}
|
||||||
try {
|
if (responseBody.contains("html")) {
|
||||||
QSimpleCrypto::QBlockCipher blockCipher;
|
qDebug() << "the response contains an html tag";
|
||||||
static_cast<void>(blockCipher.decryptAesBlockCipher(responseBody, key, iv, "", salt));
|
return true;
|
||||||
} catch (...) {
|
}
|
||||||
qDebug() << "Failed to decrypt the data";
|
if (apiHttpStatus == httpStatusCodeNotFound) {
|
||||||
|
if (responseBody.contains(errorResponsePattern1) || responseBody.contains(errorResponsePattern2)
|
||||||
|
|| responseBody.contains(errorResponsePattern3)) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
qDebug() << replyError;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (apiHttpStatus == httpStatusCodeNotImplemented) {
|
||||||
|
if (responseBody.contains(updateRequestResponsePattern)) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
qDebug() << replyError;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (apiHttpStatus == httpStatusCodeConflict) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (apiHttpStatus == httpStatusCodePaymentRequired) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (apiHttpStatus == httpStatusCodeUnprocessableEntity) {
|
||||||
|
return apiErrorMessage != unprocessableSubscriptionMessage;
|
||||||
|
}
|
||||||
|
if (replyError != QNetworkReply::NetworkError::NoError) {
|
||||||
|
qDebug() << replyError;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GatewayController::bypassProxy(const QString &endpoint, QNetworkReply *reply,
|
void GatewayController::bypassProxy(const QString &endpoint, const QString &serviceType, const QString &userCountryCode,
|
||||||
std::function<QNetworkReply *(const QString &url)> requestFunction,
|
std::function<QNetworkReply *(const QString &url)> requestFunction,
|
||||||
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction)
|
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction)
|
||||||
{
|
{
|
||||||
QStringList proxyUrls = getProxyUrls();
|
QStringList proxyUrls = getProxyUrls(serviceType, userCountryCode);
|
||||||
std::random_device randomDevice;
|
std::random_device randomDevice;
|
||||||
std::mt19937 generator(randomDevice());
|
std::mt19937 generator(randomDevice());
|
||||||
std::shuffle(proxyUrls.begin(), proxyUrls.end(), generator);
|
std::shuffle(proxyUrls.begin(), proxyUrls.end(), generator);
|
||||||
|
|
||||||
QEventLoop wait;
|
|
||||||
QList<QSslError> sslErrors;
|
|
||||||
QByteArray responseBody;
|
QByteArray responseBody;
|
||||||
|
|
||||||
for (const QString &proxyUrl : proxyUrls) {
|
auto bypassFunction = [this](const QString &endpoint, const QString &proxyUrl,
|
||||||
qDebug() << "Go to the next endpoint";
|
std::function<QNetworkReply *(const QString &url)> requestFunction,
|
||||||
reply->deleteLater(); // delete the previous reply
|
std::function<bool(QNetworkReply * reply, const QList<QSslError> &sslErrors)> replyProcessingFunction) {
|
||||||
reply = requestFunction(endpoint.arg(proxyUrl));
|
QEventLoop wait;
|
||||||
|
QList<QSslError> sslErrors;
|
||||||
|
|
||||||
|
qDebug() << "go to the next proxy endpoint";
|
||||||
|
QNetworkReply *reply = requestFunction(endpoint.arg(proxyUrl));
|
||||||
|
|
||||||
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||||
wait.exec();
|
wait.exec(QEventLoop::ExcludeUserInputEvents);
|
||||||
|
|
||||||
if (replyProcessingFunction(reply, sslErrors)) {
|
auto result = replyProcessingFunction(reply, sslErrors);
|
||||||
|
reply->deleteLater();
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (m_proxyUrl.isEmpty()) {
|
||||||
|
QNetworkRequest request;
|
||||||
|
request.setTransferTimeout(1000);
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
|
|
||||||
|
QEventLoop wait;
|
||||||
|
QList<QSslError> sslErrors;
|
||||||
|
QNetworkReply *reply;
|
||||||
|
|
||||||
|
for (const QString &proxyUrl : proxyUrls) {
|
||||||
|
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(QEventLoop::ExcludeUserInputEvents);
|
||||||
|
|
||||||
|
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, requestFunction, replyProcessingFunction)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const QString &proxyUrl : proxyUrls) {
|
||||||
|
if (bypassFunction(endpoint, proxyUrl, requestFunction, replyProcessingFunction)) {
|
||||||
|
m_proxyUrl = proxyUrl;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GatewayController::getProxyUrlsAsync(const QStringList proxyStorageUrls, const int currentProxyStorageIndex,
|
||||||
|
std::function<void(const QStringList &)> onComplete)
|
||||||
|
{
|
||||||
|
if (currentProxyStorageIndex >= proxyStorageUrls.size()) {
|
||||||
|
onComplete({});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QNetworkRequest request;
|
||||||
|
request.setTransferTimeout(proxyStorageRequestTimeoutMsecs);
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
|
request.setUrl(proxyStorageUrls[currentProxyStorageIndex]);
|
||||||
|
|
||||||
|
QNetworkReply *reply = amnApp->networkManager()->get(request);
|
||||||
|
|
||||||
|
// connect(reply, &QNetworkReply::sslErrors, this, [state](const QList<QSslError> &e) { *(state->sslErrors) = e; });
|
||||||
|
|
||||||
|
connect(reply, &QNetworkReply::finished, this, [this, proxyStorageUrls, currentProxyStorageIndex, onComplete, reply]() {
|
||||||
|
if (reply->error() == QNetworkReply::NoError) {
|
||||||
|
QByteArray encrypted = reply->readAll();
|
||||||
|
reply->deleteLater();
|
||||||
|
|
||||||
|
QByteArray responseBody;
|
||||||
|
try {
|
||||||
|
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
||||||
|
if (!m_isDevEnvironment) {
|
||||||
|
QCryptographicHash hash(QCryptographicHash::Sha512);
|
||||||
|
hash.addData(key);
|
||||||
|
QByteArray h = hash.result().toHex();
|
||||||
|
|
||||||
|
QByteArray decKey = QByteArray::fromHex(h.left(64));
|
||||||
|
QByteArray iv = QByteArray::fromHex(h.mid(64, 32));
|
||||||
|
QByteArray ba = QByteArray::fromBase64(encrypted);
|
||||||
|
|
||||||
|
QSimpleCrypto::QBlockCipher cipher;
|
||||||
|
responseBody = cipher.decryptAesBlockCipher(ba, decKey, iv);
|
||||||
|
} else {
|
||||||
|
responseBody = encrypted;
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
Utils::logException();
|
||||||
|
qCritical() << "error decrypting payload";
|
||||||
|
QMetaObject::invokeMethod(
|
||||||
|
this, [=]() { getProxyUrlsAsync(proxyStorageUrls, currentProxyStorageIndex + 1, onComplete); }, Qt::QueuedConnection);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonArray endpointsArray = QJsonDocument::fromJson(responseBody).array();
|
||||||
|
QStringList endpoints;
|
||||||
|
for (const QJsonValue &endpoint : endpointsArray)
|
||||||
|
endpoints.push_back(endpoint.toString());
|
||||||
|
|
||||||
|
QStringList shuffled = endpoints;
|
||||||
|
std::random_device randomDevice;
|
||||||
|
std::mt19937 generator(randomDevice());
|
||||||
|
std::shuffle(shuffled.begin(), shuffled.end(), generator);
|
||||||
|
|
||||||
|
onComplete(shuffled);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
|
qDebug() << httpStatusCode;
|
||||||
|
qDebug() << "go to the next storage endpoint";
|
||||||
|
reply->deleteLater();
|
||||||
|
QMetaObject::invokeMethod(
|
||||||
|
this, [=]() { getProxyUrlsAsync(proxyStorageUrls, currentProxyStorageIndex + 1, onComplete); }, Qt::QueuedConnection);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void GatewayController::getProxyUrlAsync(const QStringList proxyUrls, const int currentProxyIndex,
|
||||||
|
std::function<void(const QString &)> onComplete)
|
||||||
|
{
|
||||||
|
if (currentProxyIndex >= proxyUrls.size()) {
|
||||||
|
onComplete("");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QNetworkRequest request;
|
||||||
|
request.setTransferTimeout(1000);
|
||||||
|
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||||
|
request.setUrl(proxyUrls[currentProxyIndex] + "lmbd-health");
|
||||||
|
|
||||||
|
QNetworkReply *reply = amnApp->networkManager()->get(request);
|
||||||
|
|
||||||
|
// connect(reply, &QNetworkReply::sslErrors, this, [state](const QList<QSslError> &e) {
|
||||||
|
// *(state->sslErrors) = e;
|
||||||
|
// });
|
||||||
|
|
||||||
|
connect(reply, &QNetworkReply::finished, this, [this, proxyUrls, currentProxyIndex, onComplete, reply]() {
|
||||||
|
reply->deleteLater();
|
||||||
|
|
||||||
|
if (reply->error() == QNetworkReply::NoError) {
|
||||||
|
m_proxyUrl = proxyUrls[currentProxyIndex];
|
||||||
|
onComplete(m_proxyUrl);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "go to the next proxy endpoint";
|
||||||
|
QMetaObject::invokeMethod(this, [=]() { getProxyUrlAsync(proxyUrls, currentProxyIndex + 1, onComplete); }, Qt::QueuedConnection);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void GatewayController::bypassProxyAsync(
|
||||||
|
const QString &endpoint, const QString &proxyUrl, EncryptedRequestData encRequestData,
|
||||||
|
std::function<void(const QByteArray &, bool, const QList<QSslError> &, QNetworkReply::NetworkError, const QString &, int)> onComplete)
|
||||||
|
{
|
||||||
|
auto sslErrors = QSharedPointer<QList<QSslError>>::create();
|
||||||
|
if (proxyUrl.isEmpty()) {
|
||||||
|
onComplete(QByteArray(), false, *sslErrors, QNetworkReply::InternalServerError, "empty proxy url", 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QNetworkRequest request = encRequestData.request;
|
||||||
|
request.setUrl(endpoint.arg(proxyUrl));
|
||||||
|
|
||||||
|
QNetworkReply *reply = amnApp->networkManager()->post(request, encRequestData.requestBody);
|
||||||
|
|
||||||
|
connect(reply, &QNetworkReply::sslErrors, this, [sslErrors](const QList<QSslError> &errors) { *sslErrors = errors; });
|
||||||
|
|
||||||
|
connect(reply, &QNetworkReply::finished, this, [sslErrors, onComplete, encRequestData, reply, this]() {
|
||||||
|
QByteArray encryptedResponseBody = reply->readAll();
|
||||||
|
QString replyErrorString = reply->errorString();
|
||||||
|
auto replyError = reply->error();
|
||||||
|
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
|
|
||||||
|
reply->deleteLater();
|
||||||
|
|
||||||
|
auto decryptionResult =
|
||||||
|
tryDecryptResponseBody(encryptedResponseBody, replyError, encRequestData.key, encRequestData.iv, encRequestData.salt);
|
||||||
|
|
||||||
|
onComplete(decryptionResult.decryptedBody, decryptionResult.isDecryptionSuccessful, *sslErrors, replyError, replyErrorString,
|
||||||
|
httpStatusCode);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
#ifndef GATEWAYCONTROLLER_H
|
#ifndef GATEWAYCONTROLLER_H
|
||||||
#define GATEWAYCONTROLLER_H
|
#define GATEWAYCONTROLLER_H
|
||||||
|
|
||||||
|
#include <QFuture>
|
||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QPair>
|
||||||
|
#include <QPromise>
|
||||||
|
#include <QSharedPointer>
|
||||||
|
|
||||||
#include "core/defs.h"
|
#include "core/defs.h"
|
||||||
|
|
||||||
@@ -15,21 +19,52 @@ class GatewayController : public QObject
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit GatewayController(const QString &gatewayEndpoint, bool isDevEnvironment, int requestTimeoutMsecs, QObject *parent = nullptr);
|
explicit GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, 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);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QStringList getProxyUrls();
|
struct EncryptedRequestData
|
||||||
bool shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key = "",
|
{
|
||||||
const QByteArray &iv = "", const QByteArray &salt = "");
|
QNetworkRequest request;
|
||||||
void bypassProxy(const QString &endpoint, QNetworkReply *reply, std::function<QNetworkReply *(const QString &url)> requestFunction,
|
QByteArray requestBody;
|
||||||
|
QByteArray key;
|
||||||
|
QByteArray iv;
|
||||||
|
QByteArray salt;
|
||||||
|
amnezia::ErrorCode errorCode;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DecryptionResult
|
||||||
|
{
|
||||||
|
QByteArray decryptedBody;
|
||||||
|
bool isDecryptionSuccessful;
|
||||||
|
};
|
||||||
|
|
||||||
|
EncryptedRequestData prepareRequest(const QString &endpoint, const QJsonObject &apiPayload);
|
||||||
|
DecryptionResult tryDecryptResponseBody(const QByteArray &encryptedResponseBody, QNetworkReply::NetworkError replyError,
|
||||||
|
const QByteArray &key, const QByteArray &iv, const QByteArray &salt);
|
||||||
|
|
||||||
|
QStringList getProxyUrls(const QString &serviceType, const QString &userCountryCode);
|
||||||
|
bool shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &decryptedResponseBody, bool isDecryptionSuccessful);
|
||||||
|
void bypassProxy(const QString &endpoint, const QString &serviceType, const QString &userCountryCode,
|
||||||
|
std::function<QNetworkReply *(const QString &url)> requestFunction,
|
||||||
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction);
|
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction);
|
||||||
|
|
||||||
|
void getProxyUrlsAsync(const QStringList proxyStorageUrls, const int currentProxyStorageIndex,
|
||||||
|
std::function<void(const QStringList &)> onComplete);
|
||||||
|
void getProxyUrlAsync(const QStringList proxyUrls, const int currentProxyIndex, std::function<void(const QString &)> onComplete);
|
||||||
|
void bypassProxyAsync(
|
||||||
|
const QString &endpoint, const QString &proxyUrl, EncryptedRequestData encRequestData,
|
||||||
|
std::function<void(const QByteArray &, bool, const QList<QSslError> &, QNetworkReply::NetworkError, const QString &, int)> onComplete);
|
||||||
|
|
||||||
int m_requestTimeoutMsecs;
|
int m_requestTimeoutMsecs;
|
||||||
QString m_gatewayEndpoint;
|
QString m_gatewayEndpoint;
|
||||||
bool m_isDevEnvironment = false;
|
bool m_isDevEnvironment = false;
|
||||||
|
bool m_isStrictKillSwitchEnabled = false;
|
||||||
|
|
||||||
|
inline static QString m_proxyUrl;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // GATEWAYCONTROLLER_H
|
#endif // GATEWAYCONTROLLER_H
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
|
|||||||
|
|
||||||
if (overwriteMode == libssh::ScpOverwriteMode::ScpOverwriteExisting) {
|
if (overwriteMode == libssh::ScpOverwriteMode::ScpOverwriteExisting) {
|
||||||
e = runScript(credentials,
|
e = runScript(credentials,
|
||||||
replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(path),
|
replaceVars(QStringLiteral("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName, path),
|
||||||
genVarsForScript(credentials, container)),
|
genVarsForScript(credentials, container)),
|
||||||
cbReadStd, cbReadStd);
|
cbReadStd, cbReadStd);
|
||||||
|
|
||||||
@@ -146,7 +146,7 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
|
|||||||
return e;
|
return e;
|
||||||
} else if (overwriteMode == libssh::ScpOverwriteMode::ScpAppendToExisting) {
|
} else if (overwriteMode == libssh::ScpOverwriteMode::ScpAppendToExisting) {
|
||||||
e = runScript(credentials,
|
e = runScript(credentials,
|
||||||
replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(tmpFileName),
|
replaceVars(QStringLiteral("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName, tmpFileName),
|
||||||
genVarsForScript(credentials, container)),
|
genVarsForScript(credentials, container)),
|
||||||
cbReadStd, cbReadStd);
|
cbReadStd, cbReadStd);
|
||||||
|
|
||||||
@@ -154,7 +154,7 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
|
|||||||
return e;
|
return e;
|
||||||
|
|
||||||
e = runScript(credentials,
|
e = runScript(credentials,
|
||||||
replaceVars(QString("sudo docker exec -i $CONTAINER_NAME sh -c \"cat %1 >> %2\"").arg(tmpFileName).arg(path),
|
replaceVars(QStringLiteral("sudo docker exec -i $CONTAINER_NAME sh -c \"cat %1 >> %2\"").arg(tmpFileName, path),
|
||||||
genVarsForScript(credentials, container)),
|
genVarsForScript(credentials, container)),
|
||||||
cbReadStd, cbReadStd);
|
cbReadStd, cbReadStd);
|
||||||
|
|
||||||
@@ -177,7 +177,7 @@ QByteArray ServerController::getTextFileFromContainer(DockerContainer container,
|
|||||||
|
|
||||||
errorCode = ErrorCode::NoError;
|
errorCode = ErrorCode::NoError;
|
||||||
|
|
||||||
QString script = QString("sudo docker exec -i %1 sh -c \"xxd -p \'%2\'\"").arg(ContainerProps::containerToString(container)).arg(path);
|
QString script = QStringLiteral("sudo docker exec -i %1 sh -c \"xxd -p '%2'\"").arg(ContainerProps::containerToString(container), path);
|
||||||
|
|
||||||
QString stdOut;
|
QString stdOut;
|
||||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||||
@@ -345,11 +345,11 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (container == DockerContainer::Awg) {
|
if (ContainerProps::isAwgContainer(container)) {
|
||||||
if ((oldProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress)
|
if ((oldProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress)
|
||||||
!= newProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress))
|
!= newProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress))
|
||||||
|| (oldProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort)
|
|| (oldProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort)
|
||||||
!= newProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort))
|
!= newProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort))
|
||||||
|| (oldProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount)
|
|| (oldProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount)
|
||||||
!= newProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount))
|
!= newProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount))
|
||||||
|| (oldProtoConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize)
|
|| (oldProtoConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize)
|
||||||
@@ -366,8 +366,13 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
|
|||||||
!= newProtoConfig.value(config_key::responsePacketMagicHeader).toString(protocols::awg::defaultResponsePacketMagicHeader))
|
!= newProtoConfig.value(config_key::responsePacketMagicHeader).toString(protocols::awg::defaultResponsePacketMagicHeader))
|
||||||
|| (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)
|
||||||
|
!= newProtoConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize))
|
||||||
|
|| (oldProtoConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize)
|
||||||
|
!= newProtoConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize)))
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,7 +380,7 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
|
|||||||
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::wireguard::defaultPort)
|
|| (oldProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort)
|
||||||
!= newProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort)))
|
!= newProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort)))
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,6 +388,13 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (container == DockerContainer::Xray) {
|
||||||
|
if (oldProtoConfig.value(config_key::port).toString(protocols::xray::defaultPort)
|
||||||
|
!= newProtoConfig.value(config_key::port).toString(protocols::xray::defaultPort)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,6 +419,18 @@ ErrorCode ServerController::installDockerWorker(const ServerCredentials &credent
|
|||||||
cbReadStdOut, cbReadStdErr);
|
cbReadStdOut, cbReadStdErr);
|
||||||
|
|
||||||
qDebug().noquote() << "ServerController::installDockerWorker" << stdOut;
|
qDebug().noquote() << "ServerController::installDockerWorker" << stdOut;
|
||||||
|
if (container == DockerContainer::Awg2) {
|
||||||
|
QRegularExpression regex(R"(Linux\s+(\d+)\.(\d+)[^\d]*)");
|
||||||
|
QRegularExpressionMatch match = regex.match(stdOut);
|
||||||
|
if (match.hasMatch()) {
|
||||||
|
int majorVersion = match.captured(1).toInt();
|
||||||
|
int minorVersion = match.captured(2).toInt();
|
||||||
|
|
||||||
|
if (majorVersion < 4 || (majorVersion == 4 && minorVersion < 14)) {
|
||||||
|
return ErrorCode::ServerLinuxKernelTooOld;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (stdOut.contains("lock"))
|
if (stdOut.contains("lock"))
|
||||||
return ErrorCode::ServerPacketManagerError;
|
return ErrorCode::ServerPacketManagerError;
|
||||||
if (stdOut.contains("command not found"))
|
if (stdOut.contains("command not found"))
|
||||||
@@ -439,15 +463,24 @@ ErrorCode ServerController::buildContainerWorker(const ServerCredentials &creden
|
|||||||
stdOut += data + "\n";
|
stdOut += data + "\n";
|
||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
};
|
};
|
||||||
|
auto cbReadStdErr = [&](const QString &data, libssh::Client &) {
|
||||||
|
stdOut += data + "\n";
|
||||||
|
return ErrorCode::NoError;
|
||||||
|
};
|
||||||
|
|
||||||
errorCode =
|
ErrorCode error =
|
||||||
runScript(credentials,
|
runScript(credentials,
|
||||||
replaceVars(amnezia::scriptData(SharedScriptType::build_container), genVarsForScript(credentials, container, config)),
|
replaceVars(amnezia::scriptData(SharedScriptType::build_container), genVarsForScript(credentials, container, config)),
|
||||||
cbReadStdOut);
|
cbReadStdOut, cbReadStdErr);
|
||||||
if (errorCode)
|
|
||||||
return errorCode;
|
|
||||||
|
|
||||||
return errorCode;
|
if (stdOut.contains("doesn't work on cgroups v2"))
|
||||||
|
return ErrorCode::ServerDockerOnCgroupsV2;
|
||||||
|
if (stdOut.contains("cgroup mountpoint does not exist"))
|
||||||
|
return ErrorCode::ServerCgroupMountpoint;
|
||||||
|
if (stdOut.contains("have reached") && stdOut.contains("pull rate limit"))
|
||||||
|
return ErrorCode::DockerPullRateLimit;
|
||||||
|
|
||||||
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode ServerController::runContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config)
|
ErrorCode ServerController::runContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config)
|
||||||
@@ -625,6 +658,14 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
|
|||||||
vars.append({ { "$UNDERLOAD_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::underloadPacketMagicHeader).toString() } });
|
vars.append({ { "$UNDERLOAD_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::underloadPacketMagicHeader).toString() } });
|
||||||
vars.append({ { "$TRANSPORT_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::transportPacketMagicHeader).toString() } });
|
vars.append({ { "$TRANSPORT_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::transportPacketMagicHeader).toString() } });
|
||||||
|
|
||||||
|
vars.append({ { "$COOKIE_REPLY_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::cookieReplyPacketJunkSize).toString() } });
|
||||||
|
vars.append({ { "$TRANSPORT_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::transportPacketJunkSize).toString() } });
|
||||||
|
vars.append({ { "$SPECIAL_JUNK_1", amneziaWireguarConfig.value(config_key::specialJunk1).toString() } });
|
||||||
|
vars.append({ { "$SPECIAL_JUNK_2", amneziaWireguarConfig.value(config_key::specialJunk2).toString() } });
|
||||||
|
vars.append({ { "$SPECIAL_JUNK_3", amneziaWireguarConfig.value(config_key::specialJunk3).toString() } });
|
||||||
|
vars.append({ { "$SPECIAL_JUNK_4", amneziaWireguarConfig.value(config_key::specialJunk4).toString() } });
|
||||||
|
vars.append({ { "$SPECIAL_JUNK_5", amneziaWireguarConfig.value(config_key::specialJunk5).toString() } });
|
||||||
|
|
||||||
// Socks5 proxy vars
|
// 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) } });
|
||||||
auto username = socks5ProxyConfig.value(config_key::userName).toString();
|
auto username = socks5ProxyConfig.value(config_key::userName).toString();
|
||||||
@@ -633,7 +674,8 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
|
|||||||
vars.append({ { "$SOCKS5_USER", socks5user } });
|
vars.append({ { "$SOCKS5_USER", socks5user } });
|
||||||
vars.append({ { "$SOCKS5_AUTH_TYPE", socks5user.isEmpty() ? "none" : "strong" } });
|
vars.append({ { "$SOCKS5_AUTH_TYPE", socks5user.isEmpty() ? "none" : "strong" } });
|
||||||
|
|
||||||
QString serverIp = (container != DockerContainer::Awg && container != DockerContainer::WireGuard && container != DockerContainer::Xray)
|
QString serverIp = (!ContainerProps::isAwgContainer(container) &&
|
||||||
|
container != DockerContainer::WireGuard && container != DockerContainer::Xray)
|
||||||
? NetworkUtilities::getIPAddress(credentials.hostName)
|
? NetworkUtilities::getIPAddress(credentials.hostName)
|
||||||
: credentials.hostName;
|
: credentials.hostName;
|
||||||
if (!serverIp.isEmpty()) {
|
if (!serverIp.isEmpty()) {
|
||||||
@@ -709,7 +751,7 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential
|
|||||||
QString transportProto = containerConfig.value(config_key::transport_proto).toString(defaultTransportProto);
|
QString transportProto = containerConfig.value(config_key::transport_proto).toString(defaultTransportProto);
|
||||||
|
|
||||||
// TODO reimplement with netstat
|
// TODO reimplement with netstat
|
||||||
QString script = QString("which lsof &>/dev/null || true && sudo lsof -i -P -n 2>/dev/null | grep -E ':%1 ").arg(port);
|
QString script = QString("which lsof > /dev/null 2>&1 || true && sudo lsof -i -P -n 2>/dev/null | grep -E ':%1 ").arg(port);
|
||||||
for (auto &port : fixedPorts) {
|
for (auto &port : fixedPorts) {
|
||||||
script = script.append("|:%1").arg(port);
|
script = script.append("|:%1").arg(port);
|
||||||
}
|
}
|
||||||
@@ -757,10 +799,6 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential
|
|||||||
|
|
||||||
ErrorCode ServerController::isUserInSudo(const ServerCredentials &credentials, DockerContainer container)
|
ErrorCode ServerController::isUserInSudo(const ServerCredentials &credentials, DockerContainer container)
|
||||||
{
|
{
|
||||||
if (credentials.userName == "root") {
|
|
||||||
return ErrorCode::NoError;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString stdOut;
|
QString stdOut;
|
||||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||||
stdOut += data + "\n";
|
stdOut += data + "\n";
|
||||||
@@ -774,8 +812,16 @@ ErrorCode ServerController::isUserInSudo(const ServerCredentials &credentials, D
|
|||||||
const QString scriptData = amnezia::scriptData(SharedScriptType::check_user_in_sudo);
|
const QString scriptData = amnezia::scriptData(SharedScriptType::check_user_in_sudo);
|
||||||
ErrorCode error = runScript(credentials, replaceVars(scriptData, genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr);
|
ErrorCode error = runScript(credentials, replaceVars(scriptData, genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr);
|
||||||
|
|
||||||
if (!stdOut.contains("sudo"))
|
if (credentials.userName != "root" && stdOut.contains("sudo:") && !stdOut.contains("uname:") && stdOut.contains("not found"))
|
||||||
|
return ErrorCode::ServerSudoPackageIsNotPreinstalled;
|
||||||
|
if (credentials.userName != "root" && !stdOut.contains("sudo") && !stdOut.contains("wheel"))
|
||||||
return ErrorCode::ServerUserNotInSudo;
|
return ErrorCode::ServerUserNotInSudo;
|
||||||
|
if (stdOut.contains("can't cd to") || stdOut.contains("Permission denied") || stdOut.contains("No such file or directory"))
|
||||||
|
return ErrorCode::ServerUserDirectoryNotAccessible;
|
||||||
|
if (stdOut.contains("sudoers") || stdOut.contains("is not allowed to run sudo on"))
|
||||||
|
return ErrorCode::ServerUserNotAllowedInSudoers;
|
||||||
|
if (stdOut.contains("password is required"))
|
||||||
|
return ErrorCode::ServerUserPasswordRequired;
|
||||||
|
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
@@ -807,7 +853,7 @@ ErrorCode ServerController::isServerDpkgBusy(const ServerCredentials &credential
|
|||||||
|
|
||||||
if (stdOut.contains("Packet manager not found"))
|
if (stdOut.contains("Packet manager not found"))
|
||||||
return ErrorCode::ServerPacketManagerError;
|
return ErrorCode::ServerPacketManagerError;
|
||||||
if (stdOut.contains("fuser not installed"))
|
if (stdOut.contains("fuser not installed") || stdOut.contains("cat not installed"))
|
||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
|
|
||||||
if (stdOut.isEmpty()) {
|
if (stdOut.isEmpty()) {
|
||||||
|
|||||||
@@ -99,11 +99,12 @@ QJsonObject VpnConfigurationsController::createVpnConfiguration(const QPair<QStr
|
|||||||
protocolConfigString = configurator->processConfigWithLocalSettings(dns, isApiConfig, protocolConfigString);
|
protocolConfigString = configurator->processConfigWithLocalSettings(dns, isApiConfig, protocolConfigString);
|
||||||
|
|
||||||
QJsonObject vpnConfigData = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
|
QJsonObject vpnConfigData = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
|
||||||
if (container == DockerContainer::Awg || container == DockerContainer::WireGuard) {
|
if (ContainerProps::isAwgContainer(container) || container == DockerContainer::WireGuard) {
|
||||||
// add mtu for old configs
|
// add mtu for old configs
|
||||||
if (vpnConfigData[config_key::mtu].toString().isEmpty()) {
|
if (vpnConfigData[config_key::mtu].toString().isEmpty()) {
|
||||||
vpnConfigData[config_key::mtu] =
|
vpnConfigData[config_key::mtu] =
|
||||||
container == DockerContainer::Awg ? protocols::awg::defaultMtu : protocols::wireguard::defaultMtu;
|
ContainerProps::isAwgContainer(container) ? protocols::awg::defaultMtu :
|
||||||
|
protocols::wireguard::defaultMtu;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,14 @@ namespace amnezia
|
|||||||
ServerCancelInstallation = 204,
|
ServerCancelInstallation = 204,
|
||||||
ServerUserNotInSudo = 205,
|
ServerUserNotInSudo = 205,
|
||||||
ServerPacketManagerError = 206,
|
ServerPacketManagerError = 206,
|
||||||
|
ServerSudoPackageIsNotPreinstalled = 207,
|
||||||
|
ServerUserDirectoryNotAccessible = 208,
|
||||||
|
ServerUserNotAllowedInSudoers = 209,
|
||||||
|
ServerUserPasswordRequired = 210,
|
||||||
|
ServerDockerOnCgroupsV2 = 211,
|
||||||
|
ServerCgroupMountpoint = 212,
|
||||||
|
DockerPullRateLimit = 213,
|
||||||
|
ServerLinuxKernelTooOld = 214,
|
||||||
|
|
||||||
// Ssh connection errors
|
// Ssh connection errors
|
||||||
SshRequestDeniedError = 300,
|
SshRequestDeniedError = 300,
|
||||||
@@ -111,6 +119,13 @@ namespace amnezia
|
|||||||
ApiServicesMissingError = 1107,
|
ApiServicesMissingError = 1107,
|
||||||
ApiConfigLimitError = 1108,
|
ApiConfigLimitError = 1108,
|
||||||
ApiNotFoundError = 1109,
|
ApiNotFoundError = 1109,
|
||||||
|
ApiMigrationError = 1110,
|
||||||
|
ApiUpdateRequestError = 1111,
|
||||||
|
ApiSubscriptionExpiredError = 1112,
|
||||||
|
ApiPurchaseError = 1113,
|
||||||
|
ApiSubscriptionNotActiveError = 1114,
|
||||||
|
ApiNoPurchasedSubscriptionsError = 1115,
|
||||||
|
ApiTrialAlreadyUsedError = 1116,
|
||||||
|
|
||||||
// QFile errors
|
// QFile errors
|
||||||
OpenError = 1200,
|
OpenError = 1200,
|
||||||
|
|||||||
@@ -20,8 +20,16 @@ QString errorString(ErrorCode code) {
|
|||||||
case(ErrorCode::ServerContainerMissingError): errorMessage = QObject::tr("Server error: Docker container missing"); break;
|
case(ErrorCode::ServerContainerMissingError): errorMessage = QObject::tr("Server error: Docker container missing"); break;
|
||||||
case(ErrorCode::ServerDockerFailedError): errorMessage = QObject::tr("Server error: Docker failed"); break;
|
case(ErrorCode::ServerDockerFailedError): errorMessage = QObject::tr("Server error: Docker failed"); break;
|
||||||
case(ErrorCode::ServerCancelInstallation): errorMessage = QObject::tr("Installation canceled by user"); break;
|
case(ErrorCode::ServerCancelInstallation): errorMessage = QObject::tr("Installation canceled by user"); break;
|
||||||
case(ErrorCode::ServerUserNotInSudo): errorMessage = QObject::tr("The user does not have permission to use sudo"); break;
|
case(ErrorCode::ServerUserNotInSudo): errorMessage = QObject::tr("The user is not a member of the sudo group"); break;
|
||||||
case(ErrorCode::ServerPacketManagerError): errorMessage = QObject::tr("Server error: Packet manager error"); break;
|
case(ErrorCode::ServerPacketManagerError): errorMessage = QObject::tr("Server error: Package manager error"); break;
|
||||||
|
case(ErrorCode::ServerSudoPackageIsNotPreinstalled): errorMessage = QObject::tr("The sudo package is not pre-installed on the server"); break;
|
||||||
|
case(ErrorCode::ServerUserDirectoryNotAccessible): errorMessage = QObject::tr("The server user's home directory is not accessible"); break;
|
||||||
|
case(ErrorCode::ServerUserNotAllowedInSudoers): errorMessage = QObject::tr("Action not allowed in sudoers"); break;
|
||||||
|
case(ErrorCode::ServerUserPasswordRequired): errorMessage = QObject::tr("The user's password is required"); break;
|
||||||
|
case(ErrorCode::ServerDockerOnCgroupsV2): errorMessage = QObject::tr("Docker error: runc doesn't work on cgroups v2"); break;
|
||||||
|
case(ErrorCode::ServerCgroupMountpoint): errorMessage = QObject::tr("Server error: cgroup mountpoint does not exist"); break;
|
||||||
|
case(ErrorCode::DockerPullRateLimit): errorMessage = QObject::tr("Docker error: The pull rate limit has been reached"); break;
|
||||||
|
case(ErrorCode::ServerLinuxKernelTooOld): errorMessage = QObject::tr("Server error: Linux kernel is too old"); break;
|
||||||
|
|
||||||
// Libssh errors
|
// Libssh errors
|
||||||
case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break;
|
case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break;
|
||||||
@@ -68,6 +76,13 @@ QString errorString(ErrorCode code) {
|
|||||||
case (ErrorCode::ApiServicesMissingError): errorMessage = QObject::tr("Missing list of available services"); break;
|
case (ErrorCode::ApiServicesMissingError): errorMessage = QObject::tr("Missing list of available services"); break;
|
||||||
case (ErrorCode::ApiConfigLimitError): errorMessage = QObject::tr("The limit of allowed configurations per subscription has been exceeded"); break;
|
case (ErrorCode::ApiConfigLimitError): errorMessage = QObject::tr("The limit of allowed configurations per subscription has been exceeded"); break;
|
||||||
case (ErrorCode::ApiNotFoundError): errorMessage = QObject::tr("Error when retrieving configuration from API"); break;
|
case (ErrorCode::ApiNotFoundError): errorMessage = QObject::tr("Error when retrieving configuration from API"); break;
|
||||||
|
case (ErrorCode::ApiMigrationError): errorMessage = QObject::tr("A migration error has occurred. Please contact our technical support"); break;
|
||||||
|
case (ErrorCode::ApiUpdateRequestError): errorMessage = QObject::tr("Please update the application to use this feature"); break;
|
||||||
|
case (ErrorCode::ApiSubscriptionExpiredError): errorMessage = QObject::tr("Your Amnezia Premium subscription has expired.\n Please check your email for renewal instructions.\n If you haven't received an email, please contact our support."); break;
|
||||||
|
case (ErrorCode::ApiPurchaseError): errorMessage = QObject::tr("Unable to process purchase"); break;
|
||||||
|
case (ErrorCode::ApiSubscriptionNotActiveError): errorMessage = QObject::tr("No active subscription found"); break;
|
||||||
|
case (ErrorCode::ApiNoPurchasedSubscriptionsError): errorMessage = QObject::tr("No purchased subscriptions found. Please purchase a subscription first"); break;
|
||||||
|
case (ErrorCode::ApiTrialAlreadyUsedError): errorMessage = QObject::tr("This email address has already been used to activate a trial"); 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;
|
||||||
|
|||||||
@@ -1,132 +1,74 @@
|
|||||||
#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>());
|
||||||
}
|
}
|
||||||
|
|
||||||
IpcClient::~IpcClient()
|
IpcClient& IpcClient::Instance()
|
||||||
{
|
{
|
||||||
if (m_localSocket)
|
thread_local IpcClient ipcClient;
|
||||||
m_localSocket->close();
|
return ipcClient;
|
||||||
}
|
|
||||||
|
|
||||||
bool IpcClient::isSocketConnected() const
|
|
||||||
{
|
|
||||||
return m_isSocketConnected;
|
|
||||||
}
|
|
||||||
|
|
||||||
IpcClient *IpcClient::Instance()
|
|
||||||
{
|
|
||||||
return m_instance;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QSharedPointer<IpcInterfaceReplica> IpcClient::Interface()
|
QSharedPointer<IpcInterfaceReplica> IpcClient::Interface()
|
||||||
{
|
{
|
||||||
if (!Instance())
|
QSharedPointer<IpcInterfaceReplica> rep = Instance().m_interface;
|
||||||
return nullptr;
|
if (rep.isNull()) {
|
||||||
return Instance()->m_ipcClient;
|
qCritical() << "IpcClient::Interface(): Failed to acquire replica";
|
||||||
}
|
|
||||||
|
|
||||||
QSharedPointer<IpcProcessTun2SocksReplica> IpcClient::InterfaceTun2Socks()
|
|
||||||
{
|
|
||||||
if (!Instance())
|
|
||||||
return nullptr;
|
|
||||||
return Instance()->m_Tun2SocksClient;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IpcClient::init(IpcClient *instance)
|
|
||||||
{
|
|
||||||
m_instance = instance;
|
|
||||||
|
|
||||||
Instance()->m_localSocket = new QLocalSocket(Instance());
|
|
||||||
connect(Instance()->m_localSocket.data(), &QLocalSocket::connected, &Instance()->m_ClientNode, []() {
|
|
||||||
Instance()->m_ClientNode.addClientSideConnection(Instance()->m_localSocket.data());
|
|
||||||
auto cliNode = Instance()->m_ClientNode.acquire<IpcInterfaceReplica>();
|
|
||||||
cliNode->waitForSource(5000);
|
|
||||||
Instance()->m_ipcClient.reset(cliNode);
|
|
||||||
|
|
||||||
if (!Instance()->m_ipcClient) {
|
|
||||||
qWarning() << "IpcClient is not ready!";
|
|
||||||
}
|
|
||||||
|
|
||||||
Instance()->m_ipcClient->waitForSource(1000);
|
|
||||||
|
|
||||||
if (!Instance()->m_ipcClient->isReplicaValid()) {
|
|
||||||
qWarning() << "IpcClient replica is not connected!";
|
|
||||||
}
|
|
||||||
|
|
||||||
auto t2sNode = Instance()->m_ClientNode.acquire<IpcProcessTun2SocksReplica>();
|
|
||||||
t2sNode->waitForSource(5000);
|
|
||||||
Instance()->m_Tun2SocksClient.reset(t2sNode);
|
|
||||||
|
|
||||||
if (!Instance()->m_Tun2SocksClient) {
|
|
||||||
qWarning() << "IpcClient::m_Tun2SocksClient is not ready!";
|
|
||||||
}
|
|
||||||
|
|
||||||
Instance()->m_Tun2SocksClient->waitForSource(1000);
|
|
||||||
|
|
||||||
if (!Instance()->m_Tun2SocksClient->isReplicaValid()) {
|
|
||||||
qWarning() << "IpcClient::m_Tun2SocksClient replica is not connected!";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
connect(Instance()->m_localSocket, &QLocalSocket::disconnected,
|
|
||||||
[instance]() { instance->m_isSocketConnected = false; });
|
|
||||||
|
|
||||||
Instance()->m_localSocket->connectToServer(amnezia::getIpcServiceUrl());
|
|
||||||
Instance()->m_localSocket->waitForConnected();
|
|
||||||
|
|
||||||
if (!Instance()->m_ipcClient) {
|
|
||||||
qDebug() << "IpcClient::init failed";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
qDebug() << "IpcClient::init succeed";
|
|
||||||
|
|
||||||
return (Instance()->m_ipcClient->isReplicaValid() && Instance()->m_Tun2SocksClient->isReplicaValid());
|
|
||||||
}
|
|
||||||
|
|
||||||
QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
|
|
||||||
{
|
|
||||||
if (!Instance()->m_ipcClient || !Instance()->m_ipcClient->isReplicaValid()) {
|
|
||||||
qWarning() << "IpcClient::createPrivilegedProcess : IpcClient IpcClient replica is not valid";
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
if (!rep->waitForSource(1000)) {
|
||||||
|
qCritical() << "IpcClient::Interface(): Failed to initialize replica";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
if (!rep->isReplicaValid()) {
|
||||||
|
qWarning() << "IpcClient::Interface(): Replica is invalid";
|
||||||
|
}
|
||||||
|
return rep;
|
||||||
|
}
|
||||||
|
|
||||||
QRemoteObjectPendingReply<int> futureResult = Instance()->m_ipcClient->createPrivilegedProcess();
|
QSharedPointer<IpcProcessInterfaceReplica> IpcClient::CreatePrivilegedProcess()
|
||||||
futureResult.waitForFinished(5000);
|
{
|
||||||
|
return withInterface([](QSharedPointer<IpcInterfaceReplica> &iface) -> QSharedPointer<IpcProcessInterfaceReplica> {
|
||||||
|
auto createPrivilegedProcess = iface->createPrivilegedProcess();
|
||||||
|
if (!createPrivilegedProcess.waitForFinished()) {
|
||||||
|
qCritical() << "Failed to create privileged process";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
int pid = futureResult.returnValue();
|
const int pid = createPrivilegedProcess.returnValue();
|
||||||
|
|
||||||
auto pd = QSharedPointer<ProcessDescriptor>(new ProcessDescriptor());
|
auto* node = new QRemoteObjectNode();
|
||||||
Instance()->m_processNodes.insert(pid, pd);
|
node->connectToNode(QUrl(QString("local:%1").arg(amnezia::getIpcProcessUrl(pid))));
|
||||||
|
|
||||||
pd->localSocket.reset(new QLocalSocket(pd->replicaNode.data()));
|
QSharedPointer<IpcProcessInterfaceReplica> rep(
|
||||||
|
node->acquire<IpcProcessInterfaceReplica>(),
|
||||||
connect(pd->localSocket.data(), &QLocalSocket::connected, pd->replicaNode.data(), [pd]() {
|
[node] (IpcProcessInterfaceReplica *ptr) {
|
||||||
pd->replicaNode->addClientSideConnection(pd->localSocket.data());
|
delete ptr;
|
||||||
|
node->deleteLater();
|
||||||
IpcProcessInterfaceReplica *repl = pd->replicaNode->acquire<IpcProcessInterfaceReplica>();
|
|
||||||
PrivilegedProcess *priv = static_cast<PrivilegedProcess *>(repl);
|
|
||||||
pd->ipcProcess.reset(priv);
|
|
||||||
if (!pd->ipcProcess) {
|
|
||||||
qWarning() << "Acquire PrivilegedProcess failed";
|
|
||||||
} else {
|
|
||||||
pd->ipcProcess->waitForSource(1000);
|
|
||||||
if (!pd->ipcProcess->isReplicaValid()) {
|
|
||||||
qWarning() << "PrivilegedProcess replica is not connected!";
|
|
||||||
}
|
}
|
||||||
|
);
|
||||||
QObject::connect(pd->ipcProcess.data(), &PrivilegedProcess::destroyed, pd->ipcProcess.data(),
|
if (rep.isNull()) {
|
||||||
[pd]() { pd->replicaNode->deleteLater(); });
|
qCritical() << "IpcClient::CreatePrivilegedProcess(): Failed to acquire replica";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
if (!rep->waitForSource()) {
|
||||||
|
qCritical() << "IpcClient::CreatePrivilegedProcess(): Failed to initialize replica";
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
if (!rep->isReplicaValid()) {
|
||||||
|
qCritical() << "IpcClient::CreatePrivilegedProcess(): Replica is invalid";
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
pd->localSocket->connectToServer(amnezia::getIpcProcessUrl(pid));
|
|
||||||
pd->localSocket->waitForConnected();
|
|
||||||
|
|
||||||
auto processReplica = QSharedPointer<PrivilegedProcess>(pd->ipcProcess);
|
return rep;
|
||||||
return processReplica;
|
},
|
||||||
|
[]() -> QSharedPointer<IpcProcessInterfaceReplica> {
|
||||||
|
return nullptr;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,53 +4,53 @@
|
|||||||
#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_interface_replica.h"
|
||||||
|
|
||||||
#include "privileged_process.h"
|
|
||||||
|
|
||||||
class IpcClient : public QObject
|
class IpcClient : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit IpcClient(QObject *parent = nullptr);
|
explicit IpcClient(QObject *parent = nullptr);
|
||||||
|
|
||||||
static IpcClient *Instance();
|
static IpcClient& Instance();
|
||||||
static bool init(IpcClient *instance);
|
|
||||||
static QSharedPointer<IpcInterfaceReplica> Interface();
|
|
||||||
static QSharedPointer<IpcProcessTun2SocksReplica> InterfaceTun2Socks();
|
|
||||||
static QSharedPointer<PrivilegedProcess> CreatePrivilegedProcess();
|
|
||||||
|
|
||||||
bool isSocketConnected() const;
|
static QSharedPointer<IpcInterfaceReplica> Interface();
|
||||||
|
static QSharedPointer<IpcProcessInterfaceReplica> CreatePrivilegedProcess();
|
||||||
|
|
||||||
|
template <typename Func>
|
||||||
|
static auto withInterface(Func func)
|
||||||
|
{
|
||||||
|
QSharedPointer<IpcInterfaceReplica> iface = Instance().m_interface;
|
||||||
|
using ReturnType = decltype(func(std::declval<QSharedPointer<IpcInterfaceReplica>>()));
|
||||||
|
|
||||||
|
if (iface.isNull() || !iface->waitForSource(1000) || !iface->isReplicaValid()) {
|
||||||
|
qWarning() << "IpcClient::withInterface(): Service is not running";
|
||||||
|
|
||||||
|
if constexpr (std::is_void_v<ReturnType>)
|
||||||
|
return;
|
||||||
|
else
|
||||||
|
return ReturnType{};
|
||||||
|
}
|
||||||
|
|
||||||
|
return func(iface);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename OnSuccess, typename OnFailure>
|
||||||
|
static auto withInterface(OnSuccess onSuccess, OnFailure onFailure)
|
||||||
|
{
|
||||||
|
QSharedPointer<IpcInterfaceReplica> iface = Instance().m_interface;
|
||||||
|
if (iface.isNull() || !iface->waitForSource(1000) || !iface->isReplicaValid()) {
|
||||||
|
return onFailure();
|
||||||
|
}
|
||||||
|
|
||||||
|
return onSuccess(iface);
|
||||||
|
}
|
||||||
signals:
|
signals:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
~IpcClient() override;
|
QRemoteObjectNode m_node;
|
||||||
|
QSharedPointer<IpcInterfaceReplica> m_interface;
|
||||||
QRemoteObjectNode m_ClientNode;
|
|
||||||
QRemoteObjectNode m_Tun2SocksNode;
|
|
||||||
QSharedPointer<IpcInterfaceReplica> m_ipcClient;
|
|
||||||
QPointer<QLocalSocket> m_localSocket;
|
|
||||||
QPointer<QLocalSocket> m_tun2socksSocket;
|
|
||||||
QSharedPointer<IpcProcessTun2SocksReplica> m_Tun2SocksClient;
|
|
||||||
|
|
||||||
struct ProcessDescriptor {
|
|
||||||
ProcessDescriptor () {
|
|
||||||
replicaNode = QSharedPointer<QRemoteObjectNode>(new QRemoteObjectNode());
|
|
||||||
ipcProcess = QSharedPointer<PrivilegedProcess>();
|
|
||||||
localSocket = QSharedPointer<QLocalSocket>();
|
|
||||||
}
|
|
||||||
QSharedPointer<PrivilegedProcess> ipcProcess;
|
|
||||||
QSharedPointer<QRemoteObjectNode> replicaNode;
|
|
||||||
QSharedPointer<QLocalSocket> localSocket;
|
|
||||||
};
|
|
||||||
|
|
||||||
QMap<int, QSharedPointer<ProcessDescriptor>> m_processNodes;
|
|
||||||
bool m_isSocketConnected {false};
|
|
||||||
|
|
||||||
static IpcClient *m_instance;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // IPCCLIENT_H
|
#endif // IPCCLIENT_H
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
#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>
|
||||||
#include <winsock.h>
|
#include <winsock.h>
|
||||||
#include <QNetworkInterface>
|
#include <QNetworkInterface>
|
||||||
#include "qendian.h"
|
#include "qendian.h"
|
||||||
|
#include <QSettings>
|
||||||
#endif
|
#endif
|
||||||
#ifdef Q_OS_LINUX
|
#ifdef Q_OS_LINUX
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
@@ -22,13 +24,22 @@
|
|||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#endif
|
#endif
|
||||||
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS)
|
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||||
#include <sys/param.h>
|
#include <sys/param.h>
|
||||||
#include <sys/sysctl.h>
|
#include <sys/sysctl.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
#include <net/route.h>
|
#include <net/route.h>
|
||||||
|
#include <ifaddrs.h>
|
||||||
|
#include <net/if.h>
|
||||||
|
#include <net/if_dl.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <ifaddrs.h>
|
||||||
|
#include <net/if.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <QHostAddress>
|
#include <QHostAddress>
|
||||||
@@ -169,7 +180,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_be ipBigEndian;
|
quint32 ipBigEndian;
|
||||||
quint32 ip = dst.toIPv4Address();
|
quint32 ip = dst.toIPv4Address();
|
||||||
qToBigEndian(ip, &ipBigEndian);
|
qToBigEndian(ip, &ipBigEndian);
|
||||||
_MIB_IPFORWARDROW routeInfo;
|
_MIB_IPFORWARDROW routeInfo;
|
||||||
@@ -185,6 +196,17 @@ int NetworkUtilities::AdapterIndexTo(const QHostAddress& dst) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool NetworkUtilities::checkIpv6Enabled() {
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
QSettings RegHLM("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Tcpip6\\Parameters",
|
||||||
|
QSettings::NativeFormat);
|
||||||
|
int ret = RegHLM.value("DisabledComponents", 0).toInt();
|
||||||
|
qDebug() << "Check for Windows disabled IPv6 return " << ret;
|
||||||
|
return (ret != 255);
|
||||||
|
#endif
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
DWORD GetAdaptersAddressesWrapper(const ULONG Family,
|
DWORD GetAdaptersAddressesWrapper(const ULONG Family,
|
||||||
const ULONG Flags,
|
const ULONG Flags,
|
||||||
@@ -227,12 +249,14 @@ DWORD GetAdaptersAddressesWrapper(const ULONG Family,
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
QString NetworkUtilities::getGatewayAndIface()
|
QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
||||||
{
|
{
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
constexpr int BUFF_LEN = 100;
|
constexpr int BUFF_LEN = 100;
|
||||||
char buff[BUFF_LEN] = {'\0'};
|
char buff[BUFF_LEN] = {'\0'};
|
||||||
QString result;
|
|
||||||
|
QString resGateway;
|
||||||
|
int resIndex = -1;
|
||||||
|
|
||||||
PIP_ADAPTER_ADDRESSES pAdapterAddresses = nullptr;
|
PIP_ADAPTER_ADDRESSES pAdapterAddresses = nullptr;
|
||||||
DWORD dwRetVal =
|
DWORD dwRetVal =
|
||||||
@@ -240,7 +264,7 @@ QString NetworkUtilities::getGatewayAndIface()
|
|||||||
|
|
||||||
if (dwRetVal != NO_ERROR) {
|
if (dwRetVal != NO_ERROR) {
|
||||||
qDebug() << "ipv4 stack detect GetAdaptersAddresses failed.";
|
qDebug() << "ipv4 stack detect GetAdaptersAddresses failed.";
|
||||||
return "";
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
PIP_ADAPTER_ADDRESSES pCurAddress = pAdapterAddresses;
|
PIP_ADAPTER_ADDRESSES pCurAddress = pAdapterAddresses;
|
||||||
@@ -255,7 +279,9 @@ QString NetworkUtilities::getGatewayAndIface()
|
|||||||
struct sockaddr_in addr;
|
struct sockaddr_in addr;
|
||||||
if (inet_pton(AF_INET, buff, &addr.sin_addr) == 1) {
|
if (inet_pton(AF_INET, buff, &addr.sin_addr) == 1) {
|
||||||
qDebug() << "this is true v4 !";
|
qDebug() << "this is true v4 !";
|
||||||
result = gw;
|
|
||||||
|
resGateway = gw;
|
||||||
|
resIndex = pCurAddress->IfIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -263,7 +289,7 @@ QString NetworkUtilities::getGatewayAndIface()
|
|||||||
}
|
}
|
||||||
|
|
||||||
free(pAdapterAddresses);
|
free(pAdapterAddresses);
|
||||||
return result;
|
return { resGateway, QNetworkInterface::interfaceFromIndex(resIndex) };
|
||||||
#endif
|
#endif
|
||||||
#ifdef Q_OS_LINUX
|
#ifdef Q_OS_LINUX
|
||||||
constexpr int BUFFER_SIZE = 100;
|
constexpr int BUFFER_SIZE = 100;
|
||||||
@@ -280,7 +306,7 @@ QString NetworkUtilities::getGatewayAndIface()
|
|||||||
|
|
||||||
if ((sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) {
|
if ((sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) {
|
||||||
perror("socket failed");
|
perror("socket failed");
|
||||||
return "";
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
memset(msgbuf, 0, sizeof(msgbuf));
|
memset(msgbuf, 0, sizeof(msgbuf));
|
||||||
@@ -304,7 +330,7 @@ QString NetworkUtilities::getGatewayAndIface()
|
|||||||
/* send msg */
|
/* send msg */
|
||||||
if (send(sock, nlmsg, nlmsg->nlmsg_len, 0) < 0) {
|
if (send(sock, nlmsg, nlmsg->nlmsg_len, 0) < 0) {
|
||||||
perror("send failed");
|
perror("send failed");
|
||||||
return "";
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
/* receive response */
|
/* receive response */
|
||||||
@@ -313,7 +339,7 @@ QString NetworkUtilities::getGatewayAndIface()
|
|||||||
received_bytes = recv(sock, ptr, sizeof(buffer) - msg_len, 0);
|
received_bytes = recv(sock, ptr, sizeof(buffer) - msg_len, 0);
|
||||||
if (received_bytes < 0) {
|
if (received_bytes < 0) {
|
||||||
perror("Error in recv");
|
perror("Error in recv");
|
||||||
return "";
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
nlh = (struct nlmsghdr *) ptr;
|
nlh = (struct nlmsghdr *) ptr;
|
||||||
@@ -323,7 +349,7 @@ QString NetworkUtilities::getGatewayAndIface()
|
|||||||
(nlmsg->nlmsg_type == NLMSG_ERROR))
|
(nlmsg->nlmsg_type == NLMSG_ERROR))
|
||||||
{
|
{
|
||||||
perror("Error in received packet");
|
perror("Error in received packet");
|
||||||
return "";
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If we received all data break */
|
/* If we received all data break */
|
||||||
@@ -376,10 +402,12 @@ QString NetworkUtilities::getGatewayAndIface()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
close(sock);
|
close(sock);
|
||||||
return gateway_address;
|
return { gateway_address, QNetworkInterface::interfaceFromName(interface) };
|
||||||
#endif
|
#endif
|
||||||
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS)
|
#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};
|
||||||
|
|
||||||
@@ -389,17 +417,17 @@ QString NetworkUtilities::getGatewayAndIface()
|
|||||||
|
|
||||||
size_t needed = 0;
|
size_t needed = 0;
|
||||||
if (sysctl(mib, sizeof(mib) / sizeof(int), nullptr, &needed, nullptr, 0) < 0)
|
if (sysctl(mib, sizeof(mib) / sizeof(int), nullptr, &needed, nullptr, 0) < 0)
|
||||||
return "";
|
return {};
|
||||||
|
|
||||||
char* buf;
|
char* buf;
|
||||||
if ((buf = new char[needed]) == 0)
|
if ((buf = new char[needed]) == 0)
|
||||||
return "";
|
return {};
|
||||||
|
|
||||||
if (sysctl(mib, sizeof(mib) / sizeof(int), buf, &needed, nullptr, 0) < 0)
|
if (sysctl(mib, sizeof(mib) / sizeof(int), buf, &needed, nullptr, 0) < 0)
|
||||||
{
|
{
|
||||||
qDebug() << "sysctl: net.route.0.0.dump";
|
qDebug() << "sysctl: net.route.0.0.dump";
|
||||||
delete[] buf;
|
delete[] buf;
|
||||||
return gateway;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
struct rt_msghdr* rt;
|
struct rt_msghdr* rt;
|
||||||
@@ -437,7 +465,10 @@ QString NetworkUtilities::getGatewayAndIface()
|
|||||||
&(reinterpret_cast<struct sockaddr_in*>(sa_tab[RTAX_GATEWAY]))->sin_addr,
|
&(reinterpret_cast<struct sockaddr_in*>(sa_tab[RTAX_GATEWAY]))->sin_addr,
|
||||||
sizeof(struct in_addr));
|
sizeof(struct in_addr));
|
||||||
if (inet_ntop(AF_INET, srcStr4, dstStr4, INET_ADDRSTRLEN) != nullptr)
|
if (inet_ntop(AF_INET, srcStr4, dstStr4, INET_ADDRSTRLEN) != nullptr)
|
||||||
|
{
|
||||||
gateway = dstStr4;
|
gateway = dstStr4;
|
||||||
|
index = rt->rtm_index;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -451,7 +482,10 @@ QString NetworkUtilities::getGatewayAndIface()
|
|||||||
&(reinterpret_cast<struct sockaddr_in6*>(sa_tab[RTAX_GATEWAY]))->sin6_addr,
|
&(reinterpret_cast<struct sockaddr_in6*>(sa_tab[RTAX_GATEWAY]))->sin6_addr,
|
||||||
sizeof(struct in6_addr));
|
sizeof(struct in6_addr));
|
||||||
if (inet_ntop(AF_INET6, srcStr6, dstStr6, INET6_ADDRSTRLEN) != nullptr)
|
if (inet_ntop(AF_INET6, srcStr6, dstStr6, INET6_ADDRSTRLEN) != nullptr)
|
||||||
|
{
|
||||||
gateway = dstStr6;
|
gateway = dstStr6;
|
||||||
|
index = rt->rtm_index;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -460,6 +494,6 @@ QString NetworkUtilities::getGatewayAndIface()
|
|||||||
free(buf);
|
free(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
return gateway;
|
return { gateway, QNetworkInterface::interfaceFromIndex(index) };
|
||||||
#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
|
||||||
{
|
{
|
||||||
@@ -16,7 +16,8 @@ public:
|
|||||||
static QString getStringBetween(const QString &s, const QString &a, const QString &b);
|
static QString getStringBetween(const QString &s, const QString &a, const QString &b);
|
||||||
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 QString getGatewayAndIface();
|
static bool checkIpv6Enabled();
|
||||||
|
static QPair<QString, QNetworkInterface> getGatewayAndIface();
|
||||||
// Returns the Interface Index that could Route to dst
|
// Returns the Interface Index that could Route to dst
|
||||||
static int AdapterIndexTo(const QHostAddress& dst);
|
static int AdapterIndexTo(const QHostAddress& dst);
|
||||||
|
|
||||||
@@ -29,7 +30,6 @@ public:
|
|||||||
|
|
||||||
static QString netMaskFromIpWithSubnet(const QString ip);
|
static QString netMaskFromIpWithSubnet(const QString ip);
|
||||||
static QString ipAddressFromIpWithSubnet(const QString ip);
|
static QString ipAddressFromIpWithSubnet(const QString ip);
|
||||||
|
|
||||||
static QStringList summarizeRoutes(const QStringList &ips, const QString cidr);
|
static QStringList summarizeRoutes(const QStringList &ips, const QString cidr);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,194 @@
|
|||||||
|
#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(); });
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
#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
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
#include "privileged_process.h"
|
|
||||||
|
|
||||||
PrivilegedProcess::PrivilegedProcess() :
|
|
||||||
IpcProcessInterfaceReplica()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
PrivilegedProcess::~PrivilegedProcess()
|
|
||||||
{
|
|
||||||
qDebug() << "PrivilegedProcess::~PrivilegedProcess()";
|
|
||||||
}
|
|
||||||
|
|
||||||
void PrivilegedProcess::waitForFinished(int msecs)
|
|
||||||
{
|
|
||||||
QSharedPointer<QEventLoop> loop(new QEventLoop);
|
|
||||||
connect(this, &PrivilegedProcess::finished, this, [this, loop](int exitCode, QProcess::ExitStatus exitStatus) mutable{
|
|
||||||
loop->quit();
|
|
||||||
loop.clear();
|
|
||||||
});
|
|
||||||
|
|
||||||
QTimer::singleShot(msecs, this, [this, loop]() mutable {
|
|
||||||
loop->quit();
|
|
||||||
loop.clear();
|
|
||||||
});
|
|
||||||
|
|
||||||
loop->exec();
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
#ifndef PRIVILEGED_PROCESS_H
|
|
||||||
#define PRIVILEGED_PROCESS_H
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
#include "rep_ipc_process_interface_replica.h"
|
|
||||||
// This class is dangerous - instance of this class casted from base class,
|
|
||||||
// so it support only functions
|
|
||||||
// Do not add any members into it
|
|
||||||
//
|
|
||||||
class PrivilegedProcess : public IpcProcessInterfaceReplica
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
PrivilegedProcess();
|
|
||||||
~PrivilegedProcess() override;
|
|
||||||
|
|
||||||
void waitForFinished(int msecs);
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // PRIVILEGED_PROCESS_H
|
|
||||||
|
|
||||||
|
|
||||||
@@ -11,7 +11,8 @@ QString amnezia::scriptFolder(amnezia::DockerContainer container)
|
|||||||
case DockerContainer::Cloak: return QLatin1String("openvpn_cloak");
|
case DockerContainer::Cloak: return QLatin1String("openvpn_cloak");
|
||||||
case DockerContainer::ShadowSocks: return QLatin1String("openvpn_shadowsocks");
|
case DockerContainer::ShadowSocks: return QLatin1String("openvpn_shadowsocks");
|
||||||
case DockerContainer::WireGuard: return QLatin1String("wireguard");
|
case DockerContainer::WireGuard: return QLatin1String("wireguard");
|
||||||
case DockerContainer::Awg: return QLatin1String("awg");
|
case DockerContainer::Awg2: return QLatin1String("awg");
|
||||||
|
case DockerContainer::Awg: return QLatin1String("awg_legacy");
|
||||||
case DockerContainer::Ipsec: return QLatin1String("ipsec");
|
case DockerContainer::Ipsec: return QLatin1String("ipsec");
|
||||||
case DockerContainer::Xray: return QLatin1String("xray");
|
case DockerContainer::Xray: return QLatin1String("xray");
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QJsonArray>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
|
#include <QHostAddress>
|
||||||
|
#include <QRandomGenerator>
|
||||||
|
#include <QTcpServer>
|
||||||
|
#include <stdexcept>
|
||||||
#include "3rd/QJsonStruct/QJsonIO.hpp"
|
#include "3rd/QJsonStruct/QJsonIO.hpp"
|
||||||
#include "transfer.h"
|
#include "transfer.h"
|
||||||
#include "serialization.h"
|
#include "serialization.h"
|
||||||
@@ -14,25 +19,125 @@ namespace amnezia::serialization::inbounds
|
|||||||
// "port": 10808,
|
// "port": 10808,
|
||||||
// "protocol": "socks",
|
// "protocol": "socks",
|
||||||
// "settings": {
|
// "settings": {
|
||||||
|
// "auth": "password",
|
||||||
|
// "accounts": [{"user": "...", "pass": "..."}],
|
||||||
// "udp": true
|
// "udp": true
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
//],
|
//],
|
||||||
|
|
||||||
const static QString listen = "127.0.0.1";
|
const static QString listen = "127.0.0.1";
|
||||||
const static int port = 10808;
|
const static int defaultPort = 10808;
|
||||||
const static QString protocol = "socks";
|
const static QString protocol = "socks";
|
||||||
|
|
||||||
|
static int indexOfSocksInbound(const QJsonArray &inbounds)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < inbounds.size(); ++i) {
|
||||||
|
const QString p = inbounds.at(i).toObject().value(QLatin1String("protocol")).toString();
|
||||||
|
if (p.compare(QLatin1String("socks"), Qt::CaseInsensitive) == 0)
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ask the OS for a free TCP port on loopback (same stack as inbound "listen": "127.0.0.1").
|
||||||
|
static int acquireFreeLocalPort()
|
||||||
|
{
|
||||||
|
QTcpServer probe;
|
||||||
|
if (!probe.listen(QHostAddress(QStringLiteral("127.0.0.1")), 0)) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Failed to bind a local TCP port on 127.0.0.1 for SOCKS inbound "
|
||||||
|
"(QTcpServer::listen failed; possible permission or OS network error).");
|
||||||
|
}
|
||||||
|
return static_cast<int>(probe.serverPort());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generates a hex string of `byteCount` random bytes (URL-safe, no special chars).
|
||||||
|
static QString generateRandomHex(int byteCount)
|
||||||
|
{
|
||||||
|
if (byteCount <= 0)
|
||||||
|
return {};
|
||||||
|
// fillRange writes full quint32 words; size the buffer to a multiple of 4 bytes to avoid
|
||||||
|
// overrunning a short buffer when byteCount is not divisible by 4.
|
||||||
|
const int numUint32 = (byteCount + int(sizeof(quint32)) - 1) / int(sizeof(quint32));
|
||||||
|
QByteArray buf(numUint32 * int(sizeof(quint32)), '\0');
|
||||||
|
QRandomGenerator::system()->fillRange(reinterpret_cast<quint32 *>(buf.data()), numUint32);
|
||||||
|
return QString::fromLatin1(buf.left(byteCount).toHex());
|
||||||
|
}
|
||||||
|
|
||||||
QJsonObject GenerateInboundEntry()
|
QJsonObject GenerateInboundEntry()
|
||||||
{
|
{
|
||||||
QJsonObject root;
|
QJsonObject root;
|
||||||
QJsonIO::SetValue(root, listen, "listen");
|
QJsonIO::SetValue(root, listen, "listen");
|
||||||
QJsonIO::SetValue(root, port, "port");
|
QJsonIO::SetValue(root, defaultPort, "port");
|
||||||
QJsonIO::SetValue(root, protocol, "protocol");
|
QJsonIO::SetValue(root, protocol, "protocol");
|
||||||
QJsonIO::SetValue(root, true, "settings", "udp");
|
QJsonIO::SetValue(root, true, "settings", "udp");
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InboundCredentials GetInboundCredentials(const QJsonObject &xrayConfig)
|
||||||
|
{
|
||||||
|
InboundCredentials creds;
|
||||||
|
creds.port = defaultPort;
|
||||||
|
|
||||||
|
const QJsonArray inbounds = xrayConfig.value("inbounds").toArray();
|
||||||
|
const int socksIdx = indexOfSocksInbound(inbounds);
|
||||||
|
if (socksIdx < 0)
|
||||||
|
return creds;
|
||||||
|
|
||||||
|
const QJsonObject inbound = inbounds.at(socksIdx).toObject();
|
||||||
|
creds.port = inbound.value("port").toInt(defaultPort);
|
||||||
|
|
||||||
|
const QJsonObject settings = inbound.value("settings").toObject();
|
||||||
|
const QJsonArray accounts = settings.value("accounts").toArray();
|
||||||
|
if (accounts.isEmpty())
|
||||||
|
return creds;
|
||||||
|
|
||||||
|
const QJsonObject account = accounts.first().toObject();
|
||||||
|
creds.username = account.value("user").toString();
|
||||||
|
creds.password = account.value("pass").toString();
|
||||||
|
return creds;
|
||||||
|
}
|
||||||
|
|
||||||
|
InboundCredentials EnsureInboundAuth(QJsonObject &xrayConfig)
|
||||||
|
{
|
||||||
|
QJsonArray inbounds = xrayConfig.value("inbounds").toArray();
|
||||||
|
const int socksIdx = indexOfSocksInbound(inbounds);
|
||||||
|
if (socksIdx < 0)
|
||||||
|
return GetInboundCredentials(xrayConfig); // no SOCKS inbound to patch
|
||||||
|
|
||||||
|
QJsonObject inbound = inbounds.at(socksIdx).toObject();
|
||||||
|
InboundCredentials creds;
|
||||||
|
creds.port = acquireFreeLocalPort();
|
||||||
|
inbound["port"] = creds.port;
|
||||||
|
|
||||||
|
QJsonObject settings = inbound.value("settings").toObject();
|
||||||
|
const QJsonArray accounts = settings.value("accounts").toArray();
|
||||||
|
if (!accounts.isEmpty()) {
|
||||||
|
const QJsonObject account = accounts.first().toObject();
|
||||||
|
creds.username = account.value("user").toString();
|
||||||
|
creds.password = account.value("pass").toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (creds.username.isEmpty() || creds.password.isEmpty()) {
|
||||||
|
// Generate fresh credentials for this session (never persisted)
|
||||||
|
creds.username = generateRandomHex(8); // 16 hex chars
|
||||||
|
creds.password = generateRandomHex(16); // 32 hex chars
|
||||||
|
QJsonObject account;
|
||||||
|
account["user"] = creds.username;
|
||||||
|
account["pass"] = creds.password;
|
||||||
|
settings["accounts"] = QJsonArray{ account };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always ensure auth mode is enforced, even for imported configs that had
|
||||||
|
// accounts but auth: "noauth" (or no auth field at all).
|
||||||
|
settings["auth"] = QStringLiteral("password");
|
||||||
|
inbound["settings"] = settings;
|
||||||
|
inbounds[socksIdx] = inbound;
|
||||||
|
xrayConfig["inbounds"] = inbounds;
|
||||||
|
|
||||||
|
return creds;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace amnezia::serialization::inbounds
|
} // namespace amnezia::serialization::inbounds
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ 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
|
||||||
@@ -59,7 +60,24 @@ namespace amnezia::serialization
|
|||||||
|
|
||||||
namespace inbounds
|
namespace inbounds
|
||||||
{
|
{
|
||||||
|
struct InboundCredentials {
|
||||||
|
QString username;
|
||||||
|
QString password;
|
||||||
|
int port;
|
||||||
|
};
|
||||||
|
|
||||||
QJsonObject GenerateInboundEntry();
|
QJsonObject GenerateInboundEntry();
|
||||||
|
|
||||||
|
// Reads existing SOCKS5 auth from the first inbound with protocol "socks"
|
||||||
|
// (.settings.accounts[0]). Returns empty username/password if none.
|
||||||
|
InboundCredentials GetInboundCredentials(const QJsonObject &xrayConfig);
|
||||||
|
|
||||||
|
// Ensures SOCKS5 auth is present on the inbound whose protocol is "socks".
|
||||||
|
// Re-uses existing credentials if already set; otherwise generates random ones
|
||||||
|
// and writes them into the config. Assigns a free loopback TCP port each session
|
||||||
|
// (OS-assigned). Throws std::runtime_error if a SOCKS inbound exists but binding
|
||||||
|
// a local port on 127.0.0.1 fails (e.g. permissions or OS error).
|
||||||
|
InboundCredentials EnsureInboundAuth(QJsonObject &xrayConfig);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,25 @@ 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,5 +252,65 @@ 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -149,8 +149,7 @@ bool Daemon::activate(const InterfaceConfig& config) {
|
|||||||
// set routing
|
// set routing
|
||||||
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
|
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
|
||||||
if (!wgutils()->updateRoutePrefix(ip)) {
|
if (!wgutils()->updateRoutePrefix(ip)) {
|
||||||
logger.debug() << "Routing configuration failed for"
|
logger.debug() << "Routing configuration failed for" << ip.toString();
|
||||||
<< logger.sensitive(ip.toString());
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -170,11 +169,14 @@ bool Daemon::maybeUpdateResolvers(const InterfaceConfig& config) {
|
|||||||
if ((config.m_hopType == InterfaceConfig::MultiHopExit) ||
|
if ((config.m_hopType == InterfaceConfig::MultiHopExit) ||
|
||||||
(config.m_hopType == InterfaceConfig::SingleHop)) {
|
(config.m_hopType == InterfaceConfig::SingleHop)) {
|
||||||
QList<QHostAddress> resolvers;
|
QList<QHostAddress> resolvers;
|
||||||
resolvers.append(QHostAddress(config.m_dnsServer));
|
resolvers.append(QHostAddress(config.m_primaryDnsServer));
|
||||||
|
if (!config.m_secondaryDnsServer.isEmpty()) {
|
||||||
|
resolvers.append(QHostAddress(config.m_secondaryDnsServer));
|
||||||
|
}
|
||||||
|
|
||||||
// If the DNS is not the Gateway, it's a user defined DNS
|
// If the DNS is not the Gateway, it's a user defined DNS
|
||||||
// thus, not add any other :)
|
// thus, not add any other :)
|
||||||
if (config.m_dnsServer == config.m_serverIpv4Gateway) {
|
if (config.m_primaryDnsServer == config.m_serverIpv4Gateway) {
|
||||||
resolvers.append(QHostAddress(config.m_serverIpv6Gateway));
|
resolvers.append(QHostAddress(config.m_serverIpv6Gateway));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,15 +282,26 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
|
|||||||
config.m_serverIpv4Gateway = obj.value("serverIpv4Gateway").toString();
|
config.m_serverIpv4Gateway = obj.value("serverIpv4Gateway").toString();
|
||||||
config.m_serverIpv6Gateway = obj.value("serverIpv6Gateway").toString();
|
config.m_serverIpv6Gateway = obj.value("serverIpv6Gateway").toString();
|
||||||
|
|
||||||
if (!obj.contains("dnsServer")) {
|
if (!obj.contains("primaryDnsServer")) {
|
||||||
config.m_dnsServer = QString();
|
config.m_primaryDnsServer = QString();
|
||||||
} else {
|
} else {
|
||||||
QJsonValue value = obj.value("dnsServer");
|
QJsonValue value = obj.value("primaryDnsServer");
|
||||||
if (!value.isString()) {
|
if (!value.isString()) {
|
||||||
logger.error() << "dnsServer is not a string";
|
logger.error() << "dnsServer is not a string";
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
config.m_dnsServer = value.toString();
|
config.m_primaryDnsServer = value.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!obj.contains("secondaryDnsServer")) {
|
||||||
|
config.m_secondaryDnsServer = QString();
|
||||||
|
} else {
|
||||||
|
QJsonValue value = obj.value("secondaryDnsServer");
|
||||||
|
if (!value.isString()) {
|
||||||
|
logger.error() << "dnsServer is not a string";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
config.m_secondaryDnsServer = value.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!obj.contains("hopType")) {
|
if (!obj.contains("hopType")) {
|
||||||
@@ -371,6 +384,9 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
|
|||||||
if (!parseStringList(obj, "vpnDisabledApps", config.m_vpnDisabledApps)) {
|
if (!parseStringList(obj, "vpnDisabledApps", config.m_vpnDisabledApps)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (!parseStringList(obj, "allowedDnsServers", config.m_allowedDnsServers)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
config.m_killSwitchEnabled = QVariant(obj.value("killSwitchOption").toString()).toBool();
|
config.m_killSwitchEnabled = QVariant(obj.value("killSwitchOption").toString()).toBool();
|
||||||
|
|
||||||
@@ -389,6 +405,13 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
|
|||||||
if (!obj.value("S2").isNull()) {
|
if (!obj.value("S2").isNull()) {
|
||||||
config.m_responsePacketJunkSize = obj.value("S2").toString();
|
config.m_responsePacketJunkSize = obj.value("S2").toString();
|
||||||
}
|
}
|
||||||
|
if (!obj.value("S3").isNull()) {
|
||||||
|
config.m_cookieReplyPacketJunkSize = obj.value("S3").toString();
|
||||||
|
}
|
||||||
|
if (!obj.value("S4").isNull()) {
|
||||||
|
config.m_transportPacketJunkSize = obj.value("S4").toString();
|
||||||
|
}
|
||||||
|
|
||||||
if (!obj.value("H1").isNull()) {
|
if (!obj.value("H1").isNull()) {
|
||||||
config.m_initPacketMagicHeader = obj.value("H1").toString();
|
config.m_initPacketMagicHeader = obj.value("H1").toString();
|
||||||
}
|
}
|
||||||
@@ -402,6 +425,22 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
|
|||||||
config.m_transportPacketMagicHeader = obj.value("H4").toString();
|
config.m_transportPacketMagicHeader = obj.value("H4").toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!obj.value("I1").isNull()) {
|
||||||
|
config.m_specialJunk["I1"] = obj.value("I1").toString();
|
||||||
|
}
|
||||||
|
if (!obj.value("I2").isNull()) {
|
||||||
|
config.m_specialJunk["I2"] = obj.value("I2").toString();
|
||||||
|
}
|
||||||
|
if (!obj.value("I3").isNull()) {
|
||||||
|
config.m_specialJunk["I3"] = obj.value("I3").toString();
|
||||||
|
}
|
||||||
|
if (!obj.value("I4").isNull()) {
|
||||||
|
config.m_specialJunk["I4"] = obj.value("I4").toString();
|
||||||
|
}
|
||||||
|
if (!obj.value("I5").isNull()) {
|
||||||
|
config.m_specialJunk["I5"] = obj.value("I5").toString();
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -444,7 +483,7 @@ bool Daemon::deactivate(bool emitSignals) {
|
|||||||
|
|
||||||
m_connections.clear();
|
m_connections.clear();
|
||||||
// Delete the interface
|
// Delete the interface
|
||||||
return wgutils()->deleteInterface();
|
return wgutils()->deleteInterface();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Daemon::logs() {
|
QString Daemon::logs() {
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ QJsonObject InterfaceConfig::toJson() const {
|
|||||||
(m_hopType == InterfaceConfig::SingleHop)) {
|
(m_hopType == InterfaceConfig::SingleHop)) {
|
||||||
json.insert("serverIpv4Gateway", QJsonValue(m_serverIpv4Gateway));
|
json.insert("serverIpv4Gateway", QJsonValue(m_serverIpv4Gateway));
|
||||||
json.insert("serverIpv6Gateway", QJsonValue(m_serverIpv6Gateway));
|
json.insert("serverIpv6Gateway", QJsonValue(m_serverIpv6Gateway));
|
||||||
json.insert("dnsServer", QJsonValue(m_dnsServer));
|
json.insert("primaryDnsServer", QJsonValue(m_primaryDnsServer));
|
||||||
|
json.insert("secondaryDnsServer", QJsonValue(m_secondaryDnsServer));
|
||||||
}
|
}
|
||||||
|
|
||||||
QJsonArray allowedIPAddesses;
|
QJsonArray allowedIPAddesses;
|
||||||
@@ -48,6 +49,13 @@ QJsonObject InterfaceConfig::toJson() const {
|
|||||||
}
|
}
|
||||||
json.insert("excludedAddresses", jsExcludedAddresses);
|
json.insert("excludedAddresses", jsExcludedAddresses);
|
||||||
|
|
||||||
|
|
||||||
|
QJsonArray jsAllowedDnsServers;
|
||||||
|
for (const QString& i : m_allowedDnsServers) {
|
||||||
|
jsAllowedDnsServers.append(QJsonValue(i));
|
||||||
|
}
|
||||||
|
json.insert("allowedDnsServers", jsAllowedDnsServers);
|
||||||
|
|
||||||
QJsonArray disabledApps;
|
QJsonArray disabledApps;
|
||||||
for (const QString& i : m_vpnDisabledApps) {
|
for (const QString& i : m_vpnDisabledApps) {
|
||||||
disabledApps.append(QJsonValue(i));
|
disabledApps.append(QJsonValue(i));
|
||||||
@@ -93,11 +101,15 @@ QString InterfaceConfig::toWgConf(const QMap<QString, QString>& extra) const {
|
|||||||
out << "MTU = " << m_deviceMTU << "\n";
|
out << "MTU = " << m_deviceMTU << "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_dnsServer.isNull()) {
|
if (!m_primaryDnsServer.isEmpty()) {
|
||||||
QStringList dnsServers(m_dnsServer);
|
QStringList dnsServers;
|
||||||
|
dnsServers.append(m_primaryDnsServer);
|
||||||
|
if (!m_secondaryDnsServer.isEmpty()) {
|
||||||
|
dnsServers.append(m_secondaryDnsServer);
|
||||||
|
}
|
||||||
// If the DNS is not the Gateway, it's a user defined DNS
|
// If the DNS is not the Gateway, it's a user defined DNS
|
||||||
// thus, not add any other :)
|
// thus, not add any other :)
|
||||||
if (m_dnsServer == m_serverIpv4Gateway) {
|
if (m_primaryDnsServer == m_serverIpv4Gateway) {
|
||||||
dnsServers.append(m_serverIpv6Gateway);
|
dnsServers.append(m_serverIpv6Gateway);
|
||||||
}
|
}
|
||||||
out << "DNS = " << dnsServers.join(", ") << "\n";
|
out << "DNS = " << dnsServers.join(", ") << "\n";
|
||||||
@@ -118,6 +130,12 @@ QString InterfaceConfig::toWgConf(const QMap<QString, QString>& extra) const {
|
|||||||
if (!m_responsePacketJunkSize.isNull()) {
|
if (!m_responsePacketJunkSize.isNull()) {
|
||||||
out << "S2 = " << m_responsePacketJunkSize << "\n";
|
out << "S2 = " << m_responsePacketJunkSize << "\n";
|
||||||
}
|
}
|
||||||
|
if (!m_cookieReplyPacketJunkSize.isNull()) {
|
||||||
|
out << "S3 = " << m_cookieReplyPacketJunkSize << "\n";
|
||||||
|
}
|
||||||
|
if (!m_transportPacketJunkSize.isNull()) {
|
||||||
|
out << "S4 = " << m_transportPacketJunkSize << "\n";
|
||||||
|
}
|
||||||
if (!m_initPacketMagicHeader.isNull()) {
|
if (!m_initPacketMagicHeader.isNull()) {
|
||||||
out << "H1 = " << m_initPacketMagicHeader << "\n";
|
out << "H1 = " << m_initPacketMagicHeader << "\n";
|
||||||
}
|
}
|
||||||
@@ -131,6 +149,10 @@ QString InterfaceConfig::toWgConf(const QMap<QString, QString>& extra) const {
|
|||||||
out << "H4 = " << m_transportPacketMagicHeader << "\n";
|
out << "H4 = " << m_transportPacketMagicHeader << "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const QString& key : m_specialJunk.keys()) {
|
||||||
|
out << key << " = " << m_specialJunk[key] << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
// If any extra config was provided, append it now.
|
// If any extra config was provided, append it now.
|
||||||
for (const QString& key : extra.keys()) {
|
for (const QString& key : extra.keys()) {
|
||||||
out << key << " = " << extra[key] << "\n";
|
out << key << " = " << extra[key] << "\n";
|
||||||
|
|||||||
@@ -6,8 +6,9 @@
|
|||||||
#define INTERFACECONFIG_H
|
#define INTERFACECONFIG_H
|
||||||
|
|
||||||
#include <QList>
|
#include <QList>
|
||||||
|
#include <QMap>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QMap>
|
||||||
#include "ipaddress.h"
|
#include "ipaddress.h"
|
||||||
|
|
||||||
class QJsonObject;
|
class QJsonObject;
|
||||||
@@ -31,12 +32,14 @@ class InterfaceConfig {
|
|||||||
QString m_serverIpv4AddrIn;
|
QString m_serverIpv4AddrIn;
|
||||||
QString m_serverPskKey;
|
QString m_serverPskKey;
|
||||||
QString m_serverIpv6AddrIn;
|
QString m_serverIpv6AddrIn;
|
||||||
QString m_dnsServer;
|
QString m_primaryDnsServer;
|
||||||
|
QString m_secondaryDnsServer;
|
||||||
int m_serverPort = 0;
|
int m_serverPort = 0;
|
||||||
int m_deviceMTU = 1420;
|
int m_deviceMTU = 1420;
|
||||||
QList<IPAddress> m_allowedIPAddressRanges;
|
QList<IPAddress> m_allowedIPAddressRanges;
|
||||||
QStringList m_excludedAddresses;
|
QStringList m_excludedAddresses;
|
||||||
QStringList m_vpnDisabledApps;
|
QStringList m_vpnDisabledApps;
|
||||||
|
QStringList m_allowedDnsServers;
|
||||||
bool m_killSwitchEnabled;
|
bool m_killSwitchEnabled;
|
||||||
#if defined(MZ_ANDROID) || defined(MZ_IOS)
|
#if defined(MZ_ANDROID) || defined(MZ_IOS)
|
||||||
QString m_installationId;
|
QString m_installationId;
|
||||||
@@ -47,10 +50,13 @@ class InterfaceConfig {
|
|||||||
QString m_junkPacketMaxSize;
|
QString m_junkPacketMaxSize;
|
||||||
QString m_initPacketJunkSize;
|
QString m_initPacketJunkSize;
|
||||||
QString m_responsePacketJunkSize;
|
QString m_responsePacketJunkSize;
|
||||||
|
QString m_cookieReplyPacketJunkSize;
|
||||||
|
QString m_transportPacketJunkSize;
|
||||||
QString m_initPacketMagicHeader;
|
QString m_initPacketMagicHeader;
|
||||||
QString m_responsePacketMagicHeader;
|
QString m_responsePacketMagicHeader;
|
||||||
QString m_underloadPacketMagicHeader;
|
QString m_underloadPacketMagicHeader;
|
||||||
QString m_transportPacketMagicHeader;
|
QString m_transportPacketMagicHeader;
|
||||||
|
QMap<QString, QString> m_specialJunk;
|
||||||
|
|
||||||
QJsonObject toJson() const;
|
QJsonObject toJson() const;
|
||||||
QString toWgConf(
|
QString toWgConf(
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M15 21V17C15 16.4696 15.2107 15.9609 15.5858 15.5858C15.9609 15.2107 16.4696 15 17 15H21" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M7 4V6C7.21572 6.61347 7.62494 7.14024 8.16602 7.50096C8.7071 7.86168 9.35075 8.03682 10 8V8C10.5304 8 11.0391 8.21071 11.4142 8.58579C11.7893 8.96086 12 9.46957 12 10C12 10.5304 12.2107 11.0391 12.5858 11.4142C12.9609 11.7893 13.4696 12 14 12C14.5304 12 15.0391 11.7893 15.4142 11.4142C15.7893 11.0391 16 10.5304 16 10C16 9.46957 16.2107 8.96086 16.5858 8.58579C16.9609 8.21071 17.4696 8 18 8H21" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M3 11H5C5.53043 11 6.03914 11.2107 6.41421 11.5858C6.78929 11.9609 7 12.4696 7 13V14C7 14.5304 7.21071 15.0391 7.58579 15.4142C7.96086 15.7893 8.46957 16 9 16C9.53043 16 10.0391 16.2107 10.4142 16.5858C10.7893 16.9609 11 17.4696 11 18V22" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M18.1777 8C23.2737 8 23.2737 16 18.1777 16C13.0827 16 11.0447 8 5.43875 8C0.85375 8 0.85375 16 5.43875 16C11.0447 16 13.0828 8 18.1788 8H18.1777Z" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 342 B |
@@ -0,0 +1,14 @@
|
|||||||
|
<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>
|
||||||
|
After Width: | Height: | Size: 982 B |
@@ -0,0 +1,8 @@
|
|||||||
|
<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>
|
||||||
|
After Width: | Height: | Size: 410 B |
|
After Width: | Height: | Size: 5.9 KiB |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M17 2H7C5.89543 2 5 2.89543 5 4V20C5 21.1046 5.89543 22 7 22H17C18.1046 22 19 21.1046 19 20V4C19 2.89543 18.1046 2 17 2Z" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M12 18H12.01" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 423 B |
@@ -0,0 +1,3 @@
|
|||||||
|
<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>
|
||||||
|
After Width: | Height: | Size: 188 B |
@@ -32,17 +32,41 @@
|
|||||||
<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>
|
||||||
<true/>
|
<false/>
|
||||||
<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>
|
||||||
|
|||||||
@@ -26,10 +26,22 @@ set_target_properties(networkextension PROPERTIES
|
|||||||
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2"
|
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2"
|
||||||
|
|
||||||
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../../Frameworks"
|
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../../Frameworks"
|
||||||
|
|
||||||
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(DEPLOY)
|
||||||
|
set_target_properties(networkextension PROPERTIES
|
||||||
|
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
|
||||||
|
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
|
||||||
|
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
|
||||||
|
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "distr ios.org.amnezia.AmneziaVPN"
|
||||||
|
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "dev ios.org.amnezia.AmneziaVPN"
|
||||||
|
)
|
||||||
|
else()
|
||||||
|
set_target_properties(networkextension PROPERTIES
|
||||||
|
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
set_target_properties(networkextension PROPERTIES
|
set_target_properties(networkextension PROPERTIES
|
||||||
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
|
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
|
||||||
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES"
|
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 336 B After Width: | Height: | Size: 682 B |
|
Before Width: | Height: | Size: 593 B After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 593 B After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 340 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 11 KiB |
@@ -1,6 +1,68 @@
|
|||||||
{
|
{
|
||||||
"info" : {
|
"images": [
|
||||||
"author" : "xcode",
|
{
|
||||||
"version" : 1
|
"idiom": "mac",
|
||||||
|
"size": "16x16",
|
||||||
|
"scale": "1x",
|
||||||
|
"filename": "16.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom": "mac",
|
||||||
|
"size": "16x16",
|
||||||
|
"scale": "2x",
|
||||||
|
"filename": "16@2x.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom": "mac",
|
||||||
|
"size": "32x32",
|
||||||
|
"scale": "1x",
|
||||||
|
"filename": "32.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom": "mac",
|
||||||
|
"size": "32x32",
|
||||||
|
"scale": "2x",
|
||||||
|
"filename": "32@2x.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom": "mac",
|
||||||
|
"size": "128x128",
|
||||||
|
"scale": "1x",
|
||||||
|
"filename": "128.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom": "mac",
|
||||||
|
"size": "128x128",
|
||||||
|
"scale": "2x",
|
||||||
|
"filename": "128@2x.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom": "mac",
|
||||||
|
"size": "256x256",
|
||||||
|
"scale": "1x",
|
||||||
|
"filename": "256.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom": "mac",
|
||||||
|
"size": "256x256",
|
||||||
|
"scale": "2x",
|
||||||
|
"filename": "256@2x.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom": "mac",
|
||||||
|
"size": "512x512",
|
||||||
|
"scale": "1x",
|
||||||
|
"filename": "512.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom": "mac",
|
||||||
|
"size": "512x512",
|
||||||
|
"scale": "2x",
|
||||||
|
"filename": "512@2x.png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info": {
|
||||||
|
"version": 1,
|
||||||
|
"author": "xcode"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
|
||||||
|
|
||||||
<key>CFBundleAllowMixedLocalizations</key>
|
|
||||||
<true/>
|
|
||||||
|
|
||||||
<key>CFBundleExecutable</key>
|
|
||||||
<string>${EXECUTABLE_NAME}</string>
|
|
||||||
|
|
||||||
<key>CFBundleIdentifier</key>
|
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
|
||||||
|
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
|
||||||
<string>6.0</string>
|
|
||||||
|
|
||||||
<key>CFBundleName</key>
|
|
||||||
<string>$(PRODUCT_NAME)</string>
|
|
||||||
|
|
||||||
<key>CFBundlePackageType</key>
|
|
||||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
|
||||||
|
|
||||||
<key>CFBundleShortVersionString</key>
|
|
||||||
<string>$(MARKETING_VERSION)</string>
|
|
||||||
|
|
||||||
<key>CFBundleVersion</key>
|
|
||||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
|
||||||
|
|
||||||
<key>ITSAppUsesNonExemptEncryption</key>
|
|
||||||
<false/>
|
|
||||||
|
|
||||||
<key>LSApplicationCategoryType</key>
|
|
||||||
<string>public.app-category.utilities</string>
|
|
||||||
|
|
||||||
<key>LSMinimumSystemVersion</key>
|
|
||||||
<string>${MACOSX_DEPLOYMENT_TARGET}</string>
|
|
||||||
|
|
||||||
<key>LSMultipleInstancesProhibited</key>
|
|
||||||
<true/>
|
|
||||||
|
|
||||||
<key>NSPrincipalClass</key>
|
|
||||||
<string>NSApplication</string>
|
|
||||||
|
|
||||||
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleAllowMixedLocalizations</key>
|
||||||
|
<true/>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>en</string>
|
||||||
|
<key>CFBundleDisplayName</key>
|
||||||
|
<string>${QT_INTERNAL_DOLLAR_VAR}{PRODUCT_NAME}</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
|
||||||
|
<key>NSHumanReadableCopyright</key>
|
||||||
|
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
|
||||||
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
|
<false/>
|
||||||
|
<key>LSApplicationCategoryType</key>
|
||||||
|
<string>public.app-category.utilities</string>
|
||||||
|
|
||||||
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
<string>${MACOSX_DEPLOYMENT_TARGET}</string>
|
||||||
|
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.wireguard.ios.app_group_id</key>
|
||||||
|
<string>group.org.amnezia.AmneziaVPN</string>
|
||||||
|
<key>NSCameraUsageDescription</key>
|
||||||
|
<string>Amnezia VPN needs access to the camera for reading QR-codes.</string>
|
||||||
|
<key>NSAppTransportSecurity</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSAllowsArbitraryLoads</key>
|
||||||
|
<false/>
|
||||||
|
<key>NSAllowsLocalNetworking</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
<key>CFBundleIcons</key>
|
||||||
|
<dict/>
|
||||||
|
<key>UTImportedTypeDeclarations</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>UTTypeConformsTo</key>
|
||||||
|
<array>
|
||||||
|
<string>public.data</string>
|
||||||
|
</array>
|
||||||
|
<key>UTTypeDescription</key>
|
||||||
|
<string>Amnezia VPN config</string>
|
||||||
|
<key>UTTypeIconFiles</key>
|
||||||
|
<array/>
|
||||||
|
<key>UTTypeIdentifier</key>
|
||||||
|
<string>org.amnezia.AmneziaVPN.amnezia-config</string>
|
||||||
|
<key>UTTypeTagSpecification</key>
|
||||||
|
<dict>
|
||||||
|
<key>public.filename-extension</key>
|
||||||
|
<array>
|
||||||
|
<string>vpn</string>
|
||||||
|
</array>
|
||||||
|
<key>public.mime-type</key>
|
||||||
|
<array>
|
||||||
|
<string>text/plain</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>UTTypeConformsTo</key>
|
||||||
|
<array>
|
||||||
|
<string>public.data</string>
|
||||||
|
</array>
|
||||||
|
<key>UTTypeDescription</key>
|
||||||
|
<string>WireGuard config</string>
|
||||||
|
<key>UTTypeIconFiles</key>
|
||||||
|
<array/>
|
||||||
|
<key>UTTypeIdentifier</key>
|
||||||
|
<string>org.amnezia.AmneziaVPN.wireguard-config</string>
|
||||||
|
<key>UTTypeTagSpecification</key>
|
||||||
|
<dict>
|
||||||
|
<key>public.filename-extension</key>
|
||||||
|
<array>
|
||||||
|
<string>conf</string>
|
||||||
|
<string>cfg</string>
|
||||||
|
</array>
|
||||||
|
<key>public.mime-type</key>
|
||||||
|
<array>
|
||||||
|
<string>text/plain</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>UTTypeConformsTo</key>
|
||||||
|
<array>
|
||||||
|
<string>public.data</string>
|
||||||
|
</array>
|
||||||
|
<key>UTTypeDescription</key>
|
||||||
|
<string>OpenVPN config</string>
|
||||||
|
<key>UTTypeIconFiles</key>
|
||||||
|
<array/>
|
||||||
|
<key>UTTypeIdentifier</key>
|
||||||
|
<string>org.amnezia.AmneziaVPN.openvpn-config</string>
|
||||||
|
<key>UTTypeTagSpecification</key>
|
||||||
|
<dict>
|
||||||
|
<key>public.filename-extension</key>
|
||||||
|
<array>
|
||||||
|
<string>ovpn</string>
|
||||||
|
</array>
|
||||||
|
<key>public.mime-type</key>
|
||||||
|
<array>
|
||||||
|
<string>text/plain</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>UTTypeConformsTo</key>
|
||||||
|
<array>
|
||||||
|
<string>public.data</string>
|
||||||
|
</array>
|
||||||
|
<key>UTTypeDescription</key>
|
||||||
|
<string>AmneziaVPN backup file</string>
|
||||||
|
<key>UTTypeIconFiles</key>
|
||||||
|
<array/>
|
||||||
|
<key>UTTypeIdentifier</key>
|
||||||
|
<string>org.amnezia.AmneziaVPN.backup-config</string>
|
||||||
|
<key>UTTypeTagSpecification</key>
|
||||||
|
<dict>
|
||||||
|
<key>public.filename-extension</key>
|
||||||
|
<array>
|
||||||
|
<string>backup</string>
|
||||||
|
</array>
|
||||||
|
<key>public.mime-type</key>
|
||||||
|
<array>
|
||||||
|
<string>text/plain</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleDocumentTypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleTypeName</key>
|
||||||
|
<string>Amnezia VPN config</string>
|
||||||
|
<key>LSHandlerRank</key>
|
||||||
|
<string>Alternate</string>
|
||||||
|
<key>LSItemContentTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>org.amnezia.AmneziaVPN.amnezia-config</string>
|
||||||
|
<string>org.amnezia.AmneziaVPN.wireguard-config</string>
|
||||||
|
<string>org.amnezia.AmneziaVPN.openvpn-config</string>
|
||||||
|
<string>org.amnezia.AmneziaVPN.backup-config</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
<key>NSExtensions</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>NSExtensionPointIdentifier</key>
|
||||||
|
<string>com.apple.networkextension.packet-tunnel</string>
|
||||||
|
<key>NSExtensionPrincipalClass</key>
|
||||||
|
<string>$(PRODUCT_MODULE_NAME).PacketTunnelProvider</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -2,34 +2,40 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>com.apple.application-identifier</key>
|
<key>com.apple.developer.networking.custom-protocol</key>
|
||||||
<string>$(DEVELOPMENT_TEAM).$(APP_ID_MACOS)</string>
|
<true/>
|
||||||
|
|
||||||
<key>com.apple.developer.networking.networkextension</key>
|
<key>com.apple.developer.networking.networkextension</key>
|
||||||
<array>
|
<array>
|
||||||
|
<string>app-proxy-provider</string>
|
||||||
<string>packet-tunnel-provider</string>
|
<string>packet-tunnel-provider</string>
|
||||||
|
<string>dns-settings</string>
|
||||||
|
<string>relay</string>
|
||||||
|
<string>content-filter-provider</string>
|
||||||
|
<string>dns-proxy</string>
|
||||||
</array>
|
</array>
|
||||||
|
<key>com.apple.developer.system-extension.install</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.developer.networking.vpn.api</key>
|
||||||
|
<array>
|
||||||
|
<string>allow-vpn</string>
|
||||||
|
</array>
|
||||||
|
<key>com.apple.security.app-sandbox</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.application-groups</key>
|
||||||
|
<array>
|
||||||
|
<string>group.org.amnezia.AmneziaVPN</string>
|
||||||
|
</array>
|
||||||
|
<key>com.apple.security.files.user-selected.read-only</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.files.user-selected.read-write</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.network.client</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.network.server</key>
|
||||||
|
<true/>
|
||||||
<key>keychain-access-groups</key>
|
<key>keychain-access-groups</key>
|
||||||
<array>
|
<array>
|
||||||
<string>$(DEVELOPMENT_TEAM).*</string>
|
<string>$(DEVELOPMENT_TEAM).*</string>
|
||||||
</array>
|
</array>
|
||||||
|
|
||||||
<key>com.apple.developer.team-identifier</key>
|
|
||||||
<string>$(DEVELOPMENT_TEAM)</string>
|
|
||||||
|
|
||||||
<key>com.apple.security.app-sandbox</key>
|
|
||||||
<true/>
|
|
||||||
|
|
||||||
<key>com.apple.security.application-groups</key>
|
|
||||||
<array>
|
|
||||||
<string>$(DEVELOPMENT_TEAM).$(GROUP_ID_MACOS)</string>
|
|
||||||
</array>
|
|
||||||
|
|
||||||
<key>com.apple.security.network.client</key>
|
|
||||||
<true/>
|
|
||||||
|
|
||||||
<key>com.apple.security.network.server</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -2,41 +2,30 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>com.apple.application-identifier</key>
|
<key>com.apple.developer.networking.custom-protocol</key>
|
||||||
<string>$(DEVELOPMENT_TEAM).$(NETEXT_ID_MACOS)</string>
|
<true/>
|
||||||
|
|
||||||
<key>com.apple.developer.networking.networkextension</key>
|
<key>com.apple.developer.networking.networkextension</key>
|
||||||
<array>
|
<array>
|
||||||
|
<string>dns-settings</string>
|
||||||
|
<string>relay</string>
|
||||||
<string>packet-tunnel-provider</string>
|
<string>packet-tunnel-provider</string>
|
||||||
|
<string>content-filter-provider</string>
|
||||||
|
<string>dns-proxy</string>
|
||||||
|
<string>app-proxy-provider</string>
|
||||||
</array>
|
</array>
|
||||||
|
<key>com.apple.developer.networking.vpn.api</key>
|
||||||
<key>keychain-access-groups</key>
|
|
||||||
<array>
|
<array>
|
||||||
<string>$(DEVELOPMENT_TEAM).*</string>
|
<string>allow-vpn</string>
|
||||||
</array>
|
</array>
|
||||||
|
|
||||||
<key>com.apple.developer.team-identifier</key>
|
|
||||||
<string>$(DEVELOPMENT_TEAM)</string>
|
|
||||||
|
|
||||||
<key>com.apple.developer.system-extension.install</key>
|
|
||||||
<true/>
|
|
||||||
|
|
||||||
<key>com.apple.security.app-sandbox</key>
|
<key>com.apple.security.app-sandbox</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
|
||||||
<key>com.apple.security.application-groups</key>
|
<key>com.apple.security.application-groups</key>
|
||||||
<array>
|
<array>
|
||||||
<string>$(DEVELOPMENT_TEAM).$(GROUP_ID_MACOS)</string>
|
<string>group.org.amnezia.AmneziaVPN</string>
|
||||||
</array>
|
</array>
|
||||||
|
|
||||||
<key>com.apple.security.network.client</key>
|
<key>com.apple.security.network.client</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
|
||||||
<key>com.apple.security.network.server</key>
|
<key>com.apple.security.network.server</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>com.apple.security.app-sandbox</key>
|
|
||||||
<true/>
|
|
||||||
<key>com.apple.private.network.socket-delegate</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -0,0 +1,138 @@
|
|||||||
|
enable_language(Swift)
|
||||||
|
message("Client message >> macos build >> AmneziaVPNNetworkExtension")
|
||||||
|
set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../..)
|
||||||
|
|
||||||
|
add_executable(AmneziaVPNNetworkExtension)
|
||||||
|
|
||||||
|
message("executable_path is: @executable_path/../../Frameworks")
|
||||||
|
set_target_properties(AmneziaVPNNetworkExtension PROPERTIES
|
||||||
|
XCODE_PRODUCT_TYPE com.apple.product-type.app-extension
|
||||||
|
# MACOSX_BUNDLE YES
|
||||||
|
BUNDLE_EXTENSION appex
|
||||||
|
MACOSX_BUNDLE_SHORT_VERSION_STRING "${APPLE_PROJECT_VERSION}"
|
||||||
|
MACOSX_BUNDLE_INFO_STRING "AmneziaVPNNetworkExtension"
|
||||||
|
MACOSX_BUNDLE_BUNDLE_NAME "AmneziaVPNNetworkExtension"
|
||||||
|
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}.network-extension"
|
||||||
|
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_NAME "${BUILD_IOS_APP_IDENTIFIER}.network-extension"
|
||||||
|
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS ${CMAKE_CURRENT_SOURCE_DIR}/AmneziaVPNNetworkExtension.entitlements
|
||||||
|
XCODE_ATTRIBUTE_MARKETING_VERSION "${APP_MAJOR_VERSION}"
|
||||||
|
XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
|
||||||
|
XCODE_ATTRIBUTE_PRODUCT_NAME "AmneziaVPNNetworkExtension"
|
||||||
|
|
||||||
|
XCODE_ATTRIBUTE_APPLICATION_EXTENSION_API_ONLY "YES"
|
||||||
|
XCODE_ATTRIBUTE_ENABLE_BITCODE "NO"
|
||||||
|
XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET "11.0"
|
||||||
|
|
||||||
|
XCODE_ATTRIBUTE_INFOPLIST_FILE ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in
|
||||||
|
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../../../../Frameworks @loader_path/../../../../Frameworks"
|
||||||
|
)
|
||||||
|
|
||||||
|
if(DEPLOY)
|
||||||
|
message("DEPLOY is ON")
|
||||||
|
set_target_properties(AmneziaVPNNetworkExtension PROPERTIES
|
||||||
|
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
|
||||||
|
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
|
||||||
|
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
|
||||||
|
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "distr macos.org.amnezia.amneziaVPN.NE"
|
||||||
|
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "dev macos.org.amnezia.amneziaVPN.NE"
|
||||||
|
)
|
||||||
|
else()
|
||||||
|
set_target_properties(AmneziaVPNNetworkExtension PROPERTIES
|
||||||
|
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set_target_properties(AmneziaVPNNetworkExtension PROPERTIES
|
||||||
|
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
|
||||||
|
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES"
|
||||||
|
XCODE_ATTRIBUTE_SWIFT_OBJC_BRIDGING_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/WireGuardNetworkExtension-Bridging-Header.h"
|
||||||
|
XCODE_ATTRIBUTE_SWIFT_OPTIMIZATION_LEVEL "-Onone"
|
||||||
|
XCODE_ATTRIBUTE_SWIFT_PRECOMPILE_BRIDGING_HEADER "NO"
|
||||||
|
)
|
||||||
|
|
||||||
|
set_target_properties("AmneziaVPNNetworkExtension" PROPERTIES
|
||||||
|
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "X7UJ388FXK"
|
||||||
|
)
|
||||||
|
|
||||||
|
find_library(FW_ASSETS_LIBRARY AssetsLibrary)
|
||||||
|
find_library(FW_MOBILE_CORE MobileCoreServices)
|
||||||
|
find_library(FW_UI_KIT UIKit)
|
||||||
|
find_library(FW_LIBRESOLV libresolv.9.tbd)
|
||||||
|
|
||||||
|
|
||||||
|
# Set the root directory
|
||||||
|
set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../..)
|
||||||
|
|
||||||
|
target_link_libraries(AmneziaVPNNetworkExtension PRIVATE ${FW_LIBRESOLV})
|
||||||
|
|
||||||
|
target_compile_options(AmneziaVPNNetworkExtension PRIVATE -DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\")
|
||||||
|
target_compile_options(AmneziaVPNNetworkExtension PRIVATE -DNETWORK_EXTENSION=1)
|
||||||
|
|
||||||
|
set(WG_APPLE_SOURCE_DIR ${CLIENT_ROOT_DIR}/3rd/amneziawg-apple/Sources)
|
||||||
|
|
||||||
|
message("WG_APPLE_SOURCE_DIR is: ${WG_APPLE_SOURCE_DIR}")
|
||||||
|
message("CLIENT_ROOT_DIR is: ${CLIENT_ROOT_DIR}")
|
||||||
|
|
||||||
|
target_sources(AmneziaVPNNetworkExtension PRIVATE
|
||||||
|
${WG_APPLE_SOURCE_DIR}/WireGuardKit/WireGuardAdapter.swift
|
||||||
|
${WG_APPLE_SOURCE_DIR}/WireGuardKit/PacketTunnelSettingsGenerator.swift
|
||||||
|
${WG_APPLE_SOURCE_DIR}/WireGuardKit/DNSResolver.swift
|
||||||
|
${WG_APPLE_SOURCE_DIR}/WireGuardNetworkExtension/ErrorNotifier.swift
|
||||||
|
${WG_APPLE_SOURCE_DIR}/Shared/Keychain.swift
|
||||||
|
${WG_APPLE_SOURCE_DIR}/Shared/Model/TunnelConfiguration+WgQuickConfig.swift
|
||||||
|
${WG_APPLE_SOURCE_DIR}/Shared/Model/NETunnelProviderProtocol+Extension.swift
|
||||||
|
${WG_APPLE_SOURCE_DIR}/Shared/Model/String+ArrayConversion.swift
|
||||||
|
${WG_APPLE_SOURCE_DIR}/WireGuardKit/TunnelConfiguration.swift
|
||||||
|
${WG_APPLE_SOURCE_DIR}/WireGuardKit/IPAddressRange.swift
|
||||||
|
${WG_APPLE_SOURCE_DIR}/WireGuardKit/Endpoint.swift
|
||||||
|
${WG_APPLE_SOURCE_DIR}/WireGuardKit/DNSServer.swift
|
||||||
|
${WG_APPLE_SOURCE_DIR}/WireGuardKit/InterfaceConfiguration.swift
|
||||||
|
${WG_APPLE_SOURCE_DIR}/WireGuardKit/PeerConfiguration.swift
|
||||||
|
${WG_APPLE_SOURCE_DIR}/Shared/FileManager+Extension.swift
|
||||||
|
${WG_APPLE_SOURCE_DIR}/WireGuardKitC/x25519.c
|
||||||
|
${WG_APPLE_SOURCE_DIR}/WireGuardKit/Array+ConcurrentMap.swift
|
||||||
|
${WG_APPLE_SOURCE_DIR}/WireGuardKit/IPAddress+AddrInfo.swift
|
||||||
|
${WG_APPLE_SOURCE_DIR}/WireGuardKit/PrivateKey.swift
|
||||||
|
${CLIENT_ROOT_DIR}/platforms/ios/HevSocksTunnel.swift
|
||||||
|
${CLIENT_ROOT_DIR}/platforms/ios/NELogController.swift
|
||||||
|
${CLIENT_ROOT_DIR}/platforms/ios/Log.swift
|
||||||
|
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
|
||||||
|
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider.swift
|
||||||
|
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+WireGuard.swift
|
||||||
|
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+OpenVPN.swift
|
||||||
|
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+Xray.swift
|
||||||
|
${CLIENT_ROOT_DIR}/platforms/ios/WGConfig.swift
|
||||||
|
${CLIENT_ROOT_DIR}/platforms/ios/iosglue.mm
|
||||||
|
${CLIENT_ROOT_DIR}/platforms/ios/XrayConfig.swift
|
||||||
|
)
|
||||||
|
|
||||||
|
target_sources(AmneziaVPNNetworkExtension PRIVATE
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/PrivacyInfo.xcprivacy
|
||||||
|
)
|
||||||
|
|
||||||
|
set_property(TARGET AmneziaVPNNetworkExtension APPEND PROPERTY RESOURCE
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/PrivacyInfo.xcprivacy
|
||||||
|
)
|
||||||
|
|
||||||
|
## Build wireguard-go-version.h
|
||||||
|
execute_process(
|
||||||
|
COMMAND go list -m golang.zx2c4.com/wireguard
|
||||||
|
WORKING_DIRECTORY ${CLIENT_ROOT_DIR}/3rd/wireguard-apple/Sources/WireGuardKitGo
|
||||||
|
OUTPUT_VARIABLE WG_VERSION_FULL
|
||||||
|
)
|
||||||
|
string(REGEX REPLACE ".*v\([0-9.]*\).*" "\\1" WG_VERSION_STRING 1.1.1)
|
||||||
|
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/wireguard-go-version.h.in
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/wireguard-go-version.h)
|
||||||
|
target_sources(AmneziaVPNNetworkExtension PRIVATE
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/wireguard-go-version.h)
|
||||||
|
|
||||||
|
target_include_directories(AmneziaVPNNetworkExtension PRIVATE ${CLIENT_ROOT_DIR})
|
||||||
|
target_include_directories(AmneziaVPNNetworkExtension PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
|
||||||
|
|
||||||
|
target_link_libraries(AmneziaVPNNetworkExtension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/wireguard/macos/universal2/libwg-go.a)
|
||||||
|
|
||||||
|
message(${CLIENT_ROOT_DIR})
|
||||||
|
message(${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/xray/HevSocks5Tunnel.xcframework/macos-arm64_x86_64/libhev-socks5-tunnel.a)
|
||||||
|
target_link_libraries(AmneziaVPNNetworkExtension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/xray/HevSocks5Tunnel.xcframework/macos-arm64_x86_64/libhev-socks5-tunnel.a)
|
||||||
|
|
||||||
|
target_include_directories(AmneziaVPNNetworkExtension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/xray/HevSocks5Tunnel.xcframework/macos-arm64_x86_64/Headers)
|
||||||
@@ -3,27 +3,32 @@
|
|||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>en</string>
|
||||||
<key>CFBundleDisplayName</key>
|
|
||||||
<string>AmneziaVPNNetworkExtension</string>
|
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>$(EXECUTABLE_NAME)</string>
|
<string>AmneziaVPNNetworkExtension</string>
|
||||||
|
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
<string>6.0</string>
|
<string>6.0</string>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>$(PRODUCT_NAME)</string>
|
<string>AmneziaVPNNetworkExtension</string>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>$(MARKETING_VERSION)</string>
|
<string>$(MARKETING_VERSION)</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||||
|
|
||||||
<key>ITSAppUsesNonExemptEncryption</key>
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
<false/>
|
<false/>
|
||||||
|
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>LSMinimumSystemVersion</key>
|
||||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
<string>${CMAKE_OSX_DEPLOYMENT_TARGET}</string>
|
||||||
|
|
||||||
|
<key>CFBundleDisplayName</key>
|
||||||
|
<string>AmneziaVPNNetworkExtension</string>
|
||||||
|
|
||||||
<key>NSExtension</key>
|
<key>NSExtension</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSExtensionPointIdentifier</key>
|
<key>NSExtensionPointIdentifier</key>
|
||||||
@@ -31,5 +36,11 @@
|
|||||||
<key>NSExtensionPrincipalClass</key>
|
<key>NSExtensionPrincipalClass</key>
|
||||||
<string>$(PRODUCT_MODULE_NAME).PacketTunnelProvider</string>
|
<string>$(PRODUCT_MODULE_NAME).PacketTunnelProvider</string>
|
||||||
</dict>
|
</dict>
|
||||||
|
|
||||||
|
<key>com.wireguard.ios.app_group_id</key>
|
||||||
|
<string>group.org.amnezia.AmneziaVPN</string>
|
||||||
|
|
||||||
|
<key>com.wireguard.macos.app_group_id</key>
|
||||||
|
<string>$(DEVELOPMENT_TEAM).group.org.amnezia.AmneziaVPN</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>NSPrivacyAccessedAPITypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>NSPrivacyAccessedAPIType</key>
|
||||||
|
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
||||||
|
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||||
|
<array>
|
||||||
|
<string>1C8F.1</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>NSPrivacyAccessedAPIType</key>
|
||||||
|
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
|
||||||
|
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||||
|
<array>
|
||||||
|
<string>C617.1</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
/* 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
|
* 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/. */
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
#include "macos/gobridge/wireguard.h"
|
|
||||||
#include "wireguard-go-version.h"
|
#include "wireguard-go-version.h"
|
||||||
#include "3rd/awg-apple/Sources/WireGuardKitC/WireGuardKitC.h"
|
#include "3rd/amneziawg-apple/Sources/WireGuardKitGo/wireguard.h"
|
||||||
|
#include "3rd/amneziawg-apple/Sources/WireGuardKitC/WireGuardKitC.h"
|
||||||
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
@@ -23,3 +23,8 @@ bool key_from_hex(uint8_t key[WG_KEY_LEN], const char* hex);
|
|||||||
bool key_eq(const uint8_t key1[WG_KEY_LEN], const uint8_t key2[WG_KEY_LEN]);
|
bool key_eq(const uint8_t key1[WG_KEY_LEN], const uint8_t key2[WG_KEY_LEN]);
|
||||||
|
|
||||||
void write_msg_to_log(const char* tag, const char* msg);
|
void write_msg_to_log(const char* tag, const char* msg);
|
||||||
|
|
||||||
|
// init function definition in C
|
||||||
|
void hev_socks5_tunnel_quit(void);
|
||||||
|
// Updated function definition in C
|
||||||
|
int hev_socks5_tunnel_main(const char* configFile, int fd);
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
#ifndef WIREGUARD_GO_VERSION
|
||||||
|
#define WIREGUARD_GO_VERSION "@WG_VERSION_STRING@"
|
||||||
|
#endif // WIREGUARD_GO_VERSION
|
||||||