Compare commits
177 commits
feature/xr
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
99e3afabad | ||
|
|
d3339a7f3a | ||
|
|
678bfffe49 | ||
|
|
728b48044c | ||
|
|
7ccbfa48bc | ||
|
|
83460bc29b | ||
|
|
c28e1b468a | ||
|
|
abd7fdd19c | ||
|
|
2b1ec9c693 | ||
|
|
19fcddfdaf | ||
|
|
0bca78eca9 | ||
|
|
68046a0b7c | ||
|
|
d19017f87b | ||
|
|
46536bc60a | ||
|
|
6a424e9858 | ||
|
|
8afe50cd87 | ||
|
|
48980c486e | ||
|
|
5f6cd282d3 | ||
|
|
95121c06e2 | ||
|
|
c2b17c128d | ||
|
|
eda24765e7 | ||
|
|
35e0e146e6 | ||
|
|
a5254ac238 | ||
|
|
517b5e5ca6 | ||
|
|
cfeb6cbffd | ||
|
|
c128ba981c | ||
|
|
a1ca994c8b | ||
|
|
52c12940c4 | ||
|
|
25d759374c | ||
|
|
e9250afd2b | ||
|
|
eb83086d5c | ||
|
|
9398e0e695 | ||
|
|
915c8f46c5 | ||
|
|
ec132ac96c | ||
|
|
101838404e | ||
|
|
db3164223a | ||
|
|
5a7b5d34fb | ||
|
|
9420333c76 | ||
|
|
f6403fe82e | ||
|
|
c55b025eee | ||
|
|
fc6fc26148 | ||
|
|
48b43ee102 | ||
|
|
e091020692 | ||
|
|
07baf0ed65 | ||
|
|
42d3d9b98a | ||
|
|
389c1f5327 | ||
|
|
703b9137e0 | ||
|
|
b183a3b232 | ||
|
|
f163f0fc1d | ||
|
|
3b49d5ca59 | ||
|
|
236e5ca2e3 | ||
|
|
2f6e28b980 | ||
|
|
46d96a8887 | ||
|
|
56221881da | ||
|
|
3f55f6a629 | ||
|
|
7c8ae9c311 | ||
|
|
b173dcaa17 | ||
|
|
da5fe1d766 | ||
|
|
a15ea0e8a1 | ||
|
|
fbbba648c4 | ||
|
|
f79bfa9d2e | ||
|
|
3011a0e306 | ||
|
|
76640311ab | ||
|
|
e707471b04 | ||
|
|
6425700d1c | ||
|
|
36045c6694 | ||
|
|
52ecd6899b | ||
|
|
49a6a9ed76 | ||
|
|
4869429eb6 | ||
|
|
956dd6e37a | ||
|
|
665a2911be | ||
|
|
1cfa4e0630 | ||
|
|
5bda624576 | ||
|
|
d1f0560595 | ||
|
|
df07fc1b1f | ||
|
|
8ca31e0c90 | ||
|
|
f1c6067485 | ||
|
|
ca04c63f5e | ||
|
|
89cdd2bece | ||
|
|
73d7dfa54f | ||
|
|
0a5b54a2e4 | ||
|
|
e43aa02a5b | ||
|
|
c3fb62a6ab | ||
|
|
62f3a339b7 | ||
|
|
767b14b37a | ||
|
|
e7fa160c9c | ||
|
|
7350d79c50 | ||
|
|
86f08554cd | ||
|
|
a741186c21 | ||
|
|
6acaab0ffa | ||
|
|
212e9b3a91 | ||
|
|
2bff37efae | ||
|
|
b88ab8e432 | ||
|
|
48f6cf904e | ||
|
|
367789bda2 | ||
|
|
d06924c59d |
303 changed files with 25924 additions and 13848 deletions
103
.github/workflows/deploy.yml
vendored
103
.github/workflows/deploy.yml
vendored
|
|
@ -10,7 +10,7 @@ env:
|
|||
|
||||
jobs:
|
||||
Build-Linux-Ubuntu:
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
env:
|
||||
QT_VERSION: 6.6.2
|
||||
|
|
@ -20,6 +20,8 @@ jobs:
|
|||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Install Qt'
|
||||
|
|
@ -90,6 +92,8 @@ jobs:
|
|||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Get sources'
|
||||
|
|
@ -156,6 +160,8 @@ jobs:
|
|||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Setup xcode'
|
||||
|
|
@ -190,7 +196,7 @@ jobs:
|
|||
- name: 'Install go'
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.22.1'
|
||||
go-version: '1.24'
|
||||
cache: false
|
||||
|
||||
- name: 'Setup gomobile'
|
||||
|
|
@ -243,18 +249,82 @@ jobs:
|
|||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Build-MacOS:
|
||||
Build-MacOS-old:
|
||||
runs-on: macos-latest
|
||||
|
||||
env:
|
||||
# Keep compat with MacOS 10.15 aka Catalina by Qt 6.4
|
||||
QT_VERSION: 6.4.3
|
||||
QIF_VERSION: 4.6
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Setup xcode'
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: '15.4.0'
|
||||
|
||||
- name: 'Install Qt'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'mac'
|
||||
target: 'desktop'
|
||||
arch: 'clang_64'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools'
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
set-env: 'true'
|
||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||
|
||||
|
||||
- name: 'Get sources'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- name: 'Setup ccache'
|
||||
uses: hendrikmuhs/ccache-action@v1.2
|
||||
|
||||
- name: 'Build project'
|
||||
run: |
|
||||
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin"
|
||||
bash deploy/build_macos.sh
|
||||
|
||||
- name: 'Upload installer artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN_MacOS_old_installer
|
||||
path: deploy/build/pkg/AmneziaVPN.pkg
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload unpacked artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN_MacOS_old_unpacked
|
||||
path: deploy/build/client/AmneziaVPN.app
|
||||
retention-days: 7
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
Build-MacOS:
|
||||
runs-on: macos-latest
|
||||
|
||||
env:
|
||||
QT_VERSION: 6.8.0
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Setup xcode'
|
||||
|
|
@ -275,11 +345,6 @@ jobs:
|
|||
set-env: 'true'
|
||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Install Qt Installer Framework ${{ env.QIF_VERSION }}'
|
||||
run: |
|
||||
mkdir -pv ${{ runner.temp }}/Qt/Tools/QtInstallerFramework
|
||||
wget https://qt.amzsvc.com/tools/ifw/${{ env.QIF_VERSION }}.zip
|
||||
unzip ${{ env.QIF_VERSION }}.zip -d ${{ runner.temp }}/Qt/Tools/QtInstallerFramework/
|
||||
|
||||
- name: 'Get sources'
|
||||
uses: actions/checkout@v4
|
||||
|
|
@ -293,14 +358,13 @@ jobs:
|
|||
- name: 'Build project'
|
||||
run: |
|
||||
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
|
||||
|
||||
- name: 'Upload installer artifact'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: AmneziaVPN_MacOS_installer
|
||||
path: AmneziaVPN.dmg
|
||||
path: deploy/build/pkg/AmneziaVPN.pkg
|
||||
retention-days: 7
|
||||
|
||||
- name: 'Upload unpacked artifact'
|
||||
|
|
@ -324,6 +388,8 @@ jobs:
|
|||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Install desktop Qt'
|
||||
|
|
@ -335,7 +401,8 @@ jobs:
|
|||
arch: 'linux_gcc_64'
|
||||
modules: ${{ env.QT_MODULES }}
|
||||
dir: ${{ runner.temp }}
|
||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||
py7zrversion: '==0.22.*'
|
||||
extra: '--base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Install android_x86_64 Qt'
|
||||
uses: jurplel/install-qt-action@v4
|
||||
|
|
@ -346,7 +413,8 @@ jobs:
|
|||
arch: 'android_x86_64'
|
||||
modules: ${{ env.QT_MODULES }}
|
||||
dir: ${{ runner.temp }}
|
||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||
py7zrversion: '==0.22.*'
|
||||
extra: '--base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Install android_x86 Qt'
|
||||
uses: jurplel/install-qt-action@v4
|
||||
|
|
@ -357,7 +425,8 @@ jobs:
|
|||
arch: 'android_x86'
|
||||
modules: ${{ env.QT_MODULES }}
|
||||
dir: ${{ runner.temp }}
|
||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||
py7zrversion: '==0.22.*'
|
||||
extra: '--base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Install android_armv7 Qt'
|
||||
uses: jurplel/install-qt-action@v4
|
||||
|
|
@ -368,7 +437,8 @@ jobs:
|
|||
arch: 'android_armv7'
|
||||
modules: ${{ env.QT_MODULES }}
|
||||
dir: ${{ runner.temp }}
|
||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||
py7zrversion: '==0.22.*'
|
||||
extra: '--base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Install android_arm64_v8a Qt'
|
||||
uses: jurplel/install-qt-action@v4
|
||||
|
|
@ -379,7 +449,8 @@ jobs:
|
|||
arch: 'android_arm64_v8a'
|
||||
modules: ${{ env.QT_MODULES }}
|
||||
dir: ${{ runner.temp }}
|
||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||
py7zrversion: '==0.22.*'
|
||||
extra: '--base ${{ env.QT_MIRROR }}'
|
||||
|
||||
- name: 'Grant execute permission for qt-cmake'
|
||||
shell: bash
|
||||
|
|
|
|||
2
.github/workflows/tag-deploy.yml
vendored
2
.github/workflows/tag-deploy.yml
vendored
|
|
@ -20,6 +20,8 @@ jobs:
|
|||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
|
||||
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
|
||||
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
|
||||
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Install desktop Qt'
|
||||
|
|
|
|||
61
.github/workflows/tag-upload.yml
vendored
61
.github/workflows/tag-upload.yml
vendored
|
|
@ -1,64 +1,41 @@
|
|||
name: 'Upload a new version'
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '[0-9]+.[0-9]+.[0-9]+.[0-9]+'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
RELEASE_VERSION:
|
||||
description: 'Release version (e.g. 1.2.3.4)'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
upload:
|
||||
Upload-S3:
|
||||
runs-on: ubuntu-latest
|
||||
name: upload
|
||||
steps:
|
||||
- name: Checkout CMakeLists.txt
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.ref_name }}
|
||||
ref: ${{ inputs.RELEASE_VERSION }}
|
||||
sparse-checkout: |
|
||||
CMakeLists.txt
|
||||
deploy/deploy_s3.sh
|
||||
sparse-checkout-cone-mode: false
|
||||
|
||||
- name: Verify git tag
|
||||
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/')
|
||||
|
||||
if [[ "$GIT_TAG" == "$CMAKE_TAG" ]]; then
|
||||
echo "Git tag ($GIT_TAG) and version in CMakeLists.txt ($CMAKE_TAG) are the same. Continuing..."
|
||||
if [[ "$TAG_NAME" == "$CMAKE_TAG" ]]; then
|
||||
echo "Git tag ($TAG_NAME) matches CMakeLists.txt version ($CMAKE_TAG)."
|
||||
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
|
||||
fi
|
||||
|
||||
- name: Download artifacts from the "${{ github.ref_name }}" tag
|
||||
uses: robinraju/release-downloader@v1.8
|
||||
- name: Setup Rclone
|
||||
uses: AnimMouse/setup-rclone@v1
|
||||
with:
|
||||
tag: ${{ github.ref_name }}
|
||||
fileName: "AmneziaVPN_(Linux_|)${{ github.ref_name }}*"
|
||||
out-file-path: ${{ github.ref_name }}
|
||||
rclone_config: ${{ secrets.RCLONE_CONFIG }}
|
||||
|
||||
- name: Upload beta version
|
||||
uses: jakejarvis/s3-sync-action@master
|
||||
if: contains(github.event.base_ref, 'dev')
|
||||
with:
|
||||
args: --include "AmneziaVPN*" --delete
|
||||
env:
|
||||
AWS_S3_BUCKET: updates
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
|
||||
AWS_S3_ENDPOINT: https://${{ vars.CF_ACCOUNT_ID }}.r2.cloudflarestorage.com
|
||||
SOURCE_DIR: ${{ github.ref_name }}
|
||||
DEST_DIR: beta/${{ github.ref_name }}
|
||||
|
||||
- name: Upload stable version
|
||||
uses: jakejarvis/s3-sync-action@master
|
||||
if: contains(github.event.base_ref, 'master')
|
||||
with:
|
||||
args: --include "AmneziaVPN*" --delete
|
||||
env:
|
||||
AWS_S3_BUCKET: updates
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
|
||||
AWS_S3_ENDPOINT: https://${{ vars.CF_ACCOUNT_ID }}.r2.cloudflarestorage.com
|
||||
SOURCE_DIR: ${{ github.ref_name }}
|
||||
DEST_DIR: stable/${{ github.ref_name }}
|
||||
- name: Send dist to S3
|
||||
run: bash deploy/deploy_s3.sh ${{ inputs.RELEASE_VERSION }}
|
||||
|
|
|
|||
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -133,4 +133,8 @@ client/3rd/ShadowSocks/ss_ios.xcconfig
|
|||
out/
|
||||
|
||||
# CMake files
|
||||
CMakeFiles/
|
||||
CMakeFiles/
|
||||
|
||||
ios-ne-build.sh
|
||||
macos-ne-build.sh
|
||||
macos-signed-build.sh
|
||||
|
|
|
|||
4
.gitmodules
vendored
4
.gitmodules
vendored
|
|
@ -1,6 +1,3 @@
|
|||
[submodule "client/3rd/OpenVPNAdapter"]
|
||||
path = client/3rd/OpenVPNAdapter
|
||||
url = https://github.com/amnezia-vpn/OpenVPNAdapter.git
|
||||
[submodule "client/3rd/qtkeychain"]
|
||||
path = client/3rd/qtkeychain
|
||||
url = https://github.com/frankosterfeld/qtkeychain.git
|
||||
|
|
@ -10,6 +7,7 @@
|
|||
[submodule "client/3rd-prebuilt"]
|
||||
path = client/3rd-prebuilt
|
||||
url = https://github.com/amnezia-vpn/3rd-prebuilt
|
||||
branch = feature/special-handshake
|
||||
[submodule "client/3rd/amneziawg-apple"]
|
||||
path = client/3rd/amneziawg-apple
|
||||
url = https://github.com/amnezia-vpn/amneziawg-apple
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
|||
|
||||
set(PROJECT AmneziaVPN)
|
||||
|
||||
project(${PROJECT} VERSION 4.8.2.4
|
||||
project(${PROJECT} VERSION 4.8.8.1
|
||||
DESCRIPTION "AmneziaVPN"
|
||||
HOMEPAGE_URL "https://amnezia.org/"
|
||||
)
|
||||
|
|
@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
|
|||
set(RELEASE_DATE "${CURRENT_DATE}")
|
||||
|
||||
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||
set(APP_ANDROID_VERSION_CODE 2071)
|
||||
set(APP_ANDROID_VERSION_CODE 2087)
|
||||
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||
set(MZ_PLATFORM_NAME "linux")
|
||||
|
|
|
|||
|
|
@ -13,13 +13,13 @@
|
|||
|
||||
[](https://amnezia.org)
|
||||
|
||||
### [Website](https://amnezia.org) | [Alt website link](https://storage.googleapis.com/kldscp/amnezia.org) | [Documentation](https://docs.amnezia.org) | [Troubleshooting](https://docs.amnezia.org/troubleshooting)
|
||||
### [Website](https://amnezia.org) | [Alt website link](https://storage.googleapis.com/amnezia/amnezia.org) | [Documentation](https://docs.amnezia.org) | [Troubleshooting](https://docs.amnezia.org/troubleshooting)
|
||||
|
||||
> [!TIP]
|
||||
> If the [Amnezia website](https://amnezia.org) is blocked in your region, you can use an [Alternative website link](https://storage.googleapis.com/kldscp/amnezia.org).
|
||||
> If the [Amnezia website](https://amnezia.org) is blocked in your region, you can use an [Alternative website link](https://storage.googleapis.com/amnezia/amnezia.org ).
|
||||
|
||||
<a href="https://amnezia.org/downloads"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
|
||||
<a href="https://storage.googleapis.com/kldscp/amnezia.org/downloads"><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/q9p19109"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-alt.svg" width="150" style="max-width: 100%;"></a>
|
||||
|
||||
[All releases](https://github.com/amnezia-vpn/amnezia-client/releases)
|
||||
|
||||
|
|
@ -185,7 +185,7 @@ GPL v3.0
|
|||
|
||||
Patreon: [https://www.patreon.com/amneziavpn](https://www.patreon.com/amneziavpn)
|
||||
|
||||
Bitcoin: bc1q26eevjcg9j0wuyywd2e3uc9cs2w58lpkpjxq6p <br>
|
||||
Bitcoin: bc1qmhtgcf9637rl3kqyy22r2a8wa8laka4t9rx2mf <br>
|
||||
USDT BEP20: 0x6abD576765a826f87D1D95183438f9408C901bE4 <br>
|
||||
USDT TRC20: TELAitazF1MZGmiNjTcnxDjEiH5oe7LC9d <br>
|
||||
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3 <br>
|
||||
|
|
|
|||
126
README_RU.md
126
README_RU.md
|
|
@ -6,16 +6,16 @@
|
|||
[](https://gitpod.io/#https://github.com/amnezia-vpn/amnezia-client)
|
||||
|
||||
### [English](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README.md) | Русский
|
||||
[AmneziaVPN](https://amnezia.org) — это open sourse VPN-клиент, ключевая особенность которого заключается в возможности развернуть собственный VPN на вашем сервере.
|
||||
[AmneziaVPN](https://amnezia.org) — это open source VPN-клиент, ключевая особенность которого заключается в возможности развернуть собственный VPN на вашем сервере.
|
||||
|
||||
[](https://amnezia.org)
|
||||
|
||||
### [Сайт](https://amnezia.org) | [Зеркало на сайт](https://storage.googleapis.com/kldscp/amnezia.org) | [Документация](https://docs.amnezia.org) | [Решение проблем](https://docs.amnezia.org/troubleshooting)
|
||||
### [Сайт](https://amnezia.org) | [Зеркало сайта](https://storage.googleapis.com/amnezia/amnezia.org) | [Документация](https://docs.amnezia.org) | [Решение проблем](https://docs.amnezia.org/troubleshooting)
|
||||
|
||||
> [!TIP]
|
||||
> Если [сайт Amnezia](https://amnezia.org) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/kldscp/amnezia.org).
|
||||
> Если [сайт Amnezia](https://amnezia.org) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/amnezia/amnezia.org).
|
||||
|
||||
<a href="https://storage.googleapis.com/kldscp/amnezia.org/downloads"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website-ru.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
|
||||
<a href="https://storage.googleapis.com/amnezia/q9p19109"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website-ru.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
|
||||
|
||||
|
||||
[Все релизы](https://github.com/amnezia-vpn/amnezia-client/releases)
|
||||
|
|
@ -30,7 +30,7 @@
|
|||
- Классические 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.
|
||||
- Поддержка 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).
|
||||
|
||||
## Ссылки
|
||||
|
|
@ -38,10 +38,10 @@
|
|||
- [https://amnezia.org](https://amnezia.org) - Веб-сайт проекта | [Альтернативная ссылка (зеркало)](https://storage.googleapis.com/kldscp/amnezia.org)
|
||||
- [https://docs.amnezia.org](https://docs.amnezia.org) - Документация
|
||||
- [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_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](https://t.me/amnezia_vpn) - Канал поддржки в 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_mm](https://t.me/amnezia_vpn_mm) - Канал поддержки в 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\)
|
||||
|
||||
## Технологии
|
||||
|
|
@ -55,6 +55,112 @@ AmneziaVPN использует несколько проектов с откр
|
|||
- [LibSsh](https://libssh.org)
|
||||
- и другие...
|
||||
|
||||
## Проверка исходного кода
|
||||
После клонирования репозитория обязательно загрузите все подмодули.
|
||||
|
||||
```bash
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
|
||||
## Разработка
|
||||
Хотите внести свой вклад? Добро пожаловать!
|
||||
|
||||
### Помощь с переводами
|
||||
|
||||
Загрузите самые актуальные файлы перевода.
|
||||
|
||||
Перейдите на [вкладку "Actions"](https://github.com/amnezia-vpn/amnezia-client/actions?query=is%3Asuccess+branch%3Adev), нажмите на первую строку. Затем прокрутите вниз до раздела "Artifacts" и скачайте "AmneziaVPN_translations".
|
||||
|
||||
Распакуйте этот файл. Каждый файл с расширением *.ts содержит строки для соответствующего языка.
|
||||
|
||||
Переведите или исправьте строки в одном или нескольких файлах *.ts и загрузите их обратно в этот репозиторий в папку ``client/translations``. Это можно сделать через веб-интерфейс или любым другим знакомым вам способом.
|
||||
|
||||
### Сборка исходного кода и деплой
|
||||
Проверьте папку deploy для скриптов сборки.
|
||||
|
||||
### Как собрать iOS-приложение из исходного кода на MacOS
|
||||
1. Убедитесь, что у вас установлен Xcode версии 14 или выше.
|
||||
2. Для генерации проекта Xcode используется QT. Требуется версия QT 6.6.2. Установите QT для MacOS здесь или через QT Online Installer. Необходимые модули:
|
||||
- MacOS
|
||||
- iOS
|
||||
- Модуль совместимости с Qt 5
|
||||
- Qt Shader Tools
|
||||
- Дополнительные библиотеки:
|
||||
- Qt Image Formats
|
||||
- Qt Multimedia
|
||||
- Qt Remote Objects
|
||||
|
||||
|
||||
3. Установите CMake, если это необходимо. Рекомендуемая версия — 3.25. Скачать CMake можно здесь.
|
||||
4. Установите Go версии >= v1.16. Если Go ещё не установлен, скачайте его с [официального сайта](https://golang.org/dl/) или используйте Homebrew. Установите gomobile:
|
||||
|
||||
```bash
|
||||
export PATH=$PATH:~/go/bin
|
||||
go install golang.org/x/mobile/cmd/gomobile@latest
|
||||
gomobile init
|
||||
```
|
||||
|
||||
5. Соберите проект:
|
||||
```bash
|
||||
export QT_BIN_DIR="<PATH-TO-QT-FOLDER>/Qt/<QT-VERSION>/ios/bin"
|
||||
export QT_MACOS_ROOT_DIR="<PATH-TO-QT-FOLDER>/Qt/<QT-VERSION>/macos"
|
||||
export QT_IOS_BIN=$QT_BIN_DIR
|
||||
export PATH=$PATH:~/go/bin
|
||||
mkdir build-ios
|
||||
$QT_IOS_BIN/qt-cmake . -B build-ios -GXcode -DQT_HOST_PATH=$QT_MACOS_ROOT_DIR
|
||||
```
|
||||
Замените <PATH-TO-QT-FOLDER> и <QT-VERSION> на ваши значения.
|
||||
|
||||
Если появляется ошибка gomobile: command not found, убедитесь, что PATH настроен на папку bin, где установлен gomobile:
|
||||
```bash
|
||||
export PATH=$(PATH):/path/to/GOPATH/bin
|
||||
```
|
||||
|
||||
6. Откройте проект в Xcode. Теперь вы можете тестировать, архивировать или публиковать приложение.
|
||||
|
||||
Если сборка завершится с ошибкой:
|
||||
```
|
||||
make: ***
|
||||
[$(PROJECTDIR)/client/build/AmneziaVPN.build/Debug-iphoneos/wireguard-go-bridge/goroot/.prepared]
|
||||
Error 1
|
||||
```
|
||||
Добавьте пользовательскую переменную PATH в настройки сборки для целей AmneziaVPN и WireGuardNetworkExtension с ключом `PATH` и значением `${PATH}/path/to/bin/folder/with/go/executable`, e.g. `${PATH}:/usr/local/go/bin`.
|
||||
|
||||
Если ошибка повторяется на Mac с M1, установите версию CMake для архитектуры ARM:
|
||||
```
|
||||
arch -arm64 brew install cmake
|
||||
```
|
||||
|
||||
При первой попытке сборка может завершиться с ошибкой source files not found. Это происходит из-за параллельной компиляции зависимостей в XCode. Просто перезапустите сборку.
|
||||
|
||||
|
||||
## Как собрать Android-приложение
|
||||
Сборка тестировалась на MacOS. Требования:
|
||||
- JDK 11
|
||||
- Android SDK 33
|
||||
- CMake 3.25.0
|
||||
|
||||
Установите QT, QT Creator и Android Studio.
|
||||
Настройте QT Creator:
|
||||
|
||||
- В меню QT Creator перейдите в `QT Creator` -> `Preferences` -> `Devices` ->`Android`.
|
||||
- Укажите путь к JDK 11.
|
||||
- Укажите путь к Android SDK (`$ANDROID_HOME`)
|
||||
|
||||
Если вы сталкиваетесь с ошибками, связанными с отсутствием SDK или сообщением «SDK manager not running», их нельзя исправить просто корректировкой путей. Если у вас есть несколько свободных гигабайт на диске, вы можете позволить Qt Creator установить все необходимые компоненты, выбрав пустую папку для расположения Android SDK и нажав кнопку **Set Up SDK**. Учтите: это установит второй Android SDK и NDK на вашем компьютере!
|
||||
|
||||
Убедитесь, что настроена правильная версия CMake: перейдите в **Qt Creator -> Preferences** и в боковом меню выберите пункт **Kits**. В центральной части окна, на вкладке **Kits**, найдите запись для инструмента **CMake Tool**. Если выбранная по умолчанию версия CMake ниже 3.25.0, установите на свою систему CMake версии 3.25.0 или выше, а затем выберите опцию **System CMake at <путь>** из выпадающего списка. Если этот пункт отсутствует, это может означать, что вы еще не установили CMake, или Qt Creator не смог найти путь к нему. В таком случае в окне **Preferences** перейдите в боковое меню **CMake**, затем во вкладку **Tools** в центральной части окна и нажмите кнопку **Add**, чтобы указать путь к установленному CMake.
|
||||
|
||||
Убедитесь, что для вашего проекта выбрана Android Platform SDK 33: в главном окне на боковой панели выберите пункт **Projects**, и слева вы увидите раздел **Build & Run**, показывающий различные целевые Android-платформы. Вы можете выбрать любую из них, так как настройка проекта Amnezia VPN разработана таким образом, чтобы все Android-цели могли быть собраны. Перейдите в подраздел **Build** и прокрутите центральную часть окна до раздела **Build Steps**. Нажмите **Details** в заголовке **Build Android APK** (кнопка **Details** может быть скрыта, если окно Qt Creator не запущено в полноэкранном режиме!). Вот здесь выберите **android-33** в качестве Android Build Platform SDK.
|
||||
|
||||
### Разработка Android-компонентов
|
||||
|
||||
После сборки QT Creator копирует проект в отдельную папку, например, `build-amnezia-client-Android_Qt_<version>_Clang_<architecture>-<BuildType>`. Для разработки Android-компонентов откройте сгенерированный проект в Android Studio, указав папку `build-amnezia-client-Android_Qt_<version>_Clang_<architecture>-<BuildType>/client/android-build` в качестве корневой.
|
||||
Изменения в сгенерированном проекте нужно вручную перенести в репозиторий. После этого можно коммитить изменения.
|
||||
Если возникают проблемы со сборкой в QT Creator после работы в Android Studio, выполните команду `./gradlew clean` в корневой папке сгенерированного проекта (`<path>/client/android-build/.`).
|
||||
|
||||
|
||||
## Лицензия
|
||||
|
||||
GPL v3.0
|
||||
|
|
@ -63,7 +169,7 @@ GPL v3.0
|
|||
|
||||
Patreon: [https://www.patreon.com/amneziavpn](https://www.patreon.com/amneziavpn)
|
||||
|
||||
Bitcoin: bc1q26eevjcg9j0wuyywd2e3uc9cs2w58lpkpjxq6p <br>
|
||||
Bitcoin: bc1qmhtgcf9637rl3kqyy22r2a8wa8laka4t9rx2mf <br>
|
||||
USDT BEP20: 0x6abD576765a826f87D1D95183438f9408C901bE4 <br>
|
||||
USDT TRC20: TELAitazF1MZGmiNjTcnxDjEiH5oe7LC9d <br>
|
||||
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3 <br>
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit ba580dc5bd7784f7b1e110ff0365f3286e549a61
|
||||
Subproject commit 840b7b070e6ac8b90dda2fac6e98859b23727c0c
|
||||
1
client/3rd/OpenVPNAdapter
vendored
1
client/3rd/OpenVPNAdapter
vendored
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 7c821a8d5c1ad5ad94e0763b4f25a875b5a6fe1b
|
||||
2
client/3rd/amneziawg-apple
vendored
2
client/3rd/amneziawg-apple
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit 76e7db556a6d7e2582f9481df91db188a46c009c
|
||||
Subproject commit 811af0a83b3faeade89a9093a588595666d32066
|
||||
|
|
@ -31,9 +31,8 @@ add_definitions(-DDEV_AGW_PUBLIC_KEY="$ENV{DEV_AGW_PUBLIC_KEY}")
|
|||
add_definitions(-DDEV_AGW_ENDPOINT="$ENV{DEV_AGW_ENDPOINT}")
|
||||
add_definitions(-DDEV_S3_ENDPOINT="$ENV{DEV_S3_ENDPOINT}")
|
||||
|
||||
if(IOS)
|
||||
set(PACKAGES ${PACKAGES} Multimedia)
|
||||
endif()
|
||||
add_definitions(-DFREE_V2_ENDPOINT="$ENV{FREE_V2_ENDPOINT}")
|
||||
add_definitions(-DPREM_V1_ENDPOINT="$ENV{PREM_V1_ENDPOINT}")
|
||||
|
||||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
set(PACKAGES ${PACKAGES} Widgets)
|
||||
|
|
@ -48,10 +47,6 @@ set(LIBS ${LIBS}
|
|||
Qt6::Core5Compat Qt6::Concurrent
|
||||
)
|
||||
|
||||
if(IOS)
|
||||
set(LIBS ${LIBS} Qt6::Multimedia)
|
||||
endif()
|
||||
|
||||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
set(LIBS ${LIBS} Qt6::Widgets)
|
||||
endif()
|
||||
|
|
@ -96,11 +91,6 @@ configure_file(${CMAKE_CURRENT_LIST_DIR}/translations/translations.qrc.in ${CMAK
|
|||
qt6_add_resources(QRC ${I18NQRC} ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc)
|
||||
# -- i18n end
|
||||
|
||||
if(IOS)
|
||||
execute_process(COMMAND bash ${CMAKE_CURRENT_LIST_DIR}/ios/scripts/openvpn.sh args
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
|
||||
endif()
|
||||
|
||||
set(IS_CI ${CI})
|
||||
if(IS_CI)
|
||||
message("Detected CI env")
|
||||
|
|
@ -110,8 +100,8 @@ if(IS_CI)
|
|||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/cmake/3rdparty.cmake)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/cmake/sources.cmake)
|
||||
|
||||
include_directories(
|
||||
${CMAKE_CURRENT_LIST_DIR}/../ipc
|
||||
|
|
@ -120,165 +110,22 @@ include_directories(
|
|||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
|
||||
configure_file(${CMAKE_CURRENT_LIST_DIR}/../version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h)
|
||||
|
||||
set(HEADERS ${HEADERS}
|
||||
${CMAKE_CURRENT_LIST_DIR}/migrations.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/amnezia_application.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/containers/containers_defs.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/defs.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/errorstrings.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/scripts_registry.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/server_defs.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/controllers/apiController.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/controllers/serverController.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/controllers/vpnConfigurationController.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/qml_register_protocols.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/pages.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.h
|
||||
${CMAKE_CURRENT_BINARY_DIR}/version.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/sshclient.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/networkUtilities.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/serialization.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/transfer.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/enums/apiEnums.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/../common/logger/logger.h
|
||||
)
|
||||
|
||||
# Mozilla headres
|
||||
set(HEADERS ${HEADERS}
|
||||
${CMAKE_CURRENT_LIST_DIR}/mozilla/models/server.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/mozilla/shared/ipaddress.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/mozilla/shared/leakdetector.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/mozilla/controllerimpl.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/mozilla/localsocketcontroller.h
|
||||
)
|
||||
|
||||
include_directories(mozilla)
|
||||
include_directories(mozilla/shared)
|
||||
include_directories(mozilla/models)
|
||||
|
||||
if(NOT IOS)
|
||||
set(HEADERS ${HEADERS}
|
||||
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.h
|
||||
)
|
||||
endif()
|
||||
|
||||
if(NOT ANDROID)
|
||||
set(HEADERS ${HEADERS}
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.h
|
||||
)
|
||||
endif()
|
||||
|
||||
set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_LIST_DIR}/migrations.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/amnezia_application.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/containers/containers_defs.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/errorstrings.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/scripts_registry.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/server_defs.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/controllers/apiController.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/controllers/serverController.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/controllers/vpnConfigurationController.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/sshclient.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/networkUtilities.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/outbound.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/inbound.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/ss.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/ssd.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/vless.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/trojan.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/vmess.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/vmess_new.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/../common/logger/logger.cpp
|
||||
)
|
||||
|
||||
# Mozilla sources
|
||||
set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_LIST_DIR}/mozilla/models/server.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/mozilla/shared/ipaddress.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/mozilla/shared/leakdetector.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/mozilla/localsocketcontroller.cpp
|
||||
)
|
||||
configure_file(${CMAKE_CURRENT_LIST_DIR}/../version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h)
|
||||
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
target_compile_definitions(${PROJECT} PRIVATE "MZ_DEBUG")
|
||||
endif()
|
||||
|
||||
if(NOT IOS)
|
||||
set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
if(NOT ANDROID)
|
||||
set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
file(GLOB COMMON_FILES_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/*.h)
|
||||
file(GLOB COMMON_FILES_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/*.cpp)
|
||||
|
||||
file(GLOB_RECURSE PAGE_LOGIC_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/pages_logic/*.h)
|
||||
file(GLOB_RECURSE PAGE_LOGIC_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/pages_logic/*.cpp)
|
||||
|
||||
file(GLOB CONFIGURATORS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/configurators/*.h)
|
||||
file(GLOB CONFIGURATORS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/configurators/*.cpp)
|
||||
|
||||
file(GLOB UI_MODELS_H CONFIGURE_DEPENDS
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/models/*.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/models/protocols/*.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/models/services/*.h
|
||||
)
|
||||
file(GLOB UI_MODELS_CPP CONFIGURE_DEPENDS
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/models/*.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/models/protocols/*.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/models/services/*.cpp
|
||||
)
|
||||
|
||||
file(GLOB UI_CONTROLLERS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/controllers/*.h)
|
||||
file(GLOB UI_CONTROLLERS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/controllers/*.cpp)
|
||||
|
||||
set(HEADERS ${HEADERS}
|
||||
${COMMON_FILES_H}
|
||||
${PAGE_LOGIC_H}
|
||||
${CONFIGURATORS_H}
|
||||
${UI_MODELS_H}
|
||||
${UI_CONTROLLERS_H}
|
||||
)
|
||||
set(SOURCES ${SOURCES}
|
||||
${COMMON_FILES_CPP}
|
||||
${PAGE_LOGIC_CPP}
|
||||
${CONFIGURATORS_CPP}
|
||||
${UI_MODELS_CPP}
|
||||
${UI_CONTROLLERS_CPP}
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
configure_file(
|
||||
${CMAKE_CURRENT_LIST_DIR}/platforms/windows/amneziavpn.rc.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/amneziavpn.rc
|
||||
)
|
||||
|
||||
set(HEADERS ${HEADERS}
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/ikev2_vpn_protocol_windows.h
|
||||
)
|
||||
|
||||
set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/ikev2_vpn_protocol_windows.cpp
|
||||
)
|
||||
|
||||
set(RESOURCES ${RESOURCES}
|
||||
${CMAKE_CURRENT_BINARY_DIR}/amneziavpn.rc
|
||||
)
|
||||
|
||||
set(LIBS ${LIBS}
|
||||
user32
|
||||
rasapi32
|
||||
|
|
@ -322,30 +169,6 @@ endif()
|
|||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
message("Client desktop build")
|
||||
add_compile_definitions(AMNEZIA_DESKTOP)
|
||||
|
||||
set(HEADERS ${HEADERS}
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/ipcclient.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/privileged_process.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/systemtray_notificationhandler.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnprotocol.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/xrayprotocol.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/awgprotocol.h
|
||||
)
|
||||
|
||||
set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/ipcclient.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/privileged_process.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/systemtray_notificationhandler.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnprotocol.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/xrayprotocol.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/awgprotocol.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
if(ANDROID)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
#include <QClipboard>
|
||||
#include <QFontDatabase>
|
||||
#include <QLocalServer>
|
||||
#include <QLocalSocket>
|
||||
#include <QMimeData>
|
||||
#include <QQuickItem>
|
||||
#include <QQuickStyle>
|
||||
|
|
@ -10,26 +12,16 @@
|
|||
#include <QTextDocument>
|
||||
#include <QTimer>
|
||||
#include <QTranslator>
|
||||
#include <QLocalSocket>
|
||||
#include <QLocalServer>
|
||||
|
||||
#include "logger.h"
|
||||
#include "ui/controllers/pageController.h"
|
||||
#include "ui/models/installedAppsModel.h"
|
||||
#include "version.h"
|
||||
|
||||
#include "platforms/ios/QRCodeReaderBase.h"
|
||||
#if defined(Q_OS_ANDROID)
|
||||
#include "core/installedAppsImageProvider.h"
|
||||
#include "platforms/android/android_controller.h"
|
||||
#endif
|
||||
|
||||
#include "protocols/qml_register_protocols.h"
|
||||
|
||||
#if defined(Q_OS_IOS)
|
||||
#include "platforms/ios/ios_controller.h"
|
||||
#include <AmneziaVPN-Swift.h>
|
||||
#endif
|
||||
|
||||
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv)
|
||||
{
|
||||
setQuitOnLastWindowClosed(false);
|
||||
|
|
@ -84,79 +76,12 @@ void AmneziaApplication::init()
|
|||
m_vpnConnection->moveToThread(&m_vpnConnectionThread);
|
||||
m_vpnConnectionThread.start();
|
||||
|
||||
initModels();
|
||||
loadTranslator();
|
||||
initControllers();
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
if (!AndroidController::initLogging()) {
|
||||
qFatal("Android logging initialization failed");
|
||||
}
|
||||
AndroidController::instance()->setSaveLogs(m_settings->isSaveLogs());
|
||||
connect(m_settings.get(), &Settings::saveLogsChanged, AndroidController::instance(), &AndroidController::setSaveLogs);
|
||||
|
||||
AndroidController::instance()->setScreenshotsEnabled(m_settings->isScreenshotsEnabled());
|
||||
connect(m_settings.get(), &Settings::screenshotsEnabledChanged, AndroidController::instance(), &AndroidController::setScreenshotsEnabled);
|
||||
|
||||
connect(m_settings.get(), &Settings::serverRemoved, AndroidController::instance(), &AndroidController::resetLastServer);
|
||||
|
||||
connect(m_settings.get(), &Settings::settingsCleared, []() { AndroidController::instance()->resetLastServer(-1); });
|
||||
|
||||
connect(AndroidController::instance(), &AndroidController::initConnectionState, this, [this](Vpn::ConnectionState state) {
|
||||
m_connectionController->onConnectionStateChanged(state);
|
||||
if (m_vpnConnection)
|
||||
m_vpnConnection->restoreConnection();
|
||||
});
|
||||
if (!AndroidController::instance()->initialize()) {
|
||||
qFatal("Android controller initialization failed");
|
||||
}
|
||||
|
||||
connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, this, [this](QString data) {
|
||||
emit m_pageController->goToPageHome();
|
||||
m_importController->extractConfigFromData(data);
|
||||
data.clear();
|
||||
emit m_pageController->goToPageViewConfig();
|
||||
});
|
||||
|
||||
m_engine->addImageProvider(QLatin1String("installedAppImage"), new InstalledAppsImageProvider);
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
IosController::Instance()->initialize();
|
||||
connect(IosController::Instance(), &IosController::importConfigFromOutside, this, [this](QString data) {
|
||||
emit m_pageController->goToPageHome();
|
||||
m_importController->extractConfigFromData(data);
|
||||
emit m_pageController->goToPageViewConfig();
|
||||
});
|
||||
|
||||
connect(IosController::Instance(), &IosController::importBackupFromOutside, this, [this](QString filePath) {
|
||||
emit m_pageController->goToPageHome();
|
||||
m_pageController->goToPageSettingsBackup();
|
||||
emit m_settingsController->importBackupFromOutside(filePath);
|
||||
});
|
||||
|
||||
QTimer::singleShot(0, this, [this]() { AmneziaVPN::toggleScreenshots(m_settings->isScreenshotsEnabled()); });
|
||||
|
||||
connect(m_settings.get(), &Settings::screenshotsEnabledChanged, [](bool enabled) { AmneziaVPN::toggleScreenshots(enabled); });
|
||||
#endif
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
m_notificationHandler.reset(NotificationHandler::create(nullptr));
|
||||
|
||||
connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(),
|
||||
&NotificationHandler::setConnectionState);
|
||||
|
||||
connect(m_notificationHandler.get(), &NotificationHandler::raiseRequested, m_pageController.get(), &PageController::raiseMainWindow);
|
||||
connect(m_notificationHandler.get(), &NotificationHandler::connectRequested, m_connectionController.get(),
|
||||
static_cast<void (ConnectionController::*)()>(&ConnectionController::openConnection));
|
||||
connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(),
|
||||
&ConnectionController::closeConnection);
|
||||
connect(this, &AmneziaApplication::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated);
|
||||
#endif
|
||||
m_coreController.reset(new CoreController(m_vpnConnection, m_settings, m_engine));
|
||||
|
||||
m_engine->addImportPath("qrc:/ui/qml/Modules/");
|
||||
m_engine->load(url);
|
||||
m_systemController->setQmlRoot(m_engine->rootObjects().value(0));
|
||||
|
||||
m_coreController->setQmlRoot();
|
||||
|
||||
bool enabled = m_settings->isSaveLogs();
|
||||
#ifndef Q_OS_ANDROID
|
||||
|
|
@ -168,13 +93,13 @@ void AmneziaApplication::init()
|
|||
#endif
|
||||
Logger::setServiceLogsEnabled(enabled);
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#ifdef Q_OS_WIN //TODO
|
||||
if (m_parser.isSet("a"))
|
||||
m_pageController->showOnStartup();
|
||||
m_coreController->pageController()->showOnStartup();
|
||||
else
|
||||
emit m_pageController->raiseMainWindow();
|
||||
emit m_coreController->pageController()->raiseMainWindow();
|
||||
#else
|
||||
m_pageController->showOnStartup();
|
||||
m_coreController->pageController()->showOnStartup();
|
||||
#endif
|
||||
|
||||
// Android TextArea clipboard workaround
|
||||
|
|
@ -231,33 +156,6 @@ void AmneziaApplication::loadFonts()
|
|||
QFontDatabase::addApplicationFont(":/fonts/pt-root-ui_vf.ttf");
|
||||
}
|
||||
|
||||
void AmneziaApplication::loadTranslator()
|
||||
{
|
||||
auto locale = m_settings->getAppLanguage();
|
||||
m_translator.reset(new QTranslator());
|
||||
updateTranslator(locale);
|
||||
}
|
||||
|
||||
void AmneziaApplication::updateTranslator(const QLocale &locale)
|
||||
{
|
||||
if (!m_translator->isEmpty()) {
|
||||
QCoreApplication::removeTranslator(m_translator.get());
|
||||
}
|
||||
|
||||
QString strFileName = QString(":/translations/amneziavpn") + QLatin1String("_") + locale.name() + ".qm";
|
||||
if (m_translator->load(strFileName)) {
|
||||
if (QCoreApplication::installTranslator(m_translator.get())) {
|
||||
m_settings->setAppLanguage(locale);
|
||||
}
|
||||
} else {
|
||||
m_settings->setAppLanguage(QLocale::English);
|
||||
}
|
||||
|
||||
m_engine->retranslate();
|
||||
|
||||
emit translationsUpdated();
|
||||
}
|
||||
|
||||
bool AmneziaApplication::parseCommands()
|
||||
{
|
||||
m_parser.setApplicationDescription(APPLICATION_NAME);
|
||||
|
|
@ -282,19 +180,20 @@ bool AmneziaApplication::parseCommands()
|
|||
}
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
void AmneziaApplication::startLocalServer() {
|
||||
void AmneziaApplication::startLocalServer()
|
||||
{
|
||||
const QString serverName("AmneziaVPNInstance");
|
||||
QLocalServer::removeServer(serverName);
|
||||
|
||||
QLocalServer* server = new QLocalServer(this);
|
||||
QLocalServer *server = new QLocalServer(this);
|
||||
server->listen(serverName);
|
||||
|
||||
QObject::connect(server, &QLocalServer::newConnection, this, [server, this]() {
|
||||
if (server) {
|
||||
QLocalSocket* clientConnection = server->nextPendingConnection();
|
||||
QLocalSocket *clientConnection = server->nextPendingConnection();
|
||||
clientConnection->deleteLater();
|
||||
}
|
||||
emit m_pageController->raiseMainWindow();
|
||||
emit m_coreController->pageController()->raiseMainWindow(); //TODO
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
|
@ -304,160 +203,12 @@ QQmlApplicationEngine *AmneziaApplication::qmlEngine() const
|
|||
return m_engine;
|
||||
}
|
||||
|
||||
void AmneziaApplication::initModels()
|
||||
QNetworkAccessManager *AmneziaApplication::networkManager()
|
||||
{
|
||||
m_containersModel.reset(new ContainersModel(this));
|
||||
m_engine->rootContext()->setContextProperty("ContainersModel", m_containersModel.get());
|
||||
|
||||
m_defaultServerContainersModel.reset(new ContainersModel(this));
|
||||
m_engine->rootContext()->setContextProperty("DefaultServerContainersModel", m_defaultServerContainersModel.get());
|
||||
|
||||
m_serversModel.reset(new ServersModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get());
|
||||
connect(m_serversModel.get(), &ServersModel::containersUpdated, m_containersModel.get(), &ContainersModel::updateModel);
|
||||
connect(m_serversModel.get(), &ServersModel::defaultServerContainersUpdated, m_defaultServerContainersModel.get(),
|
||||
&ContainersModel::updateModel);
|
||||
m_serversModel->resetModel();
|
||||
|
||||
m_languageModel.reset(new LanguageModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("LanguageModel", m_languageModel.get());
|
||||
connect(m_languageModel.get(), &LanguageModel::updateTranslations, this, &AmneziaApplication::updateTranslator);
|
||||
connect(this, &AmneziaApplication::translationsUpdated, m_languageModel.get(), &LanguageModel::translationsUpdated);
|
||||
|
||||
m_sitesModel.reset(new SitesModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get());
|
||||
|
||||
m_appSplitTunnelingModel.reset(new AppSplitTunnelingModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("AppSplitTunnelingModel", m_appSplitTunnelingModel.get());
|
||||
|
||||
m_protocolsModel.reset(new ProtocolsModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get());
|
||||
|
||||
m_openVpnConfigModel.reset(new OpenVpnConfigModel(this));
|
||||
m_engine->rootContext()->setContextProperty("OpenVpnConfigModel", m_openVpnConfigModel.get());
|
||||
|
||||
m_shadowSocksConfigModel.reset(new ShadowSocksConfigModel(this));
|
||||
m_engine->rootContext()->setContextProperty("ShadowSocksConfigModel", m_shadowSocksConfigModel.get());
|
||||
|
||||
m_cloakConfigModel.reset(new CloakConfigModel(this));
|
||||
m_engine->rootContext()->setContextProperty("CloakConfigModel", m_cloakConfigModel.get());
|
||||
|
||||
m_wireGuardConfigModel.reset(new WireGuardConfigModel(this));
|
||||
m_engine->rootContext()->setContextProperty("WireGuardConfigModel", m_wireGuardConfigModel.get());
|
||||
|
||||
m_awgConfigModel.reset(new AwgConfigModel(this));
|
||||
m_engine->rootContext()->setContextProperty("AwgConfigModel", m_awgConfigModel.get());
|
||||
|
||||
m_xrayConfigModel.reset(new XrayConfigModel(this));
|
||||
m_engine->rootContext()->setContextProperty("XrayConfigModel", m_xrayConfigModel.get());
|
||||
|
||||
#ifdef Q_OS_WINDOWS
|
||||
m_ikev2ConfigModel.reset(new Ikev2ConfigModel(this));
|
||||
m_engine->rootContext()->setContextProperty("Ikev2ConfigModel", m_ikev2ConfigModel.get());
|
||||
#endif
|
||||
|
||||
m_sftpConfigModel.reset(new SftpConfigModel(this));
|
||||
m_engine->rootContext()->setContextProperty("SftpConfigModel", m_sftpConfigModel.get());
|
||||
|
||||
m_socks5ConfigModel.reset(new Socks5ProxyConfigModel(this));
|
||||
m_engine->rootContext()->setContextProperty("Socks5ProxyConfigModel", m_socks5ConfigModel.get());
|
||||
|
||||
m_clientManagementModel.reset(new ClientManagementModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get());
|
||||
connect(m_clientManagementModel.get(), &ClientManagementModel::adminConfigRevoked, m_serversModel.get(),
|
||||
&ServersModel::clearCachedProfile);
|
||||
|
||||
m_apiServicesModel.reset(new ApiServicesModel(this));
|
||||
m_engine->rootContext()->setContextProperty("ApiServicesModel", m_apiServicesModel.get());
|
||||
|
||||
m_apiCountryModel.reset(new ApiCountryModel(this));
|
||||
m_engine->rootContext()->setContextProperty("ApiCountryModel", m_apiCountryModel.get());
|
||||
connect(m_serversModel.get(), &ServersModel::updateApiLanguageModel, this, [this]() {
|
||||
m_apiCountryModel->updateModel(m_serversModel->getProcessedServerData("apiAvailableCountries").toJsonArray(),
|
||||
m_serversModel->getProcessedServerData("apiServerCountryCode").toString());
|
||||
});
|
||||
connect(m_serversModel.get(), &ServersModel::updateApiServicesModel, this,
|
||||
[this]() { m_apiServicesModel->updateModel(m_serversModel->getProcessedServerData("apiConfig").toJsonObject()); });
|
||||
return m_nam;
|
||||
}
|
||||
|
||||
void AmneziaApplication::initControllers()
|
||||
QClipboard *AmneziaApplication::getClipboard()
|
||||
{
|
||||
m_connectionController.reset(
|
||||
new ConnectionController(m_serversModel, m_containersModel, m_clientManagementModel, m_vpnConnection, m_settings));
|
||||
m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get());
|
||||
|
||||
connect(m_connectionController.get(), qOverload<const QString &>(&ConnectionController::connectionErrorOccurred), this,
|
||||
[this](const QString &errorMessage) {
|
||||
emit m_pageController->showErrorMessage(errorMessage);
|
||||
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
||||
});
|
||||
|
||||
connect(m_connectionController.get(), qOverload<ErrorCode>(&ConnectionController::connectionErrorOccurred), this,
|
||||
[this](ErrorCode errorCode) {
|
||||
emit m_pageController->showErrorMessage(errorCode);
|
||||
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
||||
});
|
||||
|
||||
connect(m_connectionController.get(), &ConnectionController::connectButtonClicked, m_connectionController.get(),
|
||||
&ConnectionController::toggleConnection, Qt::QueuedConnection);
|
||||
|
||||
m_pageController.reset(new PageController(m_serversModel, m_settings));
|
||||
m_engine->rootContext()->setContextProperty("PageController", m_pageController.get());
|
||||
|
||||
m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_clientManagementModel,
|
||||
m_apiServicesModel, m_settings));
|
||||
m_engine->rootContext()->setContextProperty("InstallController", m_installController.get());
|
||||
connect(m_installController.get(), &InstallController::passphraseRequestStarted, m_pageController.get(),
|
||||
&PageController::showPassphraseRequestDrawer);
|
||||
connect(m_pageController.get(), &PageController::passphraseRequestDrawerClosed, m_installController.get(),
|
||||
&InstallController::setEncryptedPassphrase);
|
||||
connect(m_installController.get(), &InstallController::currentContainerUpdated, m_connectionController.get(),
|
||||
&ConnectionController::onCurrentContainerUpdated);
|
||||
|
||||
connect(m_installController.get(), &InstallController::updateServerFromApiFinished, this, [this]() {
|
||||
disconnect(m_reloadConfigErrorOccurredConnection);
|
||||
emit m_connectionController->configFromApiUpdated();
|
||||
});
|
||||
|
||||
connect(m_connectionController.get(), &ConnectionController::updateApiConfigFromGateway, this, [this]() {
|
||||
m_reloadConfigErrorOccurredConnection = connect(
|
||||
m_installController.get(), qOverload<ErrorCode>(&InstallController::installationErrorOccurred), this,
|
||||
[this]() { emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); },
|
||||
static_cast<Qt::ConnectionType>(Qt::AutoConnection || Qt::SingleShotConnection));
|
||||
m_installController->updateServiceFromApi(m_serversModel->getDefaultServerIndex(), "", "");
|
||||
});
|
||||
|
||||
connect(m_connectionController.get(), &ConnectionController::updateApiConfigFromTelegram, this, [this]() {
|
||||
m_reloadConfigErrorOccurredConnection = connect(
|
||||
m_installController.get(), qOverload<ErrorCode>(&InstallController::installationErrorOccurred), this,
|
||||
[this]() { emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); },
|
||||
static_cast<Qt::ConnectionType>(Qt::AutoConnection || Qt::SingleShotConnection));
|
||||
m_serversModel->removeApiConfig(m_serversModel->getDefaultServerIndex());
|
||||
m_installController->updateServiceFromTelegram(m_serversModel->getDefaultServerIndex());
|
||||
});
|
||||
|
||||
connect(this, &AmneziaApplication::translationsUpdated, m_connectionController.get(), &ConnectionController::onTranslationsUpdated);
|
||||
|
||||
m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings));
|
||||
m_engine->rootContext()->setContextProperty("ImportController", m_importController.get());
|
||||
|
||||
m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_clientManagementModel, m_settings));
|
||||
m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get());
|
||||
|
||||
m_settingsController.reset(
|
||||
new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, m_appSplitTunnelingModel, m_settings));
|
||||
m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get());
|
||||
if (m_settingsController->isAutoConnectEnabled() && m_serversModel->getDefaultServerIndex() >= 0) {
|
||||
QTimer::singleShot(1000, this, [this]() { m_connectionController->openConnection(); });
|
||||
}
|
||||
connect(m_settingsController.get(), &SettingsController::amneziaDnsToggled, m_serversModel.get(), &ServersModel::toggleAmneziaDns);
|
||||
|
||||
m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel));
|
||||
m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get());
|
||||
|
||||
m_appSplitTunnelingController.reset(new AppSplitTunnelingController(m_settings, m_appSplitTunnelingModel));
|
||||
m_engine->rootContext()->setContextProperty("AppSplitTunnelingController", m_appSplitTunnelingController.get());
|
||||
|
||||
m_systemController.reset(new SystemController(m_settings));
|
||||
m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get());
|
||||
return this->clipboard();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,43 +11,12 @@
|
|||
#else
|
||||
#include <QApplication>
|
||||
#endif
|
||||
#include <QClipboard>
|
||||
|
||||
#include "core/controllers/coreController.h"
|
||||
#include "settings.h"
|
||||
#include "vpnconnection.h"
|
||||
|
||||
#include "ui/controllers/connectionController.h"
|
||||
#include "ui/controllers/exportController.h"
|
||||
#include "ui/controllers/importController.h"
|
||||
#include "ui/controllers/installController.h"
|
||||
#include "ui/controllers/pageController.h"
|
||||
#include "ui/controllers/settingsController.h"
|
||||
#include "ui/controllers/sitesController.h"
|
||||
#include "ui/controllers/systemController.h"
|
||||
#include "ui/controllers/appSplitTunnelingController.h"
|
||||
#include "ui/models/containers_model.h"
|
||||
#include "ui/models/languageModel.h"
|
||||
#include "ui/models/protocols/cloakConfigModel.h"
|
||||
#ifndef Q_OS_ANDROID
|
||||
#include "ui/notificationhandler.h"
|
||||
#endif
|
||||
#ifdef Q_OS_WINDOWS
|
||||
#include "ui/models/protocols/ikev2ConfigModel.h"
|
||||
#endif
|
||||
#include "ui/models/protocols/awgConfigModel.h"
|
||||
#include "ui/models/protocols/openvpnConfigModel.h"
|
||||
#include "ui/models/protocols/shadowsocksConfigModel.h"
|
||||
#include "ui/models/protocols/wireguardConfigModel.h"
|
||||
#include "ui/models/protocols/xrayConfigModel.h"
|
||||
#include "ui/models/protocols_model.h"
|
||||
#include "ui/models/servers_model.h"
|
||||
#include "ui/models/services/sftpConfigModel.h"
|
||||
#include "ui/models/services/socks5ProxyConfigModel.h"
|
||||
#include "ui/models/sites_model.h"
|
||||
#include "ui/models/clientManagementModel.h"
|
||||
#include "ui/models/appSplitTunnelingModel.h"
|
||||
#include "ui/models/apiServicesModel.h"
|
||||
#include "ui/models/apiCountryModel.h"
|
||||
|
||||
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
|
||||
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
|
|
@ -66,8 +35,6 @@ public:
|
|||
void init();
|
||||
void registerTypes();
|
||||
void loadFonts();
|
||||
void loadTranslator();
|
||||
void updateTranslator(const QLocale &locale);
|
||||
bool parseCommands();
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
|
|
@ -75,67 +42,24 @@ public:
|
|||
#endif
|
||||
|
||||
QQmlApplicationEngine *qmlEngine() const;
|
||||
QNetworkAccessManager *manager() { return m_nam; }
|
||||
|
||||
signals:
|
||||
void translationsUpdated();
|
||||
QNetworkAccessManager *networkManager();
|
||||
QClipboard *getClipboard();
|
||||
|
||||
private:
|
||||
void initModels();
|
||||
void initControllers();
|
||||
|
||||
QQmlApplicationEngine *m_engine {};
|
||||
std::shared_ptr<Settings> m_settings;
|
||||
|
||||
QScopedPointer<CoreController> m_coreController;
|
||||
|
||||
QSharedPointer<ContainerProps> m_containerProps;
|
||||
QSharedPointer<ProtocolProps> m_protocolProps;
|
||||
|
||||
QSharedPointer<QTranslator> m_translator;
|
||||
QCommandLineParser m_parser;
|
||||
|
||||
QSharedPointer<ContainersModel> m_containersModel;
|
||||
QSharedPointer<ContainersModel> m_defaultServerContainersModel;
|
||||
QSharedPointer<ServersModel> m_serversModel;
|
||||
QSharedPointer<LanguageModel> m_languageModel;
|
||||
QSharedPointer<ProtocolsModel> m_protocolsModel;
|
||||
QSharedPointer<SitesModel> m_sitesModel;
|
||||
QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel;
|
||||
QSharedPointer<ClientManagementModel> m_clientManagementModel;
|
||||
QSharedPointer<ApiServicesModel> m_apiServicesModel;
|
||||
QSharedPointer<ApiCountryModel> m_apiCountryModel;
|
||||
|
||||
QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel;
|
||||
QScopedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel;
|
||||
QScopedPointer<CloakConfigModel> m_cloakConfigModel;
|
||||
QScopedPointer<XrayConfigModel> m_xrayConfigModel;
|
||||
QScopedPointer<WireGuardConfigModel> m_wireGuardConfigModel;
|
||||
QScopedPointer<AwgConfigModel> m_awgConfigModel;
|
||||
#ifdef Q_OS_WINDOWS
|
||||
QScopedPointer<Ikev2ConfigModel> m_ikev2ConfigModel;
|
||||
#endif
|
||||
|
||||
QScopedPointer<SftpConfigModel> m_sftpConfigModel;
|
||||
QScopedPointer<Socks5ProxyConfigModel> m_socks5ConfigModel;
|
||||
|
||||
QSharedPointer<VpnConnection> m_vpnConnection;
|
||||
QThread m_vpnConnectionThread;
|
||||
#ifndef Q_OS_ANDROID
|
||||
QScopedPointer<NotificationHandler> m_notificationHandler;
|
||||
#endif
|
||||
|
||||
QScopedPointer<ConnectionController> m_connectionController;
|
||||
QScopedPointer<PageController> m_pageController;
|
||||
QScopedPointer<InstallController> m_installController;
|
||||
QScopedPointer<ImportController> m_importController;
|
||||
QScopedPointer<ExportController> m_exportController;
|
||||
QScopedPointer<SettingsController> m_settingsController;
|
||||
QScopedPointer<SitesController> m_sitesController;
|
||||
QScopedPointer<SystemController> m_systemController;
|
||||
QScopedPointer<AppSplitTunnelingController> m_appSplitTunnelingController;
|
||||
|
||||
QNetworkAccessManager *m_nam;
|
||||
|
||||
QMetaObject::Connection m_reloadConfigErrorOccurredConnection;
|
||||
};
|
||||
|
||||
#endif // AMNEZIA_APPLICATION_H
|
||||
|
|
|
|||
|
|
@ -91,6 +91,13 @@
|
|||
android:exported="false"
|
||||
android:theme="@style/Translucent" />
|
||||
|
||||
<activity android:name=".TvFilePicker"
|
||||
android:excludeFromRecents="true"
|
||||
android:launchMode="singleTask"
|
||||
android:taskAffinity=""
|
||||
android:exported="false"
|
||||
android:theme="@style/Translucent" />
|
||||
|
||||
<activity
|
||||
android:name=".ImportConfigActivity"
|
||||
android:excludeFromRecents="true"
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_banner_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_banner_foreground"/>
|
||||
</adaptive-icon>
|
||||
BIN
client/android/res/mipmap-hdpi/ic_banner.png
Normal file
BIN
client/android/res/mipmap-hdpi/ic_banner.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
BIN
client/android/res/mipmap-mdpi/ic_banner.png
Normal file
BIN
client/android/res/mipmap-mdpi/ic_banner.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.9 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB |
|
|
@ -23,4 +23,6 @@
|
|||
<string name="notificationSettingsDialogTitle">Настройки уведомлений</string>
|
||||
<string name="notificationSettingsDialogMessage">Для показа уведомлений необходимо включить уведомления в системных настройках</string>
|
||||
<string name="openNotificationSettings">Открыть настройки уведомлений</string>
|
||||
|
||||
<string name="tvNoFileBrowser">Пожалуйста, установите приложение для просмотра файлов</string>
|
||||
</resources>
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_banner_background">#1E1E1F</color>
|
||||
</resources>
|
||||
|
|
@ -23,4 +23,6 @@
|
|||
<string name="notificationSettingsDialogTitle">Notification settings</string>
|
||||
<string name="notificationSettingsDialogMessage">To show notifications, you must enable notifications in the system settings</string>
|
||||
<string name="openNotificationSettings">Open notification settings</string>
|
||||
|
||||
<string name="tvNoFileBrowser">Please install a file management utility to browse files</string>
|
||||
</resources>
|
||||
|
|
@ -4,6 +4,7 @@ import android.Manifest
|
|||
import android.annotation.SuppressLint
|
||||
import android.app.AlertDialog
|
||||
import android.app.NotificationManager
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
|
|
@ -12,6 +13,7 @@ import android.content.Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
|
|||
import android.content.ServiceConnection
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import android.net.VpnService
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
|
|
@ -20,8 +22,13 @@ import android.os.IBinder
|
|||
import android.os.Looper
|
||||
import android.os.Message
|
||||
import android.os.Messenger
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.os.SystemClock
|
||||
import android.provider.OpenableColumns
|
||||
import android.provider.Settings
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowManager.LayoutParams
|
||||
import android.webkit.MimeTypeMap
|
||||
import android.widget.Toast
|
||||
|
|
@ -30,6 +37,7 @@ import androidx.annotation.RequiresApi
|
|||
import androidx.core.content.ContextCompat
|
||||
import java.io.IOException
|
||||
import kotlin.LazyThreadSafetyMode.NONE
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.text.RegexOption.IGNORE_CASE
|
||||
import AppListProvider
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
|
|
@ -71,6 +79,7 @@ class AmneziaActivity : QtActivity() {
|
|||
private var isInBoundState = false
|
||||
private var notificationStateReceiver: BroadcastReceiver? = null
|
||||
private lateinit var vpnServiceMessenger: IpcMessenger
|
||||
private var pfd: ParcelFileDescriptor? = null
|
||||
|
||||
private val actionResultHandlers = mutableMapOf<Int, ActivityResultHandler>()
|
||||
private val permissionRequestHandlers = mutableMapOf<Int, PermissionRequestHandler>()
|
||||
|
|
@ -514,21 +523,25 @@ class AmneziaActivity : QtActivity() {
|
|||
type = "text/*"
|
||||
putExtra(Intent.EXTRA_TITLE, fileName)
|
||||
}.also {
|
||||
startActivityForResult(it, CREATE_FILE_ACTION_CODE, ActivityResultHandler(
|
||||
onSuccess = {
|
||||
it?.data?.let { uri ->
|
||||
Log.v(TAG, "Save file to $uri")
|
||||
try {
|
||||
contentResolver.openOutputStream(uri)?.use { os ->
|
||||
os.bufferedWriter().use { it.write(data) }
|
||||
try {
|
||||
startActivityForResult(it, CREATE_FILE_ACTION_CODE, ActivityResultHandler(
|
||||
onSuccess = {
|
||||
it?.data?.let { uri ->
|
||||
Log.v(TAG, "Save file to $uri")
|
||||
try {
|
||||
contentResolver.openOutputStream(uri)?.use { os ->
|
||||
os.bufferedWriter().use { it.write(data) }
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Log.e(TAG, "Failed to save file $uri: $e")
|
||||
// todo: send error to Qt
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Log.e(TAG, "Failed to save file $uri: $e")
|
||||
// todo: send error to Qt
|
||||
}
|
||||
}
|
||||
}
|
||||
))
|
||||
))
|
||||
} catch (_: ActivityNotFoundException) {
|
||||
Toast.makeText(this@AmneziaActivity, "Unsupported", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -537,35 +550,46 @@ class AmneziaActivity : QtActivity() {
|
|||
fun openFile(filter: String?) {
|
||||
Log.v(TAG, "Open file with filter: $filter")
|
||||
mainScope.launch {
|
||||
val mimeTypes = if (!filter.isNullOrEmpty()) {
|
||||
val extensionRegex = "\\*\\.([a-z0-9]+)".toRegex(IGNORE_CASE)
|
||||
val mime = MimeTypeMap.getSingleton()
|
||||
extensionRegex.findAll(filter).map {
|
||||
it.groups[1]?.value?.let { mime.getMimeTypeFromExtension(it) } ?: "*/*"
|
||||
}.toSet()
|
||||
} else emptySet()
|
||||
val intent = if (!isOnTv()) {
|
||||
val mimeTypes = if (!filter.isNullOrEmpty()) {
|
||||
val extensionRegex = "\\*\\.([a-z0-9]+)".toRegex(IGNORE_CASE)
|
||||
val mime = MimeTypeMap.getSingleton()
|
||||
extensionRegex.findAll(filter).map {
|
||||
it.groups[1]?.value?.let { mime.getMimeTypeFromExtension(it) } ?: "*/*"
|
||||
}.toSet()
|
||||
} else emptySet()
|
||||
|
||||
Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
Log.v(TAG, "File mimyType filter: $mimeTypes")
|
||||
if ("*/*" in mimeTypes) {
|
||||
type = "*/*"
|
||||
} else {
|
||||
when (mimeTypes.size) {
|
||||
1 -> type = mimeTypes.first()
|
||||
Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
Log.v(TAG, "File mimyType filter: $mimeTypes")
|
||||
if ("*/*" in mimeTypes) {
|
||||
type = "*/*"
|
||||
} else {
|
||||
when (mimeTypes.size) {
|
||||
1 -> type = mimeTypes.first()
|
||||
|
||||
in 2..Int.MAX_VALUE -> {
|
||||
type = "*/*"
|
||||
putExtra(EXTRA_MIME_TYPES, mimeTypes.toTypedArray())
|
||||
in 2..Int.MAX_VALUE -> {
|
||||
type = "*/*"
|
||||
putExtra(EXTRA_MIME_TYPES, mimeTypes.toTypedArray())
|
||||
}
|
||||
|
||||
else -> type = "*/*"
|
||||
}
|
||||
|
||||
else -> type = "*/*"
|
||||
}
|
||||
}
|
||||
}.also {
|
||||
startActivityForResult(it, OPEN_FILE_ACTION_CODE, ActivityResultHandler(
|
||||
} else {
|
||||
Intent(this@AmneziaActivity, TvFilePicker::class.java)
|
||||
}
|
||||
|
||||
try {
|
||||
startActivityForResult(intent, OPEN_FILE_ACTION_CODE, ActivityResultHandler(
|
||||
onAny = {
|
||||
val uri = it?.data?.toString() ?: ""
|
||||
if (isOnTv() && it?.hasExtra("activityNotFound") == true) {
|
||||
showNoFileBrowserAlertDialog()
|
||||
}
|
||||
val uri = it?.data?.apply {
|
||||
grantUriPermission(packageName, this, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}?.toString() ?: ""
|
||||
Log.v(TAG, "Open file: $uri")
|
||||
mainScope.launch {
|
||||
qtInitialized.await()
|
||||
|
|
@ -573,10 +597,68 @@ class AmneziaActivity : QtActivity() {
|
|||
}
|
||||
}
|
||||
))
|
||||
} catch (_: ActivityNotFoundException) {
|
||||
showNoFileBrowserAlertDialog()
|
||||
mainScope.launch {
|
||||
qtInitialized.await()
|
||||
QtAndroidController.onFileOpened("")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showNoFileBrowserAlertDialog() {
|
||||
AlertDialog.Builder(this)
|
||||
.setMessage(R.string.tvNoFileBrowser)
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
try {
|
||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("market://webstoreredirect")))
|
||||
} catch (_: Throwable) {}
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun getFd(fileName: String): Int {
|
||||
Log.v(TAG, "Get fd for $fileName")
|
||||
return blockingCall {
|
||||
try {
|
||||
pfd = contentResolver.openFileDescriptor(Uri.parse(fileName), "r")
|
||||
pfd?.fd ?: -1
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to get fd: $e")
|
||||
-1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun closeFd() {
|
||||
Log.v(TAG, "Close fd")
|
||||
mainScope.launch {
|
||||
pfd?.close()
|
||||
pfd = null
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun getFileName(uri: String): String {
|
||||
Log.v(TAG, "Get file name for uri: $uri")
|
||||
return blockingCall {
|
||||
try {
|
||||
contentResolver.query(Uri.parse(uri), arrayOf(OpenableColumns.DISPLAY_NAME), null, null, null)?.use { cursor ->
|
||||
if (cursor.moveToFirst() && !cursor.isNull(0)) {
|
||||
return@blockingCall cursor.getString(0) ?: ""
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to get file name: $e")
|
||||
}
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
@SuppressLint("UnsupportedChromeOsCameraSystemFeature")
|
||||
fun isCameraPresent(): Boolean = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)
|
||||
|
|
@ -721,6 +803,50 @@ class AmneziaActivity : QtActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
// method to workaround Qt's problem with calling the keyboard on TVs
|
||||
@Suppress("unused")
|
||||
fun sendTouch(x: Float, y: Float) {
|
||||
Log.v(TAG, "Send touch: $x, $y")
|
||||
blockingCall {
|
||||
findQtWindow(window.decorView)?.let {
|
||||
Log.v(TAG, "Send touch to $it")
|
||||
it.dispatchTouchEvent(createEvent(x, y, SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN))
|
||||
it.dispatchTouchEvent(createEvent(x, y, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun findQtWindow(view: View): View? {
|
||||
Log.v(TAG, "findQtWindow: process $view")
|
||||
if (view::class.simpleName == "QtWindow") return view
|
||||
else if (view is ViewGroup) {
|
||||
for (i in 0 until view.childCount) {
|
||||
val result = findQtWindow(view.getChildAt(i))
|
||||
if (result != null) return result
|
||||
}
|
||||
return null
|
||||
} else return null
|
||||
}
|
||||
|
||||
private fun createEvent(x: Float, y: Float, eventTime: Long, action: Int): MotionEvent =
|
||||
MotionEvent.obtain(
|
||||
eventTime,
|
||||
eventTime,
|
||||
action,
|
||||
1,
|
||||
arrayOf(MotionEvent.PointerProperties().apply {
|
||||
id = 0
|
||||
toolType = MotionEvent.TOOL_TYPE_FINGER
|
||||
}),
|
||||
arrayOf(MotionEvent.PointerCoords().apply {
|
||||
this.x = x
|
||||
this.y = y
|
||||
pressure = 1f
|
||||
size = 1f
|
||||
}),
|
||||
0, 0, 1.0f, 1.0f, 0, 0, 0,0
|
||||
)
|
||||
|
||||
// workaround for a bug in Qt that causes the mouse click event not to be handled
|
||||
// also disable right-click, as it causes the application to crash
|
||||
private var lastButtonState = 0
|
||||
|
|
@ -770,6 +896,7 @@ class AmneziaActivity : QtActivity() {
|
|||
}
|
||||
|
||||
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
|
||||
Log.v(TAG, "dispatchTouch: $ev")
|
||||
if (ev != null && ev.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) {
|
||||
return handleMouseEvent(ev) { super.dispatchTouchEvent(it) }
|
||||
}
|
||||
|
|
@ -784,6 +911,13 @@ class AmneziaActivity : QtActivity() {
|
|||
/**
|
||||
* Utils methods
|
||||
*/
|
||||
private fun <T> blockingCall(
|
||||
context: CoroutineContext = Dispatchers.Main.immediate,
|
||||
block: suspend () -> T
|
||||
) = runBlocking {
|
||||
mainScope.async(context) { block() }.await()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private fun actionCodeToString(actionCode: Int): String =
|
||||
when (actionCode) {
|
||||
|
|
|
|||
45
client/android/src/org/amnezia/vpn/TvFilePicker.kt
Normal file
45
client/android/src/org/amnezia/vpn/TvFilePicker.kt
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
package org.amnezia.vpn
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import org.amnezia.vpn.util.Log
|
||||
|
||||
private const val TAG = "TvFilePicker"
|
||||
|
||||
class TvFilePicker : ComponentActivity() {
|
||||
|
||||
private val fileChooseResultLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) {
|
||||
setResult(RESULT_OK, Intent().apply { data = it })
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
Log.v(TAG, "onCreate")
|
||||
getFile()
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
Log.v(TAG, "onNewIntent")
|
||||
getFile()
|
||||
}
|
||||
|
||||
private fun getFile() {
|
||||
try {
|
||||
Log.v(TAG, "getFile")
|
||||
fileChooseResultLauncher.launch("*/*")
|
||||
} catch (_: ActivityNotFoundException) {
|
||||
Log.w(TAG, "Activity not found")
|
||||
setResult(RESULT_CANCELED, Intent().apply { putExtra("activityNotFound", true) })
|
||||
finish()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to get file: $e")
|
||||
setResult(RESULT_CANCELED)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -120,10 +120,21 @@ open class Wireguard : Protocol() {
|
|||
configData.optStringOrNull("Jmax")?.let { setJmax(it.toInt()) }
|
||||
configData.optStringOrNull("S1")?.let { setS1(it.toInt()) }
|
||||
configData.optStringOrNull("S2")?.let { setS2(it.toInt()) }
|
||||
configData.optStringOrNull("S3")?.let { setS3(it.toInt()) }
|
||||
configData.optStringOrNull("S4")?.let { setS4(it.toInt()) }
|
||||
configData.optStringOrNull("H1")?.let { setH1(it.toLong()) }
|
||||
configData.optStringOrNull("H2")?.let { setH2(it.toLong()) }
|
||||
configData.optStringOrNull("H3")?.let { setH3(it.toLong()) }
|
||||
configData.optStringOrNull("H4")?.let { setH4(it.toLong()) }
|
||||
configData.optStringOrNull("I1")?.let { setI1(it) }
|
||||
configData.optStringOrNull("I2")?.let { setI2(it) }
|
||||
configData.optStringOrNull("I3")?.let { setI3(it) }
|
||||
configData.optStringOrNull("I4")?.let { setI4(it) }
|
||||
configData.optStringOrNull("I5")?.let { setI5(it) }
|
||||
configData.optStringOrNull("J1")?.let { setJ1(it) }
|
||||
configData.optStringOrNull("J2")?.let { setJ2(it) }
|
||||
configData.optStringOrNull("J3")?.let { setJ3(it) }
|
||||
configData.optStringOrNull("Itime")?.let { setItime(it.toInt()) }
|
||||
}
|
||||
|
||||
private fun start(config: WireguardConfig, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
||||
|
|
|
|||
|
|
@ -20,10 +20,21 @@ open class WireguardConfig protected constructor(
|
|||
val jmax: Int?,
|
||||
val s1: Int?,
|
||||
val s2: Int?,
|
||||
val s3: Int?,
|
||||
val s4: Int?,
|
||||
val h1: Long?,
|
||||
val h2: Long?,
|
||||
val h3: Long?,
|
||||
val h4: Long?
|
||||
val h4: Long?,
|
||||
var i1: String?,
|
||||
var i2: String?,
|
||||
var i3: String?,
|
||||
var i4: String?,
|
||||
var i5: String?,
|
||||
var j1: String?,
|
||||
var j2: String?,
|
||||
var j3: String?,
|
||||
var itime: Int?
|
||||
) : ProtocolConfig(protocolConfigBuilder) {
|
||||
|
||||
protected constructor(builder: Builder) : this(
|
||||
|
|
@ -39,10 +50,21 @@ open class WireguardConfig protected constructor(
|
|||
builder.jmax,
|
||||
builder.s1,
|
||||
builder.s2,
|
||||
builder.s3,
|
||||
builder.s4,
|
||||
builder.h1,
|
||||
builder.h2,
|
||||
builder.h3,
|
||||
builder.h4
|
||||
builder.h4,
|
||||
builder.i1,
|
||||
builder.i2,
|
||||
builder.i3,
|
||||
builder.i4,
|
||||
builder.i5,
|
||||
builder.j1,
|
||||
builder.j2,
|
||||
builder.j3,
|
||||
builder.itime
|
||||
)
|
||||
|
||||
fun toWgUserspaceString(): String = with(StringBuilder()) {
|
||||
|
|
@ -61,10 +83,21 @@ open class WireguardConfig protected constructor(
|
|||
appendLine("jmax=$jmax")
|
||||
appendLine("s1=$s1")
|
||||
appendLine("s2=$s2")
|
||||
s3?.let { appendLine("s3=$it") }
|
||||
s4?.let { appendLine("s4=$it") }
|
||||
appendLine("h1=$h1")
|
||||
appendLine("h2=$h2")
|
||||
appendLine("h3=$h3")
|
||||
appendLine("h4=$h4")
|
||||
i1?.let { appendLine("i1=$it") }
|
||||
i2?.let { appendLine("i2=$it") }
|
||||
i3?.let { appendLine("i3=$it") }
|
||||
i4?.let { appendLine("i4=$it") }
|
||||
i5?.let { appendLine("i5=$it") }
|
||||
j1?.let { appendLine("j1=$it") }
|
||||
j2?.let { appendLine("j2=$it") }
|
||||
j3?.let { appendLine("j3=$it") }
|
||||
itime?.let { appendLine("itime=$it") }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -117,10 +150,21 @@ open class WireguardConfig protected constructor(
|
|||
internal var jmax: Int? = null
|
||||
internal var s1: Int? = null
|
||||
internal var s2: Int? = null
|
||||
internal var s3: Int? = null
|
||||
internal var s4: Int? = null
|
||||
internal var h1: Long? = null
|
||||
internal var h2: Long? = null
|
||||
internal var h3: Long? = null
|
||||
internal var h4: Long? = null
|
||||
internal var i1: String? = null
|
||||
internal var i2: String? = null
|
||||
internal var i3: String? = null
|
||||
internal var i4: String? = null
|
||||
internal var i5: String? = null
|
||||
internal var j1: String? = null
|
||||
internal var j2: String? = null
|
||||
internal var j3: String? = null
|
||||
internal var itime: Int? = null
|
||||
|
||||
fun setEndpoint(endpoint: InetEndpoint) = apply { this.endpoint = endpoint }
|
||||
|
||||
|
|
@ -139,10 +183,21 @@ open class WireguardConfig protected constructor(
|
|||
fun setJmax(jmax: Int) = apply { this.jmax = jmax }
|
||||
fun setS1(s1: Int) = apply { this.s1 = s1 }
|
||||
fun setS2(s2: Int) = apply { this.s2 = s2 }
|
||||
fun setS3(s3: Int) = apply { this.s3 = s3 }
|
||||
fun setS4(s4: Int) = apply { this.s4 = s4 }
|
||||
fun setH1(h1: Long) = apply { this.h1 = h1 }
|
||||
fun setH2(h2: Long) = apply { this.h2 = h2 }
|
||||
fun setH3(h3: Long) = apply { this.h3 = h3 }
|
||||
fun setH4(h4: Long) = apply { this.h4 = h4 }
|
||||
fun setI1(i1: String) = apply { this.i1 = i1 }
|
||||
fun setI2(i2: String) = apply { this.i2 = i2 }
|
||||
fun setI3(i3: String) = apply { this.i3 = i3 }
|
||||
fun setI4(i4: String) = apply { this.i4 = i4 }
|
||||
fun setI5(i5: String) = apply { this.i5 = i5 }
|
||||
fun setJ1(j1: String) = apply { this.j1 = j1 }
|
||||
fun setJ2(j2: String) = apply { this.j2 = j2 }
|
||||
fun setJ3(j3: String) = apply { this.j3 = j3 }
|
||||
fun setItime(itime: Int) = apply { this.itime = itime }
|
||||
|
||||
override fun build(): WireguardConfig = configBuild().run { WireguardConfig(this@Builder) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,12 +76,22 @@ set_target_properties(${PROJECT} PROPERTIES
|
|||
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
|
||||
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/Frameworks"
|
||||
XCODE_EMBED_APP_EXTENSIONS networkextension
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
|
||||
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "match AppStore org.amnezia.AmneziaVPN"
|
||||
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "match Development org.amnezia.AmneziaVPN"
|
||||
)
|
||||
|
||||
if(DEFINED DEPLOY)
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
|
||||
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "distr ios.org.amnezia.AmneziaVPN"
|
||||
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "dev ios.org.amnezia.AmneziaVPN"
|
||||
)
|
||||
else()
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
|
||||
)
|
||||
endif()
|
||||
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
|
||||
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES"
|
||||
|
|
@ -126,9 +136,9 @@ add_subdirectory(ios/networkextension)
|
|||
add_dependencies(${PROJECT} networkextension)
|
||||
|
||||
set_property(TARGET ${PROJECT} PROPERTY XCODE_EMBED_FRAMEWORKS
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/3rd/OpenVPNAdapter/build/Release-iphoneos/OpenVPNAdapter.framework"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/OpenVPNAdapter.framework"
|
||||
)
|
||||
|
||||
set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/3rd/OpenVPNAdapter/build/Release-iphoneos)
|
||||
target_link_libraries("networkextension" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/3rd/OpenVPNAdapter/build/Release-iphoneos/OpenVPNAdapter.framework")
|
||||
set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/)
|
||||
target_link_libraries("networkextension" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/OpenVPNAdapter.framework")
|
||||
|
||||
|
|
|
|||
191
client/cmake/sources.cmake
Normal file
191
client/cmake/sources.cmake
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/..)
|
||||
|
||||
set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/migrations.h
|
||||
${CLIENT_ROOT_DIR}/../ipc/ipc.h
|
||||
${CLIENT_ROOT_DIR}/amnezia_application.h
|
||||
${CLIENT_ROOT_DIR}/containers/containers_defs.h
|
||||
${CLIENT_ROOT_DIR}/core/defs.h
|
||||
${CLIENT_ROOT_DIR}/core/errorstrings.h
|
||||
${CLIENT_ROOT_DIR}/core/scripts_registry.h
|
||||
${CLIENT_ROOT_DIR}/core/server_defs.h
|
||||
${CLIENT_ROOT_DIR}/core/api/apiDefs.h
|
||||
${CLIENT_ROOT_DIR}/core/qrCodeUtils.h
|
||||
${CLIENT_ROOT_DIR}/core/controllers/coreController.h
|
||||
${CLIENT_ROOT_DIR}/core/controllers/gatewayController.h
|
||||
${CLIENT_ROOT_DIR}/core/controllers/serverController.h
|
||||
${CLIENT_ROOT_DIR}/core/controllers/vpnConfigurationController.h
|
||||
${CLIENT_ROOT_DIR}/protocols/protocols_defs.h
|
||||
${CLIENT_ROOT_DIR}/protocols/qml_register_protocols.h
|
||||
${CLIENT_ROOT_DIR}/ui/pages.h
|
||||
${CLIENT_ROOT_DIR}/ui/qautostart.h
|
||||
${CLIENT_ROOT_DIR}/protocols/vpnprotocol.h
|
||||
${CMAKE_CURRENT_BINARY_DIR}/version.h
|
||||
${CLIENT_ROOT_DIR}/core/sshclient.h
|
||||
${CLIENT_ROOT_DIR}/core/networkUtilities.h
|
||||
${CLIENT_ROOT_DIR}/core/serialization/serialization.h
|
||||
${CLIENT_ROOT_DIR}/core/serialization/transfer.h
|
||||
${CLIENT_ROOT_DIR}/../common/logger/logger.h
|
||||
${CLIENT_ROOT_DIR}/utils/qmlUtils.h
|
||||
${CLIENT_ROOT_DIR}/core/api/apiUtils.h
|
||||
)
|
||||
|
||||
# Mozilla headres
|
||||
set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/mozilla/models/server.h
|
||||
${CLIENT_ROOT_DIR}/mozilla/shared/ipaddress.h
|
||||
${CLIENT_ROOT_DIR}/mozilla/shared/leakdetector.h
|
||||
${CLIENT_ROOT_DIR}/mozilla/controllerimpl.h
|
||||
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.h
|
||||
)
|
||||
|
||||
if(NOT IOS)
|
||||
set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/QRCodeReaderBase.h
|
||||
)
|
||||
endif()
|
||||
|
||||
if(NOT ANDROID)
|
||||
set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/ui/notificationhandler.h
|
||||
)
|
||||
endif()
|
||||
|
||||
set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/migrations.cpp
|
||||
${CLIENT_ROOT_DIR}/amnezia_application.cpp
|
||||
${CLIENT_ROOT_DIR}/containers/containers_defs.cpp
|
||||
${CLIENT_ROOT_DIR}/core/errorstrings.cpp
|
||||
${CLIENT_ROOT_DIR}/core/scripts_registry.cpp
|
||||
${CLIENT_ROOT_DIR}/core/server_defs.cpp
|
||||
${CLIENT_ROOT_DIR}/core/qrCodeUtils.cpp
|
||||
${CLIENT_ROOT_DIR}/core/controllers/coreController.cpp
|
||||
${CLIENT_ROOT_DIR}/core/controllers/gatewayController.cpp
|
||||
${CLIENT_ROOT_DIR}/core/controllers/serverController.cpp
|
||||
${CLIENT_ROOT_DIR}/core/controllers/vpnConfigurationController.cpp
|
||||
${CLIENT_ROOT_DIR}/protocols/protocols_defs.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/qautostart.cpp
|
||||
${CLIENT_ROOT_DIR}/protocols/vpnprotocol.cpp
|
||||
${CLIENT_ROOT_DIR}/core/sshclient.cpp
|
||||
${CLIENT_ROOT_DIR}/core/networkUtilities.cpp
|
||||
${CLIENT_ROOT_DIR}/core/serialization/outbound.cpp
|
||||
${CLIENT_ROOT_DIR}/core/serialization/inbound.cpp
|
||||
${CLIENT_ROOT_DIR}/core/serialization/ss.cpp
|
||||
${CLIENT_ROOT_DIR}/core/serialization/ssd.cpp
|
||||
${CLIENT_ROOT_DIR}/core/serialization/vless.cpp
|
||||
${CLIENT_ROOT_DIR}/core/serialization/trojan.cpp
|
||||
${CLIENT_ROOT_DIR}/core/serialization/vmess.cpp
|
||||
${CLIENT_ROOT_DIR}/core/serialization/vmess_new.cpp
|
||||
${CLIENT_ROOT_DIR}/../common/logger/logger.cpp
|
||||
${CLIENT_ROOT_DIR}/utils/qmlUtils.cpp
|
||||
${CLIENT_ROOT_DIR}/core/api/apiUtils.cpp
|
||||
)
|
||||
|
||||
# Mozilla sources
|
||||
set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/mozilla/models/server.cpp
|
||||
${CLIENT_ROOT_DIR}/mozilla/shared/ipaddress.cpp
|
||||
${CLIENT_ROOT_DIR}/mozilla/shared/leakdetector.cpp
|
||||
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.cpp
|
||||
)
|
||||
|
||||
if(NOT IOS)
|
||||
set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/QRCodeReaderBase.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
if(NOT ANDROID)
|
||||
set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/ui/notificationhandler.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
file(GLOB COMMON_FILES_H CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/*.h)
|
||||
file(GLOB COMMON_FILES_CPP CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/*.cpp)
|
||||
|
||||
file(GLOB_RECURSE PAGE_LOGIC_H CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/ui/pages_logic/*.h)
|
||||
file(GLOB_RECURSE PAGE_LOGIC_CPP CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/ui/pages_logic/*.cpp)
|
||||
|
||||
file(GLOB CONFIGURATORS_H CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/configurators/*.h)
|
||||
file(GLOB CONFIGURATORS_CPP CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/configurators/*.cpp)
|
||||
|
||||
file(GLOB UI_MODELS_H CONFIGURE_DEPENDS
|
||||
${CLIENT_ROOT_DIR}/ui/models/*.h
|
||||
${CLIENT_ROOT_DIR}/ui/models/protocols/*.h
|
||||
${CLIENT_ROOT_DIR}/ui/models/services/*.h
|
||||
${CLIENT_ROOT_DIR}/ui/models/api/*.h
|
||||
)
|
||||
file(GLOB UI_MODELS_CPP CONFIGURE_DEPENDS
|
||||
${CLIENT_ROOT_DIR}/ui/models/*.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/models/protocols/*.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/models/services/*.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/models/api/*.cpp
|
||||
)
|
||||
|
||||
file(GLOB UI_CONTROLLERS_H CONFIGURE_DEPENDS
|
||||
${CLIENT_ROOT_DIR}/ui/controllers/*.h
|
||||
${CLIENT_ROOT_DIR}/ui/controllers/api/*.h
|
||||
)
|
||||
file(GLOB UI_CONTROLLERS_CPP CONFIGURE_DEPENDS
|
||||
${CLIENT_ROOT_DIR}/ui/controllers/*.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/controllers/api/*.cpp
|
||||
)
|
||||
|
||||
set(HEADERS ${HEADERS}
|
||||
${COMMON_FILES_H}
|
||||
${PAGE_LOGIC_H}
|
||||
${CONFIGURATORS_H}
|
||||
${UI_MODELS_H}
|
||||
${UI_CONTROLLERS_H}
|
||||
)
|
||||
set(SOURCES ${SOURCES}
|
||||
${COMMON_FILES_CPP}
|
||||
${PAGE_LOGIC_CPP}
|
||||
${CONFIGURATORS_CPP}
|
||||
${UI_MODELS_CPP}
|
||||
${UI_CONTROLLERS_CPP}
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/protocols/ikev2_vpn_protocol_windows.h
|
||||
)
|
||||
|
||||
set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/protocols/ikev2_vpn_protocol_windows.cpp
|
||||
)
|
||||
|
||||
set(RESOURCES ${RESOURCES}
|
||||
${CMAKE_CURRENT_BINARY_DIR}/amneziavpn.rc
|
||||
)
|
||||
endif()
|
||||
|
||||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
message("Client desktop build")
|
||||
add_compile_definitions(AMNEZIA_DESKTOP)
|
||||
|
||||
set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/core/ipcclient.h
|
||||
${CLIENT_ROOT_DIR}/core/privileged_process.h
|
||||
${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.h
|
||||
${CLIENT_ROOT_DIR}/protocols/openvpnprotocol.h
|
||||
${CLIENT_ROOT_DIR}/protocols/openvpnovercloakprotocol.h
|
||||
${CLIENT_ROOT_DIR}/protocols/shadowsocksvpnprotocol.h
|
||||
${CLIENT_ROOT_DIR}/protocols/wireguardprotocol.h
|
||||
${CLIENT_ROOT_DIR}/protocols/xrayprotocol.h
|
||||
${CLIENT_ROOT_DIR}/protocols/awgprotocol.h
|
||||
)
|
||||
|
||||
set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/core/ipcclient.cpp
|
||||
${CLIENT_ROOT_DIR}/core/privileged_process.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.cpp
|
||||
${CLIENT_ROOT_DIR}/protocols/openvpnprotocol.cpp
|
||||
${CLIENT_ROOT_DIR}/protocols/openvpnovercloakprotocol.cpp
|
||||
${CLIENT_ROOT_DIR}/protocols/shadowsocksvpnprotocol.cpp
|
||||
${CLIENT_ROOT_DIR}/protocols/wireguardprotocol.cpp
|
||||
${CLIENT_ROOT_DIR}/protocols/xrayprotocol.cpp
|
||||
${CLIENT_ROOT_DIR}/protocols/awgprotocol.cpp
|
||||
)
|
||||
endif()
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
#include "awg_configurator.h"
|
||||
#include "protocols/protocols_defs.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
|
@ -39,6 +40,20 @@ QString AwgConfigurator::createConfig(const ServerCredentials &credentials, Dock
|
|||
jsonConfig[config_key::responsePacketMagicHeader] = configMap.value(config_key::responsePacketMagicHeader);
|
||||
jsonConfig[config_key::underloadPacketMagicHeader] = configMap.value(config_key::underloadPacketMagicHeader);
|
||||
jsonConfig[config_key::transportPacketMagicHeader] = configMap.value(config_key::transportPacketMagicHeader);
|
||||
|
||||
// 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::controlledJunk1] = configMap.value(amnezia::config_key::controlledJunk1);
|
||||
// jsonConfig[config_key::controlledJunk2] = configMap.value(amnezia::config_key::controlledJunk2);
|
||||
// jsonConfig[config_key::controlledJunk3] = configMap.value(amnezia::config_key::controlledJunk3);
|
||||
// jsonConfig[config_key::specialHandshakeTimeout] = configMap.value(amnezia::config_key::specialHandshakeTimeout);
|
||||
|
||||
jsonConfig[config_key::mtu] =
|
||||
containerConfig.value(ProtocolProps::protoToString(Proto::Awg)).toObject().value(config_key::mtu).toString(protocols::awg::defaultMtu);
|
||||
|
||||
|
|
|
|||
|
|
@ -13,10 +13,10 @@
|
|||
#include <QApplication>
|
||||
#endif
|
||||
|
||||
#include "core/networkUtilities.h"
|
||||
#include "containers/containers_defs.h"
|
||||
#include "core/controllers/serverController.h"
|
||||
#include "core/scripts_registry.h"
|
||||
#include "core/server_defs.h"
|
||||
#include "settings.h"
|
||||
#include "utilities.h"
|
||||
|
||||
|
|
@ -24,6 +24,7 @@
|
|||
#include <openssl/rsa.h>
|
||||
#include <openssl/x509.h>
|
||||
|
||||
|
||||
OpenVpnConfigurator::OpenVpnConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController,
|
||||
QObject *parent)
|
||||
: ConfiguratorBase(settings, serverController, parent)
|
||||
|
|
@ -117,22 +118,22 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPair<QString,
|
|||
QRegularExpression regex("redirect-gateway.*");
|
||||
config.replace(regex, "");
|
||||
|
||||
// We don't use secondary DNS if primary DNS is AmneziaDNS
|
||||
if (dns.first.contains(protocols::dns::amneziaDnsIp)) {
|
||||
QRegularExpression dnsRegex("dhcp-option DNS " + dns.second);
|
||||
config.replace(dnsRegex, "");
|
||||
}
|
||||
|
||||
if (!m_settings->isSitesSplitTunnelingEnabled()) {
|
||||
config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n");
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
// Prevent ipv6 leak
|
||||
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
|
||||
#endif
|
||||
config.append("block-ipv6\n");
|
||||
} else if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
|
||||
|
||||
// no redirect-gateway
|
||||
// no redirect-gateway
|
||||
} else if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n");
|
||||
// Prevent ipv6 leak
|
||||
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
|
||||
#endif
|
||||
config.append("block-ipv6\n");
|
||||
}
|
||||
|
|
@ -166,10 +167,15 @@ QString OpenVpnConfigurator::processConfigWithExportSettings(const QPair<QString
|
|||
QRegularExpression regex("redirect-gateway.*");
|
||||
config.replace(regex, "");
|
||||
|
||||
// We don't use secondary DNS if primary DNS is AmneziaDNS
|
||||
if (dns.first.contains(protocols::dns::amneziaDnsIp)) {
|
||||
QRegularExpression dnsRegex("dhcp-option DNS " + dns.second);
|
||||
config.replace(dnsRegex, "");
|
||||
}
|
||||
|
||||
config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n");
|
||||
|
||||
// Prevent ipv6 leak
|
||||
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
|
||||
config.append("block-ipv6\n");
|
||||
|
||||
// remove block-outside-dns for all exported configs
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include <QDebug>
|
||||
#include <QJsonDocument>
|
||||
#include <QProcess>
|
||||
#include <QRegularExpression>
|
||||
#include <QString>
|
||||
#include <QTemporaryDir>
|
||||
#include <QTemporaryFile>
|
||||
|
|
@ -19,13 +20,17 @@
|
|||
#include "settings.h"
|
||||
#include "utilities.h"
|
||||
|
||||
WireguardConfigurator::WireguardConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController,
|
||||
bool isAwg, QObject *parent)
|
||||
WireguardConfigurator::WireguardConfigurator(std::shared_ptr<Settings> settings,
|
||||
const QSharedPointer<ServerController> &serverController, bool isAwg,
|
||||
QObject *parent)
|
||||
: ConfiguratorBase(settings, serverController, parent), m_isAwg(isAwg)
|
||||
{
|
||||
m_serverConfigPath = m_isAwg ? amnezia::protocols::awg::serverConfigPath : amnezia::protocols::wireguard::serverConfigPath;
|
||||
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_serverConfigPath =
|
||||
m_isAwg ? amnezia::protocols::awg::serverConfigPath : amnezia::protocols::wireguard::serverConfigPath;
|
||||
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_protocolName = m_isAwg ? config_key::awg : config_key::wireguard;
|
||||
|
|
@ -63,9 +68,31 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::genClientKeys()
|
|||
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,
|
||||
DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode &errorCode)
|
||||
const QJsonObject &containerConfig,
|
||||
ErrorCode &errorCode)
|
||||
{
|
||||
WireguardConfigurator::ConnectionData connData = WireguardConfigurator::genClientKeys();
|
||||
connData.host = credentials.hostName;
|
||||
|
|
@ -76,65 +103,45 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
|
|||
return connData;
|
||||
}
|
||||
|
||||
// Get list of already created clients (only IP addresses)
|
||||
QString nextIpNumber;
|
||||
{
|
||||
QString script = QString("cat %1 | grep AllowedIPs").arg(m_serverConfigPath);
|
||||
QString stdOut;
|
||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||
stdOut += data + "\n";
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
QString getIpsScript = QString("cat %1 | grep AllowedIPs").arg(m_serverConfigPath);
|
||||
QString stdOut;
|
||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||
stdOut += data + "\n";
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
|
||||
errorCode = m_serverController->runContainerScript(credentials, container, script, cbReadStdOut);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return connData;
|
||||
}
|
||||
errorCode = m_serverController->runContainerScript(credentials, container, getIpsScript, cbReadStdOut);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return connData;
|
||||
}
|
||||
auto ips = getIpsFromConf(stdOut);
|
||||
|
||||
stdOut.replace("AllowedIPs = ", "");
|
||||
stdOut.replace("/32", "");
|
||||
QStringList ips = stdOut.split("\n", Qt::SkipEmptyParts);
|
||||
|
||||
// remove extra IPs from each line for case when user manually edited the wg0.conf
|
||||
// and added there more IPs for route his itnernal networks, like:
|
||||
// ...
|
||||
// AllowedIPs = 10.8.1.6/32, 192.168.1.0/24, 192.168.2.0/24, ...
|
||||
// ...
|
||||
// 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";
|
||||
QHostAddress nextIp = [&] {
|
||||
QHostAddress result;
|
||||
QHostAddress lastIp;
|
||||
if (ips.empty()) {
|
||||
lastIp.setAddress(containerConfig.value(m_protocolName)
|
||||
.toObject()
|
||||
.value(config_key::subnet_address)
|
||||
.toString(protocols::wireguard::defaultSubnetAddress));
|
||||
} else {
|
||||
int next = ips.last().split(".").last().toInt() + 1;
|
||||
if (next > 254) {
|
||||
errorCode = ErrorCode::AddressPoolError;
|
||||
return connData;
|
||||
}
|
||||
nextIpNumber = QString::number(next);
|
||||
lastIp = ips.last();
|
||||
}
|
||||
}
|
||||
|
||||
QString subnetIp = containerConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress);
|
||||
{
|
||||
QStringList l = subnetIp.split(".", Qt::SkipEmptyParts);
|
||||
if (l.isEmpty()) {
|
||||
errorCode = ErrorCode::AddressPoolError;
|
||||
return connData;
|
||||
quint8 lastOctet = static_cast<quint8>(lastIp.toIPv4Address());
|
||||
switch (lastOctet) {
|
||||
case 254: result.setAddress(lastIp.toIPv4Address() + 3); break;
|
||||
case 255: result.setAddress(lastIp.toIPv4Address() + 2); break;
|
||||
default: result.setAddress(lastIp.toIPv4Address() + 1); break;
|
||||
}
|
||||
l.removeLast();
|
||||
l.append(nextIpNumber);
|
||||
|
||||
connData.clientIP = l.join(".");
|
||||
}
|
||||
return result;
|
||||
}();
|
||||
|
||||
connData.clientIP = nextIp.toString();
|
||||
|
||||
// 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", "");
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return connData;
|
||||
|
|
@ -161,10 +168,12 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
|
|||
return connData;
|
||||
}
|
||||
|
||||
QString script = QString("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip %1)'").arg(m_serverConfigPath);
|
||||
QString script = QString("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip %1)'")
|
||||
.arg(m_serverConfigPath);
|
||||
|
||||
errorCode = m_serverController->runScript(
|
||||
credentials, m_serverController->replaceVars(script, m_serverController->genVarsForScript(credentials, container)));
|
||||
credentials,
|
||||
m_serverController->replaceVars(script, m_serverController->genVarsForScript(credentials, container)));
|
||||
|
||||
return connData;
|
||||
}
|
||||
|
|
@ -173,8 +182,8 @@ QString WireguardConfigurator::createConfig(const ServerCredentials &credentials
|
|||
const QJsonObject &containerConfig, ErrorCode &errorCode)
|
||||
{
|
||||
QString scriptData = amnezia::scriptData(m_configTemplate, container);
|
||||
QString config =
|
||||
m_serverController->replaceVars(scriptData, m_serverController->genVarsForScript(credentials, container, containerConfig));
|
||||
QString config = m_serverController->replaceVars(
|
||||
scriptData, m_serverController->genVarsForScript(credentials, container, containerConfig));
|
||||
|
||||
ConnectionData connData = prepareWireguardConfig(credentials, container, containerConfig, errorCode);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
|
|
@ -208,16 +217,16 @@ QString WireguardConfigurator::createConfig(const ServerCredentials &credentials
|
|||
return QJsonDocument(jConfig).toJson();
|
||||
}
|
||||
|
||||
QString WireguardConfigurator::processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||
QString &protocolConfigString)
|
||||
QString WireguardConfigurator::processConfigWithLocalSettings(const QPair<QString, QString> &dns,
|
||||
const bool isApiConfig, QString &protocolConfigString)
|
||||
{
|
||||
processConfigWithDnsSettings(dns, protocolConfigString);
|
||||
|
||||
return protocolConfigString;
|
||||
}
|
||||
|
||||
QString WireguardConfigurator::processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||
QString &protocolConfigString)
|
||||
QString WireguardConfigurator::processConfigWithExportSettings(const QPair<QString, QString> &dns,
|
||||
const bool isApiConfig, QString &protocolConfigString)
|
||||
{
|
||||
processConfigWithDnsSettings(dns, protocolConfigString);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#ifndef WIREGUARD_CONFIGURATOR_H
|
||||
#define WIREGUARD_CONFIGURATOR_H
|
||||
|
||||
#include <QHostAddress>
|
||||
#include <QObject>
|
||||
#include <QProcessEnvironment>
|
||||
|
||||
|
|
@ -12,8 +13,8 @@ class WireguardConfigurator : public ConfiguratorBase
|
|||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
WireguardConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, bool isAwg,
|
||||
QObject *parent = nullptr);
|
||||
WireguardConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController,
|
||||
bool isAwg, QObject *parent = nullptr);
|
||||
|
||||
struct ConnectionData
|
||||
{
|
||||
|
|
@ -26,15 +27,18 @@ public:
|
|||
QString port;
|
||||
};
|
||||
|
||||
QString createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
|
||||
ErrorCode &errorCode);
|
||||
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode &errorCode);
|
||||
|
||||
QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig, QString &protocolConfigString);
|
||||
QString processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig, QString &protocolConfigString);
|
||||
QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||
QString &protocolConfigString);
|
||||
QString processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||
QString &protocolConfigString);
|
||||
|
||||
static ConnectionData genClientKeys();
|
||||
|
||||
private:
|
||||
QList<QHostAddress> getIpsFromConf(const QString &input);
|
||||
ConnectionData prepareWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode &errorCode);
|
||||
|
||||
|
|
|
|||
|
|
@ -3,38 +3,169 @@
|
|||
#include <QFile>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QUuid>
|
||||
#include "logger.h"
|
||||
|
||||
#include "containers/containers_defs.h"
|
||||
#include "core/controllers/serverController.h"
|
||||
#include "core/scripts_registry.h"
|
||||
|
||||
namespace {
|
||||
Logger logger("XrayConfigurator");
|
||||
}
|
||||
|
||||
XrayConfigurator::XrayConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent)
|
||||
: ConfiguratorBase(settings, serverController, parent)
|
||||
{
|
||||
}
|
||||
|
||||
QString XrayConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
|
||||
ErrorCode &errorCode)
|
||||
QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode &errorCode)
|
||||
{
|
||||
QString config = m_serverController->replaceVars(amnezia::scriptData(ProtocolScriptType::xray_template, container),
|
||||
m_serverController->genVarsForScript(credentials, container, containerConfig));
|
||||
|
||||
QString xrayPublicKey =
|
||||
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::PublicKeyPath, errorCode);
|
||||
xrayPublicKey.replace("\n", "");
|
||||
|
||||
QString xrayUuid = m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::uuidPath, errorCode);
|
||||
xrayUuid.replace("\n", "");
|
||||
|
||||
QString xrayShortId =
|
||||
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::shortidPath, errorCode);
|
||||
xrayShortId.replace("\n", "");
|
||||
|
||||
// Generate new UUID for client
|
||||
QString clientId = QUuid::createUuid().toString(QUuid::WithoutBraces);
|
||||
|
||||
// Get current server config
|
||||
QString currentConfig = m_serverController->getTextFileFromContainer(
|
||||
container, credentials, amnezia::protocols::xray::serverConfigPath, errorCode);
|
||||
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
logger.error() << "Failed to get server config file";
|
||||
return "";
|
||||
}
|
||||
|
||||
config.replace("$XRAY_CLIENT_ID", xrayUuid);
|
||||
// Parse current config as JSON
|
||||
QJsonDocument doc = QJsonDocument::fromJson(currentConfig.toUtf8());
|
||||
if (doc.isNull() || !doc.isObject()) {
|
||||
logger.error() << "Failed to parse server config JSON";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return "";
|
||||
}
|
||||
|
||||
QJsonObject serverConfig = doc.object();
|
||||
|
||||
// Validate server config structure
|
||||
if (!serverConfig.contains("inbounds")) {
|
||||
logger.error() << "Server config missing 'inbounds' field";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return "";
|
||||
}
|
||||
|
||||
QJsonArray inbounds = serverConfig["inbounds"].toArray();
|
||||
if (inbounds.isEmpty()) {
|
||||
logger.error() << "Server config has empty 'inbounds' array";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return "";
|
||||
}
|
||||
|
||||
QJsonObject inbound = inbounds[0].toObject();
|
||||
if (!inbound.contains("settings")) {
|
||||
logger.error() << "Inbound missing 'settings' field";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return "";
|
||||
}
|
||||
|
||||
QJsonObject settings = inbound["settings"].toObject();
|
||||
if (!settings.contains("clients")) {
|
||||
logger.error() << "Settings missing 'clients' field";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return "";
|
||||
}
|
||||
|
||||
QJsonArray clients = settings["clients"].toArray();
|
||||
|
||||
// Create configuration for new client
|
||||
QJsonObject clientConfig {
|
||||
{"id", clientId},
|
||||
{"flow", "xtls-rprx-vision"}
|
||||
};
|
||||
|
||||
clients.append(clientConfig);
|
||||
|
||||
// Update config
|
||||
settings["clients"] = clients;
|
||||
inbound["settings"] = settings;
|
||||
inbounds[0] = inbound;
|
||||
serverConfig["inbounds"] = inbounds;
|
||||
|
||||
// Save updated config to server
|
||||
QString updatedConfig = QJsonDocument(serverConfig).toJson();
|
||||
errorCode = m_serverController->uploadTextFileToContainer(
|
||||
container,
|
||||
credentials,
|
||||
updatedConfig,
|
||||
amnezia::protocols::xray::serverConfigPath,
|
||||
libssh::ScpOverwriteMode::ScpOverwriteExisting
|
||||
);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
logger.error() << "Failed to upload updated config";
|
||||
return "";
|
||||
}
|
||||
|
||||
// Restart container
|
||||
QString restartScript = QString("sudo docker restart $CONTAINER_NAME");
|
||||
errorCode = m_serverController->runScript(
|
||||
credentials,
|
||||
m_serverController->replaceVars(restartScript, m_serverController->genVarsForScript(credentials, container))
|
||||
);
|
||||
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
logger.error() << "Failed to restart container";
|
||||
return "";
|
||||
}
|
||||
|
||||
return clientId;
|
||||
}
|
||||
|
||||
QString XrayConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode &errorCode)
|
||||
{
|
||||
// Get client ID from prepareServerConfig
|
||||
QString xrayClientId = prepareServerConfig(credentials, container, containerConfig, errorCode);
|
||||
if (errorCode != ErrorCode::NoError || xrayClientId.isEmpty()) {
|
||||
logger.error() << "Failed to prepare server config";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return "";
|
||||
}
|
||||
|
||||
QString config = m_serverController->replaceVars(amnezia::scriptData(ProtocolScriptType::xray_template, container),
|
||||
m_serverController->genVarsForScript(credentials, container, containerConfig));
|
||||
|
||||
if (config.isEmpty()) {
|
||||
logger.error() << "Failed to get config template";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return "";
|
||||
}
|
||||
|
||||
QString xrayPublicKey =
|
||||
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::PublicKeyPath, errorCode);
|
||||
if (errorCode != ErrorCode::NoError || xrayPublicKey.isEmpty()) {
|
||||
logger.error() << "Failed to get public key";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return "";
|
||||
}
|
||||
xrayPublicKey.replace("\n", "");
|
||||
|
||||
QString xrayShortId =
|
||||
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::shortidPath, errorCode);
|
||||
if (errorCode != ErrorCode::NoError || xrayShortId.isEmpty()) {
|
||||
logger.error() << "Failed to get short ID";
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return "";
|
||||
}
|
||||
xrayShortId.replace("\n", "");
|
||||
|
||||
// Validate all required variables are present
|
||||
if (!config.contains("$XRAY_CLIENT_ID") || !config.contains("$XRAY_PUBLIC_KEY") || !config.contains("$XRAY_SHORT_ID")) {
|
||||
logger.error() << "Config template missing required variables:"
|
||||
<< "XRAY_CLIENT_ID:" << !config.contains("$XRAY_CLIENT_ID")
|
||||
<< "XRAY_PUBLIC_KEY:" << !config.contains("$XRAY_PUBLIC_KEY")
|
||||
<< "XRAY_SHORT_ID:" << !config.contains("$XRAY_SHORT_ID");
|
||||
errorCode = ErrorCode::InternalError;
|
||||
return "";
|
||||
}
|
||||
|
||||
config.replace("$XRAY_CLIENT_ID", xrayClientId);
|
||||
config.replace("$XRAY_PUBLIC_KEY", xrayPublicKey);
|
||||
config.replace("$XRAY_SHORT_ID", xrayShortId);
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,10 @@ public:
|
|||
|
||||
QString createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
|
||||
ErrorCode &errorCode);
|
||||
|
||||
private:
|
||||
QString prepareServerConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
|
||||
ErrorCode &errorCode);
|
||||
};
|
||||
|
||||
#endif // XRAY_CONFIGURATOR_H
|
||||
|
|
|
|||
|
|
@ -110,22 +110,19 @@ QMap<DockerContainer, QString> ContainerProps::containerDescriptions()
|
|||
QObject::tr("OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its "
|
||||
"own security protocol with SSL/TLS for key exchange.") },
|
||||
{ DockerContainer::ShadowSocks,
|
||||
QObject::tr("Shadowsocks - masks VPN traffic, making it similar to normal web traffic, but it "
|
||||
"may be recognized by analysis systems in some highly censored regions.") },
|
||||
QObject::tr("Shadowsocks masks VPN traffic, making it resemble normal web traffic, but it may still be detected by certain analysis systems.") },
|
||||
{ DockerContainer::Cloak,
|
||||
QObject::tr("OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against "
|
||||
"active-probing detection. Ideal for bypassing blocking in regions with the highest levels "
|
||||
"of censorship.") },
|
||||
"active-probing detection. It is very resistant to detection, but offers low speed.") },
|
||||
{ DockerContainer::WireGuard,
|
||||
QObject::tr("WireGuard - New popular VPN protocol with high performance, high speed and low power "
|
||||
"consumption. Recommended for regions with low levels of censorship.") },
|
||||
QObject::tr("WireGuard - popular VPN protocol with high performance, high speed and low power "
|
||||
"consumption.") },
|
||||
{ DockerContainer::Awg,
|
||||
QObject::tr("AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, "
|
||||
"but very resistant to blockages. "
|
||||
"Recommended for regions with high levels of censorship.") },
|
||||
QObject::tr("AmneziaWG is a special protocol from Amnezia based on WireGuard. "
|
||||
"It provides high connection speed and ensures stable operation even in the most challenging network conditions.") },
|
||||
{ DockerContainer::Xray,
|
||||
QObject::tr("XRay with REALITY - Suitable for countries with the highest level of internet censorship. "
|
||||
"Traffic masking as web traffic at the TLS level, and protection against detection by active probing methods.") },
|
||||
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.") },
|
||||
{ DockerContainer::Ipsec,
|
||||
QObject::tr("IKEv2/IPsec - Modern stable protocol, a bit faster than others, restores connection after "
|
||||
"signal loss. It has native support on the latest versions of Android and iOS.") },
|
||||
|
|
@ -143,100 +140,83 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
|
|||
{
|
||||
return {
|
||||
{ DockerContainer::OpenVpn,
|
||||
QObject::tr(
|
||||
"OpenVPN stands as one of the most popular and time-tested VPN protocols available.\n"
|
||||
"It employs its unique security protocol, "
|
||||
"leveraging the strength of SSL/TLS for encryption and key exchange. "
|
||||
"Furthermore, OpenVPN's support for a multitude of authentication methods makes it versatile and adaptable, "
|
||||
"catering to a wide range of devices and operating systems. "
|
||||
"Due to its open-source nature, OpenVPN benefits from extensive scrutiny by the global community, "
|
||||
"which continually reinforces its security. "
|
||||
"With a strong balance of performance, security, and compatibility, "
|
||||
"OpenVPN remains a top choice for privacy-conscious individuals and businesses alike.\n\n"
|
||||
"* Available in the AmneziaVPN across all platforms\n"
|
||||
"* Normal power consumption on mobile devices\n"
|
||||
"* Flexible customisation to suit user needs to work with different operating systems and devices\n"
|
||||
"* Recognised by DPI analysis systems and therefore susceptible to blocking\n"
|
||||
"* Can operate over both TCP and UDP network protocols.") },
|
||||
QObject::tr("OpenVPN is one of the most popular and reliable VPN protocols. "
|
||||
"It uses SSL/TLS encryption, supports a wide variety of devices and operating systems, "
|
||||
"and is continuously improved by the community due to its open-source nature. "
|
||||
"It provides a good balance between speed and security but is easily recognized by DPI systems, "
|
||||
"making it susceptible to blocking.\n"
|
||||
"\nFeatures:\n"
|
||||
"* Available on all AmneziaVPN platforms\n"
|
||||
"* Normal battery consumption on mobile devices\n"
|
||||
"* Flexible customization for various devices and OS\n"
|
||||
"* Operates over both TCP and UDP protocols") },
|
||||
{ DockerContainer::ShadowSocks,
|
||||
QObject::tr("Shadowsocks, inspired by the SOCKS5 protocol, safeguards the connection using the AEAD cipher. "
|
||||
"Although Shadowsocks is designed to be discreet and challenging to identify, it isn't identical to a standard HTTPS connection."
|
||||
"However, certain traffic analysis systems might still detect a Shadowsocks connection. "
|
||||
"Due to limited support in Amnezia, it's recommended to use AmneziaWG protocol.\n\n"
|
||||
"* Available in the AmneziaVPN only on desktop platforms\n"
|
||||
"* Configurable encryption protocol\n"
|
||||
QObject::tr("Shadowsocks is based on the SOCKS5 protocol and encrypts connections using AEAD cipher. "
|
||||
"Although designed to be discreet, it doesn't mimic a standard HTTPS connection and can be detected by some DPI systems. "
|
||||
"Due to limited support in Amnezia, we recommend using the AmneziaWG protocol.\n"
|
||||
"\nFeatures:\n"
|
||||
"* Available in AmneziaVPN only on desktop platforms\n"
|
||||
"* Customizable encryption protocol\n"
|
||||
"* Detectable by some DPI systems\n"
|
||||
"* Works over TCP network protocol.") },
|
||||
"* Operates over TCP protocol\n") },
|
||||
{ DockerContainer::Cloak,
|
||||
QObject::tr("This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for "
|
||||
"protecting against blocking.\n\n"
|
||||
"OpenVPN provides a secure VPN connection by encrypting all internet traffic between the client "
|
||||
"and the server.\n\n"
|
||||
"Cloak protects OpenVPN from detection and blocking. \n\n"
|
||||
"Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, "
|
||||
"and also protects the VPN from detection by Active Probing. This makes it very resistant to "
|
||||
"being detected\n\n"
|
||||
"Immediately after receiving the first data packet, Cloak authenticates the incoming connection. "
|
||||
"If authentication fails, the plugin masks the server as a fake website and your VPN becomes "
|
||||
"invisible to analysis systems.\n\n"
|
||||
"If there is a extreme level of Internet censorship in your region, we advise you to use only "
|
||||
"OpenVPN over Cloak from the first connection\n\n"
|
||||
"* Available in the AmneziaVPN across all platforms\n"
|
||||
QObject::tr("This combination includes the OpenVPN protocol and the Cloak plugin, specifically designed to protect against blocking.\n"
|
||||
"\nOpenVPN securely encrypts all internet traffic between your device and the server.\n"
|
||||
"\nThe Cloak plugin further protects the connection from DPI detection. "
|
||||
"It modifies traffic metadata to disguise VPN traffic as regular web traffic and prevents detection through active probing. "
|
||||
"If an incoming connection fails authentication, Cloak serves a fake website, making your VPN invisible to traffic analysis systems.\n"
|
||||
"\nIn regions with heavy internet censorship, we strongly recommend using OpenVPN with Cloak from your first connection.\n"
|
||||
"\nFeatures:\n"
|
||||
"* Available on all AmneziaVPN platforms\n"
|
||||
"* High power consumption on mobile devices\n"
|
||||
"* Flexible settings\n"
|
||||
"* Not recognised by DPI analysis systems\n"
|
||||
"* Works over TCP network protocol, 443 port.\n") },
|
||||
"* Flexible configuration options\n"
|
||||
"* Undetectable by DPI systems\n"
|
||||
"* Operates over TCP protocol on port 443") },
|
||||
{ DockerContainer::WireGuard,
|
||||
QObject::tr("A relatively new popular VPN protocol with a simplified architecture.\n"
|
||||
"WireGuard provides stable VPN connection and high performance on all devices. It uses hard-coded encryption "
|
||||
"settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput.\n"
|
||||
"WireGuard is very susceptible to blocking due to its distinct packet signatures. "
|
||||
"Unlike some other VPN protocols that employ obfuscation techniques, "
|
||||
"the consistent signature patterns of WireGuard packets can be more easily identified and "
|
||||
"thus blocked by advanced Deep Packet Inspection (DPI) systems and other network monitoring tools.\n\n"
|
||||
"* Available in the AmneziaVPN across all platforms\n"
|
||||
"* Low power consumption\n"
|
||||
"* Minimum number of settings\n"
|
||||
"* Easily recognised by DPI analysis systems, susceptible to blocking\n"
|
||||
"* Works over UDP network protocol.") },
|
||||
QObject::tr("WireGuard is a modern, streamlined VPN protocol offering stable connectivity and excellent performance across all devices. "
|
||||
"It uses fixed encryption settings, delivering lower latency and higher data transfer speeds compared to OpenVPN. "
|
||||
"However, WireGuard is easily identifiable by DPI systems due to its distinctive packet signatures, making it susceptible to blocking.\n"
|
||||
"\nFeatures:\n"
|
||||
"* Available on all AmneziaVPN platforms\n"
|
||||
"* Low power consumption on mobile devices\n"
|
||||
"* Minimal configuration required\n"
|
||||
"* Easily detected by DPI systems (susceptible to blocking)\n"
|
||||
"* Operates over UDP protocol") },
|
||||
{ DockerContainer::Awg,
|
||||
QObject::tr("A modern iteration of the popular VPN protocol, "
|
||||
"AmneziaWG builds upon the foundation set by WireGuard, "
|
||||
"retaining its simplified architecture and high-performance capabilities across devices.\n"
|
||||
"While WireGuard is known for its efficiency, "
|
||||
"it had issues with being easily detected due to its distinct packet signatures. "
|
||||
"AmneziaWG solves this problem by using better obfuscation methods, "
|
||||
"making its traffic blend in with regular internet traffic.\n"
|
||||
"This means that AmneziaWG keeps the fast performance of the original "
|
||||
"while adding an extra layer of stealth, "
|
||||
"making it a great choice for those wanting a fast and discreet VPN connection.\n\n"
|
||||
"* Available in the AmneziaVPN across all platforms\n"
|
||||
"* Low power consumption\n"
|
||||
"* Minimum number of settings\n"
|
||||
"* Not recognised by DPI analysis systems, resistant to blocking\n"
|
||||
"* Works over UDP network protocol.") },
|
||||
QObject::tr("AmneziaWG is a modern VPN protocol based on WireGuard, "
|
||||
"combining simplified architecture with high performance across all devices. "
|
||||
"It addresses WireGuard's main vulnerability (easy detection by DPI systems) through advanced obfuscation techniques, "
|
||||
"making VPN traffic indistinguishable from regular internet traffic.\n"
|
||||
"\nAmneziaWG is an excellent choice for those seeking a fast, stealthy VPN connection.\n"
|
||||
"\nFeatures:\n"
|
||||
"* Available on all AmneziaVPN platforms\n"
|
||||
"* Low battery consumption on mobile devices\n"
|
||||
"* Minimal settings required\n"
|
||||
"* Undetectable by traffic analysis systems (DPI)\n"
|
||||
"* Operates over UDP protocol") },
|
||||
{ DockerContainer::Xray,
|
||||
QObject::tr("The REALITY protocol, a pioneering development by the creators of XRay, "
|
||||
"is specifically designed to counteract the highest levels of internet censorship through its novel approach to evasion.\n"
|
||||
"It uniquely identifies censors during the TLS handshake phase, seamlessly operating as a proxy for legitimate clients while diverting censors to genuine websites like google.com, "
|
||||
"thus presenting an authentic TLS certificate and data. \n"
|
||||
"This advanced capability differentiates REALITY from similar technologies by its ability to disguise web traffic as coming from random, "
|
||||
"legitimate sites without the need for specific configurations. \n"
|
||||
"Unlike older protocols such as VMess, VLESS, and the XTLS-Vision transport, "
|
||||
"REALITY's innovative \"friend or foe\" recognition at the TLS handshake enhances security and circumvents detection by sophisticated DPI systems employing active probing techniques. "
|
||||
"This makes REALITY a robust solution for maintaining internet freedom in environments with stringent censorship.")
|
||||
},
|
||||
QObject::tr("REALITY is an innovative protocol developed by the creators of XRay, designed specifically to combat high levels of internet censorship. "
|
||||
"REALITY identifies censorship systems during the TLS handshake, "
|
||||
"redirecting suspicious traffic seamlessly to legitimate websites like google.com while providing genuine TLS certificates. "
|
||||
"This allows VPN traffic to blend indistinguishably with regular web traffic without special configuration."
|
||||
"\nUnlike older protocols such as VMess, VLESS, and XTLS-Vision, REALITY incorporates an advanced built-in \"friend-or-foe\" detection mechanism, "
|
||||
"effectively protecting against DPI and other traffic analysis methods.\n"
|
||||
"\nFeatures:\n"
|
||||
"* Resistant to active probing and DPI detection\n"
|
||||
"* No special configuration required to disguise traffic\n"
|
||||
"* Highly effective in heavily censored regions\n"
|
||||
"* Minimal battery consumption on devices\n"
|
||||
"* Operates over TCP protocol") },
|
||||
{ DockerContainer::Ipsec,
|
||||
QObject::tr("IKEv2, paired with the IPSec encryption layer, stands as a modern and stable VPN protocol.\n"
|
||||
"One of its distinguishing features is its ability to swiftly switch between networks and devices, "
|
||||
"making it particularly adaptive in dynamic network environments. \n"
|
||||
"While it offers a blend of security, stability, and speed, "
|
||||
"it's essential to note that IKEv2 can be easily detected and is susceptible to blocking.\n\n"
|
||||
"* Available in the AmneziaVPN only on Windows\n"
|
||||
"* Low power consumption, on mobile devices\n"
|
||||
"* Minimal configuration\n"
|
||||
"* Recognised by DPI analysis systems\n"
|
||||
"* Works over UDP network protocol, ports 500 and 4500.") },
|
||||
QObject::tr("IKEv2, combined with IPSec encryption, is a modern and reliable VPN protocol. "
|
||||
"It reconnects quickly when switching networks or devices, making it ideal for dynamic network environments. "
|
||||
"While it provides good security and speed, it's easily recognized by DPI systems and susceptible to blocking.\n"
|
||||
"\nFeatures:\n"
|
||||
"* Available in AmneziaVPN only on Windows\n"
|
||||
"* Low battery consumption on mobile devices\n"
|
||||
"* Minimal configuration required\n"
|
||||
"* Detectable by DPI analysis systems(easily blocked)\n"
|
||||
"* Operates over UDP protocol(ports 500 and 4500)") },
|
||||
|
||||
{ DockerContainer::TorWebSite, QObject::tr("Website in Tor network") },
|
||||
{ DockerContainer::Dns, QObject::tr("DNS Service") },
|
||||
|
|
@ -332,9 +312,7 @@ QStringList ContainerProps::fixedPortsForContainer(DockerContainer c)
|
|||
bool ContainerProps::isEasySetupContainer(DockerContainer container)
|
||||
{
|
||||
switch (container) {
|
||||
case DockerContainer::WireGuard: return true;
|
||||
case DockerContainer::Awg: return true;
|
||||
// case DockerContainer::Cloak: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -342,9 +320,7 @@ bool ContainerProps::isEasySetupContainer(DockerContainer container)
|
|||
QString ContainerProps::easySetupHeader(DockerContainer container)
|
||||
{
|
||||
switch (container) {
|
||||
case DockerContainer::WireGuard: return tr("Low");
|
||||
case DockerContainer::Awg: return tr("High");
|
||||
// case DockerContainer::Cloak: return tr("Extreme");
|
||||
case DockerContainer::Awg: return tr("Automatic");
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
|
|
@ -352,10 +328,8 @@ QString ContainerProps::easySetupHeader(DockerContainer container)
|
|||
QString ContainerProps::easySetupDescription(DockerContainer container)
|
||||
{
|
||||
switch (container) {
|
||||
case DockerContainer::WireGuard: return tr("I just want to increase the level of my privacy.");
|
||||
case DockerContainer::Awg: return tr("I want to bypass censorship. This option recommended in most cases.");
|
||||
// case DockerContainer::Cloak:
|
||||
// return tr("Most VPN protocols are blocked. Recommended if other options are not working.");
|
||||
case DockerContainer::Awg: return tr("AmneziaWG protocol will be installed. "
|
||||
"It provides high connection speed and ensures stable operation even in the most challenging network conditions.");
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
|
|
@ -363,9 +337,7 @@ QString ContainerProps::easySetupDescription(DockerContainer container)
|
|||
int ContainerProps::easySetupOrder(DockerContainer container)
|
||||
{
|
||||
switch (container) {
|
||||
case DockerContainer::WireGuard: return 3;
|
||||
case DockerContainer::Awg: return 2;
|
||||
// case DockerContainer::Cloak: return 1;
|
||||
case DockerContainer::Awg: return 1;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -384,9 +356,9 @@ bool ContainerProps::isShareable(DockerContainer container)
|
|||
QJsonObject ContainerProps::getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig)
|
||||
{
|
||||
QString protocolConfigString = containerConfig.value(ProtocolProps::protoToString(protocol))
|
||||
.toObject()
|
||||
.value(config_key::last_config)
|
||||
.toString();
|
||||
.toObject()
|
||||
.value(config_key::last_config)
|
||||
.toString();
|
||||
|
||||
return QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
|
||||
}
|
||||
|
|
|
|||
72
client/core/api/apiDefs.h
Normal file
72
client/core/api/apiDefs.h
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
#ifndef APIDEFS_H
|
||||
#define APIDEFS_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace apiDefs
|
||||
{
|
||||
enum ConfigType {
|
||||
AmneziaFreeV2 = 0,
|
||||
AmneziaFreeV3,
|
||||
AmneziaPremiumV1,
|
||||
AmneziaPremiumV2,
|
||||
SelfHosted,
|
||||
ExternalPremium
|
||||
};
|
||||
|
||||
enum ConfigSource {
|
||||
Telegram = 1,
|
||||
AmneziaGateway
|
||||
};
|
||||
|
||||
namespace key
|
||||
{
|
||||
constexpr QLatin1String configVersion("config_version");
|
||||
constexpr QLatin1String apiEndpoint("api_endpoint");
|
||||
constexpr QLatin1String apiKey("api_key");
|
||||
constexpr QLatin1String description("description");
|
||||
constexpr QLatin1String name("name");
|
||||
constexpr QLatin1String protocol("protocol");
|
||||
|
||||
constexpr QLatin1String apiConfig("api_config");
|
||||
constexpr QLatin1String stackType("stack_type");
|
||||
constexpr QLatin1String serviceType("service_type");
|
||||
constexpr QLatin1String cliVersion("cli_version");
|
||||
constexpr QLatin1String supportedProtocols("supported_protocols");
|
||||
|
||||
constexpr QLatin1String vpnKey("vpn_key");
|
||||
constexpr QLatin1String config("config");
|
||||
constexpr QLatin1String configs("configs");
|
||||
|
||||
constexpr QLatin1String installationUuid("installation_uuid");
|
||||
constexpr QLatin1String workerLastUpdated("worker_last_updated");
|
||||
constexpr QLatin1String lastDownloaded("last_downloaded");
|
||||
constexpr QLatin1String sourceType("source_type");
|
||||
|
||||
constexpr QLatin1String serverCountryCode("server_country_code");
|
||||
constexpr QLatin1String serverCountryName("server_country_name");
|
||||
|
||||
constexpr QLatin1String osVersion("os_version");
|
||||
|
||||
constexpr QLatin1String availableCountries("available_countries");
|
||||
constexpr QLatin1String activeDeviceCount("active_device_count");
|
||||
constexpr QLatin1String maxDeviceCount("max_device_count");
|
||||
constexpr QLatin1String subscriptionEndDate("subscription_end_date");
|
||||
constexpr QLatin1String issuedConfigs("issued_configs");
|
||||
|
||||
constexpr QLatin1String 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");
|
||||
}
|
||||
|
||||
const int requestTimeoutMsecs = 12 * 1000; // 12 secs
|
||||
}
|
||||
|
||||
#endif // APIDEFS_H
|
||||
164
client/core/api/apiUtils.cpp
Normal file
164
client/core/api/apiUtils.cpp
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
#include "apiUtils.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QJsonObject>
|
||||
|
||||
namespace
|
||||
{
|
||||
const QByteArray AMNEZIA_CONFIG_SIGNATURE = QByteArray::fromHex("000000ff");
|
||||
|
||||
QString escapeUnicode(const QString &input)
|
||||
{
|
||||
QString output;
|
||||
for (QChar c : input) {
|
||||
if (c.unicode() < 0x20 || c.unicode() > 0x7E) {
|
||||
output += QString("\\u%1").arg(QString::number(c.unicode(), 16).rightJustified(4, '0'));
|
||||
} else {
|
||||
output += c;
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
bool apiUtils::isSubscriptionExpired(const QString &subscriptionEndDate)
|
||||
{
|
||||
QDateTime now = QDateTime::currentDateTime();
|
||||
QDateTime endDate = QDateTime::fromString(subscriptionEndDate, Qt::ISODateWithMs);
|
||||
return endDate < now;
|
||||
}
|
||||
|
||||
bool apiUtils::isServerFromApi(const QJsonObject &serverConfigObject)
|
||||
{
|
||||
auto configVersion = serverConfigObject.value(apiDefs::key::configVersion).toInt();
|
||||
switch (configVersion) {
|
||||
case apiDefs::ConfigSource::Telegram: return true;
|
||||
case apiDefs::ConfigSource::AmneziaGateway: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
apiDefs::ConfigType apiUtils::getConfigType(const QJsonObject &serverConfigObject)
|
||||
{
|
||||
auto configVersion = serverConfigObject.value(apiDefs::key::configVersion).toInt();
|
||||
|
||||
switch (configVersion) {
|
||||
case apiDefs::ConfigSource::Telegram: {
|
||||
constexpr QLatin1String freeV2Endpoint(FREE_V2_ENDPOINT);
|
||||
constexpr QLatin1String premiumV1Endpoint(PREM_V1_ENDPOINT);
|
||||
|
||||
auto apiEndpoint = serverConfigObject.value(apiDefs::key::apiEndpoint).toString();
|
||||
|
||||
if (apiEndpoint.contains(premiumV1Endpoint)) {
|
||||
return apiDefs::ConfigType::AmneziaPremiumV1;
|
||||
} else if (apiEndpoint.contains(freeV2Endpoint)) {
|
||||
return apiDefs::ConfigType::AmneziaFreeV2;
|
||||
}
|
||||
};
|
||||
case apiDefs::ConfigSource::AmneziaGateway: {
|
||||
constexpr QLatin1String servicePremium("amnezia-premium");
|
||||
constexpr QLatin1String serviceFree("amnezia-free");
|
||||
constexpr QLatin1String serviceExternalPremium("external-premium");
|
||||
|
||||
auto apiConfigObject = serverConfigObject.value(apiDefs::key::apiConfig).toObject();
|
||||
auto serviceType = apiConfigObject.value(apiDefs::key::serviceType).toString();
|
||||
|
||||
if (serviceType == servicePremium) {
|
||||
return apiDefs::ConfigType::AmneziaPremiumV2;
|
||||
} else if (serviceType == serviceFree) {
|
||||
return apiDefs::ConfigType::AmneziaFreeV3;
|
||||
} else if (serviceType == serviceExternalPremium) {
|
||||
return apiDefs::ConfigType::ExternalPremium;
|
||||
}
|
||||
}
|
||||
default: {
|
||||
return apiDefs::ConfigType::SelfHosted;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
apiDefs::ConfigSource apiUtils::getConfigSource(const QJsonObject &serverConfigObject)
|
||||
{
|
||||
return static_cast<apiDefs::ConfigSource>(serverConfigObject.value(apiDefs::key::configVersion).toInt());
|
||||
}
|
||||
|
||||
amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &sslErrors, QNetworkReply *reply)
|
||||
{
|
||||
const int httpStatusCodeConflict = 409;
|
||||
const int httpStatusCodeNotFound = 404;
|
||||
|
||||
if (!sslErrors.empty()) {
|
||||
qDebug().noquote() << sslErrors;
|
||||
return amnezia::ErrorCode::ApiConfigSslError;
|
||||
} else if (reply->error() == QNetworkReply::NoError) {
|
||||
return amnezia::ErrorCode::NoError;
|
||||
} else if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
||||
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||
qDebug() << reply->error();
|
||||
return amnezia::ErrorCode::ApiConfigTimeoutError;
|
||||
} else if (reply->error() == QNetworkReply::NetworkError::OperationNotImplementedError) {
|
||||
qDebug() << reply->error();
|
||||
return amnezia::ErrorCode::ApiUpdateRequestError;
|
||||
} else {
|
||||
QString err = reply->errorString();
|
||||
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
qDebug() << QString::fromUtf8(reply->readAll());
|
||||
qDebug() << reply->error();
|
||||
qDebug() << err;
|
||||
qDebug() << httpStatusCode;
|
||||
if (httpStatusCode == httpStatusCodeConflict) {
|
||||
return amnezia::ErrorCode::ApiConfigLimitError;
|
||||
} else if (httpStatusCode == httpStatusCodeNotFound) {
|
||||
return amnezia::ErrorCode::ApiNotFoundError;
|
||||
}
|
||||
return amnezia::ErrorCode::ApiConfigDownloadError;
|
||||
}
|
||||
|
||||
qDebug() << "something went wrong";
|
||||
return amnezia::ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
bool apiUtils::isPremiumServer(const QJsonObject &serverConfigObject)
|
||||
{
|
||||
static const QSet<apiDefs::ConfigType> premiumTypes = { apiDefs::ConfigType::AmneziaPremiumV1, apiDefs::ConfigType::AmneziaPremiumV2,
|
||||
apiDefs::ConfigType::ExternalPremium };
|
||||
return premiumTypes.contains(getConfigType(serverConfigObject));
|
||||
}
|
||||
|
||||
QString apiUtils::getPremiumV1VpnKey(const QJsonObject &serverConfigObject)
|
||||
{
|
||||
if (apiUtils::getConfigType(serverConfigObject) != apiDefs::ConfigType::AmneziaPremiumV1) {
|
||||
return {};
|
||||
}
|
||||
|
||||
QList<QPair<QString, QVariant>> orderedFields;
|
||||
orderedFields.append(qMakePair(apiDefs::key::name, serverConfigObject[apiDefs::key::name].toString()));
|
||||
orderedFields.append(qMakePair(apiDefs::key::description, serverConfigObject[apiDefs::key::description].toString()));
|
||||
orderedFields.append(qMakePair(apiDefs::key::configVersion, serverConfigObject[apiDefs::key::configVersion].toDouble()));
|
||||
orderedFields.append(qMakePair(apiDefs::key::protocol, serverConfigObject[apiDefs::key::protocol].toString()));
|
||||
orderedFields.append(qMakePair(apiDefs::key::apiEndpoint, serverConfigObject[apiDefs::key::apiEndpoint].toString()));
|
||||
orderedFields.append(qMakePair(apiDefs::key::apiKey, serverConfigObject[apiDefs::key::apiKey].toString()));
|
||||
|
||||
QString vpnKeyStr = "{";
|
||||
for (int i = 0; i < orderedFields.size(); ++i) {
|
||||
const auto &pair = orderedFields[i];
|
||||
if (pair.second.typeId() == QMetaType::Type::QString) {
|
||||
vpnKeyStr += "\"" + pair.first + "\": \"" + pair.second.toString() + "\"";
|
||||
} else if (pair.second.typeId() == QMetaType::Type::Double || pair.second.typeId() == QMetaType::Type::Int) {
|
||||
vpnKeyStr += "\"" + pair.first + "\": " + QString::number(pair.second.toDouble(), 'f', 1);
|
||||
}
|
||||
|
||||
if (i < orderedFields.size() - 1) {
|
||||
vpnKeyStr += ", ";
|
||||
}
|
||||
}
|
||||
vpnKeyStr += "}";
|
||||
|
||||
QByteArray vpnKeyCompressed = escapeUnicode(vpnKeyStr).toUtf8();
|
||||
vpnKeyCompressed = qCompress(vpnKeyCompressed, 6);
|
||||
vpnKeyCompressed = vpnKeyCompressed.mid(4);
|
||||
|
||||
QByteArray signedData = AMNEZIA_CONFIG_SIGNATURE + vpnKeyCompressed;
|
||||
|
||||
return QString("vpn://%1").arg(QString(signedData.toBase64(QByteArray::Base64UrlEncoding)));
|
||||
}
|
||||
26
client/core/api/apiUtils.h
Normal file
26
client/core/api/apiUtils.h
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
#ifndef APIUTILS_H
|
||||
#define APIUTILS_H
|
||||
|
||||
#include <QNetworkReply>
|
||||
#include <QObject>
|
||||
|
||||
#include "apiDefs.h"
|
||||
#include "core/defs.h"
|
||||
|
||||
namespace apiUtils
|
||||
{
|
||||
bool isServerFromApi(const QJsonObject &serverConfigObject);
|
||||
|
||||
bool isSubscriptionExpired(const QString &subscriptionEndDate);
|
||||
|
||||
bool isPremiumServer(const QJsonObject &serverConfigObject);
|
||||
|
||||
apiDefs::ConfigType getConfigType(const QJsonObject &serverConfigObject);
|
||||
apiDefs::ConfigSource getConfigSource(const QJsonObject &serverConfigObject);
|
||||
|
||||
amnezia::ErrorCode checkNetworkReplyErrors(const QList<QSslError> &sslErrors, QNetworkReply *reply);
|
||||
|
||||
QString getPremiumV1VpnKey(const QJsonObject &serverConfigObject);
|
||||
}
|
||||
|
||||
#endif // APIUTILS_H
|
||||
|
|
@ -1,509 +0,0 @@
|
|||
#include "apiController.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <random>
|
||||
|
||||
#include <QEventLoop>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QtConcurrent>
|
||||
|
||||
#include "QBlockCipher.h"
|
||||
#include "QRsa.h"
|
||||
|
||||
#include "amnezia_application.h"
|
||||
#include "configurators/wireguard_configurator.h"
|
||||
#include "core/enums/apiEnums.h"
|
||||
#include "utilities.h"
|
||||
#include "version.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
namespace configKey
|
||||
{
|
||||
constexpr char cloak[] = "cloak";
|
||||
constexpr char awg[] = "awg";
|
||||
|
||||
constexpr char apiEdnpoint[] = "api_endpoint";
|
||||
constexpr char accessToken[] = "api_key";
|
||||
constexpr char certificate[] = "certificate";
|
||||
constexpr char publicKey[] = "public_key";
|
||||
constexpr char protocol[] = "protocol";
|
||||
|
||||
constexpr char uuid[] = "installation_uuid";
|
||||
constexpr char osVersion[] = "os_version";
|
||||
constexpr char appVersion[] = "app_version";
|
||||
|
||||
constexpr char userCountryCode[] = "user_country_code";
|
||||
constexpr char serverCountryCode[] = "server_country_code";
|
||||
constexpr char serviceType[] = "service_type";
|
||||
constexpr char serviceInfo[] = "service_info";
|
||||
|
||||
constexpr char aesKey[] = "aes_key";
|
||||
constexpr char aesIv[] = "aes_iv";
|
||||
constexpr char aesSalt[] = "aes_salt";
|
||||
|
||||
constexpr char apiPayload[] = "api_payload";
|
||||
constexpr char keyPayload[] = "key_payload";
|
||||
|
||||
constexpr char apiConfig[] = "api_config";
|
||||
constexpr char authData[] = "auth_data";
|
||||
}
|
||||
|
||||
const int requestTimeoutMsecs = 12 * 1000; // 12 secs
|
||||
|
||||
ErrorCode checkErrors(const QList<QSslError> &sslErrors, QNetworkReply *reply)
|
||||
{
|
||||
if (!sslErrors.empty()) {
|
||||
qDebug().noquote() << sslErrors;
|
||||
return ErrorCode::ApiConfigSslError;
|
||||
} else if (reply->error() == QNetworkReply::NoError) {
|
||||
return ErrorCode::NoError;
|
||||
} else if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
||||
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||
return ErrorCode::ApiConfigTimeoutError;
|
||||
} else {
|
||||
QString err = reply->errorString();
|
||||
qDebug() << QString::fromUtf8(reply->readAll());
|
||||
qDebug() << reply->error();
|
||||
qDebug() << err;
|
||||
qDebug() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
|
||||
return ErrorCode::ApiConfigDownloadError;
|
||||
}
|
||||
}
|
||||
|
||||
bool shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key = "",
|
||||
const QByteArray &iv = "", const QByteArray &salt = "")
|
||||
{
|
||||
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
||||
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||
qDebug() << "Timeout occurred";
|
||||
return true;
|
||||
} else if (responseBody.contains("html")) {
|
||||
qDebug() << "The response contains an html tag";
|
||||
return true;
|
||||
} else if (checkEncryption) {
|
||||
try {
|
||||
QSimpleCrypto::QBlockCipher blockCipher;
|
||||
static_cast<void>(blockCipher.decryptAesBlockCipher(responseBody, key, iv, "", salt));
|
||||
} catch (...) {
|
||||
qDebug() << "Failed to decrypt the data";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ApiController::ApiController(const QString &gatewayEndpoint, bool isDevEnvironment, QObject *parent)
|
||||
: QObject(parent), m_gatewayEndpoint(gatewayEndpoint), m_isDevEnvironment(isDevEnvironment)
|
||||
{
|
||||
}
|
||||
|
||||
void ApiController::fillServerConfig(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData,
|
||||
const QByteArray &apiResponseBody, QJsonObject &serverConfig)
|
||||
{
|
||||
QString data = QJsonDocument::fromJson(apiResponseBody).object().value(config_key::config).toString();
|
||||
|
||||
data.replace("vpn://", "");
|
||||
QByteArray ba = QByteArray::fromBase64(data.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
||||
|
||||
if (ba.isEmpty()) {
|
||||
emit errorOccurred(ErrorCode::ApiConfigEmptyError);
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray ba_uncompressed = qUncompress(ba);
|
||||
if (!ba_uncompressed.isEmpty()) {
|
||||
ba = ba_uncompressed;
|
||||
}
|
||||
|
||||
QString configStr = ba;
|
||||
if (protocol == configKey::cloak) {
|
||||
configStr.replace("<key>", "<key>\n");
|
||||
configStr.replace("$OPENVPN_PRIV_KEY", apiPayloadData.certRequest.privKey);
|
||||
} else if (protocol == configKey::awg) {
|
||||
configStr.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", apiPayloadData.wireGuardClientPrivKey);
|
||||
auto newServerConfig = QJsonDocument::fromJson(configStr.toUtf8()).object();
|
||||
auto containers = newServerConfig.value(config_key::containers).toArray();
|
||||
if (containers.isEmpty()) {
|
||||
return; // todo process error
|
||||
}
|
||||
auto container = containers.at(0).toObject();
|
||||
QString containerName = ContainerProps::containerTypeToString(DockerContainer::Awg);
|
||||
auto containerConfig = container.value(containerName).toObject();
|
||||
auto protocolConfig = QJsonDocument::fromJson(containerConfig.value(config_key::last_config).toString().toUtf8()).object();
|
||||
containerConfig[config_key::junkPacketCount] = protocolConfig.value(config_key::junkPacketCount);
|
||||
containerConfig[config_key::junkPacketMinSize] = protocolConfig.value(config_key::junkPacketMinSize);
|
||||
containerConfig[config_key::junkPacketMaxSize] = protocolConfig.value(config_key::junkPacketMaxSize);
|
||||
containerConfig[config_key::initPacketJunkSize] = protocolConfig.value(config_key::initPacketJunkSize);
|
||||
containerConfig[config_key::responsePacketJunkSize] = protocolConfig.value(config_key::responsePacketJunkSize);
|
||||
containerConfig[config_key::initPacketMagicHeader] = protocolConfig.value(config_key::initPacketMagicHeader);
|
||||
containerConfig[config_key::responsePacketMagicHeader] = protocolConfig.value(config_key::responsePacketMagicHeader);
|
||||
containerConfig[config_key::underloadPacketMagicHeader] = protocolConfig.value(config_key::underloadPacketMagicHeader);
|
||||
containerConfig[config_key::transportPacketMagicHeader] = protocolConfig.value(config_key::transportPacketMagicHeader);
|
||||
container[containerName] = containerConfig;
|
||||
containers.replace(0, container);
|
||||
newServerConfig[config_key::containers] = containers;
|
||||
configStr = QString(QJsonDocument(newServerConfig).toJson());
|
||||
}
|
||||
|
||||
QJsonObject newServerConfig = QJsonDocument::fromJson(configStr.toUtf8()).object();
|
||||
serverConfig[config_key::dns1] = newServerConfig.value(config_key::dns1);
|
||||
serverConfig[config_key::dns2] = newServerConfig.value(config_key::dns2);
|
||||
serverConfig[config_key::containers] = newServerConfig.value(config_key::containers);
|
||||
serverConfig[config_key::hostName] = newServerConfig.value(config_key::hostName);
|
||||
|
||||
if (newServerConfig.value(config_key::configVersion).toInt() == ApiConfigSources::AmneziaGateway) {
|
||||
serverConfig[config_key::configVersion] = newServerConfig.value(config_key::configVersion);
|
||||
serverConfig[config_key::description] = newServerConfig.value(config_key::description);
|
||||
serverConfig[config_key::name] = newServerConfig.value(config_key::name);
|
||||
}
|
||||
|
||||
auto defaultContainer = newServerConfig.value(config_key::defaultContainer).toString();
|
||||
serverConfig[config_key::defaultContainer] = defaultContainer;
|
||||
|
||||
QVariantMap map = serverConfig.value(configKey::apiConfig).toObject().toVariantMap();
|
||||
map.insert(newServerConfig.value(configKey::apiConfig).toObject().toVariantMap());
|
||||
auto apiConfig = QJsonObject::fromVariantMap(map);
|
||||
|
||||
if (newServerConfig.value(config_key::configVersion).toInt() == ApiConfigSources::AmneziaGateway) {
|
||||
apiConfig.insert(configKey::serviceInfo, QJsonDocument::fromJson(apiResponseBody).object().value(configKey::serviceInfo).toObject());
|
||||
}
|
||||
|
||||
serverConfig[configKey::apiConfig] = apiConfig;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
QStringList ApiController::getProxyUrls()
|
||||
{
|
||||
QNetworkRequest request;
|
||||
request.setTransferTimeout(requestTimeoutMsecs);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
QEventLoop wait;
|
||||
QList<QSslError> sslErrors;
|
||||
QNetworkReply *reply;
|
||||
|
||||
QStringList proxyStorageUrl;
|
||||
if (m_isDevEnvironment) {
|
||||
proxyStorageUrl = QStringList { DEV_S3_ENDPOINT };
|
||||
} else {
|
||||
proxyStorageUrl = QStringList { PROD_S3_ENDPOINT };
|
||||
}
|
||||
|
||||
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
||||
|
||||
for (const auto &proxyStorageUrl : proxyStorageUrl) {
|
||||
request.setUrl(proxyStorageUrl);
|
||||
reply = amnApp->manager()->get(request);
|
||||
|
||||
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||
wait.exec();
|
||||
|
||||
if (reply->error() == QNetworkReply::NetworkError::NoError) {
|
||||
break;
|
||||
}
|
||||
reply->deleteLater();
|
||||
}
|
||||
|
||||
auto encryptedResponseBody = reply->readAll();
|
||||
reply->deleteLater();
|
||||
|
||||
EVP_PKEY *privateKey = nullptr;
|
||||
QByteArray responseBody;
|
||||
try {
|
||||
if (!m_isDevEnvironment) {
|
||||
QCryptographicHash hash(QCryptographicHash::Sha512);
|
||||
hash.addData(key);
|
||||
QByteArray hashResult = hash.result().toHex();
|
||||
|
||||
QByteArray key = QByteArray::fromHex(hashResult.left(64));
|
||||
QByteArray iv = QByteArray::fromHex(hashResult.mid(64, 32));
|
||||
|
||||
QByteArray ba = QByteArray::fromBase64(encryptedResponseBody);
|
||||
|
||||
QSimpleCrypto::QBlockCipher blockCipher;
|
||||
responseBody = blockCipher.decryptAesBlockCipher(ba, key, iv);
|
||||
} else {
|
||||
responseBody = encryptedResponseBody;
|
||||
}
|
||||
} catch (...) {
|
||||
Utils::logException();
|
||||
qCritical() << "error loading private key from environment variables or decrypting payload";
|
||||
return {};
|
||||
}
|
||||
|
||||
auto endpointsArray = QJsonDocument::fromJson(responseBody).array();
|
||||
|
||||
QStringList endpoints;
|
||||
for (const auto &endpoint : endpointsArray) {
|
||||
endpoints.push_back(endpoint.toString());
|
||||
}
|
||||
return endpoints;
|
||||
}
|
||||
|
||||
ApiController::ApiPayloadData ApiController::generateApiPayloadData(const QString &protocol)
|
||||
{
|
||||
ApiController::ApiPayloadData apiPayload;
|
||||
if (protocol == configKey::cloak) {
|
||||
apiPayload.certRequest = OpenVpnConfigurator::createCertRequest();
|
||||
} else if (protocol == configKey::awg) {
|
||||
auto connData = WireguardConfigurator::genClientKeys();
|
||||
apiPayload.wireGuardClientPubKey = connData.clientPubKey;
|
||||
apiPayload.wireGuardClientPrivKey = connData.clientPrivKey;
|
||||
}
|
||||
return apiPayload;
|
||||
}
|
||||
|
||||
QJsonObject ApiController::fillApiPayload(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData)
|
||||
{
|
||||
QJsonObject obj;
|
||||
if (protocol == configKey::cloak) {
|
||||
obj[configKey::certificate] = apiPayloadData.certRequest.request;
|
||||
} else if (protocol == configKey::awg) {
|
||||
obj[configKey::publicKey] = apiPayloadData.wireGuardClientPubKey;
|
||||
}
|
||||
|
||||
obj[configKey::osVersion] = QSysInfo::productType();
|
||||
obj[configKey::appVersion] = QString(APP_VERSION);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
void ApiController::updateServerConfigFromApi(const QString &installationUuid, const int serverIndex, QJsonObject serverConfig)
|
||||
{
|
||||
#ifdef Q_OS_IOS
|
||||
IosController::Instance()->requestInetAccess();
|
||||
QThread::msleep(10);
|
||||
#endif
|
||||
|
||||
if (serverConfig.value(config_key::configVersion).toInt()) {
|
||||
QNetworkRequest request;
|
||||
request.setTransferTimeout(requestTimeoutMsecs);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Authorization", "Api-Key " + serverConfig.value(configKey::accessToken).toString().toUtf8());
|
||||
QString endpoint = serverConfig.value(configKey::apiEdnpoint).toString();
|
||||
request.setUrl(endpoint);
|
||||
|
||||
QString protocol = serverConfig.value(configKey::protocol).toString();
|
||||
|
||||
ApiPayloadData apiPayloadData = generateApiPayloadData(protocol);
|
||||
|
||||
QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData);
|
||||
apiPayload[configKey::uuid] = installationUuid;
|
||||
|
||||
QByteArray requestBody = QJsonDocument(apiPayload).toJson();
|
||||
|
||||
QNetworkReply *reply = amnApp->manager()->post(request, requestBody);
|
||||
|
||||
QObject::connect(reply, &QNetworkReply::finished, [this, reply, protocol, apiPayloadData, serverIndex, serverConfig]() mutable {
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
auto apiResponseBody = reply->readAll();
|
||||
fillServerConfig(protocol, apiPayloadData, apiResponseBody, serverConfig);
|
||||
emit finished(serverConfig, serverIndex);
|
||||
} else {
|
||||
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
||||
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||
emit errorOccurred(ErrorCode::ApiConfigTimeoutError);
|
||||
} else {
|
||||
QString err = reply->errorString();
|
||||
qDebug() << QString::fromUtf8(reply->readAll());
|
||||
qDebug() << reply->error();
|
||||
qDebug() << err;
|
||||
qDebug() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
|
||||
emit errorOccurred(ErrorCode::ApiConfigDownloadError);
|
||||
}
|
||||
}
|
||||
|
||||
reply->deleteLater();
|
||||
});
|
||||
|
||||
QObject::connect(reply, &QNetworkReply::errorOccurred,
|
||||
[this, reply](QNetworkReply::NetworkError error) { qDebug() << reply->errorString() << error; });
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, reply](const QList<QSslError> &errors) {
|
||||
qDebug().noquote() << errors;
|
||||
emit errorOccurred(ErrorCode::ApiConfigSslError);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCode ApiController::getServicesList(QByteArray &responseBody)
|
||||
{
|
||||
#ifdef Q_OS_IOS
|
||||
IosController::Instance()->requestInetAccess();
|
||||
QThread::msleep(10);
|
||||
#endif
|
||||
|
||||
QNetworkRequest request;
|
||||
request.setTransferTimeout(requestTimeoutMsecs);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
request.setUrl(QString("%1v1/services").arg(m_gatewayEndpoint));
|
||||
|
||||
QNetworkReply *reply;
|
||||
reply = amnApp->manager()->get(request);
|
||||
|
||||
QEventLoop wait;
|
||||
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
|
||||
QList<QSslError> sslErrors;
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||
wait.exec();
|
||||
|
||||
responseBody = reply->readAll();
|
||||
|
||||
if (sslErrors.isEmpty() && shouldBypassProxy(reply, responseBody, false)) {
|
||||
m_proxyUrls = getProxyUrls();
|
||||
std::random_device randomDevice;
|
||||
std::mt19937 generator(randomDevice());
|
||||
std::shuffle(m_proxyUrls.begin(), m_proxyUrls.end(), generator);
|
||||
for (const QString &proxyUrl : m_proxyUrls) {
|
||||
qDebug() << "Go to the next endpoint";
|
||||
request.setUrl(QString("%1v1/services").arg(proxyUrl));
|
||||
reply->deleteLater(); // delete the previous reply
|
||||
reply = amnApp->manager()->get(request);
|
||||
|
||||
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||
wait.exec();
|
||||
|
||||
responseBody = reply->readAll();
|
||||
if (!sslErrors.isEmpty() || !shouldBypassProxy(reply, responseBody, false)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto errorCode = checkErrors(sslErrors, reply);
|
||||
reply->deleteLater();
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
if (!responseBody.contains("services")) {
|
||||
return ErrorCode::ApiServicesMissingError;
|
||||
}
|
||||
}
|
||||
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
ErrorCode ApiController::getConfigForService(const QString &installationUuid, const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &protocol, const QString &serverCountryCode, const QJsonObject &authData,
|
||||
QJsonObject &serverConfig)
|
||||
{
|
||||
#ifdef Q_OS_IOS
|
||||
IosController::Instance()->requestInetAccess();
|
||||
QThread::msleep(10);
|
||||
#endif
|
||||
|
||||
QNetworkRequest request;
|
||||
request.setTransferTimeout(requestTimeoutMsecs);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
request.setUrl(QString("%1v1/config").arg(m_gatewayEndpoint));
|
||||
|
||||
ApiPayloadData apiPayloadData = generateApiPayloadData(protocol);
|
||||
|
||||
QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData);
|
||||
apiPayload[configKey::userCountryCode] = userCountryCode;
|
||||
if (!serverCountryCode.isEmpty()) {
|
||||
apiPayload[configKey::serverCountryCode] = serverCountryCode;
|
||||
}
|
||||
apiPayload[configKey::serviceType] = serviceType;
|
||||
apiPayload[configKey::uuid] = installationUuid;
|
||||
if (!authData.isEmpty()) {
|
||||
apiPayload[configKey::authData] = authData;
|
||||
}
|
||||
|
||||
QSimpleCrypto::QBlockCipher blockCipher;
|
||||
QByteArray key = blockCipher.generatePrivateSalt(32);
|
||||
QByteArray iv = blockCipher.generatePrivateSalt(32);
|
||||
QByteArray salt = blockCipher.generatePrivateSalt(8);
|
||||
|
||||
QJsonObject keyPayload;
|
||||
keyPayload[configKey::aesKey] = QString(key.toBase64());
|
||||
keyPayload[configKey::aesIv] = QString(iv.toBase64());
|
||||
keyPayload[configKey::aesSalt] = QString(salt.toBase64());
|
||||
|
||||
QByteArray encryptedKeyPayload;
|
||||
QByteArray encryptedApiPayload;
|
||||
try {
|
||||
QSimpleCrypto::QRsa rsa;
|
||||
|
||||
EVP_PKEY *publicKey = nullptr;
|
||||
try {
|
||||
QByteArray rsaKey = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
||||
QSimpleCrypto::QRsa rsa;
|
||||
publicKey = rsa.getPublicKeyFromByteArray(rsaKey);
|
||||
} catch (...) {
|
||||
Utils::logException();
|
||||
qCritical() << "error loading public key from environment variables";
|
||||
return ErrorCode::ApiMissingAgwPublicKey;
|
||||
}
|
||||
|
||||
encryptedKeyPayload = rsa.encrypt(QJsonDocument(keyPayload).toJson(), publicKey, RSA_PKCS1_PADDING);
|
||||
EVP_PKEY_free(publicKey);
|
||||
|
||||
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), key, iv, "", salt);
|
||||
} catch (...) { // todo change error handling in QSimpleCrypto?
|
||||
Utils::logException();
|
||||
qCritical() << "error when encrypting the request body";
|
||||
return ErrorCode::ApiConfigDecryptionError;
|
||||
}
|
||||
|
||||
QJsonObject requestBody;
|
||||
requestBody[configKey::keyPayload] = QString(encryptedKeyPayload.toBase64());
|
||||
requestBody[configKey::apiPayload] = QString(encryptedApiPayload.toBase64());
|
||||
|
||||
QNetworkReply *reply = amnApp->manager()->post(request, QJsonDocument(requestBody).toJson());
|
||||
|
||||
QEventLoop wait;
|
||||
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
|
||||
QList<QSslError> sslErrors;
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||
wait.exec();
|
||||
|
||||
auto encryptedResponseBody = reply->readAll();
|
||||
|
||||
if (sslErrors.isEmpty() && shouldBypassProxy(reply, encryptedResponseBody, true, key, iv, salt)) {
|
||||
m_proxyUrls = getProxyUrls();
|
||||
std::random_device randomDevice;
|
||||
std::mt19937 generator(randomDevice());
|
||||
std::shuffle(m_proxyUrls.begin(), m_proxyUrls.end(), generator);
|
||||
for (const QString &proxyUrl : m_proxyUrls) {
|
||||
qDebug() << "Go to the next endpoint";
|
||||
request.setUrl(QString("%1v1/config").arg(proxyUrl));
|
||||
reply->deleteLater(); // delete the previous reply
|
||||
reply = amnApp->manager()->post(request, QJsonDocument(requestBody).toJson());
|
||||
|
||||
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||
wait.exec();
|
||||
|
||||
encryptedResponseBody = reply->readAll();
|
||||
if (!sslErrors.isEmpty() || !shouldBypassProxy(reply, encryptedResponseBody, true, key, iv, salt)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto errorCode = checkErrors(sslErrors, reply);
|
||||
reply->deleteLater();
|
||||
if (errorCode) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
try {
|
||||
auto responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt);
|
||||
fillServerConfig(protocol, apiPayloadData, responseBody, serverConfig);
|
||||
} catch (...) { // todo change error handling in QSimpleCrypto?
|
||||
Utils::logException();
|
||||
qCritical() << "error when decrypting the request body";
|
||||
return ErrorCode::ApiConfigDecryptionError;
|
||||
}
|
||||
|
||||
return errorCode;
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
#ifndef APICONTROLLER_H
|
||||
#define APICONTROLLER_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "configurators/openvpn_configurator.h"
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
#include "platforms/ios/ios_controller.h"
|
||||
#endif
|
||||
|
||||
class ApiController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ApiController(const QString &gatewayEndpoint, bool isDevEnvironment, QObject *parent = nullptr);
|
||||
|
||||
public slots:
|
||||
void updateServerConfigFromApi(const QString &installationUuid, const int serverIndex, QJsonObject serverConfig);
|
||||
|
||||
ErrorCode getServicesList(QByteArray &responseBody);
|
||||
ErrorCode getConfigForService(const QString &installationUuid, const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &protocol, const QString &serverCountryCode, const QJsonObject &authData, QJsonObject &serverConfig);
|
||||
|
||||
signals:
|
||||
void errorOccurred(ErrorCode errorCode);
|
||||
void finished(const QJsonObject &config, const int serverIndex);
|
||||
|
||||
private:
|
||||
struct ApiPayloadData
|
||||
{
|
||||
OpenVpnConfigurator::ConnectionData certRequest;
|
||||
|
||||
QString wireGuardClientPrivKey;
|
||||
QString wireGuardClientPubKey;
|
||||
};
|
||||
|
||||
ApiPayloadData generateApiPayloadData(const QString &protocol);
|
||||
QJsonObject fillApiPayload(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData);
|
||||
void fillServerConfig(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData, const QByteArray &apiResponseBody,
|
||||
QJsonObject &serverConfig);
|
||||
QStringList getProxyUrls();
|
||||
|
||||
QString m_gatewayEndpoint;
|
||||
QStringList m_proxyUrls;
|
||||
bool m_isDevEnvironment = false;
|
||||
};
|
||||
|
||||
#endif // APICONTROLLER_H
|
||||
399
client/core/controllers/coreController.cpp
Normal file
399
client/core/controllers/coreController.cpp
Normal file
|
|
@ -0,0 +1,399 @@
|
|||
#include "coreController.h"
|
||||
|
||||
#include <QDirIterator>
|
||||
#include <QTranslator>
|
||||
|
||||
#if defined(Q_OS_ANDROID)
|
||||
#include "core/installedAppsImageProvider.h"
|
||||
#include "platforms/android/android_controller.h"
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_IOS)
|
||||
#include "platforms/ios/ios_controller.h"
|
||||
#include <AmneziaVPN-Swift.h>
|
||||
#endif
|
||||
|
||||
CoreController::CoreController(const QSharedPointer<VpnConnection> &vpnConnection, const std::shared_ptr<Settings> &settings,
|
||||
QQmlApplicationEngine *engine, QObject *parent)
|
||||
: QObject(parent), m_vpnConnection(vpnConnection), m_settings(settings), m_engine(engine)
|
||||
{
|
||||
initModels();
|
||||
initControllers();
|
||||
initSignalHandlers();
|
||||
|
||||
initAndroidController();
|
||||
initAppleController();
|
||||
|
||||
initNotificationHandler();
|
||||
|
||||
auto locale = m_settings->getAppLanguage();
|
||||
m_translator.reset(new QTranslator());
|
||||
updateTranslator(locale);
|
||||
}
|
||||
|
||||
void CoreController::initModels()
|
||||
{
|
||||
m_containersModel.reset(new ContainersModel(this));
|
||||
m_engine->rootContext()->setContextProperty("ContainersModel", m_containersModel.get());
|
||||
|
||||
m_defaultServerContainersModel.reset(new ContainersModel(this));
|
||||
m_engine->rootContext()->setContextProperty("DefaultServerContainersModel", m_defaultServerContainersModel.get());
|
||||
|
||||
m_serversModel.reset(new ServersModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get());
|
||||
|
||||
m_languageModel.reset(new LanguageModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("LanguageModel", m_languageModel.get());
|
||||
|
||||
m_sitesModel.reset(new SitesModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get());
|
||||
|
||||
m_allowedDnsModel.reset(new AllowedDnsModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("AllowedDnsModel", m_allowedDnsModel.get());
|
||||
|
||||
m_appSplitTunnelingModel.reset(new AppSplitTunnelingModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("AppSplitTunnelingModel", m_appSplitTunnelingModel.get());
|
||||
|
||||
m_protocolsModel.reset(new ProtocolsModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get());
|
||||
|
||||
m_openVpnConfigModel.reset(new OpenVpnConfigModel(this));
|
||||
m_engine->rootContext()->setContextProperty("OpenVpnConfigModel", m_openVpnConfigModel.get());
|
||||
|
||||
m_shadowSocksConfigModel.reset(new ShadowSocksConfigModel(this));
|
||||
m_engine->rootContext()->setContextProperty("ShadowSocksConfigModel", m_shadowSocksConfigModel.get());
|
||||
|
||||
m_cloakConfigModel.reset(new CloakConfigModel(this));
|
||||
m_engine->rootContext()->setContextProperty("CloakConfigModel", m_cloakConfigModel.get());
|
||||
|
||||
m_wireGuardConfigModel.reset(new WireGuardConfigModel(this));
|
||||
m_engine->rootContext()->setContextProperty("WireGuardConfigModel", m_wireGuardConfigModel.get());
|
||||
|
||||
m_awgConfigModel.reset(new AwgConfigModel(this));
|
||||
m_engine->rootContext()->setContextProperty("AwgConfigModel", m_awgConfigModel.get());
|
||||
|
||||
m_xrayConfigModel.reset(new XrayConfigModel(this));
|
||||
m_engine->rootContext()->setContextProperty("XrayConfigModel", m_xrayConfigModel.get());
|
||||
|
||||
#ifdef Q_OS_WINDOWS
|
||||
m_ikev2ConfigModel.reset(new Ikev2ConfigModel(this));
|
||||
m_engine->rootContext()->setContextProperty("Ikev2ConfigModel", m_ikev2ConfigModel.get());
|
||||
#endif
|
||||
|
||||
m_sftpConfigModel.reset(new SftpConfigModel(this));
|
||||
m_engine->rootContext()->setContextProperty("SftpConfigModel", m_sftpConfigModel.get());
|
||||
|
||||
m_socks5ConfigModel.reset(new Socks5ProxyConfigModel(this));
|
||||
m_engine->rootContext()->setContextProperty("Socks5ProxyConfigModel", m_socks5ConfigModel.get());
|
||||
|
||||
m_clientManagementModel.reset(new ClientManagementModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get());
|
||||
|
||||
m_apiServicesModel.reset(new ApiServicesModel(this));
|
||||
m_engine->rootContext()->setContextProperty("ApiServicesModel", m_apiServicesModel.get());
|
||||
|
||||
m_apiCountryModel.reset(new ApiCountryModel(this));
|
||||
m_engine->rootContext()->setContextProperty("ApiCountryModel", m_apiCountryModel.get());
|
||||
|
||||
m_apiAccountInfoModel.reset(new ApiAccountInfoModel(this));
|
||||
m_engine->rootContext()->setContextProperty("ApiAccountInfoModel", m_apiAccountInfoModel.get());
|
||||
|
||||
m_apiDevicesModel.reset(new ApiDevicesModel(m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("ApiDevicesModel", m_apiDevicesModel.get());
|
||||
}
|
||||
|
||||
void CoreController::initControllers()
|
||||
{
|
||||
m_connectionController.reset(
|
||||
new ConnectionController(m_serversModel, m_containersModel, m_clientManagementModel, m_vpnConnection, m_settings));
|
||||
m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get());
|
||||
|
||||
m_pageController.reset(new PageController(m_serversModel, m_settings));
|
||||
m_engine->rootContext()->setContextProperty("PageController", m_pageController.get());
|
||||
|
||||
m_focusController.reset(new FocusController(m_engine, this));
|
||||
m_engine->rootContext()->setContextProperty("FocusController", m_focusController.get());
|
||||
|
||||
m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_clientManagementModel, m_settings));
|
||||
m_engine->rootContext()->setContextProperty("InstallController", m_installController.get());
|
||||
|
||||
connect(m_installController.get(), &InstallController::currentContainerUpdated, m_connectionController.get(),
|
||||
&ConnectionController::onCurrentContainerUpdated); // TODO remove this
|
||||
|
||||
m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings));
|
||||
m_engine->rootContext()->setContextProperty("ImportController", m_importController.get());
|
||||
|
||||
m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_clientManagementModel, m_settings));
|
||||
m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get());
|
||||
|
||||
m_settingsController.reset(
|
||||
new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, m_appSplitTunnelingModel, m_settings));
|
||||
m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get());
|
||||
|
||||
m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel));
|
||||
m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get());
|
||||
|
||||
m_allowedDnsController.reset(new AllowedDnsController(m_settings, m_allowedDnsModel));
|
||||
m_engine->rootContext()->setContextProperty("AllowedDnsController", m_allowedDnsController.get());
|
||||
|
||||
m_appSplitTunnelingController.reset(new AppSplitTunnelingController(m_settings, m_appSplitTunnelingModel));
|
||||
m_engine->rootContext()->setContextProperty("AppSplitTunnelingController", m_appSplitTunnelingController.get());
|
||||
|
||||
m_systemController.reset(new SystemController(m_settings));
|
||||
m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get());
|
||||
|
||||
m_apiSettingsController.reset(
|
||||
new ApiSettingsController(m_serversModel, m_apiAccountInfoModel, m_apiCountryModel, m_apiDevicesModel, m_settings));
|
||||
m_engine->rootContext()->setContextProperty("ApiSettingsController", m_apiSettingsController.get());
|
||||
|
||||
m_apiConfigsController.reset(new ApiConfigsController(m_serversModel, m_apiServicesModel, m_settings));
|
||||
m_engine->rootContext()->setContextProperty("ApiConfigsController", m_apiConfigsController.get());
|
||||
|
||||
m_apiPremV1MigrationController.reset(new ApiPremV1MigrationController(m_serversModel, m_settings, this));
|
||||
m_engine->rootContext()->setContextProperty("ApiPremV1MigrationController", m_apiPremV1MigrationController.get());
|
||||
}
|
||||
|
||||
void CoreController::initAndroidController()
|
||||
{
|
||||
#ifdef Q_OS_ANDROID
|
||||
if (!AndroidController::initLogging()) {
|
||||
qFatal("Android logging initialization failed");
|
||||
}
|
||||
AndroidController::instance()->setSaveLogs(m_settings->isSaveLogs());
|
||||
connect(m_settings.get(), &Settings::saveLogsChanged, AndroidController::instance(), &AndroidController::setSaveLogs);
|
||||
|
||||
AndroidController::instance()->setScreenshotsEnabled(m_settings->isScreenshotsEnabled());
|
||||
connect(m_settings.get(), &Settings::screenshotsEnabledChanged, AndroidController::instance(), &AndroidController::setScreenshotsEnabled);
|
||||
|
||||
connect(m_settings.get(), &Settings::serverRemoved, AndroidController::instance(), &AndroidController::resetLastServer);
|
||||
|
||||
connect(m_settings.get(), &Settings::settingsCleared, []() { AndroidController::instance()->resetLastServer(-1); });
|
||||
|
||||
connect(AndroidController::instance(), &AndroidController::initConnectionState, this, [this](Vpn::ConnectionState state) {
|
||||
m_connectionController->onConnectionStateChanged(state);
|
||||
if (m_vpnConnection)
|
||||
m_vpnConnection->restoreConnection();
|
||||
});
|
||||
if (!AndroidController::instance()->initialize()) {
|
||||
qFatal("Android controller initialization failed");
|
||||
}
|
||||
|
||||
connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, this, [this](QString data) {
|
||||
emit m_pageController->goToPageHome();
|
||||
m_importController->extractConfigFromData(data);
|
||||
data.clear();
|
||||
emit m_pageController->goToPageViewConfig();
|
||||
});
|
||||
|
||||
m_engine->addImageProvider(QLatin1String("installedAppImage"), new InstalledAppsImageProvider);
|
||||
#endif
|
||||
}
|
||||
|
||||
void CoreController::initAppleController()
|
||||
{
|
||||
#ifdef Q_OS_IOS
|
||||
IosController::Instance()->initialize();
|
||||
connect(IosController::Instance(), &IosController::importConfigFromOutside, this, [this](QString data) {
|
||||
emit m_pageController->goToPageHome();
|
||||
m_importController->extractConfigFromData(data);
|
||||
emit m_pageController->goToPageViewConfig();
|
||||
});
|
||||
|
||||
connect(IosController::Instance(), &IosController::importBackupFromOutside, this, [this](QString filePath) {
|
||||
emit m_pageController->goToPageHome();
|
||||
m_pageController->goToPageSettingsBackup();
|
||||
emit m_settingsController->importBackupFromOutside(filePath);
|
||||
});
|
||||
|
||||
QTimer::singleShot(0, this, [this]() { AmneziaVPN::toggleScreenshots(m_settings->isScreenshotsEnabled()); });
|
||||
|
||||
connect(m_settings.get(), &Settings::screenshotsEnabledChanged, [](bool enabled) { AmneziaVPN::toggleScreenshots(enabled); });
|
||||
#endif
|
||||
}
|
||||
|
||||
void CoreController::initSignalHandlers()
|
||||
{
|
||||
initErrorMessagesHandler();
|
||||
|
||||
initApiCountryModelUpdateHandler();
|
||||
initContainerModelUpdateHandler();
|
||||
initAdminConfigRevokedHandler();
|
||||
initPassphraseRequestHandler();
|
||||
initTranslationsUpdatedHandler();
|
||||
initAutoConnectHandler();
|
||||
initAmneziaDnsToggledHandler();
|
||||
initPrepareConfigHandler();
|
||||
initImportPremiumV2VpnKeyHandler();
|
||||
initShowMigrationDrawerHandler();
|
||||
initStrictKillSwitchHandler();
|
||||
}
|
||||
|
||||
void CoreController::initNotificationHandler()
|
||||
{
|
||||
#ifndef Q_OS_ANDROID
|
||||
m_notificationHandler.reset(NotificationHandler::create(nullptr));
|
||||
|
||||
connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(),
|
||||
&NotificationHandler::setConnectionState);
|
||||
|
||||
connect(m_notificationHandler.get(), &NotificationHandler::raiseRequested, m_pageController.get(), &PageController::raiseMainWindow);
|
||||
connect(m_notificationHandler.get(), &NotificationHandler::connectRequested, m_connectionController.get(),
|
||||
static_cast<void (ConnectionController::*)()>(&ConnectionController::openConnection));
|
||||
connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(),
|
||||
&ConnectionController::closeConnection);
|
||||
connect(this, &CoreController::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated);
|
||||
#endif
|
||||
}
|
||||
|
||||
void CoreController::updateTranslator(const QLocale &locale)
|
||||
{
|
||||
if (!m_translator->isEmpty()) {
|
||||
QCoreApplication::removeTranslator(m_translator.get());
|
||||
}
|
||||
|
||||
QStringList availableTranslations;
|
||||
QDirIterator it(":/translations", QStringList("amneziavpn_*.qm"), QDir::Files);
|
||||
while (it.hasNext()) {
|
||||
availableTranslations << it.next();
|
||||
}
|
||||
|
||||
// This code allow to load translation for the language only, without country code
|
||||
const QString lang = locale.name().split("_").first();
|
||||
const QString translationFilePrefix = QString(":/translations/amneziavpn_") + lang;
|
||||
QString strFileName = QString(":/translations/amneziavpn_%1.qm").arg(locale.name());
|
||||
for (const QString &translation : availableTranslations) {
|
||||
if (translation.contains(translationFilePrefix)) {
|
||||
strFileName = translation;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_translator->load(strFileName)) {
|
||||
if (QCoreApplication::installTranslator(m_translator.get())) {
|
||||
m_settings->setAppLanguage(locale);
|
||||
}
|
||||
} else {
|
||||
m_settings->setAppLanguage(QLocale::English);
|
||||
}
|
||||
|
||||
m_engine->retranslate();
|
||||
|
||||
emit translationsUpdated();
|
||||
}
|
||||
|
||||
void CoreController::initErrorMessagesHandler()
|
||||
{
|
||||
connect(m_connectionController.get(), &ConnectionController::connectionErrorOccurred, this, [this](ErrorCode errorCode) {
|
||||
emit m_pageController->showErrorMessage(errorCode);
|
||||
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
||||
});
|
||||
|
||||
connect(m_apiConfigsController.get(), &ApiConfigsController::errorOccurred, m_pageController.get(),
|
||||
qOverload<ErrorCode>(&PageController::showErrorMessage));
|
||||
}
|
||||
|
||||
void CoreController::setQmlRoot()
|
||||
{
|
||||
m_systemController->setQmlRoot(m_engine->rootObjects().value(0));
|
||||
}
|
||||
|
||||
void CoreController::initApiCountryModelUpdateHandler()
|
||||
{
|
||||
// TODO
|
||||
connect(m_serversModel.get(), &ServersModel::updateApiCountryModel, this, [this]() {
|
||||
m_apiCountryModel->updateModel(m_serversModel->getProcessedServerData("apiAvailableCountries").toJsonArray(),
|
||||
m_serversModel->getProcessedServerData("apiServerCountryCode").toString());
|
||||
});
|
||||
connect(m_serversModel.get(), &ServersModel::updateApiServicesModel, this,
|
||||
[this]() { m_apiServicesModel->updateModel(m_serversModel->getProcessedServerData("apiConfig").toJsonObject()); });
|
||||
}
|
||||
|
||||
void CoreController::initContainerModelUpdateHandler()
|
||||
{
|
||||
connect(m_serversModel.get(), &ServersModel::containersUpdated, m_containersModel.get(), &ContainersModel::updateModel);
|
||||
connect(m_serversModel.get(), &ServersModel::defaultServerContainersUpdated, m_defaultServerContainersModel.get(),
|
||||
&ContainersModel::updateModel);
|
||||
m_serversModel->resetModel();
|
||||
}
|
||||
|
||||
void CoreController::initAdminConfigRevokedHandler()
|
||||
{
|
||||
connect(m_clientManagementModel.get(), &ClientManagementModel::adminConfigRevoked, m_serversModel.get(),
|
||||
&ServersModel::clearCachedProfile);
|
||||
}
|
||||
|
||||
void CoreController::initPassphraseRequestHandler()
|
||||
{
|
||||
connect(m_installController.get(), &InstallController::passphraseRequestStarted, m_pageController.get(),
|
||||
&PageController::showPassphraseRequestDrawer);
|
||||
connect(m_pageController.get(), &PageController::passphraseRequestDrawerClosed, m_installController.get(),
|
||||
&InstallController::setEncryptedPassphrase);
|
||||
}
|
||||
|
||||
void CoreController::initTranslationsUpdatedHandler()
|
||||
{
|
||||
connect(m_languageModel.get(), &LanguageModel::updateTranslations, this, &CoreController::updateTranslator);
|
||||
connect(this, &CoreController::translationsUpdated, m_languageModel.get(), &LanguageModel::translationsUpdated);
|
||||
connect(this, &CoreController::translationsUpdated, m_connectionController.get(), &ConnectionController::onTranslationsUpdated);
|
||||
}
|
||||
|
||||
void CoreController::initAutoConnectHandler()
|
||||
{
|
||||
if (m_settingsController->isAutoConnectEnabled() && m_serversModel->getDefaultServerIndex() >= 0) {
|
||||
QTimer::singleShot(1000, this, [this]() { m_connectionController->openConnection(); });
|
||||
}
|
||||
}
|
||||
|
||||
void CoreController::initAmneziaDnsToggledHandler()
|
||||
{
|
||||
connect(m_settingsController.get(), &SettingsController::amneziaDnsToggled, m_serversModel.get(), &ServersModel::toggleAmneziaDns);
|
||||
}
|
||||
|
||||
void CoreController::initPrepareConfigHandler()
|
||||
{
|
||||
connect(m_connectionController.get(), &ConnectionController::prepareConfig, this, [this]() {
|
||||
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Preparing);
|
||||
|
||||
if (!m_apiConfigsController->isConfigValid()) {
|
||||
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_installController->isConfigValid()) {
|
||||
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
||||
return;
|
||||
}
|
||||
|
||||
m_connectionController->openConnection();
|
||||
});
|
||||
}
|
||||
|
||||
void CoreController::initImportPremiumV2VpnKeyHandler()
|
||||
{
|
||||
connect(m_apiPremV1MigrationController.get(), &ApiPremV1MigrationController::importPremiumV2VpnKey, this, [this](const QString &vpnKey) {
|
||||
m_importController->extractConfigFromData(vpnKey);
|
||||
m_importController->importConfig();
|
||||
|
||||
emit m_apiPremV1MigrationController->migrationFinished();
|
||||
});
|
||||
}
|
||||
|
||||
void CoreController::initShowMigrationDrawerHandler()
|
||||
{
|
||||
QTimer::singleShot(1000, this, [this]() {
|
||||
if (m_apiPremV1MigrationController->isPremV1MigrationReminderActive() && m_apiPremV1MigrationController->hasConfigsToMigration()) {
|
||||
m_apiPremV1MigrationController->showMigrationDrawer();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void CoreController::initStrictKillSwitchHandler()
|
||||
{
|
||||
connect(m_settingsController.get(), &SettingsController::strictKillSwitchEnabledChanged, m_vpnConnection.get(),
|
||||
&VpnConnection::onKillSwitchModeChanged);
|
||||
}
|
||||
|
||||
QSharedPointer<PageController> CoreController::pageController() const
|
||||
{
|
||||
return m_pageController;
|
||||
}
|
||||
145
client/core/controllers/coreController.h
Normal file
145
client/core/controllers/coreController.h
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
#ifndef CORECONTROLLER_H
|
||||
#define CORECONTROLLER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QQmlContext>
|
||||
#include <QThread>
|
||||
|
||||
#include "ui/controllers/api/apiConfigsController.h"
|
||||
#include "ui/controllers/api/apiSettingsController.h"
|
||||
#include "ui/controllers/api/apiPremV1MigrationController.h"
|
||||
#include "ui/controllers/appSplitTunnelingController.h"
|
||||
#include "ui/controllers/allowedDnsController.h"
|
||||
#include "ui/controllers/connectionController.h"
|
||||
#include "ui/controllers/exportController.h"
|
||||
#include "ui/controllers/focusController.h"
|
||||
#include "ui/controllers/importController.h"
|
||||
#include "ui/controllers/installController.h"
|
||||
#include "ui/controllers/pageController.h"
|
||||
#include "ui/controllers/settingsController.h"
|
||||
#include "ui/controllers/sitesController.h"
|
||||
#include "ui/controllers/systemController.h"
|
||||
|
||||
#include "ui/models/allowed_dns_model.h"
|
||||
#include "ui/models/containers_model.h"
|
||||
#include "ui/models/languageModel.h"
|
||||
#include "ui/models/protocols/cloakConfigModel.h"
|
||||
#ifdef Q_OS_WINDOWS
|
||||
#include "ui/models/protocols/ikev2ConfigModel.h"
|
||||
#endif
|
||||
#include "ui/models/api/apiAccountInfoModel.h"
|
||||
#include "ui/models/api/apiCountryModel.h"
|
||||
#include "ui/models/api/apiDevicesModel.h"
|
||||
#include "ui/models/api/apiServicesModel.h"
|
||||
#include "ui/models/appSplitTunnelingModel.h"
|
||||
#include "ui/models/clientManagementModel.h"
|
||||
#include "ui/models/protocols/awgConfigModel.h"
|
||||
#include "ui/models/protocols/openvpnConfigModel.h"
|
||||
#include "ui/models/protocols/shadowsocksConfigModel.h"
|
||||
#include "ui/models/protocols/wireguardConfigModel.h"
|
||||
#include "ui/models/protocols/xrayConfigModel.h"
|
||||
#include "ui/models/protocols_model.h"
|
||||
#include "ui/models/servers_model.h"
|
||||
#include "ui/models/services/sftpConfigModel.h"
|
||||
#include "ui/models/services/socks5ProxyConfigModel.h"
|
||||
#include "ui/models/sites_model.h"
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
#include "ui/notificationhandler.h"
|
||||
#endif
|
||||
|
||||
class CoreController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CoreController(const QSharedPointer<VpnConnection> &vpnConnection, const std::shared_ptr<Settings> &settings,
|
||||
QQmlApplicationEngine *engine, QObject *parent = nullptr);
|
||||
|
||||
QSharedPointer<PageController> pageController() const;
|
||||
void setQmlRoot();
|
||||
|
||||
signals:
|
||||
void translationsUpdated();
|
||||
|
||||
private:
|
||||
void initModels();
|
||||
void initControllers();
|
||||
void initAndroidController();
|
||||
void initAppleController();
|
||||
void initSignalHandlers();
|
||||
|
||||
void initNotificationHandler();
|
||||
|
||||
void updateTranslator(const QLocale &locale);
|
||||
|
||||
void initErrorMessagesHandler();
|
||||
|
||||
void initApiCountryModelUpdateHandler();
|
||||
void initContainerModelUpdateHandler();
|
||||
void initAdminConfigRevokedHandler();
|
||||
void initPassphraseRequestHandler();
|
||||
void initTranslationsUpdatedHandler();
|
||||
void initAutoConnectHandler();
|
||||
void initAmneziaDnsToggledHandler();
|
||||
void initPrepareConfigHandler();
|
||||
void initImportPremiumV2VpnKeyHandler();
|
||||
void initShowMigrationDrawerHandler();
|
||||
void initStrictKillSwitchHandler();
|
||||
|
||||
QQmlApplicationEngine *m_engine {}; // TODO use parent child system here?
|
||||
std::shared_ptr<Settings> m_settings;
|
||||
QSharedPointer<VpnConnection> m_vpnConnection;
|
||||
QSharedPointer<QTranslator> m_translator;
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
QScopedPointer<NotificationHandler> m_notificationHandler;
|
||||
#endif
|
||||
|
||||
QMetaObject::Connection m_reloadConfigErrorOccurredConnection;
|
||||
|
||||
QScopedPointer<ConnectionController> m_connectionController;
|
||||
QScopedPointer<FocusController> m_focusController;
|
||||
QSharedPointer<PageController> m_pageController; // TODO
|
||||
QScopedPointer<InstallController> m_installController;
|
||||
QScopedPointer<ImportController> m_importController;
|
||||
QScopedPointer<ExportController> m_exportController;
|
||||
QScopedPointer<SettingsController> m_settingsController;
|
||||
QScopedPointer<SitesController> m_sitesController;
|
||||
QScopedPointer<SystemController> m_systemController;
|
||||
QScopedPointer<AppSplitTunnelingController> m_appSplitTunnelingController;
|
||||
QScopedPointer<AllowedDnsController> m_allowedDnsController;
|
||||
|
||||
QScopedPointer<ApiSettingsController> m_apiSettingsController;
|
||||
QScopedPointer<ApiConfigsController> m_apiConfigsController;
|
||||
QScopedPointer<ApiPremV1MigrationController> m_apiPremV1MigrationController;
|
||||
|
||||
QSharedPointer<ContainersModel> m_containersModel;
|
||||
QSharedPointer<ContainersModel> m_defaultServerContainersModel;
|
||||
QSharedPointer<ServersModel> m_serversModel;
|
||||
QSharedPointer<LanguageModel> m_languageModel;
|
||||
QSharedPointer<ProtocolsModel> m_protocolsModel;
|
||||
QSharedPointer<SitesModel> m_sitesModel;
|
||||
QSharedPointer<AllowedDnsModel> m_allowedDnsModel;
|
||||
QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel;
|
||||
QSharedPointer<ClientManagementModel> m_clientManagementModel;
|
||||
|
||||
QSharedPointer<ApiServicesModel> m_apiServicesModel;
|
||||
QSharedPointer<ApiCountryModel> m_apiCountryModel;
|
||||
QSharedPointer<ApiAccountInfoModel> m_apiAccountInfoModel;
|
||||
QSharedPointer<ApiDevicesModel> m_apiDevicesModel;
|
||||
|
||||
QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel;
|
||||
QScopedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel;
|
||||
QScopedPointer<CloakConfigModel> m_cloakConfigModel;
|
||||
QScopedPointer<XrayConfigModel> m_xrayConfigModel;
|
||||
QScopedPointer<WireGuardConfigModel> m_wireGuardConfigModel;
|
||||
QScopedPointer<AwgConfigModel> m_awgConfigModel;
|
||||
#ifdef Q_OS_WINDOWS
|
||||
QScopedPointer<Ikev2ConfigModel> m_ikev2ConfigModel;
|
||||
#endif
|
||||
QScopedPointer<SftpConfigModel> m_sftpConfigModel;
|
||||
QScopedPointer<Socks5ProxyConfigModel> m_socks5ConfigModel;
|
||||
};
|
||||
|
||||
#endif // CORECONTROLLER_H
|
||||
364
client/core/controllers/gatewayController.cpp
Normal file
364
client/core/controllers/gatewayController.cpp
Normal file
|
|
@ -0,0 +1,364 @@
|
|||
#include "gatewayController.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <random>
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkReply>
|
||||
#include <QUrl>
|
||||
|
||||
#include "QBlockCipher.h"
|
||||
#include "QRsa.h"
|
||||
|
||||
#include "amnezia_application.h"
|
||||
#include "core/api/apiUtils.h"
|
||||
#include "core/networkUtilities.h"
|
||||
#include "utilities.h"
|
||||
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
#include "core/ipcclient.h"
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
namespace configKey
|
||||
{
|
||||
constexpr char aesKey[] = "aes_key";
|
||||
constexpr char aesIv[] = "aes_iv";
|
||||
constexpr char aesSalt[] = "aes_salt";
|
||||
|
||||
constexpr char apiPayload[] = "api_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");
|
||||
}
|
||||
|
||||
GatewayController::GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int 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)
|
||||
{
|
||||
#ifdef Q_OS_IOS
|
||||
IosController::Instance()->requestInetAccess();
|
||||
QThread::msleep(10);
|
||||
#endif
|
||||
|
||||
QNetworkRequest request;
|
||||
request.setTransferTimeout(m_requestTimeoutMsecs);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
request.setUrl(QString(endpoint).arg(m_gatewayEndpoint));
|
||||
|
||||
// bypass killSwitch exceptions for API-gateway
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
if (m_isStrictKillSwitchEnabled) {
|
||||
QString host = QUrl(request.url()).host();
|
||||
QString ip = NetworkUtilities::getIPAddress(host);
|
||||
if (!ip.isEmpty()) {
|
||||
IpcClient::Interface()->addKillSwitchAllowedRange(QStringList { ip });
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
QNetworkReply *reply;
|
||||
reply = amnApp->networkManager()->get(request);
|
||||
|
||||
QEventLoop wait;
|
||||
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
|
||||
QList<QSslError> sslErrors;
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||
wait.exec();
|
||||
|
||||
responseBody = reply->readAll();
|
||||
|
||||
if (sslErrors.isEmpty() && shouldBypassProxy(reply, responseBody, false)) {
|
||||
auto requestFunction = [&request, &responseBody](const QString &url) {
|
||||
request.setUrl(url);
|
||||
return amnApp->networkManager()->get(request);
|
||||
};
|
||||
|
||||
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
|
||||
|
||||
QNetworkRequest request;
|
||||
request.setTransferTimeout(m_requestTimeoutMsecs);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
request.setUrl(endpoint.arg(m_gatewayEndpoint));
|
||||
|
||||
// bypass killSwitch exceptions for API-gateway
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
if (m_isStrictKillSwitchEnabled) {
|
||||
QString host = QUrl(request.url()).host();
|
||||
QString ip = NetworkUtilities::getIPAddress(host);
|
||||
if (!ip.isEmpty()) {
|
||||
IpcClient::Interface()->addKillSwitchAllowedRange(QStringList { ip });
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
QSimpleCrypto::QBlockCipher blockCipher;
|
||||
QByteArray key = blockCipher.generatePrivateSalt(32);
|
||||
QByteArray iv = blockCipher.generatePrivateSalt(32);
|
||||
QByteArray salt = blockCipher.generatePrivateSalt(8);
|
||||
|
||||
QJsonObject keyPayload;
|
||||
keyPayload[configKey::aesKey] = QString(key.toBase64());
|
||||
keyPayload[configKey::aesIv] = QString(iv.toBase64());
|
||||
keyPayload[configKey::aesSalt] = QString(salt.toBase64());
|
||||
|
||||
QByteArray encryptedKeyPayload;
|
||||
QByteArray encryptedApiPayload;
|
||||
try {
|
||||
QSimpleCrypto::QRsa rsa;
|
||||
|
||||
EVP_PKEY *publicKey = nullptr;
|
||||
try {
|
||||
QByteArray rsaKey = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
||||
QSimpleCrypto::QRsa rsa;
|
||||
publicKey = rsa.getPublicKeyFromByteArray(rsaKey);
|
||||
} catch (...) {
|
||||
Utils::logException();
|
||||
qCritical() << "error loading public key from environment variables";
|
||||
return ErrorCode::ApiMissingAgwPublicKey;
|
||||
}
|
||||
|
||||
encryptedKeyPayload = rsa.encrypt(QJsonDocument(keyPayload).toJson(), publicKey, RSA_PKCS1_PADDING);
|
||||
EVP_PKEY_free(publicKey);
|
||||
|
||||
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), key, iv, "", salt);
|
||||
} catch (...) { // todo change error handling in QSimpleCrypto?
|
||||
Utils::logException();
|
||||
qCritical() << "error when encrypting the request body";
|
||||
return ErrorCode::ApiConfigDecryptionError;
|
||||
}
|
||||
|
||||
QJsonObject requestBody;
|
||||
requestBody[configKey::keyPayload] = QString(encryptedKeyPayload.toBase64());
|
||||
requestBody[configKey::apiPayload] = QString(encryptedApiPayload.toBase64());
|
||||
|
||||
QNetworkReply *reply = amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson());
|
||||
|
||||
QEventLoop wait;
|
||||
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
|
||||
QList<QSslError> sslErrors;
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||
wait.exec();
|
||||
|
||||
QByteArray encryptedResponseBody = reply->readAll();
|
||||
|
||||
if (sslErrors.isEmpty() && shouldBypassProxy(reply, encryptedResponseBody, true, key, iv, salt)) {
|
||||
auto requestFunction = [&request, &encryptedResponseBody, &requestBody](const QString &url) {
|
||||
request.setUrl(url);
|
||||
return amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson());
|
||||
};
|
||||
|
||||
auto replyProcessingFunction = [&encryptedResponseBody, &reply, &sslErrors, &key, &iv, &salt,
|
||||
this](QNetworkReply *nestedReply, const QList<QSslError> &nestedSslErrors) {
|
||||
encryptedResponseBody = nestedReply->readAll();
|
||||
reply = nestedReply;
|
||||
if (!sslErrors.isEmpty() || shouldBypassProxy(nestedReply, encryptedResponseBody, true, key, iv, salt)) {
|
||||
sslErrors = nestedSslErrors;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
bypassProxy(endpoint, reply, requestFunction, replyProcessingFunction);
|
||||
}
|
||||
|
||||
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, reply);
|
||||
reply->deleteLater();
|
||||
if (errorCode) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
try {
|
||||
responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt);
|
||||
return ErrorCode::NoError;
|
||||
} catch (...) { // todo change error handling in QSimpleCrypto?
|
||||
Utils::logException();
|
||||
qCritical() << "error when decrypting the request body";
|
||||
return ErrorCode::ApiConfigDecryptionError;
|
||||
}
|
||||
}
|
||||
|
||||
QStringList GatewayController::getProxyUrls()
|
||||
{
|
||||
QNetworkRequest request;
|
||||
request.setTransferTimeout(m_requestTimeoutMsecs);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
QEventLoop wait;
|
||||
QList<QSslError> sslErrors;
|
||||
QNetworkReply *reply;
|
||||
|
||||
QStringList proxyStorageUrls;
|
||||
if (m_isDevEnvironment) {
|
||||
proxyStorageUrls = QString(DEV_S3_ENDPOINT).split(", ");
|
||||
} else {
|
||||
proxyStorageUrls = QString(PROD_S3_ENDPOINT).split(", ");
|
||||
}
|
||||
|
||||
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
||||
|
||||
for (const auto &proxyStorageUrl : proxyStorageUrls) {
|
||||
request.setUrl(proxyStorageUrl);
|
||||
reply = amnApp->networkManager()->get(request);
|
||||
|
||||
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||
wait.exec();
|
||||
|
||||
if (reply->error() == QNetworkReply::NetworkError::NoError) {
|
||||
auto encryptedResponseBody = reply->readAll();
|
||||
reply->deleteLater();
|
||||
|
||||
EVP_PKEY *privateKey = nullptr;
|
||||
QByteArray responseBody;
|
||||
try {
|
||||
if (!m_isDevEnvironment) {
|
||||
QCryptographicHash hash(QCryptographicHash::Sha512);
|
||||
hash.addData(key);
|
||||
QByteArray hashResult = hash.result().toHex();
|
||||
|
||||
QByteArray key = QByteArray::fromHex(hashResult.left(64));
|
||||
QByteArray iv = QByteArray::fromHex(hashResult.mid(64, 32));
|
||||
|
||||
QByteArray ba = QByteArray::fromBase64(encryptedResponseBody);
|
||||
|
||||
QSimpleCrypto::QBlockCipher blockCipher;
|
||||
responseBody = blockCipher.decryptAesBlockCipher(ba, key, iv);
|
||||
} else {
|
||||
responseBody = encryptedResponseBody;
|
||||
}
|
||||
} catch (...) {
|
||||
Utils::logException();
|
||||
qCritical() << "error loading private key from environment variables or decrypting payload" << encryptedResponseBody;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto endpointsArray = QJsonDocument::fromJson(responseBody).array();
|
||||
|
||||
QStringList endpoints;
|
||||
for (const auto &endpoint : endpointsArray) {
|
||||
endpoints.push_back(endpoint.toString());
|
||||
}
|
||||
return endpoints;
|
||||
} else {
|
||||
apiUtils::checkNetworkReplyErrors(sslErrors, reply);
|
||||
qDebug() << "go to the next storage endpoint";
|
||||
|
||||
reply->deleteLater();
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool GatewayController::shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key,
|
||||
const QByteArray &iv, const QByteArray &salt)
|
||||
{
|
||||
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError || reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||
qDebug() << "timeout occurred";
|
||||
qDebug() << reply->error();
|
||||
return true;
|
||||
} else if (responseBody.contains("html")) {
|
||||
qDebug() << "the response contains an html tag";
|
||||
return true;
|
||||
} else if (reply->error() == QNetworkReply::NetworkError::ContentNotFoundError) {
|
||||
if (responseBody.contains(errorResponsePattern1) || responseBody.contains(errorResponsePattern2)
|
||||
|| responseBody.contains(errorResponsePattern3)) {
|
||||
return false;
|
||||
} else {
|
||||
qDebug() << reply->error();
|
||||
return true;
|
||||
}
|
||||
} else if (reply->error() == QNetworkReply::NetworkError::OperationNotImplementedError) {
|
||||
if (responseBody.contains(updateRequestResponsePattern)) {
|
||||
return false;
|
||||
} else {
|
||||
qDebug() << reply->error();
|
||||
return true;
|
||||
}
|
||||
} else if (reply->error() != QNetworkReply::NetworkError::NoError) {
|
||||
qDebug() << reply->error();
|
||||
return true;
|
||||
} else if (checkEncryption) {
|
||||
try {
|
||||
QSimpleCrypto::QBlockCipher blockCipher;
|
||||
static_cast<void>(blockCipher.decryptAesBlockCipher(responseBody, key, iv, "", salt));
|
||||
} catch (...) {
|
||||
qDebug() << "failed to decrypt the data";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void GatewayController::bypassProxy(const QString &endpoint, QNetworkReply *reply,
|
||||
std::function<QNetworkReply *(const QString &url)> requestFunction,
|
||||
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction)
|
||||
{
|
||||
QStringList proxyUrls = getProxyUrls();
|
||||
std::random_device randomDevice;
|
||||
std::mt19937 generator(randomDevice());
|
||||
std::shuffle(proxyUrls.begin(), proxyUrls.end(), generator);
|
||||
|
||||
QEventLoop wait;
|
||||
QList<QSslError> sslErrors;
|
||||
QByteArray responseBody;
|
||||
|
||||
for (const QString &proxyUrl : proxyUrls) {
|
||||
qDebug() << "go to the next proxy endpoint";
|
||||
reply->deleteLater(); // delete the previous reply
|
||||
reply = requestFunction(endpoint.arg(proxyUrl));
|
||||
|
||||
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
|
||||
wait.exec();
|
||||
|
||||
if (replyProcessingFunction(reply, sslErrors)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
37
client/core/controllers/gatewayController.h
Normal file
37
client/core/controllers/gatewayController.h
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#ifndef GATEWAYCONTROLLER_H
|
||||
#define GATEWAYCONTROLLER_H
|
||||
|
||||
#include <QNetworkReply>
|
||||
#include <QObject>
|
||||
|
||||
#include "core/defs.h"
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
#include "platforms/ios/ios_controller.h"
|
||||
#endif
|
||||
|
||||
class GatewayController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
|
||||
const bool isStrictKillSwitchEnabled, QObject *parent = nullptr);
|
||||
|
||||
amnezia::ErrorCode get(const QString &endpoint, QByteArray &responseBody);
|
||||
amnezia::ErrorCode post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody);
|
||||
|
||||
private:
|
||||
QStringList getProxyUrls();
|
||||
bool shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key = "",
|
||||
const QByteArray &iv = "", const QByteArray &salt = "");
|
||||
void bypassProxy(const QString &endpoint, QNetworkReply *reply, std::function<QNetworkReply *(const QString &url)> requestFunction,
|
||||
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction);
|
||||
|
||||
int m_requestTimeoutMsecs;
|
||||
QString m_gatewayEndpoint;
|
||||
bool m_isDevEnvironment = false;
|
||||
bool m_isStrictKillSwitchEnabled = false;
|
||||
};
|
||||
|
||||
#endif // GATEWAYCONTROLLER_H
|
||||
|
|
@ -138,7 +138,7 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
|
|||
|
||||
if (overwriteMode == libssh::ScpOverwriteMode::ScpOverwriteExisting) {
|
||||
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)),
|
||||
cbReadStd, cbReadStd);
|
||||
|
||||
|
|
@ -146,7 +146,7 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
|
|||
return e;
|
||||
} else if (overwriteMode == libssh::ScpOverwriteMode::ScpAppendToExisting) {
|
||||
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)),
|
||||
cbReadStd, cbReadStd);
|
||||
|
||||
|
|
@ -154,7 +154,7 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
|
|||
return e;
|
||||
|
||||
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)),
|
||||
cbReadStd, cbReadStd);
|
||||
|
||||
|
|
@ -177,7 +177,7 @@ QByteArray ServerController::getTextFileFromContainer(DockerContainer container,
|
|||
|
||||
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;
|
||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||
|
|
@ -346,8 +346,10 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
|
|||
}
|
||||
|
||||
if (container == DockerContainer::Awg) {
|
||||
if ((oldProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort)
|
||||
!= newProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort))
|
||||
if ((oldProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress)
|
||||
!= newProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress))
|
||||
|| (oldProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort)
|
||||
!= newProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort))
|
||||
|| (oldProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount)
|
||||
!= newProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount))
|
||||
|| (oldProtoConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize)
|
||||
|
|
@ -364,14 +366,21 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
|
|||
!= newProtoConfig.value(config_key::responsePacketMagicHeader).toString(protocols::awg::defaultResponsePacketMagicHeader))
|
||||
|| (oldProtoConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader)
|
||||
!= newProtoConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader))
|
||||
|| (oldProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader)
|
||||
!= newProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader)))
|
||||
|| (oldProtoConfig.value(config_key::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;
|
||||
}
|
||||
|
||||
if (container == DockerContainer::WireGuard) {
|
||||
if (oldProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort)
|
||||
!= newProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort))
|
||||
if ((oldProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress)
|
||||
!= newProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress))
|
||||
|| (oldProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort)
|
||||
!= newProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort)))
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -379,6 +388,13 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
|
|||
return true;
|
||||
}
|
||||
|
||||
if (container == DockerContainer::Xray) {
|
||||
if (oldProtoConfig.value(config_key::port).toString(protocols::xray::defaultPort)
|
||||
!= newProtoConfig.value(config_key::port).toString(protocols::xray::defaultPort)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -435,15 +451,24 @@ ErrorCode ServerController::buildContainerWorker(const ServerCredentials &creden
|
|||
stdOut += data + "\n";
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
auto cbReadStdErr = [&](const QString &data, libssh::Client &) {
|
||||
stdOut += data + "\n";
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
|
||||
errorCode =
|
||||
ErrorCode error =
|
||||
runScript(credentials,
|
||||
replaceVars(amnezia::scriptData(SharedScriptType::build_container), genVarsForScript(credentials, container, config)),
|
||||
cbReadStdOut);
|
||||
if (errorCode)
|
||||
return errorCode;
|
||||
cbReadStdOut, cbReadStdErr);
|
||||
|
||||
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)
|
||||
|
|
@ -607,6 +632,8 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
|
|||
vars.append({ { "$SFTP_PASSWORD", sftpConfig.value(config_key::password).toString() } });
|
||||
|
||||
// Amnezia wireguard vars
|
||||
vars.append({ { "$AWG_SUBNET_IP",
|
||||
amneziaWireguarConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress) } });
|
||||
vars.append({ { "$AWG_SERVER_PORT", amneziaWireguarConfig.value(config_key::port).toString(protocols::awg::defaultPort) } });
|
||||
|
||||
vars.append({ { "$JUNK_PACKET_COUNT", amneziaWireguarConfig.value(config_key::junkPacketCount).toString() } });
|
||||
|
|
@ -619,6 +646,9 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
|
|||
vars.append({ { "$UNDERLOAD_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::underloadPacketMagicHeader).toString() } });
|
||||
vars.append({ { "$TRANSPORT_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::transportPacketMagicHeader).toString() } });
|
||||
|
||||
vars.append({ { "$COOKIE_REPLY_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::cookieReplyPacketJunkSize).toString() } });
|
||||
vars.append({ { "$TRANSPORT_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::transportPacketJunkSize).toString() } });
|
||||
|
||||
// Socks5 proxy vars
|
||||
vars.append({ { "$SOCKS5_PROXY_PORT", socks5ProxyConfig.value(config_key::port).toString(protocols::socks5Proxy::defaultPort) } });
|
||||
auto username = socks5ProxyConfig.value(config_key::userName).toString();
|
||||
|
|
@ -703,7 +733,7 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential
|
|||
QString transportProto = containerConfig.value(config_key::transport_proto).toString(defaultTransportProto);
|
||||
|
||||
// TODO reimplement with netstat
|
||||
QString script = QString("which lsof &>/dev/null || 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) {
|
||||
script = script.append("|:%1").arg(port);
|
||||
}
|
||||
|
|
@ -751,10 +781,6 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential
|
|||
|
||||
ErrorCode ServerController::isUserInSudo(const ServerCredentials &credentials, DockerContainer container)
|
||||
{
|
||||
if (credentials.userName == "root") {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
QString stdOut;
|
||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||
stdOut += data + "\n";
|
||||
|
|
@ -768,8 +794,16 @@ ErrorCode ServerController::isUserInSudo(const ServerCredentials &credentials, D
|
|||
const QString scriptData = amnezia::scriptData(SharedScriptType::check_user_in_sudo);
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
|
@ -801,7 +835,7 @@ ErrorCode ServerController::isServerDpkgBusy(const ServerCredentials &credential
|
|||
|
||||
if (stdOut.contains("Packet manager not found"))
|
||||
return ErrorCode::ServerPacketManagerError;
|
||||
if (stdOut.contains("fuser not installed"))
|
||||
if (stdOut.contains("fuser not installed") || stdOut.contains("cat not installed"))
|
||||
return ErrorCode::NoError;
|
||||
|
||||
if (stdOut.isEmpty()) {
|
||||
|
|
|
|||
|
|
@ -77,8 +77,7 @@ ErrorCode VpnConfigurationsController::createProtocolConfigString(const bool isA
|
|||
}
|
||||
|
||||
QJsonObject VpnConfigurationsController::createVpnConfiguration(const QPair<QString, QString> &dns, const QJsonObject &serverConfig,
|
||||
const QJsonObject &containerConfig, const DockerContainer container,
|
||||
ErrorCode &errorCode)
|
||||
const QJsonObject &containerConfig, const DockerContainer container)
|
||||
{
|
||||
QJsonObject vpnConfiguration {};
|
||||
|
||||
|
|
@ -103,7 +102,8 @@ QJsonObject VpnConfigurationsController::createVpnConfiguration(const QPair<QStr
|
|||
if (container == DockerContainer::Awg || container == DockerContainer::WireGuard) {
|
||||
// add mtu for old configs
|
||||
if (vpnConfigData[config_key::mtu].toString().isEmpty()) {
|
||||
vpnConfigData[config_key::mtu] = container == DockerContainer::Awg ? protocols::awg::defaultMtu : protocols::wireguard::defaultMtu;
|
||||
vpnConfigData[config_key::mtu] =
|
||||
container == DockerContainer::Awg ? protocols::awg::defaultMtu : protocols::wireguard::defaultMtu;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ class VpnConfigurationsController : public QObject
|
|||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit VpnConfigurationsController(const std::shared_ptr<Settings> &settings, QSharedPointer<ServerController> serverController, QObject *parent = nullptr);
|
||||
explicit VpnConfigurationsController(const std::shared_ptr<Settings> &settings, QSharedPointer<ServerController> serverController,
|
||||
QObject *parent = nullptr);
|
||||
|
||||
public slots:
|
||||
ErrorCode createProtocolConfigForContainer(const ServerCredentials &credentials, const DockerContainer container,
|
||||
|
|
@ -21,7 +22,7 @@ public slots:
|
|||
const DockerContainer container, const QJsonObject &containerConfig, const Proto protocol,
|
||||
QString &protocolConfigString);
|
||||
QJsonObject createVpnConfiguration(const QPair<QString, QString> &dns, const QJsonObject &serverConfig,
|
||||
const QJsonObject &containerConfig, const DockerContainer container, ErrorCode &errorCode);
|
||||
const QJsonObject &containerConfig, const DockerContainer container);
|
||||
|
||||
static void updateContainerConfigAfterInstallation(const DockerContainer container, QJsonObject &containerConfig, const QString &stdOut);
|
||||
signals:
|
||||
|
|
|
|||
|
|
@ -6,9 +6,6 @@
|
|||
|
||||
namespace amnezia
|
||||
{
|
||||
|
||||
constexpr const qint16 qrMagicCode = 1984;
|
||||
|
||||
struct ServerCredentials
|
||||
{
|
||||
QString hostName;
|
||||
|
|
@ -47,6 +44,7 @@ namespace amnezia
|
|||
InternalError = 101,
|
||||
NotImplementedError = 102,
|
||||
AmneziaServiceNotRunning = 103,
|
||||
NotSupportedOnThisPlatform = 104,
|
||||
|
||||
// Server errors
|
||||
ServerCheckFailed = 200,
|
||||
|
|
@ -56,6 +54,13 @@ namespace amnezia
|
|||
ServerCancelInstallation = 204,
|
||||
ServerUserNotInSudo = 205,
|
||||
ServerPacketManagerError = 206,
|
||||
ServerSudoPackageIsNotPreinstalled = 207,
|
||||
ServerUserDirectoryNotAccessible = 208,
|
||||
ServerUserNotAllowedInSudoers = 209,
|
||||
ServerUserPasswordRequired = 210,
|
||||
ServerDockerOnCgroupsV2 = 211,
|
||||
ServerCgroupMountpoint = 212,
|
||||
DockerPullRateLimit = 213,
|
||||
|
||||
// Ssh connection errors
|
||||
SshRequestDeniedError = 300,
|
||||
|
|
@ -97,6 +102,7 @@ namespace amnezia
|
|||
// import and install errors
|
||||
ImportInvalidConfigError = 900,
|
||||
ImportOpenConfigError = 901,
|
||||
NoInstalledContainersError = 902,
|
||||
|
||||
// Android errors
|
||||
AndroidError = 1000,
|
||||
|
|
@ -110,6 +116,10 @@ namespace amnezia
|
|||
ApiMissingAgwPublicKey = 1105,
|
||||
ApiConfigDecryptionError = 1106,
|
||||
ApiServicesMissingError = 1107,
|
||||
ApiConfigLimitError = 1108,
|
||||
ApiNotFoundError = 1109,
|
||||
ApiMigrationError = 1110,
|
||||
ApiUpdateRequestError = 1111,
|
||||
|
||||
// QFile errors
|
||||
OpenError = 1200,
|
||||
|
|
|
|||
|
|
@ -1,9 +0,0 @@
|
|||
#ifndef APIENUMS_H
|
||||
#define APIENUMS_H
|
||||
|
||||
enum ApiConfigSources {
|
||||
Telegram = 1,
|
||||
AmneziaGateway
|
||||
};
|
||||
|
||||
#endif // APIENUMS_H
|
||||
|
|
@ -12,6 +12,7 @@ QString errorString(ErrorCode code) {
|
|||
case(ErrorCode::UnknownError): errorMessage = QObject::tr("Unknown error"); break;
|
||||
case(ErrorCode::NotImplementedError): errorMessage = QObject::tr("Function not implemented"); break;
|
||||
case(ErrorCode::AmneziaServiceNotRunning): errorMessage = QObject::tr("Background service is not running"); break;
|
||||
case(ErrorCode::NotSupportedOnThisPlatform): errorMessage = QObject::tr("The selected protocol is not supported on the current platform"); break;
|
||||
|
||||
// Server errors
|
||||
case(ErrorCode::ServerCheckFailed): errorMessage = QObject::tr("Server check failed"); break;
|
||||
|
|
@ -19,8 +20,15 @@ QString errorString(ErrorCode code) {
|
|||
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::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::ServerPacketManagerError): errorMessage = QObject::tr("Server error: Packet manager error"); 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: 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;
|
||||
|
||||
// Libssh errors
|
||||
case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break;
|
||||
|
|
@ -51,6 +59,7 @@ QString errorString(ErrorCode code) {
|
|||
|
||||
case (ErrorCode::ImportInvalidConfigError): errorMessage = QObject::tr("The config does not contain any containers and credentials for connecting to the server"); break;
|
||||
case (ErrorCode::ImportOpenConfigError): errorMessage = QObject::tr("Unable to open config file"); break;
|
||||
case(ErrorCode::NoInstalledContainersError): errorMessage = QObject::tr("VPN Protocols is not installed.\n Please install VPN container at first"); break;
|
||||
|
||||
// Android errors
|
||||
case (ErrorCode::AndroidError): errorMessage = QObject::tr("VPN connection error"); break;
|
||||
|
|
@ -64,6 +73,10 @@ QString errorString(ErrorCode code) {
|
|||
case (ErrorCode::ApiMissingAgwPublicKey): errorMessage = QObject::tr("Missing AGW public key"); break;
|
||||
case (ErrorCode::ApiConfigDecryptionError): errorMessage = QObject::tr("Failed to decrypt response payload"); 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::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;
|
||||
|
||||
// QFile errors
|
||||
case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;
|
||||
|
|
|
|||
|
|
@ -5,12 +5,12 @@ IpcClient *IpcClient::m_instance = nullptr;
|
|||
|
||||
IpcClient::IpcClient(QObject *parent) : QObject(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
IpcClient::~IpcClient()
|
||||
{
|
||||
if (m_localSocket) m_localSocket->close();
|
||||
if (m_localSocket)
|
||||
m_localSocket->close();
|
||||
}
|
||||
|
||||
bool IpcClient::isSocketConnected() const
|
||||
|
|
@ -25,13 +25,15 @@ IpcClient *IpcClient::Instance()
|
|||
|
||||
QSharedPointer<IpcInterfaceReplica> IpcClient::Interface()
|
||||
{
|
||||
if (!Instance()) return nullptr;
|
||||
if (!Instance())
|
||||
return nullptr;
|
||||
return Instance()->m_ipcClient;
|
||||
}
|
||||
|
||||
QSharedPointer<IpcProcessTun2SocksReplica> IpcClient::InterfaceTun2Socks()
|
||||
{
|
||||
if (!Instance()) return nullptr;
|
||||
if (!Instance())
|
||||
return nullptr;
|
||||
return Instance()->m_Tun2SocksClient;
|
||||
}
|
||||
|
||||
|
|
@ -42,15 +44,28 @@ bool IpcClient::init(IpcClient *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.reset(Instance()->m_ClientNode.acquire<IpcInterfaceReplica>());
|
||||
Instance()->m_ipcClient->waitForSource(1000);
|
||||
|
||||
if (!Instance()->m_ipcClient->isReplicaValid()) {
|
||||
qWarning() << "IpcClient replica is not connected!";
|
||||
}
|
||||
|
||||
Instance()->m_Tun2SocksClient.reset(Instance()->m_ClientNode.acquire<IpcProcessTun2SocksReplica>());
|
||||
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()) {
|
||||
|
|
@ -58,9 +73,8 @@ bool IpcClient::init(IpcClient *instance)
|
|||
}
|
||||
});
|
||||
|
||||
connect(Instance()->m_localSocket, &QLocalSocket::disconnected, [instance](){
|
||||
instance->m_isSocketConnected = false;
|
||||
});
|
||||
connect(Instance()->m_localSocket, &QLocalSocket::disconnected,
|
||||
[instance]() { instance->m_isSocketConnected = false; });
|
||||
|
||||
Instance()->m_localSocket->connectToServer(amnezia::getIpcServiceUrl());
|
||||
Instance()->m_localSocket->waitForConnected();
|
||||
|
|
@ -77,7 +91,7 @@ bool IpcClient::init(IpcClient *instance)
|
|||
|
||||
QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
|
||||
{
|
||||
if (! Instance()->m_ipcClient || ! Instance()->m_ipcClient->isReplicaValid()) {
|
||||
if (!Instance()->m_ipcClient || !Instance()->m_ipcClient->isReplicaValid()) {
|
||||
qWarning() << "IpcClient::createPrivilegedProcess : IpcClient IpcClient replica is not valid";
|
||||
return nullptr;
|
||||
}
|
||||
|
|
@ -100,18 +114,15 @@ QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
|
|||
pd->ipcProcess.reset(priv);
|
||||
if (!pd->ipcProcess) {
|
||||
qWarning() << "Acquire PrivilegedProcess failed";
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
pd->ipcProcess->waitForSource(1000);
|
||||
if (!pd->ipcProcess->isReplicaValid()) {
|
||||
qWarning() << "PrivilegedProcess replica is not connected!";
|
||||
}
|
||||
|
||||
QObject::connect(pd->ipcProcess.data(), &PrivilegedProcess::destroyed, pd->ipcProcess.data(), [pd](){
|
||||
pd->replicaNode->deleteLater();
|
||||
});
|
||||
QObject::connect(pd->ipcProcess.data(), &PrivilegedProcess::destroyed, pd->ipcProcess.data(),
|
||||
[pd]() { pd->replicaNode->deleteLater(); });
|
||||
}
|
||||
|
||||
});
|
||||
pd->localSocket->connectToServer(amnezia::getIpcProcessUrl(pid));
|
||||
pd->localSocket->waitForConnected();
|
||||
|
|
@ -119,5 +130,3 @@ QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
|
|||
auto processReplica = QSharedPointer<PrivilegedProcess>(pd->ipcProcess);
|
||||
return processReplica;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
#include <winsock.h>
|
||||
#include <QNetworkInterface>
|
||||
#include "qendian.h"
|
||||
#include <QSettings>
|
||||
#endif
|
||||
#ifdef Q_OS_LINUX
|
||||
#include <arpa/inet.h>
|
||||
|
|
@ -185,6 +186,17 @@ int NetworkUtilities::AdapterIndexTo(const QHostAddress& dst) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
bool NetworkUtilities::checkIpv6Enabled() {
|
||||
#ifdef Q_OS_WIN
|
||||
QSettings RegHLM("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Tcpip6\\Parameters",
|
||||
QSettings::NativeFormat);
|
||||
int ret = RegHLM.value("DisabledComponents", 0).toInt();
|
||||
qDebug() << "Check for Windows disabled IPv6 return " << ret;
|
||||
return (ret != 255);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
DWORD GetAdaptersAddressesWrapper(const ULONG Family,
|
||||
const ULONG Flags,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include <QRegExp>
|
||||
#include <QString>
|
||||
#include <QHostAddress>
|
||||
#include <QNetworkReply>
|
||||
|
||||
|
||||
class NetworkUtilities : public QObject
|
||||
|
|
@ -15,6 +16,7 @@ public:
|
|||
static QString getStringBetween(const QString &s, const QString &a, const QString &b);
|
||||
static bool checkIPv4Format(const QString &ip);
|
||||
static bool checkIpSubnetFormat(const QString &ip);
|
||||
static bool checkIpv6Enabled();
|
||||
static QString getGatewayAndIface();
|
||||
// Returns the Interface Index that could Route to dst
|
||||
static int AdapterIndexTo(const QHostAddress& dst);
|
||||
|
|
@ -28,9 +30,7 @@ public:
|
|||
|
||||
static QString netMaskFromIpWithSubnet(const QString ip);
|
||||
static QString ipAddressFromIpWithSubnet(const QString ip);
|
||||
|
||||
static QStringList summarizeRoutes(const QStringList &ips, const QString cidr);
|
||||
|
||||
};
|
||||
|
||||
#endif // NETWORKUTILITIES_H
|
||||
|
|
|
|||
35
client/core/qrCodeUtils.cpp
Normal file
35
client/core/qrCodeUtils.cpp
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#include "qrCodeUtils.h"
|
||||
|
||||
#include <QIODevice>
|
||||
#include <QList>
|
||||
|
||||
QList<QString> qrCodeUtils::generateQrCodeImageSeries(const QByteArray &data)
|
||||
{
|
||||
double k = 850;
|
||||
|
||||
quint8 chunksCount = std::ceil(data.size() / k);
|
||||
QList<QString> chunks;
|
||||
for (int i = 0; i < data.size(); i = i + k) {
|
||||
QByteArray chunk;
|
||||
QDataStream s(&chunk, QIODevice::WriteOnly);
|
||||
s << qrCodeUtils::qrMagicCode << chunksCount << (quint8)std::round(i / k) << data.mid(i, k);
|
||||
|
||||
QByteArray ba = chunk.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
||||
|
||||
qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(ba, qrcodegen::QrCode::Ecc::LOW);
|
||||
QString svg = QString::fromStdString(toSvgString(qr, 1));
|
||||
chunks.append(svgToBase64(svg));
|
||||
}
|
||||
|
||||
return chunks;
|
||||
}
|
||||
|
||||
QString qrCodeUtils::svgToBase64(const QString &image)
|
||||
{
|
||||
return "data:image/svg;base64," + QString::fromLatin1(image.toUtf8().toBase64().data());
|
||||
}
|
||||
|
||||
qrcodegen::QrCode qrCodeUtils::generateQrCode(const QByteArray &data)
|
||||
{
|
||||
return qrcodegen::QrCode::encodeText(data, qrcodegen::QrCode::Ecc::LOW);
|
||||
}
|
||||
17
client/core/qrCodeUtils.h
Normal file
17
client/core/qrCodeUtils.h
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#ifndef QRCODEUTILS_H
|
||||
#define QRCODEUTILS_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
#include "qrcodegen.hpp"
|
||||
|
||||
namespace qrCodeUtils
|
||||
{
|
||||
constexpr const qint16 qrMagicCode = 1984;
|
||||
|
||||
QList<QString> generateQrCodeImageSeries(const QByteArray &data);
|
||||
qrcodegen::QrCode generateQrCode(const QByteArray &data);
|
||||
QString svgToBase64(const QString &image);
|
||||
};
|
||||
|
||||
#endif // QRCODEUTILS_H
|
||||
|
|
@ -104,7 +104,7 @@ QJsonObject Deserialize(const QString &vmessStr, QString *alias, QString *errMes
|
|||
server.users.first().security = "auto";
|
||||
}
|
||||
|
||||
const static auto getQueryValue = [&query](const QString &key, const QString &defaultValue) {
|
||||
const auto getQueryValue = [&query](const QString &key, const QString &defaultValue) {
|
||||
if (query.hasQueryItem(key))
|
||||
return query.queryItemValue(key, QUrl::FullyDecoded);
|
||||
else
|
||||
|
|
|
|||
|
|
@ -114,12 +114,23 @@ bool Daemon::activate(const InterfaceConfig& config) {
|
|||
|
||||
// Bring up the wireguard interface if not already done.
|
||||
if (!wgutils()->interfaceExists()) {
|
||||
// Create the interface.
|
||||
if (!wgutils()->addInterface(config)) {
|
||||
logger.error() << "Interface creation failed.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Bring the interface up.
|
||||
if (supportIPUtils()) {
|
||||
if (!iputils()->addInterfaceIPs(config)) {
|
||||
return false;
|
||||
}
|
||||
if (!iputils()->setMTUAndUp(config)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Configure routing for excluded addresses.
|
||||
for (const QString& i : config.m_excludedAddresses) {
|
||||
addExclusionRoute(IPAddress(i));
|
||||
|
|
@ -135,20 +146,10 @@ bool Daemon::activate(const InterfaceConfig& config) {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (supportIPUtils()) {
|
||||
if (!iputils()->addInterfaceIPs(config)) {
|
||||
return false;
|
||||
}
|
||||
if (!iputils()->setMTUAndUp(config)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// set routing
|
||||
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
|
||||
if (!wgutils()->updateRoutePrefix(ip)) {
|
||||
logger.debug() << "Routing configuration failed for"
|
||||
<< logger.sensitive(ip.toString());
|
||||
logger.debug() << "Routing configuration failed for" << ip.toString();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -168,11 +169,14 @@ bool Daemon::maybeUpdateResolvers(const InterfaceConfig& config) {
|
|||
if ((config.m_hopType == InterfaceConfig::MultiHopExit) ||
|
||||
(config.m_hopType == InterfaceConfig::SingleHop)) {
|
||||
QList<QHostAddress> resolvers;
|
||||
resolvers.append(QHostAddress(config.m_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
|
||||
// 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));
|
||||
}
|
||||
|
||||
|
|
@ -278,15 +282,26 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
|
|||
config.m_serverIpv4Gateway = obj.value("serverIpv4Gateway").toString();
|
||||
config.m_serverIpv6Gateway = obj.value("serverIpv6Gateway").toString();
|
||||
|
||||
if (!obj.contains("dnsServer")) {
|
||||
config.m_dnsServer = QString();
|
||||
if (!obj.contains("primaryDnsServer")) {
|
||||
config.m_primaryDnsServer = QString();
|
||||
} else {
|
||||
QJsonValue value = obj.value("dnsServer");
|
||||
QJsonValue value = obj.value("primaryDnsServer");
|
||||
if (!value.isString()) {
|
||||
logger.error() << "dnsServer is not a string";
|
||||
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")) {
|
||||
|
|
@ -369,6 +384,9 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
|
|||
if (!parseStringList(obj, "vpnDisabledApps", config.m_vpnDisabledApps)) {
|
||||
return false;
|
||||
}
|
||||
if (!parseStringList(obj, "allowedDnsServers", config.m_allowedDnsServers)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
config.m_killSwitchEnabled = QVariant(obj.value("killSwitchOption").toString()).toBool();
|
||||
|
||||
|
|
@ -387,6 +405,13 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
|
|||
if (!obj.value("S2").isNull()) {
|
||||
config.m_responsePacketJunkSize = obj.value("S2").toString();
|
||||
}
|
||||
if (!obj.value("S3").isNull()) {
|
||||
config.m_cookieReplyPacketJunkSize = obj.value("S3").toString();
|
||||
}
|
||||
if (!obj.value("S4").isNull()) {
|
||||
config.m_transportPacketJunkSize = obj.value("S4").toString();
|
||||
}
|
||||
|
||||
if (!obj.value("H1").isNull()) {
|
||||
config.m_initPacketMagicHeader = obj.value("H1").toString();
|
||||
}
|
||||
|
|
@ -400,6 +425,34 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
|
|||
config.m_transportPacketMagicHeader = obj.value("H4").toString();
|
||||
}
|
||||
|
||||
if (!obj.value("I1").isNull()) {
|
||||
config.m_specialJunk["I1"] = obj.value("I1").toString();
|
||||
}
|
||||
if (!obj.value("I2").isNull()) {
|
||||
config.m_specialJunk["I2"] = obj.value("I2").toString();
|
||||
}
|
||||
if (!obj.value("I3").isNull()) {
|
||||
config.m_specialJunk["I3"] = obj.value("I3").toString();
|
||||
}
|
||||
if (!obj.value("I4").isNull()) {
|
||||
config.m_specialJunk["I4"] = obj.value("I4").toString();
|
||||
}
|
||||
if (!obj.value("I5").isNull()) {
|
||||
config.m_specialJunk["I5"] = obj.value("I5").toString();
|
||||
}
|
||||
if (!obj.value("J1").isNull()) {
|
||||
config.m_controlledJunk["J1"] = obj.value("J1").toString();
|
||||
}
|
||||
if (!obj.value("J2").isNull()) {
|
||||
config.m_controlledJunk["J2"] = obj.value("J2").toString();
|
||||
}
|
||||
if (!obj.value("J3").isNull()) {
|
||||
config.m_controlledJunk["J3"] = obj.value("J3").toString();
|
||||
}
|
||||
if (!obj.value("Itime").isNull()) {
|
||||
config.m_specialHandshakeTimeout = obj.value("Itime").toString();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -442,7 +495,7 @@ bool Daemon::deactivate(bool emitSignals) {
|
|||
|
||||
m_connections.clear();
|
||||
// Delete the interface
|
||||
return wgutils()->deleteInterface();
|
||||
return wgutils()->deleteInterface();
|
||||
}
|
||||
|
||||
QString Daemon::logs() {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@
|
|||
#include <QDateTime>
|
||||
#include <QTimer>
|
||||
|
||||
#include "daemon/daemonerrors.h"
|
||||
#include "daemonerrors.h"
|
||||
#include "dnsutils.h"
|
||||
#include "interfaceconfig.h"
|
||||
#include "iputils.h"
|
||||
|
|
@ -51,7 +53,7 @@ class Daemon : public QObject {
|
|||
*/
|
||||
void activationFailure();
|
||||
void disconnected();
|
||||
void backendFailure();
|
||||
void backendFailure(DaemonError reason = DaemonError::ERROR_FATAL);
|
||||
|
||||
private:
|
||||
bool maybeUpdateResolvers(const InterfaceConfig& config);
|
||||
|
|
|
|||
17
client/daemon/daemonerrors.h
Normal file
17
client/daemon/daemonerrors.h
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
enum class DaemonError : uint8_t {
|
||||
ERROR_NONE = 0u,
|
||||
ERROR_FATAL = 1u,
|
||||
ERROR_SPLIT_TUNNEL_INIT_FAILURE = 2u,
|
||||
ERROR_SPLIT_TUNNEL_START_FAILURE = 3u,
|
||||
ERROR_SPLIT_TUNNEL_EXCLUDE_FAILURE = 4u,
|
||||
|
||||
DAEMON_ERROR_MAX = 5u,
|
||||
};
|
||||
|
|
@ -159,9 +159,10 @@ void DaemonLocalServerConnection::disconnected() {
|
|||
write(obj);
|
||||
}
|
||||
|
||||
void DaemonLocalServerConnection::backendFailure() {
|
||||
void DaemonLocalServerConnection::backendFailure(DaemonError err) {
|
||||
QJsonObject obj;
|
||||
obj.insert("type", "backendFailure");
|
||||
obj.insert("errorCode", static_cast<int>(err));
|
||||
write(obj);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
#include <QObject>
|
||||
|
||||
#include "daemonerrors.h"
|
||||
|
||||
class QLocalSocket;
|
||||
|
||||
class DaemonLocalServerConnection final : public QObject {
|
||||
|
|
@ -23,7 +25,7 @@ class DaemonLocalServerConnection final : public QObject {
|
|||
|
||||
void connected(const QString& pubkey);
|
||||
void disconnected();
|
||||
void backendFailure();
|
||||
void backendFailure(DaemonError err);
|
||||
|
||||
void write(const QJsonObject& obj);
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@ QJsonObject InterfaceConfig::toJson() const {
|
|||
(m_hopType == InterfaceConfig::SingleHop)) {
|
||||
json.insert("serverIpv4Gateway", QJsonValue(m_serverIpv4Gateway));
|
||||
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;
|
||||
|
|
@ -48,6 +49,13 @@ QJsonObject InterfaceConfig::toJson() const {
|
|||
}
|
||||
json.insert("excludedAddresses", jsExcludedAddresses);
|
||||
|
||||
|
||||
QJsonArray jsAllowedDnsServers;
|
||||
for (const QString& i : m_allowedDnsServers) {
|
||||
jsAllowedDnsServers.append(QJsonValue(i));
|
||||
}
|
||||
json.insert("allowedDnsServers", jsAllowedDnsServers);
|
||||
|
||||
QJsonArray disabledApps;
|
||||
for (const QString& i : m_vpnDisabledApps) {
|
||||
disabledApps.append(QJsonValue(i));
|
||||
|
|
@ -93,11 +101,15 @@ QString InterfaceConfig::toWgConf(const QMap<QString, QString>& extra) const {
|
|||
out << "MTU = " << m_deviceMTU << "\n";
|
||||
}
|
||||
|
||||
if (!m_dnsServer.isNull()) {
|
||||
QStringList dnsServers(m_dnsServer);
|
||||
if (!m_primaryDnsServer.isNull()) {
|
||||
QStringList dnsServers;
|
||||
dnsServers.append(m_primaryDnsServer);
|
||||
if (!m_secondaryDnsServer.isNull()) {
|
||||
dnsServers.append(m_secondaryDnsServer);
|
||||
}
|
||||
// If the DNS is not the Gateway, it's a user defined DNS
|
||||
// thus, not add any other :)
|
||||
if (m_dnsServer == m_serverIpv4Gateway) {
|
||||
if (m_primaryDnsServer == m_serverIpv4Gateway) {
|
||||
dnsServers.append(m_serverIpv6Gateway);
|
||||
}
|
||||
out << "DNS = " << dnsServers.join(", ") << "\n";
|
||||
|
|
@ -118,6 +130,12 @@ QString InterfaceConfig::toWgConf(const QMap<QString, QString>& extra) const {
|
|||
if (!m_responsePacketJunkSize.isNull()) {
|
||||
out << "S2 = " << m_responsePacketJunkSize << "\n";
|
||||
}
|
||||
if (!m_cookieReplyPacketJunkSize.isNull()) {
|
||||
out << "S3 = " << m_cookieReplyPacketJunkSize << "\n";
|
||||
}
|
||||
if (!m_transportPacketJunkSize.isNull()) {
|
||||
out << "S4 = " << m_transportPacketJunkSize << "\n";
|
||||
}
|
||||
if (!m_initPacketMagicHeader.isNull()) {
|
||||
out << "H1 = " << m_initPacketMagicHeader << "\n";
|
||||
}
|
||||
|
|
@ -131,6 +149,16 @@ QString InterfaceConfig::toWgConf(const QMap<QString, QString>& extra) const {
|
|||
out << "H4 = " << m_transportPacketMagicHeader << "\n";
|
||||
}
|
||||
|
||||
for (const QString& key : m_specialJunk.keys()) {
|
||||
out << key << " = " << m_specialJunk[key] << "\n";
|
||||
}
|
||||
for (const QString& key : m_controlledJunk.keys()) {
|
||||
out << key << " = " << m_controlledJunk[key] << "\n";
|
||||
}
|
||||
if (!m_specialHandshakeTimeout.isNull()) {
|
||||
out << "Itime = " << m_specialHandshakeTimeout << "\n";
|
||||
}
|
||||
|
||||
// If any extra config was provided, append it now.
|
||||
for (const QString& key : extra.keys()) {
|
||||
out << key << " = " << extra[key] << "\n";
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
#define INTERFACECONFIG_H
|
||||
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
|
||||
#include "ipaddress.h"
|
||||
|
|
@ -31,12 +32,14 @@ class InterfaceConfig {
|
|||
QString m_serverIpv4AddrIn;
|
||||
QString m_serverPskKey;
|
||||
QString m_serverIpv6AddrIn;
|
||||
QString m_dnsServer;
|
||||
QString m_primaryDnsServer;
|
||||
QString m_secondaryDnsServer;
|
||||
int m_serverPort = 0;
|
||||
int m_deviceMTU = 1420;
|
||||
QList<IPAddress> m_allowedIPAddressRanges;
|
||||
QStringList m_excludedAddresses;
|
||||
QStringList m_vpnDisabledApps;
|
||||
QStringList m_allowedDnsServers;
|
||||
bool m_killSwitchEnabled;
|
||||
#if defined(MZ_ANDROID) || defined(MZ_IOS)
|
||||
QString m_installationId;
|
||||
|
|
@ -47,10 +50,15 @@ class InterfaceConfig {
|
|||
QString m_junkPacketMaxSize;
|
||||
QString m_initPacketJunkSize;
|
||||
QString m_responsePacketJunkSize;
|
||||
QString m_cookieReplyPacketJunkSize;
|
||||
QString m_transportPacketJunkSize;
|
||||
QString m_initPacketMagicHeader;
|
||||
QString m_responsePacketMagicHeader;
|
||||
QString m_underloadPacketMagicHeader;
|
||||
QString m_transportPacketMagicHeader;
|
||||
QMap<QString, QString> m_specialJunk;
|
||||
QMap<QString, QString> m_controlledJunk;
|
||||
QString m_specialHandshakeTimeout;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
QString toWgConf(
|
||||
|
|
|
|||
|
|
@ -45,9 +45,11 @@ class WireguardUtils : public QObject {
|
|||
|
||||
virtual bool updateRoutePrefix(const IPAddress& prefix) = 0;
|
||||
virtual bool deleteRoutePrefix(const IPAddress& prefix) = 0;
|
||||
|
||||
|
||||
virtual bool addExclusionRoute(const IPAddress& prefix) = 0;
|
||||
virtual bool deleteExclusionRoute(const IPAddress& prefix) = 0;
|
||||
|
||||
virtual bool excludeLocalNetworks(const QList<IPAddress>& addresses) = 0;
|
||||
};
|
||||
|
||||
#endif // WIREGUARDUTILS_H
|
||||
|
|
|
|||
5
client/images/controls/external-link.svg
Normal file
5
client/images/controls/external-link.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M18 13V19C18 19.5304 17.7893 20.0391 17.4142 20.4142C17.0391 20.7893 16.5304 21 16 21H5C4.46957 21 3.96086 20.7893 3.58579 20.4142C3.21071 20.0391 3 19.5304 3 19V8C3 7.46957 3.21071 6.96086 3.58579 6.58579C3.96086 6.21071 4.46957 6 5 6H11" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M15 3H21V9" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M10 14L21 3" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 644 B |
5
client/images/controls/monitor.svg
Normal file
5
client/images/controls/monitor.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M20 3H4C2.89543 3 2 3.89543 2 5V15C2 16.1046 2.89543 17 4 17H20C21.1046 17 22 16.1046 22 15V5C22 3.89543 21.1046 3 20 3Z" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M8 21H16" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M12 17V21" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 522 B |
|
|
@ -26,15 +26,22 @@ set_target_properties(networkextension PROPERTIES
|
|||
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2"
|
||||
|
||||
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../../Frameworks"
|
||||
|
||||
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 "match AppStore org.amnezia.AmneziaVPN.network-extension"
|
||||
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "match Development org.amnezia.AmneziaVPN.network-extension"
|
||||
)
|
||||
|
||||
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
|
||||
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
|
||||
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES"
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
XCODEBUILD="/usr/bin/xcodebuild"
|
||||
WORKINGDIR=`pwd`
|
||||
PATCH="/usr/bin/patch"
|
||||
|
||||
cat $WORKINGDIR/3rd/OpenVPNAdapter/Configuration/Project.xcconfig > $WORKINGDIR/3rd/OpenVPNAdapter/Configuration/amnezia.xcconfig
|
||||
cat << EOF >> $WORKINGDIR/3rd/OpenVPNAdapter/Configuration/amnezia.xcconfig
|
||||
PROJECT_TEMP_DIR = $WORKINGDIR/3rd/OpenVPNAdapter/build/OpenVPNAdapter.build
|
||||
CONFIGURATION_BUILD_DIR = $WORKINGDIR/3rd/OpenVPNAdapter/build/Release-iphoneos
|
||||
BUILT_PRODUCTS_DIR = $WORKINGDIR/3rd/OpenVPNAdapter/build/Release-iphoneos
|
||||
EOF
|
||||
|
||||
|
||||
cd 3rd/OpenVPNAdapter
|
||||
if $XCODEBUILD -scheme OpenVPNAdapter -configuration Release -xcconfig Configuration/amnezia.xcconfig -sdk iphoneos -destination 'generic/platform=iOS' -project OpenVPNAdapter.xcodeproj ; then
|
||||
echo "OpenVPNAdapter built successfully"
|
||||
else
|
||||
echo "OpenVPNAdapter build failed"
|
||||
fi
|
||||
cd ../../
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "protocols/protocols_defs.h"
|
||||
#include "localsocketcontroller.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QHostAddress>
|
||||
|
|
@ -17,6 +18,9 @@
|
|||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
#include "models/server.h"
|
||||
#include "daemon/daemonerrors.h"
|
||||
|
||||
#include "protocols/protocols_defs.h"
|
||||
|
||||
// How many times do we try to reconnect.
|
||||
constexpr int MAX_CONNECTION_RETRY = 10;
|
||||
|
|
@ -34,7 +38,7 @@ LocalSocketController::LocalSocketController() {
|
|||
m_socket = new QLocalSocket(this);
|
||||
connect(m_socket, &QLocalSocket::connected, this,
|
||||
&LocalSocketController::daemonConnected);
|
||||
connect(m_socket, &QLocalSocket::disconnected, this,
|
||||
connect(m_socket, &QLocalSocket::disconnected, this,
|
||||
[&] { errorOccurred(QLocalSocket::PeerClosedError); });
|
||||
connect(m_socket, &QLocalSocket::errorOccurred, this,
|
||||
&LocalSocketController::errorOccurred);
|
||||
|
|
@ -119,6 +123,7 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
|||
|
||||
int appSplitTunnelType = rawConfig.value(amnezia::config_key::appSplitTunnelType).toInt();
|
||||
QJsonArray splitTunnelApps = rawConfig.value(amnezia::config_key::splitTunnelApps).toArray();
|
||||
QJsonArray allowedDns = rawConfig.value(amnezia::config_key::allowedDnsServers).toArray();
|
||||
|
||||
QJsonObject wgConfig = rawConfig.value(protocolName + "_config_data").toObject();
|
||||
|
||||
|
|
@ -130,7 +135,7 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
|||
|
||||
// set up IPv6 unique-local-address, ULA, with "fd00::/8" prefix, not globally routable.
|
||||
// this will be default IPv6 gateway, OS recognizes that IPv6 link is local and switches to IPv4.
|
||||
// Otherwise some OSes (Linux) try IPv6 forever and hang.
|
||||
// Otherwise some OSes (Linux) try IPv6 forever and hang.
|
||||
// https://en.wikipedia.org/wiki/Unique_local_address (RFC 4193)
|
||||
// https://man7.org/linux/man-pages/man5/gai.conf.5.html
|
||||
json.insert("deviceIpv6Address", "fd58:baa6:dead::1"); // simply "dead::1" is globally-routable, don't use it
|
||||
|
|
@ -144,7 +149,14 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
|||
json.insert("serverPort", wgConfig.value(amnezia::config_key::port).toInt());
|
||||
json.insert("serverIpv4Gateway", wgConfig.value(amnezia::config_key::hostName));
|
||||
// json.insert("serverIpv6Gateway", QJsonValue(hop.m_server.ipv6Gateway()));
|
||||
json.insert("dnsServer", rawConfig.value(amnezia::config_key::dns1));
|
||||
|
||||
json.insert("primaryDnsServer", rawConfig.value(amnezia::config_key::dns1));
|
||||
|
||||
// We don't use secondary DNS if primary DNS is AmneziaDNS
|
||||
if (!rawConfig.value(amnezia::config_key::dns1).toString().
|
||||
contains(amnezia::protocols::dns::amneziaDnsIp)) {
|
||||
json.insert("secondaryDnsServer", rawConfig.value(amnezia::config_key::dns2));
|
||||
}
|
||||
|
||||
QJsonArray jsAllowedIPAddesses;
|
||||
|
||||
|
|
@ -222,6 +234,8 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
|||
|
||||
json.insert("vpnDisabledApps", splitTunnelApps);
|
||||
|
||||
json.insert("allowedDnsServers", allowedDns);
|
||||
|
||||
json.insert(amnezia::config_key::killSwitchOption, rawConfig.value(amnezia::config_key::killSwitchOption));
|
||||
|
||||
if (protocolName == amnezia::config_key::awg) {
|
||||
|
|
@ -230,28 +244,61 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
|||
json.insert(amnezia::config_key::junkPacketMaxSize, wgConfig.value(amnezia::config_key::junkPacketMaxSize));
|
||||
json.insert(amnezia::config_key::initPacketJunkSize, wgConfig.value(amnezia::config_key::initPacketJunkSize));
|
||||
json.insert(amnezia::config_key::responsePacketJunkSize, wgConfig.value(amnezia::config_key::responsePacketJunkSize));
|
||||
json.insert(amnezia::config_key::cookieReplyPacketJunkSize, wgConfig.value(amnezia::config_key::cookieReplyPacketJunkSize));
|
||||
json.insert(amnezia::config_key::transportPacketJunkSize, wgConfig.value(amnezia::config_key::transportPacketJunkSize));
|
||||
json.insert(amnezia::config_key::initPacketMagicHeader, wgConfig.value(amnezia::config_key::initPacketMagicHeader));
|
||||
json.insert(amnezia::config_key::responsePacketMagicHeader, wgConfig.value(amnezia::config_key::responsePacketMagicHeader));
|
||||
json.insert(amnezia::config_key::underloadPacketMagicHeader, wgConfig.value(amnezia::config_key::underloadPacketMagicHeader));
|
||||
json.insert(amnezia::config_key::transportPacketMagicHeader, wgConfig.value(amnezia::config_key::transportPacketMagicHeader));
|
||||
json.insert(amnezia::config_key::specialJunk1, wgConfig.value(amnezia::config_key::specialJunk1));
|
||||
json.insert(amnezia::config_key::specialJunk2, wgConfig.value(amnezia::config_key::specialJunk2));
|
||||
json.insert(amnezia::config_key::specialJunk3, wgConfig.value(amnezia::config_key::specialJunk3));
|
||||
json.insert(amnezia::config_key::specialJunk4, wgConfig.value(amnezia::config_key::specialJunk4));
|
||||
json.insert(amnezia::config_key::specialJunk5, wgConfig.value(amnezia::config_key::specialJunk5));
|
||||
json.insert(amnezia::config_key::controlledJunk1, wgConfig.value(amnezia::config_key::controlledJunk1));
|
||||
json.insert(amnezia::config_key::controlledJunk2, wgConfig.value(amnezia::config_key::controlledJunk2));
|
||||
json.insert(amnezia::config_key::controlledJunk3, wgConfig.value(amnezia::config_key::controlledJunk3));
|
||||
json.insert(amnezia::config_key::specialHandshakeTimeout, wgConfig.value(amnezia::config_key::specialHandshakeTimeout));
|
||||
} else if (!wgConfig.value(amnezia::config_key::junkPacketCount).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::junkPacketMinSize).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::junkPacketMaxSize).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::initPacketJunkSize).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::responsePacketJunkSize).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::cookieReplyPacketJunkSize).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::transportPacketJunkSize).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::initPacketMagicHeader).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::responsePacketMagicHeader).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::underloadPacketMagicHeader).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::transportPacketMagicHeader).isUndefined()) {
|
||||
&& !wgConfig.value(amnezia::config_key::transportPacketMagicHeader).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::specialJunk1).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::specialJunk2).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::specialJunk3).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::specialJunk4).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::specialJunk5).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::controlledJunk1).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::controlledJunk2).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::controlledJunk3).isUndefined()
|
||||
&& !wgConfig.value(amnezia::config_key::specialHandshakeTimeout).isUndefined()) {
|
||||
json.insert(amnezia::config_key::junkPacketCount, wgConfig.value(amnezia::config_key::junkPacketCount));
|
||||
json.insert(amnezia::config_key::junkPacketMinSize, wgConfig.value(amnezia::config_key::junkPacketMinSize));
|
||||
json.insert(amnezia::config_key::junkPacketMaxSize, wgConfig.value(amnezia::config_key::junkPacketMaxSize));
|
||||
json.insert(amnezia::config_key::initPacketJunkSize, wgConfig.value(amnezia::config_key::initPacketJunkSize));
|
||||
json.insert(amnezia::config_key::responsePacketJunkSize, wgConfig.value(amnezia::config_key::responsePacketJunkSize));
|
||||
json.insert(amnezia::config_key::cookieReplyPacketJunkSize, wgConfig.value(amnezia::config_key::cookieReplyPacketJunkSize));
|
||||
json.insert(amnezia::config_key::transportPacketJunkSize, wgConfig.value(amnezia::config_key::transportPacketJunkSize));
|
||||
json.insert(amnezia::config_key::initPacketMagicHeader, wgConfig.value(amnezia::config_key::initPacketMagicHeader));
|
||||
json.insert(amnezia::config_key::responsePacketMagicHeader, wgConfig.value(amnezia::config_key::responsePacketMagicHeader));
|
||||
json.insert(amnezia::config_key::underloadPacketMagicHeader, wgConfig.value(amnezia::config_key::underloadPacketMagicHeader));
|
||||
json.insert(amnezia::config_key::transportPacketMagicHeader, wgConfig.value(amnezia::config_key::transportPacketMagicHeader));
|
||||
json.insert(amnezia::config_key::specialJunk1, wgConfig.value(amnezia::config_key::specialJunk1));
|
||||
json.insert(amnezia::config_key::specialJunk2, wgConfig.value(amnezia::config_key::specialJunk2));
|
||||
json.insert(amnezia::config_key::specialJunk3, wgConfig.value(amnezia::config_key::specialJunk3));
|
||||
json.insert(amnezia::config_key::specialJunk4, wgConfig.value(amnezia::config_key::specialJunk4));
|
||||
json.insert(amnezia::config_key::specialJunk5, wgConfig.value(amnezia::config_key::specialJunk5));
|
||||
json.insert(amnezia::config_key::controlledJunk1, wgConfig.value(amnezia::config_key::controlledJunk1));
|
||||
json.insert(amnezia::config_key::controlledJunk2, wgConfig.value(amnezia::config_key::controlledJunk2));
|
||||
json.insert(amnezia::config_key::controlledJunk3, wgConfig.value(amnezia::config_key::controlledJunk3));
|
||||
json.insert(amnezia::config_key::specialHandshakeTimeout, wgConfig.value(amnezia::config_key::specialHandshakeTimeout));
|
||||
}
|
||||
|
||||
write(json);
|
||||
|
|
@ -451,8 +498,39 @@ void LocalSocketController::parseCommand(const QByteArray& command) {
|
|||
}
|
||||
|
||||
if (type == "backendFailure") {
|
||||
qCritical() << "backendFailure";
|
||||
return;
|
||||
if (!obj.contains("errorCode")) {
|
||||
// report a generic error if we dont know what it is.
|
||||
logger.error() << "generic backend failure error";
|
||||
// REPORTERROR(ErrorHandler::ControllerError, "controller");
|
||||
return;
|
||||
}
|
||||
auto errorCode = static_cast<uint8_t>(obj["errorCode"].toInt());
|
||||
if (errorCode >= (uint8_t)DaemonError::DAEMON_ERROR_MAX) {
|
||||
// Also report a generic error if the code is invalid.
|
||||
logger.error() << "invalid backend failure error code";
|
||||
// REPORTERROR(ErrorHandler::ControllerError, "controller");
|
||||
return;
|
||||
}
|
||||
switch (static_cast<DaemonError>(errorCode)) {
|
||||
case DaemonError::ERROR_NONE:
|
||||
[[fallthrough]];
|
||||
case DaemonError::ERROR_FATAL:
|
||||
logger.error() << "generic backend failure error (fatal or error none)";
|
||||
// REPORTERROR(ErrorHandler::ControllerError, "controller");
|
||||
break;
|
||||
case DaemonError::ERROR_SPLIT_TUNNEL_INIT_FAILURE:
|
||||
[[fallthrough]];
|
||||
case DaemonError::ERROR_SPLIT_TUNNEL_START_FAILURE:
|
||||
[[fallthrough]];
|
||||
case DaemonError::ERROR_SPLIT_TUNNEL_EXCLUDE_FAILURE:
|
||||
logger.error() << "split tunnel backend failure error";
|
||||
//REPORTERROR(ErrorHandler::SplitTunnelError, "controller");
|
||||
break;
|
||||
case DaemonError::DAEMON_ERROR_MAX:
|
||||
// We should not get here.
|
||||
Q_ASSERT(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (type == "logs") {
|
||||
|
|
|
|||
|
|
@ -163,9 +163,7 @@ QString AndroidController::openFile(const QString &filter)
|
|||
QString fileName;
|
||||
connect(this, &AndroidController::fileOpened, this,
|
||||
[&fileName, &wait](const QString &uri) {
|
||||
qDebug() << "Android event: file opened; uri:" << uri;
|
||||
fileName = QQmlFile::urlToLocalFileOrQrc(uri);
|
||||
qDebug() << "Android opened filename:" << fileName;
|
||||
fileName = uri;
|
||||
wait.quit();
|
||||
},
|
||||
static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::SingleShotConnection));
|
||||
|
|
@ -175,6 +173,25 @@ QString AndroidController::openFile(const QString &filter)
|
|||
return fileName;
|
||||
}
|
||||
|
||||
int AndroidController::getFd(const QString &fileName)
|
||||
{
|
||||
return callActivityMethod<jint>("getFd", "(Ljava/lang/String;)I",
|
||||
QJniObject::fromString(fileName).object<jstring>());
|
||||
}
|
||||
|
||||
void AndroidController::closeFd()
|
||||
{
|
||||
callActivityMethod("closeFd", "()V");
|
||||
}
|
||||
|
||||
QString AndroidController::getFileName(const QString &uri)
|
||||
{
|
||||
auto fileName = callActivityMethod<jstring, jstring>("getFileName", "(Ljava/lang/String;)Ljava/lang/String;",
|
||||
QJniObject::fromString(uri).object<jstring>());
|
||||
QJniEnvironment env;
|
||||
return AndroidUtils::convertJString(env.jniEnv(), fileName.object<jstring>());
|
||||
}
|
||||
|
||||
bool AndroidController::isCameraPresent()
|
||||
{
|
||||
return callActivityMethod<jboolean>("isCameraPresent", "()Z");
|
||||
|
|
@ -287,6 +304,11 @@ bool AndroidController::requestAuthentication()
|
|||
return result;
|
||||
}
|
||||
|
||||
void AndroidController::sendTouch(float x, float y)
|
||||
{
|
||||
callActivityMethod("sendTouch", "(FF)V", x, y);
|
||||
}
|
||||
|
||||
// Moving log processing to the Android side
|
||||
jclass AndroidController::log;
|
||||
jmethodID AndroidController::logDebug;
|
||||
|
|
|
|||
|
|
@ -34,6 +34,9 @@ public:
|
|||
void resetLastServer(int serverIndex);
|
||||
void saveFile(const QString &fileName, const QString &data);
|
||||
QString openFile(const QString &filter);
|
||||
int getFd(const QString &fileName);
|
||||
void closeFd();
|
||||
QString getFileName(const QString &uri);
|
||||
bool isCameraPresent();
|
||||
bool isOnTv();
|
||||
void startQrReaderActivity();
|
||||
|
|
@ -48,6 +51,7 @@ public:
|
|||
bool isNotificationPermissionGranted();
|
||||
void requestNotificationPermission();
|
||||
bool requestAuthentication();
|
||||
void sendTouch(float x, float y);
|
||||
|
||||
static bool initLogging();
|
||||
static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import HevSocks5Tunnel
|
||||
import NetworkExtension
|
||||
|
||||
public enum Socks5Tunnel {
|
||||
|
||||
|
|
|
|||
|
|
@ -14,10 +14,15 @@ extension UIApplication {
|
|||
var keyWindows: [UIWindow] {
|
||||
connectedScenes
|
||||
.compactMap {
|
||||
guard let windowScene = $0 as? UIWindowScene else { return nil }
|
||||
if #available(iOS 15.0, *) {
|
||||
($0 as? UIWindowScene)?.keyWindow
|
||||
guard let keywindow = windowScene.keyWindow else {
|
||||
windowScene.windows.first?.makeKey()
|
||||
return windowScene.windows.first
|
||||
}
|
||||
return keywindow
|
||||
} else {
|
||||
($0 as? UIWindowScene)?.windows.first { $0.isKeyWindow }
|
||||
return windowScene.windows.first { $0.isKeyWindow }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,10 @@ struct WGConfig: Decodable {
|
|||
let initPacketMagicHeader, responsePacketMagicHeader: String?
|
||||
let underloadPacketMagicHeader, transportPacketMagicHeader: String?
|
||||
let junkPacketCount, junkPacketMinSize, junkPacketMaxSize: String?
|
||||
let initPacketJunkSize, responsePacketJunkSize: String?
|
||||
let initPacketJunkSize, responsePacketJunkSize, cookieReplyPacketJunkSize, transportPacketJunkSize: String?
|
||||
let specialJunk1, specialJunk2, specialJunk3, specialJunk4, specialJunk5: String?
|
||||
let controlledJunk1, controlledJunk2, controlledJunk3: String?
|
||||
let specialHandshakeTimeout: String?
|
||||
let dns1: String
|
||||
let dns2: String
|
||||
let mtu: String
|
||||
|
|
@ -23,7 +26,10 @@ struct WGConfig: Decodable {
|
|||
case initPacketMagicHeader = "H1", responsePacketMagicHeader = "H2"
|
||||
case underloadPacketMagicHeader = "H3", transportPacketMagicHeader = "H4"
|
||||
case junkPacketCount = "Jc", junkPacketMinSize = "Jmin", junkPacketMaxSize = "Jmax"
|
||||
case initPacketJunkSize = "S1", responsePacketJunkSize = "S2"
|
||||
case initPacketJunkSize = "S1", responsePacketJunkSize = "S2", cookieReplyPacketJunkSize = "S3", transportPacketJunkSize = "S4"
|
||||
case specialJunk1 = "I1", specialJunk2 = "I2", specialJunk3 = "I3", specialJunk4 = "I4", specialJunk5 = "I5"
|
||||
case controlledJunk1 = "J1", controlledJunk2 = "J2", controlledJunk3 = "J3"
|
||||
case specialHandshakeTimeout = "Itime"
|
||||
case dns1
|
||||
case dns2
|
||||
case mtu
|
||||
|
|
@ -40,19 +46,59 @@ struct WGConfig: Decodable {
|
|||
}
|
||||
|
||||
var settings: String {
|
||||
junkPacketCount == nil ? "" :
|
||||
"""
|
||||
Jc = \(junkPacketCount!)
|
||||
Jmin = \(junkPacketMinSize!)
|
||||
Jmax = \(junkPacketMaxSize!)
|
||||
S1 = \(initPacketJunkSize!)
|
||||
S2 = \(responsePacketJunkSize!)
|
||||
H1 = \(initPacketMagicHeader!)
|
||||
H2 = \(responsePacketMagicHeader!)
|
||||
H3 = \(underloadPacketMagicHeader!)
|
||||
H4 = \(transportPacketMagicHeader!)
|
||||
guard junkPacketCount != nil else { return "" }
|
||||
|
||||
var settingsLines: [String] = []
|
||||
|
||||
// Required parameters when junkPacketCount is present
|
||||
settingsLines.append("Jc = \(junkPacketCount!)")
|
||||
settingsLines.append("Jmin = \(junkPacketMinSize!)")
|
||||
settingsLines.append("Jmax = \(junkPacketMaxSize!)")
|
||||
settingsLines.append("S1 = \(initPacketJunkSize!)")
|
||||
settingsLines.append("S2 = \(responsePacketJunkSize!)")
|
||||
|
||||
settingsLines.append("H1 = \(initPacketMagicHeader!)")
|
||||
settingsLines.append("H2 = \(responsePacketMagicHeader!)")
|
||||
settingsLines.append("H3 = \(underloadPacketMagicHeader!)")
|
||||
settingsLines.append("H4 = \(transportPacketMagicHeader!)")
|
||||
|
||||
"""
|
||||
// Optional parameters - only add if not nil and not empty
|
||||
if let s3 = cookieReplyPacketJunkSize, !s3.isEmpty {
|
||||
settingsLines.append("S3 = \(s3)")
|
||||
}
|
||||
if let s4 = transportPacketJunkSize, !s4.isEmpty {
|
||||
settingsLines.append("S4 = \(s4)")
|
||||
}
|
||||
|
||||
if let i1 = specialJunk1, !i1.isEmpty {
|
||||
settingsLines.append("I1 = \(i1)")
|
||||
}
|
||||
if let i2 = specialJunk2, !i2.isEmpty {
|
||||
settingsLines.append("I2 = \(i2)")
|
||||
}
|
||||
if let i3 = specialJunk3, !i3.isEmpty {
|
||||
settingsLines.append("I3 = \(i3)")
|
||||
}
|
||||
if let i4 = specialJunk4, !i4.isEmpty {
|
||||
settingsLines.append("I4 = \(i4)")
|
||||
}
|
||||
if let i5 = specialJunk5, !i5.isEmpty {
|
||||
settingsLines.append("I5 = \(i5)")
|
||||
}
|
||||
if let j1 = controlledJunk1, !j1.isEmpty {
|
||||
settingsLines.append("J1 = \(j1)")
|
||||
}
|
||||
if let j2 = controlledJunk2, !j2.isEmpty {
|
||||
settingsLines.append("J2 = \(j2)")
|
||||
}
|
||||
if let j3 = controlledJunk3, !j3.isEmpty {
|
||||
settingsLines.append("J3 = \(j3)")
|
||||
}
|
||||
if let itime = specialHandshakeTimeout, !itime.isEmpty {
|
||||
settingsLines.append("Itime = \(itime)")
|
||||
}
|
||||
|
||||
return settingsLines.joined(separator: "\n")
|
||||
}
|
||||
|
||||
var str: String {
|
||||
|
|
|
|||
|
|
@ -507,6 +507,8 @@ bool IosController::setupWireGuard()
|
|||
|
||||
wgConfig.insert(config_key::initPacketJunkSize, config[config_key::initPacketJunkSize]);
|
||||
wgConfig.insert(config_key::responsePacketJunkSize, config[config_key::responsePacketJunkSize]);
|
||||
wgConfig.insert(config_key::cookieReplyPacketJunkSize, config[config_key::cookieReplyPacketJunkSize]);
|
||||
wgConfig.insert(config_key::transportPacketJunkSize, config[config_key::transportPacketJunkSize]);
|
||||
|
||||
wgConfig.insert(config_key::junkPacketCount, config[config_key::junkPacketCount]);
|
||||
wgConfig.insert(config_key::junkPacketMinSize, config[config_key::junkPacketMinSize]);
|
||||
|
|
@ -605,11 +607,23 @@ bool IosController::setupAwg()
|
|||
|
||||
wgConfig.insert(config_key::initPacketJunkSize, config[config_key::initPacketJunkSize]);
|
||||
wgConfig.insert(config_key::responsePacketJunkSize, config[config_key::responsePacketJunkSize]);
|
||||
wgConfig.insert(config_key::cookieReplyPacketJunkSize, config[config_key::cookieReplyPacketJunkSize]);
|
||||
wgConfig.insert(config_key::transportPacketJunkSize, config[config_key::transportPacketJunkSize]);
|
||||
|
||||
wgConfig.insert(config_key::junkPacketCount, config[config_key::junkPacketCount]);
|
||||
wgConfig.insert(config_key::junkPacketMinSize, config[config_key::junkPacketMinSize]);
|
||||
wgConfig.insert(config_key::junkPacketMaxSize, config[config_key::junkPacketMaxSize]);
|
||||
|
||||
wgConfig.insert(config_key::specialJunk1, config[config_key::specialJunk1]);
|
||||
wgConfig.insert(config_key::specialJunk2, config[config_key::specialJunk2]);
|
||||
wgConfig.insert(config_key::specialJunk3, config[config_key::specialJunk3]);
|
||||
wgConfig.insert(config_key::specialJunk4, config[config_key::specialJunk4]);
|
||||
wgConfig.insert(config_key::specialJunk5, config[config_key::specialJunk5]);
|
||||
wgConfig.insert(config_key::controlledJunk1, config[config_key::controlledJunk1]);
|
||||
wgConfig.insert(config_key::controlledJunk2, config[config_key::controlledJunk2]);
|
||||
wgConfig.insert(config_key::controlledJunk3, config[config_key::controlledJunk3]);
|
||||
wgConfig.insert(config_key::specialHandshakeTimeout, config[config_key::specialHandshakeTimeout]);
|
||||
|
||||
QJsonDocument wgConfigDoc(wgConfig);
|
||||
QString wgConfigDocStr(wgConfigDoc.toJson(QJsonDocument::Compact));
|
||||
|
||||
|
|
@ -794,9 +808,9 @@ bool IosController::shareText(const QStringList& filesToSend) {
|
|||
if (!qtController) return;
|
||||
|
||||
UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:sharingItems applicationActivities:nil];
|
||||
|
||||
|
||||
__block bool isAccepted = false;
|
||||
|
||||
|
||||
[activityController setCompletionWithItemsHandler:^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
|
||||
isAccepted = completed;
|
||||
emit finished();
|
||||
|
|
@ -808,11 +822,11 @@ bool IosController::shareText(const QStringList& filesToSend) {
|
|||
popController.sourceView = qtController.view;
|
||||
popController.sourceRect = CGRectMake(100, 100, 100, 100);
|
||||
}
|
||||
|
||||
|
||||
QEventLoop wait;
|
||||
QObject::connect(this, &IosController::finished, &wait, &QEventLoop::quit);
|
||||
wait.exec();
|
||||
|
||||
|
||||
return isAccepted;
|
||||
}
|
||||
|
||||
|
|
@ -826,7 +840,7 @@ QString IosController::openFile() {
|
|||
if (!qtController) return;
|
||||
|
||||
[qtController presentViewController:documentPicker animated:YES completion:nil];
|
||||
|
||||
|
||||
__block QString filePath;
|
||||
|
||||
documentPickerDelegate.documentPickerClosedCallback = ^(NSString *path) {
|
||||
|
|
@ -841,7 +855,7 @@ QString IosController::openFile() {
|
|||
QEventLoop wait;
|
||||
QObject::connect(this, &IosController::finished, &wait, &QEventLoop::quit);
|
||||
wait.exec();
|
||||
|
||||
|
||||
return filePath;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,7 +31,9 @@ IPUtilsLinux::~IPUtilsLinux() {
|
|||
}
|
||||
|
||||
bool IPUtilsLinux::addInterfaceIPs(const InterfaceConfig& config) {
|
||||
return addIP4AddressToDevice(config) && addIP6AddressToDevice(config);
|
||||
bool ret = addIP4AddressToDevice(config);
|
||||
addIP6AddressToDevice(config);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool IPUtilsLinux::setMTUAndUp(const InterfaceConfig& config) {
|
||||
|
|
@ -95,7 +97,7 @@ bool IPUtilsLinux::addIP4AddressToDevice(const InterfaceConfig& config) {
|
|||
// Set ifr to interface
|
||||
int ret = ioctl(sockfd, SIOCSIFADDR, &ifr);
|
||||
if (ret) {
|
||||
logger.error() << "Failed to set IPv4: " << logger.sensitive(deviceAddr)
|
||||
logger.error() << "Failed to set IPv4: " << deviceAddr
|
||||
<< "error:" << strerror(errno);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -136,7 +138,7 @@ bool IPUtilsLinux::addIP6AddressToDevice(const InterfaceConfig& config) {
|
|||
// Set ifr6 to the interface
|
||||
ret = ioctl(sockfd, SIOCSIFADDR, &ifr6);
|
||||
if (ret && (errno != EEXIST)) {
|
||||
logger.error() << "Failed to set IPv6: " << logger.sensitive(deviceAddr)
|
||||
logger.error() << "Failed to set IPv6: " << deviceAddr
|
||||
<< "error:" << strerror(errno);
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -196,6 +196,8 @@ QStringList LinuxFirewall::getDNSRules(const QStringList& servers)
|
|||
result << QStringLiteral("-o amn0+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server);
|
||||
result << QStringLiteral("-o tun0+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server);
|
||||
result << QStringLiteral("-o tun0+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server);
|
||||
result << QStringLiteral("-o tun2+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server);
|
||||
result << QStringLiteral("-o tun2+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
@ -277,6 +279,7 @@ void LinuxFirewall::install()
|
|||
installAnchor(Both, QStringLiteral("200.allowVPN"), {
|
||||
QStringLiteral("-o amn0+ -j ACCEPT"),
|
||||
QStringLiteral("-o tun0+ -j ACCEPT"),
|
||||
QStringLiteral("-o tun2+ -j ACCEPT"),
|
||||
});
|
||||
|
||||
installAnchor(IPv4, QStringLiteral("120.blockNets"), {});
|
||||
|
|
@ -452,9 +455,6 @@ void LinuxFirewall::updateDNSServers(const QStringList& servers)
|
|||
|
||||
void LinuxFirewall::updateAllowNets(const QStringList& servers)
|
||||
{
|
||||
static QStringList existingServers {};
|
||||
|
||||
existingServers = servers;
|
||||
execute(QStringLiteral("iptables -F %1.110.allowNets").arg(kAnchorName));
|
||||
for (const QString& rule : getAllowRule(servers))
|
||||
execute(QStringLiteral("iptables -A %1.110.allowNets %2").arg(kAnchorName, rule));
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@
|
|||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
|
||||
#include "killswitch.h"
|
||||
|
||||
constexpr const int WG_TUN_PROC_TIMEOUT = 5000;
|
||||
constexpr const char* WG_RUNTIME_DIR = "/var/run/amneziawg";
|
||||
|
||||
|
|
@ -119,6 +121,12 @@ bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) {
|
|||
if (!config.m_responsePacketJunkSize.isEmpty()) {
|
||||
out << "s2=" << config.m_responsePacketJunkSize << "\n";
|
||||
}
|
||||
if (!config.m_cookieReplyPacketJunkSize.isEmpty()) {
|
||||
out << "s3=" << config.m_cookieReplyPacketJunkSize << "\n";
|
||||
}
|
||||
if (!config.m_transportPacketJunkSize.isEmpty()) {
|
||||
out << "s4=" << config.m_transportPacketJunkSize << "\n";
|
||||
}
|
||||
if (!config.m_initPacketMagicHeader.isEmpty()) {
|
||||
out << "h1=" << config.m_initPacketMagicHeader << "\n";
|
||||
}
|
||||
|
|
@ -132,13 +140,26 @@ bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) {
|
|||
out << "h4=" << config.m_transportPacketMagicHeader << "\n";
|
||||
}
|
||||
|
||||
for (const QString& key : config.m_specialJunk.keys()) {
|
||||
out << key.toLower() << "=" << config.m_specialJunk.value(key) << "\n";
|
||||
}
|
||||
for (const QString& key : config.m_controlledJunk.keys()) {
|
||||
out << key.toLower() << "=" << config.m_controlledJunk.value(key) << "\n";
|
||||
}
|
||||
if (!config.m_specialHandshakeTimeout.isEmpty()) {
|
||||
out << "itime=" << config.m_specialHandshakeTimeout << "\n";
|
||||
}
|
||||
|
||||
int err = uapiErrno(uapiCommand(message));
|
||||
if (err != 0) {
|
||||
logger.error() << "Interface configuration failed:" << strerror(err);
|
||||
} else {
|
||||
if (config.m_killSwitchEnabled) {
|
||||
FirewallParams params { };
|
||||
params.dnsServers.append(config.m_dnsServer);
|
||||
params.dnsServers.append(config.m_primaryDnsServer);
|
||||
if (!config.m_secondaryDnsServer.isEmpty()) {
|
||||
params.dnsServers.append(config.m_secondaryDnsServer);
|
||||
}
|
||||
if (config.m_allowedIPAddressRanges.contains(IPAddress("0.0.0.0/0"))) {
|
||||
params.blockAll = true;
|
||||
if (config.m_excludedAddresses.size()) {
|
||||
|
|
@ -182,7 +203,7 @@ bool WireguardUtilsLinux::deleteInterface() {
|
|||
QFile::remove(wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".name"));
|
||||
|
||||
// double-check + ensure our firewall is installed and enabled
|
||||
LinuxFirewall::uninstall();
|
||||
KillSwitch::instance()->disableKillSwitch();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -297,31 +318,6 @@ QList<WireguardUtils::PeerStatus> WireguardUtilsLinux::getPeerStatus() {
|
|||
return peerList;
|
||||
}
|
||||
|
||||
|
||||
void WireguardUtilsLinux::applyFirewallRules(FirewallParams& params)
|
||||
{
|
||||
// double-check + ensure our firewall is installed and enabled
|
||||
if (!LinuxFirewall::isInstalled()) LinuxFirewall::install();
|
||||
|
||||
// Note: rule precedence is handled inside IpTablesFirewall
|
||||
LinuxFirewall::ensureRootAnchorPriority();
|
||||
|
||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("000.allowLoopback"), true);
|
||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("100.blockAll"), params.blockAll);
|
||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("110.allowNets"), params.allowNets);
|
||||
LinuxFirewall::updateAllowNets(params.allowAddrs);
|
||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("120.blockNets"), params.blockNets);
|
||||
LinuxFirewall::updateBlockNets(params.blockAddrs);
|
||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("200.allowVPN"), true);
|
||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv6, QStringLiteral("250.blockIPv6"), true);
|
||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("290.allowDHCP"), true);
|
||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("300.allowLAN"), true);
|
||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("310.blockDNS"), true);
|
||||
LinuxFirewall::updateDNSServers(params.dnsServers);
|
||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("320.allowDNS"), true);
|
||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("400.allowPIA"), true);
|
||||
}
|
||||
|
||||
bool WireguardUtilsLinux::updateRoutePrefix(const IPAddress& prefix) {
|
||||
if (!m_rtmonitor) {
|
||||
return false;
|
||||
|
|
@ -377,6 +373,26 @@ bool WireguardUtilsLinux::deleteExclusionRoute(const IPAddress& prefix) {
|
|||
return m_rtmonitor->deleteExclusionRoute(prefix);
|
||||
}
|
||||
|
||||
bool WireguardUtilsLinux::excludeLocalNetworks(const QList<IPAddress>& routes) {
|
||||
if (!m_rtmonitor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Explicitly discard LAN traffic that makes its way into the tunnel. This
|
||||
// doesn't really exclude the LAN traffic, we just don't take any action to
|
||||
// overrule the routes of other interfaces.
|
||||
bool result = true;
|
||||
for (const auto& prefix : routes) {
|
||||
logger.error() << "Attempting to exclude:" << prefix.toString();
|
||||
if (!m_rtmonitor->insertRoute(prefix)) {
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: A kill switch would be nice though :)
|
||||
return result;
|
||||
}
|
||||
|
||||
QString WireguardUtilsLinux::uapiCommand(const QString& command) {
|
||||
QLocalSocket socket;
|
||||
QTimer uapiTimeout;
|
||||
|
|
@ -450,3 +466,27 @@ QString WireguardUtilsLinux::waitForTunnelName(const QString& filename) {
|
|||
|
||||
return QString();
|
||||
}
|
||||
|
||||
void WireguardUtilsLinux::applyFirewallRules(FirewallParams& params)
|
||||
{
|
||||
// double-check + ensure our firewall is installed and enabled
|
||||
if (!LinuxFirewall::isInstalled()) LinuxFirewall::install();
|
||||
|
||||
// Note: rule precedence is handled inside IpTablesFirewall
|
||||
LinuxFirewall::ensureRootAnchorPriority();
|
||||
|
||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("000.allowLoopback"), true);
|
||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("100.blockAll"), params.blockAll);
|
||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("110.allowNets"), params.allowNets);
|
||||
LinuxFirewall::updateAllowNets(params.allowAddrs);
|
||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("120.blockNets"), params.blockNets);
|
||||
LinuxFirewall::updateBlockNets(params.blockAddrs);
|
||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("200.allowVPN"), true);
|
||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv6, QStringLiteral("250.blockIPv6"), true);
|
||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("290.allowDHCP"), true);
|
||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("300.allowLAN"), true);
|
||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("310.blockDNS"), true);
|
||||
LinuxFirewall::updateDNSServers(params.dnsServers);
|
||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("320.allowDNS"), true);
|
||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("400.allowPIA"), true);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,9 @@ public:
|
|||
|
||||
bool addExclusionRoute(const IPAddress& prefix) override;
|
||||
bool deleteExclusionRoute(const IPAddress& prefix) override;
|
||||
|
||||
bool excludeLocalNetworks(const QList<IPAddress>& lanAddressRanges) override;
|
||||
|
||||
void applyFirewallRules(FirewallParams& params);
|
||||
signals:
|
||||
void backendFailure();
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ bool IPUtilsMacos::addIP4AddressToDevice(const InterfaceConfig& config) {
|
|||
// Set ifr to interface
|
||||
int ret = ioctl(sockfd, SIOCAIFADDR, &ifr);
|
||||
if (ret) {
|
||||
logger.error() << "Failed to set IPv4: " << logger.sensitive(deviceAddr)
|
||||
logger.error() << "Failed to set IPv4: " << deviceAddr
|
||||
<< "error:" << strerror(errno);
|
||||
return false;
|
||||
}
|
||||
|
|
@ -162,7 +162,7 @@ bool IPUtilsMacos::addIP6AddressToDevice(const InterfaceConfig& config) {
|
|||
// Set ifr to interface
|
||||
int ret = ioctl(sockfd, SIOCAIFADDR_IN6, &ifr6);
|
||||
if (ret) {
|
||||
logger.error() << "Failed to set IPv6: " << logger.sensitive(deviceAddr)
|
||||
logger.error() << "Failed to set IPv6: " << deviceAddr
|
||||
<< "error:" << strerror(errno);
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,8 +43,16 @@ namespace {
|
|||
|
||||
#include "macosfirewall.h"
|
||||
|
||||
#define ResourceDir qApp->applicationDirPath() + "/pf"
|
||||
#define DaemonDataDir qApp->applicationDirPath() + "/pf"
|
||||
#include <QDir>
|
||||
#include <QStandardPaths>
|
||||
|
||||
// Read-only rules bundled with the application.
|
||||
#define ResourceDir (qApp->applicationDirPath() + "/pf")
|
||||
|
||||
// Writable location that does NOT live inside the signed bundle. Using a
|
||||
// constant path under /Library/Application Support keeps the signature intact
|
||||
// and is accessible to the root helper.
|
||||
#define DaemonDataDir QStringLiteral("/Library/Application Support/AmneziaVPN/pf")
|
||||
|
||||
#include <QProcess>
|
||||
|
||||
|
|
@ -121,6 +129,8 @@ void MacOSFirewall::install()
|
|||
logger.info() << "Installing PF root anchor";
|
||||
|
||||
installRootAnchors();
|
||||
// Ensure writable directory exists, then store the token there.
|
||||
QDir().mkpath(DaemonDataDir);
|
||||
execute(QStringLiteral("pfctl -E 2>&1 | grep -F 'Token : ' | cut -c9- > '%1/pf.token'").arg(DaemonDataDir));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ void MacosRouteMonitor::handleRtmDelete(const struct rt_msghdr* rtm,
|
|||
for (const IPAddress& prefix : m_exclusionRoutes) {
|
||||
if (prefix.address().protocol() == protocol) {
|
||||
logger.debug() << "Removing exclusion route to"
|
||||
<< logger.sensitive(prefix.toString());
|
||||
<< prefix.toString();
|
||||
rtmSendRoute(RTM_DELETE, prefix, rtm->rtm_index, nullptr);
|
||||
}
|
||||
}
|
||||
|
|
@ -259,7 +259,7 @@ void MacosRouteMonitor::handleRtmUpdate(const struct rt_msghdr* rtm,
|
|||
for (const IPAddress& prefix : m_exclusionRoutes) {
|
||||
if (prefix.address().protocol() == protocol) {
|
||||
logger.debug() << "Updating exclusion route to"
|
||||
<< logger.sensitive(prefix.toString());
|
||||
<< prefix.toString();
|
||||
rtmSendRoute(rtm_type, prefix, ifindex, addrlist[1].constData());
|
||||
}
|
||||
}
|
||||
|
|
@ -358,8 +358,8 @@ void MacosRouteMonitor::rtmAppendAddr(struct rt_msghdr* rtm, size_t maxlen,
|
|||
}
|
||||
|
||||
bool MacosRouteMonitor::rtmSendRoute(int action, const IPAddress& prefix,
|
||||
unsigned int ifindex,
|
||||
const void* gateway) {
|
||||
unsigned int ifindex, const void* gateway,
|
||||
int flags) {
|
||||
constexpr size_t rtm_max_size = sizeof(struct rt_msghdr) +
|
||||
sizeof(struct sockaddr_in6) * 2 +
|
||||
sizeof(struct sockaddr_storage);
|
||||
|
|
@ -370,7 +370,7 @@ bool MacosRouteMonitor::rtmSendRoute(int action, const IPAddress& prefix,
|
|||
rtm->rtm_version = RTM_VERSION;
|
||||
rtm->rtm_type = action;
|
||||
rtm->rtm_index = ifindex;
|
||||
rtm->rtm_flags = RTF_STATIC | RTF_UP;
|
||||
rtm->rtm_flags = flags | RTF_STATIC | RTF_UP;
|
||||
rtm->rtm_addrs = 0;
|
||||
rtm->rtm_pid = 0;
|
||||
rtm->rtm_seq = m_rtseq++;
|
||||
|
|
@ -490,7 +490,7 @@ bool MacosRouteMonitor::rtmFetchRoutes(int family) {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool MacosRouteMonitor::insertRoute(const IPAddress& prefix) {
|
||||
bool MacosRouteMonitor::insertRoute(const IPAddress& prefix, int flags) {
|
||||
struct sockaddr_dl datalink;
|
||||
memset(&datalink, 0, sizeof(datalink));
|
||||
datalink.sdl_family = AF_LINK;
|
||||
|
|
@ -502,16 +502,15 @@ bool MacosRouteMonitor::insertRoute(const IPAddress& prefix) {
|
|||
datalink.sdl_slen = 0;
|
||||
memcpy(&datalink.sdl_data, qPrintable(m_ifname), datalink.sdl_nlen);
|
||||
|
||||
return rtmSendRoute(RTM_ADD, prefix, m_ifindex, &datalink);
|
||||
return rtmSendRoute(RTM_ADD, prefix, m_ifindex, &datalink, flags);
|
||||
}
|
||||
|
||||
bool MacosRouteMonitor::deleteRoute(const IPAddress& prefix) {
|
||||
return rtmSendRoute(RTM_DELETE, prefix, m_ifindex, nullptr);
|
||||
bool MacosRouteMonitor::deleteRoute(const IPAddress& prefix, int flags) {
|
||||
return rtmSendRoute(RTM_DELETE, prefix, m_ifindex, nullptr, flags);
|
||||
}
|
||||
|
||||
bool MacosRouteMonitor::addExclusionRoute(const IPAddress& prefix) {
|
||||
logger.debug() << "Adding exclusion route for"
|
||||
<< logger.sensitive(prefix.toString());
|
||||
logger.debug() << "Adding exclusion route for" << prefix.toString();
|
||||
|
||||
if (m_exclusionRoutes.contains(prefix)) {
|
||||
logger.warning() << "Exclusion route already exists";
|
||||
|
|
@ -536,8 +535,7 @@ bool MacosRouteMonitor::addExclusionRoute(const IPAddress& prefix) {
|
|||
}
|
||||
|
||||
bool MacosRouteMonitor::deleteExclusionRoute(const IPAddress& prefix) {
|
||||
logger.debug() << "Deleting exclusion route for"
|
||||
<< logger.sensitive(prefix.toString());
|
||||
logger.debug() << "Deleting exclusion route for" << prefix.toString();
|
||||
|
||||
m_exclusionRoutes.removeAll(prefix);
|
||||
if (prefix.address().protocol() == QAbstractSocket::IPv4Protocol) {
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ class MacosRouteMonitor final : public QObject {
|
|||
MacosRouteMonitor(const QString& ifname, QObject* parent = nullptr);
|
||||
~MacosRouteMonitor();
|
||||
|
||||
bool insertRoute(const IPAddress& prefix);
|
||||
bool deleteRoute(const IPAddress& prefix);
|
||||
bool insertRoute(const IPAddress& prefix, int flags = 0);
|
||||
bool deleteRoute(const IPAddress& prefix, int flags = 0);
|
||||
int interfaceFlags() { return m_ifflags; }
|
||||
|
||||
bool addExclusionRoute(const IPAddress& prefix);
|
||||
|
|
@ -37,7 +37,7 @@ class MacosRouteMonitor final : public QObject {
|
|||
void handleRtmUpdate(const struct rt_msghdr* msg, const QByteArray& payload);
|
||||
void handleIfaceInfo(const struct if_msghdr* msg, const QByteArray& payload);
|
||||
bool rtmSendRoute(int action, const IPAddress& prefix, unsigned int ifindex,
|
||||
const void* gateway);
|
||||
const void* gateway, int flags = 0);
|
||||
bool rtmFetchRoutes(int family);
|
||||
static void rtmAppendAddr(struct rt_msghdr* rtm, size_t maxlen, int rtaddr,
|
||||
const void* sa);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include "wireguardutilsmacos.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <net/route.h>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QDir>
|
||||
|
|
@ -15,6 +16,8 @@
|
|||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
|
||||
#include "killswitch.h"
|
||||
|
||||
constexpr const int WG_TUN_PROC_TIMEOUT = 5000;
|
||||
constexpr const char* WG_RUNTIME_DIR = "/var/run/amneziawg";
|
||||
|
||||
|
|
@ -116,6 +119,12 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) {
|
|||
if (!config.m_responsePacketJunkSize.isEmpty()) {
|
||||
out << "s2=" << config.m_responsePacketJunkSize << "\n";
|
||||
}
|
||||
if (!config.m_cookieReplyPacketJunkSize.isEmpty()) {
|
||||
out << "s3=" << config.m_cookieReplyPacketJunkSize << "\n";
|
||||
}
|
||||
if (!config.m_transportPacketJunkSize.isEmpty()) {
|
||||
out << "s4=" << config.m_transportPacketJunkSize << "\n";
|
||||
}
|
||||
if (!config.m_initPacketMagicHeader.isEmpty()) {
|
||||
out << "h1=" << config.m_initPacketMagicHeader << "\n";
|
||||
}
|
||||
|
|
@ -129,31 +138,43 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) {
|
|||
out << "h4=" << config.m_transportPacketMagicHeader << "\n";
|
||||
}
|
||||
|
||||
int err = uapiErrno(uapiCommand(message));
|
||||
for (const QString& key : config.m_specialJunk.keys()) {
|
||||
out << key.toLower() << "=" << config.m_specialJunk.value(key) << "\n";
|
||||
}
|
||||
for (const QString& key : config.m_controlledJunk.keys()) {
|
||||
out << key.toLower() << "=" << config.m_controlledJunk.value(key) << "\n";
|
||||
}
|
||||
if (!config.m_specialHandshakeTimeout.isEmpty()) {
|
||||
out << "itime=" << config.m_specialHandshakeTimeout << "\n";
|
||||
}
|
||||
|
||||
int err = uapiErrno(uapiCommand(message));
|
||||
if (err != 0) {
|
||||
logger.error() << "Interface configuration failed:" << strerror(err);
|
||||
} else {
|
||||
if (config.m_killSwitchEnabled) {
|
||||
FirewallParams params { };
|
||||
params.dnsServers.append(config.m_dnsServer);
|
||||
if (config.m_killSwitchEnabled) {
|
||||
FirewallParams params { };
|
||||
params.dnsServers.append(config.m_primaryDnsServer);
|
||||
if (!config.m_secondaryDnsServer.isEmpty()) {
|
||||
params.dnsServers.append(config.m_secondaryDnsServer);
|
||||
}
|
||||
|
||||
if (config.m_allowedIPAddressRanges.contains(IPAddress("0.0.0.0/0"))) {
|
||||
if (config.m_allowedIPAddressRanges.contains(IPAddress("0.0.0.0/0"))) {
|
||||
params.blockAll = true;
|
||||
if (config.m_excludedAddresses.size()) {
|
||||
params.allowNets = true;
|
||||
foreach (auto net, config.m_excludedAddresses) {
|
||||
params.allowAddrs.append(net.toUtf8());
|
||||
}
|
||||
params.allowNets = true;
|
||||
foreach (auto net, config.m_excludedAddresses) {
|
||||
params.allowAddrs.append(net.toUtf8());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
} else {
|
||||
params.blockNets = true;
|
||||
foreach (auto net, config.m_allowedIPAddressRanges) {
|
||||
params.blockAddrs.append(net.toString());
|
||||
params.blockAddrs.append(net.toString());
|
||||
}
|
||||
}
|
||||
applyFirewallRules(params);
|
||||
}
|
||||
applyFirewallRules(params);
|
||||
}
|
||||
}
|
||||
return (err == 0);
|
||||
}
|
||||
|
|
@ -180,7 +201,7 @@ bool WireguardUtilsMacos::deleteInterface() {
|
|||
QFile::remove(wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".name"));
|
||||
|
||||
// double-check + ensure our firewall is installed and enabled
|
||||
MacOSFirewall::uninstall();
|
||||
KillSwitch::instance()->disableKillSwitch();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -211,7 +232,6 @@ bool WireguardUtilsMacos::updatePeer(const InterfaceConfig& config) {
|
|||
logger.warning() << "Failed to create peer with no endpoints";
|
||||
return false;
|
||||
}
|
||||
|
||||
out << config.m_serverPort << "\n";
|
||||
|
||||
out << "replace_allowed_ips=true\n";
|
||||
|
|
@ -323,10 +343,10 @@ bool WireguardUtilsMacos::deleteRoutePrefix(const IPAddress& prefix) {
|
|||
if (!m_rtmonitor) {
|
||||
return false;
|
||||
}
|
||||
if (prefix.prefixLength() > 0) {
|
||||
return m_rtmonitor->insertRoute(prefix);
|
||||
}
|
||||
|
||||
if (prefix.prefixLength() > 0) {
|
||||
return m_rtmonitor->deleteRoute(prefix);
|
||||
}
|
||||
// Ensure that we do not replace the default route.
|
||||
if (prefix.type() == QAbstractSocket::IPv4Protocol) {
|
||||
return m_rtmonitor->deleteRoute(IPAddress("0.0.0.0/1")) &&
|
||||
|
|
@ -346,31 +366,6 @@ bool WireguardUtilsMacos::addExclusionRoute(const IPAddress& prefix) {
|
|||
return m_rtmonitor->addExclusionRoute(prefix);
|
||||
}
|
||||
|
||||
void WireguardUtilsMacos::applyFirewallRules(FirewallParams& params)
|
||||
{
|
||||
// double-check + ensure our firewall is installed and enabled. This is necessary as
|
||||
// other software may disable pfctl before re-enabling with their own rules (e.g other VPNs)
|
||||
if (!MacOSFirewall::isInstalled()) MacOSFirewall::install();
|
||||
|
||||
MacOSFirewall::ensureRootAnchorPriority();
|
||||
MacOSFirewall::setAnchorEnabled(QStringLiteral("000.allowLoopback"), true);
|
||||
MacOSFirewall::setAnchorEnabled(QStringLiteral("100.blockAll"), params.blockAll);
|
||||
MacOSFirewall::setAnchorEnabled(QStringLiteral("110.allowNets"), params.allowNets);
|
||||
MacOSFirewall::setAnchorTable(QStringLiteral("110.allowNets"), params.allowNets,
|
||||
QStringLiteral("allownets"), params.allowAddrs);
|
||||
|
||||
MacOSFirewall::setAnchorEnabled(QStringLiteral("120.blockNets"), params.blockNets);
|
||||
MacOSFirewall::setAnchorTable(QStringLiteral("120.blockNets"), params.blockNets,
|
||||
QStringLiteral("blocknets"), params.blockAddrs);
|
||||
|
||||
MacOSFirewall::setAnchorEnabled(QStringLiteral("200.allowVPN"), true);
|
||||
MacOSFirewall::setAnchorEnabled(QStringLiteral("250.blockIPv6"), true);
|
||||
MacOSFirewall::setAnchorEnabled(QStringLiteral("290.allowDHCP"), true);
|
||||
MacOSFirewall::setAnchorEnabled(QStringLiteral("300.allowLAN"), true);
|
||||
MacOSFirewall::setAnchorEnabled(QStringLiteral("310.blockDNS"), true);
|
||||
MacOSFirewall::setAnchorTable(QStringLiteral("310.blockDNS"), true, QStringLiteral("dnsaddr"), params.dnsServers);
|
||||
}
|
||||
|
||||
bool WireguardUtilsMacos::deleteExclusionRoute(const IPAddress& prefix) {
|
||||
if (!m_rtmonitor) {
|
||||
return false;
|
||||
|
|
@ -378,6 +373,26 @@ bool WireguardUtilsMacos::deleteExclusionRoute(const IPAddress& prefix) {
|
|||
return m_rtmonitor->deleteExclusionRoute(prefix);
|
||||
}
|
||||
|
||||
bool WireguardUtilsMacos::excludeLocalNetworks(const QList<IPAddress>& routes) {
|
||||
if (!m_rtmonitor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Explicitly discard LAN traffic that makes its way into the tunnel. This
|
||||
// doesn't really exclude the LAN traffic, we just don't take any action to
|
||||
// overrule the routes of other interfaces.
|
||||
bool result = true;
|
||||
for (const auto& prefix : routes) {
|
||||
logger.error() << "Attempting to exclude:" << prefix.toString();
|
||||
if (!m_rtmonitor->insertRoute(prefix, RTF_IFSCOPE | RTF_REJECT)) {
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: A kill switch would be nice though :)
|
||||
return result;
|
||||
}
|
||||
|
||||
QString WireguardUtilsMacos::uapiCommand(const QString& command) {
|
||||
QLocalSocket socket;
|
||||
QTimer uapiTimeout;
|
||||
|
|
@ -454,3 +469,28 @@ QString WireguardUtilsMacos::waitForTunnelName(const QString& filename) {
|
|||
|
||||
return QString();
|
||||
}
|
||||
|
||||
void WireguardUtilsMacos::applyFirewallRules(FirewallParams& params)
|
||||
{
|
||||
// double-check + ensure our firewall is installed and enabled. This is necessary as
|
||||
// other software may disable pfctl before re-enabling with their own rules (e.g other VPNs)
|
||||
if (!MacOSFirewall::isInstalled()) MacOSFirewall::install();
|
||||
|
||||
MacOSFirewall::ensureRootAnchorPriority();
|
||||
MacOSFirewall::setAnchorEnabled(QStringLiteral("000.allowLoopback"), true);
|
||||
MacOSFirewall::setAnchorEnabled(QStringLiteral("100.blockAll"), params.blockAll);
|
||||
MacOSFirewall::setAnchorEnabled(QStringLiteral("110.allowNets"), params.allowNets);
|
||||
MacOSFirewall::setAnchorTable(QStringLiteral("110.allowNets"), params.allowNets,
|
||||
QStringLiteral("allownets"), params.allowAddrs);
|
||||
|
||||
MacOSFirewall::setAnchorEnabled(QStringLiteral("120.blockNets"), params.blockNets);
|
||||
MacOSFirewall::setAnchorTable(QStringLiteral("120.blockNets"), params.blockNets,
|
||||
QStringLiteral("blocknets"), params.blockAddrs);
|
||||
|
||||
MacOSFirewall::setAnchorEnabled(QStringLiteral("200.allowVPN"), true);
|
||||
MacOSFirewall::setAnchorEnabled(QStringLiteral("250.blockIPv6"), true);
|
||||
MacOSFirewall::setAnchorEnabled(QStringLiteral("290.allowDHCP"), true);
|
||||
MacOSFirewall::setAnchorEnabled(QStringLiteral("300.allowLAN"), true);
|
||||
MacOSFirewall::setAnchorEnabled(QStringLiteral("310.blockDNS"), true);
|
||||
MacOSFirewall::setAnchorTable(QStringLiteral("310.blockDNS"), true, QStringLiteral("dnsaddr"), params.dnsServers);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,9 @@ class WireguardUtilsMacos final : public WireguardUtils {
|
|||
|
||||
bool addExclusionRoute(const IPAddress& prefix) override;
|
||||
bool deleteExclusionRoute(const IPAddress& prefix) override;
|
||||
|
||||
bool excludeLocalNetworks(const QList<IPAddress>& lanAddressRanges) override;
|
||||
|
||||
void applyFirewallRules(FirewallParams& params);
|
||||
|
||||
signals:
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include "windowsdaemon.h"
|
||||
|
||||
#include <Windows.h>
|
||||
#include <qassert.h>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QJsonDocument>
|
||||
|
|
@ -15,28 +16,34 @@
|
|||
#include <QTextStream>
|
||||
#include <QtGlobal>
|
||||
|
||||
#include "daemon/daemonerrors.h"
|
||||
#include "dnsutilswindows.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
#include "core/networkUtilities.h"
|
||||
#include "platforms/windows/daemon/windowsfirewall.h"
|
||||
#include "platforms/windows/daemon/windowssplittunnel.h"
|
||||
#include "platforms/windows/windowscommons.h"
|
||||
#include "platforms/windows/windowsservicemanager.h"
|
||||
#include "windowsfirewall.h"
|
||||
|
||||
#include "core/networkUtilities.h"
|
||||
|
||||
namespace {
|
||||
Logger logger("WindowsDaemon");
|
||||
}
|
||||
|
||||
WindowsDaemon::WindowsDaemon() : Daemon(nullptr), m_splitTunnelManager(this) {
|
||||
WindowsDaemon::WindowsDaemon() : Daemon(nullptr) {
|
||||
MZ_COUNT_CTOR(WindowsDaemon);
|
||||
m_firewallManager = WindowsFirewall::create(this);
|
||||
Q_ASSERT(m_firewallManager != nullptr);
|
||||
|
||||
m_wgutils = new WireguardUtilsWindows(this);
|
||||
m_wgutils = WireguardUtilsWindows::create(m_firewallManager, this);
|
||||
m_dnsutils = new DnsUtilsWindows(this);
|
||||
m_splitTunnelManager = WindowsSplitTunnel::create(m_firewallManager);
|
||||
|
||||
connect(m_wgutils, &WireguardUtilsWindows::backendFailure, this,
|
||||
connect(m_wgutils.get(), &WireguardUtilsWindows::backendFailure, this,
|
||||
&WindowsDaemon::monitorBackendFailure);
|
||||
connect(this, &WindowsDaemon::activationFailure,
|
||||
[]() { WindowsFirewall::instance()->disableKillSwitch(); });
|
||||
[this]() { m_firewallManager->disableKillSwitch(); });
|
||||
}
|
||||
|
||||
WindowsDaemon::~WindowsDaemon() {
|
||||
|
|
@ -57,28 +64,42 @@ void WindowsDaemon::prepareActivation(const InterfaceConfig& config, int inetAda
|
|||
|
||||
void WindowsDaemon::activateSplitTunnel(const InterfaceConfig& config, int vpnAdapterIndex) {
|
||||
if (config.m_vpnDisabledApps.length() > 0) {
|
||||
m_splitTunnelManager.start(m_inetAdapterIndex, vpnAdapterIndex);
|
||||
m_splitTunnelManager.setRules(config.m_vpnDisabledApps);
|
||||
m_splitTunnelManager->start(m_inetAdapterIndex, vpnAdapterIndex);
|
||||
m_splitTunnelManager->excludeApps(config.m_vpnDisabledApps);
|
||||
} else {
|
||||
m_splitTunnelManager.stop();
|
||||
m_splitTunnelManager->stop();
|
||||
}
|
||||
}
|
||||
|
||||
bool WindowsDaemon::run(Op op, const InterfaceConfig& config) {
|
||||
if (op == Down) {
|
||||
m_splitTunnelManager.stop();
|
||||
if (!m_splitTunnelManager) {
|
||||
if (config.m_vpnDisabledApps.length() > 0) {
|
||||
// The Client has sent us a list of disabled apps, but we failed
|
||||
// to init the the split tunnel driver.
|
||||
// So let the client know this was not possible
|
||||
emit backendFailure(DaemonError::ERROR_SPLIT_TUNNEL_INIT_FAILURE);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (op == Up) {
|
||||
logger.debug() << "Tunnel UP, Starting SplitTunneling";
|
||||
if (!WindowsSplitTunnel::isInstalled()) {
|
||||
logger.warning() << "Split Tunnel Driver not Installed yet, fixing this.";
|
||||
WindowsSplitTunnel::installDriver();
|
||||
}
|
||||
if (op == Down) {
|
||||
m_splitTunnelManager->stop();
|
||||
return true;
|
||||
}
|
||||
|
||||
activateSplitTunnel(config);
|
||||
if (config.m_vpnDisabledApps.length() > 0) {
|
||||
if (!m_splitTunnelManager->start(m_inetAdapterIndex)) {
|
||||
emit backendFailure(DaemonError::ERROR_SPLIT_TUNNEL_START_FAILURE);
|
||||
};
|
||||
if (!m_splitTunnelManager->excludeApps(config.m_vpnDisabledApps)) {
|
||||
emit backendFailure(DaemonError::ERROR_SPLIT_TUNNEL_EXCLUDE_FAILURE);
|
||||
};
|
||||
// Now the driver should be running (State == 4)
|
||||
if (!m_splitTunnelManager->isRunning()) {
|
||||
emit backendFailure(DaemonError::ERROR_SPLIT_TUNNEL_START_FAILURE);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
m_splitTunnelManager->stop();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,11 @@
|
|||
#ifndef WINDOWSDAEMON_H
|
||||
#define WINDOWSDAEMON_H
|
||||
|
||||
#include <qpointer.h>
|
||||
|
||||
#include "daemon/daemon.h"
|
||||
#include "dnsutilswindows.h"
|
||||
#include "windowsfirewall.h"
|
||||
#include "windowssplittunnel.h"
|
||||
#include "windowstunnelservice.h"
|
||||
#include "wireguardutilswindows.h"
|
||||
|
|
@ -25,7 +28,7 @@ class WindowsDaemon final : public Daemon {
|
|||
|
||||
protected:
|
||||
bool run(Op op, const InterfaceConfig& config) override;
|
||||
WireguardUtils* wgutils() const override { return m_wgutils; }
|
||||
WireguardUtils* wgutils() const override { return m_wgutils.get(); }
|
||||
DnsUtils* dnsutils() override { return m_dnsutils; }
|
||||
|
||||
private:
|
||||
|
|
@ -39,9 +42,10 @@ class WindowsDaemon final : public Daemon {
|
|||
|
||||
int m_inetAdapterIndex = -1;
|
||||
|
||||
WireguardUtilsWindows* m_wgutils = nullptr;
|
||||
std::unique_ptr<WireguardUtilsWindows> m_wgutils;
|
||||
DnsUtilsWindows* m_dnsutils = nullptr;
|
||||
WindowsSplitTunnel m_splitTunnelManager;
|
||||
std::unique_ptr<WindowsSplitTunnel> m_splitTunnelManager;
|
||||
QPointer<WindowsFirewall> m_firewallManager;
|
||||
};
|
||||
|
||||
#endif // WINDOWSDAEMON_H
|
||||
|
|
|
|||
|
|
@ -9,11 +9,12 @@
|
|||
#include <guiddef.h>
|
||||
#include <initguid.h>
|
||||
#include <netfw.h>
|
||||
//#include <qaccessible.h>
|
||||
#include <Ws2tcpip.h>
|
||||
|
||||
#include <qaccessible.h>
|
||||
#include <qassert.h>
|
||||
#include <stdio.h>
|
||||
#include <windows.h>
|
||||
#include <Ws2tcpip.h>
|
||||
#include "winsock.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QFileInfo>
|
||||
|
|
@ -27,7 +28,8 @@
|
|||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
#include "platforms/windows/windowsutils.h"
|
||||
#include "winsock.h"
|
||||
|
||||
#include "killswitch.h"
|
||||
|
||||
#define IPV6_ADDRESS_SIZE 16
|
||||
|
||||
|
|
@ -49,18 +51,13 @@ constexpr uint8_t HIGH_WEIGHT = 13;
|
|||
constexpr uint8_t MAX_WEIGHT = 15;
|
||||
} // namespace
|
||||
|
||||
WindowsFirewall* WindowsFirewall::instance() {
|
||||
if (s_instance == nullptr) {
|
||||
s_instance = new WindowsFirewall(qApp);
|
||||
WindowsFirewall* WindowsFirewall::create(QObject* parent) {
|
||||
if (s_instance != nullptr) {
|
||||
// Only one instance of the firewall is allowed
|
||||
// Q_ASSERT(false);
|
||||
return s_instance;
|
||||
}
|
||||
return s_instance;
|
||||
}
|
||||
|
||||
WindowsFirewall::WindowsFirewall(QObject* parent) : QObject(parent) {
|
||||
MZ_COUNT_CTOR(WindowsFirewall);
|
||||
Q_ASSERT(s_instance == nullptr);
|
||||
|
||||
HANDLE engineHandle = NULL;
|
||||
HANDLE engineHandle = nullptr;
|
||||
DWORD result = ERROR_SUCCESS;
|
||||
// Use dynamic sessions for efficiency and safety:
|
||||
// -> Filtering policy objects are deleted even when the application crashes/
|
||||
|
|
@ -71,15 +68,24 @@ WindowsFirewall::WindowsFirewall(QObject* parent) : QObject(parent) {
|
|||
|
||||
logger.debug() << "Opening the filter engine.";
|
||||
|
||||
result =
|
||||
FwpmEngineOpen0(NULL, RPC_C_AUTHN_WINNT, NULL, &session, &engineHandle);
|
||||
result = FwpmEngineOpen0(nullptr, RPC_C_AUTHN_WINNT, nullptr, &session,
|
||||
&engineHandle);
|
||||
|
||||
if (result != ERROR_SUCCESS) {
|
||||
WindowsUtils::windowsLog("FwpmEngineOpen0 failed");
|
||||
return;
|
||||
return nullptr;
|
||||
}
|
||||
logger.debug() << "Filter engine opened successfully.";
|
||||
m_sessionHandle = engineHandle;
|
||||
if (!initSublayer()) {
|
||||
return nullptr;
|
||||
}
|
||||
s_instance = new WindowsFirewall(engineHandle, parent);
|
||||
return s_instance;
|
||||
}
|
||||
|
||||
WindowsFirewall::WindowsFirewall(HANDLE session, QObject* parent)
|
||||
: QObject(parent), m_sessionHandle(session) {
|
||||
MZ_COUNT_CTOR(WindowsFirewall);
|
||||
}
|
||||
|
||||
WindowsFirewall::~WindowsFirewall() {
|
||||
|
|
@ -89,15 +95,8 @@ WindowsFirewall::~WindowsFirewall() {
|
|||
}
|
||||
}
|
||||
|
||||
bool WindowsFirewall::init() {
|
||||
if (m_init) {
|
||||
logger.warning() << "Alread initialised FW_WFP layer";
|
||||
return true;
|
||||
}
|
||||
if (m_sessionHandle == INVALID_HANDLE_VALUE) {
|
||||
logger.error() << "Cant Init Sublayer with invalid wfp handle";
|
||||
return false;
|
||||
}
|
||||
// static
|
||||
bool WindowsFirewall::initSublayer() {
|
||||
// If we were not able to aquire a handle, this will fail anyway.
|
||||
// We need to open up another handle because of wfp rules:
|
||||
// If a wfp resource was created with SESSION_DYNAMIC,
|
||||
|
|
@ -157,11 +156,10 @@ bool WindowsFirewall::init() {
|
|||
return false;
|
||||
}
|
||||
logger.debug() << "Initialised Sublayer";
|
||||
m_init = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WindowsFirewall::enableKillSwitch(int vpnAdapterIndex) {
|
||||
bool WindowsFirewall::enableInterface(int vpnAdapterIndex) {
|
||||
// Checks if the FW_Rule was enabled succesfully,
|
||||
// disables the whole killswitch and returns false if not.
|
||||
#define FW_OK(rule) \
|
||||
|
|
@ -185,21 +183,95 @@ bool WindowsFirewall::enableKillSwitch(int vpnAdapterIndex) {
|
|||
}
|
||||
|
||||
logger.info() << "Enabling Killswitch Using Adapter:" << vpnAdapterIndex;
|
||||
if (vpnAdapterIndex < 0)
|
||||
{
|
||||
IPAddress allv4("0.0.0.0/0");
|
||||
if (!blockTrafficTo(allv4, MED_WEIGHT,
|
||||
"Block Internet", "killswitch")) {
|
||||
return false;
|
||||
}
|
||||
IPAddress allv6("::/0");
|
||||
if (!blockTrafficTo(allv6, MED_WEIGHT,
|
||||
"Block Internet", "killswitch")) {
|
||||
return false;
|
||||
}
|
||||
} else
|
||||
FW_OK(allowTrafficOfAdapter(vpnAdapterIndex, MED_WEIGHT,
|
||||
"Allow usage of VPN Adapter"));
|
||||
"Allow usage of VPN Adapter"));
|
||||
FW_OK(allowDHCPTraffic(MED_WEIGHT, "Allow DHCP Traffic"));
|
||||
FW_OK(allowHyperVTraffic(MED_WEIGHT, "Allow Hyper-V Traffic"));
|
||||
FW_OK(allowHyperVTraffic(MAX_WEIGHT, "Allow Hyper-V Traffic"));
|
||||
FW_OK(allowTrafficForAppOnAll(getCurrentPath(), MAX_WEIGHT,
|
||||
"Allow all for AmneziaVPN.exe"));
|
||||
FW_OK(blockTrafficOnPort(53, MED_WEIGHT, "Block all DNS"));
|
||||
FW_OK(
|
||||
allowLoopbackTraffic(MED_WEIGHT, "Allow Loopback traffic on device %1"));
|
||||
FW_OK(allowLoopbackTraffic(MED_WEIGHT,
|
||||
"Allow Loopback traffic on device %1"));
|
||||
|
||||
logger.debug() << "Killswitch on! Rules:" << m_activeRules.length();
|
||||
return true;
|
||||
#undef FW_OK
|
||||
}
|
||||
|
||||
// Allow unprotected traffic sent to the following local address ranges.
|
||||
bool WindowsFirewall::enableLanBypass(const QList<IPAddress>& ranges) {
|
||||
// Start the firewall transaction
|
||||
auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
|
||||
if (result != ERROR_SUCCESS) {
|
||||
disableKillSwitch();
|
||||
return false;
|
||||
}
|
||||
auto cleanup = qScopeGuard([&] {
|
||||
FwpmTransactionAbort0(m_sessionHandle);
|
||||
disableKillSwitch();
|
||||
});
|
||||
|
||||
// Blocking unprotected traffic
|
||||
for (const IPAddress& prefix : ranges) {
|
||||
if (!allowTrafficTo(prefix, LOW_WEIGHT + 1, "Allow LAN bypass traffic")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
result = FwpmTransactionCommit0(m_sessionHandle);
|
||||
if (result != ERROR_SUCCESS) {
|
||||
logger.error() << "FwpmTransactionCommit0 failed with error:" << result;
|
||||
return false;
|
||||
}
|
||||
|
||||
cleanup.dismiss();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Allow unprotected traffic sent to the following address ranges.
|
||||
bool WindowsFirewall::allowTrafficRange(const QStringList& ranges) {
|
||||
// Start the firewall transaction
|
||||
auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
|
||||
if (result != ERROR_SUCCESS) {
|
||||
disableKillSwitch();
|
||||
return false;
|
||||
}
|
||||
auto cleanup = qScopeGuard([&] {
|
||||
FwpmTransactionAbort0(m_sessionHandle);
|
||||
disableKillSwitch();
|
||||
});
|
||||
|
||||
for (const QString& addr : ranges) {
|
||||
logger.debug() << "Allow killswitch exclude: " << addr;
|
||||
if (!allowTrafficTo(QHostAddress(addr), HIGH_WEIGHT, "Allow killswitch bypass traffic")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
result = FwpmTransactionCommit0(m_sessionHandle);
|
||||
if (result != ERROR_SUCCESS) {
|
||||
logger.error() << "FwpmTransactionCommit0 failed with error:" << result;
|
||||
return false;
|
||||
}
|
||||
|
||||
cleanup.dismiss();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
|
||||
// Start the firewall transaction
|
||||
auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
|
||||
|
|
@ -219,15 +291,15 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
|
|||
"Block Internet", config.m_serverPublicKey)) {
|
||||
return false;
|
||||
}
|
||||
if (!config.m_dnsServer.isEmpty()) {
|
||||
if (!allowTrafficTo(QHostAddress(config.m_dnsServer), 53, HIGH_WEIGHT,
|
||||
if (!config.m_primaryDnsServer.isEmpty()) {
|
||||
if (!allowTrafficTo(QHostAddress(config.m_primaryDnsServer), 53, HIGH_WEIGHT,
|
||||
"Allow DNS-Server", config.m_serverPublicKey)) {
|
||||
return false;
|
||||
}
|
||||
// In some cases, we might configure a 2nd DNS server for IPv6, however
|
||||
// this should probably be cleaned up by converting m_dnsServer into
|
||||
// a QStringList instead.
|
||||
if (config.m_dnsServer == config.m_serverIpv4Gateway) {
|
||||
if (config.m_primaryDnsServer == config.m_serverIpv4Gateway) {
|
||||
if (!allowTrafficTo(QHostAddress(config.m_serverIpv6Gateway), 53,
|
||||
HIGH_WEIGHT, "Allow extra IPv6 DNS-Server",
|
||||
config.m_serverPublicKey)) {
|
||||
|
|
@ -236,11 +308,36 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
|
|||
}
|
||||
}
|
||||
|
||||
if (!config.m_secondaryDnsServer.isEmpty()) {
|
||||
if (!allowTrafficTo(QHostAddress(config.m_secondaryDnsServer), 53, HIGH_WEIGHT,
|
||||
"Allow DNS-Server", config.m_serverPublicKey)) {
|
||||
return false;
|
||||
}
|
||||
// In some cases, we might configure a 2nd DNS server for IPv6, however
|
||||
// this should probably be cleaned up by converting m_dnsServer into
|
||||
// a QStringList instead.
|
||||
if (config.m_secondaryDnsServer == config.m_serverIpv4Gateway) {
|
||||
if (!allowTrafficTo(QHostAddress(config.m_serverIpv6Gateway), 53,
|
||||
HIGH_WEIGHT, "Allow extra IPv6 DNS-Server",
|
||||
config.m_serverPublicKey)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const QString& dns : config.m_allowedDnsServers) {
|
||||
logger.debug() << "Allow DNS: " << dns;
|
||||
if (!allowTrafficTo(QHostAddress(dns), 53, HIGH_WEIGHT,
|
||||
"Allow DNS-Server", config.m_serverPublicKey)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!config.m_excludedAddresses.empty()) {
|
||||
for (const QString& i : config.m_excludedAddresses) {
|
||||
logger.debug() << "range: " << i;
|
||||
logger.debug() << "excludedAddresses range: " << i;
|
||||
|
||||
if (!allowTrafficToRange(i, HIGH_WEIGHT,
|
||||
if (!allowTrafficTo(i, HIGH_WEIGHT,
|
||||
"Allow Ecxlude route", config.m_serverPublicKey)) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -287,37 +384,41 @@ bool WindowsFirewall::disablePeerTraffic(const QString& pubkey) {
|
|||
}
|
||||
|
||||
bool WindowsFirewall::disableKillSwitch() {
|
||||
auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
|
||||
auto cleanup = qScopeGuard([&] {
|
||||
return KillSwitch::instance()->disableKillSwitch();
|
||||
}
|
||||
|
||||
bool WindowsFirewall::allowAllTraffic() {
|
||||
auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
|
||||
auto cleanup = qScopeGuard([&] {
|
||||
if (result != ERROR_SUCCESS) {
|
||||
FwpmTransactionAbort0(m_sessionHandle);
|
||||
}
|
||||
});
|
||||
if (result != ERROR_SUCCESS) {
|
||||
FwpmTransactionAbort0(m_sessionHandle);
|
||||
logger.error() << "FwpmTransactionBegin0 failed. Return value:.\n"
|
||||
<< result;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if (result != ERROR_SUCCESS) {
|
||||
logger.error() << "FwpmTransactionBegin0 failed. Return value:.\n"
|
||||
<< result;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& filterID : m_peerRules.values()) {
|
||||
FwpmFilterDeleteById0(m_sessionHandle, filterID);
|
||||
}
|
||||
for (const auto& filterID : m_peerRules.values()) {
|
||||
FwpmFilterDeleteById0(m_sessionHandle, filterID);
|
||||
}
|
||||
|
||||
for (const auto& filterID : qAsConst(m_activeRules)) {
|
||||
FwpmFilterDeleteById0(m_sessionHandle, filterID);
|
||||
}
|
||||
for (const auto& filterID : qAsConst(m_activeRules)) {
|
||||
FwpmFilterDeleteById0(m_sessionHandle, filterID);
|
||||
}
|
||||
|
||||
// Commit!
|
||||
result = FwpmTransactionCommit0(m_sessionHandle);
|
||||
if (result != ERROR_SUCCESS) {
|
||||
logger.error() << "FwpmTransactionCommit0 failed. Return value:.\n"
|
||||
<< result;
|
||||
return false;
|
||||
}
|
||||
m_peerRules.clear();
|
||||
m_activeRules.clear();
|
||||
logger.debug() << "Firewall Disabled!";
|
||||
return true;
|
||||
// Commit!
|
||||
result = FwpmTransactionCommit0(m_sessionHandle);
|
||||
if (result != ERROR_SUCCESS) {
|
||||
logger.error() << "FwpmTransactionCommit0 failed. Return value:.\n"
|
||||
<< result;
|
||||
return false;
|
||||
}
|
||||
m_peerRules.clear();
|
||||
m_activeRules.clear();
|
||||
logger.debug() << "Firewall Disabled!";
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WindowsFirewall::allowTrafficForAppOnAll(const QString& exePath,
|
||||
|
|
@ -421,9 +522,59 @@ bool WindowsFirewall::allowTrafficOfAdapter(int networkAdapter, uint8_t weight,
|
|||
return true;
|
||||
}
|
||||
|
||||
bool WindowsFirewall::allowTrafficTo(const IPAddress& addr, int weight,
|
||||
const QString& title,
|
||||
const QString& peer) {
|
||||
GUID layerKeyOut;
|
||||
GUID layerKeyIn;
|
||||
if (addr.type() == QAbstractSocket::IPv4Protocol) {
|
||||
layerKeyOut = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
|
||||
layerKeyIn = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4;
|
||||
} else {
|
||||
layerKeyOut = FWPM_LAYER_ALE_AUTH_CONNECT_V6;
|
||||
layerKeyIn = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6;
|
||||
}
|
||||
|
||||
// Match the IP address range.
|
||||
FWPM_FILTER_CONDITION0 cond[1] = {};
|
||||
FWP_RANGE0 ipRange;
|
||||
QByteArray lowIpV6Buffer;
|
||||
QByteArray highIpV6Buffer;
|
||||
|
||||
importAddress(addr.address(), ipRange.valueLow, &lowIpV6Buffer);
|
||||
importAddress(addr.broadcastAddress(), ipRange.valueHigh, &highIpV6Buffer);
|
||||
|
||||
cond[0].fieldKey = FWPM_CONDITION_IP_REMOTE_ADDRESS;
|
||||
cond[0].matchType = FWP_MATCH_RANGE;
|
||||
cond[0].conditionValue.type = FWP_RANGE_TYPE;
|
||||
cond[0].conditionValue.rangeValue = &ipRange;
|
||||
|
||||
// Assemble the Filter base
|
||||
FWPM_FILTER0 filter;
|
||||
memset(&filter, 0, sizeof(filter));
|
||||
filter.action.type = FWP_ACTION_PERMIT;
|
||||
filter.weight.type = FWP_UINT8;
|
||||
filter.weight.uint8 = weight;
|
||||
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
|
||||
filter.numFilterConditions = 1;
|
||||
filter.filterCondition = cond;
|
||||
|
||||
// Send the filters down to the firewall.
|
||||
QString description = "Permit traffic %1 " + addr.toString();
|
||||
filter.layerKey = layerKeyOut;
|
||||
if (!enableFilter(&filter, title, description.arg("to"), peer)) {
|
||||
return false;
|
||||
}
|
||||
filter.layerKey = layerKeyIn;
|
||||
if (!enableFilter(&filter, title, description.arg("from"), peer)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WindowsFirewall::allowTrafficTo(const QHostAddress& targetIP, uint port,
|
||||
int weight, const QString& title,
|
||||
const QString& peer) {
|
||||
int weight, const QString& title,
|
||||
const QString& peer) {
|
||||
bool isIPv4 = targetIP.protocol() == QAbstractSocket::IPv4Protocol;
|
||||
GUID layerOut =
|
||||
isIPv4 ? FWPM_LAYER_ALE_AUTH_CONNECT_V4 : FWPM_LAYER_ALE_AUTH_CONNECT_V6;
|
||||
|
|
@ -484,57 +635,6 @@ bool WindowsFirewall::allowTrafficTo(const QHostAddress& targetIP, uint port,
|
|||
return true;
|
||||
}
|
||||
|
||||
bool WindowsFirewall::allowTrafficToRange(const IPAddress& addr, uint8_t weight,
|
||||
const QString& title,
|
||||
const QString& peer) {
|
||||
QString description("Allow traffic %1 %2 ");
|
||||
|
||||
auto lower = addr.address();
|
||||
auto upper = addr.broadcastAddress();
|
||||
|
||||
const bool isV4 = addr.type() == QAbstractSocket::IPv4Protocol;
|
||||
const GUID layerKeyOut =
|
||||
isV4 ? FWPM_LAYER_ALE_AUTH_CONNECT_V4 : FWPM_LAYER_ALE_AUTH_CONNECT_V6;
|
||||
const GUID layerKeyIn = isV4 ? FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4
|
||||
: FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6;
|
||||
|
||||
// Assemble the Filter base
|
||||
FWPM_FILTER0 filter;
|
||||
memset(&filter, 0, sizeof(filter));
|
||||
filter.action.type = FWP_ACTION_PERMIT;
|
||||
filter.weight.type = FWP_UINT8;
|
||||
filter.weight.uint8 = weight;
|
||||
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
|
||||
|
||||
FWPM_FILTER_CONDITION0 cond[1] = {0};
|
||||
FWP_RANGE0 ipRange;
|
||||
QByteArray lowIpV6Buffer;
|
||||
QByteArray highIpV6Buffer;
|
||||
|
||||
importAddress(lower, ipRange.valueLow, &lowIpV6Buffer);
|
||||
importAddress(upper, ipRange.valueHigh, &highIpV6Buffer);
|
||||
|
||||
cond[0].fieldKey = FWPM_CONDITION_IP_REMOTE_ADDRESS;
|
||||
cond[0].matchType = FWP_MATCH_RANGE;
|
||||
cond[0].conditionValue.type = FWP_RANGE_TYPE;
|
||||
cond[0].conditionValue.rangeValue = &ipRange;
|
||||
|
||||
filter.numFilterConditions = 1;
|
||||
filter.filterCondition = cond;
|
||||
|
||||
filter.layerKey = layerKeyOut;
|
||||
if (!enableFilter(&filter, title, description.arg("to").arg(addr.toString()),
|
||||
peer)) {
|
||||
return false;
|
||||
}
|
||||
filter.layerKey = layerKeyIn;
|
||||
if (!enableFilter(&filter, title,
|
||||
description.arg("from").arg(addr.toString()), peer)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WindowsFirewall::allowDHCPTraffic(uint8_t weight, const QString& title) {
|
||||
// Allow outbound DHCPv4
|
||||
{
|
||||
|
|
@ -734,7 +834,7 @@ bool WindowsFirewall::blockTrafficTo(const IPAddress& addr, uint8_t weight,
|
|||
filter.weight.uint8 = weight;
|
||||
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
|
||||
|
||||
FWPM_FILTER_CONDITION0 cond[1] = {0};
|
||||
FWPM_FILTER_CONDITION0 cond[1] = {};
|
||||
FWP_RANGE0 ipRange;
|
||||
QByteArray lowIpV6Buffer;
|
||||
QByteArray highIpV6Buffer;
|
||||
|
|
|
|||
|
|
@ -26,18 +26,29 @@ struct FWP_CONDITION_VALUE0_;
|
|||
|
||||
class WindowsFirewall final : public QObject {
|
||||
public:
|
||||
~WindowsFirewall();
|
||||
/**
|
||||
* @brief Opens the Windows Filtering Platform, initializes the session,
|
||||
* sublayer. Returns a WindowsFirewall object if successful, otherwise
|
||||
* nullptr. If there is already a WindowsFirewall object, it will be returned.
|
||||
*
|
||||
* @param parent - parent QObject
|
||||
* @return WindowsFirewall* - nullptr if failed to open the Windows Filtering
|
||||
* Platform.
|
||||
*/
|
||||
static WindowsFirewall* create(QObject* parent);
|
||||
~WindowsFirewall() override;
|
||||
|
||||
static WindowsFirewall* instance();
|
||||
bool init();
|
||||
|
||||
bool enableKillSwitch(int vpnAdapterIndex);
|
||||
bool enableInterface(int vpnAdapterIndex);
|
||||
bool enableLanBypass(const QList<IPAddress>& ranges);
|
||||
bool enablePeerTraffic(const InterfaceConfig& config);
|
||||
bool disablePeerTraffic(const QString& pubkey);
|
||||
bool disableKillSwitch();
|
||||
bool allowAllTraffic();
|
||||
bool allowTrafficRange(const QStringList& ranges);
|
||||
|
||||
private:
|
||||
WindowsFirewall(QObject* parent);
|
||||
static bool initSublayer();
|
||||
WindowsFirewall(HANDLE session, QObject* parent);
|
||||
HANDLE m_sessionHandle;
|
||||
bool m_init = false;
|
||||
QList<uint64_t> m_activeRules;
|
||||
|
|
@ -50,11 +61,10 @@ class WindowsFirewall final : public QObject {
|
|||
bool blockTrafficTo(const IPAddress& addr, uint8_t weight,
|
||||
const QString& title, const QString& peer = QString());
|
||||
bool blockTrafficOnPort(uint port, uint8_t weight, const QString& title);
|
||||
bool allowTrafficTo(const IPAddress& addr, int weight, const QString& title,
|
||||
const QString& peer = QString());
|
||||
bool allowTrafficTo(const QHostAddress& targetIP, uint port, int weight,
|
||||
const QString& title, const QString& peer = QString());
|
||||
bool allowTrafficToRange(const IPAddress& addr, uint8_t weight,
|
||||
const QString& title,
|
||||
const QString& peer);
|
||||
bool allowTrafficOfAdapter(int networkAdapter, uint8_t weight,
|
||||
const QString& title);
|
||||
bool allowDHCPTraffic(uint8_t weight, const QString& title);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,12 @@ namespace {
|
|||
Logger logger("WindowsRouteMonitor");
|
||||
}; // namespace
|
||||
|
||||
// Attempt to mark routing entries that we create with a relatively
|
||||
// high metric. This ensures that we can skip over routes of our own
|
||||
// creation when processing route changes, and ensures that we give
|
||||
// way to other routing entries.
|
||||
constexpr const ULONG EXCLUSION_ROUTE_METRIC = 0x5e72;
|
||||
|
||||
// Called by the kernel on route changes - perform some basic filtering and
|
||||
// invoke the routeChanged slot to do the real work.
|
||||
static void routeChangeCallback(PVOID context, PMIB_IPFORWARD_ROW2 row,
|
||||
|
|
@ -20,22 +26,17 @@ static void routeChangeCallback(PVOID context, PMIB_IPFORWARD_ROW2 row,
|
|||
WindowsRouteMonitor* monitor = (WindowsRouteMonitor*)context;
|
||||
Q_UNUSED(type);
|
||||
|
||||
// Ignore host route changes, and unsupported protocols.
|
||||
if (row->DestinationPrefix.Prefix.si_family == AF_INET6) {
|
||||
if (row->DestinationPrefix.PrefixLength >= 128) {
|
||||
return;
|
||||
}
|
||||
} else if (row->DestinationPrefix.Prefix.si_family == AF_INET) {
|
||||
if (row->DestinationPrefix.PrefixLength >= 32) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Ignore route changes that we created.
|
||||
if ((row->Protocol == MIB_IPPROTO_NETMGMT) &&
|
||||
(row->Metric == EXCLUSION_ROUTE_METRIC)) {
|
||||
return;
|
||||
}
|
||||
if (monitor->getLuid() == row->InterfaceLuid.Value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (monitor->getLuid() != row->InterfaceLuid.Value) {
|
||||
QMetaObject::invokeMethod(monitor, "routeChanged", Qt::QueuedConnection);
|
||||
}
|
||||
// Invoke the route changed signal to do the real work in Qt.
|
||||
QMetaObject::invokeMethod(monitor, "routeChanged", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
// Perform prefix matching comparison on IP addresses in host order.
|
||||
|
|
@ -57,7 +58,8 @@ static int prefixcmp(const void* a, const void* b, size_t bits) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
WindowsRouteMonitor::WindowsRouteMonitor(QObject* parent) : QObject(parent) {
|
||||
WindowsRouteMonitor::WindowsRouteMonitor(quint64 luid, QObject* parent)
|
||||
: QObject(parent), m_luid(luid) {
|
||||
MZ_COUNT_CTOR(WindowsRouteMonitor);
|
||||
logger.debug() << "WindowsRouteMonitor created.";
|
||||
|
||||
|
|
@ -67,11 +69,13 @@ WindowsRouteMonitor::WindowsRouteMonitor(QObject* parent) : QObject(parent) {
|
|||
WindowsRouteMonitor::~WindowsRouteMonitor() {
|
||||
MZ_COUNT_DTOR(WindowsRouteMonitor);
|
||||
CancelMibChangeNotify2(m_routeHandle);
|
||||
flushExclusionRoutes();
|
||||
|
||||
flushRouteTable(m_exclusionRoutes);
|
||||
flushRouteTable(m_clonedRoutes);
|
||||
logger.debug() << "WindowsRouteMonitor destroyed.";
|
||||
}
|
||||
|
||||
void WindowsRouteMonitor::updateValidInterfaces(int family) {
|
||||
void WindowsRouteMonitor::updateInterfaceMetrics(int family) {
|
||||
PMIB_IPINTERFACE_TABLE table;
|
||||
DWORD result = GetIpInterfaceTable(family, &table);
|
||||
if (result != NO_ERROR) {
|
||||
|
|
@ -82,10 +86,10 @@ void WindowsRouteMonitor::updateValidInterfaces(int family) {
|
|||
|
||||
// Flush the list of interfaces that are valid for routing.
|
||||
if ((family == AF_INET) || (family == AF_UNSPEC)) {
|
||||
m_validInterfacesIpv4.clear();
|
||||
m_interfaceMetricsIpv4.clear();
|
||||
}
|
||||
if ((family == AF_INET6) || (family == AF_UNSPEC)) {
|
||||
m_validInterfacesIpv6.clear();
|
||||
m_interfaceMetricsIpv6.clear();
|
||||
}
|
||||
|
||||
// Rebuild the list of interfaces that are valid for routing.
|
||||
|
|
@ -101,12 +105,12 @@ void WindowsRouteMonitor::updateValidInterfaces(int family) {
|
|||
if (row->Family == AF_INET) {
|
||||
logger.debug() << "Interface" << row->InterfaceIndex
|
||||
<< "is valid for IPv4 routing";
|
||||
m_validInterfacesIpv4.append(row->InterfaceLuid.Value);
|
||||
m_interfaceMetricsIpv4[row->InterfaceLuid.Value] = row->Metric;
|
||||
}
|
||||
if (row->Family == AF_INET6) {
|
||||
logger.debug() << "Interface" << row->InterfaceIndex
|
||||
<< "is valid for IPv6 routing";
|
||||
m_validInterfacesIpv6.append(row->InterfaceLuid.Value);
|
||||
m_interfaceMetricsIpv6[row->InterfaceLuid.Value] = row->Metric;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -126,72 +130,72 @@ void WindowsRouteMonitor::updateExclusionRoute(MIB_IPFORWARD_ROW2* data,
|
|||
if (row->InterfaceLuid.Value == m_luid) {
|
||||
continue;
|
||||
}
|
||||
// Ignore host routes, and shorter potential matches.
|
||||
if (row->DestinationPrefix.PrefixLength >=
|
||||
data->DestinationPrefix.PrefixLength) {
|
||||
if (row->DestinationPrefix.PrefixLength < bestMatch) {
|
||||
continue;
|
||||
}
|
||||
if (row->DestinationPrefix.PrefixLength < bestMatch) {
|
||||
// Ignore routes of our own creation.
|
||||
if ((row->Protocol == data->Protocol) && (row->Metric == data->Metric)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the routing table entry matches the destination.
|
||||
if (!routeContainsDest(&row->DestinationPrefix, &data->DestinationPrefix)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Compute the combined interface and routing metric.
|
||||
ULONG routeMetric = row->Metric;
|
||||
if (data->DestinationPrefix.Prefix.si_family == AF_INET6) {
|
||||
if (row->DestinationPrefix.Prefix.Ipv6.sin6_family != AF_INET6) {
|
||||
continue;
|
||||
}
|
||||
if (!m_validInterfacesIpv6.contains(row->InterfaceLuid.Value)) {
|
||||
continue;
|
||||
}
|
||||
if (prefixcmp(&data->DestinationPrefix.Prefix.Ipv6.sin6_addr,
|
||||
&row->DestinationPrefix.Prefix.Ipv6.sin6_addr,
|
||||
row->DestinationPrefix.PrefixLength) != 0) {
|
||||
if (!m_interfaceMetricsIpv6.contains(row->InterfaceLuid.Value)) {
|
||||
continue;
|
||||
}
|
||||
routeMetric += m_interfaceMetricsIpv6[row->InterfaceLuid.Value];
|
||||
} else if (data->DestinationPrefix.Prefix.si_family == AF_INET) {
|
||||
if (row->DestinationPrefix.Prefix.Ipv4.sin_family != AF_INET) {
|
||||
continue;
|
||||
}
|
||||
if (!m_validInterfacesIpv4.contains(row->InterfaceLuid.Value)) {
|
||||
continue;
|
||||
}
|
||||
if (prefixcmp(&data->DestinationPrefix.Prefix.Ipv4.sin_addr,
|
||||
&row->DestinationPrefix.Prefix.Ipv4.sin_addr,
|
||||
row->DestinationPrefix.PrefixLength) != 0) {
|
||||
if (!m_interfaceMetricsIpv4.contains(row->InterfaceLuid.Value)) {
|
||||
continue;
|
||||
}
|
||||
routeMetric += m_interfaceMetricsIpv4[row->InterfaceLuid.Value];
|
||||
} else {
|
||||
// Unsupported destination address family.
|
||||
continue;
|
||||
}
|
||||
if (routeMetric < row->Metric) {
|
||||
routeMetric = ULONG_MAX;
|
||||
}
|
||||
|
||||
// Prefer routes with lower metric if we find multiple matches
|
||||
// with the same prefix length.
|
||||
if ((row->DestinationPrefix.PrefixLength == bestMatch) &&
|
||||
(row->Metric >= bestMetric)) {
|
||||
(routeMetric >= bestMetric)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we got here, then this is the longest prefix match so far.
|
||||
memcpy(&nexthop, &row->NextHop, sizeof(SOCKADDR_INET));
|
||||
bestLuid = row->InterfaceLuid.Value;
|
||||
bestMatch = row->DestinationPrefix.PrefixLength;
|
||||
bestMetric = row->Metric;
|
||||
bestMetric = routeMetric;
|
||||
if (bestMatch == data->DestinationPrefix.PrefixLength) {
|
||||
bestLuid = 0; // Don't write to the table if we find an exact match.
|
||||
} else {
|
||||
bestLuid = row->InterfaceLuid.Value;
|
||||
}
|
||||
}
|
||||
|
||||
// If neither the interface nor next-hop have changed, then do nothing.
|
||||
if ((data->InterfaceLuid.Value) == bestLuid &&
|
||||
if (data->InterfaceLuid.Value == bestLuid &&
|
||||
memcmp(&nexthop, &data->NextHop, sizeof(SOCKADDR_INET)) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the routing table entry.
|
||||
// Delete the previous routing table entry, if any.
|
||||
if (data->InterfaceLuid.Value != 0) {
|
||||
DWORD result = DeleteIpForwardEntry2(data);
|
||||
if ((result != NO_ERROR) && (result != ERROR_NOT_FOUND)) {
|
||||
logger.error() << "Failed to delete route:" << result;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the routing table entry.
|
||||
data->InterfaceLuid.Value = bestLuid;
|
||||
memcpy(&data->NextHop, &nexthop, sizeof(SOCKADDR_INET));
|
||||
if (data->InterfaceLuid.Value != 0) {
|
||||
|
|
@ -202,9 +206,174 @@ void WindowsRouteMonitor::updateExclusionRoute(MIB_IPFORWARD_ROW2* data,
|
|||
}
|
||||
}
|
||||
|
||||
// static
|
||||
bool WindowsRouteMonitor::routeContainsDest(const IP_ADDRESS_PREFIX* route,
|
||||
const IP_ADDRESS_PREFIX* dest) {
|
||||
if (route->Prefix.si_family != dest->Prefix.si_family) {
|
||||
return false;
|
||||
}
|
||||
if (route->PrefixLength > dest->PrefixLength) {
|
||||
return false;
|
||||
}
|
||||
if (route->Prefix.si_family == AF_INET) {
|
||||
return prefixcmp(&route->Prefix.Ipv4.sin_addr, &dest->Prefix.Ipv4.sin_addr,
|
||||
route->PrefixLength) == 0;
|
||||
} else if (route->Prefix.si_family == AF_INET6) {
|
||||
return prefixcmp(&route->Prefix.Ipv6.sin6_addr,
|
||||
&dest->Prefix.Ipv6.sin6_addr, route->PrefixLength) == 0;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
QHostAddress WindowsRouteMonitor::prefixToAddress(
|
||||
const IP_ADDRESS_PREFIX* dest) {
|
||||
if (dest->Prefix.si_family == AF_INET6) {
|
||||
return QHostAddress(dest->Prefix.Ipv6.sin6_addr.s6_addr);
|
||||
} else if (dest->Prefix.si_family == AF_INET) {
|
||||
quint32 addr = htonl(dest->Prefix.Ipv4.sin_addr.s_addr);
|
||||
return QHostAddress(addr);
|
||||
} else {
|
||||
return QHostAddress();
|
||||
}
|
||||
}
|
||||
|
||||
bool WindowsRouteMonitor::isRouteExcluded(const IP_ADDRESS_PREFIX* dest) const {
|
||||
auto i = m_exclusionRoutes.constBegin();
|
||||
while (i != m_exclusionRoutes.constEnd()) {
|
||||
const MIB_IPFORWARD_ROW2* row = i.value();
|
||||
if (routeContainsDest(&row->DestinationPrefix, dest)) {
|
||||
return true;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void WindowsRouteMonitor::updateCapturedRoutes(int family) {
|
||||
if (!m_defaultRouteCapture) {
|
||||
return;
|
||||
}
|
||||
|
||||
PMIB_IPFORWARD_TABLE2 table;
|
||||
DWORD error = GetIpForwardTable2(family, &table);
|
||||
if (error != NO_ERROR) {
|
||||
updateCapturedRoutes(family, table);
|
||||
FreeMibTable(table);
|
||||
}
|
||||
}
|
||||
|
||||
void WindowsRouteMonitor::updateCapturedRoutes(int family, void* ptable) {
|
||||
PMIB_IPFORWARD_TABLE2 table = reinterpret_cast<PMIB_IPFORWARD_TABLE2>(ptable);
|
||||
if (!m_defaultRouteCapture) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (ULONG i = 0; i < table->NumEntries; i++) {
|
||||
MIB_IPFORWARD_ROW2* row = &table->Table[i];
|
||||
// Ignore routes into the VPN interface.
|
||||
if (row->InterfaceLuid.Value == m_luid) {
|
||||
continue;
|
||||
}
|
||||
// Ignore the default route
|
||||
if (row->DestinationPrefix.PrefixLength == 0) {
|
||||
continue;
|
||||
}
|
||||
// Ignore routes of our own creation.
|
||||
if ((row->Protocol == MIB_IPPROTO_NETMGMT) &&
|
||||
(row->Metric == EXCLUSION_ROUTE_METRIC)) {
|
||||
continue;
|
||||
}
|
||||
// Ignore routes which should be excluded.
|
||||
if (isRouteExcluded(&row->DestinationPrefix)) {
|
||||
continue;
|
||||
}
|
||||
QHostAddress destination = prefixToAddress(&row->DestinationPrefix);
|
||||
if (destination.isLoopback() || destination.isBroadcast() ||
|
||||
destination.isLinkLocal() || destination.isMulticast()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we get here, this route should be cloned.
|
||||
IPAddress prefix(destination, row->DestinationPrefix.PrefixLength);
|
||||
MIB_IPFORWARD_ROW2* data = m_clonedRoutes.value(prefix, nullptr);
|
||||
if (data != nullptr) {
|
||||
// Count the number of matching entries in the main table.
|
||||
data->Age++;
|
||||
continue;
|
||||
}
|
||||
logger.debug() << "Capturing route to" << prefix.toString();
|
||||
|
||||
// Clone the route and direct it into the VPN tunnel.
|
||||
data = new MIB_IPFORWARD_ROW2;
|
||||
InitializeIpForwardEntry(data);
|
||||
data->InterfaceLuid.Value = m_luid;
|
||||
data->DestinationPrefix = row->DestinationPrefix;
|
||||
data->NextHop.si_family = data->DestinationPrefix.Prefix.si_family;
|
||||
|
||||
// Set the rest of the flags for a static route.
|
||||
data->ValidLifetime = 0xffffffff;
|
||||
data->PreferredLifetime = 0xffffffff;
|
||||
data->Metric = 0;
|
||||
data->Protocol = MIB_IPPROTO_NETMGMT;
|
||||
data->Loopback = false;
|
||||
data->AutoconfigureAddress = false;
|
||||
data->Publish = false;
|
||||
data->Immortal = false;
|
||||
data->Age = 0;
|
||||
|
||||
// Route this traffic into the VPN tunnel.
|
||||
DWORD result = CreateIpForwardEntry2(data);
|
||||
if (result != NO_ERROR) {
|
||||
logger.error() << "Failed to update route:" << result;
|
||||
delete data;
|
||||
} else {
|
||||
m_clonedRoutes.insert(prefix, data);
|
||||
data->Age++;
|
||||
}
|
||||
}
|
||||
|
||||
// Finally scan for any routes which were removed from the table. We do this
|
||||
// by reusing the age field to count the number of matching entries in the
|
||||
// main table.
|
||||
auto i = m_clonedRoutes.begin();
|
||||
while (i != m_clonedRoutes.end()) {
|
||||
MIB_IPFORWARD_ROW2* data = i.value();
|
||||
if (data->Age > 0) {
|
||||
// Entry is in use, don't delete it.
|
||||
data->Age = 0;
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
if ((family != AF_UNSPEC) &&
|
||||
(data->DestinationPrefix.Prefix.si_family != family)) {
|
||||
// We are not processing updates to this address family.
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
logger.debug() << "Removing route capture for" << i.key().toString();
|
||||
|
||||
// Otherwise, this route is no longer in use.
|
||||
DWORD result = DeleteIpForwardEntry2(data);
|
||||
if ((result != NO_ERROR) && (result != ERROR_NOT_FOUND)) {
|
||||
logger.error() << "Failed to delete route:" << result;
|
||||
}
|
||||
delete data;
|
||||
i = m_clonedRoutes.erase(i);
|
||||
}
|
||||
}
|
||||
|
||||
bool WindowsRouteMonitor::addExclusionRoute(const IPAddress& prefix) {
|
||||
logger.debug() << "Adding exclusion route for"
|
||||
<< logger.sensitive(prefix.toString());
|
||||
logger.debug() << "Adding exclusion route for" << prefix.toString();
|
||||
|
||||
// Silently ignore non-routeable addresses.
|
||||
QHostAddress addr = prefix.address();
|
||||
if (addr.isLoopback() || addr.isBroadcast() || addr.isLinkLocal() ||
|
||||
addr.isMulticast()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_exclusionRoutes.contains(prefix)) {
|
||||
logger.warning() << "Exclusion route already exists";
|
||||
|
|
@ -232,7 +401,7 @@ bool WindowsRouteMonitor::addExclusionRoute(const IPAddress& prefix) {
|
|||
// Set the rest of the flags for a static route.
|
||||
data->ValidLifetime = 0xffffffff;
|
||||
data->PreferredLifetime = 0xffffffff;
|
||||
data->Metric = 0;
|
||||
data->Metric = EXCLUSION_ROUTE_METRIC;
|
||||
data->Protocol = MIB_IPPROTO_NETMGMT;
|
||||
data->Loopback = false;
|
||||
data->AutoconfigureAddress = false;
|
||||
|
|
@ -254,7 +423,8 @@ bool WindowsRouteMonitor::addExclusionRoute(const IPAddress& prefix) {
|
|||
delete data;
|
||||
return false;
|
||||
}
|
||||
updateValidInterfaces(family);
|
||||
updateInterfaceMetrics(family);
|
||||
updateCapturedRoutes(family, table);
|
||||
updateExclusionRoute(data, table);
|
||||
FreeMibTable(table);
|
||||
|
||||
|
|
@ -264,38 +434,50 @@ bool WindowsRouteMonitor::addExclusionRoute(const IPAddress& prefix) {
|
|||
|
||||
bool WindowsRouteMonitor::deleteExclusionRoute(const IPAddress& prefix) {
|
||||
logger.debug() << "Deleting exclusion route for"
|
||||
<< logger.sensitive(prefix.address().toString());
|
||||
<< prefix.address().toString();
|
||||
|
||||
for (;;) {
|
||||
MIB_IPFORWARD_ROW2* data = m_exclusionRoutes.take(prefix);
|
||||
if (data == nullptr) {
|
||||
break;
|
||||
}
|
||||
|
||||
DWORD result = DeleteIpForwardEntry2(data);
|
||||
if ((result != ERROR_NOT_FOUND) && (result != NO_ERROR)) {
|
||||
logger.error() << "Failed to delete route to"
|
||||
<< logger.sensitive(prefix.toString())
|
||||
<< "result:" << result;
|
||||
}
|
||||
delete data;
|
||||
MIB_IPFORWARD_ROW2* data = m_exclusionRoutes.take(prefix);
|
||||
if (data == nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
DWORD result = DeleteIpForwardEntry2(data);
|
||||
if ((result != ERROR_NOT_FOUND) && (result != NO_ERROR)) {
|
||||
logger.error() << "Failed to delete route to"
|
||||
<< prefix.toString()
|
||||
<< "result:" << result;
|
||||
}
|
||||
|
||||
// Captured routes might have changed.
|
||||
updateCapturedRoutes(data->DestinationPrefix.Prefix.si_family);
|
||||
|
||||
delete data;
|
||||
return true;
|
||||
}
|
||||
|
||||
void WindowsRouteMonitor::flushExclusionRoutes() {
|
||||
for (auto i = m_exclusionRoutes.begin(); i != m_exclusionRoutes.end(); i++) {
|
||||
void WindowsRouteMonitor::flushRouteTable(
|
||||
QHash<IPAddress, MIB_IPFORWARD_ROW2*>& table) {
|
||||
for (auto i = table.begin(); i != table.end(); i++) {
|
||||
MIB_IPFORWARD_ROW2* data = i.value();
|
||||
DWORD result = DeleteIpForwardEntry2(data);
|
||||
if ((result != ERROR_NOT_FOUND) && (result != NO_ERROR)) {
|
||||
logger.error() << "Failed to delete route to"
|
||||
<< logger.sensitive(i.key().toString())
|
||||
<< i.key().toString()
|
||||
<< "result:" << result;
|
||||
}
|
||||
delete data;
|
||||
}
|
||||
m_exclusionRoutes.clear();
|
||||
table.clear();
|
||||
}
|
||||
|
||||
void WindowsRouteMonitor::setDetaultRouteCapture(bool enable) {
|
||||
m_defaultRouteCapture = enable;
|
||||
|
||||
// Flush any captured routes when disabling the feature.
|
||||
if (!m_defaultRouteCapture) {
|
||||
flushRouteTable(m_clonedRoutes);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void WindowsRouteMonitor::routeChanged() {
|
||||
|
|
@ -308,7 +490,8 @@ void WindowsRouteMonitor::routeChanged() {
|
|||
return;
|
||||
}
|
||||
|
||||
updateValidInterfaces(AF_UNSPEC);
|
||||
updateInterfaceMetrics(AF_UNSPEC);
|
||||
updateCapturedRoutes(AF_UNSPEC, table);
|
||||
for (MIB_IPFORWARD_ROW2* data : m_exclusionRoutes) {
|
||||
updateExclusionRoute(data, table);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@
|
|||
#include <winsock2.h>
|
||||
#include <ws2ipdef.h>
|
||||
|
||||
#include <QHash>
|
||||
#include <QMap>
|
||||
#include <QObject>
|
||||
|
||||
#include "ipaddress.h"
|
||||
|
|
@ -19,28 +21,41 @@ class WindowsRouteMonitor final : public QObject {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
WindowsRouteMonitor(QObject* parent);
|
||||
WindowsRouteMonitor(quint64 luid, QObject* parent);
|
||||
~WindowsRouteMonitor();
|
||||
|
||||
void setDetaultRouteCapture(bool enable);
|
||||
|
||||
bool addExclusionRoute(const IPAddress& prefix);
|
||||
bool deleteExclusionRoute(const IPAddress& prefix);
|
||||
void flushExclusionRoutes();
|
||||
void flushExclusionRoutes() { return flushRouteTable(m_exclusionRoutes); };
|
||||
|
||||
void setLuid(quint64 luid) { m_luid = luid; }
|
||||
quint64 getLuid() { return m_luid; }
|
||||
quint64 getLuid() const { return m_luid; }
|
||||
|
||||
public slots:
|
||||
void routeChanged();
|
||||
|
||||
private:
|
||||
bool isRouteExcluded(const IP_ADDRESS_PREFIX* dest) const;
|
||||
static bool routeContainsDest(const IP_ADDRESS_PREFIX* route,
|
||||
const IP_ADDRESS_PREFIX* dest);
|
||||
static QHostAddress prefixToAddress(const IP_ADDRESS_PREFIX* dest);
|
||||
|
||||
void flushRouteTable(QHash<IPAddress, MIB_IPFORWARD_ROW2*>& table);
|
||||
void updateExclusionRoute(MIB_IPFORWARD_ROW2* data, void* table);
|
||||
void updateValidInterfaces(int family);
|
||||
void updateInterfaceMetrics(int family);
|
||||
void updateCapturedRoutes(int family);
|
||||
void updateCapturedRoutes(int family, void* table);
|
||||
|
||||
QHash<IPAddress, MIB_IPFORWARD_ROW2*> m_exclusionRoutes;
|
||||
QList<quint64> m_validInterfacesIpv4;
|
||||
QList<quint64> m_validInterfacesIpv6;
|
||||
QMap<quint64, ULONG> m_interfaceMetricsIpv4;
|
||||
QMap<quint64, ULONG> m_interfaceMetricsIpv6;
|
||||
|
||||
quint64 m_luid = 0;
|
||||
// Default route cloning
|
||||
bool m_defaultRouteCapture = false;
|
||||
QHash<IPAddress, MIB_IPFORWARD_ROW2*> m_clonedRoutes;
|
||||
|
||||
const quint64 m_luid = 0;
|
||||
HANDLE m_routeHandle = INVALID_HANDLE_VALUE;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -4,9 +4,15 @@
|
|||
|
||||
#include "windowssplittunnel.h"
|
||||
|
||||
#include <qassert.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "../windowscommons.h"
|
||||
#include "../windowsservicemanager.h"
|
||||
#include "logger.h"
|
||||
#include "platforms/windows/daemon/windowsfirewall.h"
|
||||
#include "platforms/windows/daemon/windowssplittunnel.h"
|
||||
#include "platforms/windows/windowsutils.h"
|
||||
#include "windowsfirewall.h"
|
||||
|
||||
|
|
@ -18,34 +24,252 @@
|
|||
#include <QFileInfo>
|
||||
#include <QNetworkInterface>
|
||||
#include <QScopeGuard>
|
||||
#include <QThread>
|
||||
|
||||
#pragma region
|
||||
|
||||
// Driver Configuration structures
|
||||
using CONFIGURATION_ENTRY = struct {
|
||||
// Offset into buffer region that follows all entries.
|
||||
// The image name uses the device path.
|
||||
SIZE_T ImageNameOffset;
|
||||
// Length of the String
|
||||
USHORT ImageNameLength;
|
||||
};
|
||||
|
||||
using CONFIGURATION_HEADER = struct {
|
||||
// Number of entries immediately following the header.
|
||||
SIZE_T NumEntries;
|
||||
|
||||
// Total byte length: header + entries + string buffer.
|
||||
SIZE_T TotalLength;
|
||||
};
|
||||
|
||||
// Used to Configure Which IP is network/vpn
|
||||
using IP_ADDRESSES_CONFIG = struct {
|
||||
IN_ADDR TunnelIpv4;
|
||||
IN_ADDR InternetIpv4;
|
||||
|
||||
IN6_ADDR TunnelIpv6;
|
||||
IN6_ADDR InternetIpv6;
|
||||
};
|
||||
|
||||
// Used to Define Which Processes are alive on activation
|
||||
using PROCESS_DISCOVERY_HEADER = struct {
|
||||
SIZE_T NumEntries;
|
||||
SIZE_T TotalLength;
|
||||
};
|
||||
|
||||
using PROCESS_DISCOVERY_ENTRY = struct {
|
||||
HANDLE ProcessId;
|
||||
HANDLE ParentProcessId;
|
||||
|
||||
SIZE_T ImageNameOffset;
|
||||
USHORT ImageNameLength;
|
||||
};
|
||||
|
||||
using ProcessInfo = struct {
|
||||
DWORD ProcessId;
|
||||
DWORD ParentProcessId;
|
||||
FILETIME CreationTime;
|
||||
std::wstring DevicePath;
|
||||
};
|
||||
|
||||
#ifndef CTL_CODE
|
||||
|
||||
# define FILE_ANY_ACCESS 0x0000
|
||||
|
||||
# define METHOD_BUFFERED 0
|
||||
# define METHOD_IN_DIRECT 1
|
||||
# define METHOD_NEITHER 3
|
||||
|
||||
# define CTL_CODE(DeviceType, Function, Method, Access) \
|
||||
(((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method))
|
||||
#endif
|
||||
|
||||
// Known ControlCodes
|
||||
#define IOCTL_INITIALIZE CTL_CODE(0x8000, 1, METHOD_NEITHER, FILE_ANY_ACCESS)
|
||||
|
||||
#define IOCTL_DEQUEUE_EVENT \
|
||||
CTL_CODE(0x8000, 2, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
#define IOCTL_REGISTER_PROCESSES \
|
||||
CTL_CODE(0x8000, 3, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
#define IOCTL_REGISTER_IP_ADDRESSES \
|
||||
CTL_CODE(0x8000, 4, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
#define IOCTL_GET_IP_ADDRESSES \
|
||||
CTL_CODE(0x8000, 5, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
#define IOCTL_SET_CONFIGURATION \
|
||||
CTL_CODE(0x8000, 6, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
#define IOCTL_GET_CONFIGURATION \
|
||||
CTL_CODE(0x8000, 7, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
#define IOCTL_CLEAR_CONFIGURATION \
|
||||
CTL_CODE(0x8000, 8, METHOD_NEITHER, FILE_ANY_ACCESS)
|
||||
|
||||
#define IOCTL_GET_STATE CTL_CODE(0x8000, 9, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
#define IOCTL_QUERY_PROCESS \
|
||||
CTL_CODE(0x8000, 10, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
#define IOCTL_ST_RESET CTL_CODE(0x8000, 11, METHOD_NEITHER, FILE_ANY_ACCESS)
|
||||
|
||||
constexpr static const auto DRIVER_SYMLINK = L"\\\\.\\MULLVADSPLITTUNNEL";
|
||||
constexpr static const auto DRIVER_FILENAME = "mullvad-split-tunnel.sys";
|
||||
constexpr static const auto DRIVER_SERVICE_NAME = L"AmneziaVPNSplitTunnel";
|
||||
constexpr static const auto MV_SERVICE_NAME = L"MullvadVPN";
|
||||
|
||||
#pragma endregion
|
||||
|
||||
namespace {
|
||||
Logger logger("WindowsSplitTunnel");
|
||||
|
||||
ProcessInfo getProcessInfo(HANDLE process, const PROCESSENTRY32W& processMeta) {
|
||||
ProcessInfo pi;
|
||||
pi.ParentProcessId = processMeta.th32ParentProcessID;
|
||||
pi.ProcessId = processMeta.th32ProcessID;
|
||||
pi.CreationTime = {0, 0};
|
||||
pi.DevicePath = L"";
|
||||
|
||||
FILETIME creationTime, null_time;
|
||||
auto ok = GetProcessTimes(process, &creationTime, &null_time, &null_time,
|
||||
&null_time);
|
||||
if (ok) {
|
||||
pi.CreationTime = creationTime;
|
||||
}
|
||||
wchar_t imagepath[MAX_PATH + 1];
|
||||
if (K32GetProcessImageFileNameW(
|
||||
process, imagepath, sizeof(imagepath) / sizeof(*imagepath)) != 0) {
|
||||
pi.DevicePath = imagepath;
|
||||
}
|
||||
return pi;
|
||||
}
|
||||
|
||||
WindowsSplitTunnel::WindowsSplitTunnel(QObject* parent) : QObject(parent) {
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<WindowsSplitTunnel> WindowsSplitTunnel::create(
|
||||
WindowsFirewall* fw) {
|
||||
if (fw == nullptr) {
|
||||
// Pre-Condition:
|
||||
// Make sure the Windows Firewall has created the sublayer
|
||||
// otherwise the driver will fail to initialize
|
||||
logger.error() << "Failed to did not pass a WindowsFirewall obj"
|
||||
<< "The Driver cannot work with the sublayer not created";
|
||||
return nullptr;
|
||||
}
|
||||
// 00: Check if we conflict with mullvad, if so.
|
||||
if (detectConflict()) {
|
||||
logger.error() << "Conflict detected, abort Split-Tunnel init.";
|
||||
uninstallDriver();
|
||||
return;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
m_tries = 0;
|
||||
|
||||
// 01: Check if the driver is installed, if not do so.
|
||||
if (!isInstalled()) {
|
||||
logger.debug() << "Driver is not Installed, doing so";
|
||||
auto handle = installDriver();
|
||||
if (handle == INVALID_HANDLE_VALUE) {
|
||||
WindowsUtils::windowsLog("Failed to install Driver");
|
||||
return;
|
||||
return nullptr;
|
||||
}
|
||||
logger.debug() << "Driver installed";
|
||||
CloseServiceHandle(handle);
|
||||
} else {
|
||||
logger.debug() << "Driver is installed";
|
||||
logger.debug() << "Driver was installed";
|
||||
}
|
||||
initDriver();
|
||||
// 02: Now check if the service is running
|
||||
auto driver_manager =
|
||||
WindowsServiceManager::open(QString::fromWCharArray(DRIVER_SERVICE_NAME));
|
||||
if (Q_UNLIKELY(driver_manager == nullptr)) {
|
||||
// Let's be fair if we end up here,
|
||||
// after checking it exists and installing it,
|
||||
// this is super unlikeley
|
||||
Q_ASSERT(false);
|
||||
logger.error()
|
||||
<< "WindowsServiceManager was unable fo find Split Tunnel service?";
|
||||
return nullptr;
|
||||
}
|
||||
if (!driver_manager->isRunning()) {
|
||||
logger.debug() << "Driver is not running, starting it";
|
||||
// Start the service
|
||||
if (!driver_manager->startService()) {
|
||||
logger.error() << "Failed to start Split Tunnel Service";
|
||||
return nullptr;
|
||||
};
|
||||
}
|
||||
// 03: Open the Driver Symlink
|
||||
auto driverFile = CreateFileW(DRIVER_SYMLINK, GENERIC_READ | GENERIC_WRITE, 0,
|
||||
nullptr, OPEN_EXISTING, 0, nullptr);
|
||||
;
|
||||
if (driverFile == INVALID_HANDLE_VALUE) {
|
||||
WindowsUtils::windowsLog("Failed to open Driver: ");
|
||||
// Only once, if the opening did not work. Try to reboot it. #
|
||||
logger.info()
|
||||
<< "Failed to open driver, attempting only once to reboot driver";
|
||||
if (!driver_manager->stopService()) {
|
||||
logger.error() << "Unable stop driver";
|
||||
return nullptr;
|
||||
};
|
||||
logger.info() << "Stopped driver, starting it again.";
|
||||
if (!driver_manager->startService()) {
|
||||
logger.error() << "Unable start driver";
|
||||
return nullptr;
|
||||
};
|
||||
logger.info() << "Opening again.";
|
||||
driverFile = CreateFileW(DRIVER_SYMLINK, GENERIC_READ | GENERIC_WRITE, 0,
|
||||
nullptr, OPEN_EXISTING, 0, nullptr);
|
||||
if (driverFile == INVALID_HANDLE_VALUE) {
|
||||
logger.error() << "Opening Failed again, sorry!";
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
if (!initDriver(driverFile)) {
|
||||
logger.error() << "Failed to init driver";
|
||||
return nullptr;
|
||||
}
|
||||
// We're ready to talk to the driver, it's alive and setup.
|
||||
return std::make_unique<WindowsSplitTunnel>(driverFile);
|
||||
}
|
||||
|
||||
bool WindowsSplitTunnel::initDriver(HANDLE driverIO) {
|
||||
// We need to now check the state and init it, if required
|
||||
auto state = getState(driverIO);
|
||||
if (state == STATE_UNKNOWN) {
|
||||
logger.debug() << "Cannot check if driver is initialized";
|
||||
return false;
|
||||
}
|
||||
if (state >= STATE_INITIALIZED) {
|
||||
logger.debug() << "Driver already initialized: " << state;
|
||||
// Reset Driver as it has wfp handles probably >:(
|
||||
resetDriver(driverIO);
|
||||
|
||||
auto newState = getState(driverIO);
|
||||
logger.debug() << "New state after reset:" << newState;
|
||||
if (newState >= STATE_INITIALIZED) {
|
||||
logger.debug() << "Reset unsuccesfull";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
DWORD bytesReturned;
|
||||
auto ok = DeviceIoControl(driverIO, IOCTL_INITIALIZE, nullptr, 0, nullptr, 0,
|
||||
&bytesReturned, nullptr);
|
||||
if (!ok) {
|
||||
auto err = GetLastError();
|
||||
logger.error() << "Driver init failed err -" << err;
|
||||
logger.error() << "State:" << getState(driverIO);
|
||||
|
||||
return false;
|
||||
}
|
||||
logger.debug() << "Driver initialized" << getState(driverIO);
|
||||
return true;
|
||||
}
|
||||
|
||||
WindowsSplitTunnel::WindowsSplitTunnel(HANDLE driverIO) : m_driver(driverIO) {
|
||||
logger.debug() << "Connected to the Driver";
|
||||
|
||||
Q_ASSERT(getState() == STATE_INITIALIZED);
|
||||
}
|
||||
|
||||
WindowsSplitTunnel::~WindowsSplitTunnel() {
|
||||
|
|
@ -53,73 +277,12 @@ WindowsSplitTunnel::~WindowsSplitTunnel() {
|
|||
uninstallDriver();
|
||||
}
|
||||
|
||||
void WindowsSplitTunnel::initDriver() {
|
||||
if (detectConflict()) {
|
||||
logger.error() << "Conflict detected, abort Split-Tunnel init.";
|
||||
return;
|
||||
}
|
||||
logger.debug() << "Try to open Split Tunnel Driver";
|
||||
// Open the Driver Symlink
|
||||
m_driver = CreateFileW(DRIVER_SYMLINK, GENERIC_READ | GENERIC_WRITE, 0,
|
||||
nullptr, OPEN_EXISTING, 0, nullptr);
|
||||
;
|
||||
if (m_driver == INVALID_HANDLE_VALUE && m_tries < 500) {
|
||||
WindowsUtils::windowsLog("Failed to open Driver: ");
|
||||
m_tries++;
|
||||
Sleep(100);
|
||||
// If the handle is not present, try again after the serivce has started;
|
||||
auto driver_manager = WindowsServiceManager(DRIVER_SERVICE_NAME);
|
||||
QObject::connect(&driver_manager, &WindowsServiceManager::serviceStarted,
|
||||
this, &WindowsSplitTunnel::initDriver);
|
||||
driver_manager.startService();
|
||||
return;
|
||||
}
|
||||
|
||||
logger.debug() << "Connected to the Driver";
|
||||
// Reset Driver as it has wfp handles probably >:(
|
||||
|
||||
if (!WindowsFirewall::instance()->init()) {
|
||||
logger.error() << "Init WFP-Sublayer failed, driver won't be functional";
|
||||
return;
|
||||
}
|
||||
|
||||
// We need to now check the state and init it, if required
|
||||
|
||||
auto state = getState();
|
||||
if (state == STATE_UNKNOWN) {
|
||||
logger.debug() << "Cannot check if driver is initialized";
|
||||
}
|
||||
if (state >= STATE_INITIALIZED) {
|
||||
logger.debug() << "Driver already initialized: " << state;
|
||||
reset();
|
||||
|
||||
auto newState = getState();
|
||||
logger.debug() << "New state after reset:" << newState;
|
||||
if (newState >= STATE_INITIALIZED) {
|
||||
logger.debug() << "Reset unsuccesfull";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
DWORD bytesReturned;
|
||||
auto ok = DeviceIoControl(m_driver, IOCTL_INITIALIZE, nullptr, 0, nullptr, 0,
|
||||
&bytesReturned, nullptr);
|
||||
if (!ok) {
|
||||
auto err = GetLastError();
|
||||
logger.error() << "Driver init failed err -" << err;
|
||||
logger.error() << "State:" << getState();
|
||||
|
||||
return;
|
||||
}
|
||||
logger.debug() << "Driver initialized" << getState();
|
||||
}
|
||||
|
||||
void WindowsSplitTunnel::setRules(const QStringList& appPaths) {
|
||||
bool WindowsSplitTunnel::excludeApps(const QStringList& appPaths) {
|
||||
auto state = getState();
|
||||
if (state != STATE_READY && state != STATE_RUNNING) {
|
||||
logger.warning() << "Driver is not in the right State to set Rules"
|
||||
<< state;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
logger.debug() << "Pushing new Ruleset for Split-Tunnel " << state;
|
||||
|
|
@ -133,12 +296,13 @@ void WindowsSplitTunnel::setRules(const QStringList& appPaths) {
|
|||
auto err = GetLastError();
|
||||
WindowsUtils::windowsLog("Set Config Failed:");
|
||||
logger.error() << "Failed to set Config err code " << err;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
logger.debug() << "New Configuration applied: " << getState();
|
||||
logger.debug() << "New Configuration applied: " << stateString();
|
||||
return true;
|
||||
}
|
||||
|
||||
void WindowsSplitTunnel::start(int inetAdapterIndex, int vpnAdapterIndex) {
|
||||
bool WindowsSplitTunnel::start(int inetAdapterIndex, int vpnAdapterIndex) {
|
||||
// To Start we need to send 2 things:
|
||||
// Network info (what is vpn what is network)
|
||||
logger.debug() << "Starting SplitTunnel";
|
||||
|
|
@ -151,7 +315,7 @@ void WindowsSplitTunnel::start(int inetAdapterIndex, int vpnAdapterIndex) {
|
|||
0, &bytesReturned, nullptr);
|
||||
if (!ok) {
|
||||
logger.error() << "Driver init failed";
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -164,16 +328,16 @@ void WindowsSplitTunnel::start(int inetAdapterIndex, int vpnAdapterIndex) {
|
|||
nullptr);
|
||||
if (!ok) {
|
||||
logger.error() << "Failed to set Process Config";
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
logger.debug() << "Set Process Config ok || new State:" << getState();
|
||||
logger.debug() << "Set Process Config ok || new State:" << stateString();
|
||||
}
|
||||
|
||||
if (getState() == STATE_INITIALIZED) {
|
||||
logger.warning() << "Driver is still not ready after process list send";
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
logger.debug() << "Driver is ready || new State:" << getState();
|
||||
logger.debug() << "Driver is ready || new State:" << stateString();
|
||||
|
||||
auto config = generateIPConfiguration(inetAdapterIndex, vpnAdapterIndex);
|
||||
auto ok = DeviceIoControl(m_driver, IOCTL_REGISTER_IP_ADDRESSES, &config[0],
|
||||
|
|
@ -181,9 +345,10 @@ void WindowsSplitTunnel::start(int inetAdapterIndex, int vpnAdapterIndex) {
|
|||
nullptr);
|
||||
if (!ok) {
|
||||
logger.error() << "Failed to set Network Config";
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
logger.debug() << "New Network Config Applied || new State:" << getState();
|
||||
logger.debug() << "New Network Config Applied || new State:" << stateString();
|
||||
return true;
|
||||
}
|
||||
|
||||
void WindowsSplitTunnel::stop() {
|
||||
|
|
@ -197,25 +362,27 @@ void WindowsSplitTunnel::stop() {
|
|||
logger.debug() << "Stopping Split tunnel successfull";
|
||||
}
|
||||
|
||||
void WindowsSplitTunnel::reset() {
|
||||
bool WindowsSplitTunnel::resetDriver(HANDLE driverIO) {
|
||||
DWORD bytesReturned;
|
||||
auto ok = DeviceIoControl(m_driver, IOCTL_ST_RESET, nullptr, 0, nullptr, 0,
|
||||
auto ok = DeviceIoControl(driverIO, IOCTL_ST_RESET, nullptr, 0, nullptr, 0,
|
||||
&bytesReturned, nullptr);
|
||||
if (!ok) {
|
||||
logger.error() << "Reset Split tunnel not successfull";
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
logger.debug() << "Reset Split tunnel successfull";
|
||||
return true;
|
||||
}
|
||||
|
||||
DRIVER_STATE WindowsSplitTunnel::getState() {
|
||||
if (m_driver == INVALID_HANDLE_VALUE) {
|
||||
// static
|
||||
WindowsSplitTunnel::DRIVER_STATE WindowsSplitTunnel::getState(HANDLE driverIO) {
|
||||
if (driverIO == INVALID_HANDLE_VALUE) {
|
||||
logger.debug() << "Can't query State from non Opened Driver";
|
||||
return STATE_UNKNOWN;
|
||||
}
|
||||
DWORD bytesReturned;
|
||||
SIZE_T outBuffer;
|
||||
bool ok = DeviceIoControl(m_driver, IOCTL_GET_STATE, nullptr, 0, &outBuffer,
|
||||
bool ok = DeviceIoControl(driverIO, IOCTL_GET_STATE, nullptr, 0, &outBuffer,
|
||||
sizeof(outBuffer), &bytesReturned, nullptr);
|
||||
if (!ok) {
|
||||
WindowsUtils::windowsLog("getState response failure");
|
||||
|
|
@ -225,7 +392,10 @@ DRIVER_STATE WindowsSplitTunnel::getState() {
|
|||
WindowsUtils::windowsLog("getState response is empty");
|
||||
return STATE_UNKNOWN;
|
||||
}
|
||||
return static_cast<DRIVER_STATE>(outBuffer);
|
||||
return static_cast<WindowsSplitTunnel::DRIVER_STATE>(outBuffer);
|
||||
}
|
||||
WindowsSplitTunnel::DRIVER_STATE WindowsSplitTunnel::getState() {
|
||||
return getState(m_driver);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> WindowsSplitTunnel::generateAppConfiguration(
|
||||
|
|
@ -273,58 +443,59 @@ std::vector<uint8_t> WindowsSplitTunnel::generateAppConfiguration(
|
|||
return outBuffer;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> WindowsSplitTunnel::generateIPConfiguration(
|
||||
std::vector<std::byte> WindowsSplitTunnel::generateIPConfiguration(
|
||||
int inetAdapterIndex, int vpnAdapterIndex) {
|
||||
std::vector<uint8_t> out(sizeof(IP_ADDRESSES_CONFIG));
|
||||
std::vector<std::byte> out(sizeof(IP_ADDRESSES_CONFIG));
|
||||
|
||||
auto config = reinterpret_cast<IP_ADDRESSES_CONFIG*>(&out[0]);
|
||||
|
||||
auto ifaces = QNetworkInterface::allInterfaces();
|
||||
|
||||
if (vpnAdapterIndex == 0) {
|
||||
if (vpnAdapterIndex == 0) {
|
||||
vpnAdapterIndex = WindowsCommons::VPNAdapterIndex();
|
||||
}
|
||||
|
||||
// Always the VPN
|
||||
getAddress(vpnAdapterIndex, &config->TunnelIpv4,
|
||||
&config->TunnelIpv6);
|
||||
// 2nd best route
|
||||
getAddress(inetAdapterIndex, &config->InternetIpv4, &config->InternetIpv6);
|
||||
if (!getAddress(vpnAdapterIndex, &config->TunnelIpv4,
|
||||
&config->TunnelIpv6)) {
|
||||
return {};
|
||||
}
|
||||
// 2nd best route is usually the internet adapter
|
||||
if (!getAddress(inetAdapterIndex, &config->InternetIpv4,
|
||||
&config->InternetIpv6)) {
|
||||
return {};
|
||||
};
|
||||
return out;
|
||||
}
|
||||
void WindowsSplitTunnel::getAddress(int adapterIndex, IN_ADDR* out_ipv4,
|
||||
bool WindowsSplitTunnel::getAddress(int adapterIndex, IN_ADDR* out_ipv4,
|
||||
IN6_ADDR* out_ipv6) {
|
||||
QNetworkInterface target =
|
||||
QNetworkInterface::interfaceFromIndex(adapterIndex);
|
||||
logger.debug() << "Getting adapter info for:" << target.humanReadableName();
|
||||
|
||||
// take the first v4/v6 Adress and convert to in_addr
|
||||
for (auto address : target.addressEntries()) {
|
||||
if (address.ip().protocol() == QAbstractSocket::IPv4Protocol) {
|
||||
auto adrr = address.ip().toString();
|
||||
std::wstring wstr = adrr.toStdWString();
|
||||
logger.debug() << "IpV4" << logger.sensitive(adrr);
|
||||
PCWSTR w_str_ip = wstr.c_str();
|
||||
auto ok = InetPtonW(AF_INET, w_str_ip, out_ipv4);
|
||||
if (ok != 1) {
|
||||
logger.debug() << "Ipv4 Conversation error" << WSAGetLastError();
|
||||
auto get = [&target](QAbstractSocket::NetworkLayerProtocol protocol) {
|
||||
for (auto address : target.addressEntries()) {
|
||||
if (address.ip().protocol() != protocol) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
return address.ip().toString().toStdWString();
|
||||
}
|
||||
return std::wstring{};
|
||||
};
|
||||
auto ipv4 = get(QAbstractSocket::IPv4Protocol);
|
||||
auto ipv6 = get(QAbstractSocket::IPv6Protocol);
|
||||
|
||||
if (InetPtonW(AF_INET, ipv4.c_str(), out_ipv4) != 1) {
|
||||
logger.debug() << "Ipv4 Conversation error" << WSAGetLastError();
|
||||
return false;
|
||||
}
|
||||
for (auto address : target.addressEntries()) {
|
||||
if (address.ip().protocol() == QAbstractSocket::IPv6Protocol) {
|
||||
auto adrr = address.ip().toString();
|
||||
std::wstring wstr = adrr.toStdWString();
|
||||
logger.debug() << "IpV6" << logger.sensitive(adrr);
|
||||
PCWSTR w_str_ip = wstr.c_str();
|
||||
auto ok = InetPtonW(AF_INET6, w_str_ip, out_ipv6);
|
||||
if (ok != 1) {
|
||||
logger.error() << "Ipv6 Conversation error" << WSAGetLastError();
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (ipv6.empty()) {
|
||||
std::memset(out_ipv6, 0x00, sizeof(IN6_ADDR));
|
||||
return true;
|
||||
}
|
||||
if (InetPtonW(AF_INET6, ipv6.c_str(), out_ipv6) != 1) {
|
||||
logger.debug() << "Ipv6 Conversation error" << WSAGetLastError();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> WindowsSplitTunnel::generateProcessBlob() {
|
||||
|
|
@ -411,33 +582,6 @@ std::vector<uint8_t> WindowsSplitTunnel::generateProcessBlob() {
|
|||
return out;
|
||||
}
|
||||
|
||||
void WindowsSplitTunnel::close() {
|
||||
CloseHandle(m_driver);
|
||||
m_driver = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
ProcessInfo WindowsSplitTunnel::getProcessInfo(
|
||||
HANDLE process, const PROCESSENTRY32W& processMeta) {
|
||||
ProcessInfo pi;
|
||||
pi.ParentProcessId = processMeta.th32ParentProcessID;
|
||||
pi.ProcessId = processMeta.th32ProcessID;
|
||||
pi.CreationTime = {0, 0};
|
||||
pi.DevicePath = L"";
|
||||
|
||||
FILETIME creationTime, null_time;
|
||||
auto ok = GetProcessTimes(process, &creationTime, &null_time, &null_time,
|
||||
&null_time);
|
||||
if (ok) {
|
||||
pi.CreationTime = creationTime;
|
||||
}
|
||||
wchar_t imagepath[MAX_PATH + 1];
|
||||
if (K32GetProcessImageFileNameW(
|
||||
process, imagepath, sizeof(imagepath) / sizeof(*imagepath)) != 0) {
|
||||
pi.DevicePath = imagepath;
|
||||
}
|
||||
return pi;
|
||||
}
|
||||
|
||||
// static
|
||||
SC_HANDLE WindowsSplitTunnel::installDriver() {
|
||||
LPCWSTR displayName = L"Amnezia Split Tunnel Service";
|
||||
|
|
@ -448,15 +592,15 @@ SC_HANDLE WindowsSplitTunnel::installDriver() {
|
|||
return (SC_HANDLE)INVALID_HANDLE_VALUE;
|
||||
}
|
||||
auto path = driver.absolutePath() + "/" + DRIVER_FILENAME;
|
||||
LPCWSTR binPath = (const wchar_t*)path.utf16();
|
||||
auto binPath = (const wchar_t*)path.utf16();
|
||||
auto scm_rights = SC_MANAGER_ALL_ACCESS;
|
||||
auto serviceManager = OpenSCManager(NULL, // local computer
|
||||
NULL, // servicesActive database
|
||||
auto serviceManager = OpenSCManager(nullptr, // local computer
|
||||
nullptr, // servicesActive database
|
||||
scm_rights);
|
||||
auto service = CreateService(serviceManager, DRIVER_SERVICE_NAME, displayName,
|
||||
SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER,
|
||||
SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,
|
||||
binPath, nullptr, 0, nullptr, nullptr, nullptr);
|
||||
auto service = CreateService(
|
||||
serviceManager, DRIVER_SERVICE_NAME, displayName, SERVICE_ALL_ACCESS,
|
||||
SERVICE_KERNEL_DRIVER, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, binPath,
|
||||
nullptr, nullptr, nullptr, nullptr, nullptr);
|
||||
CloseServiceHandle(serviceManager);
|
||||
return service;
|
||||
}
|
||||
|
|
@ -554,3 +698,25 @@ bool WindowsSplitTunnel::detectConflict() {
|
|||
CloseServiceHandle(servicehandle);
|
||||
return err == ERROR_SERVICE_DOES_NOT_EXIST;
|
||||
}
|
||||
|
||||
bool WindowsSplitTunnel::isRunning() { return getState() == STATE_RUNNING; }
|
||||
QString WindowsSplitTunnel::stateString() {
|
||||
switch (getState()) {
|
||||
case STATE_UNKNOWN:
|
||||
return "STATE_UNKNOWN";
|
||||
case STATE_NONE:
|
||||
return "STATE_NONE";
|
||||
case STATE_STARTED:
|
||||
return "STATE_STARTED";
|
||||
case STATE_INITIALIZED:
|
||||
return "STATE_INITIALIZED";
|
||||
case STATE_READY:
|
||||
return "STATE_READY";
|
||||
case STATE_RUNNING:
|
||||
return "STATE_RUNNING";
|
||||
case STATE_ZOMBIE:
|
||||
return "STATE_ZOMBIE";
|
||||
break;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <memory>
|
||||
|
||||
// Note: the ws2tcpip.h import must come before the others.
|
||||
// clang-format off
|
||||
|
|
@ -18,160 +19,78 @@
|
|||
#include <tlhelp32.h>
|
||||
#include <windows.h>
|
||||
|
||||
// States for GetState
|
||||
enum DRIVER_STATE {
|
||||
STATE_UNKNOWN = -1,
|
||||
STATE_NONE = 0,
|
||||
STATE_STARTED = 1,
|
||||
STATE_INITIALIZED = 2,
|
||||
STATE_READY = 3,
|
||||
STATE_RUNNING = 4,
|
||||
STATE_ZOMBIE = 5,
|
||||
};
|
||||
class WindowsFirewall;
|
||||
|
||||
#ifndef CTL_CODE
|
||||
|
||||
# define FILE_ANY_ACCESS 0x0000
|
||||
|
||||
# define METHOD_BUFFERED 0
|
||||
# define METHOD_IN_DIRECT 1
|
||||
# define METHOD_NEITHER 3
|
||||
|
||||
# define CTL_CODE(DeviceType, Function, Method, Access) \
|
||||
(((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method))
|
||||
#endif
|
||||
|
||||
// Known ControlCodes
|
||||
#define IOCTL_INITIALIZE CTL_CODE(0x8000, 1, METHOD_NEITHER, FILE_ANY_ACCESS)
|
||||
|
||||
#define IOCTL_DEQUEUE_EVENT \
|
||||
CTL_CODE(0x8000, 2, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
#define IOCTL_REGISTER_PROCESSES \
|
||||
CTL_CODE(0x8000, 3, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
#define IOCTL_REGISTER_IP_ADDRESSES \
|
||||
CTL_CODE(0x8000, 4, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
#define IOCTL_GET_IP_ADDRESSES \
|
||||
CTL_CODE(0x8000, 5, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
#define IOCTL_SET_CONFIGURATION \
|
||||
CTL_CODE(0x8000, 6, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
#define IOCTL_GET_CONFIGURATION \
|
||||
CTL_CODE(0x8000, 7, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
#define IOCTL_CLEAR_CONFIGURATION \
|
||||
CTL_CODE(0x8000, 8, METHOD_NEITHER, FILE_ANY_ACCESS)
|
||||
|
||||
#define IOCTL_GET_STATE CTL_CODE(0x8000, 9, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
#define IOCTL_QUERY_PROCESS \
|
||||
CTL_CODE(0x8000, 10, METHOD_BUFFERED, FILE_ANY_ACCESS)
|
||||
|
||||
#define IOCTL_ST_RESET CTL_CODE(0x8000, 11, METHOD_NEITHER, FILE_ANY_ACCESS)
|
||||
|
||||
// Driver Configuration structures
|
||||
|
||||
typedef struct {
|
||||
// Offset into buffer region that follows all entries.
|
||||
// The image name uses the device path.
|
||||
SIZE_T ImageNameOffset;
|
||||
// Length of the String
|
||||
USHORT ImageNameLength;
|
||||
} CONFIGURATION_ENTRY;
|
||||
|
||||
typedef struct {
|
||||
// Number of entries immediately following the header.
|
||||
SIZE_T NumEntries;
|
||||
|
||||
// Total byte length: header + entries + string buffer.
|
||||
SIZE_T TotalLength;
|
||||
} CONFIGURATION_HEADER;
|
||||
|
||||
// Used to Configure Which IP is network/vpn
|
||||
typedef struct {
|
||||
IN_ADDR TunnelIpv4;
|
||||
IN_ADDR InternetIpv4;
|
||||
|
||||
IN6_ADDR TunnelIpv6;
|
||||
IN6_ADDR InternetIpv6;
|
||||
} IP_ADDRESSES_CONFIG;
|
||||
|
||||
// Used to Define Which Processes are alive on activation
|
||||
typedef struct {
|
||||
SIZE_T NumEntries;
|
||||
SIZE_T TotalLength;
|
||||
} PROCESS_DISCOVERY_HEADER;
|
||||
|
||||
typedef struct {
|
||||
HANDLE ProcessId;
|
||||
HANDLE ParentProcessId;
|
||||
|
||||
SIZE_T ImageNameOffset;
|
||||
USHORT ImageNameLength;
|
||||
} PROCESS_DISCOVERY_ENTRY;
|
||||
|
||||
typedef struct {
|
||||
DWORD ProcessId;
|
||||
DWORD ParentProcessId;
|
||||
FILETIME CreationTime;
|
||||
std::wstring DevicePath;
|
||||
} ProcessInfo;
|
||||
|
||||
class WindowsSplitTunnel final : public QObject {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(WindowsSplitTunnel)
|
||||
class WindowsSplitTunnel final {
|
||||
public:
|
||||
explicit WindowsSplitTunnel(QObject* parent);
|
||||
/**
|
||||
* @brief Installs and Initializes the Split Tunnel Driver.
|
||||
*
|
||||
* @param fw -
|
||||
* @return std::unique_ptr<WindowsSplitTunnel> - Is null on failure.
|
||||
*/
|
||||
static std::unique_ptr<WindowsSplitTunnel> create(WindowsFirewall* fw);
|
||||
|
||||
/**
|
||||
* @brief Construct a new Windows Split Tunnel object
|
||||
*
|
||||
* @param driverIO - The Handle to the Driver's IO file, it assumes the driver
|
||||
* is in STATE_INITIALIZED and the Firewall has been setup.
|
||||
* Prefer using create() to get to this state.
|
||||
*/
|
||||
WindowsSplitTunnel(HANDLE driverIO);
|
||||
/**
|
||||
* @brief Destroy the Windows Split Tunnel object and uninstalls the Driver.
|
||||
*/
|
||||
~WindowsSplitTunnel();
|
||||
|
||||
// void excludeApps(const QStringList& paths);
|
||||
// Excludes an Application from the VPN
|
||||
void setRules(const QStringList& appPaths);
|
||||
bool excludeApps(const QStringList& appPaths);
|
||||
|
||||
// Fetches and Pushed needed info to move to engaged mode
|
||||
void start(int inetAdapterIndex, int vpnAdapterIndex = 0);
|
||||
bool start(int inetAdapterIndex, int vpnAdapterIndex = 0);
|
||||
// Deletes Rules and puts the driver into passive mode
|
||||
void stop();
|
||||
// Resets the Whole Driver
|
||||
void reset();
|
||||
|
||||
// Just close connection, leave state as is
|
||||
void close();
|
||||
// Returns true if the split-tunnel driver is now up and running.
|
||||
bool isRunning();
|
||||
|
||||
static bool detectConflict();
|
||||
|
||||
// States for GetState
|
||||
enum DRIVER_STATE {
|
||||
STATE_UNKNOWN = -1,
|
||||
STATE_NONE = 0,
|
||||
STATE_STARTED = 1,
|
||||
STATE_INITIALIZED = 2,
|
||||
STATE_READY = 3,
|
||||
STATE_RUNNING = 4,
|
||||
STATE_ZOMBIE = 5,
|
||||
};
|
||||
|
||||
private:
|
||||
// Installes the Kernel Driver as Driver Service
|
||||
static SC_HANDLE installDriver();
|
||||
static bool uninstallDriver();
|
||||
static bool isInstalled();
|
||||
static bool detectConflict();
|
||||
static bool initDriver(HANDLE driverIO);
|
||||
static DRIVER_STATE getState(HANDLE driverIO);
|
||||
static bool resetDriver(HANDLE driverIO);
|
||||
|
||||
private slots:
|
||||
void initDriver();
|
||||
|
||||
private:
|
||||
HANDLE m_driver = INVALID_HANDLE_VALUE;
|
||||
constexpr static const auto DRIVER_SYMLINK = L"\\\\.\\MULLVADSPLITTUNNEL";
|
||||
constexpr static const auto DRIVER_FILENAME = "mullvad-split-tunnel.sys";
|
||||
constexpr static const auto DRIVER_SERVICE_NAME = L"AmneziaVPNSplitTunnel";
|
||||
constexpr static const auto MV_SERVICE_NAME = L"MullvadVPN";
|
||||
DRIVER_STATE getState();
|
||||
|
||||
int m_tries;
|
||||
// Initializes the WFP Sublayer
|
||||
bool initSublayer();
|
||||
QString stateString();
|
||||
|
||||
// Generates a Configuration for Each APP
|
||||
std::vector<uint8_t> generateAppConfiguration(const QStringList& appPaths);
|
||||
// Generates a Configuration which IP's are VPN and which network
|
||||
std::vector<uint8_t> generateIPConfiguration(int inetAdapterIndex, int vpnAdapterIndex = 0);
|
||||
std::vector<std::byte> generateIPConfiguration(int inetAdapterIndex, int vpnAdapterIndex = 0);
|
||||
std::vector<uint8_t> generateProcessBlob();
|
||||
|
||||
void getAddress(int adapterIndex, IN_ADDR* out_ipv4, IN6_ADDR* out_ipv6);
|
||||
[[nodiscard]] bool getAddress(int adapterIndex, IN_ADDR* out_ipv4,
|
||||
IN6_ADDR* out_ipv6);
|
||||
// Collects info about an Opened Process
|
||||
ProcessInfo getProcessInfo(HANDLE process,
|
||||
const PROCESSENTRY32W& processMeta);
|
||||
|
||||
// Converts a path to a Dos Path:
|
||||
// e.g C:/a.exe -> /harddisk0/a.exe
|
||||
|
|
|
|||
|
|
@ -14,8 +14,6 @@
|
|||
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
#include "platforms/windows/windowscommons.h"
|
||||
#include "windowsdaemon.h"
|
||||
#include "windowsfirewall.h"
|
||||
|
||||
#pragma comment(lib, "iphlpapi.lib")
|
||||
|
|
@ -24,8 +22,20 @@ namespace {
|
|||
Logger logger("WireguardUtilsWindows");
|
||||
}; // namespace
|
||||
|
||||
WireguardUtilsWindows::WireguardUtilsWindows(QObject* parent)
|
||||
: WireguardUtils(parent), m_tunnel(this), m_routeMonitor(this) {
|
||||
std::unique_ptr<WireguardUtilsWindows> WireguardUtilsWindows::create(
|
||||
WindowsFirewall* fw, QObject* parent) {
|
||||
if (!fw) {
|
||||
logger.error() << "WireguardUtilsWindows::create: no wfp handle";
|
||||
return {};
|
||||
}
|
||||
|
||||
// Can't use make_unique here as the Constructor is private :(
|
||||
auto utils = new WireguardUtilsWindows(parent, fw);
|
||||
return std::unique_ptr<WireguardUtilsWindows>(utils);
|
||||
}
|
||||
|
||||
WireguardUtilsWindows::WireguardUtilsWindows(QObject* parent, WindowsFirewall* fw)
|
||||
: WireguardUtils(parent), m_tunnel(this), m_firewall(fw) {
|
||||
MZ_COUNT_CTOR(WireguardUtilsWindows);
|
||||
logger.debug() << "WireguardUtilsWindows created.";
|
||||
|
||||
|
|
@ -114,13 +124,14 @@ bool WireguardUtilsWindows::addInterface(const InterfaceConfig& config) {
|
|||
return false;
|
||||
}
|
||||
m_luid = luid.Value;
|
||||
m_routeMonitor.setLuid(luid.Value);
|
||||
m_routeMonitor = new WindowsRouteMonitor(luid.Value, this);
|
||||
|
||||
if (config.m_killSwitchEnabled) {
|
||||
// Enable the windows firewall
|
||||
NET_IFINDEX ifindex;
|
||||
ConvertInterfaceLuidToIndex(&luid, &ifindex);
|
||||
WindowsFirewall::instance()->enableKillSwitch(ifindex);
|
||||
m_firewall->allowAllTraffic();
|
||||
m_firewall->enableInterface(ifindex);
|
||||
}
|
||||
|
||||
logger.debug() << "Registration completed";
|
||||
|
|
@ -128,7 +139,11 @@ bool WireguardUtilsWindows::addInterface(const InterfaceConfig& config) {
|
|||
}
|
||||
|
||||
bool WireguardUtilsWindows::deleteInterface() {
|
||||
WindowsFirewall::instance()->disableKillSwitch();
|
||||
if (m_routeMonitor) {
|
||||
m_routeMonitor->deleteLater();
|
||||
}
|
||||
|
||||
m_firewall->disableKillSwitch();
|
||||
m_tunnel.stop();
|
||||
return true;
|
||||
}
|
||||
|
|
@ -141,7 +156,7 @@ bool WireguardUtilsWindows::updatePeer(const InterfaceConfig& config) {
|
|||
|
||||
if (config.m_killSwitchEnabled) {
|
||||
// Enable the windows firewall for this peer.
|
||||
WindowsFirewall::instance()->enablePeerTraffic(config);
|
||||
m_firewall->enablePeerTraffic(config);
|
||||
}
|
||||
logger.debug() << "Configuring peer" << publicKey.toHex()
|
||||
<< "via" << config.m_serverIpv4AddrIn;
|
||||
|
|
@ -171,9 +186,9 @@ bool WireguardUtilsWindows::updatePeer(const InterfaceConfig& config) {
|
|||
}
|
||||
|
||||
// Exclude the server address, except for multihop exit servers.
|
||||
if (config.m_hopType != InterfaceConfig::MultiHopExit) {
|
||||
m_routeMonitor.addExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
|
||||
m_routeMonitor.addExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
|
||||
if (m_routeMonitor && config.m_hopType != InterfaceConfig::MultiHopExit) {
|
||||
m_routeMonitor->addExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
|
||||
m_routeMonitor->addExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
|
||||
}
|
||||
|
||||
QString reply = m_tunnel.uapiCommand(message);
|
||||
|
|
@ -186,13 +201,13 @@ bool WireguardUtilsWindows::deletePeer(const InterfaceConfig& config) {
|
|||
QByteArray::fromBase64(qPrintable(config.m_serverPublicKey));
|
||||
|
||||
// Clear exclustion routes for this peer.
|
||||
if (config.m_hopType != InterfaceConfig::MultiHopExit) {
|
||||
m_routeMonitor.deleteExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
|
||||
m_routeMonitor.deleteExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
|
||||
if (m_routeMonitor && config.m_hopType != InterfaceConfig::MultiHopExit) {
|
||||
m_routeMonitor->deleteExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
|
||||
m_routeMonitor->deleteExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
|
||||
}
|
||||
|
||||
// Disable the windows firewall for this peer.
|
||||
WindowsFirewall::instance()->disablePeerTraffic(config.m_serverPublicKey);
|
||||
m_firewall->disablePeerTraffic(config.m_serverPublicKey);
|
||||
|
||||
QString message;
|
||||
QTextStream out(&message);
|
||||
|
|
@ -238,6 +253,13 @@ void WireguardUtilsWindows::buildMibForwardRow(const IPAddress& prefix,
|
|||
}
|
||||
|
||||
bool WireguardUtilsWindows::updateRoutePrefix(const IPAddress& prefix) {
|
||||
if (m_routeMonitor && (prefix.prefixLength() == 0)) {
|
||||
// If we are setting up a default route, instruct the route monitor to
|
||||
// capture traffic to all non-excluded destinations
|
||||
m_routeMonitor->setDetaultRouteCapture(true);
|
||||
}
|
||||
// Build the route
|
||||
|
||||
MIB_IPFORWARD_ROW2 entry;
|
||||
buildMibForwardRow(prefix, &entry);
|
||||
|
||||
|
|
@ -246,6 +268,13 @@ bool WireguardUtilsWindows::updateRoutePrefix(const IPAddress& prefix) {
|
|||
if (result == ERROR_OBJECT_ALREADY_EXISTS) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Case for ipv6 route with disabled ipv6
|
||||
if (prefix.address().protocol() == QAbstractSocket::IPv6Protocol
|
||||
&& result == ERROR_NOT_FOUND) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (result != NO_ERROR) {
|
||||
logger.error() << "Failed to create route to"
|
||||
<< prefix.toString()
|
||||
|
|
@ -255,6 +284,12 @@ bool WireguardUtilsWindows::updateRoutePrefix(const IPAddress& prefix) {
|
|||
}
|
||||
|
||||
bool WireguardUtilsWindows::deleteRoutePrefix(const IPAddress& prefix) {
|
||||
if (m_routeMonitor && (prefix.prefixLength() == 0)) {
|
||||
// Deactivate the route capture feature.
|
||||
m_routeMonitor->setDetaultRouteCapture(false);
|
||||
}
|
||||
// Build the route
|
||||
|
||||
MIB_IPFORWARD_ROW2 entry;
|
||||
buildMibForwardRow(prefix, &entry);
|
||||
|
||||
|
|
@ -272,9 +307,28 @@ bool WireguardUtilsWindows::deleteRoutePrefix(const IPAddress& prefix) {
|
|||
}
|
||||
|
||||
bool WireguardUtilsWindows::addExclusionRoute(const IPAddress& prefix) {
|
||||
return m_routeMonitor.addExclusionRoute(prefix);
|
||||
return m_routeMonitor->addExclusionRoute(prefix);
|
||||
}
|
||||
|
||||
bool WireguardUtilsWindows::deleteExclusionRoute(const IPAddress& prefix) {
|
||||
return m_routeMonitor.deleteExclusionRoute(prefix);
|
||||
return m_routeMonitor->deleteExclusionRoute(prefix);
|
||||
}
|
||||
|
||||
bool WireguardUtilsWindows::excludeLocalNetworks(
|
||||
const QList<IPAddress>& addresses) {
|
||||
// If the interface isn't up then something went horribly wrong.
|
||||
Q_ASSERT(m_routeMonitor);
|
||||
// For each destination - attempt to exclude it from the VPN tunnel.
|
||||
bool result = true;
|
||||
for (const IPAddress& prefix : addresses) {
|
||||
if (!m_routeMonitor->addExclusionRoute(prefix)) {
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
// Permit LAN traffic through the firewall.
|
||||
if (!m_firewall->enableLanBypass(addresses)) {
|
||||
result = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,16 +9,21 @@
|
|||
|
||||
#include <QHostAddress>
|
||||
#include <QObject>
|
||||
#include <QPointer>
|
||||
|
||||
#include "daemon/wireguardutils.h"
|
||||
#include "windowsroutemonitor.h"
|
||||
#include "windowstunnelservice.h"
|
||||
|
||||
class WindowsFirewall;
|
||||
class WindowsRouteMonitor;
|
||||
|
||||
class WireguardUtilsWindows final : public WireguardUtils {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
WireguardUtilsWindows(QObject* parent);
|
||||
static std::unique_ptr<WireguardUtilsWindows> create(WindowsFirewall* fw,
|
||||
QObject* parent);
|
||||
~WireguardUtilsWindows();
|
||||
|
||||
bool interfaceExists() override { return m_tunnel.isRunning(); }
|
||||
|
|
@ -39,15 +44,19 @@ class WireguardUtilsWindows final : public WireguardUtils {
|
|||
bool addExclusionRoute(const IPAddress& prefix) override;
|
||||
bool deleteExclusionRoute(const IPAddress& prefix) override;
|
||||
|
||||
bool WireguardUtilsWindows::excludeLocalNetworks(const QList<IPAddress>& addresses) override;
|
||||
|
||||
signals:
|
||||
void backendFailure();
|
||||
|
||||
private:
|
||||
WireguardUtilsWindows(QObject* parent, WindowsFirewall* fw);
|
||||
void buildMibForwardRow(const IPAddress& prefix, void* row);
|
||||
|
||||
quint64 m_luid = 0;
|
||||
WindowsTunnelService m_tunnel;
|
||||
WindowsRouteMonitor m_routeMonitor;
|
||||
QPointer<WindowsRouteMonitor> m_routeMonitor;
|
||||
QPointer<WindowsFirewall> m_firewall;
|
||||
};
|
||||
|
||||
#endif // WIREGUARDUTILSWINDOWS_H
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include "windowsservicemanager.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QTimer>
|
||||
|
||||
#include "Windows.h"
|
||||
|
|
@ -16,35 +17,44 @@ namespace {
|
|||
Logger logger("WindowsServiceManager");
|
||||
}
|
||||
|
||||
WindowsServiceManager::WindowsServiceManager(LPCWSTR serviceName) {
|
||||
WindowsServiceManager::WindowsServiceManager(SC_HANDLE serviceManager,
|
||||
SC_HANDLE service)
|
||||
: QObject(qApp), m_serviceManager(serviceManager), m_service(service) {
|
||||
m_timer.setSingleShot(false);
|
||||
}
|
||||
|
||||
std::unique_ptr<WindowsServiceManager> WindowsServiceManager::open(
|
||||
const QString serviceName) {
|
||||
LPCWSTR service = (const wchar_t*)serviceName.utf16();
|
||||
|
||||
DWORD err = NULL;
|
||||
auto scm_rights = SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE |
|
||||
SC_MANAGER_QUERY_LOCK_STATUS | STANDARD_RIGHTS_READ;
|
||||
m_serviceManager = OpenSCManager(NULL, // local computer
|
||||
NULL, // servicesActive database
|
||||
scm_rights);
|
||||
auto manager = OpenSCManager(NULL, // local computer
|
||||
NULL, // servicesActive database
|
||||
scm_rights);
|
||||
err = GetLastError();
|
||||
if (err != NULL) {
|
||||
logger.error() << " OpenSCManager failed code: " << err;
|
||||
return;
|
||||
return {};
|
||||
}
|
||||
logger.debug() << "OpenSCManager access given - " << err;
|
||||
|
||||
logger.debug() << "Opening Service - "
|
||||
<< QString::fromWCharArray(serviceName);
|
||||
logger.debug() << "Opening Service - " << serviceName;
|
||||
// Try to get an elevated handle
|
||||
m_service = OpenService(m_serviceManager, // SCM database
|
||||
serviceName, // name of service
|
||||
(GENERIC_READ | SERVICE_START | SERVICE_STOP));
|
||||
auto serviceHandle =
|
||||
OpenService(manager, // SCM database
|
||||
service, // name of service
|
||||
(GENERIC_READ | SERVICE_START | SERVICE_STOP));
|
||||
err = GetLastError();
|
||||
if (err != NULL) {
|
||||
CloseServiceHandle(manager);
|
||||
WindowsUtils::windowsLog("OpenService failed");
|
||||
return;
|
||||
return {};
|
||||
}
|
||||
m_has_access = true;
|
||||
m_timer.setSingleShot(false);
|
||||
|
||||
logger.debug() << "Service manager execute access granted";
|
||||
return std::make_unique<WindowsServiceManager>(manager, serviceHandle);
|
||||
}
|
||||
|
||||
WindowsServiceManager::~WindowsServiceManager() {
|
||||
|
|
@ -85,10 +95,6 @@ bool WindowsServiceManager::startPolling(DWORD goal_state, int max_wait_sec) {
|
|||
|
||||
SERVICE_STATUS_PROCESS WindowsServiceManager::getStatus() {
|
||||
SERVICE_STATUS_PROCESS serviceStatus;
|
||||
if (!m_has_access) {
|
||||
logger.debug() << "Need read access to get service state";
|
||||
return serviceStatus;
|
||||
}
|
||||
DWORD dwBytesNeeded; // Contains missing bytes if struct is too small?
|
||||
QueryServiceStatusEx(m_service, // handle to service
|
||||
SC_STATUS_PROCESS_INFO, // information level
|
||||
|
|
@ -119,10 +125,6 @@ bool WindowsServiceManager::startService() {
|
|||
}
|
||||
|
||||
bool WindowsServiceManager::stopService() {
|
||||
if (!m_has_access) {
|
||||
logger.error() << "Need execute access to stop services";
|
||||
return false;
|
||||
}
|
||||
auto state = getStatus().dwCurrentState;
|
||||
if (state != SERVICE_RUNNING && state != SERVICE_START_PENDING) {
|
||||
logger.warning() << ("Service stop not possible, as its not running");
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
#include "Winsvc.h"
|
||||
|
||||
/**
|
||||
* @brief The WindowsServiceManager provides control over the MozillaVPNBroker
|
||||
* @brief The WindowsServiceManager provides control over the a
|
||||
* service via SCM
|
||||
*/
|
||||
class WindowsServiceManager : public QObject {
|
||||
|
|
@ -20,7 +20,10 @@ class WindowsServiceManager : public QObject {
|
|||
Q_DISABLE_COPY_MOVE(WindowsServiceManager)
|
||||
|
||||
public:
|
||||
WindowsServiceManager(LPCWSTR serviceName);
|
||||
// Creates a WindowsServiceManager for the Named service.
|
||||
// returns nullptr if
|
||||
static std::unique_ptr<WindowsServiceManager> open(const QString serviceName);
|
||||
WindowsServiceManager(SC_HANDLE serviceManager, SC_HANDLE service);
|
||||
~WindowsServiceManager();
|
||||
|
||||
// true if the Service is running
|
||||
|
|
@ -45,8 +48,6 @@ class WindowsServiceManager : public QObject {
|
|||
// See
|
||||
// SERVICE_STOPPED,SERVICE_STOP_PENDING,SERVICE_START_PENDING,SERVICE_RUNNING
|
||||
SERVICE_STATUS_PROCESS getStatus();
|
||||
bool m_has_access = false;
|
||||
LPWSTR m_serviceName;
|
||||
SC_HANDLE m_serviceManager;
|
||||
SC_HANDLE m_service; // Service handle with r/w priv.
|
||||
DWORD m_state_target;
|
||||
|
|
|
|||
|
|
@ -238,7 +238,7 @@ ErrorCode Ikev2Protocol::start()
|
|||
"-CipherTransformConstants GCMAES128 "
|
||||
"-EncryptionMethod AES256 "
|
||||
"-IntegrityCheckMethod SHA256 "
|
||||
"-PfsGroup None "
|
||||
"-PfsGroup PFS2048 "
|
||||
"-DHGroup Group14 "
|
||||
"-PassThru -Force\"")
|
||||
.arg(tunnelName());
|
||||
|
|
|
|||
|
|
@ -171,6 +171,11 @@ ErrorCode OpenVpnProtocol::start()
|
|||
return lastError();
|
||||
}
|
||||
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
IpcClient::Interface()->addKillSwitchAllowedRange(QStringList(NetworkUtilities::getIPAddress(
|
||||
m_configData.value(amnezia::config_key::hostName).toString())));
|
||||
#endif
|
||||
|
||||
// Detect default gateway
|
||||
#ifdef Q_OS_MAC
|
||||
QProcess p;
|
||||
|
|
@ -338,7 +343,7 @@ void OpenVpnProtocol::updateVpnGateway(const QString &line)
|
|||
// killSwitch toggle
|
||||
if (m_vpnLocalAddress == netInterfaces.at(i).addressEntries().at(j).ip().toString()) {
|
||||
if (QVariant(m_configData.value(config_key::killSwitchOption).toString()).toBool()) {
|
||||
IpcClient::Interface()->enableKillSwitch(QJsonObject(), netInterfaces.at(i).index());
|
||||
IpcClient::Interface()->enableKillSwitch(m_configData, netInterfaces.at(i).index());
|
||||
}
|
||||
m_configData.insert("vpnAdapterIndex", netInterfaces.at(i).index());
|
||||
m_configData.insert("vpnGateway", m_vpnGateway);
|
||||
|
|
|
|||
|
|
@ -72,10 +72,21 @@ namespace amnezia
|
|||
constexpr char junkPacketMaxSize[] = "Jmax";
|
||||
constexpr char initPacketJunkSize[] = "S1";
|
||||
constexpr char responsePacketJunkSize[] = "S2";
|
||||
constexpr char cookieReplyPacketJunkSize[] = "S3";
|
||||
constexpr char transportPacketJunkSize[] = "S4";
|
||||
constexpr char initPacketMagicHeader[] = "H1";
|
||||
constexpr char responsePacketMagicHeader[] = "H2";
|
||||
constexpr char underloadPacketMagicHeader[] = "H3";
|
||||
constexpr char transportPacketMagicHeader[] = "H4";
|
||||
constexpr char specialJunk1[] = "I1";
|
||||
constexpr char specialJunk2[] = "I2";
|
||||
constexpr char specialJunk3[] = "I3";
|
||||
constexpr char specialJunk4[] = "I4";
|
||||
constexpr char specialJunk5[] = "I5";
|
||||
constexpr char controlledJunk1[] = "J1";
|
||||
constexpr char controlledJunk2[] = "J2";
|
||||
constexpr char controlledJunk3[] = "J3";
|
||||
constexpr char specialHandshakeTimeout[] = "Itime";
|
||||
|
||||
constexpr char openvpn[] = "openvpn";
|
||||
constexpr char wireguard[] = "wireguard";
|
||||
|
|
@ -95,12 +106,16 @@ namespace amnezia
|
|||
constexpr char splitTunnelApps[] = "splitTunnelApps";
|
||||
constexpr char appSplitTunnelType[] = "appSplitTunnelType";
|
||||
|
||||
constexpr char allowedDnsServers[] = "allowedDnsServers";
|
||||
|
||||
constexpr char killSwitchOption[] = "killSwitchOption";
|
||||
|
||||
constexpr char crc[] = "crc";
|
||||
|
||||
constexpr char clientId[] = "clientId";
|
||||
|
||||
constexpr char nameOverriddenByUser[] = "nameOverriddenByUser";
|
||||
|
||||
}
|
||||
|
||||
namespace protocols
|
||||
|
|
@ -212,10 +227,22 @@ namespace amnezia
|
|||
constexpr char defaultJunkPacketMaxSize[] = "30";
|
||||
constexpr char defaultInitPacketJunkSize[] = "15";
|
||||
constexpr char defaultResponsePacketJunkSize[] = "18";
|
||||
constexpr char defaultCookieReplyPacketJunkSize[] = "20";
|
||||
constexpr char defaultTransportPacketJunkSize[] = "23";
|
||||
|
||||
constexpr char defaultInitPacketMagicHeader[] = "1020325451";
|
||||
constexpr char defaultResponsePacketMagicHeader[] = "3288052141";
|
||||
constexpr char defaultTransportPacketMagicHeader[] = "2528465083";
|
||||
constexpr char defaultUnderloadPacketMagicHeader[] = "1766607858";
|
||||
constexpr char defaultSpecialJunk1[] = "";
|
||||
constexpr char defaultSpecialJunk2[] = "";
|
||||
constexpr char defaultSpecialJunk3[] = "";
|
||||
constexpr char defaultSpecialJunk4[] = "";
|
||||
constexpr char defaultSpecialJunk5[] = "";
|
||||
constexpr char defaultControlledJunk1[] = "";
|
||||
constexpr char defaultControlledJunk2[] = "";
|
||||
constexpr char defaultControlledJunk3[] = "";
|
||||
constexpr char defaultSpecialHandshakeTimeout[] = "";
|
||||
}
|
||||
|
||||
namespace socks5Proxy
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue