Compare commits

..

64 Commits

Author SHA1 Message Date
lunardunno
464aa4ceae Merge branch 'check_sudo_permissions' into checking_sudo_permissions 2025-01-16 01:18:42 +04:00
lunardunno
db83555a03 Checking server user permissions to use sudo
Checking server user permissions to use sudo using a package manager.
2025-01-16 01:14:42 +04:00
Nethius
956dd6e37a chore: bump version code (#1364) 2025-01-15 12:23:30 +07:00
Nethius
665a2911be bugfix/minor-ui-fixes (#1363)
* bugfix: fixed amfree availability display

* bugfix: fixed selection of exported config type

* chore: hide ad label

* chore: hide ampremium for mobile platforms
2025-01-15 12:04:48 +07:00
KsZnak
1cfa4e0630 Update amneziavpn_ru (#1360)
* Update amneziavpn_ru_RU.ts
2025-01-15 09:31:39 +07:00
pokamest
5bda624576 Update BTC donation address in README_RU 2025-01-14 17:33:50 +00:00
pokamest
d1f0560595 Update donation BTC address 2025-01-14 17:15:01 +00:00
albexk
df07fc1b1f chore: bump version code (#1359) 2025-01-13 22:58:26 +07:00
Nethius
8ca31e0c90 feature/mozilla upstream (#1237)
* cherry-pick 4dfcad96506fb5b88c5bb27342b6d9413fc361c9 from mozilla upstream

* cherry-pick a95fa8c088b9edaff2de18751336942c2d145a9a from mozilla

* cherry-pick commit 4fc1ebbad86a9abcafdc761725a7afd811c8d2d3 from mozilla

* cherry-pick 4dfcad96506fb5b88c5bb27342b6d9413fc361c9 from mozilla upstream

* cherry-pick 22de4fcbd454c64ff496c3380eeaeeb6afff4d64 from mozilla upstream

* cherry-pick 649673be561b66c96367adf379da1545f8838763 from mozilla upstream

* cherry-pick 41bdad34517d0ddaef32139482e5505d92e4b533 from mozilla upstream

* cherry-pick f6e49a85538eaa230d3a8634fa7600966132ccab from mozilla upstream

* cherry-pick 86c585387efa0a09c7937dfe799a90a666404fcd from mozilla upstream

* cherry-pick a18c1fac740469ca3566751b74a16227518630c4 from mozilla upstream

* fixed missing ;

* added excludeLocalNetworks() for linux

* build fixes on windows after cherry-picks

* Add rules for excluded sites splittunell mode

* Fix app splittunell when ipv6 is not setup

* Fix Linux build

---------

Co-authored-by: Mykola Baibuz <mykola.baibuz@gmail.com>
2025-01-13 21:45:06 +07:00
Nethius
f1c6067485 bugfix: fixed work period visibility on page setup api service info (#1355) 2025-01-13 09:55:52 +07:00
KsZnak
ca04c63f5e Update amneziavpn_ru_RU.ts (#1356) 2025-01-13 09:55:41 +07:00
Nethius
89cdd2bece bugfix: fixed site split tunneling mode selector (#1354) 2025-01-12 10:34:43 +07:00
Pokamest Nikak
73d7dfa54f Update translations 2025-01-11 12:49:50 +00:00
albexk
0a5b54a2e4 fix: remove mandatory requirement for android.software.leanback (#1248) 2025-01-09 20:10:42 +07:00
Nethius
e43aa02a5b chore: changed the icon for the settings section (#1348) 2025-01-09 13:33:35 +07:00
Mikhail Kiselev
c3fb62a6ab fix: rewrite linux router dns flusher (#1335)
Co-authored-by: sund3RRR <evenquantity@gamil.com>
2025-01-08 14:38:53 +07:00
Nethius
62f3a339b7 bugfix: ui fixes after merge with android tab navigation branch (#1339)
* bugfix: ui fixes after merge with android tab navigation branch

* bugfix: fix crash on quit

* chore: fix typos

* chore: remove useless comment

* bugfix: fix trigger behavior for `ListViewWithRadioButtonType`

* bugfix: fixed dropdown listview scrolling

* bugfix: fixed amfree availability display

* chore: remove item existence check in triggerCurrentItem function

---------

Co-authored-by: Cyril Anisimov <CyAn84@gmail.com>
2025-01-08 13:12:55 +07:00
Mykola Baibuz
767b14b37a Improve XRay protocol process close (#1318) 2025-01-07 21:52:10 +07:00
Nethius
e7fa160c9c feature: added ad label on page home (#1316)
* feature: added ad label on page home
2025-01-07 10:38:32 +07:00
Vitaly
7350d79c50 feature: WG and AWG: Subnet IP setting change support (#1323)
feature: wg/awg subnet ip setting change support
2025-01-02 14:07:12 +07:00
Mykola Baibuz
86f08554cd fix: check for Linux firewall install before use it (#1328)
* bugfix: check for Linux firewall install before use it

* XRay Linux firewall rules
2024-12-31 10:23:53 +07:00
Andrey Alekseenko
a741186c21 bugfix: Correctly use QProcess::start and QProcess::execute (#1331)
Affected functions (all on Linux/Mac):
 - `RouterLinux::flushDns` was not reloading the DNS manager.
 - `Utils::processIsRunning` was always saying that the process
   is not running when `fullFlag` was set to `false`.
 - `Utils::killProcessByName` was not killing anything.
2024-12-31 10:21:40 +07:00
Cyril Anisimov
6acaab0ffa Improve navigation cpp (#1061)
* add focusController class

* add more key handlers

* add focus navigation to qml

* fixed language selector

* add reverse focus change to FocusController

* add default focus item

* update transitions

* update pages

* add ListViewFocusController

* fix ListView navigation

* update CardType for using with focus navigation

* remove useless key navigation

* remove useless slots, logs, Drawer open and close

* fix reverse focus move on listView

* fix drawer radio buttons selection

* fix drawer layout and focus move

* fix PageSetupWizardProtocolSettings focus move

* fix back navigation on default focus item

* fix crashes after ListView navigation

* fix protocol settings focus move

* fix focus on users on page share

* clean up page share

* fix server rename

* fix page share default server selection

* refactor about page for correct focus move

* fix focus move on list views with header and-or footer

* minor fixes

* fix server list back button handler

* fix spawn signals on switch

* fix share details drawer

* fix drawer open close usage

* refactor listViewFocusController

* refactor focusController to make the logic more
straightforward

* fix focus on notification

* update config page for scrolling with tab

* fix crash on return with esc key

* fix focus navigation in dynamic delegate of list view

* fix focus move on qr code on share page

* refactor page logging settings for focus navigation

* update popup

* Bump version

* Add mandatory requirement for android.software.leanback.

* Fix importing files on TVs

* fix: add separate method for reading files to fix file reading on Android TV

* fix(android): add CHANGE_NETWORK_STATE permission for all Android versions

* Fix connection check for AWG/WG

* chore: minor fixes (#1235)

* fix: add a workaround to open files on Android TV due to lack of SAF

* fix: change the banner format for TV

* refactor: make TvFilePicker activity more sustainable

* fix: add the touch emulation method for Android TV

* fix: null uri processing

* fix: add the touch emulation method for Android TV

* fix: hide UI elements that use file saving

* chore: bump version code

* add `ScrollBarType`

* update initial config page

* refactor credentials setup page to handle the focus navigation

* add `setDelegateIndex` method to `listViewFocusController`

* fix focus behavior on new page/popup

* make minor fixes and clean up

* fix: get rid of the assign function call

* Scrollbar is on if the content is larger than a screen

* Fix selection in language change list

* Update select language list

* update logging settings page

* fix checked item in lists

* fix split tunneling settings

* make unchangable properties readonly

* refactor SwitcherType

* fix hide/unhide password

* `PageShare` readonly properties

* Fix list view focus moving on `PageShare`

* remove manual focus control on `PageShare`

* format `ListViewFocusController`

* format `FocusController`

* add `focusControl` with utility functions for
focus control

* refactor `listViewFocusController` acoording to `focusControl`

* refactor `focusConroller` according to `focusControl`

* add `printSectionName` method to `listViewController`

* remove arrow from `Close application` item

* fix focus movement in `ServersListView`

* `Restore from backup` is visible only on start screen

* `I have nothing` is visible only on start screen

* fix back button on `SelectLanguageDrawer`

* rename `focusControl` to `qmlUtils`

* fix `CMakeLists.txt`

* fix `ScrollBarType`

* fix `PageSetupWizardApiServicesList`

* fix focus movement on dynamic delegates in listView

* refactor `PageSetupWizardProtocols`

* remove comments and clean up

* fix `ListViewWithLabelsType`

* fix `PageProtocolCloakSettings`

* fix `PageSettingsAppSplitTunneling`

* fix `PageDevMenu`

* remove debug output from `FocusController`

* remove debug output from `ListViewFocusController`

* remove debug output from `focusControl`

* `focusControl` => `FocusControl`

---------

Co-authored-by: albexk <albexk@proton.me>
Co-authored-by: Nethius <nethiuswork@gmail.com>
2024-12-31 10:16:52 +07:00
Andrey Alekseenko
212e9b3a91 fix: adding second new VMess links now works (#1325) 2024-12-30 12:45:26 +07:00
Mikhail Kiselev
2bff37efae fix: segmentation violation due to missing return (#1321) 2024-12-28 12:02:14 +07:00
albexk
b88ab8e432 fix(build): fix aqtinstall (#1312) 2024-12-23 08:27:09 +07:00
Nethius
48f6cf904e chore/minor UI fixes (#1308)
* chore: corrected the translation error

* bugfix: fixed basic button left iamge color
2024-12-19 14:36:20 +07:00
KsZnak
367789bda2 Update README_RU.md (#1300)
* Update README_RU.md
2024-12-14 19:29:33 +07:00
lunardunno
7b2a4ea922 improved script readability 2024-12-10 20:14:29 +04:00
Cyril Anisimov
d06924c59d feature/xray user management (#972)
* feature: implement client management functionality for Xray

---------

Co-authored-by: aiamnezia <ai@amnezia.org>
Co-authored-by: vladimir.kuznetsov <nethiuswork@gmail.com>
2024-12-10 09:17:16 +07:00
lunardunno
795ccaa080 added timestamp removal for sudo 2024-12-10 06:13:08 +04:00
lunardunno
ce4cb28e74 improved script readability 2024-12-10 05:44:55 +04:00
lunardunno
c9423dd9af improved script readability 2024-12-09 15:31:58 +04:00
Nethius
2db99715b1 feature: added subscription expiration date for premium v2 (#1261)
* feature: added subscription expiration date for premium v2

* feature: added a check for the presence of the “services” field in the response body of the getServicesList() function

* feature: added prohibition to change location when connection is active

* bugfix: renamed public_key->end_date to public_key->expires_at according to the changes on the backend
2024-12-09 13:32:49 +07:00
lunardunno
055c8f7b66 splitting the sudo check script
The sudo command check script is split to check permissions correctly
2024-12-09 08:51:55 +04:00
lunardunno
2b85dafa15 сhanged extended description
Changed extended description for: not allowed in sudoers.
2024-12-09 08:31:19 +04:00
lunardunno
45fb4b0982 exception for root
Exception for root if sudo package is not installed
2024-12-08 14:31:23 +04:00
lunardunno
62497024f9 script simplification
Simplifying the script for later adding an exception for root to the server controller
2024-12-08 14:22:10 +04:00
pokamest
9688a8e52d Merge pull request #1292 from amnezia-vpn/Add-english-option
Update README.md
2024-12-08 10:40:20 +01:00
pokamest
b2c429f74d Merge pull request #1291 from amnezia-vpn/readme_ru_update
Update README_RU.md
2024-12-08 10:39:01 +01:00
Nethius
6ea6ab1bd9 chore: added clang-format config files (#1293) 2024-12-08 12:14:22 +07:00
KsZnak
c5aa070bf4 Update README_RU.md 2024-12-08 05:49:26 +02:00
KsZnak
d67201ede9 Update README.md 2024-12-08 05:34:18 +02:00
pokamest
4323fb2063 Merge pull request #1290 from amnezia-vpn/Readme_ru
Add files via upload
2024-12-07 15:36:56 +01:00
pokamest
6d5452b8ee Merge pull request #1288 from amnezia-vpn/Readme-ru
Update README_RU.md
2024-12-07 15:36:08 +01:00
KsZnak
569d63ef0f Add files via upload 2024-12-07 15:53:40 +02:00
KsZnak
ea910ba300 Update README_RU.md 2024-12-06 22:15:01 +02:00
Pokamest Nikak
1c1e74d06f ru readme 2024-12-06 12:40:04 +00:00
Nethius
5dc16c06f1 chore: increased the api request timeout (#1276) 2024-12-03 12:47:33 +07:00
Nethius
4efaf20a1c chore: fix deploy workflow (#1280) 2024-12-02 14:46:20 +07:00
lunardunno
076b076cd9 Verifying the server user to work with sudo (#1254)
* checking that the username is root

Changing the mechanism for checking that the username is root

* wheel group check (#1198)

Checking if the user is included in the wheel group

* Checking requirements in script (#1210)

* Checking requirements in script

Checking requirements for sudo users in script

* Adding error handling

Adding error handling in the server controller for:
Sudo package is not pre-installed for sudo users.
Server user or associated group is not listed in the sudoers file.
Server user password required

* adding error codes

* added extended error descriptions

* checking sudo permission for root

Сhecking sudo permission for root.
Сhecking and redefining the system language.

* Username if whoami returns an error

Сommand to use home directory name if whoami returns error or is missing.

* Correcting text error

Correction of the text of the extended description of the package manager error

* Updating translations

* Optimization check_user_in_sudo.sh

* exceptions for missing uname

* output only for groups sudo or wheel
2024-12-01 13:51:03 +04:00
lunardunno
85fa1ad8b1 added check for the root user and the wheel group 2024-12-01 12:22:14 +04:00
Pokamest Nikak
9d96b1cd13 Update Readme 2024-11-29 22:10:35 +00:00
Anton Sosnin
1d721ffb9a SteamDeck/OS installation fix (#1270) 2024-11-27 09:55:23 +07:00
Nethius
2130131a9d bugfix: added scroll on page with services list (#1262)
* added scroll on page with services list

* fixed margins on PageSetupWizardApiServicesList
2024-11-26 11:41:17 +07:00
Aftershock669
e0b091b474 Update readme (#1267) 2024-11-25 23:51:46 +07:00
Nethius
8547de82ea bump xcode-version for macos build (#1249) 2024-11-14 10:58:04 +07:00
Nethius
aa871bd1c9 feature: added country selection on home page drawer (#1215) 2024-11-12 13:22:34 +07:00
albexk
23806e1def chore: bump version to 4.8.2.4 (#1240) 2024-11-08 15:22:16 +07:00
Nethius
31867993ce chore: minor fixes (#1235) 2024-11-06 12:57:39 +07:00
pokamest
7b7a922d92 Merge pull request #1226 from amnezia-vpn/fix/android-awg-connection
Fix connection check for AWG/WG
2024-11-05 12:25:49 +01:00
pokamest
09bd958d8d Merge pull request #1231 from amnezia-vpn/fix/android-network-state
Add CHANGE_NETWORK_STATE permission for all Android versions
2024-11-05 12:25:11 +01:00
albexk
576e2226fe fix(android): add CHANGE_NETWORK_STATE permission for all Android versions 2024-11-03 16:11:23 +03:00
albexk
1533270e4e Fix connection check for AWG/WG 2024-11-02 00:54:24 +03:00
241 changed files with 12019 additions and 10403 deletions

39
.clang-format Normal file
View 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
View 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

View File

@@ -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
View File

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

View File

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

View File

@@ -1,24 +1,29 @@
# Amnezia VPN
## _The best client for self-hosted VPN_
### _The best client for self-hosted VPN_
[![Build Status](https://github.com/amnezia-vpn/amnezia-client/actions/workflows/deploy.yml/badge.svg?branch=dev)](https://github.com/amnezia-vpn/amnezia-client/actions/workflows/deploy.yml?query=branch:dev)
[![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/amnezia-vpn/amnezia-client)
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)
![Image](https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/uipic4.png)
<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>
[![Image](https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/uipic4.png)](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
View File

@@ -0,0 +1,181 @@
# Amnezia VPN
### _Лучший клиент для создания VPN на собственном сервере_
[![Build Status](https://github.com/amnezia-vpn/amnezia-client/actions/workflows/deploy.yml/badge.svg?branch=dev)](https://github.com/amnezia-vpn/amnezia-client/actions/workflows/deploy.yml?query=branch:dev)
[![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/amnezia-vpn/amnezia-client)
### [English](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README.md) | Русский
[AmneziaVPN](https://amnezia.org) — это open sourse VPN-клиент, ключевая особенность которого заключается в возможности развернуть собственный VPN на вашем сервере.
[![Image](https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/uipic4.png)](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) за поддержку нашего проекта.

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -23,4 +23,6 @@
<string name="notificationSettingsDialogTitle">Настройки уведомлений</string>
<string name="notificationSettingsDialogMessage">Для показа уведомлений необходимо включить уведомления в системных настройках</string>
<string name="openNotificationSettings">Открыть настройки уведомлений</string>
<string name="tvNoFileBrowser">Пожалуйста, установите приложение для просмотра файлов</string>
</resources>

View File

@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_banner_background">#1E1E1F</color>
</resources>

View File

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

View File

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

View 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()
}
}
}

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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,
};

View File

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

View File

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

View File

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

View 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

View File

@@ -16,7 +16,5 @@
<array>
<string>$(AppIdentifierPrefix)group.org.amnezia.AmneziaVPN</string>
</array>
<key>com.apple.security.app-sandbox</key>
<true/>
</dict>
</plist>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 421 B

After

Width:  |  Height:  |  Size: 336 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 946 B

After

Width:  |  Height:  |  Size: 593 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 946 B

After

Width:  |  Height:  |  Size: 593 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,10 +1,10 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "macos/gobridge/wireguard.h"
#include "wireguard-go-version.h"
#include "3rd/amneziawg-apple/Sources/WireGuardKitGo/wireguard.h"
#include "3rd/amneziawg-apple/Sources/WireGuardKitC/WireGuardKitC.h"
#include "3rd/awg-apple/Sources/WireGuardKitC/WireGuardKitC.h"
#include <stdbool.h>
#include <stdint.h>
@@ -23,8 +23,3 @@ bool key_from_hex(uint8_t key[WG_KEY_LEN], const char* hex);
bool key_eq(const uint8_t key1[WG_KEY_LEN], const uint8_t key2[WG_KEY_LEN]);
void write_msg_to_log(const char* tag, const char* msg);
// init function definition in C
void hev_socks5_tunnel_quit(void);
// Updated function definition in C
int hev_socks5_tunnel_main(const char* configFile, int fd);

View File

@@ -1,3 +0,0 @@
#ifndef WIREGUARD_GO_VERSION
#define WIREGUARD_GO_VERSION "@WG_VERSION_STRING@"
#endif // WIREGUARD_GO_VERSION

View File

@@ -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 ../../

View File

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

View File

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

View File

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

View File

@@ -47,7 +47,7 @@ extension PacketTunnelProvider {
let configuration = OpenVPNConfiguration()
configuration.fileContent = ovpnConfiguration
if str.contains("cloak") {
configuration.setPTCloak()
configuration.setPTCloak()
}
let evaluation: OpenVPNConfigurationEvaluation?

View File

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

View File

@@ -1,6 +1,5 @@
#if !MACOS_NE
#import <UIKit/UIKit.h>
#endif
@interface QIOSApplicationDelegate
@end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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"), {});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View File

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

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