Compare commits
64 Commits
fixbug/mac
...
checking_s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
464aa4ceae | ||
|
|
db83555a03 | ||
|
|
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 | ||
|
|
7b2a4ea922 | ||
|
|
d06924c59d | ||
|
|
795ccaa080 | ||
|
|
ce4cb28e74 | ||
|
|
c9423dd9af | ||
|
|
2db99715b1 | ||
|
|
055c8f7b66 | ||
|
|
2b85dafa15 | ||
|
|
45fb4b0982 | ||
|
|
62497024f9 | ||
|
|
9688a8e52d | ||
|
|
b2c429f74d | ||
|
|
6ea6ab1bd9 | ||
|
|
c5aa070bf4 | ||
|
|
d67201ede9 | ||
|
|
4323fb2063 | ||
|
|
6d5452b8ee | ||
|
|
569d63ef0f | ||
|
|
ea910ba300 | ||
|
|
1c1e74d06f | ||
|
|
5dc16c06f1 | ||
|
|
4efaf20a1c | ||
|
|
076b076cd9 | ||
|
|
85fa1ad8b1 | ||
|
|
9d96b1cd13 | ||
|
|
1d721ffb9a | ||
|
|
2130131a9d | ||
|
|
e0b091b474 | ||
|
|
8547de82ea | ||
|
|
aa871bd1c9 | ||
|
|
23806e1def | ||
|
|
31867993ce | ||
|
|
7b7a922d92 | ||
|
|
09bd958d8d | ||
|
|
576e2226fe | ||
|
|
1533270e4e |
39
.clang-format
Normal file
@@ -0,0 +1,39 @@
|
||||
BasedOnStyle: WebKit
|
||||
AccessModifierOffset: '-4'
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignConsecutiveMacros: 'true'
|
||||
AlignTrailingComments: 'true'
|
||||
AllowAllArgumentsOnNextLine: 'true'
|
||||
AllowAllParametersOfDeclarationOnNextLine: 'true'
|
||||
AllowShortBlocksOnASingleLine: 'false'
|
||||
AllowShortCaseLabelsOnASingleLine: 'true'
|
||||
AllowShortEnumsOnASingleLine: 'false'
|
||||
AllowShortFunctionsOnASingleLine: None
|
||||
AlwaysBreakTemplateDeclarations: 'No'
|
||||
BreakBeforeBinaryOperators: NonAssignment
|
||||
BreakBeforeBraces: Custom
|
||||
BraceWrapping:
|
||||
AfterClass: true
|
||||
AfterControlStatement: false
|
||||
AfterEnum: false
|
||||
AfterFunction: true
|
||||
AfterNamespace: true
|
||||
AfterObjCDeclaration: false
|
||||
AfterStruct: true
|
||||
AfterUnion: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
IndentBraces: false
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
ColumnLimit: '120'
|
||||
CommentPragmas: '"^!|^:"'
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: 'true'
|
||||
ConstructorInitializerIndentWidth: '4'
|
||||
ContinuationIndentWidth: '8'
|
||||
IndentPPDirectives: BeforeHash
|
||||
NamespaceIndentation: All
|
||||
PenaltyExcessCharacter: '10'
|
||||
PointerAlignment: Right
|
||||
SortIncludes: 'true'
|
||||
SpaceAfterTemplateKeyword: 'false'
|
||||
Standard: Auto
|
||||
20
.clang-format-ignore
Normal file
@@ -0,0 +1,20 @@
|
||||
/client/3rd
|
||||
/client/3rd-prebuild
|
||||
/client/android
|
||||
/client/cmake
|
||||
/client/core/serialization
|
||||
/client/daemon
|
||||
/client/fonts
|
||||
/client/images
|
||||
/client/ios
|
||||
/client/mozilla
|
||||
/client/platforms/dummy
|
||||
/client/platforms/linux
|
||||
/client/platforms/macos
|
||||
/client/platforms/windows
|
||||
/client/server_scripts
|
||||
/client/translations
|
||||
/deploy
|
||||
/docs
|
||||
/metadata
|
||||
/service/src
|
||||
89
.github/workflows/deploy.yml
vendored
@@ -217,7 +217,11 @@ jobs:
|
||||
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/ios/bin"
|
||||
export QT_MACOS_ROOT_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos"
|
||||
export PATH=$PATH:~/go/bin
|
||||
sh deploy/build_ios.sh
|
||||
sh deploy/build_ios.sh | \
|
||||
sed -e '/-Xcc -DPROD_AGW_PUBLIC_KEY/,/-Xcc/ { /-Xcc/!d; }' -e '/-Xcc -DPROD_AGW_PUBLIC_KEY/d' | \
|
||||
sed -e '/-Xcc -DDEV_AGW_PUBLIC_KEY/,/-Xcc/ { /-Xcc/!d; }' -e '/-Xcc -DDEV_AGW_PUBLIC_KEY/d' | \
|
||||
sed -e '/-DPROD_AGW_PUBLIC_KEY/,/-D/ { /-D/!d; }' -e '/-DPROD_AGW_PUBLIC_KEY/d' | \
|
||||
sed -e '/-DDEV_AGW_PUBLIC_KEY/,/-D/ { /-D/!d; }' -e '/-DDEV_AGW_PUBLIC_KEY/d'
|
||||
env:
|
||||
IOS_TRUST_CERT_BASE64: ${{ secrets.IOS_TRUST_CERT_BASE64 }}
|
||||
IOS_SIGNING_CERT_BASE64: ${{ secrets.IOS_SIGNING_CERT_BASE64 }}
|
||||
@@ -256,7 +260,7 @@ jobs:
|
||||
- name: 'Setup xcode'
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: '14.3.1'
|
||||
xcode-version: '15.4.0'
|
||||
|
||||
- name: 'Install Qt'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
@@ -306,70 +310,6 @@ jobs:
|
||||
path: deploy/build/client/AmneziaVPN.app
|
||||
retention-days: 7
|
||||
|
||||
# ------------------------------------------------------
|
||||
Build-MacOS-NE:
|
||||
runs-on: macos-latest
|
||||
|
||||
env:
|
||||
QT_VERSION: 6.4.3
|
||||
QIF_VERSION: 4.6
|
||||
QT_MIRROR: https://mirrors.ocf.berkeley.edu/qt/
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
|
||||
|
||||
steps:
|
||||
- name: 'Setup Xcode'
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: '14.3.1'
|
||||
|
||||
- name: 'Install desktop Qt'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'mac'
|
||||
target: 'desktop'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools qtmultimedia'
|
||||
arch: 'clang_64'
|
||||
dir: ${{ runner.temp }}
|
||||
set-env: 'true'
|
||||
extra: '--base ${{ env.QT_MIRROR }}'
|
||||
- name: 'Install 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: 'Install Go'
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.22.1'
|
||||
cache: false
|
||||
|
||||
- name: 'Get sources'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- name: 'Install dependencies'
|
||||
run: pip install jsonschema jinja2
|
||||
|
||||
- name: 'Set execute permissions for deploy script'
|
||||
run: chmod +x deploy/build_macos_ne.sh
|
||||
|
||||
# - name: 'Build and deploy macOS NE'
|
||||
# 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"
|
||||
# export QT_MACOS_ROOT_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos"
|
||||
# bash deploy/build_macos_ne.sh
|
||||
# env:
|
||||
# APPSTORE_CONNECT_KEY_ID: ${{ secrets.APPSTORE_CONNECT_KEY_ID }}
|
||||
# APPSTORE_CONNECT_ISSUER_ID: ${{ secrets.APPSTORE_CONNECT_ISSUER_ID }}
|
||||
# APPSTORE_CONNECT_PRIVATE_KEY: ${{ secrets.APPSTORE_CONNECT_PRIVATE_KEY }}
|
||||
# MAC_TRUST_CERT_BASE64: ${{ secrets.IOS_TRUST_CERT_BASE64 }}
|
||||
# MAC_SIGNING_CERT_BASE64: ${{ secrets.IOS_SIGNING_CERT_BASE64 }}
|
||||
# MAC_SIGNING_CERT_PASSWORD: ${{ secrets.IOS_SIGNING_CERT_PASSWORD }}
|
||||
# ------------------------------------------------------
|
||||
|
||||
Build-Android:
|
||||
@@ -395,7 +335,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
|
||||
@@ -406,7 +347,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
|
||||
@@ -417,7 +359,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
|
||||
@@ -428,7 +371,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
|
||||
@@ -439,7 +383,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
|
||||
@@ -540,4 +485,4 @@ jobs:
|
||||
if: ${{ fromJSON(steps.pull_request.outputs.data)[0].number != '' }}
|
||||
run: |
|
||||
echo "Pull request:" >> $GITHUB_STEP_SUMMARY
|
||||
echo "[[#${{ fromJSON(steps.pull_request.outputs.data)[0].number }}] ${{ fromJSON(steps.pull_request.outputs.data)[0].title }}](${{ fromJSON(steps.pull_request.outputs.data)[0].html_url }})" >> $GITHUB_STEP_SUMMARY
|
||||
echo "[[#${{ fromJSON(steps.pull_request.outputs.data)[0].number }}] ${{ fromJSON(steps.pull_request.outputs.data)[0].title }}](${{ fromJSON(steps.pull_request.outputs.data)[0].html_url }})" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
2
.gitmodules
vendored
@@ -1,7 +1,6 @@
|
||||
[submodule "client/3rd/OpenVPNAdapter"]
|
||||
path = client/3rd/OpenVPNAdapter
|
||||
url = https://github.com/amnezia-vpn/OpenVPNAdapter.git
|
||||
branch = macos-dirty-build
|
||||
[submodule "client/3rd/qtkeychain"]
|
||||
path = client/3rd/qtkeychain
|
||||
url = https://github.com/frankosterfeld/qtkeychain.git
|
||||
@@ -11,7 +10,6 @@
|
||||
[submodule "client/3rd-prebuilt"]
|
||||
path = client/3rd-prebuilt
|
||||
url = https://github.com/amnezia-vpn/3rd-prebuilt
|
||||
branch = fixbug/mac-network-extension
|
||||
[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.3
|
||||
project(${PROJECT} VERSION 4.8.3.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 2069)
|
||||
set(APP_ANDROID_VERSION_CODE 2074)
|
||||
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||
set(MZ_PLATFORM_NAME "linux")
|
||||
@@ -31,13 +31,13 @@ set(QT_BUILD_TOOLS_WHEN_CROSS_COMPILING ON)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
if((APPLE AND NOT IOS) OR (DEFINED MACOS_NE AND MACOS_NE AND NOT IOS))
|
||||
if(APPLE AND NOT IOS)
|
||||
set(CMAKE_OSX_ARCHITECTURES "x86_64")
|
||||
endif()
|
||||
|
||||
add_subdirectory(client)
|
||||
|
||||
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
|
||||
if(NOT IOS AND NOT ANDROID)
|
||||
add_subdirectory(service)
|
||||
|
||||
include(${CMAKE_SOURCE_DIR}/deploy/installer/config.cmake)
|
||||
|
||||
32
README.md
@@ -1,24 +1,29 @@
|
||||
# Amnezia VPN
|
||||
## _The best client for self-hosted VPN_
|
||||
|
||||
### _The best client for self-hosted VPN_
|
||||
|
||||
|
||||
[](https://github.com/amnezia-vpn/amnezia-client/actions/workflows/deploy.yml?query=branch:dev)
|
||||
[](https://gitpod.io/#https://github.com/amnezia-vpn/amnezia-client)
|
||||
|
||||
Amnezia is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
|
||||
### [English]([https://github.com/amnezia-vpn/amnezia-client/blob/dev/README_RU.md](https://github.com/amnezia-vpn/amnezia-client/tree/dev?tab=readme-ov-file#)) | [Русский](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README_RU.md)
|
||||
|
||||

|
||||
|
||||
<br>
|
||||
[Amnezia](https://amnezia.org) is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
|
||||
|
||||
<a href="https://amnezia.org/downloads"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download.png" width="150" style="max-width: 100%;"></a>
|
||||
<a href="https://play.google.com/store/search?q=amnezia+vpn&c=apps"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/play.png" width="150" style="max-width: 100%;"></a>
|
||||
<a href="https://apps.apple.com/us/app/amneziavpn/id1600529900"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/apl.png" width="150" style="max-width: 100%;"></a>
|
||||
[](https://amnezia.org)
|
||||
|
||||
[Alternative download link (mirror)](https://storage.googleapis.com/kldscp/amnezia.org/downloads)
|
||||
### [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)
|
||||
|
||||
> [!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).
|
||||
|
||||
<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>
|
||||
|
||||
[All releases](https://github.com/amnezia-vpn/amnezia-client/releases)
|
||||
|
||||
<br>
|
||||
<br/>
|
||||
|
||||
<a href="https://www.testiny.io"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/testiny.png" height="28px"></a>
|
||||
|
||||
@@ -33,7 +38,8 @@ Amnezia is an open-source VPN client, with a key feature that enables you to dep
|
||||
|
||||
## Links
|
||||
|
||||
- [https://amnezia.org](https://amnezia.org) - project website | [Alternative link (mirror)](https://storage.googleapis.com/kldscp/amnezia.org)
|
||||
- [https://amnezia.org](https://amnezia.org) - Project website | [Alternative link (mirror)](https://storage.googleapis.com/kldscp/amnezia.org)
|
||||
- [https://docs.amnezia.org](https://docs.amnezia.org) - Documentation
|
||||
- [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 support channel (English)
|
||||
- [https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Telegram support channel (Farsi)
|
||||
@@ -179,11 +185,11 @@ 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
|
||||
|
||||
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3 <br>
|
||||
TON: UQDpU1CyKRmg7L8mNScKk9FRc2SlESuI7N-Hby4nX-CcVmns
|
||||
## Acknowledgments
|
||||
|
||||
This project is tested with BrowserStack.
|
||||
|
||||
181
README_RU.md
Normal file
@@ -0,0 +1,181 @@
|
||||
# Amnezia VPN
|
||||
|
||||
### _Лучший клиент для создания VPN на собственном сервере_
|
||||
|
||||
[](https://github.com/amnezia-vpn/amnezia-client/actions/workflows/deploy.yml?query=branch:dev)
|
||||
[](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 на вашем сервере.
|
||||
|
||||
[](https://amnezia.org)
|
||||
|
||||
### [Сайт](https://amnezia.org) | [Зеркало на сайт](https://storage.googleapis.com/kldscp/amnezia.org) | [Документация](https://docs.amnezia.org) | [Решение проблем](https://docs.amnezia.org/troubleshooting)
|
||||
|
||||
> [!TIP]
|
||||
> Если [сайт Amnezia](https://amnezia.org) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/kldscp/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>
|
||||
|
||||
|
||||
[Все релизы](https://github.com/amnezia-vpn/amnezia-client/releases)
|
||||
|
||||
<br/>
|
||||
|
||||
<a href="https://www.testiny.io"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/testiny.png" height="28px"></a>
|
||||
|
||||
## Особенности
|
||||
|
||||
- Простой в использовании — введите IP-адрес, SSH-логин и пароль, и Amnezia автоматически установит VPN-контейнеры Docker на ваш сервер и подключится к VPN.
|
||||
- Классические 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.
|
||||
- Поддержка конфигурации протокола 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).
|
||||
|
||||
## Ссылки
|
||||
|
||||
- [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://vpnpay.io/en/amnezia-premium/](https://vpnpay.io/en/amnezia-premium/) - Amnezia Premium | [Зеркало](https://storage.googleapis.com/kldscp/vpnpay.io/ru/amnezia-premium\)
|
||||
|
||||
## Технологии
|
||||
|
||||
AmneziaVPN использует несколько проектов с открытым исходным кодом:
|
||||
|
||||
- [OpenSSL](https://www.openssl.org/)
|
||||
- [OpenVPN](https://openvpn.net/)
|
||||
- [Shadowsocks](https://shadowsocks.org/)
|
||||
- [Qt](https://www.qt.io/)
|
||||
- [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
|
||||
|
||||
## Донаты
|
||||
|
||||
Patreon: [https://www.patreon.com/amneziavpn](https://www.patreon.com/amneziavpn)
|
||||
|
||||
Bitcoin: bc1qmhtgcf9637rl3kqyy22r2a8wa8laka4t9rx2mf <br>
|
||||
USDT BEP20: 0x6abD576765a826f87D1D95183438f9408C901bE4 <br>
|
||||
USDT TRC20: TELAitazF1MZGmiNjTcnxDjEiH5oe7LC9d <br>
|
||||
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3 <br>
|
||||
TON: UQDpU1CyKRmg7L8mNScKk9FRc2SlESuI7N-Hby4nX-CcVmns
|
||||
|
||||
## Благодарности
|
||||
|
||||
Этот проект тестируется с помощью BrowserStack.
|
||||
Мы выражаем благодарность [BrowserStack](https://www.browserstack.com) за поддержку нашего проекта.
|
||||
2
client/3rd/OpenVPNAdapter
vendored
2
client/3rd/amneziawg-apple
vendored
@@ -31,12 +31,11 @@ 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 OR MACOS_NE)
|
||||
if(IOS)
|
||||
set(PACKAGES ${PACKAGES} Multimedia)
|
||||
endif()
|
||||
|
||||
#Macos Network Extension doesn't need Widgets
|
||||
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
|
||||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
set(PACKAGES ${PACKAGES} Widgets)
|
||||
endif()
|
||||
|
||||
@@ -49,23 +48,18 @@ set(LIBS ${LIBS}
|
||||
Qt6::Core5Compat Qt6::Concurrent
|
||||
)
|
||||
|
||||
if(IOS OR MACOS_NE)
|
||||
if(IOS)
|
||||
set(LIBS ${LIBS} Qt6::Multimedia)
|
||||
endif()
|
||||
# message("Client desktop build ", ${MACOS_NE})
|
||||
#Macos Network Extension doesn't need Widgets
|
||||
|
||||
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
|
||||
message("Run this block when MACOS_NE is not defined or set to FALSE")
|
||||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
set(LIBS ${LIBS} Qt6::Widgets)
|
||||
endif()
|
||||
|
||||
qt_standard_project_setup()
|
||||
qt_add_executable(${PROJECT} MANUAL_FINALIZATION)
|
||||
|
||||
# TODO error in there
|
||||
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
|
||||
message("Run this block when MACOS_NE is not defined or set to FALSE")
|
||||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep)
|
||||
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_interface.rep)
|
||||
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_tun2socks.rep)
|
||||
@@ -103,18 +97,10 @@ qt6_add_resources(QRC ${I18NQRC} ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc)
|
||||
# -- i18n end
|
||||
|
||||
if(IOS)
|
||||
message("Building for iOS")
|
||||
execute_process(COMMAND bash ${CMAKE_CURRENT_LIST_DIR}/ios/scripts/openvpn.sh args
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
|
||||
endif()
|
||||
|
||||
# Build openvpn adapter for MacOS Network Extension
|
||||
if(MACOS_NE)
|
||||
message("Building for MacOS Network Extension")
|
||||
execute_process(COMMAND bash ${CMAKE_CURRENT_LIST_DIR}/macos/scripts/openvpn.sh args
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
|
||||
endif()
|
||||
|
||||
set(IS_CI ${CI})
|
||||
if(IS_CI)
|
||||
message("Detected CI env")
|
||||
@@ -160,6 +146,7 @@ set(HEADERS ${HEADERS}
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/serialization/transfer.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/enums/apiEnums.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/../common/logger/logger.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/utils/qmlUtils.h
|
||||
)
|
||||
|
||||
# Mozilla headres
|
||||
@@ -175,24 +162,12 @@ include_directories(mozilla)
|
||||
include_directories(mozilla/shared)
|
||||
include_directories(mozilla/models)
|
||||
|
||||
if(MACOS_NE)
|
||||
message("MACOS_NE is ON")
|
||||
add_definitions(-DQ_OS_IOS)
|
||||
add_definitions(-DMACOS_NE)
|
||||
message("Add macros for MacOS Network Extension")
|
||||
else()
|
||||
message("MACOS_NE is OFF")
|
||||
endif()
|
||||
|
||||
|
||||
if(NOT IOS AND NOT MACOS_NE)
|
||||
message(" Add header for non-IOS and non-MACOS_NE")
|
||||
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
|
||||
@@ -223,6 +198,7 @@ set(SOURCES ${SOURCES}
|
||||
${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
|
||||
${CMAKE_CURRENT_LIST_DIR}/utils/qmlUtils.cpp
|
||||
)
|
||||
|
||||
# Mozilla sources
|
||||
@@ -237,7 +213,7 @@ if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
target_compile_definitions(${PROJECT} PRIVATE "MZ_DEBUG")
|
||||
endif()
|
||||
|
||||
if(NOT IOS AND NOT MACOS_NE)
|
||||
if(NOT IOS)
|
||||
set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.cpp
|
||||
)
|
||||
@@ -338,7 +314,6 @@ if(APPLE)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM ${BUILD_VPN_DEVELOPMENT_TEAM})
|
||||
set(CMAKE_XCODE_ATTRIBUTE_GROUP_ID_IOS ${BUILD_IOS_GROUP_IDENTIFIER})
|
||||
|
||||
set(MACOSX_DEPLOYMENT_TARGET "12.0")
|
||||
endif()
|
||||
|
||||
if(LINUX AND NOT ANDROID)
|
||||
@@ -346,9 +321,10 @@ if(LINUX AND NOT ANDROID)
|
||||
link_directories(${CMAKE_CURRENT_LIST_DIR}/platforms/linux)
|
||||
endif()
|
||||
|
||||
# Macos Network Extension doesn't need
|
||||
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
|
||||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
message("Client desktop build")
|
||||
add_compile_definitions(AMNEZIA_DESKTOP)
|
||||
|
||||
set(HEADERS ${HEADERS}
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/ipcclient.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/privileged_process.h
|
||||
@@ -381,12 +357,9 @@ endif()
|
||||
if(IOS)
|
||||
include(cmake/ios.cmake)
|
||||
include(cmake/ios-arch-fixup.cmake)
|
||||
elseif(APPLE AND NOT IOS AND NOT DEFINED MACOS_NE)
|
||||
# include(cmake/osxtools.cmake)
|
||||
include(cmake/macos.cmake)
|
||||
elseif(APPLE AND NOT IOS AND MACOS_NE)
|
||||
elseif(APPLE AND NOT IOS)
|
||||
include(cmake/osxtools.cmake)
|
||||
include(cmake/macos_ne.cmake)
|
||||
include(cmake/macos.cmake)
|
||||
endif()
|
||||
|
||||
target_link_libraries(${PROJECT} PRIVATE ${LIBS})
|
||||
@@ -405,7 +378,7 @@ elseif(APPLE AND NOT IOS)
|
||||
set(DEPLOY_PLATFORM_PATH "macos")
|
||||
endif()
|
||||
|
||||
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
|
||||
if(NOT IOS AND NOT ANDROID)
|
||||
add_custom_command(
|
||||
TARGET ${PROJECT} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E $<IF:$<CONFIG:Debug>,copy_directory,true>
|
||||
@@ -424,36 +397,4 @@ if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
|
||||
endif()
|
||||
|
||||
target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC} ${I18NQRC})
|
||||
if(MACOS_NE)
|
||||
message("Copy MacOS Network Extension files")
|
||||
|
||||
add_custom_command(TARGET ${PROJECT} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory
|
||||
$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks
|
||||
|
||||
COMMAND ${CMAKE_COMMAND} -E echo "Copying OpenVPNAdapter.framework..."
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
${CMAKE_SOURCE_DIR}/client/3rd/OpenVPNAdapter/build/Release-macos/OpenVPNAdapter.framework/Versions/A
|
||||
$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework/Versions/A
|
||||
|
||||
COMMAND ${CMAKE_COMMAND} -E echo "OpenVPNAdapter.framework copied successfully."
|
||||
)
|
||||
|
||||
# MacOS specific application deployment
|
||||
add_custom_command(TARGET ${PROJECT} POST_BUILD
|
||||
COMMAND ${QT_BIN_DIR_DETECTED}/macdeployqt $<TARGET_BUNDLE_DIR:AmneziaVPN> -appstore-compliant -qmldir=${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
|
||||
# MacOS specific code signing for Release
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Release")
|
||||
SET(SIGN_CMD codesign --deep --force --sign 'Apple Distribution: Privacy Technologies OU \(X7UJ388FXK\)' --timestamp --options runtime $<TARGET_BUNDLE_DIR:AmneziaVPN>)
|
||||
message("Manual signing bundle...")
|
||||
message(${SIGN_CMD})
|
||||
|
||||
add_custom_command(TARGET ${PROJECT} POST_BUILD
|
||||
COMMAND ${SIGN_CMD}
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
qt_finalize_target(${PROJECT})
|
||||
|
||||
@@ -404,6 +404,9 @@ void AmneziaApplication::initControllers()
|
||||
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_apiServicesModel, m_settings));
|
||||
m_engine->rootContext()->setContextProperty("InstallController", m_installController.get());
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "ui/controllers/exportController.h"
|
||||
#include "ui/controllers/importController.h"
|
||||
#include "ui/controllers/installController.h"
|
||||
#include "ui/controllers/focusController.h"
|
||||
#include "ui/controllers/pageController.h"
|
||||
#include "ui/controllers/settingsController.h"
|
||||
#include "ui/controllers/sitesController.h"
|
||||
@@ -124,6 +125,7 @@ private:
|
||||
#endif
|
||||
|
||||
QScopedPointer<ConnectionController> m_connectionController;
|
||||
QScopedPointer<FocusController> m_focusController;
|
||||
QScopedPointer<PageController> m_pageController;
|
||||
QScopedPointer<InstallController> m_installController;
|
||||
QScopedPointer<ImportController> m_importController;
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<!-- To request network state -->
|
||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" android:maxSdkVersion="30" />
|
||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
@@ -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
|
After Width: | Height: | Size: 15 KiB |
BIN
client/android/res/mipmap-mdpi/ic_banner.png
Normal file
|
After Width: | Height: | Size: 9.9 KiB |
|
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
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
package org.amnezia.vpn.protocol.wireguard
|
||||
|
||||
import android.net.VpnService.Builder
|
||||
import java.io.IOException
|
||||
import java.util.Locale
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.launch
|
||||
import org.amnezia.awg.GoBackend
|
||||
import org.amnezia.vpn.protocol.Protocol
|
||||
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
|
||||
@@ -27,6 +28,8 @@ open class Wireguard : Protocol() {
|
||||
|
||||
private var tunnelHandle: Int = -1
|
||||
protected open val ifName: String = "amn0"
|
||||
private lateinit var scope: CoroutineScope
|
||||
private var statusJob: Job? = null
|
||||
|
||||
override val statistics: Statistics
|
||||
get() {
|
||||
@@ -49,46 +52,17 @@ open class Wireguard : Protocol() {
|
||||
|
||||
override fun internalInit() {
|
||||
if (!isInitialized) loadSharedLibrary(context, "wg-go")
|
||||
if (this::scope.isInitialized) {
|
||||
scope.cancel()
|
||||
}
|
||||
scope = CoroutineScope(Dispatchers.IO)
|
||||
}
|
||||
|
||||
override suspend fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) {
|
||||
val wireguardConfig = parseConfig(config)
|
||||
val startTime = System.currentTimeMillis()
|
||||
start(wireguardConfig, vpnBuilder, protect)
|
||||
waitForConnection(startTime)
|
||||
state.value = CONNECTED
|
||||
}
|
||||
|
||||
private suspend fun waitForConnection(startTime: Long) {
|
||||
Log.d(TAG, "Waiting for connection")
|
||||
withContext(Dispatchers.IO) {
|
||||
val time = String.format(Locale.ROOT,"%.3f", startTime / 1000.0)
|
||||
try {
|
||||
delay(1000)
|
||||
var log = getLogcat(time)
|
||||
Log.v(TAG, "First waiting log: $log")
|
||||
// check that there is a connection log,
|
||||
// to avoid infinite connection
|
||||
if (!log.contains("Attaching to interface")) {
|
||||
Log.w(TAG, "Logs do not contain a connection log")
|
||||
return@withContext
|
||||
}
|
||||
while (!log.contains("Received handshake response")) {
|
||||
delay(1000)
|
||||
log = getLogcat(time)
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Log.e(TAG, "Failed to get logcat: $e")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLogcat(time: String): String =
|
||||
ProcessBuilder("logcat", "--buffer=main", "--format=raw", "*:S AmneziaWG/awg0", "-t", time)
|
||||
.redirectErrorStream(true)
|
||||
.start()
|
||||
.inputStream.reader().readText()
|
||||
|
||||
protected open fun parseConfig(config: JSONObject): WireguardConfig {
|
||||
val configData = config.getJSONObject("wireguard_config_data")
|
||||
return WireguardConfig.build {
|
||||
@@ -178,6 +152,43 @@ open class Wireguard : Protocol() {
|
||||
tunnelHandle = -1
|
||||
throw VpnStartException("Protect VPN interface: permission not granted or revoked")
|
||||
}
|
||||
launchStatusJob()
|
||||
}
|
||||
|
||||
private fun launchStatusJob() {
|
||||
Log.d(TAG, "Launch status job")
|
||||
statusJob = scope.launch {
|
||||
while (true) {
|
||||
val lastHandshake = getLastHandshake()
|
||||
Log.v(TAG, "lastHandshake=$lastHandshake")
|
||||
if (lastHandshake == 0L) {
|
||||
delay(1000)
|
||||
continue
|
||||
}
|
||||
if (lastHandshake == -2L || lastHandshake > 0L) state.value = CONNECTED
|
||||
else if (lastHandshake == -1L) state.value = DISCONNECTED
|
||||
statusJob = null
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLastHandshake(): Long {
|
||||
if (tunnelHandle == -1) {
|
||||
Log.e(TAG, "Trying to get config of a non-existent tunnel")
|
||||
return -1
|
||||
}
|
||||
val config = GoBackend.awgGetConfig(tunnelHandle)
|
||||
if (config == null) {
|
||||
Log.e(TAG, "Failed to get tunnel config")
|
||||
return -2
|
||||
}
|
||||
val lastHandshake = config.lines().find { it.startsWith("last_handshake_time_sec=") }?.substring(24)?.toLong()
|
||||
if (lastHandshake == null) {
|
||||
Log.e(TAG, "Failed to get last_handshake_time_sec")
|
||||
return -2
|
||||
}
|
||||
return lastHandshake
|
||||
}
|
||||
|
||||
override fun stopVpn() {
|
||||
@@ -185,6 +196,8 @@ open class Wireguard : Protocol() {
|
||||
Log.w(TAG, "Tunnel already down")
|
||||
return
|
||||
}
|
||||
statusJob?.cancel()
|
||||
statusJob = null
|
||||
val handleToClose = tunnelHandle
|
||||
tunnelHandle = -1
|
||||
GoBackend.awgTurnOff(handleToClose)
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
message("Client ==> MacOS NE build")
|
||||
|
||||
set_target_properties(${PROJECT} PROPERTIES MACOSX_BUNDLE TRUE)
|
||||
set(CMAKE_OSX_ARCHITECTURES "x86_64")
|
||||
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15)
|
||||
set(CMAKE_XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT "dwarf-with-dsym")
|
||||
set(APPLE_PROJECT_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||
|
||||
|
||||
enable_language(OBJC)
|
||||
enable_language(Swift)
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS ShaderTools)
|
||||
set(LIBS ${LIBS} Qt6::ShaderTools)
|
||||
|
||||
find_library(FW_AUTHENTICATIONSERVICES AuthenticationServices)
|
||||
find_library(FW_AVFOUNDATION AVFoundation)
|
||||
find_library(FW_FOUNDATION Foundation)
|
||||
find_library(FW_STOREKIT StoreKit)
|
||||
find_library(FW_USERNOTIFICATIONS UserNotifications)
|
||||
find_library(FW_NETWORKEXTENSION NetworkExtension)
|
||||
|
||||
set(LIBS ${LIBS}
|
||||
${FW_AUTHENTICATIONSERVICES}
|
||||
${FW_AVFOUNDATION}
|
||||
${FW_FOUNDATION}
|
||||
${FW_STOREKIT}
|
||||
${FW_USERNOTIFICATIONS}
|
||||
${FW_NETWORKEXTENSION}
|
||||
)
|
||||
|
||||
|
||||
set(HEADERS ${HEADERS}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate-C-Interface.h
|
||||
)
|
||||
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h PROPERTIES OBJECTIVE_CPP_HEADER TRUE)
|
||||
|
||||
|
||||
set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
|
||||
)
|
||||
|
||||
target_include_directories(${PROJECT} PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS})
|
||||
|
||||
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
|
||||
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Info.plist.in
|
||||
MACOSX_BUNDLE_ICON_FILE "AppIcon"
|
||||
MACOSX_BUNDLE_INFO_STRING "AmneziaVPN"
|
||||
MACOSX_BUNDLE_BUNDLE_NAME "AmneziaVPN"
|
||||
MACOSX_BUNDLE_BUNDLE_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
|
||||
MACOSX_BUNDLE_LONG_VERSION_STRING "${APPLE_PROJECT_VERSION}-${CMAKE_PROJECT_VERSION_TWEAK}"
|
||||
MACOSX_BUNDLE_SHORT_VERSION_STRING "${APPLE_PROJECT_VERSION}"
|
||||
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}"
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/ios/app/main.entitlements"
|
||||
XCODE_ATTRIBUTE_MARKETING_VERSION "${APPLE_PROJECT_VERSION}"
|
||||
XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
|
||||
XCODE_ATTRIBUTE_PRODUCT_NAME "AmneziaVPN"
|
||||
XCODE_ATTRIBUTE_BUNDLE_INFO_STRING "AmneziaVPN"
|
||||
XCODE_GENERATE_SCHEME TRUE
|
||||
XCODE_ATTRIBUTE_ENABLE_BITCODE "NO"
|
||||
XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME "AppIcon"
|
||||
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2"
|
||||
XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY "NO"
|
||||
XCODE_EMBED_FRAMEWORKS_REMOVE_HEADERS_ON_COPY "YES"
|
||||
|
||||
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
|
||||
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../Frameworks"
|
||||
XCODE_EMBED_APP_EXTENSIONS networkextension
|
||||
|
||||
# XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution: Privacy Technologies OU (X7UJ388FXK)"
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
|
||||
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "Mac AppStore AmneziaVPN"
|
||||
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "org.amnezia.AmneziaVPNManual"
|
||||
)
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
|
||||
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES"
|
||||
XCODE_ATTRIBUTE_SWIFT_PRECOMPILE_BRIDGING_HEADER "NO"
|
||||
XCODE_ATTRIBUTE_SWIFT_OBJC_INTERFACE_HEADER_NAME "AmneziaVPN-Swift.h"
|
||||
XCODE_ATTRIBUTE_SWIFT_OBJC_INTEROP_MODE "objcxx"
|
||||
)
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "X7UJ388FXK"
|
||||
)
|
||||
target_include_directories(${PROJECT} PRIVATE ${CMAKE_CURRENT_LIST_DIR})
|
||||
target_compile_options(${PROJECT} PRIVATE
|
||||
-DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\"
|
||||
-DVPN_NE_BUNDLEID=\"${BUILD_IOS_APP_IDENTIFIER}.network-extension\"
|
||||
)
|
||||
|
||||
set(WG_APPLE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rd/amneziawg-apple/Sources)
|
||||
|
||||
target_sources(${PROJECT} PRIVATE
|
||||
${WG_APPLE_SOURCE_DIR}/WireGuardKitC/x25519.c
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/LogController.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/Log.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/ScreenProtection.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/VPNCController.swift
|
||||
)
|
||||
|
||||
target_sources(${PROJECT} PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Images.xcassets
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
|
||||
)
|
||||
|
||||
set_property(TARGET ${PROJECT} APPEND PROPERTY RESOURCE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Images.xcassets
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
|
||||
)
|
||||
|
||||
add_subdirectory(macos/networkextension)
|
||||
add_dependencies(${PROJECT} networkextension)
|
||||
|
||||
get_target_property(QtCore_location Qt6::Core LOCATION)
|
||||
message("QtCore_location")
|
||||
message(${QtCore_location})
|
||||
|
||||
get_filename_component(QT_BIN_DIR_DETECTED "${QtCore_location}/../../../../../bin" ABSOLUTE)
|
||||
|
||||
set_property(TARGET ${PROJECT} PROPERTY XCODE_EMBED_FRAMEWORKS
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/3rd/OpenVPNAdapter/build/Release-macos/OpenVPNAdapter.framework"
|
||||
)
|
||||
|
||||
set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/3rd/OpenVPNAdapter/build/Release-macos)
|
||||
target_link_libraries("networkextension" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/3rd/OpenVPNAdapter/build/Release-macos/OpenVPNAdapter.framework")
|
||||
|
||||
@@ -76,7 +76,7 @@ function(osx_bundle_assetcatalog TARGET)
|
||||
)
|
||||
|
||||
## Patch the asset catalog into the target bundle.
|
||||
if(NOT IOS AND NOT MACOS_NE)
|
||||
if(NOT IOS)
|
||||
set(XCASSETS_RESOURCE_DIR "Resources")
|
||||
endif()
|
||||
add_custom_command(TARGET ${TARGET} POST_BUILD
|
||||
@@ -141,7 +141,6 @@ function(osx_codesign_target TARGET)
|
||||
endif()
|
||||
|
||||
foreach(FILE ${CODESIGN_FILES})
|
||||
message(STATUS "Signing ${TARGET}: ${FILE}")
|
||||
add_custom_command(TARGET ${TARGET} POST_BUILD VERBATIM
|
||||
COMMAND ${COMMENT_ECHO_COMMAND} "Signing ${TARGET}: ${FILE}"
|
||||
COMMAND ${CODESIGN_BIN} ${CODESIGN_ARGS} ${FILE}
|
||||
|
||||
@@ -102,9 +102,7 @@ QProcessEnvironment SshConfigurator::prepareEnv()
|
||||
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\cygwin;");
|
||||
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\openvpn;");
|
||||
#elif defined(Q_OS_MACX)
|
||||
#if !defined(MACOS_NE)
|
||||
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "/Contents/MacOS");
|
||||
#endif
|
||||
#endif
|
||||
|
||||
env.insert("PATH", pathEnvVar);
|
||||
|
||||
@@ -120,7 +120,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
|
||||
}
|
||||
}
|
||||
|
||||
QString subnetIp = containerConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress);
|
||||
QString subnetIp = containerConfig.value(m_protocolName).toObject().value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress);
|
||||
{
|
||||
QStringList l = subnetIp.split(".", Qt::SkipEmptyParts);
|
||||
if (l.isEmpty()) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -50,6 +50,8 @@ namespace
|
||||
constexpr char authData[] = "auth_data";
|
||||
}
|
||||
|
||||
const int requestTimeoutMsecs = 12 * 1000; // 12 secs
|
||||
|
||||
ErrorCode checkErrors(const QList<QSslError> &sslErrors, QNetworkReply *reply)
|
||||
{
|
||||
if (!sslErrors.empty()) {
|
||||
@@ -177,7 +179,7 @@ void ApiController::fillServerConfig(const QString &protocol, const ApiControlle
|
||||
QStringList ApiController::getProxyUrls()
|
||||
{
|
||||
QNetworkRequest request;
|
||||
request.setTransferTimeout(7000);
|
||||
request.setTransferTimeout(requestTimeoutMsecs);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
QEventLoop wait;
|
||||
@@ -280,7 +282,7 @@ void ApiController::updateServerConfigFromApi(const QString &installationUuid, c
|
||||
|
||||
if (serverConfig.value(config_key::configVersion).toInt()) {
|
||||
QNetworkRequest request;
|
||||
request.setTransferTimeout(7000);
|
||||
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();
|
||||
@@ -336,7 +338,7 @@ ErrorCode ApiController::getServicesList(QByteArray &responseBody)
|
||||
#endif
|
||||
|
||||
QNetworkRequest request;
|
||||
request.setTransferTimeout(7000);
|
||||
request.setTransferTimeout(requestTimeoutMsecs);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
request.setUrl(QString("%1v1/services").arg(m_gatewayEndpoint));
|
||||
@@ -377,6 +379,13 @@ ErrorCode ApiController::getServicesList(QByteArray &responseBody)
|
||||
|
||||
auto errorCode = checkErrors(sslErrors, reply);
|
||||
reply->deleteLater();
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
if (!responseBody.contains("services")) {
|
||||
return ErrorCode::ApiServicesMissingError;
|
||||
}
|
||||
}
|
||||
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
@@ -390,7 +399,7 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co
|
||||
#endif
|
||||
|
||||
QNetworkRequest request;
|
||||
request.setTransferTimeout(7000);
|
||||
request.setTransferTimeout(requestTimeoutMsecs);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
|
||||
request.setUrl(QString("%1v1/config").arg(m_gatewayEndpoint));
|
||||
|
||||
@@ -346,7 +346,9 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
|
||||
}
|
||||
|
||||
if (container == DockerContainer::Awg) {
|
||||
if ((oldProtoConfig.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))
|
||||
@@ -370,8 +372,10 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -607,6 +611,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() } });
|
||||
@@ -751,10 +757,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 +770,14 @@ 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("wheel"))
|
||||
return ErrorCode::ServerUserNotInSudo;
|
||||
if (credentials.userName != "root" && stdOut.contains("sudo:") && !stdOut.contains("uname:") && stdOut.contains("not found"))
|
||||
return ErrorCode::SudoPackageIsNotPreinstalled;
|
||||
if (stdOut.contains("sudoers"))
|
||||
return ErrorCode::ServerUserNotAllowedInSudoers;
|
||||
if (stdOut.contains("password is required"))
|
||||
return ErrorCode::ServerUserPasswordRequired;
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
@@ -56,6 +56,9 @@ namespace amnezia
|
||||
ServerCancelInstallation = 204,
|
||||
ServerUserNotInSudo = 205,
|
||||
ServerPacketManagerError = 206,
|
||||
SudoPackageIsNotPreinstalled = 207,
|
||||
ServerUserNotAllowedInSudoers = 208,
|
||||
ServerUserPasswordRequired = 209,
|
||||
|
||||
// Ssh connection errors
|
||||
SshRequestDeniedError = 300,
|
||||
@@ -109,6 +112,7 @@ namespace amnezia
|
||||
ApiConfigSslError = 1104,
|
||||
ApiMissingAgwPublicKey = 1105,
|
||||
ApiConfigDecryptionError = 1106,
|
||||
ApiServicesMissingError = 1107,
|
||||
|
||||
// QFile errors
|
||||
OpenError = 1200,
|
||||
|
||||
@@ -19,8 +19,11 @@ 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::SudoPackageIsNotPreinstalled): errorMessage = QObject::tr("The sudo package is not pre-installed"); 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;
|
||||
|
||||
// Libssh errors
|
||||
case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break;
|
||||
@@ -63,7 +66,8 @@ QString errorString(ErrorCode code) {
|
||||
case (ErrorCode::ApiConfigTimeoutError): errorMessage = QObject::tr("Server response timeout on api request"); break;
|
||||
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;
|
||||
|
||||
// QFile errors
|
||||
case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;
|
||||
case(ErrorCode::ReadError): errorMessage = QObject::tr("QFile error: An error occurred when reading from the file"); break;
|
||||
|
||||
@@ -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,15 +146,6 @@ 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)) {
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
|
||||
@@ -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
@@ -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 |
@@ -16,7 +16,5 @@
|
||||
<array>
|
||||
<string>$(AppIdentifierPrefix)group.org.amnezia.AmneziaVPN</string>
|
||||
</array>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 421 B After Width: | Height: | Size: 336 B |
|
Before Width: | Height: | Size: 946 B After Width: | Height: | Size: 593 B |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 946 B After Width: | Height: | Size: 593 B |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 24 KiB |
@@ -1,68 +1,68 @@
|
||||
{
|
||||
"images": [
|
||||
"images" : [
|
||||
{
|
||||
"idiom": "mac",
|
||||
"size": "16x16",
|
||||
"scale": "1x",
|
||||
"filename": "16.png"
|
||||
"filename" : "16.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"idiom": "mac",
|
||||
"size": "16x16",
|
||||
"scale": "2x",
|
||||
"filename": "16@2x.png"
|
||||
"filename" : "16@2x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "16x16"
|
||||
},
|
||||
{
|
||||
"idiom": "mac",
|
||||
"size": "32x32",
|
||||
"scale": "1x",
|
||||
"filename": "32.png"
|
||||
"filename" : "32.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"idiom": "mac",
|
||||
"size": "32x32",
|
||||
"scale": "2x",
|
||||
"filename": "32@2x.png"
|
||||
"filename" : "32@2x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "32x32"
|
||||
},
|
||||
{
|
||||
"idiom": "mac",
|
||||
"size": "128x128",
|
||||
"scale": "1x",
|
||||
"filename": "128.png"
|
||||
"filename" : "128.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"idiom": "mac",
|
||||
"size": "128x128",
|
||||
"scale": "2x",
|
||||
"filename": "128@2x.png"
|
||||
"filename" : "128@2x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "128x128"
|
||||
},
|
||||
{
|
||||
"idiom": "mac",
|
||||
"size": "256x256",
|
||||
"scale": "1x",
|
||||
"filename": "256.png"
|
||||
"filename" : "256.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"idiom": "mac",
|
||||
"size": "256x256",
|
||||
"scale": "2x",
|
||||
"filename": "256@2x.png"
|
||||
"filename" : "256@2x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "256x256"
|
||||
},
|
||||
{
|
||||
"idiom": "mac",
|
||||
"size": "512x512",
|
||||
"scale": "1x",
|
||||
"filename": "512.png"
|
||||
"filename" : "512.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "1x",
|
||||
"size" : "512x512"
|
||||
},
|
||||
{
|
||||
"idiom": "mac",
|
||||
"size": "512x512",
|
||||
"scale": "2x",
|
||||
"filename": "512@2x.png"
|
||||
"filename" : "512@2x.png",
|
||||
"idiom" : "mac",
|
||||
"scale" : "2x",
|
||||
"size" : "512x512"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"version": 1,
|
||||
"author": "xcode"
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,68 +1,6 @@
|
||||
{
|
||||
"images": [
|
||||
{
|
||||
"idiom": "mac",
|
||||
"size": "16x16",
|
||||
"scale": "1x",
|
||||
"filename": "16.png"
|
||||
},
|
||||
{
|
||||
"idiom": "mac",
|
||||
"size": "16x16",
|
||||
"scale": "2x",
|
||||
"filename": "16@2x.png"
|
||||
},
|
||||
{
|
||||
"idiom": "mac",
|
||||
"size": "32x32",
|
||||
"scale": "1x",
|
||||
"filename": "32.png"
|
||||
},
|
||||
{
|
||||
"idiom": "mac",
|
||||
"size": "32x32",
|
||||
"scale": "2x",
|
||||
"filename": "32@2x.png"
|
||||
},
|
||||
{
|
||||
"idiom": "mac",
|
||||
"size": "128x128",
|
||||
"scale": "1x",
|
||||
"filename": "128.png"
|
||||
},
|
||||
{
|
||||
"idiom": "mac",
|
||||
"size": "128x128",
|
||||
"scale": "2x",
|
||||
"filename": "128@2x.png"
|
||||
},
|
||||
{
|
||||
"idiom": "mac",
|
||||
"size": "256x256",
|
||||
"scale": "1x",
|
||||
"filename": "256.png"
|
||||
},
|
||||
{
|
||||
"idiom": "mac",
|
||||
"size": "256x256",
|
||||
"scale": "2x",
|
||||
"filename": "256@2x.png"
|
||||
},
|
||||
{
|
||||
"idiom": "mac",
|
||||
"size": "512x512",
|
||||
"scale": "1x",
|
||||
"filename": "512.png"
|
||||
},
|
||||
{
|
||||
"idiom": "mac",
|
||||
"size": "512x512",
|
||||
"scale": "2x",
|
||||
"filename": "512@2x.png"
|
||||
}
|
||||
],
|
||||
"info": {
|
||||
"version": 1,
|
||||
"author": "xcode"
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,184 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleAllowMixedLocalizations</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>${QT_INTERNAL_DOLLAR_VAR}{PRODUCT_NAME}</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.utilities</string>
|
||||
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>${MACOSX_DEPLOYMENT_TARGET}</string>
|
||||
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>AmneziaVPNLaunchScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array/>
|
||||
<key>UIRequiresFullScreen</key>
|
||||
<true/>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array/>
|
||||
<key>UIUserInterfaceStyle</key>
|
||||
<string>Light</string>
|
||||
<key>com.wireguard.ios.app_group_id</key>
|
||||
<string>group.org.amnezia.AmneziaVPN</string>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<true/>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Amnezia VPN needs access to the camera for reading QR-codes.</string>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<false/>
|
||||
<key>NSAllowsLocalNetworking</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>CFBundleIcons</key>
|
||||
<dict/>
|
||||
<key>CFBundleIcons~ipad</key>
|
||||
<dict/>
|
||||
<key>UTImportedTypeDeclarations</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.data</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>Amnezia VPN config</string>
|
||||
<key>UTTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>org.amnezia.AmneziaVPN.amnezia-config</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>vpn</string>
|
||||
</array>
|
||||
<key>public.mime-type</key>
|
||||
<array>
|
||||
<string>text/plain</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.data</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>WireGuard config</string>
|
||||
<key>UTTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>org.amnezia.AmneziaVPN.wireguard-config</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>conf</string>
|
||||
<string>cfg</string>
|
||||
</array>
|
||||
<key>public.mime-type</key>
|
||||
<array>
|
||||
<string>text/plain</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.data</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>OpenVPN config</string>
|
||||
<key>UTTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>org.amnezia.AmneziaVPN.openvpn-config</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>ovpn</string>
|
||||
</array>
|
||||
<key>public.mime-type</key>
|
||||
<array>
|
||||
<string>text/plain</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.data</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>AmneziaVPN backup file</string>
|
||||
<key>UTTypeIconFiles</key>
|
||||
<array/>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>org.amnezia.AmneziaVPN.backup-config</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>backup</string>
|
||||
</array>
|
||||
<key>public.mime-type</key>
|
||||
<array>
|
||||
<string>text/plain</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Amnezia VPN config</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Alternate</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>org.amnezia.AmneziaVPN.amnezia-config</string>
|
||||
<string>org.amnezia.AmneziaVPN.wireguard-config</string>
|
||||
<string>org.amnezia.AmneziaVPN.openvpn-config</string>
|
||||
<string>org.amnezia.AmneziaVPN.backup-config</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -3,52 +3,40 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.application-identifier</key>
|
||||
<string>X7UJ388FXK.org.amnezia.AmneziaVPN.network-extension</string>
|
||||
<string>$(DEVELOPMENT_TEAM).$(NETEXT_ID_MACOS)</string>
|
||||
|
||||
<key>com.apple.developer.networking.networkextension</key>
|
||||
<array>
|
||||
<string>packet-tunnel-provider</string>
|
||||
</array>
|
||||
<key>com.apple.developer.team-identifier</key>
|
||||
<string>X7UJ388FXK</string>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.org.amnezia.AmneziaVPN</string>
|
||||
</array>
|
||||
<key>com.apple.security.assets.movies.read-write</key>
|
||||
<true/>
|
||||
<key>com.apple.security.assets.music.read-write</key>
|
||||
<true/>
|
||||
<key>com.apple.security.assets.pictures.read-write</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.audio-input</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.bluetooth</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.camera</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.usb</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.downloads.read-write</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-write</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.server</key>
|
||||
<true/>
|
||||
<key>com.apple.security.personal-information.addressbook</key>
|
||||
<true/>
|
||||
<key>com.apple.security.personal-information.calendars</key>
|
||||
<true/>
|
||||
<key>com.apple.security.personal-information.location</key>
|
||||
<true/>
|
||||
<key>com.apple.security.print</key>
|
||||
<true/>
|
||||
|
||||
<key>keychain-access-groups</key>
|
||||
<array>
|
||||
<string>$(AppIdentifierPrefix)org.amnezia.AmneziaVPN.network-extension</string>
|
||||
<string>$(DEVELOPMENT_TEAM).*</string>
|
||||
</array>
|
||||
|
||||
<key>com.apple.developer.team-identifier</key>
|
||||
<string>$(DEVELOPMENT_TEAM)</string>
|
||||
|
||||
<key>com.apple.developer.system-extension.install</key>
|
||||
<true/>
|
||||
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>$(DEVELOPMENT_TEAM).$(GROUP_ID_MACOS)</string>
|
||||
</array>
|
||||
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
|
||||
<key>com.apple.security.network.server</key>
|
||||
<true/>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.private.network.socket-delegate</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -1,139 +0,0 @@
|
||||
enable_language(Swift)
|
||||
message("Client message >> macos build >> networkextension")
|
||||
set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../..)
|
||||
|
||||
add_executable(networkextension)
|
||||
|
||||
if(MACOS_NE)
|
||||
message("MACOS_NE is ON")
|
||||
add_definitions(-DQ_OS_IOS)
|
||||
add_definitions(-DMACOS_NE)
|
||||
else()
|
||||
message("MACOS_NE is OFF")
|
||||
endif()
|
||||
message("executable_path is: @executable_path/../../Frameworks")
|
||||
set_target_properties(networkextension PROPERTIES
|
||||
XCODE_PRODUCT_TYPE com.apple.product-type.app-extension
|
||||
BUNDLE_EXTENSION appex
|
||||
|
||||
MACOSX_BUNDLE_SHORT_VERSION_STRING "${APPLE_PROJECT_VERSION}"
|
||||
MACOSX_BUNDLE_INFO_STRING "AmneziaVPNNetworkExtension"
|
||||
MACOSX_BUNDLE_BUNDLE_NAME "AmneziaVPNNetworkExtension"
|
||||
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}.network-extension"
|
||||
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_NAME "${BUILD_IOS_APP_IDENTIFIER}.network-extension"
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS ${CMAKE_CURRENT_SOURCE_DIR}/AmneziaVPNNetworkExtension.entitlements
|
||||
XCODE_ATTRIBUTE_MARKETING_VERSION "${APP_MAJOR_VERSION}"
|
||||
XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION "${BUILD_ID}"
|
||||
XCODE_ATTRIBUTE_PRODUCT_NAME "AmneziaVPNNetworkExtension"
|
||||
|
||||
XCODE_ATTRIBUTE_APPLICATION_EXTENSION_API_ONLY "YES"
|
||||
XCODE_ATTRIBUTE_ENABLE_BITCODE "NO"
|
||||
|
||||
# XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
|
||||
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "Mac AppStore AmneziaVPN.network-extension"
|
||||
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "amnezia.AmneziaVPN.network-extensionManual"
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution: Privacy Technologies OU (X7UJ388FXK)"
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
|
||||
|
||||
|
||||
XCODE_ATTRIBUTE_INFOPLIST_FILE ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in
|
||||
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../../../../Frameworks @loader_path/../../../../Frameworks"
|
||||
)
|
||||
|
||||
set_target_properties(networkextension PROPERTIES
|
||||
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
|
||||
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES"
|
||||
XCODE_ATTRIBUTE_SWIFT_OBJC_BRIDGING_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/WireGuardNetworkExtension-Bridging-Header.h"
|
||||
XCODE_ATTRIBUTE_SWIFT_OPTIMIZATION_LEVEL "-Onone"
|
||||
XCODE_ATTRIBUTE_SWIFT_PRECOMPILE_BRIDGING_HEADER "NO"
|
||||
)
|
||||
|
||||
set_target_properties("networkextension" PROPERTIES
|
||||
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "X7UJ388FXK"
|
||||
)
|
||||
|
||||
find_library(FW_ASSETS_LIBRARY AssetsLibrary)
|
||||
find_library(FW_MOBILE_CORE MobileCoreServices)
|
||||
find_library(FW_UI_KIT UIKit)
|
||||
find_library(FW_LIBRESOLV libresolv.9.tbd)
|
||||
|
||||
|
||||
# Set the root directory
|
||||
set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../..)
|
||||
|
||||
set(CMAKE_FRAMEWORK_PATH ${CLIENT_ROOT_DIR}/3rd/OpenVPNAdapter/build/Release-macos)
|
||||
|
||||
target_link_libraries(networkextension PRIVATE ${FW_LIBRESOLV})
|
||||
|
||||
target_compile_options(networkextension PRIVATE -DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\")
|
||||
target_compile_options(networkextension PRIVATE -DNETWORK_EXTENSION=1)
|
||||
|
||||
set(WG_APPLE_SOURCE_DIR ${CLIENT_ROOT_DIR}/3rd/amneziawg-apple/Sources)
|
||||
|
||||
message("WG_APPLE_SOURCE_DIR is: ${WG_APPLE_SOURCE_DIR}")
|
||||
message("CLIENT_ROOT_DIR is: ${CLIENT_ROOT_DIR}")
|
||||
|
||||
target_sources(networkextension PRIVATE
|
||||
${WG_APPLE_SOURCE_DIR}/WireGuardKit/WireGuardAdapter.swift
|
||||
${WG_APPLE_SOURCE_DIR}/WireGuardKit/PacketTunnelSettingsGenerator.swift
|
||||
${WG_APPLE_SOURCE_DIR}/WireGuardKit/DNSResolver.swift
|
||||
${WG_APPLE_SOURCE_DIR}/WireGuardNetworkExtension/ErrorNotifier.swift
|
||||
${WG_APPLE_SOURCE_DIR}/Shared/Keychain.swift
|
||||
${WG_APPLE_SOURCE_DIR}/Shared/Model/TunnelConfiguration+WgQuickConfig.swift
|
||||
${WG_APPLE_SOURCE_DIR}/Shared/Model/NETunnelProviderProtocol+Extension.swift
|
||||
${WG_APPLE_SOURCE_DIR}/Shared/Model/String+ArrayConversion.swift
|
||||
${WG_APPLE_SOURCE_DIR}/WireGuardKit/TunnelConfiguration.swift
|
||||
${WG_APPLE_SOURCE_DIR}/WireGuardKit/IPAddressRange.swift
|
||||
${WG_APPLE_SOURCE_DIR}/WireGuardKit/Endpoint.swift
|
||||
${WG_APPLE_SOURCE_DIR}/WireGuardKit/DNSServer.swift
|
||||
${WG_APPLE_SOURCE_DIR}/WireGuardKit/InterfaceConfiguration.swift
|
||||
${WG_APPLE_SOURCE_DIR}/WireGuardKit/PeerConfiguration.swift
|
||||
${WG_APPLE_SOURCE_DIR}/Shared/FileManager+Extension.swift
|
||||
${WG_APPLE_SOURCE_DIR}/WireGuardKitC/x25519.c
|
||||
${WG_APPLE_SOURCE_DIR}/WireGuardKit/Array+ConcurrentMap.swift
|
||||
${WG_APPLE_SOURCE_DIR}/WireGuardKit/IPAddress+AddrInfo.swift
|
||||
${WG_APPLE_SOURCE_DIR}/WireGuardKit/PrivateKey.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/HevSocksTunnel.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/NELogController.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/Log.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+WireGuard.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+OpenVPN.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+Xray.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/WGConfig.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/iosglue.mm
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/XrayConfig.swift
|
||||
)
|
||||
|
||||
target_sources(networkextension PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/PrivacyInfo.xcprivacy
|
||||
)
|
||||
|
||||
set_property(TARGET networkextension APPEND PROPERTY RESOURCE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/PrivacyInfo.xcprivacy
|
||||
)
|
||||
|
||||
## Build wireguard-go-version.h
|
||||
execute_process(
|
||||
COMMAND go list -m golang.zx2c4.com/wireguard
|
||||
WORKING_DIRECTORY ${CLIENT_ROOT_DIR}/3rd/wireguard-apple/Sources/WireGuardKitGo
|
||||
OUTPUT_VARIABLE WG_VERSION_FULL
|
||||
)
|
||||
string(REGEX REPLACE ".*v\([0-9.]*\).*" "\\1" WG_VERSION_STRING 1.1.1)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/wireguard-go-version.h.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/wireguard-go-version.h)
|
||||
target_sources(networkextension PRIVATE
|
||||
${CMAKE_CURRENT_BINARY_DIR}/wireguard-go-version.h)
|
||||
|
||||
target_include_directories(networkextension PRIVATE ${CLIENT_ROOT_DIR})
|
||||
target_include_directories(networkextension PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
target_link_libraries(networkextension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/wireguard/macos/x86_64/libwg-go.a)
|
||||
|
||||
message(${CLIENT_ROOT_DIR})
|
||||
message(${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/xray/HevSocks5Tunnel.xcframework/macos-arm64_x86_64/libhev-socks5-tunnel.a)
|
||||
target_link_libraries(networkextension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/xray/HevSocks5Tunnel.xcframework/macos-arm64_x86_64/libhev-socks5-tunnel.a)
|
||||
|
||||
target_include_directories(networkextension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/xray/HevSocks5Tunnel.xcframework/macos-arm64_x86_64/Headers)
|
||||
@@ -3,32 +3,27 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>AmneziaVPNNetworkExtension</string>
|
||||
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>org.amnezia.AmneziaVPN.network-extension</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>AmneziaVPNNetworkExtension</string>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>${APPLE_PROJECT_VERSION}</string>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${CMAKE_PROJECT_VERSION_TWEAK}</string>
|
||||
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>${CMAKE_OSX_DEPLOYMENT_TARGET}</string>
|
||||
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>AmneziaVPNNetworkExtension</string>
|
||||
|
||||
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
@@ -36,11 +31,5 @@
|
||||
<key>NSExtensionPrincipalClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).PacketTunnelProvider</string>
|
||||
</dict>
|
||||
|
||||
<key>com.wireguard.ios.app_group_id</key>
|
||||
<string>group.org.amnezia.AmneziaVPN</string>
|
||||
|
||||
<key>com.wireguard.macos.app_group_id</key>
|
||||
<string>${BUILD_VPN_DEVELOPMENT_TEAM}.group.org.amnezia.AmneziaVPN</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,25 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPITypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>1C8F.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>NSPrivacyAccessedAPIType</key>
|
||||
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
|
||||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>C617.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,10 +1,10 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
|
||||
#include "macos/gobridge/wireguard.h"
|
||||
#include "wireguard-go-version.h"
|
||||
#include "3rd/amneziawg-apple/Sources/WireGuardKitGo/wireguard.h"
|
||||
#include "3rd/amneziawg-apple/Sources/WireGuardKitC/WireGuardKitC.h"
|
||||
#include "3rd/awg-apple/Sources/WireGuardKitC/WireGuardKitC.h"
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
@@ -23,8 +23,3 @@ bool key_from_hex(uint8_t key[WG_KEY_LEN], const char* hex);
|
||||
bool key_eq(const uint8_t key1[WG_KEY_LEN], const uint8_t key2[WG_KEY_LEN]);
|
||||
|
||||
void write_msg_to_log(const char* tag, const char* msg);
|
||||
|
||||
// init function definition in C
|
||||
void hev_socks5_tunnel_quit(void);
|
||||
// Updated function definition in C
|
||||
int hev_socks5_tunnel_main(const char* configFile, int fd);
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
#ifndef WIREGUARD_GO_VERSION
|
||||
#define WIREGUARD_GO_VERSION "@WG_VERSION_STRING@"
|
||||
#endif // WIREGUARD_GO_VERSION
|
||||
@@ -1,36 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
XCODEBUILD="/usr/bin/xcodebuild"
|
||||
WORKINGDIR=`pwd`
|
||||
PATCH="/usr/bin/patch"
|
||||
|
||||
echo "Building OpenVPNAdapter for macOS Network Extension (MacNE)..."
|
||||
|
||||
# Copy the Project-MacNE.xcconfig settings to amnezia.xcconfig
|
||||
cat $WORKINGDIR/3rd/OpenVPNAdapter/Configuration/Project-MacNE.xcconfig > $WORKINGDIR/3rd/OpenVPNAdapter/Configuration/amnezia.xcconfig
|
||||
|
||||
# Append macOS-specific build directory configurations to 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-macos
|
||||
BUILT_PRODUCTS_DIR = $WORKINGDIR/3rd/OpenVPNAdapter/build/Release-macos
|
||||
EOF
|
||||
|
||||
# Fetch the current macOS SDK version dynamically
|
||||
MACOSX_SDK=$(xcrun --sdk macosx --show-sdk-path | sed -E 's/.*MacOSX([0-9]+\.[0-9]+)\.sdk/\1/')
|
||||
|
||||
echo "Using macOS SDK version: $MACOSX_SDK"
|
||||
|
||||
cd 3rd/OpenVPNAdapter
|
||||
|
||||
# Build for macOS using the correct SDK and destination
|
||||
if $XCODEBUILD -scheme OpenVPNAdapter -configuration Release -xcconfig Configuration/amnezia.xcconfig -sdk macosx$MACOSX_SDK -destination 'generic/platform=macOS' -project OpenVPNAdapter.xcodeproj ; then
|
||||
echo "OpenVPNAdapter built successfully for macOS Network Extension (MacNE)"
|
||||
else
|
||||
echo "OpenVPNAdapter macOS Network Extension (MacNE) build failed..."
|
||||
fi
|
||||
|
||||
# Remove CodeSignature if needed for macOS
|
||||
rm -rf ./build/Release-macos/OpenVPNAdapter.framework/Versions/A/_CodeSignature
|
||||
|
||||
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;
|
||||
@@ -451,8 +455,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);
|
||||
|
||||
@@ -47,7 +47,7 @@ extension PacketTunnelProvider {
|
||||
let configuration = OpenVPNConfiguration()
|
||||
configuration.fileContent = ovpnConfiguration
|
||||
if str.contains("cloak") {
|
||||
configuration.setPTCloak()
|
||||
configuration.setPTCloak()
|
||||
}
|
||||
|
||||
let evaluation: OpenVPNConfigurationEvaluation?
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
#if !MACOS_NE
|
||||
#include "QRCodeReaderBase.h"
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
@@ -109,19 +108,3 @@ void QRCodeReader::startReading() {
|
||||
void QRCodeReader::stopReading() {
|
||||
[m_qrCodeReader stopReading];
|
||||
}
|
||||
#else
|
||||
#include "QRCodeReaderBase.h"
|
||||
|
||||
QRCodeReader::QRCodeReader()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
QRect QRCodeReader::cameraSize() {
|
||||
return QRect();
|
||||
}
|
||||
|
||||
void QRCodeReader::startReading() {}
|
||||
void QRCodeReader::stopReading() {}
|
||||
void QRCodeReader::setCameraSize(QRect) {}
|
||||
#endif
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#if !MACOS_NE
|
||||
#import <UIKit/UIKit.h>
|
||||
#endif
|
||||
|
||||
@interface QIOSApplicationDelegate
|
||||
@end
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
|
||||
@implementation QIOSApplicationDelegate (AmneziaVPNDelegate)
|
||||
#if !MACOS_NE
|
||||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
||||
{
|
||||
[application setMinimumBackgroundFetchInterval: UIApplicationBackgroundFetchIntervalMinimum];
|
||||
@@ -57,5 +57,5 @@
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
#endif
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,13 +1,3 @@
|
||||
#if MACOS_NE
|
||||
public func toggleScreenshots(_ isEnabled: Bool) {
|
||||
|
||||
}
|
||||
|
||||
class ScreenProtection {
|
||||
|
||||
|
||||
}
|
||||
#else
|
||||
import UIKit
|
||||
|
||||
public func toggleScreenshots(_ isEnabled: Bool) {
|
||||
@@ -95,4 +85,3 @@ struct ProtectionPair {
|
||||
textField.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -27,7 +27,6 @@ const char* MessageKey::isOnDemand = "is-on-demand";
|
||||
const char* MessageKey::SplitTunnelType = "SplitTunnelType";
|
||||
const char* MessageKey::SplitTunnelSites = "SplitTunnelSites";
|
||||
|
||||
#if !MACOS_NE
|
||||
static UIViewController* getViewController() {
|
||||
NSArray *windows = [[UIApplication sharedApplication]windows];
|
||||
for (UIWindow *window in windows) {
|
||||
@@ -37,7 +36,6 @@ static UIViewController* getViewController() {
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
#endif
|
||||
|
||||
Vpn::ConnectionState iosStatusToState(NEVPNStatus status) {
|
||||
switch (status) {
|
||||
@@ -791,14 +789,14 @@ bool IosController::shareText(const QStringList& filesToSend) {
|
||||
NSURL *logFileUrl = [[NSURL alloc] initFileURLWithPath:filesToSend[i].toNSString()];
|
||||
[sharingItems addObject:logFileUrl];
|
||||
}
|
||||
#if !MACOS_NE
|
||||
|
||||
UIViewController *qtController = getViewController();
|
||||
if (!qtController) return;
|
||||
|
||||
UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:sharingItems applicationActivities:nil];
|
||||
#endif
|
||||
|
||||
__block bool isAccepted = false;
|
||||
#if !MACOS_NE
|
||||
|
||||
[activityController setCompletionWithItemsHandler:^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
|
||||
isAccepted = completed;
|
||||
emit finished();
|
||||
@@ -810,7 +808,7 @@ bool IosController::shareText(const QStringList& filesToSend) {
|
||||
popController.sourceView = qtController.view;
|
||||
popController.sourceRect = CGRectMake(100, 100, 100, 100);
|
||||
}
|
||||
#endif
|
||||
|
||||
QEventLoop wait;
|
||||
QObject::connect(this, &IosController::finished, &wait, &QEventLoop::quit);
|
||||
wait.exec();
|
||||
@@ -819,7 +817,6 @@ bool IosController::shareText(const QStringList& filesToSend) {
|
||||
}
|
||||
|
||||
QString IosController::openFile() {
|
||||
#if !MACOS_NE
|
||||
UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"public.item"] inMode:UIDocumentPickerModeOpen];
|
||||
|
||||
DocumentPickerDelegate *documentPickerDelegate = [[DocumentPickerDelegate alloc] init];
|
||||
@@ -829,9 +826,9 @@ QString IosController::openFile() {
|
||||
if (!qtController) return;
|
||||
|
||||
[qtController presentViewController:documentPicker animated:YES completion:nil];
|
||||
#endif
|
||||
|
||||
__block QString filePath;
|
||||
#if !MACOS_NE
|
||||
|
||||
documentPickerDelegate.documentPickerClosedCallback = ^(NSString *path) {
|
||||
if (path) {
|
||||
filePath = QString::fromUtf8(path.UTF8String);
|
||||
@@ -840,7 +837,7 @@ QString IosController::openFile() {
|
||||
}
|
||||
emit finished();
|
||||
};
|
||||
#endif
|
||||
|
||||
QEventLoop wait;
|
||||
QObject::connect(this, &IosController::finished, &wait, &QEventLoop::quit);
|
||||
wait.exec();
|
||||
@@ -866,4 +863,3 @@ void IosController::requestInetAccess() {
|
||||
}];
|
||||
[task resume];
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
#import <NetworkExtension/NetworkExtension.h>
|
||||
#import <NetworkExtension/NETunnelProviderSession.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#if !MACOS_NE
|
||||
#include <UIKit/UIKit.h>
|
||||
#endif
|
||||
|
||||
#include <Security/Security.h>
|
||||
|
||||
class IosController;
|
||||
@@ -21,10 +17,9 @@ class IosController;
|
||||
@end
|
||||
|
||||
typedef void (^DocumentPickerClosedCallback)(NSString *path);
|
||||
#if !MACOS_NE
|
||||
|
||||
@interface DocumentPickerDelegate : NSObject <UIDocumentPickerDelegate>
|
||||
|
||||
@property (nonatomic, copy) DocumentPickerClosedCallback documentPickerClosedCallback;
|
||||
|
||||
@end
|
||||
#endif
|
||||
|
||||
@@ -26,8 +26,7 @@
|
||||
|
||||
@end
|
||||
|
||||
#if !MACOS_NE
|
||||
@implementation DocumentPickerDelegate
|
||||
@implementation DocumentPickerDelegate
|
||||
|
||||
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
|
||||
for (NSURL *url in urls) {
|
||||
@@ -43,5 +42,4 @@
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
#endif
|
||||
@end
|
||||
@@ -6,8 +6,6 @@
|
||||
|
||||
#import <UserNotifications/UserNotifications.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#if !MACOS_NE
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface IOSNotificationDelegate
|
||||
@@ -89,86 +87,3 @@ void IOSNotificationHandler::notify(NotificationHandler::Message type, const QSt
|
||||
}
|
||||
}];
|
||||
}
|
||||
#else
|
||||
|
||||
// Removed the UIResponder and UIApplicationDelegate references as these are not available in macOS
|
||||
@interface IOSNotificationDelegate
|
||||
: NSObject <UNUserNotificationCenterDelegate> {
|
||||
IOSNotificationHandler* m_iosNotificationHandler;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation IOSNotificationDelegate
|
||||
|
||||
- (id)initWithObject:(IOSNotificationHandler*)notification {
|
||||
self = [super init]; // Removed `super init` as it refers to UIResponder, which is iOS specific
|
||||
if (self) {
|
||||
m_iosNotificationHandler = notification;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)userNotificationCenter:(UNUserNotificationCenter*)center
|
||||
willPresentNotification:(UNNotification*)notification
|
||||
withCompletionHandler:
|
||||
(void (^)(UNNotificationPresentationOptions options))completionHandler {
|
||||
Q_UNUSED(center)
|
||||
completionHandler(UNNotificationPresentationOptionList | UNNotificationPresentationOptionBanner);
|
||||
}
|
||||
|
||||
- (void)userNotificationCenter:(UNUserNotificationCenter*)center
|
||||
didReceiveNotificationResponse:(UNNotificationResponse*)response
|
||||
withCompletionHandler:(void (^)())completionHandler {
|
||||
Q_UNUSED(center)
|
||||
Q_UNUSED(response)
|
||||
completionHandler();
|
||||
}
|
||||
@end
|
||||
|
||||
IOSNotificationHandler::IOSNotificationHandler(QObject* parent) : NotificationHandler(parent) {
|
||||
|
||||
UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
|
||||
[center requestAuthorizationWithOptions:(UNAuthorizationOptionSound | UNAuthorizationOptionAlert |
|
||||
UNAuthorizationOptionBadge)
|
||||
completionHandler:^(BOOL granted, NSError* _Nullable error) {
|
||||
Q_UNUSED(granted);
|
||||
if (!error) {
|
||||
m_delegate = [[IOSNotificationDelegate alloc] initWithObject:this];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
IOSNotificationHandler::~IOSNotificationHandler() { }
|
||||
|
||||
void IOSNotificationHandler::notify(NotificationHandler::Message type, const QString& title,
|
||||
const QString& message, int timerMsec) {
|
||||
Q_UNUSED(type);
|
||||
|
||||
if (!m_delegate) {
|
||||
return;
|
||||
}
|
||||
|
||||
UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init];
|
||||
content.title = title.toNSString();
|
||||
content.body = message.toNSString();
|
||||
content.sound = [UNNotificationSound defaultSound];
|
||||
|
||||
int timerSec = timerMsec / 1000;
|
||||
UNTimeIntervalNotificationTrigger* trigger =
|
||||
[UNTimeIntervalNotificationTrigger triggerWithTimeInterval:timerSec repeats:NO];
|
||||
|
||||
UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:@"amneziavpn"
|
||||
content:content
|
||||
trigger:trigger];
|
||||
|
||||
UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
|
||||
center.delegate = (id<UNUserNotificationCenterDelegate>)m_delegate;
|
||||
|
||||
[center addNotificationRequest:request
|
||||
withCompletionHandler:^(NSError* _Nullable error) {
|
||||
if (error) {
|
||||
NSLog(@"Local Notification failed");
|
||||
}
|
||||
}];
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -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"), {});
|
||||
|
||||
@@ -297,31 +297,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 +352,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 +445,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();
|
||||
|
||||
@@ -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,11 +502,11 @@ 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) {
|
||||
|
||||
@@ -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>
|
||||
@@ -130,7 +131,6 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) {
|
||||
}
|
||||
|
||||
int err = uapiErrno(uapiCommand(message));
|
||||
|
||||
if (err != 0) {
|
||||
logger.error() << "Interface configuration failed:" << strerror(err);
|
||||
} else {
|
||||
@@ -211,7 +211,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 +322,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 +345,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 +352,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 +448,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,6 @@
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
#include "platforms/windows/windowsutils.h"
|
||||
#include "winsock.h"
|
||||
|
||||
#define IPV6_ADDRESS_SIZE 16
|
||||
|
||||
@@ -49,18 +49,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 +66,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 +93,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 +154,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) \
|
||||
@@ -184,7 +180,7 @@ bool WindowsFirewall::enableKillSwitch(int vpnAdapterIndex) {
|
||||
} \
|
||||
}
|
||||
|
||||
logger.info() << "Enabling Killswitch Using Adapter:" << vpnAdapterIndex;
|
||||
logger.info() << "Enabling firewall Using Adapter:" << vpnAdapterIndex;
|
||||
FW_OK(allowTrafficOfAdapter(vpnAdapterIndex, MED_WEIGHT,
|
||||
"Allow usage of VPN Adapter"));
|
||||
FW_OK(allowDHCPTraffic(MED_WEIGHT, "Allow DHCP Traffic"));
|
||||
@@ -200,6 +196,36 @@ bool WindowsFirewall::enableKillSwitch(int vpnAdapterIndex) {
|
||||
#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;
|
||||
}
|
||||
|
||||
bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
|
||||
// Start the firewall transaction
|
||||
auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
|
||||
@@ -238,10 +264,10 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
|
||||
|
||||
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,
|
||||
"Allow Ecxlude route", config.m_serverPublicKey)) {
|
||||
if (!allowTrafficTo(i, HIGH_WEIGHT,
|
||||
"Allow Ecxlude route", config.m_serverPublicKey)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -421,9 +447,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 +560,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 +759,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,27 @@ 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();
|
||||
|
||||
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 +59,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,10 +206,178 @@ 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"
|
||||
<< logger.sensitive(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"
|
||||
<< logger.sensitive(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());
|
||||
|
||||
// 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";
|
||||
return false;
|
||||
@@ -232,7 +404,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 +426,8 @@ bool WindowsRouteMonitor::addExclusionRoute(const IPAddress& prefix) {
|
||||
delete data;
|
||||
return false;
|
||||
}
|
||||
updateValidInterfaces(family);
|
||||
updateInterfaceMetrics(family);
|
||||
updateCapturedRoutes(family, table);
|
||||
updateExclusionRoute(data, table);
|
||||
FreeMibTable(table);
|
||||
|
||||
@@ -266,26 +439,28 @@ bool WindowsRouteMonitor::deleteExclusionRoute(const IPAddress& prefix) {
|
||||
logger.debug() << "Deleting exclusion route for"
|
||||
<< logger.sensitive(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"
|
||||
<< logger.sensitive(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)) {
|
||||
@@ -295,7 +470,17 @@ void WindowsRouteMonitor::flushExclusionRoutes() {
|
||||
}
|
||||
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 +493,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
|
||||
|
||||
@@ -24,8 +24,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 +126,13 @@ 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->enableInterface(ifindex);
|
||||
}
|
||||
|
||||
logger.debug() << "Registration completed";
|
||||
@@ -128,7 +140,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 +157,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 +187,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 +202,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 +254,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);
|
||||
|
||||
@@ -255,6 +278,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 +301,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;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include "core/errorstrings.h"
|
||||
#include "vpnprotocol.h"
|
||||
|
||||
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) and !defined MACOS_NE || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
|
||||
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
|
||||
#include "openvpnovercloakprotocol.h"
|
||||
#include "openvpnprotocol.h"
|
||||
#include "shadowsocksvpnprotocol.h"
|
||||
@@ -109,7 +109,7 @@ VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject &
|
||||
#if defined(Q_OS_WINDOWS)
|
||||
case DockerContainer::Ipsec: return new Ikev2Protocol(configuration);
|
||||
#endif
|
||||
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) and !defined MACOS_NE || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
|
||||
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
|
||||
case DockerContainer::OpenVpn: return new OpenVpnProtocol(configuration);
|
||||
case DockerContainer::Cloak: return new OpenVpnOverCloakProtocol(configuration);
|
||||
case DockerContainer::ShadowSocks: return new ShadowSocksVpnProtocol(configuration);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "xrayprotocol.h"
|
||||
|
||||
#include "utilities.h"
|
||||
#include "containers/containers_defs.h"
|
||||
#include "core/networkUtilities.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
@@ -22,9 +21,8 @@ XrayProtocol::XrayProtocol(const QJsonObject &configuration, QObject *parent):
|
||||
|
||||
XrayProtocol::~XrayProtocol()
|
||||
{
|
||||
qDebug() << "XrayProtocol::~XrayProtocol()";
|
||||
XrayProtocol::stop();
|
||||
QThread::msleep(200);
|
||||
m_xrayProcess.close();
|
||||
}
|
||||
|
||||
ErrorCode XrayProtocol::start()
|
||||
@@ -36,10 +34,6 @@ ErrorCode XrayProtocol::start()
|
||||
return lastError();
|
||||
}
|
||||
|
||||
if (Utils::processIsRunning(Utils::executable(xrayExecPath(), true))) {
|
||||
Utils::killProcessByName(Utils::executable(xrayExecPath(), true));
|
||||
}
|
||||
|
||||
#ifdef QT_DEBUG
|
||||
m_xrayCfgFile.setAutoRemove(false);
|
||||
#endif
|
||||
@@ -54,9 +48,16 @@ ErrorCode XrayProtocol::start()
|
||||
qDebug().noquote() << "XrayProtocol::start()"
|
||||
<< xrayExecPath() << args.join(" ");
|
||||
|
||||
m_xrayProcess.setProcessChannelMode(QProcess::MergedChannels);
|
||||
|
||||
|
||||
m_xrayProcess.setProcessChannelMode(QProcess::MergedChannels);
|
||||
m_xrayProcess.setProgram(xrayExecPath());
|
||||
|
||||
if (Utils::processIsRunning(Utils::executable("xray", false))) {
|
||||
qDebug().noquote() << "kill previos xray";
|
||||
Utils::killProcessByName(Utils::executable("xray", false));
|
||||
}
|
||||
|
||||
m_xrayProcess.setArguments(args);
|
||||
|
||||
connect(&m_xrayProcess, &QProcess::readyReadStandardOutput, this, [this]() {
|
||||
@@ -68,13 +69,9 @@ ErrorCode XrayProtocol::start()
|
||||
connect(&m_xrayProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus) {
|
||||
qDebug().noquote() << "XrayProtocol finished, exitCode, exitStatus" << exitCode << exitStatus;
|
||||
setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||
if (exitStatus != QProcess::NormalExit) {
|
||||
emit protocolError(amnezia::ErrorCode::XrayExecutableCrashed);
|
||||
stop();
|
||||
}
|
||||
if (exitCode != 0) {
|
||||
emit protocolError(amnezia::ErrorCode::InternalError);
|
||||
stop();
|
||||
if ((exitStatus != QProcess::NormalExit) || (exitCode != 0)) {
|
||||
emit protocolError(amnezia::ErrorCode::XrayExecutableCrashed);
|
||||
emit setConnectionState(Vpn::ConnectionState::Error);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -177,14 +174,14 @@ void XrayProtocol::stop()
|
||||
IpcClient::Interface()->StartRoutingIpv6();
|
||||
#endif
|
||||
qDebug() << "XrayProtocol::stop()";
|
||||
m_xrayProcess.terminate();
|
||||
m_xrayProcess.disconnect();
|
||||
m_xrayProcess.kill();
|
||||
m_xrayProcess.waitForFinished(3000);
|
||||
if (m_t2sProcess) {
|
||||
m_t2sProcess->stop();
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
Utils::signalCtrl(m_xrayProcess.processId(), CTRL_C_EVENT);
|
||||
#endif
|
||||
setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||
}
|
||||
|
||||
QString XrayProtocol::xrayExecPath()
|
||||
|
||||
@@ -1,225 +1,229 @@
|
||||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>fonts/pt-root-ui_vf.ttf</file>
|
||||
<file>images/amneziaBigLogo.png</file>
|
||||
<file>images/AmneziaVPN.png</file>
|
||||
<file>images/controls/alert-circle.svg</file>
|
||||
<file>images/controls/amnezia.svg</file>
|
||||
<file>images/controls/app.svg</file>
|
||||
<file>images/controls/archive-restore.svg</file>
|
||||
<file>images/controls/arrow-left.svg</file>
|
||||
<file>images/controls/arrow-right.svg</file>
|
||||
<file>images/controls/bug.svg</file>
|
||||
<file>images/controls/check.svg</file>
|
||||
<file>images/controls/chevron-down.svg</file>
|
||||
<file>images/controls/chevron-right.svg</file>
|
||||
<file>images/controls/chevron-up.svg</file>
|
||||
<file>images/controls/close.svg</file>
|
||||
<file>images/controls/copy.svg</file>
|
||||
<file>images/controls/delete.svg</file>
|
||||
<file>images/controls/download.svg</file>
|
||||
<file>images/controls/edit-3.svg</file>
|
||||
<file>images/controls/eye-off.svg</file>
|
||||
<file>images/controls/eye.svg</file>
|
||||
<file>images/controls/external-link.svg</file>
|
||||
<file>images/controls/file-check-2.svg</file>
|
||||
<file>images/controls/file-cog-2.svg</file>
|
||||
<file>images/controls/folder-open.svg</file>
|
||||
<file>images/controls/folder-search-2.svg</file>
|
||||
<file>images/controls/gauge.svg</file>
|
||||
<file>images/controls/github.svg</file>
|
||||
<file>images/controls/help-circle.svg</file>
|
||||
<file>images/controls/history.svg</file>
|
||||
<file>images/controls/home.svg</file>
|
||||
<file>images/controls/info.svg</file>
|
||||
<file>images/controls/mail.svg</file>
|
||||
<file>images/controls/map-pin.svg</file>
|
||||
<file>images/controls/more-vertical.svg</file>
|
||||
<file>images/controls/plus.svg</file>
|
||||
<file>images/controls/qr-code.svg</file>
|
||||
<file>images/controls/radio-button-inner-circle-pressed.png</file>
|
||||
<file>images/controls/radio-button-inner-circle.png</file>
|
||||
<file>images/controls/radio-button-pressed.svg</file>
|
||||
<file>images/controls/radio-button.svg</file>
|
||||
<file>images/controls/radio.svg</file>
|
||||
<file>images/controls/refresh-cw.svg</file>
|
||||
<file>images/controls/save.svg</file>
|
||||
<file>images/controls/scan-line.svg</file>
|
||||
<file>images/controls/search.svg</file>
|
||||
<file>images/controls/server.svg</file>
|
||||
<file>images/controls/settings-2.svg</file>
|
||||
<file>images/controls/settings.svg</file>
|
||||
<file>images/controls/share-2.svg</file>
|
||||
<file>images/controls/split-tunneling.svg</file>
|
||||
<file>images/controls/tag.svg</file>
|
||||
<file>images/controls/telegram.svg</file>
|
||||
<file>images/controls/text-cursor.svg</file>
|
||||
<file>images/controls/trash.svg</file>
|
||||
<file>images/controls/x-circle.svg</file>
|
||||
<file>images/tray/active.png</file>
|
||||
<file>images/tray/default.png</file>
|
||||
<file>images/tray/error.png</file>
|
||||
<file>images/AmneziaVPN.png</file>
|
||||
<file>server_scripts/remove_container.sh</file>
|
||||
<file>server_scripts/setup_host_firewall.sh</file>
|
||||
<file>server_scripts/openvpn_cloak/Dockerfile</file>
|
||||
<file>server_scripts/awg/configure_container.sh</file>
|
||||
<file>server_scripts/awg/Dockerfile</file>
|
||||
<file>server_scripts/awg/run_container.sh</file>
|
||||
<file>server_scripts/awg/start.sh</file>
|
||||
<file>server_scripts/awg/template.conf</file>
|
||||
<file>server_scripts/build_container.sh</file>
|
||||
<file>server_scripts/check_connection.sh</file>
|
||||
<file>server_scripts/check_server_is_busy.sh</file>
|
||||
<file>server_scripts/check_user_in_sudo.sh</file>
|
||||
<file>server_scripts/dns/configure_container.sh</file>
|
||||
<file>server_scripts/dns/Dockerfile</file>
|
||||
<file>server_scripts/dns/run_container.sh</file>
|
||||
<file>server_scripts/install_docker.sh</file>
|
||||
<file>server_scripts/ipsec/configure_container.sh</file>
|
||||
<file>server_scripts/ipsec/Dockerfile</file>
|
||||
<file>server_scripts/ipsec/mobileconfig.plist</file>
|
||||
<file>server_scripts/ipsec/run_container.sh</file>
|
||||
<file>server_scripts/ipsec/start.sh</file>
|
||||
<file>server_scripts/ipsec/strongswan.profile</file>
|
||||
<file>server_scripts/openvpn_cloak/configure_container.sh</file>
|
||||
<file>server_scripts/openvpn_cloak/Dockerfile</file>
|
||||
<file>server_scripts/openvpn_cloak/run_container.sh</file>
|
||||
<file>server_scripts/openvpn_cloak/start.sh</file>
|
||||
<file>server_scripts/openvpn_cloak/template.ovpn</file>
|
||||
<file>server_scripts/install_docker.sh</file>
|
||||
<file>server_scripts/build_container.sh</file>
|
||||
<file>server_scripts/prepare_host.sh</file>
|
||||
<file>server_scripts/check_connection.sh</file>
|
||||
<file>server_scripts/remove_all_containers.sh</file>
|
||||
<file>server_scripts/openvpn_cloak/run_container.sh</file>
|
||||
<file>server_scripts/openvpn/configure_container.sh</file>
|
||||
<file>server_scripts/openvpn/run_container.sh</file>
|
||||
<file>server_scripts/openvpn/template.ovpn</file>
|
||||
<file>server_scripts/openvpn/Dockerfile</file>
|
||||
<file>server_scripts/openvpn/start.sh</file>
|
||||
<file>server_scripts/openvpn_shadowsocks/configure_container.sh</file>
|
||||
<file>server_scripts/openvpn_shadowsocks/Dockerfile</file>
|
||||
<file>server_scripts/openvpn_shadowsocks/run_container.sh</file>
|
||||
<file>server_scripts/openvpn_shadowsocks/start.sh</file>
|
||||
<file>server_scripts/openvpn_shadowsocks/template.ovpn</file>
|
||||
<file>server_scripts/openvpn/configure_container.sh</file>
|
||||
<file>server_scripts/openvpn/Dockerfile</file>
|
||||
<file>server_scripts/openvpn/run_container.sh</file>
|
||||
<file>server_scripts/openvpn/start.sh</file>
|
||||
<file>server_scripts/openvpn/template.ovpn</file>
|
||||
<file>server_scripts/prepare_host.sh</file>
|
||||
<file>server_scripts/remove_all_containers.sh</file>
|
||||
<file>server_scripts/remove_container.sh</file>
|
||||
<file>server_scripts/setup_host_firewall.sh</file>
|
||||
<file>server_scripts/sftp/configure_container.sh</file>
|
||||
<file>server_scripts/sftp/Dockerfile</file>
|
||||
<file>server_scripts/sftp/run_container.sh</file>
|
||||
<file>server_scripts/socks5_proxy/configure_container.sh</file>
|
||||
<file>server_scripts/socks5_proxy/Dockerfile</file>
|
||||
<file>server_scripts/socks5_proxy/run_container.sh</file>
|
||||
<file>server_scripts/socks5_proxy/start.sh</file>
|
||||
<file>server_scripts/website_tor/configure_container.sh</file>
|
||||
<file>server_scripts/website_tor/Dockerfile</file>
|
||||
<file>server_scripts/website_tor/run_container.sh</file>
|
||||
<file>server_scripts/wireguard/configure_container.sh</file>
|
||||
<file>server_scripts/wireguard/Dockerfile</file>
|
||||
<file>server_scripts/wireguard/run_container.sh</file>
|
||||
<file>server_scripts/wireguard/start.sh</file>
|
||||
<file>server_scripts/wireguard/template.conf</file>
|
||||
<file>server_scripts/website_tor/configure_container.sh</file>
|
||||
<file>server_scripts/website_tor/run_container.sh</file>
|
||||
<file>ui/qml/Config/GlobalConfig.qml</file>
|
||||
<file>ui/qml/Config/qmldir</file>
|
||||
<file>server_scripts/check_server_is_busy.sh</file>
|
||||
<file>server_scripts/dns/configure_container.sh</file>
|
||||
<file>server_scripts/dns/Dockerfile</file>
|
||||
<file>server_scripts/dns/run_container.sh</file>
|
||||
<file>server_scripts/sftp/configure_container.sh</file>
|
||||
<file>server_scripts/sftp/Dockerfile</file>
|
||||
<file>server_scripts/sftp/run_container.sh</file>
|
||||
<file>server_scripts/ipsec/configure_container.sh</file>
|
||||
<file>server_scripts/ipsec/Dockerfile</file>
|
||||
<file>server_scripts/ipsec/run_container.sh</file>
|
||||
<file>server_scripts/ipsec/start.sh</file>
|
||||
<file>server_scripts/ipsec/mobileconfig.plist</file>
|
||||
<file>server_scripts/ipsec/strongswan.profile</file>
|
||||
<file>server_scripts/website_tor/Dockerfile</file>
|
||||
<file>server_scripts/check_user_in_sudo.sh</file>
|
||||
<file>ui/qml/Controls2/BasicButtonType.qml</file>
|
||||
<file>ui/qml/Controls2/TextFieldWithHeaderType.qml</file>
|
||||
<file>ui/qml/Controls2/LabelWithButtonType.qml</file>
|
||||
<file>images/controls/arrow-right.svg</file>
|
||||
<file>images/controls/chevron-right.svg</file>
|
||||
<file>ui/qml/Controls2/ImageButtonType.qml</file>
|
||||
<file>ui/qml/Controls2/CardType.qml</file>
|
||||
<file>ui/qml/Controls2/CheckBoxType.qml</file>
|
||||
<file>images/controls/check.svg</file>
|
||||
<file>ui/qml/Controls2/DropDownType.qml</file>
|
||||
<file>ui/qml/Pages2/PageSetupWizardStart.qml</file>
|
||||
<file>ui/qml/main2.qml</file>
|
||||
<file>images/amneziaBigLogo.png</file>
|
||||
<file>ui/qml/Controls2/FlickableType.qml</file>
|
||||
<file>ui/qml/Pages2/PageSetupWizardCredentials.qml</file>
|
||||
<file>ui/qml/Controls2/HeaderType.qml</file>
|
||||
<file>images/controls/arrow-left.svg</file>
|
||||
<file>ui/qml/Pages2/PageSetupWizardProtocols.qml</file>
|
||||
<file>ui/qml/Pages2/PageSetupWizardEasy.qml</file>
|
||||
<file>images/controls/chevron-down.svg</file>
|
||||
<file>images/controls/chevron-up.svg</file>
|
||||
<file>ui/qml/Controls2/TextTypes/ParagraphTextType.qml</file>
|
||||
<file>ui/qml/Controls2/TextTypes/Header2TextType.qml</file>
|
||||
<file>ui/qml/Controls2/HorizontalRadioButton.qml</file>
|
||||
<file>ui/qml/Controls2/VerticalRadioButton.qml</file>
|
||||
<file>ui/qml/Controls2/SwitcherType.qml</file>
|
||||
<file>ui/qml/Controls2/TabButtonType.qml</file>
|
||||
<file>ui/qml/Pages2/PageSetupWizardProtocolSettings.qml</file>
|
||||
<file>ui/qml/Pages2/PageSetupWizardInstalling.qml</file>
|
||||
<file>ui/qml/Pages2/PageSetupWizardConfigSource.qml</file>
|
||||
<file>images/controls/folder-open.svg</file>
|
||||
<file>images/controls/qr-code.svg</file>
|
||||
<file>images/controls/text-cursor.svg</file>
|
||||
<file>ui/qml/Pages2/PageSetupWizardTextKey.qml</file>
|
||||
<file>ui/qml/Pages2/PageStart.qml</file>
|
||||
<file>ui/qml/Controls2/TabImageButtonType.qml</file>
|
||||
<file>images/controls/home.svg</file>
|
||||
<file>images/controls/settings-2.svg</file>
|
||||
<file>images/controls/share-2.svg</file>
|
||||
<file>ui/qml/Pages2/PageHome.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsServersList.qml</file>
|
||||
<file>ui/qml/Pages2/PageShare.qml</file>
|
||||
<file>ui/qml/Controls2/TextTypes/Header1TextType.qml</file>
|
||||
<file>ui/qml/Controls2/TextTypes/LabelTextType.qml</file>
|
||||
<file>ui/qml/Controls2/TextTypes/ButtonTextType.qml</file>
|
||||
<file>ui/qml/Controls2/Header2Type.qml</file>
|
||||
<file>images/controls/plus.svg</file>
|
||||
<file>ui/qml/Components/ConnectButton.qml</file>
|
||||
<file>images/controls/download.svg</file>
|
||||
<file>ui/qml/Controls2/ProgressBarType.qml</file>
|
||||
<file>ui/qml/Components/ConnectionTypeSelectionDrawer.qml</file>
|
||||
<file>ui/qml/Components/HomeContainersListView.qml</file>
|
||||
<file>ui/qml/Controls2/TextTypes/CaptionTextType.qml</file>
|
||||
<file>images/controls/settings.svg</file>
|
||||
<file>ui/qml/Pages2/PageSettingsServerInfo.qml</file>
|
||||
<file>ui/qml/Controls2/PageType.qml</file>
|
||||
<file>ui/qml/Controls2/PopupType.qml</file>
|
||||
<file>images/controls/edit-3.svg</file>
|
||||
<file>ui/qml/Pages2/PageSettingsServerData.qml</file>
|
||||
<file>ui/qml/Components/SettingsContainersListView.qml</file>
|
||||
<file>ui/qml/Controls2/TextTypes/ListItemTitleType.qml</file>
|
||||
<file>ui/qml/Controls2/DividerType.qml</file>
|
||||
<file>ui/qml/Controls2/StackViewType.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettings.qml</file>
|
||||
<file>images/controls/amnezia.svg</file>
|
||||
<file>images/controls/app.svg</file>
|
||||
<file>images/controls/radio.svg</file>
|
||||
<file>images/controls/save.svg</file>
|
||||
<file>images/controls/server.svg</file>
|
||||
<file>ui/qml/Pages2/PageSettingsServerProtocols.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsServerServices.qml</file>
|
||||
<file>ui/qml/Pages2/PageSetupWizardViewConfig.qml</file>
|
||||
<file>images/controls/file-cog-2.svg</file>
|
||||
<file>ui/qml/Components/QuestionDrawer.qml</file>
|
||||
<file>ui/qml/Pages2/PageDeinstalling.qml</file>
|
||||
<file>ui/qml/Controls2/BackButtonType.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsServerProtocol.qml</file>
|
||||
<file>ui/qml/Components/TransportProtoSelector.qml</file>
|
||||
<file>ui/qml/Controls2/ListViewWithRadioButtonType.qml</file>
|
||||
<file>images/controls/radio-button.svg</file>
|
||||
<file>images/controls/radio-button-inner-circle.png</file>
|
||||
<file>images/controls/radio-button-pressed.svg</file>
|
||||
<file>images/controls/radio-button-inner-circle-pressed.png</file>
|
||||
<file>ui/qml/Components/ShareConnectionDrawer.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsConnection.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsDns.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsApplication.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsBackup.qml</file>
|
||||
<file>images/controls/delete.svg</file>
|
||||
<file>ui/qml/Pages2/PageSettingsAbout.qml</file>
|
||||
<file>images/controls/github.svg</file>
|
||||
<file>images/controls/mail.svg</file>
|
||||
<file>images/controls/telegram.svg</file>
|
||||
<file>ui/qml/Controls2/TextTypes/SmallTextType.qml</file>
|
||||
<file>ui/qml/Filters/ContainersModelFilters.qml</file>
|
||||
<file>ui/qml/Components/SelectLanguageDrawer.qml</file>
|
||||
<file>ui/qml/Controls2/BusyIndicatorType.qml</file>
|
||||
<file>ui/qml/Pages2/PageProtocolOpenVpnSettings.qml</file>
|
||||
<file>ui/qml/Pages2/PageProtocolShadowSocksSettings.qml</file>
|
||||
<file>ui/qml/Pages2/PageProtocolCloakSettings.qml</file>
|
||||
<file>ui/qml/Pages2/PageProtocolXraySettings.qml</file>
|
||||
<file>ui/qml/Pages2/PageProtocolRaw.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsLogging.qml</file>
|
||||
<file>ui/qml/Pages2/PageServiceSftpSettings.qml</file>
|
||||
<file>images/controls/copy.svg</file>
|
||||
<file>ui/qml/Pages2/PageServiceTorWebsiteSettings.qml</file>
|
||||
<file>ui/qml/Pages2/PageSetupWizardQrReader.qml</file>
|
||||
<file>images/controls/eye.svg</file>
|
||||
<file>images/controls/eye-off.svg</file>
|
||||
<file>ui/qml/Pages2/PageSettingsSplitTunneling.qml</file>
|
||||
<file>ui/qml/Controls2/ContextMenuType.qml</file>
|
||||
<file>ui/qml/Controls2/TextAreaType.qml</file>
|
||||
<file>images/controls/trash.svg</file>
|
||||
<file>images/controls/more-vertical.svg</file>
|
||||
<file>ui/qml/Controls2/ListViewWithLabelsType.qml</file>
|
||||
<file>ui/qml/Pages2/PageServiceDnsSettings.qml</file>
|
||||
<file>ui/qml/Controls2/TopCloseButtonType.qml</file>
|
||||
<file>images/controls/x-circle.svg</file>
|
||||
<file>ui/qml/Pages2/PageProtocolAwgSettings.qml</file>
|
||||
<file>server_scripts/awg/template.conf</file>
|
||||
<file>server_scripts/awg/start.sh</file>
|
||||
<file>server_scripts/awg/configure_container.sh</file>
|
||||
<file>server_scripts/awg/run_container.sh</file>
|
||||
<file>server_scripts/awg/Dockerfile</file>
|
||||
<file>ui/qml/Pages2/PageShareFullAccess.qml</file>
|
||||
<file>images/controls/close.svg</file>
|
||||
<file>images/controls/search.svg</file>
|
||||
<file>server_scripts/xray/configure_container.sh</file>
|
||||
<file>server_scripts/xray/Dockerfile</file>
|
||||
<file>server_scripts/xray/run_container.sh</file>
|
||||
<file>server_scripts/xray/start.sh</file>
|
||||
<file>server_scripts/xray/template.json</file>
|
||||
<file>ui/qml/Pages2/PageProtocolWireGuardSettings.qml</file>
|
||||
<file>ui/qml/Components/AdLabel.qml</file>
|
||||
<file>ui/qml/Components/ConnectButton.qml</file>
|
||||
<file>ui/qml/Components/ConnectionTypeSelectionDrawer.qml</file>
|
||||
<file>ui/qml/Components/HomeContainersListView.qml</file>
|
||||
<file>ui/qml/Components/HomeSplitTunnelingDrawer.qml</file>
|
||||
<file>images/controls/split-tunneling.svg</file>
|
||||
<file>ui/qml/Controls2/DrawerType2.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsAppSplitTunneling.qml</file>
|
||||
<file>ui/qml/Components/InstalledAppsDrawer.qml</file>
|
||||
<file>images/controls/alert-circle.svg</file>
|
||||
<file>images/controls/file-check-2.svg</file>
|
||||
<file>ui/qml/Components/QuestionDrawer.qml</file>
|
||||
<file>ui/qml/Components/SelectLanguageDrawer.qml</file>
|
||||
<file>ui/qml/Components/ServersListView.qml</file>
|
||||
<file>ui/qml/Components/SettingsContainersListView.qml</file>
|
||||
<file>ui/qml/Components/ShareConnectionDrawer.qml</file>
|
||||
<file>ui/qml/Components/TransportProtoSelector.qml</file>
|
||||
<file>ui/qml/Config/GlobalConfig.qml</file>
|
||||
<file>ui/qml/Config/qmldir</file>
|
||||
<file>ui/qml/Controls2/BackButtonType.qml</file>
|
||||
<file>ui/qml/Controls2/BasicButtonType.qml</file>
|
||||
<file>ui/qml/Controls2/BusyIndicatorType.qml</file>
|
||||
<file>ui/qml/Controls2/CardType.qml</file>
|
||||
<file>ui/qml/Controls2/CardWithIconsType.qml</file>
|
||||
<file>ui/qml/Controls2/CheckBoxType.qml</file>
|
||||
<file>ui/qml/Controls2/ContextMenuType.qml</file>
|
||||
<file>ui/qml/Controls2/DividerType.qml</file>
|
||||
<file>ui/qml/Controls2/DrawerType2.qml</file>
|
||||
<file>ui/qml/Controls2/DropDownType.qml</file>
|
||||
<file>ui/qml/Controls2/FlickableType.qml</file>
|
||||
<file>ui/qml/Controls2/Header2Type.qml</file>
|
||||
<file>ui/qml/Controls2/HeaderType.qml</file>
|
||||
<file>ui/qml/Controls2/HorizontalRadioButton.qml</file>
|
||||
<file>ui/qml/Controls2/ImageButtonType.qml</file>
|
||||
<file>ui/qml/Controls2/LabelWithButtonType.qml</file>
|
||||
<file>ui/qml/Controls2/LabelWithImageType.qml</file>
|
||||
<file>ui/qml/Controls2/ListViewWithLabelsType.qml</file>
|
||||
<file>ui/qml/Controls2/ListViewWithRadioButtonType.qml</file>
|
||||
<file>ui/qml/Controls2/PageType.qml</file>
|
||||
<file>ui/qml/Controls2/PopupType.qml</file>
|
||||
<file>ui/qml/Controls2/ProgressBarType.qml</file>
|
||||
<file>ui/qml/Controls2/ScrollBarType.qml</file>
|
||||
<file>ui/qml/Controls2/StackViewType.qml</file>
|
||||
<file>ui/qml/Controls2/SwitcherType.qml</file>
|
||||
<file>ui/qml/Controls2/TabButtonType.qml</file>
|
||||
<file>ui/qml/Controls2/TabImageButtonType.qml</file>
|
||||
<file>ui/qml/Controls2/TextAreaType.qml</file>
|
||||
<file>ui/qml/Controls2/TextAreaWithFooterType.qml</file>
|
||||
<file>ui/qml/Controls2/TextFieldWithHeaderType.qml</file>
|
||||
<file>ui/qml/Controls2/TextTypes/ButtonTextType.qml</file>
|
||||
<file>ui/qml/Controls2/TextTypes/CaptionTextType.qml</file>
|
||||
<file>ui/qml/Controls2/TextTypes/Header1TextType.qml</file>
|
||||
<file>ui/qml/Controls2/TextTypes/Header2TextType.qml</file>
|
||||
<file>ui/qml/Controls2/TextTypes/LabelTextType.qml</file>
|
||||
<file>ui/qml/Controls2/TextTypes/ListItemTitleType.qml</file>
|
||||
<file>ui/qml/Controls2/TextTypes/ParagraphTextType.qml</file>
|
||||
<file>ui/qml/Controls2/TextTypes/SmallTextType.qml</file>
|
||||
<file>ui/qml/Controls2/TopCloseButtonType.qml</file>
|
||||
<file>ui/qml/Controls2/VerticalRadioButton.qml</file>
|
||||
<file>ui/qml/Controls2/WarningType.qml</file>
|
||||
<file>fonts/pt-root-ui_vf.ttf</file>
|
||||
<file>ui/qml/Modules/Style/qmldir</file>
|
||||
<file>ui/qml/Filters/ContainersModelFilters.qml</file>
|
||||
<file>ui/qml/main2.qml</file>
|
||||
<file>ui/qml/Modules/Style/AmneziaStyle.qml</file>
|
||||
<file>ui/qml/Modules/Style/qmldir</file>
|
||||
<file>ui/qml/Pages2/PageDeinstalling.qml</file>
|
||||
<file>ui/qml/Pages2/PageDevMenu.qml</file>
|
||||
<file>ui/qml/Pages2/PageHome.qml</file>
|
||||
<file>ui/qml/Pages2/PageProtocolAwgSettings.qml</file>
|
||||
<file>ui/qml/Pages2/PageProtocolCloakSettings.qml</file>
|
||||
<file>ui/qml/Pages2/PageProtocolOpenVpnSettings.qml</file>
|
||||
<file>ui/qml/Pages2/PageProtocolRaw.qml</file>
|
||||
<file>ui/qml/Pages2/PageProtocolShadowSocksSettings.qml</file>
|
||||
<file>ui/qml/Pages2/PageProtocolWireGuardSettings.qml</file>
|
||||
<file>ui/qml/Pages2/PageProtocolXraySettings.qml</file>
|
||||
<file>ui/qml/Pages2/PageServiceDnsSettings.qml</file>
|
||||
<file>ui/qml/Pages2/PageServiceSftpSettings.qml</file>
|
||||
<file>ui/qml/Pages2/PageServiceSocksProxySettings.qml</file>
|
||||
<file>server_scripts/socks5_proxy/run_container.sh</file>
|
||||
<file>server_scripts/socks5_proxy/Dockerfile</file>
|
||||
<file>server_scripts/socks5_proxy/configure_container.sh</file>
|
||||
<file>server_scripts/socks5_proxy/start.sh</file>
|
||||
<file>ui/qml/Pages2/PageServiceTorWebsiteSettings.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettings.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsAbout.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsApiLanguageList.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsApiServerInfo.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsApplication.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsAppSplitTunneling.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsBackup.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsConnection.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsDns.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsLogging.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsServerData.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsServerInfo.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsServerProtocol.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsServerProtocols.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsServerServices.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsServersList.qml</file>
|
||||
<file>ui/qml/Pages2/PageSettingsSplitTunneling.qml</file>
|
||||
<file>ui/qml/Pages2/PageProtocolAwgClientSettings.qml</file>
|
||||
<file>ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml</file>
|
||||
<file>ui/qml/Pages2/PageSetupWizardApiServicesList.qml</file>
|
||||
<file>ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml</file>
|
||||
<file>ui/qml/Controls2/CardWithIconsType.qml</file>
|
||||
<file>images/controls/tag.svg</file>
|
||||
<file>images/controls/history.svg</file>
|
||||
<file>images/controls/gauge.svg</file>
|
||||
<file>images/controls/map-pin.svg</file>
|
||||
<file>ui/qml/Controls2/LabelWithImageType.qml</file>
|
||||
<file>images/controls/info.svg</file>
|
||||
<file>ui/qml/Controls2/TextAreaWithFooterType.qml</file>
|
||||
<file>images/controls/scan-line.svg</file>
|
||||
<file>images/controls/folder-search-2.svg</file>
|
||||
<file>ui/qml/Pages2/PageSettingsApiServerInfo.qml</file>
|
||||
<file>images/controls/bug.svg</file>
|
||||
<file>ui/qml/Pages2/PageDevMenu.qml</file>
|
||||
<file>images/controls/refresh-cw.svg</file>
|
||||
<file>ui/qml/Pages2/PageSettingsApiLanguageList.qml</file>
|
||||
<file>images/controls/archive-restore.svg</file>
|
||||
<file>images/controls/help-circle.svg</file>
|
||||
<file>ui/qml/Pages2/PageSetupWizardApiServicesList.qml</file>
|
||||
<file>ui/qml/Pages2/PageSetupWizardConfigSource.qml</file>
|
||||
<file>ui/qml/Pages2/PageSetupWizardCredentials.qml</file>
|
||||
<file>ui/qml/Pages2/PageSetupWizardEasy.qml</file>
|
||||
<file>ui/qml/Pages2/PageSetupWizardInstalling.qml</file>
|
||||
<file>ui/qml/Pages2/PageSetupWizardProtocols.qml</file>
|
||||
<file>ui/qml/Pages2/PageSetupWizardProtocolSettings.qml</file>
|
||||
<file>ui/qml/Pages2/PageSetupWizardQrReader.qml</file>
|
||||
<file>ui/qml/Pages2/PageSetupWizardStart.qml</file>
|
||||
<file>ui/qml/Pages2/PageSetupWizardTextKey.qml</file>
|
||||
<file>ui/qml/Pages2/PageSetupWizardViewConfig.qml</file>
|
||||
<file>ui/qml/Pages2/PageShare.qml</file>
|
||||
<file>ui/qml/Pages2/PageShareFullAccess.qml</file>
|
||||
<file>ui/qml/Pages2/PageStart.qml</file>
|
||||
</qresource>
|
||||
<qresource prefix="/countriesFlags">
|
||||
<file>images/flagKit/ZW.svg</file>
|
||||
|
||||
@@ -12,7 +12,7 @@ echo $WIREGUARD_PSK > /opt/amnezia/awg/wireguard_psk.key
|
||||
cat > /opt/amnezia/awg/wg0.conf <<EOF
|
||||
[Interface]
|
||||
PrivateKey = $WIREGUARD_SERVER_PRIVATE_KEY
|
||||
Address = $WIREGUARD_SUBNET_IP/$WIREGUARD_SUBNET_CIDR
|
||||
Address = $AWG_SUBNET_IP/$WIREGUARD_SUBNET_CIDR
|
||||
ListenPort = $AWG_SERVER_PORT
|
||||
Jc = $JUNK_PACKET_COUNT
|
||||
Jmin = $JUNK_PACKET_MIN_SIZE
|
||||
|
||||
@@ -17,12 +17,12 @@ iptables -A FORWARD -i wg0 -j ACCEPT
|
||||
iptables -A OUTPUT -o wg0 -j ACCEPT
|
||||
|
||||
# Allow forwarding traffic only from the VPN.
|
||||
iptables -A FORWARD -i wg0 -o eth0 -s $WIREGUARD_SUBNET_IP/$WIREGUARD_SUBNET_CIDR -j ACCEPT
|
||||
iptables -A FORWARD -i wg0 -o eth1 -s $WIREGUARD_SUBNET_IP/$WIREGUARD_SUBNET_CIDR -j ACCEPT
|
||||
iptables -A FORWARD -i wg0 -o eth0 -s $AWG_SUBNET_IP/$WIREGUARD_SUBNET_CIDR -j ACCEPT
|
||||
iptables -A FORWARD -i wg0 -o eth1 -s $AWG_SUBNET_IP/$WIREGUARD_SUBNET_CIDR -j ACCEPT
|
||||
|
||||
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
|
||||
|
||||
iptables -t nat -A POSTROUTING -s $WIREGUARD_SUBNET_IP/$WIREGUARD_SUBNET_CIDR -o eth0 -j MASQUERADE
|
||||
iptables -t nat -A POSTROUTING -s $WIREGUARD_SUBNET_IP/$WIREGUARD_SUBNET_CIDR -o eth1 -j MASQUERADE
|
||||
iptables -t nat -A POSTROUTING -s $AWG_SUBNET_IP/$WIREGUARD_SUBNET_CIDR -o eth0 -j MASQUERADE
|
||||
iptables -t nat -A POSTROUTING -s $AWG_SUBNET_IP/$WIREGUARD_SUBNET_CIDR -o eth1 -j MASQUERADE
|
||||
|
||||
tail -f /dev/null
|
||||
|
||||