Compare commits

..

1 Commits

Author SHA1 Message Date
vladimir.kuznetsov bbcf928a66 added tab navigation to the share connection drawer 2024-02-19 19:17:24 +05:00
292 changed files with 6816 additions and 20979 deletions
+7 -12
View File
@@ -14,8 +14,8 @@ jobs:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
env: env:
QT_VERSION: 6.6.2 QT_VERSION: 6.5.1
QIF_VERSION: 4.7 QIF_VERSION: 4.6
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.6.2 QT_VERSION: 6.5.1
QIF_VERSION: 4.7 QIF_VERSION: 4.6
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.6.2 QT_VERSION: 6.5.2
CC: cc CC: cc
CXX: c++ CXX: c++
@@ -245,15 +245,10 @@ 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:
@@ -291,7 +286,7 @@ jobs:
env: env:
ANDROID_BUILD_PLATFORM: android-34 ANDROID_BUILD_PLATFORM: android-34
QT_VERSION: 6.6.2 QT_VERSION: 6.6.1
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools' QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
steps: steps:
-3
View File
@@ -131,6 +131,3 @@ client/3rd/ShadowSocks/ss_ios.xcconfig
# UML generated pics # UML generated pics
out/ out/
# CMake files
CMakeFiles/
+2 -2
View File
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN) set(PROJECT AmneziaVPN)
project(${PROJECT} VERSION 4.5.2.0 project(${PROJECT} VERSION 4.4.0.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 51) set(APP_ANDROID_VERSION_CODE 46)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux") set(MZ_PLATFORM_NAME "linux")
+35 -39
View File
@@ -7,15 +7,13 @@
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.
- 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, IKEv2 protocols support.
- 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 the client to enable VPN only for them (only for desktops) - Split tunneling support - add any sites to 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)
@@ -23,13 +21,13 @@ Amnezia is an open-source VPN client, with a key feature that enables you to dep
## Tech ## Tech
AmneziaVPN uses several open-source projects to work: AmneziaVPN uses a number of 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 from Qt Creator - [LibSsh](https://libssh.org) - forked form Qt Creator
- and more... - and more...
## Checking out the source code ## Checking out the source code
@@ -45,15 +43,14 @@ 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.
Check deploy folder for build scripts. ### How to build iOS app from source code on MacOS
### 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 [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 in [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
@@ -62,18 +59,18 @@ Check deploy folder for build scripts.
- Qt Multimedia - Qt Multimedia
- Qt Remote Objects - Qt Remote Objects
3. Install CMake if required. We recommend CMake version 3.25. You can install CMake [here](https://cmake.org/download/) 3. Install cmake is require. We recommend cmake version 3.25. You can install cmake in [here](https://cmake.org/download/)
4. You also need to install go >= v1.16. If you don't have it installed already, 4. You also need to install go >= v1.16. If you don't have it done 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.
The latest version is recommended. Install gomobile 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 the project 5. Build 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"
@@ -91,63 +88,62 @@ 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
``` ```
6. Open the XCode project. You can then run /test/archive/ship the app. 5. Open XCode project. You can then run/test/archive/ship the app.
If the build fails with the following error If 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 the above error persists on your M1 Mac, then most probably you need to install arch based CMake if above error still persists on you 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 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 Build might fail with "source files not found" error the first time you try it, because modern XCode build system compiles
require them. In this case, simply restart the build. dependencies in parallel, and some dependencies end up being built after the ones that
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, 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 installed, 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 '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!  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!
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`.
That's it! You should be ready to compile the project from QT Creator! 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.
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!
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>`. 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.
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
View File
@@ -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(RSA* rsa, const QMap<QByteArray, QByteArray>& additionalData, X509* generateSelfSignedCertificate(const 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);
+1 -1
View File
@@ -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(RSA* rsa, const QMap<QByteArray, QByteArray>& additionalData, X509* QSimpleCrypto::QX509::generateSelfSignedCertificate(const 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)
+2 -21
View File
@@ -15,15 +15,6 @@ 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()
@@ -63,12 +54,10 @@ 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_RU.ts ${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_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_ar.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)
@@ -120,9 +109,7 @@ 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
@@ -132,7 +119,6 @@ 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
@@ -162,15 +148,12 @@ 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
@@ -300,7 +283,6 @@ 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
) )
@@ -312,7 +294,6 @@ 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()
+48 -57
View File
@@ -9,15 +9,14 @@
#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
@@ -25,14 +24,13 @@
#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, int timeout, AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecondary, SingleApplication::Options options,
const QString &userData) int timeout, const QString &userData)
: SingleApplication(argc, argv, allowSecondary, options, timeout, userData) : SingleApplication(argc, argv, allowSecondary, options, timeout, userData)
#endif #endif
{ {
@@ -45,12 +43,12 @@ AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecond
s.setValue("permFixed", true); s.setValue("permFixed", true);
} }
QString configLoc1 = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/" + ORGANIZATION_NAME + "/" QString configLoc1 = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/"
+ APPLICATION_NAME + ".conf"; + ORGANIZATION_NAME + "/" + APPLICATION_NAME + ".conf";
QFile::setPermissions(configLoc1, QFileDevice::ReadOwner | QFileDevice::WriteOwner); QFile::setPermissions(configLoc1, QFileDevice::ReadOwner | QFileDevice::WriteOwner);
QString configLoc2 = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/" + ORGANIZATION_NAME + "/" QString configLoc2 = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/"
+ APPLICATION_NAME + "/" + APPLICATION_NAME + ".conf"; + ORGANIZATION_NAME + "/" + APPLICATION_NAME + "/" + APPLICATION_NAME + ".conf";
QFile::setPermissions(configLoc2, QFileDevice::ReadOwner | QFileDevice::WriteOwner); QFile::setPermissions(configLoc2, QFileDevice::ReadOwner | QFileDevice::WriteOwner);
#endif #endif
@@ -83,7 +81,8 @@ void AmneziaApplication::init()
m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance()); m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance());
m_vpnConnection.reset(new VpnConnection(m_settings)); m_configurator = std::shared_ptr<VpnConfigurator>(new VpnConfigurator(m_settings, this));
m_vpnConnection.reset(new VpnConnection(m_settings, m_configurator));
m_vpnConnection->moveToThread(&m_vpnConnectionThread); m_vpnConnection->moveToThread(&m_vpnConnectionThread);
m_vpnConnectionThread.start(); m_vpnConnectionThread.start();
@@ -98,14 +97,8 @@ 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);
AndroidController::instance()->setScreenshotsEnabled(m_settings->isScreenshotsEnabled()); connect(AndroidController::instance(), &AndroidController::initConnectionState, this,
connect(m_settings.get(), &Settings::screenshotsEnabledChanged, AndroidController::instance(), &AndroidController::setScreenshotsEnabled); [this](Vpn::ConnectionState state) {
connect(m_settings.get(), &Settings::serverRemoved, AndroidController::instance(), &AndroidController::resetLastServer);
connect(m_settings.get(), &Settings::settingsCleared, []() { AndroidController::instance()->resetLastServer(-1); });
connect(AndroidController::instance(), &AndroidController::initConnectionState, this, [this](Vpn::ConnectionState state) {
m_connectionController->onConnectionStateChanged(state); m_connectionController->onConnectionStateChanged(state);
if (m_vpnConnection) if (m_vpnConnection)
m_vpnConnection->restoreConnection(); m_vpnConnection->restoreConnection();
@@ -119,8 +112,6 @@ 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
@@ -136,10 +127,6 @@ 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));
@@ -147,12 +134,14 @@ 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(), &PageController::raiseMainWindow); connect(m_notificationHandler.get(), &NotificationHandler::raiseRequested, m_pageController.get(),
&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(), &NotificationHandler::onTranslationsUpdated); connect(this, &AmneziaApplication::translationsUpdated, m_notificationHandler.get(),
&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));
@@ -225,8 +214,7 @@ 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();
} }
@@ -298,15 +286,10 @@ 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(), &ContainersModel::updateModel); connect(m_serversModel.get(), &ServersModel::containersUpdated, m_containersModel.get(),
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());
@@ -316,9 +299,6 @@ 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());
@@ -337,9 +317,6 @@ 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());
@@ -352,27 +329,28 @@ 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( m_connectionController.reset(new ConnectionController(m_serversModel, m_containersModel, m_vpnConnection));
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(m_connectionController.get(), &ConnectionController::connectionErrorOccurred, this, [this](const QString &errorMessage) { connect(this, &AmneziaApplication::translationsUpdated, m_connectionController.get(),
emit m_pageController->showErrorMessage(errorMessage); &ConnectionController::onTranslationsUpdated);
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_clientManagementModel, m_settings)); m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, 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);
@@ -384,23 +362,36 @@ 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_settings)); m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_clientManagementModel,
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_appSplitTunnelingModel, m_settings)); new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, 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(), &ServersModel::toggleAmneziaDns); connect(m_settingsController.get(), &SettingsController::amneziaDnsToggled, m_serversModel.get(),
&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](const QString &errorMessage) {
if (m_connectionController->isConnectionInProgress()) {
emit m_pageController->showErrorMessage(errorMessage);
}
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
});
connect(m_apiController.get(), &ApiController::updateFinished, m_connectionController.get(),
&ConnectionController::toggleConnection);
} }
+5 -7
View File
@@ -14,6 +14,8 @@
#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"
@@ -22,7 +24,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/appSplitTunnelingController.h" #include "ui/controllers/apiController.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"
@@ -34,13 +36,11 @@
#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()))
@@ -83,6 +83,7 @@ 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;
@@ -91,18 +92,15 @@ 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
@@ -123,7 +121,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<AppSplitTunnelingController> m_appSplitTunnelingController; QScopedPointer<ApiController> m_apiController;
}; };
#endif // AMNEZIA_APPLICATION_H #endif // AMNEZIA_APPLICATION_H
+3 -21
View File
@@ -24,7 +24,9 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED" /> <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"
@@ -54,10 +56,6 @@
<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%% --" />
@@ -148,22 +146,6 @@
</intent-filter> </intent-filter>
</service> </service>
<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>
<provider <provider
android:name="androidx.core.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="org.amnezia.vpn.qtprovider" android:authorities="org.amnezia.vpn.qtprovider"
+1 -2
View File
@@ -64,9 +64,8 @@ class Awg : Wireguard() {
val configDataJson = config.getJSONObject("awg_config_data") val configDataJson = config.getJSONObject("awg_config_data")
val configData = parseConfigData(configDataJson.getString("config")) val configData = parseConfigData(configDataJson.getString("config"))
return AwgConfig.build { return AwgConfig.build {
configWireguard(configData, configDataJson) configWireguard(configData)
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()) }
-1
View File
@@ -111,5 +111,4 @@ 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)
} }
-2
View File
@@ -6,7 +6,6 @@ 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"
@@ -19,7 +18,6 @@ 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,7 +79,6 @@ 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,22 +64,6 @@ 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)
@@ -117,11 +101,6 @@ 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,7 +16,6 @@ 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,
@@ -32,7 +31,6 @@ 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,
@@ -47,7 +45,6 @@ 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
@@ -91,9 +88,6 @@ 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,10 +28,6 @@ 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)])
-12
View File
@@ -1,12 +0,0 @@
<?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>
-12
View File
@@ -1,12 +0,0 @@
<?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,13 +1,11 @@
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
@@ -16,8 +14,6 @@ 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
@@ -25,15 +21,14 @@ 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 kotlinx.coroutines.runBlocking import org.amnezia.vpn.protocol.ProtocolState
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
@@ -41,11 +36,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() {
@@ -63,17 +58,25 @@ 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.STATUS_CHANGED -> { ServiceEvent.CONNECTED -> {
msg.data?.getStatus()?.let { (state) -> QtAndroidController.onVpnConnected()
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 { QtAndroidController.onStatus(it) } msg.data?.getStatus()?.let { (state) ->
QtAndroidController.onStatus(state.ordinal)
}
} }
} }
@@ -84,7 +87,7 @@ class AmneziaActivity : QtActivity() {
} }
ServiceEvent.ERROR -> { ServiceEvent.ERROR -> {
msg.data?.getString(MSG_ERROR)?.let { error -> msg.data?.getString(ERROR_MSG)?.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
@@ -106,15 +109,14 @@ 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 { Action.REGISTER_CLIENT.packToMessage().apply {
putString(MSG_CLIENT_NAME, ACTIVITY_MESSENGER_NAME)
},
replyTo = activityMessenger replyTo = activityMessenger
) }
}
isServiceConnected = true isServiceConnected = true
if (isWaitingStatus) { if (isWaitingStatus) {
vpnServiceMessenger.send(Action.REQUEST_STATUS, replyTo = activityMessenger) vpnServiceMessenger.send(Action.REQUEST_STATUS)
} }
} }
@@ -124,7 +126,6 @@ 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?) {
@@ -147,11 +148,8 @@ 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(
"VpnService", onDeadObjectException = ::doUnbindService,
onDeadObjectException = { messengerName = "VpnService"
doUnbindService()
doBindService()
}
) )
intent?.let(::processIntent) intent?.let(::processIntent)
} }
@@ -222,13 +220,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, resources.getText(R.string.vpnGranted), Toast.LENGTH_LONG).show() Toast.makeText(this, "Vpn permission granted", 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")
showOnVpnPermissionRejectDialog() Toast.makeText(this, "Vpn permission denied", Toast.LENGTH_LONG).show()
checkVpnPermissionCallbacks?.run { onFail() } checkVpnPermissionCallbacks?.run { onFail() }
} }
} }
@@ -246,9 +244,10 @@ 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 and BIND_AUTO_CREATE) bindService(it, serviceConnection, BIND_ABOVE_CLIENT)
} }
isInBoundState = true isInBoundState = true
handleBindTimeout()
} }
@MainThread @MainThread
@@ -257,14 +256,26 @@ class AmneziaActivity : QtActivity() {
Log.d(TAG, "Unbind service") Log.d(TAG, "Unbind service")
isWaitingStatus = true isWaitingStatus = true
QtAndroidController.onServiceDisconnected() QtAndroidController.onServiceDisconnected()
isServiceConnected = false
vpnServiceMessenger.send(Action.UNREGISTER_CLIENT, activityMessenger)
vpnServiceMessenger.reset() vpnServiceMessenger.reset()
isServiceConnected = false
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
*/ */
@@ -286,17 +297,6 @@ 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(MSG_VPN_CONFIG, vpnConfig) putString(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(MSG_VPN_CONFIG, vpnConfig) putExtra(VPN_CONFIG, vpnConfig)
}.also { }.also {
ContextCompat.startForegroundService(this, it) ContextCompat.startForegroundService(this, it)
} }
@@ -369,22 +369,6 @@ 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")
@@ -406,19 +390,16 @@ 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-z0-9]+)".toRegex(IGNORE_CASE) val extensionRegex = "\\*\\.[a-z .]+".toRegex(IGNORE_CASE)
val mime = MimeTypeMap.getSingleton() val mime = MimeTypeMap.getSingleton()
extensionRegex.findAll(filter).map { extensionRegex.findAll(filter).map {
it.groups[1]?.value?.let { mime.getMimeTypeFromExtension(it) } ?: "*/*" mime.getMimeTypeFromExtension(it.value.drop(2))
}.toSet() }.filterNotNull().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")
if ("*/*" in mimeTypes) {
type = "*/*"
} else {
when (mimeTypes.size) { when (mimeTypes.size) {
1 -> type = mimeTypes.first() 1 -> type = mimeTypes.first()
@@ -429,7 +410,6 @@ class AmneziaActivity : QtActivity() {
else -> type = "*/*" else -> type = "*/*"
} }
}
}.also { }.also {
startActivityForResult(it, OPEN_FILE_ACTION_CODE) startActivityForResult(it, OPEN_FILE_ACTION_CODE)
} }
@@ -458,7 +438,7 @@ class AmneziaActivity : QtActivity() {
Log.saveLogs = enabled Log.saveLogs = enabled
vpnServiceMessenger.send { vpnServiceMessenger.send {
Action.SET_SAVE_LOGS.packToMessage { Action.SET_SAVE_LOGS.packToMessage {
putBoolean(MSG_SAVE_LOGS, enabled) putBoolean(SAVE_LOGS, enabled)
} }
} }
} }
@@ -475,41 +455,4 @@ class AmneziaActivity : QtActivity() {
Log.v(TAG, "Clear logs") Log.v(TAG, "Clear logs")
Log.clearLogs() 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,7 +18,6 @@ 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()
} }
@@ -1,272 +0,0 @@
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,7 @@
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_SYSTEM_EXEMPTED import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED
@@ -19,7 +16,6 @@ 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
@@ -30,7 +26,6 @@ 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
@@ -44,11 +39,14 @@ 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
@@ -59,16 +57,12 @@ import org.json.JSONObject
private const val TAG = "AmneziaVpnService" private const val TAG = "AmneziaVpnService"
const val MSG_VPN_CONFIG = "VPN_CONFIG" const val VPN_CONFIG = "VPN_CONFIG"
const val MSG_ERROR = "ERROR" const val ERROR_MSG = "ERROR_MSG"
const val MSG_SAVE_LOGS = "SAVE_LOGS" const val 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
@@ -82,8 +76,6 @@ 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
@@ -97,11 +89,8 @@ 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
@@ -127,22 +116,13 @@ 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 -> {
val clientName = msg.data.getString(MSG_CLIENT_NAME) clientMessenger.set(msg.replyTo)
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 -> {
connect(msg.data.getString(MSG_VPN_CONFIG)) val vpnConfig = msg.data.getString(VPN_CONFIG)
Prefs.save(PREFS_CONFIG_KEY, vpnConfig)
connect(vpnConfig)
} }
Action.DISCONNECT -> { Action.DISCONNECT -> {
@@ -150,17 +130,17 @@ class AmneziaVpnService : VpnService() {
} }
Action.REQUEST_STATUS -> { Action.REQUEST_STATUS -> {
clientMessengers[msg.replyTo]?.let { clientMessenger ->
clientMessenger.send { clientMessenger.send {
ServiceEvent.STATUS.packToMessage { ServiceEvent.STATUS.packToMessage {
putStatus(this@AmneziaVpnService.protocolState.value) putStatus(Status.build {
} setState(this@AmneziaVpnService.protocolState.value)
})
} }
} }
} }
Action.SET_SAVE_LOGS -> { Action.SET_SAVE_LOGS -> {
Log.saveLogs = msg.data.getBoolean(MSG_SAVE_LOGS) Log.saveLogs = msg.data.getBoolean(SAVE_LOGS)
} }
} }
} }
@@ -209,23 +189,27 @@ 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)
loadServerData() clientMessenger = IpcMessenger(messengerName = "Client")
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 isAlwaysOn = intent != null && intent.action == SERVICE_INTERFACE val isAlwaysOnCompat =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) isAlwaysOn
else intent?.component?.packageName != packageName
if (isAlwaysOn) { if (isAlwaysOnCompat) {
Log.d(TAG, "Start service via Always-on") Log.d(TAG, "Start service via Always-on")
connect() connect(Prefs.load(PREFS_CONFIG_KEY))
} 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() connect(Prefs.load(PREFS_CONFIG_KEY))
} else { } else {
Log.d(TAG, "Start service") Log.d(TAG, "Start service")
connect(intent?.getStringExtra(MSG_VPN_CONFIG)) val vpnConfig = intent?.getStringExtra(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
@@ -235,17 +219,18 @@ 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) {
if (clientMessengers.isEmpty()) {
isServiceBound = false isServiceBound = false
stopSendingStatistics()
clientMessenger.reset()
if (isUnknown || isDisconnected) stopService() if (isUnknown || isDisconnected) stopService()
} }
}
return true return true
} }
@@ -253,6 +238,7 @@ 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)
} }
@@ -292,16 +278,17 @@ class AmneziaVpnService : VpnService() {
*/ */
private fun launchProtocolStateHandler() { private fun launchProtocolStateHandler() {
mainScope.launch { mainScope.launch {
// drop first default UNKNOWN state protocolState.collect { protocolState ->
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 (isActivityConnected) launchSendingStatistics() if (isServiceBound) launchSendingStatistics()
} }
DISCONNECTED -> { DISCONNECTED -> {
clientMessenger.send(ServiceEvent.DISCONNECTED)
networkState.unbindNetworkListener() networkState.unbindNetworkListener()
stopSendingStatistics() stopSendingStatistics()
if (!isServiceBound) stopService() if (!isServiceBound) stopService()
@@ -313,19 +300,12 @@ 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) }
} }
} }
} }
@@ -352,17 +332,7 @@ class AmneziaVpnService : VpnService() {
} }
@MainThread @MainThread
private fun connect(vpnConfig: String? = null) { private fun connect(vpnConfig: String?) {
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")
@@ -370,7 +340,6 @@ 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
@@ -448,37 +417,23 @@ 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 {
clientMessengers.send { clientMessenger.send {
ServiceEvent.ERROR.packToMessage { ServiceEvent.ERROR.packToMessage {
putString(MSG_ERROR, msg) putString(ERROR_MSG, msg)
} }
} }
} }
} }
private fun parseConfigToJson(vpnConfig: String): JSONObject? = private fun parseConfigToJson(vpnConfig: String?): JSONObject? =
if (vpnConfig.isBlank()) {
null
} else {
try { try {
JSONObject(vpnConfig) vpnConfig?.let {
JSONObject(it)
}
} catch (e: JSONException) { } catch (e: JSONException) {
onError("Invalid VPN config json format: ${e.message}") onError("Invalid VPN config json format: ${e.message}")
null 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) {
@@ -491,12 +446,4 @@ 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
}
}
} }
@@ -1,73 +0,0 @@
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,7 +20,9 @@ sealed interface IpcMessage {
} }
enum class ServiceEvent : IpcMessage { enum class ServiceEvent : IpcMessage {
STATUS_CHANGED, CONNECTED,
DISCONNECTED,
RECONNECTING,
STATUS, STATUS,
STATISTICS_UPDATE, STATISTICS_UPDATE,
ERROR ERROR
@@ -28,7 +30,6 @@ 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,21 +9,11 @@ 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
@@ -35,29 +25,19 @@ 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, "$name messenger is dead") Log.w(TAG, "$messengerName messenger is dead")
messenger = null messenger = null
onDeadObjectException() onDeadObjectException()
} catch (e: RemoteException) { } catch (e: RemoteException) {
Log.w(TAG, "Sending a message to the $name messenger failed: ${e.message}") Log.w(TAG, "Sending a message to the $messengerName 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,16 +1,12 @@
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
@@ -56,43 +52,19 @@ class VpnRequestActivity : ComponentActivity() {
} }
private fun checkRequestResult(result: ActivityResult) { private fun checkRequestResult(result: ActivityResult) {
when (val resultCode = result.resultCode) { when (result.resultCode) {
RESULT_OK -> { RESULT_OK -> onPermissionGranted()
onPermissionGranted() else -> Toast.makeText(this, "Vpn permission denied", Toast.LENGTH_LONG).show()
}
finish() finish()
} }
else -> {
Log.w(TAG, "Vpn permission denied, resultCode: $resultCode")
showOnVpnPermissionRejectDialog()
}
}
}
private fun onPermissionGranted() { private fun onPermissionGranted() {
Toast.makeText(this, resources.getString(R.string.vpnGranted), Toast.LENGTH_LONG).show() Toast.makeText(this, "Vpn permission granted", 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
} }
@@ -1,75 +0,0 @@
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,23 +1,18 @@
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 onVpnStateChanged(stateCode: Int) external fun onVpnConnected()
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)
@@ -0,0 +1,10 @@
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
}
@@ -4,7 +4,6 @@ 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
@@ -62,7 +61,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.awgGetConfig(tunnelHandle) ?: return Statistics.EMPTY_STATISTICS val config = GoBackend.wgGetConfig(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 ->
@@ -93,13 +92,12 @@ open class Wireguard : Protocol() {
val configDataJson = config.getJSONObject("wireguard_config_data") val configDataJson = config.getJSONObject("wireguard_config_data")
val configData = parseConfigData(configDataJson.getString("config")) val configData = parseConfigData(configDataJson.getString("config"))
return WireguardConfig.build { return WireguardConfig.build {
configWireguard(configData, configDataJson) configWireguard(configData)
configSplitTunneling(config) configSplitTunneling(config)
configAppSplitTunneling(config)
} }
} }
protected fun WireguardConfig.Builder.configWireguard(configData: Map<String, String>, configDataJson: JSONObject) { protected fun WireguardConfig.Builder.configWireguard(configData: Map<String, String>) {
configData["Address"]?.split(",")?.map { address -> configData["Address"]?.split(",")?.map { address ->
InetNetwork.parse(address.trim()) InetNetwork.parse(address.trim())
}?.forEach(::addAddress) }?.forEach(::addAddress)
@@ -120,14 +118,7 @@ open class Wireguard : Protocol() {
if (routes.any { it !in defRoutes }) disableSplitTunneling() if (routes.any { it !in defRoutes }) disableSplitTunneling()
addRoutes(routes) addRoutes(routes)
configDataJson.optString("mtu").let { mtu ->
if (mtu.isNotEmpty()) {
setMtu(mtu.toInt())
} else {
configData["MTU"]?.let { setMtu(it.toInt()) } configData["MTU"]?.let { setMtu(it.toInt()) }
}
}
configData["Endpoint"]?.let { setEndpoint(InetEndpoint.parse(it)) } configData["Endpoint"]?.let { setEndpoint(InetEndpoint.parse(it)) }
configData["PersistentKeepalive"]?.let { setPersistentKeepalive(it.toInt()) } configData["PersistentKeepalive"]?.let { setPersistentKeepalive(it.toInt()) }
configData["PrivateKey"]?.let { setPrivateKeyHex(it.base64ToHex()) } configData["PrivateKey"]?.let { setPrivateKeyHex(it.base64ToHex()) }
@@ -158,8 +149,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.i(TAG, "awg-go backend ${GoBackend.awgVersion()}") Log.v(TAG, "Wg-go backend ${GoBackend.wgVersion()}")
tunnelHandle = GoBackend.awgTurnOn(ifName, tunFd.detachFd(), config.toWgUserspaceString()) tunnelHandle = GoBackend.wgTurnOn(ifName, tunFd.detachFd(), config.toWgUserspaceString())
} }
if (tunnelHandle < 0) { if (tunnelHandle < 0) {
@@ -167,8 +158,8 @@ open class Wireguard : Protocol() {
throw VpnStartException("Wireguard tunnel creation error") throw VpnStartException("Wireguard tunnel creation error")
} }
if (!protect(GoBackend.awgGetSocketV4(tunnelHandle)) || !protect(GoBackend.awgGetSocketV6(tunnelHandle))) { if (!protect(GoBackend.wgGetSocketV4(tunnelHandle)) || !protect(GoBackend.wgGetSocketV6(tunnelHandle))) {
GoBackend.awgTurnOff(tunnelHandle) GoBackend.wgTurnOff(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")
} }
@@ -181,7 +172,7 @@ open class Wireguard : Protocol() {
} }
val handleToClose = tunnelHandle val handleToClose = tunnelHandle
tunnelHandle = -1 tunnelHandle = -1
GoBackend.awgTurnOff(handleToClose) GoBackend.wgTurnOff(handleToClose)
state.value = DISCONNECTED state.value = DISCONNECTED
} }
@@ -1,10 +0,0 @@
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
}
+6 -5
View File
@@ -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 -ljnigraphics) set(LIBS ${LIBS} Qt6::CorePrivate)
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/platforms/android) link_directories(${CMAKE_CURRENT_SOURCE_DIR}/platforms/android)
@@ -30,7 +30,6 @@ 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}
@@ -39,18 +38,20 @@ 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()
-4
View File
@@ -107,20 +107,16 @@ 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)
+7 -7
View File
@@ -3,15 +3,17 @@
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
AwgConfigurator::AwgConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent) #include "core/controllers/serverController.h"
: WireguardConfigurator(settings, serverController, true, parent)
AwgConfigurator::AwgConfigurator(std::shared_ptr<Settings> settings, QObject *parent)
: WireguardConfigurator(settings, true, parent)
{ {
} }
QString AwgConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig, QString AwgConfigurator::genAwgConfig(const ServerCredentials &credentials, DockerContainer container,
ErrorCode errorCode) const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode)
{ {
QString config = WireguardConfigurator::createConfig(credentials, container, containerConfig, errorCode); QString config = WireguardConfigurator::genWireguardConfig(credentials, container, containerConfig, clientId, 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();
@@ -39,8 +41,6 @@ QString AwgConfigurator::createConfig(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().value(config_key::mtu).toString(protocols::awg::defaultMtu);
return QJsonDocument(jsonConfig).toJson(); return QJsonDocument(jsonConfig).toJson();
} }
+3 -3
View File
@@ -9,10 +9,10 @@ class AwgConfigurator : public WireguardConfigurator
{ {
Q_OBJECT Q_OBJECT
public: public:
AwgConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr); AwgConfigurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
QString createConfig(const ServerCredentials &credentials, DockerContainer container, QString genAwgConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode errorCode); const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode = nullptr);
}; };
#endif // AWGCONFIGURATOR_H #endif // AWGCONFIGURATOR_H
+19 -17
View File
@@ -1,33 +1,34 @@
#include "cloak_configurator.h" #include "cloak_configurator.h"
#include <QFile> #include <QFile>
#include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
#include <QJsonDocument>
#include "containers/containers_defs.h"
#include "core/controllers/serverController.h" #include "core/controllers/serverController.h"
#include "containers/containers_defs.h"
CloakConfigurator::CloakConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent) CloakConfigurator::CloakConfigurator(std::shared_ptr<Settings> settings, QObject *parent):
: ConfiguratorBase(settings, serverController, parent) ConfiguratorBase(settings, parent)
{ {
} }
QString CloakConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig, QString CloakConfigurator::genCloakConfig(const ServerCredentials &credentials,
ErrorCode errorCode) DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode)
{ {
QString cloakPublicKey = ErrorCode e = ErrorCode::NoError;
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::cloak::ckPublicKeyPath, errorCode); ServerController serverController(m_settings);
QString cloakPublicKey = serverController.getTextFileFromContainer(container, credentials,
amnezia::protocols::cloak::ckPublicKeyPath, &e);
cloakPublicKey.replace("\n", ""); cloakPublicKey.replace("\n", "");
if (errorCode != ErrorCode::NoError) { QString cloakBypassUid = serverController.getTextFileFromContainer(container, credentials,
return ""; amnezia::protocols::cloak::ckBypassUidKeyPath, &e);
}
QString cloakBypassUid =
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::cloak::ckBypassUidKeyPath, errorCode);
cloakBypassUid.replace("\n", ""); cloakBypassUid.replace("\n", "");
if (errorCode != ErrorCode::NoError) { if (e) {
if (errorCode) *errorCode = e;
return ""; return "";
} }
@@ -44,8 +45,9 @@ QString CloakConfigurator::createConfig(const ServerCredentials &credentials, Do
config.insert("RemoteHost", credentials.hostName); config.insert("RemoteHost", credentials.hostName);
config.insert("RemotePort", "$CLOAK_SERVER_PORT"); config.insert("RemotePort", "$CLOAK_SERVER_PORT");
QString textCfg = m_serverController->replaceVars(QJsonDocument(config).toJson(), QString textCfg = serverController.replaceVars(QJsonDocument(config).toJson(),
m_serverController->genVarsForScript(credentials, container, containerConfig)); serverController.genVarsForScript(credentials, container, containerConfig));
// qDebug().noquote() << textCfg;
return textCfg; return textCfg;
} }
+4 -4
View File
@@ -7,14 +7,14 @@
using namespace amnezia; using namespace amnezia;
class CloakConfigurator : public ConfiguratorBase class CloakConfigurator : ConfiguratorBase
{ {
Q_OBJECT Q_OBJECT
public: public:
CloakConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr); CloakConfigurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
QString createConfig(const ServerCredentials &credentials, DockerContainer container, QString genCloakConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode errorCode); const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
}; };
#endif // CLOAK_CONFIGURATOR_H #endif // CLOAK_CONFIGURATOR_H
+3 -21
View File
@@ -1,26 +1,8 @@
#include "configurator_base.h" #include "configurator_base.h"
ConfiguratorBase::ConfiguratorBase(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent) ConfiguratorBase::ConfiguratorBase(std::shared_ptr<Settings> settings, QObject *parent)
: QObject { parent }, m_settings(settings), m_serverController(serverController) : QObject{parent},
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 -15
View File
@@ -3,31 +3,19 @@
#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, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr); explicit ConfiguratorBase(std::shared_ptr<Settings> settings, 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
+18 -14
View File
@@ -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, const QSharedPointer<ServerController> &serverController, QObject *parent) Ikev2Configurator::Ikev2Configurator(std::shared_ptr<Settings> settings, QObject *parent)
: ConfiguratorBase(settings, serverController, parent) : ConfiguratorBase(settings, parent)
{ {
} }
Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const ServerCredentials &credentials, DockerContainer container, Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const ServerCredentials &credentials,
ErrorCode errorCode) DockerContainer container, ErrorCode *errorCode)
{ {
Ikev2Configurator::ConnectionData connData; Ikev2Configurator::ConnectionData connData;
connData.host = credentials.hostName; connData.host = credentials.hostName;
@@ -39,14 +39,18 @@ Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const Se
"--extKeyUsage serverAuth,clientAuth -8 \"%1\"") "--extKeyUsage serverAuth,clientAuth -8 \"%1\"")
.arg(connData.clientId); .arg(connData.clientId);
errorCode = m_serverController->runContainerScript(credentials, container, scriptCreateCert); ServerController serverController(m_settings);
ErrorCode e = serverController.runContainerScript(credentials, container, scriptCreateCert);
QString scriptExportCert = QString scriptExportCert = QString("pk12util -W \"%1\" -d sql:/etc/ipsec.d -n \"%2\" -o \"%3\"")
QString("pk12util -W \"%1\" -d sql:/etc/ipsec.d -n \"%2\" -o \"%3\"").arg(connData.password).arg(connData.clientId).arg(certFileName); .arg(connData.password)
errorCode = m_serverController->runContainerScript(credentials, container, scriptExportCert); .arg(connData.clientId)
.arg(certFileName);
e = serverController.runContainerScript(credentials, container, scriptExportCert);
connData.clientCert = m_serverController->getTextFileFromContainer(container, credentials, certFileName, errorCode); connData.clientCert = serverController.getTextFileFromContainer(container, credentials, certFileName, &e);
connData.caCert = m_serverController->getTextFileFromContainer(container, credentials, "/etc/ipsec.d/ca_cert_base64.p12", errorCode); connData.caCert =
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();
@@ -54,13 +58,13 @@ Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const Se
return connData; return connData;
} }
QString Ikev2Configurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig, QString Ikev2Configurator::genIkev2Config(const ServerCredentials &credentials, DockerContainer container,
ErrorCode errorCode) const QJsonObject &containerConfig, ErrorCode *errorCode)
{ {
Q_UNUSED(containerConfig) Q_UNUSED(containerConfig)
ConnectionData connData = prepareIkev2Config(credentials, container, errorCode); ConnectionData connData = prepareIkev2Config(credentials, container, errorCode);
if (errorCode != ErrorCode::NoError) { if (errorCode && *errorCode) {
return ""; return "";
} }
+5 -5
View File
@@ -7,11 +7,11 @@
#include "configurator_base.h" #include "configurator_base.h"
#include "core/defs.h" #include "core/defs.h"
class Ikev2Configurator : public ConfiguratorBase class Ikev2Configurator : ConfiguratorBase
{ {
Q_OBJECT Q_OBJECT
public: public:
Ikev2Configurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr); Ikev2Configurator(std::shared_ptr<Settings> settings, 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 createConfig(const ServerCredentials &credentials, DockerContainer container, QString genIkev2Config(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode errorCode); const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
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); DockerContainer container, ErrorCode *errorCode = nullptr);
}; };
#endif // IKEV2_CONFIGURATOR_H #endif // IKEV2_CONFIGURATOR_H
+52 -41
View File
@@ -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,61 +24,74 @@
#include <openssl/rsa.h> #include <openssl/rsa.h>
#include <openssl/x509.h> #include <openssl/x509.h>
OpenVpnConfigurator::OpenVpnConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, OpenVpnConfigurator::OpenVpnConfigurator(std::shared_ptr<Settings> settings, QObject *parent)
QObject *parent) : ConfiguratorBase(settings, parent)
: ConfiguratorBase(settings, serverController, parent)
{ {
} }
OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(const ServerCredentials &credentials, OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(const ServerCredentials &credentials,
DockerContainer container, ErrorCode errorCode) DockerContainer container,
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()) {
errorCode = ErrorCode::OpenSslFailed; if (errorCode)
*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);
errorCode = m_serverController->uploadTextFileToContainer(container, credentials, connData.request, reqFileName); ServerController serverController(m_settings);
if (errorCode != ErrorCode::NoError) { ErrorCode e = serverController.uploadTextFileToContainer(container, credentials, connData.request, reqFileName);
if (e) {
if (errorCode)
*errorCode = e;
return connData; return connData;
} }
errorCode = signCert(container, credentials, connData.clientId); e = signCert(container, credentials, connData.clientId);
if (errorCode != ErrorCode::NoError) { if (e) {
if (errorCode)
*errorCode = e;
return connData; return connData;
} }
connData.caCert = connData.caCert = serverController.getTextFileFromContainer(container, credentials,
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::caCertPath, errorCode); amnezia::protocols::openvpn::caCertPath, &e);
connData.clientCert = m_serverController->getTextFileFromContainer( connData.clientCert = serverController.getTextFileFromContainer(
container, credentials, QString("%1/%2.crt").arg(amnezia::protocols::openvpn::clientCertPath).arg(connData.clientId), errorCode); container, credentials,
QString("%1/%2.crt").arg(amnezia::protocols::openvpn::clientCertPath).arg(connData.clientId), &e);
if (errorCode != ErrorCode::NoError) { if (e) {
if (errorCode)
*errorCode = e;
return connData; return connData;
} }
connData.taKey = m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::taKeyPath, errorCode); connData.taKey = serverController.getTextFileFromContainer(container, credentials,
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()) {
errorCode = ErrorCode::SshScpFailureError; if (errorCode)
*errorCode = ErrorCode::SshSftpFailureError;
} }
return connData; return connData;
} }
QString OpenVpnConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode errorCode) const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode)
{ {
QString config = m_serverController->replaceVars(amnezia::scriptData(ProtocolScriptType::openvpn_template, container), ServerController serverController(m_settings);
m_serverController->genVarsForScript(credentials, container, containerConfig)); QString config =
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::NoError) { if (errorCode && *errorCode) {
return ""; return "";
} }
@@ -100,32 +113,31 @@ QString OpenVpnConfigurator::createConfig(const ServerCredentials &credentials,
QJsonObject jConfig; QJsonObject jConfig;
jConfig[config_key::config] = config; jConfig[config_key::config] = config;
jConfig[config_key::clientId] = connData.clientId; clientId = connData.clientId;
return QJsonDocument(jConfig).toJson(); return QJsonDocument(jConfig).toJson();
} }
QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig, QString OpenVpnConfigurator::processConfigWithLocalSettings(QString jsonConfig, const int serverIndex)
QString &protocolConfigString)
{ {
processConfigWithDnsSettings(dns, protocolConfigString); QJsonObject json = QJsonDocument::fromJson(jsonConfig.toUtf8()).object();
QJsonObject json = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
QString config = json[config_key::config].toString(); QString config = json[config_key::config].toString();
if (!isApiConfig) { if (!m_settings->server(serverIndex).value(config_key::configVersion).toInt()) {
QRegularExpression regex("redirect-gateway.*"); QRegularExpression regex("redirect-gateway.*");
config.replace(regex, ""); config.replace(regex, "");
if (!m_settings->getSitesSplitTunnelingEnabled()) { if (m_settings->routeMode() == Settings::VpnAllSites) {
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
@@ -152,12 +164,9 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPair<QString,
return QJsonDocument(json).toJson(); return QJsonDocument(json).toJson();
} }
QString OpenVpnConfigurator::processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig, QString OpenVpnConfigurator::processConfigWithExportSettings(QString jsonConfig)
QString &protocolConfigString)
{ {
processConfigWithDnsSettings(dns, protocolConfigString); QJsonObject json = QJsonDocument::fromJson(jsonConfig.toUtf8()).object();
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.*");
@@ -189,10 +198,12 @@ 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 = m_serverController->replaceVars(scriptList.join("\n"), m_serverController->genVarsForScript(credentials, container)); QString script = serverController.replaceVars(scriptList.join("\n"),
serverController.genVarsForScript(credentials, container));
return m_serverController->runScript(credentials, script); return serverController.runScript(credentials, script);
} }
OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest() OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest()
@@ -226,8 +237,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, reinterpret_cast<unsigned char const *>(clientIdUtf8.data()), X509_NAME_add_entry_by_txt(x509_name, "CN", MBSTRING_ASC,
clientIdUtf8.size(), -1, 0); reinterpret_cast<unsigned char const *>(clientIdUtf8.data()), 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);
+13 -13
View File
@@ -7,14 +7,13 @@
#include "configurator_base.h" #include "configurator_base.h"
#include "core/defs.h" #include "core/defs.h"
class OpenVpnConfigurator : public ConfiguratorBase class OpenVpnConfigurator : ConfiguratorBase
{ {
Q_OBJECT Q_OBJECT
public: public:
OpenVpnConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr); OpenVpnConfigurator(std::shared_ptr<Settings> settings, 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
@@ -24,20 +23,21 @@ public:
QString host; // host ip QString host; // host ip
}; };
QString createConfig(const ServerCredentials &credentials, DockerContainer container, QString genOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode errorCode); const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode = nullptr);
QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig, QString processConfigWithLocalSettings(QString jsonConfig, const int serverIndex);
QString &protocolConfigString); QString processConfigWithExportSettings(QString jsonConfig);
QString processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString); ErrorCode signCert(DockerContainer container,
const ServerCredentials &credentials, QString clientId);
static ConnectionData createCertRequest(); static ConnectionData createCertRequest();
private: private:
ConnectionData prepareOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container, ConnectionData prepareOpenVpnConfig(const ServerCredentials &credentials,
ErrorCode errorCode); DockerContainer container, ErrorCode *errorCode = nullptr);
ErrorCode signCert(DockerContainer container, const ServerCredentials &credentials, QString clientId);
}; };
#endif // OPENVPN_CONFIGURATOR_H #endif // OPENVPN_CONFIGURATOR_H
@@ -1,26 +1,30 @@
#include "shadowsocks_configurator.h" #include "shadowsocks_configurator.h"
#include <QFile> #include <QFile>
#include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
#include <QJsonDocument>
#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, const QSharedPointer<ServerController> &serverController, ShadowSocksConfigurator::ShadowSocksConfigurator(std::shared_ptr<Settings> settings, QObject *parent):
QObject *parent) ConfiguratorBase(settings, parent)
: ConfiguratorBase(settings, serverController, parent)
{ {
} }
QString ShadowSocksConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, QString ShadowSocksConfigurator::genShadowSocksConfig(const ServerCredentials &credentials,
const QJsonObject &containerConfig, ErrorCode errorCode) DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode)
{ {
QString ssKey = ErrorCode e = ErrorCode::NoError;
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::shadowsocks::ssKeyPath, errorCode); ServerController serverController(m_settings);
QString ssKey = serverController.getTextFileFromContainer(container, credentials,
amnezia::protocols::shadowsocks::ssKeyPath, &e);
ssKey.replace("\n", ""); ssKey.replace("\n", "");
if (errorCode != ErrorCode::NoError) { if (e) {
if (errorCode) *errorCode = e;
return ""; return "";
} }
@@ -32,8 +36,9 @@ QString ShadowSocksConfigurator::createConfig(const ServerCredentials &credentia
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(),
serverController.genVarsForScript(credentials, container, containerConfig));
//qDebug().noquote() << textCfg; //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 : public ConfiguratorBase class ShadowSocksConfigurator : ConfiguratorBase
{ {
Q_OBJECT Q_OBJECT
public: public:
ShadowSocksConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr); ShadowSocksConfigurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
QString createConfig(const ServerCredentials &credentials, DockerContainer container, QString genShadowSocksConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode errorCode); const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
}; };
#endif // SHADOWSOCKS_CONFIGURATOR_H #endif // SHADOWSOCKS_CONFIGURATOR_H
+4 -3
View File
@@ -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, const QSharedPointer<ServerController> &serverController, QObject *parent) SshConfigurator::SshConfigurator(std::shared_ptr<Settings> settings, QObject *parent)
: ConfiguratorBase(settings, serverController, parent) : ConfiguratorBase(settings, parent)
{ {
} }
@@ -82,7 +82,8 @@ 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(QString("%1@%2 -pw %3").arg(credentials.userName).arg(credentials.hostName).arg(credentials.secretData)); p->setNativeArguments(
QString("%1@%2 -pw %3").arg(credentials.userName).arg(credentials.hostName).arg(credentials.secretData));
} }
#else #else
p->setProgram("/bin/bash"); p->setProgram("/bin/bash");
+1 -1
View File
@@ -11,7 +11,7 @@ class SshConfigurator : ConfiguratorBase
{ {
Q_OBJECT Q_OBJECT
public: public:
SshConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr); SshConfigurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
QProcessEnvironment prepareEnv(); QProcessEnvironment prepareEnv();
QString convertOpenSShKey(const QString &key); QString convertOpenSShKey(const QString &key);
+127
View File
@@ -0,0 +1,127 @@
#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);
}
}
+52
View File
@@ -0,0 +1,52 @@
#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
+63 -41
View File
@@ -13,20 +13,23 @@
#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, const QSharedPointer<ServerController> &serverController, WireguardConfigurator::WireguardConfigurator(std::shared_ptr<Settings> settings, bool isAwg, QObject *parent)
bool isAwg, QObject *parent) : ConfiguratorBase(settings, parent), m_isAwg(isAwg)
: ConfiguratorBase(settings, serverController, parent), m_isAwg(isAwg)
{ {
m_serverConfigPath = m_isAwg ? amnezia::protocols::awg::serverConfigPath : amnezia::protocols::wireguard::serverConfigPath; m_serverConfigPath = m_isAwg ? amnezia::protocols::awg::serverConfigPath
m_serverPublicKeyPath = m_isAwg ? amnezia::protocols::awg::serverPublicKeyPath : amnezia::protocols::wireguard::serverPublicKeyPath; : amnezia::protocols::wireguard::serverConfigPath;
m_serverPskKeyPath = m_isAwg ? amnezia::protocols::awg::serverPskKeyPath : amnezia::protocols::wireguard::serverPskKeyPath; m_serverPublicKeyPath = m_isAwg ? amnezia::protocols::awg::serverPublicKeyPath
m_configTemplate = m_isAwg ? ProtocolScriptType::awg_template : ProtocolScriptType::wireguard_template; : amnezia::protocols::wireguard::serverPublicKeyPath;
m_serverPskKeyPath = m_isAwg ? amnezia::protocols::awg::serverPskKeyPath
: amnezia::protocols::wireguard::serverPskKeyPath;
m_configTemplate = m_isAwg ? ProtocolScriptType::awg_template
: ProtocolScriptType::wireguard_template;
m_protocolName = m_isAwg ? config_key::awg : config_key::wireguard; 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;
@@ -65,17 +68,22 @@ 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, ErrorCode errorCode) const QJsonObject &containerConfig,
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()) {
errorCode = ErrorCode::InternalError; if (errorCode)
*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;
{ {
@@ -86,8 +94,9 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
return ErrorCode::NoError; return ErrorCode::NoError;
}; };
errorCode = m_serverController->runContainerScript(credentials, container, script, cbReadStdOut); e = serverController.runContainerScript(credentials, container, script, cbReadStdOut);
if (errorCode != ErrorCode::NoError) { if (errorCode && e) {
*errorCode = e;
return connData; return connData;
} }
@@ -101,18 +110,21 @@ 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) {
errorCode = ErrorCode::AddressPoolError; if (errorCode)
*errorCode = ErrorCode::AddressPoolError;
return connData; return connData;
} }
nextIpNumber = QString::number(next); nextIpNumber = QString::number(next);
} }
} }
QString subnetIp = containerConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress); QString subnetIp =
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()) {
errorCode = ErrorCode::AddressPoolError; if (errorCode)
*errorCode = ErrorCode::AddressPoolError;
return connData; return connData;
} }
l.removeLast(); l.removeLast();
@@ -122,16 +134,20 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
} }
// Get keys // Get keys
connData.serverPubKey = m_serverController->getTextFileFromContainer(container, credentials, m_serverPublicKeyPath, errorCode); connData.serverPubKey = serverController.getTextFileFromContainer(container, credentials, m_serverPublicKeyPath, &e);
connData.serverPubKey.replace("\n", ""); connData.serverPubKey.replace("\n", "");
if (errorCode != ErrorCode::NoError) { if (e) {
if (errorCode)
*errorCode = e;
return connData; return connData;
} }
connData.pskKey = m_serverController->getTextFileFromContainer(container, credentials, m_serverPskKeyPath, errorCode); connData.pskKey = serverController.getTextFileFromContainer(container, credentials, m_serverPskKeyPath, &e);
connData.pskKey.replace("\n", ""); connData.pskKey.replace("\n", "");
if (errorCode != ErrorCode::NoError) { if (e) {
if (errorCode)
*errorCode = e;
return connData; return connData;
} }
@@ -142,30 +158,34 @@ 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);
errorCode = m_serverController->uploadTextFileToContainer(container, credentials, configPart, m_serverConfigPath, e = serverController.uploadTextFileToContainer(container, credentials, configPart, m_serverConfigPath,
libssh::ScpOverwriteMode::ScpAppendToExisting); libssh::SftpOverwriteMode::SftpAppendToExisting);
if (errorCode != ErrorCode::NoError) { if (e) {
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)'").arg(m_serverConfigPath); QString script = QString("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip %1)'")
.arg(m_serverConfigPath);
errorCode = m_serverController->runScript( e = serverController.runScript(
credentials, m_serverController->replaceVars(script, m_serverController->genVarsForScript(credentials, container))); credentials, serverController.replaceVars(script, serverController.genVarsForScript(credentials, container)));
return connData; return connData;
} }
QString WireguardConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, QString WireguardConfigurator::genWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode errorCode) const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode)
{ {
ServerController serverController(m_settings);
QString scriptData = amnezia::scriptData(m_configTemplate, container); QString scriptData = amnezia::scriptData(m_configTemplate, container);
QString config = QString config = serverController.replaceVars(
m_serverController->replaceVars(scriptData, m_serverController->genVarsForScript(credentials, container, containerConfig)); scriptData, serverController.genVarsForScript(credentials, container, containerConfig));
ConnectionData connData = prepareWireguardConfig(credentials, container, containerConfig, errorCode); ConnectionData connData = prepareWireguardConfig(credentials, container, containerConfig, errorCode);
if (errorCode != ErrorCode::NoError) { if (errorCode && *errorCode) {
return ""; return "";
} }
@@ -174,7 +194,6 @@ QString WireguardConfigurator::createConfig(const ServerCredentials &credentials
config.replace("$WIREGUARD_SERVER_PUBLIC_KEY", connData.serverPubKey); config.replace("$WIREGUARD_SERVER_PUBLIC_KEY", connData.serverPubKey);
config.replace("$WIREGUARD_PSK", connData.pskKey); config.replace("$WIREGUARD_PSK", connData.pskKey);
const QJsonObject &wireguarConfig = containerConfig.value(ProtocolProps::protoToString(Proto::WireGuard)).toObject();
QJsonObject jConfig; QJsonObject jConfig;
jConfig[config_key::config] = config; jConfig[config_key::config] = config;
@@ -185,25 +204,28 @@ QString WireguardConfigurator::createConfig(const ServerCredentials &credentials
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::clientId] = connData.clientPubKey; clientId = connData.clientPubKey;
return QJsonDocument(jConfig).toJson(); return QJsonDocument(jConfig).toJson();
} }
QString WireguardConfigurator::processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig, QString WireguardConfigurator::processConfigWithLocalSettings(QString config)
QString &protocolConfigString)
{ {
processConfigWithDnsSettings(dns, protocolConfigString); // TODO replace DNS if it already set
config.replace("$PRIMARY_DNS", m_settings->primaryDns());
config.replace("$SECONDARY_DNS", m_settings->secondaryDns());
return protocolConfigString; QJsonObject jConfig;
jConfig[config_key::config] = config;
return QJsonDocument(jConfig).toJson();
} }
QString WireguardConfigurator::processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig, QString WireguardConfigurator::processConfigWithExportSettings(QString config)
QString &protocolConfigString)
{ {
processConfigWithDnsSettings(dns, protocolConfigString); config.replace("$PRIMARY_DNS", m_settings->primaryDns());
config.replace("$SECONDARY_DNS", m_settings->secondaryDns());
return protocolConfigString; return config;
} }
@@ -12,8 +12,7 @@ class WireguardConfigurator : public ConfiguratorBase
{ {
Q_OBJECT Q_OBJECT
public: public:
WireguardConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, bool isAwg, WireguardConfigurator(std::shared_ptr<Settings> settings, bool isAwg, QObject *parent = nullptr);
QObject *parent = nullptr);
struct ConnectionData struct ConnectionData
{ {
@@ -26,17 +25,17 @@ public:
QString port; QString port;
}; };
QString createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig, QString genWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
ErrorCode errorCode); const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode = nullptr);
QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig, QString &protocolConfigString); QString processConfigWithLocalSettings(QString config);
QString processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig, QString &protocolConfigString); QString processConfigWithExportSettings(QString config);
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); const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
bool m_isAwg; bool m_isAwg;
QString m_serverConfigPath; QString m_serverConfigPath;
@@ -1,42 +0,0 @@
#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;
}
-19
View File
@@ -1,19 +0,0 @@
#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
+2 -36
View File
@@ -1,8 +1,5 @@
#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);
@@ -61,8 +58,6 @@ 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 };
@@ -90,7 +85,6 @@ 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") },
@@ -117,9 +111,6 @@ 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.") },
@@ -208,17 +199,6 @@ 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, "
@@ -233,11 +213,7 @@ 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, { DockerContainer::Sftp, QObject::tr("Sftp file sharing service - is secure FTP service") }
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.\" ") }
}; };
} }
@@ -255,7 +231,6 @@ 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;
@@ -299,6 +274,7 @@ 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;
} }
@@ -366,13 +342,3 @@ 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();
}
-3
View File
@@ -22,7 +22,6 @@ namespace amnezia
Cloak, Cloak,
ShadowSocks, ShadowSocks,
Ipsec, Ipsec,
Xray,
// non-vpn // non-vpn
TorWebSite, TorWebSite,
@@ -68,8 +67,6 @@ 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()
+270 -97
View File
@@ -23,13 +23,13 @@
#include <thread> #include <thread>
#include "containers/containers_defs.h" #include "containers/containers_defs.h"
#include "core/networkUtilities.h" #include "logger.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,7 +95,8 @@ ErrorCode ServerController::runScript(const ServerCredentials &credentials, QStr
return ErrorCode::NoError; return ErrorCode::NoError;
} }
ErrorCode ServerController::runContainerScript(const ServerCredentials &credentials, DockerContainer container, QString script, ErrorCode
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)
{ {
@@ -115,8 +116,9 @@ ErrorCode ServerController::runContainerScript(const ServerCredentials &credenti
return e; return e;
} }
ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container, const ServerCredentials &credentials, const QString &file, ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container, const ServerCredentials &credentials,
const QString &path, libssh::ScpOverwriteMode overwriteMode) const QString &file, const QString &path,
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));
@@ -137,7 +139,7 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
if (e) if (e)
return e; return e;
if (overwriteMode == libssh::ScpOverwriteMode::ScpOverwriteExisting) { if (overwriteMode == libssh::SftpOverwriteMode::SftpOverwriteExisting) {
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)),
@@ -145,7 +147,7 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
if (e) if (e)
return e; return e;
} else if (overwriteMode == libssh::ScpOverwriteMode::ScpAppendToExisting) { } else if (overwriteMode == libssh::SftpOverwriteMode::SftpAppendToExisting) {
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)),
@@ -154,8 +156,10 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
if (e) if (e)
return e; return e;
e = runScript(credentials, e = runScript(
replaceVars(QString("sudo docker exec -i $CONTAINER_NAME sh -c \"cat %1 >> %2\"").arg(tmpFileName).arg(path), credentials,
replaceVars(
QString("sudo docker exec -i $CONTAINER_NAME sh -c \"cat %1 >> %2\"").arg(tmpFileName).arg(path),
genVarsForScript(credentials, container)), genVarsForScript(credentials, container)),
cbReadStd, cbReadStd); cbReadStd, cbReadStd);
@@ -168,17 +172,21 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
return ErrorCode::ServerContainerMissingError; return ErrorCode::ServerContainerMissingError;
} }
runScript(credentials, replaceVars(QString("sudo shred -u %1").arg(tmpFileName), genVarsForScript(credentials, container))); runScript(credentials,
replaceVars(QString("sudo shred -u %1").arg(tmpFileName), genVarsForScript(credentials, container)));
return e; return e;
} }
QByteArray ServerController::getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials, const QString &path, QByteArray ServerController::getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials,
ErrorCode errorCode) const QString &path, ErrorCode *errorCode)
{ {
errorCode = ErrorCode::NoError; if (errorCode)
*errorCode = ErrorCode::NoError;
QString script = QString("sudo docker exec -i %1 sh -c \"xxd -p \'%2\'\"").arg(ContainerProps::containerToString(container)).arg(path); QString script = QString("sudo docker exec -i %1 sh -c \"xxd -p \'%2\'\"")
.arg(ContainerProps::containerToString(container))
.arg(path);
QString stdOut; QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) { auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
@@ -186,12 +194,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, const QString &remotePath, ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data,
libssh::ScpOverwriteMode overwriteMode) const QString &remotePath, libssh::SftpOverwriteMode overwriteMode)
{ {
auto error = m_sshClient.connectToHost(credentials); auto error = m_sshClient.connectToHost(credentials);
if (error != ErrorCode::NoError) { if (error != ErrorCode::NoError) {
@@ -203,7 +211,7 @@ ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credential
localFile.write(data); localFile.write(data);
localFile.close(); localFile.close();
error = m_sshClient.scpFileCopy(overwriteMode, localFile.fileName(), remotePath, "non_desc"); error = m_sshClient.sftpFileCopy(overwriteMode, localFile.fileName(), remotePath, "non_desc");
if (error != ErrorCode::NoError) { if (error != ErrorCode::NoError) {
return error; return error;
@@ -237,10 +245,12 @@ 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), genVarsForScript(credentials, container))); replaceVars(amnezia::scriptData(SharedScriptType::remove_container),
genVarsForScript(credentials, container)));
} }
ErrorCode ServerController::setupContainer(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config, bool isUpdate) ErrorCode ServerController::setupContainer(const ServerCredentials &credentials, DockerContainer container,
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;
@@ -300,11 +310,12 @@ ErrorCode ServerController::setupContainer(const ServerCredentials &credentials,
return startupContainerWorker(credentials, container, config); return startupContainerWorker(credentials, container, config);
} }
ErrorCode ServerController::updateContainer(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &oldConfig, ErrorCode ServerController::updateContainer(const ServerCredentials &credentials, DockerContainer container,
QJsonObject &newConfig) const QJsonObject &oldConfig, QJsonObject &newConfig)
{ {
bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig); bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig);
qDebug() << "ServerController::updateContainer for container" << container << "reinstall required is" << reinstallRequired; qDebug() << "ServerController::updateContainer for container" << container << "reinstall required is"
<< reinstallRequired;
if (reinstallRequired) { if (reinstallRequired) {
return setupContainer(credentials, container, newConfig, true); return setupContainer(credentials, container, newConfig, true);
@@ -317,7 +328,8 @@ ErrorCode ServerController::updateContainer(const ServerCredentials &credentials
} }
} }
bool ServerController::isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig) bool ServerController::isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig,
const QJsonObject &newConfig)
{ {
Proto mainProto = ContainerProps::defaultProtocol(container); Proto mainProto = ContainerProps::defaultProtocol(container);
@@ -347,32 +359,6 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
} }
if (container == DockerContainer::Awg) { if (container == DockerContainer::Awg) {
if ((oldProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort)
!= newProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort))
|| (oldProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount)
!= newProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount))
|| (oldProtoConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize)
!= newProtoConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize))
|| (oldProtoConfig.value(config_key::junkPacketMaxSize).toString(protocols::awg::defaultJunkPacketMaxSize)
!= newProtoConfig.value(config_key::junkPacketMaxSize).toString(protocols::awg::defaultJunkPacketMaxSize))
|| (oldProtoConfig.value(config_key::initPacketJunkSize).toString(protocols::awg::defaultInitPacketJunkSize)
!= newProtoConfig.value(config_key::initPacketJunkSize).toString(protocols::awg::defaultInitPacketJunkSize))
|| (oldProtoConfig.value(config_key::responsePacketJunkSize).toString(protocols::awg::defaultResponsePacketJunkSize)
!= newProtoConfig.value(config_key::responsePacketJunkSize).toString(protocols::awg::defaultResponsePacketJunkSize))
|| (oldProtoConfig.value(config_key::initPacketMagicHeader).toString(protocols::awg::defaultInitPacketMagicHeader)
!= newProtoConfig.value(config_key::initPacketMagicHeader).toString(protocols::awg::defaultInitPacketMagicHeader))
|| (oldProtoConfig.value(config_key::responsePacketMagicHeader).toString(protocols::awg::defaultResponsePacketMagicHeader)
!= newProtoConfig.value(config_key::responsePacketMagicHeader).toString(protocols::awg::defaultResponsePacketMagicHeader))
|| (oldProtoConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader)
!= newProtoConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader))
|| (oldProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader)
!= newProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader)))
return true;
}
if (container == DockerContainer::WireGuard) {
if (oldProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort)
!= newProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort))
return true; return true;
} }
@@ -396,7 +382,8 @@ ErrorCode ServerController::installDockerWorker(const ServerCredentials &credent
}; };
ErrorCode error = ErrorCode error =
runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::install_docker), genVarsForScript(credentials)), runScript(credentials,
replaceVars(amnezia::scriptData(SharedScriptType::install_docker), genVarsForScript(credentials)),
cbReadStdOut, cbReadStdErr); cbReadStdOut, cbReadStdErr);
qDebug().noquote() << "ServerController::installDockerWorker" << stdOut; qDebug().noquote() << "ServerController::installDockerWorker" << stdOut;
@@ -408,13 +395,17 @@ ErrorCode ServerController::installDockerWorker(const ServerCredentials &credent
return error; return error;
} }
ErrorCode ServerController::prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config) ErrorCode ServerController::prepareHostWorker(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &config)
{ {
// create folder on host // create folder on host
return runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::prepare_host), genVarsForScript(credentials, container))); return runScript(
credentials,
replaceVars(amnezia::scriptData(SharedScriptType::prepare_host), genVarsForScript(credentials, container)));
} }
ErrorCode ServerController::buildContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config) ErrorCode ServerController::buildContainerWorker(const ServerCredentials &credentials, DockerContainer container,
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");
@@ -427,9 +418,13 @@ 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), genVarsForScript(credentials, container, config)), replaceVars(amnezia::scriptData(SharedScriptType::build_container),
genVarsForScript(credentials, container, config)),
cbReadStdOut); cbReadStdOut);
if (e) if (e)
return e; return e;
@@ -437,13 +432,17 @@ ErrorCode ServerController::buildContainerWorker(const ServerCredentials &creden
return e; return e;
} }
ErrorCode ServerController::runContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config) ErrorCode ServerController::runContainerWorker(const ServerCredentials &credentials, DockerContainer container,
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),
@@ -460,7 +459,8 @@ ErrorCode ServerController::runContainerWorker(const ServerCredentials &credenti
return e; return e;
} }
ErrorCode ServerController::configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config) ErrorCode ServerController::configureContainerWorker(const ServerCredentials &credentials, DockerContainer container,
QJsonObject &config)
{ {
QString stdOut; QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) { auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
@@ -477,12 +477,13 @@ ErrorCode ServerController::configureContainerWorker(const ServerCredentials &cr
genVarsForScript(credentials, container, config)), genVarsForScript(credentials, container, config)),
cbReadStdOut, cbReadStdErr); cbReadStdOut, cbReadStdErr);
VpnConfigurationsController::updateContainerConfigAfterInstallation(container, config, stdOut); m_configurator->updateContainerConfigAfterInstallation(container, config, stdOut);
return e; return e;
} }
ErrorCode ServerController::startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config) ErrorCode ServerController::startupContainerWorker(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &config)
{ {
QString script = amnezia::scriptData(ProtocolScriptType::container_startup, container); QString script = amnezia::scriptData(ProtocolScriptType::container_startup, container);
@@ -490,7 +491,8 @@ ErrorCode ServerController::startupContainerWorker(const ServerCredentials &cred
return ErrorCode::NoError; return ErrorCode::NoError;
} }
ErrorCode e = uploadTextFileToContainer(container, credentials, replaceVars(script, genVarsForScript(credentials, container, config)), ErrorCode e = uploadTextFileToContainer(container, credentials,
replaceVars(script, genVarsForScript(credentials, container, config)),
"/opt/amnezia/start.sh"); "/opt/amnezia/start.sh");
if (e) if (e)
return e; return e;
@@ -501,15 +503,14 @@ ErrorCode ServerController::startupContainerWorker(const ServerCredentials &cred
genVarsForScript(credentials, container, config))); genVarsForScript(credentials, container, config)));
} }
ServerController::Vars ServerController::genVarsForScript(const ServerCredentials &credentials, DockerContainer container, ServerController::Vars ServerController::genVarsForScript(const ServerCredentials &credentials,
const QJsonObject &config) DockerContainer container, 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;
@@ -517,19 +518,24 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
vars.append({ { "$REMOTE_HOST", credentials.hostName } }); vars.append({ { "$REMOTE_HOST", credentials.hostName } });
// OpenVPN vars // OpenVPN vars
vars.append({ { "$OPENVPN_SUBNET_IP", vars.append(
{ { "$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({ { "$OPENVPN_TRANSPORT_PROTO", vars.append(
{ { "$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", openvpnConfig.value(config_key::cipher).toString(protocols::openvpn::defaultCipher) } }); vars.append({ { "$OPENVPN_CIPHER",
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);
@@ -540,35 +546,39 @@ 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).toString(protocols::openvpn::defaultAdditionalClientConfig) } }); openvpnConfig.value(config_key::additional_client_config)
.toString(protocols::openvpn::defaultAdditionalClientConfig) } });
vars.append({ { "$OPENVPN_ADDITIONAL_SERVER_CONFIG", vars.append({ { "$OPENVPN_ADDITIONAL_SERVER_CONFIG",
openvpnConfig.value(config_key::additional_server_config).toString(protocols::openvpn::defaultAdditionalServerConfig) } }); openvpnConfig.value(config_key::additional_server_config)
.toString(protocols::openvpn::defaultAdditionalServerConfig) } });
// ShadowSocks vars // ShadowSocks vars
vars.append({ { "$SHADOWSOCKS_SERVER_PORT", ssConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort) } }); vars.append({ { "$SHADOWSOCKS_SERVER_PORT",
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", ssConfig.value(config_key::cipher).toString(protocols::shadowsocks::defaultCipher) } }); vars.append({ { "$SHADOWSOCKS_CIPHER",
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", cloakConfig.value(config_key::site).toString(protocols::cloak::defaultRedirSite) } }); vars.append({ { "$FAKE_WEB_SITE_ADDRESS",
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({ { "$WIREGUARD_SUBNET_IP", vars.append(
{ { "$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", wireguarConfig.value(config_key::port).toString(protocols::wireguard::defaultPort) } }); vars.append({ { "$WIREGUARD_SERVER_PORT",
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" } });
@@ -591,24 +601,32 @@ 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({ { "$SFTP_PORT", sftpConfig.value(config_key::port).toString(QString::number(ProtocolProps::defaultPort(Proto::Sftp))) } }); vars.append(
{ { "$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", amneziaWireguarConfig.value(config_key::port).toString(protocols::awg::defaultPort) } }); vars.append({ { "$AWG_SERVER_PORT",
amneziaWireguarConfig.value(config_key::port).toString(protocols::awg::defaultPort) } });
vars.append({ { "$JUNK_PACKET_COUNT", amneziaWireguarConfig.value(config_key::junkPacketCount).toString() } }); 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", amneziaWireguarConfig.value(config_key::responsePacketJunkSize).toString() } }); vars.append({ { "$RESPONSE_PACKET_JUNK_SIZE",
vars.append({ { "$INIT_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::initPacketMagicHeader).toString() } }); amneziaWireguarConfig.value(config_key::responsePacketJunkSize).toString() } });
vars.append({ { "$RESPONSE_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::responsePacketMagicHeader).toString() } }); vars.append({ { "$INIT_PACKET_MAGIC_HEADER",
vars.append({ { "$UNDERLOAD_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::underloadPacketMagicHeader).toString() } }); amneziaWireguarConfig.value(config_key::initPacketMagicHeader).toString() } });
vars.append({ { "$TRANSPORT_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::transportPacketMagicHeader).toString() } }); vars.append({ { "$RESPONSE_PACKET_MAGIC_HEADER",
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 = NetworkUtilities::getIPAddress(credentials.hostName); QString serverIp = Utils::getIPAddress(credentials.hostName);
if (!serverIp.isEmpty()) { if (!serverIp.isEmpty()) {
vars.append({ { "$SERVER_IP_ADDRESS", serverIp } }); vars.append({ { "$SERVER_IP_ADDRESS", serverIp } });
} else { } else {
@@ -618,7 +636,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 &) {
@@ -630,7 +648,11 @@ QString ServerController::checkSshConnection(const ServerCredentials &credential
return ErrorCode::NoError; return ErrorCode::NoError;
}; };
errorCode = runScript(credentials, amnezia::scriptData(SharedScriptType::check_connection), cbReadStdOut, cbReadStdErr); ErrorCode e =
runScript(credentials, amnezia::scriptData(SharedScriptType::check_connection), cbReadStdOut, cbReadStdErr);
if (errorCode)
*errorCode = e;
return stdOut; return stdOut;
} }
@@ -642,7 +664,9 @@ void ServerController::cancelInstallation()
ErrorCode ServerController::setupServerFirewall(const ServerCredentials &credentials) ErrorCode ServerController::setupServerFirewall(const ServerCredentials &credentials)
{ {
return runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::setup_host_firewall), genVarsForScript(credentials))); return runScript(
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)
@@ -654,7 +678,8 @@ QString ServerController::replaceVars(const QString &script, const Vars &vars)
return s; return s;
} }
ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config) ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &config)
{ {
if (container == DockerContainer::Dns) { if (container == DockerContainer::Dns) {
return ErrorCode::NoError; return ErrorCode::NoError;
@@ -677,12 +702,15 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential
QStringList fixedPorts = ContainerProps::fixedPortsForContainer(container); QStringList fixedPorts = ContainerProps::fixedPortsForContainer(container);
QString defaultPort("%1"); QString defaultPort("%1");
QString port = containerConfig.value(config_key::port).toString(defaultPort.arg(ProtocolProps::defaultPort(protocol))); QString port =
QString defaultTransportProto = ProtocolProps::transportProtoToString(ProtocolProps::defaultTransportProto(protocol), protocol); containerConfig.value(config_key::port).toString(defaultPort.arg(ProtocolProps::defaultPort(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("which lsof &>/dev/null || true && sudo lsof -i -P -n 2>/dev/null | grep -E ':%1 ").arg(port); QString script =
QString("which lsof &>/dev/null || 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);
} }
@@ -692,7 +720,8 @@ 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)), cbReadStdOut, cbReadStdErr); ErrorCode errorCode = runScript(credentials, replaceVars(script, genVarsForScript(credentials, container)),
cbReadStdOut, cbReadStdErr);
if (errorCode != ErrorCode::NoError) { if (errorCode != ErrorCode::NoError) {
return errorCode; return errorCode;
} }
@@ -720,7 +749,8 @@ 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 = runScript(credentials, replaceVars(scriptData, genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr); ErrorCode error =
runScript(credentials, replaceVars(scriptData, genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr);
if (!stdOut.contains("sudo")) if (!stdOut.contains("sudo"))
return ErrorCode::ServerUserNotInSudo; return ErrorCode::ServerUserNotInSudo;
@@ -750,7 +780,9 @@ ErrorCode ServerController::isServerDpkgBusy(const ServerCredentials &credential
return ErrorCode::ServerCancelInstallation; return ErrorCode::ServerCancelInstallation;
} }
stdOut.clear(); stdOut.clear();
runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::check_server_is_busy), genVarsForScript(credentials)), runScript(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"))
@@ -781,6 +813,147 @@ 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)
{ {
+24 -15
View File
@@ -25,18 +25,22 @@ 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, bool isUpdate = false); ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config,
ErrorCode updateContainer(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &oldConfig, bool isUpdate = false);
QJsonObject &newConfig); ErrorCode updateContainer(const ServerCredentials &credentials, DockerContainer container,
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(DockerContainer container, const ServerCredentials &credentials, const QString &file, ErrorCode uploadTextFileToContainer(
const QString &path, DockerContainer container, const ServerCredentials &credentials, const QString &file, const QString &path,
libssh::ScpOverwriteMode overwriteMode = libssh::ScpOverwriteMode::ScpOverwriteExisting); libssh::SftpOverwriteMode overwriteMode = libssh::SftpOverwriteMode::SftpOverwriteExisting);
QByteArray getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials, const QString &path, QByteArray getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials,
ErrorCode errorCode); const QString &path, ErrorCode *errorCode = nullptr);
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,
@@ -46,11 +50,12 @@ 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 runContainerScript(const ServerCredentials &credentials, DockerContainer container, QString script, ErrorCode
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); QString checkSshConnection(const ServerCredentials &credentials, ErrorCode *errorCode = nullptr);
void cancelInstallation(); void cancelInstallation();
@@ -59,19 +64,23 @@ public:
private: private:
ErrorCode installDockerWorker(const ServerCredentials &credentials, DockerContainer container); ErrorCode installDockerWorker(const ServerCredentials &credentials, DockerContainer container);
ErrorCode prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject()); ErrorCode prepareHostWorker(const ServerCredentials &credentials, DockerContainer container,
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, QJsonObject &config); ErrorCode configureContainerWorker(const ServerCredentials &credentials, DockerContainer container,
QJsonObject &config);
ErrorCode isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config); ErrorCode isServerPortBusy(const ServerCredentials &credentials, DockerContainer container,
bool isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig); const QJsonObject &config);
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::ScpOverwriteMode overwriteMode = libssh::ScpOverwriteMode::ScpOverwriteExisting); libssh::SftpOverwriteMode overwriteMode = libssh::SftpOverwriteMode::SftpOverwriteExisting);
ErrorCode setupServerFirewall(const ServerCredentials &credentials); ErrorCode setupServerFirewall(const ServerCredentials &credentials);
@@ -1,138 +0,0 @@
#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);
}
}
@@ -1,36 +0,0 @@
#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
+16 -29
View File
@@ -22,20 +22,6 @@ 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,
@@ -60,12 +46,25 @@ namespace amnezia
SshPrivateKeyFormatError = 304, SshPrivateKeyFormatError = 304,
SshTimeoutError = 305, SshTimeoutError = 305,
// Ssh scp errors // Ssh sftp errors
SshScpFailureError = 400, SshSftpEofError = 400,
SshSftpNoSuchFileError = 401,
SshSftpPermissionDeniedError = 402,
SshSftpFailureError = 403,
SshSftpBadMessageError = 404,
SshSftpNoConnectionError = 405,
SshSftpConnectionLostError = 406,
SshSftpOpUnsupportedError = 407,
SshSftpInvalidHandleError = 408,
SshSftpNoSuchPathError = 409,
SshSftpFileAlreadyExistsError = 410,
SshSftpWriteProtectError = 411,
SshSftpNoMediaError = 412,
// Local errors // Local errors
OpenVpnConfigMissing = 500, OpenVpnConfigMissing = 500,
OpenVpnManagementServerError = 501, OpenVpnManagementServerError = 501,
ConfigMissing = 502,
// Distro errors // Distro errors
OpenVpnExecutableMissing = 600, OpenVpnExecutableMissing = 600,
@@ -73,8 +72,6 @@ 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,
@@ -86,8 +83,6 @@ 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,
@@ -97,15 +92,7 @@ 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
+14 -11
View File
@@ -19,7 +19,6 @@ 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;
@@ -29,8 +28,20 @@ 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;
// Ssh scp errors // Libssh sftp errors
case(SshScpFailureError): errorMessage = QObject::tr("Scp error: Generic failure"); break; case(SshSftpEofError): errorMessage = QObject::tr("Sftp error: End-of-file encountered"); break;
case(SshSftpNoSuchFileError): errorMessage = QObject::tr("Sftp error: File does not exist"); break;
case(SshSftpPermissionDeniedError): errorMessage = QObject::tr("Sftp error: Permission denied"); break;
case(SshSftpFailureError): errorMessage = QObject::tr("Sftp error: Generic failure"); break;
case(SshSftpBadMessageError): errorMessage = QObject::tr("Sftp error: Garbage received from server"); break;
case(SshSftpNoConnectionError): errorMessage = QObject::tr("Sftp error: No connection has been set up"); break;
case(SshSftpConnectionLostError): errorMessage = QObject::tr("Sftp error: There was a connection, but we lost it"); break;
case(SshSftpOpUnsupportedError): errorMessage = QObject::tr("Sftp error: Operation not supported by libssh yet"); break;
case(SshSftpInvalidHandleError): errorMessage = QObject::tr("Sftp error: Invalid file handle"); break;
case(SshSftpNoSuchPathError): errorMessage = QObject::tr("Sftp error: No such file or directory path exists"); break;
case(SshSftpFileAlreadyExistsError): errorMessage = QObject::tr("Sftp error: An attempt to create an already existing file or directory has been made"); break;
case(SshSftpWriteProtectError): errorMessage = QObject::tr("Sftp error: Write-protected filesystem"); break;
case(SshSftpNoMediaError): errorMessage = QObject::tr("Sftp error: No media was in remote drive"); break;
// Local errors // Local errors
case (OpenVpnConfigMissing): errorMessage = QObject::tr("OpenVPN config missing"); break; case (OpenVpnConfigMissing): errorMessage = QObject::tr("OpenVPN config missing"); break;
@@ -57,14 +68,6 @@ 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;
@@ -1,12 +0,0 @@
#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);
}
-15
View File
@@ -1,15 +0,0 @@
#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
+1 -1
View File
@@ -71,7 +71,7 @@ QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
} }
QRemoteObjectPendingReply<int> futureResult = Instance()->m_ipcClient->createPrivilegedProcess(); QRemoteObjectPendingReply<int> futureResult = Instance()->m_ipcClient->createPrivilegedProcess();
futureResult.waitForFinished(5000); futureResult.waitForFinished(1000);
int pid = futureResult.returnValue(); int pid = futureResult.returnValue();
-462
View File
@@ -1,462 +0,0 @@
#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
}
-36
View File
@@ -1,36 +0,0 @@
#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
+1 -3
View File
@@ -13,12 +13,11 @@ 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 QString(); default: return "";
} }
} }
@@ -48,7 +47,6 @@ 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();
} }
} }
+1 -2
View File
@@ -27,8 +27,7 @@ enum ProtocolScriptType {
container_startup, container_startup,
openvpn_template, openvpn_template,
wireguard_template, wireguard_template,
awg_template, awg_template
xray_template
}; };
+90 -63
View File
@@ -10,10 +10,16 @@ const uint32_t S_IRWXU = 0644;
#endif #endif
namespace libssh { namespace libssh {
constexpr auto libsshTimeoutError{"Timeout connecting to"}; const QString 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();
@@ -23,13 +29,6 @@ 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();
@@ -172,13 +171,13 @@ namespace libssh {
return ErrorCode::NoError; return ErrorCode::NoError;
}; };
auto errorCode = readOutput(false); auto error = readOutput(false);
if (errorCode != ErrorCode::NoError) { if (error != ErrorCode::NoError) {
return errorCode; return error;
} }
errorCode = readOutput(true); error = readOutput(true);
if (errorCode != ErrorCode::NoError) { if (error != ErrorCode::NoError) {
return errorCode; return error;
} }
} else { } else {
return closeChannel(); return closeChannel();
@@ -223,79 +222,100 @@ namespace libssh {
return fromLibsshErrorCode(); return fromLibsshErrorCode();
} }
ErrorCode Client::scpFileCopy(const ScpOverwriteMode overwriteMode, const QString& localPath, const QString& remotePath, const QString &fileDesc) ErrorCode Client::sftpFileCopy(const SftpOverwriteMode overwriteMode, const QString& localPath, const QString& remotePath, const QString &fileDesc)
{ {
m_scpSession = ssh_scp_new(m_session, SSH_SCP_WRITE, remotePath.toStdString().c_str()); m_sftpSession = sftp_new(m_session);
if (m_scpSession == nullptr) { if (m_sftpSession == nullptr) {
return fromLibsshErrorCode(); return closeSftpSession();
} }
if (ssh_scp_init(m_scpSession) != SSH_OK) { int result = sftp_init(m_sftpSession);
auto errorCode = fromLibsshErrorCode();
closeScpSession(); if (result != SSH_OK) {
return errorCode; return closeSftpSession();
} }
QFutureWatcher<ErrorCode> watcher; QFutureWatcher<ErrorCode> watcher;
connect(&watcher, &QFutureWatcher<ErrorCode>::finished, this, &Client::scpFileCopyFinished); connect(&watcher, &QFutureWatcher<ErrorCode>::finished, this, &Client::sftpFileCopyFinished);
QFuture<ErrorCode> future = QtConcurrent::run([this, overwriteMode, &localPath, &remotePath, &fileDesc]() {
const int accessType = O_WRONLY | O_CREAT | overwriteMode;
const int localFileSize = QFileInfo(localPath).size();
int result = ssh_scp_push_file(m_scpSession, remotePath.toStdString().c_str(), localFileSize, accessType); QFuture<ErrorCode> future = QtConcurrent::run([this, overwriteMode, &localPath, &remotePath, &fileDesc]() {
if (result != SSH_OK) { int accessType = O_WRONLY | O_CREAT | overwriteMode;
return fromLibsshErrorCode(); sftp_file file;
const size_t bufferSize = 16384;
char buffer[bufferSize];
file = sftp_open(m_sftpSession, remotePath.toStdString().c_str(), accessType, S_IRWXU);
if (file == nullptr) {
return closeSftpSession();
} }
int localFileSize = QFileInfo(localPath).size();
int chunksCount = localFileSize / (bufferSize);
QFile fin(localPath); QFile fin(localPath);
if (fin.open(QIODevice::ReadOnly)) { if (fin.open(QIODevice::ReadOnly)) {
constexpr size_t bufferSize = 16384; for (int currentChunkId = 0; currentChunkId < chunksCount; currentChunkId++) {
int transferred = 0; QByteArray chunk = fin.read(bufferSize);
int currentChunkSize = bufferSize; if (chunk.size() != bufferSize) return ErrorCode::SshSftpEofError;
while (transferred < localFileSize) { int bytesWritten = sftp_write(file, chunk.data(), chunk.size());
// Last Chunk if (bytesWritten != chunk.size()) {
if ((localFileSize - transferred) < bufferSize) { fin.close();
currentChunkSize = localFileSize % bufferSize; sftp_close(file);
return closeSftpSession();
}
} }
QByteArray chunk = fin.read(currentChunkSize); int lastChunkSize = localFileSize % bufferSize;
if (chunk.size() != currentChunkSize) {
return fromFileErrorCode(fin.error());
}
result = ssh_scp_write(m_scpSession, chunk.data(), chunk.size()); if (lastChunkSize != 0) {
if (result != SSH_OK) { QByteArray lastChunk = fin.read(lastChunkSize);
return fromLibsshErrorCode(); if (lastChunk.size() != lastChunkSize) return ErrorCode::SshSftpEofError;
}
transferred += currentChunkSize; int bytesWritten = sftp_write(file, lastChunk.data(), lastChunkSize);
if (bytesWritten != lastChunkSize) {
fin.close();
sftp_close(file);
return closeSftpSession();
}
} }
} else { } else {
return fromFileErrorCode(fin.error()); sftp_close(file);
return closeSftpSession();
} }
return ErrorCode::NoError; fin.close();
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::scpFileCopyFinished, &wait, &QEventLoop::quit); QObject::connect(this, &Client::sftpFileCopyFinished, &wait, &QEventLoop::quit);
wait.exec(); wait.exec();
closeScpSession();
return watcher.result(); return watcher.result();
} }
void Client::closeScpSession() ErrorCode Client::closeSftpSession()
{ {
if (m_scpSession != nullptr) { auto errorCode = fromLibsshSftpErrorCode(sftp_get_error(m_sftpSession));
ssh_scp_free(m_scpSession); if (m_sftpSession != nullptr) {
m_scpSession = nullptr; sftp_free(m_sftpSession);
m_sftpSession = nullptr;
} }
qCritical() << ssh_get_error(m_session);
return errorCode;
} }
ErrorCode Client::fromLibsshErrorCode() ErrorCode Client::fromLibsshErrorCode()
@@ -317,17 +337,24 @@ namespace libssh {
default: return ErrorCode::SshInternalError; default: return ErrorCode::SshInternalError;
} }
} }
ErrorCode Client::fromLibsshSftpErrorCode(int errorCode)
ErrorCode Client::fromFileErrorCode(QFileDevice::FileError fileError)
{ {
switch (fileError) { switch (errorCode) {
case QFileDevice::NoError: return ErrorCode::NoError; case(SSH_FX_OK): return ErrorCode::NoError;
case QFileDevice::ReadError: return ErrorCode::ReadError; case(SSH_FX_EOF): return ErrorCode::SshSftpEofError;
case QFileDevice::OpenError: return ErrorCode::OpenError; case(SSH_FX_NO_SUCH_FILE): return ErrorCode::SshSftpNoSuchFileError;
case QFileDevice::PermissionsError: return ErrorCode::PermissionsError; case(SSH_FX_PERMISSION_DENIED): return ErrorCode::SshSftpPermissionDeniedError;
case QFileDevice::FatalError: return ErrorCode::FatalError; case(SSH_FX_FAILURE): return ErrorCode::SshSftpFailureError;
case QFileDevice::AbortError: return ErrorCode::AbortError; case(SSH_FX_BAD_MESSAGE): return ErrorCode::SshSftpBadMessageError;
default: return ErrorCode::UnspecifiedError; case(SSH_FX_NO_CONNECTION): return ErrorCode::SshSftpNoConnectionError;
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;
} }
} }
+11 -11
View File
@@ -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 ScpOverwriteMode { enum SftpOverwriteMode {
/*! Overwrite any existing files */ /*! Overwrite any existing files */
ScpOverwriteExisting = O_TRUNC, SftpOverwriteExisting = O_TRUNC,
/*! Append new content if the file already exists */ /*! Append new content if the file already exists */
ScpAppendToExisting = O_APPEND SftpAppendToExisting = O_APPEND
}; };
class Client : public QObject class Client : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
Client() = default; Client(QObject *parent = nullptr);
~Client() = default; ~Client();
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 scpFileCopy(const ScpOverwriteMode overwriteMode, ErrorCode sftpFileCopy(const SftpOverwriteMode overwriteMode,
const QString &localPath, const QString &localPath,
const QString &remotePath, const QString &remotePath,
const QString& 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();
void closeScpSession(); ErrorCode closeSftpSession();
ErrorCode fromLibsshErrorCode(); ErrorCode fromLibsshErrorCode();
ErrorCode fromFileErrorCode(QFileDevice::FileError fileError); ErrorCode fromLibsshSftpErrorCode(int errorCode);
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;
ssh_scp m_scpSession = nullptr; sftp_session m_sftpSession = nullptr;
static std::function<QString()> m_passphraseCallback; static std::function<QString()> m_passphraseCallback;
signals: signals:
void writeToChannelFinished(); void writeToChannelFinished();
void scpFileCopyFinished(); void sftpFileCopyFinished();
}; };
} }
+6 -32
View File
@@ -248,23 +248,9 @@ 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)
{
config.m_deviceMTU = 1420;
} else {
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();
config.m_deviceIpv6Address = obj.value("deviceIpv6Address").toString(); config.m_deviceIpv6Address = obj.value("deviceIpv6Address").toString();
if (config.m_deviceIpv4Address.isNull() && if (config.m_deviceIpv4Address.isNull() &&
@@ -374,31 +360,19 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
return false; return false;
} }
if (!obj.value("Jc").isNull()) { if (!obj.value("Jc").isNull() && !obj.value("Jmin").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();
} }
+1 -3
View File
@@ -35,9 +35,7 @@ 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, int inetAdapterIndex = 0) { virtual void prepareActivation(const InterfaceConfig& config){
Q_UNUSED(config) };
virtual void activateSplitTunnel(const InterfaceConfig& config, int vpnAdapterIndex = 0) {
Q_UNUSED(config)}; Q_UNUSED(config)};
QString logs(); QString logs();
-6
View File
@@ -23,7 +23,6 @@ QJsonObject InterfaceConfig::toJson() const {
json.insert("serverIpv4AddrIn", QJsonValue(m_serverIpv4AddrIn)); json.insert("serverIpv4AddrIn", QJsonValue(m_serverIpv4AddrIn));
json.insert("serverIpv6AddrIn", QJsonValue(m_serverIpv6AddrIn)); json.insert("serverIpv6AddrIn", QJsonValue(m_serverIpv6AddrIn));
json.insert("serverPort", QJsonValue((double)m_serverPort)); json.insert("serverPort", QJsonValue((double)m_serverPort));
json.insert("deviceMTU", QJsonValue(m_deviceMTU));
if ((m_hopType == InterfaceConfig::MultiHopExit) || if ((m_hopType == InterfaceConfig::MultiHopExit) ||
(m_hopType == InterfaceConfig::SingleHop)) { (m_hopType == InterfaceConfig::SingleHop)) {
json.insert("serverIpv4Gateway", QJsonValue(m_serverIpv4Gateway)); json.insert("serverIpv4Gateway", QJsonValue(m_serverIpv4Gateway));
@@ -86,13 +85,8 @@ QString InterfaceConfig::toWgConf(const QMap<QString, QString>& extra) const {
if (addresses.isEmpty()) { if (addresses.isEmpty()) {
return ""; return "";
} }
out << "Address = " << addresses.join(", ") << "\n"; out << "Address = " << addresses.join(", ") << "\n";
if (m_deviceMTU) {
out << "MTU = " << m_deviceMTU << "\n";
}
if (!m_dnsServer.isNull()) { if (!m_dnsServer.isNull()) {
QStringList dnsServers(m_dnsServer); QStringList dnsServers(m_dnsServer);
// If the DNS is not the Gateway, it's a user defined DNS // If the DNS is not the Gateway, it's a user defined DNS
-1
View File
@@ -33,7 +33,6 @@ class InterfaceConfig {
QString m_serverIpv6AddrIn; QString m_serverIpv6AddrIn;
QString m_dnsServer; QString m_dnsServer;
int m_serverPort = 0; int m_serverPort = 0;
int m_deviceMTU = 1420;
QList<IPAddress> m_allowedIPAddressRanges; QList<IPAddress> m_allowedIPAddressRanges;
QStringList m_excludedAddresses; QStringList m_excludedAddresses;
QStringList m_vpnDisabledApps; QStringList m_vpnDisabledApps;
-5
View File
@@ -1,5 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 518 B

-5
View File
@@ -1,5 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 581 B

@@ -1,6 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 511 B

-33
View File
@@ -1,33 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>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>
+1 -10
View File
@@ -84,20 +84,11 @@ 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+WireGuard.swift ${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+OpenVPNAdapterDelegate.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
@@ -1,25 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>1C8F.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
</array>
</dict>
</plist>
+6 -3
View File
@@ -26,10 +26,9 @@ int main(int argc, char *argv[])
AllowSetForegroundWindow(ASFW_ANY); AllowSetForegroundWindow(ASFW_ANY);
#endif #endif
#ifdef Q_OS_ANDROID
// QTBUG-95974 QTBUG-95764 QTBUG-102168 // QTBUG-95974 QTBUG-95764 QTBUG-102168
#ifdef Q_OS_ANDROID
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)
@@ -49,6 +48,10 @@ 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);
@@ -62,7 +65,7 @@ int main(int argc, char *argv[])
if (doExec) { if (doExec) {
app.init(); app.init();
qInfo().noquote() << QString("Started %1 version %2 %3").arg(APPLICATION_NAME, APP_VERSION, GIT_COMMIT_HASH); qInfo().noquote() << QString("Started %1 version %2").arg(APPLICATION_NAME, APP_VERSION);
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();
+8 -32
View File
@@ -117,9 +117,6 @@ 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;
@@ -127,21 +124,13 @@ 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));
// json.insert("serverIpv6AddrIn", QJsonValue(hop.m_server.ipv6AddrIn())); // json.insert("serverIpv6AddrIn", QJsonValue(hop.m_server.ipv6AddrIn()));
json.insert("deviceMTU", wgConfig.value(amnezia::config_key::mtu));
json.insert("serverPort", wgConfig.value(amnezia::config_key::port).toInt()); json.insert("serverPort", wgConfig.value(amnezia::config_key::port).toInt());
json.insert("serverIpv4Gateway", wgConfig.value(amnezia::config_key::hostName)); json.insert("serverIpv4Gateway", wgConfig.value(amnezia::config_key::hostName));
// json.insert("serverIpv6Gateway", QJsonValue(hop.m_server.ipv6Gateway())); // json.insert("serverIpv6Gateway", QJsonValue(hop.m_server.ipv6Gateway()));
json.insert("dnsServer", rawConfig.value(amnezia::config_key::dns1)); json.insert("dnsServer", rawConfig.value(amnezia::config_key::dns1));
@@ -220,7 +209,12 @@ 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));
@@ -232,24 +226,6 @@ 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);
+42 -66
View File
@@ -2,9 +2,6 @@
#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"
@@ -59,10 +56,26 @@ AndroidController::AndroidController() : QObject()
Qt::QueuedConnection); Qt::QueuedConnection);
connect( connect(
this, &AndroidController::vpnStateChanged, this, this, &AndroidController::vpnConnected, this,
[this](AndroidController::ConnectionState state) { [this]() {
qDebug() << "Android event: VPN state changed:" << textConnectionState(state); qDebug() << "Android event: VPN connected";
emit connectionStateChanged(convertState(state)); emit connectionStateChanged(Vpn::ConnectionState::Connected);
},
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);
@@ -93,7 +106,9 @@ 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)},
{"onVpnStateChanged", "(I)V", reinterpret_cast<void *>(onVpnStateChanged)}, {"onVpnConnected", "()V", reinterpret_cast<void *>(onVpnConnected)},
{"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)},
@@ -143,11 +158,6 @@ 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",
@@ -207,56 +217,6 @@ 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;
@@ -410,14 +370,30 @@ void AndroidController::onVpnPermissionRejected(JNIEnv *env, jobject thiz)
} }
// static // static
void AndroidController::onVpnStateChanged(JNIEnv *env, jobject thiz, jint stateCode) void AndroidController::onVpnConnected(JNIEnv *env, jobject thiz)
{ {
Q_UNUSED(env); Q_UNUSED(env);
Q_UNUSED(thiz); Q_UNUSED(thiz);
auto state = ConnectionState(stateCode); emit AndroidController::instance()->vpnConnected();
}
emit AndroidController::instance()->vpnStateChanged(state); // static
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,7 +2,6 @@
#define ANDROID_CONTROLLER_H #define ANDROID_CONTROLLER_H
#include <QJniObject> #include <QJniObject>
#include <QPixmap>
#include "protocols/vpnprotocol.h" #include "protocols/vpnprotocol.h"
@@ -21,9 +20,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
@@ -31,7 +30,6 @@ 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);
@@ -40,10 +38,6 @@ 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);
@@ -54,7 +48,9 @@ signals:
void serviceDisconnected(); void serviceDisconnected();
void serviceError(); void serviceError();
void vpnPermissionRejected(); void vpnPermissionRejected();
void vpnStateChanged(ConnectionState state); void vpnConnected();
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);
@@ -81,7 +77,9 @@ 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 onVpnStateChanged(JNIEnv *env, jobject thiz, jint stateCode); static void onVpnConnected(JNIEnv *env, jobject thiz);
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);
+4 -44
View File
@@ -2,8 +2,6 @@ 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 {
@@ -16,11 +14,6 @@ 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)
@@ -36,23 +29,16 @@ 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) {
records = str.split(whereSeparator: \.isNewline) self.records = str.split(whereSeparator: \.isNewline)
.map { .compactMap {
if let record = Record(String($0)) { Record(String($0))!
lastRecordDate = record.date
return record
} else {
return Record(date: lastRecordDate, level: .error, message: "LOG: \($0)")
}
} }
} }
@@ -74,28 +60,6 @@ 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 }
@@ -116,7 +80,3 @@ 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)
}
+1 -8
View File
@@ -4,27 +4,20 @@ 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)
} }
+5 -26
View File
@@ -30,8 +30,6 @@ 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) {
@@ -66,38 +64,19 @@ extension Log.Record {
init(from osLogType: OSLogType) { init(from osLogType: OSLogType) {
switch osLogType { switch osLogType {
case .default: case OSLogType.default:
self = .info self = .info
case .info: case OSLogType.info:
self = .info self = .info
case .debug: case OSLogType.debug:
self = .debug self = .debug
case .error: case OSLogType.error:
self = .error self = .error
case .fault: case OSLogType.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
}
}
} }
} }
+9 -9
View File
@@ -1,18 +1,18 @@
import Foundation import Foundation
import os.log import os.log
public func wg_log(_ type: OSLogType, title: String = "", staticMessage: StaticString) { public func wg_log(_ type: OSLogType, staticMessage: StaticString) {
neLog(type, title: "WG: \(title)", message: "\(staticMessage)") guard Log.isLoggingEnabled else { return }
Log.Record(date: Date(), level: Log.Record.Level(from: type), message: "\(staticMessage)").save(at: Log.neLogURL)
} }
public func wg_log(_ type: OSLogType, title: String = "", message: String) { public func wg_log(_ type: OSLogType, message: String) {
neLog(type, title: "WG: \(title)", message: message) log(type, message: message)
} }
public func ovpnLog(_ type: OSLogType, title: String = "", message: String) { public func log(_ type: OSLogType, message: String) {
neLog(type, title: "OVPN: \(title)", message: message) guard Log.isLoggingEnabled else { return }
}
public func neLog(_ type: OSLogType, title: String = "", message: String) { Log.Record(date: Date(), level: Log.Record.Level(from: type), message: message).save(at: Log.neLogURL)
Log.log(type, title: "NE: \(title)", message: message)
} }
@@ -1,225 +0,0 @@
import Foundation
import NetworkExtension
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 {
// OpenVPNAdapter calls this delegate method to configure a VPN tunnel.
// `completionHandler` callback requires an object conforming to `OpenVPNAdapterPacketFlow`
// protocol if the tunnel is configured without errors. Otherwise send nil.
// `OpenVPNAdapterPacketFlow` method signatures are similar to `NEPacketTunnelFlow` so
// you can just extend that class to adopt `OpenVPNAdapterPacketFlow` protocol and
// send `self.packetFlow` to `completionHandler` callback.
func openVPNAdapter(
_ openVPNAdapter: OpenVPNAdapter,
configureTunnelWithNetworkSettings networkSettings: NEPacketTunnelNetworkSettings?,
completionHandler: @escaping (Error?) -> Void
) {
// In order to direct all DNS queries first to the VPN DNS servers before the primary DNS servers
// send empty string to NEDNSSettings.matchDomains
networkSettings?.dnsSettings?.matchDomains = [""]
if splitTunnelType == 1 {
var ipv4IncludedRoutes = [NEIPv4Route]()
for allowedIPString in splitTunnelSites {
if let allowedIP = IPAddressRange(from: allowedIPString) {
ipv4IncludedRoutes.append(NEIPv4Route(
destinationAddress: "\(allowedIP.address)",
subnetMask: "\(allowedIP.subnetMask())"))
}
}
networkSettings?.ipv4Settings?.includedRoutes = ipv4IncludedRoutes
} else {
if splitTunnelType == 2 {
var ipv4ExcludedRoutes = [NEIPv4Route]()
var ipv4IncludedRoutes = [NEIPv4Route]()
var ipv6IncludedRoutes = [NEIPv6Route]()
for excludeIPString in splitTunnelSites {
if let excludeIP = IPAddressRange(from: excludeIPString) {
ipv4ExcludedRoutes.append(NEIPv4Route(
destinationAddress: "\(excludeIP.address)",
subnetMask: "\(excludeIP.subnetMask())"))
}
}
if let allIPv4 = IPAddressRange(from: "0.0.0.0/0") {
ipv4IncludedRoutes.append(NEIPv4Route(
destinationAddress: "\(allIPv4.address)",
subnetMask: "\(allIPv4.subnetMask())"))
}
if let allIPv6 = IPAddressRange(from: "::/0") {
ipv6IncludedRoutes.append(NEIPv6Route(
destinationAddress: "\(allIPv6.address)",
networkPrefixLength: NSNumber(value: allIPv6.networkPrefixLength)))
}
networkSettings?.ipv4Settings?.includedRoutes = ipv4IncludedRoutes
networkSettings?.ipv6Settings?.includedRoutes = ipv6IncludedRoutes
networkSettings?.ipv4Settings?.excludedRoutes = ipv4ExcludedRoutes
}
}
// Set the network settings for the current tunneling session.
setTunnelNetworkSettings(networkSettings, completionHandler: completionHandler)
}
// Process events returned by the OpenVPN library
func openVPNAdapter(
_ openVPNAdapter: OpenVPNAdapter,
handleEvent event: OpenVPNAdapterEvent,
message: String?) {
switch event {
case .connected:
if reasserting {
reasserting = false
}
guard let startHandler = startHandler else { return }
startHandler(nil)
self.startHandler = nil
case .disconnected:
guard let stopHandler = stopHandler else { return }
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
stopHandler()
self.stopHandler = nil
case .reconnecting:
reasserting = true
default:
break
}
}
// Handle errors thrown by the OpenVPN library
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleError error: Error) {
// Handle only fatal errors
guard let fatal = (error as NSError).userInfo[OpenVPNAdapterErrorFatalKey] as? Bool,
fatal == true else { return }
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
if let startHandler {
startHandler(error)
self.startHandler = nil
} else {
cancelTunnelWithError(error)
}
}
// Use this method to process any log message returned by OpenVPN library.
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleLogMessage logMessage: String) {
// Handle log messages
ovpnLog(.info, message: logMessage)
}
}
extension NEPacketTunnelFlow: OpenVPNAdapterPacketFlow {}
@@ -0,0 +1,129 @@
import Foundation
import NetworkExtension
import OpenVPNAdapter
extension PacketTunnelProvider: OpenVPNAdapterDelegate {
// OpenVPNAdapter calls this delegate method to configure a VPN tunnel.
// `completionHandler` callback requires an object conforming to `OpenVPNAdapterPacketFlow`
// protocol if the tunnel is configured without errors. Otherwise send nil.
// `OpenVPNAdapterPacketFlow` method signatures are similar to `NEPacketTunnelFlow` so
// you can just extend that class to adopt `OpenVPNAdapterPacketFlow` protocol and
// send `self.packetFlow` to `completionHandler` callback.
func openVPNAdapter(
_ openVPNAdapter: OpenVPNAdapter,
configureTunnelWithNetworkSettings networkSettings: NEPacketTunnelNetworkSettings?,
completionHandler: @escaping (Error?) -> Void
) {
// In order to direct all DNS queries first to the VPN DNS servers before the primary DNS servers
// send empty string to NEDNSSettings.matchDomains
networkSettings?.dnsSettings?.matchDomains = [""]
if splitTunnelType == "1" {
var ipv4IncludedRoutes = [NEIPv4Route]()
let STSdata = Data(splitTunnelSites!.utf8)
do {
guard let STSArray = try JSONSerialization.jsonObject(with: STSdata) as? [String] else { return }
for allowedIPString in STSArray {
if let allowedIP = IPAddressRange(from: allowedIPString) {
ipv4IncludedRoutes.append(NEIPv4Route(
destinationAddress: "\(allowedIP.address)",
subnetMask: "\(allowedIP.subnetMask())"))
}
}
} catch {
wg_log(.error, message: "Parse JSONSerialization Error")
}
networkSettings?.ipv4Settings?.includedRoutes = ipv4IncludedRoutes
} else {
if splitTunnelType == "2" {
var ipv4ExcludedRoutes = [NEIPv4Route]()
var ipv4IncludedRoutes = [NEIPv4Route]()
var ipv6IncludedRoutes = [NEIPv6Route]()
let STSdata = Data(splitTunnelSites!.utf8)
do {
guard let STSArray = try JSONSerialization.jsonObject(with: STSdata) as? [String] else { return }
for excludeIPString in STSArray {
if let excludeIP = IPAddressRange(from: excludeIPString) {
ipv4ExcludedRoutes.append(NEIPv4Route(
destinationAddress: "\(excludeIP.address)",
subnetMask: "\(excludeIP.subnetMask())"))
}
}
} catch {
wg_log(.error, message: "Parse JSONSerialization Error")
}
if let allIPv4 = IPAddressRange(from: "0.0.0.0/0") {
ipv4IncludedRoutes.append(NEIPv4Route(
destinationAddress: "\(allIPv4.address)",
subnetMask: "\(allIPv4.subnetMask())"))
}
if let allIPv6 = IPAddressRange(from: "::/0") {
ipv6IncludedRoutes.append(NEIPv6Route(
destinationAddress: "\(allIPv6.address)",
networkPrefixLength: NSNumber(value: allIPv6.networkPrefixLength)))
}
networkSettings?.ipv4Settings?.includedRoutes = ipv4IncludedRoutes
networkSettings?.ipv6Settings?.includedRoutes = ipv6IncludedRoutes
networkSettings?.ipv4Settings?.excludedRoutes = ipv4ExcludedRoutes
}
}
// Set the network settings for the current tunneling session.
setTunnelNetworkSettings(networkSettings, completionHandler: completionHandler)
}
// Process events returned by the OpenVPN library
func openVPNAdapter(
_ openVPNAdapter: OpenVPNAdapter,
handleEvent event: OpenVPNAdapterEvent,
message: String?) {
switch event {
case .connected:
if reasserting {
reasserting = false
}
guard let startHandler = startHandler else { return }
startHandler(nil)
self.startHandler = nil
case .disconnected:
guard let stopHandler = stopHandler else { return }
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
stopHandler()
self.stopHandler = nil
case .reconnecting:
reasserting = true
default:
break
}
}
// Handle errors thrown by the OpenVPN library
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleError error: Error) {
// Handle only fatal errors
guard let fatal = (error as NSError).userInfo[OpenVPNAdapterErrorFatalKey] as? Bool,
fatal == true else { return }
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
if let startHandler {
startHandler(error)
self.startHandler = nil
} else {
cancelTunnelWithError(error)
}
}
// Use this method to process any log message returned by OpenVPN library.
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleLogMessage logMessage: String) {
// Handle log messages
wg_log(.info, message: logMessage)
}
}
@@ -1,221 +0,0 @@
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
}
}
}
+327 -60
View File
@@ -34,13 +34,13 @@ struct Constants {
} }
class PacketTunnelProvider: NEPacketTunnelProvider { class PacketTunnelProvider: NEPacketTunnelProvider {
lazy var wgAdapter = { private 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)
} }
}() }()
lazy var ovpnAdapter: OpenVPNAdapter = { private lazy var ovpnAdapter: OpenVPNAdapter = {
let adapter = OpenVPNAdapter() let adapter = OpenVPNAdapter()
adapter.delegate = self adapter.delegate = self
return adapter return adapter
@@ -49,8 +49,9 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
/// Internal queue. /// Internal queue.
private let dispatchQueue = DispatchQueue(label: "PacketTunnel", qos: .utility) private let dispatchQueue = DispatchQueue(label: "PacketTunnel", qos: .utility)
var splitTunnelType: Int! private var openVPNConfig: Data?
var splitTunnelSites: [String]! var splitTunnelType: String?
var splitTunnelSites: String?
let vpnReachability = OpenVPNReachability() let vpnReachability = OpenVPNReachability()
@@ -59,27 +60,20 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
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) {
guard let message = String(data: messageData, encoding: .utf8) else { let tmpStr = String(data: messageData, encoding: .utf8)!
if let completionHandler { wg_log(.error, message: tmpStr)
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 {
neLog(.error, message: "Failed to serialize message from app") log(.error, message: "Failed to serialize message from app")
return return
} }
guard let completionHandler else { guard let completionHandler else {
neLog(.error, message: "Missing message completion handler") log(.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 {
neLog(.error, message: "Missing action key in app message") log(.error, message: "Missing action key in app message")
completionHandler(nil) completionHandler(nil)
return return
} }
@@ -87,6 +81,22 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
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) {
@@ -94,7 +104,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
let activationAttemptId = options?[Constants.kActivationAttemptId] as? String let activationAttemptId = options?[Constants.kActivationAttemptId] as? String
let errorNotifier = ErrorNotifier(activationAttemptId: activationAttemptId) let errorNotifier = ErrorNotifier(activationAttemptId: activationAttemptId)
neLog(.info, message: "Start tunnel") log(.info, message: "PacketTunnelProvider startTunnel")
if let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol { if let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol {
let providerConfiguration = protocolConfiguration.providerConfiguration let providerConfiguration = protocolConfiguration.providerConfiguration
@@ -153,6 +163,271 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
} }
} }
// 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 wgConfig: Data = providerConfiguration[Constants.wireGuardConfigKey] as? Data else {
wg_log(.error, message: "Can't start WireGuard config missing")
completionHandler(nil)
return
}
guard let wgConfigStr = try? JSONDecoder().decode(WGConfig.self, from: wgConfig).str,
let tunnelConfiguration = try? TunnelConfiguration(fromWgQuickConfig: wgConfigStr)
else {
wg_log(.error, message: "Can't parse WireGuard config")
completionHandler(nil)
return
}
log(.info, message: "wgConfig: \(wgConfigStr.replacingOccurrences(of: "\n", with: " "))")
if tunnelConfiguration.peers.first!.allowedIPs
.map({ $0.stringRepresentation })
.joined(separator: ", ") == "0.0.0.0/0, ::/0" {
if splitTunnelType == "1" {
for index in tunnelConfiguration.peers.indices {
tunnelConfiguration.peers[index].allowedIPs.removeAll()
var allowedIPs = [IPAddressRange]()
let STSdata = Data(splitTunnelSites!.utf8)
do {
guard let STSArray = try JSONSerialization.jsonObject(with: STSdata) as? [String] else { return }
for allowedIPString in STSArray {
if let allowedIP = IPAddressRange(from: allowedIPString) {
allowedIPs.append(allowedIP)
}
}
} catch {
wg_log(.error, message: "Parse JSONSerialization Error")
}
tunnelConfiguration.peers[index].allowedIPs = allowedIPs
}
} else if splitTunnelType == "2" {
for index in tunnelConfiguration.peers.indices {
var excludeIPs = [IPAddressRange]()
let STSdata = Data(splitTunnelSites!.utf8)
do {
guard let STSArray = try JSONSerialization.jsonObject(with: STSdata) as? [String] else { return }
for excludeIPString in STSArray {
if let excludeIP = IPAddressRange(from: excludeIPString) {
excludeIPs.append(excludeIP)
}
}
} catch {
wg_log(.error, message: "Parse JSONSerialization Error")
}
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()
}
}
}
private func startOpenVPN(completionHandler: @escaping (Error?) -> Void) {
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
let providerConfiguration = protocolConfiguration.providerConfiguration,
let ovpnConfiguration: Data = providerConfiguration[Constants.ovpnConfigKey] as? Data else {
wg_log(.error, message: "Can't start startOpenVPN()")
return
}
setupAndlaunchOpenVPN(withConfig: ovpnConfiguration, completionHandler: completionHandler)
}
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() {
@@ -188,7 +463,42 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
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 {
@@ -200,46 +510,3 @@ 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 devices 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
View File
@@ -1,7 +1,4 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
@interface QIOSApplicationDelegate @interface QtAppDelegate : UIResponder <UIApplicationDelegate>
@end
@interface QIOSApplicationDelegate (AmneziaVPNDelegate)
@end @end
+46 -7
View File
@@ -3,17 +3,41 @@
#include <QFile> #include <QFile>
@implementation QtAppDelegate {
UIView *_screen;
}
+(QtAppDelegate *)sharedQtAppDelegate {
static dispatch_once_t pred;
static QtAppDelegate *shared = nil;
dispatch_once(&pred, ^{
shared = [[super alloc] init];
});
return shared;
}
@implementation QIOSApplicationDelegate (AmneziaVPNDelegate)
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{ {
[application setMinimumBackgroundFetchInterval: UIApplicationBackgroundFetchIntervalMinimum]; [application setMinimumBackgroundFetchInterval: UIApplicationBackgroundFetchIntervalMinimum];
// Override point for customization after application launch. // Override point for customization after application launch.
NSLog(@"Application didFinishLaunchingWithOptions"); NSLog(@"Did this launch option happen");
return YES; return YES;
} }
- (void)applicationWillResignActive:(UIApplication *)application
{
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
_screen = [UIScreen.mainScreen snapshotViewAfterScreenUpdates: false];
UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle: UIBlurEffectStyleDark];
UIVisualEffectView *blurBackground = [[UIVisualEffectView alloc] initWithEffect: blurEffect];
[_screen addSubview: blurBackground];
blurBackground.frame = _screen.frame;
UIWindow *_window = UIApplication.sharedApplication.keyWindow;
[_window addSubview: _screen];
}
- (void)applicationDidEnterBackground:(UIApplication *)application - (void)applicationDidEnterBackground:(UIApplication *)application
{ {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
@@ -27,6 +51,17 @@
NSLog(@"In the foreground"); NSLog(@"In the foreground");
} }
- (void)applicationDidBecomeActive:(UIApplication *)application
{
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
[_screen removeFromSuperview];
}
- (void)applicationWillTerminate:(UIApplication *)application
{
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { -(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
// We will add content here soon. // We will add content here soon.
NSLog(@"In the completionHandler"); NSLog(@"In the completionHandler");
@@ -35,13 +70,12 @@
- (BOOL)application:(UIApplication *)app - (BOOL)application:(UIApplication *)app
openURL:(NSURL *)url openURL:(NSURL *)url
options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options { options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
NSLog(@"Application openURL: %@", url);
if (url.fileURL) { if (url.fileURL) {
QString filePath(url.path.UTF8String); QString filePath(url.path.UTF8String);
if (filePath.isEmpty()) return NO; if (filePath.isEmpty()) return NO;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
NSLog(@"Application openURL: %@", url);
if (filePath.contains("backup")) { if (filePath.contains("backup")) {
IosController::Instance()->importBackupFromOutside(filePath); IosController::Instance()->importBackupFromOutside(filePath);
} else { } else {
@@ -51,11 +85,16 @@
IosController::Instance()->importConfigFromOutside(QString(data)); IosController::Instance()->importConfigFromOutside(QString(data));
} }
});
return YES; return YES;
} }
return NO; return NO;
} }
void QtAppDelegateInitialize()
{
[[UIApplication sharedApplication] setDelegate: [QtAppDelegate sharedQtAppDelegate]];
NSLog(@"Created a new AppDelegate");
}
@end @end
@@ -1,87 +0,0 @@
import UIKit
public func toggleScreenshots(_ isEnabled: Bool) {
let window = UIApplication.shared.keyWindows.first!
if isEnabled {
ScreenProtection.shared.disable(for: window.rootViewController!.view)
} else {
ScreenProtection.shared.enable(for: window.rootViewController!.view)
}
}
extension UIApplication {
var keyWindows: [UIWindow] {
connectedScenes
.compactMap {
if #available(iOS 15.0, *) {
($0 as? UIWindowScene)?.keyWindow
} else {
($0 as? UIWindowScene)?.windows.first { $0.isKeyWindow }
}
}
}
}
class ScreenProtection {
public static let shared = ScreenProtection()
var pairs = [ProtectionPair]()
private var blurView: UIVisualEffectView?
private var recordingObservation: NSKeyValueObservation?
public func enable(for view: UIView) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
view.subviews.forEach {
self.pairs.append(ProtectionPair(from: $0))
}
}
}
public func disable(for view: UIView) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
self.pairs.forEach {
$0.removeProtection()
}
self.pairs.removeAll()
}
}
}
struct ProtectionPair {
let textField: UITextField
let layer: CALayer
init(from view: UIView) {
let secureTextField = UITextField()
secureTextField.backgroundColor = .clear
secureTextField.translatesAutoresizingMaskIntoConstraints = false
secureTextField.isSecureTextEntry = true
view.insertSubview(secureTextField, at: 0)
secureTextField.isUserInteractionEnabled = false
view.layer.superlayer?.addSublayer(secureTextField.layer)
secureTextField.layer.sublayers?.last?.addSublayer(view.layer)
secureTextField.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true
secureTextField.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true
secureTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
secureTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive = true
self.init(textField: secureTextField, layer: view.layer)
}
init(textField: UITextField, layer: CALayer) {
self.textField = textField
self.layer = layer
}
func removeProtection() {
textField.superview?.superview?.layer.addSublayer(layer)
textField.layer.removeFromSuperlayer()
textField.removeFromSuperview()
}
}

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