mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-22 02:01:08 +07:00
Compare commits
203 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fdf4b8a5bd | |||
| 7da9539f2a | |||
| 12083c098a | |||
| dfdb89d7a9 | |||
| 477d7214c5 | |||
| 245aa8eb8c | |||
| f14a2add0f | |||
| 89703ba58f | |||
| 23715fca8b | |||
| d90685600e | |||
| f007e5eb5c | |||
| a8ccea00c7 | |||
| cd2ee00769 | |||
| c98a418807 | |||
| 0e4ae26bae | |||
| d50e7dd3f4 | |||
| f0085f52eb | |||
| 5c19b08e5e | |||
| 79edbe52a3 | |||
| 0dd181bb5b | |||
| d8682003fa | |||
| f4a2cf9984 | |||
| 98e6358fd3 | |||
| af90065d2e | |||
| f372f4074b | |||
| 6a2e5f83a1 | |||
| a2badd46c4 | |||
| 8623a983b8 | |||
| 151e662027 | |||
| f588fe29db | |||
| 030b0351a2 | |||
| d4453a5f38 | |||
| 2252905596 | |||
| ec650a65f7 | |||
| 6953f8d814 | |||
| 624a84cbfb | |||
| 506d9793e1 | |||
| ef52f6ab08 | |||
| 5312a6e885 | |||
| fdd600794e | |||
| 7bfbdca72a | |||
| a22c08a41d | |||
| 10ea9b418a | |||
| e39efb1d68 | |||
| 7db84122f9 | |||
| 84ad167ab4 | |||
| ed7e217a6b | |||
| c1b0d4a4a7 | |||
| 2f84e24353 | |||
| f73586185b | |||
| 653ffb9a68 | |||
| fe8c2d157a | |||
| 86367a1276 | |||
| b0fcf92ada | |||
| 283b6ebf81 | |||
| d0a7fc5116 | |||
| 9851aacba7 | |||
| 51f9fb9e0a | |||
| 0325761f3e | |||
| a6ca1b12da | |||
| 82a9e7e27d | |||
| f5301e1315 | |||
| adab30fc81 | |||
| e7bd24f065 | |||
| 2ec448ba13 | |||
| c6e6f2ae84 | |||
| e9468a4c2f | |||
| 45de951897 | |||
| db8d966fac | |||
| 6b69bc9618 | |||
| 0089b0b799 | |||
| e4841e809b | |||
| ba4237f1dd | |||
| f6acec53c0 | |||
| 5f631eaa76 | |||
| 7730dd510c | |||
| 30bd264f17 | |||
| 5206665fa0 | |||
| 073491ccb4 | |||
| 561b62cd40 | |||
| 1284ed4d84 | |||
| 6f34443191 | |||
| 02f186c54e | |||
| 784c6cf585 | |||
| 14f132e127 | |||
| 9cb624e681 | |||
| 516e3da7e2 | |||
| 0e83586cae | |||
| 95bdae68f4 | |||
| 294778884b | |||
| 10caecbffd | |||
| 553a6a73dd | |||
| e646b85e56 | |||
| b7c513c05f | |||
| 9f82b4c21f | |||
| 02b2da38cf | |||
| f51077b2be | |||
| 33f49bfddb | |||
| 9a81f13f81 | |||
| 915fb6759a | |||
| c5a5bfde69 | |||
| 0a90fd110d | |||
| 541d6eb0b8 | |||
| d443a0063d | |||
| f0c6edb670 | |||
| 9189b53a0d | |||
| fceccaefcc | |||
| fbeabf43ca | |||
| 78c7893f90 | |||
| cb9a25006c | |||
| 0e87550d85 | |||
| dceb0ab832 | |||
| a33590476a | |||
| deaf618520 | |||
| 3d8a56d922 | |||
| 36af7cf471 | |||
| ebd3449b4a | |||
| 99182f4a67 | |||
| da84ba1a4d | |||
| bca68fc185 | |||
| 59a7265bac | |||
| 9201ca1e03 | |||
| 6b6a76d2cc | |||
| 840c388ab9 | |||
| 5b4ec608c8 | |||
| 79ff1b81e0 | |||
| ea67c01da8 | |||
| 1137e169ea | |||
| 17748cca47 | |||
| 080e1d98c6 | |||
| ca633ae882 | |||
| bb7b64fb96 | |||
| bf901631bf | |||
| 0c0ce54b1f | |||
| ee762c4cef | |||
| ed9efb5a79 | |||
| 73eb85f2f4 | |||
| cd055cff62 | |||
| f8b2cce618 | |||
| e648054c7a | |||
| fe558163cc | |||
| 3883b8ff34 | |||
| d286664763 | |||
| b05ad2392b | |||
| 6dbdb85aaf | |||
| 26b48cfe4f | |||
| 2f39136143 | |||
| 8d0d3c5ce9 | |||
| 256081e4ed | |||
| 1dd7b0a221 | |||
| 82c0b28906 | |||
| 985fe083f0 | |||
| 6a0000dc4b | |||
| 1dd2f38066 | |||
| 004e1e3ca5 | |||
| 7c560d709b | |||
| d3743ad62f | |||
| ac234b77e2 | |||
| 9886987e68 | |||
| d34cb8898f | |||
| 13aadbda64 | |||
| c7c7c8eb01 | |||
| b1e5bba33f | |||
| 474e7c6d62 | |||
| 794ec921b8 | |||
| b674240362 | |||
| a768c7c451 | |||
| 28d2a4ec2c | |||
| 9f1210d18f | |||
| 3012559627 | |||
| b3ed57aee7 | |||
| 89d0a8107d | |||
| 6c0b71bd1b | |||
| 61abf74b2d | |||
| 21fdf02921 | |||
| 7a245d80ee | |||
| da85922f23 | |||
| a5356b6319 | |||
| 3828891b9b | |||
| 15d866ce04 | |||
| 560eb3d620 | |||
| ac894254cc | |||
| 17e3fbde25 | |||
| ee11a8410c | |||
| ff5c51cfd9 | |||
| b3943ae5e3 | |||
| a32952fde6 | |||
| 9c4ee4014d | |||
| dc9069f1f4 | |||
| e402cacc05 | |||
| a98cd248d6 | |||
| 00fbfb6a01 | |||
| 86c31c3766 | |||
| 698cfe910c | |||
| 16db23c159 | |||
| b05a5ee1c6 | |||
| 8cb298937f | |||
| 68fe20ddf6 | |||
| fab167bb34 | |||
| f640d4b5f5 | |||
| 074562b141 | |||
| fd030a5fd4 | |||
| 82fa6b13c6 |
@@ -14,8 +14,8 @@ jobs:
|
|||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-20.04
|
||||||
|
|
||||||
env:
|
env:
|
||||||
QT_VERSION: 6.5.1
|
QT_VERSION: 6.6.2
|
||||||
QIF_VERSION: 4.6
|
QIF_VERSION: 4.7
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: 'Install Qt'
|
- name: 'Install Qt'
|
||||||
@@ -72,8 +72,8 @@ jobs:
|
|||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
|
|
||||||
env:
|
env:
|
||||||
QT_VERSION: 6.5.1
|
QT_VERSION: 6.6.2
|
||||||
QIF_VERSION: 4.6
|
QIF_VERSION: 4.7
|
||||||
BUILD_ARCH: 64
|
BUILD_ARCH: 64
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -134,7 +134,7 @@ jobs:
|
|||||||
runs-on: macos-13
|
runs-on: macos-13
|
||||||
|
|
||||||
env:
|
env:
|
||||||
QT_VERSION: 6.5.2
|
QT_VERSION: 6.6.2
|
||||||
CC: cc
|
CC: cc
|
||||||
CXX: c++
|
CXX: c++
|
||||||
|
|
||||||
@@ -233,7 +233,7 @@ jobs:
|
|||||||
- name: 'Setup xcode'
|
- name: 'Setup xcode'
|
||||||
uses: maxim-lobanov/setup-xcode@v1
|
uses: maxim-lobanov/setup-xcode@v1
|
||||||
with:
|
with:
|
||||||
xcode-version: '13.4'
|
xcode-version: '14.3.1'
|
||||||
|
|
||||||
- name: 'Install Qt'
|
- name: 'Install Qt'
|
||||||
uses: jurplel/install-qt-action@v3
|
uses: jurplel/install-qt-action@v3
|
||||||
@@ -245,10 +245,15 @@ jobs:
|
|||||||
modules: 'qtremoteobjects qt5compat qtshadertools'
|
modules: 'qtremoteobjects qt5compat qtshadertools'
|
||||||
dir: ${{ runner.temp }}
|
dir: ${{ runner.temp }}
|
||||||
setup-python: 'true'
|
setup-python: 'true'
|
||||||
tools: 'tools_ifw'
|
|
||||||
set-env: 'true'
|
set-env: 'true'
|
||||||
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
|
||||||
|
|
||||||
|
- name: 'Install Qt Installer Framework ${{ env.QIF_VERSION }}'
|
||||||
|
run: |
|
||||||
|
mkdir -pv ${{ runner.temp }}/Qt/Tools/QtInstallerFramework
|
||||||
|
wget https://qt.amzsvc.com/tools/ifw/${{ env.QIF_VERSION }}.zip
|
||||||
|
unzip ${{ env.QIF_VERSION }}.zip -d ${{ runner.temp }}/Qt/Tools/QtInstallerFramework/
|
||||||
|
|
||||||
- name: 'Get sources'
|
- name: 'Get sources'
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
@@ -286,7 +291,7 @@ jobs:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
ANDROID_BUILD_PLATFORM: android-34
|
ANDROID_BUILD_PLATFORM: android-34
|
||||||
QT_VERSION: 6.6.1
|
QT_VERSION: 6.6.2
|
||||||
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -131,3 +131,6 @@ client/3rd/ShadowSocks/ss_ios.xcconfig
|
|||||||
|
|
||||||
# UML generated pics
|
# UML generated pics
|
||||||
out/
|
out/
|
||||||
|
|
||||||
|
# CMake files
|
||||||
|
CMakeFiles/
|
||||||
+2
-2
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
|||||||
|
|
||||||
set(PROJECT AmneziaVPN)
|
set(PROJECT AmneziaVPN)
|
||||||
|
|
||||||
project(${PROJECT} VERSION 4.4.0.0
|
project(${PROJECT} VERSION 4.5.3.0
|
||||||
DESCRIPTION "AmneziaVPN"
|
DESCRIPTION "AmneziaVPN"
|
||||||
HOMEPAGE_URL "https://amnezia.org/"
|
HOMEPAGE_URL "https://amnezia.org/"
|
||||||
)
|
)
|
||||||
@@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
|
|||||||
set(RELEASE_DATE "${CURRENT_DATE}")
|
set(RELEASE_DATE "${CURRENT_DATE}")
|
||||||
|
|
||||||
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||||
set(APP_ANDROID_VERSION_CODE 45)
|
set(APP_ANDROID_VERSION_CODE 52)
|
||||||
|
|
||||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
set(MZ_PLATFORM_NAME "linux")
|
set(MZ_PLATFORM_NAME "linux")
|
||||||
|
|||||||
@@ -7,13 +7,15 @@
|
|||||||
Amnezia is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
|
Amnezia is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
- Very easy to use - enter your ip address, ssh login and password, and Amnezia will automatically install VPN docker containers to your server and connect to VPN.
|
|
||||||
- OpenVPN, ShadowSocks, WireGuard, IKEv2 protocols support.
|
- Very easy to use - enter your IP address, SSH login, and password, and Amnezia will automatically install VPN docker containers to your server and connect to the VPN.
|
||||||
|
- OpenVPN, ShadowSocks, WireGuard, and IKEv2 protocols support.
|
||||||
- Masking VPN with OpenVPN over Cloak plugin
|
- Masking VPN with OpenVPN over Cloak plugin
|
||||||
- Split tunneling support - add any sites to client to enable VPN only for them (only for desktops)
|
- Split tunneling support - add any sites to the client to enable VPN only for them (only for desktops)
|
||||||
- Windows, MacOS, Linux, Android, iOS releases.
|
- Windows, MacOS, Linux, Android, iOS releases.
|
||||||
|
|
||||||
## Links
|
## Links
|
||||||
|
|
||||||
[https://amnezia.org](https://amnezia.org) - project website
|
[https://amnezia.org](https://amnezia.org) - project website
|
||||||
[https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit
|
[https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit
|
||||||
[https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Telegram support channel (English)
|
[https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Telegram support channel (English)
|
||||||
@@ -21,13 +23,13 @@ Amnezia is an open-source VPN client, with a key feature that enables you to dep
|
|||||||
|
|
||||||
## Tech
|
## Tech
|
||||||
|
|
||||||
AmneziaVPN uses a number of open source projects to work:
|
AmneziaVPN uses several open-source projects to work:
|
||||||
|
|
||||||
- [OpenSSL](https://www.openssl.org/)
|
- [OpenSSL](https://www.openssl.org/)
|
||||||
- [OpenVPN](https://openvpn.net/)
|
- [OpenVPN](https://openvpn.net/)
|
||||||
- [ShadowSocks](https://shadowsocks.org/)
|
- [ShadowSocks](https://shadowsocks.org/)
|
||||||
- [Qt](https://www.qt.io/)
|
- [Qt](https://www.qt.io/)
|
||||||
- [LibSsh](https://libssh.org) - forked form Qt Creator
|
- [LibSsh](https://libssh.org) - forked from Qt Creator
|
||||||
- and more...
|
- and more...
|
||||||
|
|
||||||
## Checking out the source code
|
## Checking out the source code
|
||||||
@@ -43,14 +45,15 @@ git submodule update --init --recursive
|
|||||||
Want to contribute? Welcome!
|
Want to contribute? Welcome!
|
||||||
|
|
||||||
### Building sources and deployment
|
### Building sources and deployment
|
||||||
Look deploy folder for build scripts.
|
|
||||||
|
|
||||||
### How to build iOS app from source code on MacOS
|
Check deploy folder for build scripts.
|
||||||
|
|
||||||
|
### How to build an iOS app from source code on MacOS
|
||||||
|
|
||||||
1. First, make sure you have [XCode](https://developer.apple.com/xcode/) installed, at least version 14 or higher.
|
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.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:
|
2. We use QT to generate the XCode project. We need QT version 6.6.1. Install QT for MacOS [here](https://doc.qt.io/qt-6/macos.html) or [QT Online Installer](https://www.qt.io/download-open-source). Required modules:
|
||||||
- macOS
|
- MacOS
|
||||||
- iOS
|
- iOS
|
||||||
- Qt 5 Compatibility Module
|
- Qt 5 Compatibility Module
|
||||||
- Qt Shader Tools
|
- Qt Shader Tools
|
||||||
@@ -59,18 +62,18 @@ Look deploy folder for build scripts.
|
|||||||
- Qt Multimedia
|
- Qt Multimedia
|
||||||
- Qt Remote Objects
|
- Qt Remote Objects
|
||||||
|
|
||||||
3. Install cmake is require. We recommend cmake version 3.25. You can install cmake in [here](https://cmake.org/download/)
|
3. Install CMake if required. We recommend CMake version 3.25. You can install CMake [here](https://cmake.org/download/)
|
||||||
|
|
||||||
4. You also need to install go >= v1.16. If you don't have it done already,
|
4. You also need to install go >= v1.16. If you don't have it installed already,
|
||||||
download go from the [official website](https://golang.org/dl/) or use Homebrew.
|
download go from the [official website](https://golang.org/dl/) or use Homebrew.
|
||||||
Latest version is recommended. Install gomobile
|
The latest version is recommended. Install gomobile
|
||||||
```bash
|
```bash
|
||||||
export PATH=$PATH:~/go/bin
|
export PATH=$PATH:~/go/bin
|
||||||
go install golang.org/x/mobile/cmd/gomobile@latest
|
go install golang.org/x/mobile/cmd/gomobile@latest
|
||||||
gomobile init
|
gomobile init
|
||||||
```
|
```
|
||||||
|
|
||||||
5. Build project
|
5. Build the project
|
||||||
```bash
|
```bash
|
||||||
export QT_BIN_DIR="<PATH-TO-QT-FOLDER>/Qt/<QT-VERSION>/ios/bin"
|
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_MACOS_ROOT_DIR="<PATH-TO-QT-FOLDER>/Qt/<QT-VERSION>/macos"
|
||||||
@@ -88,62 +91,63 @@ of the bin folder where gomobile was installed. Usually, it's in `GOPATH`.
|
|||||||
export PATH=$(PATH):/path/to/GOPATH/bin
|
export PATH=$(PATH):/path/to/GOPATH/bin
|
||||||
```
|
```
|
||||||
|
|
||||||
5. Open XCode project. You can then run/test/archive/ship the app.
|
6. Open the XCode project. You can then run /test/archive/ship the app.
|
||||||
|
|
||||||
If build fails with the following error
|
If the build fails with the following error
|
||||||
```
|
```
|
||||||
make: ***
|
make: ***
|
||||||
[$(PROJECTDIR)/client/build/AmneziaVPN.build/Debug-iphoneos/wireguard-go-bridge/goroot/.prepared]
|
[$(PROJECTDIR)/client/build/AmneziaVPN.build/Debug-iphoneos/wireguard-go-bridge/goroot/.prepared]
|
||||||
Error 1
|
Error 1
|
||||||
```
|
```
|
||||||
Add a user defined variable to both AmneziaVPN and WireGuardNetworkExtension targets' build settings with
|
Add a user-defined variable to both AmneziaVPN and WireGuardNetworkExtension targets' build settings with
|
||||||
key `PATH` and value `${PATH}/path/to/bin/folder/with/go/executable`, e.g. `${PATH}:/usr/local/go/bin`.
|
key `PATH` and value `${PATH}/path/to/bin/folder/with/go/executable`, e.g. `${PATH}:/usr/local/go/bin`.
|
||||||
|
|
||||||
if above error still persists on you M1 Mac, then most probably you need to install arch based cmake
|
if the above error persists on your M1 Mac, then most probably you need to install arch based CMake
|
||||||
```
|
```
|
||||||
arch -arm64 brew install cmake
|
arch -arm64 brew install cmake
|
||||||
```
|
```
|
||||||
|
|
||||||
Build might fail with "source files not found" error the first time you try it, because modern XCode build system compiles
|
Build might fail with the "source files not found" error the first time you try it, because the modern XCode build system compiles dependencies in parallel, and some dependencies end up being built after the ones that
|
||||||
dependencies in parallel, and some dependencies end up being built after the ones that
|
require them. In this case, simply restart the build.
|
||||||
require them. In this case simply restart the build.
|
|
||||||
|
|
||||||
## How to build the Android app
|
## How to build the Android app
|
||||||
_tested on Mac OS_
|
|
||||||
|
_Tested on Mac OS_
|
||||||
|
|
||||||
The Android app has the following requirements:
|
The Android app has the following requirements:
|
||||||
* JDK 11
|
* JDK 11
|
||||||
* Android platform SDK 33
|
* Android platform SDK 33
|
||||||
* cmake 3.25.0
|
* CMake 3.25.0
|
||||||
|
|
||||||
After you have installed QT, QT Creator and Android Studio installed, you need to configure QT Creator correctly. Click in the top menu bar on `QT Creator` -> `Preferences` -> `Devices` and select the tab `Android`.
|
After you have installed QT, QT Creator, and Android Studio, you need to configure QT Creator correctly. Click in the top menu bar on `QT Creator` -> `Preferences` -> `Devices` and select the tab `Android`.
|
||||||
* set path to jdk 11
|
* set path to JDK 11
|
||||||
* set path to Android SDK ($ANDROID_HOME)
|
* set path to Android SDK ($ANDROID_HOME)
|
||||||
|
|
||||||
In case you get errors regarding missing SDK or 'sdkmanager not running', you cannot fix them by correcting the paths and you have some spare GBs on your disk, you can let QT Creator install all requirements by choosing an empty folder for `Android SDK location` and click on `Set Up SDK`. Be aware: This will install a second Android SDK and NDK on your machine!
|
In case you get errors regarding missing SDK or 'SDK manager not running', you cannot fix them by correcting the paths. If you have some spare GBs on your disk, you can let QT Creator install all requirements by choosing an empty folder for `Android SDK location` and clicking on `Set Up SDK`. Be aware: This will install a second Android SDK and NDK on your machine!
|
||||||
|
Double-check that the right CMake version is configured: Click on `QT Creator` -> `Preferences` and click on the side menu on `Kits`. Under the center content view's `Kits` tab, you'll find an entry for `CMake Tool`. If the default selected CMake version is lower than 3.25.0, install on your system CMake >= 3.25.0 and choose `System CMake at <path>` from the drop-down list. If this entry is missing, you either have not installed CMake yet or QT Creator hasn't found the path to it. In that case, click in the preferences window on the side menu item `CMake`, then on the tab `Tools` in the center content view, and finally on the button `Add` to set the path to your installed CMake.
|
||||||
|
Please make sure that you have selected Android Platform SDK 33 for your project: click in the main view's side menu on `Projects`, and on the left, you'll see a section `Build & Run` showing different Android build targets. You can select any of them, Amnezia VPN's project setup is designed in a way that all Android targets will be built. Click on the targets submenu item `Build` and scroll in the center content view to `Build Steps`. Click on `Details` at the end of the headline `Build Android APK` (the `Details` button might be hidden in case the QT Creator Window is not running in full screen!). Here we are: Choose `android-33` as `Android Build Platform SDK`.
|
||||||
|
|
||||||
Double check that the right cmake version is configured: Click on `QT Creator` -> `Preferences` and click on the side menu on `Kits`. Under the center content view's `Kits` tab you'll find an entry `CMake Tool`. If the default selected CMake version is lower than 3.25.0, install on your system CMake >= 3.25.0 and choose `System CMake at <path>` from the drop down list. If this entry is missing, you either have not installed CMake yet or QT Creator hasn't found the path to it. In that case click in the preferences window on the side menu item `CMake`, then on the tab `Tools`in the center content view and finally on the Button `Add` to set the path to your installed CMake.
|
That's it! You should be ready to compile the project from QT Creator!
|
||||||
|
|
||||||
Please make sure that you have selected Android Platform SDK 33 for your project: click in the main view's side menu on on `Projects`, on the left you'll see a section `Build & Run` showing different Android build targets. You can select any of them, Amnezia VPN's project setup is designed in a way that always all Android targets will be build. Click on the targets submenu item `Build` and scroll in the center content view to `Build Steps`. Click on `Details` at the end of the headline `Build Android APK` (The `Details` button might be hidden in case QT Creator Window is not running in full screen!). Here we are: choose `android-33` as `Android Build platform SDK`.
|
|
||||||
|
|
||||||
That's it you should be ready to compile the project from QT Creator!
|
|
||||||
|
|
||||||
### Development flow
|
### Development flow
|
||||||
After you've hit the build button, QT-Creator copies the whole project to a folder in the repositories parent directory. The folder should look something like `build-amnezia-client-Android_Qt_<version>_Clang_<architecture>-<BuildType>`.
|
|
||||||
If you want to develop Amnezia VPNs Android components written in Kotlin, such as components using system APIs, you need to import the generated project in Android Studio with `build-amnezia-client-Android_Qt_<version>_Clang_<architecture>-<BuildType>/client/android-build` as the projects root directory. While you should be able to compile the generated project from Android Studio, you cannot work directly in the repository's Android project. So whenever you are confident with your work in the generated project, you'll need to copy and paste the affected files to the corresponding path in the repositories Android project so that you can add and commit your changes!
|
|
||||||
|
|
||||||
You may face compiling issues in QT Creator after you've worked in Android Studio on the generated project. Just do a `./gradlew clean` in the generated project's root directory (`<path>/client/android-build/.`) and you should be good to continue.
|
After you've hit the build button, QT-Creator copies the whole project to a folder in the repository parent directory. The folder should look something like `build-amnezia-client-Android_Qt_<version>_Clang_<architecture>-<BuildType>`.
|
||||||
|
If you want to develop Amnezia VPNs Android components written in Kotlin, such as components using system APIs, you need to import the generated project in Android Studio with `build-amnezia-client-Android_Qt_<version>_Clang_<architecture>-<BuildType>/client/android-build` as the projects root directory. While you should be able to compile the generated project from Android Studio, you cannot work directly in the repository's Android project. So whenever you are confident with your work in the generated project, you'll need to copy and paste the affected files to the corresponding path in the repository's Android project so that you can add and commit your changes!
|
||||||
|
|
||||||
|
You may face compiling issues in QT Creator after you've worked in Android Studio on the generated project. Just do a `./gradlew clean` in the generated project's root directory (`<path>/client/android-build/.`) and you should be good to go.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
GPL v.3
|
|
||||||
|
GPL v3.0
|
||||||
|
|
||||||
## Donate
|
## Donate
|
||||||
|
|
||||||
Bitcoin: bc1qn9rhsffuxwnhcuuu4qzrwp4upkrq94xnh8r26u
|
Bitcoin: bc1qn9rhsffuxwnhcuuu4qzrwp4upkrq94xnh8r26u
|
||||||
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3
|
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3
|
||||||
payeer.com: P2561305
|
payeer.com: P2561305
|
||||||
ko-fi.com: [https://ko-fi.com/amnezia_vpn](https://ko-fi.com/amnezia_vpn)
|
ko-fi.com: [https://ko-fi.com/amnezia_vpn](https://ko-fi.com/amnezia_vpn)
|
||||||
|
|
||||||
|
## Acknowledgments
|
||||||
|
|
||||||
## etc
|
|
||||||
This project is tested with BrowserStack.
|
This project is tested with BrowserStack.
|
||||||
We express our gratitude to [BrowserStack](https://www.browserstack.com) for supporting our project.
|
We express our gratitude to [BrowserStack](https://www.browserstack.com) for supporting our project.
|
||||||
|
|||||||
+1
-1
Submodule client/3rd-prebuilt updated: 2fa21880b9...c969f28b84
Vendored
+1
-1
Submodule client/3rd/OpenVPNAdapter updated: 6f71d0743d...7c821a8d5c
@@ -72,7 +72,7 @@ namespace QSimpleCrypto
|
|||||||
/// \param notAfter - X509 end date.
|
/// \param notAfter - X509 end date.
|
||||||
/// \return Returns OpenSSL X509 structure or nullptr, if error happened. Returned value must be cleaned up with 'X509_free' to avoid memory leak.
|
/// \return Returns OpenSSL X509 structure or nullptr, if error happened. Returned value must be cleaned up with 'X509_free' to avoid memory leak.
|
||||||
///
|
///
|
||||||
X509* generateSelfSignedCertificate(const RSA* rsa, const QMap<QByteArray, QByteArray>& additionalData,
|
X509* generateSelfSignedCertificate(RSA* rsa, const QMap<QByteArray, QByteArray>& additionalData,
|
||||||
const QByteArray& certificateFileName = "", const EVP_MD* md = EVP_sha512(),
|
const QByteArray& certificateFileName = "", const EVP_MD* md = EVP_sha512(),
|
||||||
const long& serialNumber = 1, const long& version = x509LastVersion,
|
const long& serialNumber = 1, const long& version = x509LastVersion,
|
||||||
const long& notBefore = 0, const long& notAfter = oneYear);
|
const long& notBefore = 0, const long& notAfter = oneYear);
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ X509* QSimpleCrypto::QX509::verifyCertificate(X509* x509, X509_STORE* store)
|
|||||||
/// \param notAfter - X509 end date.
|
/// \param notAfter - X509 end date.
|
||||||
/// \return Returns OpenSSL X509 structure or nullptr, if error happened. Returned value must be cleaned up with 'X509_free' to avoid memory leak.
|
/// \return Returns OpenSSL X509 structure or nullptr, if error happened. Returned value must be cleaned up with 'X509_free' to avoid memory leak.
|
||||||
///
|
///
|
||||||
X509* QSimpleCrypto::QX509::generateSelfSignedCertificate(const RSA* rsa, const QMap<QByteArray, QByteArray>& additionalData,
|
X509* QSimpleCrypto::QX509::generateSelfSignedCertificate(RSA* rsa, const QMap<QByteArray, QByteArray>& additionalData,
|
||||||
const QByteArray& certificateFileName, const EVP_MD* md,
|
const QByteArray& certificateFileName, const EVP_MD* md,
|
||||||
const long& serialNumber, const long& version,
|
const long& serialNumber, const long& version,
|
||||||
const long& notBefore, const long& notAfter)
|
const long& notBefore, const long& notAfter)
|
||||||
|
|||||||
+21
-1
@@ -15,6 +15,15 @@ set(PACKAGES
|
|||||||
Core5Compat Concurrent LinguistTools
|
Core5Compat Concurrent LinguistTools
|
||||||
)
|
)
|
||||||
|
|
||||||
|
execute_process(
|
||||||
|
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
||||||
|
COMMAND git rev-parse --short HEAD
|
||||||
|
OUTPUT_VARIABLE GIT_COMMIT_HASH
|
||||||
|
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||||
|
)
|
||||||
|
|
||||||
|
add_definitions(-DGIT_COMMIT_HASH="${GIT_COMMIT_HASH}")
|
||||||
|
|
||||||
if(IOS)
|
if(IOS)
|
||||||
set(PACKAGES ${PACKAGES} Multimedia)
|
set(PACKAGES ${PACKAGES} Multimedia)
|
||||||
endif()
|
endif()
|
||||||
@@ -54,9 +63,12 @@ qt6_add_resources(QRC ${QRC} ${CMAKE_CURRENT_LIST_DIR}/resources.qrc)
|
|||||||
set(CMAKE_AUTORCC ON)
|
set(CMAKE_AUTORCC ON)
|
||||||
|
|
||||||
set(AMNEZIAVPN_TS_FILES
|
set(AMNEZIAVPN_TS_FILES
|
||||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ru.ts
|
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ru_RU.ts
|
||||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_zh_CN.ts
|
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_zh_CN.ts
|
||||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_fa_IR.ts
|
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_fa_IR.ts
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ar_EG.ts
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_my_MM.ts
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_uk_UA.ts
|
||||||
)
|
)
|
||||||
|
|
||||||
file(GLOB_RECURSE AMNEZIAVPN_TS_SOURCES *.qrc *.cpp *.h *.ui)
|
file(GLOB_RECURSE AMNEZIAVPN_TS_SOURCES *.qrc *.cpp *.h *.ui)
|
||||||
@@ -108,7 +120,9 @@ set(HEADERS ${HEADERS}
|
|||||||
${CMAKE_CURRENT_LIST_DIR}/core/errorstrings.h
|
${CMAKE_CURRENT_LIST_DIR}/core/errorstrings.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/scripts_registry.h
|
${CMAKE_CURRENT_LIST_DIR}/core/scripts_registry.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/server_defs.h
|
${CMAKE_CURRENT_LIST_DIR}/core/server_defs.h
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/core/controllers/apiController.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/controllers/serverController.h
|
${CMAKE_CURRENT_LIST_DIR}/core/controllers/serverController.h
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/core/controllers/vpnConfigurationController.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.h
|
${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/qml_register_protocols.h
|
${CMAKE_CURRENT_LIST_DIR}/protocols/qml_register_protocols.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.h
|
${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.h
|
||||||
@@ -118,6 +132,7 @@ set(HEADERS ${HEADERS}
|
|||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.h
|
${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.h
|
||||||
${CMAKE_CURRENT_BINARY_DIR}/version.h
|
${CMAKE_CURRENT_BINARY_DIR}/version.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/sshclient.h
|
${CMAKE_CURRENT_LIST_DIR}/core/sshclient.h
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/core/networkUtilities.h
|
||||||
)
|
)
|
||||||
|
|
||||||
# Mozilla headres
|
# Mozilla headres
|
||||||
@@ -147,12 +162,15 @@ set(SOURCES ${SOURCES}
|
|||||||
${CMAKE_CURRENT_LIST_DIR}/core/errorstrings.cpp
|
${CMAKE_CURRENT_LIST_DIR}/core/errorstrings.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/scripts_registry.cpp
|
${CMAKE_CURRENT_LIST_DIR}/core/scripts_registry.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/server_defs.cpp
|
${CMAKE_CURRENT_LIST_DIR}/core/server_defs.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/core/controllers/apiController.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/controllers/serverController.cpp
|
${CMAKE_CURRENT_LIST_DIR}/core/controllers/serverController.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/core/controllers/vpnConfigurationController.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.cpp
|
${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.cpp
|
${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.cpp
|
${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.cpp
|
${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/sshclient.cpp
|
${CMAKE_CURRENT_LIST_DIR}/core/sshclient.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/core/networkUtilities.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
# Mozilla sources
|
# Mozilla sources
|
||||||
@@ -282,6 +300,7 @@ if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
|||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.h
|
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.h
|
${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.h
|
${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.h
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/protocols/xrayprotocol.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/awgprotocol.h
|
${CMAKE_CURRENT_LIST_DIR}/protocols/awgprotocol.h
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -293,6 +312,7 @@ if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
|||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.cpp
|
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.cpp
|
${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.cpp
|
${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/protocols/xrayprotocol.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/awgprotocol.cpp
|
${CMAKE_CURRENT_LIST_DIR}/protocols/awgprotocol.cpp
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -9,14 +9,15 @@
|
|||||||
#include <QTextDocument>
|
#include <QTextDocument>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QTranslator>
|
#include <QTranslator>
|
||||||
|
|
||||||
#include <QQuickItem>
|
#include <QQuickItem>
|
||||||
|
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
|
#include "ui/models/installedAppsModel.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
|
|
||||||
#include "platforms/ios/QRCodeReaderBase.h"
|
#include "platforms/ios/QRCodeReaderBase.h"
|
||||||
#if defined(Q_OS_ANDROID)
|
#if defined(Q_OS_ANDROID)
|
||||||
|
#include "core/installedAppsImageProvider.h"
|
||||||
#include "platforms/android/android_controller.h"
|
#include "platforms/android/android_controller.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -24,13 +25,14 @@
|
|||||||
|
|
||||||
#if defined(Q_OS_IOS)
|
#if defined(Q_OS_IOS)
|
||||||
#include "platforms/ios/ios_controller.h"
|
#include "platforms/ios/ios_controller.h"
|
||||||
|
#include <AmneziaVPN-Swift.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||||
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv)
|
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv)
|
||||||
#else
|
#else
|
||||||
AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecondary, SingleApplication::Options options,
|
AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecondary, SingleApplication::Options options, int timeout,
|
||||||
int timeout, const QString &userData)
|
const QString &userData)
|
||||||
: SingleApplication(argc, argv, allowSecondary, options, timeout, userData)
|
: SingleApplication(argc, argv, allowSecondary, options, timeout, userData)
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
@@ -43,12 +45,12 @@ AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecond
|
|||||||
s.setValue("permFixed", true);
|
s.setValue("permFixed", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString configLoc1 = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/"
|
QString configLoc1 = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/" + ORGANIZATION_NAME + "/"
|
||||||
+ ORGANIZATION_NAME + "/" + APPLICATION_NAME + ".conf";
|
+ APPLICATION_NAME + ".conf";
|
||||||
QFile::setPermissions(configLoc1, QFileDevice::ReadOwner | QFileDevice::WriteOwner);
|
QFile::setPermissions(configLoc1, QFileDevice::ReadOwner | QFileDevice::WriteOwner);
|
||||||
|
|
||||||
QString configLoc2 = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/"
|
QString configLoc2 = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/" + ORGANIZATION_NAME + "/"
|
||||||
+ ORGANIZATION_NAME + "/" + APPLICATION_NAME + "/" + APPLICATION_NAME + ".conf";
|
+ APPLICATION_NAME + "/" + APPLICATION_NAME + ".conf";
|
||||||
QFile::setPermissions(configLoc2, QFileDevice::ReadOwner | QFileDevice::WriteOwner);
|
QFile::setPermissions(configLoc2, QFileDevice::ReadOwner | QFileDevice::WriteOwner);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -81,8 +83,7 @@ void AmneziaApplication::init()
|
|||||||
|
|
||||||
m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance());
|
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_vpnConnection.reset(new VpnConnection(m_settings, m_configurator));
|
|
||||||
m_vpnConnection->moveToThread(&m_vpnConnectionThread);
|
m_vpnConnection->moveToThread(&m_vpnConnectionThread);
|
||||||
m_vpnConnectionThread.start();
|
m_vpnConnectionThread.start();
|
||||||
|
|
||||||
@@ -97,12 +98,18 @@ void AmneziaApplication::init()
|
|||||||
AndroidController::instance()->setSaveLogs(m_settings->isSaveLogs());
|
AndroidController::instance()->setSaveLogs(m_settings->isSaveLogs());
|
||||||
connect(m_settings.get(), &Settings::saveLogsChanged, AndroidController::instance(), &AndroidController::setSaveLogs);
|
connect(m_settings.get(), &Settings::saveLogsChanged, AndroidController::instance(), &AndroidController::setSaveLogs);
|
||||||
|
|
||||||
connect(AndroidController::instance(), &AndroidController::initConnectionState, this,
|
AndroidController::instance()->setScreenshotsEnabled(m_settings->isScreenshotsEnabled());
|
||||||
[this](Vpn::ConnectionState state) {
|
connect(m_settings.get(), &Settings::screenshotsEnabledChanged, AndroidController::instance(), &AndroidController::setScreenshotsEnabled);
|
||||||
m_connectionController->onConnectionStateChanged(state);
|
|
||||||
if (m_vpnConnection)
|
connect(m_settings.get(), &Settings::serverRemoved, AndroidController::instance(), &AndroidController::resetLastServer);
|
||||||
m_vpnConnection->restoreConnection();
|
|
||||||
});
|
connect(m_settings.get(), &Settings::settingsCleared, []() { AndroidController::instance()->resetLastServer(-1); });
|
||||||
|
|
||||||
|
connect(AndroidController::instance(), &AndroidController::initConnectionState, this, [this](Vpn::ConnectionState state) {
|
||||||
|
m_connectionController->onConnectionStateChanged(state);
|
||||||
|
if (m_vpnConnection)
|
||||||
|
m_vpnConnection->restoreConnection();
|
||||||
|
});
|
||||||
if (!AndroidController::instance()->initialize()) {
|
if (!AndroidController::instance()->initialize()) {
|
||||||
qFatal("Android controller initialization failed");
|
qFatal("Android controller initialization failed");
|
||||||
}
|
}
|
||||||
@@ -112,6 +119,8 @@ void AmneziaApplication::init()
|
|||||||
m_importController->extractConfigFromData(data);
|
m_importController->extractConfigFromData(data);
|
||||||
m_pageController->goToPageViewConfig();
|
m_pageController->goToPageViewConfig();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
m_engine->addImageProvider(QLatin1String("installedAppImage"), new InstalledAppsImageProvider);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef Q_OS_IOS
|
#ifdef Q_OS_IOS
|
||||||
@@ -127,6 +136,10 @@ void AmneziaApplication::init()
|
|||||||
m_pageController->goToPageSettingsBackup();
|
m_pageController->goToPageSettingsBackup();
|
||||||
m_settingsController->importBackupFromOutside(filePath);
|
m_settingsController->importBackupFromOutside(filePath);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
QTimer::singleShot(0, this, [this]() { AmneziaVPN::toggleScreenshots(m_settings->isScreenshotsEnabled()); });
|
||||||
|
|
||||||
|
connect(m_settings.get(), &Settings::screenshotsEnabledChanged, [](bool enabled) { AmneziaVPN::toggleScreenshots(enabled); });
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
m_notificationHandler.reset(NotificationHandler::create(nullptr));
|
m_notificationHandler.reset(NotificationHandler::create(nullptr));
|
||||||
@@ -134,14 +147,12 @@ void AmneziaApplication::init()
|
|||||||
connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(),
|
connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(),
|
||||||
&NotificationHandler::setConnectionState);
|
&NotificationHandler::setConnectionState);
|
||||||
|
|
||||||
connect(m_notificationHandler.get(), &NotificationHandler::raiseRequested, m_pageController.get(),
|
connect(m_notificationHandler.get(), &NotificationHandler::raiseRequested, m_pageController.get(), &PageController::raiseMainWindow);
|
||||||
&PageController::raiseMainWindow);
|
|
||||||
connect(m_notificationHandler.get(), &NotificationHandler::connectRequested, m_connectionController.get(),
|
connect(m_notificationHandler.get(), &NotificationHandler::connectRequested, m_connectionController.get(),
|
||||||
&ConnectionController::openConnection);
|
&ConnectionController::openConnection);
|
||||||
connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(),
|
connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(),
|
||||||
&ConnectionController::closeConnection);
|
&ConnectionController::closeConnection);
|
||||||
connect(this, &AmneziaApplication::translationsUpdated, m_notificationHandler.get(),
|
connect(this, &AmneziaApplication::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated);
|
||||||
&NotificationHandler::onTranslationsUpdated);
|
|
||||||
|
|
||||||
m_engine->load(url);
|
m_engine->load(url);
|
||||||
m_systemController->setQmlRoot(m_engine->rootObjects().value(0));
|
m_systemController->setQmlRoot(m_engine->rootObjects().value(0));
|
||||||
@@ -214,7 +225,8 @@ void AmneziaApplication::registerTypes()
|
|||||||
qmlRegisterSingletonType(QUrl("qrc:/ui/qml/Filters/ContainersModelFilters.qml"), "ContainersModelFilters", 1, 0,
|
qmlRegisterSingletonType(QUrl("qrc:/ui/qml/Filters/ContainersModelFilters.qml"), "ContainersModelFilters", 1, 0,
|
||||||
"ContainersModelFilters");
|
"ContainersModelFilters");
|
||||||
|
|
||||||
//
|
qmlRegisterType<InstalledAppsModel>("InstalledAppsModel", 1, 0, "InstalledAppsModel");
|
||||||
|
|
||||||
Vpn::declareQmlVpnConnectionStateEnum();
|
Vpn::declareQmlVpnConnectionStateEnum();
|
||||||
PageLoader::declareQmlPageEnum();
|
PageLoader::declareQmlPageEnum();
|
||||||
}
|
}
|
||||||
@@ -281,15 +293,37 @@ QQmlApplicationEngine *AmneziaApplication::qmlEngine() const
|
|||||||
return m_engine;
|
return m_engine;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef Q_OS_MACOS
|
||||||
|
bool AmneziaApplication::event(QEvent *event)
|
||||||
|
{
|
||||||
|
if (event->type() == QEvent::FileOpen) {
|
||||||
|
QFileOpenEvent *openEvent = static_cast<QFileOpenEvent *>(event);
|
||||||
|
const QUrl url = openEvent->url();
|
||||||
|
if (url.isLocalFile()) {
|
||||||
|
m_pageController->replaceStartPage();
|
||||||
|
m_importController->extractConfigFromFile(url.toLocalFile());
|
||||||
|
m_pageController->goToPageViewConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return QApplication::event(event);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
void AmneziaApplication::initModels()
|
void AmneziaApplication::initModels()
|
||||||
{
|
{
|
||||||
m_containersModel.reset(new ContainersModel(this));
|
m_containersModel.reset(new ContainersModel(this));
|
||||||
m_engine->rootContext()->setContextProperty("ContainersModel", m_containersModel.get());
|
m_engine->rootContext()->setContextProperty("ContainersModel", m_containersModel.get());
|
||||||
|
|
||||||
|
m_defaultServerContainersModel.reset(new ContainersModel(this));
|
||||||
|
m_engine->rootContext()->setContextProperty("DefaultServerContainersModel", m_defaultServerContainersModel.get());
|
||||||
|
|
||||||
m_serversModel.reset(new ServersModel(m_settings, this));
|
m_serversModel.reset(new ServersModel(m_settings, this));
|
||||||
m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get());
|
m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get());
|
||||||
connect(m_serversModel.get(), &ServersModel::containersUpdated, m_containersModel.get(),
|
connect(m_serversModel.get(), &ServersModel::containersUpdated, m_containersModel.get(), &ContainersModel::updateModel);
|
||||||
|
connect(m_serversModel.get(), &ServersModel::defaultServerContainersUpdated, m_defaultServerContainersModel.get(),
|
||||||
&ContainersModel::updateModel);
|
&ContainersModel::updateModel);
|
||||||
|
m_serversModel->resetModel();
|
||||||
|
|
||||||
m_languageModel.reset(new LanguageModel(m_settings, this));
|
m_languageModel.reset(new LanguageModel(m_settings, this));
|
||||||
m_engine->rootContext()->setContextProperty("LanguageModel", m_languageModel.get());
|
m_engine->rootContext()->setContextProperty("LanguageModel", m_languageModel.get());
|
||||||
@@ -299,6 +333,9 @@ void AmneziaApplication::initModels()
|
|||||||
m_sitesModel.reset(new SitesModel(m_settings, this));
|
m_sitesModel.reset(new SitesModel(m_settings, this));
|
||||||
m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get());
|
m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get());
|
||||||
|
|
||||||
|
m_appSplitTunnelingModel.reset(new AppSplitTunnelingModel(m_settings, this));
|
||||||
|
m_engine->rootContext()->setContextProperty("AppSplitTunnelingModel", m_appSplitTunnelingModel.get());
|
||||||
|
|
||||||
m_protocolsModel.reset(new ProtocolsModel(m_settings, this));
|
m_protocolsModel.reset(new ProtocolsModel(m_settings, this));
|
||||||
m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get());
|
m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get());
|
||||||
|
|
||||||
@@ -317,6 +354,9 @@ void AmneziaApplication::initModels()
|
|||||||
m_awgConfigModel.reset(new AwgConfigModel(this));
|
m_awgConfigModel.reset(new AwgConfigModel(this));
|
||||||
m_engine->rootContext()->setContextProperty("AwgConfigModel", m_awgConfigModel.get());
|
m_engine->rootContext()->setContextProperty("AwgConfigModel", m_awgConfigModel.get());
|
||||||
|
|
||||||
|
m_xrayConfigModel.reset(new XrayConfigModel(this));
|
||||||
|
m_engine->rootContext()->setContextProperty("XrayConfigModel", m_xrayConfigModel.get());
|
||||||
|
|
||||||
#ifdef Q_OS_WINDOWS
|
#ifdef Q_OS_WINDOWS
|
||||||
m_ikev2ConfigModel.reset(new Ikev2ConfigModel(this));
|
m_ikev2ConfigModel.reset(new Ikev2ConfigModel(this));
|
||||||
m_engine->rootContext()->setContextProperty("Ikev2ConfigModel", m_ikev2ConfigModel.get());
|
m_engine->rootContext()->setContextProperty("Ikev2ConfigModel", m_ikev2ConfigModel.get());
|
||||||
@@ -329,28 +369,27 @@ void AmneziaApplication::initModels()
|
|||||||
m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get());
|
m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get());
|
||||||
connect(m_clientManagementModel.get(), &ClientManagementModel::adminConfigRevoked, m_serversModel.get(),
|
connect(m_clientManagementModel.get(), &ClientManagementModel::adminConfigRevoked, m_serversModel.get(),
|
||||||
&ServersModel::clearCachedProfile);
|
&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()
|
void AmneziaApplication::initControllers()
|
||||||
{
|
{
|
||||||
m_connectionController.reset(new ConnectionController(m_serversModel, m_containersModel, m_vpnConnection));
|
m_connectionController.reset(
|
||||||
|
new ConnectionController(m_serversModel, m_containersModel, m_clientManagementModel, m_vpnConnection, m_settings));
|
||||||
m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get());
|
m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get());
|
||||||
|
|
||||||
connect(this, &AmneziaApplication::translationsUpdated, m_connectionController.get(),
|
connect(m_connectionController.get(), &ConnectionController::connectionErrorOccurred, this, [this](const QString &errorMessage) {
|
||||||
&ConnectionController::onTranslationsUpdated);
|
emit m_pageController->showErrorMessage(errorMessage);
|
||||||
|
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
||||||
|
});
|
||||||
|
connect(m_connectionController.get(), &ConnectionController::connectButtonClicked, m_connectionController.get(),
|
||||||
|
&ConnectionController::toggleConnection, Qt::QueuedConnection);
|
||||||
|
|
||||||
|
connect(this, &AmneziaApplication::translationsUpdated, m_connectionController.get(), &ConnectionController::onTranslationsUpdated);
|
||||||
|
|
||||||
m_pageController.reset(new PageController(m_serversModel, m_settings));
|
m_pageController.reset(new PageController(m_serversModel, m_settings));
|
||||||
m_engine->rootContext()->setContextProperty("PageController", m_pageController.get());
|
m_engine->rootContext()->setContextProperty("PageController", m_pageController.get());
|
||||||
|
|
||||||
m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_settings));
|
m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_clientManagementModel, m_settings));
|
||||||
m_engine->rootContext()->setContextProperty("InstallController", m_installController.get());
|
m_engine->rootContext()->setContextProperty("InstallController", m_installController.get());
|
||||||
connect(m_installController.get(), &InstallController::passphraseRequestStarted, m_pageController.get(),
|
connect(m_installController.get(), &InstallController::passphraseRequestStarted, m_pageController.get(),
|
||||||
&PageController::showPassphraseRequestDrawer);
|
&PageController::showPassphraseRequestDrawer);
|
||||||
@@ -362,30 +401,23 @@ void AmneziaApplication::initControllers()
|
|||||||
m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings));
|
m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings));
|
||||||
m_engine->rootContext()->setContextProperty("ImportController", m_importController.get());
|
m_engine->rootContext()->setContextProperty("ImportController", m_importController.get());
|
||||||
|
|
||||||
m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_clientManagementModel,
|
m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_clientManagementModel, m_settings));
|
||||||
m_settings, m_configurator));
|
|
||||||
m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get());
|
m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get());
|
||||||
|
|
||||||
m_settingsController.reset(
|
m_settingsController.reset(
|
||||||
new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, m_settings));
|
new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, m_appSplitTunnelingModel, m_settings));
|
||||||
m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get());
|
m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get());
|
||||||
if (m_settingsController->isAutoConnectEnabled() && m_serversModel->getDefaultServerIndex() >= 0) {
|
if (m_settingsController->isAutoConnectEnabled() && m_serversModel->getDefaultServerIndex() >= 0) {
|
||||||
QTimer::singleShot(1000, this, [this]() { m_connectionController->openConnection(); });
|
QTimer::singleShot(1000, this, [this]() { m_connectionController->openConnection(); });
|
||||||
}
|
}
|
||||||
connect(m_settingsController.get(), &SettingsController::amneziaDnsToggled, m_serversModel.get(),
|
connect(m_settingsController.get(), &SettingsController::amneziaDnsToggled, m_serversModel.get(), &ServersModel::toggleAmneziaDns);
|
||||||
&ServersModel::toggleAmneziaDns);
|
|
||||||
|
|
||||||
m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel));
|
m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel));
|
||||||
m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get());
|
m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get());
|
||||||
|
|
||||||
|
m_appSplitTunnelingController.reset(new AppSplitTunnelingController(m_settings, m_appSplitTunnelingModel));
|
||||||
|
m_engine->rootContext()->setContextProperty("AppSplitTunnelingController", m_appSplitTunnelingController.get());
|
||||||
|
|
||||||
m_systemController.reset(new SystemController(m_settings));
|
m_systemController.reset(new SystemController(m_settings));
|
||||||
m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get());
|
m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get());
|
||||||
|
|
||||||
m_apiController.reset(new ApiController(m_serversModel, m_containersModel));
|
|
||||||
m_engine->rootContext()->setContextProperty("ApiController", m_apiController.get());
|
|
||||||
connect(m_apiController.get(), &ApiController::updateStarted, this,
|
|
||||||
[this]() { emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Connecting); });
|
|
||||||
connect(m_apiController.get(), &ApiController::errorOccurred, this,
|
|
||||||
[this]() { emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); });
|
|
||||||
connect(m_apiController.get(), &ApiController::updateFinished, m_connectionController.get(), &ConnectionController::toggleConnection);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include <QQmlApplicationEngine>
|
#include <QQmlApplicationEngine>
|
||||||
#include <QQmlContext>
|
#include <QQmlContext>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
|
#include <QFileOpenEvent>
|
||||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
#else
|
#else
|
||||||
@@ -14,8 +15,6 @@
|
|||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "vpnconnection.h"
|
#include "vpnconnection.h"
|
||||||
|
|
||||||
#include "configurators/vpn_configurator.h"
|
|
||||||
|
|
||||||
#include "ui/controllers/connectionController.h"
|
#include "ui/controllers/connectionController.h"
|
||||||
#include "ui/controllers/exportController.h"
|
#include "ui/controllers/exportController.h"
|
||||||
#include "ui/controllers/importController.h"
|
#include "ui/controllers/importController.h"
|
||||||
@@ -24,7 +23,7 @@
|
|||||||
#include "ui/controllers/settingsController.h"
|
#include "ui/controllers/settingsController.h"
|
||||||
#include "ui/controllers/sitesController.h"
|
#include "ui/controllers/sitesController.h"
|
||||||
#include "ui/controllers/systemController.h"
|
#include "ui/controllers/systemController.h"
|
||||||
#include "ui/controllers/apiController.h"
|
#include "ui/controllers/appSplitTunnelingController.h"
|
||||||
#include "ui/models/containers_model.h"
|
#include "ui/models/containers_model.h"
|
||||||
#include "ui/models/languageModel.h"
|
#include "ui/models/languageModel.h"
|
||||||
#include "ui/models/protocols/cloakConfigModel.h"
|
#include "ui/models/protocols/cloakConfigModel.h"
|
||||||
@@ -36,11 +35,13 @@
|
|||||||
#include "ui/models/protocols/openvpnConfigModel.h"
|
#include "ui/models/protocols/openvpnConfigModel.h"
|
||||||
#include "ui/models/protocols/shadowsocksConfigModel.h"
|
#include "ui/models/protocols/shadowsocksConfigModel.h"
|
||||||
#include "ui/models/protocols/wireguardConfigModel.h"
|
#include "ui/models/protocols/wireguardConfigModel.h"
|
||||||
|
#include "ui/models/protocols/xrayConfigModel.h"
|
||||||
#include "ui/models/protocols_model.h"
|
#include "ui/models/protocols_model.h"
|
||||||
#include "ui/models/servers_model.h"
|
#include "ui/models/servers_model.h"
|
||||||
#include "ui/models/services/sftpConfigModel.h"
|
#include "ui/models/services/sftpConfigModel.h"
|
||||||
#include "ui/models/sites_model.h"
|
#include "ui/models/sites_model.h"
|
||||||
#include "ui/models/clientManagementModel.h"
|
#include "ui/models/clientManagementModel.h"
|
||||||
|
#include "ui/models/appSplitTunnelingModel.h"
|
||||||
|
|
||||||
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
|
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
|
||||||
|
|
||||||
@@ -74,6 +75,10 @@ public:
|
|||||||
|
|
||||||
QQmlApplicationEngine *qmlEngine() const;
|
QQmlApplicationEngine *qmlEngine() const;
|
||||||
|
|
||||||
|
#ifdef Q_OS_MACOS
|
||||||
|
bool event(QEvent *event) override;
|
||||||
|
#endif
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void translationsUpdated();
|
void translationsUpdated();
|
||||||
|
|
||||||
@@ -83,7 +88,6 @@ private:
|
|||||||
|
|
||||||
QQmlApplicationEngine *m_engine {};
|
QQmlApplicationEngine *m_engine {};
|
||||||
std::shared_ptr<Settings> m_settings;
|
std::shared_ptr<Settings> m_settings;
|
||||||
std::shared_ptr<VpnConfigurator> m_configurator;
|
|
||||||
|
|
||||||
QSharedPointer<ContainerProps> m_containerProps;
|
QSharedPointer<ContainerProps> m_containerProps;
|
||||||
QSharedPointer<ProtocolProps> m_protocolProps;
|
QSharedPointer<ProtocolProps> m_protocolProps;
|
||||||
@@ -92,15 +96,18 @@ private:
|
|||||||
QCommandLineParser m_parser;
|
QCommandLineParser m_parser;
|
||||||
|
|
||||||
QSharedPointer<ContainersModel> m_containersModel;
|
QSharedPointer<ContainersModel> m_containersModel;
|
||||||
|
QSharedPointer<ContainersModel> m_defaultServerContainersModel;
|
||||||
QSharedPointer<ServersModel> m_serversModel;
|
QSharedPointer<ServersModel> m_serversModel;
|
||||||
QSharedPointer<LanguageModel> m_languageModel;
|
QSharedPointer<LanguageModel> m_languageModel;
|
||||||
QSharedPointer<ProtocolsModel> m_protocolsModel;
|
QSharedPointer<ProtocolsModel> m_protocolsModel;
|
||||||
QSharedPointer<SitesModel> m_sitesModel;
|
QSharedPointer<SitesModel> m_sitesModel;
|
||||||
|
QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel;
|
||||||
QSharedPointer<ClientManagementModel> m_clientManagementModel;
|
QSharedPointer<ClientManagementModel> m_clientManagementModel;
|
||||||
|
|
||||||
QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel;
|
QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel;
|
||||||
QScopedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel;
|
QScopedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel;
|
||||||
QScopedPointer<CloakConfigModel> m_cloakConfigModel;
|
QScopedPointer<CloakConfigModel> m_cloakConfigModel;
|
||||||
|
QScopedPointer<XrayConfigModel> m_xrayConfigModel;
|
||||||
QScopedPointer<WireGuardConfigModel> m_wireGuardConfigModel;
|
QScopedPointer<WireGuardConfigModel> m_wireGuardConfigModel;
|
||||||
QScopedPointer<AwgConfigModel> m_awgConfigModel;
|
QScopedPointer<AwgConfigModel> m_awgConfigModel;
|
||||||
#ifdef Q_OS_WINDOWS
|
#ifdef Q_OS_WINDOWS
|
||||||
@@ -121,7 +128,7 @@ private:
|
|||||||
QScopedPointer<SettingsController> m_settingsController;
|
QScopedPointer<SettingsController> m_settingsController;
|
||||||
QScopedPointer<SitesController> m_sitesController;
|
QScopedPointer<SitesController> m_sitesController;
|
||||||
QScopedPointer<SystemController> m_systemController;
|
QScopedPointer<SystemController> m_systemController;
|
||||||
QScopedPointer<ApiController> m_apiController;
|
QScopedPointer<AppSplitTunnelingController> m_appSplitTunnelingController;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // AMNEZIA_APPLICATION_H
|
#endif // AMNEZIA_APPLICATION_H
|
||||||
|
|||||||
@@ -22,11 +22,9 @@
|
|||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
|
||||||
<uses-permission android:name="android.permission.CAMERA" />
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED" />
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" tools:ignore="QueryAllPackagesPermission" />
|
||||||
<!-- Enable when VPN-per-app mode will be implemented -->
|
|
||||||
<!-- <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/> -->
|
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".AmneziaApplication"
|
android:name=".AmneziaApplication"
|
||||||
@@ -56,6 +54,10 @@
|
|||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="android.app.lib_name"
|
android:name="android.app.lib_name"
|
||||||
android:value="-- %%INSERT_APP_LIB_NAME%% --" />
|
android:value="-- %%INSERT_APP_LIB_NAME%% --" />
|
||||||
@@ -137,14 +139,29 @@
|
|||||||
android:name=".AmneziaVpnService"
|
android:name=".AmneziaVpnService"
|
||||||
android:process=":amneziaVpnService"
|
android:process=":amneziaVpnService"
|
||||||
android:permission="android.permission.BIND_VPN_SERVICE"
|
android:permission="android.permission.BIND_VPN_SERVICE"
|
||||||
android:foregroundServiceType="specialUse"
|
android:foregroundServiceType="systemExempted"
|
||||||
android:exported="false">
|
android:exported="false"
|
||||||
|
tools:ignore="ForegroundServicePermission">
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.net.VpnService" />
|
<action android:name="android.net.VpnService" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
|
||||||
<property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE" android:value="vpn" />
|
<service
|
||||||
|
android:name=".AmneziaTileService"
|
||||||
|
android:process=":amneziaTileService"
|
||||||
|
android:icon="@drawable/ic_amnezia_round"
|
||||||
|
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
|
||||||
|
android:exported="true">
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
||||||
|
</intent-filter>
|
||||||
|
|
||||||
|
<meta-data
|
||||||
|
android:name="android.service.quicksettings.TOGGLEABLE_TILE"
|
||||||
|
android:value="true" />
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ class Awg : Wireguard() {
|
|||||||
return AwgConfig.build {
|
return AwgConfig.build {
|
||||||
configWireguard(configData, configDataJson)
|
configWireguard(configData, configDataJson)
|
||||||
configSplitTunneling(config)
|
configSplitTunneling(config)
|
||||||
|
configAppSplitTunneling(config)
|
||||||
configData["Jc"]?.let { setJc(it.toInt()) }
|
configData["Jc"]?.let { setJc(it.toInt()) }
|
||||||
configData["Jmin"]?.let { setJmin(it.toInt()) }
|
configData["Jmin"]?.let { setJmin(it.toInt()) }
|
||||||
configData["Jmax"]?.let { setJmax(it.toInt()) }
|
configData["Jmax"]?.let { setJmax(it.toInt()) }
|
||||||
|
|||||||
@@ -111,4 +111,5 @@ dependencies {
|
|||||||
implementation(libs.kotlinx.coroutines)
|
implementation(libs.kotlinx.coroutines)
|
||||||
implementation(libs.bundles.androidx.camera)
|
implementation(libs.bundles.androidx.camera)
|
||||||
implementation(libs.google.mlkit)
|
implementation(libs.google.mlkit)
|
||||||
|
implementation(libs.androidx.datastore)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ androidx-activity = "1.8.1"
|
|||||||
androidx-annotation = "1.7.0"
|
androidx-annotation = "1.7.0"
|
||||||
androidx-camera = "1.3.0"
|
androidx-camera = "1.3.0"
|
||||||
androidx-security-crypto = "1.1.0-alpha06"
|
androidx-security-crypto = "1.1.0-alpha06"
|
||||||
|
androidx-datastore = "1.1.0-beta01"
|
||||||
kotlinx-coroutines = "1.7.3"
|
kotlinx-coroutines = "1.7.3"
|
||||||
google-mlkit = "17.2.0"
|
google-mlkit = "17.2.0"
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.r
|
|||||||
androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "androidx-camera" }
|
androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "androidx-camera" }
|
||||||
androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "androidx-camera" }
|
androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "androidx-camera" }
|
||||||
androidx-security-crypto = { module = "androidx.security:security-crypto-ktx", version.ref = "androidx-security-crypto" }
|
androidx-security-crypto = { module = "androidx.security:security-crypto-ktx", version.ref = "androidx-security-crypto" }
|
||||||
|
androidx-datastore = { module = "androidx.datastore:datastore-preferences", version.ref = "androidx-datastore" }
|
||||||
kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" }
|
kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" }
|
||||||
google-mlkit = { module = "com.google.mlkit:barcode-scanning", version.ref = "google-mlkit" }
|
google-mlkit = { module = "com.google.mlkit:barcode-scanning", version.ref = "google-mlkit" }
|
||||||
|
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ open class OpenVpn : Protocol() {
|
|||||||
}
|
}
|
||||||
configPluggableTransport(configBuilder, config)
|
configPluggableTransport(configBuilder, config)
|
||||||
configBuilder.configSplitTunneling(config)
|
configBuilder.configSplitTunneling(config)
|
||||||
|
configBuilder.configAppSplitTunneling(config)
|
||||||
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
val status = client.connect()
|
val status = client.connect()
|
||||||
|
|||||||
@@ -64,6 +64,22 @@ abstract class Protocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected fun ProtocolConfig.Builder.configAppSplitTunneling(config: JSONObject) {
|
||||||
|
val splitTunnelType = config.optInt("appSplitTunnelType")
|
||||||
|
if (splitTunnelType == SPLIT_TUNNEL_DISABLE) return
|
||||||
|
val splitTunnelApps = config.getJSONArray("splitTunnelApps")
|
||||||
|
val appHandlerFunc = when (splitTunnelType) {
|
||||||
|
SPLIT_TUNNEL_INCLUDE -> ::includeApplication
|
||||||
|
SPLIT_TUNNEL_EXCLUDE -> ::excludeApplication
|
||||||
|
|
||||||
|
else -> throw BadConfigException("Unexpected value of the 'appSplitTunnelType' parameter: $splitTunnelType")
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i in 0 until splitTunnelApps.length()) {
|
||||||
|
appHandlerFunc(splitTunnelApps.getString(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected open fun buildVpnInterface(config: ProtocolConfig, vpnBuilder: Builder) {
|
protected open fun buildVpnInterface(config: ProtocolConfig, vpnBuilder: Builder) {
|
||||||
vpnBuilder.setSession(VPN_SESSION_NAME)
|
vpnBuilder.setSession(VPN_SESSION_NAME)
|
||||||
|
|
||||||
@@ -101,6 +117,11 @@ abstract class Protocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (app in config.includedApplications) {
|
||||||
|
Log.d(TAG, "addAllowedApplication: $app")
|
||||||
|
vpnBuilder.addAllowedApplication(app)
|
||||||
|
}
|
||||||
|
|
||||||
for (app in config.excludedApplications) {
|
for (app in config.excludedApplications) {
|
||||||
Log.d(TAG, "addDisallowedApplication: $app")
|
Log.d(TAG, "addDisallowedApplication: $app")
|
||||||
vpnBuilder.addDisallowedApplication(app)
|
vpnBuilder.addDisallowedApplication(app)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ open class ProtocolConfig protected constructor(
|
|||||||
val excludedRoutes: Set<InetNetwork>,
|
val excludedRoutes: Set<InetNetwork>,
|
||||||
val includedAddresses: Set<InetNetwork>,
|
val includedAddresses: Set<InetNetwork>,
|
||||||
val excludedAddresses: Set<InetNetwork>,
|
val excludedAddresses: Set<InetNetwork>,
|
||||||
|
val includedApplications: Set<String>,
|
||||||
val excludedApplications: Set<String>,
|
val excludedApplications: Set<String>,
|
||||||
val httpProxy: ProxyInfo?,
|
val httpProxy: ProxyInfo?,
|
||||||
val allowAllAF: Boolean,
|
val allowAllAF: Boolean,
|
||||||
@@ -31,6 +32,7 @@ open class ProtocolConfig protected constructor(
|
|||||||
builder.excludedRoutes,
|
builder.excludedRoutes,
|
||||||
builder.includedAddresses,
|
builder.includedAddresses,
|
||||||
builder.excludedAddresses,
|
builder.excludedAddresses,
|
||||||
|
builder.includedApplications,
|
||||||
builder.excludedApplications,
|
builder.excludedApplications,
|
||||||
builder.httpProxy,
|
builder.httpProxy,
|
||||||
builder.allowAllAF,
|
builder.allowAllAF,
|
||||||
@@ -45,6 +47,7 @@ open class ProtocolConfig protected constructor(
|
|||||||
internal val excludedRoutes: MutableSet<InetNetwork> = hashSetOf()
|
internal val excludedRoutes: MutableSet<InetNetwork> = hashSetOf()
|
||||||
internal val includedAddresses: MutableSet<InetNetwork> = hashSetOf()
|
internal val includedAddresses: MutableSet<InetNetwork> = hashSetOf()
|
||||||
internal val excludedAddresses: MutableSet<InetNetwork> = hashSetOf()
|
internal val excludedAddresses: MutableSet<InetNetwork> = hashSetOf()
|
||||||
|
internal val includedApplications: MutableSet<String> = hashSetOf()
|
||||||
internal val excludedApplications: MutableSet<String> = hashSetOf()
|
internal val excludedApplications: MutableSet<String> = hashSetOf()
|
||||||
|
|
||||||
internal var searchDomain: String? = null
|
internal var searchDomain: String? = null
|
||||||
@@ -88,6 +91,9 @@ open class ProtocolConfig protected constructor(
|
|||||||
fun excludeAddress(addr: InetNetwork) = apply { this.excludedAddresses += addr }
|
fun excludeAddress(addr: InetNetwork) = apply { this.excludedAddresses += addr }
|
||||||
fun excludeAddresses(addresses: Collection<InetNetwork>) = apply { this.excludedAddresses += addresses }
|
fun excludeAddresses(addresses: Collection<InetNetwork>) = apply { this.excludedAddresses += addresses }
|
||||||
|
|
||||||
|
fun includeApplication(application: String) = apply { this.includedApplications += application }
|
||||||
|
fun includeApplications(applications: Collection<String>) = apply { this.includedApplications += applications }
|
||||||
|
|
||||||
fun excludeApplication(application: String) = apply { this.excludedApplications += application }
|
fun excludeApplication(application: String) = apply { this.excludedApplications += application }
|
||||||
fun excludeApplications(applications: Collection<String>) = apply { this.excludedApplications += applications }
|
fun excludeApplications(applications: Collection<String>) = apply { this.excludedApplications += applications }
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ package org.amnezia.vpn.protocol
|
|||||||
|
|
||||||
// keep synchronized with client/platforms/android/android_controller.h ConnectionState
|
// keep synchronized with client/platforms/android/android_controller.h ConnectionState
|
||||||
enum class ProtocolState {
|
enum class ProtocolState {
|
||||||
|
DISCONNECTED,
|
||||||
CONNECTED,
|
CONNECTED,
|
||||||
CONNECTING,
|
CONNECTING,
|
||||||
DISCONNECTED,
|
|
||||||
DISCONNECTING,
|
DISCONNECTING,
|
||||||
RECONNECTING,
|
RECONNECTING,
|
||||||
UNKNOWN
|
UNKNOWN
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ fun Bundle.putStatus(status: Status) {
|
|||||||
putInt(STATE_KEY, status.state.ordinal)
|
putInt(STATE_KEY, status.state.ordinal)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Bundle.putStatus(state: ProtocolState) {
|
||||||
|
putInt(STATE_KEY, state.ordinal)
|
||||||
|
}
|
||||||
|
|
||||||
fun Bundle.getStatus(): Status =
|
fun Bundle.getStatus(): Status =
|
||||||
Status.build {
|
Status.build {
|
||||||
setState(ProtocolState.entries[getInt(STATE_KEY)])
|
setState(ProtocolState.entries[getInt(STATE_KEY)])
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<resources>
|
||||||
|
<string name="connecting">Подключение</string>
|
||||||
|
<string name="disconnecting">Отключение</string>
|
||||||
|
<string name="cancel">Отмена</string>
|
||||||
|
<string name="ok">ОК</string>
|
||||||
|
<string name="vpnGranted">VPN-подключение разрешено</string>
|
||||||
|
<string name="vpnDenied">VPN-подключение запрещено</string>
|
||||||
|
<string name="vpnSetupFailed">Ошибка настройки VPN</string>
|
||||||
|
<string name="vpnSetupFailedMessage">Чтобы подключиться к AmneziaVPN необходимо:\n\n- Разрешить приложению подключаться к сети VPN\n- Отключить функцию \"Постоянная VPN\" для всех остальных VPN-приложений в системных настройках VPN</string>
|
||||||
|
<string name="openVpnSettings">Открыть настройки VPN</string>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
|
<resources>
|
||||||
|
<string name="connecting">Connecting</string>
|
||||||
|
<string name="disconnecting">Disconnecting</string>
|
||||||
|
<string name="cancel">Cancel</string>
|
||||||
|
<string name="ok">OK</string>
|
||||||
|
<string name="vpnGranted">VPN permission granted</string>
|
||||||
|
<string name="vpnDenied">VPN permission denied</string>
|
||||||
|
<string name="vpnSetupFailed">VPN setup error</string>
|
||||||
|
<string name="vpnSetupFailedMessage">To connect to AmneziaVPN, please do the following:\n\n- Allow the app to set up a VPN connection\n- Disable Always-on VPN for any other VPN app in the VPN system settings</string>
|
||||||
|
<string name="openVpnSettings">Open VPN settings</string>
|
||||||
|
</resources>
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
package org.amnezia.vpn
|
package org.amnezia.vpn
|
||||||
|
|
||||||
|
import android.app.AlertDialog
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.Intent.EXTRA_MIME_TYPES
|
import android.content.Intent.EXTRA_MIME_TYPES
|
||||||
import android.content.Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
|
import android.content.Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
|
||||||
import android.content.ServiceConnection
|
import android.content.ServiceConnection
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
|
import android.graphics.Bitmap
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.net.VpnService
|
import android.net.VpnService
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@@ -14,6 +16,8 @@ import android.os.IBinder
|
|||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.os.Message
|
import android.os.Message
|
||||||
import android.os.Messenger
|
import android.os.Messenger
|
||||||
|
import android.provider.Settings
|
||||||
|
import android.view.WindowManager.LayoutParams
|
||||||
import android.webkit.MimeTypeMap
|
import android.webkit.MimeTypeMap
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.annotation.MainThread
|
import androidx.annotation.MainThread
|
||||||
@@ -21,14 +25,15 @@ import androidx.core.content.ContextCompat
|
|||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import kotlin.LazyThreadSafetyMode.NONE
|
import kotlin.LazyThreadSafetyMode.NONE
|
||||||
import kotlin.text.RegexOption.IGNORE_CASE
|
import kotlin.text.RegexOption.IGNORE_CASE
|
||||||
|
import AppListProvider
|
||||||
import kotlinx.coroutines.CompletableDeferred
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.amnezia.vpn.protocol.ProtocolState
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import org.amnezia.vpn.protocol.getStatistics
|
import org.amnezia.vpn.protocol.getStatistics
|
||||||
import org.amnezia.vpn.protocol.getStatus
|
import org.amnezia.vpn.protocol.getStatus
|
||||||
import org.amnezia.vpn.qt.QtAndroidController
|
import org.amnezia.vpn.qt.QtAndroidController
|
||||||
@@ -36,11 +41,11 @@ import org.amnezia.vpn.util.Log
|
|||||||
import org.qtproject.qt.android.bindings.QtActivity
|
import org.qtproject.qt.android.bindings.QtActivity
|
||||||
|
|
||||||
private const val TAG = "AmneziaActivity"
|
private const val TAG = "AmneziaActivity"
|
||||||
|
const val ACTIVITY_MESSENGER_NAME = "Activity"
|
||||||
|
|
||||||
private const val CHECK_VPN_PERMISSION_ACTION_CODE = 1
|
private const val CHECK_VPN_PERMISSION_ACTION_CODE = 1
|
||||||
private const val CREATE_FILE_ACTION_CODE = 2
|
private const val CREATE_FILE_ACTION_CODE = 2
|
||||||
private const val OPEN_FILE_ACTION_CODE = 3
|
private const val OPEN_FILE_ACTION_CODE = 3
|
||||||
private const val BIND_SERVICE_TIMEOUT = 1000L
|
|
||||||
|
|
||||||
class AmneziaActivity : QtActivity() {
|
class AmneziaActivity : QtActivity() {
|
||||||
|
|
||||||
@@ -58,25 +63,17 @@ class AmneziaActivity : QtActivity() {
|
|||||||
val event = msg.extractIpcMessage<ServiceEvent>()
|
val event = msg.extractIpcMessage<ServiceEvent>()
|
||||||
Log.d(TAG, "Handle event: $event")
|
Log.d(TAG, "Handle event: $event")
|
||||||
when (event) {
|
when (event) {
|
||||||
ServiceEvent.CONNECTED -> {
|
ServiceEvent.STATUS_CHANGED -> {
|
||||||
QtAndroidController.onVpnConnected()
|
msg.data?.getStatus()?.let { (state) ->
|
||||||
}
|
Log.d(TAG, "Handle protocol state: $state")
|
||||||
|
QtAndroidController.onVpnStateChanged(state.ordinal)
|
||||||
ServiceEvent.DISCONNECTED -> {
|
}
|
||||||
QtAndroidController.onVpnDisconnected()
|
|
||||||
doUnbindService()
|
|
||||||
}
|
|
||||||
|
|
||||||
ServiceEvent.RECONNECTING -> {
|
|
||||||
QtAndroidController.onVpnReconnecting()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ServiceEvent.STATUS -> {
|
ServiceEvent.STATUS -> {
|
||||||
if (isWaitingStatus) {
|
if (isWaitingStatus) {
|
||||||
isWaitingStatus = false
|
isWaitingStatus = false
|
||||||
msg.data?.getStatus()?.let { (state) ->
|
msg.data?.getStatus()?.let { QtAndroidController.onStatus(it) }
|
||||||
QtAndroidController.onStatus(state.ordinal)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,7 +84,7 @@ class AmneziaActivity : QtActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ServiceEvent.ERROR -> {
|
ServiceEvent.ERROR -> {
|
||||||
msg.data?.getString(ERROR_MSG)?.let { error ->
|
msg.data?.getString(MSG_ERROR)?.let { error ->
|
||||||
Log.e(TAG, "From VpnService: $error")
|
Log.e(TAG, "From VpnService: $error")
|
||||||
}
|
}
|
||||||
// todo: add error reporting to Qt
|
// todo: add error reporting to Qt
|
||||||
@@ -109,14 +106,15 @@ class AmneziaActivity : QtActivity() {
|
|||||||
// get a messenger from the service to send actions to the service
|
// get a messenger from the service to send actions to the service
|
||||||
vpnServiceMessenger.set(Messenger(service))
|
vpnServiceMessenger.set(Messenger(service))
|
||||||
// send a messenger to the service to process service events
|
// send a messenger to the service to process service events
|
||||||
vpnServiceMessenger.send {
|
vpnServiceMessenger.send(
|
||||||
Action.REGISTER_CLIENT.packToMessage().apply {
|
Action.REGISTER_CLIENT.packToMessage {
|
||||||
replyTo = activityMessenger
|
putString(MSG_CLIENT_NAME, ACTIVITY_MESSENGER_NAME)
|
||||||
}
|
},
|
||||||
}
|
replyTo = activityMessenger
|
||||||
|
)
|
||||||
isServiceConnected = true
|
isServiceConnected = true
|
||||||
if (isWaitingStatus) {
|
if (isWaitingStatus) {
|
||||||
vpnServiceMessenger.send(Action.REQUEST_STATUS)
|
vpnServiceMessenger.send(Action.REQUEST_STATUS, replyTo = activityMessenger)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,6 +124,7 @@ class AmneziaActivity : QtActivity() {
|
|||||||
vpnServiceMessenger.reset()
|
vpnServiceMessenger.reset()
|
||||||
isWaitingStatus = true
|
isWaitingStatus = true
|
||||||
QtAndroidController.onServiceDisconnected()
|
QtAndroidController.onServiceDisconnected()
|
||||||
|
doBindService()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindingDied(name: ComponentName?) {
|
override fun onBindingDied(name: ComponentName?) {
|
||||||
@@ -148,8 +147,11 @@ class AmneziaActivity : QtActivity() {
|
|||||||
Log.d(TAG, "Create Amnezia activity: $intent")
|
Log.d(TAG, "Create Amnezia activity: $intent")
|
||||||
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
||||||
vpnServiceMessenger = IpcMessenger(
|
vpnServiceMessenger = IpcMessenger(
|
||||||
onDeadObjectException = ::doUnbindService,
|
"VpnService",
|
||||||
messengerName = "VpnService"
|
onDeadObjectException = {
|
||||||
|
doUnbindService()
|
||||||
|
doBindService()
|
||||||
|
}
|
||||||
)
|
)
|
||||||
intent?.let(::processIntent)
|
intent?.let(::processIntent)
|
||||||
}
|
}
|
||||||
@@ -220,13 +222,13 @@ class AmneziaActivity : QtActivity() {
|
|||||||
when (resultCode) {
|
when (resultCode) {
|
||||||
RESULT_OK -> {
|
RESULT_OK -> {
|
||||||
Log.d(TAG, "Vpn permission granted")
|
Log.d(TAG, "Vpn permission granted")
|
||||||
Toast.makeText(this, "Vpn permission granted", Toast.LENGTH_LONG).show()
|
Toast.makeText(this, resources.getText(R.string.vpnGranted), Toast.LENGTH_LONG).show()
|
||||||
checkVpnPermissionCallbacks?.run { onSuccess() }
|
checkVpnPermissionCallbacks?.run { onSuccess() }
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
Log.w(TAG, "Vpn permission denied, resultCode: $resultCode")
|
Log.w(TAG, "Vpn permission denied, resultCode: $resultCode")
|
||||||
Toast.makeText(this, "Vpn permission denied", Toast.LENGTH_LONG).show()
|
showOnVpnPermissionRejectDialog()
|
||||||
checkVpnPermissionCallbacks?.run { onFail() }
|
checkVpnPermissionCallbacks?.run { onFail() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -244,10 +246,9 @@ class AmneziaActivity : QtActivity() {
|
|||||||
private fun doBindService() {
|
private fun doBindService() {
|
||||||
Log.d(TAG, "Bind service")
|
Log.d(TAG, "Bind service")
|
||||||
Intent(this, AmneziaVpnService::class.java).also {
|
Intent(this, AmneziaVpnService::class.java).also {
|
||||||
bindService(it, serviceConnection, BIND_ABOVE_CLIENT)
|
bindService(it, serviceConnection, BIND_ABOVE_CLIENT and BIND_AUTO_CREATE)
|
||||||
}
|
}
|
||||||
isInBoundState = true
|
isInBoundState = true
|
||||||
handleBindTimeout()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainThread
|
@MainThread
|
||||||
@@ -256,26 +257,14 @@ class AmneziaActivity : QtActivity() {
|
|||||||
Log.d(TAG, "Unbind service")
|
Log.d(TAG, "Unbind service")
|
||||||
isWaitingStatus = true
|
isWaitingStatus = true
|
||||||
QtAndroidController.onServiceDisconnected()
|
QtAndroidController.onServiceDisconnected()
|
||||||
vpnServiceMessenger.reset()
|
|
||||||
isServiceConnected = false
|
isServiceConnected = false
|
||||||
|
vpnServiceMessenger.send(Action.UNREGISTER_CLIENT, activityMessenger)
|
||||||
|
vpnServiceMessenger.reset()
|
||||||
isInBoundState = false
|
isInBoundState = false
|
||||||
unbindService(serviceConnection)
|
unbindService(serviceConnection)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleBindTimeout() {
|
|
||||||
mainScope.launch {
|
|
||||||
if (isWaitingStatus) {
|
|
||||||
delay(BIND_SERVICE_TIMEOUT)
|
|
||||||
if (isWaitingStatus && !isServiceConnected) {
|
|
||||||
Log.d(TAG, "Bind timeout, reset connection status")
|
|
||||||
isWaitingStatus = false
|
|
||||||
QtAndroidController.onStatus(ProtocolState.DISCONNECTED.ordinal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Methods of starting and stopping VpnService
|
* Methods of starting and stopping VpnService
|
||||||
*/
|
*/
|
||||||
@@ -297,6 +286,17 @@ class AmneziaActivity : QtActivity() {
|
|||||||
onSuccess()
|
onSuccess()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showOnVpnPermissionRejectDialog() {
|
||||||
|
AlertDialog.Builder(this)
|
||||||
|
.setTitle(R.string.vpnSetupFailed)
|
||||||
|
.setMessage(R.string.vpnSetupFailedMessage)
|
||||||
|
.setNegativeButton(R.string.ok) { _, _ -> }
|
||||||
|
.setPositiveButton(R.string.openVpnSettings) { _, _ ->
|
||||||
|
startActivity(Intent(Settings.ACTION_VPN_SETTINGS))
|
||||||
|
}
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
@MainThread
|
@MainThread
|
||||||
private fun startVpn(vpnConfig: String) {
|
private fun startVpn(vpnConfig: String) {
|
||||||
if (isServiceConnected) {
|
if (isServiceConnected) {
|
||||||
@@ -312,7 +312,7 @@ class AmneziaActivity : QtActivity() {
|
|||||||
Log.d(TAG, "Connect to VPN")
|
Log.d(TAG, "Connect to VPN")
|
||||||
vpnServiceMessenger.send {
|
vpnServiceMessenger.send {
|
||||||
Action.CONNECT.packToMessage {
|
Action.CONNECT.packToMessage {
|
||||||
putString(VPN_CONFIG, vpnConfig)
|
putString(MSG_VPN_CONFIG, vpnConfig)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -320,7 +320,7 @@ class AmneziaActivity : QtActivity() {
|
|||||||
private fun startVpnService(vpnConfig: String) {
|
private fun startVpnService(vpnConfig: String) {
|
||||||
Log.d(TAG, "Start VPN service")
|
Log.d(TAG, "Start VPN service")
|
||||||
Intent(this, AmneziaVpnService::class.java).apply {
|
Intent(this, AmneziaVpnService::class.java).apply {
|
||||||
putExtra(VPN_CONFIG, vpnConfig)
|
putExtra(MSG_VPN_CONFIG, vpnConfig)
|
||||||
}.also {
|
}.also {
|
||||||
ContextCompat.startForegroundService(this, it)
|
ContextCompat.startForegroundService(this, it)
|
||||||
}
|
}
|
||||||
@@ -369,6 +369,22 @@ class AmneziaActivity : QtActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
fun resetLastServer(index: Int) {
|
||||||
|
Log.v(TAG, "Reset server: $index")
|
||||||
|
mainScope.launch {
|
||||||
|
VpnStateStore.store {
|
||||||
|
if (index == -1 || it.serverIndex == index) {
|
||||||
|
VpnState.defaultState
|
||||||
|
} else if (it.serverIndex > index) {
|
||||||
|
it.copy(serverIndex = it.serverIndex - 1)
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun saveFile(fileName: String, data: String) {
|
fun saveFile(fileName: String, data: String) {
|
||||||
Log.d(TAG, "Save file $fileName")
|
Log.d(TAG, "Save file $fileName")
|
||||||
@@ -390,25 +406,29 @@ class AmneziaActivity : QtActivity() {
|
|||||||
Log.v(TAG, "Open file with filter: $filter")
|
Log.v(TAG, "Open file with filter: $filter")
|
||||||
|
|
||||||
val mimeTypes = if (!filter.isNullOrEmpty()) {
|
val mimeTypes = if (!filter.isNullOrEmpty()) {
|
||||||
val extensionRegex = "\\*\\.[a-z .]+".toRegex(IGNORE_CASE)
|
val extensionRegex = "\\*\\.([a-z0-9]+)".toRegex(IGNORE_CASE)
|
||||||
val mime = MimeTypeMap.getSingleton()
|
val mime = MimeTypeMap.getSingleton()
|
||||||
extensionRegex.findAll(filter).map {
|
extensionRegex.findAll(filter).map {
|
||||||
mime.getMimeTypeFromExtension(it.value.drop(2))
|
it.groups[1]?.value?.let { mime.getMimeTypeFromExtension(it) } ?: "*/*"
|
||||||
}.filterNotNull().toSet()
|
}.toSet()
|
||||||
} else emptySet()
|
} else emptySet()
|
||||||
|
|
||||||
Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
||||||
addCategory(Intent.CATEGORY_OPENABLE)
|
addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
Log.v(TAG, "File mimyType filter: $mimeTypes")
|
Log.v(TAG, "File mimyType filter: $mimeTypes")
|
||||||
when (mimeTypes.size) {
|
if ("*/*" in mimeTypes) {
|
||||||
1 -> type = mimeTypes.first()
|
type = "*/*"
|
||||||
|
} else {
|
||||||
|
when (mimeTypes.size) {
|
||||||
|
1 -> type = mimeTypes.first()
|
||||||
|
|
||||||
in 2..Int.MAX_VALUE -> {
|
in 2..Int.MAX_VALUE -> {
|
||||||
type = "*/*"
|
type = "*/*"
|
||||||
putExtra(EXTRA_MIME_TYPES, mimeTypes.toTypedArray())
|
putExtra(EXTRA_MIME_TYPES, mimeTypes.toTypedArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> type = "*/*"
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> type = "*/*"
|
|
||||||
}
|
}
|
||||||
}.also {
|
}.also {
|
||||||
startActivityForResult(it, OPEN_FILE_ACTION_CODE)
|
startActivityForResult(it, OPEN_FILE_ACTION_CODE)
|
||||||
@@ -433,12 +453,12 @@ class AmneziaActivity : QtActivity() {
|
|||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun setSaveLogs(enabled: Boolean) {
|
fun setSaveLogs(enabled: Boolean) {
|
||||||
Log.d(TAG, "Set save logs: $enabled")
|
Log.v(TAG, "Set save logs: $enabled")
|
||||||
mainScope.launch {
|
mainScope.launch {
|
||||||
Log.saveLogs = enabled
|
Log.saveLogs = enabled
|
||||||
vpnServiceMessenger.send {
|
vpnServiceMessenger.send {
|
||||||
Action.SET_SAVE_LOGS.packToMessage {
|
Action.SET_SAVE_LOGS.packToMessage {
|
||||||
putBoolean(SAVE_LOGS, enabled)
|
putBoolean(MSG_SAVE_LOGS, enabled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -453,6 +473,45 @@ class AmneziaActivity : QtActivity() {
|
|||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun clearLogs() {
|
fun clearLogs() {
|
||||||
Log.v(TAG, "Clear logs")
|
Log.v(TAG, "Clear logs")
|
||||||
Log.clearLogs()
|
mainScope.launch {
|
||||||
|
Log.clearLogs()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
fun setScreenshotsEnabled(enabled: Boolean) {
|
||||||
|
Log.v(TAG, "Set screenshots enabled: $enabled")
|
||||||
|
mainScope.launch {
|
||||||
|
val flag = if (enabled) 0 else LayoutParams.FLAG_SECURE
|
||||||
|
window.setFlags(flag, LayoutParams.FLAG_SECURE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
fun minimizeApp() {
|
||||||
|
Log.v(TAG, "Minimize application")
|
||||||
|
mainScope.launch {
|
||||||
|
moveTaskToBack(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
fun getAppList(): String {
|
||||||
|
Log.v(TAG, "Get app list")
|
||||||
|
var appList = ""
|
||||||
|
runBlocking {
|
||||||
|
mainScope.launch {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
appList = AppListProvider.getAppList(packageManager, packageName)
|
||||||
|
}
|
||||||
|
}.join()
|
||||||
|
}
|
||||||
|
return appList
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
fun getAppIcon(packageName: String, width: Int, height: Int): Bitmap {
|
||||||
|
Log.v(TAG, "Get app icon: $packageName")
|
||||||
|
return AppListProvider.getAppIcon(packageManager, packageName, width, height)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ class AmneziaApplication : QtApplication(), CameraXConfig.Provider {
|
|||||||
super.onCreate()
|
super.onCreate()
|
||||||
Prefs.init(this)
|
Prefs.init(this)
|
||||||
Log.init(this)
|
Log.init(this)
|
||||||
|
VpnStateStore.init(this)
|
||||||
Log.d(TAG, "Create Amnezia application")
|
Log.d(TAG, "Create Amnezia application")
|
||||||
createNotificationChannel()
|
createNotificationChannel()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,272 @@
|
|||||||
|
package org.amnezia.vpn
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.ServiceConnection
|
||||||
|
import android.net.VpnService
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.IBinder
|
||||||
|
import android.os.Messenger
|
||||||
|
import android.service.quicksettings.Tile
|
||||||
|
import android.service.quicksettings.TileService
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import kotlin.LazyThreadSafetyMode.NONE
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.amnezia.vpn.protocol.ProtocolState
|
||||||
|
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
|
||||||
|
import org.amnezia.vpn.protocol.ProtocolState.CONNECTING
|
||||||
|
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
||||||
|
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTING
|
||||||
|
import org.amnezia.vpn.protocol.ProtocolState.RECONNECTING
|
||||||
|
import org.amnezia.vpn.protocol.ProtocolState.UNKNOWN
|
||||||
|
import org.amnezia.vpn.util.Log
|
||||||
|
|
||||||
|
private const val TAG = "AmneziaTileService"
|
||||||
|
private const val DEFAULT_TILE_LABEL = "AmneziaVPN"
|
||||||
|
|
||||||
|
class AmneziaTileService : TileService() {
|
||||||
|
|
||||||
|
private lateinit var scope: CoroutineScope
|
||||||
|
private var vpnStateListeningJob: Job? = null
|
||||||
|
private lateinit var vpnServiceMessenger: IpcMessenger
|
||||||
|
|
||||||
|
@Volatile
|
||||||
|
private var isServiceConnected = false
|
||||||
|
private var isInBoundState = false
|
||||||
|
@Volatile
|
||||||
|
private var isVpnConfigExists = false
|
||||||
|
|
||||||
|
private val serviceConnection: ServiceConnection by lazy(NONE) {
|
||||||
|
object : ServiceConnection {
|
||||||
|
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
|
||||||
|
Log.d(TAG, "Service ${name?.flattenToString()} was connected")
|
||||||
|
vpnServiceMessenger.set(Messenger(service))
|
||||||
|
isServiceConnected = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onServiceDisconnected(name: ComponentName?) {
|
||||||
|
Log.w(TAG, "Service ${name?.flattenToString()} was unexpectedly disconnected")
|
||||||
|
isServiceConnected = false
|
||||||
|
vpnServiceMessenger.reset()
|
||||||
|
updateVpnState(DISCONNECTED)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindingDied(name: ComponentName?) {
|
||||||
|
Log.w(TAG, "Binding to the ${name?.flattenToString()} unexpectedly died")
|
||||||
|
doUnbindService()
|
||||||
|
doBindService()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
Log.d(TAG, "Create Amnezia Tile Service")
|
||||||
|
scope = CoroutineScope(SupervisorJob())
|
||||||
|
vpnServiceMessenger = IpcMessenger(
|
||||||
|
"VpnService",
|
||||||
|
onDeadObjectException = ::doUnbindService
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
Log.d(TAG, "Destroy Amnezia Tile Service")
|
||||||
|
doUnbindService()
|
||||||
|
scope.cancel()
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Workaround for some bugs
|
||||||
|
override fun onBind(intent: Intent?): IBinder? =
|
||||||
|
try {
|
||||||
|
super.onBind(intent)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Log.e(TAG, "Failed to bind AmneziaTileService: $e")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStartListening() {
|
||||||
|
super.onStartListening()
|
||||||
|
Log.d(TAG, "Start listening")
|
||||||
|
if (AmneziaVpnService.isRunning(applicationContext)) {
|
||||||
|
Log.d(TAG, "Vpn service is running")
|
||||||
|
doBindService()
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "Vpn service is not running")
|
||||||
|
isServiceConnected = false
|
||||||
|
updateVpnState(DISCONNECTED)
|
||||||
|
}
|
||||||
|
vpnStateListeningJob = launchVpnStateListening()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStopListening() {
|
||||||
|
Log.d(TAG, "Stop listening")
|
||||||
|
vpnStateListeningJob?.cancel()
|
||||||
|
vpnStateListeningJob = null
|
||||||
|
doUnbindService()
|
||||||
|
super.onStopListening()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onClick() {
|
||||||
|
Log.d(TAG, "onClick")
|
||||||
|
if (isLocked) {
|
||||||
|
unlockAndRun { onClickInternal() }
|
||||||
|
} else {
|
||||||
|
onClickInternal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onClickInternal() {
|
||||||
|
if (isVpnConfigExists) {
|
||||||
|
Log.d(TAG, "Change VPN state")
|
||||||
|
if (qsTile.state == Tile.STATE_INACTIVE) {
|
||||||
|
Log.d(TAG, "Start VPN")
|
||||||
|
updateVpnState(CONNECTING)
|
||||||
|
startVpn()
|
||||||
|
} else if (qsTile.state == Tile.STATE_ACTIVE) {
|
||||||
|
Log.d(TAG, "Stop vpn")
|
||||||
|
updateVpnState(DISCONNECTING)
|
||||||
|
stopVpn()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "Start Activity")
|
||||||
|
Intent(this, AmneziaActivity::class.java).apply {
|
||||||
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
}.also {
|
||||||
|
startActivityAndCollapseCompat(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun doBindService() {
|
||||||
|
Log.d(TAG, "Bind service")
|
||||||
|
Intent(this, AmneziaVpnService::class.java).also {
|
||||||
|
bindService(it, serviceConnection, BIND_ABOVE_CLIENT)
|
||||||
|
}
|
||||||
|
isInBoundState = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun doUnbindService() {
|
||||||
|
if (isInBoundState) {
|
||||||
|
Log.d(TAG, "Unbind service")
|
||||||
|
isServiceConnected = false
|
||||||
|
vpnServiceMessenger.reset()
|
||||||
|
isInBoundState = false
|
||||||
|
unbindService(serviceConnection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startVpn() {
|
||||||
|
if (isServiceConnected) {
|
||||||
|
connectToVpn()
|
||||||
|
} else {
|
||||||
|
if (checkPermission()) {
|
||||||
|
startVpnService()
|
||||||
|
doBindService()
|
||||||
|
} else {
|
||||||
|
updateVpnState(DISCONNECTED)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkPermission() =
|
||||||
|
if (VpnService.prepare(applicationContext) != null) {
|
||||||
|
Intent(this, VpnRequestActivity::class.java).apply {
|
||||||
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
|
}.also {
|
||||||
|
startActivityAndCollapseCompat(it)
|
||||||
|
}
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startVpnService() =
|
||||||
|
ContextCompat.startForegroundService(
|
||||||
|
applicationContext,
|
||||||
|
Intent(this, AmneziaVpnService::class.java)
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun connectToVpn() = vpnServiceMessenger.send(Action.CONNECT)
|
||||||
|
|
||||||
|
private fun stopVpn() = vpnServiceMessenger.send(Action.DISCONNECT)
|
||||||
|
|
||||||
|
@SuppressLint("StartActivityAndCollapseDeprecated")
|
||||||
|
private fun startActivityAndCollapseCompat(intent: Intent) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||||
|
startActivityAndCollapse(
|
||||||
|
PendingIntent.getActivity(
|
||||||
|
applicationContext,
|
||||||
|
0,
|
||||||
|
intent,
|
||||||
|
PendingIntent.FLAG_IMMUTABLE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
startActivityAndCollapse(intent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateVpnState(state: ProtocolState) {
|
||||||
|
scope.launch {
|
||||||
|
VpnStateStore.store { it.copy(protocolState = state) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun launchVpnStateListening() =
|
||||||
|
scope.launch { VpnStateStore.dataFlow().collectLatest(::updateTile) }
|
||||||
|
|
||||||
|
private fun updateTile(vpnState: VpnState) {
|
||||||
|
Log.d(TAG, "Update tile: $vpnState")
|
||||||
|
isVpnConfigExists = vpnState.serverName != null
|
||||||
|
val tile = qsTile ?: return
|
||||||
|
tile.apply {
|
||||||
|
label = vpnState.serverName ?: DEFAULT_TILE_LABEL
|
||||||
|
when (vpnState.protocolState) {
|
||||||
|
CONNECTED -> {
|
||||||
|
state = Tile.STATE_ACTIVE
|
||||||
|
subtitleCompat = null
|
||||||
|
}
|
||||||
|
|
||||||
|
DISCONNECTED, UNKNOWN -> {
|
||||||
|
state = Tile.STATE_INACTIVE
|
||||||
|
subtitleCompat = null
|
||||||
|
}
|
||||||
|
|
||||||
|
CONNECTING, RECONNECTING -> {
|
||||||
|
state = Tile.STATE_UNAVAILABLE
|
||||||
|
subtitleCompat = resources.getString(R.string.connecting)
|
||||||
|
}
|
||||||
|
|
||||||
|
DISCONNECTING -> {
|
||||||
|
state = Tile.STATE_UNAVAILABLE
|
||||||
|
subtitleCompat = resources.getString(R.string.disconnecting)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateTile()
|
||||||
|
}
|
||||||
|
// double update to fix weird visual glitches
|
||||||
|
tile.updateTile()
|
||||||
|
}
|
||||||
|
|
||||||
|
private var Tile.subtitleCompat: CharSequence?
|
||||||
|
set(value) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
this.subtitle = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
get() {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
return this.subtitle
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
package org.amnezia.vpn
|
package org.amnezia.vpn
|
||||||
|
|
||||||
|
import android.app.ActivityManager
|
||||||
|
import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE
|
||||||
import android.app.Notification
|
import android.app.Notification
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST
|
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST
|
||||||
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE
|
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED
|
||||||
import android.net.VpnService
|
import android.net.VpnService
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
@@ -16,6 +19,7 @@ import android.os.Process
|
|||||||
import androidx.annotation.MainThread
|
import androidx.annotation.MainThread
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.ServiceCompat
|
import androidx.core.app.ServiceCompat
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import kotlin.LazyThreadSafetyMode.NONE
|
import kotlin.LazyThreadSafetyMode.NONE
|
||||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -26,6 +30,7 @@ import kotlinx.coroutines.TimeoutCancellationException
|
|||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.drop
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
@@ -39,14 +44,11 @@ import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
|||||||
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTING
|
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTING
|
||||||
import org.amnezia.vpn.protocol.ProtocolState.RECONNECTING
|
import org.amnezia.vpn.protocol.ProtocolState.RECONNECTING
|
||||||
import org.amnezia.vpn.protocol.ProtocolState.UNKNOWN
|
import org.amnezia.vpn.protocol.ProtocolState.UNKNOWN
|
||||||
import org.amnezia.vpn.protocol.Statistics
|
|
||||||
import org.amnezia.vpn.protocol.Status
|
|
||||||
import org.amnezia.vpn.protocol.VpnException
|
import org.amnezia.vpn.protocol.VpnException
|
||||||
import org.amnezia.vpn.protocol.VpnStartException
|
import org.amnezia.vpn.protocol.VpnStartException
|
||||||
import org.amnezia.vpn.protocol.awg.Awg
|
import org.amnezia.vpn.protocol.awg.Awg
|
||||||
import org.amnezia.vpn.protocol.cloak.Cloak
|
import org.amnezia.vpn.protocol.cloak.Cloak
|
||||||
import org.amnezia.vpn.protocol.openvpn.OpenVpn
|
import org.amnezia.vpn.protocol.openvpn.OpenVpn
|
||||||
import org.amnezia.vpn.protocol.putStatistics
|
|
||||||
import org.amnezia.vpn.protocol.putStatus
|
import org.amnezia.vpn.protocol.putStatus
|
||||||
import org.amnezia.vpn.protocol.wireguard.Wireguard
|
import org.amnezia.vpn.protocol.wireguard.Wireguard
|
||||||
import org.amnezia.vpn.util.Log
|
import org.amnezia.vpn.util.Log
|
||||||
@@ -57,12 +59,16 @@ import org.json.JSONObject
|
|||||||
|
|
||||||
private const val TAG = "AmneziaVpnService"
|
private const val TAG = "AmneziaVpnService"
|
||||||
|
|
||||||
const val VPN_CONFIG = "VPN_CONFIG"
|
const val MSG_VPN_CONFIG = "VPN_CONFIG"
|
||||||
const val ERROR_MSG = "ERROR_MSG"
|
const val MSG_ERROR = "ERROR"
|
||||||
const val SAVE_LOGS = "SAVE_LOGS"
|
const val MSG_SAVE_LOGS = "SAVE_LOGS"
|
||||||
|
const val MSG_CLIENT_NAME = "CLIENT_NAME"
|
||||||
|
|
||||||
const val AFTER_PERMISSION_CHECK = "AFTER_PERMISSION_CHECK"
|
const val AFTER_PERMISSION_CHECK = "AFTER_PERMISSION_CHECK"
|
||||||
private const val PREFS_CONFIG_KEY = "LAST_CONF"
|
private const val PREFS_CONFIG_KEY = "LAST_CONF"
|
||||||
|
private const val PREFS_SERVER_NAME = "LAST_SERVER_NAME"
|
||||||
|
private const val PREFS_SERVER_INDEX = "LAST_SERVER_INDEX"
|
||||||
|
private const val PROCESS_NAME = "org.amnezia.vpn:amneziaVpnService"
|
||||||
private const val NOTIFICATION_ID = 1337
|
private const val NOTIFICATION_ID = 1337
|
||||||
private const val STATISTICS_SENDING_TIMEOUT = 1000L
|
private const val STATISTICS_SENDING_TIMEOUT = 1000L
|
||||||
private const val DISCONNECT_TIMEOUT = 5000L
|
private const val DISCONNECT_TIMEOUT = 5000L
|
||||||
@@ -76,6 +82,8 @@ class AmneziaVpnService : VpnService() {
|
|||||||
private var protocol: Protocol? = null
|
private var protocol: Protocol? = null
|
||||||
private val protocolCache = mutableMapOf<String, Protocol>()
|
private val protocolCache = mutableMapOf<String, Protocol>()
|
||||||
private var protocolState = MutableStateFlow(UNKNOWN)
|
private var protocolState = MutableStateFlow(UNKNOWN)
|
||||||
|
private var serverName: String? = null
|
||||||
|
private var serverIndex: Int = -1
|
||||||
|
|
||||||
private val isConnected
|
private val isConnected
|
||||||
get() = protocolState.value == CONNECTED
|
get() = protocolState.value == CONNECTED
|
||||||
@@ -89,8 +97,11 @@ class AmneziaVpnService : VpnService() {
|
|||||||
private var connectionJob: Job? = null
|
private var connectionJob: Job? = null
|
||||||
private var disconnectionJob: Job? = null
|
private var disconnectionJob: Job? = null
|
||||||
private var statisticsSendingJob: Job? = null
|
private var statisticsSendingJob: Job? = null
|
||||||
private lateinit var clientMessenger: IpcMessenger
|
|
||||||
private lateinit var networkState: NetworkState
|
private lateinit var networkState: NetworkState
|
||||||
|
private val clientMessengers = ConcurrentHashMap<Messenger, IpcMessenger>()
|
||||||
|
|
||||||
|
private val isActivityConnected
|
||||||
|
get() = clientMessengers.any { it.value.name == ACTIVITY_MESSENGER_NAME }
|
||||||
|
|
||||||
private val connectionExceptionHandler = CoroutineExceptionHandler { _, e ->
|
private val connectionExceptionHandler = CoroutineExceptionHandler { _, e ->
|
||||||
protocolState.value = DISCONNECTED
|
protocolState.value = DISCONNECTED
|
||||||
@@ -116,13 +127,22 @@ class AmneziaVpnService : VpnService() {
|
|||||||
Log.d(TAG, "Handle action: $action")
|
Log.d(TAG, "Handle action: $action")
|
||||||
when (action) {
|
when (action) {
|
||||||
Action.REGISTER_CLIENT -> {
|
Action.REGISTER_CLIENT -> {
|
||||||
clientMessenger.set(msg.replyTo)
|
val clientName = msg.data.getString(MSG_CLIENT_NAME)
|
||||||
|
val messenger = IpcMessenger(msg.replyTo, clientName)
|
||||||
|
clientMessengers[msg.replyTo] = messenger
|
||||||
|
Log.d(TAG, "Messenger client '$clientName' was registered")
|
||||||
|
if (clientName == ACTIVITY_MESSENGER_NAME && isConnected) launchSendingStatistics()
|
||||||
|
}
|
||||||
|
|
||||||
|
Action.UNREGISTER_CLIENT -> {
|
||||||
|
clientMessengers.remove(msg.replyTo)?.let {
|
||||||
|
Log.d(TAG, "Messenger client '${it.name}' was unregistered")
|
||||||
|
if (it.name == ACTIVITY_MESSENGER_NAME) stopSendingStatistics()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Action.CONNECT -> {
|
Action.CONNECT -> {
|
||||||
val vpnConfig = msg.data.getString(VPN_CONFIG)
|
connect(msg.data.getString(MSG_VPN_CONFIG))
|
||||||
Prefs.save(PREFS_CONFIG_KEY, vpnConfig)
|
|
||||||
connect(vpnConfig)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Action.DISCONNECT -> {
|
Action.DISCONNECT -> {
|
||||||
@@ -130,17 +150,17 @@ class AmneziaVpnService : VpnService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Action.REQUEST_STATUS -> {
|
Action.REQUEST_STATUS -> {
|
||||||
clientMessenger.send {
|
clientMessengers[msg.replyTo]?.let { clientMessenger ->
|
||||||
ServiceEvent.STATUS.packToMessage {
|
clientMessenger.send {
|
||||||
putStatus(Status.build {
|
ServiceEvent.STATUS.packToMessage {
|
||||||
setState(this@AmneziaVpnService.protocolState.value)
|
putStatus(this@AmneziaVpnService.protocolState.value)
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Action.SET_SAVE_LOGS -> {
|
Action.SET_SAVE_LOGS -> {
|
||||||
Log.saveLogs = msg.data.getBoolean(SAVE_LOGS)
|
Log.saveLogs = msg.data.getBoolean(MSG_SAVE_LOGS)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,7 +176,7 @@ class AmneziaVpnService : VpnService() {
|
|||||||
*/
|
*/
|
||||||
private val foregroundServiceTypeCompat
|
private val foregroundServiceTypeCompat
|
||||||
get() = when {
|
get() = when {
|
||||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE -> FOREGROUND_SERVICE_TYPE_SPECIAL_USE
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE -> FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED
|
||||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> FOREGROUND_SERVICE_TYPE_MANIFEST
|
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> FOREGROUND_SERVICE_TYPE_MANIFEST
|
||||||
else -> 0
|
else -> 0
|
||||||
}
|
}
|
||||||
@@ -189,27 +209,23 @@ class AmneziaVpnService : VpnService() {
|
|||||||
Log.d(TAG, "Create Amnezia VPN service")
|
Log.d(TAG, "Create Amnezia VPN service")
|
||||||
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
||||||
connectionScope = CoroutineScope(SupervisorJob() + Dispatchers.IO + connectionExceptionHandler)
|
connectionScope = CoroutineScope(SupervisorJob() + Dispatchers.IO + connectionExceptionHandler)
|
||||||
clientMessenger = IpcMessenger(messengerName = "Client")
|
loadServerData()
|
||||||
launchProtocolStateHandler()
|
launchProtocolStateHandler()
|
||||||
networkState = NetworkState(this, ::reconnect)
|
networkState = NetworkState(this, ::reconnect)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
val isAlwaysOnCompat =
|
val isAlwaysOn = intent != null && intent.action == SERVICE_INTERFACE
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) isAlwaysOn
|
|
||||||
else intent?.component?.packageName != packageName
|
|
||||||
|
|
||||||
if (isAlwaysOnCompat) {
|
if (isAlwaysOn) {
|
||||||
Log.d(TAG, "Start service via Always-on")
|
Log.d(TAG, "Start service via Always-on")
|
||||||
connect(Prefs.load(PREFS_CONFIG_KEY))
|
connect()
|
||||||
} else if (intent?.getBooleanExtra(AFTER_PERMISSION_CHECK, false) == true) {
|
} else if (intent?.getBooleanExtra(AFTER_PERMISSION_CHECK, false) == true) {
|
||||||
Log.d(TAG, "Start service after permission check")
|
Log.d(TAG, "Start service after permission check")
|
||||||
connect(Prefs.load(PREFS_CONFIG_KEY))
|
connect()
|
||||||
} else {
|
} else {
|
||||||
Log.d(TAG, "Start service")
|
Log.d(TAG, "Start service")
|
||||||
val vpnConfig = intent?.getStringExtra(VPN_CONFIG)
|
connect(intent?.getStringExtra(MSG_VPN_CONFIG))
|
||||||
Prefs.save(PREFS_CONFIG_KEY, vpnConfig)
|
|
||||||
connect(vpnConfig)
|
|
||||||
}
|
}
|
||||||
ServiceCompat.startForeground(this, NOTIFICATION_ID, notification, foregroundServiceTypeCompat)
|
ServiceCompat.startForeground(this, NOTIFICATION_ID, notification, foregroundServiceTypeCompat)
|
||||||
return START_REDELIVER_INTENT
|
return START_REDELIVER_INTENT
|
||||||
@@ -219,17 +235,16 @@ class AmneziaVpnService : VpnService() {
|
|||||||
Log.d(TAG, "onBind by $intent")
|
Log.d(TAG, "onBind by $intent")
|
||||||
if (intent?.action == SERVICE_INTERFACE) return super.onBind(intent)
|
if (intent?.action == SERVICE_INTERFACE) return super.onBind(intent)
|
||||||
isServiceBound = true
|
isServiceBound = true
|
||||||
if (isConnected) launchSendingStatistics()
|
|
||||||
return vpnServiceMessenger.binder
|
return vpnServiceMessenger.binder
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onUnbind(intent: Intent?): Boolean {
|
override fun onUnbind(intent: Intent?): Boolean {
|
||||||
Log.d(TAG, "onUnbind by $intent")
|
Log.d(TAG, "onUnbind by $intent")
|
||||||
if (intent?.action != SERVICE_INTERFACE) {
|
if (intent?.action != SERVICE_INTERFACE) {
|
||||||
isServiceBound = false
|
if (clientMessengers.isEmpty()) {
|
||||||
stopSendingStatistics()
|
isServiceBound = false
|
||||||
clientMessenger.reset()
|
if (isUnknown || isDisconnected) stopService()
|
||||||
if (isUnknown || isDisconnected) stopService()
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -238,7 +253,6 @@ class AmneziaVpnService : VpnService() {
|
|||||||
Log.d(TAG, "onRebind by $intent")
|
Log.d(TAG, "onRebind by $intent")
|
||||||
if (intent?.action != SERVICE_INTERFACE) {
|
if (intent?.action != SERVICE_INTERFACE) {
|
||||||
isServiceBound = true
|
isServiceBound = true
|
||||||
if (isConnected) launchSendingStatistics()
|
|
||||||
}
|
}
|
||||||
super.onRebind(intent)
|
super.onRebind(intent)
|
||||||
}
|
}
|
||||||
@@ -278,17 +292,16 @@ class AmneziaVpnService : VpnService() {
|
|||||||
*/
|
*/
|
||||||
private fun launchProtocolStateHandler() {
|
private fun launchProtocolStateHandler() {
|
||||||
mainScope.launch {
|
mainScope.launch {
|
||||||
protocolState.collect { protocolState ->
|
// drop first default UNKNOWN state
|
||||||
|
protocolState.drop(1).collect { protocolState ->
|
||||||
Log.d(TAG, "Protocol state changed: $protocolState")
|
Log.d(TAG, "Protocol state changed: $protocolState")
|
||||||
when (protocolState) {
|
when (protocolState) {
|
||||||
CONNECTED -> {
|
CONNECTED -> {
|
||||||
clientMessenger.send(ServiceEvent.CONNECTED)
|
|
||||||
networkState.bindNetworkListener()
|
networkState.bindNetworkListener()
|
||||||
if (isServiceBound) launchSendingStatistics()
|
if (isActivityConnected) launchSendingStatistics()
|
||||||
}
|
}
|
||||||
|
|
||||||
DISCONNECTED -> {
|
DISCONNECTED -> {
|
||||||
clientMessenger.send(ServiceEvent.DISCONNECTED)
|
|
||||||
networkState.unbindNetworkListener()
|
networkState.unbindNetworkListener()
|
||||||
stopSendingStatistics()
|
stopSendingStatistics()
|
||||||
if (!isServiceBound) stopService()
|
if (!isServiceBound) stopService()
|
||||||
@@ -300,12 +313,19 @@ class AmneziaVpnService : VpnService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
RECONNECTING -> {
|
RECONNECTING -> {
|
||||||
clientMessenger.send(ServiceEvent.RECONNECTING)
|
|
||||||
stopSendingStatistics()
|
stopSendingStatistics()
|
||||||
}
|
}
|
||||||
|
|
||||||
CONNECTING, UNKNOWN -> {}
|
CONNECTING, UNKNOWN -> {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clientMessengers.send {
|
||||||
|
ServiceEvent.STATUS_CHANGED.packToMessage {
|
||||||
|
putStatus(protocolState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VpnStateStore.store { VpnState(protocolState, serverName, serverIndex) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -332,7 +352,17 @@ class AmneziaVpnService : VpnService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@MainThread
|
@MainThread
|
||||||
private fun connect(vpnConfig: String?) {
|
private fun connect(vpnConfig: String? = null) {
|
||||||
|
if (vpnConfig == null) {
|
||||||
|
connectToVpn(Prefs.load(PREFS_CONFIG_KEY))
|
||||||
|
} else {
|
||||||
|
Prefs.save(PREFS_CONFIG_KEY, vpnConfig)
|
||||||
|
connectToVpn(vpnConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainThread
|
||||||
|
private fun connectToVpn(vpnConfig: String) {
|
||||||
if (isConnected || protocolState.value == CONNECTING) return
|
if (isConnected || protocolState.value == CONNECTING) return
|
||||||
|
|
||||||
Log.d(TAG, "Start VPN connection")
|
Log.d(TAG, "Start VPN connection")
|
||||||
@@ -340,6 +370,7 @@ class AmneziaVpnService : VpnService() {
|
|||||||
protocolState.value = CONNECTING
|
protocolState.value = CONNECTING
|
||||||
|
|
||||||
val config = parseConfigToJson(vpnConfig)
|
val config = parseConfigToJson(vpnConfig)
|
||||||
|
saveServerData(config)
|
||||||
if (config == null) {
|
if (config == null) {
|
||||||
onError("Invalid VPN config")
|
onError("Invalid VPN config")
|
||||||
protocolState.value = DISCONNECTED
|
protocolState.value = DISCONNECTED
|
||||||
@@ -417,24 +448,38 @@ class AmneziaVpnService : VpnService() {
|
|||||||
private fun onError(msg: String) {
|
private fun onError(msg: String) {
|
||||||
Log.e(TAG, msg)
|
Log.e(TAG, msg)
|
||||||
mainScope.launch {
|
mainScope.launch {
|
||||||
clientMessenger.send {
|
clientMessengers.send {
|
||||||
ServiceEvent.ERROR.packToMessage {
|
ServiceEvent.ERROR.packToMessage {
|
||||||
putString(ERROR_MSG, msg)
|
putString(MSG_ERROR, msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseConfigToJson(vpnConfig: String?): JSONObject? =
|
private fun parseConfigToJson(vpnConfig: String): JSONObject? =
|
||||||
try {
|
if (vpnConfig.isBlank()) {
|
||||||
vpnConfig?.let {
|
|
||||||
JSONObject(it)
|
|
||||||
}
|
|
||||||
} catch (e: JSONException) {
|
|
||||||
onError("Invalid VPN config json format: ${e.message}")
|
|
||||||
null
|
null
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
JSONObject(vpnConfig)
|
||||||
|
} catch (e: JSONException) {
|
||||||
|
onError("Invalid VPN config json format: ${e.message}")
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun saveServerData(config: JSONObject?) {
|
||||||
|
serverName = config?.opt("description") as String?
|
||||||
|
serverIndex = config?.opt("serverIndex") as Int? ?: -1
|
||||||
|
Prefs.save(PREFS_SERVER_NAME, serverName)
|
||||||
|
Prefs.save(PREFS_SERVER_INDEX, serverIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadServerData() {
|
||||||
|
serverName = Prefs.load<String>(PREFS_SERVER_NAME).ifBlank { null }
|
||||||
|
if (serverName != null) serverIndex = Prefs.load(PREFS_SERVER_INDEX)
|
||||||
|
}
|
||||||
|
|
||||||
private fun checkPermission(): Boolean =
|
private fun checkPermission(): Boolean =
|
||||||
if (prepare(applicationContext) != null) {
|
if (prepare(applicationContext) != null) {
|
||||||
Intent(this, VpnRequestActivity::class.java).apply {
|
Intent(this, VpnRequestActivity::class.java).apply {
|
||||||
@@ -446,4 +491,12 @@ class AmneziaVpnService : VpnService() {
|
|||||||
} else {
|
} else {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun isRunning(context: Context): Boolean =
|
||||||
|
(context.getSystemService(ACTIVITY_SERVICE) as ActivityManager)
|
||||||
|
.runningAppProcesses.any {
|
||||||
|
it.processName == PROCESS_NAME && it.importance <= IMPORTANCE_FOREGROUND_SERVICE
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import android.Manifest.permission.INTERNET
|
||||||
|
import android.content.pm.ApplicationInfo
|
||||||
|
import android.content.pm.PackageInfo
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.content.pm.PackageManager.NameNotFoundException
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.Bitmap.Config.ARGB_8888
|
||||||
|
import androidx.core.graphics.drawable.toBitmapOrNull
|
||||||
|
import org.amnezia.vpn.util.Log
|
||||||
|
import org.json.JSONArray
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
private const val TAG = "AppListProvider"
|
||||||
|
|
||||||
|
object AppListProvider {
|
||||||
|
fun getAppList(pm: PackageManager, selfPackageName: String): String {
|
||||||
|
val jsonArray = JSONArray()
|
||||||
|
pm.getPackagesHoldingPermissions(arrayOf(INTERNET), 0)
|
||||||
|
.filter { it.packageName != selfPackageName }
|
||||||
|
.map { App(it, pm) }
|
||||||
|
.sortedWith(App::compareTo)
|
||||||
|
.map(App::toJson)
|
||||||
|
.forEach(jsonArray::put)
|
||||||
|
return jsonArray.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAppIcon(pm: PackageManager, packageName: String, width: Int, height: Int): Bitmap {
|
||||||
|
val icon = try {
|
||||||
|
pm.getApplicationIcon(packageName)
|
||||||
|
} catch (e: NameNotFoundException) {
|
||||||
|
Log.e(TAG, "Package $packageName was not found: $e")
|
||||||
|
pm.defaultActivityIcon
|
||||||
|
}
|
||||||
|
val w: Int = if (width > 0) width else icon.intrinsicWidth
|
||||||
|
val h: Int = if (height > 0) height else icon.intrinsicHeight
|
||||||
|
return icon.toBitmapOrNull(w, h, ARGB_8888)
|
||||||
|
?: Bitmap.createBitmap(w, h, ARGB_8888)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class App(pi: PackageInfo, pm: PackageManager, ai: ApplicationInfo = pi.applicationInfo) : Comparable<App> {
|
||||||
|
val name: String?
|
||||||
|
val packageName: String = pi.packageName
|
||||||
|
val icon: Boolean = ai.icon != 0
|
||||||
|
val isLaunchable: Boolean = pm.getLaunchIntentForPackage(packageName) != null
|
||||||
|
|
||||||
|
init {
|
||||||
|
val name = ai.loadLabel(pm).toString()
|
||||||
|
this.name = if (name != packageName) name else null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun compareTo(other: App): Int {
|
||||||
|
val r = other.isLaunchable.compareTo(isLaunchable)
|
||||||
|
if (r != 0) return r
|
||||||
|
if (name != other.name) {
|
||||||
|
return when {
|
||||||
|
name == null -> 1
|
||||||
|
other.name == null -> -1
|
||||||
|
else -> String.CASE_INSENSITIVE_ORDER.compare(name, other.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return String.CASE_INSENSITIVE_ORDER.compare(packageName, other.packageName)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toJson(): JSONObject {
|
||||||
|
val jsonObject = JSONObject()
|
||||||
|
jsonObject.put("package", packageName)
|
||||||
|
jsonObject.put("name", name)
|
||||||
|
jsonObject.put("icon", icon)
|
||||||
|
jsonObject.put("launchable", isLaunchable)
|
||||||
|
return jsonObject
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,9 +20,7 @@ sealed interface IpcMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum class ServiceEvent : IpcMessage {
|
enum class ServiceEvent : IpcMessage {
|
||||||
CONNECTED,
|
STATUS_CHANGED,
|
||||||
DISCONNECTED,
|
|
||||||
RECONNECTING,
|
|
||||||
STATUS,
|
STATUS,
|
||||||
STATISTICS_UPDATE,
|
STATISTICS_UPDATE,
|
||||||
ERROR
|
ERROR
|
||||||
@@ -30,6 +28,7 @@ enum class ServiceEvent : IpcMessage {
|
|||||||
|
|
||||||
enum class Action : IpcMessage {
|
enum class Action : IpcMessage {
|
||||||
REGISTER_CLIENT,
|
REGISTER_CLIENT,
|
||||||
|
UNREGISTER_CLIENT,
|
||||||
CONNECT,
|
CONNECT,
|
||||||
DISCONNECT,
|
DISCONNECT,
|
||||||
REQUEST_STATUS,
|
REQUEST_STATUS,
|
||||||
|
|||||||
@@ -9,11 +9,21 @@ import org.amnezia.vpn.util.Log
|
|||||||
private const val TAG = "IpcMessenger"
|
private const val TAG = "IpcMessenger"
|
||||||
|
|
||||||
class IpcMessenger(
|
class IpcMessenger(
|
||||||
|
messengerName: String? = null,
|
||||||
private val onDeadObjectException: () -> Unit = {},
|
private val onDeadObjectException: () -> Unit = {},
|
||||||
private val onRemoteException: () -> Unit = {},
|
private val onRemoteException: () -> Unit = {}
|
||||||
private val messengerName: String = "Unknown"
|
|
||||||
) {
|
) {
|
||||||
private var messenger: Messenger? = null
|
private var messenger: Messenger? = null
|
||||||
|
val name = messengerName ?: "Unknown"
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
messenger: Messenger,
|
||||||
|
messengerName: String? = null,
|
||||||
|
onDeadObjectException: () -> Unit = {},
|
||||||
|
onRemoteException: () -> Unit = {}
|
||||||
|
) : this(messengerName, onDeadObjectException, onRemoteException) {
|
||||||
|
this.messenger = messenger
|
||||||
|
}
|
||||||
|
|
||||||
fun set(messenger: Messenger) {
|
fun set(messenger: Messenger) {
|
||||||
this.messenger = messenger
|
this.messenger = messenger
|
||||||
@@ -25,19 +35,29 @@ class IpcMessenger(
|
|||||||
|
|
||||||
fun send(msg: () -> Message) = messenger?.sendMsg(msg())
|
fun send(msg: () -> Message) = messenger?.sendMsg(msg())
|
||||||
|
|
||||||
|
fun send(msg: Message, replyTo: Messenger) = messenger?.sendMsg(msg.apply { this.replyTo = replyTo })
|
||||||
|
|
||||||
fun <T> send(msg: T)
|
fun <T> send(msg: T)
|
||||||
where T : Enum<T>, T : IpcMessage = messenger?.sendMsg(msg.packToMessage())
|
where T : Enum<T>, T : IpcMessage = messenger?.sendMsg(msg.packToMessage())
|
||||||
|
|
||||||
|
fun <T> send(msg: T, replyTo: Messenger)
|
||||||
|
where T : Enum<T>, T : IpcMessage = messenger?.sendMsg(msg.packToMessage().apply { this.replyTo = replyTo })
|
||||||
|
|
||||||
private fun Messenger.sendMsg(msg: Message) {
|
private fun Messenger.sendMsg(msg: Message) {
|
||||||
try {
|
try {
|
||||||
send(msg)
|
send(msg)
|
||||||
} catch (e: DeadObjectException) {
|
} catch (e: DeadObjectException) {
|
||||||
Log.w(TAG, "$messengerName messenger is dead")
|
Log.w(TAG, "$name messenger is dead")
|
||||||
messenger = null
|
messenger = null
|
||||||
onDeadObjectException()
|
onDeadObjectException()
|
||||||
} catch (e: RemoteException) {
|
} catch (e: RemoteException) {
|
||||||
Log.w(TAG, "Sending a message to the $messengerName messenger failed: ${e.message}")
|
Log.w(TAG, "Sending a message to the $name messenger failed: ${e.message}")
|
||||||
onRemoteException()
|
onRemoteException()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Map<Messenger, IpcMessenger>.send(msg: () -> Message) = this.values.forEach { it.send(msg) }
|
||||||
|
|
||||||
|
fun <T> Map<Messenger, IpcMessenger>.send(msg: T)
|
||||||
|
where T : Enum<T>, T : IpcMessage = this.values.forEach { it.send(msg) }
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
package org.amnezia.vpn
|
package org.amnezia.vpn
|
||||||
|
|
||||||
|
import android.app.AlertDialog
|
||||||
import android.app.KeyguardManager
|
import android.app.KeyguardManager
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
|
import android.content.res.Configuration.UI_MODE_NIGHT_MASK
|
||||||
|
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||||
import android.net.VpnService
|
import android.net.VpnService
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.provider.Settings
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.result.ActivityResult
|
import androidx.activity.result.ActivityResult
|
||||||
@@ -52,19 +56,43 @@ class VpnRequestActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun checkRequestResult(result: ActivityResult) {
|
private fun checkRequestResult(result: ActivityResult) {
|
||||||
when (result.resultCode) {
|
when (val resultCode = result.resultCode) {
|
||||||
RESULT_OK -> onPermissionGranted()
|
RESULT_OK -> {
|
||||||
else -> Toast.makeText(this, "Vpn permission denied", Toast.LENGTH_LONG).show()
|
onPermissionGranted()
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
Log.w(TAG, "Vpn permission denied, resultCode: $resultCode")
|
||||||
|
showOnVpnPermissionRejectDialog()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
finish()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onPermissionGranted() {
|
private fun onPermissionGranted() {
|
||||||
Toast.makeText(this, "Vpn permission granted", Toast.LENGTH_LONG).show()
|
Toast.makeText(this, resources.getString(R.string.vpnGranted), Toast.LENGTH_LONG).show()
|
||||||
Intent(applicationContext, AmneziaVpnService::class.java).apply {
|
Intent(applicationContext, AmneziaVpnService::class.java).apply {
|
||||||
putExtra(AFTER_PERMISSION_CHECK, true)
|
putExtra(AFTER_PERMISSION_CHECK, true)
|
||||||
}.also {
|
}.also {
|
||||||
ContextCompat.startForegroundService(this, it)
|
ContextCompat.startForegroundService(this, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showOnVpnPermissionRejectDialog() {
|
||||||
|
AlertDialog.Builder(this, getDialogTheme())
|
||||||
|
.setTitle(R.string.vpnSetupFailed)
|
||||||
|
.setMessage(R.string.vpnSetupFailedMessage)
|
||||||
|
.setNegativeButton(R.string.ok) { _, _ -> }
|
||||||
|
.setPositiveButton(R.string.openVpnSettings) { _, _ ->
|
||||||
|
startActivity(Intent(Settings.ACTION_VPN_SETTINGS))
|
||||||
|
}
|
||||||
|
.setOnDismissListener { finish() }
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getDialogTheme(): Int =
|
||||||
|
if (resources.configuration.uiMode and UI_MODE_NIGHT_MASK == UI_MODE_NIGHT_YES)
|
||||||
|
android.R.style.Theme_DeviceDefault_Dialog_Alert
|
||||||
|
else
|
||||||
|
android.R.style.Theme_DeviceDefault_Light_Dialog_Alert
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package org.amnezia.vpn
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import androidx.datastore.core.MultiProcessDataStoreFactory
|
||||||
|
import androidx.datastore.core.Serializer
|
||||||
|
import androidx.datastore.dataStoreFile
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.ObjectInputStream
|
||||||
|
import java.io.ObjectOutputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
import java.io.Serializable
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.amnezia.vpn.protocol.ProtocolState
|
||||||
|
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
||||||
|
import org.amnezia.vpn.util.Log
|
||||||
|
|
||||||
|
private const val TAG = "VpnState"
|
||||||
|
private const val STORE_FILE_NAME = "vpnState"
|
||||||
|
|
||||||
|
data class VpnState(
|
||||||
|
val protocolState: ProtocolState,
|
||||||
|
val serverName: String? = null,
|
||||||
|
val serverIndex: Int = -1
|
||||||
|
) : Serializable {
|
||||||
|
companion object {
|
||||||
|
private const val serialVersionUID: Long = -1760654961004181606
|
||||||
|
val defaultState: VpnState = VpnState(DISCONNECTED)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object VpnStateStore {
|
||||||
|
private lateinit var app: Application
|
||||||
|
|
||||||
|
private val dataStore = MultiProcessDataStoreFactory.create(
|
||||||
|
serializer = VpnStateSerializer(),
|
||||||
|
produceFile = { app.dataStoreFile(STORE_FILE_NAME) }
|
||||||
|
)
|
||||||
|
|
||||||
|
fun init(app: Application) {
|
||||||
|
Log.v(TAG, "Init VpnStateStore")
|
||||||
|
this.app = app
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dataFlow(): Flow<VpnState> = dataStore.data
|
||||||
|
|
||||||
|
suspend fun store(f: (vpnState: VpnState) -> VpnState) {
|
||||||
|
try {
|
||||||
|
dataStore.updateData(f)
|
||||||
|
} catch (e : Exception) {
|
||||||
|
Log.e(TAG, "Failed to store VpnState: $e")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class VpnStateSerializer : Serializer<VpnState> {
|
||||||
|
override val defaultValue: VpnState = VpnState.defaultState
|
||||||
|
|
||||||
|
override suspend fun readFrom(input: InputStream): VpnState {
|
||||||
|
return withContext(Dispatchers.IO) {
|
||||||
|
ObjectInputStream(input).use {
|
||||||
|
it.readObject() as VpnState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun writeTo(t: VpnState, output: OutputStream) {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
ObjectOutputStream(output).use {
|
||||||
|
it.writeObject(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +1,23 @@
|
|||||||
package org.amnezia.vpn.qt
|
package org.amnezia.vpn.qt
|
||||||
|
|
||||||
|
import org.amnezia.vpn.protocol.ProtocolState
|
||||||
|
import org.amnezia.vpn.protocol.Status
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JNI functions of the AndroidController class from android_controller.cpp,
|
* JNI functions of the AndroidController class from android_controller.cpp,
|
||||||
* called by events in the Android part of the client
|
* called by events in the Android part of the client
|
||||||
*/
|
*/
|
||||||
object QtAndroidController {
|
object QtAndroidController {
|
||||||
|
|
||||||
|
fun onStatus(status: Status) = onStatus(status.state)
|
||||||
|
fun onStatus(protocolState: ProtocolState) = onStatus(protocolState.ordinal)
|
||||||
|
|
||||||
external fun onStatus(stateCode: Int)
|
external fun onStatus(stateCode: Int)
|
||||||
external fun onServiceDisconnected()
|
external fun onServiceDisconnected()
|
||||||
external fun onServiceError()
|
external fun onServiceError()
|
||||||
|
|
||||||
external fun onVpnPermissionRejected()
|
external fun onVpnPermissionRejected()
|
||||||
external fun onVpnConnected()
|
external fun onVpnStateChanged(stateCode: Int)
|
||||||
external fun onVpnDisconnected()
|
|
||||||
external fun onVpnReconnecting()
|
|
||||||
external fun onStatisticsUpdate(rxBytes: Long, txBytes: Long)
|
external fun onStatisticsUpdate(rxBytes: Long, txBytes: Long)
|
||||||
|
|
||||||
external fun onFileOpened(uri: String)
|
external fun onFileOpened(uri: String)
|
||||||
|
|||||||
@@ -109,9 +109,11 @@ object Log {
|
|||||||
"${deviceInfo()}\n${readLogs()}\nLOGCAT:\n${getLogcat()}"
|
"${deviceInfo()}\n${readLogs()}\nLOGCAT:\n${getLogcat()}"
|
||||||
|
|
||||||
fun clearLogs() {
|
fun clearLogs() {
|
||||||
withLock {
|
if (logDir.exists()) {
|
||||||
logFile.delete()
|
withLock {
|
||||||
rotateLogFile.delete()
|
logFile.delete()
|
||||||
|
rotateLogFile.delete()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
package org.amnezia.vpn.protocol.wireguard
|
|
||||||
|
|
||||||
object GoBackend {
|
|
||||||
external fun wgGetConfig(handle: Int): String?
|
|
||||||
external fun wgGetSocketV4(handle: Int): Int
|
|
||||||
external fun wgGetSocketV6(handle: Int): Int
|
|
||||||
external fun wgTurnOff(handle: Int)
|
|
||||||
external fun wgTurnOn(ifName: String, tunFd: Int, settings: String): Int
|
|
||||||
external fun wgVersion(): String
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package org.amnezia.awg
|
||||||
|
|
||||||
|
object GoBackend {
|
||||||
|
external fun awgGetConfig(handle: Int): String?
|
||||||
|
external fun awgGetSocketV4(handle: Int): Int
|
||||||
|
external fun awgGetSocketV6(handle: Int): Int
|
||||||
|
external fun awgTurnOff(handle: Int)
|
||||||
|
external fun awgTurnOn(ifName: String, tunFd: Int, settings: String): Int
|
||||||
|
external fun awgVersion(): String
|
||||||
|
}
|
||||||
+8
-6
@@ -4,6 +4,7 @@ import android.content.Context
|
|||||||
import android.net.VpnService.Builder
|
import android.net.VpnService.Builder
|
||||||
import java.util.TreeMap
|
import java.util.TreeMap
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import org.amnezia.awg.GoBackend
|
||||||
import org.amnezia.vpn.protocol.Protocol
|
import org.amnezia.vpn.protocol.Protocol
|
||||||
import org.amnezia.vpn.protocol.ProtocolState
|
import org.amnezia.vpn.protocol.ProtocolState
|
||||||
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
|
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
|
||||||
@@ -61,7 +62,7 @@ open class Wireguard : Protocol() {
|
|||||||
override val statistics: Statistics
|
override val statistics: Statistics
|
||||||
get() {
|
get() {
|
||||||
if (tunnelHandle == -1) return Statistics.EMPTY_STATISTICS
|
if (tunnelHandle == -1) return Statistics.EMPTY_STATISTICS
|
||||||
val config = GoBackend.wgGetConfig(tunnelHandle) ?: return Statistics.EMPTY_STATISTICS
|
val config = GoBackend.awgGetConfig(tunnelHandle) ?: return Statistics.EMPTY_STATISTICS
|
||||||
return Statistics.build {
|
return Statistics.build {
|
||||||
var optsCount = 0
|
var optsCount = 0
|
||||||
config.splitToSequence("\n").forEach { line ->
|
config.splitToSequence("\n").forEach { line ->
|
||||||
@@ -94,6 +95,7 @@ open class Wireguard : Protocol() {
|
|||||||
return WireguardConfig.build {
|
return WireguardConfig.build {
|
||||||
configWireguard(configData, configDataJson)
|
configWireguard(configData, configDataJson)
|
||||||
configSplitTunneling(config)
|
configSplitTunneling(config)
|
||||||
|
configAppSplitTunneling(config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,8 +158,8 @@ open class Wireguard : Protocol() {
|
|||||||
if (tunFd == null) {
|
if (tunFd == null) {
|
||||||
throw VpnStartException("Create VPN interface: permission not granted or revoked")
|
throw VpnStartException("Create VPN interface: permission not granted or revoked")
|
||||||
}
|
}
|
||||||
Log.v(TAG, "Wg-go backend ${GoBackend.wgVersion()}")
|
Log.i(TAG, "awg-go backend ${GoBackend.awgVersion()}")
|
||||||
tunnelHandle = GoBackend.wgTurnOn(ifName, tunFd.detachFd(), config.toWgUserspaceString())
|
tunnelHandle = GoBackend.awgTurnOn(ifName, tunFd.detachFd(), config.toWgUserspaceString())
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tunnelHandle < 0) {
|
if (tunnelHandle < 0) {
|
||||||
@@ -165,8 +167,8 @@ open class Wireguard : Protocol() {
|
|||||||
throw VpnStartException("Wireguard tunnel creation error")
|
throw VpnStartException("Wireguard tunnel creation error")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!protect(GoBackend.wgGetSocketV4(tunnelHandle)) || !protect(GoBackend.wgGetSocketV6(tunnelHandle))) {
|
if (!protect(GoBackend.awgGetSocketV4(tunnelHandle)) || !protect(GoBackend.awgGetSocketV6(tunnelHandle))) {
|
||||||
GoBackend.wgTurnOff(tunnelHandle)
|
GoBackend.awgTurnOff(tunnelHandle)
|
||||||
tunnelHandle = -1
|
tunnelHandle = -1
|
||||||
throw VpnStartException("Protect VPN interface: permission not granted or revoked")
|
throw VpnStartException("Protect VPN interface: permission not granted or revoked")
|
||||||
}
|
}
|
||||||
@@ -179,7 +181,7 @@ open class Wireguard : Protocol() {
|
|||||||
}
|
}
|
||||||
val handleToClose = tunnelHandle
|
val handleToClose = tunnelHandle
|
||||||
tunnelHandle = -1
|
tunnelHandle = -1
|
||||||
GoBackend.wgTurnOff(handleToClose)
|
GoBackend.awgTurnOff(handleToClose)
|
||||||
state.value = DISCONNECTED
|
state.value = DISCONNECTED
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ set(QT_ANDROID_MULTI_ABI_FORWARD_VARS "QT_NO_GLOBAL_APK_TARGET_PART_OF_ALL;CMAKE
|
|||||||
|
|
||||||
# We need to include qtprivate api's
|
# We need to include qtprivate api's
|
||||||
# As QAndroidBinder is not yet implemented with a public api
|
# As QAndroidBinder is not yet implemented with a public api
|
||||||
set(LIBS ${LIBS} Qt6::CorePrivate)
|
set(LIBS ${LIBS} Qt6::CorePrivate -ljnigraphics)
|
||||||
|
|
||||||
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/platforms/android)
|
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/platforms/android)
|
||||||
|
|
||||||
@@ -30,6 +30,7 @@ set(HEADERS ${HEADERS}
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.h
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.h
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.h
|
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/core/installedAppsImageProvider.h
|
||||||
)
|
)
|
||||||
|
|
||||||
set(SOURCES ${SOURCES}
|
set(SOURCES ${SOURCES}
|
||||||
@@ -38,20 +39,18 @@ set(SOURCES ${SOURCES}
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/core/installedAppsImageProvider.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
foreach(abi IN ITEMS ${QT_ANDROID_ABIS})
|
foreach(abi IN ITEMS ${QT_ANDROID_ABIS})
|
||||||
set_property(TARGET ${PROJECT} PROPERTY QT_ANDROID_EXTRA_LIBS
|
set_property(TARGET ${PROJECT} PROPERTY QT_ANDROID_EXTRA_LIBS
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/amneziawg/android/${abi}/libwg.so
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/amneziawg/android/${abi}/libwg-go.so
|
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/amneziawg/android/${abi}/libwg-go.so
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/amneziawg/android/${abi}/libwg-quick.so
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/shadowsocks/android/${abi}/libredsocks.so
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/shadowsocks/android/${abi}/libsslocal.so
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/shadowsocks/android/${abi}/libtun2socks.so
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/libck-ovpn-plugin.so
|
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/libck-ovpn-plugin.so
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/libovpn3.so
|
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/libovpn3.so
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/libovpnutil.so
|
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/libovpnutil.so
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/librsapss.so
|
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/librsapss.so
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openssl/android/${abi}/libcrypto_3.so
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openssl/android/${abi}/libssl_3.so
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/libssh/android/${abi}/libssh.so
|
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/libssh/android/${abi}/libssh.so
|
||||||
)
|
)
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|||||||
@@ -107,16 +107,20 @@ target_sources(${PROJECT} PRIVATE
|
|||||||
${CLIENT_ROOT_DIR}/platforms/ios/LogController.swift
|
${CLIENT_ROOT_DIR}/platforms/ios/LogController.swift
|
||||||
${CLIENT_ROOT_DIR}/platforms/ios/Log.swift
|
${CLIENT_ROOT_DIR}/platforms/ios/Log.swift
|
||||||
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
|
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
|
||||||
|
${CLIENT_ROOT_DIR}/platforms/ios/ScreenProtection.swift
|
||||||
|
${CLIENT_ROOT_DIR}/platforms/ios/VPNCController.swift
|
||||||
)
|
)
|
||||||
|
|
||||||
target_sources(${PROJECT} PRIVATE
|
target_sources(${PROJECT} PRIVATE
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/AmneziaVPNLaunchScreen.storyboard
|
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/AmneziaVPNLaunchScreen.storyboard
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Media.xcassets
|
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Media.xcassets
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
|
||||||
)
|
)
|
||||||
|
|
||||||
set_property(TARGET ${PROJECT} APPEND PROPERTY RESOURCE
|
set_property(TARGET ${PROJECT} APPEND PROPERTY RESOURCE
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/AmneziaVPNLaunchScreen.storyboard
|
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/AmneziaVPNLaunchScreen.storyboard
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Media.xcassets
|
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Media.xcassets
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
|
||||||
)
|
)
|
||||||
|
|
||||||
add_subdirectory(ios/networkextension)
|
add_subdirectory(ios/networkextension)
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
message("MAC build")
|
message("MAC build")
|
||||||
|
|
||||||
|
set(APPLE_PROJECT_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||||
|
|
||||||
find_library(FW_SYSTEMCONFIG SystemConfiguration)
|
find_library(FW_SYSTEMCONFIG SystemConfiguration)
|
||||||
find_library(FW_SERVICEMGMT ServiceManagement)
|
find_library(FW_SERVICEMGMT ServiceManagement)
|
||||||
find_library(FW_SECURITY Security)
|
find_library(FW_SECURITY Security)
|
||||||
@@ -19,6 +21,17 @@ set(LIBS ${LIBS}
|
|||||||
)
|
)
|
||||||
|
|
||||||
set_target_properties(${PROJECT} PROPERTIES MACOSX_BUNDLE TRUE)
|
set_target_properties(${PROJECT} PROPERTIES MACOSX_BUNDLE TRUE)
|
||||||
|
|
||||||
|
set_target_properties(${PROJECT} PROPERTIES
|
||||||
|
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Info.plist
|
||||||
|
XCODE_ATTRIBUTE_PRODUCT_NAME "AmneziaVPN"
|
||||||
|
XCODE_ATTRIBUTE_BUNDLE_INFO_STRING "AmneziaVPN"
|
||||||
|
MACOSX_BUNDLE_GUI_IDENTIFIER "${BUILD_OSX_APP_IDENTIFIER}"
|
||||||
|
MACOSX_BUNDLE_BUNDLE_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
|
||||||
|
MACOSX_BUNDLE_LONG_VERSION_STRING "${APPLE_PROJECT_VERSION}-${CMAKE_PROJECT_VERSION_TWEAK}"
|
||||||
|
MACOSX_BUNDLE_SHORT_VERSION_STRING "${APPLE_PROJECT_VERSION}"
|
||||||
|
)
|
||||||
|
|
||||||
set(CMAKE_OSX_ARCHITECTURES "x86_64" CACHE INTERNAL "" FORCE)
|
set(CMAKE_OSX_ARCHITECTURES "x86_64" CACHE INTERNAL "" FORCE)
|
||||||
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15)
|
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15)
|
||||||
|
|
||||||
|
|||||||
@@ -3,17 +3,15 @@
|
|||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
|
||||||
#include "core/controllers/serverController.h"
|
AwgConfigurator::AwgConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent)
|
||||||
|
: WireguardConfigurator(settings, serverController, true, parent)
|
||||||
AwgConfigurator::AwgConfigurator(std::shared_ptr<Settings> settings, QObject *parent)
|
|
||||||
: WireguardConfigurator(settings, true, parent)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
QString AwgConfigurator::genAwgConfig(const ServerCredentials &credentials, DockerContainer container,
|
QString AwgConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
|
||||||
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode)
|
ErrorCode errorCode)
|
||||||
{
|
{
|
||||||
QString config = WireguardConfigurator::genWireguardConfig(credentials, container, containerConfig, clientId, errorCode);
|
QString config = WireguardConfigurator::createConfig(credentials, container, containerConfig, errorCode);
|
||||||
|
|
||||||
QJsonObject jsonConfig = QJsonDocument::fromJson(config.toUtf8()).object();
|
QJsonObject jsonConfig = QJsonDocument::fromJson(config.toUtf8()).object();
|
||||||
QString awgConfig = jsonConfig.value(config_key::config).toString();
|
QString awgConfig = jsonConfig.value(config_key::config).toString();
|
||||||
@@ -41,8 +39,8 @@ QString AwgConfigurator::genAwgConfig(const ServerCredentials &credentials, Dock
|
|||||||
jsonConfig[config_key::responsePacketMagicHeader] = configMap.value(config_key::responsePacketMagicHeader);
|
jsonConfig[config_key::responsePacketMagicHeader] = configMap.value(config_key::responsePacketMagicHeader);
|
||||||
jsonConfig[config_key::underloadPacketMagicHeader] = configMap.value(config_key::underloadPacketMagicHeader);
|
jsonConfig[config_key::underloadPacketMagicHeader] = configMap.value(config_key::underloadPacketMagicHeader);
|
||||||
jsonConfig[config_key::transportPacketMagicHeader] = configMap.value(config_key::transportPacketMagicHeader);
|
jsonConfig[config_key::transportPacketMagicHeader] = configMap.value(config_key::transportPacketMagicHeader);
|
||||||
jsonConfig[config_key::mtu] = containerConfig.value(ProtocolProps::protoToString(Proto::Awg)).toObject().
|
jsonConfig[config_key::mtu] =
|
||||||
value(config_key::mtu).toString(protocols::awg::defaultMtu);
|
containerConfig.value(ProtocolProps::protoToString(Proto::Awg)).toObject().value(config_key::mtu).toString(protocols::awg::defaultMtu);
|
||||||
|
|
||||||
return QJsonDocument(jsonConfig).toJson();
|
return QJsonDocument(jsonConfig).toJson();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ class AwgConfigurator : public WireguardConfigurator
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
AwgConfigurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
|
AwgConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
|
||||||
|
|
||||||
QString genAwgConfig(const ServerCredentials &credentials, DockerContainer container,
|
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||||
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode = nullptr);
|
const QJsonObject &containerConfig, ErrorCode errorCode);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // AWGCONFIGURATOR_H
|
#endif // AWGCONFIGURATOR_H
|
||||||
|
|||||||
@@ -1,34 +1,33 @@
|
|||||||
#include "cloak_configurator.h"
|
#include "cloak_configurator.h"
|
||||||
|
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
|
||||||
#include "core/controllers/serverController.h"
|
|
||||||
#include "containers/containers_defs.h"
|
#include "containers/containers_defs.h"
|
||||||
|
#include "core/controllers/serverController.h"
|
||||||
|
|
||||||
CloakConfigurator::CloakConfigurator(std::shared_ptr<Settings> settings, QObject *parent):
|
CloakConfigurator::CloakConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent)
|
||||||
ConfiguratorBase(settings, parent)
|
: ConfiguratorBase(settings, serverController, parent)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString CloakConfigurator::genCloakConfig(const ServerCredentials &credentials,
|
QString CloakConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
|
||||||
DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode)
|
ErrorCode errorCode)
|
||||||
{
|
{
|
||||||
ErrorCode e = ErrorCode::NoError;
|
QString cloakPublicKey =
|
||||||
ServerController serverController(m_settings);
|
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::cloak::ckPublicKeyPath, errorCode);
|
||||||
|
|
||||||
QString cloakPublicKey = serverController.getTextFileFromContainer(container, credentials,
|
|
||||||
amnezia::protocols::cloak::ckPublicKeyPath, &e);
|
|
||||||
cloakPublicKey.replace("\n", "");
|
cloakPublicKey.replace("\n", "");
|
||||||
|
|
||||||
QString cloakBypassUid = serverController.getTextFileFromContainer(container, credentials,
|
if (errorCode != ErrorCode::NoError) {
|
||||||
amnezia::protocols::cloak::ckBypassUidKeyPath, &e);
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
QString cloakBypassUid =
|
||||||
|
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::cloak::ckBypassUidKeyPath, errorCode);
|
||||||
cloakBypassUid.replace("\n", "");
|
cloakBypassUid.replace("\n", "");
|
||||||
|
|
||||||
if (e) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
if (errorCode) *errorCode = e;
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,9 +44,8 @@ QString CloakConfigurator::genCloakConfig(const ServerCredentials &credentials,
|
|||||||
config.insert("RemoteHost", credentials.hostName);
|
config.insert("RemoteHost", credentials.hostName);
|
||||||
config.insert("RemotePort", "$CLOAK_SERVER_PORT");
|
config.insert("RemotePort", "$CLOAK_SERVER_PORT");
|
||||||
|
|
||||||
QString textCfg = serverController.replaceVars(QJsonDocument(config).toJson(),
|
QString textCfg = m_serverController->replaceVars(QJsonDocument(config).toJson(),
|
||||||
serverController.genVarsForScript(credentials, container, containerConfig));
|
m_serverController->genVarsForScript(credentials, container, containerConfig));
|
||||||
|
|
||||||
// qDebug().noquote() << textCfg;
|
|
||||||
return textCfg;
|
return textCfg;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,14 +7,14 @@
|
|||||||
|
|
||||||
using namespace amnezia;
|
using namespace amnezia;
|
||||||
|
|
||||||
class CloakConfigurator : ConfiguratorBase
|
class CloakConfigurator : public ConfiguratorBase
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
CloakConfigurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
|
CloakConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
|
||||||
|
|
||||||
QString genCloakConfig(const ServerCredentials &credentials, DockerContainer container,
|
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||||
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
|
const QJsonObject &containerConfig, ErrorCode errorCode);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CLOAK_CONFIGURATOR_H
|
#endif // CLOAK_CONFIGURATOR_H
|
||||||
|
|||||||
@@ -1,8 +1,26 @@
|
|||||||
#include "configurator_base.h"
|
#include "configurator_base.h"
|
||||||
|
|
||||||
ConfiguratorBase::ConfiguratorBase(std::shared_ptr<Settings> settings, QObject *parent)
|
ConfiguratorBase::ConfiguratorBase(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent)
|
||||||
: QObject{parent},
|
: QObject { parent }, m_settings(settings), m_serverController(serverController)
|
||||||
m_settings(settings)
|
|
||||||
{
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ConfiguratorBase::processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||||
|
QString &protocolConfigString)
|
||||||
|
{
|
||||||
|
processConfigWithDnsSettings(dns, protocolConfigString);
|
||||||
|
return protocolConfigString;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ConfiguratorBase::processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||||
|
QString &protocolConfigString)
|
||||||
|
{
|
||||||
|
processConfigWithDnsSettings(dns, protocolConfigString);
|
||||||
|
return protocolConfigString;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfiguratorBase::processConfigWithDnsSettings(const QPair<QString, QString> &dns, QString &protocolConfigString)
|
||||||
|
{
|
||||||
|
protocolConfigString.replace("$PRIMARY_DNS", dns.first);
|
||||||
|
protocolConfigString.replace("$SECONDARY_DNS", dns.second);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,19 +3,31 @@
|
|||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
class Settings;
|
|
||||||
|
|
||||||
#include "containers/containers_defs.h"
|
#include "containers/containers_defs.h"
|
||||||
#include "core/defs.h"
|
#include "core/defs.h"
|
||||||
|
#include "core/controllers/serverController.h"
|
||||||
|
#include "settings.h"
|
||||||
|
|
||||||
class ConfiguratorBase : public QObject
|
class ConfiguratorBase : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit ConfiguratorBase(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
|
explicit ConfiguratorBase(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
virtual QString createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||||
|
const QJsonObject &containerConfig, ErrorCode errorCode) = 0;
|
||||||
|
|
||||||
|
virtual QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||||
|
QString &protocolConfigString);
|
||||||
|
virtual QString processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||||
|
QString &protocolConfigString);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
void processConfigWithDnsSettings(const QPair<QString, QString> &dns, QString &protocolConfigString);
|
||||||
|
|
||||||
std::shared_ptr<Settings> m_settings;
|
std::shared_ptr<Settings> m_settings;
|
||||||
|
QSharedPointer<ServerController> m_serverController;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CONFIGURATORBASE_H
|
#endif // CONFIGURATORBASE_H
|
||||||
|
|||||||
@@ -9,18 +9,18 @@
|
|||||||
#include <QUuid>
|
#include <QUuid>
|
||||||
|
|
||||||
#include "containers/containers_defs.h"
|
#include "containers/containers_defs.h"
|
||||||
|
#include "core/controllers/serverController.h"
|
||||||
#include "core/scripts_registry.h"
|
#include "core/scripts_registry.h"
|
||||||
#include "core/server_defs.h"
|
#include "core/server_defs.h"
|
||||||
#include "core/controllers/serverController.h"
|
|
||||||
#include "utilities.h"
|
#include "utilities.h"
|
||||||
|
|
||||||
Ikev2Configurator::Ikev2Configurator(std::shared_ptr<Settings> settings, QObject *parent)
|
Ikev2Configurator::Ikev2Configurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent)
|
||||||
: ConfiguratorBase(settings, parent)
|
: ConfiguratorBase(settings, serverController, parent)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const ServerCredentials &credentials,
|
Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const ServerCredentials &credentials, DockerContainer container,
|
||||||
DockerContainer container, ErrorCode *errorCode)
|
ErrorCode errorCode)
|
||||||
{
|
{
|
||||||
Ikev2Configurator::ConnectionData connData;
|
Ikev2Configurator::ConnectionData connData;
|
||||||
connData.host = credentials.hostName;
|
connData.host = credentials.hostName;
|
||||||
@@ -39,18 +39,14 @@ Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const Se
|
|||||||
"--extKeyUsage serverAuth,clientAuth -8 \"%1\"")
|
"--extKeyUsage serverAuth,clientAuth -8 \"%1\"")
|
||||||
.arg(connData.clientId);
|
.arg(connData.clientId);
|
||||||
|
|
||||||
ServerController serverController(m_settings);
|
errorCode = m_serverController->runContainerScript(credentials, container, scriptCreateCert);
|
||||||
ErrorCode e = serverController.runContainerScript(credentials, container, scriptCreateCert);
|
|
||||||
|
|
||||||
QString scriptExportCert = QString("pk12util -W \"%1\" -d sql:/etc/ipsec.d -n \"%2\" -o \"%3\"")
|
QString scriptExportCert =
|
||||||
.arg(connData.password)
|
QString("pk12util -W \"%1\" -d sql:/etc/ipsec.d -n \"%2\" -o \"%3\"").arg(connData.password).arg(connData.clientId).arg(certFileName);
|
||||||
.arg(connData.clientId)
|
errorCode = m_serverController->runContainerScript(credentials, container, scriptExportCert);
|
||||||
.arg(certFileName);
|
|
||||||
e = serverController.runContainerScript(credentials, container, scriptExportCert);
|
|
||||||
|
|
||||||
connData.clientCert = serverController.getTextFileFromContainer(container, credentials, certFileName, &e);
|
connData.clientCert = m_serverController->getTextFileFromContainer(container, credentials, certFileName, errorCode);
|
||||||
connData.caCert =
|
connData.caCert = m_serverController->getTextFileFromContainer(container, credentials, "/etc/ipsec.d/ca_cert_base64.p12", errorCode);
|
||||||
serverController.getTextFileFromContainer(container, credentials, "/etc/ipsec.d/ca_cert_base64.p12", &e);
|
|
||||||
|
|
||||||
qDebug() << "Ikev2Configurator::ConnectionData client cert size:" << connData.clientCert.size();
|
qDebug() << "Ikev2Configurator::ConnectionData client cert size:" << connData.clientCert.size();
|
||||||
qDebug() << "Ikev2Configurator::ConnectionData ca cert size:" << connData.caCert.size();
|
qDebug() << "Ikev2Configurator::ConnectionData ca cert size:" << connData.caCert.size();
|
||||||
@@ -58,13 +54,13 @@ Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const Se
|
|||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Ikev2Configurator::genIkev2Config(const ServerCredentials &credentials, DockerContainer container,
|
QString Ikev2Configurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
|
||||||
const QJsonObject &containerConfig, ErrorCode *errorCode)
|
ErrorCode errorCode)
|
||||||
{
|
{
|
||||||
Q_UNUSED(containerConfig)
|
Q_UNUSED(containerConfig)
|
||||||
|
|
||||||
ConnectionData connData = prepareIkev2Config(credentials, container, errorCode);
|
ConnectionData connData = prepareIkev2Config(credentials, container, errorCode);
|
||||||
if (errorCode && *errorCode) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,11 +7,11 @@
|
|||||||
#include "configurator_base.h"
|
#include "configurator_base.h"
|
||||||
#include "core/defs.h"
|
#include "core/defs.h"
|
||||||
|
|
||||||
class Ikev2Configurator : ConfiguratorBase
|
class Ikev2Configurator : public ConfiguratorBase
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
Ikev2Configurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
|
Ikev2Configurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
|
||||||
|
|
||||||
struct ConnectionData {
|
struct ConnectionData {
|
||||||
QByteArray clientCert; // p12 client cert
|
QByteArray clientCert; // p12 client cert
|
||||||
@@ -21,15 +21,15 @@ public:
|
|||||||
QString host; // host ip
|
QString host; // host ip
|
||||||
};
|
};
|
||||||
|
|
||||||
QString genIkev2Config(const ServerCredentials &credentials, DockerContainer container,
|
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||||
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
|
const QJsonObject &containerConfig, ErrorCode errorCode);
|
||||||
|
|
||||||
QString genIkev2Config(const ConnectionData &connData);
|
QString genIkev2Config(const ConnectionData &connData);
|
||||||
QString genMobileConfig(const ConnectionData &connData);
|
QString genMobileConfig(const ConnectionData &connData);
|
||||||
QString genStrongSwanConfig(const ConnectionData &connData);
|
QString genStrongSwanConfig(const ConnectionData &connData);
|
||||||
|
|
||||||
ConnectionData prepareIkev2Config(const ServerCredentials &credentials,
|
ConnectionData prepareIkev2Config(const ServerCredentials &credentials,
|
||||||
DockerContainer container, ErrorCode *errorCode = nullptr);
|
DockerContainer container, ErrorCode errorCode);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // IKEV2_CONFIGURATOR_H
|
#endif // IKEV2_CONFIGURATOR_H
|
||||||
|
|||||||
@@ -14,9 +14,9 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "containers/containers_defs.h"
|
#include "containers/containers_defs.h"
|
||||||
|
#include "core/controllers/serverController.h"
|
||||||
#include "core/scripts_registry.h"
|
#include "core/scripts_registry.h"
|
||||||
#include "core/server_defs.h"
|
#include "core/server_defs.h"
|
||||||
#include "core/controllers/serverController.h"
|
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "utilities.h"
|
#include "utilities.h"
|
||||||
|
|
||||||
@@ -24,74 +24,61 @@
|
|||||||
#include <openssl/rsa.h>
|
#include <openssl/rsa.h>
|
||||||
#include <openssl/x509.h>
|
#include <openssl/x509.h>
|
||||||
|
|
||||||
OpenVpnConfigurator::OpenVpnConfigurator(std::shared_ptr<Settings> settings, QObject *parent)
|
OpenVpnConfigurator::OpenVpnConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController,
|
||||||
: ConfiguratorBase(settings, parent)
|
QObject *parent)
|
||||||
|
: ConfiguratorBase(settings, serverController, parent)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(const ServerCredentials &credentials,
|
OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(const ServerCredentials &credentials,
|
||||||
DockerContainer container,
|
DockerContainer container, ErrorCode errorCode)
|
||||||
ErrorCode *errorCode)
|
|
||||||
{
|
{
|
||||||
OpenVpnConfigurator::ConnectionData connData = OpenVpnConfigurator::createCertRequest();
|
OpenVpnConfigurator::ConnectionData connData = OpenVpnConfigurator::createCertRequest();
|
||||||
connData.host = credentials.hostName;
|
connData.host = credentials.hostName;
|
||||||
|
|
||||||
if (connData.privKey.isEmpty() || connData.request.isEmpty()) {
|
if (connData.privKey.isEmpty() || connData.request.isEmpty()) {
|
||||||
if (errorCode)
|
errorCode = ErrorCode::OpenSslFailed;
|
||||||
*errorCode = ErrorCode::OpenSslFailed;
|
|
||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString reqFileName = QString("%1/%2.req").arg(amnezia::protocols::openvpn::clientsDirPath).arg(connData.clientId);
|
QString reqFileName = QString("%1/%2.req").arg(amnezia::protocols::openvpn::clientsDirPath).arg(connData.clientId);
|
||||||
|
|
||||||
ServerController serverController(m_settings);
|
errorCode = m_serverController->uploadTextFileToContainer(container, credentials, connData.request, reqFileName);
|
||||||
ErrorCode e = serverController.uploadTextFileToContainer(container, credentials, connData.request, reqFileName);
|
if (errorCode != ErrorCode::NoError) {
|
||||||
if (e) {
|
|
||||||
if (errorCode)
|
|
||||||
*errorCode = e;
|
|
||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
|
||||||
e = signCert(container, credentials, connData.clientId);
|
errorCode = signCert(container, credentials, connData.clientId);
|
||||||
if (e) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
if (errorCode)
|
|
||||||
*errorCode = e;
|
|
||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
|
||||||
connData.caCert = serverController.getTextFileFromContainer(container, credentials,
|
connData.caCert =
|
||||||
amnezia::protocols::openvpn::caCertPath, &e);
|
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::caCertPath, errorCode);
|
||||||
connData.clientCert = serverController.getTextFileFromContainer(
|
connData.clientCert = m_serverController->getTextFileFromContainer(
|
||||||
container, credentials,
|
container, credentials, QString("%1/%2.crt").arg(amnezia::protocols::openvpn::clientCertPath).arg(connData.clientId), errorCode);
|
||||||
QString("%1/%2.crt").arg(amnezia::protocols::openvpn::clientCertPath).arg(connData.clientId), &e);
|
|
||||||
|
|
||||||
if (e) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
if (errorCode)
|
|
||||||
*errorCode = e;
|
|
||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
|
||||||
connData.taKey = serverController.getTextFileFromContainer(container, credentials,
|
connData.taKey = m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::taKeyPath, errorCode);
|
||||||
amnezia::protocols::openvpn::taKeyPath, &e);
|
|
||||||
|
|
||||||
if (connData.caCert.isEmpty() || connData.clientCert.isEmpty() || connData.taKey.isEmpty()) {
|
if (connData.caCert.isEmpty() || connData.clientCert.isEmpty() || connData.taKey.isEmpty()) {
|
||||||
if (errorCode)
|
errorCode = ErrorCode::SshScpFailureError;
|
||||||
*errorCode = ErrorCode::SshSftpFailureError;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container,
|
QString OpenVpnConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||||
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode)
|
const QJsonObject &containerConfig, ErrorCode errorCode)
|
||||||
{
|
{
|
||||||
ServerController serverController(m_settings);
|
QString config = m_serverController->replaceVars(amnezia::scriptData(ProtocolScriptType::openvpn_template, container),
|
||||||
QString config =
|
m_serverController->genVarsForScript(credentials, container, containerConfig));
|
||||||
serverController.replaceVars(amnezia::scriptData(ProtocolScriptType::openvpn_template, container),
|
|
||||||
serverController.genVarsForScript(credentials, container, containerConfig));
|
|
||||||
|
|
||||||
ConnectionData connData = prepareOpenVpnConfig(credentials, container, errorCode);
|
ConnectionData connData = prepareOpenVpnConfig(credentials, container, errorCode);
|
||||||
if (errorCode && *errorCode) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,34 +100,35 @@ QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentia
|
|||||||
QJsonObject jConfig;
|
QJsonObject jConfig;
|
||||||
jConfig[config_key::config] = config;
|
jConfig[config_key::config] = config;
|
||||||
|
|
||||||
clientId = connData.clientId;
|
jConfig[config_key::clientId] = connData.clientId;
|
||||||
|
|
||||||
return QJsonDocument(jConfig).toJson();
|
return QJsonDocument(jConfig).toJson();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString OpenVpnConfigurator::processConfigWithLocalSettings(QString jsonConfig, const int serverIndex)
|
QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||||
|
QString &protocolConfigString)
|
||||||
{
|
{
|
||||||
QJsonObject json = QJsonDocument::fromJson(jsonConfig.toUtf8()).object();
|
processConfigWithDnsSettings(dns, protocolConfigString);
|
||||||
|
|
||||||
|
QJsonObject json = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
|
||||||
QString config = json[config_key::config].toString();
|
QString config = json[config_key::config].toString();
|
||||||
|
|
||||||
if (!m_settings->server(serverIndex).value(config_key::configVersion).toInt()) {
|
if (!isApiConfig) {
|
||||||
QRegularExpression regex("redirect-gateway.*");
|
QRegularExpression regex("redirect-gateway.*");
|
||||||
config.replace(regex, "");
|
config.replace(regex, "");
|
||||||
|
|
||||||
if (m_settings->routeMode() == Settings::VpnAllSites) {
|
if (!m_settings->getSitesSplitTunnelingEnabled()) {
|
||||||
config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n");
|
config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n");
|
||||||
// Prevent ipv6 leak
|
// Prevent ipv6 leak
|
||||||
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
|
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
|
||||||
config.append("block-ipv6\n");
|
config.append("block-ipv6\n");
|
||||||
}
|
} else if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
|
||||||
if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
|
|
||||||
|
|
||||||
// no redirect-gateway
|
// no redirect-gateway
|
||||||
}
|
} else if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
|
||||||
if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
|
#ifndef Q_OS_ANDROID
|
||||||
#ifndef Q_OS_ANDROID
|
|
||||||
config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n");
|
config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n");
|
||||||
#endif
|
#endif
|
||||||
// Prevent ipv6 leak
|
// Prevent ipv6 leak
|
||||||
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
|
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
|
||||||
config.append("block-ipv6\n");
|
config.append("block-ipv6\n");
|
||||||
@@ -164,9 +152,12 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(QString jsonConfig,
|
|||||||
return QJsonDocument(json).toJson();
|
return QJsonDocument(json).toJson();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString OpenVpnConfigurator::processConfigWithExportSettings(QString jsonConfig)
|
QString OpenVpnConfigurator::processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||||
|
QString &protocolConfigString)
|
||||||
{
|
{
|
||||||
QJsonObject json = QJsonDocument::fromJson(jsonConfig.toUtf8()).object();
|
processConfigWithDnsSettings(dns, protocolConfigString);
|
||||||
|
|
||||||
|
QJsonObject json = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
|
||||||
QString config = json[config_key::config].toString();
|
QString config = json[config_key::config].toString();
|
||||||
|
|
||||||
QRegularExpression regex("redirect-gateway.*");
|
QRegularExpression regex("redirect-gateway.*");
|
||||||
@@ -198,12 +189,10 @@ ErrorCode OpenVpnConfigurator::signCert(DockerContainer container, const ServerC
|
|||||||
.arg(ContainerProps::containerToString(container))
|
.arg(ContainerProps::containerToString(container))
|
||||||
.arg(clientId);
|
.arg(clientId);
|
||||||
|
|
||||||
ServerController serverController(m_settings);
|
|
||||||
QStringList scriptList { script_import, script_sign };
|
QStringList scriptList { script_import, script_sign };
|
||||||
QString script = serverController.replaceVars(scriptList.join("\n"),
|
QString script = m_serverController->replaceVars(scriptList.join("\n"), m_serverController->genVarsForScript(credentials, container));
|
||||||
serverController.genVarsForScript(credentials, container));
|
|
||||||
|
|
||||||
return serverController.runScript(credentials, script);
|
return m_serverController->runScript(credentials, script);
|
||||||
}
|
}
|
||||||
|
|
||||||
OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest()
|
OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest()
|
||||||
@@ -237,8 +226,8 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest()
|
|||||||
|
|
||||||
X509_NAME_add_entry_by_txt(x509_name, "C", MBSTRING_ASC, (unsigned char *)"ORG", -1, -1, 0);
|
X509_NAME_add_entry_by_txt(x509_name, "C", MBSTRING_ASC, (unsigned char *)"ORG", -1, -1, 0);
|
||||||
X509_NAME_add_entry_by_txt(x509_name, "O", MBSTRING_ASC, (unsigned char *)"", -1, -1, 0);
|
X509_NAME_add_entry_by_txt(x509_name, "O", MBSTRING_ASC, (unsigned char *)"", -1, -1, 0);
|
||||||
X509_NAME_add_entry_by_txt(x509_name, "CN", MBSTRING_ASC,
|
X509_NAME_add_entry_by_txt(x509_name, "CN", MBSTRING_ASC, reinterpret_cast<unsigned char const *>(clientIdUtf8.data()),
|
||||||
reinterpret_cast<unsigned char const *>(clientIdUtf8.data()), clientIdUtf8.size(), -1, 0);
|
clientIdUtf8.size(), -1, 0);
|
||||||
|
|
||||||
// 4. set public key of x509 req
|
// 4. set public key of x509 req
|
||||||
ret = X509_REQ_set_pubkey(x509_req, pKey);
|
ret = X509_REQ_set_pubkey(x509_req, pKey);
|
||||||
|
|||||||
@@ -7,37 +7,37 @@
|
|||||||
#include "configurator_base.h"
|
#include "configurator_base.h"
|
||||||
#include "core/defs.h"
|
#include "core/defs.h"
|
||||||
|
|
||||||
class OpenVpnConfigurator : ConfiguratorBase
|
class OpenVpnConfigurator : public ConfiguratorBase
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
OpenVpnConfigurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
|
OpenVpnConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
|
||||||
|
|
||||||
struct ConnectionData {
|
struct ConnectionData
|
||||||
|
{
|
||||||
QString clientId;
|
QString clientId;
|
||||||
QString request; // certificate request
|
QString request; // certificate request
|
||||||
QString privKey; // client private key
|
QString privKey; // client private key
|
||||||
QString clientCert; // client signed certificate
|
QString clientCert; // client signed certificate
|
||||||
QString caCert; // server certificate
|
QString caCert; // server certificate
|
||||||
QString taKey; // tls-auth key
|
QString taKey; // tls-auth key
|
||||||
QString host; // host ip
|
QString host; // host ip
|
||||||
};
|
};
|
||||||
|
|
||||||
QString genOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container,
|
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||||
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode = nullptr);
|
const QJsonObject &containerConfig, ErrorCode errorCode);
|
||||||
|
|
||||||
QString processConfigWithLocalSettings(QString jsonConfig, const int serverIndex);
|
QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||||
QString processConfigWithExportSettings(QString jsonConfig);
|
QString &protocolConfigString);
|
||||||
|
QString processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||||
ErrorCode signCert(DockerContainer container,
|
QString &protocolConfigString);
|
||||||
const ServerCredentials &credentials, QString clientId);
|
|
||||||
|
|
||||||
static ConnectionData createCertRequest();
|
static ConnectionData createCertRequest();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ConnectionData prepareOpenVpnConfig(const ServerCredentials &credentials,
|
ConnectionData prepareOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||||
DockerContainer container, ErrorCode *errorCode = nullptr);
|
ErrorCode errorCode);
|
||||||
|
ErrorCode signCert(DockerContainer container, const ServerCredentials &credentials, QString clientId);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // OPENVPN_CONFIGURATOR_H
|
#endif // OPENVPN_CONFIGURATOR_H
|
||||||
|
|||||||
@@ -1,30 +1,26 @@
|
|||||||
#include "shadowsocks_configurator.h"
|
#include "shadowsocks_configurator.h"
|
||||||
|
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
|
||||||
#include "containers/containers_defs.h"
|
#include "containers/containers_defs.h"
|
||||||
#include "core/controllers/serverController.h"
|
#include "core/controllers/serverController.h"
|
||||||
|
|
||||||
ShadowSocksConfigurator::ShadowSocksConfigurator(std::shared_ptr<Settings> settings, QObject *parent):
|
ShadowSocksConfigurator::ShadowSocksConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController,
|
||||||
ConfiguratorBase(settings, parent)
|
QObject *parent)
|
||||||
|
: ConfiguratorBase(settings, serverController, parent)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ShadowSocksConfigurator::genShadowSocksConfig(const ServerCredentials &credentials,
|
QString ShadowSocksConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||||
DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode)
|
const QJsonObject &containerConfig, ErrorCode errorCode)
|
||||||
{
|
{
|
||||||
ErrorCode e = ErrorCode::NoError;
|
QString ssKey =
|
||||||
ServerController serverController(m_settings);
|
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::shadowsocks::ssKeyPath, errorCode);
|
||||||
|
|
||||||
QString ssKey = serverController.getTextFileFromContainer(container, credentials,
|
|
||||||
amnezia::protocols::shadowsocks::ssKeyPath, &e);
|
|
||||||
ssKey.replace("\n", "");
|
ssKey.replace("\n", "");
|
||||||
|
|
||||||
if (e) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
if (errorCode) *errorCode = e;
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,10 +32,9 @@ QString ShadowSocksConfigurator::genShadowSocksConfig(const ServerCredentials &c
|
|||||||
config.insert("timeout", 60);
|
config.insert("timeout", 60);
|
||||||
config.insert("method", "$SHADOWSOCKS_CIPHER");
|
config.insert("method", "$SHADOWSOCKS_CIPHER");
|
||||||
|
|
||||||
|
QString textCfg = m_serverController->replaceVars(QJsonDocument(config).toJson(),
|
||||||
|
m_serverController->genVarsForScript(credentials, container, containerConfig));
|
||||||
|
|
||||||
QString textCfg = serverController.replaceVars(QJsonDocument(config).toJson(),
|
// qDebug().noquote() << textCfg;
|
||||||
serverController.genVarsForScript(credentials, container, containerConfig));
|
|
||||||
|
|
||||||
//qDebug().noquote() << textCfg;
|
|
||||||
return textCfg;
|
return textCfg;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,14 +6,14 @@
|
|||||||
#include "configurator_base.h"
|
#include "configurator_base.h"
|
||||||
#include "core/defs.h"
|
#include "core/defs.h"
|
||||||
|
|
||||||
class ShadowSocksConfigurator : ConfiguratorBase
|
class ShadowSocksConfigurator : public ConfiguratorBase
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
ShadowSocksConfigurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
|
ShadowSocksConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
|
||||||
|
|
||||||
QString genShadowSocksConfig(const ServerCredentials &credentials, DockerContainer container,
|
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||||
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
|
const QJsonObject &containerConfig, ErrorCode errorCode);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // SHADOWSOCKS_CONFIGURATOR_H
|
#endif // SHADOWSOCKS_CONFIGURATOR_H
|
||||||
|
|||||||
@@ -17,8 +17,8 @@
|
|||||||
#include "core/server_defs.h"
|
#include "core/server_defs.h"
|
||||||
#include "utilities.h"
|
#include "utilities.h"
|
||||||
|
|
||||||
SshConfigurator::SshConfigurator(std::shared_ptr<Settings> settings, QObject *parent)
|
SshConfigurator::SshConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent)
|
||||||
: ConfiguratorBase(settings, parent)
|
: ConfiguratorBase(settings, serverController, parent)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,8 +82,7 @@ void SshConfigurator::openSshTerminal(const ServerCredentials &credentials)
|
|||||||
// p->setNativeArguments(QString("%1@%2")
|
// p->setNativeArguments(QString("%1@%2")
|
||||||
// .arg(credentials.userName).arg(credentials.hostName).arg(credentials.secretData));
|
// .arg(credentials.userName).arg(credentials.hostName).arg(credentials.secretData));
|
||||||
} else {
|
} else {
|
||||||
p->setNativeArguments(
|
p->setNativeArguments(QString("%1@%2 -pw %3").arg(credentials.userName).arg(credentials.hostName).arg(credentials.secretData));
|
||||||
QString("%1@%2 -pw %3").arg(credentials.userName).arg(credentials.hostName).arg(credentials.secretData));
|
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
p->setProgram("/bin/bash");
|
p->setProgram("/bin/bash");
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class SshConfigurator : ConfiguratorBase
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
SshConfigurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
|
SshConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
|
||||||
|
|
||||||
QProcessEnvironment prepareEnv();
|
QProcessEnvironment prepareEnv();
|
||||||
QString convertOpenSShKey(const QString &key);
|
QString convertOpenSShKey(const QString &key);
|
||||||
|
|||||||
@@ -1,127 +0,0 @@
|
|||||||
#include "vpn_configurator.h"
|
|
||||||
#include "cloak_configurator.h"
|
|
||||||
#include "ikev2_configurator.h"
|
|
||||||
#include "openvpn_configurator.h"
|
|
||||||
#include "shadowsocks_configurator.h"
|
|
||||||
#include "ssh_configurator.h"
|
|
||||||
#include "wireguard_configurator.h"
|
|
||||||
#include "awg_configurator.h"
|
|
||||||
|
|
||||||
#include <QFile>
|
|
||||||
#include <QJsonDocument>
|
|
||||||
#include <QJsonObject>
|
|
||||||
|
|
||||||
#include "containers/containers_defs.h"
|
|
||||||
#include "settings.h"
|
|
||||||
#include "utilities.h"
|
|
||||||
|
|
||||||
VpnConfigurator::VpnConfigurator(std::shared_ptr<Settings> settings, QObject *parent)
|
|
||||||
: ConfiguratorBase(settings, parent)
|
|
||||||
{
|
|
||||||
openVpnConfigurator = std::shared_ptr<OpenVpnConfigurator>(new OpenVpnConfigurator(settings, this));
|
|
||||||
shadowSocksConfigurator = std::shared_ptr<ShadowSocksConfigurator>(new ShadowSocksConfigurator(settings, this));
|
|
||||||
cloakConfigurator = std::shared_ptr<CloakConfigurator>(new CloakConfigurator(settings, this));
|
|
||||||
wireguardConfigurator = std::shared_ptr<WireguardConfigurator>(new WireguardConfigurator(settings, false, this));
|
|
||||||
ikev2Configurator = std::shared_ptr<Ikev2Configurator>(new Ikev2Configurator(settings, this));
|
|
||||||
sshConfigurator = std::shared_ptr<SshConfigurator>(new SshConfigurator(settings, this));
|
|
||||||
awgConfigurator = std::shared_ptr<AwgConfigurator>(new AwgConfigurator(settings, this));
|
|
||||||
}
|
|
||||||
|
|
||||||
QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentials, DockerContainer container,
|
|
||||||
const QJsonObject &containerConfig, Proto proto, QString &clientId, ErrorCode *errorCode)
|
|
||||||
{
|
|
||||||
switch (proto) {
|
|
||||||
case Proto::OpenVpn:
|
|
||||||
return openVpnConfigurator->genOpenVpnConfig(credentials, container, containerConfig, clientId, errorCode);
|
|
||||||
|
|
||||||
case Proto::ShadowSocks:
|
|
||||||
return shadowSocksConfigurator->genShadowSocksConfig(credentials, container, containerConfig, errorCode);
|
|
||||||
|
|
||||||
case Proto::Cloak: return cloakConfigurator->genCloakConfig(credentials, container, containerConfig, errorCode);
|
|
||||||
|
|
||||||
case Proto::WireGuard:
|
|
||||||
return wireguardConfigurator->genWireguardConfig(credentials, container, containerConfig, clientId, errorCode);
|
|
||||||
|
|
||||||
case Proto::Awg:
|
|
||||||
return awgConfigurator->genAwgConfig(credentials, container, containerConfig, clientId, errorCode);
|
|
||||||
|
|
||||||
case Proto::Ikev2: return ikev2Configurator->genIkev2Config(credentials, container, containerConfig, errorCode);
|
|
||||||
|
|
||||||
default: return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QPair<QString, QString> VpnConfigurator::getDnsForConfig(int serverIndex)
|
|
||||||
{
|
|
||||||
QPair<QString, QString> dns;
|
|
||||||
|
|
||||||
bool useAmneziaDns = m_settings->useAmneziaDns();
|
|
||||||
const QJsonObject &server = m_settings->server(serverIndex);
|
|
||||||
|
|
||||||
dns.first = server.value(config_key::dns1).toString();
|
|
||||||
dns.second = server.value(config_key::dns2).toString();
|
|
||||||
|
|
||||||
if (dns.first.isEmpty() || !Utils::checkIPv4Format(dns.first)) {
|
|
||||||
if (useAmneziaDns && m_settings->containers(serverIndex).contains(DockerContainer::Dns)) {
|
|
||||||
dns.first = protocols::dns::amneziaDnsIp;
|
|
||||||
} else
|
|
||||||
dns.first = m_settings->primaryDns();
|
|
||||||
}
|
|
||||||
if (dns.second.isEmpty() || !Utils::checkIPv4Format(dns.second)) {
|
|
||||||
dns.second = m_settings->secondaryDns();
|
|
||||||
}
|
|
||||||
|
|
||||||
qDebug() << "VpnConfigurator::getDnsForConfig" << dns.first << dns.second;
|
|
||||||
return dns;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString &VpnConfigurator::processConfigWithDnsSettings(int serverIndex, DockerContainer container, Proto proto,
|
|
||||||
QString &config)
|
|
||||||
{
|
|
||||||
auto dns = getDnsForConfig(serverIndex);
|
|
||||||
|
|
||||||
config.replace("$PRIMARY_DNS", dns.first);
|
|
||||||
config.replace("$SECONDARY_DNS", dns.second);
|
|
||||||
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString &VpnConfigurator::processConfigWithLocalSettings(int serverIndex, DockerContainer container, Proto proto,
|
|
||||||
QString &config)
|
|
||||||
{
|
|
||||||
processConfigWithDnsSettings(serverIndex, container, proto, config);
|
|
||||||
|
|
||||||
if (proto == Proto::OpenVpn) {
|
|
||||||
config = openVpnConfigurator->processConfigWithLocalSettings(config, serverIndex);
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString &VpnConfigurator::processConfigWithExportSettings(int serverIndex, DockerContainer container, Proto proto,
|
|
||||||
QString &config)
|
|
||||||
{
|
|
||||||
processConfigWithDnsSettings(serverIndex, container, proto, config);
|
|
||||||
|
|
||||||
if (proto == Proto::OpenVpn) {
|
|
||||||
config = openVpnConfigurator->processConfigWithExportSettings(config);
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
void VpnConfigurator::updateContainerConfigAfterInstallation(DockerContainer container, QJsonObject &containerConfig,
|
|
||||||
const QString &stdOut)
|
|
||||||
{
|
|
||||||
Proto mainProto = ContainerProps::defaultProtocol(container);
|
|
||||||
|
|
||||||
if (container == DockerContainer::TorWebSite) {
|
|
||||||
QJsonObject protocol = containerConfig.value(ProtocolProps::protoToString(mainProto)).toObject();
|
|
||||||
|
|
||||||
qDebug() << "amnezia-tor onions" << stdOut;
|
|
||||||
|
|
||||||
QString onion = stdOut;
|
|
||||||
onion.replace("\n", "");
|
|
||||||
protocol.insert(config_key::site, onion);
|
|
||||||
|
|
||||||
containerConfig.insert(ProtocolProps::protoToString(mainProto), protocol);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
#ifndef VPN_CONFIGURATOR_H
|
|
||||||
#define VPN_CONFIGURATOR_H
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
#include "configurator_base.h"
|
|
||||||
#include "core/defs.h"
|
|
||||||
|
|
||||||
class OpenVpnConfigurator;
|
|
||||||
class ShadowSocksConfigurator;
|
|
||||||
class CloakConfigurator;
|
|
||||||
class WireguardConfigurator;
|
|
||||||
class Ikev2Configurator;
|
|
||||||
class SshConfigurator;
|
|
||||||
class AwgConfigurator;
|
|
||||||
|
|
||||||
// Retrieve connection settings from server
|
|
||||||
class VpnConfigurator : public ConfiguratorBase
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit VpnConfigurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
|
|
||||||
|
|
||||||
QString genVpnProtocolConfig(const ServerCredentials &credentials, DockerContainer container,
|
|
||||||
const QJsonObject &containerConfig, Proto proto, QString &clientId,
|
|
||||||
ErrorCode *errorCode = nullptr);
|
|
||||||
|
|
||||||
QPair<QString, QString> getDnsForConfig(int serverIndex);
|
|
||||||
QString &processConfigWithDnsSettings(int serverIndex, DockerContainer container, Proto proto, QString &config);
|
|
||||||
|
|
||||||
QString &processConfigWithLocalSettings(int serverIndex, DockerContainer container, Proto proto, QString &config);
|
|
||||||
QString &processConfigWithExportSettings(int serverIndex, DockerContainer container, Proto proto, QString &config);
|
|
||||||
|
|
||||||
// workaround for containers which is not support normal configuration
|
|
||||||
void updateContainerConfigAfterInstallation(DockerContainer container, QJsonObject &containerConfig,
|
|
||||||
const QString &stdOut);
|
|
||||||
|
|
||||||
std::shared_ptr<OpenVpnConfigurator> openVpnConfigurator;
|
|
||||||
std::shared_ptr<ShadowSocksConfigurator> shadowSocksConfigurator;
|
|
||||||
std::shared_ptr<CloakConfigurator> cloakConfigurator;
|
|
||||||
std::shared_ptr<WireguardConfigurator> wireguardConfigurator;
|
|
||||||
std::shared_ptr<Ikev2Configurator> ikev2Configurator;
|
|
||||||
std::shared_ptr<SshConfigurator> sshConfigurator;
|
|
||||||
std::shared_ptr<AwgConfigurator> awgConfigurator;
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void newVpnConfigCreated(const QString &clientId, const QString &clientName, const DockerContainer container,
|
|
||||||
ServerCredentials credentials);
|
|
||||||
void clientModelUpdated();
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // VPN_CONFIGURATOR_H
|
|
||||||
@@ -13,23 +13,20 @@
|
|||||||
#include <openssl/x509.h>
|
#include <openssl/x509.h>
|
||||||
|
|
||||||
#include "containers/containers_defs.h"
|
#include "containers/containers_defs.h"
|
||||||
|
#include "core/controllers/serverController.h"
|
||||||
#include "core/scripts_registry.h"
|
#include "core/scripts_registry.h"
|
||||||
#include "core/server_defs.h"
|
#include "core/server_defs.h"
|
||||||
#include "core/controllers/serverController.h"
|
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "utilities.h"
|
#include "utilities.h"
|
||||||
|
|
||||||
WireguardConfigurator::WireguardConfigurator(std::shared_ptr<Settings> settings, bool isAwg, QObject *parent)
|
WireguardConfigurator::WireguardConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController,
|
||||||
: ConfiguratorBase(settings, parent), m_isAwg(isAwg)
|
bool isAwg, QObject *parent)
|
||||||
|
: ConfiguratorBase(settings, serverController, parent), m_isAwg(isAwg)
|
||||||
{
|
{
|
||||||
m_serverConfigPath = m_isAwg ? amnezia::protocols::awg::serverConfigPath
|
m_serverConfigPath = m_isAwg ? amnezia::protocols::awg::serverConfigPath : amnezia::protocols::wireguard::serverConfigPath;
|
||||||
: amnezia::protocols::wireguard::serverConfigPath;
|
m_serverPublicKeyPath = m_isAwg ? amnezia::protocols::awg::serverPublicKeyPath : amnezia::protocols::wireguard::serverPublicKeyPath;
|
||||||
m_serverPublicKeyPath = m_isAwg ? amnezia::protocols::awg::serverPublicKeyPath
|
m_serverPskKeyPath = m_isAwg ? amnezia::protocols::awg::serverPskKeyPath : amnezia::protocols::wireguard::serverPskKeyPath;
|
||||||
: amnezia::protocols::wireguard::serverPublicKeyPath;
|
m_configTemplate = m_isAwg ? ProtocolScriptType::awg_template : ProtocolScriptType::wireguard_template;
|
||||||
m_serverPskKeyPath = m_isAwg ? amnezia::protocols::awg::serverPskKeyPath
|
|
||||||
: amnezia::protocols::wireguard::serverPskKeyPath;
|
|
||||||
m_configTemplate = m_isAwg ? ProtocolScriptType::awg_template
|
|
||||||
: ProtocolScriptType::wireguard_template;
|
|
||||||
|
|
||||||
m_protocolName = m_isAwg ? config_key::awg : config_key::wireguard;
|
m_protocolName = m_isAwg ? config_key::awg : config_key::wireguard;
|
||||||
m_defaultPort = m_isAwg ? protocols::wireguard::defaultPort : protocols::awg::defaultPort;
|
m_defaultPort = m_isAwg ? protocols::wireguard::defaultPort : protocols::awg::defaultPort;
|
||||||
@@ -68,22 +65,17 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::genClientKeys()
|
|||||||
|
|
||||||
WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardConfig(const ServerCredentials &credentials,
|
WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardConfig(const ServerCredentials &credentials,
|
||||||
DockerContainer container,
|
DockerContainer container,
|
||||||
const QJsonObject &containerConfig,
|
const QJsonObject &containerConfig, ErrorCode errorCode)
|
||||||
ErrorCode *errorCode)
|
|
||||||
{
|
{
|
||||||
WireguardConfigurator::ConnectionData connData = WireguardConfigurator::genClientKeys();
|
WireguardConfigurator::ConnectionData connData = WireguardConfigurator::genClientKeys();
|
||||||
connData.host = credentials.hostName;
|
connData.host = credentials.hostName;
|
||||||
connData.port = containerConfig.value(m_protocolName).toObject().value(config_key::port).toString(m_defaultPort);
|
connData.port = containerConfig.value(m_protocolName).toObject().value(config_key::port).toString(m_defaultPort);
|
||||||
|
|
||||||
if (connData.clientPrivKey.isEmpty() || connData.clientPubKey.isEmpty()) {
|
if (connData.clientPrivKey.isEmpty() || connData.clientPubKey.isEmpty()) {
|
||||||
if (errorCode)
|
errorCode = ErrorCode::InternalError;
|
||||||
*errorCode = ErrorCode::InternalError;
|
|
||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode e = ErrorCode::NoError;
|
|
||||||
ServerController serverController(m_settings);
|
|
||||||
|
|
||||||
// Get list of already created clients (only IP addresses)
|
// Get list of already created clients (only IP addresses)
|
||||||
QString nextIpNumber;
|
QString nextIpNumber;
|
||||||
{
|
{
|
||||||
@@ -94,9 +86,8 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
|
|||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
};
|
};
|
||||||
|
|
||||||
e = serverController.runContainerScript(credentials, container, script, cbReadStdOut);
|
errorCode = m_serverController->runContainerScript(credentials, container, script, cbReadStdOut);
|
||||||
if (errorCode && e) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
*errorCode = e;
|
|
||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,21 +101,18 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
|
|||||||
} else {
|
} else {
|
||||||
int next = ips.last().split(".").last().toInt() + 1;
|
int next = ips.last().split(".").last().toInt() + 1;
|
||||||
if (next > 254) {
|
if (next > 254) {
|
||||||
if (errorCode)
|
errorCode = ErrorCode::AddressPoolError;
|
||||||
*errorCode = ErrorCode::AddressPoolError;
|
|
||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
nextIpNumber = QString::number(next);
|
nextIpNumber = QString::number(next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString subnetIp =
|
QString subnetIp = containerConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress);
|
||||||
containerConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress);
|
|
||||||
{
|
{
|
||||||
QStringList l = subnetIp.split(".", Qt::SkipEmptyParts);
|
QStringList l = subnetIp.split(".", Qt::SkipEmptyParts);
|
||||||
if (l.isEmpty()) {
|
if (l.isEmpty()) {
|
||||||
if (errorCode)
|
errorCode = ErrorCode::AddressPoolError;
|
||||||
*errorCode = ErrorCode::AddressPoolError;
|
|
||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
l.removeLast();
|
l.removeLast();
|
||||||
@@ -134,20 +122,16 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get keys
|
// Get keys
|
||||||
connData.serverPubKey = serverController.getTextFileFromContainer(container, credentials, m_serverPublicKeyPath, &e);
|
connData.serverPubKey = m_serverController->getTextFileFromContainer(container, credentials, m_serverPublicKeyPath, errorCode);
|
||||||
connData.serverPubKey.replace("\n", "");
|
connData.serverPubKey.replace("\n", "");
|
||||||
if (e) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
if (errorCode)
|
|
||||||
*errorCode = e;
|
|
||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
|
||||||
connData.pskKey = serverController.getTextFileFromContainer(container, credentials, m_serverPskKeyPath, &e);
|
connData.pskKey = m_serverController->getTextFileFromContainer(container, credentials, m_serverPskKeyPath, errorCode);
|
||||||
connData.pskKey.replace("\n", "");
|
connData.pskKey.replace("\n", "");
|
||||||
|
|
||||||
if (e) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
if (errorCode)
|
|
||||||
*errorCode = e;
|
|
||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,34 +142,30 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
|
|||||||
"AllowedIPs = %3/32\n\n")
|
"AllowedIPs = %3/32\n\n")
|
||||||
.arg(connData.clientPubKey, connData.pskKey, connData.clientIP);
|
.arg(connData.clientPubKey, connData.pskKey, connData.clientIP);
|
||||||
|
|
||||||
e = serverController.uploadTextFileToContainer(container, credentials, configPart, m_serverConfigPath,
|
errorCode = m_serverController->uploadTextFileToContainer(container, credentials, configPart, m_serverConfigPath,
|
||||||
libssh::SftpOverwriteMode::SftpAppendToExisting);
|
libssh::ScpOverwriteMode::ScpAppendToExisting);
|
||||||
|
|
||||||
if (e) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
if (errorCode)
|
|
||||||
*errorCode = e;
|
|
||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString script = QString("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip %1)'")
|
QString script = QString("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip %1)'").arg(m_serverConfigPath);
|
||||||
.arg(m_serverConfigPath);
|
|
||||||
|
|
||||||
e = serverController.runScript(
|
errorCode = m_serverController->runScript(
|
||||||
credentials, serverController.replaceVars(script, serverController.genVarsForScript(credentials, container)));
|
credentials, m_serverController->replaceVars(script, m_serverController->genVarsForScript(credentials, container)));
|
||||||
|
|
||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString WireguardConfigurator::genWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
|
QString WireguardConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||||
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode)
|
const QJsonObject &containerConfig, ErrorCode errorCode)
|
||||||
{
|
{
|
||||||
ServerController serverController(m_settings);
|
|
||||||
QString scriptData = amnezia::scriptData(m_configTemplate, container);
|
QString scriptData = amnezia::scriptData(m_configTemplate, container);
|
||||||
QString config = serverController.replaceVars(
|
QString config =
|
||||||
scriptData, serverController.genVarsForScript(credentials, container, containerConfig));
|
m_serverController->replaceVars(scriptData, m_serverController->genVarsForScript(credentials, container, containerConfig));
|
||||||
|
|
||||||
ConnectionData connData = prepareWireguardConfig(credentials, container, containerConfig, errorCode);
|
ConnectionData connData = prepareWireguardConfig(credentials, container, containerConfig, errorCode);
|
||||||
if (errorCode && *errorCode) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,30 +185,25 @@ QString WireguardConfigurator::genWireguardConfig(const ServerCredentials &crede
|
|||||||
jConfig[config_key::client_pub_key] = connData.clientPubKey;
|
jConfig[config_key::client_pub_key] = connData.clientPubKey;
|
||||||
jConfig[config_key::psk_key] = connData.pskKey;
|
jConfig[config_key::psk_key] = connData.pskKey;
|
||||||
jConfig[config_key::server_pub_key] = connData.serverPubKey;
|
jConfig[config_key::server_pub_key] = connData.serverPubKey;
|
||||||
|
|
||||||
jConfig[config_key::mtu] = wireguarConfig.value(config_key::mtu).toString(protocols::wireguard::defaultMtu);
|
jConfig[config_key::mtu] = wireguarConfig.value(config_key::mtu).toString(protocols::wireguard::defaultMtu);
|
||||||
|
|
||||||
clientId = connData.clientPubKey;
|
jConfig[config_key::clientId] = connData.clientPubKey;
|
||||||
|
|
||||||
return QJsonDocument(jConfig).toJson();
|
return QJsonDocument(jConfig).toJson();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString WireguardConfigurator::processConfigWithLocalSettings(QString config)
|
QString WireguardConfigurator::processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||||
|
QString &protocolConfigString)
|
||||||
{
|
{
|
||||||
// TODO replace DNS if it already set
|
processConfigWithDnsSettings(dns, protocolConfigString);
|
||||||
config.replace("$PRIMARY_DNS", m_settings->primaryDns());
|
|
||||||
config.replace("$SECONDARY_DNS", m_settings->secondaryDns());
|
|
||||||
|
|
||||||
QJsonObject jConfig;
|
return protocolConfigString;
|
||||||
jConfig[config_key::config] = config;
|
|
||||||
|
|
||||||
return QJsonDocument(jConfig).toJson();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString WireguardConfigurator::processConfigWithExportSettings(QString config)
|
QString WireguardConfigurator::processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||||
|
QString &protocolConfigString)
|
||||||
{
|
{
|
||||||
config.replace("$PRIMARY_DNS", m_settings->primaryDns());
|
processConfigWithDnsSettings(dns, protocolConfigString);
|
||||||
config.replace("$SECONDARY_DNS", m_settings->secondaryDns());
|
|
||||||
|
|
||||||
return config;
|
return protocolConfigString;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ class WireguardConfigurator : public ConfiguratorBase
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
WireguardConfigurator(std::shared_ptr<Settings> settings, bool isAwg, QObject *parent = nullptr);
|
WireguardConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, bool isAwg,
|
||||||
|
QObject *parent = nullptr);
|
||||||
|
|
||||||
struct ConnectionData
|
struct ConnectionData
|
||||||
{
|
{
|
||||||
@@ -25,18 +26,18 @@ public:
|
|||||||
QString port;
|
QString port;
|
||||||
};
|
};
|
||||||
|
|
||||||
QString genWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
|
QString createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
|
||||||
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode = nullptr);
|
ErrorCode errorCode);
|
||||||
|
|
||||||
QString processConfigWithLocalSettings(QString config);
|
QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig, QString &protocolConfigString);
|
||||||
QString processConfigWithExportSettings(QString config);
|
QString processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig, QString &protocolConfigString);
|
||||||
|
|
||||||
static ConnectionData genClientKeys();
|
static ConnectionData genClientKeys();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ConnectionData prepareWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
|
ConnectionData prepareWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||||
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
|
const QJsonObject &containerConfig, ErrorCode errorCode);
|
||||||
|
|
||||||
bool m_isAwg;
|
bool m_isAwg;
|
||||||
QString m_serverConfigPath;
|
QString m_serverConfigPath;
|
||||||
QString m_serverPublicKeyPath;
|
QString m_serverPublicKeyPath;
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
#include "xray_configurator.h"
|
||||||
|
|
||||||
|
#include <QFile>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
|
||||||
|
#include "containers/containers_defs.h"
|
||||||
|
#include "core/controllers/serverController.h"
|
||||||
|
#include "core/scripts_registry.h"
|
||||||
|
|
||||||
|
XrayConfigurator::XrayConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent)
|
||||||
|
: ConfiguratorBase(settings, serverController, parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QString XrayConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
|
||||||
|
ErrorCode errorCode)
|
||||||
|
{
|
||||||
|
QString config = m_serverController->replaceVars(amnezia::scriptData(ProtocolScriptType::xray_template, container),
|
||||||
|
m_serverController->genVarsForScript(credentials, container, containerConfig));
|
||||||
|
|
||||||
|
QString xrayPublicKey =
|
||||||
|
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::PublicKeyPath, errorCode);
|
||||||
|
xrayPublicKey.replace("\n", "");
|
||||||
|
|
||||||
|
QString xrayUuid = m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::uuidPath, errorCode);
|
||||||
|
xrayUuid.replace("\n", "");
|
||||||
|
|
||||||
|
QString xrayShortId =
|
||||||
|
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::shortidPath, errorCode);
|
||||||
|
xrayShortId.replace("\n", "");
|
||||||
|
|
||||||
|
if (errorCode != ErrorCode::NoError) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
config.replace("$XRAY_CLIENT_ID", xrayUuid);
|
||||||
|
config.replace("$XRAY_PUBLIC_KEY", xrayPublicKey);
|
||||||
|
config.replace("$XRAY_SHORT_ID", xrayShortId);
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
#ifndef XRAY_CONFIGURATOR_H
|
||||||
|
#define XRAY_CONFIGURATOR_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
#include "configurator_base.h"
|
||||||
|
#include "core/defs.h"
|
||||||
|
|
||||||
|
class XrayConfigurator : public ConfiguratorBase
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
XrayConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
QString createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
|
||||||
|
ErrorCode errorCode);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // XRAY_CONFIGURATOR_H
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
#include "containers_defs.h"
|
#include "containers_defs.h"
|
||||||
|
|
||||||
|
#include "QJsonObject"
|
||||||
|
#include "QJsonDocument"
|
||||||
|
|
||||||
QDebug operator<<(QDebug debug, const amnezia::DockerContainer &c)
|
QDebug operator<<(QDebug debug, const amnezia::DockerContainer &c)
|
||||||
{
|
{
|
||||||
QDebugStateSaver saver(debug);
|
QDebugStateSaver saver(debug);
|
||||||
@@ -58,6 +61,8 @@ QVector<amnezia::Proto> ContainerProps::protocolsForContainer(amnezia::DockerCon
|
|||||||
|
|
||||||
case DockerContainer::Ipsec: return { Proto::Ikev2 /*, Protocol::L2tp */ };
|
case DockerContainer::Ipsec: return { Proto::Ikev2 /*, Protocol::L2tp */ };
|
||||||
|
|
||||||
|
case DockerContainer::Xray: return { Proto::Xray };
|
||||||
|
|
||||||
case DockerContainer::Dns: return { Proto::Dns };
|
case DockerContainer::Dns: return { Proto::Dns };
|
||||||
|
|
||||||
case DockerContainer::Sftp: return { Proto::Sftp };
|
case DockerContainer::Sftp: return { Proto::Sftp };
|
||||||
@@ -85,6 +90,7 @@ QMap<DockerContainer, QString> ContainerProps::containerHumanNames()
|
|||||||
{ DockerContainer::Cloak, "OpenVPN over Cloak" },
|
{ DockerContainer::Cloak, "OpenVPN over Cloak" },
|
||||||
{ DockerContainer::WireGuard, "WireGuard" },
|
{ DockerContainer::WireGuard, "WireGuard" },
|
||||||
{ DockerContainer::Awg, "AmneziaWG" },
|
{ DockerContainer::Awg, "AmneziaWG" },
|
||||||
|
{ DockerContainer::Xray, "XRay" },
|
||||||
{ DockerContainer::Ipsec, QObject::tr("IPsec") },
|
{ DockerContainer::Ipsec, QObject::tr("IPsec") },
|
||||||
|
|
||||||
{ DockerContainer::TorWebSite, QObject::tr("Website in Tor network") },
|
{ DockerContainer::TorWebSite, QObject::tr("Website in Tor network") },
|
||||||
@@ -111,6 +117,9 @@ QMap<DockerContainer, QString> ContainerProps::containerDescriptions()
|
|||||||
QObject::tr("AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, "
|
QObject::tr("AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, "
|
||||||
"but very resistant to blockages. "
|
"but very resistant to blockages. "
|
||||||
"Recommended for regions with high levels of censorship.") },
|
"Recommended for regions with high levels of censorship.") },
|
||||||
|
{ DockerContainer::Xray,
|
||||||
|
QObject::tr("XRay with REALITY - Suitable for countries with the highest level of internet censorship. "
|
||||||
|
"Traffic masking as web traffic at the TLS level, and protection against detection by active probing methods.") },
|
||||||
{ DockerContainer::Ipsec,
|
{ DockerContainer::Ipsec,
|
||||||
QObject::tr("IKEv2 - Modern stable protocol, a bit faster than others, restores connection after "
|
QObject::tr("IKEv2 - Modern stable protocol, a bit faster than others, restores connection after "
|
||||||
"signal loss. It has native support on the latest versions of Android and iOS.") },
|
"signal loss. It has native support on the latest versions of Android and iOS.") },
|
||||||
@@ -199,6 +208,17 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
|
|||||||
"* Minimum number of settings\n"
|
"* Minimum number of settings\n"
|
||||||
"* Not recognised by DPI analysis systems, resistant to blocking\n"
|
"* Not recognised by DPI analysis systems, resistant to blocking\n"
|
||||||
"* Works over UDP network protocol.") },
|
"* Works over UDP network protocol.") },
|
||||||
|
{ DockerContainer::Xray,
|
||||||
|
QObject::tr("The REALITY protocol, a pioneering development by the creators of XRay, "
|
||||||
|
"is specifically designed to counteract the highest levels of internet censorship through its novel approach to evasion.\n"
|
||||||
|
"It uniquely identifies censors during the TLS handshake phase, seamlessly operating as a proxy for legitimate clients while diverting censors to genuine websites like google.com, "
|
||||||
|
"thus presenting an authentic TLS certificate and data. \n"
|
||||||
|
"This advanced capability differentiates REALITY from similar technologies by its ability to disguise web traffic as coming from random, "
|
||||||
|
"legitimate sites without the need for specific configurations. \n"
|
||||||
|
"Unlike older protocols such as VMess, VLESS, and the XTLS-Vision transport, "
|
||||||
|
"REALITY's innovative \"friend or foe\" recognition at the TLS handshake enhances security and circumvents detection by sophisticated DPI systems employing active probing techniques. "
|
||||||
|
"This makes REALITY a robust solution for maintaining internet freedom in environments with stringent censorship.")
|
||||||
|
},
|
||||||
{ DockerContainer::Ipsec,
|
{ DockerContainer::Ipsec,
|
||||||
QObject::tr("IKEv2, paired with the IPSec encryption layer, stands as a modern and stable VPN protocol.\n"
|
QObject::tr("IKEv2, paired with the IPSec encryption layer, stands as a modern and stable VPN protocol.\n"
|
||||||
"One of its distinguishing features is its ability to swiftly switch between networks and devices, "
|
"One of its distinguishing features is its ability to swiftly switch between networks and devices, "
|
||||||
@@ -213,7 +233,11 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
|
|||||||
|
|
||||||
{ DockerContainer::TorWebSite, QObject::tr("Website in Tor network") },
|
{ DockerContainer::TorWebSite, QObject::tr("Website in Tor network") },
|
||||||
{ DockerContainer::Dns, QObject::tr("DNS Service") },
|
{ DockerContainer::Dns, QObject::tr("DNS Service") },
|
||||||
{ DockerContainer::Sftp, QObject::tr("Sftp file sharing service - is secure FTP service") }
|
{ DockerContainer::Sftp,
|
||||||
|
QObject::tr("After installation, Amnezia will create a\n\n file storage on your server. "
|
||||||
|
"You will be able to access it using\n FileZilla or other SFTP clients, "
|
||||||
|
"as well as mount the disk on your device to access\n it directly from your device.\n\n"
|
||||||
|
"For more detailed information, you can\n find it in the support section under \"Create SFTP file storage.\" ") }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,6 +255,7 @@ Proto ContainerProps::defaultProtocol(DockerContainer c)
|
|||||||
case DockerContainer::ShadowSocks: return Proto::ShadowSocks;
|
case DockerContainer::ShadowSocks: return Proto::ShadowSocks;
|
||||||
case DockerContainer::WireGuard: return Proto::WireGuard;
|
case DockerContainer::WireGuard: return Proto::WireGuard;
|
||||||
case DockerContainer::Awg: return Proto::Awg;
|
case DockerContainer::Awg: return Proto::Awg;
|
||||||
|
case DockerContainer::Xray: return Proto::Xray;
|
||||||
case DockerContainer::Ipsec: return Proto::Ikev2;
|
case DockerContainer::Ipsec: return Proto::Ikev2;
|
||||||
|
|
||||||
case DockerContainer::TorWebSite: return Proto::TorWebSite;
|
case DockerContainer::TorWebSite: return Proto::TorWebSite;
|
||||||
@@ -274,7 +299,6 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
|
|||||||
|
|
||||||
#elif defined(Q_OS_LINUX)
|
#elif defined(Q_OS_LINUX)
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case DockerContainer::WireGuard: return true;
|
|
||||||
case DockerContainer::Ipsec: return false;
|
case DockerContainer::Ipsec: return false;
|
||||||
default: return true;
|
default: return true;
|
||||||
}
|
}
|
||||||
@@ -342,3 +366,13 @@ bool ContainerProps::isShareable(DockerContainer container)
|
|||||||
default: return true;
|
default: return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QJsonObject ContainerProps::getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig)
|
||||||
|
{
|
||||||
|
QString protocolConfigString = containerConfig.value(ProtocolProps::protoToString(protocol))
|
||||||
|
.toObject()
|
||||||
|
.value(config_key::last_config)
|
||||||
|
.toString();
|
||||||
|
|
||||||
|
return QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ namespace amnezia
|
|||||||
Cloak,
|
Cloak,
|
||||||
ShadowSocks,
|
ShadowSocks,
|
||||||
Ipsec,
|
Ipsec,
|
||||||
|
Xray,
|
||||||
|
|
||||||
// non-vpn
|
// non-vpn
|
||||||
TorWebSite,
|
TorWebSite,
|
||||||
@@ -67,6 +68,8 @@ namespace amnezia
|
|||||||
static int easySetupOrder(amnezia::DockerContainer container);
|
static int easySetupOrder(amnezia::DockerContainer container);
|
||||||
|
|
||||||
static bool isShareable(amnezia::DockerContainer container);
|
static bool isShareable(amnezia::DockerContainer container);
|
||||||
|
|
||||||
|
static QJsonObject getProtocolConfigFromContainer(const amnezia::Proto protocol, const QJsonObject &containerConfig);
|
||||||
};
|
};
|
||||||
|
|
||||||
static void declareQmlContainerEnum()
|
static void declareQmlContainerEnum()
|
||||||
|
|||||||
+28
-37
@@ -5,9 +5,7 @@
|
|||||||
#include <QNetworkReply>
|
#include <QNetworkReply>
|
||||||
#include <QtConcurrent>
|
#include <QtConcurrent>
|
||||||
|
|
||||||
#include "configurators/openvpn_configurator.h"
|
|
||||||
#include "configurators/wireguard_configurator.h"
|
#include "configurators/wireguard_configurator.h"
|
||||||
#include "core/errorstrings.h"
|
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -21,12 +19,11 @@ namespace
|
|||||||
constexpr char certificate[] = "certificate";
|
constexpr char certificate[] = "certificate";
|
||||||
constexpr char publicKey[] = "public_key";
|
constexpr char publicKey[] = "public_key";
|
||||||
constexpr char protocol[] = "protocol";
|
constexpr char protocol[] = "protocol";
|
||||||
|
constexpr char uuid[] = "installation_uuid";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ApiController::ApiController(const QSharedPointer<ServersModel> &serversModel,
|
ApiController::ApiController(QObject *parent) : QObject(parent)
|
||||||
const QSharedPointer<ContainersModel> &containersModel, QObject *parent)
|
|
||||||
: QObject(parent), m_serversModel(serversModel), m_containersModel(containersModel)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,18 +64,14 @@ QJsonObject ApiController::fillApiPayload(const QString &protocol, const ApiCont
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ApiController::updateServerConfigFromApi()
|
ErrorCode ApiController::updateServerConfigFromApi(const QString &installationUuid, QJsonObject &serverConfig)
|
||||||
{
|
{
|
||||||
QtConcurrent::run([this]() {
|
QFutureWatcher<ErrorCode> watcher;
|
||||||
auto serverConfig = m_serversModel->getDefaultServerConfig();
|
|
||||||
|
QFuture<ErrorCode> future = QtConcurrent::run([this, &serverConfig, &installationUuid]() {
|
||||||
auto containerConfig = serverConfig.value(config_key::containers).toArray();
|
auto containerConfig = serverConfig.value(config_key::containers).toArray();
|
||||||
|
|
||||||
bool isConfigUpdateStarted = false;
|
if (serverConfig.value(config_key::configVersion).toInt()) {
|
||||||
|
|
||||||
if (serverConfig.value(config_key::configVersion).toInt() && containerConfig.isEmpty()) {
|
|
||||||
emit updateStarted();
|
|
||||||
isConfigUpdateStarted = true;
|
|
||||||
|
|
||||||
QNetworkAccessManager manager;
|
QNetworkAccessManager manager;
|
||||||
|
|
||||||
QNetworkRequest request;
|
QNetworkRequest request;
|
||||||
@@ -87,13 +80,16 @@ void ApiController::updateServerConfigFromApi()
|
|||||||
request.setRawHeader("Authorization",
|
request.setRawHeader("Authorization",
|
||||||
"Api-Key " + serverConfig.value(configKey::accessToken).toString().toUtf8());
|
"Api-Key " + serverConfig.value(configKey::accessToken).toString().toUtf8());
|
||||||
QString endpoint = serverConfig.value(configKey::apiEdnpoint).toString();
|
QString endpoint = serverConfig.value(configKey::apiEdnpoint).toString();
|
||||||
request.setUrl(endpoint.replace("https", "http")); // todo remove
|
request.setUrl(endpoint);
|
||||||
|
|
||||||
QString protocol = serverConfig.value(configKey::protocol).toString();
|
QString protocol = serverConfig.value(configKey::protocol).toString();
|
||||||
|
|
||||||
auto apiPayloadData = generateApiPayloadData(protocol);
|
auto apiPayloadData = generateApiPayloadData(protocol);
|
||||||
|
|
||||||
QByteArray requestBody = QJsonDocument(fillApiPayload(protocol, apiPayloadData)).toJson();
|
auto apiPayload = fillApiPayload(protocol, apiPayloadData);
|
||||||
|
apiPayload[configKey::uuid] = installationUuid;
|
||||||
|
|
||||||
|
QByteArray requestBody = QJsonDocument(apiPayload).toJson();
|
||||||
|
|
||||||
QScopedPointer<QNetworkReply> reply;
|
QScopedPointer<QNetworkReply> reply;
|
||||||
reply.reset(manager.post(request, requestBody));
|
reply.reset(manager.post(request, requestBody));
|
||||||
@@ -110,6 +106,10 @@ void ApiController::updateServerConfigFromApi()
|
|||||||
QByteArray ba = QByteArray::fromBase64(data.toUtf8(),
|
QByteArray ba = QByteArray::fromBase64(data.toUtf8(),
|
||||||
QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
||||||
|
|
||||||
|
if (ba.isEmpty()) {
|
||||||
|
return ErrorCode::ApiConfigDownloadError;
|
||||||
|
}
|
||||||
|
|
||||||
QByteArray ba_uncompressed = qUncompress(ba);
|
QByteArray ba_uncompressed = qUncompress(ba);
|
||||||
if (!ba_uncompressed.isEmpty()) {
|
if (!ba_uncompressed.isEmpty()) {
|
||||||
ba = ba_uncompressed;
|
ba = ba_uncompressed;
|
||||||
@@ -127,31 +127,22 @@ void ApiController::updateServerConfigFromApi()
|
|||||||
|
|
||||||
auto defaultContainer = apiConfig.value(config_key::defaultContainer).toString();
|
auto defaultContainer = apiConfig.value(config_key::defaultContainer).toString();
|
||||||
serverConfig.insert(config_key::defaultContainer, defaultContainer);
|
serverConfig.insert(config_key::defaultContainer, defaultContainer);
|
||||||
m_serversModel->editServer(serverConfig);
|
|
||||||
emit m_serversModel->defaultContainerChanged(ContainerProps::containerFromString(defaultContainer));
|
|
||||||
} else {
|
} else {
|
||||||
|
QString err = reply->errorString();
|
||||||
|
qDebug() << QString::fromUtf8(reply->readAll());
|
||||||
qDebug() << reply->error();
|
qDebug() << reply->error();
|
||||||
|
qDebug() << err;
|
||||||
qDebug() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
|
qDebug() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
|
||||||
emit errorOccurred(errorString(ApiConfigDownloadError));
|
return ErrorCode::ApiConfigDownloadError;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return ErrorCode::NoError;
|
||||||
emit updateFinished(isConfigUpdateStarted);
|
|
||||||
return;
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
QEventLoop wait;
|
||||||
void ApiController::clearApiConfig()
|
connect(&watcher, &QFutureWatcher<ErrorCode>::finished, &wait, &QEventLoop::quit);
|
||||||
{
|
watcher.setFuture(future);
|
||||||
auto serverConfig = m_serversModel->getDefaultServerConfig();
|
wait.exec();
|
||||||
|
|
||||||
serverConfig.remove(config_key::dns1);
|
return watcher.result();
|
||||||
serverConfig.remove(config_key::dns2);
|
|
||||||
serverConfig.remove(config_key::containers);
|
|
||||||
serverConfig.remove(config_key::hostName);
|
|
||||||
|
|
||||||
serverConfig.insert(config_key::defaultContainer, ContainerProps::containerToString(DockerContainer::None));
|
|
||||||
|
|
||||||
m_serversModel->editServer(serverConfig);
|
|
||||||
}
|
}
|
||||||
@@ -4,26 +4,16 @@
|
|||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
#include "configurators/openvpn_configurator.h"
|
#include "configurators/openvpn_configurator.h"
|
||||||
#include "ui/models/containers_model.h"
|
|
||||||
#include "ui/models/servers_model.h"
|
|
||||||
|
|
||||||
class ApiController : public QObject
|
class ApiController : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ApiController(const QSharedPointer<ServersModel> &serversModel,
|
explicit ApiController(QObject *parent = nullptr);
|
||||||
const QSharedPointer<ContainersModel> &containersModel, QObject *parent = nullptr);
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void updateServerConfigFromApi();
|
ErrorCode updateServerConfigFromApi(const QString &installationUuid, QJsonObject &serverConfig);
|
||||||
|
|
||||||
void clearApiConfig();
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void updateStarted();
|
|
||||||
void updateFinished(bool isConfigUpdateStarted);
|
|
||||||
void errorOccurred(const QString &errorMessage);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct ApiPayloadData {
|
struct ApiPayloadData {
|
||||||
@@ -36,9 +26,6 @@ private:
|
|||||||
ApiPayloadData generateApiPayloadData(const QString &protocol);
|
ApiPayloadData generateApiPayloadData(const QString &protocol);
|
||||||
QJsonObject fillApiPayload(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData);
|
QJsonObject fillApiPayload(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData);
|
||||||
void processApiConfig(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData, QString &config);
|
void processApiConfig(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData, QString &config);
|
||||||
|
|
||||||
QSharedPointer<ServersModel> m_serversModel;
|
|
||||||
QSharedPointer<ContainersModel> m_containersModel;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // APICONTROLLER_H
|
#endif // APICONTROLLER_H
|
||||||
@@ -23,13 +23,13 @@
|
|||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
#include "containers/containers_defs.h"
|
#include "containers/containers_defs.h"
|
||||||
#include "logger.h"
|
#include "core/networkUtilities.h"
|
||||||
#include "core/scripts_registry.h"
|
#include "core/scripts_registry.h"
|
||||||
#include "core/server_defs.h"
|
#include "core/server_defs.h"
|
||||||
|
#include "logger.h"
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "utilities.h"
|
#include "utilities.h"
|
||||||
|
#include "vpnConfigurationController.h"
|
||||||
#include <configurators/vpn_configurator.h>
|
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -95,10 +95,9 @@ ErrorCode ServerController::runScript(const ServerCredentials &credentials, QStr
|
|||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode
|
ErrorCode ServerController::runContainerScript(const ServerCredentials &credentials, DockerContainer container, QString script,
|
||||||
ServerController::runContainerScript(const ServerCredentials &credentials, DockerContainer container, QString script,
|
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdOut,
|
||||||
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdOut,
|
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdErr)
|
||||||
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdErr)
|
|
||||||
{
|
{
|
||||||
QString fileName = "/opt/amnezia/" + Utils::getRandomString(16) + ".sh";
|
QString fileName = "/opt/amnezia/" + Utils::getRandomString(16) + ".sh";
|
||||||
Logger::appendSshLog("Run container script for " + ContainerProps::containerToString(container) + ":\n" + script);
|
Logger::appendSshLog("Run container script for " + ContainerProps::containerToString(container) + ":\n" + script);
|
||||||
@@ -116,9 +115,8 @@ ServerController::runContainerScript(const ServerCredentials &credentials, Docke
|
|||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container, const ServerCredentials &credentials,
|
ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container, const ServerCredentials &credentials, const QString &file,
|
||||||
const QString &file, const QString &path,
|
const QString &path, libssh::ScpOverwriteMode overwriteMode)
|
||||||
libssh::SftpOverwriteMode overwriteMode)
|
|
||||||
{
|
{
|
||||||
ErrorCode e = ErrorCode::NoError;
|
ErrorCode e = ErrorCode::NoError;
|
||||||
QString tmpFileName = QString("/tmp/%1.tmp").arg(Utils::getRandomString(16));
|
QString tmpFileName = QString("/tmp/%1.tmp").arg(Utils::getRandomString(16));
|
||||||
@@ -139,7 +137,7 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
|
|||||||
if (e)
|
if (e)
|
||||||
return e;
|
return e;
|
||||||
|
|
||||||
if (overwriteMode == libssh::SftpOverwriteMode::SftpOverwriteExisting) {
|
if (overwriteMode == libssh::ScpOverwriteMode::ScpOverwriteExisting) {
|
||||||
e = runScript(credentials,
|
e = runScript(credentials,
|
||||||
replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(path),
|
replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(path),
|
||||||
genVarsForScript(credentials, container)),
|
genVarsForScript(credentials, container)),
|
||||||
@@ -147,7 +145,7 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
|
|||||||
|
|
||||||
if (e)
|
if (e)
|
||||||
return e;
|
return e;
|
||||||
} else if (overwriteMode == libssh::SftpOverwriteMode::SftpAppendToExisting) {
|
} else if (overwriteMode == libssh::ScpOverwriteMode::ScpAppendToExisting) {
|
||||||
e = runScript(credentials,
|
e = runScript(credentials,
|
||||||
replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(tmpFileName),
|
replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(tmpFileName),
|
||||||
genVarsForScript(credentials, container)),
|
genVarsForScript(credentials, container)),
|
||||||
@@ -156,12 +154,10 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
|
|||||||
if (e)
|
if (e)
|
||||||
return e;
|
return e;
|
||||||
|
|
||||||
e = runScript(
|
e = runScript(credentials,
|
||||||
credentials,
|
replaceVars(QString("sudo docker exec -i $CONTAINER_NAME sh -c \"cat %1 >> %2\"").arg(tmpFileName).arg(path),
|
||||||
replaceVars(
|
genVarsForScript(credentials, container)),
|
||||||
QString("sudo docker exec -i $CONTAINER_NAME sh -c \"cat %1 >> %2\"").arg(tmpFileName).arg(path),
|
cbReadStd, cbReadStd);
|
||||||
genVarsForScript(credentials, container)),
|
|
||||||
cbReadStd, cbReadStd);
|
|
||||||
|
|
||||||
if (e)
|
if (e)
|
||||||
return e;
|
return e;
|
||||||
@@ -172,21 +168,17 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
|
|||||||
return ErrorCode::ServerContainerMissingError;
|
return ErrorCode::ServerContainerMissingError;
|
||||||
}
|
}
|
||||||
|
|
||||||
runScript(credentials,
|
runScript(credentials, replaceVars(QString("sudo shred -u %1").arg(tmpFileName), genVarsForScript(credentials, container)));
|
||||||
replaceVars(QString("sudo shred -u %1").arg(tmpFileName), genVarsForScript(credentials, container)));
|
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray ServerController::getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials,
|
QByteArray ServerController::getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials, const QString &path,
|
||||||
const QString &path, ErrorCode *errorCode)
|
ErrorCode errorCode)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (errorCode)
|
errorCode = ErrorCode::NoError;
|
||||||
*errorCode = ErrorCode::NoError;
|
|
||||||
|
|
||||||
QString script = QString("sudo docker exec -i %1 sh -c \"xxd -p \'%2\'\"")
|
QString script = QString("sudo docker exec -i %1 sh -c \"xxd -p \'%2\'\"").arg(ContainerProps::containerToString(container)).arg(path);
|
||||||
.arg(ContainerProps::containerToString(container))
|
|
||||||
.arg(path);
|
|
||||||
|
|
||||||
QString stdOut;
|
QString stdOut;
|
||||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||||
@@ -194,12 +186,12 @@ QByteArray ServerController::getTextFileFromContainer(DockerContainer container,
|
|||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
};
|
};
|
||||||
|
|
||||||
*errorCode = runScript(credentials, script, cbReadStdOut);
|
errorCode = runScript(credentials, script, cbReadStdOut);
|
||||||
return QByteArray::fromHex(stdOut.toUtf8());
|
return QByteArray::fromHex(stdOut.toUtf8());
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data,
|
ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath,
|
||||||
const QString &remotePath, libssh::SftpOverwriteMode overwriteMode)
|
libssh::ScpOverwriteMode overwriteMode)
|
||||||
{
|
{
|
||||||
auto error = m_sshClient.connectToHost(credentials);
|
auto error = m_sshClient.connectToHost(credentials);
|
||||||
if (error != ErrorCode::NoError) {
|
if (error != ErrorCode::NoError) {
|
||||||
@@ -211,13 +203,7 @@ ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credential
|
|||||||
localFile.write(data);
|
localFile.write(data);
|
||||||
localFile.close();
|
localFile.close();
|
||||||
|
|
||||||
#ifdef Q_OS_WINDOWS
|
error = m_sshClient.scpFileCopy(overwriteMode, localFile.fileName(), remotePath, "non_desc");
|
||||||
error = m_sshClient.sftpFileCopy(overwriteMode, localFile.fileName().toLocal8Bit().toStdString(), remotePath.toStdString(),
|
|
||||||
"non_desc");
|
|
||||||
#else
|
|
||||||
error = m_sshClient.sftpFileCopy(overwriteMode, localFile.fileName().toStdString(), remotePath.toStdString(),
|
|
||||||
"non_desc");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (error != ErrorCode::NoError) {
|
if (error != ErrorCode::NoError) {
|
||||||
return error;
|
return error;
|
||||||
@@ -251,12 +237,10 @@ ErrorCode ServerController::removeAllContainers(const ServerCredentials &credent
|
|||||||
ErrorCode ServerController::removeContainer(const ServerCredentials &credentials, DockerContainer container)
|
ErrorCode ServerController::removeContainer(const ServerCredentials &credentials, DockerContainer container)
|
||||||
{
|
{
|
||||||
return runScript(credentials,
|
return runScript(credentials,
|
||||||
replaceVars(amnezia::scriptData(SharedScriptType::remove_container),
|
replaceVars(amnezia::scriptData(SharedScriptType::remove_container), genVarsForScript(credentials, container)));
|
||||||
genVarsForScript(credentials, container)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode ServerController::setupContainer(const ServerCredentials &credentials, DockerContainer container,
|
ErrorCode ServerController::setupContainer(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config, bool isUpdate)
|
||||||
QJsonObject &config, bool isUpdate)
|
|
||||||
{
|
{
|
||||||
qDebug().noquote() << "ServerController::setupContainer" << ContainerProps::containerToString(container);
|
qDebug().noquote() << "ServerController::setupContainer" << ContainerProps::containerToString(container);
|
||||||
ErrorCode e = ErrorCode::NoError;
|
ErrorCode e = ErrorCode::NoError;
|
||||||
@@ -316,12 +300,11 @@ ErrorCode ServerController::setupContainer(const ServerCredentials &credentials,
|
|||||||
return startupContainerWorker(credentials, container, config);
|
return startupContainerWorker(credentials, container, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode ServerController::updateContainer(const ServerCredentials &credentials, DockerContainer container,
|
ErrorCode ServerController::updateContainer(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &oldConfig,
|
||||||
const QJsonObject &oldConfig, QJsonObject &newConfig)
|
QJsonObject &newConfig)
|
||||||
{
|
{
|
||||||
bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig);
|
bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig);
|
||||||
qDebug() << "ServerController::updateContainer for container" << container << "reinstall required is"
|
qDebug() << "ServerController::updateContainer for container" << container << "reinstall required is" << reinstallRequired;
|
||||||
<< reinstallRequired;
|
|
||||||
|
|
||||||
if (reinstallRequired) {
|
if (reinstallRequired) {
|
||||||
return setupContainer(credentials, container, newConfig, true);
|
return setupContainer(credentials, container, newConfig, true);
|
||||||
@@ -334,8 +317,7 @@ ErrorCode ServerController::updateContainer(const ServerCredentials &credentials
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ServerController::isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig,
|
bool ServerController::isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig)
|
||||||
const QJsonObject &newConfig)
|
|
||||||
{
|
{
|
||||||
Proto mainProto = ContainerProps::defaultProtocol(container);
|
Proto mainProto = ContainerProps::defaultProtocol(container);
|
||||||
|
|
||||||
@@ -366,7 +348,7 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
|
|||||||
|
|
||||||
if (container == DockerContainer::Awg) {
|
if (container == DockerContainer::Awg) {
|
||||||
if ((oldProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort)
|
if ((oldProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort)
|
||||||
!= newProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort))
|
!= newProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort))
|
||||||
|| (oldProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount)
|
|| (oldProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount)
|
||||||
!= newProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount))
|
!= newProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount))
|
||||||
|| (oldProtoConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize)
|
|| (oldProtoConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize)
|
||||||
@@ -388,7 +370,7 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (container == DockerContainer::WireGuard){
|
if (container == DockerContainer::WireGuard) {
|
||||||
if (oldProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort)
|
if (oldProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort)
|
||||||
!= newProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort))
|
!= newProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort))
|
||||||
return true;
|
return true;
|
||||||
@@ -414,8 +396,7 @@ ErrorCode ServerController::installDockerWorker(const ServerCredentials &credent
|
|||||||
};
|
};
|
||||||
|
|
||||||
ErrorCode error =
|
ErrorCode error =
|
||||||
runScript(credentials,
|
runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::install_docker), genVarsForScript(credentials)),
|
||||||
replaceVars(amnezia::scriptData(SharedScriptType::install_docker), genVarsForScript(credentials)),
|
|
||||||
cbReadStdOut, cbReadStdErr);
|
cbReadStdOut, cbReadStdErr);
|
||||||
|
|
||||||
qDebug().noquote() << "ServerController::installDockerWorker" << stdOut;
|
qDebug().noquote() << "ServerController::installDockerWorker" << stdOut;
|
||||||
@@ -427,17 +408,13 @@ ErrorCode ServerController::installDockerWorker(const ServerCredentials &credent
|
|||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode ServerController::prepareHostWorker(const ServerCredentials &credentials, DockerContainer container,
|
ErrorCode ServerController::prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
|
||||||
const QJsonObject &config)
|
|
||||||
{
|
{
|
||||||
// create folder on host
|
// create folder on host
|
||||||
return runScript(
|
return runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::prepare_host), genVarsForScript(credentials, container)));
|
||||||
credentials,
|
|
||||||
replaceVars(amnezia::scriptData(SharedScriptType::prepare_host), genVarsForScript(credentials, container)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode ServerController::buildContainerWorker(const ServerCredentials &credentials, DockerContainer container,
|
ErrorCode ServerController::buildContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
|
||||||
const QJsonObject &config)
|
|
||||||
{
|
{
|
||||||
ErrorCode e = uploadFileToHost(credentials, amnezia::scriptData(ProtocolScriptType::dockerfile, container).toUtf8(),
|
ErrorCode e = uploadFileToHost(credentials, amnezia::scriptData(ProtocolScriptType::dockerfile, container).toUtf8(),
|
||||||
amnezia::server::getDockerfileFolder(container) + "/Dockerfile");
|
amnezia::server::getDockerfileFolder(container) + "/Dockerfile");
|
||||||
@@ -450,13 +427,9 @@ ErrorCode ServerController::buildContainerWorker(const ServerCredentials &creden
|
|||||||
stdOut += data + "\n";
|
stdOut += data + "\n";
|
||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
};
|
};
|
||||||
// auto cbReadStdErr = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
|
|
||||||
// stdOut += data + "\n";
|
|
||||||
// };
|
|
||||||
|
|
||||||
e = runScript(credentials,
|
e = runScript(credentials,
|
||||||
replaceVars(amnezia::scriptData(SharedScriptType::build_container),
|
replaceVars(amnezia::scriptData(SharedScriptType::build_container), genVarsForScript(credentials, container, config)),
|
||||||
genVarsForScript(credentials, container, config)),
|
|
||||||
cbReadStdOut);
|
cbReadStdOut);
|
||||||
if (e)
|
if (e)
|
||||||
return e;
|
return e;
|
||||||
@@ -464,17 +437,13 @@ ErrorCode ServerController::buildContainerWorker(const ServerCredentials &creden
|
|||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode ServerController::runContainerWorker(const ServerCredentials &credentials, DockerContainer container,
|
ErrorCode ServerController::runContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config)
|
||||||
QJsonObject &config)
|
|
||||||
{
|
{
|
||||||
QString stdOut;
|
QString stdOut;
|
||||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||||
stdOut += data + "\n";
|
stdOut += data + "\n";
|
||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
};
|
};
|
||||||
// auto cbReadStdErr = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
|
|
||||||
// stdOut += data + "\n";
|
|
||||||
// };
|
|
||||||
|
|
||||||
ErrorCode e = runScript(credentials,
|
ErrorCode e = runScript(credentials,
|
||||||
replaceVars(amnezia::scriptData(ProtocolScriptType::run_container, container),
|
replaceVars(amnezia::scriptData(ProtocolScriptType::run_container, container),
|
||||||
@@ -491,8 +460,7 @@ ErrorCode ServerController::runContainerWorker(const ServerCredentials &credenti
|
|||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode ServerController::configureContainerWorker(const ServerCredentials &credentials, DockerContainer container,
|
ErrorCode ServerController::configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config)
|
||||||
QJsonObject &config)
|
|
||||||
{
|
{
|
||||||
QString stdOut;
|
QString stdOut;
|
||||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||||
@@ -509,13 +477,12 @@ ErrorCode ServerController::configureContainerWorker(const ServerCredentials &cr
|
|||||||
genVarsForScript(credentials, container, config)),
|
genVarsForScript(credentials, container, config)),
|
||||||
cbReadStdOut, cbReadStdErr);
|
cbReadStdOut, cbReadStdErr);
|
||||||
|
|
||||||
m_configurator->updateContainerConfigAfterInstallation(container, config, stdOut);
|
VpnConfigurationsController::updateContainerConfigAfterInstallation(container, config, stdOut);
|
||||||
|
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode ServerController::startupContainerWorker(const ServerCredentials &credentials, DockerContainer container,
|
ErrorCode ServerController::startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
|
||||||
const QJsonObject &config)
|
|
||||||
{
|
{
|
||||||
QString script = amnezia::scriptData(ProtocolScriptType::container_startup, container);
|
QString script = amnezia::scriptData(ProtocolScriptType::container_startup, container);
|
||||||
|
|
||||||
@@ -523,8 +490,7 @@ ErrorCode ServerController::startupContainerWorker(const ServerCredentials &cred
|
|||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode e = uploadTextFileToContainer(container, credentials,
|
ErrorCode e = uploadTextFileToContainer(container, credentials, replaceVars(script, genVarsForScript(credentials, container, config)),
|
||||||
replaceVars(script, genVarsForScript(credentials, container, config)),
|
|
||||||
"/opt/amnezia/start.sh");
|
"/opt/amnezia/start.sh");
|
||||||
if (e)
|
if (e)
|
||||||
return e;
|
return e;
|
||||||
@@ -535,14 +501,15 @@ ErrorCode ServerController::startupContainerWorker(const ServerCredentials &cred
|
|||||||
genVarsForScript(credentials, container, config)));
|
genVarsForScript(credentials, container, config)));
|
||||||
}
|
}
|
||||||
|
|
||||||
ServerController::Vars ServerController::genVarsForScript(const ServerCredentials &credentials,
|
ServerController::Vars ServerController::genVarsForScript(const ServerCredentials &credentials, DockerContainer container,
|
||||||
DockerContainer container, const QJsonObject &config)
|
const QJsonObject &config)
|
||||||
{
|
{
|
||||||
const QJsonObject &openvpnConfig = config.value(ProtocolProps::protoToString(Proto::OpenVpn)).toObject();
|
const QJsonObject &openvpnConfig = config.value(ProtocolProps::protoToString(Proto::OpenVpn)).toObject();
|
||||||
const QJsonObject &cloakConfig = config.value(ProtocolProps::protoToString(Proto::Cloak)).toObject();
|
const QJsonObject &cloakConfig = config.value(ProtocolProps::protoToString(Proto::Cloak)).toObject();
|
||||||
const QJsonObject &ssConfig = config.value(ProtocolProps::protoToString(Proto::ShadowSocks)).toObject();
|
const QJsonObject &ssConfig = config.value(ProtocolProps::protoToString(Proto::ShadowSocks)).toObject();
|
||||||
const QJsonObject &wireguarConfig = config.value(ProtocolProps::protoToString(Proto::WireGuard)).toObject();
|
const QJsonObject &wireguarConfig = config.value(ProtocolProps::protoToString(Proto::WireGuard)).toObject();
|
||||||
const QJsonObject &amneziaWireguarConfig = config.value(ProtocolProps::protoToString(Proto::Awg)).toObject();
|
const QJsonObject &amneziaWireguarConfig = config.value(ProtocolProps::protoToString(Proto::Awg)).toObject();
|
||||||
|
const QJsonObject &xrayConfig = config.value(ProtocolProps::protoToString(Proto::Xray)).toObject();
|
||||||
const QJsonObject &sftpConfig = config.value(ProtocolProps::protoToString(Proto::Sftp)).toObject();
|
const QJsonObject &sftpConfig = config.value(ProtocolProps::protoToString(Proto::Sftp)).toObject();
|
||||||
|
|
||||||
Vars vars;
|
Vars vars;
|
||||||
@@ -550,24 +517,19 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
|
|||||||
vars.append({ { "$REMOTE_HOST", credentials.hostName } });
|
vars.append({ { "$REMOTE_HOST", credentials.hostName } });
|
||||||
|
|
||||||
// OpenVPN vars
|
// OpenVPN vars
|
||||||
vars.append(
|
vars.append({ { "$OPENVPN_SUBNET_IP",
|
||||||
{ { "$OPENVPN_SUBNET_IP",
|
openvpnConfig.value(config_key::subnet_address).toString(protocols::openvpn::defaultSubnetAddress) } });
|
||||||
openvpnConfig.value(config_key::subnet_address).toString(protocols::openvpn::defaultSubnetAddress) } });
|
vars.append({ { "$OPENVPN_SUBNET_CIDR", openvpnConfig.value(config_key::subnet_cidr).toString(protocols::openvpn::defaultSubnetCidr) } });
|
||||||
vars.append({ { "$OPENVPN_SUBNET_CIDR",
|
vars.append({ { "$OPENVPN_SUBNET_MASK", openvpnConfig.value(config_key::subnet_mask).toString(protocols::openvpn::defaultSubnetMask) } });
|
||||||
openvpnConfig.value(config_key::subnet_cidr).toString(protocols::openvpn::defaultSubnetCidr) } });
|
|
||||||
vars.append({ { "$OPENVPN_SUBNET_MASK",
|
|
||||||
openvpnConfig.value(config_key::subnet_mask).toString(protocols::openvpn::defaultSubnetMask) } });
|
|
||||||
|
|
||||||
vars.append({ { "$OPENVPN_PORT", openvpnConfig.value(config_key::port).toString(protocols::openvpn::defaultPort) } });
|
vars.append({ { "$OPENVPN_PORT", openvpnConfig.value(config_key::port).toString(protocols::openvpn::defaultPort) } });
|
||||||
vars.append(
|
vars.append({ { "$OPENVPN_TRANSPORT_PROTO",
|
||||||
{ { "$OPENVPN_TRANSPORT_PROTO",
|
openvpnConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto) } });
|
||||||
openvpnConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto) } });
|
|
||||||
|
|
||||||
bool isNcpDisabled = openvpnConfig.value(config_key::ncp_disable).toBool(protocols::openvpn::defaultNcpDisable);
|
bool isNcpDisabled = openvpnConfig.value(config_key::ncp_disable).toBool(protocols::openvpn::defaultNcpDisable);
|
||||||
vars.append({ { "$OPENVPN_NCP_DISABLE", isNcpDisabled ? protocols::openvpn::ncpDisableString : "" } });
|
vars.append({ { "$OPENVPN_NCP_DISABLE", isNcpDisabled ? protocols::openvpn::ncpDisableString : "" } });
|
||||||
|
|
||||||
vars.append({ { "$OPENVPN_CIPHER",
|
vars.append({ { "$OPENVPN_CIPHER", openvpnConfig.value(config_key::cipher).toString(protocols::openvpn::defaultCipher) } });
|
||||||
openvpnConfig.value(config_key::cipher).toString(protocols::openvpn::defaultCipher) } });
|
|
||||||
vars.append({ { "$OPENVPN_HASH", openvpnConfig.value(config_key::hash).toString(protocols::openvpn::defaultHash) } });
|
vars.append({ { "$OPENVPN_HASH", openvpnConfig.value(config_key::hash).toString(protocols::openvpn::defaultHash) } });
|
||||||
|
|
||||||
bool isTlsAuth = openvpnConfig.value(config_key::tls_auth).toBool(protocols::openvpn::defaultTlsAuth);
|
bool isTlsAuth = openvpnConfig.value(config_key::tls_auth).toBool(protocols::openvpn::defaultTlsAuth);
|
||||||
@@ -578,39 +540,35 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
|
|||||||
}
|
}
|
||||||
|
|
||||||
vars.append({ { "$OPENVPN_ADDITIONAL_CLIENT_CONFIG",
|
vars.append({ { "$OPENVPN_ADDITIONAL_CLIENT_CONFIG",
|
||||||
openvpnConfig.value(config_key::additional_client_config)
|
openvpnConfig.value(config_key::additional_client_config).toString(protocols::openvpn::defaultAdditionalClientConfig) } });
|
||||||
.toString(protocols::openvpn::defaultAdditionalClientConfig) } });
|
|
||||||
vars.append({ { "$OPENVPN_ADDITIONAL_SERVER_CONFIG",
|
vars.append({ { "$OPENVPN_ADDITIONAL_SERVER_CONFIG",
|
||||||
openvpnConfig.value(config_key::additional_server_config)
|
openvpnConfig.value(config_key::additional_server_config).toString(protocols::openvpn::defaultAdditionalServerConfig) } });
|
||||||
.toString(protocols::openvpn::defaultAdditionalServerConfig) } });
|
|
||||||
|
|
||||||
// ShadowSocks vars
|
// ShadowSocks vars
|
||||||
vars.append({ { "$SHADOWSOCKS_SERVER_PORT",
|
vars.append({ { "$SHADOWSOCKS_SERVER_PORT", ssConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort) } });
|
||||||
ssConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort) } });
|
|
||||||
vars.append({ { "$SHADOWSOCKS_LOCAL_PORT",
|
vars.append({ { "$SHADOWSOCKS_LOCAL_PORT",
|
||||||
ssConfig.value(config_key::local_port).toString(protocols::shadowsocks::defaultLocalProxyPort) } });
|
ssConfig.value(config_key::local_port).toString(protocols::shadowsocks::defaultLocalProxyPort) } });
|
||||||
vars.append({ { "$SHADOWSOCKS_CIPHER",
|
vars.append({ { "$SHADOWSOCKS_CIPHER", ssConfig.value(config_key::cipher).toString(protocols::shadowsocks::defaultCipher) } });
|
||||||
ssConfig.value(config_key::cipher).toString(protocols::shadowsocks::defaultCipher) } });
|
|
||||||
|
|
||||||
vars.append({ { "$CONTAINER_NAME", ContainerProps::containerToString(container) } });
|
vars.append({ { "$CONTAINER_NAME", ContainerProps::containerToString(container) } });
|
||||||
vars.append({ { "$DOCKERFILE_FOLDER", "/opt/amnezia/" + ContainerProps::containerToString(container) } });
|
vars.append({ { "$DOCKERFILE_FOLDER", "/opt/amnezia/" + ContainerProps::containerToString(container) } });
|
||||||
|
|
||||||
// Cloak vars
|
// Cloak vars
|
||||||
vars.append({ { "$CLOAK_SERVER_PORT", cloakConfig.value(config_key::port).toString(protocols::cloak::defaultPort) } });
|
vars.append({ { "$CLOAK_SERVER_PORT", cloakConfig.value(config_key::port).toString(protocols::cloak::defaultPort) } });
|
||||||
vars.append({ { "$FAKE_WEB_SITE_ADDRESS",
|
vars.append({ { "$FAKE_WEB_SITE_ADDRESS", cloakConfig.value(config_key::site).toString(protocols::cloak::defaultRedirSite) } });
|
||||||
cloakConfig.value(config_key::site).toString(protocols::cloak::defaultRedirSite) } });
|
|
||||||
|
// Xray vars
|
||||||
|
vars.append({ { "$XRAY_SITE_NAME", xrayConfig.value(config_key::site).toString(protocols::xray::defaultSite) } });
|
||||||
|
|
||||||
// Wireguard vars
|
// Wireguard vars
|
||||||
vars.append(
|
vars.append({ { "$WIREGUARD_SUBNET_IP",
|
||||||
{ { "$WIREGUARD_SUBNET_IP",
|
wireguarConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress) } });
|
||||||
wireguarConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress) } });
|
|
||||||
vars.append({ { "$WIREGUARD_SUBNET_CIDR",
|
vars.append({ { "$WIREGUARD_SUBNET_CIDR",
|
||||||
wireguarConfig.value(config_key::subnet_cidr).toString(protocols::wireguard::defaultSubnetCidr) } });
|
wireguarConfig.value(config_key::subnet_cidr).toString(protocols::wireguard::defaultSubnetCidr) } });
|
||||||
vars.append({ { "$WIREGUARD_SUBNET_MASK",
|
vars.append({ { "$WIREGUARD_SUBNET_MASK",
|
||||||
wireguarConfig.value(config_key::subnet_mask).toString(protocols::wireguard::defaultSubnetMask) } });
|
wireguarConfig.value(config_key::subnet_mask).toString(protocols::wireguard::defaultSubnetMask) } });
|
||||||
|
|
||||||
vars.append({ { "$WIREGUARD_SERVER_PORT",
|
vars.append({ { "$WIREGUARD_SERVER_PORT", wireguarConfig.value(config_key::port).toString(protocols::wireguard::defaultPort) } });
|
||||||
wireguarConfig.value(config_key::port).toString(protocols::wireguard::defaultPort) } });
|
|
||||||
|
|
||||||
// IPsec vars
|
// IPsec vars
|
||||||
vars.append({ { "$IPSEC_VPN_L2TP_NET", "192.168.42.0/24" } });
|
vars.append({ { "$IPSEC_VPN_L2TP_NET", "192.168.42.0/24" } });
|
||||||
@@ -633,32 +591,24 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
|
|||||||
vars.append({ { "$SECONDARY_SERVER_DNS", m_settings->secondaryDns() } });
|
vars.append({ { "$SECONDARY_SERVER_DNS", m_settings->secondaryDns() } });
|
||||||
|
|
||||||
// Sftp vars
|
// Sftp vars
|
||||||
vars.append(
|
vars.append({ { "$SFTP_PORT", sftpConfig.value(config_key::port).toString(QString::number(ProtocolProps::defaultPort(Proto::Sftp))) } });
|
||||||
{ { "$SFTP_PORT",
|
|
||||||
sftpConfig.value(config_key::port).toString(QString::number(ProtocolProps::defaultPort(Proto::Sftp))) } });
|
|
||||||
vars.append({ { "$SFTP_USER", sftpConfig.value(config_key::userName).toString() } });
|
vars.append({ { "$SFTP_USER", sftpConfig.value(config_key::userName).toString() } });
|
||||||
vars.append({ { "$SFTP_PASSWORD", sftpConfig.value(config_key::password).toString() } });
|
vars.append({ { "$SFTP_PASSWORD", sftpConfig.value(config_key::password).toString() } });
|
||||||
|
|
||||||
// Amnezia wireguard vars
|
// Amnezia wireguard vars
|
||||||
vars.append({ { "$AWG_SERVER_PORT",
|
vars.append({ { "$AWG_SERVER_PORT", amneziaWireguarConfig.value(config_key::port).toString(protocols::awg::defaultPort) } });
|
||||||
amneziaWireguarConfig.value(config_key::port).toString(protocols::awg::defaultPort) } });
|
|
||||||
|
|
||||||
vars.append({ { "$JUNK_PACKET_COUNT", amneziaWireguarConfig.value(config_key::junkPacketCount).toString() } });
|
vars.append({ { "$JUNK_PACKET_COUNT", amneziaWireguarConfig.value(config_key::junkPacketCount).toString() } });
|
||||||
vars.append({ { "$JUNK_PACKET_MIN_SIZE", amneziaWireguarConfig.value(config_key::junkPacketMinSize).toString() } });
|
vars.append({ { "$JUNK_PACKET_MIN_SIZE", amneziaWireguarConfig.value(config_key::junkPacketMinSize).toString() } });
|
||||||
vars.append({ { "$JUNK_PACKET_MAX_SIZE", amneziaWireguarConfig.value(config_key::junkPacketMaxSize).toString() } });
|
vars.append({ { "$JUNK_PACKET_MAX_SIZE", amneziaWireguarConfig.value(config_key::junkPacketMaxSize).toString() } });
|
||||||
vars.append({ { "$INIT_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::initPacketJunkSize).toString() } });
|
vars.append({ { "$INIT_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::initPacketJunkSize).toString() } });
|
||||||
vars.append({ { "$RESPONSE_PACKET_JUNK_SIZE",
|
vars.append({ { "$RESPONSE_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::responsePacketJunkSize).toString() } });
|
||||||
amneziaWireguarConfig.value(config_key::responsePacketJunkSize).toString() } });
|
vars.append({ { "$INIT_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::initPacketMagicHeader).toString() } });
|
||||||
vars.append({ { "$INIT_PACKET_MAGIC_HEADER",
|
vars.append({ { "$RESPONSE_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::responsePacketMagicHeader).toString() } });
|
||||||
amneziaWireguarConfig.value(config_key::initPacketMagicHeader).toString() } });
|
vars.append({ { "$UNDERLOAD_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::underloadPacketMagicHeader).toString() } });
|
||||||
vars.append({ { "$RESPONSE_PACKET_MAGIC_HEADER",
|
vars.append({ { "$TRANSPORT_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::transportPacketMagicHeader).toString() } });
|
||||||
amneziaWireguarConfig.value(config_key::responsePacketMagicHeader).toString() } });
|
|
||||||
vars.append({ { "$UNDERLOAD_PACKET_MAGIC_HEADER",
|
|
||||||
amneziaWireguarConfig.value(config_key::underloadPacketMagicHeader).toString() } });
|
|
||||||
vars.append({ { "$TRANSPORT_PACKET_MAGIC_HEADER",
|
|
||||||
amneziaWireguarConfig.value(config_key::transportPacketMagicHeader).toString() } });
|
|
||||||
|
|
||||||
QString serverIp = Utils::getIPAddress(credentials.hostName);
|
QString serverIp = NetworkUtilities::getIPAddress(credentials.hostName);
|
||||||
if (!serverIp.isEmpty()) {
|
if (!serverIp.isEmpty()) {
|
||||||
vars.append({ { "$SERVER_IP_ADDRESS", serverIp } });
|
vars.append({ { "$SERVER_IP_ADDRESS", serverIp } });
|
||||||
} else {
|
} else {
|
||||||
@@ -668,7 +618,7 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
|
|||||||
return vars;
|
return vars;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ServerController::checkSshConnection(const ServerCredentials &credentials, ErrorCode *errorCode)
|
QString ServerController::checkSshConnection(const ServerCredentials &credentials, ErrorCode errorCode)
|
||||||
{
|
{
|
||||||
QString stdOut;
|
QString stdOut;
|
||||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||||
@@ -680,11 +630,7 @@ QString ServerController::checkSshConnection(const ServerCredentials &credential
|
|||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
};
|
};
|
||||||
|
|
||||||
ErrorCode e =
|
errorCode = runScript(credentials, amnezia::scriptData(SharedScriptType::check_connection), cbReadStdOut, cbReadStdErr);
|
||||||
runScript(credentials, amnezia::scriptData(SharedScriptType::check_connection), cbReadStdOut, cbReadStdErr);
|
|
||||||
|
|
||||||
if (errorCode)
|
|
||||||
*errorCode = e;
|
|
||||||
|
|
||||||
return stdOut;
|
return stdOut;
|
||||||
}
|
}
|
||||||
@@ -696,9 +642,7 @@ void ServerController::cancelInstallation()
|
|||||||
|
|
||||||
ErrorCode ServerController::setupServerFirewall(const ServerCredentials &credentials)
|
ErrorCode ServerController::setupServerFirewall(const ServerCredentials &credentials)
|
||||||
{
|
{
|
||||||
return runScript(
|
return runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::setup_host_firewall), genVarsForScript(credentials)));
|
||||||
credentials,
|
|
||||||
replaceVars(amnezia::scriptData(SharedScriptType::setup_host_firewall), genVarsForScript(credentials)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ServerController::replaceVars(const QString &script, const Vars &vars)
|
QString ServerController::replaceVars(const QString &script, const Vars &vars)
|
||||||
@@ -710,8 +654,7 @@ QString ServerController::replaceVars(const QString &script, const Vars &vars)
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credentials, DockerContainer container,
|
ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
|
||||||
const QJsonObject &config)
|
|
||||||
{
|
{
|
||||||
if (container == DockerContainer::Dns) {
|
if (container == DockerContainer::Dns) {
|
||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
@@ -734,15 +677,12 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential
|
|||||||
QStringList fixedPorts = ContainerProps::fixedPortsForContainer(container);
|
QStringList fixedPorts = ContainerProps::fixedPortsForContainer(container);
|
||||||
|
|
||||||
QString defaultPort("%1");
|
QString defaultPort("%1");
|
||||||
QString port =
|
QString port = containerConfig.value(config_key::port).toString(defaultPort.arg(ProtocolProps::defaultPort(protocol)));
|
||||||
containerConfig.value(config_key::port).toString(defaultPort.arg(ProtocolProps::defaultPort(protocol)));
|
QString defaultTransportProto = ProtocolProps::transportProtoToString(ProtocolProps::defaultTransportProto(protocol), protocol);
|
||||||
QString defaultTransportProto =
|
|
||||||
ProtocolProps::transportProtoToString(ProtocolProps::defaultTransportProto(protocol), protocol);
|
|
||||||
QString transportProto = containerConfig.value(config_key::transport_proto).toString(defaultTransportProto);
|
QString transportProto = containerConfig.value(config_key::transport_proto).toString(defaultTransportProto);
|
||||||
|
|
||||||
// TODO reimplement with netstat
|
// TODO reimplement with netstat
|
||||||
QString script =
|
QString script = QString("which lsof &>/dev/null || true && sudo lsof -i -P -n 2>/dev/null | grep -E ':%1 ").arg(port);
|
||||||
QString("which lsof &>/dev/null || true && sudo lsof -i -P -n 2>/dev/null | grep -E ':%1 ").arg(port);
|
|
||||||
for (auto &port : fixedPorts) {
|
for (auto &port : fixedPorts) {
|
||||||
script = script.append("|:%1").arg(port);
|
script = script.append("|:%1").arg(port);
|
||||||
}
|
}
|
||||||
@@ -752,8 +692,7 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential
|
|||||||
script = script.append(" | grep LISTEN");
|
script = script.append(" | grep LISTEN");
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode errorCode = runScript(credentials, replaceVars(script, genVarsForScript(credentials, container)),
|
ErrorCode errorCode = runScript(credentials, replaceVars(script, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr);
|
||||||
cbReadStdOut, cbReadStdErr);
|
|
||||||
if (errorCode != ErrorCode::NoError) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
return errorCode;
|
return errorCode;
|
||||||
}
|
}
|
||||||
@@ -781,8 +720,7 @@ ErrorCode ServerController::isUserInSudo(const ServerCredentials &credentials, D
|
|||||||
};
|
};
|
||||||
|
|
||||||
const QString scriptData = amnezia::scriptData(SharedScriptType::check_user_in_sudo);
|
const QString scriptData = amnezia::scriptData(SharedScriptType::check_user_in_sudo);
|
||||||
ErrorCode error =
|
ErrorCode error = runScript(credentials, replaceVars(scriptData, genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr);
|
||||||
runScript(credentials, replaceVars(scriptData, genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr);
|
|
||||||
|
|
||||||
if (!stdOut.contains("sudo"))
|
if (!stdOut.contains("sudo"))
|
||||||
return ErrorCode::ServerUserNotInSudo;
|
return ErrorCode::ServerUserNotInSudo;
|
||||||
@@ -812,9 +750,7 @@ ErrorCode ServerController::isServerDpkgBusy(const ServerCredentials &credential
|
|||||||
return ErrorCode::ServerCancelInstallation;
|
return ErrorCode::ServerCancelInstallation;
|
||||||
}
|
}
|
||||||
stdOut.clear();
|
stdOut.clear();
|
||||||
runScript(credentials,
|
runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::check_server_is_busy), genVarsForScript(credentials)),
|
||||||
replaceVars(amnezia::scriptData(SharedScriptType::check_server_is_busy),
|
|
||||||
genVarsForScript(credentials)),
|
|
||||||
cbReadStdOut, cbReadStdErr);
|
cbReadStdOut, cbReadStdErr);
|
||||||
|
|
||||||
if (stdOut.contains("Packet manager not found"))
|
if (stdOut.contains("Packet manager not found"))
|
||||||
@@ -845,147 +781,6 @@ ErrorCode ServerController::isServerDpkgBusy(const ServerCredentials &credential
|
|||||||
return future.result();
|
return future.result();
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode ServerController::getAlreadyInstalledContainers(const ServerCredentials &credentials,
|
|
||||||
QMap<DockerContainer, QJsonObject> &installedContainers)
|
|
||||||
{
|
|
||||||
QString stdOut;
|
|
||||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
|
||||||
stdOut += data + "\n";
|
|
||||||
return ErrorCode::NoError;
|
|
||||||
};
|
|
||||||
auto cbReadStdErr = [&](const QString &data, libssh::Client &) {
|
|
||||||
stdOut += data + "\n";
|
|
||||||
return ErrorCode::NoError;
|
|
||||||
};
|
|
||||||
|
|
||||||
QString script = QString("sudo docker ps --format '{{.Names}} {{.Ports}}'");
|
|
||||||
|
|
||||||
ErrorCode errorCode = runScript(credentials, script, cbReadStdOut, cbReadStdErr);
|
|
||||||
if (errorCode != ErrorCode::NoError) {
|
|
||||||
return errorCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto containersInfo = stdOut.split("\n");
|
|
||||||
for (auto &containerInfo : containersInfo) {
|
|
||||||
if (containerInfo.isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const static QRegularExpression containerAndPortRegExp("(amnezia[-a-z]*).*?:([0-9]*)->[0-9]*/(udp|tcp).*");
|
|
||||||
QRegularExpressionMatch containerAndPortMatch = containerAndPortRegExp.match(containerInfo);
|
|
||||||
if (containerAndPortMatch.hasMatch()) {
|
|
||||||
QString name = containerAndPortMatch.captured(1);
|
|
||||||
QString port = containerAndPortMatch.captured(2);
|
|
||||||
QString transportProto = containerAndPortMatch.captured(3);
|
|
||||||
DockerContainer container = ContainerProps::containerFromString(name);
|
|
||||||
|
|
||||||
QJsonObject config;
|
|
||||||
Proto mainProto = ContainerProps::defaultProtocol(container);
|
|
||||||
for (auto protocol : ContainerProps::protocolsForContainer(container)) {
|
|
||||||
QJsonObject containerConfig;
|
|
||||||
if (protocol == mainProto) {
|
|
||||||
containerConfig.insert(config_key::port, port);
|
|
||||||
containerConfig.insert(config_key::transport_proto, transportProto);
|
|
||||||
|
|
||||||
if (protocol == Proto::Awg) {
|
|
||||||
QString serverConfig = getTextFileFromContainer(container, credentials, protocols::awg::serverConfigPath, &errorCode);
|
|
||||||
|
|
||||||
QMap<QString, QString> serverConfigMap;
|
|
||||||
auto serverConfigLines = serverConfig.split("\n");
|
|
||||||
for (auto &line : serverConfigLines) {
|
|
||||||
auto trimmedLine = line.trimmed();
|
|
||||||
if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) {
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
QStringList parts = trimmedLine.split(" = ");
|
|
||||||
if (parts.count() == 2) {
|
|
||||||
serverConfigMap.insert(parts[0].trimmed(), parts[1].trimmed());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
containerConfig[config_key::junkPacketCount] = serverConfigMap.value(config_key::junkPacketCount);
|
|
||||||
containerConfig[config_key::junkPacketMinSize] = serverConfigMap.value(config_key::junkPacketMinSize);
|
|
||||||
containerConfig[config_key::junkPacketMaxSize] = serverConfigMap.value(config_key::junkPacketMaxSize);
|
|
||||||
containerConfig[config_key::initPacketJunkSize] = serverConfigMap.value(config_key::initPacketJunkSize);
|
|
||||||
containerConfig[config_key::responsePacketJunkSize] = serverConfigMap.value(config_key::responsePacketJunkSize);
|
|
||||||
containerConfig[config_key::initPacketMagicHeader] = serverConfigMap.value(config_key::initPacketMagicHeader);
|
|
||||||
containerConfig[config_key::responsePacketMagicHeader] = serverConfigMap.value(config_key::responsePacketMagicHeader);
|
|
||||||
containerConfig[config_key::underloadPacketMagicHeader] = serverConfigMap.value(config_key::underloadPacketMagicHeader);
|
|
||||||
containerConfig[config_key::transportPacketMagicHeader] = serverConfigMap.value(config_key::transportPacketMagicHeader);
|
|
||||||
} else if (protocol == Proto::Sftp) {
|
|
||||||
stdOut.clear();
|
|
||||||
script = QString("sudo docker inspect --format '{{.Config.Cmd}}' %1").arg(name);
|
|
||||||
|
|
||||||
ErrorCode errorCode = runScript(credentials, script, cbReadStdOut, cbReadStdErr);
|
|
||||||
if (errorCode != ErrorCode::NoError) {
|
|
||||||
return errorCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto sftpInfo = stdOut.split(":");
|
|
||||||
if (sftpInfo.size() < 2) {
|
|
||||||
logger.error() << "Key parameters for the sftp container are missing";
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
auto userName = sftpInfo.at(0);
|
|
||||||
userName = userName.remove(0, 1);
|
|
||||||
auto password = sftpInfo.at(1);
|
|
||||||
|
|
||||||
containerConfig.insert(config_key::userName, userName);
|
|
||||||
containerConfig.insert(config_key::password, password);
|
|
||||||
}
|
|
||||||
|
|
||||||
config.insert(config_key::container, ContainerProps::containerToString(container));
|
|
||||||
}
|
|
||||||
config.insert(ProtocolProps::protoToString(protocol), containerConfig);
|
|
||||||
}
|
|
||||||
installedContainers.insert(container, config);
|
|
||||||
}
|
|
||||||
const static QRegularExpression torOrDnsRegExp("(amnezia-(?:torwebsite|dns)).*?([0-9]*)/(udp|tcp).*");
|
|
||||||
QRegularExpressionMatch torOrDnsRegMatch = torOrDnsRegExp.match(containerInfo);
|
|
||||||
if (torOrDnsRegMatch.hasMatch()) {
|
|
||||||
QString name = torOrDnsRegMatch.captured(1);
|
|
||||||
QString port = torOrDnsRegMatch.captured(2);
|
|
||||||
QString transportProto = torOrDnsRegMatch.captured(3);
|
|
||||||
DockerContainer container = ContainerProps::containerFromString(name);
|
|
||||||
|
|
||||||
QJsonObject config;
|
|
||||||
Proto mainProto = ContainerProps::defaultProtocol(container);
|
|
||||||
for (auto protocol : ContainerProps::protocolsForContainer(container)) {
|
|
||||||
QJsonObject containerConfig;
|
|
||||||
if (protocol == mainProto) {
|
|
||||||
containerConfig.insert(config_key::port, port);
|
|
||||||
containerConfig.insert(config_key::transport_proto, transportProto);
|
|
||||||
|
|
||||||
if (protocol == Proto::TorWebSite) {
|
|
||||||
stdOut.clear();
|
|
||||||
script = QString("sudo docker exec -i %1 sh -c 'cat /var/lib/tor/hidden_service/hostname'").arg(name);
|
|
||||||
|
|
||||||
ErrorCode errorCode = runScript(credentials, script, cbReadStdOut, cbReadStdErr);
|
|
||||||
if (errorCode != ErrorCode::NoError) {
|
|
||||||
return errorCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stdOut.isEmpty()) {
|
|
||||||
logger.error() << "Key parameters for the tor container are missing";
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString onion = stdOut;
|
|
||||||
onion.replace("\n", "");
|
|
||||||
containerConfig.insert(config_key::site, onion);
|
|
||||||
}
|
|
||||||
|
|
||||||
config.insert(config_key::container, ContainerProps::containerToString(container));
|
|
||||||
}
|
|
||||||
config.insert(ProtocolProps::protoToString(protocol), containerConfig);
|
|
||||||
}
|
|
||||||
installedContainers.insert(container, config);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ErrorCode::NoError;
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorCode ServerController::getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey,
|
ErrorCode ServerController::getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey,
|
||||||
const std::function<QString()> &callback)
|
const std::function<QString()> &callback)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -25,22 +25,18 @@ public:
|
|||||||
ErrorCode rebootServer(const ServerCredentials &credentials);
|
ErrorCode rebootServer(const ServerCredentials &credentials);
|
||||||
ErrorCode removeAllContainers(const ServerCredentials &credentials);
|
ErrorCode removeAllContainers(const ServerCredentials &credentials);
|
||||||
ErrorCode removeContainer(const ServerCredentials &credentials, DockerContainer container);
|
ErrorCode removeContainer(const ServerCredentials &credentials, DockerContainer container);
|
||||||
ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config,
|
ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config, bool isUpdate = false);
|
||||||
bool isUpdate = false);
|
ErrorCode updateContainer(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &oldConfig,
|
||||||
ErrorCode updateContainer(const ServerCredentials &credentials, DockerContainer container,
|
QJsonObject &newConfig);
|
||||||
const QJsonObject &oldConfig, QJsonObject &newConfig);
|
|
||||||
|
|
||||||
ErrorCode getAlreadyInstalledContainers(const ServerCredentials &credentials,
|
|
||||||
QMap<DockerContainer, QJsonObject> &installedContainers);
|
|
||||||
|
|
||||||
ErrorCode startupContainerWorker(const ServerCredentials &credentials, DockerContainer container,
|
ErrorCode startupContainerWorker(const ServerCredentials &credentials, DockerContainer container,
|
||||||
const QJsonObject &config = QJsonObject());
|
const QJsonObject &config = QJsonObject());
|
||||||
|
|
||||||
ErrorCode uploadTextFileToContainer(
|
ErrorCode uploadTextFileToContainer(DockerContainer container, const ServerCredentials &credentials, const QString &file,
|
||||||
DockerContainer container, const ServerCredentials &credentials, const QString &file, const QString &path,
|
const QString &path,
|
||||||
libssh::SftpOverwriteMode overwriteMode = libssh::SftpOverwriteMode::SftpOverwriteExisting);
|
libssh::ScpOverwriteMode overwriteMode = libssh::ScpOverwriteMode::ScpOverwriteExisting);
|
||||||
QByteArray getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials,
|
QByteArray getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials, const QString &path,
|
||||||
const QString &path, ErrorCode *errorCode = nullptr);
|
ErrorCode errorCode);
|
||||||
|
|
||||||
QString replaceVars(const QString &script, const Vars &vars);
|
QString replaceVars(const QString &script, const Vars &vars);
|
||||||
Vars genVarsForScript(const ServerCredentials &credentials, DockerContainer container = DockerContainer::None,
|
Vars genVarsForScript(const ServerCredentials &credentials, DockerContainer container = DockerContainer::None,
|
||||||
@@ -50,12 +46,11 @@ public:
|
|||||||
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdOut = nullptr,
|
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdOut = nullptr,
|
||||||
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdErr = nullptr);
|
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdErr = nullptr);
|
||||||
|
|
||||||
ErrorCode
|
ErrorCode runContainerScript(const ServerCredentials &credentials, DockerContainer container, QString script,
|
||||||
runContainerScript(const ServerCredentials &credentials, DockerContainer container, QString script,
|
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdOut = nullptr,
|
||||||
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdOut = nullptr,
|
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdErr = nullptr);
|
||||||
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdErr = nullptr);
|
|
||||||
|
|
||||||
QString checkSshConnection(const ServerCredentials &credentials, ErrorCode *errorCode = nullptr);
|
QString checkSshConnection(const ServerCredentials &credentials, ErrorCode errorCode);
|
||||||
|
|
||||||
void cancelInstallation();
|
void cancelInstallation();
|
||||||
|
|
||||||
@@ -64,23 +59,19 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
ErrorCode installDockerWorker(const ServerCredentials &credentials, DockerContainer container);
|
ErrorCode installDockerWorker(const ServerCredentials &credentials, DockerContainer container);
|
||||||
ErrorCode prepareHostWorker(const ServerCredentials &credentials, DockerContainer container,
|
ErrorCode prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject());
|
||||||
const QJsonObject &config = QJsonObject());
|
|
||||||
ErrorCode buildContainerWorker(const ServerCredentials &credentials, DockerContainer container,
|
ErrorCode buildContainerWorker(const ServerCredentials &credentials, DockerContainer container,
|
||||||
const QJsonObject &config = QJsonObject());
|
const QJsonObject &config = QJsonObject());
|
||||||
ErrorCode runContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config);
|
ErrorCode runContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config);
|
||||||
ErrorCode configureContainerWorker(const ServerCredentials &credentials, DockerContainer container,
|
ErrorCode configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config);
|
||||||
QJsonObject &config);
|
|
||||||
|
|
||||||
ErrorCode isServerPortBusy(const ServerCredentials &credentials, DockerContainer container,
|
ErrorCode isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config);
|
||||||
const QJsonObject &config);
|
bool isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig);
|
||||||
bool isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig,
|
|
||||||
const QJsonObject &newConfig);
|
|
||||||
ErrorCode isUserInSudo(const ServerCredentials &credentials, DockerContainer container);
|
ErrorCode isUserInSudo(const ServerCredentials &credentials, DockerContainer container);
|
||||||
ErrorCode isServerDpkgBusy(const ServerCredentials &credentials, DockerContainer container);
|
ErrorCode isServerDpkgBusy(const ServerCredentials &credentials, DockerContainer container);
|
||||||
|
|
||||||
ErrorCode uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath,
|
ErrorCode uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath,
|
||||||
libssh::SftpOverwriteMode overwriteMode = libssh::SftpOverwriteMode::SftpOverwriteExisting);
|
libssh::ScpOverwriteMode overwriteMode = libssh::ScpOverwriteMode::ScpOverwriteExisting);
|
||||||
|
|
||||||
ErrorCode setupServerFirewall(const ServerCredentials &credentials);
|
ErrorCode setupServerFirewall(const ServerCredentials &credentials);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,138 @@
|
|||||||
|
#include "vpnConfigurationController.h"
|
||||||
|
|
||||||
|
#include "configurators/awg_configurator.h"
|
||||||
|
#include "configurators/cloak_configurator.h"
|
||||||
|
#include "configurators/ikev2_configurator.h"
|
||||||
|
#include "configurators/openvpn_configurator.h"
|
||||||
|
#include "configurators/shadowsocks_configurator.h"
|
||||||
|
#include "configurators/wireguard_configurator.h"
|
||||||
|
#include "configurators/xray_configurator.h"
|
||||||
|
|
||||||
|
VpnConfigurationsController::VpnConfigurationsController(const std::shared_ptr<Settings> &settings,
|
||||||
|
QSharedPointer<ServerController> serverController, QObject *parent)
|
||||||
|
: QObject { parent }, m_settings(settings), m_serverController(serverController)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QScopedPointer<ConfiguratorBase> VpnConfigurationsController::createConfigurator(const Proto protocol)
|
||||||
|
{
|
||||||
|
switch (protocol) {
|
||||||
|
case Proto::OpenVpn: return QScopedPointer<ConfiguratorBase>(new OpenVpnConfigurator(m_settings, m_serverController));
|
||||||
|
case Proto::ShadowSocks: return QScopedPointer<ConfiguratorBase>(new ShadowSocksConfigurator(m_settings, m_serverController));
|
||||||
|
case Proto::Cloak: return QScopedPointer<ConfiguratorBase>(new CloakConfigurator(m_settings, m_serverController));
|
||||||
|
case Proto::WireGuard: return QScopedPointer<ConfiguratorBase>(new WireguardConfigurator(m_settings, m_serverController, false));
|
||||||
|
case Proto::Awg: return QScopedPointer<ConfiguratorBase>(new AwgConfigurator(m_settings, m_serverController));
|
||||||
|
case Proto::Ikev2: return QScopedPointer<ConfiguratorBase>(new Ikev2Configurator(m_settings, m_serverController));
|
||||||
|
case Proto::Xray: return QScopedPointer<ConfiguratorBase>(new XrayConfigurator(m_settings, m_serverController));
|
||||||
|
default: return QScopedPointer<ConfiguratorBase>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorCode VpnConfigurationsController::createProtocolConfigForContainer(const ServerCredentials &credentials,
|
||||||
|
const DockerContainer container, QJsonObject &containerConfig)
|
||||||
|
{
|
||||||
|
ErrorCode errorCode = ErrorCode::NoError;
|
||||||
|
|
||||||
|
if (ContainerProps::containerService(container) == ServiceType::Other) {
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Proto protocol : ContainerProps::protocolsForContainer(container)) {
|
||||||
|
QJsonObject protocolConfig = containerConfig.value(ProtocolProps::protoToString(protocol)).toObject();
|
||||||
|
|
||||||
|
auto configurator = createConfigurator(protocol);
|
||||||
|
QString protocolConfigString = configurator->createConfig(credentials, container, containerConfig, errorCode);
|
||||||
|
if (errorCode != ErrorCode::NoError) {
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
protocolConfig.insert(config_key::last_config, protocolConfigString);
|
||||||
|
containerConfig.insert(ProtocolProps::protoToString(protocol), protocolConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorCode VpnConfigurationsController::createProtocolConfigString(const bool isApiConfig, const QPair<QString, QString> &dns,
|
||||||
|
const ServerCredentials &credentials, const DockerContainer container,
|
||||||
|
const QJsonObject &containerConfig, const Proto protocol,
|
||||||
|
QString &protocolConfigString)
|
||||||
|
{
|
||||||
|
ErrorCode errorCode = ErrorCode::NoError;
|
||||||
|
|
||||||
|
if (ContainerProps::containerService(container) == ServiceType::Other) {
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto configurator = createConfigurator(protocol);
|
||||||
|
|
||||||
|
protocolConfigString = configurator->createConfig(credentials, container, containerConfig, errorCode);
|
||||||
|
if (errorCode != ErrorCode::NoError) {
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
protocolConfigString = configurator->processConfigWithExportSettings(dns, isApiConfig, protocolConfigString);
|
||||||
|
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject VpnConfigurationsController::createVpnConfiguration(const QPair<QString, QString> &dns, const QJsonObject &serverConfig,
|
||||||
|
const QJsonObject &containerConfig, const DockerContainer container,
|
||||||
|
ErrorCode errorCode)
|
||||||
|
{
|
||||||
|
QJsonObject vpnConfiguration {};
|
||||||
|
|
||||||
|
if (ContainerProps::containerService(container) == ServiceType::Other) {
|
||||||
|
return vpnConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isApiConfig = serverConfig.value(config_key::configVersion).toInt();
|
||||||
|
|
||||||
|
for (ProtocolEnumNS::Proto proto : ContainerProps::protocolsForContainer(container)) {
|
||||||
|
if (isApiConfig && container == DockerContainer::Cloak && proto == ProtocolEnumNS::Proto::ShadowSocks) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString protocolConfigString =
|
||||||
|
containerConfig.value(ProtocolProps::protoToString(proto)).toObject().value(config_key::last_config).toString();
|
||||||
|
|
||||||
|
auto configurator = createConfigurator(proto);
|
||||||
|
protocolConfigString = configurator->processConfigWithLocalSettings(dns, isApiConfig, protocolConfigString);
|
||||||
|
|
||||||
|
QJsonObject vpnConfigData = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
|
||||||
|
vpnConfigData = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
|
||||||
|
vpnConfiguration.insert(ProtocolProps::key_proto_config_data(proto), vpnConfigData);
|
||||||
|
}
|
||||||
|
|
||||||
|
Proto proto = ContainerProps::defaultProtocol(container);
|
||||||
|
vpnConfiguration[config_key::vpnproto] = ProtocolProps::protoToString(proto);
|
||||||
|
|
||||||
|
vpnConfiguration[config_key::dns1] = dns.first;
|
||||||
|
vpnConfiguration[config_key::dns2] = dns.second;
|
||||||
|
|
||||||
|
vpnConfiguration[config_key::hostName] = serverConfig.value(config_key::hostName).toString();
|
||||||
|
vpnConfiguration[config_key::description] = serverConfig.value(config_key::description).toString();
|
||||||
|
|
||||||
|
vpnConfiguration[config_key::configVersion] = serverConfig.value(config_key::configVersion).toInt();
|
||||||
|
// TODO: try to get hostName, port, description for 3rd party configs
|
||||||
|
// vpnConfiguration[config_key::port] = ...;
|
||||||
|
|
||||||
|
return vpnConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VpnConfigurationsController::updateContainerConfigAfterInstallation(const DockerContainer container, QJsonObject &containerConfig,
|
||||||
|
const QString &stdOut)
|
||||||
|
{
|
||||||
|
Proto mainProto = ContainerProps::defaultProtocol(container);
|
||||||
|
|
||||||
|
if (container == DockerContainer::TorWebSite) {
|
||||||
|
QJsonObject protocol = containerConfig.value(ProtocolProps::protoToString(mainProto)).toObject();
|
||||||
|
|
||||||
|
qDebug() << "amnezia-tor onions" << stdOut;
|
||||||
|
|
||||||
|
QString onion = stdOut;
|
||||||
|
onion.replace("\n", "");
|
||||||
|
protocol.insert(config_key::site, onion);
|
||||||
|
|
||||||
|
containerConfig.insert(ProtocolProps::protoToString(mainProto), protocol);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#ifndef VPNCONFIGIRATIONSCONTROLLER_H
|
||||||
|
#define VPNCONFIGIRATIONSCONTROLLER_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
#include "configurators/configurator_base.h"
|
||||||
|
#include "containers/containers_defs.h"
|
||||||
|
#include "core/defs.h"
|
||||||
|
#include "settings.h"
|
||||||
|
|
||||||
|
class VpnConfigurationsController : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit VpnConfigurationsController(const std::shared_ptr<Settings> &settings, QSharedPointer<ServerController> serverController, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
ErrorCode createProtocolConfigForContainer(const ServerCredentials &credentials, const DockerContainer container,
|
||||||
|
QJsonObject &containerConfig);
|
||||||
|
ErrorCode createProtocolConfigString(const bool isApiConfig, const QPair<QString, QString> &dns, const ServerCredentials &credentials,
|
||||||
|
const DockerContainer container, const QJsonObject &containerConfig, const Proto protocol,
|
||||||
|
QString &protocolConfigString);
|
||||||
|
QJsonObject createVpnConfiguration(const QPair<QString, QString> &dns, const QJsonObject &serverConfig,
|
||||||
|
const QJsonObject &containerConfig, const DockerContainer container, ErrorCode errorCode);
|
||||||
|
|
||||||
|
static void updateContainerConfigAfterInstallation(const DockerContainer container, QJsonObject &containerConfig, const QString &stdOut);
|
||||||
|
signals:
|
||||||
|
|
||||||
|
private:
|
||||||
|
QScopedPointer<ConfiguratorBase> createConfigurator(const Proto protocol);
|
||||||
|
|
||||||
|
std::shared_ptr<Settings> m_settings;
|
||||||
|
QSharedPointer<ServerController> m_serverController;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // VPNCONFIGIRATIONSCONTROLLER_H
|
||||||
+29
-16
@@ -22,6 +22,20 @@ namespace amnezia
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct InstalledAppInfo {
|
||||||
|
QString appName;
|
||||||
|
QString packageName;
|
||||||
|
QString appPath;
|
||||||
|
|
||||||
|
bool operator==(const InstalledAppInfo& other) const {
|
||||||
|
if (!packageName.isEmpty()) {
|
||||||
|
return packageName == other.packageName;
|
||||||
|
} else {
|
||||||
|
return appPath == other.appPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
enum ErrorCode {
|
enum ErrorCode {
|
||||||
// General error codes
|
// General error codes
|
||||||
NoError = 0,
|
NoError = 0,
|
||||||
@@ -46,25 +60,12 @@ namespace amnezia
|
|||||||
SshPrivateKeyFormatError = 304,
|
SshPrivateKeyFormatError = 304,
|
||||||
SshTimeoutError = 305,
|
SshTimeoutError = 305,
|
||||||
|
|
||||||
// Ssh sftp errors
|
// Ssh scp errors
|
||||||
SshSftpEofError = 400,
|
SshScpFailureError = 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,
|
|
||||||
|
|
||||||
// Local errors
|
// Local errors
|
||||||
OpenVpnConfigMissing = 500,
|
OpenVpnConfigMissing = 500,
|
||||||
OpenVpnManagementServerError = 501,
|
OpenVpnManagementServerError = 501,
|
||||||
ConfigMissing = 502,
|
|
||||||
|
|
||||||
// Distro errors
|
// Distro errors
|
||||||
OpenVpnExecutableMissing = 600,
|
OpenVpnExecutableMissing = 600,
|
||||||
@@ -72,6 +73,8 @@ namespace amnezia
|
|||||||
CloakExecutableMissing = 602,
|
CloakExecutableMissing = 602,
|
||||||
AmneziaServiceConnectionFailed = 603,
|
AmneziaServiceConnectionFailed = 603,
|
||||||
ExecutableMissing = 604,
|
ExecutableMissing = 604,
|
||||||
|
XrayExecutableMissing = 605,
|
||||||
|
Tun2SockExecutableMissing = 606,
|
||||||
|
|
||||||
// VPN errors
|
// VPN errors
|
||||||
OpenVpnAdaptersInUseError = 700,
|
OpenVpnAdaptersInUseError = 700,
|
||||||
@@ -83,6 +86,8 @@ namespace amnezia
|
|||||||
OpenSslFailed = 800,
|
OpenSslFailed = 800,
|
||||||
ShadowSocksExecutableCrashed = 801,
|
ShadowSocksExecutableCrashed = 801,
|
||||||
CloakExecutableCrashed = 802,
|
CloakExecutableCrashed = 802,
|
||||||
|
XrayExecutableCrashed = 803,
|
||||||
|
Tun2SockExecutableCrashed = 804,
|
||||||
|
|
||||||
// import and install errors
|
// import and install errors
|
||||||
ImportInvalidConfigError = 900,
|
ImportInvalidConfigError = 900,
|
||||||
@@ -92,7 +97,15 @@ namespace amnezia
|
|||||||
|
|
||||||
// Api errors
|
// Api errors
|
||||||
ApiConfigDownloadError = 1100,
|
ApiConfigDownloadError = 1100,
|
||||||
ApiConfigAlreadyAdded = 1101
|
ApiConfigAlreadyAdded = 1101,
|
||||||
|
|
||||||
|
// QFile errors
|
||||||
|
OpenError = 1200,
|
||||||
|
ReadError = 1201,
|
||||||
|
PermissionsError = 1202,
|
||||||
|
UnspecifiedError = 1203,
|
||||||
|
FatalError = 1204,
|
||||||
|
AbortError = 1205
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace amnezia
|
} // namespace amnezia
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ QString errorString(ErrorCode code) {
|
|||||||
case(ServerDockerFailedError): errorMessage = QObject::tr("Server error: Docker failed"); break;
|
case(ServerDockerFailedError): errorMessage = QObject::tr("Server error: Docker failed"); break;
|
||||||
case(ServerCancelInstallation): errorMessage = QObject::tr("Installation canceled by user"); 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;
|
case(ServerUserNotInSudo): errorMessage = QObject::tr("The user does not have permission to use sudo"); break;
|
||||||
|
case(ServerPacketManagerError): errorMessage = QObject::tr("Server error: Packet manager error"); break;
|
||||||
|
|
||||||
// Libssh errors
|
// Libssh errors
|
||||||
case(SshRequestDeniedError): errorMessage = QObject::tr("Ssh request was denied"); break;
|
case(SshRequestDeniedError): errorMessage = QObject::tr("Ssh request was denied"); break;
|
||||||
@@ -28,20 +29,8 @@ QString errorString(ErrorCode code) {
|
|||||||
case(SshPrivateKeyFormatError): errorMessage = QObject::tr("The selected private key format is not supported, use openssh ED25519 key types or PEM key types"); 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;
|
case(SshTimeoutError): errorMessage = QObject::tr("Timeout connecting to server"); break;
|
||||||
|
|
||||||
// Libssh sftp errors
|
// Ssh scp errors
|
||||||
case(SshSftpEofError): errorMessage = QObject::tr("Sftp error: End-of-file encountered"); break;
|
case(SshScpFailureError): errorMessage = QObject::tr("Scp error: Generic failure"); 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
|
// Local errors
|
||||||
case (OpenVpnConfigMissing): errorMessage = QObject::tr("OpenVPN config missing"); break;
|
case (OpenVpnConfigMissing): errorMessage = QObject::tr("OpenVPN config missing"); break;
|
||||||
@@ -68,6 +57,14 @@ QString errorString(ErrorCode code) {
|
|||||||
case (ApiConfigDownloadError): errorMessage = QObject::tr("Error when retrieving configuration from API"); break;
|
case (ApiConfigDownloadError): errorMessage = QObject::tr("Error when retrieving configuration from API"); break;
|
||||||
case (ApiConfigAlreadyAdded): errorMessage = QObject::tr("This config has already been added to the application"); break;
|
case (ApiConfigAlreadyAdded): errorMessage = QObject::tr("This config has already been added to the application"); break;
|
||||||
|
|
||||||
|
// QFile errors
|
||||||
|
case(OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;
|
||||||
|
case(ReadError): errorMessage = QObject::tr("QFile error: An error occurred when reading from the file"); break;
|
||||||
|
case(PermissionsError): errorMessage = QObject::tr("QFile error: The file could not be accessed"); break;
|
||||||
|
case(UnspecifiedError): errorMessage = QObject::tr("QFile error: An unspecified error occurred"); break;
|
||||||
|
case(FatalError): errorMessage = QObject::tr("QFile error: A fatal error occurred"); break;
|
||||||
|
case(AbortError): errorMessage = QObject::tr("QFile error: The operation was aborted"); break;
|
||||||
|
|
||||||
case(InternalError):
|
case(InternalError):
|
||||||
default:
|
default:
|
||||||
errorMessage = QObject::tr("Internal error"); break;
|
errorMessage = QObject::tr("Internal error"); break;
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
#include "installedAppsImageProvider.h"
|
||||||
|
|
||||||
|
#include "platforms/android/android_controller.h"
|
||||||
|
|
||||||
|
InstalledAppsImageProvider::InstalledAppsImageProvider() : QQuickImageProvider(QQuickImageProvider::Pixmap)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QPixmap InstalledAppsImageProvider::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize)
|
||||||
|
{
|
||||||
|
return AndroidController::instance()->getAppIcon(id, size, requestedSize);
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
#ifndef INSTALLEDAPPSIMAGEPROVIDER_H
|
||||||
|
#define INSTALLEDAPPSIMAGEPROVIDER_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QQuickImageProvider>
|
||||||
|
|
||||||
|
class InstalledAppsImageProvider : public QQuickImageProvider
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
InstalledAppsImageProvider();
|
||||||
|
|
||||||
|
QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // INSTALLEDAPPSIMAGEPROVIDER_H
|
||||||
@@ -71,7 +71,7 @@ QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
|
|||||||
}
|
}
|
||||||
|
|
||||||
QRemoteObjectPendingReply<int> futureResult = Instance()->m_ipcClient->createPrivilegedProcess();
|
QRemoteObjectPendingReply<int> futureResult = Instance()->m_ipcClient->createPrivilegedProcess();
|
||||||
futureResult.waitForFinished(1000);
|
futureResult.waitForFinished(5000);
|
||||||
|
|
||||||
int pid = futureResult.returnValue();
|
int pid = futureResult.returnValue();
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,462 @@
|
|||||||
|
#include "networkUtilities.h"
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
#include <windows.h>
|
||||||
|
#include <Ipexport.h>
|
||||||
|
#include <Ws2tcpip.h>
|
||||||
|
#include <ws2ipdef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <Iphlpapi.h>
|
||||||
|
#include <Iptypes.h>
|
||||||
|
#include <WinSock2.h>
|
||||||
|
#include <winsock.h>
|
||||||
|
#include <QNetworkInterface>
|
||||||
|
#include "qendian.h"
|
||||||
|
#endif
|
||||||
|
#ifdef Q_OS_LINUX
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <linux/netlink.h>
|
||||||
|
#include <linux/rtnetlink.h>
|
||||||
|
#include <net/if.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#endif
|
||||||
|
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS)
|
||||||
|
#include <sys/param.h>
|
||||||
|
#include <sys/sysctl.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <net/route.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <QHostAddress>
|
||||||
|
#include <QHostInfo>
|
||||||
|
|
||||||
|
QRegularExpression NetworkUtilities::ipAddressRegExp()
|
||||||
|
{
|
||||||
|
return QRegularExpression("^((25[0-5]|(2[0-4]|1[0-9]|[1-9]|)[0-9])(\\.(?!$)|$)){4}$");
|
||||||
|
}
|
||||||
|
|
||||||
|
QRegularExpression NetworkUtilities::ipAddressPortRegExp()
|
||||||
|
{
|
||||||
|
return QRegularExpression("^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}"
|
||||||
|
"(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(\\:[0-9]{1,5}){0,1}$");
|
||||||
|
}
|
||||||
|
|
||||||
|
QRegExp NetworkUtilities::ipAddressWithSubnetRegExp()
|
||||||
|
{
|
||||||
|
return QRegExp("(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}"
|
||||||
|
"(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(\\/[0-9]{1,2}){0,1}");
|
||||||
|
}
|
||||||
|
|
||||||
|
QRegExp NetworkUtilities::ipNetwork24RegExp()
|
||||||
|
{
|
||||||
|
return QRegExp("^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}"
|
||||||
|
"0$");
|
||||||
|
}
|
||||||
|
|
||||||
|
QRegExp NetworkUtilities::ipPortRegExp()
|
||||||
|
{
|
||||||
|
return QRegExp("^()([1-9]|[1-5]?[0-9]{2,4}|6[1-4][0-9]{3}|65[1-4][0-9]{2}|655[1-2][0-9]|6553[1-5])$");
|
||||||
|
}
|
||||||
|
|
||||||
|
QRegExp NetworkUtilities::domainRegExp()
|
||||||
|
{
|
||||||
|
return QRegExp("(((?!\\-))(xn\\-\\-)?[a-z0-9\\-_]{0,61}[a-z0-9]{1,1}\\.)*(xn\\-\\-)?([a-z0-9\\-]{1,61}|[a-z0-"
|
||||||
|
"9\\-]{1,30})\\.[a-z]{2,}");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString NetworkUtilities::netMaskFromIpWithSubnet(const QString ip)
|
||||||
|
{
|
||||||
|
if (!ip.contains("/"))
|
||||||
|
return "255.255.255.255";
|
||||||
|
|
||||||
|
bool ok;
|
||||||
|
int prefix = ip.split("/").at(1).toInt(&ok);
|
||||||
|
if (!ok)
|
||||||
|
return "255.255.255.255";
|
||||||
|
|
||||||
|
unsigned long mask = (0xFFFFFFFF << (32 - prefix)) & 0xFFFFFFFF;
|
||||||
|
|
||||||
|
return QString("%1.%2.%3.%4").arg(mask >> 24).arg((mask >> 16) & 0xFF).arg((mask >> 8) & 0xFF).arg(mask & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString NetworkUtilities::ipAddressFromIpWithSubnet(const QString ip)
|
||||||
|
{
|
||||||
|
if (ip.count(".") != 3)
|
||||||
|
return "";
|
||||||
|
return ip.split("/").first();
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList NetworkUtilities::summarizeRoutes(const QStringList &ips, const QString cidr)
|
||||||
|
{
|
||||||
|
// QMap<int, int>
|
||||||
|
// QHostAddress
|
||||||
|
|
||||||
|
// QMap<QString, QStringList> subnets; // <"a.b", <list subnets>>
|
||||||
|
|
||||||
|
// for (const QString &ip : ips) {
|
||||||
|
// if (ip.count(".") != 3) continue;
|
||||||
|
|
||||||
|
// const QStringList &parts = ip.split(".");
|
||||||
|
// subnets[parts.at(0) + "." + parts.at(1)].append(ip);
|
||||||
|
// }
|
||||||
|
|
||||||
|
return QStringList();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString NetworkUtilities::getIPAddress(const QString &host)
|
||||||
|
{
|
||||||
|
if (ipAddressRegExp().match(host).hasMatch()) {
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QHostAddress> addresses = QHostInfo::fromName(host).addresses();
|
||||||
|
if (!addresses.isEmpty()) {
|
||||||
|
return addresses.first().toString();
|
||||||
|
}
|
||||||
|
qDebug() << "Unable to resolve address for " << host;
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
QString NetworkUtilities::getStringBetween(const QString &s, const QString &a, const QString &b)
|
||||||
|
{
|
||||||
|
int ap = s.indexOf(a), bp = s.indexOf(b, ap + a.length());
|
||||||
|
if (ap < 0 || bp < 0)
|
||||||
|
return QString();
|
||||||
|
ap += a.length();
|
||||||
|
if (bp - ap <= 0)
|
||||||
|
return QString();
|
||||||
|
return s.mid(ap, bp - ap).trimmed();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NetworkUtilities::checkIPv4Format(const QString &ip)
|
||||||
|
{
|
||||||
|
if (ip.isEmpty())
|
||||||
|
return false;
|
||||||
|
int count = ip.count(".");
|
||||||
|
if (count != 3)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
QHostAddress addr(ip);
|
||||||
|
return (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NetworkUtilities::checkIpSubnetFormat(const QString &ip)
|
||||||
|
{
|
||||||
|
if (!ip.contains("/"))
|
||||||
|
return checkIPv4Format(ip);
|
||||||
|
|
||||||
|
QStringList parts = ip.split("/");
|
||||||
|
if (parts.size() != 2)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
bool ok;
|
||||||
|
int subnet = parts.at(1).toInt(&ok);
|
||||||
|
if (subnet >= 0 && subnet <= 32 && ok)
|
||||||
|
return checkIPv4Format(parts.at(0));
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
int NetworkUtilities::AdapterIndexTo(const QHostAddress& dst) {
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
qDebug() << "Getting Current Internet Adapter that routes to"
|
||||||
|
<< dst.toString();
|
||||||
|
quint32_be ipBigEndian;
|
||||||
|
quint32 ip = dst.toIPv4Address();
|
||||||
|
qToBigEndian(ip, &ipBigEndian);
|
||||||
|
_MIB_IPFORWARDROW routeInfo;
|
||||||
|
auto result = GetBestRoute(ipBigEndian, 0, &routeInfo);
|
||||||
|
if (result != NO_ERROR) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
auto adapter =
|
||||||
|
QNetworkInterface::interfaceFromIndex(routeInfo.dwForwardIfIndex);
|
||||||
|
qDebug() << "Internet Adapter:" << adapter.name();
|
||||||
|
return routeInfo.dwForwardIfIndex;
|
||||||
|
#endif
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
DWORD GetAdaptersAddressesWrapper(const ULONG Family,
|
||||||
|
const ULONG Flags,
|
||||||
|
const PVOID Reserved,
|
||||||
|
_Out_ PIP_ADAPTER_ADDRESSES& pAdapterAddresses) {
|
||||||
|
DWORD dwRetVal = 0;
|
||||||
|
int iter = 0;
|
||||||
|
constexpr int max_iter = 3;
|
||||||
|
ULONG AdapterAddressesLen = 15000;
|
||||||
|
do {
|
||||||
|
// xassert2(pAdapterAddresses == nullptr);
|
||||||
|
pAdapterAddresses = (IP_ADAPTER_ADDRESSES*)malloc(AdapterAddressesLen);
|
||||||
|
if (pAdapterAddresses == nullptr) {
|
||||||
|
qDebug() << "can not malloc" << AdapterAddressesLen << "bytes";
|
||||||
|
return ERROR_OUTOFMEMORY;
|
||||||
|
}
|
||||||
|
|
||||||
|
dwRetVal = GetAdaptersAddresses(Family, Flags, NULL, pAdapterAddresses, &AdapterAddressesLen);
|
||||||
|
|
||||||
|
if (dwRetVal == ERROR_BUFFER_OVERFLOW) {
|
||||||
|
free(pAdapterAddresses);
|
||||||
|
pAdapterAddresses = nullptr;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
iter++;
|
||||||
|
} while ((dwRetVal == ERROR_BUFFER_OVERFLOW) && (iter < max_iter));
|
||||||
|
|
||||||
|
if (dwRetVal != NO_ERROR) {
|
||||||
|
qDebug() << "Family: " << Family << ", Flags: " << Flags << " AdapterAddressesLen: " << AdapterAddressesLen <<
|
||||||
|
", dwRetVal:" << dwRetVal << ", iter: " << iter;
|
||||||
|
if (pAdapterAddresses) {
|
||||||
|
free(pAdapterAddresses);
|
||||||
|
pAdapterAddresses = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dwRetVal;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QString NetworkUtilities::getGatewayAndIface()
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
constexpr int BUFF_LEN = 100;
|
||||||
|
char buff[BUFF_LEN] = {'\0'};
|
||||||
|
QString result;
|
||||||
|
|
||||||
|
PIP_ADAPTER_ADDRESSES pAdapterAddresses = nullptr;
|
||||||
|
DWORD dwRetVal =
|
||||||
|
GetAdaptersAddressesWrapper(AF_INET, GAA_FLAG_INCLUDE_GATEWAYS, NULL, pAdapterAddresses);
|
||||||
|
|
||||||
|
if (dwRetVal != NO_ERROR) {
|
||||||
|
qDebug() << "ipv4 stack detect GetAdaptersAddresses failed.";
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
PIP_ADAPTER_ADDRESSES pCurAddress = pAdapterAddresses;
|
||||||
|
while (pCurAddress) {
|
||||||
|
PIP_ADAPTER_GATEWAY_ADDRESS_LH gateway = pCurAddress->FirstGatewayAddress;
|
||||||
|
if (gateway) {
|
||||||
|
SOCKET_ADDRESS gateway_address = gateway->Address;
|
||||||
|
if (gateway->Address.lpSockaddr->sa_family == AF_INET) {
|
||||||
|
sockaddr_in* sa_in = (sockaddr_in*)gateway->Address.lpSockaddr;
|
||||||
|
QString gw = inet_ntop(AF_INET, &(sa_in->sin_addr), buff, BUFF_LEN);
|
||||||
|
qDebug() << "gateway IPV4:" << gw;
|
||||||
|
struct sockaddr_in addr;
|
||||||
|
if (inet_pton(AF_INET, buff, &addr.sin_addr) == 1) {
|
||||||
|
qDebug() << "this is true v4 !";
|
||||||
|
result = gw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pCurAddress = pCurAddress->Next;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(pAdapterAddresses);
|
||||||
|
return result;
|
||||||
|
#endif
|
||||||
|
#ifdef Q_OS_LINUX
|
||||||
|
constexpr int BUFFER_SIZE = 100;
|
||||||
|
int received_bytes = 0, msg_len = 0, route_attribute_len = 0;
|
||||||
|
int sock = -1, msgseq = 0;
|
||||||
|
struct nlmsghdr *nlh, *nlmsg;
|
||||||
|
struct rtmsg *route_entry;
|
||||||
|
// This struct contain route attributes (route type)
|
||||||
|
struct rtattr *route_attribute;
|
||||||
|
char gateway_address[INET_ADDRSTRLEN], interface[IF_NAMESIZE];
|
||||||
|
char msgbuf[BUFFER_SIZE], buffer[BUFFER_SIZE];
|
||||||
|
char *ptr = buffer;
|
||||||
|
struct timeval tv;
|
||||||
|
|
||||||
|
if ((sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) {
|
||||||
|
perror("socket failed");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(msgbuf, 0, sizeof(msgbuf));
|
||||||
|
memset(gateway_address, 0, sizeof(gateway_address));
|
||||||
|
memset(interface, 0, sizeof(interface));
|
||||||
|
memset(buffer, 0, sizeof(buffer));
|
||||||
|
|
||||||
|
/* point the header and the msg structure pointers into the buffer */
|
||||||
|
nlmsg = (struct nlmsghdr *)msgbuf;
|
||||||
|
|
||||||
|
/* Fill in the nlmsg header*/
|
||||||
|
nlmsg->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
|
||||||
|
nlmsg->nlmsg_type = RTM_GETROUTE; // Get the routes from kernel routing table .
|
||||||
|
nlmsg->nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST; // The message is a request for dump.
|
||||||
|
nlmsg->nlmsg_seq = msgseq++; // Sequence of the message packet.
|
||||||
|
nlmsg->nlmsg_pid = getpid(); // PID of process sending the request.
|
||||||
|
|
||||||
|
/* 1 Sec Timeout to avoid stall */
|
||||||
|
tv.tv_sec = 1;
|
||||||
|
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (struct timeval *)&tv, sizeof(struct timeval));
|
||||||
|
/* send msg */
|
||||||
|
if (send(sock, nlmsg, nlmsg->nlmsg_len, 0) < 0) {
|
||||||
|
perror("send failed");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* receive response */
|
||||||
|
do
|
||||||
|
{
|
||||||
|
received_bytes = recv(sock, ptr, sizeof(buffer) - msg_len, 0);
|
||||||
|
if (received_bytes < 0) {
|
||||||
|
perror("Error in recv");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
nlh = (struct nlmsghdr *) ptr;
|
||||||
|
|
||||||
|
/* Check if the header is valid */
|
||||||
|
if((NLMSG_OK(nlmsg, received_bytes) == 0) ||
|
||||||
|
(nlmsg->nlmsg_type == NLMSG_ERROR))
|
||||||
|
{
|
||||||
|
perror("Error in received packet");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If we received all data break */
|
||||||
|
if (nlh->nlmsg_type == NLMSG_DONE)
|
||||||
|
break;
|
||||||
|
else {
|
||||||
|
ptr += received_bytes;
|
||||||
|
msg_len += received_bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Break if its not a multi part message */
|
||||||
|
if ((nlmsg->nlmsg_flags & NLM_F_MULTI) == 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
while ((nlmsg->nlmsg_seq != msgseq) || (nlmsg->nlmsg_pid != getpid()));
|
||||||
|
|
||||||
|
/* parse response */
|
||||||
|
for ( ; NLMSG_OK(nlh, received_bytes); nlh = NLMSG_NEXT(nlh, received_bytes))
|
||||||
|
{
|
||||||
|
/* Get the route data */
|
||||||
|
route_entry = (struct rtmsg *) NLMSG_DATA(nlh);
|
||||||
|
|
||||||
|
/* We are just interested in main routing table */
|
||||||
|
if (route_entry->rtm_table != RT_TABLE_MAIN)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
route_attribute = (struct rtattr *) RTM_RTA(route_entry);
|
||||||
|
route_attribute_len = RTM_PAYLOAD(nlh);
|
||||||
|
|
||||||
|
/* Loop through all attributes */
|
||||||
|
for ( ; RTA_OK(route_attribute, route_attribute_len);
|
||||||
|
route_attribute = RTA_NEXT(route_attribute, route_attribute_len))
|
||||||
|
{
|
||||||
|
switch(route_attribute->rta_type) {
|
||||||
|
case RTA_OIF:
|
||||||
|
if_indextoname(*(int *)RTA_DATA(route_attribute), interface);
|
||||||
|
break;
|
||||||
|
case RTA_GATEWAY:
|
||||||
|
inet_ntop(AF_INET, RTA_DATA(route_attribute),
|
||||||
|
gateway_address, sizeof(gateway_address));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((*gateway_address) && (*interface)) {
|
||||||
|
qDebug() << "Gateway " << gateway_address << " for interface " << interface;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(sock);
|
||||||
|
return gateway_address;
|
||||||
|
#endif
|
||||||
|
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS)
|
||||||
|
QString gateway;
|
||||||
|
int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_FLAGS, RTF_GATEWAY};
|
||||||
|
int afinet_type[] = {AF_INET, AF_INET6};
|
||||||
|
|
||||||
|
for (int ip_type = 0; ip_type <= 1; ip_type++)
|
||||||
|
{
|
||||||
|
mib[3] = afinet_type[ip_type];
|
||||||
|
|
||||||
|
size_t needed = 0;
|
||||||
|
if (sysctl(mib, sizeof(mib) / sizeof(int), nullptr, &needed, nullptr, 0) < 0)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
char* buf;
|
||||||
|
if ((buf = new char[needed]) == 0)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
if (sysctl(mib, sizeof(mib) / sizeof(int), buf, &needed, nullptr, 0) < 0)
|
||||||
|
{
|
||||||
|
qDebug() << "sysctl: net.route.0.0.dump";
|
||||||
|
delete[] buf;
|
||||||
|
return gateway;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct rt_msghdr* rt;
|
||||||
|
for (char* p = buf; p < buf + needed; p += rt->rtm_msglen)
|
||||||
|
{
|
||||||
|
rt = reinterpret_cast<struct rt_msghdr*>(p);
|
||||||
|
struct sockaddr* sa = reinterpret_cast<struct sockaddr*>(rt + 1);
|
||||||
|
struct sockaddr* sa_tab[RTAX_MAX];
|
||||||
|
for (int i = 0; i < RTAX_MAX; i++)
|
||||||
|
{
|
||||||
|
if (rt->rtm_addrs & (1 << i))
|
||||||
|
{
|
||||||
|
sa_tab[i] = sa;
|
||||||
|
sa = reinterpret_cast<struct sockaddr*>(
|
||||||
|
reinterpret_cast<char*>(sa) +
|
||||||
|
((sa->sa_len) > 0 ? (1 + (((sa->sa_len) - 1) | (sizeof(long) - 1))) : sizeof(long)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sa_tab[i] = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (((rt->rtm_addrs & (RTA_DST | RTA_GATEWAY)) == (RTA_DST | RTA_GATEWAY)) &&
|
||||||
|
sa_tab[RTAX_DST]->sa_family == afinet_type[ip_type] &&
|
||||||
|
sa_tab[RTAX_GATEWAY]->sa_family == afinet_type[ip_type])
|
||||||
|
{
|
||||||
|
if (afinet_type[ip_type] == AF_INET)
|
||||||
|
{
|
||||||
|
if ((reinterpret_cast<struct sockaddr_in*>(sa_tab[RTAX_DST]))->sin_addr.s_addr == 0)
|
||||||
|
{
|
||||||
|
char dstStr4[INET_ADDRSTRLEN];
|
||||||
|
char srcStr4[INET_ADDRSTRLEN];
|
||||||
|
memcpy(srcStr4,
|
||||||
|
&(reinterpret_cast<struct sockaddr_in*>(sa_tab[RTAX_GATEWAY]))->sin_addr,
|
||||||
|
sizeof(struct in_addr));
|
||||||
|
if (inet_ntop(AF_INET, srcStr4, dstStr4, INET_ADDRSTRLEN) != nullptr)
|
||||||
|
gateway = dstStr4;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (afinet_type[ip_type] == AF_INET6)
|
||||||
|
{
|
||||||
|
if ((reinterpret_cast<struct sockaddr_in*>(sa_tab[RTAX_DST]))->sin_addr.s_addr == 0)
|
||||||
|
{
|
||||||
|
char dstStr6[INET6_ADDRSTRLEN];
|
||||||
|
char srcStr6[INET6_ADDRSTRLEN];
|
||||||
|
memcpy(srcStr6,
|
||||||
|
&(reinterpret_cast<struct sockaddr_in6*>(sa_tab[RTAX_GATEWAY]))->sin6_addr,
|
||||||
|
sizeof(struct in6_addr));
|
||||||
|
if (inet_ntop(AF_INET6, srcStr6, dstStr6, INET6_ADDRSTRLEN) != nullptr)
|
||||||
|
gateway = dstStr6;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
return gateway;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#ifndef NETWORKUTILITIES_H
|
||||||
|
#define NETWORKUTILITIES_H
|
||||||
|
|
||||||
|
#include <QRegularExpression>
|
||||||
|
#include <QRegExp>
|
||||||
|
#include <QString>
|
||||||
|
#include <QHostAddress>
|
||||||
|
|
||||||
|
|
||||||
|
class NetworkUtilities : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
static QString getIPAddress(const QString &host);
|
||||||
|
static QString getStringBetween(const QString &s, const QString &a, const QString &b);
|
||||||
|
static bool checkIPv4Format(const QString &ip);
|
||||||
|
static bool checkIpSubnetFormat(const QString &ip);
|
||||||
|
static QString getGatewayAndIface();
|
||||||
|
// Returns the Interface Index that could Route to dst
|
||||||
|
static int AdapterIndexTo(const QHostAddress& dst);
|
||||||
|
|
||||||
|
static QRegularExpression ipAddressRegExp();
|
||||||
|
static QRegularExpression ipAddressPortRegExp();
|
||||||
|
static QRegExp ipAddressWithSubnetRegExp();
|
||||||
|
static QRegExp ipNetwork24RegExp();
|
||||||
|
static QRegExp ipPortRegExp();
|
||||||
|
static QRegExp domainRegExp();
|
||||||
|
|
||||||
|
static QString netMaskFromIpWithSubnet(const QString ip);
|
||||||
|
static QString ipAddressFromIpWithSubnet(const QString ip);
|
||||||
|
|
||||||
|
static QStringList summarizeRoutes(const QStringList &ips, const QString cidr);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // NETWORKUTILITIES_H
|
||||||
@@ -13,11 +13,12 @@ QString amnezia::scriptFolder(amnezia::DockerContainer container)
|
|||||||
case DockerContainer::WireGuard: return QLatin1String("wireguard");
|
case DockerContainer::WireGuard: return QLatin1String("wireguard");
|
||||||
case DockerContainer::Awg: return QLatin1String("awg");
|
case DockerContainer::Awg: return QLatin1String("awg");
|
||||||
case DockerContainer::Ipsec: return QLatin1String("ipsec");
|
case DockerContainer::Ipsec: return QLatin1String("ipsec");
|
||||||
|
case DockerContainer::Xray: return QLatin1String("xray");
|
||||||
|
|
||||||
case DockerContainer::TorWebSite: return QLatin1String("website_tor");
|
case DockerContainer::TorWebSite: return QLatin1String("website_tor");
|
||||||
case DockerContainer::Dns: return QLatin1String("dns");
|
case DockerContainer::Dns: return QLatin1String("dns");
|
||||||
case DockerContainer::Sftp: return QLatin1String("sftp");
|
case DockerContainer::Sftp: return QLatin1String("sftp");
|
||||||
default: return "";
|
default: return QString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,6 +48,7 @@ QString amnezia::scriptName(ProtocolScriptType type)
|
|||||||
case ProtocolScriptType::openvpn_template: return QLatin1String("template.ovpn");
|
case ProtocolScriptType::openvpn_template: return QLatin1String("template.ovpn");
|
||||||
case ProtocolScriptType::wireguard_template: return QLatin1String("template.conf");
|
case ProtocolScriptType::wireguard_template: return QLatin1String("template.conf");
|
||||||
case ProtocolScriptType::awg_template: return QLatin1String("template.conf");
|
case ProtocolScriptType::awg_template: return QLatin1String("template.conf");
|
||||||
|
case ProtocolScriptType::xray_template: return QLatin1String("template.json");
|
||||||
default: return QString();
|
default: return QString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ enum ProtocolScriptType {
|
|||||||
container_startup,
|
container_startup,
|
||||||
openvpn_template,
|
openvpn_template,
|
||||||
wireguard_template,
|
wireguard_template,
|
||||||
awg_template
|
awg_template,
|
||||||
|
xray_template
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+65
-94
@@ -10,16 +10,10 @@ const uint32_t S_IRWXU = 0644;
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace libssh {
|
namespace libssh {
|
||||||
const QString libsshTimeoutError = "Timeout connecting to";
|
constexpr auto libsshTimeoutError{"Timeout connecting to"};
|
||||||
|
|
||||||
std::function<QString()> Client::m_passphraseCallback;
|
std::function<QString()> Client::m_passphraseCallback;
|
||||||
|
|
||||||
Client::Client(QObject *parent) : QObject(parent)
|
|
||||||
{ }
|
|
||||||
|
|
||||||
Client::~Client()
|
|
||||||
{ }
|
|
||||||
|
|
||||||
int Client::callback(const char *prompt, char *buf, size_t len, int echo, int verify, void *userdata)
|
int Client::callback(const char *prompt, char *buf, size_t len, int echo, int verify, void *userdata)
|
||||||
{
|
{
|
||||||
auto passphrase = m_passphraseCallback();
|
auto passphrase = m_passphraseCallback();
|
||||||
@@ -29,6 +23,13 @@ namespace libssh {
|
|||||||
|
|
||||||
ErrorCode Client::connectToHost(const ServerCredentials &credentials)
|
ErrorCode Client::connectToHost(const ServerCredentials &credentials)
|
||||||
{
|
{
|
||||||
|
if (m_session != nullptr) {
|
||||||
|
if (!ssh_is_connected(m_session)) {
|
||||||
|
ssh_free(m_session);
|
||||||
|
m_session = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (m_session == nullptr) {
|
if (m_session == nullptr) {
|
||||||
m_session = ssh_new();
|
m_session = ssh_new();
|
||||||
|
|
||||||
@@ -171,13 +172,13 @@ namespace libssh {
|
|||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
};
|
};
|
||||||
|
|
||||||
auto error = readOutput(false);
|
auto errorCode = readOutput(false);
|
||||||
if (error != ErrorCode::NoError) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
return error;
|
return errorCode;
|
||||||
}
|
}
|
||||||
error = readOutput(true);
|
errorCode = readOutput(true);
|
||||||
if (error != ErrorCode::NoError) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
return error;
|
return errorCode;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return closeChannel();
|
return closeChannel();
|
||||||
@@ -222,102 +223,79 @@ namespace libssh {
|
|||||||
return fromLibsshErrorCode();
|
return fromLibsshErrorCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode Client::sftpFileCopy(const SftpOverwriteMode overwriteMode, const std::string& localPath, const std::string& remotePath, const std::string& fileDesc)
|
ErrorCode Client::scpFileCopy(const ScpOverwriteMode overwriteMode, const QString& localPath, const QString& remotePath, const QString &fileDesc)
|
||||||
{
|
{
|
||||||
m_sftpSession = sftp_new(m_session);
|
m_scpSession = ssh_scp_new(m_session, SSH_SCP_WRITE, remotePath.toStdString().c_str());
|
||||||
|
|
||||||
if (m_sftpSession == nullptr) {
|
if (m_scpSession == nullptr) {
|
||||||
return closeSftpSession();
|
return fromLibsshErrorCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
int result = sftp_init(m_sftpSession);
|
if (ssh_scp_init(m_scpSession) != SSH_OK) {
|
||||||
|
auto errorCode = fromLibsshErrorCode();
|
||||||
if (result != SSH_OK) {
|
closeScpSession();
|
||||||
return closeSftpSession();
|
return errorCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
QFutureWatcher<ErrorCode> watcher;
|
QFutureWatcher<ErrorCode> watcher;
|
||||||
connect(&watcher, &QFutureWatcher<ErrorCode>::finished, this, &Client::sftpFileCopyFinished);
|
connect(&watcher, &QFutureWatcher<ErrorCode>::finished, this, &Client::scpFileCopyFinished);
|
||||||
|
|
||||||
QFuture<ErrorCode> future = QtConcurrent::run([this, overwriteMode, &localPath, &remotePath, &fileDesc]() {
|
QFuture<ErrorCode> future = QtConcurrent::run([this, overwriteMode, &localPath, &remotePath, &fileDesc]() {
|
||||||
int accessType = O_WRONLY | O_CREAT | overwriteMode;
|
const int accessType = O_WRONLY | O_CREAT | overwriteMode;
|
||||||
sftp_file file;
|
const int localFileSize = QFileInfo(localPath).size();
|
||||||
const size_t bufferSize = 16384;
|
|
||||||
char buffer[bufferSize];
|
|
||||||
|
|
||||||
file = sftp_open(m_sftpSession, remotePath.c_str(), accessType, S_IRWXU);
|
int result = ssh_scp_push_file(m_scpSession, remotePath.toStdString().c_str(), localFileSize, accessType);
|
||||||
|
if (result != SSH_OK) {
|
||||||
if (file == nullptr) {
|
return fromLibsshErrorCode();
|
||||||
return closeSftpSession();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int localFileSize = std::filesystem::file_size(localPath);
|
QFile fin(localPath);
|
||||||
int chunksCount = localFileSize / (bufferSize);
|
|
||||||
|
|
||||||
std::ifstream fin(localPath, std::ios::binary | std::ios::in);
|
if (fin.open(QIODevice::ReadOnly)) {
|
||||||
|
constexpr size_t bufferSize = 16384;
|
||||||
|
int transferred = 0;
|
||||||
|
int currentChunkSize = bufferSize;
|
||||||
|
|
||||||
if (fin.is_open()) {
|
while (transferred < localFileSize) {
|
||||||
for (int currentChunkId = 0; currentChunkId < chunksCount; currentChunkId++) {
|
|
||||||
fin.read(buffer, bufferSize);
|
|
||||||
|
|
||||||
int bytesWritten = sftp_write(file, buffer, bufferSize);
|
// Last Chunk
|
||||||
|
if ((localFileSize - transferred) < bufferSize) {
|
||||||
std::string chunk(buffer, bufferSize);
|
currentChunkSize = localFileSize % bufferSize;
|
||||||
|
|
||||||
if (bytesWritten != bufferSize) {
|
|
||||||
fin.close();
|
|
||||||
sftp_close(file);
|
|
||||||
return closeSftpSession();
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
int lastChunkSize = localFileSize % (bufferSize);
|
QByteArray chunk = fin.read(currentChunkSize);
|
||||||
|
if (chunk.size() != currentChunkSize) {
|
||||||
if (lastChunkSize != 0) {
|
return fromFileErrorCode(fin.error());
|
||||||
fin.read(buffer, lastChunkSize);
|
|
||||||
|
|
||||||
std::string chunk(buffer, lastChunkSize);
|
|
||||||
|
|
||||||
int bytesWritten = sftp_write(file, buffer, lastChunkSize);
|
|
||||||
|
|
||||||
if (bytesWritten != lastChunkSize) {
|
|
||||||
fin.close();
|
|
||||||
sftp_close(file);
|
|
||||||
return closeSftpSession();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result = ssh_scp_write(m_scpSession, chunk.data(), chunk.size());
|
||||||
|
if (result != SSH_OK) {
|
||||||
|
return fromLibsshErrorCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
transferred += currentChunkSize;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sftp_close(file);
|
return fromFileErrorCode(fin.error());
|
||||||
return closeSftpSession();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fin.close();
|
return ErrorCode::NoError;
|
||||||
|
|
||||||
int result = sftp_close(file);
|
|
||||||
if (result != SSH_OK) {
|
|
||||||
return closeSftpSession();
|
|
||||||
}
|
|
||||||
|
|
||||||
return closeSftpSession();
|
|
||||||
});
|
});
|
||||||
watcher.setFuture(future);
|
watcher.setFuture(future);
|
||||||
|
|
||||||
QEventLoop wait;
|
QEventLoop wait;
|
||||||
QObject::connect(this, &Client::sftpFileCopyFinished, &wait, &QEventLoop::quit);
|
QObject::connect(this, &Client::scpFileCopyFinished, &wait, &QEventLoop::quit);
|
||||||
wait.exec();
|
wait.exec();
|
||||||
|
|
||||||
|
closeScpSession();
|
||||||
return watcher.result();
|
return watcher.result();
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode Client::closeSftpSession()
|
void Client::closeScpSession()
|
||||||
{
|
{
|
||||||
auto errorCode = fromLibsshSftpErrorCode(sftp_get_error(m_sftpSession));
|
if (m_scpSession != nullptr) {
|
||||||
if (m_sftpSession != nullptr) {
|
ssh_scp_free(m_scpSession);
|
||||||
sftp_free(m_sftpSession);
|
m_scpSession = nullptr;
|
||||||
m_sftpSession = nullptr;
|
|
||||||
}
|
}
|
||||||
qCritical() << ssh_get_error(m_session);
|
|
||||||
return errorCode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode Client::fromLibsshErrorCode()
|
ErrorCode Client::fromLibsshErrorCode()
|
||||||
@@ -339,24 +317,17 @@ namespace libssh {
|
|||||||
default: return ErrorCode::SshInternalError;
|
default: return ErrorCode::SshInternalError;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ErrorCode Client::fromLibsshSftpErrorCode(int errorCode)
|
|
||||||
|
ErrorCode Client::fromFileErrorCode(QFileDevice::FileError fileError)
|
||||||
{
|
{
|
||||||
switch (errorCode) {
|
switch (fileError) {
|
||||||
case(SSH_FX_OK): return ErrorCode::NoError;
|
case QFileDevice::NoError: return ErrorCode::NoError;
|
||||||
case(SSH_FX_EOF): return ErrorCode::SshSftpEofError;
|
case QFileDevice::ReadError: return ErrorCode::ReadError;
|
||||||
case(SSH_FX_NO_SUCH_FILE): return ErrorCode::SshSftpNoSuchFileError;
|
case QFileDevice::OpenError: return ErrorCode::OpenError;
|
||||||
case(SSH_FX_PERMISSION_DENIED): return ErrorCode::SshSftpPermissionDeniedError;
|
case QFileDevice::PermissionsError: return ErrorCode::PermissionsError;
|
||||||
case(SSH_FX_FAILURE): return ErrorCode::SshSftpFailureError;
|
case QFileDevice::FatalError: return ErrorCode::FatalError;
|
||||||
case(SSH_FX_BAD_MESSAGE): return ErrorCode::SshSftpBadMessageError;
|
case QFileDevice::AbortError: return ErrorCode::AbortError;
|
||||||
case(SSH_FX_NO_CONNECTION): return ErrorCode::SshSftpNoConnectionError;
|
default: return ErrorCode::UnspecifiedError;
|
||||||
case(SSH_FX_CONNECTION_LOST): return ErrorCode::SshSftpConnectionLostError;
|
|
||||||
case(SSH_FX_OP_UNSUPPORTED): return ErrorCode::SshSftpOpUnsupportedError;
|
|
||||||
case(SSH_FX_INVALID_HANDLE): return ErrorCode::SshSftpInvalidHandleError;
|
|
||||||
case(SSH_FX_NO_SUCH_PATH): return ErrorCode::SshSftpNoSuchPathError;
|
|
||||||
case(SSH_FX_FILE_ALREADY_EXISTS): return ErrorCode::SshSftpFileAlreadyExistsError;
|
|
||||||
case(SSH_FX_WRITE_PROTECT): return ErrorCode::SshSftpWriteProtectError;
|
|
||||||
case(SSH_FX_NO_MEDIA): return ErrorCode::SshSftpNoMediaError;
|
|
||||||
default: return ErrorCode::SshSftpFailureError;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+14
-14
@@ -2,29 +2,29 @@
|
|||||||
#define SSHCLIENT_H
|
#define SSHCLIENT_H
|
||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QFile>
|
||||||
|
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
|
||||||
#include <libssh/libssh.h>
|
#include <libssh/libssh.h>
|
||||||
#include <libssh/sftp.h>
|
|
||||||
|
|
||||||
#include "defs.h"
|
#include "defs.h"
|
||||||
|
|
||||||
using namespace amnezia;
|
using namespace amnezia;
|
||||||
|
|
||||||
namespace libssh {
|
namespace libssh {
|
||||||
enum SftpOverwriteMode {
|
enum ScpOverwriteMode {
|
||||||
/*! Overwrite any existing files */
|
/*! Overwrite any existing files */
|
||||||
SftpOverwriteExisting = O_TRUNC,
|
ScpOverwriteExisting = O_TRUNC,
|
||||||
/*! Append new content if the file already exists */
|
/*! Append new content if the file already exists */
|
||||||
SftpAppendToExisting = O_APPEND
|
ScpAppendToExisting = O_APPEND
|
||||||
};
|
};
|
||||||
class Client : public QObject
|
class Client : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
Client(QObject *parent = nullptr);
|
Client() = default;
|
||||||
~Client();
|
~Client() = default;
|
||||||
|
|
||||||
ErrorCode connectToHost(const ServerCredentials &credentials);
|
ErrorCode connectToHost(const ServerCredentials &credentials);
|
||||||
void disconnectFromHost();
|
void disconnectFromHost();
|
||||||
@@ -32,26 +32,26 @@ namespace libssh {
|
|||||||
const std::function<ErrorCode (const QString &, Client &)> &cbReadStdOut,
|
const std::function<ErrorCode (const QString &, Client &)> &cbReadStdOut,
|
||||||
const std::function<ErrorCode (const QString &, Client &)> &cbReadStdErr);
|
const std::function<ErrorCode (const QString &, Client &)> &cbReadStdErr);
|
||||||
ErrorCode writeResponse(const QString &data);
|
ErrorCode writeResponse(const QString &data);
|
||||||
ErrorCode sftpFileCopy(const SftpOverwriteMode overwriteMode,
|
ErrorCode scpFileCopy(const ScpOverwriteMode overwriteMode,
|
||||||
const std::string& localPath,
|
const QString &localPath,
|
||||||
const std::string& remotePath,
|
const QString &remotePath,
|
||||||
const std::string& fileDesc);
|
const QString &fileDesc);
|
||||||
ErrorCode getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, const std::function<QString()> &passphraseCallback);
|
ErrorCode getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, const std::function<QString()> &passphraseCallback);
|
||||||
private:
|
private:
|
||||||
ErrorCode closeChannel();
|
ErrorCode closeChannel();
|
||||||
ErrorCode closeSftpSession();
|
void closeScpSession();
|
||||||
ErrorCode fromLibsshErrorCode();
|
ErrorCode fromLibsshErrorCode();
|
||||||
ErrorCode fromLibsshSftpErrorCode(int errorCode);
|
ErrorCode fromFileErrorCode(QFileDevice::FileError fileError);
|
||||||
static int callback(const char *prompt, char *buf, size_t len, int echo, int verify, void *userdata);
|
static int callback(const char *prompt, char *buf, size_t len, int echo, int verify, void *userdata);
|
||||||
|
|
||||||
ssh_session m_session = nullptr;
|
ssh_session m_session = nullptr;
|
||||||
ssh_channel m_channel = nullptr;
|
ssh_channel m_channel = nullptr;
|
||||||
sftp_session m_sftpSession = nullptr;
|
ssh_scp m_scpSession = nullptr;
|
||||||
|
|
||||||
static std::function<QString()> m_passphraseCallback;
|
static std::function<QString()> m_passphraseCallback;
|
||||||
signals:
|
signals:
|
||||||
void writeToChannelFinished();
|
void writeToChannelFinished();
|
||||||
void sftpFileCopyFinished();
|
void scpFileCopyFinished();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -248,14 +248,21 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
|
|||||||
|
|
||||||
GETVALUE("privateKey", config.m_privateKey, String);
|
GETVALUE("privateKey", config.m_privateKey, String);
|
||||||
GETVALUE("serverPublicKey", config.m_serverPublicKey, String);
|
GETVALUE("serverPublicKey", config.m_serverPublicKey, String);
|
||||||
GETVALUE("serverPskKey", config.m_serverPskKey, String);
|
|
||||||
GETVALUE("serverPort", config.m_serverPort, Double);
|
GETVALUE("serverPort", config.m_serverPort, Double);
|
||||||
|
|
||||||
|
config.m_serverPskKey = obj.value("serverPskKey").toString();
|
||||||
|
|
||||||
if (!obj.contains("deviceMTU") || obj.value("deviceMTU").toString().toInt() == 0)
|
if (!obj.contains("deviceMTU") || obj.value("deviceMTU").toString().toInt() == 0)
|
||||||
{
|
{
|
||||||
config.m_deviceMTU = 1420;
|
config.m_deviceMTU = 1420;
|
||||||
} else {
|
} else {
|
||||||
config.m_deviceMTU = obj.value("deviceMTU").toString().toInt();
|
config.m_deviceMTU = obj.value("deviceMTU").toString().toInt();
|
||||||
|
#ifdef Q_OS_WINDOWS
|
||||||
|
// For Windows min MTU value is 1280 (the smallest MTU legal with IPv6).
|
||||||
|
if (config.m_deviceMTU < 1280) {
|
||||||
|
config.m_deviceMTU = 1280;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
config.m_deviceIpv4Address = obj.value("deviceIpv4Address").toString();
|
config.m_deviceIpv4Address = obj.value("deviceIpv4Address").toString();
|
||||||
@@ -367,19 +374,31 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!obj.value("Jc").isNull() && !obj.value("Jmin").isNull()
|
if (!obj.value("Jc").isNull()) {
|
||||||
&& !obj.value("Jmax").isNull() && !obj.value("S1").isNull()
|
|
||||||
&& !obj.value("S2").isNull() && !obj.value("H1").isNull()
|
|
||||||
&& !obj.value("H2").isNull() && !obj.value("H3").isNull()
|
|
||||||
&& !obj.value("H4").isNull()) {
|
|
||||||
config.m_junkPacketCount = obj.value("Jc").toString();
|
config.m_junkPacketCount = obj.value("Jc").toString();
|
||||||
|
}
|
||||||
|
if (!obj.value("Jmin").isNull()) {
|
||||||
config.m_junkPacketMinSize = obj.value("Jmin").toString();
|
config.m_junkPacketMinSize = obj.value("Jmin").toString();
|
||||||
|
}
|
||||||
|
if (!obj.value("Jmax").isNull()) {
|
||||||
config.m_junkPacketMaxSize = obj.value("Jmax").toString();
|
config.m_junkPacketMaxSize = obj.value("Jmax").toString();
|
||||||
|
}
|
||||||
|
if (!obj.value("S1").isNull()) {
|
||||||
config.m_initPacketJunkSize = obj.value("S1").toString();
|
config.m_initPacketJunkSize = obj.value("S1").toString();
|
||||||
|
}
|
||||||
|
if (!obj.value("S2").isNull()) {
|
||||||
config.m_responsePacketJunkSize = obj.value("S2").toString();
|
config.m_responsePacketJunkSize = obj.value("S2").toString();
|
||||||
|
}
|
||||||
|
if (!obj.value("H1").isNull()) {
|
||||||
config.m_initPacketMagicHeader = obj.value("H1").toString();
|
config.m_initPacketMagicHeader = obj.value("H1").toString();
|
||||||
|
}
|
||||||
|
if (!obj.value("H2").isNull()) {
|
||||||
config.m_responsePacketMagicHeader = obj.value("H2").toString();
|
config.m_responsePacketMagicHeader = obj.value("H2").toString();
|
||||||
|
}
|
||||||
|
if (!obj.value("H3").isNull()) {
|
||||||
config.m_underloadPacketMagicHeader = obj.value("H3").toString();
|
config.m_underloadPacketMagicHeader = obj.value("H3").toString();
|
||||||
|
}
|
||||||
|
if (!obj.value("H4").isNull()) {
|
||||||
config.m_transportPacketMagicHeader = obj.value("H4").toString();
|
config.m_transportPacketMagicHeader = obj.value("H4").toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,8 +35,10 @@ class Daemon : public QObject {
|
|||||||
virtual QJsonObject getStatus();
|
virtual QJsonObject getStatus();
|
||||||
|
|
||||||
// Callback before any Activating measure is done
|
// Callback before any Activating measure is done
|
||||||
virtual void prepareActivation(const InterfaceConfig& config){
|
virtual void prepareActivation(const InterfaceConfig& config, int inetAdapterIndex = 0) {
|
||||||
Q_UNUSED(config)};
|
Q_UNUSED(config) };
|
||||||
|
virtual void activateSplitTunnel(const InterfaceConfig& config, int vpnAdapterIndex = 0) {
|
||||||
|
Q_UNUSED(config) };
|
||||||
|
|
||||||
QString logs();
|
QString logs();
|
||||||
void cleanLogs();
|
void cleanLogs();
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M12 8V12" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M12 16H12.01" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 518 B |
@@ -0,0 +1,5 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M4 22H18C18.5304 22 19.0391 21.7893 19.4142 21.4142C19.7893 21.0391 20 20.5304 20 20V7.5L14.5 2H6C5.46957 2 4.96086 2.21071 4.58579 2.58579C4.21071 2.96086 4 3.46957 4 4V8" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M14 2V8H20" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M3 15L5 17L9 13" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 581 B |
@@ -0,0 +1,6 @@
|
|||||||
|
<svg width="19" height="18" viewBox="0 0 19 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<rect x="0.5" width="18" height="18" rx="5" fill="white"/>
|
||||||
|
<path d="M8.49219 13.5L8.49219 9.44141L14.0191 4.99484" stroke="#0E0E11" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M4.47363 5.49805L6.98828 8.0127" stroke="#0E0E11" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M14.4727 9.5L14.4727 4.5033L9.50195 4.5033" stroke="#0E0E11" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 511 B |
@@ -0,0 +1,33 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>NSPrivacyAccessedAPITypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>NSPrivacyAccessedAPIType</key>
|
||||||
|
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
|
||||||
|
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||||
|
<array>
|
||||||
|
<string>C617.1</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>NSPrivacyAccessedAPIType</key>
|
||||||
|
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
||||||
|
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||||
|
<array>
|
||||||
|
<string>1C8F.1</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>NSPrivacyAccessedAPIType</key>
|
||||||
|
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
|
||||||
|
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||||
|
<array>
|
||||||
|
<string>35F9.1</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -84,11 +84,20 @@ target_sources(networkextension PRIVATE
|
|||||||
${CLIENT_ROOT_DIR}/platforms/ios/Log.swift
|
${CLIENT_ROOT_DIR}/platforms/ios/Log.swift
|
||||||
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
|
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
|
||||||
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider.swift
|
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider.swift
|
||||||
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+OpenVPNAdapterDelegate.swift
|
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+WireGuard.swift
|
||||||
|
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+OpenVPN.swift
|
||||||
${CLIENT_ROOT_DIR}/platforms/ios/WGConfig.swift
|
${CLIENT_ROOT_DIR}/platforms/ios/WGConfig.swift
|
||||||
${CLIENT_ROOT_DIR}/platforms/ios/iosglue.mm
|
${CLIENT_ROOT_DIR}/platforms/ios/iosglue.mm
|
||||||
)
|
)
|
||||||
|
|
||||||
|
target_sources(networkextension PRIVATE
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/PrivacyInfo.xcprivacy
|
||||||
|
)
|
||||||
|
|
||||||
|
set_property(TARGET networkextension APPEND PROPERTY RESOURCE
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/PrivacyInfo.xcprivacy
|
||||||
|
)
|
||||||
|
|
||||||
## Build wireguard-go-version.h
|
## Build wireguard-go-version.h
|
||||||
execute_process(
|
execute_process(
|
||||||
COMMAND go list -m golang.zx2c4.com/wireguard
|
COMMAND go list -m golang.zx2c4.com/wireguard
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>NSPrivacyAccessedAPITypes</key>
|
||||||
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>NSPrivacyAccessedAPIType</key>
|
||||||
|
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
|
||||||
|
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||||
|
<array>
|
||||||
|
<string>1C8F.1</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
<dict>
|
||||||
|
<key>NSPrivacyAccessedAPIType</key>
|
||||||
|
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
|
||||||
|
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||||
|
<array>
|
||||||
|
<string>C617.1</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
+51
-31
@@ -2,49 +2,69 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>6.0</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
|
||||||
<key>CFBundleAllowMixedLocalizations</key>
|
<key>CFBundleName</key>
|
||||||
<true/>
|
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
|
||||||
|
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleIdentifier</key>
|
||||||
<string>${EXECUTABLE_NAME}</string>
|
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
|
||||||
|
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
|
||||||
|
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>6.0</string>
|
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
|
||||||
|
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>$(PRODUCT_NAME)</string>
|
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
|
||||||
|
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundleDocumentTypes</key>
|
||||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
<array>
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleTypeExtensions</key>
|
||||||
|
<array>
|
||||||
|
<string>conf</string>
|
||||||
|
<string>vpn</string>
|
||||||
|
<string>ovpn</string>
|
||||||
|
<string>backup</string>
|
||||||
|
</array>
|
||||||
|
<key>CFBundleTypeRole</key>
|
||||||
|
<string>Viewer</string>
|
||||||
|
<key>CFBundleTypeName</key>
|
||||||
|
<string>AmneziaVPN configuration file</string>
|
||||||
|
<key>LSItemContentTypes</key>
|
||||||
|
<array>
|
||||||
|
<string>org.amnezia.AmneziaVPN.amnezia-config</string>
|
||||||
|
<string>org.amnezia.AmneziaVPN.wireguard-config</string>
|
||||||
|
<string>org.amnezia.AmneziaVPN.openvpn-config</string>
|
||||||
|
<string>org.amnezia.AmneziaVPN.backup-config</string>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>LSMinimumSystemVersion</key>
|
||||||
<string>$(MARKETING_VERSION)</string>
|
<string>${CMAKE_OSX_DEPLOYMENT_TARGET}</string>
|
||||||
|
|
||||||
<key>CFBundleVersion</key>
|
<key>NSHumanReadableCopyright</key>
|
||||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
|
||||||
|
|
||||||
<key>ITSAppUsesNonExemptEncryption</key>
|
<key>CFBundleIconFile</key>
|
||||||
<false/>
|
<string>${MACOSX_BUNDLE_ICON_FILE}</string>
|
||||||
|
|
||||||
<key>LSApplicationCategoryType</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>public.app-category.utilities</string>
|
<string>en</string>
|
||||||
|
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>CFBundleAllowMixedLocalizations</key>
|
||||||
<string>${MACOSX_DEPLOYMENT_TARGET}</string>
|
<true/>
|
||||||
|
|
||||||
<key>LSMultipleInstancesProhibited</key>
|
<key>NSPrincipalClass</key>
|
||||||
<true/>
|
<string>NSApplication</string>
|
||||||
|
|
||||||
<key>NSPrincipalClass</key>
|
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
||||||
<string>NSApplication</string>
|
<true/>
|
||||||
|
|
||||||
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
+3
-6
@@ -26,9 +26,10 @@ int main(int argc, char *argv[])
|
|||||||
AllowSetForegroundWindow(ASFW_ANY);
|
AllowSetForegroundWindow(ASFW_ANY);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// QTBUG-95974 QTBUG-95764 QTBUG-102168
|
|
||||||
#ifdef Q_OS_ANDROID
|
#ifdef Q_OS_ANDROID
|
||||||
|
// QTBUG-95974 QTBUG-95764 QTBUG-102168
|
||||||
qputenv("QT_ANDROID_DISABLE_ACCESSIBILITY", "1");
|
qputenv("QT_ANDROID_DISABLE_ACCESSIBILITY", "1");
|
||||||
|
qputenv("ANDROID_OPENSSL_SUFFIX", "_3");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||||
@@ -48,10 +49,6 @@ int main(int argc, char *argv[])
|
|||||||
AllowSetForegroundWindow(0);
|
AllowSetForegroundWindow(0);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(Q_OS_IOS)
|
|
||||||
QtAppDelegateInitialize();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
app.registerTypes();
|
app.registerTypes();
|
||||||
|
|
||||||
app.setApplicationName(APPLICATION_NAME);
|
app.setApplicationName(APPLICATION_NAME);
|
||||||
@@ -65,7 +62,7 @@ int main(int argc, char *argv[])
|
|||||||
if (doExec) {
|
if (doExec) {
|
||||||
app.init();
|
app.init();
|
||||||
|
|
||||||
qInfo().noquote() << QString("Started %1 version %2").arg(APPLICATION_NAME, APP_VERSION);
|
qInfo().noquote() << QString("Started %1 version %2 %3").arg(APPLICATION_NAME, APP_VERSION, GIT_COMMIT_HASH);
|
||||||
qInfo().noquote() << QString("%1 (%2)").arg(QSysInfo::prettyProductName(), QSysInfo::currentCpuArchitecture());
|
qInfo().noquote() << QString("%1 (%2)").arg(QSysInfo::prettyProductName(), QSysInfo::currentCpuArchitecture());
|
||||||
|
|
||||||
return app.exec();
|
return app.exec();
|
||||||
|
|||||||
@@ -117,6 +117,9 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
|||||||
int splitTunnelType = rawConfig.value("splitTunnelType").toInt();
|
int splitTunnelType = rawConfig.value("splitTunnelType").toInt();
|
||||||
QJsonArray splitTunnelSites = rawConfig.value("splitTunnelSites").toArray();
|
QJsonArray splitTunnelSites = rawConfig.value("splitTunnelSites").toArray();
|
||||||
|
|
||||||
|
int appSplitTunnelType = rawConfig.value(amnezia::config_key::appSplitTunnelType).toInt();
|
||||||
|
QJsonArray splitTunnelApps = rawConfig.value(amnezia::config_key::splitTunnelApps).toArray();
|
||||||
|
|
||||||
QJsonObject wgConfig = rawConfig.value(protocolName + "_config_data").toObject();
|
QJsonObject wgConfig = rawConfig.value(protocolName + "_config_data").toObject();
|
||||||
|
|
||||||
QJsonObject json;
|
QJsonObject json;
|
||||||
@@ -124,7 +127,14 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
|||||||
// json.insert("hopindex", QJsonValue((double)hop.m_hopindex));
|
// json.insert("hopindex", QJsonValue((double)hop.m_hopindex));
|
||||||
json.insert("privateKey", wgConfig.value(amnezia::config_key::client_priv_key));
|
json.insert("privateKey", wgConfig.value(amnezia::config_key::client_priv_key));
|
||||||
json.insert("deviceIpv4Address", wgConfig.value(amnezia::config_key::client_ip));
|
json.insert("deviceIpv4Address", wgConfig.value(amnezia::config_key::client_ip));
|
||||||
json.insert("deviceIpv6Address", "dead::1");
|
|
||||||
|
// set up IPv6 unique-local-address, ULA, with "fd00::/8" prefix, not globally routable.
|
||||||
|
// this will be default IPv6 gateway, OS recognizes that IPv6 link is local and switches to IPv4.
|
||||||
|
// Otherwise some OSes (Linux) try IPv6 forever and hang.
|
||||||
|
// https://en.wikipedia.org/wiki/Unique_local_address (RFC 4193)
|
||||||
|
// https://man7.org/linux/man-pages/man5/gai.conf.5.html
|
||||||
|
json.insert("deviceIpv6Address", "fd58:baa6:dead::1"); // simply "dead::1" is globally-routable, don't use it
|
||||||
|
|
||||||
json.insert("serverPublicKey", wgConfig.value(amnezia::config_key::server_pub_key));
|
json.insert("serverPublicKey", wgConfig.value(amnezia::config_key::server_pub_key));
|
||||||
json.insert("serverPskKey", wgConfig.value(amnezia::config_key::psk_key));
|
json.insert("serverPskKey", wgConfig.value(amnezia::config_key::psk_key));
|
||||||
json.insert("serverIpv4AddrIn", wgConfig.value(amnezia::config_key::hostName));
|
json.insert("serverIpv4AddrIn", wgConfig.value(amnezia::config_key::hostName));
|
||||||
@@ -210,12 +220,7 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
|||||||
|
|
||||||
json.insert("excludedAddresses", jsExcludedAddresses);
|
json.insert("excludedAddresses", jsExcludedAddresses);
|
||||||
|
|
||||||
|
json.insert("vpnDisabledApps", splitTunnelApps);
|
||||||
// QJsonArray splitTunnelApps;
|
|
||||||
// for (const auto& uri : hop.m_vpnDisabledApps) {
|
|
||||||
// splitTunnelApps.append(QJsonValue(uri));
|
|
||||||
// }
|
|
||||||
// json.insert("vpnDisabledApps", splitTunnelApps);
|
|
||||||
|
|
||||||
if (protocolName == amnezia::config_key::awg) {
|
if (protocolName == amnezia::config_key::awg) {
|
||||||
json.insert(amnezia::config_key::junkPacketCount, wgConfig.value(amnezia::config_key::junkPacketCount));
|
json.insert(amnezia::config_key::junkPacketCount, wgConfig.value(amnezia::config_key::junkPacketCount));
|
||||||
@@ -227,6 +232,24 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
|||||||
json.insert(amnezia::config_key::responsePacketMagicHeader, wgConfig.value(amnezia::config_key::responsePacketMagicHeader));
|
json.insert(amnezia::config_key::responsePacketMagicHeader, wgConfig.value(amnezia::config_key::responsePacketMagicHeader));
|
||||||
json.insert(amnezia::config_key::underloadPacketMagicHeader, wgConfig.value(amnezia::config_key::underloadPacketMagicHeader));
|
json.insert(amnezia::config_key::underloadPacketMagicHeader, wgConfig.value(amnezia::config_key::underloadPacketMagicHeader));
|
||||||
json.insert(amnezia::config_key::transportPacketMagicHeader, wgConfig.value(amnezia::config_key::transportPacketMagicHeader));
|
json.insert(amnezia::config_key::transportPacketMagicHeader, wgConfig.value(amnezia::config_key::transportPacketMagicHeader));
|
||||||
|
} else if (!wgConfig.value(amnezia::config_key::junkPacketCount).isUndefined()
|
||||||
|
&& !wgConfig.value(amnezia::config_key::junkPacketMinSize).isUndefined()
|
||||||
|
&& !wgConfig.value(amnezia::config_key::junkPacketMaxSize).isUndefined()
|
||||||
|
&& !wgConfig.value(amnezia::config_key::initPacketJunkSize).isUndefined()
|
||||||
|
&& !wgConfig.value(amnezia::config_key::responsePacketJunkSize).isUndefined()
|
||||||
|
&& !wgConfig.value(amnezia::config_key::initPacketMagicHeader).isUndefined()
|
||||||
|
&& !wgConfig.value(amnezia::config_key::responsePacketMagicHeader).isUndefined()
|
||||||
|
&& !wgConfig.value(amnezia::config_key::underloadPacketMagicHeader).isUndefined()
|
||||||
|
&& !wgConfig.value(amnezia::config_key::transportPacketMagicHeader).isUndefined()) {
|
||||||
|
json.insert(amnezia::config_key::junkPacketCount, wgConfig.value(amnezia::config_key::junkPacketCount));
|
||||||
|
json.insert(amnezia::config_key::junkPacketMinSize, wgConfig.value(amnezia::config_key::junkPacketMinSize));
|
||||||
|
json.insert(amnezia::config_key::junkPacketMaxSize, wgConfig.value(amnezia::config_key::junkPacketMaxSize));
|
||||||
|
json.insert(amnezia::config_key::initPacketJunkSize, wgConfig.value(amnezia::config_key::initPacketJunkSize));
|
||||||
|
json.insert(amnezia::config_key::responsePacketJunkSize, wgConfig.value(amnezia::config_key::responsePacketJunkSize));
|
||||||
|
json.insert(amnezia::config_key::initPacketMagicHeader, wgConfig.value(amnezia::config_key::initPacketMagicHeader));
|
||||||
|
json.insert(amnezia::config_key::responsePacketMagicHeader, wgConfig.value(amnezia::config_key::responsePacketMagicHeader));
|
||||||
|
json.insert(amnezia::config_key::underloadPacketMagicHeader, wgConfig.value(amnezia::config_key::underloadPacketMagicHeader));
|
||||||
|
json.insert(amnezia::config_key::transportPacketMagicHeader, wgConfig.value(amnezia::config_key::transportPacketMagicHeader));
|
||||||
}
|
}
|
||||||
|
|
||||||
write(json);
|
write(json);
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QQmlFile>
|
#include <QQmlFile>
|
||||||
#include <QEventLoop>
|
#include <QEventLoop>
|
||||||
|
#include <QImage>
|
||||||
|
|
||||||
|
#include <android/bitmap.h>
|
||||||
|
|
||||||
#include "android_controller.h"
|
#include "android_controller.h"
|
||||||
#include "android_utils.h"
|
#include "android_utils.h"
|
||||||
@@ -56,26 +59,10 @@ AndroidController::AndroidController() : QObject()
|
|||||||
Qt::QueuedConnection);
|
Qt::QueuedConnection);
|
||||||
|
|
||||||
connect(
|
connect(
|
||||||
this, &AndroidController::vpnConnected, this,
|
this, &AndroidController::vpnStateChanged, this,
|
||||||
[this]() {
|
[this](AndroidController::ConnectionState state) {
|
||||||
qDebug() << "Android event: VPN connected";
|
qDebug() << "Android event: VPN state changed:" << textConnectionState(state);
|
||||||
emit connectionStateChanged(Vpn::ConnectionState::Connected);
|
emit connectionStateChanged(convertState(state));
|
||||||
},
|
|
||||||
Qt::QueuedConnection);
|
|
||||||
|
|
||||||
connect(
|
|
||||||
this, &AndroidController::vpnDisconnected, this,
|
|
||||||
[this]() {
|
|
||||||
qDebug() << "Android event: VPN disconnected";
|
|
||||||
emit connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
|
||||||
},
|
|
||||||
Qt::QueuedConnection);
|
|
||||||
|
|
||||||
connect(
|
|
||||||
this, &AndroidController::vpnReconnecting, this,
|
|
||||||
[this]() {
|
|
||||||
qDebug() << "Android event: VPN reconnecting";
|
|
||||||
emit connectionStateChanged(Vpn::ConnectionState::Reconnecting);
|
|
||||||
},
|
},
|
||||||
Qt::QueuedConnection);
|
Qt::QueuedConnection);
|
||||||
|
|
||||||
@@ -106,9 +93,7 @@ bool AndroidController::initialize()
|
|||||||
{"onServiceDisconnected", "()V", reinterpret_cast<void *>(onServiceDisconnected)},
|
{"onServiceDisconnected", "()V", reinterpret_cast<void *>(onServiceDisconnected)},
|
||||||
{"onServiceError", "()V", reinterpret_cast<void *>(onServiceError)},
|
{"onServiceError", "()V", reinterpret_cast<void *>(onServiceError)},
|
||||||
{"onVpnPermissionRejected", "()V", reinterpret_cast<void *>(onVpnPermissionRejected)},
|
{"onVpnPermissionRejected", "()V", reinterpret_cast<void *>(onVpnPermissionRejected)},
|
||||||
{"onVpnConnected", "()V", reinterpret_cast<void *>(onVpnConnected)},
|
{"onVpnStateChanged", "(I)V", reinterpret_cast<void *>(onVpnStateChanged)},
|
||||||
{"onVpnDisconnected", "()V", reinterpret_cast<void *>(onVpnDisconnected)},
|
|
||||||
{"onVpnReconnecting", "()V", reinterpret_cast<void *>(onVpnReconnecting)},
|
|
||||||
{"onStatisticsUpdate", "(JJ)V", reinterpret_cast<void *>(onStatisticsUpdate)},
|
{"onStatisticsUpdate", "(JJ)V", reinterpret_cast<void *>(onStatisticsUpdate)},
|
||||||
{"onFileOpened", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onFileOpened)},
|
{"onFileOpened", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onFileOpened)},
|
||||||
{"onConfigImported", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onConfigImported)},
|
{"onConfigImported", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onConfigImported)},
|
||||||
@@ -158,6 +143,11 @@ void AndroidController::stop()
|
|||||||
callActivityMethod("stop", "()V");
|
callActivityMethod("stop", "()V");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AndroidController::resetLastServer(int serverIndex)
|
||||||
|
{
|
||||||
|
callActivityMethod("resetLastServer", "(I)V", serverIndex);
|
||||||
|
}
|
||||||
|
|
||||||
void AndroidController::saveFile(const QString &fileName, const QString &data)
|
void AndroidController::saveFile(const QString &fileName, const QString &data)
|
||||||
{
|
{
|
||||||
callActivityMethod("saveFile", "(Ljava/lang/String;Ljava/lang/String;)V",
|
callActivityMethod("saveFile", "(Ljava/lang/String;Ljava/lang/String;)V",
|
||||||
@@ -217,6 +207,56 @@ void AndroidController::clearLogs()
|
|||||||
callActivityMethod("clearLogs", "()V");
|
callActivityMethod("clearLogs", "()V");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AndroidController::setScreenshotsEnabled(bool enabled)
|
||||||
|
{
|
||||||
|
callActivityMethod("setScreenshotsEnabled", "(Z)V", enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AndroidController::minimizeApp()
|
||||||
|
{
|
||||||
|
callActivityMethod("minimizeApp", "()V");
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonArray AndroidController::getAppList()
|
||||||
|
{
|
||||||
|
QJniObject appList = callActivityMethod<jstring>("getAppList", "()Ljava/lang/String;");
|
||||||
|
QJsonArray jsonAppList = QJsonDocument::fromJson(appList.toString().toUtf8()).array();
|
||||||
|
return jsonAppList;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPixmap AndroidController::getAppIcon(const QString &package, QSize *size, const QSize &requestedSize)
|
||||||
|
{
|
||||||
|
QJniObject bitmap = callActivityMethod<jobject>("getAppIcon", "(Ljava/lang/String;II)Landroid/graphics/Bitmap;",
|
||||||
|
QJniObject::fromString(package).object<jstring>(),
|
||||||
|
requestedSize.width(), requestedSize.height());
|
||||||
|
|
||||||
|
QJniEnvironment env;
|
||||||
|
AndroidBitmapInfo info;
|
||||||
|
if (AndroidBitmap_getInfo(env.jniEnv(), bitmap.object(), &info) != ANDROID_BITMAP_RESULT_SUCCESS) return {};
|
||||||
|
|
||||||
|
void *pixels;
|
||||||
|
if (AndroidBitmap_lockPixels(env.jniEnv(), bitmap.object(), &pixels) != ANDROID_BITMAP_RESULT_SUCCESS) return {};
|
||||||
|
|
||||||
|
int width = info.width;
|
||||||
|
int height = info.height;
|
||||||
|
|
||||||
|
size->setWidth(width);
|
||||||
|
size->setHeight(height);
|
||||||
|
|
||||||
|
QImage image(width, height, QImage::Format_RGBA8888);
|
||||||
|
if (info.stride == uint32_t(image.bytesPerLine())) {
|
||||||
|
memcpy((void *) image.constBits(), pixels, info.stride * height);
|
||||||
|
} else {
|
||||||
|
auto *bmpPtr = static_cast<uchar *>(pixels);
|
||||||
|
for (int i = 0; i < height; i++, bmpPtr += info.stride)
|
||||||
|
memcpy((void *) image.constScanLine(i), bmpPtr, width);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (AndroidBitmap_unlockPixels(env.jniEnv(), bitmap.object()) != ANDROID_BITMAP_RESULT_SUCCESS) return {};
|
||||||
|
|
||||||
|
return QPixmap::fromImage(image);
|
||||||
|
}
|
||||||
|
|
||||||
// Moving log processing to the Android side
|
// Moving log processing to the Android side
|
||||||
jclass AndroidController::log;
|
jclass AndroidController::log;
|
||||||
jmethodID AndroidController::logDebug;
|
jmethodID AndroidController::logDebug;
|
||||||
@@ -370,30 +410,14 @@ void AndroidController::onVpnPermissionRejected(JNIEnv *env, jobject thiz)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
void AndroidController::onVpnConnected(JNIEnv *env, jobject thiz)
|
void AndroidController::onVpnStateChanged(JNIEnv *env, jobject thiz, jint stateCode)
|
||||||
{
|
{
|
||||||
Q_UNUSED(env);
|
Q_UNUSED(env);
|
||||||
Q_UNUSED(thiz);
|
Q_UNUSED(thiz);
|
||||||
|
|
||||||
emit AndroidController::instance()->vpnConnected();
|
auto state = ConnectionState(stateCode);
|
||||||
}
|
|
||||||
|
|
||||||
// static
|
emit AndroidController::instance()->vpnStateChanged(state);
|
||||||
void AndroidController::onVpnDisconnected(JNIEnv *env, jobject thiz)
|
|
||||||
{
|
|
||||||
Q_UNUSED(env);
|
|
||||||
Q_UNUSED(thiz);
|
|
||||||
|
|
||||||
emit AndroidController::instance()->vpnDisconnected();
|
|
||||||
}
|
|
||||||
|
|
||||||
// static
|
|
||||||
void AndroidController::onVpnReconnecting(JNIEnv *env, jobject thiz)
|
|
||||||
{
|
|
||||||
Q_UNUSED(env);
|
|
||||||
Q_UNUSED(thiz);
|
|
||||||
|
|
||||||
emit AndroidController::instance()->vpnReconnecting();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#define ANDROID_CONTROLLER_H
|
#define ANDROID_CONTROLLER_H
|
||||||
|
|
||||||
#include <QJniObject>
|
#include <QJniObject>
|
||||||
|
#include <QPixmap>
|
||||||
|
|
||||||
#include "protocols/vpnprotocol.h"
|
#include "protocols/vpnprotocol.h"
|
||||||
|
|
||||||
@@ -20,9 +21,9 @@ public:
|
|||||||
// keep synchronized with org.amnezia.vpn.protocol.ProtocolState
|
// keep synchronized with org.amnezia.vpn.protocol.ProtocolState
|
||||||
enum class ConnectionState
|
enum class ConnectionState
|
||||||
{
|
{
|
||||||
|
DISCONNECTED,
|
||||||
CONNECTED,
|
CONNECTED,
|
||||||
CONNECTING,
|
CONNECTING,
|
||||||
DISCONNECTED,
|
|
||||||
DISCONNECTING,
|
DISCONNECTING,
|
||||||
RECONNECTING,
|
RECONNECTING,
|
||||||
UNKNOWN
|
UNKNOWN
|
||||||
@@ -30,6 +31,7 @@ public:
|
|||||||
|
|
||||||
ErrorCode start(const QJsonObject &vpnConfig);
|
ErrorCode start(const QJsonObject &vpnConfig);
|
||||||
void stop();
|
void stop();
|
||||||
|
void resetLastServer(int serverIndex);
|
||||||
void setNotificationText(const QString &title, const QString &message, int timerSec);
|
void setNotificationText(const QString &title, const QString &message, int timerSec);
|
||||||
void saveFile(const QString &fileName, const QString &data);
|
void saveFile(const QString &fileName, const QString &data);
|
||||||
QString openFile(const QString &filter);
|
QString openFile(const QString &filter);
|
||||||
@@ -38,6 +40,10 @@ public:
|
|||||||
void setSaveLogs(bool enabled);
|
void setSaveLogs(bool enabled);
|
||||||
void exportLogsFile(const QString &fileName);
|
void exportLogsFile(const QString &fileName);
|
||||||
void clearLogs();
|
void clearLogs();
|
||||||
|
void setScreenshotsEnabled(bool enabled);
|
||||||
|
void minimizeApp();
|
||||||
|
QJsonArray getAppList();
|
||||||
|
QPixmap getAppIcon(const QString &package, QSize *size, const QSize &requestedSize);
|
||||||
|
|
||||||
static bool initLogging();
|
static bool initLogging();
|
||||||
static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message);
|
static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message);
|
||||||
@@ -48,9 +54,7 @@ signals:
|
|||||||
void serviceDisconnected();
|
void serviceDisconnected();
|
||||||
void serviceError();
|
void serviceError();
|
||||||
void vpnPermissionRejected();
|
void vpnPermissionRejected();
|
||||||
void vpnConnected();
|
void vpnStateChanged(ConnectionState state);
|
||||||
void vpnDisconnected();
|
|
||||||
void vpnReconnecting();
|
|
||||||
void statisticsUpdated(quint64 rxBytes, quint64 txBytes);
|
void statisticsUpdated(quint64 rxBytes, quint64 txBytes);
|
||||||
void fileOpened(QString uri);
|
void fileOpened(QString uri);
|
||||||
void configImported(QString config);
|
void configImported(QString config);
|
||||||
@@ -77,9 +81,7 @@ private:
|
|||||||
static void onServiceDisconnected(JNIEnv *env, jobject thiz);
|
static void onServiceDisconnected(JNIEnv *env, jobject thiz);
|
||||||
static void onServiceError(JNIEnv *env, jobject thiz);
|
static void onServiceError(JNIEnv *env, jobject thiz);
|
||||||
static void onVpnPermissionRejected(JNIEnv *env, jobject thiz);
|
static void onVpnPermissionRejected(JNIEnv *env, jobject thiz);
|
||||||
static void onVpnConnected(JNIEnv *env, jobject thiz);
|
static void onVpnStateChanged(JNIEnv *env, jobject thiz, jint stateCode);
|
||||||
static void onVpnDisconnected(JNIEnv *env, jobject thiz);
|
|
||||||
static void onVpnReconnecting(JNIEnv *env, jobject thiz);
|
|
||||||
static void onStatisticsUpdate(JNIEnv *env, jobject thiz, jlong rxBytes, jlong txBytes);
|
static void onStatisticsUpdate(JNIEnv *env, jobject thiz, jlong rxBytes, jlong txBytes);
|
||||||
static void onConfigImported(JNIEnv *env, jobject thiz, jstring data);
|
static void onConfigImported(JNIEnv *env, jobject thiz, jstring data);
|
||||||
static void onFileOpened(JNIEnv *env, jobject thiz, jstring uri);
|
static void onFileOpened(JNIEnv *env, jobject thiz, jstring uri);
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import Foundation
|
|||||||
import os.log
|
import os.log
|
||||||
|
|
||||||
struct Log {
|
struct Log {
|
||||||
|
static let osLog = Logger()
|
||||||
|
|
||||||
private static let IsLoggingEnabledKey = "IsLoggingEnabled"
|
private static let IsLoggingEnabledKey = "IsLoggingEnabled"
|
||||||
static var isLoggingEnabled: Bool {
|
static var isLoggingEnabled: Bool {
|
||||||
get {
|
get {
|
||||||
@@ -14,6 +16,11 @@ struct Log {
|
|||||||
|
|
||||||
private static let appGroupID = "group.org.amnezia.AmneziaVPN"
|
private static let appGroupID = "group.org.amnezia.AmneziaVPN"
|
||||||
|
|
||||||
|
static let appLogURL = {
|
||||||
|
let sharedContainerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupID)!
|
||||||
|
return sharedContainerURL.appendingPathComponent("app.log", isDirectory: false)
|
||||||
|
}()
|
||||||
|
|
||||||
static let neLogURL = {
|
static let neLogURL = {
|
||||||
let sharedContainerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupID)!
|
let sharedContainerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupID)!
|
||||||
return sharedContainerURL.appendingPathComponent("ne.log", isDirectory: false)
|
return sharedContainerURL.appendingPathComponent("ne.log", isDirectory: false)
|
||||||
@@ -29,16 +36,23 @@ struct Log {
|
|||||||
return dateFormatter
|
return dateFormatter
|
||||||
}()
|
}()
|
||||||
|
|
||||||
var records: [Record]
|
var records = [Record]()
|
||||||
|
|
||||||
|
var lastRecordDate = Date.distantPast
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
self.records = []
|
self.records = []
|
||||||
}
|
}
|
||||||
|
|
||||||
init(_ str: String) {
|
init(_ str: String) {
|
||||||
self.records = str.split(whereSeparator: \.isNewline)
|
records = str.split(whereSeparator: \.isNewline)
|
||||||
.compactMap {
|
.map {
|
||||||
Record(String($0))
|
if let record = Record(String($0)) {
|
||||||
|
lastRecordDate = record.date
|
||||||
|
return record
|
||||||
|
} else {
|
||||||
|
return Record(date: lastRecordDate, level: .error, message: "LOG: \($0)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,6 +74,28 @@ struct Log {
|
|||||||
self.init(str)
|
self.init(str)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func log(_ type: OSLogType, title: String = "", message: String, url: URL = neLogURL) {
|
||||||
|
NSLog("\(title) \(message)")
|
||||||
|
|
||||||
|
guard isLoggingEnabled else { return }
|
||||||
|
|
||||||
|
osLog.log(level: type, "\(title) \(message)")
|
||||||
|
|
||||||
|
let date = Date()
|
||||||
|
let level = Record.Level(from: type)
|
||||||
|
let messages = message.split(whereSeparator: \.isNewline)
|
||||||
|
|
||||||
|
for index in 0..<messages.count {
|
||||||
|
let message = String(messages[index])
|
||||||
|
|
||||||
|
if index != 0 && message.first != " " {
|
||||||
|
Record(date: date, level: level, message: "\(title) \(message)").save(at: url)
|
||||||
|
} else {
|
||||||
|
Record(date: date, level: level, message: "\(title)\(message)").save(at: url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static func clear(at url: URL) {
|
static func clear(at url: URL) {
|
||||||
if FileManager.default.fileExists(atPath: url.path) {
|
if FileManager.default.fileExists(atPath: url.path) {
|
||||||
guard let fileHandle = try? FileHandle(forUpdating: url) else { return }
|
guard let fileHandle = try? FileHandle(forUpdating: url) else { return }
|
||||||
@@ -80,3 +116,7 @@ extension Log: CustomStringConvertible {
|
|||||||
.joined(separator: "\n")
|
.joined(separator: "\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func log(_ type: OSLogType, title: String = "", message: String) {
|
||||||
|
Log.log(type, title: "App: \(title)", message: message, url: Log.appLogURL)
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,20 +4,27 @@ public func swiftUpdateLogData(_ qtString: std.string) -> std.string {
|
|||||||
let qtLog = Log(String(describing: qtString))
|
let qtLog = Log(String(describing: qtString))
|
||||||
var log = qtLog
|
var log = qtLog
|
||||||
|
|
||||||
|
if let appLog = Log(at: Log.appLogURL) {
|
||||||
|
appLog.records.forEach {
|
||||||
|
log.records.append($0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let neLog = Log(at: Log.neLogURL) {
|
if let neLog = Log(at: Log.neLogURL) {
|
||||||
neLog.records.forEach {
|
neLog.records.forEach {
|
||||||
log.records.append($0)
|
log.records.append($0)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
log.records.sort {
|
log.records.sort {
|
||||||
$0.date < $1.date
|
$0.date < $1.date
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return std.string(log.description)
|
return std.string(log.description)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func swiftDeleteLog() {
|
public func swiftDeleteLog() {
|
||||||
|
Log.clear(at: Log.appLogURL)
|
||||||
Log.clear(at: Log.neLogURL)
|
Log.clear(at: Log.neLogURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ extension Log {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func save(at url: URL) {
|
func save(at url: URL) {
|
||||||
|
osLog.log(level: level.osLogType, "\(message)")
|
||||||
|
|
||||||
guard let data = "\n\(description)".data(using: .utf8) else { return }
|
guard let data = "\n\(description)".data(using: .utf8) else { return }
|
||||||
|
|
||||||
if !FileManager.default.fileExists(atPath: url.path) {
|
if !FileManager.default.fileExists(atPath: url.path) {
|
||||||
@@ -64,19 +66,38 @@ extension Log.Record {
|
|||||||
|
|
||||||
init(from osLogType: OSLogType) {
|
init(from osLogType: OSLogType) {
|
||||||
switch osLogType {
|
switch osLogType {
|
||||||
case OSLogType.default:
|
case .default:
|
||||||
self = .info
|
self = .info
|
||||||
case OSLogType.info:
|
case .info:
|
||||||
self = .info
|
self = .info
|
||||||
case OSLogType.debug:
|
case .debug:
|
||||||
self = .debug
|
self = .debug
|
||||||
case OSLogType.error:
|
case .error:
|
||||||
self = .error
|
self = .error
|
||||||
case OSLogType.fault:
|
case .fault:
|
||||||
self = .fatal
|
self = .fatal
|
||||||
default:
|
default:
|
||||||
self = .info
|
self = .info
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var osLogType: OSLogType {
|
||||||
|
switch self {
|
||||||
|
case .info:
|
||||||
|
return .info
|
||||||
|
case .debug:
|
||||||
|
return .debug
|
||||||
|
case .error:
|
||||||
|
return .error
|
||||||
|
case .fatal:
|
||||||
|
return .fault
|
||||||
|
case .warning:
|
||||||
|
return .info
|
||||||
|
case .critical:
|
||||||
|
return .fault
|
||||||
|
case .system:
|
||||||
|
return .fault
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import os.log
|
import os.log
|
||||||
|
|
||||||
public func wg_log(_ type: OSLogType, staticMessage: StaticString) {
|
public func wg_log(_ type: OSLogType, title: String = "", staticMessage: StaticString) {
|
||||||
guard Log.isLoggingEnabled else { return }
|
neLog(type, title: "WG: \(title)", message: "\(staticMessage)")
|
||||||
|
|
||||||
Log.Record(date: Date(), level: Log.Record.Level(from: type), message: "\(staticMessage)").save(at: Log.neLogURL)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func wg_log(_ type: OSLogType, message: String) {
|
public func wg_log(_ type: OSLogType, title: String = "", message: String) {
|
||||||
log(type, message: message)
|
neLog(type, title: "WG: \(title)", message: message)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func log(_ type: OSLogType, message: String) {
|
public func ovpnLog(_ type: OSLogType, title: String = "", message: String) {
|
||||||
guard Log.isLoggingEnabled else { return }
|
neLog(type, title: "OVPN: \(title)", message: message)
|
||||||
|
}
|
||||||
Log.Record(date: Date(), level: Log.Record.Level(from: type), message: message).save(at: Log.neLogURL)
|
|
||||||
|
public func neLog(_ type: OSLogType, title: String = "", message: String) {
|
||||||
|
Log.log(type, title: "NE: \(title)", message: message)
|
||||||
}
|
}
|
||||||
|
|||||||
+105
-1
@@ -2,6 +2,108 @@ import Foundation
|
|||||||
import NetworkExtension
|
import NetworkExtension
|
||||||
import OpenVPNAdapter
|
import OpenVPNAdapter
|
||||||
|
|
||||||
|
struct OpenVPNConfig: Decodable {
|
||||||
|
let config: String
|
||||||
|
let splitTunnelType: Int
|
||||||
|
let splitTunnelSites: [String]
|
||||||
|
|
||||||
|
var str: String {
|
||||||
|
"splitTunnelType: \(splitTunnelType) splitTunnelSites: \(splitTunnelSites) config: \(config)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension PacketTunnelProvider {
|
||||||
|
func startOpenVPN(completionHandler: @escaping (Error?) -> Void) {
|
||||||
|
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
|
||||||
|
let providerConfiguration = protocolConfiguration.providerConfiguration,
|
||||||
|
let openVPNConfigData = providerConfiguration[Constants.ovpnConfigKey] as? Data else {
|
||||||
|
ovpnLog(.error, message: "Can't start")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
// ovpnLog(.info, message: "providerConfiguration: \(String(decoding: openVPNConfigData, as: UTF8.self))")
|
||||||
|
|
||||||
|
let openVPNConfig = try JSONDecoder().decode(OpenVPNConfig.self, from: openVPNConfigData)
|
||||||
|
ovpnLog(.info, title: "config: ", message: openVPNConfig.str)
|
||||||
|
let ovpnConfiguration = Data(openVPNConfig.config.utf8)
|
||||||
|
setupAndlaunchOpenVPN(withConfig: ovpnConfiguration, completionHandler: completionHandler)
|
||||||
|
} catch {
|
||||||
|
ovpnLog(.error, message: "Can't parse config: \(error.localizedDescription)")
|
||||||
|
|
||||||
|
if let underlyingError = (error as NSError).userInfo[NSUnderlyingErrorKey] as? NSError {
|
||||||
|
ovpnLog(.error, message: "Can't parse config: \(underlyingError.localizedDescription)")
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupAndlaunchOpenVPN(withConfig ovpnConfiguration: Data,
|
||||||
|
withShadowSocks viaSS: Bool = false,
|
||||||
|
completionHandler: @escaping (Error?) -> Void) {
|
||||||
|
ovpnLog(.info, message: "Setup and launch")
|
||||||
|
|
||||||
|
let str = String(decoding: ovpnConfiguration, as: UTF8.self)
|
||||||
|
|
||||||
|
let configuration = OpenVPNConfiguration()
|
||||||
|
configuration.fileContent = ovpnConfiguration
|
||||||
|
if str.contains("cloak") {
|
||||||
|
configuration.setPTCloak()
|
||||||
|
}
|
||||||
|
|
||||||
|
let evaluation: OpenVPNConfigurationEvaluation
|
||||||
|
do {
|
||||||
|
evaluation = try ovpnAdapter.apply(configuration: configuration)
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
completionHandler(error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !evaluation.autologin {
|
||||||
|
ovpnLog(.info, message: "Implement login with user credentials")
|
||||||
|
}
|
||||||
|
|
||||||
|
vpnReachability.startTracking { [weak self] status in
|
||||||
|
guard status == .reachableViaWiFi else { return }
|
||||||
|
self?.ovpnAdapter.reconnect(afterTimeInterval: 5)
|
||||||
|
}
|
||||||
|
|
||||||
|
startHandler = completionHandler
|
||||||
|
ovpnAdapter.connect(using: packetFlow)
|
||||||
|
|
||||||
|
// let ifaces = Interface.allInterfaces()
|
||||||
|
// .filter { $0.family == .ipv4 }
|
||||||
|
// .map { iface in iface.name }
|
||||||
|
|
||||||
|
// ovpn_log(.error, message: "Available TUN Interfaces: \(ifaces)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleOpenVPNStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||||
|
guard let completionHandler = completionHandler else { return }
|
||||||
|
let bytesin = ovpnAdapter.transportStatistics.bytesIn
|
||||||
|
let bytesout = ovpnAdapter.transportStatistics.bytesOut
|
||||||
|
|
||||||
|
let response: [String: Any] = [
|
||||||
|
"rx_bytes": bytesin,
|
||||||
|
"tx_bytes": bytesout
|
||||||
|
]
|
||||||
|
|
||||||
|
completionHandler(try? JSONSerialization.data(withJSONObject: response, options: []))
|
||||||
|
}
|
||||||
|
|
||||||
|
func stopOpenVPN(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
||||||
|
ovpnLog(.info, message: "Stopping tunnel: reason: \(reason.description)")
|
||||||
|
|
||||||
|
stopHandler = completionHandler
|
||||||
|
if vpnReachability.isTracking {
|
||||||
|
vpnReachability.stopTracking()
|
||||||
|
}
|
||||||
|
ovpnAdapter.disconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension PacketTunnelProvider: OpenVPNAdapterDelegate {
|
extension PacketTunnelProvider: OpenVPNAdapterDelegate {
|
||||||
// OpenVPNAdapter calls this delegate method to configure a VPN tunnel.
|
// OpenVPNAdapter calls this delegate method to configure a VPN tunnel.
|
||||||
// `completionHandler` callback requires an object conforming to `OpenVPNAdapterPacketFlow`
|
// `completionHandler` callback requires an object conforming to `OpenVPNAdapterPacketFlow`
|
||||||
@@ -116,6 +218,8 @@ extension PacketTunnelProvider: OpenVPNAdapterDelegate {
|
|||||||
// Use this method to process any log message returned by OpenVPN library.
|
// Use this method to process any log message returned by OpenVPN library.
|
||||||
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleLogMessage logMessage: String) {
|
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleLogMessage logMessage: String) {
|
||||||
// Handle log messages
|
// Handle log messages
|
||||||
wg_log(.info, message: logMessage)
|
ovpnLog(.info, message: logMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension NEPacketTunnelFlow: OpenVPNAdapterPacketFlow {}
|
||||||
@@ -0,0 +1,221 @@
|
|||||||
|
import Foundation
|
||||||
|
import NetworkExtension
|
||||||
|
|
||||||
|
extension PacketTunnelProvider {
|
||||||
|
func startWireguard(activationAttemptId: String?,
|
||||||
|
errorNotifier: ErrorNotifier,
|
||||||
|
completionHandler: @escaping (Error?) -> Void) {
|
||||||
|
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
|
||||||
|
let providerConfiguration = protocolConfiguration.providerConfiguration,
|
||||||
|
let wgConfigData: Data = providerConfiguration[Constants.wireGuardConfigKey] as? Data else {
|
||||||
|
wg_log(.error, message: "Can't start, config missing")
|
||||||
|
completionHandler(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
let wgConfig = try JSONDecoder().decode(WGConfig.self, from: wgConfigData)
|
||||||
|
let wgConfigStr = wgConfig.str
|
||||||
|
wg_log(.info, title: "config: ", message: wgConfig.redux)
|
||||||
|
|
||||||
|
let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: wgConfigStr)
|
||||||
|
|
||||||
|
if tunnelConfiguration.peers.first!.allowedIPs
|
||||||
|
.map({ $0.stringRepresentation })
|
||||||
|
.joined(separator: ", ") == "0.0.0.0/0, ::/0" {
|
||||||
|
if wgConfig.splitTunnelType == 1 {
|
||||||
|
for index in tunnelConfiguration.peers.indices {
|
||||||
|
tunnelConfiguration.peers[index].allowedIPs.removeAll()
|
||||||
|
var allowedIPs = [IPAddressRange]()
|
||||||
|
|
||||||
|
for allowedIPString in wgConfig.splitTunnelSites {
|
||||||
|
if let allowedIP = IPAddressRange(from: allowedIPString) {
|
||||||
|
allowedIPs.append(allowedIP)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tunnelConfiguration.peers[index].allowedIPs = allowedIPs
|
||||||
|
}
|
||||||
|
} else if wgConfig.splitTunnelType == 2 {
|
||||||
|
for index in tunnelConfiguration.peers.indices {
|
||||||
|
var excludeIPs = [IPAddressRange]()
|
||||||
|
|
||||||
|
for excludeIPString in wgConfig.splitTunnelSites {
|
||||||
|
if let excludeIP = IPAddressRange(from: excludeIPString) {
|
||||||
|
excludeIPs.append(excludeIP)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tunnelConfiguration.peers[index].excludeIPs = excludeIPs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
wg_log(.info, message: "Starting tunnel from the " +
|
||||||
|
(activationAttemptId == nil ? "OS directly, rather than the app" : "app"))
|
||||||
|
|
||||||
|
// Start the tunnel
|
||||||
|
wgAdapter.start(tunnelConfiguration: tunnelConfiguration) { adapterError in
|
||||||
|
guard let adapterError else {
|
||||||
|
let interfaceName = self.wgAdapter.interfaceName ?? "unknown"
|
||||||
|
wg_log(.info, message: "Tunnel interface is \(interfaceName)")
|
||||||
|
completionHandler(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch adapterError {
|
||||||
|
case .cannotLocateTunnelFileDescriptor:
|
||||||
|
wg_log(.error, staticMessage: "Starting tunnel failed: could not determine file descriptor")
|
||||||
|
errorNotifier.notify(PacketTunnelProviderError.couldNotDetermineFileDescriptor)
|
||||||
|
completionHandler(PacketTunnelProviderError.couldNotDetermineFileDescriptor)
|
||||||
|
case .dnsResolution(let dnsErrors):
|
||||||
|
let hostnamesWithDnsResolutionFailure = dnsErrors.map { $0.address }
|
||||||
|
.joined(separator: ", ")
|
||||||
|
wg_log(.error, message:
|
||||||
|
"DNS resolution failed for the following hostnames: \(hostnamesWithDnsResolutionFailure)")
|
||||||
|
errorNotifier.notify(PacketTunnelProviderError.dnsResolutionFailure)
|
||||||
|
completionHandler(PacketTunnelProviderError.dnsResolutionFailure)
|
||||||
|
case .setNetworkSettings(let error):
|
||||||
|
wg_log(.error, message:
|
||||||
|
"Starting tunnel failed with setTunnelNetworkSettings returning \(error.localizedDescription)")
|
||||||
|
errorNotifier.notify(PacketTunnelProviderError.couldNotSetNetworkSettings)
|
||||||
|
completionHandler(PacketTunnelProviderError.couldNotSetNetworkSettings)
|
||||||
|
case .startWireGuardBackend(let errorCode):
|
||||||
|
wg_log(.error, message: "Starting tunnel failed with wgTurnOn returning \(errorCode)")
|
||||||
|
errorNotifier.notify(PacketTunnelProviderError.couldNotStartBackend)
|
||||||
|
completionHandler(PacketTunnelProviderError.couldNotStartBackend)
|
||||||
|
case .invalidState:
|
||||||
|
fatalError()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
wg_log(.error, message: "Can't parse WG config: \(error.localizedDescription)")
|
||||||
|
completionHandler(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleWireguardStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||||
|
guard let completionHandler = completionHandler else { return }
|
||||||
|
wgAdapter.getRuntimeConfiguration { settings in
|
||||||
|
var data: Data?
|
||||||
|
if let settings {
|
||||||
|
data = settings.data(using: .utf8)!
|
||||||
|
}
|
||||||
|
|
||||||
|
let components = settings!.components(separatedBy: "\n")
|
||||||
|
|
||||||
|
var settingsDictionary: [String: String] = [:]
|
||||||
|
for component in components {
|
||||||
|
let pair = component.components(separatedBy: "=")
|
||||||
|
if pair.count == 2 {
|
||||||
|
settingsDictionary[pair[0]] = pair[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let response: [String: Any] = [
|
||||||
|
"rx_bytes": settingsDictionary["rx_bytes"] ?? "0",
|
||||||
|
"tx_bytes": settingsDictionary["tx_bytes"] ?? "0"
|
||||||
|
]
|
||||||
|
|
||||||
|
completionHandler(try? JSONSerialization.data(withJSONObject: response, options: []))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func handleWireguardAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||||
|
guard let completionHandler = completionHandler else { return }
|
||||||
|
if messageData.count == 1 && messageData[0] == 0 {
|
||||||
|
wgAdapter.getRuntimeConfiguration { settings in
|
||||||
|
var data: Data?
|
||||||
|
if let settings {
|
||||||
|
data = settings.data(using: .utf8)!
|
||||||
|
}
|
||||||
|
completionHandler(data)
|
||||||
|
}
|
||||||
|
} else if messageData.count >= 1 {
|
||||||
|
// Updates the tunnel configuration and responds with the active configuration
|
||||||
|
wg_log(.info, message: "Switching tunnel configuration")
|
||||||
|
guard let configString = String(data: messageData, encoding: .utf8)
|
||||||
|
else {
|
||||||
|
completionHandler(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: configString)
|
||||||
|
wgAdapter.update(tunnelConfiguration: tunnelConfiguration) { error in
|
||||||
|
if let error {
|
||||||
|
wg_log(.error, message: "Failed to switch tunnel configuration: \(error.localizedDescription)")
|
||||||
|
completionHandler(nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
self.wgAdapter.getRuntimeConfiguration { settings in
|
||||||
|
var data: Data?
|
||||||
|
if let settings {
|
||||||
|
data = settings.data(using: .utf8)!
|
||||||
|
}
|
||||||
|
completionHandler(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
completionHandler(nil)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
completionHandler(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// private func startEmptyTunnel(completionHandler: @escaping (Error?) -> Void) {
|
||||||
|
// dispatchPrecondition(condition: .onQueue(dispatchQueue))
|
||||||
|
//
|
||||||
|
// let emptyTunnelConfiguration = TunnelConfiguration(
|
||||||
|
// name: nil,
|
||||||
|
// interface: InterfaceConfiguration(privateKey: PrivateKey()),
|
||||||
|
// peers: []
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// wgAdapter.start(tunnelConfiguration: emptyTunnelConfiguration) { error in
|
||||||
|
// self.dispatchQueue.async {
|
||||||
|
// if let error {
|
||||||
|
// wg_log(.error, message: "Failed to start an empty tunnel")
|
||||||
|
// completionHandler(error)
|
||||||
|
// } else {
|
||||||
|
// wg_log(.info, message: "Started an empty tunnel")
|
||||||
|
// self.tunnelAdapterDidStart()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// let settings = NETunnelNetworkSettings(tunnelRemoteAddress: "1.1.1.1")
|
||||||
|
//
|
||||||
|
// self.setTunnelNetworkSettings(settings) { error in
|
||||||
|
// completionHandler(error)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// private func tunnelAdapterDidStart() {
|
||||||
|
// dispatchPrecondition(condition: .onQueue(dispatchQueue))
|
||||||
|
// // ...
|
||||||
|
// }
|
||||||
|
|
||||||
|
func stopWireguard(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
||||||
|
wg_log(.info, message: "Stopping tunnel: reason: \(reason.description)")
|
||||||
|
|
||||||
|
wgAdapter.stop { error in
|
||||||
|
ErrorNotifier.removeLastErrorFile()
|
||||||
|
|
||||||
|
if let error {
|
||||||
|
wg_log(.error, message: "Failed to stop WireGuard adapter: \(error.localizedDescription)")
|
||||||
|
}
|
||||||
|
completionHandler()
|
||||||
|
|
||||||
|
#if os(macOS)
|
||||||
|
// HACK: This is a filthy hack to work around Apple bug 32073323 (dup'd by us as 47526107).
|
||||||
|
// Remove it when they finally fix this upstream and the fix has been rolled out to
|
||||||
|
// sufficient quantities of users.
|
||||||
|
exit(0)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@ struct Constants {
|
|||||||
static let ovpnConfigKey = "ovpn"
|
static let ovpnConfigKey = "ovpn"
|
||||||
static let wireGuardConfigKey = "wireguard"
|
static let wireGuardConfigKey = "wireguard"
|
||||||
static let loggerTag = "NET"
|
static let loggerTag = "NET"
|
||||||
|
|
||||||
static let kActionStart = "start"
|
static let kActionStart = "start"
|
||||||
static let kActionRestart = "restart"
|
static let kActionRestart = "restart"
|
||||||
static let kActionStop = "stop"
|
static let kActionStop = "stop"
|
||||||
@@ -34,78 +34,68 @@ struct Constants {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class PacketTunnelProvider: NEPacketTunnelProvider {
|
class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||||
private lazy var wgAdapter = {
|
lazy var wgAdapter = {
|
||||||
WireGuardAdapter(with: self) { logLevel, message in
|
WireGuardAdapter(with: self) { logLevel, message in
|
||||||
wg_log(logLevel.osLogLevel, message: message)
|
wg_log(logLevel.osLogLevel, message: message)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
private lazy var ovpnAdapter: OpenVPNAdapter = {
|
lazy var ovpnAdapter: OpenVPNAdapter = {
|
||||||
let adapter = OpenVPNAdapter()
|
let adapter = OpenVPNAdapter()
|
||||||
adapter.delegate = self
|
adapter.delegate = self
|
||||||
return adapter
|
return adapter
|
||||||
}()
|
}()
|
||||||
|
|
||||||
/// Internal queue.
|
/// Internal queue.
|
||||||
private let dispatchQueue = DispatchQueue(label: "PacketTunnel", qos: .utility)
|
private let dispatchQueue = DispatchQueue(label: "PacketTunnel", qos: .utility)
|
||||||
|
|
||||||
private var openVPNConfig: Data?
|
|
||||||
var splitTunnelType: Int!
|
var splitTunnelType: Int!
|
||||||
var splitTunnelSites: [String]!
|
var splitTunnelSites: [String]!
|
||||||
|
|
||||||
let vpnReachability = OpenVPNReachability()
|
let vpnReachability = OpenVPNReachability()
|
||||||
|
|
||||||
var startHandler: ((Error?) -> Void)?
|
var startHandler: ((Error?) -> Void)?
|
||||||
var stopHandler: (() -> Void)?
|
var stopHandler: (() -> Void)?
|
||||||
var protoType: TunnelProtoType = .none
|
var protoType: TunnelProtoType = .none
|
||||||
|
|
||||||
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||||
let tmpStr = String(data: messageData, encoding: .utf8)!
|
guard let message = String(data: messageData, encoding: .utf8) else {
|
||||||
wg_log(.error, message: tmpStr)
|
if let completionHandler {
|
||||||
|
completionHandler(nil)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
neLog(.info, title: "App said: ", message: message)
|
||||||
|
|
||||||
guard let message = try? JSONSerialization.jsonObject(with: messageData, options: []) as? [String: Any] else {
|
guard let message = try? JSONSerialization.jsonObject(with: messageData, options: []) as? [String: Any] else {
|
||||||
log(.error, message: "Failed to serialize message from app")
|
neLog(.error, message: "Failed to serialize message from app")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let completionHandler else {
|
guard let completionHandler else {
|
||||||
log(.error, message: "Missing message completion handler")
|
neLog(.error, message: "Missing message completion handler")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
guard let action = message[Constants.kMessageKeyAction] as? String else {
|
guard let action = message[Constants.kMessageKeyAction] as? String else {
|
||||||
log(.error, message: "Missing action key in app message")
|
neLog(.error, message: "Missing action key in app message")
|
||||||
completionHandler(nil)
|
completionHandler(nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if action == Constants.kActionStatus {
|
if action == Constants.kActionStatus {
|
||||||
handleStatusAppMessage(messageData, completionHandler: completionHandler)
|
handleStatusAppMessage(messageData, completionHandler: completionHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
if action == Constants.kActionStart {
|
|
||||||
// splitTunnelType = message[Constants.kMessageKeySplitTunnelType] as? String
|
|
||||||
// splitTunnelSites = message[Constants.kMessageKeySplitTunnelSites] as? String
|
|
||||||
}
|
|
||||||
|
|
||||||
let callbackWrapper: (NSNumber?) -> Void = { errorCode in
|
|
||||||
// let tunnelId = self.tunnelConfig?.id ?? ""
|
|
||||||
let response: [String: Any] = [
|
|
||||||
Constants.kMessageKeyAction: action,
|
|
||||||
Constants.kMessageKeyErrorCode: errorCode ?? NSNull(),
|
|
||||||
Constants.kMessageKeyTunnelId: 0
|
|
||||||
]
|
|
||||||
|
|
||||||
completionHandler(try? JSONSerialization.data(withJSONObject: response, options: []))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) {
|
override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) {
|
||||||
dispatchQueue.async {
|
dispatchQueue.async {
|
||||||
let activationAttemptId = options?[Constants.kActivationAttemptId] as? String
|
let activationAttemptId = options?[Constants.kActivationAttemptId] as? String
|
||||||
let errorNotifier = ErrorNotifier(activationAttemptId: activationAttemptId)
|
let errorNotifier = ErrorNotifier(activationAttemptId: activationAttemptId)
|
||||||
|
|
||||||
log(.info, message: "PacketTunnelProvider startTunnel")
|
neLog(.info, message: "Start tunnel")
|
||||||
|
|
||||||
if let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol {
|
if let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol {
|
||||||
let providerConfiguration = protocolConfiguration.providerConfiguration
|
let providerConfiguration = protocolConfiguration.providerConfiguration
|
||||||
if (providerConfiguration?[Constants.ovpnConfigKey] as? Data) != nil {
|
if (providerConfiguration?[Constants.ovpnConfigKey] as? Data) != nil {
|
||||||
@@ -116,7 +106,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||||||
} else {
|
} else {
|
||||||
self.protoType = .none
|
self.protoType = .none
|
||||||
}
|
}
|
||||||
|
|
||||||
switch self.protoType {
|
switch self.protoType {
|
||||||
case .wireguard:
|
case .wireguard:
|
||||||
self.startWireguard(activationAttemptId: activationAttemptId,
|
self.startWireguard(activationAttemptId: activationAttemptId,
|
||||||
@@ -132,7 +122,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
||||||
dispatchQueue.async {
|
dispatchQueue.async {
|
||||||
switch self.protoType {
|
switch self.protoType {
|
||||||
@@ -148,7 +138,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleStatusAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
func handleStatusAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||||
switch protoType {
|
switch protoType {
|
||||||
case .wireguard:
|
case .wireguard:
|
||||||
@@ -162,291 +152,18 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Private methods
|
|
||||||
private func startWireguard(activationAttemptId: String?,
|
|
||||||
errorNotifier: ErrorNotifier,
|
|
||||||
completionHandler: @escaping (Error?) -> Void) {
|
|
||||||
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
|
|
||||||
let providerConfiguration = protocolConfiguration.providerConfiguration,
|
|
||||||
let wgConfigData: Data = providerConfiguration[Constants.wireGuardConfigKey] as? Data else {
|
|
||||||
wg_log(.error, message: "Can't start WireGuard config missing")
|
|
||||||
completionHandler(nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
let wgConfig = try JSONDecoder().decode(WGConfig.self, from: wgConfigData)
|
|
||||||
let wgConfigStr = wgConfig.str
|
|
||||||
log(.info, message: "wgConfig: \(wgConfig.redux.replacingOccurrences(of: "\n", with: " "))")
|
|
||||||
|
|
||||||
let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: wgConfigStr)
|
|
||||||
|
|
||||||
if tunnelConfiguration.peers.first!.allowedIPs
|
|
||||||
.map({ $0.stringRepresentation })
|
|
||||||
.joined(separator: ", ") == "0.0.0.0/0, ::/0" {
|
|
||||||
if wgConfig.splitTunnelType == 1 {
|
|
||||||
for index in tunnelConfiguration.peers.indices {
|
|
||||||
tunnelConfiguration.peers[index].allowedIPs.removeAll()
|
|
||||||
var allowedIPs = [IPAddressRange]()
|
|
||||||
|
|
||||||
for allowedIPString in wgConfig.splitTunnelSites {
|
|
||||||
if let allowedIP = IPAddressRange(from: allowedIPString) {
|
|
||||||
allowedIPs.append(allowedIP)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tunnelConfiguration.peers[index].allowedIPs = allowedIPs
|
|
||||||
}
|
|
||||||
} else if wgConfig.splitTunnelType == 2 {
|
|
||||||
for index in tunnelConfiguration.peers.indices {
|
|
||||||
var excludeIPs = [IPAddressRange]()
|
|
||||||
|
|
||||||
for excludeIPString in wgConfig.splitTunnelSites {
|
|
||||||
if let excludeIP = IPAddressRange(from: excludeIPString) {
|
|
||||||
excludeIPs.append(excludeIP)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tunnelConfiguration.peers[index].excludeIPs = excludeIPs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
wg_log(.info, message: "Starting wireguard tunnel from the " +
|
|
||||||
(activationAttemptId == nil ? "OS directly, rather than the app" : "app"))
|
|
||||||
|
|
||||||
// Start the tunnel
|
|
||||||
wgAdapter.start(tunnelConfiguration: tunnelConfiguration) { adapterError in
|
|
||||||
guard let adapterError else {
|
|
||||||
let interfaceName = self.wgAdapter.interfaceName ?? "unknown"
|
|
||||||
wg_log(.info, message: "Tunnel interface is \(interfaceName)")
|
|
||||||
completionHandler(nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch adapterError {
|
|
||||||
case .cannotLocateTunnelFileDescriptor:
|
|
||||||
wg_log(.error, staticMessage: "Starting tunnel failed: could not determine file descriptor")
|
|
||||||
errorNotifier.notify(PacketTunnelProviderError.couldNotDetermineFileDescriptor)
|
|
||||||
completionHandler(PacketTunnelProviderError.couldNotDetermineFileDescriptor)
|
|
||||||
case .dnsResolution(let dnsErrors):
|
|
||||||
let hostnamesWithDnsResolutionFailure = dnsErrors.map { $0.address }
|
|
||||||
.joined(separator: ", ")
|
|
||||||
wg_log(.error, message:
|
|
||||||
"DNS resolution failed for the following hostnames: \(hostnamesWithDnsResolutionFailure)")
|
|
||||||
errorNotifier.notify(PacketTunnelProviderError.dnsResolutionFailure)
|
|
||||||
completionHandler(PacketTunnelProviderError.dnsResolutionFailure)
|
|
||||||
case .setNetworkSettings(let error):
|
|
||||||
wg_log(.error, message:
|
|
||||||
"Starting tunnel failed with setTunnelNetworkSettings returning \(error.localizedDescription)")
|
|
||||||
errorNotifier.notify(PacketTunnelProviderError.couldNotSetNetworkSettings)
|
|
||||||
completionHandler(PacketTunnelProviderError.couldNotSetNetworkSettings)
|
|
||||||
case .startWireGuardBackend(let errorCode):
|
|
||||||
wg_log(.error, message: "Starting tunnel failed with wgTurnOn returning \(errorCode)")
|
|
||||||
errorNotifier.notify(PacketTunnelProviderError.couldNotStartBackend)
|
|
||||||
completionHandler(PacketTunnelProviderError.couldNotStartBackend)
|
|
||||||
case .invalidState:
|
|
||||||
fatalError()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
log(.error, message: "Can't parse WG config: \(error.localizedDescription)")
|
|
||||||
completionHandler(nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func startOpenVPN(completionHandler: @escaping (Error?) -> Void) {
|
|
||||||
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
|
|
||||||
let providerConfiguration = protocolConfiguration.providerConfiguration,
|
|
||||||
let openVPNConfigData = providerConfiguration[Constants.ovpnConfigKey] as? Data else {
|
|
||||||
wg_log(.error, message: "Can't start startOpenVPN()")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
log(.info, message: "providerConfiguration: \(String(decoding: openVPNConfigData, as: UTF8.self).replacingOccurrences(of: "\n", with: " "))")
|
|
||||||
|
|
||||||
let openVPNConfig = try JSONDecoder().decode(OpenVPNConfig.self, from: openVPNConfigData)
|
|
||||||
log(.info, message: "openVPNConfig: \(openVPNConfig.str.replacingOccurrences(of: "\n", with: " "))")
|
|
||||||
let ovpnConfiguration = Data(openVPNConfig.config.utf8)
|
|
||||||
setupAndlaunchOpenVPN(withConfig: ovpnConfiguration, completionHandler: completionHandler)
|
|
||||||
} catch {
|
|
||||||
log(.error, message: "Can't parse OpenVPN config: \(error.localizedDescription)")
|
|
||||||
|
|
||||||
if let underlyingError = (error as NSError).userInfo[NSUnderlyingErrorKey] as? NSError {
|
|
||||||
log(.error, message: "Can't parse OpenVPN config: \(underlyingError.localizedDescription)")
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func stopWireguard(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
|
||||||
wg_log(.info, staticMessage: "Stopping tunnel")
|
|
||||||
|
|
||||||
wgAdapter.stop { error in
|
|
||||||
ErrorNotifier.removeLastErrorFile()
|
|
||||||
|
|
||||||
if let error {
|
|
||||||
wg_log(.error, message: "Failed to stop WireGuard adapter: \(error.localizedDescription)")
|
|
||||||
}
|
|
||||||
completionHandler()
|
|
||||||
|
|
||||||
#if os(macOS)
|
|
||||||
// HACK: This is a filthy hack to work around Apple bug 32073323 (dup'd by us as 47526107).
|
|
||||||
// Remove it when they finally fix this upstream and the fix has been rolled out to
|
|
||||||
// sufficient quantities of users.
|
|
||||||
exit(0)
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func stopOpenVPN(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
|
||||||
stopHandler = completionHandler
|
|
||||||
if vpnReachability.isTracking {
|
|
||||||
vpnReachability.stopTracking()
|
|
||||||
}
|
|
||||||
ovpnAdapter.disconnect()
|
|
||||||
}
|
|
||||||
|
|
||||||
func handleWireguardStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
|
||||||
guard let completionHandler = completionHandler else { return }
|
|
||||||
wgAdapter.getRuntimeConfiguration { settings in
|
|
||||||
var data: Data?
|
|
||||||
if let settings {
|
|
||||||
data = settings.data(using: .utf8)!
|
|
||||||
}
|
|
||||||
|
|
||||||
let components = settings!.components(separatedBy: "\n")
|
|
||||||
|
|
||||||
var settingsDictionary: [String: String] = [:]
|
|
||||||
for component in components {
|
|
||||||
let pair = component.components(separatedBy: "=")
|
|
||||||
if pair.count == 2 {
|
|
||||||
settingsDictionary[pair[0]] = pair[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let response: [String: Any] = [
|
|
||||||
"rx_bytes": settingsDictionary["rx_bytes"] ?? "0",
|
|
||||||
"tx_bytes": settingsDictionary["tx_bytes"] ?? "0"
|
|
||||||
]
|
|
||||||
|
|
||||||
completionHandler(try? JSONSerialization.data(withJSONObject: response, options: []))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func handleWireguardAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
|
||||||
guard let completionHandler = completionHandler else { return }
|
|
||||||
if messageData.count == 1 && messageData[0] == 0 {
|
|
||||||
wgAdapter.getRuntimeConfiguration { settings in
|
|
||||||
var data: Data?
|
|
||||||
if let settings {
|
|
||||||
data = settings.data(using: .utf8)!
|
|
||||||
}
|
|
||||||
completionHandler(data)
|
|
||||||
}
|
|
||||||
} else if messageData.count >= 1 {
|
|
||||||
// Updates the tunnel configuration and responds with the active configuration
|
|
||||||
wg_log(.info, message: "Switching tunnel configuration")
|
|
||||||
guard let configString = String(data: messageData, encoding: .utf8)
|
|
||||||
else {
|
|
||||||
completionHandler(nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: configString)
|
|
||||||
wgAdapter.update(tunnelConfiguration: tunnelConfiguration) { error in
|
|
||||||
if let error {
|
|
||||||
wg_log(.error, message: "Failed to switch tunnel configuration: \(error.localizedDescription)")
|
|
||||||
completionHandler(nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
self.wgAdapter.getRuntimeConfiguration { settings in
|
|
||||||
var data: Data?
|
|
||||||
if let settings {
|
|
||||||
data = settings.data(using: .utf8)!
|
|
||||||
}
|
|
||||||
completionHandler(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
completionHandler(nil)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
completionHandler(nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func handleOpenVPNStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
|
||||||
guard let completionHandler = completionHandler else { return }
|
|
||||||
let bytesin = ovpnAdapter.transportStatistics.bytesIn
|
|
||||||
let bytesout = ovpnAdapter.transportStatistics.bytesOut
|
|
||||||
|
|
||||||
let response: [String: Any] = [
|
|
||||||
"rx_bytes": bytesin,
|
|
||||||
"tx_bytes": bytesout
|
|
||||||
]
|
|
||||||
|
|
||||||
completionHandler(try? JSONSerialization.data(withJSONObject: response, options: []))
|
|
||||||
}
|
|
||||||
|
|
||||||
private func setupAndlaunchOpenVPN(withConfig ovpnConfiguration: Data,
|
|
||||||
withShadowSocks viaSS: Bool = false,
|
|
||||||
completionHandler: @escaping (Error?) -> Void) {
|
|
||||||
wg_log(.info, message: "setupAndlaunchOpenVPN")
|
|
||||||
|
|
||||||
let str = String(decoding: ovpnConfiguration, as: UTF8.self)
|
|
||||||
|
|
||||||
let configuration = OpenVPNConfiguration()
|
|
||||||
configuration.fileContent = ovpnConfiguration
|
|
||||||
if str.contains("cloak") {
|
|
||||||
configuration.setPTCloak()
|
|
||||||
}
|
|
||||||
|
|
||||||
let evaluation: OpenVPNConfigurationEvaluation
|
|
||||||
do {
|
|
||||||
evaluation = try ovpnAdapter.apply(configuration: configuration)
|
|
||||||
|
|
||||||
} catch {
|
|
||||||
completionHandler(error)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !evaluation.autologin {
|
|
||||||
wg_log(.info, message: "Implement login with user credentials")
|
|
||||||
}
|
|
||||||
|
|
||||||
vpnReachability.startTracking { [weak self] status in
|
|
||||||
guard status == .reachableViaWiFi else { return }
|
|
||||||
self?.ovpnAdapter.reconnect(afterTimeInterval: 5)
|
|
||||||
}
|
|
||||||
|
|
||||||
startHandler = completionHandler
|
|
||||||
ovpnAdapter.connect(using: packetFlow)
|
|
||||||
|
|
||||||
// let ifaces = Interface.allInterfaces()
|
|
||||||
// .filter { $0.family == .ipv4 }
|
|
||||||
// .map { iface in iface.name }
|
|
||||||
|
|
||||||
// wg_log(.error, message: "Available TUN Interfaces: \(ifaces)")
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Network observing methods
|
// MARK: Network observing methods
|
||||||
|
|
||||||
private func startListeningForNetworkChanges() {
|
private func startListeningForNetworkChanges() {
|
||||||
stopListeningForNetworkChanges()
|
stopListeningForNetworkChanges()
|
||||||
addObserver(self, forKeyPath: Constants.kDefaultPathKey, options: .old, context: nil)
|
addObserver(self, forKeyPath: Constants.kDefaultPathKey, options: .old, context: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func stopListeningForNetworkChanges() {
|
private func stopListeningForNetworkChanges() {
|
||||||
removeObserver(self, forKeyPath: Constants.kDefaultPathKey)
|
removeObserver(self, forKeyPath: Constants.kDefaultPathKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func observeValue(forKeyPath keyPath: String?,
|
override func observeValue(forKeyPath keyPath: String?,
|
||||||
of object: Any?,
|
of object: Any?,
|
||||||
change: [NSKeyValueChangeKey: Any]?,
|
change: [NSKeyValueChangeKey: Any]?,
|
||||||
@@ -466,48 +183,13 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
|||||||
self.handle(networkChange: self.defaultPath!) { _ in }
|
self.handle(networkChange: self.defaultPath!) { _ in }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func handle(networkChange changePath: NWPath, completion: @escaping (Error?) -> Void) {
|
private func handle(networkChange changePath: NWPath, completion: @escaping (Error?) -> Void) {
|
||||||
wg_log(.info, message: "Tunnel restarted.")
|
wg_log(.info, message: "Tunnel restarted.")
|
||||||
startTunnel(options: nil, completionHandler: completion)
|
startTunnel(options: nil, completionHandler: completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func startEmptyTunnel(completionHandler: @escaping (Error?) -> Void) {
|
|
||||||
dispatchPrecondition(condition: .onQueue(dispatchQueue))
|
|
||||||
|
|
||||||
let emptyTunnelConfiguration = TunnelConfiguration(
|
|
||||||
name: nil,
|
|
||||||
interface: InterfaceConfiguration(privateKey: PrivateKey()),
|
|
||||||
peers: []
|
|
||||||
)
|
|
||||||
|
|
||||||
wgAdapter.start(tunnelConfiguration: emptyTunnelConfiguration) { error in
|
|
||||||
self.dispatchQueue.async {
|
|
||||||
if let error {
|
|
||||||
log(.error, message: "Failed to start an empty tunnel")
|
|
||||||
completionHandler(error)
|
|
||||||
} else {
|
|
||||||
log(.info, message: "Started an empty tunnel")
|
|
||||||
self.tunnelAdapterDidStart()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let settings = NETunnelNetworkSettings(tunnelRemoteAddress: "1.1.1.1")
|
|
||||||
|
|
||||||
self.setTunnelNetworkSettings(settings) { error in
|
|
||||||
completionHandler(error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func tunnelAdapterDidStart() {
|
|
||||||
dispatchPrecondition(condition: .onQueue(dispatchQueue))
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension NEPacketTunnelFlow: OpenVPNAdapterPacketFlow {}
|
|
||||||
|
|
||||||
extension WireGuardLogLevel {
|
extension WireGuardLogLevel {
|
||||||
var osLogLevel: OSLogType {
|
var osLogLevel: OSLogType {
|
||||||
switch self {
|
switch self {
|
||||||
@@ -518,3 +200,46 @@ extension WireGuardLogLevel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension NEProviderStopReason: CustomStringConvertible {
|
||||||
|
public var description: String {
|
||||||
|
switch self {
|
||||||
|
case .none:
|
||||||
|
return "No specific reason"
|
||||||
|
case .userInitiated:
|
||||||
|
return "The user stopped the NE"
|
||||||
|
case .providerFailed:
|
||||||
|
return "The NE failed to function correctly"
|
||||||
|
case .noNetworkAvailable:
|
||||||
|
return "No network connectivity is currently available"
|
||||||
|
case .unrecoverableNetworkChange:
|
||||||
|
return "The device’s network connectivity changed"
|
||||||
|
case .providerDisabled:
|
||||||
|
return "The NE was disabled"
|
||||||
|
case .authenticationCanceled:
|
||||||
|
return "The authentication process was canceled"
|
||||||
|
case .configurationFailed:
|
||||||
|
return "The VPNC is invalid"
|
||||||
|
case .idleTimeout:
|
||||||
|
return "The session timed out"
|
||||||
|
case .configurationDisabled:
|
||||||
|
return "The VPNC was disabled"
|
||||||
|
case .configurationRemoved:
|
||||||
|
return "The VPNC was removed"
|
||||||
|
case .superceded:
|
||||||
|
return "VPNC was superceded by a higher-priority VPNC"
|
||||||
|
case .userLogout:
|
||||||
|
return "The user logged out"
|
||||||
|
case .userSwitch:
|
||||||
|
return "The current console user changed"
|
||||||
|
case .connectionFailed:
|
||||||
|
return "The connection failed"
|
||||||
|
case .sleep:
|
||||||
|
return "A stop reason indicating the VPNC enabled disconnect on sleep and the device went to sleep"
|
||||||
|
case .appUpdate:
|
||||||
|
return "appUpdat"
|
||||||
|
@unknown default:
|
||||||
|
return "@unknown default"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
#import <UIKit/UIKit.h>
|
#import <UIKit/UIKit.h>
|
||||||
|
|
||||||
@interface QtAppDelegate : UIResponder <UIApplicationDelegate>
|
@interface QIOSApplicationDelegate
|
||||||
|
@end
|
||||||
|
|
||||||
|
@interface QIOSApplicationDelegate (AmneziaVPNDelegate)
|
||||||
@end
|
@end
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user