Compare commits

..

111 Commits

Author SHA1 Message Date
vladimir.kuznetsov
4723019624 Merge branch 'dev' of github.com:amnezia-vpn/amnezia-client into HEAD 2024-01-31 12:42:19 +07:00
vladimir.kuznetsov
1be9078b6c added break after each line in errorstrings 2024-01-31 12:42:05 +07:00
pokamest
520658a295 Merge pull request #550 from amnezia-vpn/KsZnak-patch-1
Ks znak patch 1
2024-01-30 15:58:50 -08:00
pokamest
b9ec722abb Merge pull request #543 from amnezia-vpn/KsZnak-patch-7
Update PageSettingsSplitTunneling.qml
2024-01-30 15:57:40 -08:00
pokamest
d917c798d7 Merge pull request #544 from amnezia-vpn/KsZnak-patch-8
Update PageSettingsAbout.qml
2024-01-30 15:57:10 -08:00
pokamest
0d168c039f Merge pull request #545 from amnezia-vpn/KsZnak-patch-6
Update PageSettingsSplitTunneling.qml
2024-01-30 15:56:50 -08:00
pokamest
af8f265555 Merge pull request #546 from amnezia-vpn/KsZnak-patch-5
Update PageSettingsConnection.qml
2024-01-30 15:56:29 -08:00
pokamest
c0e0d64284 Merge pull request #547 from amnezia-vpn/KsZnak-patch-4
Update PageServiceTorWebsiteSettings.qml
2024-01-30 15:56:02 -08:00
pokamest
9466a71141 Merge pull request #548 from amnezia-vpn/KsZnak-patch-3
Update PageProtocolOpenVpnSettings.qml
2024-01-30 15:55:29 -08:00
pokamest
d28a586a97 Merge pull request #549 from amnezia-vpn/KsZnak-patch-2
Update containers_defs.cpp
2024-01-30 15:55:04 -08:00
pokamest
6fde0b6663 Merge pull request #551 from amnezia-vpn/KsZnak-change-eng-text
Update PageSetupWizardCredentials.qml
2024-01-30 15:54:10 -08:00
KsZnak
d143b9213b Update PageSettingsAbout.qml 2024-01-30 20:50:30 +02:00
KsZnak
16433e9e46 Update PageSettingsSplitTunneling.qml 2024-01-30 20:40:11 +02:00
KsZnak
39479d1999 Update PageSettingsSplitTunneling.qml 2024-01-30 20:35:05 +02:00
KsZnak
a03b766e33 Update PageSettingsConnection.qml 2024-01-30 20:27:01 +02:00
KsZnak
7df2655ba0 Update PageServiceTorWebsiteSettings.qml 2024-01-30 20:22:26 +02:00
KsZnak
5c2ca9803d Update PageProtocolOpenVpnSettings.qml 2024-01-30 20:08:14 +02:00
KsZnak
4e6af947fa Update containers_defs.cpp 2024-01-30 20:05:18 +02:00
pokamest
f412ac6260 Merge pull request #537 from amnezia-vpn/ios-log-3
Disable ioslogger (due memory leak)
2024-01-29 11:37:17 -08:00
pokamest
b45517bafd Merge pull request #444 from amnezia-vpn/feature/killswitch
Kill Switch for desktop client
2024-01-29 11:23:23 -08:00
pokamest
bda64fa391 Merge pull request #536 from amnezia-vpn/bugfix/revoke-openvpn-client
fixed cache clearing when deleting admin configure
2024-01-29 07:31:09 -08:00
vladimir.kuznetsov
e7040f7cc8 specified error codes 2024-01-29 21:33:35 +07:00
pokamest
229970e799 Merge pull request #542 from amnezia-vpn/KsZnak-patch-2
Update amneziavpn_ru.ts
2024-01-28 13:10:49 -08:00
KsZnak
7b0a9a055f Update amneziavpn_ru.ts 2024-01-28 21:46:20 +02:00
pokamest
06d370f716 Merge pull request #541 from amnezia-vpn/version-bump
Version bump 4.2.1.1
2024-01-28 07:23:58 -08:00
albexk
ce0a4f1f96 Version bump 4.2.1.1 2024-01-28 17:29:54 +03:00
vladimir.kuznetsov
3240aa3cb3 Merge branch 'dev' of github.com:amnezia-vpn/amnezia-client into HEAD 2024-01-28 21:24:23 +07:00
pokamest
5c5935c738 Merge pull request #539 from amnezia-vpn/bugfix/android
Fix Android bugs
2024-01-28 05:25:38 -08:00
pokamest
00b2b1abcd Merge pull request #529 from amnezia-vpn/bugfix/container-selection-after-cleanup-server
fixed first container selection on HomeContainersListView after server cleanup
2024-01-28 02:56:16 -08:00
Mykola Baibuz
e0891e1a15 Change license text 2024-01-28 05:39:12 -05:00
vladimir.kuznetsov
f7df621c56 fixed cache clearing when deleting admin configure
- added permissions for the crl.pem file
2024-01-27 18:09:14 +03:00
albexk
0b6dc5bcfc Add unbinding and destroying vpn service after disconnection 2024-01-27 17:34:57 +03:00
albexk
cbd6755aa5 Fix OpenVpn over Cloak 2024-01-27 17:30:56 +03:00
albexk
3afbc248b1 Refactor split-tunneling: separate site addresses from routes 2024-01-27 17:28:31 +03:00
Mykola Baibuz
30af81fe0a Move linuxfirewall header to "headers" part 2024-01-27 07:59:36 -05:00
Mykola Baibuz
427b43c99b Add code license 2024-01-27 07:50:50 -05:00
KsZnak
ed08ac6b46 Update containers_defs.cpp 2024-01-27 00:58:40 +02:00
KsZnak
1a2c1fa1b5 Update PageSetupWizardCredentials.qml 2024-01-27 00:18:17 +02:00
Igor Sorokin
709fbac231 Disable ioslogger (due memory leak) 2024-01-25 19:07:22 +03:00
pokamest
6b80a56f92 Merge pull request #530 from amnezia-vpn/bugfix/admin-user-clinet-management
fixed adding admin user to client management
2024-01-25 01:58:39 -08:00
Mykola Baibuz
5c9d45a8a8 Use MacOS logic for LinuxFirewall 2024-01-24 17:20:50 -05:00
pokamest
2edac24945 Merge pull request #532 from amnezia-vpn/bugfix/issue485
Fix autostart for Linux Desktop
2024-01-24 11:32:37 -08:00
albexk
b68c9cc145 Fix the double extension of the config file name: 'amnezia_config.vpn.vpn' 2024-01-24 17:00:48 +03:00
albexk
6f02d4eb62 Remove useless 'Open folder with logs' button for Adnroid too 2024-01-24 16:34:37 +03:00
pokamest
6e60688e70 Merge pull request #533 from amnezia-vpn/bugfix/easy-setup-random-port
random ports are now used for easy setup
2024-01-24 05:20:17 -08:00
vladimir.kuznetsov
140b5c4292 random ports are now used for easy setup 2024-01-24 12:27:11 +07:00
pokamest
8ee2ebbd20 Merge pull request #531 from amnezia-vpn/ios-log-2
Fix/iOS log (Part 2)
2024-01-23 14:18:17 -08:00
Igor Sorokin
b3eda4106d Fix: Share view is not showing on iPadOS 2024-01-23 23:41:08 +03:00
Mykola Baibuz
885e22be7c Fix autostart for Linux Desktop 2024-01-23 15:17:07 -05:00
vladimir.kuznetsov
83ec073734 fixed adding admin user to client management 2024-01-24 00:33:19 +07:00
vladimir.kuznetsov
0160b0f9dc editServer now does not update the whole model, but only the modified element 2024-01-23 23:57:14 +07:00
vladimir.kuznetsov
f55bd5e1a1 fixed first container selection on HomeContainersListView after server cleanup 2024-01-23 23:56:16 +07:00
Igor Sorokin
4d88eb8e79 Try to expand 'internal error' 2024-01-23 18:41:33 +03:00
Mykola Baibuz
874de74ac8 Add exclusion for VPN Server host (MacOS/OpenVPN) 2024-01-22 20:32:40 +02:00
pokamest
b3a4b34d48 Merge pull request #524 from amnezia-vpn/KsZnak-patch-1
Update amneziavpn_ru.ts
2024-01-22 07:27:45 -08:00
KsZnak
ec4574bfcf Update amneziavpn_ru.ts 2024-01-22 01:52:14 +02:00
pokamest
73cfab166f Merge pull request #523 from amnezia-vpn/fix/ios-log
Fix/iOS log (Part 1)
2024-01-21 13:02:28 -08:00
Igor Sorokin
0e8f85057d Remove distracting 'stale focus object' records from log 2024-01-21 21:15:34 +03:00
Igor Sorokin
2e4908c557 Records detailed disconnect error info to log 2024-01-21 20:41:06 +03:00
Igor Sorokin
401784414a Fix: 'OpenVPN' is recorded to the log instead of 'WireGuard' 2024-01-21 19:05:24 +03:00
Igor Sorokin
8495124bc8 Remove useless 'Open folder with logs' button (iOS) 2024-01-21 17:49:24 +03:00
pokamest
ba6ed540f5 Merge pull request #521 from amnezia-vpn/KsZnak-patch-2
Update README.md
2024-01-20 12:16:16 -08:00
KsZnak
19a60ea856 Update README.md
del signal link
2024-01-20 22:13:00 +02:00
pokamest
aecf1b463c Merge pull request #519 from amnezia-vpn/fix/android-deploy
Enable AAB build for all branches
2024-01-20 11:08:24 -08:00
pokamest
ff24ba1011 Merge pull request #518 from amnezia-vpn/fix/android-wgipv6
Fix wg address parameter parsing
2024-01-20 10:57:36 -08:00
albexk
802b708232 Enable AAB build for all branches 2024-01-20 21:49:37 +03:00
pokamest
adeff3efb9 Version bump 4.2.1.0 2024-01-20 18:46:33 +00:00
albexk
0103c1722e Fix wg address parameter parsing 2024-01-20 21:38:58 +03:00
pokamest
f9123e7b71 Merge pull request #516 from amnezia-vpn/bugfix/easy-setup-default-container
fixed easy setup default container selection
2024-01-20 09:18:57 -08:00
pokamest
0f9ed4e69c Merge pull request #515 from amnezia-vpn/fix/android-wg
set wg PresharedKey parameter as optional
2024-01-20 09:16:16 -08:00
vladimir.kuznetsov
9dfc95bac0 fixed easy setup default container selection 2024-01-20 23:28:20 +07:00
albexk
9b7914538f set wg PresharedKey parameter as optional 2024-01-20 18:43:26 +03:00
pokamest
85c9cd260d Merge pull request #514 from amnezia-vpn/fix/android-camera
Hide "QR-code" button from config import menu for Android devices wit…
2024-01-20 07:42:55 -08:00
pokamest
73751cc049 Merge pull request #513 from amnezia-vpn/bugfix/issue499
Fix import of some native WG configs
2024-01-20 07:27:54 -08:00
albexk
2b61c48303 Hide "QR-code" button from config import menu for Android devices without camera 2024-01-20 18:20:06 +03:00
Mykola Baibuz
47b03f2bf4 PSK paramater is not mandatory 2024-01-20 08:44:48 -05:00
albexk
3e02dfef63 Refactoring Android logging (#511)
Refactoring Android logging
2024-01-20 13:40:12 +00:00
Mykola Baibuz
4176d0130a Fix import of some native WG configs 2024-01-20 08:35:24 -05:00
pokamest
f6175c2c69 Merge pull request #512 from amnezia-vpn/fix/line-ending
Fix amnezia_application line-ending
2024-01-20 05:04:22 -08:00
albexk
b8b423ca19 Fix amnezia_application line-ending 2024-01-20 16:02:35 +03:00
pokamest
3702d69b9d Merge branch 'dev' into feature/killswitch 2024-01-19 14:58:28 +00:00
pokamest
3253de9384 Merge pull request #509 from amnezia-vpn/featute/amneziawg-apple
Change to amneziawg-apple
2024-01-19 02:17:02 -08:00
Igor Sorokin
51070635a5 Change to amneziawg-apple 2024-01-19 03:27:44 +03:00
pokamest
9b533f1ba6 Merge pull request #507 from amnezia-vpn/linux-installer
Improve Linux installer
2024-01-18 09:39:00 -08:00
tiaga
9371dd405e Improve Linux installer
Pack installer to a .tar archive in order to save executable bit for `AmneziaVPN_Linux_Installer.bin`.
2024-01-19 00:16:19 +07:00
pokamest
27d8108e55 Merge pull request #501 from amnezia-vpn/feature/api-spit-tunneling
for servers received via api, ignore the split tunneling settings
2024-01-18 08:51:12 -08:00
pokamest
820a3f0b77 Merge pull request #506 from amnezia-vpn/bugfix/client-management-ss-cloak
fixed append/rename/revoke client for cloak and ss on client management panel
2024-01-18 07:09:14 -08:00
pokamest
3d61b20271 Merge pull request #505 from amnezia-vpn/fix/readme
Update README.md
2024-01-18 07:03:08 -08:00
vladimir.kuznetsov
eec81f8124 fixed append/rename/revoke client for cloak and ss on client management panel 2024-01-18 21:29:56 +07:00
Igor Sorokin
d7fbddb97f Update README.md 2024-01-18 17:13:04 +03:00
pokamest
9170cb0318 Merge pull request #498 from amnezia-vpn/sort-installer-containers-first
show installed protocols first in protocols tab
2024-01-18 05:57:16 -08:00
pokamest
76dec6fa9c Merge pull request #504 from amnezia-vpn/feature/android-deploy
Add Android App Bundle build and upload
2024-01-18 05:56:50 -08:00
pokamest
4e3edcfc0a Merge pull request #503 from amnezia-vpn/fix/ipv6
Fix IPv6 problem
2024-01-18 05:56:07 -08:00
albexk
ad21d7ab64 Add Android App Bundle build and upload
Up upload-artifact action to version 4
2024-01-18 16:06:37 +03:00
Igor Sorokin
f576eb509d Fix IPv6 problem 2024-01-18 15:25:36 +03:00
vladimir.kuznetsov
33e229d0b2 added blocking of the page with dns settings for servers received by API 2024-01-17 23:38:20 +07:00
pokamest
78420d617b Merge pull request #502 from amnezia-vpn/fix/android-clipboard
Fix/android clipboard
2024-01-17 07:24:24 -08:00
albexk
2552e33d64 Add Android TextArea clipboard workaround 2024-01-17 17:46:54 +03:00
albexk
301141c755 Remove Android TextField workaround
It has been fixed in Qt 6.5.3, 6.6.0
2024-01-17 17:43:54 +03:00
vladimir.kuznetsov
fac57ac89a for servers received via api, ignore the split tunneling settings 2024-01-17 11:28:57 +07:00
pokamest
da5ad0a845 Merge pull request #500 from amnezia-vpn/fix/android-keyboard
Restore previous Android keyboard behavior
2024-01-16 05:50:28 -08:00
albexk
60aeefe1c2 Restore previous Android keyboard behaviour
There is a Qt bug related to this behaviour: https://bugreports.qt.io/browse/QTBUG-41170
2024-01-16 15:06:34 +03:00
Morteza Sherafati
d527f6a5ce show installed protocols first in protocols tab 2024-01-15 20:18:10 +00:00
pokamest
65a04799ef Merge branch 'dev' into feature/killswitch 2024-01-11 13:45:56 +00:00
vladimir.kuznetsov
11641c5e22 added error code output 2023-12-24 21:11:47 +07:00
Mykola Baibuz
a8f5e95fb1 MacOS OpenVPN/OpenVPN over Cloak killswitch 2023-12-23 16:04:17 +02:00
Mykola Baibuz
a4f3d08c02 Fix PF config path 2023-12-23 14:21:13 +02:00
Mykola Baibuz
3d2174d84e MacOS WG/AWG killswitch 2023-12-23 12:51:55 +02:00
Mykola Baibuz
1a17f2956a Fix build action 2023-12-16 10:05:54 -05:00
Mykola Baibuz
d94e27bfa9 Linux killswitch 2023-12-16 09:19:04 -05:00
Mykola Baibuz
c3fdd977b1 Windows OpenVPN/OpenVPN+Cloak killswitch feature 2023-11-29 22:50:36 +02:00
107 changed files with 3011 additions and 1111 deletions

View File

@@ -48,14 +48,18 @@ jobs:
export QIF_BIN_DIR=${{ runner.temp }}/Qt/Tools/QtInstallerFramework/${{ env.QIF_VERSION }}/bin
bash deploy/build_linux.sh
- name: 'Pack installer'
run: cd deploy && tar -cf AmneziaVPN_Linux_Installer.tar AmneziaVPN_Linux_Installer.bin
- name: 'Upload installer artifact'
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_Linux_installer
path: deploy/AmneziaVPN_Linux_Installer
name: AmneziaVPN_Linux_installer.tar
path: deploy/AmneziaVPN_Linux_Installer.tar
retention-days: 7
- name: 'Upload unpacked artifact'
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_Linux_unpacked
path: deploy/AppDir
@@ -110,13 +114,14 @@ jobs:
call deploy\\build_windows.bat
- name: 'Upload installer artifact'
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_Windows_installer
path: AmneziaVPN_x${{ env.BUILD_ARCH }}.exe
retention-days: 7
- name: 'Upload unpacked artifact'
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_Windows_unpacked
path: deploy\\build_${{ env.BUILD_ARCH }}\\client\\Release
@@ -200,7 +205,7 @@ jobs:
IOS_NE_PROVISIONING_PROFILE: ${{ secrets.IOS_NE_PROVISIONING_PROFILE }}
# - name: 'Upload appstore .ipa and dSYMs to artifacts'
# uses: actions/upload-artifact@v3
# uses: actions/upload-artifact@v4
# with:
# name: app-store ipa & dsyms
# path: |
@@ -255,13 +260,14 @@ jobs:
bash deploy/build_macos.sh
- name: 'Upload installer artifact'
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_MacOS_installer
path: AmneziaVPN.dmg
retention-days: 7
- name: 'Upload unpacked artifact'
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_MacOS_unpacked
path: deploy/build/client/AmneziaVPN.app
@@ -375,32 +381,44 @@ jobs:
ANDROID_KEYSTORE_KEY_ALIAS: ${{ secrets.ANDROID_RELEASE_KEYSTORE_KEY_ALIAS }}
ANDROID_KEYSTORE_KEY_PASS: ${{ secrets.ANDROID_RELEASE_KEYSTORE_KEY_PASS }}
shell: bash
run: ./deploy/build_android.sh --apk all --build-platform ${{ env.ANDROID_BUILD_PLATFORM }}
run: ./deploy/build_android.sh --aab --apk all --build-platform ${{ env.ANDROID_BUILD_PLATFORM }}
- name: 'Upload x86_64 apk'
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN-android-x86_64
path: deploy/build/AmneziaVPN-x86_64-release.apk
compression-level: 0
retention-days: 7
- name: 'Upload x86 apk'
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN-android-x86
path: deploy/build/AmneziaVPN-x86-release.apk
compression-level: 0
retention-days: 7
- name: 'Upload arm64-v8a apk'
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN-android-arm64-v8a
path: deploy/build/AmneziaVPN-arm64-v8a-release.apk
compression-level: 0
retention-days: 7
- name: 'Upload armeabi-v7a apk'
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN-android-armeabi-v7a
path: deploy/build/AmneziaVPN-armeabi-v7a-release.apk
compression-level: 0
retention-days: 7
- name: 'Upload aab'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN-android
path: deploy/build/AmneziaVPN-release.aab
compression-level: 0
retention-days: 7

6
.gitmodules vendored
View File

@@ -22,6 +22,6 @@
[submodule "client/3rd-prebuilt"]
path = client/3rd-prebuilt
url = https://github.com/amnezia-vpn/3rd-prebuilt
[submodule "client/3rd/awg-apple"]
path = client/3rd/awg-apple
url = https://github.com/amnezia-vpn/awg-apple
[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.2.0.1
project(${PROJECT} VERSION 4.2.1.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 39)
set(APP_ANDROID_VERSION_CODE 43)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux")

View File

@@ -18,7 +18,6 @@ Amnezia is an open-source VPN client, with a key feature that enables you to dep
[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](https://t.me/amnezia_vpn) - Telegram support channel (Russian)
[https://signal.group/...](https://signal.group/#CjQKIB2gUf8QH_IXnOJMGQWMDjYz9cNfmRQipGWLFiIgc4MwEhAKBONrSiWHvoUFbbD0xwdh) - Signal channel
## Tech
@@ -36,7 +35,7 @@ AmneziaVPN uses a number of open source projects to work:
Make sure to pull all submodules after checking out the repo.
```bash
git submodule update --init
git submodule update --init --recursive
```
## Development
@@ -50,7 +49,15 @@ Look deploy folder for build scripts.
1. First, make sure you have [XCode](https://developer.apple.com/xcode/) installed, at least version 14 or higher.
2. We use QT to generate the XCode project. we need QT version 6.4. Install QT for macos in [here](https://doc.qt.io/qt-6/macos.html)
2. We use QT to generate the XCode project. we need QT version 6.6.1. Install QT for macos in [here](https://doc.qt.io/qt-6/macos.html) or [QT Online Installer](https://www.qt.io/download-open-source). Required modules:
- macOS
- iOS
- Qt 5 Compatibility Module
- Qt Shader Tools
- Additional Libraries:
- Qt Image Formats
- Qt Multimedia
- Qt Remote Objects
3. Install cmake is require. We recommend cmake version 3.25. You can install cmake in [here](https://cmake.org/download/)
@@ -66,10 +73,11 @@ gomobile init
5. Build project
```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_BIN_DIR
$QT_IOS_BIN/qt-cmake . -B build-ios -GXcode -DQT_HOST_PATH=$QT_MACOS_ROOT_DIR
```
Replace PATH-TO-QT-FOLDER and QT-VERSION to your environment

1
client/3rd/amneziawg-apple vendored Submodule

Submodule client/3rd/awg-apple deleted from 233eda6760

View File

@@ -1,380 +1,389 @@
#include "amnezia_application.h"
#include <QClipboard>
#include <QFontDatabase>
#include <QMimeData>
#include <QQuickStyle>
#include <QResource>
#include <QStandardPaths>
#include <QTextDocument>
#include <QTimer>
#include <QTranslator>
#include <QQuickItem>
#include "logger.h"
#include "version.h"
#include "platforms/ios/QRCodeReaderBase.h"
#if defined(Q_OS_ANDROID)
#include "platforms/android/android_controller.h"
#endif
#include "protocols/qml_register_protocols.h"
#if defined(Q_OS_IOS)
#include "platforms/ios/ios_controller.h"
#endif
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv)
#else
AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecondary, SingleApplication::Options options,
int timeout, const QString &userData)
: SingleApplication(argc, argv, allowSecondary, options, timeout, userData)
#endif
{
setQuitOnLastWindowClosed(false);
// Fix config file permissions
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
{
QSettings s(ORGANIZATION_NAME, APPLICATION_NAME);
s.setValue("permFixed", true);
}
QString configLoc1 = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/"
+ ORGANIZATION_NAME + "/" + APPLICATION_NAME + ".conf";
QFile::setPermissions(configLoc1, QFileDevice::ReadOwner | QFileDevice::WriteOwner);
QString configLoc2 = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/"
+ ORGANIZATION_NAME + "/" + APPLICATION_NAME + "/" + APPLICATION_NAME + ".conf";
QFile::setPermissions(configLoc2, QFileDevice::ReadOwner | QFileDevice::WriteOwner);
#endif
m_settings = std::shared_ptr<Settings>(new Settings);
}
AmneziaApplication::~AmneziaApplication()
{
m_vpnConnectionThread.quit();
m_vpnConnectionThread.wait(3000);
if (m_engine) {
QObject::disconnect(m_engine, 0, 0, 0);
delete m_engine;
}
}
void AmneziaApplication::init()
{
m_engine = new QQmlApplicationEngine;
const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml"));
QObject::connect(
m_engine, &QQmlApplicationEngine::objectCreated, this,
[url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
},
Qt::QueuedConnection);
m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance());
m_configurator = std::shared_ptr<VpnConfigurator>(new VpnConfigurator(m_settings, this));
m_vpnConnection.reset(new VpnConnection(m_settings, m_configurator));
m_vpnConnection->moveToThread(&m_vpnConnectionThread);
m_vpnConnectionThread.start();
initModels();
loadTranslator();
initControllers();
#ifdef Q_OS_ANDROID
connect(AndroidController::instance(), &AndroidController::initConnectionState, this,
[this](Vpn::ConnectionState state) {
m_connectionController->onConnectionStateChanged(state);
if (m_vpnConnection)
m_vpnConnection->restoreConnection();
});
if (!AndroidController::instance()->initialize()) {
qCritical() << QString("Init failed");
if (m_vpnConnection)
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Error);
return;
}
connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, [this](QString data) {
m_pageController->replaceStartPage();
m_importController->extractConfigFromData(data);
m_pageController->goToPageViewConfig();
});
#endif
#ifdef Q_OS_IOS
IosController::Instance()->initialize();
connect(IosController::Instance(), &IosController::importConfigFromOutside, [this](QString data) {
m_pageController->replaceStartPage();
m_importController->extractConfigFromData(data);
m_pageController->goToPageViewConfig();
});
connect(IosController::Instance(), &IosController::importBackupFromOutside, [this](QString filePath) {
m_pageController->replaceStartPage();
m_pageController->goToPageSettingsBackup();
m_settingsController->importBackupFromOutside(filePath);
});
#endif
m_notificationHandler.reset(NotificationHandler::create(nullptr));
connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(),
&NotificationHandler::setConnectionState);
connect(m_notificationHandler.get(), &NotificationHandler::raiseRequested, m_pageController.get(),
&PageController::raiseMainWindow);
connect(m_notificationHandler.get(), &NotificationHandler::connectRequested, m_connectionController.get(),
&ConnectionController::openConnection);
connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(),
&ConnectionController::closeConnection);
connect(this, &AmneziaApplication::translationsUpdated, m_notificationHandler.get(),
&NotificationHandler::onTranslationsUpdated);
m_engine->load(url);
m_systemController->setQmlRoot(m_engine->rootObjects().value(0));
if (m_settings->isSaveLogs()) {
if (!Logger::init()) {
qWarning() << "Initialization of debug subsystem failed";
}
}
#ifdef Q_OS_WIN
if (m_parser.isSet("a"))
m_pageController->showOnStartup();
else
emit m_pageController->raiseMainWindow();
#else
m_pageController->showOnStartup();
#endif
// TODO - fix
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
if (isPrimary()) {
QObject::connect(this, &SingleApplication::instanceStarted, m_pageController.get(), [this]() {
qDebug() << "Secondary instance started, showing this window instead";
emit m_pageController->raiseMainWindow();
});
}
#endif
// Android TextField clipboard workaround
// https://bugreports.qt.io/browse/QTBUG-113461
#ifdef Q_OS_ANDROID
QObject::connect(qApp, &QGuiApplication::applicationStateChanged, [](Qt::ApplicationState state) {
if (state == Qt::ApplicationActive) {
if (qApp->clipboard()->mimeData()->formats().contains("text/html")) {
QTextDocument doc;
doc.setHtml(qApp->clipboard()->mimeData()->html());
qApp->clipboard()->setText(doc.toPlainText());
}
}
});
#endif
}
void AmneziaApplication::registerTypes()
{
qRegisterMetaType<ServerCredentials>("ServerCredentials");
qRegisterMetaType<DockerContainer>("DockerContainer");
qRegisterMetaType<TransportProto>("TransportProto");
qRegisterMetaType<Proto>("Proto");
qRegisterMetaType<ServiceType>("ServiceType");
declareQmlProtocolEnum();
declareQmlContainerEnum();
qmlRegisterType<QRCodeReader>("QRCodeReader", 1, 0, "QRCodeReader");
m_containerProps.reset(new ContainerProps());
qmlRegisterSingletonInstance("ContainerProps", 1, 0, "ContainerProps", m_containerProps.get());
m_protocolProps.reset(new ProtocolProps());
qmlRegisterSingletonInstance("ProtocolProps", 1, 0, "ProtocolProps", m_protocolProps.get());
qmlRegisterSingletonType(QUrl("qrc:/ui/qml/Filters/ContainersModelFilters.qml"), "ContainersModelFilters", 1, 0,
"ContainersModelFilters");
//
Vpn::declareQmlVpnConnectionStateEnum();
PageLoader::declareQmlPageEnum();
}
void AmneziaApplication::loadFonts()
{
QQuickStyle::setStyle("Basic");
QFontDatabase::addApplicationFont(":/fonts/pt-root-ui_vf.ttf");
}
void AmneziaApplication::loadTranslator()
{
auto locale = m_settings->getAppLanguage();
m_translator.reset(new QTranslator());
updateTranslator(locale);
}
void AmneziaApplication::updateTranslator(const QLocale &locale)
{
if (!m_translator->isEmpty()) {
QCoreApplication::removeTranslator(m_translator.get());
}
QString strFileName = QString(":/translations/amneziavpn") + QLatin1String("_") + locale.name() + ".qm";
if (m_translator->load(strFileName)) {
if (QCoreApplication::installTranslator(m_translator.get())) {
m_settings->setAppLanguage(locale);
}
} else {
m_settings->setAppLanguage(QLocale::English);
}
m_engine->retranslate();
emit translationsUpdated();
}
bool AmneziaApplication::parseCommands()
{
m_parser.setApplicationDescription(APPLICATION_NAME);
m_parser.addHelpOption();
m_parser.addVersionOption();
QCommandLineOption c_autostart { { "a", "autostart" }, "System autostart" };
m_parser.addOption(c_autostart);
QCommandLineOption c_cleanup { { "c", "cleanup" }, "Cleanup logs" };
m_parser.addOption(c_cleanup);
m_parser.process(*this);
if (m_parser.isSet(c_cleanup)) {
Logger::cleanUp();
QTimer::singleShot(100, this, [this] { quit(); });
exec();
return false;
}
return true;
}
QQmlApplicationEngine *AmneziaApplication::qmlEngine() const
{
return m_engine;
}
void AmneziaApplication::initModels()
{
m_containersModel.reset(new ContainersModel(this));
m_engine->rootContext()->setContextProperty("ContainersModel", m_containersModel.get());
m_serversModel.reset(new ServersModel(m_settings, this));
m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get());
connect(m_serversModel.get(), &ServersModel::containersUpdated, m_containersModel.get(),
&ContainersModel::updateModel);
connect(m_serversModel.get(), &ServersModel::defaultContainerChanged, m_containersModel.get(),
&ContainersModel::setDefaultContainer);
m_containersModel->setDefaultContainer(m_serversModel->getDefaultContainer()); // make better?
m_languageModel.reset(new LanguageModel(m_settings, this));
m_engine->rootContext()->setContextProperty("LanguageModel", m_languageModel.get());
connect(m_languageModel.get(), &LanguageModel::updateTranslations, this, &AmneziaApplication::updateTranslator);
connect(this, &AmneziaApplication::translationsUpdated, m_languageModel.get(), &LanguageModel::translationsUpdated);
m_sitesModel.reset(new SitesModel(m_settings, this));
m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get());
m_protocolsModel.reset(new ProtocolsModel(m_settings, this));
m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get());
m_openVpnConfigModel.reset(new OpenVpnConfigModel(this));
m_engine->rootContext()->setContextProperty("OpenVpnConfigModel", m_openVpnConfigModel.get());
m_shadowSocksConfigModel.reset(new ShadowSocksConfigModel(this));
m_engine->rootContext()->setContextProperty("ShadowSocksConfigModel", m_shadowSocksConfigModel.get());
m_cloakConfigModel.reset(new CloakConfigModel(this));
m_engine->rootContext()->setContextProperty("CloakConfigModel", m_cloakConfigModel.get());
m_wireGuardConfigModel.reset(new WireGuardConfigModel(this));
m_engine->rootContext()->setContextProperty("WireGuardConfigModel", m_wireGuardConfigModel.get());
m_awgConfigModel.reset(new AwgConfigModel(this));
m_engine->rootContext()->setContextProperty("AwgConfigModel", m_awgConfigModel.get());
#ifdef Q_OS_WINDOWS
m_ikev2ConfigModel.reset(new Ikev2ConfigModel(this));
m_engine->rootContext()->setContextProperty("Ikev2ConfigModel", m_ikev2ConfigModel.get());
#endif
m_sftpConfigModel.reset(new SftpConfigModel(this));
m_engine->rootContext()->setContextProperty("SftpConfigModel", m_sftpConfigModel.get());
m_clientManagementModel.reset(new ClientManagementModel(m_settings, this));
m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get());
connect(m_clientManagementModel.get(), &ClientManagementModel::adminConfigRevoked,
m_serversModel.get(), &ServersModel::clearCachedProfile);
connect(m_configurator.get(), &VpnConfigurator::newVpnConfigCreated, this,
[this](const QString &clientId, const QString &clientName, const DockerContainer container,
ServerCredentials credentials) {
m_serversModel->reloadContainerConfig();
m_clientManagementModel->appendClient(clientId, clientName, container, credentials);
emit m_configurator->clientModelUpdated();
});
}
void AmneziaApplication::initControllers()
{
m_connectionController.reset(new ConnectionController(m_serversModel, m_containersModel, m_vpnConnection));
m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get());
connect(this, &AmneziaApplication::translationsUpdated, m_connectionController.get(),
&ConnectionController::onTranslationsUpdated);
m_pageController.reset(new PageController(m_serversModel, m_settings));
m_engine->rootContext()->setContextProperty("PageController", m_pageController.get());
m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_settings));
m_engine->rootContext()->setContextProperty("InstallController", m_installController.get());
connect(m_installController.get(), &InstallController::passphraseRequestStarted, m_pageController.get(),
&PageController::showPassphraseRequestDrawer);
connect(m_pageController.get(), &PageController::passphraseRequestDrawerClosed, m_installController.get(),
&InstallController::setEncryptedPassphrase);
connect(m_installController.get(), &InstallController::currentContainerUpdated, m_connectionController.get(),
&ConnectionController::onCurrentContainerUpdated);
m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings));
m_engine->rootContext()->setContextProperty("ImportController", m_importController.get());
m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_clientManagementModel,
m_settings, m_configurator));
m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get());
m_settingsController.reset(new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, m_settings));
m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get());
if (m_settingsController->isAutoConnectEnabled() && m_serversModel->getDefaultServerIndex() >= 0) {
QTimer::singleShot(1000, this, [this]() { m_connectionController->openConnection(); });
}
connect(m_settingsController.get(), &SettingsController::amneziaDnsToggled , m_serversModel.get(),
&ServersModel::toggleAmneziaDns);
m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel));
m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get());
m_systemController.reset(new SystemController(m_settings));
m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get());
m_cloudController.reset(new ApiController(m_serversModel, m_containersModel));
m_engine->rootContext()->setContextProperty("ApiController", m_cloudController.get());
}
#include "amnezia_application.h"
#include <QClipboard>
#include <QFontDatabase>
#include <QMimeData>
#include <QQuickStyle>
#include <QResource>
#include <QStandardPaths>
#include <QTextDocument>
#include <QTimer>
#include <QTranslator>
#include <QQuickItem>
#include "logger.h"
#include "version.h"
#include "platforms/ios/QRCodeReaderBase.h"
#if defined(Q_OS_ANDROID)
#include "platforms/android/android_controller.h"
#endif
#include "protocols/qml_register_protocols.h"
#if defined(Q_OS_IOS)
#include "platforms/ios/ios_controller.h"
#endif
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv)
#else
AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecondary, SingleApplication::Options options,
int timeout, const QString &userData)
: SingleApplication(argc, argv, allowSecondary, options, timeout, userData)
#endif
{
setQuitOnLastWindowClosed(false);
// Fix config file permissions
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
{
QSettings s(ORGANIZATION_NAME, APPLICATION_NAME);
s.setValue("permFixed", true);
}
QString configLoc1 = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/"
+ ORGANIZATION_NAME + "/" + APPLICATION_NAME + ".conf";
QFile::setPermissions(configLoc1, QFileDevice::ReadOwner | QFileDevice::WriteOwner);
QString configLoc2 = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/"
+ ORGANIZATION_NAME + "/" + APPLICATION_NAME + "/" + APPLICATION_NAME + ".conf";
QFile::setPermissions(configLoc2, QFileDevice::ReadOwner | QFileDevice::WriteOwner);
#endif
m_settings = std::shared_ptr<Settings>(new Settings);
}
AmneziaApplication::~AmneziaApplication()
{
m_vpnConnectionThread.quit();
m_vpnConnectionThread.wait(3000);
if (m_engine) {
QObject::disconnect(m_engine, 0, 0, 0);
delete m_engine;
}
}
void AmneziaApplication::init()
{
m_engine = new QQmlApplicationEngine;
const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml"));
QObject::connect(
m_engine, &QQmlApplicationEngine::objectCreated, this,
[url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
},
Qt::QueuedConnection);
m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance());
m_configurator = std::shared_ptr<VpnConfigurator>(new VpnConfigurator(m_settings, this));
m_vpnConnection.reset(new VpnConnection(m_settings, m_configurator));
m_vpnConnection->moveToThread(&m_vpnConnectionThread);
m_vpnConnectionThread.start();
initModels();
loadTranslator();
initControllers();
#ifdef Q_OS_ANDROID
if(!AndroidController::initLogging()) {
qFatal("Android logging initialization failed");
}
AndroidController::instance()->setSaveLogs(m_settings->isSaveLogs());
connect(m_settings.get(), &Settings::saveLogsChanged,
AndroidController::instance(), &AndroidController::setSaveLogs);
connect(AndroidController::instance(), &AndroidController::initConnectionState, this,
[this](Vpn::ConnectionState state) {
m_connectionController->onConnectionStateChanged(state);
if (m_vpnConnection)
m_vpnConnection->restoreConnection();
});
if (!AndroidController::instance()->initialize()) {
qFatal("Android controller initialization failed");
}
connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, [this](QString data) {
m_pageController->replaceStartPage();
m_importController->extractConfigFromData(data);
m_pageController->goToPageViewConfig();
});
#endif
#ifdef Q_OS_IOS
IosController::Instance()->initialize();
connect(IosController::Instance(), &IosController::importConfigFromOutside, [this](QString data) {
m_pageController->replaceStartPage();
m_importController->extractConfigFromData(data);
m_pageController->goToPageViewConfig();
});
connect(IosController::Instance(), &IosController::importBackupFromOutside, [this](QString filePath) {
m_pageController->replaceStartPage();
m_pageController->goToPageSettingsBackup();
m_settingsController->importBackupFromOutside(filePath);
});
#endif
m_notificationHandler.reset(NotificationHandler::create(nullptr));
connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(),
&NotificationHandler::setConnectionState);
connect(m_notificationHandler.get(), &NotificationHandler::raiseRequested, m_pageController.get(),
&PageController::raiseMainWindow);
connect(m_notificationHandler.get(), &NotificationHandler::connectRequested, m_connectionController.get(),
&ConnectionController::openConnection);
connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(),
&ConnectionController::closeConnection);
connect(this, &AmneziaApplication::translationsUpdated, m_notificationHandler.get(),
&NotificationHandler::onTranslationsUpdated);
m_engine->load(url);
m_systemController->setQmlRoot(m_engine->rootObjects().value(0));
#ifndef Q_OS_ANDROID
if (m_settings->isSaveLogs()) {
if (!Logger::init()) {
qWarning() << "Initialization of debug subsystem failed";
}
}
#endif
#ifdef Q_OS_WIN
if (m_parser.isSet("a"))
m_pageController->showOnStartup();
else
emit m_pageController->raiseMainWindow();
#else
m_pageController->showOnStartup();
#endif
// TODO - fix
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
if (isPrimary()) {
QObject::connect(this, &SingleApplication::instanceStarted, m_pageController.get(), [this]() {
qDebug() << "Secondary instance started, showing this window instead";
emit m_pageController->raiseMainWindow();
});
}
#endif
// Android TextArea clipboard workaround
// Text from TextArea always has "text/html" mime-type:
// /qt/6.6.1/Src/qtdeclarative/src/quick/items/qquicktextcontrol.cpp:1865
// Next, html is created for this mime-type:
// /qt/6.6.1/Src/qtdeclarative/src/quick/items/qquicktextcontrol.cpp:1885
// And this html goes to the Androids clipboard, i.e. text from TextArea is always copied as richText:
// /qt/6.6.1/Src/qtbase/src/plugins/platforms/android/androidjniclipboard.cpp:46
// So we catch all the copies to the clipboard and clear them from "text/html"
#ifdef Q_OS_ANDROID
connect(QGuiApplication::clipboard(), &QClipboard::dataChanged, []() {
auto clipboard = QGuiApplication::clipboard();
if (clipboard->mimeData()->hasHtml()) {
clipboard->setText(clipboard->text());
}
});
#endif
}
void AmneziaApplication::registerTypes()
{
qRegisterMetaType<ServerCredentials>("ServerCredentials");
qRegisterMetaType<DockerContainer>("DockerContainer");
qRegisterMetaType<TransportProto>("TransportProto");
qRegisterMetaType<Proto>("Proto");
qRegisterMetaType<ServiceType>("ServiceType");
declareQmlProtocolEnum();
declareQmlContainerEnum();
qmlRegisterType<QRCodeReader>("QRCodeReader", 1, 0, "QRCodeReader");
m_containerProps.reset(new ContainerProps());
qmlRegisterSingletonInstance("ContainerProps", 1, 0, "ContainerProps", m_containerProps.get());
m_protocolProps.reset(new ProtocolProps());
qmlRegisterSingletonInstance("ProtocolProps", 1, 0, "ProtocolProps", m_protocolProps.get());
qmlRegisterSingletonType(QUrl("qrc:/ui/qml/Filters/ContainersModelFilters.qml"), "ContainersModelFilters", 1, 0,
"ContainersModelFilters");
//
Vpn::declareQmlVpnConnectionStateEnum();
PageLoader::declareQmlPageEnum();
}
void AmneziaApplication::loadFonts()
{
QQuickStyle::setStyle("Basic");
QFontDatabase::addApplicationFont(":/fonts/pt-root-ui_vf.ttf");
}
void AmneziaApplication::loadTranslator()
{
auto locale = m_settings->getAppLanguage();
m_translator.reset(new QTranslator());
updateTranslator(locale);
}
void AmneziaApplication::updateTranslator(const QLocale &locale)
{
if (!m_translator->isEmpty()) {
QCoreApplication::removeTranslator(m_translator.get());
}
QString strFileName = QString(":/translations/amneziavpn") + QLatin1String("_") + locale.name() + ".qm";
if (m_translator->load(strFileName)) {
if (QCoreApplication::installTranslator(m_translator.get())) {
m_settings->setAppLanguage(locale);
}
} else {
m_settings->setAppLanguage(QLocale::English);
}
m_engine->retranslate();
emit translationsUpdated();
}
bool AmneziaApplication::parseCommands()
{
m_parser.setApplicationDescription(APPLICATION_NAME);
m_parser.addHelpOption();
m_parser.addVersionOption();
QCommandLineOption c_autostart { { "a", "autostart" }, "System autostart" };
m_parser.addOption(c_autostart);
QCommandLineOption c_cleanup { { "c", "cleanup" }, "Cleanup logs" };
m_parser.addOption(c_cleanup);
m_parser.process(*this);
if (m_parser.isSet(c_cleanup)) {
Logger::cleanUp();
QTimer::singleShot(100, this, [this] { quit(); });
exec();
return false;
}
return true;
}
QQmlApplicationEngine *AmneziaApplication::qmlEngine() const
{
return m_engine;
}
void AmneziaApplication::initModels()
{
m_containersModel.reset(new ContainersModel(this));
m_engine->rootContext()->setContextProperty("ContainersModel", m_containersModel.get());
m_serversModel.reset(new ServersModel(m_settings, this));
m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get());
connect(m_serversModel.get(), &ServersModel::containersUpdated, m_containersModel.get(),
&ContainersModel::updateModel);
connect(m_serversModel.get(), &ServersModel::defaultContainerChanged, m_containersModel.get(),
&ContainersModel::setDefaultContainer);
m_containersModel->setDefaultContainer(m_serversModel->getDefaultContainer()); // make better?
m_languageModel.reset(new LanguageModel(m_settings, this));
m_engine->rootContext()->setContextProperty("LanguageModel", m_languageModel.get());
connect(m_languageModel.get(), &LanguageModel::updateTranslations, this, &AmneziaApplication::updateTranslator);
connect(this, &AmneziaApplication::translationsUpdated, m_languageModel.get(), &LanguageModel::translationsUpdated);
m_sitesModel.reset(new SitesModel(m_settings, this));
m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get());
m_protocolsModel.reset(new ProtocolsModel(m_settings, this));
m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get());
m_openVpnConfigModel.reset(new OpenVpnConfigModel(this));
m_engine->rootContext()->setContextProperty("OpenVpnConfigModel", m_openVpnConfigModel.get());
m_shadowSocksConfigModel.reset(new ShadowSocksConfigModel(this));
m_engine->rootContext()->setContextProperty("ShadowSocksConfigModel", m_shadowSocksConfigModel.get());
m_cloakConfigModel.reset(new CloakConfigModel(this));
m_engine->rootContext()->setContextProperty("CloakConfigModel", m_cloakConfigModel.get());
m_wireGuardConfigModel.reset(new WireGuardConfigModel(this));
m_engine->rootContext()->setContextProperty("WireGuardConfigModel", m_wireGuardConfigModel.get());
m_awgConfigModel.reset(new AwgConfigModel(this));
m_engine->rootContext()->setContextProperty("AwgConfigModel", m_awgConfigModel.get());
#ifdef Q_OS_WINDOWS
m_ikev2ConfigModel.reset(new Ikev2ConfigModel(this));
m_engine->rootContext()->setContextProperty("Ikev2ConfigModel", m_ikev2ConfigModel.get());
#endif
m_sftpConfigModel.reset(new SftpConfigModel(this));
m_engine->rootContext()->setContextProperty("SftpConfigModel", m_sftpConfigModel.get());
m_clientManagementModel.reset(new ClientManagementModel(m_settings, this));
m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get());
connect(m_clientManagementModel.get(), &ClientManagementModel::adminConfigRevoked,
m_serversModel.get(), &ServersModel::clearCachedProfile);
connect(m_configurator.get(), &VpnConfigurator::newVpnConfigCreated, this,
[this](const QString &clientId, const QString &clientName, const DockerContainer container,
ServerCredentials credentials) {
m_serversModel->reloadContainerConfig();
m_clientManagementModel->appendClient(clientId, clientName, container, credentials);
emit m_configurator->clientModelUpdated();
});
}
void AmneziaApplication::initControllers()
{
m_connectionController.reset(new ConnectionController(m_serversModel, m_containersModel, m_vpnConnection));
m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get());
connect(this, &AmneziaApplication::translationsUpdated, m_connectionController.get(),
&ConnectionController::onTranslationsUpdated);
m_pageController.reset(new PageController(m_serversModel, m_settings));
m_engine->rootContext()->setContextProperty("PageController", m_pageController.get());
m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_settings));
m_engine->rootContext()->setContextProperty("InstallController", m_installController.get());
connect(m_installController.get(), &InstallController::passphraseRequestStarted, m_pageController.get(),
&PageController::showPassphraseRequestDrawer);
connect(m_pageController.get(), &PageController::passphraseRequestDrawerClosed, m_installController.get(),
&InstallController::setEncryptedPassphrase);
connect(m_installController.get(), &InstallController::currentContainerUpdated, m_connectionController.get(),
&ConnectionController::onCurrentContainerUpdated);
m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings));
m_engine->rootContext()->setContextProperty("ImportController", m_importController.get());
m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_clientManagementModel,
m_settings, m_configurator));
m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get());
m_settingsController.reset(new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, m_settings));
m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get());
if (m_settingsController->isAutoConnectEnabled() && m_serversModel->getDefaultServerIndex() >= 0) {
QTimer::singleShot(1000, this, [this]() { m_connectionController->openConnection(); });
}
connect(m_settingsController.get(), &SettingsController::amneziaDnsToggled , m_serversModel.get(),
&ServersModel::toggleAmneziaDns);
m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel));
m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get());
m_systemController.reset(new SystemController(m_settings));
m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get());
m_cloudController.reset(new ApiController(m_serversModel, m_containersModel));
m_engine->rootContext()->setContextProperty("ApiController", m_cloudController.get());
}

View File

@@ -1,127 +1,127 @@
#ifndef AMNEZIA_APPLICATION_H
#define AMNEZIA_APPLICATION_H
#include <QCommandLineParser>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QThread>
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#include <QGuiApplication>
#else
#include <QApplication>
#endif
#include "settings.h"
#include "vpnconnection.h"
#include "configurators/vpn_configurator.h"
#include "ui/controllers/connectionController.h"
#include "ui/controllers/exportController.h"
#include "ui/controllers/importController.h"
#include "ui/controllers/installController.h"
#include "ui/controllers/pageController.h"
#include "ui/controllers/settingsController.h"
#include "ui/controllers/sitesController.h"
#include "ui/controllers/systemController.h"
#include "ui/controllers/apiController.h"
#include "ui/models/containers_model.h"
#include "ui/models/languageModel.h"
#include "ui/models/protocols/cloakConfigModel.h"
#include "ui/notificationhandler.h"
#ifdef Q_OS_WINDOWS
#include "ui/models/protocols/ikev2ConfigModel.h"
#endif
#include "ui/models/protocols/awgConfigModel.h"
#include "ui/models/protocols/openvpnConfigModel.h"
#include "ui/models/protocols/shadowsocksConfigModel.h"
#include "ui/models/protocols/wireguardConfigModel.h"
#include "ui/models/protocols_model.h"
#include "ui/models/servers_model.h"
#include "ui/models/services/sftpConfigModel.h"
#include "ui/models/sites_model.h"
#include "ui/models/clientManagementModel.h"
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#define AMNEZIA_BASE_CLASS QGuiApplication
#else
#define AMNEZIA_BASE_CLASS SingleApplication
#define QAPPLICATION_CLASS QApplication
#include "singleapplication.h"
#endif
class AmneziaApplication : public AMNEZIA_BASE_CLASS
{
Q_OBJECT
public:
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
AmneziaApplication(int &argc, char *argv[]);
#else
AmneziaApplication(int &argc, char *argv[], bool allowSecondary = false,
SingleApplication::Options options = SingleApplication::User, int timeout = 1000,
const QString &userData = {});
#endif
virtual ~AmneziaApplication();
void init();
void registerTypes();
void loadFonts();
void loadTranslator();
void updateTranslator(const QLocale &locale);
bool parseCommands();
QQmlApplicationEngine *qmlEngine() const;
signals:
void translationsUpdated();
private:
void initModels();
void initControllers();
QQmlApplicationEngine *m_engine {};
std::shared_ptr<Settings> m_settings;
std::shared_ptr<VpnConfigurator> m_configurator;
QSharedPointer<ContainerProps> m_containerProps;
QSharedPointer<ProtocolProps> m_protocolProps;
QSharedPointer<QTranslator> m_translator;
QCommandLineParser m_parser;
QSharedPointer<ContainersModel> m_containersModel;
QSharedPointer<ServersModel> m_serversModel;
QSharedPointer<LanguageModel> m_languageModel;
QSharedPointer<ProtocolsModel> m_protocolsModel;
QSharedPointer<SitesModel> m_sitesModel;
QSharedPointer<ClientManagementModel> m_clientManagementModel;
QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel;
QScopedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel;
QScopedPointer<CloakConfigModel> m_cloakConfigModel;
QScopedPointer<WireGuardConfigModel> m_wireGuardConfigModel;
QScopedPointer<AwgConfigModel> m_awgConfigModel;
#ifdef Q_OS_WINDOWS
QScopedPointer<Ikev2ConfigModel> m_ikev2ConfigModel;
#endif
QScopedPointer<SftpConfigModel> m_sftpConfigModel;
QSharedPointer<VpnConnection> m_vpnConnection;
QThread m_vpnConnectionThread;
QScopedPointer<NotificationHandler> m_notificationHandler;
QScopedPointer<ConnectionController> m_connectionController;
QScopedPointer<PageController> m_pageController;
QScopedPointer<InstallController> m_installController;
QScopedPointer<ImportController> m_importController;
QScopedPointer<ExportController> m_exportController;
QScopedPointer<SettingsController> m_settingsController;
QScopedPointer<SitesController> m_sitesController;
QScopedPointer<SystemController> m_systemController;
QScopedPointer<ApiController> m_cloudController;
};
#endif // AMNEZIA_APPLICATION_H
#ifndef AMNEZIA_APPLICATION_H
#define AMNEZIA_APPLICATION_H
#include <QCommandLineParser>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QThread>
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#include <QGuiApplication>
#else
#include <QApplication>
#endif
#include "settings.h"
#include "vpnconnection.h"
#include "configurators/vpn_configurator.h"
#include "ui/controllers/connectionController.h"
#include "ui/controllers/exportController.h"
#include "ui/controllers/importController.h"
#include "ui/controllers/installController.h"
#include "ui/controllers/pageController.h"
#include "ui/controllers/settingsController.h"
#include "ui/controllers/sitesController.h"
#include "ui/controllers/systemController.h"
#include "ui/controllers/apiController.h"
#include "ui/models/containers_model.h"
#include "ui/models/languageModel.h"
#include "ui/models/protocols/cloakConfigModel.h"
#include "ui/notificationhandler.h"
#ifdef Q_OS_WINDOWS
#include "ui/models/protocols/ikev2ConfigModel.h"
#endif
#include "ui/models/protocols/awgConfigModel.h"
#include "ui/models/protocols/openvpnConfigModel.h"
#include "ui/models/protocols/shadowsocksConfigModel.h"
#include "ui/models/protocols/wireguardConfigModel.h"
#include "ui/models/protocols_model.h"
#include "ui/models/servers_model.h"
#include "ui/models/services/sftpConfigModel.h"
#include "ui/models/sites_model.h"
#include "ui/models/clientManagementModel.h"
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#define AMNEZIA_BASE_CLASS QGuiApplication
#else
#define AMNEZIA_BASE_CLASS SingleApplication
#define QAPPLICATION_CLASS QApplication
#include "singleapplication.h"
#endif
class AmneziaApplication : public AMNEZIA_BASE_CLASS
{
Q_OBJECT
public:
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
AmneziaApplication(int &argc, char *argv[]);
#else
AmneziaApplication(int &argc, char *argv[], bool allowSecondary = false,
SingleApplication::Options options = SingleApplication::User, int timeout = 1000,
const QString &userData = {});
#endif
virtual ~AmneziaApplication();
void init();
void registerTypes();
void loadFonts();
void loadTranslator();
void updateTranslator(const QLocale &locale);
bool parseCommands();
QQmlApplicationEngine *qmlEngine() const;
signals:
void translationsUpdated();
private:
void initModels();
void initControllers();
QQmlApplicationEngine *m_engine {};
std::shared_ptr<Settings> m_settings;
std::shared_ptr<VpnConfigurator> m_configurator;
QSharedPointer<ContainerProps> m_containerProps;
QSharedPointer<ProtocolProps> m_protocolProps;
QSharedPointer<QTranslator> m_translator;
QCommandLineParser m_parser;
QSharedPointer<ContainersModel> m_containersModel;
QSharedPointer<ServersModel> m_serversModel;
QSharedPointer<LanguageModel> m_languageModel;
QSharedPointer<ProtocolsModel> m_protocolsModel;
QSharedPointer<SitesModel> m_sitesModel;
QSharedPointer<ClientManagementModel> m_clientManagementModel;
QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel;
QScopedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel;
QScopedPointer<CloakConfigModel> m_cloakConfigModel;
QScopedPointer<WireGuardConfigModel> m_wireGuardConfigModel;
QScopedPointer<AwgConfigModel> m_awgConfigModel;
#ifdef Q_OS_WINDOWS
QScopedPointer<Ikev2ConfigModel> m_ikev2ConfigModel;
#endif
QScopedPointer<SftpConfigModel> m_sftpConfigModel;
QSharedPointer<VpnConnection> m_vpnConnection;
QThread m_vpnConnectionThread;
QScopedPointer<NotificationHandler> m_notificationHandler;
QScopedPointer<ConnectionController> m_connectionController;
QScopedPointer<PageController> m_pageController;
QScopedPointer<InstallController> m_installController;
QScopedPointer<ImportController> m_importController;
QScopedPointer<ExportController> m_exportController;
QScopedPointer<SettingsController> m_settingsController;
QScopedPointer<SitesController> m_sitesController;
QScopedPointer<SystemController> m_systemController;
QScopedPointer<ApiController> m_cloudController;
};
#endif // AMNEZIA_APPLICATION_H

View File

@@ -1,6 +1,8 @@
<?xml version="1.0"?>
<!-- Leave package attribute for androiddeployqt -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.amnezia.vpn"
android:versionName="-- %%INSERT_VERSION_NAME%% --"
android:versionCode="-- %%INSERT_VERSION_CODE%% --"
@@ -31,13 +33,17 @@
android:label="-- %%INSERT_APP_NAME%% --"
android:icon="@mipmap/icon"
android:roundIcon="@mipmap/icon_round"
android:theme="@style/NoActionBar">
android:theme="@style/NoActionBar"
android:fullBackupContent="@xml/backup_content"
android:dataExtractionRules="@xml/data_extraction_rules"
tools:targetApi="s">
<activity
android:name=".AmneziaActivity"
android:configChanges="uiMode|screenSize|smallestScreenSize|screenLayout|orientation|density
|fontScale|layoutDirection|locale|keyboard|keyboardHidden|navigation|mcc|mnc"
android:launchMode="singleInstance"
android:windowSoftInputMode="adjustResize"
android:exported="true">
<intent-filter>
@@ -146,7 +152,7 @@
android:authorities="org.amnezia.vpn.qtprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/qtprovider_paths" />
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/qtprovider_paths" />
</provider>
</application>
</manifest>

View File

@@ -99,7 +99,7 @@ class AwgConfig private constructor(
fun setH3(h3: Long) = apply { this.h3 = h3 }
fun setH4(h4: Long) = apply { this.h4 = h4 }
override fun build(): AwgConfig = AwgConfig(this)
override fun build(): AwgConfig = configBuild().run { AwgConfig(this@Builder) }
}
companion object {

View File

@@ -108,7 +108,6 @@ dependencies {
implementation(project(":cloak"))
implementation(libs.androidx.core)
implementation(libs.androidx.activity)
implementation(libs.androidx.security.crypto)
implementation(libs.kotlinx.coroutines)
implementation(libs.bundles.androidx.camera)
implementation(libs.google.mlkit)

View File

@@ -3,6 +3,9 @@ package org.amnezia.vpn.protocol.cloak
import android.util.Base64
import net.openvpn.ovpn3.ClientAPI_Config
import org.amnezia.vpn.protocol.openvpn.OpenVpn
import org.amnezia.vpn.protocol.openvpn.OpenVpnConfig
import org.amnezia.vpn.util.net.InetNetwork
import org.amnezia.vpn.util.net.parseInetAddress
import org.json.JSONObject
/**
@@ -51,6 +54,13 @@ class Cloak : OpenVpn() {
return openVpnConfig
}
override fun configPluggableTransport(configBuilder: OpenVpnConfig.Builder, config: JSONObject) {
// exclude remote server ip from vpn routes
val remoteServer = config.getString("hostName")
val remoteServerAddress = InetNetwork(parseInetAddress(remoteServer))
configBuilder.excludeRoute(remoteServerAddress)
}
private fun checkCloakJson(cloakConfigJson: JSONObject): JSONObject {
cloakConfigJson.put("NumConn", 1)
cloakConfigJson.put("ProxyMethod", "openvpn")

View File

@@ -2,7 +2,6 @@ package org.amnezia.vpn.protocol.openvpn
import android.content.Context
import android.net.VpnService.Builder
import android.os.Build
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
@@ -14,7 +13,6 @@ import org.amnezia.vpn.protocol.ProtocolState
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
import org.amnezia.vpn.protocol.Statistics
import org.amnezia.vpn.protocol.VpnStartException
import org.amnezia.vpn.util.net.InetNetwork
import org.amnezia.vpn.util.net.getLocalNetworks
import org.json.JSONObject
@@ -79,16 +77,8 @@ open class OpenVpn : Protocol() {
if (evalConfig.error) {
throw BadConfigException("OpenVPN config parse error: ${evalConfig.message}")
}
configBuilder.apply {
// fix for split tunneling
// The exclude split tunneling OpenVpn configuration does not contain a default route.
// It is required for split tunneling in newer versions of Android.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
addRoute(InetNetwork("0.0.0.0", 0))
addRoute(InetNetwork("::", 0))
}
configSplitTunneling(config)
}
configPluggableTransport(configBuilder, config)
configBuilder.configSplitTunneling(config)
scope.launch {
val status = client.connect()
@@ -122,6 +112,8 @@ open class OpenVpn : Protocol() {
return openVpnConfig
}
protected open fun configPluggableTransport(configBuilder: OpenVpnConfig.Builder, config: JSONObject) {}
private fun makeEstablish(vpnBuilder: Builder): (OpenVpnConfig.Builder) -> Int = { configBuilder ->
val openVpnConfig = configBuilder.build()
buildVpnInterface(openVpnConfig, vpnBuilder)

View File

@@ -52,7 +52,7 @@ class OpenVpnClient(
// Callback to construct a new tun builder
// Should be called first.
override fun tun_builder_new(): Boolean {
Log.v(TAG, "tun_builder_new")
Log.d(TAG, "tun_builder_new")
configBuilder.clearAddresses()
return true
}
@@ -60,7 +60,7 @@ class OpenVpnClient(
// Callback to set MTU of the VPN interface
// Never called more than once per tun_builder session.
override fun tun_builder_set_mtu(mtu: Int): Boolean {
Log.v(TAG, "tun_builder_set_mtu: $mtu")
Log.d(TAG, "tun_builder_set_mtu: $mtu")
configBuilder.setMtu(mtu)
return true
}
@@ -71,7 +71,7 @@ class OpenVpnClient(
address: String, prefix_length: Int,
gateway: String, ipv6: Boolean, net30: Boolean
): Boolean {
Log.v(TAG, "tun_builder_add_address: $address, $prefix_length, $gateway, $ipv6, $net30")
Log.d(TAG, "tun_builder_add_address: $address, $prefix_length, $gateway, $ipv6, $net30")
configBuilder.addAddress(InetNetwork(address, prefix_length))
return true
}
@@ -80,7 +80,7 @@ class OpenVpnClient(
// May be called more than once per tun_builder session
// metric is optional and should be ignored if < 0
override fun tun_builder_add_route(address: String, prefix_length: Int, metric: Int, ipv6: Boolean): Boolean {
Log.v(TAG, "tun_builder_add_route: $address, $prefix_length, $metric, $ipv6")
Log.d(TAG, "tun_builder_add_route: $address, $prefix_length, $metric, $ipv6")
if (address == "remote_host") return false
configBuilder.addRoute(InetNetwork(address, prefix_length))
return true
@@ -90,10 +90,8 @@ class OpenVpnClient(
// May be called more than once per tun_builder session
// metric is optional and should be ignored if < 0
override fun tun_builder_exclude_route(address: String, prefix_length: Int, metric: Int, ipv6: Boolean): Boolean {
Log.v(TAG, "tun_builder_exclude_route: $address, $prefix_length, $metric, $ipv6")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
configBuilder.excludeRoute(InetNetwork(address, prefix_length))
}
Log.d(TAG, "tun_builder_exclude_route: $address, $prefix_length, $metric, $ipv6")
configBuilder.excludeRoute(InetNetwork(address, prefix_length))
return true
}
@@ -104,7 +102,7 @@ class OpenVpnClient(
// domain should be routed.
// Guaranteed to be called after tun_builder_reroute_gw.
override fun tun_builder_add_dns_server(address: String, ipv6: Boolean): Boolean {
Log.v(TAG, "tun_builder_add_dns_server: $address, $ipv6")
Log.d(TAG, "tun_builder_add_dns_server: $address, $ipv6")
configBuilder.addDnsServer(parseInetAddress(address))
return true
}
@@ -119,28 +117,28 @@ class OpenVpnClient(
// ignored for that family
// See also Android's VPNService.Builder.allowFamily method
/* override fun tun_builder_set_allow_family(af: Int, allow: Boolean): Boolean {
Log.v(TAG, "tun_builder_set_allow_family: $af, $allow")
Log.d(TAG, "tun_builder_set_allow_family: $af, $allow")
return true
} */
// Callback to set address of remote server
// Never called more than once per tun_builder session.
override fun tun_builder_set_remote_address(address: String, ipv6: Boolean): Boolean {
Log.v(TAG, "tun_builder_set_remote_address: $address, $ipv6")
Log.d(TAG, "tun_builder_set_remote_address: $address, $ipv6")
return true
}
// Optional callback that indicates OSI layer, should be 2 or 3.
// Defaults to 3.
override fun tun_builder_set_layer(layer: Int): Boolean {
Log.v(TAG, "tun_builder_set_layer: $layer")
Log.d(TAG, "tun_builder_set_layer: $layer")
return layer == 3
}
// Callback to set the session name
// Never called more than once per tun_builder session.
override fun tun_builder_set_session_name(name: String): Boolean {
Log.v(TAG, "tun_builder_set_session_name: $name")
Log.d(TAG, "tun_builder_set_session_name: $name")
return true
}
@@ -149,7 +147,7 @@ class OpenVpnClient(
// if the tunnel could not be established.
// Always called last after tun_builder session has been configured.
override fun tun_builder_establish(): Int {
Log.v(TAG, "tun_builder_establish")
Log.d(TAG, "tun_builder_establish")
return establish(configBuilder)
}
@@ -159,7 +157,7 @@ class OpenVpnClient(
// flags are defined in RGWFlags (rgwflags.hpp).
// Never called more than once per tun_builder session.
override fun tun_builder_reroute_gw(ipv4: Boolean, ipv6: Boolean, flags: Long): Boolean {
Log.v(TAG, "tun_builder_reroute_gw: $ipv4, $ipv6, $flags")
Log.d(TAG, "tun_builder_reroute_gw: $ipv4, $ipv6, $flags")
if ((flags and EMULATED_EXCLUDE_ROUTES.toLong()) != 0L) return true
if (ipv4) {
configBuilder.addRoute(InetNetwork("0.0.0.0", 0))
@@ -176,7 +174,7 @@ class OpenVpnClient(
// reroute_dns parameter.
// Guaranteed to be called after tun_builder_reroute_gw.
override fun tun_builder_add_search_domain(domain: String): Boolean {
Log.v(TAG, "tun_builder_add_search_domain: $domain")
Log.d(TAG, "tun_builder_add_search_domain: $domain")
configBuilder.setSearchDomain(domain)
return true
}
@@ -184,7 +182,7 @@ class OpenVpnClient(
// Callback to set the HTTP proxy
// Never called more than once per tun_builder session.
override fun tun_builder_set_proxy_http(host: String, port: Int): Boolean {
Log.v(TAG, "tun_builder_set_proxy_http: $host, $port")
Log.d(TAG, "tun_builder_set_proxy_http: $host, $port")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
try {
configBuilder.setHttpProxy(ProxyInfo.buildDirectProxy(host, port))
@@ -199,7 +197,7 @@ class OpenVpnClient(
// Callback to set the HTTPS proxy
// Never called more than once per tun_builder session.
override fun tun_builder_set_proxy_https(host: String, port: Int): Boolean {
Log.v(TAG, "tun_builder_set_proxy_https: $host, $port")
Log.d(TAG, "tun_builder_set_proxy_https: $host, $port")
return false
}
@@ -208,7 +206,7 @@ class OpenVpnClient(
// to exclude them from the VPN network are generated
// This should be a list of CIDR networks (e.g. 192.168.0.0/24)
override fun tun_builder_get_local_networks(ipv6: Boolean): ClientAPI_StringVec {
Log.v(TAG, "tun_builder_get_local_networks: $ipv6")
Log.d(TAG, "tun_builder_get_local_networks: $ipv6")
val networks = ClientAPI_StringVec()
for (address in getLocalNetworks(ipv6)) {
networks.add(address.toString())
@@ -222,21 +220,21 @@ class OpenVpnClient(
// tun_builder_reroute_gw. Route metric is ignored
// if < 0.
/* override fun tun_builder_set_route_metric_default(metric: Int): Boolean {
Log.v(TAG, "tun_builder_set_route_metric_default: $metric")
Log.d(TAG, "tun_builder_set_route_metric_default: $metric")
return super.tun_builder_set_route_metric_default(metric)
} */
// Callback to add a host which should bypass the proxy
// May be called more than once per tun_builder session
/* override fun tun_builder_add_proxy_bypass(bypass_host: String): Boolean {
Log.v(TAG, "tun_builder_add_proxy_bypass: $bypass_host")
Log.d(TAG, "tun_builder_add_proxy_bypass: $bypass_host")
return super.tun_builder_add_proxy_bypass(bypass_host)
} */
// Callback to set the proxy "Auto Config URL"
// Never called more than once per tun_builder session.
/* override fun tun_builder_set_proxy_auto_config_url(url: String): Boolean {
Log.v(TAG, "tun_builder_set_proxy_auto_config_url: $url")
Log.d(TAG, "tun_builder_set_proxy_auto_config_url: $url")
return super.tun_builder_set_proxy_auto_config_url(url)
} */
@@ -245,7 +243,7 @@ class OpenVpnClient(
// May be called more than once per tun_builder session.
// Guaranteed to be called after tun_builder_reroute_gw.
/* override fun tun_builder_add_wins_server(address: String): Boolean {
Log.v(TAG, "tun_builder_add_wins_server: $address")
Log.d(TAG, "tun_builder_add_wins_server: $address")
return super.tun_builder_add_wins_server(address)
} */
@@ -254,7 +252,7 @@ class OpenVpnClient(
// set the "Connection-specific DNS Suffix" property on
// the TAP driver.
/* override fun tun_builder_set_adapter_domain_suffix(name: String): Boolean {
Log.v(TAG, "tun_builder_set_adapter_domain_suffix: $name")
Log.d(TAG, "tun_builder_set_adapter_domain_suffix: $name")
return super.tun_builder_set_adapter_domain_suffix(name)
} */
@@ -266,13 +264,13 @@ class OpenVpnClient(
// tun_builder_establish_lite() will be called. Otherwise,
// tun_builder_establish() will be called.
/* override fun tun_builder_persist(): Boolean {
Log.v(TAG, "tun_builder_persist")
Log.d(TAG, "tun_builder_persist")
return super.tun_builder_persist()
} */
// Indicates a reconnection with persisted tun state.
/* override fun tun_builder_establish_lite() {
Log.v(TAG, "tun_builder_establish_lite")
Log.d(TAG, "tun_builder_establish_lite")
super.tun_builder_establish_lite()
} */
@@ -280,7 +278,7 @@ class OpenVpnClient(
// If disconnect == true, then the teardown is occurring
// prior to final disconnect.
/* override fun tun_builder_teardown(disconnect: Boolean) {
Log.v(TAG, "tun_builder_teardown: $disconnect")
Log.d(TAG, "tun_builder_teardown: $disconnect")
super.tun_builder_teardown(disconnect)
} */
@@ -290,7 +288,7 @@ class OpenVpnClient(
// Parse OpenVPN configuration file.
override fun eval_config(arg0: ClientAPI_Config): ClientAPI_EvalConfig {
Log.v(TAG, "eval_config")
Log.d(TAG, "eval_config")
return super.eval_config(arg0)
}
@@ -299,7 +297,7 @@ class OpenVpnClient(
// to event() and log() functions. Make sure to call eval_config()
// and possibly provide_creds() as well before this function.
override fun connect(): ClientAPI_Status {
Log.v(TAG, "connect")
Log.d(TAG, "connect")
return super.connect()
}
@@ -307,7 +305,7 @@ class OpenVpnClient(
// Will be called from the thread executing connect().
// The remote and ipv6 are the remote host this socket will connect to
override fun socket_protect(socket: Int, remote: String, ipv6: Boolean): Boolean {
Log.v(TAG, "socket_protect: $socket, $remote, $ipv6")
Log.d(TAG, "socket_protect: $socket, $remote, $ipv6")
return protect(socket)
}
@@ -315,7 +313,7 @@ class OpenVpnClient(
// May be called asynchronously from a different thread
// when connect() is running.
override fun stop() {
Log.v(TAG, "stop")
Log.d(TAG, "stop")
super.stop()
}
@@ -323,21 +321,21 @@ class OpenVpnClient(
// when network is down. May be called from a different thread
// when connect() is running.
override fun pause(reason: String) {
Log.v(TAG, "pause: $reason")
Log.d(TAG, "pause: $reason")
super.pause(reason)
}
// Resume the client after it has been paused. May be called from a
// different thread when connect() is running.
override fun resume() {
Log.v(TAG, "resume")
Log.d(TAG, "resume")
super.resume()
}
// Do a disconnect/reconnect cycle n seconds from now. May be called
// from a different thread when connect() is running.
override fun reconnect(seconds: Int) {
Log.v(TAG, "reconnect")
Log.d(TAG, "reconnect: $seconds")
super.reconnect(seconds)
}
@@ -346,14 +344,14 @@ class OpenVpnClient(
// CONNECTION_TIMEOUT event. If true, the core will enter a PAUSE
// state.
override fun pause_on_connection_timeout(): Boolean {
Log.v(TAG, "pause_on_connection_timeout")
Log.d(TAG, "pause_on_connection_timeout")
return false
}
// Return information about the most recent connection. Should be called
// after an event of type "CONNECTED".
/* override fun connection_info(): ClientAPI_ConnectionInfo {
Log.v(TAG, "connection_info")
Log.d(TAG, "connection_info")
return super.connection_info()
} */
@@ -366,7 +364,7 @@ class OpenVpnClient(
override fun event(event: ClientAPI_Event) {
val name = event.name
val info = event.info
Log.v(TAG, "OpenVpn event: $name: $info")
Log.d(TAG, "OpenVpn event: $name: $info")
when (name) {
"COMPRESSION_ENABLED", "WARN" -> Log.w(TAG, "$name: $info")
"CONNECTED" -> state.value = CONNECTED
@@ -398,31 +396,31 @@ class OpenVpnClient(
// return transport stats only
override fun transport_stats(): ClientAPI_TransportStats {
Log.v(TAG, "transport_stats")
Log.d(TAG, "transport_stats")
return super.transport_stats()
}
// return a stats value, index should be >= 0 and < stats_n()
/* override fun stats_value(index: Int): Long {
Log.v(TAG, "stats_value: $index")
Log.d(TAG, "stats_value: $index")
return super.stats_value(index)
} */
// return all stats in a bundle
/* override fun stats_bundle(): ClientAPI_LLVector {
Log.v(TAG, "stats_bundle")
Log.d(TAG, "stats_bundle")
return super.stats_bundle()
} */
// return tun stats only
/* override fun tun_stats(): ClientAPI_InterfaceStats {
Log.v(TAG, "tun_stats")
Log.d(TAG, "tun_stats")
return super.tun_stats()
} */
// post control channel message
/* override fun post_cc_msg(msg: String) {
Log.v(TAG, "post_cc_msg: $msg")
Log.d(TAG, "post_cc_msg: $msg")
super.post_cc_msg(msg)
} */
}

View File

@@ -11,7 +11,7 @@ class OpenVpnConfig private constructor(
class Builder : ProtocolConfig.Builder(false) {
override var mtu: Int = OPENVPN_DEFAULT_MTU
override fun build(): OpenVpnConfig = OpenVpnConfig(this)
override fun build(): OpenVpnConfig = configBuild().run { OpenVpnConfig(this@Builder) }
}
companion object {

View File

@@ -14,8 +14,6 @@ import java.util.zip.ZipFile
import kotlinx.coroutines.flow.MutableStateFlow
import org.amnezia.vpn.util.Log
import org.amnezia.vpn.util.net.InetNetwork
import org.amnezia.vpn.util.net.IpRange
import org.amnezia.vpn.util.net.IpRangeSet
import org.json.JSONObject
private const val TAG = "Protocol"
@@ -53,40 +51,16 @@ abstract class Protocol {
val splitTunnelType = config.optInt("splitTunnelType")
if (splitTunnelType == SPLIT_TUNNEL_DISABLE) return
val splitTunnelSites = config.getJSONArray("splitTunnelSites")
when (splitTunnelType) {
SPLIT_TUNNEL_INCLUDE -> {
// remove default routes, if any
removeRoute(InetNetwork("0.0.0.0", 0))
removeRoute(InetNetwork("::", 0))
// add routes from config
for (i in 0 until splitTunnelSites.length()) {
val address = InetNetwork.parse(splitTunnelSites.getString(i))
addRoute(address)
}
}
val addressHandlerFunc = when (splitTunnelType) {
SPLIT_TUNNEL_INCLUDE -> ::includeAddress
SPLIT_TUNNEL_EXCLUDE -> ::excludeAddress
SPLIT_TUNNEL_EXCLUDE -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// exclude routes from config
for (i in 0 until splitTunnelSites.length()) {
val address = InetNetwork.parse(splitTunnelSites.getString(i))
excludeRoute(address)
}
} else {
// For older versions of Android, build a list of subnets without excluded addresses
val ipRangeSet = IpRangeSet()
ipRangeSet.remove(IpRange("127.0.0.0", 8))
for (i in 0 until splitTunnelSites.length()) {
val address = InetNetwork.parse(splitTunnelSites.getString(i))
ipRangeSet.remove(IpRange(address))
}
// remove default routes, if any
removeRoute(InetNetwork("0.0.0.0", 0))
removeRoute(InetNetwork("::", 0))
ipRangeSet.subnets().forEach(::addRoute)
addRoute(InetNetwork("2000::", 3))
}
}
else -> throw BadConfigException("Unexpected value of the 'splitTunnelType' parameter: $splitTunnelType")
}
for (i in 0 until splitTunnelSites.length()) {
val address = InetNetwork.parse(splitTunnelSites.getString(i))
addressHandlerFunc(address)
}
}

View File

@@ -5,6 +5,8 @@ import android.os.Build
import androidx.annotation.RequiresApi
import java.net.InetAddress
import org.amnezia.vpn.util.net.InetNetwork
import org.amnezia.vpn.util.net.IpRange
import org.amnezia.vpn.util.net.IpRangeSet
open class ProtocolConfig protected constructor(
val addresses: Set<InetNetwork>,
@@ -12,6 +14,8 @@ open class ProtocolConfig protected constructor(
val searchDomain: String?,
val routes: Set<InetNetwork>,
val excludedRoutes: Set<InetNetwork>,
val includedAddresses: Set<InetNetwork>,
val excludedAddresses: Set<InetNetwork>,
val excludedApplications: Set<String>,
val httpProxy: ProxyInfo?,
val allowAllAF: Boolean,
@@ -25,6 +29,8 @@ open class ProtocolConfig protected constructor(
builder.searchDomain,
builder.routes,
builder.excludedRoutes,
builder.includedAddresses,
builder.excludedAddresses,
builder.excludedApplications,
builder.httpProxy,
builder.allowAllAF,
@@ -37,6 +43,8 @@ open class ProtocolConfig protected constructor(
internal val dnsServers: MutableSet<InetAddress> = hashSetOf()
internal val routes: MutableSet<InetNetwork> = hashSetOf()
internal val excludedRoutes: MutableSet<InetNetwork> = hashSetOf()
internal val includedAddresses: MutableSet<InetNetwork> = hashSetOf()
internal val excludedAddresses: MutableSet<InetNetwork> = hashSetOf()
internal val excludedApplications: MutableSet<String> = hashSetOf()
internal var searchDomain: String? = null
@@ -71,12 +79,15 @@ open class ProtocolConfig protected constructor(
fun removeRoute(route: InetNetwork) = apply { this.routes.remove(route) }
fun clearRoutes() = apply { this.routes.clear() }
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
fun excludeRoute(route: InetNetwork) = apply { this.excludedRoutes += route }
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
fun excludeRoutes(routes: Collection<InetNetwork>) = apply { this.excludedRoutes += routes }
fun includeAddress(addr: InetNetwork) = apply { this.includedAddresses += addr }
fun includeAddresses(addresses: Collection<InetNetwork>) = apply { this.includedAddresses += addresses }
fun excludeAddress(addr: InetNetwork) = apply { this.excludedAddresses += addr }
fun excludeAddresses(addresses: Collection<InetNetwork>) = apply { this.excludedAddresses += addresses }
fun excludeApplication(application: String) = apply { this.excludedApplications += application }
fun excludeApplications(applications: Collection<String>) = apply { this.excludedApplications += applications }
@@ -91,6 +102,48 @@ open class ProtocolConfig protected constructor(
fun setMtu(mtu: Int) = apply { this.mtu = mtu }
private fun processSplitTunneling() {
if (includedAddresses.isNotEmpty() && excludedAddresses.isNotEmpty()) {
throw BadConfigException("Config contains addresses for inclusive and exclusive split tunneling at the same time")
}
if (includedAddresses.isNotEmpty()) {
// remove default routes, if any
removeRoute(InetNetwork("0.0.0.0", 0))
removeRoute(InetNetwork("::", 0))
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
// for older versions of Android, add the default route to the excluded routes
// to correctly build the excluded subnets list later
excludeRoute(InetNetwork("0.0.0.0", 0))
}
addRoutes(includedAddresses)
} else if (excludedAddresses.isNotEmpty()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// default routes are required for split tunneling in newer versions of Android
addRoute(InetNetwork("0.0.0.0", 0))
addRoute(InetNetwork("::", 0))
}
excludeRoutes(excludedAddresses)
}
}
private fun processExcludedRoutes() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
// for older versions of Android, build a list of subnets without excluded routes
// and add them to routes
val ipRangeSet = IpRangeSet()
ipRangeSet.remove(IpRange("127.0.0.0", 8))
excludedRoutes.forEach {
ipRangeSet.remove(IpRange(it))
}
// remove default routes, if any
removeRoute(InetNetwork("0.0.0.0", 0))
removeRoute(InetNetwork("::", 0))
ipRangeSet.subnets().forEach(::addRoute)
addRoute(InetNetwork("2000::", 3))
}
}
private fun validate() {
val errorMessage = StringBuilder()
@@ -103,7 +156,13 @@ open class ProtocolConfig protected constructor(
if (errorMessage.isNotEmpty()) throw BadConfigException(errorMessage.toString())
}
open fun build(): ProtocolConfig = validate().run { ProtocolConfig(this@Builder) }
protected fun configBuild() {
processSplitTunneling()
processExcludedRoutes()
validate()
}
open fun build(): ProtocolConfig = configBuild().run { ProtocolConfig(this@Builder) }
}
companion object {

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
<exclude domain="sharedpref" path="." />
</full-backup-content>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<data-extraction-rules>
<cloud-backup>
<exclude domain="sharedpref" path="." />
</cloud-backup>
<device-transfer>
<exclude domain="sharedpref" path="." />
</device-transfer>
</data-extraction-rules>

View File

@@ -5,6 +5,7 @@ import android.content.Intent
import android.content.Intent.EXTRA_MIME_TYPES
import android.content.Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
import android.content.ServiceConnection
import android.content.pm.PackageManager
import android.net.Uri
import android.net.VpnService
import android.os.Bundle
@@ -63,6 +64,7 @@ class AmneziaActivity : QtActivity() {
ServiceEvent.DISCONNECTED -> {
QtAndroidController.onVpnDisconnected()
doUnbindService()
}
ServiceEvent.RECONNECTING -> {
@@ -143,7 +145,7 @@ class AmneziaActivity : QtActivity() {
*/
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.v(TAG, "Create Amnezia activity: $intent")
Log.d(TAG, "Create Amnezia activity: $intent")
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
vpnServiceMessenger = IpcMessenger(
onDeadObjectException = ::doUnbindService,
@@ -154,7 +156,7 @@ class AmneziaActivity : QtActivity() {
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
Log.v(TAG, "onNewIntent: $intent")
Log.d(TAG, "onNewIntent: $intent")
intent?.let(::processIntent)
}
@@ -174,7 +176,7 @@ class AmneziaActivity : QtActivity() {
override fun onStart() {
super.onStart()
Log.v(TAG, "Start Amnezia activity")
Log.d(TAG, "Start Amnezia activity")
mainScope.launch {
qtInitialized.await()
doBindService()
@@ -182,13 +184,13 @@ class AmneziaActivity : QtActivity() {
}
override fun onStop() {
Log.v(TAG, "Stop Amnezia activity")
Log.d(TAG, "Stop Amnezia activity")
doUnbindService()
super.onStop()
}
override fun onDestroy() {
Log.v(TAG, "Destroy Amnezia activity")
Log.d(TAG, "Destroy Amnezia activity")
mainScope.cancel()
super.onDestroy()
}
@@ -217,7 +219,7 @@ class AmneziaActivity : QtActivity() {
CHECK_VPN_PERMISSION_ACTION_CODE -> {
when (resultCode) {
RESULT_OK -> {
Log.v(TAG, "Vpn permission granted")
Log.d(TAG, "Vpn permission granted")
Toast.makeText(this, "Vpn permission granted", Toast.LENGTH_LONG).show()
checkVpnPermissionCallbacks?.run { onSuccess() }
}
@@ -240,7 +242,7 @@ class AmneziaActivity : QtActivity() {
*/
@MainThread
private fun doBindService() {
Log.v(TAG, "Bind service")
Log.d(TAG, "Bind service")
Intent(this, AmneziaVpnService::class.java).also {
bindService(it, serviceConnection, BIND_ABOVE_CLIENT)
}
@@ -251,7 +253,7 @@ class AmneziaActivity : QtActivity() {
@MainThread
private fun doUnbindService() {
if (isInBoundState) {
Log.v(TAG, "Unbind service")
Log.d(TAG, "Unbind service")
isWaitingStatus = true
QtAndroidController.onServiceDisconnected()
vpnServiceMessenger.reset()
@@ -286,7 +288,7 @@ class AmneziaActivity : QtActivity() {
@MainThread
private fun checkVpnPermission(onSuccess: () -> Unit, onFail: () -> Unit) {
Log.v(TAG, "Check VPN permission")
Log.d(TAG, "Check VPN permission")
VpnService.prepare(applicationContext)?.let {
checkVpnPermissionCallbacks = CheckVpnPermissionCallbacks(onSuccess, onFail)
startActivityForResult(it, CHECK_VPN_PERMISSION_ACTION_CODE)
@@ -307,7 +309,7 @@ class AmneziaActivity : QtActivity() {
}
private fun connectToVpn(vpnConfig: String) {
Log.v(TAG, "Connect to VPN")
Log.d(TAG, "Connect to VPN")
vpnServiceMessenger.send {
Action.CONNECT.packToMessage {
putString(VPN_CONFIG, vpnConfig)
@@ -316,7 +318,7 @@ class AmneziaActivity : QtActivity() {
}
private fun startVpnService(vpnConfig: String) {
Log.v(TAG, "Start VPN service")
Log.d(TAG, "Start VPN service")
Intent(this, AmneziaVpnService::class.java).apply {
putExtra(VPN_CONFIG, vpnConfig)
}.also {
@@ -325,7 +327,7 @@ class AmneziaActivity : QtActivity() {
}
private fun disconnectFromVpn() {
Log.v(TAG, "Disconnect from VPN")
Log.d(TAG, "Disconnect from VPN")
vpnServiceMessenger.send(Action.DISCONNECT)
}
@@ -369,7 +371,7 @@ class AmneziaActivity : QtActivity() {
@Suppress("unused")
fun saveFile(fileName: String, data: String) {
Log.v(TAG, "Save file $fileName")
Log.d(TAG, "Save file $fileName")
mainScope.launch {
tmpFileContentToSave = data
@@ -397,7 +399,7 @@ class AmneziaActivity : QtActivity() {
Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
Log.d(TAG, "File mimyType filter: $mimeTypes")
Log.v(TAG, "File mimyType filter: $mimeTypes")
when (mimeTypes.size) {
1 -> type = mimeTypes.first()
@@ -416,14 +418,10 @@ class AmneziaActivity : QtActivity() {
@Suppress("unused")
fun setNotificationText(title: String, message: String, timerSec: Int) {
Log.v(TAG, "Set notification text")
Log.w(TAG, "Not yet implemented")
}
@Suppress("unused")
fun cleanupLogs() {
Log.v(TAG, "Cleanup logs")
Log.w(TAG, "Not yet implemented")
}
fun isCameraPresent(): Boolean = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)
@Suppress("unused")
fun startQrCodeReader() {
@@ -432,4 +430,29 @@ class AmneziaActivity : QtActivity() {
startActivity(it)
}
}
@Suppress("unused")
fun setSaveLogs(enabled: Boolean) {
Log.d(TAG, "Set save logs: $enabled")
mainScope.launch {
Log.saveLogs = enabled
vpnServiceMessenger.send {
Action.SET_SAVE_LOGS.packToMessage {
putBoolean(SAVE_LOGS, enabled)
}
}
}
}
@Suppress("unused")
fun exportLogsFile(fileName: String) {
Log.v(TAG, "Export logs file")
saveFile(fileName, Log.getLogs())
}
@Suppress("unused")
fun clearLogs() {
Log.v(TAG, "Clear logs")
Log.clearLogs()
}
}

View File

@@ -5,14 +5,20 @@ import androidx.camera.core.CameraSelector
import androidx.camera.core.CameraXConfig
import androidx.core.app.NotificationChannelCompat.Builder
import androidx.core.app.NotificationManagerCompat
import org.amnezia.vpn.util.Log
import org.amnezia.vpn.util.Prefs
import org.qtproject.qt.android.bindings.QtApplication
private const val TAG = "AmneziaApplication"
const val NOTIFICATION_CHANNEL_ID: String = "org.amnezia.vpn.notification"
class AmneziaApplication : QtApplication(), CameraXConfig.Provider {
override fun onCreate() {
super.onCreate()
Prefs.init(this)
Log.init(this)
Log.d(TAG, "Create Amnezia application")
createNotificationChannel()
}

View File

@@ -50,6 +50,7 @@ import org.amnezia.vpn.protocol.putStatistics
import org.amnezia.vpn.protocol.putStatus
import org.amnezia.vpn.protocol.wireguard.Wireguard
import org.amnezia.vpn.util.Log
import org.amnezia.vpn.util.Prefs
import org.amnezia.vpn.util.net.NetworkState
import org.json.JSONException
import org.json.JSONObject
@@ -58,6 +59,8 @@ private const val TAG = "AmneziaVpnService"
const val VPN_CONFIG = "VPN_CONFIG"
const val ERROR_MSG = "ERROR_MSG"
const val SAVE_LOGS = "SAVE_LOGS"
const val AFTER_PERMISSION_CHECK = "AFTER_PERMISSION_CHECK"
private const val PREFS_CONFIG_KEY = "LAST_CONF"
private const val NOTIFICATION_ID = 1337
@@ -118,7 +121,7 @@ class AmneziaVpnService : VpnService() {
Action.CONNECT -> {
val vpnConfig = msg.data.getString(VPN_CONFIG)
saveConfigToPrefs(vpnConfig)
Prefs.save(PREFS_CONFIG_KEY, vpnConfig)
connect(vpnConfig)
}
@@ -135,6 +138,10 @@ class AmneziaVpnService : VpnService() {
}
}
}
Action.SET_SAVE_LOGS -> {
Log.saveLogs = msg.data.getBoolean(SAVE_LOGS)
}
}
}
}
@@ -179,7 +186,7 @@ class AmneziaVpnService : VpnService() {
*/
override fun onCreate() {
super.onCreate()
Log.v(TAG, "Create Amnezia VPN service")
Log.d(TAG, "Create Amnezia VPN service")
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
connectionScope = CoroutineScope(SupervisorJob() + Dispatchers.IO + connectionExceptionHandler)
clientMessenger = IpcMessenger(messengerName = "Client")
@@ -193,15 +200,15 @@ class AmneziaVpnService : VpnService() {
else intent?.component?.packageName != packageName
if (isAlwaysOnCompat) {
Log.v(TAG, "Start service via Always-on")
connect(loadConfigFromPrefs())
Log.d(TAG, "Start service via Always-on")
connect(Prefs.load(PREFS_CONFIG_KEY))
} else if (intent?.getBooleanExtra(AFTER_PERMISSION_CHECK, false) == true) {
Log.v(TAG, "Start service after permission check")
connect(loadConfigFromPrefs())
Log.d(TAG, "Start service after permission check")
connect(Prefs.load(PREFS_CONFIG_KEY))
} else {
Log.v(TAG, "Start service")
Log.d(TAG, "Start service")
val vpnConfig = intent?.getStringExtra(VPN_CONFIG)
saveConfigToPrefs(vpnConfig)
Prefs.save(PREFS_CONFIG_KEY, vpnConfig)
connect(vpnConfig)
}
ServiceCompat.startForeground(this, NOTIFICATION_ID, notification, foregroundServiceTypeCompat)
@@ -237,7 +244,7 @@ class AmneziaVpnService : VpnService() {
}
override fun onRevoke() {
Log.v(TAG, "onRevoke")
Log.d(TAG, "onRevoke")
// Calls to onRevoke() method may not happen on the main thread of the process
mainScope.launch {
disconnect()
@@ -245,7 +252,7 @@ class AmneziaVpnService : VpnService() {
}
override fun onDestroy() {
Log.v(TAG, "Destroy service")
Log.d(TAG, "Destroy service")
runBlocking {
disconnect()
disconnectionJob?.join()
@@ -256,7 +263,7 @@ class AmneziaVpnService : VpnService() {
}
private fun stopService() {
Log.v(TAG, "Stop service")
Log.d(TAG, "Stop service")
// the coroutine below will be canceled during the onDestroy call
mainScope.launch {
delay(STOP_SERVICE_TIMEOUT)
@@ -272,7 +279,7 @@ class AmneziaVpnService : VpnService() {
private fun launchProtocolStateHandler() {
mainScope.launch {
protocolState.collect { protocolState ->
Log.d(TAG, "Protocol state: $protocolState")
Log.d(TAG, "Protocol state changed: $protocolState")
when (protocolState) {
CONNECTED -> {
clientMessenger.send(ServiceEvent.CONNECTED)
@@ -305,7 +312,7 @@ class AmneziaVpnService : VpnService() {
@MainThread
private fun launchSendingStatistics() {
if (isServiceBound && isConnected) {
/* if (isServiceBound && isConnected) {
statisticsSendingJob = mainScope.launch {
while (true) {
clientMessenger.send {
@@ -316,7 +323,7 @@ class AmneziaVpnService : VpnService() {
delay(STATISTICS_SENDING_TIMEOUT)
}
}
}
} */
}
@MainThread
@@ -328,7 +335,7 @@ class AmneziaVpnService : VpnService() {
private fun connect(vpnConfig: String?) {
if (isConnected || protocolState.value == CONNECTING) return
Log.v(TAG, "Start VPN connection")
Log.d(TAG, "Start VPN connection")
protocolState.value = CONNECTING
@@ -357,7 +364,7 @@ class AmneziaVpnService : VpnService() {
private fun disconnect() {
if (isUnknown || isDisconnected || protocolState.value == DISCONNECTING) return
Log.v(TAG, "Stop VPN connection")
Log.d(TAG, "Stop VPN connection")
protocolState.value = DISCONNECTING
@@ -383,7 +390,7 @@ class AmneziaVpnService : VpnService() {
private fun reconnect() {
if (!isConnected) return
Log.v(TAG, "Reconnect VPN")
Log.d(TAG, "Reconnect VPN")
protocolState.value = RECONNECTING
@@ -439,10 +446,4 @@ class AmneziaVpnService : VpnService() {
} else {
true
}
private fun loadConfigFromPrefs(): String? =
Prefs.get(this).getString(PREFS_CONFIG_KEY, null)
private fun saveConfigToPrefs(config: String?) =
Prefs.get(this).edit().putString(PREFS_CONFIG_KEY, config).apply()
}

View File

@@ -29,20 +29,20 @@ class ImportConfigActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.v(TAG, "Create Import Config Activity: $intent")
Log.d(TAG, "Create Import Config Activity: $intent")
intent?.let(::readConfig)
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
Log.v(TAG, "onNewIntent: $intent")
Log.d(TAG, "onNewIntent: $intent")
intent?.let(::readConfig)
}
private fun readConfig(intent: Intent) {
when (intent.action) {
ACTION_SEND -> {
Log.v(TAG, "Process SEND action, type: ${intent.type}")
Log.d(TAG, "Process SEND action, type: ${intent.type}")
when (intent.type) {
"application/octet-stream" -> {
intent.getUriCompat()?.let { uri ->
@@ -60,7 +60,7 @@ class ImportConfigActivity : ComponentActivity() {
}
ACTION_VIEW -> {
Log.v(TAG, "Process VIEW action, scheme: ${intent.scheme}")
Log.d(TAG, "Process VIEW action, scheme: ${intent.scheme}")
when (intent.scheme) {
"file", "content" -> {
intent.data?.let { uri ->
@@ -128,7 +128,7 @@ class ImportConfigActivity : ComponentActivity() {
private fun startMainActivity(config: String) {
if (config.isNotBlank()) {
Log.v(TAG, "startMainActivity")
Log.d(TAG, "startMainActivity")
Intent(applicationContext, AmneziaActivity::class.java).apply {
action = ACTION_IMPORT_CONFIG
addCategory(CATEGORY_DEFAULT)

View File

@@ -32,7 +32,8 @@ enum class Action : IpcMessage {
REGISTER_CLIENT,
CONNECT,
DISCONNECT,
REQUEST_STATUS
REQUEST_STATUS,
SET_SAVE_LOGS
}
fun <T> T.packToMessage(): Message

View File

@@ -1,25 +0,0 @@
package org.amnezia.vpn
import android.content.Context
import android.content.SharedPreferences
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
import org.amnezia.vpn.util.Log
private const val TAG = "Prefs"
private const val PREFS_FILE = "org.amnezia.vpn.prefs"
private const val SECURE_PREFS_FILE = "$PREFS_FILE.secure"
object Prefs {
fun get(context: Context, appContext: Context = context.applicationContext): SharedPreferences =
try {
EncryptedSharedPreferences(
appContext,
SECURE_PREFS_FILE,
MasterKey(appContext)
)
} catch (e: Exception) {
Log.e(TAG, "Getting Encryption Storage failed: ${e.message}, plaintext fallback")
appContext.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE)
}
}

View File

@@ -25,7 +25,7 @@ class VpnRequestActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.v(TAG, "Start request activity")
Log.d(TAG, "Start request activity")
val requestIntent = VpnService.prepare(applicationContext)
if (requestIntent != null) {
if (getSystemService<KeyguardManager>()!!.isKeyguardLocked) {

View File

@@ -15,3 +15,7 @@ android {
buildConfig = true
}
}
dependencies {
implementation(libs.androidx.security.crypto)
}

View File

@@ -1,33 +1,252 @@
package org.amnezia.vpn.util
import android.content.Context
import android.icu.text.DateFormat
import android.icu.text.SimpleDateFormat
import android.os.Build
import android.os.Process
import java.io.File
import java.io.IOException
import java.io.RandomAccessFile
import java.nio.channels.FileChannel
import java.nio.channels.FileLock
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.Date
import java.util.Locale
import java.util.concurrent.locks.ReentrantLock
import org.amnezia.vpn.util.Log.Priority.D
import org.amnezia.vpn.util.Log.Priority.E
import org.amnezia.vpn.util.Log.Priority.F
import org.amnezia.vpn.util.Log.Priority.I
import org.amnezia.vpn.util.Log.Priority.V
import org.amnezia.vpn.util.Log.Priority.W
import android.util.Log as NativeLog
class Log {
companion object {
fun v(tag: String, msg: String) = debugLog(tag, msg, NativeLog::v)
private const val TAG = "Log"
private const val LOG_FILE_NAME = "amneziaVPN.log"
private const val ROTATE_LOG_FILE_NAME = "amneziaVPN.rotate.log"
private const val LOCK_FILE_NAME = ".lock"
private const val DATE_TIME_PATTERN = "MM-dd HH:mm:ss.SSS"
private const val PREFS_SAVE_LOGS_KEY = "SAVE_LOGS"
private const val LOG_MAX_FILE_SIZE = 1024 * 1024
fun d(tag: String, msg: String) = debugLog(tag, msg, NativeLog::d)
/**
* | Priority | Save to file | Logcat logging |
* |-------------------|--------------|----------------------------------------------|
* | Verbose | Don't save | Only in Debug build |
* | Debug | Save | In Debug build or if log saving is enabled |
* | Info, Warn, Error | Save | Enabled |
* | Fatal (Assert) | Save | Enabled. Depending on system configuration, |
* | | | create a report and/or terminate the process |
*/
object Log {
private val dateTimeFormat: Any =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) DateTimeFormatter.ofPattern(DATE_TIME_PATTERN)
else object : ThreadLocal<DateFormat>() {
override fun initialValue(): DateFormat = SimpleDateFormat(DATE_TIME_PATTERN, Locale.US)
}
fun i(tag: String, msg: String) = log(tag, msg, NativeLog::i)
private lateinit var logDir: File
private val logFile: File by lazy { File(logDir, LOG_FILE_NAME) }
private val rotateLogFile: File by lazy { File(logDir, ROTATE_LOG_FILE_NAME) }
fun w(tag: String, msg: String) = log(tag, msg, NativeLog::w)
private val fileLock: FileChannel by lazy { RandomAccessFile(File(logDir, LOCK_FILE_NAME).path, "rw").channel }
private val threadLock: ReentrantLock by lazy { ReentrantLock() }
fun e(tag: String, msg: String) = log(tag, msg, NativeLog::e)
@Volatile
private var _saveLogs: Boolean = false
var saveLogs: Boolean
get() = _saveLogs
set(value) {
if (_saveLogs != value) {
if (value && !logDir.exists() && !logDir.mkdir()) {
NativeLog.e(TAG, "Failed to create dir: $logDir")
return
}
_saveLogs = value
Prefs.save(PREFS_SAVE_LOGS_KEY, value)
}
}
fun v(tag: String, msg: Any?) = v(tag, msg.toString())
@JvmStatic
fun v(tag: String, msg: String) = log(tag, msg, V)
fun d(tag: String, msg: Any?) = d(tag, msg.toString())
@JvmStatic
fun d(tag: String, msg: String) = log(tag, msg, D)
fun i(tag: String, msg: Any?) = i(tag, msg.toString())
@JvmStatic
fun i(tag: String, msg: String) = log(tag, msg, I)
fun w(tag: String, msg: Any?) = w(tag, msg.toString())
@JvmStatic
fun w(tag: String, msg: String) = log(tag, msg, W)
fun e(tag: String, msg: Any?) = e(tag, msg.toString())
@JvmStatic
fun e(tag: String, msg: String) = log(tag, msg, E)
private inline fun log(tag: String, msg: String, delegate: (String, String) -> Unit) = delegate(tag, msg)
@JvmStatic
fun f(tag: String, msg: String) = log(tag, msg, F)
private inline fun debugLog(tag: String, msg: String, delegate: (String, String) -> Unit) {
if (BuildConfig.DEBUG) delegate(tag, msg)
fun v(tag: String, msg: Any?) = v(tag, msg.toString())
fun d(tag: String, msg: Any?) = d(tag, msg.toString())
fun i(tag: String, msg: Any?) = i(tag, msg.toString())
fun w(tag: String, msg: Any?) = w(tag, msg.toString())
fun e(tag: String, msg: Any?) = e(tag, msg.toString())
fun f(tag: String, msg: Any?) = f(tag, msg.toString())
fun init(context: Context) {
v(TAG, "Init Log")
logDir = File(context.cacheDir, "logs")
saveLogs = Prefs.load(PREFS_SAVE_LOGS_KEY)
}
fun getLogs(): String =
"${deviceInfo()}\n${readLogs()}\nLOGCAT:\n${getLogcat()}"
fun clearLogs() {
withLock {
logFile.delete()
rotateLogFile.delete()
}
}
private fun log(tag: String, msg: String, priority: Priority) {
if (saveLogs && priority != V) saveLogMsg(formatLogMsg(tag, msg, priority))
if (priority == F) {
NativeLog.wtf(tag, msg)
} else if (
(priority != V && priority != D) ||
(priority == V && BuildConfig.DEBUG) ||
(priority == D && (BuildConfig.DEBUG || saveLogs))
) {
NativeLog.println(priority.level, tag, msg)
}
}
private fun saveLogMsg(msg: String) {
withTryLock(condition = { logFile.length() > LOG_MAX_FILE_SIZE }) {
logFile.renameTo(rotateLogFile)
}
try {
logFile.appendText(msg)
} catch (e: IOException) {
NativeLog.e(TAG, "Failed to write log: $e")
}
}
private fun formatLogMsg(tag: String, msg: String, priority: Priority): String {
val date = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
LocalDateTime.now().format(dateTimeFormat as DateTimeFormatter)
} else {
@Suppress("UNCHECKED_CAST")
(dateTimeFormat as ThreadLocal<DateFormat>).get()?.format(Date())
}
return "$date ${Process.myPid()} ${Process.myTid()} $priority [${Thread.currentThread().name}] " +
"$tag: $msg\n"
}
private fun deviceInfo(): String {
val sb = StringBuilder()
sb.append("Model: ").appendLine(Build.MODEL)
sb.append("Brand: ").appendLine(Build.BRAND)
sb.append("Product: ").appendLine(Build.PRODUCT)
sb.append("Device: ").appendLine(Build.DEVICE)
sb.append("Codename: ").appendLine(Build.VERSION.CODENAME)
sb.append("Release: ").appendLine(Build.VERSION.RELEASE)
sb.append("SDK: ").appendLine(Build.VERSION.SDK_INT)
sb.append("ABI: ").appendLine(Build.SUPPORTED_ABIS.joinToString())
return sb.toString()
}
private fun readLogs(): String {
var logText = ""
withLock {
try {
if (rotateLogFile.exists()) logText = rotateLogFile.readText()
if (logFile.exists()) logText += logFile.readText()
} catch (e: IOException) {
val errorMsg = "Failed to read log: $e"
NativeLog.e(TAG, errorMsg)
logText += errorMsg
}
}
return logText
}
private fun getLogcat(): String {
try {
val process = ProcessBuilder("logcat", "-d").redirectErrorStream(true).start()
return process.inputStream.reader().readText()
} catch (e: IOException) {
val errorMsg = "Failed to get logcat log: $e"
NativeLog.e(TAG, errorMsg)
return errorMsg
}
}
private fun withLock(block: () -> Unit) {
threadLock.lock()
try {
var l: FileLock? = null
try {
l = fileLock.lock()
block()
} catch (e: IOException) {
NativeLog.e(TAG, "Failed to get file lock: $e")
} finally {
try {
l?.release()
} catch (e: IOException) {
NativeLog.e(TAG, "Failed to release file lock: $e")
}
}
} finally {
threadLock.unlock()
}
}
private fun withTryLock(condition: () -> Boolean, block: () -> Unit) {
if (condition()) {
if (threadLock.tryLock()) {
try {
if (condition()) {
var l: FileLock? = null
try {
l = fileLock.tryLock()
if (l != null) {
if (condition()) {
block()
}
}
} catch (e: IOException) {
NativeLog.e(TAG, "Failed to get file tryLock: $e")
} finally {
try {
l?.release()
} catch (e: IOException) {
NativeLog.e(TAG, "Failed to release file tryLock: $e")
}
}
}
} finally {
threadLock.unlock()
}
}
}
}
private enum class Priority(val level: Int) {
V(2),
D(3),
I(4),
W(5),
E(6),
F(7)
}
}

View File

@@ -0,0 +1,58 @@
package org.amnezia.vpn.util
import android.app.Application
import android.content.Context
import android.content.SharedPreferences
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
import kotlin.reflect.typeOf
private const val TAG = "Prefs"
private const val PREFS_FILE = "org.amnezia.vpn.prefs"
private const val SECURE_PREFS_FILE = "$PREFS_FILE.secure"
object Prefs {
private lateinit var app: Application
val prefs: SharedPreferences
get() = try {
EncryptedSharedPreferences(
app,
SECURE_PREFS_FILE,
MasterKey(app)
)
} catch (e: Exception) {
Log.e(TAG, "Getting Encryption Storage failed: $e, plaintext fallback")
app.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE)
}
fun init(app: Application) {
Log.v(TAG, "Init Prefs")
this.app = app
}
fun save(key: String, value: Boolean) =
prefs.edit().putBoolean(key, value).apply()
fun save(key: String, value: String?) =
prefs.edit().putString(key, value).apply()
fun save(key: String, value: Int) =
prefs.edit().putInt(key, value).apply()
fun save(key: String, value: Long) =
prefs.edit().putLong(key, value).apply()
fun save(key: String, value: Float) =
prefs.edit().putFloat(key, value).apply()
inline fun <reified T> load(key: String): T {
return when (typeOf<T>()) {
typeOf<Boolean>() -> prefs.getBoolean(key, false)
typeOf<String>() -> prefs.getString(key, "")
typeOf<Int>() -> prefs.getInt(key, 0)
typeOf<Long>() -> prefs.getLong(key, 0L)
typeOf<Float>() -> prefs.getFloat(key, 0f)
else -> throw IllegalArgumentException("SharedPreferences does not support type: ${typeOf<T>()}")
} as T
}
}

View File

@@ -82,7 +82,7 @@ class NetworkState(
fun bindNetworkListener() {
if (isListenerBound) return
Log.v(TAG, "Bind network listener")
Log.d(TAG, "Bind network listener")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
connectivityManager.registerBestMatchingNetworkCallback(networkRequest, networkCallback, handler)
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@@ -95,7 +95,7 @@ class NetworkState(
fun unbindNetworkListener() {
if (!isListenerBound) return
Log.v(TAG, "Unbind network listener")
Log.d(TAG, "Unbind network listener")
connectivityManager.unregisterNetworkCallback(networkCallback)
isListenerBound = false
currentNetwork = null

View File

@@ -99,7 +99,10 @@ open class Wireguard : Protocol() {
}
protected fun WireguardConfig.Builder.configWireguard(configData: Map<String, String>) {
configData["Address"]?.let { addAddress(InetNetwork.parse(it)) }
configData["Address"]?.split(",")?.map { address ->
InetNetwork.parse(address.trim())
}?.forEach(::addAddress)
configData["DNS"]?.split(",")?.map { dns ->
parseInetAddress(dns.trim())
}?.forEach(::addDnsServer)

View File

@@ -11,7 +11,7 @@ open class WireguardConfig protected constructor(
val endpoint: InetEndpoint,
val persistentKeepalive: Int,
val publicKeyHex: String,
val preSharedKeyHex: String,
val preSharedKeyHex: String?,
val privateKeyHex: String
) : ProtocolConfig(protocolConfigBuilder) {
@@ -43,7 +43,8 @@ open class WireguardConfig protected constructor(
appendLine("endpoint=$endpoint")
if (persistentKeepalive != 0)
appendLine("persistent_keepalive_interval=$persistentKeepalive")
appendLine("preshared_key=$preSharedKeyHex")
if (preSharedKeyHex != null)
appendLine("preshared_key=$preSharedKeyHex")
}
open class Builder : ProtocolConfig.Builder(true) {
@@ -56,7 +57,7 @@ open class WireguardConfig protected constructor(
internal lateinit var publicKeyHex: String
private set
internal lateinit var preSharedKeyHex: String
internal var preSharedKeyHex: String? = null
private set
internal lateinit var privateKeyHex: String
@@ -74,7 +75,7 @@ open class WireguardConfig protected constructor(
fun setPrivateKeyHex(privateKeyHex: String) = apply { this.privateKeyHex = privateKeyHex }
override fun build(): WireguardConfig = WireguardConfig(this)
override fun build(): WireguardConfig = configBuild().run { WireguardConfig(this@Builder) }
}
companion object {

View File

@@ -97,7 +97,7 @@ target_compile_options(${PROJECT} PRIVATE
-DVPN_NE_BUNDLEID=\"${BUILD_IOS_APP_IDENTIFIER}.network-extension\"
)
set(WG_APPLE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rd/awg-apple/Sources)
set(WG_APPLE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rd/amneziawg-apple/Sources)
target_sources(${PROJECT} PRIVATE
# ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosvpnprotocol.swift

View File

@@ -118,31 +118,33 @@ QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentia
return QJsonDocument(jConfig).toJson();
}
QString OpenVpnConfigurator::processConfigWithLocalSettings(QString jsonConfig)
QString OpenVpnConfigurator::processConfigWithLocalSettings(QString jsonConfig, const int serverIndex)
{
QJsonObject json = QJsonDocument::fromJson(jsonConfig.toUtf8()).object();
QString config = json[config_key::config].toString();
QRegularExpression regex("redirect-gateway.*");
config.replace(regex, "");
if (!m_settings->server(serverIndex).value(config_key::configVersion).toInt()) {
QRegularExpression regex("redirect-gateway.*");
config.replace(regex, "");
if (m_settings->routeMode() == Settings::VpnAllSites) {
config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n");
// Prevent ipv6 leak
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
config.append("block-ipv6\n");
}
if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
if (m_settings->routeMode() == Settings::VpnAllSites) {
config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n");
// Prevent ipv6 leak
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
config.append("block-ipv6\n");
}
if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
// no redirect-gateway
}
if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
#ifndef Q_OS_ANDROID
config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n");
#endif
// Prevent ipv6 leak
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
config.append("block-ipv6\n");
// no redirect-gateway
}
if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
#ifndef Q_OS_ANDROID
config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n");
#endif
// Prevent ipv6 leak
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
config.append("block-ipv6\n");
}
}
#ifndef MZ_WINDOWS

View File

@@ -26,7 +26,7 @@ public:
QString genOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode = nullptr);
QString processConfigWithLocalSettings(QString jsonConfig);
QString processConfigWithLocalSettings(QString jsonConfig, const int serverIndex);
QString processConfigWithExportSettings(QString jsonConfig);
ErrorCode signCert(DockerContainer container,

View File

@@ -92,7 +92,7 @@ QString &VpnConfigurator::processConfigWithLocalSettings(int serverIndex, Docker
processConfigWithDnsSettings(serverIndex, container, proto, config);
if (proto == Proto::OpenVpn) {
config = openVpnConfigurator->processConfigWithLocalSettings(config);
config = openVpnConfigurator->processConfigWithLocalSettings(config, serverIndex);
}
return config;
}

View File

@@ -98,11 +98,11 @@ QMap<DockerContainer, QString> ContainerProps::containerDescriptions()
QObject::tr("OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its "
"own security protocol with SSL/TLS for key exchange.") },
{ DockerContainer::ShadowSocks,
QObject::tr("ShadowSocks - masks VPN traffic, making it similar to normal web traffic, but is "
"recognised by analysis systems in some highly censored regions.") },
QObject::tr("ShadowSocks - masks VPN traffic, making it similar to normal web traffic, but it "
"may be recognized by analysis systems in some highly censored regions.") },
{ DockerContainer::Cloak,
QObject::tr("OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against "
"active-probbing detection. Ideal for bypassing blocking in regions with the highest levels "
"active-probing detection. Ideal for bypassing blocking in regions with the highest levels "
"of censorship.") },
{ DockerContainer::WireGuard,
QObject::tr("WireGuard - New popular VPN protocol with high performance, high speed and low power "
@@ -119,7 +119,7 @@ QMap<DockerContainer, QString> ContainerProps::containerDescriptions()
{ DockerContainer::Dns,
QObject::tr("Replace the current DNS server with your own. This will increase your privacy level.") },
{ DockerContainer::Sftp,
QObject::tr("Creates a file vault on your server to securely store and transfer files.") } };
QObject::tr("Create a file vault on your server to securely store and transfer files.") } };
}
QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
@@ -153,8 +153,8 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
"* Works over TCP network protocol.") },
{ DockerContainer::Cloak,
QObject::tr("This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for "
"blocking protection.\n\n"
"OpenVPN provides a secure VPN connection by encrypting all Internet traffic between the client "
"protecting against blocking.\n\n"
"OpenVPN provides a secure VPN connection by encrypting all internet traffic between the client "
"and the server.\n\n"
"Cloak protects OpenVPN from detection and blocking. \n\n"
"Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, "
@@ -172,7 +172,7 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
"* Works over TCP network protocol, 443 port.\n") },
{ DockerContainer::WireGuard,
QObject::tr("A relatively new popular VPN protocol with a simplified architecture.\n"
"Provides stable VPN connection, high performance on all devices. Uses hard-coded encryption "
"WireGuard provides stable VPN connection and high performance on all devices. It uses hard-coded encryption "
"settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput.\n"
"WireGuard is very susceptible to blocking due to its distinct packet signatures. "
"Unlike some other VPN protocols that employ obfuscation techniques, "

View File

@@ -4,77 +4,92 @@
#include <QMetaEnum>
#include <QObject>
namespace amnezia {
constexpr const qint16 qrMagicCode = 1984;
struct ServerCredentials
namespace amnezia
{
QString hostName;
QString userName;
QString secretData;
int port = 22;
bool isValid() const { return !hostName.isEmpty() && !userName.isEmpty() && !secretData.isEmpty() && port > 0; }
};
constexpr const qint16 qrMagicCode = 1984;
enum ErrorCode
{
// General error codes
NoError = 0,
UnknownError,
InternalError,
NotImplementedError,
struct ServerCredentials
{
QString hostName;
QString userName;
QString secretData;
int port = 22;
// Server errors
ServerCheckFailed,
ServerPortAlreadyAllocatedError,
ServerContainerMissingError,
ServerDockerFailedError,
ServerCancelInstallation,
ServerUserNotInSudo,
ServerPacketManagerError,
bool isValid() const
{
return !hostName.isEmpty() && !userName.isEmpty() && !secretData.isEmpty() && port > 0;
}
};
// Ssh connection errors
SshRequestDeniedError, SshInterruptedError, SshInternalError,
SshPrivateKeyError, SshPrivateKeyFormatError, SshTimeoutError,
enum ErrorCode {
// General error codes
NoError = 0,
UnknownError = 100,
InternalError = 101,
NotImplementedError = 102,
// Ssh sftp errors
SshSftpEofError, SshSftpNoSuchFileError, SshSftpPermissionDeniedError,
SshSftpFailureError, SshSftpBadMessageError, SshSftpNoConnectionError,
SshSftpConnectionLostError, SshSftpOpUnsupportedError, SshSftpInvalidHandleError,
SshSftpNoSuchPathError, SshSftpFileAlreadyExistsError, SshSftpWriteProtectError,
SshSftpNoMediaError,
// Server errors
ServerCheckFailed = 200,
ServerPortAlreadyAllocatedError = 201,
ServerContainerMissingError = 202,
ServerDockerFailedError = 203,
ServerCancelInstallation = 204,
ServerUserNotInSudo = 205,
ServerPacketManagerError = 206,
// Local errors
OpenVpnConfigMissing,
OpenVpnManagementServerError,
ConfigMissing,
// Ssh connection errors
SshRequestDeniedError = 300,
SshInterruptedError = 301,
SshInternalError = 302,
SshPrivateKeyError = 303,
SshPrivateKeyFormatError = 304,
SshTimeoutError = 305,
// Distro errors
OpenVpnExecutableMissing,
ShadowSocksExecutableMissing,
CloakExecutableMissing,
AmneziaServiceConnectionFailed,
ExecutableMissing,
// Ssh sftp errors
SshSftpEofError = 400,
SshSftpNoSuchFileError = 401,
SshSftpPermissionDeniedError = 402,
SshSftpFailureError = 403,
SshSftpBadMessageError = 404,
SshSftpNoConnectionError = 405,
SshSftpConnectionLostError = 406,
SshSftpOpUnsupportedError = 407,
SshSftpInvalidHandleError = 408,
SshSftpNoSuchPathError = 409,
SshSftpFileAlreadyExistsError = 410,
SshSftpWriteProtectError = 411,
SshSftpNoMediaError = 412,
// VPN errors
OpenVpnAdaptersInUseError,
OpenVpnUnknownError,
OpenVpnTapAdapterError,
AddressPoolError,
// Local errors
OpenVpnConfigMissing = 500,
OpenVpnManagementServerError = 501,
ConfigMissing = 502,
// 3rd party utils errors
OpenSslFailed,
ShadowSocksExecutableCrashed,
CloakExecutableCrashed,
// Distro errors
OpenVpnExecutableMissing = 600,
ShadowSocksExecutableMissing = 601,
CloakExecutableMissing = 602,
AmneziaServiceConnectionFailed = 603,
ExecutableMissing = 604,
// import and install errors
ImportInvalidConfigError,
// VPN errors
OpenVpnAdaptersInUseError = 700,
OpenVpnUnknownError = 701,
OpenVpnTapAdapterError = 702,
AddressPoolError = 703,
// Android errors
AndroidError
};
// 3rd party utils errors
OpenSslFailed = 800,
ShadowSocksExecutableCrashed = 801,
CloakExecutableCrashed = 802,
// import and install errors
ImportInvalidConfigError = 900,
// Android errors
AndroidError = 1000
};
} // namespace amnezia

View File

@@ -2,70 +2,74 @@
using namespace amnezia;
QString errorString(ErrorCode code){
QString errorString(ErrorCode code) {
QString errorMessage;
switch (code) {
// General error codes
case(NoError): return QObject::tr("No error");
case(UnknownError): return QObject::tr("Unknown Error");
case(NotImplementedError): return QObject::tr("Function not implemented");
case(NoError): errorMessage = QObject::tr("No error"); break;
case(UnknownError): errorMessage = QObject::tr("Unknown Error"); break;
case(NotImplementedError): errorMessage = QObject::tr("Function not implemented"); break;
// Server errors
case(ServerCheckFailed): return QObject::tr("Server check failed");
case(ServerPortAlreadyAllocatedError): return QObject::tr("Server port already used. Check for another software");
case(ServerContainerMissingError): return QObject::tr("Server error: Docker container missing");
case(ServerDockerFailedError): return QObject::tr("Server error: Docker failed");
case(ServerCancelInstallation): return QObject::tr("Installation canceled by user");
case(ServerUserNotInSudo): return QObject::tr("The user does not have permission to use sudo");
case(ServerCheckFailed): errorMessage = QObject::tr("Server check failed"); break;
case(ServerPortAlreadyAllocatedError): errorMessage = QObject::tr("Server port already used. Check for another software"); break;
case(ServerContainerMissingError): errorMessage = QObject::tr("Server error: Docker container missing"); break;
case(ServerDockerFailedError): errorMessage = QObject::tr("Server error: Docker failed"); break;
case(ServerCancelInstallation): errorMessage = QObject::tr("Installation canceled by user"); break;
case(ServerUserNotInSudo): errorMessage = QObject::tr("The user does not have permission to use sudo"); break;
// Libssh errors
case(SshRequestDeniedError): return QObject::tr("Ssh request was denied");
case(SshInterruptedError): return QObject::tr("Ssh request was interrupted");
case(SshInternalError): return QObject::tr("Ssh internal error");
case(SshPrivateKeyError): return QObject::tr("Invalid private key or invalid passphrase entered");
case(SshPrivateKeyFormatError): return QObject::tr("The selected private key format is not supported, use openssh ED25519 key types or PEM key types");
case(SshTimeoutError): return QObject::tr("Timeout connecting to server");
case(SshRequestDeniedError): errorMessage = QObject::tr("Ssh request was denied"); break;
case(SshInterruptedError): errorMessage = QObject::tr("Ssh request was interrupted"); break;
case(SshInternalError): errorMessage = QObject::tr("Ssh internal error"); break;
case(SshPrivateKeyError): errorMessage = QObject::tr("Invalid private key or invalid passphrase entered"); break;
case(SshPrivateKeyFormatError): errorMessage = QObject::tr("The selected private key format is not supported, use openssh ED25519 key types or PEM key types"); break;
case(SshTimeoutError): errorMessage = QObject::tr("Timeout connecting to server"); break;
// Libssh sftp errors
case(SshSftpEofError): return QObject::tr("Sftp error: End-of-file encountered");
case(SshSftpNoSuchFileError): return QObject::tr("Sftp error: File does not exist");
case(SshSftpPermissionDeniedError): return QObject::tr("Sftp error: Permission denied");
case(SshSftpFailureError): return QObject::tr("Sftp error: Generic failure");
case(SshSftpBadMessageError): return QObject::tr("Sftp error: Garbage received from server");
case(SshSftpNoConnectionError): return QObject::tr("Sftp error: No connection has been set up");
case(SshSftpConnectionLostError): return QObject::tr("Sftp error: There was a connection, but we lost it");
case(SshSftpOpUnsupportedError): return QObject::tr("Sftp error: Operation not supported by libssh yet");
case(SshSftpInvalidHandleError): return QObject::tr("Sftp error: Invalid file handle");
case(SshSftpNoSuchPathError): return QObject::tr("Sftp error: No such file or directory path exists");
case(SshSftpFileAlreadyExistsError): return QObject::tr("Sftp error: An attempt to create an already existing file or directory has been made");
case(SshSftpWriteProtectError): return QObject::tr("Sftp error: Write-protected filesystem");
case(SshSftpNoMediaError): return QObject::tr("Sftp error: No media was in remote drive");
case(SshSftpEofError): errorMessage = QObject::tr("Sftp error: End-of-file encountered"); break;
case(SshSftpNoSuchFileError): errorMessage = QObject::tr("Sftp error: File does not exist"); break;
case(SshSftpPermissionDeniedError): errorMessage = QObject::tr("Sftp error: Permission denied"); break;
case(SshSftpFailureError): errorMessage = QObject::tr("Sftp error: Generic failure"); break;
case(SshSftpBadMessageError): errorMessage = QObject::tr("Sftp error: Garbage received from server"); break;
case(SshSftpNoConnectionError): errorMessage = QObject::tr("Sftp error: No connection has been set up"); break;
case(SshSftpConnectionLostError): errorMessage = QObject::tr("Sftp error: There was a connection, but we lost it"); break;
case(SshSftpOpUnsupportedError): errorMessage = QObject::tr("Sftp error: Operation not supported by libssh yet"); break;
case(SshSftpInvalidHandleError): errorMessage = QObject::tr("Sftp error: Invalid file handle"); break;
case(SshSftpNoSuchPathError): errorMessage = QObject::tr("Sftp error: No such file or directory path exists"); break;
case(SshSftpFileAlreadyExistsError): errorMessage = QObject::tr("Sftp error: An attempt to create an already existing file or directory has been made"); break;
case(SshSftpWriteProtectError): errorMessage = QObject::tr("Sftp error: Write-protected filesystem"); break;
case(SshSftpNoMediaError): errorMessage = QObject::tr("Sftp error: No media was in remote drive"); break;
// Local errors
case (OpenVpnConfigMissing): return QObject::tr("OpenVPN config missing");
case (OpenVpnManagementServerError): return QObject::tr("OpenVPN management server error");
case (OpenVpnConfigMissing): errorMessage = QObject::tr("OpenVPN config missing"); break;
case (OpenVpnManagementServerError): errorMessage = QObject::tr("OpenVPN management server error"); break;
// Distro errors
case (OpenVpnExecutableMissing): return QObject::tr("OpenVPN executable missing");
case (ShadowSocksExecutableMissing): return QObject::tr("ShadowSocks (ss-local) executable missing");
case (CloakExecutableMissing): return QObject::tr("Cloak (ck-client) executable missing");
case (AmneziaServiceConnectionFailed): return QObject::tr("Amnezia helper service error");
case (OpenSslFailed): return QObject::tr("OpenSSL failed");
case (OpenVpnExecutableMissing): errorMessage = QObject::tr("OpenVPN executable missing"); break;
case (ShadowSocksExecutableMissing): errorMessage = QObject::tr("ShadowSocks (ss-local) executable missing"); break;
case (CloakExecutableMissing): errorMessage = QObject::tr("Cloak (ck-client) executable missing"); break;
case (AmneziaServiceConnectionFailed): errorMessage = QObject::tr("Amnezia helper service error"); break;
case (OpenSslFailed): errorMessage = QObject::tr("OpenSSL failed"); break;
// VPN errors
case (OpenVpnAdaptersInUseError): return QObject::tr("Can't connect: another VPN connection is active");
case (OpenVpnTapAdapterError): return QObject::tr("Can't setup OpenVPN TAP network adapter");
case (AddressPoolError): return QObject::tr("VPN pool error: no available addresses");
case (OpenVpnAdaptersInUseError): errorMessage = QObject::tr("Can't connect: another VPN connection is active"); break;
case (OpenVpnTapAdapterError): errorMessage = QObject::tr("Can't setup OpenVPN TAP network adapter"); break;
case (AddressPoolError): errorMessage = QObject::tr("VPN pool error: no available addresses"); break;
case (ImportInvalidConfigError): return QObject::tr("The config does not contain any containers and credentials for connecting to the server");
case (ImportInvalidConfigError): errorMessage = QObject::tr("The config does not contain any containers and credentials for connecting to the server"); break;
// Android errors
case (AndroidError): return QObject::tr("VPN connection error");
case (AndroidError): errorMessage = QObject::tr("VPN connection error"); break;
case(InternalError):
default:
return QObject::tr("Internal error");
errorMessage = QObject::tr("Internal error"); break;
}
return QObject::tr("ErrorCode: %1. ").arg(code) + errorMessage;
}
QDebug operator<<(QDebug debug, const ErrorCode &e)

View File

@@ -58,7 +58,7 @@ target_link_libraries(networkextension PRIVATE ${FW_UI_KIT})
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/awg-apple/Sources)
set(WG_APPLE_SOURCE_DIR ${CLIENT_ROOT_DIR}/3rd/amneziawg-apple/Sources)
target_sources(networkextension PRIVATE
${WG_APPLE_SOURCE_DIR}/WireGuardKit/WireGuardAdapter.swift

View File

@@ -1,6 +1,6 @@
#include "wireguard-go-version.h"
#include "3rd/awg-apple/Sources/WireGuardKitGo/wireguard.h"
#include "3rd/awg-apple/Sources/WireGuardKitC/WireGuardKitC.h"
#include "3rd/amneziawg-apple/Sources/WireGuardKitGo/wireguard.h"
#include "3rd/amneziawg-apple/Sources/WireGuardKitC/WireGuardKitC.h"
#include <stdbool.h>
#include <stdint.h>

View File

@@ -29,7 +29,7 @@ void debugMessageHandler(QtMsgType type, const QMessageLogContext& context, cons
}
// Skip annoying messages from Qt
if (msg.startsWith("Unknown property") || msg.startsWith("Could not create pixmap") || msg.startsWith("Populating font")) {
if (msg.startsWith("Unknown property") || msg.startsWith("Could not create pixmap") || msg.startsWith("Populating font") || msg.startsWith("stale focus object")) {
return;
}

View File

@@ -12,13 +12,15 @@ namespace
AndroidController *s_instance = nullptr;
constexpr auto QT_ANDROID_CONTROLLER_CLASS = "org/amnezia/vpn/qt/QtAndroidController";
constexpr auto ANDROID_LOG_CLASS = "org/amnezia/vpn/util/Log";
constexpr auto TAG = "AmneziaQt";
} // namespace
AndroidController::AndroidController() : QObject()
{
connect(this, &AndroidController::status, this,
[this](AndroidController::ConnectionState state) {
qDebug() << "Android event: status; state:" << textConnectionState(state);
qDebug() << "Android event: status =" << textConnectionState(state);
if (isWaitingStatus) {
qDebug() << "Initialization by service status";
isWaitingStatus = false;
@@ -126,24 +128,19 @@ bool AndroidController::initialize()
// static
template <typename Ret, typename ...Args>
auto AndroidController::callActivityMethod(const char *methodName, const char *signature,
const std::function<Ret()> &defValue, Args &&...args)
auto AndroidController::callActivityMethod(const char *methodName, const char *signature, Args &&...args)
{
qDebug() << "Call activity method:" << methodName;
QJniObject activity = AndroidUtils::getActivity();
if (activity.isValid()) {
return activity.callMethod<Ret>(methodName, signature, std::forward<Args>(args)...);
} else {
qCritical() << "Activity is not valid";
return defValue();
}
Q_ASSERT(activity.isValid());
return activity.callMethod<Ret>(methodName, signature, std::forward<Args>(args)...);
}
// static
template <typename ...Args>
void AndroidController::callActivityMethod(const char *methodName, const char *signature, Args &&...args)
{
callActivityMethod<void>(methodName, signature, [] {}, std::forward<Args>(args)...);
callActivityMethod<void>(methodName, signature, std::forward<Args>(args)...);
}
ErrorCode AndroidController::start(const QJsonObject &vpnConfig)
@@ -194,11 +191,114 @@ void AndroidController::setNotificationText(const QString &title, const QString
(jint) timerSec);
}
bool AndroidController::isCameraPresent()
{
return callActivityMethod<jboolean>("isCameraPresent", "()Z");
}
void AndroidController::startQrReaderActivity()
{
callActivityMethod("startQrCodeReader", "()V");
}
void AndroidController::setSaveLogs(bool enabled)
{
callActivityMethod("setSaveLogs", "(Z)V", enabled);
}
void AndroidController::exportLogsFile(const QString &fileName)
{
callActivityMethod("exportLogsFile", "(Ljava/lang/String;)V",
QJniObject::fromString(fileName).object<jstring>());
}
void AndroidController::clearLogs()
{
callActivityMethod("clearLogs", "()V");
}
// Moving log processing to the Android side
jclass AndroidController::log;
jmethodID AndroidController::logDebug;
jmethodID AndroidController::logInfo;
jmethodID AndroidController::logWarning;
jmethodID AndroidController::logError;
jmethodID AndroidController::logFatal;
// static
bool AndroidController::initLogging()
{
QJniEnvironment env;
log = env.findClass(ANDROID_LOG_CLASS);
if (log == nullptr) {
qCritical() << "Android log class" << ANDROID_LOG_CLASS << "not found";
return false;
}
auto logMethodSignature = "(Ljava/lang/String;Ljava/lang/String;)V";
logDebug = env.findStaticMethod(log, "d", logMethodSignature);
if (logDebug == nullptr) {
qCritical() << "Android debug log method not found";
return false;
}
logInfo = env.findStaticMethod(log, "i", logMethodSignature);
if (logInfo == nullptr) {
qCritical() << "Android info log method not found";
return false;
}
logWarning = env.findStaticMethod(log, "w", logMethodSignature);
if (logWarning == nullptr) {
qCritical() << "Android warning log method not found";
return false;
}
logError = env.findStaticMethod(log, "e", logMethodSignature);
if (logError == nullptr) {
qCritical() << "Android error log method not found";
return false;
}
logFatal = env.findStaticMethod(log, "f", logMethodSignature);
if (logFatal == nullptr) {
qCritical() << "Android fatal log method not found";
return false;
}
qInstallMessageHandler(messageHandler);
return true;
}
// static
void AndroidController::messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message)
{
jmethodID logMethod = logDebug;
switch (type) {
case QtDebugMsg:
logMethod = logDebug;
break;
case QtInfoMsg:
logMethod = logInfo;
break;
case QtWarningMsg:
logMethod = logWarning;
break;
case QtCriticalMsg:
logMethod = logError;
break;
case QtFatalMsg:
logMethod = logFatal;
break;
}
QString formattedMessage = qFormatLogMessage(type, context, message);
QJniObject::callStaticMethod<void>(log, logMethod,
QJniObject::fromString(TAG).object<jstring>(),
QJniObject::fromString(formattedMessage).object<jstring>());
}
void AndroidController::qtAndroidControllerInitialized()
{
callActivityMethod("qtAndroidControllerInitialized", "()V");

View File

@@ -33,7 +33,14 @@ public:
void setNotificationText(const QString &title, const QString &message, int timerSec);
void saveFile(const QString &fileName, const QString &data);
QString openFile(const QString &filter);
bool isCameraPresent();
void startQrReaderActivity();
void setSaveLogs(bool enabled);
void exportLogsFile(const QString &fileName);
void clearLogs();
static bool initLogging();
static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message);
signals:
void connectionStateChanged(Vpn::ConnectionState state);
@@ -53,6 +60,13 @@ signals:
private:
bool isWaitingStatus = true;
static jclass log;
static jmethodID logDebug;
static jmethodID logInfo;
static jmethodID logWarning;
static jmethodID logError;
static jmethodID logFatal;
void qtAndroidControllerInitialized();
static Vpn::ConnectionState convertState(ConnectionState state);
@@ -72,8 +86,7 @@ private:
static bool decodeQrCode(JNIEnv *env, jobject thiz, jstring data);
template <typename Ret, typename ...Args>
static auto callActivityMethod(const char *methodName, const char *signature,
const std::function<Ret()> &defValue, Args &&...args);
static auto callActivityMethod(const char *methodName, const char *signature, Args &&...args);
template <typename ...Args>
static void callActivityMethod(const char *methodName, const char *signature, Args &&...args);
};

View File

@@ -43,6 +43,7 @@ bool MobileUtils::shareText(const QStringList& filesToSend) {
UIPopoverPresentationController *popController = activityController.popoverPresentationController;
if (popController) {
popController.sourceView = qtController.view;
popController.sourceRect = CGRectMake(100, 100, 100, 100);
}
QEventLoop wait;

View File

@@ -2,7 +2,7 @@
* 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 "3rd/awg-apple/Sources/WireGuardKitC/WireGuardKitC.h"
#include "3rd/amneziawg-apple/Sources/WireGuardKitC/WireGuardKitC.h"
#include <stdbool.h>
#include <stdint.h>

View File

@@ -249,8 +249,107 @@ void IosController::vpnStatusDidChange(void *pNotification)
NETunnelProviderSession *session = (NETunnelProviderSession *)pNotification;
if (session /* && session == TunnelManager.session */ ) {
qDebug() << "IosController::vpnStatusDidChange" << iosStatusToState(session.status) << session;
emit connectionStateChanged(iosStatusToState(session.status));
qDebug() << "IosController::vpnStatusDidChange" << iosStatusToState(session.status) << session;
if (session.status == NEVPNStatusDisconnected) {
if (@available(iOS 16.0, *)) {
[session fetchLastDisconnectErrorWithCompletionHandler:^(NSError * _Nullable error) {
if (error != nil) {
qDebug() << "Disconnect error" << error.domain << error.code << error.localizedDescription;
if ([error.domain isEqualToString:NEVPNConnectionErrorDomain]) {
switch (error.code) {
case NEVPNConnectionErrorOverslept:
qDebug() << "Disconnect error info" << "The VPN connection was terminated because the system slept for an extended period of time.";
break;
case NEVPNConnectionErrorNoNetworkAvailable:
qDebug() << "Disconnect error info" << "The VPN connection could not be established because the system is not connected to a network.";
break;
case NEVPNConnectionErrorUnrecoverableNetworkChange:
qDebug() << "Disconnect error info" << "The VPN connection was terminated because the network conditions changed in such a way that the VPN connection could not be maintained.";
break;
case NEVPNConnectionErrorConfigurationFailed:
qDebug() << "Disconnect error info" << "The VPN connection could not be established because the configuration is invalid. ";
break;
case NEVPNConnectionErrorServerAddressResolutionFailed:
qDebug() << "Disconnect error info" << "The address of the VPN server could not be determined.";
break;
case NEVPNConnectionErrorServerNotResponding:
qDebug() << "Disconnect error info" << "Network communication with the VPN server has failed.";
break;
case NEVPNConnectionErrorServerDead:
qDebug() << "Disconnect error info" << "The VPN server is no longer functioning.";
break;
case NEVPNConnectionErrorAuthenticationFailed:
qDebug() << "Disconnect error info" << "The user credentials were rejected by the VPN server.";
break;
case NEVPNConnectionErrorClientCertificateInvalid:
qDebug() << "Disconnect error info" << "The client certificate is invalid.";
break;
case NEVPNConnectionErrorClientCertificateNotYetValid:
qDebug() << "Disconnect error info" << "The client certificate will not be valid until some future point in time.";
break;
case NEVPNConnectionErrorClientCertificateExpired:
qDebug() << "Disconnect error info" << "The validity period of the client certificate has passed.";
break;
case NEVPNConnectionErrorPluginFailed:
qDebug() << "Disconnect error info" << "The VPN plugin died unexpectedly.";
break;
case NEVPNConnectionErrorConfigurationNotFound:
qDebug() << "Disconnect error info" << "The VPN configuration could not be found.";
break;
case NEVPNConnectionErrorPluginDisabled:
qDebug() << "Disconnect error info" << "The VPN plugin could not be found or needed to be updated.";
break;
case NEVPNConnectionErrorNegotiationFailed:
qDebug() << "Disconnect error info" << "The VPN protocol negotiation failed.";
break;
case NEVPNConnectionErrorServerDisconnected:
qDebug() << "Disconnect error info" << "The VPN server terminated the connection.";
break;
case NEVPNConnectionErrorServerCertificateInvalid:
qDebug() << "Disconnect error info" << "The server certificate is invalid.";
break;
case NEVPNConnectionErrorServerCertificateNotYetValid:
qDebug() << "Disconnect error info" << "The server certificate will not be valid until some future point in time.";
break;
case NEVPNConnectionErrorServerCertificateExpired:
qDebug() << "Disconnect error info" << "The validity period of the server certificate has passed.";
break;
default:
qDebug() << "Disconnect error info" << "Unknown code.";
break;
}
}
NSError *underlyingError = error.userInfo[@"NSUnderlyingError"];
if (underlyingError != nil) {
qDebug() << "Disconnect underlying error" << underlyingError.domain << underlyingError.code << underlyingError.localizedDescription;
if ([underlyingError.domain isEqualToString:@"NEAgentErrorDomain"]) {
switch (underlyingError.code) {
case 1:
qDebug() << "Disconnect underlying error" << "General. Use sysdiagnose.";
break;
case 2:
qDebug() << "Disconnect underlying error" << "Plug-in unavailable. Use sysdiagnose.";
break;
default:
qDebug() << "Disconnect underlying error" << "Unknown code. Use sysdiagnose.";
break;
}
}
}
} else {
qDebug() << "Disconnect error is absent";
}
}];
} else {
qDebug() << "Disconnect error is unavailable on iOS < 16.0";
}
}
emit connectionStateChanged(iosStatusToState(session.status));
}
}
@@ -352,6 +451,15 @@ bool IosController::startWireGuard(const QString &config)
void IosController::startTunnel()
{
NSString *protocolName = @"Unknown";
NETunnelProviderProtocol *tunnelProtocol = (NETunnelProviderProtocol *)m_currentTunnel.protocolConfiguration;
if (tunnelProtocol.providerConfiguration[@"wireguard"] != nil) {
protocolName = @"WireGuard";
} else if (tunnelProtocol.providerConfiguration[@"ovpn"] != nil) {
protocolName = @"OpenVPN";
}
m_rxBytes = 0;
m_txBytes = 0;
@@ -373,7 +481,7 @@ void IosController::startTunnel()
[m_currentTunnel loadFromPreferencesWithCompletionHandler:^(NSError *loadError) {
if (loadError) {
qDebug() << "IosController::startOpenVPN : Connect OpenVPN Tunnel Load Error" << loadError.localizedDescription.UTF8String;
qDebug().nospace() << "IosController::start" << protocolName << ": Connect " << protocolName << " Tunnel Load Error" << loadError.localizedDescription.UTF8String;
emit connectionStateChanged(Vpn::ConnectionState::Error);
return;
}
@@ -401,11 +509,11 @@ void IosController::startTunnel()
BOOL started = [m_currentTunnel.connection startVPNTunnelWithOptions:nil andReturnError:&startError];
if (!started || startError) {
qDebug() << "IosController::startOpenVPN : Connect OpenVPN Tunnel Start Error"
qDebug().nospace() << "IosController::start" << protocolName << " : Connect " << protocolName << " Tunnel Start Error"
<< (startError ? startError.localizedDescription.UTF8String : "");
emit connectionStateChanged(Vpn::ConnectionState::Error);
} else {
qDebug() << "IosController::startOpenVPN : Starting the tunnel succeeded";
qDebug().nospace() << "IosController::start" << protocolName << " : Starting the tunnel succeeded";
}
}];
});

View File

@@ -17,41 +17,41 @@ public class Logger {
deinit {}
func log(message: String) {
let suiteName = "group.org.amnezia.AmneziaVPN"
let logKey = "logMessages"
let sharedDefaults = UserDefaults(suiteName: suiteName)
var logs = sharedDefaults?.array(forKey: logKey) as? [String] ?? []
logs.append(message)
sharedDefaults?.set(logs, forKey: logKey)
// let suiteName = "group.org.amnezia.AmneziaVPN"
// let logKey = "logMessages"
// let sharedDefaults = UserDefaults(suiteName: suiteName)
// var logs = sharedDefaults?.array(forKey: logKey) as? [String] ?? []
// logs.append(message)
// sharedDefaults?.set(logs, forKey: logKey)
}
func writeLog(to targetFile: String) -> Bool {
private func writeLog(to targetFile: String) -> Bool {
return true;
}
static func configureGlobal(tagged tag: String, withFilePath filePath: String?) {
if Logger.global != nil {
return
}
Logger.global = Logger(tagged: tag)
var appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown version"
if let appBuild = Bundle.main.infoDictionary?["CFBundleVersion"] as? String {
appVersion += " (\(appBuild))"
}
Logger.global?.log(message: "App version: \(appVersion)")
// if Logger.global != nil {
// return
// }
//
// Logger.global = Logger(tagged: tag)
//
// var appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown version"
//
// if let appBuild = Bundle.main.infoDictionary?["CFBundleVersion"] as? String {
// appVersion += " (\(appBuild))"
// }
//
// Logger.global?.log(message: "App version: \(appVersion)")
}
}
func wg_log(_ type: OSLogType, staticMessage msg: StaticString) {
os_log(msg, log: OSLog.default, type: type)
Logger.global?.log(message: "\(msg)")
// os_log(msg, log: OSLog.default, type: type)
// Logger.global?.log(message: "\(msg)")
}
func wg_log(_ type: OSLogType, message msg: String) {
os_log("%{AMNEZIA}s", log: OSLog.default, type: type, msg)
Logger.global?.log(message: msg)
// os_log("%{AMNEZIA}s", log: OSLog.default, type: type, msg)
// Logger.global?.log(message: msg)
}

View File

@@ -0,0 +1,518 @@
// Copyright (c) 2023 Private Internet Access, Inc.
//
// This file is part of the Private Internet Access Desktop Client.
//
// The Private Internet Access Desktop Client is free software: you can
// redistribute it and/or modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
//
// The Private Internet Access Desktop Client is distributed in the hope that
// it will be useful, but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with the Private Internet Access Desktop Client. If not, see
// <https://www.gnu.org/licenses/>.
// Copyright (c) 2024 AmneziaVPN
// This file has been modified for AmneziaVPN
//
// This file is based on the work of the Private Internet Access Desktop Client.
// The original code of the Private Internet Access Desktop Client is copyrighted (c) 2023 Private Internet Access, Inc. and licensed under GPL3.
//
// The modified version of this file is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this file. If not, see <https://www.gnu.org/licenses/>.
#include "linuxfirewall.h"
#include "logger.h"
#include <QProcess>
#define BRAND_CODE "amn"
namespace {
Logger logger("LinuxFirewall");
} // namespace
namespace
{
const QString kAnchorName{BRAND_CODE "vpn"};
const QString kPacketTag{"0x3211"};
const QString kCGroupId{"0x567"};
const QString enabledKeyTemplate = "enabled:%1:%2";
const QString disabledKeyTemplate = "disabled:%1:%2";
const QString kVpnGroupName = BRAND_CODE "vpn";
QHash<QString, LinuxFirewall::FilterCallbackFunc> anchorCallbacks;
}
QString LinuxFirewall::kRtableName = QStringLiteral("%1rt").arg(kAnchorName);
QString LinuxFirewall::kOutputChain = QStringLiteral("OUTPUT");
QString LinuxFirewall::kPostRoutingChain = QStringLiteral("POSTROUTING");
QString LinuxFirewall::kPreRoutingChain = QStringLiteral("PREROUTING");
QString LinuxFirewall::kRootChain = QStringLiteral("%1.anchors").arg(kAnchorName);
QString LinuxFirewall::kFilterTable = QStringLiteral("filter");
QString LinuxFirewall::kNatTable = QStringLiteral("nat");
QString LinuxFirewall::kRawTable = QStringLiteral("raw");
QString LinuxFirewall::kMangleTable = QStringLiteral("mangle");
static QString getCommand(LinuxFirewall::IPVersion ip)
{
return ip == LinuxFirewall::IPv6 ? QStringLiteral("ip6tables") : QStringLiteral("iptables");
}
int LinuxFirewall::createChain(LinuxFirewall::IPVersion ip, const QString& chain, const QString& tableName)
{
if (ip == Both)
{
int result4 = createChain(IPv4, chain, tableName);
int result6 = createChain(IPv6, chain, tableName);
return result4 ? result4 : result6;
}
const QString cmd = getCommand(ip);
return execute(QStringLiteral("%1 -N %2 -t %3 || %1 -F %2 -t %3").arg(cmd, chain, tableName));
}
int LinuxFirewall::deleteChain(LinuxFirewall::IPVersion ip, const QString& chain, const QString& tableName)
{
if (ip == Both)
{
int result4 = deleteChain(IPv4, chain, tableName);
int result6 = deleteChain(IPv6, chain, tableName);
return result4 ? result4 : result6;
}
const QString cmd = getCommand(ip);
return execute(QStringLiteral("if %1 -L %2 -n -t %3 > /dev/null 2> /dev/null ; then %1 -F %2 -t %3 && %1 -X %2 -t %3; fi").arg(cmd, chain, tableName));
}
int LinuxFirewall::linkChain(LinuxFirewall::IPVersion ip, const QString& chain, const QString& parent, bool mustBeFirst, const QString& tableName)
{
if (ip == Both)
{
int result4 = linkChain(IPv4, chain, parent, mustBeFirst, tableName);
int result6 = linkChain(IPv6, chain, parent, mustBeFirst, tableName);
return result4 ? result4 : result6;
}
const QString cmd = getCommand(ip);
if (mustBeFirst)
{
// This monster shell script does the following:
// 1. Check if a rule with the appropriate target exists at the top of the parent chain
// 2. If not, insert a jump rule at the top of the parent chain
// 3. Look for and delete a single rule with the designated target at an index > 1
// (we can't safely delete all rules at once since rule numbers change)
// TODO: occasionally this script results in warnings in logs "Bad rule (does a matching rule exist in the chain?)" - this happens when
// the e.g OUTPUT chain is empty but this script attempts to delete things from it anyway. It doesn't cause any problems, but we should still fix at some point..
return execute(QStringLiteral("if ! %1 -L %2 -n --line-numbers -t %4 2> /dev/null | awk 'int($1) == 1 && $2 == \"%3\" { found=1 } END { if(found==1) { exit 0 } else { exit 1 } }' ; then %1 -I %2 -j %3 -t %4 && %1 -L %2 -n --line-numbers -t %4 2> /dev/null | awk 'int($1) > 1 && $2 == \"%3\" { print $1; exit }' | xargs %1 -t %4 -D %2 ; fi").arg(cmd, parent, chain, tableName));
}
else
return execute(QStringLiteral("if ! %1 -C %2 -j %3 -t %4 2> /dev/null ; then %1 -A %2 -j %3 -t %4; fi").arg(cmd, parent, chain, tableName));
}
int LinuxFirewall::unlinkChain(LinuxFirewall::IPVersion ip, const QString& chain, const QString& parent, const QString& tableName)
{
if (ip == Both)
{
int result4 = unlinkChain(IPv4, chain, parent, tableName);
int result6 = unlinkChain(IPv6, chain, parent, tableName);
return result4 ? result4 : result6;
}
const QString cmd = getCommand(ip);
return execute(QStringLiteral("if %1 -C %2 -j %3 -t %4 2> /dev/null ; then %1 -D %2 -j %3 -t %4; fi").arg(cmd, parent, chain, tableName));
}
void LinuxFirewall::ensureRootAnchorPriority(LinuxFirewall::IPVersion ip)
{
linkChain(ip, kRootChain, kOutputChain, true);
}
void LinuxFirewall::installAnchor(LinuxFirewall::IPVersion ip, const QString& anchor, const QStringList& rules, const QString& tableName,
const FilterCallbackFunc& enableFunc, const FilterCallbackFunc& disableFunc)
{
if (ip == Both)
{
installAnchor(IPv4, anchor, rules, tableName, enableFunc, disableFunc);
installAnchor(IPv6, anchor, rules, tableName, enableFunc, disableFunc);
return;
}
const QString cmd = getCommand(ip);
const QString anchorChain = QStringLiteral("%1.a.%2").arg(kAnchorName, anchor);
const QString actualChain = QStringLiteral("%1.%2").arg(kAnchorName, anchor);
// Start by defining a placeholder chain, which stays locked into place
// in the root chain without being removed or recreated, ensuring the
// intended precedence order.
createChain(ip, anchorChain, tableName);
linkChain(ip, anchorChain, kRootChain, false, tableName);
if(enableFunc)
{
const QString key = enabledKeyTemplate.arg(tableName, anchor);
if(!anchorCallbacks.contains(key)) anchorCallbacks[key] = enableFunc;
}
if(disableFunc)
{
const QString key = disabledKeyTemplate.arg(tableName, anchor);
if(!anchorCallbacks.contains(key)) anchorCallbacks[key] = disableFunc;
}
// Create the actual rule chain, which we'll insert or remove from the
// placeholder anchor when needed.
createChain(ip, actualChain, tableName);
for (const QString& rule : rules)
execute(QStringLiteral("%1 -A %2 %3 -t %4").arg(cmd, actualChain, rule, tableName));
}
void LinuxFirewall::uninstallAnchor(LinuxFirewall::IPVersion ip, const QString& anchor, const QString& tableName)
{
if (ip == Both)
{
uninstallAnchor(IPv4, anchor, tableName);
uninstallAnchor(IPv6, anchor, tableName);
return;
}
const QString cmd = getCommand(ip);
const QString anchorChain = QStringLiteral("%1.a.%2").arg(kAnchorName, anchor);
const QString actualChain = QStringLiteral("%1.%2").arg(kAnchorName, anchor);
unlinkChain(ip, anchorChain, kRootChain, tableName);
deleteChain(ip, anchorChain, tableName);
deleteChain(ip, actualChain, tableName);
}
QStringList LinuxFirewall::getDNSRules(const QStringList& servers)
{
QStringList result;
for (const QString& server : servers)
{
result << QStringLiteral("-o amn0+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server);
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);
}
return result;
}
QStringList LinuxFirewall::getAllowRule(const QStringList& servers)
{
QStringList result;
for (const QString& server : servers)
{
result << QStringLiteral("-d %1 -j ACCEPT").arg(server);
}
return result;
}
QStringList LinuxFirewall::getBlockRule(const QStringList& servers)
{
QStringList result;
for (const QString& server : servers)
{
result << QStringLiteral("-d %1 -j REJECT").arg(server);
}
return result;
}
void LinuxFirewall::install()
{
// Clean up any existing rules if they exist.
uninstall();
// Create a root filter chain to hold all our other anchors in order.
createChain(Both, kRootChain, kFilterTable);
// Create a root raw chain
createChain(Both, kRootChain, kRawTable);
// Create a root NAT chain
createChain(Both, kRootChain, kNatTable);
// Create a root Mangle chain
createChain(Both, kRootChain, kMangleTable);
// Install our filter rulesets in each corresponding anchor chain.
installAnchor(Both, QStringLiteral("000.allowLoopback"), {
QStringLiteral("-o lo+ -j ACCEPT"),
});
installAnchor(IPv4, QStringLiteral("320.allowDNS"), {});
installAnchor(Both, QStringLiteral("310.blockDNS"), {
QStringLiteral("-p udp --dport 53 -j REJECT"),
QStringLiteral("-p tcp --dport 53 -j REJECT"),
});
installAnchor(IPv4, QStringLiteral("300.allowLAN"), {
QStringLiteral("-d 10.0.0.0/8 -j ACCEPT"),
QStringLiteral("-d 169.254.0.0/16 -j ACCEPT"),
QStringLiteral("-d 172.16.0.0/12 -j ACCEPT"),
QStringLiteral("-d 192.168.0.0/16 -j ACCEPT"),
QStringLiteral("-d 224.0.0.0/4 -j ACCEPT"),
QStringLiteral("-d 255.255.255.255/32 -j ACCEPT"),
});
installAnchor(IPv6, QStringLiteral("300.allowLAN"), {
QStringLiteral("-d fc00::/7 -j ACCEPT"),
QStringLiteral("-d fe80::/10 -j ACCEPT"),
QStringLiteral("-d ff00::/8 -j ACCEPT"),
});
installAnchor(IPv4, QStringLiteral("290.allowDHCP"), {
QStringLiteral("-p udp -d 255.255.255.255 --sport 68 --dport 67 -j ACCEPT"),
});
installAnchor(IPv6, QStringLiteral("290.allowDHCP"), {
QStringLiteral("-p udp -d ff00::/8 --sport 546 --dport 547 -j ACCEPT"),
});
installAnchor(IPv6, QStringLiteral("250.blockIPv6"), {
QStringLiteral("! -o lo+ -j REJECT"),
});
installAnchor(Both, QStringLiteral("200.allowVPN"), {
QStringLiteral("-o amn0+ -j ACCEPT"),
QStringLiteral("-o tun0+ -j ACCEPT"),
});
installAnchor(IPv4, QStringLiteral("120.blockNets"), {});
installAnchor(IPv4, QStringLiteral("110.allowNets"), {});
installAnchor(Both, QStringLiteral("100.blockAll"), {
QStringLiteral("-j REJECT"),
});
// NAT rules
installAnchor(Both, QStringLiteral("100.transIp"), {
// Only need the original interface, not the IP.
// The interface should remain much more stable/unchangeable than the IP
// (IP can change when changing networks, but interface only changes if adding/removing NICs)
// this is just a stub rule - the real rule is set at run-time
// and updates dynamically (via replaceAnchor) when our interface changes
// it'll take this form: "-o <interface name> -j MASQUERADE"
QStringLiteral("-j MASQUERADE")
}, kNatTable);
// Mangle rules
installAnchor(Both, QStringLiteral("100.tagPkts"), {
QStringLiteral("-m cgroup --cgroup %1 -j MARK --set-mark %2").arg(kCGroupId, kPacketTag)
}, kMangleTable, setupTrafficSplitting, teardownTrafficSplitting);
// A rule to mitigate CVE-2019-14899 - drop packets addressed to the local
// VPN IP but that are not actually received on the VPN interface.
// See here: https://seclists.org/oss-sec/2019/q4/122
installAnchor(Both, QStringLiteral("100.vpnTunOnly"), {
// To be replaced at runtime
QStringLiteral("-j ACCEPT")
}, kRawTable);
// Insert our fitler root chain at the top of the OUTPUT chain.
linkChain(Both, kRootChain, kOutputChain, true, kFilterTable);
// Insert our NAT root chain at the top of the POSTROUTING chain.
linkChain(Both, kRootChain, kPostRoutingChain, true, kNatTable);
// Insert our Mangle root chain at the top of the OUTPUT chain.
linkChain(Both, kRootChain, kOutputChain, true, kMangleTable);
// Insert our Raw root chain at the top of the PREROUTING chain.
linkChain(Both, kRootChain, kPreRoutingChain, true, kRawTable);
setupTrafficSplitting();
}
void LinuxFirewall::uninstall()
{
// Filter chain
unlinkChain(Both, kRootChain, kOutputChain, kFilterTable);
deleteChain(Both, kRootChain, kFilterTable);
// Raw chain
unlinkChain(Both, kRootChain, kPreRoutingChain, kRawTable);
deleteChain(Both, kRootChain, kRawTable);
// NAT chain
unlinkChain(Both, kRootChain, kPostRoutingChain, kNatTable);
deleteChain(Both, kRootChain, kNatTable);
// Mangle chain
unlinkChain(Both, kRootChain, kOutputChain, kMangleTable);
deleteChain(Both, kRootChain, kMangleTable);
// Remove filter anchors
uninstallAnchor(Both, QStringLiteral("000.allowLoopback"));
uninstallAnchor(Both, QStringLiteral("400.allowPIA"));
uninstallAnchor(IPv4, QStringLiteral("320.allowDNS"));
uninstallAnchor(Both, QStringLiteral("310.blockDNS"));
uninstallAnchor(Both, QStringLiteral("300.allowLAN"));
uninstallAnchor(Both, QStringLiteral("290.allowDHCP"));
uninstallAnchor(IPv6, QStringLiteral("250.blockIPv6"));
uninstallAnchor(Both, QStringLiteral("200.allowVPN"));
uninstallAnchor(IPv4, QStringLiteral("120.blockNets"));
uninstallAnchor(IPv4, QStringLiteral("110.allowNets"));
uninstallAnchor(Both, QStringLiteral("100.blockAll"));
// Remove Nat anchors
uninstallAnchor(Both, QStringLiteral("100.transIp"), kNatTable);
// Remove Mangle anchors
uninstallAnchor(Both, QStringLiteral("100.tagPkts"), kMangleTable);
// Remove Raw anchors
uninstallAnchor(Both, QStringLiteral("100.vpnTunOnly"), kRawTable);
teardownTrafficSplitting();
logger.debug() << "LinuxFirewall::uninstall() complete";
}
bool LinuxFirewall::isInstalled()
{
return execute(QStringLiteral("iptables -C %1 -j %2 2> /dev/null").arg(kOutputChain, kRootChain)) == 0;
}
void LinuxFirewall::enableAnchor(LinuxFirewall::IPVersion ip, const QString &anchor, const QString& tableName)
{
if (ip == Both)
{
enableAnchor(IPv4, anchor, tableName);
enableAnchor(IPv6, anchor, tableName);
return;
}
const QString cmd = getCommand(ip);
const QString ipStr = ip == IPv6 ? QStringLiteral("(IPv6)") : QStringLiteral("(IPv4)");
execute(QStringLiteral("if %1 -C %5.a.%2 -j %5.%2 -t %4 2> /dev/null ; then echo '%2%3: ON' ; else echo '%2%3: OFF -> ON' ; %1 -A %5.a.%2 -j %5.%2 -t %4; fi").arg(cmd, anchor, ipStr, tableName, kAnchorName));
}
void LinuxFirewall::replaceAnchor(LinuxFirewall::IPVersion ip, const QString &anchor, const QString &newRule, const QString& tableName)
{
if (ip == Both)
{
replaceAnchor(IPv4, anchor, newRule, tableName);
replaceAnchor(IPv6, anchor, newRule, tableName);
return;
}
const QString cmd = getCommand(ip);
const QString ipStr = ip == IPv6 ? QStringLiteral("(IPv6)") : QStringLiteral("(IPv4)");
execute(QStringLiteral("%1 -R %7.%2 1 %3 -t %4 ; echo 'Replaced rule %7.%2 %5 with %6'").arg(cmd, anchor, newRule, tableName, ipStr, newRule, kAnchorName));
}
void LinuxFirewall::disableAnchor(LinuxFirewall::IPVersion ip, const QString &anchor, const QString& tableName)
{
if (ip == Both)
{
disableAnchor(IPv4, anchor, tableName);
disableAnchor(IPv6, anchor, tableName);
return;
}
const QString cmd = getCommand(ip);
const QString ipStr = ip == IPv6 ? QStringLiteral("(IPv6)") : QStringLiteral("(IPv4)");
execute(QStringLiteral("if ! %1 -C %5.a.%2 -j %5.%2 -t %4 2> /dev/null ; then echo '%2%3: OFF' ; else echo '%2%3: ON -> OFF' ; %1 -F %5.a.%2 -t %4; fi").arg(cmd, anchor, ipStr, tableName, kAnchorName));
}
bool LinuxFirewall::isAnchorEnabled(LinuxFirewall::IPVersion ip, const QString &anchor, const QString& tableName)
{
const QString cmd = getCommand(ip);
return execute(QStringLiteral("%1 -C %4.a.%2 -j %4.%2 -t %3 2> /dev/null").arg(cmd, anchor, tableName, kAnchorName)) == 0;
}
void LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPVersion ip, const QString &anchor, bool enabled, const QString &tableName)
{
if (enabled)
{
enableAnchor(ip, anchor, tableName);
const QString key = enabledKeyTemplate.arg(tableName, anchor);
if(anchorCallbacks.contains(key)) anchorCallbacks[key]();
}
else
{
disableAnchor(ip, anchor, tableName);
const QString key = disabledKeyTemplate.arg(tableName, anchor);
if(anchorCallbacks.contains(key)) anchorCallbacks[key]();
}
}
void LinuxFirewall::updateDNSServers(const QStringList& servers)
{
static QStringList existingServers {};
existingServers = servers;
execute(QStringLiteral("iptables -F %1.320.allowDNS").arg(kAnchorName));
for (const QString& rule : getDNSRules(servers))
execute(QStringLiteral("iptables -A %1.320.allowDNS %2").arg(kAnchorName, rule));
}
void LinuxFirewall::updateAllowNets(const QStringList& servers)
{
static QStringList existingServers {};
existingServers = servers;
execute(QStringLiteral("iptables -F %1.110.allowNets").arg(kAnchorName));
for (const QString& rule : getAllowRule(servers))
execute(QStringLiteral("iptables -A %1.110.allowNets %2").arg(kAnchorName, rule));
}
void LinuxFirewall::updateBlockNets(const QStringList& servers)
{
static QStringList existingServers {};
existingServers = servers;
execute(QStringLiteral("iptables -F %1.120.blockNets").arg(kAnchorName));
for (const QString& rule : getBlockRule(servers))
execute(QStringLiteral("iptables -A %1.120.blockNets %2").arg(kAnchorName, rule));
}
int waitForExitCode(QProcess& process)
{
if (!process.waitForFinished() || process.error() == QProcess::FailedToStart)
return -2;
else if (process.exitStatus() != QProcess::NormalExit)
return -1;
else
return process.exitCode();
}
int LinuxFirewall::execute(const QString &command, bool ignoreErrors)
{
QProcess p;
p.start(QStringLiteral("/bin/bash"), {QStringLiteral("-c"), command}, QProcess::ReadOnly);
p.closeWriteChannel();
int exitCode = waitForExitCode(p);
auto out = p.readAllStandardOutput().trimmed();
auto err = p.readAllStandardError().trimmed();
if ((exitCode != 0 || !err.isEmpty()) && !ignoreErrors)
logger.warning() << "(" << exitCode << ") $ " << command;
else if (false)
logger.debug() << "(" << exitCode << ") $ " << command;
if (!out.isEmpty())
logger.info() << out;
if (!err.isEmpty())
logger.warning() << err;
return exitCode;
}
void LinuxFirewall::setupTrafficSplitting()
{
auto cGroupDir = "/sys/fs/cgroup/net_cls/" BRAND_CODE "vpnexclusions/";
logger.info() << "Should be setting up cgroup in" << cGroupDir << "for traffic splitting";
execute(QStringLiteral("if [ ! -d %1 ] ; then mkdir %1 ; sleep 0.1 ; echo %2 > %1/net_cls.classid ; fi").arg(cGroupDir).arg(kCGroupId));
// Set a rule with priority 100 (lower priority than local but higher than main/default, 0 is highest priority)
execute(QStringLiteral("if ! ip rule list | grep -q %1 ; then ip rule add from all fwmark %1 lookup %2 pri 100 ; fi").arg(kPacketTag, kRtableName));
}
void LinuxFirewall::teardownTrafficSplitting()
{
logger.info() << "Tearing down cgroup and routing rules";
execute(QStringLiteral("if ip rule list | grep -q %1; then ip rule del from all fwmark %1 lookup %2 2> /dev/null ; fi").arg(kPacketTag, kRtableName));
execute(QStringLiteral("ip route flush table %1").arg(kRtableName));
execute(QStringLiteral("ip route flush cache"));
}

View File

@@ -0,0 +1,107 @@
// Copyright (c) 2023 Private Internet Access, Inc.
//
// This file is part of the Private Internet Access Desktop Client.
//
// The Private Internet Access Desktop Client is free software: you can
// redistribute it and/or modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
//
// The Private Internet Access Desktop Client is distributed in the hope that
// it will be useful, but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with the Private Internet Access Desktop Client. If not, see
// <https://www.gnu.org/licenses/>.
// Copyright (c) 2024 AmneziaVPN
// This file has been modified for AmneziaVPN
//
// This file is based on the work of the Private Internet Access Desktop Client.
// The original code of the Private Internet Access Desktop Client is copyrighted (c) 2023 Private Internet Access, Inc. and licensed under GPL3.
//
// The modified version of this file is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this file. If not, see <https://www.gnu.org/licenses/>.
#ifndef LINUXFIREWALL_H
#define LINUXFIREWALL_H
#include <QString>
#include <QStringList>
// Descriptor for a set of firewall rules to be appled.
//
struct FirewallParams
{
QStringList dnsServers;
QVector<QString> excludeApps; // Apps to exclude if VPN exemptions are enabled
QStringList allowAddrs;
QStringList blockAddrs;
// The follow flags indicate which general rulesets are needed. Note that
// this is after some sanity filtering, i.e. an allow rule may be listed
// as not needed if there were no block rules preceding it. The rulesets
// should be thought of as in last-match order.
bool blockAll; // Block all traffic by default
bool allowVPN; // Exempt traffic through VPN tunnel
bool allowDHCP; // Exempt DHCP traffic
bool blockIPv6; // Block all IPv6 traffic
bool allowLAN; // Exempt LAN traffic, including IPv6 LAN traffic
bool blockDNS; // Block all DNS traffic except specified DNS servers
bool allowPIA; // Exempt PIA executables
bool allowLoopback; // Exempt loopback traffic
bool allowHnsd; // Exempt Handshake DNS traffic
bool allowVpnExemptions; // Exempt specified traffic from the tunnel (route it over the physical uplink instead)
bool allowNets;
bool blockNets;
};
class LinuxFirewall
{
public:
enum IPVersion { IPv4, IPv6, Both };
// Table names
static QString kFilterTable, kNatTable, kMangleTable, kRtableName, kRawTable;
public:
using FilterCallbackFunc = std::function<void()>;
private:
static int createChain(IPVersion ip, const QString& chain, const QString& tableName = kFilterTable);
static int deleteChain(IPVersion ip, const QString& chain, const QString& tableName = kFilterTable);
static int linkChain(IPVersion ip, const QString& chain, const QString& parent, bool mustBeFirst = false, const QString& tableName = kFilterTable);
static int unlinkChain(IPVersion ip, const QString& chain, const QString& parent, const QString& tableName = kFilterTable);
static void installAnchor(IPVersion ip, const QString& anchor, const QStringList& rules, const QString& tableName = kFilterTable, const FilterCallbackFunc& enableFunc = {}, const FilterCallbackFunc& disableFunc = {});
static void uninstallAnchor(IPVersion ip, const QString& anchor, const QString& tableName = kFilterTable);
static QStringList getDNSRules(const QStringList& servers);
static QStringList getAllowRule(const QStringList& servers);
static QStringList getBlockRule(const QStringList& servers);
static void setupTrafficSplitting();
static void teardownTrafficSplitting();
static int execute(const QString& command, bool ignoreErrors = false);
private:
// Chain names
static QString kOutputChain, kRootChain, kPostRoutingChain, kPreRoutingChain;
public:
static void install();
static void uninstall();
static bool isInstalled();
static void ensureRootAnchorPriority(IPVersion ip = Both);
static void enableAnchor(IPVersion ip, const QString& anchor, const QString& tableName = kFilterTable);
static void disableAnchor(IPVersion ip, const QString& anchor, const QString& tableName = kFilterTable);
static bool isAnchorEnabled(IPVersion ip, const QString& anchor, const QString& tableName = kFilterTable);
static void setAnchorEnabled(IPVersion ip, const QString& anchor, bool enabled, const QString& tableName = kFilterTable);
static void replaceAnchor(LinuxFirewall::IPVersion ip, const QString &anchor, const QString &newRule, const QString& tableName);
static void updateDNSServers(const QStringList& servers);
static void updateAllowNets(const QStringList& servers);
static void updateBlockNets(const QStringList& servers);
};
#endif // LINUXFIREWALL_H

View File

@@ -11,7 +11,9 @@
#include <QFile>
#include <QLocalSocket>
#include <QTimer>
#include <QThread>
#include "linuxfirewall.h"
#include "leakdetector.h"
#include "logger.h"
@@ -116,7 +118,27 @@ bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) {
int err = uapiErrno(uapiCommand(message));
if (err != 0) {
logger.error() << "Interface configuration failed:" << strerror(err);
} else {
FirewallParams params { };
params.dnsServers.append(config.m_dnsServer);
if (config.m_allowedIPAddressRanges.at(0).toString() == "0.0.0.0/0"){
params.blockAll = true;
if (config.m_excludedAddresses.size()) {
params.allowNets = true;
foreach (auto net, config.m_excludedAddresses) {
params.allowAddrs.append(net.toUtf8());
}
}
} else {
params.blockNets = true;
foreach (auto net, config.m_allowedIPAddressRanges) {
params.blockAddrs.append(net.toString());
}
}
applyFirewallRules(params);
}
return (err == 0);
}
@@ -140,6 +162,9 @@ bool WireguardUtilsLinux::deleteInterface() {
// Garbage collect.
QDir wgRuntimeDir(WG_RUNTIME_DIR);
QFile::remove(wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".name"));
// double-check + ensure our firewall is installed and enabled
LinuxFirewall::uninstall();
return true;
}
@@ -252,6 +277,31 @@ 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;

View File

@@ -8,8 +8,11 @@
#include <QObject>
#include <QProcess>
#include "daemon/wireguardutils.h"
#include "linuxroutemonitor.h"
#include "linuxfirewall.h"
class WireguardUtilsLinux final : public WireguardUtils {
Q_OBJECT
@@ -34,7 +37,7 @@ public:
bool addExclusionRoute(const IPAddress& prefix) override;
bool deleteExclusionRoute(const IPAddress& prefix) override;
void applyFirewallRules(FirewallParams& params);
signals:
void backendFailure();

View File

@@ -0,0 +1,199 @@
// Copyright (c) 2023 Private Internet Access, Inc.
//
// This file is part of the Private Internet Access Desktop Client.
//
// The Private Internet Access Desktop Client is free software: you can
// redistribute it and/or modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
//
// The Private Internet Access Desktop Client is distributed in the hope that
// it will be useful, but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with the Private Internet Access Desktop Client. If not, see
// <https://www.gnu.org/licenses/>.
// Copyright (c) 2024 AmneziaVPN
// This file has been modified for AmneziaVPN
//
// This file is based on the work of the Private Internet Access Desktop Client.
// The original code of the Private Internet Access Desktop Client is copyrighted (c) 2023 Private Internet Access, Inc. and licensed under GPL3.
//
// The modified version of this file is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this file. If not, see <https://www.gnu.org/licenses/>.
#include "macosfirewall.h"
#include "logger.h"
#include <QProcess>
#include <QCoreApplication>
#define BRAND_IDENTIFIER "amn"
namespace {
Logger logger("MacOSFirewall");
} // namespace
#include "macosfirewall.h"
#define ResourceDir qApp->applicationDirPath() + "/pf"
#define DaemonDataDir qApp->applicationDirPath() + "/pf"
#include <QProcess>
static QString kRootAnchor = QStringLiteral(BRAND_IDENTIFIER);
static QByteArray kPfWarning = "pfctl: Use of -f option, could result in flushing of rules\npresent in the main ruleset added by the system at startup.\nSee /etc/pf.conf for further details.\n";
int waitForExitCode(QProcess& process)
{
if (!process.waitForFinished() || process.error() == QProcess::FailedToStart)
return -2;
else if (process.exitStatus() != QProcess::NormalExit)
return -1;
else
return process.exitCode();
}
int MacOSFirewall::execute(const QString& command, bool ignoreErrors)
{
QProcess p;
p.start(QStringLiteral("/bin/bash"), { QStringLiteral("-c"), command }, QProcess::ReadOnly);
p.closeWriteChannel();
int exitCode = waitForExitCode(p);
auto out = p.readAllStandardOutput().trimmed();
auto err = p.readAllStandardError().replace(kPfWarning, "").trimmed();
if ((exitCode != 0 || !err.isEmpty()) && !ignoreErrors)
logger.info() << "(" << exitCode << ") $ " << command;
else if (false)
logger.info() << "(" << exitCode << ") $ " << command;
if (!out.isEmpty()) logger.info() << out;
if (!err.isEmpty()) logger.info() << err;
return exitCode;
}
void MacOSFirewall::installRootAnchors()
{
logger.info() << "Installing PF root anchors";
// Append our NAT anchors by reading back and re-applying NAT rules only
auto insertNatAnchors = QStringLiteral(
"( "
R"(pfctl -sn | grep -v '%1/*'; )" // Translation rules (includes both nat and rdr, despite the modifier being 'nat')
R"(echo 'nat-anchor "%2/*"'; )" // PIA's translation anchors
R"(echo 'rdr-anchor "%3/*"'; )"
R"(echo 'load anchor "%4" from "%5/%6.conf"'; )" // Load the PIA anchors from file
") | pfctl -N -f -").arg(kRootAnchor, kRootAnchor, kRootAnchor, kRootAnchor, ResourceDir, kRootAnchor);
execute(insertNatAnchors);
// Append our filter anchor by reading back and re-applying filter rules
// only. pfctl -sr also includes scrub rules, but these will be ignored
// due to -R.
auto insertFilterAnchor = QStringLiteral(
"( "
R"(pfctl -sr | grep -v '%1/*'; )" // Filter rules (everything from pfctl -sr except 'scrub')
R"(echo 'anchor "%2/*"'; )" // PIA's filter anchors
R"(echo 'load anchor "%3" from "%4/%5.conf"'; )" // Load the PIA anchors from file
" ) | pfctl -R -f -").arg(kRootAnchor, kRootAnchor, kRootAnchor, ResourceDir, kRootAnchor);
execute(insertFilterAnchor);
}
void MacOSFirewall::install()
{
// remove hard-coded (legacy) pia anchor from /etc/pf.conf if it exists
execute(QStringLiteral("if grep -Fq '%1' /etc/pf.conf ; then echo \"`cat /etc/pf.conf | grep -vF '%1'`\" > /etc/pf.conf ; fi").arg(kRootAnchor));
// Clean up any existing rules if they exist.
uninstall();
timespec waitTime{0, 10'000'000};
::nanosleep(&waitTime, nullptr);
logger.info() << "Installing PF root anchor";
installRootAnchors();
execute(QStringLiteral("pfctl -E 2>&1 | grep -F 'Token : ' | cut -c9- > '%1/pf.token'").arg(DaemonDataDir));
}
void MacOSFirewall::uninstall()
{
logger.info() << "Uninstalling PF root anchor";
execute(QStringLiteral("pfctl -q -a '%1' -F all").arg(kRootAnchor));
execute(QStringLiteral("test -f '%1/pf.token' && pfctl -X `cat '%1/pf.token'` && rm '%1/pf.token'").arg(DaemonDataDir));
execute(QStringLiteral("test -f /etc/pf.conf && pfctl -F all -f /etc/pf.conf"));
}
bool MacOSFirewall::isInstalled()
{
return isPFEnabled() && isRootAnchorLoaded();
}
bool MacOSFirewall::isPFEnabled()
{
return 0 == execute(QStringLiteral("test -s '%1/pf.token' && pfctl -s References | grep -qFf '%1/pf.token'").arg(DaemonDataDir), true);
}
void MacOSFirewall::ensureRootAnchorPriority()
{
// We check whether our anchor appears last in the ruleset. If it does not, then remove it and re-add it last (this happens atomically).
// Appearing last ensures priority.
execute(QStringLiteral("if ! pfctl -sr | tail -1 | grep -qF '%1'; then echo -e \"$(pfctl -sr | grep -vF '%1')\\n\"'anchor \"%1\"' | pfctl -f - ; fi").arg(kRootAnchor));
}
bool MacOSFirewall::isRootAnchorLoaded()
{
// Our Root anchor is loaded if:
// 1. It is is included among the top-level anchors
// 2. It is not empty (i.e it contains sub-anchors)
return 0 == execute(QStringLiteral("pfctl -sr | grep -q '%1' && pfctl -q -a '%1' -s rules 2> /dev/null | grep -q .").arg(kRootAnchor), true);
}
void MacOSFirewall::enableAnchor(const QString& anchor)
{
execute(QStringLiteral("if pfctl -q -a '%1/%2' -s rules 2> /dev/null | grep -q . ; then echo '%2: ON' ; else echo '%2: OFF -> ON' ; pfctl -q -a '%1/%2' -F all -f '%3/%1.%2.conf' ; fi").arg(kRootAnchor, anchor, ResourceDir));
}
void MacOSFirewall::disableAnchor(const QString& anchor)
{
execute(QStringLiteral("if ! pfctl -q -a '%1/%2' -s rules 2> /dev/null | grep -q . ; then echo '%2: OFF' ; else echo '%2: ON -> OFF' ; pfctl -q -a '%1/%2' -F all ; fi").arg(kRootAnchor, anchor));
}
bool MacOSFirewall::isAnchorEnabled(const QString& anchor)
{
return 0 == execute(QStringLiteral("pfctl -q -a '%1/%2' -s rules 2> /dev/null | grep -q .").arg(kRootAnchor, anchor), true);
}
void MacOSFirewall::setAnchorEnabled(const QString& anchor, bool enabled)
{
if (enabled)
enableAnchor(anchor);
else
disableAnchor(anchor);
}
void MacOSFirewall::setAnchorTable(const QString& anchor, bool enabled, const QString& table, const QStringList& items)
{
if (enabled)
execute(QStringLiteral("pfctl -q -a '%1/%2' -t '%3' -T replace %4").arg(kRootAnchor, anchor, table, items.join(' ')));
else
execute(QStringLiteral("pfctl -q -a '%1/%2' -t '%3' -T kill").arg(kRootAnchor, anchor, table), true);
}
void MacOSFirewall::setAnchorWithRules(const QString& anchor, bool enabled, const QStringList &ruleList)
{
if (!enabled)
return (void)execute(QStringLiteral("pfctl -q -a '%1/%2' -F rules").arg(kRootAnchor, anchor), true);
else
return (void)execute(QStringLiteral("echo -e \"%1\" | pfctl -q -a '%2/%3' -f -").arg(ruleList.join('\n'), kRootAnchor, anchor), true);
}

View File

@@ -0,0 +1,90 @@
// Copyright (c) 2023 Private Internet Access, Inc.
//
// This file is part of the Private Internet Access Desktop Client.
//
// The Private Internet Access Desktop Client is free software: you can
// redistribute it and/or modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
//
// The Private Internet Access Desktop Client is distributed in the hope that
// it will be useful, but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with the Private Internet Access Desktop Client. If not, see
// <https://www.gnu.org/licenses/>.
// Copyright (c) 2024 AmneziaVPN
// This file has been modified for AmneziaVPN
//
// This file is based on the work of the Private Internet Access Desktop Client.
// The original code of the Private Internet Access Desktop Client is copyrighted (c) 2023 Private Internet Access, Inc. and licensed under GPL3.
//
// The modified version of this file is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this file. If not, see <https://www.gnu.org/licenses/>.
#ifndef MACOSFIREWALL_H
#define MACOSFIREWALL_H
#include <QString>
#include <QStringList>
// Descriptor for a set of firewall rules to be appled.
//
struct FirewallParams
{
QStringList dnsServers;
QVector<QString> excludeApps; // Apps to exclude if VPN exemptions are enabled
QStringList allowAddrs;
QStringList blockAddrs;
// The follow flags indicate which general rulesets are needed. Note that
// this is after some sanity filtering, i.e. an allow rule may be listed
// as not needed if there were no block rules preceding it. The rulesets
// should be thought of as in last-match order.
bool blockAll; // Block all traffic by default
bool blockNets;
bool allowNets;
bool allowVPN; // Exempt traffic through VPN tunnel
bool allowDHCP; // Exempt DHCP traffic
bool blockIPv6; // Block all IPv6 traffic
bool allowLAN; // Exempt LAN traffic, including IPv6 LAN traffic
bool blockDNS; // Block all DNS traffic except specified DNS servers
bool allowPIA; // Exempt PIA executables
bool allowLoopback; // Exempt loopback traffic
bool allowHnsd; // Exempt Handshake DNS traffic
bool allowVpnExemptions; // Exempt specified traffic from the tunnel (route it over the physical uplink instead)
};
class MacOSFirewall
{
private:
static int execute(const QString &command, bool ignoreErrors = false);
static bool isPFEnabled();
static bool isRootAnchorLoaded();
public:
static void install();
static void uninstall();
static bool isInstalled();
static void enableAnchor(const QString &anchor);
static void disableAnchor(const QString &anchor);
static bool isAnchorEnabled(const QString &anchor);
static void setAnchorEnabled(const QString &anchor, bool enable);
static void setAnchorTable(const QString &anchor, bool enabled, const QString &table, const QStringList &items);
static void setAnchorWithRules(const QString &anchor, bool enabled, const QStringList &rules);
static void ensureRootAnchorPriority();
static void installRootAnchors();
};
#endif // MACOSFIREWALL_H

View File

@@ -114,9 +114,30 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) {
}
int err = uapiErrno(uapiCommand(message));
if (err != 0) {
logger.error() << "Interface configuration failed:" << strerror(err);
} else {
FirewallParams params { };
params.dnsServers.append(config.m_dnsServer);
if (config.m_allowedIPAddressRanges.at(0).toString() == "0.0.0.0/0"){
params.blockAll = true;
if (config.m_excludedAddresses.size()) {
params.allowNets = true;
foreach (auto net, config.m_excludedAddresses) {
params.allowAddrs.append(net.toUtf8());
}
}
} else {
params.blockNets = true;
foreach (auto net, config.m_allowedIPAddressRanges) {
params.blockAddrs.append(net.toString());
}
}
applyFirewallRules(params);
}
return (err == 0);
}
@@ -140,6 +161,10 @@ bool WireguardUtilsMacos::deleteInterface() {
// Garbage collect.
QDir wgRuntimeDir(WG_RUNTIME_DIR);
QFile::remove(wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".name"));
// double-check + ensure our firewall is installed and enabled
MacOSFirewall::uninstall();
return true;
}
@@ -302,6 +327,31 @@ 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;

View File

@@ -10,6 +10,7 @@
#include "daemon/wireguardutils.h"
#include "macosroutemonitor.h"
#include "macosfirewall.h"
class WireguardUtilsMacos final : public WireguardUtils {
Q_OBJECT
@@ -34,6 +35,7 @@ class WireguardUtilsMacos final : public WireguardUtils {
bool addExclusionRoute(const IPAddress& prefix) override;
bool deleteExclusionRoute(const IPAddress& prefix) override;
void applyFirewallRules(FirewallParams& params);
signals:
void backendFailure();

View File

@@ -66,7 +66,7 @@ ErrorCode OpenVpnOverCloakProtocol::start()
emit protocolError(amnezia::ErrorCode::CloakExecutableCrashed);
stop();
}
if (exitCode !=0 ){
if (exitCode !=0 ) {
emit protocolError(amnezia::ErrorCode::InternalError);
stop();
}

View File

@@ -4,6 +4,7 @@
#include <QRandomGenerator>
#include <QTcpServer>
#include <QTcpSocket>
#include <QNetworkInterface>
#include "logger.h"
#include "openvpnprotocol.h"
@@ -53,6 +54,11 @@ void OpenVpnProtocol::stop()
QThread::msleep(10);
m_managementServer.stop();
}
#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
IpcClient::Interface()->disableKillSwitch();
#endif
setConnectionState(Vpn::ConnectionState::Disconnected);
}
@@ -85,13 +91,13 @@ void OpenVpnProtocol::killOpenVpnProcess()
void OpenVpnProtocol::readOpenVpnConfiguration(const QJsonObject &configuration)
{
if (configuration.contains(ProtocolProps::key_proto_config_data(Proto::OpenVpn))) {
m_configData = configuration;
QJsonObject jConfig = configuration.value(ProtocolProps::key_proto_config_data(Proto::OpenVpn)).toObject();
m_configFile.open();
m_configFile.write(jConfig.value(config_key::config).toString().toUtf8());
m_configFile.close();
m_configFileName = m_configFile.fileName();
qDebug().noquote() << QString("Set config data") << m_configFileName;
}
}
@@ -138,12 +144,18 @@ uint OpenVpnProtocol::selectMgmtPort()
void OpenVpnProtocol::updateRouteGateway(QString line)
{
// TODO: fix for macos
line = line.split("ROUTE_GATEWAY", Qt::SkipEmptyParts).at(1);
if (!line.contains("/"))
return;
m_routeGateway = line.split("/", Qt::SkipEmptyParts).first();
m_routeGateway.replace(" ", "");
if (line.contains("net_route_v4_best_gw")) {
QStringList params = line.split(" ");
if (params.size() == 6) {
m_routeGateway = params.at(3);
}
} else {
line = line.split("ROUTE_GATEWAY", Qt::SkipEmptyParts).at(1);
if (!line.contains("/"))
return;
m_routeGateway = line.split("/", Qt::SkipEmptyParts).first();
m_routeGateway.replace(" ", "");
}
qDebug() << "Set VPN route gateway" << m_routeGateway;
}
@@ -282,7 +294,7 @@ void OpenVpnProtocol::onReadyReadDataFromManagementServer()
}
}
if (line.contains("ROUTE_GATEWAY")) {
if (line.contains("ROUTE_GATEWAY") || line.contains("net_route_v4_best_gw")) {
updateRouteGateway(line);
}
@@ -320,14 +332,28 @@ void OpenVpnProtocol::updateVpnGateway(const QString &line)
// line looks like
// PUSH: Received control message: 'PUSH_REPLY,route 10.8.0.1,topology net30,ping 10,ping-restart
// 120,ifconfig 10.8.0.6 10.8.0.5,peer-id 0,cipher AES-256-GCM'
QStringList params = line.split(",");
for (const QString &l : params) {
if (l.contains("ifconfig")) {
if (l.split(" ").size() == 3) {
m_vpnLocalAddress = l.split(" ").at(1);
m_vpnGateway = l.split(" ").at(2);
#ifdef Q_OS_WIN
QList<QNetworkInterface> netInterfaces = QNetworkInterface::allInterfaces();
for (int i = 0; i < netInterfaces.size(); i++) {
for (int j=0; j < netInterfaces.at(i).addressEntries().size(); j++)
{
if (m_vpnLocalAddress == netInterfaces.at(i).addressEntries().at(j).ip().toString()) {
IpcClient::Interface()->enableKillSwitch(QJsonObject(), netInterfaces.at(i).index());
m_configData.insert("vpnGateway", m_vpnGateway);
IpcClient::Interface()->enablePeerTraffic(m_configData);
}
}
}
#endif
#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
IpcClient::Interface()->enableKillSwitch(m_configData, 0);
#endif
qDebug() << QString("Set vpn local address %1, gw %2").arg(m_vpnLocalAddress).arg(vpnGateway());
}
}

View File

@@ -44,6 +44,7 @@ private:
ManagementServer m_managementServer;
QString m_configFileName;
QJsonObject m_configData;
QTemporaryFile m_configFile;
uint selectMgmtPort();

View File

@@ -13,9 +13,6 @@
WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *parent)
: VpnProtocol(configuration, parent)
{
m_configFile.setFileName(QDir::tempPath() + QDir::separator() + serviceName() + ".conf");
writeWireguardConfiguration(configuration);
m_impl.reset(new LocalSocketController());
connect(m_impl.get(), &ControllerImpl::connected, this,
[this](const QString &pubkey, const QDateTime &connectionTimestamp) {
@@ -50,45 +47,9 @@ ErrorCode WireguardProtocol::stopMzImpl()
return ErrorCode::NoError;
}
void WireguardProtocol::writeWireguardConfiguration(const QJsonObject &configuration)
{
QJsonObject jConfig = configuration.value(ProtocolProps::key_proto_config_data(Proto::WireGuard)).toObject();
if (!m_configFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
qCritical() << "Failed to save wireguard config to" << m_configFile.fileName();
return;
}
m_configFile.write(jConfig.value(config_key::config).toString().toUtf8());
m_configFile.close();
m_configFileName = m_configFile.fileName();
m_isConfigLoaded = true;
qDebug().noquote() << QString("Set config data") << configPath();
qDebug().noquote() << QString("Set config data")
<< configuration.value(ProtocolProps::key_proto_config_data(Proto::WireGuard)).toString().toUtf8();
}
QString WireguardProtocol::configPath() const
{
return m_configFileName;
}
QString WireguardProtocol::serviceName() const
{
return "AmneziaVPN.WireGuard0";
}
ErrorCode WireguardProtocol::start()
{
if (!m_isConfigLoaded) {
setLastError(ErrorCode::ConfigMissing);
return lastError();
}
return startMzImpl();
}

View File

@@ -26,15 +26,6 @@ public:
ErrorCode stopMzImpl();
private:
QString configPath() const;
void writeWireguardConfiguration(const QJsonObject &configuration);
QString serviceName() const;
private:
QString m_configFileName;
QFile m_configFile;
bool m_isConfigLoaded = false;
QScopedPointer<ControllerImpl> m_impl;
};

View File

@@ -216,6 +216,7 @@ QString Settings::nextAvailableServerName() const
void Settings::setSaveLogs(bool enabled)
{
setValue("Conf/saveLogs", enabled);
#ifndef Q_OS_ANDROID
if (!isSaveLogs()) {
Logger::deInit();
} else {
@@ -223,7 +224,8 @@ void Settings::setSaveLogs(bool enabled)
qWarning() << "Initialization of debug subsystem failed";
}
}
emit saveLogsChanged();
#endif
emit saveLogsChanged(enabled);
}
QString Settings::routeModeString(RouteMode mode) const

View File

@@ -190,7 +190,7 @@ public:
void clearSettings();
signals:
void saveLogsChanged();
void saveLogsChanged(bool enabled);
private:
QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const;

View File

@@ -379,7 +379,7 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="311"/>
<source>Save and Restart Amnezia</source>
<translation>Сохранить и пререзагрузить Amnezia</translation>
<translation>Сохранить и перезагрузить Amnezia</translation>
</message>
</context>
<context>
@@ -416,7 +416,7 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageProtocolOpenVpnSettings.qml" line="77"/>
<source>OpenVPN settings</source>
<translation>Настройки OpenVPN</translation>
<translation>OpenVPN настройки</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageProtocolOpenVpnSettings.qml" line="84"/>
@@ -1192,7 +1192,7 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageSettingsConnection.qml" line="86"/>
<source>If AmneziaDNS is not used or installed</source>
<translation>Эти серверы будут использоваться, если не включен AmneziaDNS</translation>
<translation>Эти адреса будут использоваться, если не включен AmneziaDNS</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsConnection.qml" line="101"/>
@@ -2047,7 +2047,7 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="598"/>
<source>Rename</source>
<translation type="unfinished"></translation>
<translation type="unfinished">Переименовать</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="627"/>
@@ -2062,12 +2062,12 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="668"/>
<source>Revoke</source>
<translation type="unfinished"></translation>
<translation type="unfinished">Отозвать</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="671"/>
<source>Revoke the config for a user - %1?</source>
<translation type="unfinished"></translation>
<translation type="unfinished">Отозвать доступ для пользователя - %1?</translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="672"/>

View File

@@ -327,7 +327,8 @@ void ExportController::updateClientManagementModel(const DockerContainer contain
void ExportController::revokeConfig(const int row, const DockerContainer container, ServerCredentials credentials)
{
ErrorCode errorCode = m_clientManagementModel->revokeClient(row, container, credentials);
ErrorCode errorCode = m_clientManagementModel->revokeClient(row, container, credentials,
m_serversModel->getCurrentlyProcessedServerIndex());
if (errorCode != ErrorCode::NoError) {
emit exportErrorOccurred(errorString(errorCode));
}

View File

@@ -239,7 +239,12 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data)
// && !configMap.value("PresharedKey").isEmpty() && !configMap.value("PublicKey").isEmpty()) {
lastConfig[config_key::client_priv_key] = configMap.value("PrivateKey");
lastConfig[config_key::client_ip] = configMap.value("Address");
lastConfig[config_key::psk_key] = configMap.value("PresharedKey");
if (!configMap.value("PresharedKey").isEmpty()) {
lastConfig[config_key::psk_key] = configMap.value("PresharedKey");
} else if (!configMap.value("PreSharedKey").isEmpty()) {
lastConfig[config_key::psk_key] = configMap.value("PreSharedKey");
}
lastConfig[config_key::server_pub_key] = configMap.value("PublicKey");
// } else {
// qDebug() << "Failed to import profile";

View File

@@ -8,6 +8,7 @@
#include "version.h"
#ifdef Q_OS_ANDROID
#include "platforms/android/android_utils.h"
#include "platforms/android/android_controller.h"
#include <QJniObject>
#endif
@@ -91,13 +92,21 @@ void SettingsController::openLogsFolder()
void SettingsController::exportLogsFile(const QString &fileName)
{
#ifdef Q_OS_ANDROID
AndroidController::instance()->exportLogsFile(fileName);
#else
SystemController::saveFile(fileName, Logger::getLogFile());
#endif
}
void SettingsController::clearLogs()
{
#ifdef Q_OS_ANDROID
AndroidController::instance()->clearLogs();
#else
Logger::clearLogs();
Logger::clearServiceLogs();
#endif
}
void SettingsController::backupAppConfig(const QString &fileName)
@@ -197,3 +206,14 @@ void SettingsController::toggleScreenshotsEnabled(bool enable)
});
#endif
}
bool SettingsController::isCameraPresent()
{
#if defined Q_OS_IOS
return true;
#elif defined Q_OS_ANDROID
return AndroidController::instance()->isCameraPresent();
#else
return false;
#endif
}

View File

@@ -59,6 +59,8 @@ public slots:
bool isScreenshotsEnabled();
void toggleScreenshotsEnabled(bool enable);
bool isCameraPresent();
signals:
void primaryDnsChanged();
void secondaryDnsChanged();

View File

@@ -245,8 +245,13 @@ ErrorCode ClientManagementModel::appendClient(const QString &clientId, const QSt
const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson();
ServerController serverController(m_settings);
const QString clientsTableFile =
QString("/opt/amnezia/%1/clientsTable").arg(ContainerProps::containerTypeToString(container));
QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable");
if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks
|| container == DockerContainer::Cloak) {
clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(DockerContainer::OpenVpn));
} else {
clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(container));
}
error = serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile);
if (error != ErrorCode::NoError) {
@@ -273,8 +278,13 @@ ErrorCode ClientManagementModel::renameClient(const int row, const QString &clie
const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson();
ServerController serverController(m_settings);
const QString clientsTableFile =
QString("/opt/amnezia/%1/clientsTable").arg(ContainerProps::containerTypeToString(container));
QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable");
if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks
|| container == DockerContainer::Cloak) {
clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(DockerContainer::OpenVpn));
} else {
clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(container));
}
ErrorCode error =
serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile);
@@ -286,30 +296,36 @@ ErrorCode ClientManagementModel::renameClient(const int row, const QString &clie
}
ErrorCode ClientManagementModel::revokeClient(const int row, const DockerContainer container,
ServerCredentials credentials)
ServerCredentials credentials, const int serverIndex)
{
ErrorCode errorCode = ErrorCode::NoError;
auto client = m_clientsTable.at(row).toObject();
QString clientId = client.value(configKey::clientId).toString();
if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks
|| container == DockerContainer::Cloak) {
errorCode = revokeOpenVpn(row, container, credentials);
errorCode = revokeOpenVpn(row, container, credentials, serverIndex);
} else if (container == DockerContainer::WireGuard || container == DockerContainer::Awg) {
errorCode = revokeWireGuard(row, container, credentials);
}
if (errorCode == ErrorCode::NoError) {
auto client = m_clientsTable.at(row).toObject();
QString clientId = client.value(configKey::clientId).toString();
const auto server = m_settings->defaultServer();
const auto server = m_settings->server(serverIndex);
QJsonArray containers = server.value(config_key::containers).toArray();
for (auto i = 0; i < containers.size(); i++) {
auto containerConfig = containers.at(i).toObject();
auto containerType = ContainerProps::containerFromString(containerConfig.value(config_key::container).toString());
auto protocolConfig = containerConfig.value(ContainerProps::containerTypeToString(containerType)).toObject();
if (containerType == container) {
QJsonObject protocolConfig;
if (container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) {
protocolConfig = containerConfig.value(ContainerProps::containerTypeToString(DockerContainer::OpenVpn)).toObject();
} else {
protocolConfig = containerConfig.value(ContainerProps::containerTypeToString(containerType)).toObject();
}
if (protocolConfig.value(config_key::last_config).toString().contains(clientId)) {
emit adminConfigRevoked(container);
if (protocolConfig.value(config_key::last_config).toString().contains(clientId)) {
emit adminConfigRevoked(container);
}
}
}
}
@@ -318,7 +334,7 @@ ErrorCode ClientManagementModel::revokeClient(const int row, const DockerContain
}
ErrorCode ClientManagementModel::revokeOpenVpn(const int row, const DockerContainer container,
ServerCredentials credentials)
ServerCredentials credentials, const int serverIndex)
{
auto client = m_clientsTable.at(row).toObject();
QString clientId = client.value(configKey::clientId).toString();
@@ -327,6 +343,7 @@ ErrorCode ClientManagementModel::revokeOpenVpn(const int row, const DockerContai
"cd /opt/amnezia/openvpn ;\\"
"easyrsa revoke %1 ;\\"
"easyrsa gen-crl ;\\"
"chmod 666 pki/crl.pem ;\\"
"cp pki/crl.pem .'")
.arg(clientId);
@@ -345,8 +362,8 @@ ErrorCode ClientManagementModel::revokeOpenVpn(const int row, const DockerContai
const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson();
const QString clientsTableFile =
QString("/opt/amnezia/%1/clientsTable").arg(ContainerProps::containerTypeToString(container));
QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable");
clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(DockerContainer::OpenVpn));
error = serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile);
if (error != ErrorCode::NoError) {
logger.error() << "Failed to upload the clientsTable file to the server";
@@ -395,8 +412,13 @@ ErrorCode ClientManagementModel::revokeWireGuard(const int row, const DockerCont
const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson();
const QString clientsTableFile =
QString("/opt/amnezia/%1/clientsTable").arg(ContainerProps::containerTypeToString(container));
QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable");
if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks
|| container == DockerContainer::Cloak) {
clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(DockerContainer::OpenVpn));
} else {
clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(container));
}
error = serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile);
if (error != ErrorCode::NoError) {
logger.error() << "Failed to upload the clientsTable file to the server";

View File

@@ -28,7 +28,7 @@ public slots:
ServerCredentials credentials);
ErrorCode renameClient(const int row, const QString &userName, const DockerContainer container,
ServerCredentials credentials, bool addTimeStamp = false);
ErrorCode revokeClient(const int index, const DockerContainer container, ServerCredentials credentials);
ErrorCode revokeClient(const int index, const DockerContainer container, ServerCredentials credentials, const int serverIndex);
protected:
QHash<int, QByteArray> roleNames() const override;
@@ -41,7 +41,7 @@ private:
void migration(const QByteArray &clientsTableString);
ErrorCode revokeOpenVpn(const int row, const DockerContainer container, ServerCredentials credentials);
ErrorCode revokeOpenVpn(const int row, const DockerContainer container, ServerCredentials credentials, const int serverIndex);
ErrorCode revokeWireGuard(const int row, const DockerContainer container, ServerCredentials credentials);
ErrorCode getOpenVpnClients(ServerController &serverController, DockerContainer container, ServerCredentials credentials, int &count);

View File

@@ -1,4 +1,4 @@
#include "ikev2ConfigModel.h".h "
#include "ikev2ConfigModel.h"
#include "protocols/protocols_defs.h"

View File

@@ -251,10 +251,9 @@ void ServersModel::addServer(const QJsonObject &server)
void ServersModel::editServer(const QJsonObject &server)
{
beginResetModel();
m_settings->editServer(m_currentlyProcessedServerIndex, server);
m_servers = m_settings->serversArray();
endResetModel();
m_servers.replace(m_currentlyProcessedServerIndex, m_settings->serversArray().at(m_currentlyProcessedServerIndex));
emit dataChanged(index(m_currentlyProcessedServerIndex, 0), index(m_currentlyProcessedServerIndex, 0));
updateContainersModel();
}
@@ -531,3 +530,8 @@ void ServersModel::toggleAmneziaDns(bool enabled)
emit defaultServerDescriptionChanged();
}
bool ServersModel::isDefaultServerFromApi()
{
return m_settings->server(m_defaultServerIndex).value(config_key::configVersion).toInt();
}

View File

@@ -97,6 +97,8 @@ public slots:
void toggleAmneziaDns(bool enabled);
bool isDefaultServerFromApi();
protected:
QHash<int, QByteArray> roleNames() const override;

View File

@@ -124,8 +124,13 @@ void Autostart::setAutostart(bool autostart) {
if (file.open(QIODevice::ReadWrite)) {
QTextStream stream(&file);
stream << "[Desktop Entry]" << Qt::endl;
stream << "Exec=" << appPath() << Qt::endl;
stream << "Exec=AmneziaVPN" << Qt::endl;
stream << "Type=Application" << Qt::endl;
stream << "Name=AmneziaVPN" << Qt::endl;
stream << "Comment=Client of your self-hosted VPN" << Qt::endl;
stream << "Icon=/usr/share/pixmaps/AmneziaVPN.png" << Qt::endl;
stream << "Categories=Network;Qt;Security;" << Qt::endl;
stream << "Terminal=false" << Qt::endl;
}
}
}

View File

@@ -30,7 +30,9 @@ ListView {
target: ServersModel
function onCurrentlyProcessedServerIndexChanged() {
menuContent.checkCurrentItem()
if (ContainersModel.getDefaultContainer()) {
menuContent.checkCurrentItem()
}
}
}
@@ -76,9 +78,8 @@ ListView {
}
if (checked) {
ServersModel.setDefaultContainer(proxyContainersModel.mapToSource(index))
containersDropDown.menuVisible = false
ServersModel.setDefaultContainer(proxyContainersModel.mapToSource(index))
} else {
if (!isSupported && isInstalled) {
PageController.showErrorMessage(qsTr("The selected protocol is not supported on the current platform"))

View File

@@ -25,7 +25,7 @@ DrawerType {
property string configExtension: ".vpn"
property string configCaption: qsTr("Save AmneziaVPN config")
property string configFileName: "amnezia_config.vpn"
property string configFileName: "amnezia_config"
width: parent.width
height: parent.height * 0.9

View File

@@ -81,7 +81,7 @@ PageType {
Layout.fillWidth: true
Layout.topMargin: 32
headerText: qsTr("VPN Addresses Subnet")
headerText: qsTr("VPN address subnet")
textFieldText: subnetAddress
textField.onEditingFinished: {

View File

@@ -91,7 +91,7 @@ PageType {
onLinkActivated: Qt.openUrlExternally(link)
textFormat: Text.RichText
text: qsTr("Use <a href=\"https://www.torproject.org/download/\" style=\"color: #FBB26A;\">Tor Browser</a> to open this url.")
text: qsTr("Use <a href=\"https://www.torproject.org/download/\" style=\"color: #FBB26A;\">Tor Browser</a> to open this URL.")
}
ParagraphTextType {
@@ -100,7 +100,7 @@ PageType {
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("After installation it takes several minutes while your onion site will become available in the Tor Network.")
text: qsTr("After creating your onion site, it takes a few minutes for the Tor network to make it available for use.")
}
ParagraphTextType {

View File

@@ -53,7 +53,7 @@ PageType {
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("Support the project with a donation")
text: qsTr("Support Amnezia")
horizontalAlignment: Text.AlignHCenter
}

View File

@@ -83,7 +83,7 @@ PageType {
Layout.fillWidth: true
text: qsTr("DNS servers")
descriptionText: qsTr("If AmneziaDNS is not used or installed")
descriptionText: qsTr("When AmneziaDNS is not used or installed")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() {
@@ -117,7 +117,7 @@ PageType {
Layout.fillWidth: true
text: qsTr("App-based split tunneling")
descriptionText: qsTr("Allows you to use the VPN only for certain applications")
descriptionText: qsTr("Allows you to use the VPN only for certain Apps")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() {

View File

@@ -28,6 +28,14 @@ PageType {
anchors.bottom: parent.bottom
contentHeight: content.height
enabled: !ServersModel.isDefaultServerFromApi()
Component.onCompleted: {
if (ServersModel.isDefaultServerFromApi()) {
PageController.showNotificationMessage(qsTr("Default server does not support custom dns"))
}
}
ColumnLayout {
id: content

View File

@@ -66,7 +66,8 @@ PageType {
ColumnLayout {
Layout.alignment: Qt.AlignBaseline
Layout.preferredWidth: root.width / 3
Layout.preferredWidth: GC.isMobile() ? 0 : root.width / 3
visible: !GC.isMobile()
ImageButtonType {
Layout.alignment: Qt.AlignHCenter
@@ -90,7 +91,7 @@ PageType {
ColumnLayout {
Layout.alignment: Qt.AlignBaseline
Layout.preferredWidth: root.width / 3
Layout.preferredWidth: root.width / ( GC.isMobile() ? 2 : 3 )
ImageButtonType {
Layout.alignment: Qt.AlignHCenter
@@ -131,7 +132,7 @@ PageType {
ColumnLayout {
Layout.alignment: Qt.AlignBaseline
Layout.preferredWidth: root.width / 3
Layout.preferredWidth: root.width / ( GC.isMobile() ? 2 : 3 )
ImageButtonType {
Layout.alignment: Qt.AlignHCenter

View File

@@ -55,6 +55,9 @@ PageType {
model: SortFilterProxyModel {
id: proxyContainersModel
sourceModel: ContainersModel
sorters: [
RoleSorter { roleName: "isInstalled"; sortOrder: Qt.DescendingOrder }
]
}
Component.onCompleted: updateContainersModelFilters()

View File

@@ -21,7 +21,13 @@ PageType {
id: root
property bool pageEnabled: {
return !ConnectionController.isConnected
return !ConnectionController.isConnected && !ServersModel.isDefaultServerFromApi()
}
Component.onCompleted: {
if (ServersModel.isDefaultServerFromApi()) {
PageController.showNotificationMessage(qsTr("Default server does not support split tunneling function"))
}
}
Connections {
@@ -50,7 +56,7 @@ PageType {
QtObject {
id: onlyForwardSites
property string name: qsTr("Addresses from the list should be accessed via VPN")
property string name: qsTr("Only the sites listed here will be accessed through the VPN")
property int type: routeMode.onlyForwardSites
}
QtObject {
@@ -245,7 +251,7 @@ PageType {
TextFieldWithHeaderType {
Layout.fillWidth: true
textFieldPlaceholderText: qsTr("Site or IP")
textFieldPlaceholderText: qsTr("website or IP")
buttonImageSource: "qrc:/images/controls/plus.svg"
clickedFunc: function() {
@@ -289,7 +295,7 @@ PageType {
Layout.fillWidth: true
Layout.margins: 16
headerText: qsTr("Import/Export Sites")
headerText: qsTr("Import / Export Sites")
}
LabelWithButtonType {

View File

@@ -90,7 +90,7 @@ It's okay as long as it's from someone you trust.")
LabelWithButtonType {
Layout.fillWidth: true
visible: GC.isMobile()
visible: SettingsController.isCameraPresent()
text: qsTr("QR-code")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
@@ -105,7 +105,7 @@ It's okay as long as it's from someone you trust.")
}
DividerType {
visible: GC.isMobile()
visible: SettingsController.isCameraPresent()
}
LabelWithButtonType {

View File

@@ -73,7 +73,7 @@ PageType {
property bool hidePassword: true
Layout.fillWidth: true
headerText: qsTr("Password / SSH private key")
headerText: qsTr("Password or SSH private key")
textField.echoMode: hidePassword ? TextInput.Password : TextInput.Normal
buttonImageSource: textFieldText !== "" ? (hidePassword ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg")
: ""

View File

@@ -112,17 +112,19 @@ PageType {
var defaultContainerProto = ContainerProps.defaultProtocol(dockerContainer)
containers.dockerContainer = dockerContainer
containers.containerDefaultPort = ProtocolProps.defaultPort(defaultContainerProto)
containers.containerDefaultPort = ProtocolProps.getPortForInstall(defaultContainerProto)
containers.containerDefaultTransportProto = ProtocolProps.defaultTransportProto(defaultContainerProto)
}
}
}
}
Component.onCompleted: {
if (index === containers.currentIndex) {
card.checked = true
card.clicked()
}
Component.onCompleted: {
var item = containers.itemAtIndex(containers.currentIndex)
if (item !== null) {
var button = item.children[0].children[0]
button.checked = true
button.clicked()
}
}
}

View File

@@ -64,27 +64,26 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state)
IpcClient::Interface()->resetIpStack();
IpcClient::Interface()->flushDns();
if (m_settings->routeMode() != Settings::VpnAllSites) {
qDebug() << "1";
IpcClient::Interface()->routeDeleteList(m_vpnProtocol->vpnGateway(), QStringList() << "0.0.0.0");
// qDebug() << "VpnConnection::onConnectionStateChanged :: adding custom routes, count:" << forwardIps.size();
}
QString dns1 = m_vpnConfiguration.value(config_key::dns1).toString();
QString dns2 = m_vpnConfiguration.value(config_key::dns2).toString();
if (!m_vpnConfiguration.value(config_key::configVersion).toInt()) {
if (m_settings->routeMode() != Settings::VpnAllSites) {
IpcClient::Interface()->routeDeleteList(m_vpnProtocol->vpnGateway(), QStringList() << "0.0.0.0");
// qDebug() << "VpnConnection::onConnectionStateChanged :: adding custom routes, count:" << forwardIps.size();
}
QString dns1 = m_vpnConfiguration.value(config_key::dns1).toString();
QString dns2 = m_vpnConfiguration.value(config_key::dns2).toString();
IpcClient::Interface()->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << dns1 << dns2);
IpcClient::Interface()->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << dns1 << dns2);
if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
qDebug() << "2";
QTimer::singleShot(1000, m_vpnProtocol.data(),
[this]() { addSitesRoutes(m_vpnProtocol->vpnGateway(), m_settings->routeMode()); });
} else if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
qDebug() << "3";
IpcClient::Interface()->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << "0.0.0.0/1");
IpcClient::Interface()->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << "128.0.0.0/1");
if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
QTimer::singleShot(1000, m_vpnProtocol.data(),
[this]() { addSitesRoutes(m_vpnProtocol->vpnGateway(), m_settings->routeMode()); });
} else if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
IpcClient::Interface()->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << "0.0.0.0/1");
IpcClient::Interface()->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << "128.0.0.0/1");
IpcClient::Interface()->routeAddList(m_vpnProtocol->routeGateway(), QStringList() << remoteAddress());
addSitesRoutes(m_vpnProtocol->routeGateway(), m_settings->routeMode());
IpcClient::Interface()->routeAddList(m_vpnProtocol->routeGateway(), QStringList() << remoteAddress());
addSitesRoutes(m_vpnProtocol->routeGateway(), m_settings->routeMode());
}
}
} else if (state == Vpn::ConnectionState::Error) {
@@ -254,10 +253,13 @@ QString VpnConnection::createVpnConfigurationForProto(int serverIndex, const Ser
m_settings->setProtocolConfig(serverIndex, container, proto, protoObject);
}
QEventLoop wait;
emit m_configurator->newVpnConfigCreated(clientId, QString("Admin [%1]").arg(QSysInfo::prettyProductName()), container, credentials);
QObject::connect(m_configurator.get(), &VpnConfigurator::clientModelUpdated, &wait, &QEventLoop::quit);
wait.exec();
if ((container != DockerContainer::Cloak && container != DockerContainer::ShadowSocks) ||
((container == DockerContainer::Cloak || container == DockerContainer::ShadowSocks) && proto == Proto::OpenVpn)) {
QEventLoop wait;
emit m_configurator->newVpnConfigCreated(clientId, QString("Admin [%1]").arg(QSysInfo::prettyProductName()), container, credentials);
QObject::connect(m_configurator.get(), &VpnConfigurator::clientModelUpdated, &wait, &QEventLoop::quit);
wait.exec();
}
}
return configData;
@@ -299,6 +301,7 @@ QJsonObject VpnConnection::createVpnConfiguration(int serverIndex, const ServerC
vpnConfiguration[config_key::hostName] = server.value(config_key::hostName).toString();
vpnConfiguration[config_key::description] = server.value(config_key::description).toString();
vpnConfiguration[config_key::configVersion] = server.value(config_key::configVersion).toInt();
// TODO: try to get hostName, port, description for 3rd party configs
// vpnConfiguration[config_key::port] = ...;

View File

@@ -7,15 +7,18 @@ usage() {
cat <<EOT
Usage:
build_android [options]
build_android [options] <artifact_types>
Build AmneziaVPN android client. By default, a signed Android App Bundle (AAB) is built.
Build AmneziaVPN android client.
Options:
-d, --debug Build debug version
Artifact types:
-u, --aab Build Android App Bundle (AAB)
-a, --apk (<abi_list> | all) Build APKs for the specified ABIs or for all available ABIs
Available ABIs: 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a'
<abi_list> - list of ABIs delimited by ';'
Options:
-d, --debug Build debug version
-b, --build-platform <platform> The SDK platform used for building the Java code of the application
By default, the latest available platform is used
-m, --move Move the build result to the root of the build directory
@@ -25,14 +28,14 @@ EOT
}
BUILD_TYPE="release"
AAB=1
opts=$(getopt -l debug,apk:,build-platform:,move,help -o "da:b:mh" -- "$@")
opts=$(getopt -l debug,aab,apk:,build-platform:,move,help -o "dua:b:mh" -- "$@")
eval set -- "$opts"
while true; do
case "$1" in
-d | --debug) BUILD_TYPE="debug"; shift;;
-a | --apk) ABIS=$2; unset AAB; shift 2;;
-u | --aab) AAB=1; shift;;
-a | --apk) ABIS=$2; shift 2;;
-b | --build-platform) ANDROID_BUILD_PLATFORM=$2; shift 2;;
-m | --move) MOVE_RESULT=1; shift;;
-h | --help) usage; exit 0;;
@@ -49,6 +52,11 @@ if [[ -v ABIS && \
exit 1
fi
# At least one artifact type must be specified
if [[ ! (-v AAB || -v ABIS) ]]; then
usage; exit 0
fi
echo "Build script started..."
PROJECT_DIR=$(pwd)
@@ -137,7 +145,8 @@ gradle_opts=()
if [ -v AAB ]; then
gradle_opts+=(bundle"${BUILD_TYPE^}")
else
fi
if [ -v ABIS ]; then
gradle_opts+=(assemble"${BUILD_TYPE^}")
fi
@@ -151,7 +160,9 @@ if [[ -v CI || -v MOVE_RESULT ]]; then
if [ -v AAB ]; then
mv -u $OUT_APP_DIR/android-build/build/outputs/bundle/$BUILD_TYPE/AmneziaVPN-$BUILD_TYPE.aab \
$PROJECT_DIR/deploy/build/
else
fi
if [ -v ABIS ]; then
if [ "$ABIS" = "all" ]; then
ABIS="x86;x86_64;armeabi-v7a;arm64-v8a"
fi

View File

@@ -83,6 +83,4 @@ ldd $CQTDEPLOYER_DIR/bin/binarycreator
cp -r $PROJECT_DIR/deploy/installer $BUILD_DIR
$CQTDEPLOYER_DIR/binarycreator.sh --offline-only -v -c $BUILD_DIR/installer/config/linux.xml -p $BUILD_DIR/installer/packages -f $PROJECT_DIR/deploy/AmneziaVPN_Linux_Installer
$CQTDEPLOYER_DIR/binarycreator.sh --offline-only -v -c $BUILD_DIR/installer/config/linux.xml -p $BUILD_DIR/installer/packages -f $PROJECT_DIR/deploy/AmneziaVPN_Linux_Installer.bin

View File

@@ -0,0 +1,3 @@
# Always allow at least loopback/localhost traffic
set skip on lo0
pass quick on lo0 flags any

View File

@@ -0,0 +1,3 @@
# Block all traffic by default (can be overridden by later rules)
block out all flags any no state

View File

@@ -0,0 +1,2 @@
table <allownets> {}
pass out to <allownets> flags any no state

View File

@@ -0,0 +1,2 @@
table <blocknets> {}
block out to <blocknets> flags any no state

View File

@@ -0,0 +1,2 @@
# Rules are set at runtime

View File

@@ -0,0 +1,9 @@
# Exempt the tunnel interface(s) used by the VPN connection
utunInterfaces = "{ \
utun0, utun1, utun2, utun3, utun4, utun5, utun6, utun7, utun8, utun9, utun10, \
utun11, utun12, utun13, utun14, utun15, utun16, utun17, utun18, utun19, utun20, \
utun21, utun22, utun23, utun24, utun25, utun26, utun27, utun28, utun29, utun30 \
}"
pass out on $utunInterfaces flags any no state

View File

@@ -0,0 +1,2 @@
# Block all outgoing IPv6 traffic (even over the VPN)
block return out inet6 flags any no state

View File

@@ -0,0 +1,5 @@
# Allow DHCP
pass out inet proto udp from port 68 to 255.255.255.255 port 67 no state
# Allow DHCPv6
pass out inet6 proto udp from port 546 to ff00::/8 port 547 no state

View File

@@ -0,0 +1,3 @@
# Allow LAN IP ranges
table <lanips> { 10.0.0.0/8, 169.254.0.0/16, 172.16.0.0/12, 192.168.0.0/16, 224.0.0.0/4, 255.255.255.255/32, fc00::/7, fe80::/10, ff00::/8 }
pass out to <lanips> flags any no state

View File

@@ -0,0 +1,7 @@
# Block all DNS traffic
block return out proto { tcp, udp } to port 53 flags any no state
# Allow our DNS servers
table <dnsaddr> {}
pass out proto { tcp, udp } to <dnsaddr> port 53 flags any no state

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