mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-08-07 08:16:38 +03:00
Compare commits
40 Commits
fix/ssh-cr
...
feature/lo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7e99d1d5d | ||
|
|
661fca64dd | ||
|
|
be4df3594b | ||
|
|
5c8c143833 | ||
|
|
24f545d001 | ||
|
|
2b0d86c626 | ||
|
|
fe773a108e | ||
|
|
a0997156f6 | ||
|
|
9a6e975622 | ||
|
|
78f09634a4 | ||
|
|
be692001b0 | ||
|
|
850b8ea03b | ||
|
|
c645d07a87 | ||
|
|
7f8786720e | ||
|
|
25c70b5bf6 | ||
|
|
7bf16f075c | ||
|
|
6518d4866e | ||
|
|
4c2010244b | ||
|
|
e946ee2430 | ||
|
|
5fab8363e7 | ||
|
|
35c2e1564b | ||
|
|
ca5bca085b | ||
|
|
33b2a3d2fc | ||
|
|
917f5858a8 | ||
|
|
e98c11079a | ||
|
|
84d908d4d8 | ||
|
|
412e69af9b | ||
|
|
4492b0af7e | ||
|
|
806b1d75af | ||
|
|
9740e7557a | ||
|
|
5ab82e2196 | ||
|
|
ae44de7101 | ||
|
|
2be6079e21 | ||
|
|
2a653f8876 | ||
|
|
41ab51a5ef | ||
|
|
300558c33c | ||
|
|
774deb87f5 | ||
|
|
bae7fbd222 | ||
|
|
97e4b95673 | ||
|
|
2ae97c5cda |
44
.github/workflows/deploy.yml
vendored
44
.github/workflows/deploy.yml
vendored
@@ -28,7 +28,7 @@ jobs:
|
||||
- 'cmake/recipes_bootstrap.cmake'
|
||||
|
||||
Bake-Prebuilts-Linux:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-latest
|
||||
needs: Detect-Changes
|
||||
if: needs.Detect-Changes.outputs.recipes_changed == 'true'
|
||||
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
# ------------------------------------------------------
|
||||
|
||||
Build-Linux-Ubuntu:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: android-runner
|
||||
|
||||
needs: Bake-Prebuilts-Linux
|
||||
if: ${{ always() }}
|
||||
@@ -88,7 +88,7 @@ jobs:
|
||||
host: 'linux'
|
||||
target: 'desktop'
|
||||
arch: 'linux_gcc_64'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools qthttpserver qtwebsockets'
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
tools: 'tools_ifw'
|
||||
@@ -201,7 +201,7 @@ jobs:
|
||||
host: 'windows'
|
||||
target: 'desktop'
|
||||
arch: 'win64_msvc2022_64'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools qthttpserver qtwebsockets'
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
tools: 'tools_ifw'
|
||||
@@ -365,7 +365,7 @@ jobs:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'mac'
|
||||
target: 'desktop'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools qtmultimedia'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools qtmultimedia qthttpserver qtwebsockets'
|
||||
arch: 'clang_64'
|
||||
dir: ${{ runner.temp }}
|
||||
set-env: 'true'
|
||||
@@ -377,7 +377,7 @@ jobs:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'mac'
|
||||
target: 'ios'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools qtmultimedia'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools qtmultimedia qthttpserver qtwebsockets'
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
set-env: 'true'
|
||||
@@ -414,10 +414,6 @@ jobs:
|
||||
matrix:
|
||||
xcode-version: [16.2, 16.4, 26.4]
|
||||
include:
|
||||
- xcode-version: 16.2
|
||||
os: macos-15
|
||||
- xcode-version: 16.4
|
||||
os: macos-15
|
||||
- xcode-version: 26.4
|
||||
os: macos-26
|
||||
|
||||
@@ -454,7 +450,7 @@ jobs:
|
||||
# ------------------------------------------------------
|
||||
|
||||
Build-MacOS:
|
||||
runs-on: macos-15
|
||||
runs-on: macos-latest
|
||||
|
||||
needs: Bake-Prebuilts-MacOS
|
||||
if: ${{ always() }}
|
||||
@@ -501,7 +497,7 @@ jobs:
|
||||
- name: 'Setup xcode'
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: '26.3.0'
|
||||
xcode-version: '16.2.0'
|
||||
|
||||
- name: 'Install Qt'
|
||||
uses: jurplel/install-qt-action@v4
|
||||
@@ -510,7 +506,7 @@ jobs:
|
||||
host: 'mac'
|
||||
target: 'desktop'
|
||||
arch: 'clang_64'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools qthttpserver qtwebsockets'
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
set-env: 'true'
|
||||
@@ -531,8 +527,6 @@ jobs:
|
||||
QT_INSTALL_DIR: ${{ runner.temp }}
|
||||
CODESIGN_SIGNATURE: ${{ secrets.MAC_SIGNER_ID }}
|
||||
CODESIGN_INSTALLER_SIGNATURE: ${{ secrets.MAC_INSTALLER_SIGNER_ID }}
|
||||
CODESIGN_KEYCHAIN: ${{ steps.setup-keychain.outputs.keychain-path }}
|
||||
CODESIGN_INSTALLER_KEYCHAIN: ${{ steps.setup-keychain.outputs.keychain-path }}
|
||||
NOTARYTOOL_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
|
||||
NOTARYTOOL_EMAIL: ${{ secrets.APPLE_DEV_EMAIL }}
|
||||
NOTARYTOOL_PASSWORD: ${{ secrets.APPLE_DEV_PASSWORD }}
|
||||
@@ -556,10 +550,6 @@ jobs:
|
||||
matrix:
|
||||
xcode-version: [16.2, 16.4, 26.4]
|
||||
include:
|
||||
- xcode-version: 16.2
|
||||
os: macos-15
|
||||
- xcode-version: 16.4
|
||||
os: macos-15
|
||||
- xcode-version: 26.4
|
||||
os: macos-26
|
||||
|
||||
@@ -596,7 +586,7 @@ jobs:
|
||||
# ------------------------------------------------------
|
||||
|
||||
Build-MacOS-NE:
|
||||
runs-on: macos-26
|
||||
runs-on: macos-latest
|
||||
|
||||
needs: Bake-Prebuilts-MacOS-NE
|
||||
if: ${{ always() }}
|
||||
@@ -620,12 +610,6 @@ jobs:
|
||||
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
|
||||
|
||||
steps:
|
||||
- name: 'Get sources'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- uses: ./.github/actions/apple-setup-provisioning-profile
|
||||
with:
|
||||
provisioning_profile_base64: ${{ secrets.MAC_APP_PROVISIONING_PROFILE }}
|
||||
@@ -663,6 +647,12 @@ jobs:
|
||||
go install golang.org/x/mobile/cmd/gomobile@latest
|
||||
gomobile init
|
||||
|
||||
- name: 'Get sources'
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: 'true'
|
||||
fetch-depth: 10
|
||||
|
||||
- name: 'Setup python'
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
@@ -749,7 +739,7 @@ jobs:
|
||||
ANDROID_PLATFORM: android-28
|
||||
NDK_VERSION: 27.0.11718014
|
||||
QT_VERSION: 6.10.3
|
||||
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
||||
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools qthttpserver qtwebsockets'
|
||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -10,9 +10,9 @@ deploy/build_64/*
|
||||
winbuild*.bat
|
||||
.cache/
|
||||
.vscode/
|
||||
.cursorignore
|
||||
.cursor/
|
||||
.venv/
|
||||
.cursor*
|
||||
|
||||
|
||||
# Qt-es
|
||||
/.qmake.cache
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -7,6 +7,9 @@
|
||||
[submodule "client/3rd/amneziawg-apple"]
|
||||
path = client/3rd/amneziawg-apple
|
||||
url = https://github.com/amnezia-vpn/amneziawg-apple
|
||||
[submodule "client/3rd/QSimpleCrypto"]
|
||||
path = client/3rd/QSimpleCrypto
|
||||
url = https://github.com/amnezia-vpn/QSimpleCrypto.git
|
||||
[submodule "client/3rd/qtgamepad"]
|
||||
path = client/3rd/qtgamepad
|
||||
url = https://github.com/amnezia-vpn/qtgamepad.git
|
||||
|
||||
@@ -4,7 +4,7 @@ set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
set(PROJECT AmneziaVPN)
|
||||
set(AMNEZIAVPN_VERSION 5.0.1.0)
|
||||
set(AMNEZIAVPN_VERSION 4.9.0.3)
|
||||
|
||||
set(QT_CREATOR_SKIP_PACKAGE_MANAGER_SETUP ON CACHE BOOL "" FORCE)
|
||||
set(CMAKE_PROJECT_TOP_LEVEL_INCLUDES
|
||||
@@ -28,7 +28,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
|
||||
set(RELEASE_DATE "${CURRENT_DATE}")
|
||||
|
||||
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||
set(APP_ANDROID_VERSION_CODE 2141)
|
||||
set(APP_ANDROID_VERSION_CODE 2126)
|
||||
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||
set(MZ_PLATFORM_NAME "linux")
|
||||
|
||||
14
README.md
14
README.md
@@ -38,14 +38,14 @@
|
||||
|
||||
## Links
|
||||
|
||||
- [https://amnezia.org](https://amnezia.org/?utm_source=github&utm_campaign=amnezia_website-read) - Project website | [Alternative link (mirror)](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-read)
|
||||
- [https://docs.amnezia.org](https://docs.amnezia.org/?utm_source=github&utm_campaign=amnezia_website-read) - Documentation | [Alternative link (mirror)](https://storage.googleapis.com/amnezia/docs?utm_source=github&utm_campaign=amnezia_website-read)
|
||||
- [https://amnezia.org](https://amnezia.org) - Project website | [Alternative link (mirror)](https://storage.googleapis.com/kldscp/amnezia.org)
|
||||
- [https://docs.amnezia.org](https://docs.amnezia.org) - Documentation
|
||||
- [https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit
|
||||
- [https://telegram.me/amnezia_vpn_en](https://telegram.me/amnezia_vpn_en) - Telegram support channel (English)
|
||||
- [https://telegram.me/amnezia_vpn_ir](https://telegram.me/amnezia_vpn_ir) - Telegram support channel (Farsi)
|
||||
- [https://telegram.me/amnezia_vpn_mm](https://telegram.me/amnezia_vpn_mm) - Telegram support channel (Myanmar)
|
||||
- [https://telegram.me/amnezia_vpn](https://telegram.me/amnezia_vpn) - Telegram support channel (Russian)
|
||||
- [Get Premium for 6 or 12 months](https://storage.googleapis.com/amnezia/pay?utm_source=github&utm_campaign=ampay-read)
|
||||
- [https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Telegram support channel (English)
|
||||
- [https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Telegram support channel (Farsi)
|
||||
- [https://t.me/amnezia_vpn_mm](https://t.me/amnezia_vpn_mm) - Telegram support channel (Myanmar)
|
||||
- [https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - Telegram support channel (Russian)
|
||||
- [https://vpnpay.io/en/amnezia-premium/](https://vpnpay.io/en/amnezia-premium/) - Amnezia Premium
|
||||
|
||||
## Tech
|
||||
|
||||
|
||||
16
README_RU.md
16
README_RU.md
@@ -28,21 +28,21 @@
|
||||
|
||||
- Простой в использовании — введите IP-адрес, SSH-логин и пароль, и Amnezia автоматически установит VPN-контейнеры Docker на ваш сервер и подключится к VPN.
|
||||
- Классические VPN-протоколы: OpenVPN, WireGuard и IKEv2.
|
||||
- Протоколы с маскировкой трафика (обфускацией): OpenVPN с плагином [Cloak](https://github.com/cbeuw/Cloak), Shadowsocks (OpenVPN over Shadowsocks), [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/) и XRay.
|
||||
- Протоколы с маскировкой трафика (обфускацией): OpenVPN с плагином [Cloak](https://github.com/cbeuw/Cloak), Shadowsocks (OpenVPN over Shadowsocks), [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/) and XRay.
|
||||
- Поддержка Split Tunneling — добавляйте любые сайты или приложения в список, чтобы включить VPN только для них.
|
||||
- Поддерживает платформы: Windows, macOS, Linux, Android, iOS.
|
||||
- Поддержка конфигурации протокола AmneziaWG на [бета-прошивке Keenetic](https://docs.keenetic.com/ua/air/kn-1611/en/6319-latest-development-release.html#UUID-186c4108-5afd-c10b-f38a-cdff6c17fab3_section-idm33192196168192-improved).
|
||||
|
||||
## Ссылки
|
||||
|
||||
- [https://amnezia.org](https://amnezia.org/?utm_source=github&utm_campaign=amnezia_website-read) - Веб-сайт проекта | [Альтернативная ссылка (зеркало)](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-read)
|
||||
- [https://docs.amnezia.org](https://docs.amnezia.org/?utm_source=github&utm_campaign=amnezia_website-read) - Документация | [Альтернативная ссылка (зеркало)](https://storage.googleapis.com/amnezia/docs?utm_source=github&utm_campaign=amnezia_website-read)
|
||||
- [https://amnezia.org](https://amnezia.org) - Веб-сайт проекта | [Альтернативная ссылка (зеркало)](https://storage.googleapis.com/kldscp/amnezia.org)
|
||||
- [https://docs.amnezia.org](https://docs.amnezia.org) - Документация
|
||||
- [https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit
|
||||
- [https://telegram.me/amnezia_vpn_en](https://telegram.me/amnezia_vpn_en) - Канал поддержки в Telegram (Английский)
|
||||
- [https://telegram.me/amnezia_vpn_ir](https://telegram.me/amnezia_vpn_ir) - Канал поддержки в Telegram (Фарси)
|
||||
- [https://telegram.me/amnezia_vpn_mm](https://telegram.me/amnezia_vpn_mm) - Канал поддержки в Telegram (Мьянма)
|
||||
- [https://telegram.me/amnezia_vpn](https://telegram.me/amnezia_vpn) - Канал поддержки в Telegram (Русский)
|
||||
- [Оформите Premium на 6 или 12 месяцев](https://storage.googleapis.com/amnezia/pay?utm_source=github&utm_campaign=ampay-read)
|
||||
- [https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Канал поддержки в Telegram (Английский)
|
||||
- [https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Канал поддержки в Telegram (Фарси)
|
||||
- [https://t.me/amnezia_vpn_mm](https://t.me/amnezia_vpn_mm) - Канал поддержки в Telegram (Мьянма)
|
||||
- [https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - Канал поддержки в Telegram (Русский)
|
||||
- [https://vpnpay.io/en/amnezia-premium/](https://vpnpay.io/en/amnezia-premium/) - Amnezia Premium | [Зеркало](https://storage.googleapis.com/kldscp/vpnpay.io/ru/amnezia-premium\)
|
||||
|
||||
## Технологии
|
||||
|
||||
|
||||
1
client/3rd/QSimpleCrypto
vendored
Submodule
1
client/3rd/QSimpleCrypto
vendored
Submodule
Submodule client/3rd/QSimpleCrypto added at c99b33f0e0
2
client/3rd/amneziawg-apple
vendored
2
client/3rd/amneziawg-apple
vendored
Submodule client/3rd/amneziawg-apple updated: 4bafa5958a...cf63135331
@@ -14,6 +14,10 @@ set(PACKAGES
|
||||
Core5Compat Concurrent LinguistTools
|
||||
)
|
||||
|
||||
if(NOT ANDROID AND NOT IOS)
|
||||
list(APPEND PACKAGES HttpServer)
|
||||
endif()
|
||||
|
||||
execute_process(
|
||||
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}"
|
||||
COMMAND git rev-parse --short HEAD
|
||||
@@ -39,7 +43,6 @@ if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
endif()
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS ${PACKAGES})
|
||||
find_package(OpenSSL REQUIRED)
|
||||
|
||||
set(LIBS ${LIBS}
|
||||
Qt6::Core Qt6::Gui
|
||||
@@ -48,6 +51,10 @@ set(LIBS ${LIBS}
|
||||
Qt6::Core5Compat Qt6::Concurrent
|
||||
)
|
||||
|
||||
if(NOT ANDROID AND NOT IOS)
|
||||
list(APPEND LIBS Qt6::HttpServer)
|
||||
endif()
|
||||
|
||||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
set(LIBS ${LIBS} Qt6::Widgets)
|
||||
endif()
|
||||
@@ -104,7 +111,6 @@ include(${CMAKE_CURRENT_LIST_DIR}/cmake/sources.cmake)
|
||||
include_directories(
|
||||
${CMAKE_CURRENT_LIST_DIR}/../ipc
|
||||
${CMAKE_CURRENT_LIST_DIR}/../common/logger
|
||||
${CMAKE_CURRENT_LIST_DIR}/../common/crypto
|
||||
${CMAKE_CURRENT_LIST_DIR}
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
@@ -175,7 +181,6 @@ endif()
|
||||
if(LINUX AND NOT ANDROID)
|
||||
set(LIBS ${LIBS} -static-libstdc++ -static-libgcc -ldl)
|
||||
link_directories(${CMAKE_CURRENT_LIST_DIR}/platforms/linux)
|
||||
set(LIBS ${LIBS} -Wl,--no-as-needed $<TARGET_LINKER_FILE:Qt6::RemoteObjects> -Wl,--as-needed)
|
||||
endif()
|
||||
|
||||
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
|
||||
@@ -233,7 +238,7 @@ install(RUNTIME_DEPENDENCY_SET client_deps
|
||||
[[hvsifiletrust\.dll]]
|
||||
[[libc\.so\..*]] [[libgcc_s\.so\..*]] [[libm\.so\..*]] [[libstdc\+\+\.so\..*]]
|
||||
[[.*\.framework]]
|
||||
[[^(lib)?[Qq]t.*]]
|
||||
[[^[Qq]t.*]]
|
||||
POST_EXCLUDE_REGEXES
|
||||
[[^.*[\\/]system32[\\/].*\.dll$]]
|
||||
[[^/lib.*]]
|
||||
@@ -260,10 +265,6 @@ install(SCRIPT ${QT_DEPLOY_SCRIPT}
|
||||
|
||||
if (APPLE AND NOT IOS AND NOT MACOS_NE)
|
||||
list(APPEND OVPN_SCRIPTS "${CMAKE_SOURCE_DIR}/deploy/data/macos/update-resolv-conf.sh")
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO"
|
||||
XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO"
|
||||
)
|
||||
endif()
|
||||
if (LINUX AND NOT ANDROID)
|
||||
list(APPEND OVPN_SCRIPTS "${CMAKE_SOURCE_DIR}/deploy/data/linux/update-resolv-conf.sh")
|
||||
|
||||
@@ -22,13 +22,9 @@
|
||||
#include "logger.h"
|
||||
#include "ui/controllers/qml/pageController.h"
|
||||
#include "ui/models/installedAppsModel.h"
|
||||
#include "ui/utils/mtProxyPublicHostInput.h"
|
||||
#include "version.h"
|
||||
|
||||
#include "platforms/ios/QRCodeReaderBase.h"
|
||||
#ifdef Q_OS_IOS
|
||||
#include "platforms/ios/ioscontextmenu.h"
|
||||
#endif
|
||||
|
||||
|
||||
bool AmneziaApplication::m_forceQuit = false;
|
||||
@@ -142,19 +138,12 @@ void AmneziaApplication::init()
|
||||
m_engine->rootContext()->setContextProperty("IsMacOsNeBuild", false);
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
m_engine->rootContext()->setContextProperty("IosContextMenu", new IosContextMenu(this));
|
||||
#endif
|
||||
|
||||
m_vpnConnection.reset(new VpnConnection(nullptr, nullptr));
|
||||
m_vpnConnection->moveToThread(&m_vpnConnectionThread);
|
||||
m_vpnConnectionThread.start();
|
||||
|
||||
m_coreController.reset(new CoreController(m_vpnConnection, m_settings, m_engine));
|
||||
|
||||
m_marketplaceUpdateController.reset(new MarketplaceUpdateController());
|
||||
m_marketplaceUpdateController->start();
|
||||
|
||||
m_engine->addImportPath("qrc:/ui/qml/Modules/");
|
||||
|
||||
if (m_parser.isSet(m_optImport)) {
|
||||
@@ -232,9 +221,6 @@ void AmneziaApplication::registerTypes()
|
||||
|
||||
qmlRegisterType<InstalledAppsModel>("InstalledAppsModel", 1, 0, "InstalledAppsModel");
|
||||
|
||||
qmlRegisterType<PublicHostInputValidator>("MtProxyConfig", 1, 0, "PublicHostInputValidator");
|
||||
qmlRegisterType<PublicHostInputValidator>("TelemtConfig", 1, 0, "PublicHostInputValidator");
|
||||
|
||||
amnezia::declareQmlProtocolEnum();
|
||||
Vpn::declareQmlVpnConnectionStateEnum();
|
||||
PageLoader::declareQmlPageEnum();
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
|
||||
#include "core/controllers/coreController.h"
|
||||
#include "secureQSettings.h"
|
||||
#include "ui/controllers/marketplaceUpdateController.h"
|
||||
#include "vpnConnection.h"
|
||||
#include "ui/models/containerProps.h"
|
||||
#include "ui/models/protocolProps.h"
|
||||
@@ -57,7 +56,6 @@ private:
|
||||
SecureQSettings* m_settings;
|
||||
|
||||
QScopedPointer<CoreController> m_coreController;
|
||||
QScopedPointer<MarketplaceUpdateController> m_marketplaceUpdateController;
|
||||
|
||||
QSharedPointer<ContainerProps> m_containerProps;
|
||||
QSharedPointer<ProtocolProps> m_protocolProps;
|
||||
|
||||
@@ -3,13 +3,6 @@ package org.amnezia.vpn
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.AlertDialog
|
||||
import android.app.Dialog
|
||||
import android.graphics.Typeface
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.view.Gravity
|
||||
import android.widget.Button
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import android.app.NotificationManager
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.BroadcastReceiver
|
||||
@@ -49,7 +42,6 @@ import androidx.core.view.OnApplyWindowInsetsListener
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import kotlin.LazyThreadSafetyMode.NONE
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
@@ -107,8 +99,6 @@ class AmneziaActivity : QtActivity() {
|
||||
private var pendingOpenFileUri: String? = null
|
||||
private var openFileDeliveryScheduled = false
|
||||
|
||||
private var updateCoverDialog: Dialog? = null
|
||||
|
||||
private val vpnServiceEventHandler: Handler by lazy(NONE) {
|
||||
object : Handler(Looper.getMainLooper()) {
|
||||
override fun handleMessage(msg: Message) {
|
||||
@@ -496,116 +486,6 @@ class AmneziaActivity : QtActivity() {
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
fun showUpdateCover() {
|
||||
runOnUiThread {
|
||||
if (isFinishing || isDestroyed || updateCoverDialog != null) return@runOnUiThread
|
||||
val dialog = Dialog(this, android.R.style.Theme_Black_NoTitleBar_Fullscreen)
|
||||
dialog.setCancelable(false)
|
||||
val root = LinearLayout(this).apply {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
gravity = Gravity.CENTER
|
||||
setBackgroundColor(0xFF0E0E11.toInt())
|
||||
}
|
||||
dialog.setContentView(root)
|
||||
dialog.show()
|
||||
updateCoverDialog = dialog
|
||||
}
|
||||
}
|
||||
|
||||
fun hideUpdateCover() {
|
||||
runOnUiThread {
|
||||
updateCoverDialog?.dismiss()
|
||||
updateCoverDialog = null
|
||||
}
|
||||
}
|
||||
|
||||
fun showUpdatePrompt(title: String, message: String, updateTitle: String, skipTitle: String, storeUrl: String) {
|
||||
runOnUiThread {
|
||||
if (isFinishing || isDestroyed) return@runOnUiThread
|
||||
|
||||
val dialog = updateCoverDialog ?: Dialog(this, android.R.style.Theme_Black_NoTitleBar_Fullscreen).also {
|
||||
it.setCancelable(false)
|
||||
it.show()
|
||||
updateCoverDialog = it
|
||||
}
|
||||
|
||||
val density = resources.displayMetrics.density
|
||||
fun dp(value: Int) = (value * density).toInt()
|
||||
|
||||
val root = LinearLayout(this).apply {
|
||||
orientation = LinearLayout.VERTICAL
|
||||
gravity = Gravity.CENTER
|
||||
setBackgroundColor(0xFF0E0E11.toInt())
|
||||
setPadding(dp(32), dp(32), dp(32), dp(32))
|
||||
}
|
||||
|
||||
val titleView = TextView(this).apply {
|
||||
text = title
|
||||
textSize = 22f
|
||||
setTextColor(0xFFFFFFFF.toInt())
|
||||
gravity = Gravity.CENTER
|
||||
typeface = Typeface.create(typeface, Typeface.BOLD)
|
||||
}
|
||||
|
||||
val messageView = TextView(this).apply {
|
||||
text = message
|
||||
textSize = 16f
|
||||
setTextColor(0xFFC7C8CB.toInt())
|
||||
gravity = Gravity.CENTER
|
||||
setPadding(0, dp(16), 0, dp(28))
|
||||
}
|
||||
|
||||
val updateButton = Button(this).apply {
|
||||
text = updateTitle
|
||||
isAllCaps = false
|
||||
textSize = 17f
|
||||
setTextColor(0xFF0E0E11.toInt())
|
||||
stateListAnimator = null
|
||||
background = GradientDrawable().apply {
|
||||
cornerRadius = dp(12).toFloat()
|
||||
setColor(0xFFFBB26A.toInt())
|
||||
}
|
||||
setOnClickListener {
|
||||
try {
|
||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(storeUrl)))
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Log.w(TAG, "open store failed: ${e.message}")
|
||||
}
|
||||
hideUpdateCover()
|
||||
}
|
||||
}
|
||||
|
||||
val skipButton = Button(this).apply {
|
||||
text = skipTitle
|
||||
isAllCaps = false
|
||||
textSize = 17f
|
||||
setTextColor(0xFFD7D8DB.toInt())
|
||||
stateListAnimator = null
|
||||
background = GradientDrawable().apply {
|
||||
cornerRadius = dp(12).toFloat()
|
||||
setColor(0x00000000)
|
||||
setStroke(dp(1), 0xFF2C2D30.toInt())
|
||||
}
|
||||
setOnClickListener { hideUpdateCover() }
|
||||
}
|
||||
|
||||
val updateParams = LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, dp(52)
|
||||
).apply { topMargin = dp(8) }
|
||||
|
||||
val skipParams = LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT, dp(52)
|
||||
).apply { topMargin = dp(12) }
|
||||
|
||||
root.addView(titleView)
|
||||
root.addView(messageView)
|
||||
root.addView(updateButton, updateParams)
|
||||
root.addView(skipButton, skipParams)
|
||||
|
||||
dialog.setContentView(root)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
Log.d(TAG, "Process activity result, code: ${actionCodeToString(requestCode)}, " +
|
||||
"resultCode: $resultCode, data: $data")
|
||||
@@ -883,13 +763,7 @@ class AmneziaActivity : QtActivity() {
|
||||
fun openFile(filter: String?) {
|
||||
Log.v(TAG, "Open file with filter: $filter")
|
||||
mainScope.launch {
|
||||
val systemPickerPackage = listOf("com.google.android.documentsui", "com.android.documentsui")
|
||||
.firstOrNull { pkg ->
|
||||
try { packageManager.getPackageInfo(pkg, 0); true }
|
||||
catch (_: PackageManager.NameNotFoundException) { false }
|
||||
}
|
||||
|
||||
val intent = if (!isOnTv() && systemPickerPackage != null) {
|
||||
val intent = if (!isOnTv()) {
|
||||
val mimeTypes = if (!filter.isNullOrEmpty()) {
|
||||
val extensionRegex = "\\*\\.([a-z0-9]+)".toRegex(IGNORE_CASE)
|
||||
val mime = MimeTypeMap.getSingleton()
|
||||
@@ -915,7 +789,6 @@ class AmneziaActivity : QtActivity() {
|
||||
else -> type = "*/*"
|
||||
}
|
||||
}
|
||||
`package` = systemPickerPackage
|
||||
}
|
||||
} else {
|
||||
Intent(this@AmneziaActivity, TvFilePicker::class.java)
|
||||
@@ -927,11 +800,8 @@ class AmneziaActivity : QtActivity() {
|
||||
if (isOnTv() && it?.hasExtra("activityNotFound") == true) {
|
||||
showNoFileBrowserAlertDialog()
|
||||
}
|
||||
val uri = it?.data?.let { u ->
|
||||
if (u.scheme == "content") {
|
||||
try { grantUriPermission(packageName, u, Intent.FLAG_GRANT_READ_URI_PERMISSION) } catch (_: Exception) {}
|
||||
}
|
||||
u
|
||||
val uri = it?.data?.apply {
|
||||
grantUriPermission(packageName, this, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
}?.toString() ?: ""
|
||||
Log.v(TAG, "Open file: $uri")
|
||||
if (uri.isNotEmpty()) {
|
||||
@@ -971,12 +841,7 @@ class AmneziaActivity : QtActivity() {
|
||||
Log.v(TAG, "Get fd for $fileName")
|
||||
return blockingCall(Dispatchers.IO) {
|
||||
try {
|
||||
val uri = Uri.parse(fileName)
|
||||
pfd = if (uri.scheme == "file") {
|
||||
ParcelFileDescriptor.open(File(uri.path!!), ParcelFileDescriptor.MODE_READ_ONLY)
|
||||
} else {
|
||||
contentResolver.openFileDescriptor(uri, "r")
|
||||
}
|
||||
pfd = contentResolver.openFileDescriptor(Uri.parse(fileName), "r")
|
||||
pfd?.fd ?: -1
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to get fd: $e")
|
||||
@@ -1196,10 +1061,12 @@ class AmneziaActivity : QtActivity() {
|
||||
@Suppress("unused")
|
||||
fun sendTouch(x: Float, y: Float) {
|
||||
Log.v(TAG, "Send touch: $x, $y")
|
||||
findQtWindow(window.decorView)?.let {
|
||||
Log.v(TAG, "Send touch to $it")
|
||||
it.dispatchTouchEvent(createEvent(x, y, SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN))
|
||||
it.dispatchTouchEvent(createEvent(x, y, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP))
|
||||
blockingCall {
|
||||
findQtWindow(window.decorView)?.let {
|
||||
Log.v(TAG, "Send touch to $it")
|
||||
it.dispatchTouchEvent(createEvent(x, y, SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN))
|
||||
it.dispatchTouchEvent(createEvent(x, y, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,36 +1,30 @@
|
||||
package org.amnezia.vpn
|
||||
|
||||
import android.Manifest
|
||||
import android.app.AlertDialog
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import org.amnezia.vpn.util.Log
|
||||
import java.io.File
|
||||
|
||||
private const val TAG = "TvFilePicker"
|
||||
private const val READ_STORAGE_REQUEST_CODE = 1001
|
||||
|
||||
class TvFilePicker : ComponentActivity() {
|
||||
|
||||
// SAF launcher for Android 10+ where File API is blocked by scoped storage
|
||||
private val safLauncher = registerForActivityResult(object : ActivityResultContracts.OpenDocument() {
|
||||
private val fileChooseResultLauncher = registerForActivityResult(object : ActivityResultContracts.OpenDocument() {
|
||||
override fun createIntent(context: Context, input: Array<String>): Intent {
|
||||
val intent = super.createIntent(context, input)
|
||||
val activities = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
|
||||
val activitiesToResolveIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
context.packageManager.queryIntentActivities(intent, PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong()))
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
context.packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
|
||||
}
|
||||
if (activities.all {
|
||||
if (activitiesToResolveIntent.all {
|
||||
val name = it.activityInfo.packageName
|
||||
name.startsWith("com.google.android.tv.frameworkpackagestubs") || name.startsWith("com.android.tv.frameworkpackagestubs")
|
||||
}) {
|
||||
@@ -38,140 +32,38 @@ class TvFilePicker : ComponentActivity() {
|
||||
}
|
||||
return intent
|
||||
}
|
||||
}) { uri ->
|
||||
}) {
|
||||
setResult(RESULT_OK, Intent().apply {
|
||||
data = uri
|
||||
data = it
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
})
|
||||
finish()
|
||||
}
|
||||
|
||||
private val directoryStack = ArrayDeque<File>()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
Log.v(TAG, "onCreate")
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
launchSaf()
|
||||
} else {
|
||||
checkPermissionAndBrowse()
|
||||
}
|
||||
getFile()
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated in Java")
|
||||
override fun onBackPressed() {
|
||||
navigateBack()
|
||||
override fun onNewIntent(intent: Intent) {
|
||||
super.onNewIntent(intent)
|
||||
Log.v(TAG, "onNewIntent")
|
||||
getFile()
|
||||
}
|
||||
|
||||
private fun launchSaf() {
|
||||
private fun getFile() {
|
||||
try {
|
||||
safLauncher.launch(arrayOf("*/*"))
|
||||
Log.v(TAG, "getFile")
|
||||
fileChooseResultLauncher.launch(arrayOf("*/*"))
|
||||
} catch (_: ActivityNotFoundException) {
|
||||
Log.w(TAG, "No SAF activity found")
|
||||
Log.w(TAG, "Activity not found")
|
||||
setResult(RESULT_CANCELED, Intent().apply { putExtra("activityNotFound", true) })
|
||||
finish()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "SAF launch failed: $e")
|
||||
Log.e(TAG, "Failed to get file: $e")
|
||||
setResult(RESULT_CANCELED)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkPermissionAndBrowse() {
|
||||
if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
||||
requestPermissions(arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), READ_STORAGE_REQUEST_CODE)
|
||||
} else {
|
||||
showRootDirectory()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
if (requestCode == READ_STORAGE_REQUEST_CODE &&
|
||||
grantResults.firstOrNull() == PackageManager.PERMISSION_GRANTED) {
|
||||
showRootDirectory()
|
||||
} else {
|
||||
setResult(RESULT_CANCELED)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showRootDirectory() {
|
||||
@Suppress("DEPRECATION")
|
||||
val primaryExternal = Environment.getExternalStorageDirectory()
|
||||
val storageDir = File("/storage")
|
||||
// Pre-seed stack with /storage so Back from primary storage goes there (USB drives etc.)
|
||||
if (storageDir.exists() && storageDir.canonicalPath != primaryExternal.canonicalPath) {
|
||||
directoryStack.addLast(storageDir)
|
||||
}
|
||||
showDirectory(primaryExternal)
|
||||
}
|
||||
|
||||
private fun navigateBack() {
|
||||
if (directoryStack.size > 1) {
|
||||
directoryStack.removeLast()
|
||||
val parent = directoryStack.removeLast()
|
||||
showDirectory(parent)
|
||||
} else {
|
||||
setResult(RESULT_CANCELED)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showDirectory(dir: File) {
|
||||
directoryStack.addLast(dir)
|
||||
Log.v(TAG, "Showing directory: ${dir.absolutePath}")
|
||||
|
||||
val entries = try {
|
||||
dir.listFiles()
|
||||
?.sortedWith(compareBy({ !it.isDirectory }, { it.name.lowercase() }))
|
||||
?: emptyList()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to list directory: $e")
|
||||
emptyList()
|
||||
}
|
||||
|
||||
val names = entries.map { if (it.isDirectory) "[${it.name}]" else it.name }.toTypedArray()
|
||||
|
||||
val builder = AlertDialog.Builder(this)
|
||||
.setTitle(dir.absolutePath)
|
||||
|
||||
if (entries.isEmpty()) {
|
||||
builder.setMessage("No files available")
|
||||
} else {
|
||||
builder.setItems(names) { dialog, which ->
|
||||
dialog.dismiss()
|
||||
val selected = entries[which]
|
||||
if (selected.isDirectory) {
|
||||
showDirectory(selected)
|
||||
} else {
|
||||
Log.v(TAG, "Selected file: ${selected.absolutePath}")
|
||||
setResult(RESULT_OK, Intent().apply {
|
||||
data = Uri.fromFile(selected)
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
})
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (directoryStack.size > 1) {
|
||||
builder.setNegativeButton("↑ Back") { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
navigateBack()
|
||||
}
|
||||
} else {
|
||||
builder.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||
setResult(RESULT_CANCELED)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
builder.setOnCancelListener {
|
||||
setResult(RESULT_CANCELED)
|
||||
finish()
|
||||
}
|
||||
|
||||
builder.show()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ open class Wireguard : Protocol() {
|
||||
configExtensionParameters(configData)
|
||||
}
|
||||
|
||||
configData.optStringOrNull("persistent_keep_alive")?.let { setPersistentKeepalive(it) }
|
||||
configData.optStringOrNull("persistent_keep_alive")?.let { setPersistentKeepalive(it.toInt()) }
|
||||
configData.getString("client_priv_key").let { setPrivateKeyHex(it.base64ToHex()) }
|
||||
configData.getString("server_pub_key").let { setPublicKeyHex(it.base64ToHex()) }
|
||||
configData.optStringOrNull("psk_key")?.let { setPreSharedKeyHex(it.base64ToHex()) }
|
||||
@@ -134,20 +134,6 @@ open class Wireguard : Protocol() {
|
||||
configData.optStringOrNull("I3")?.let { setI3(it) }
|
||||
configData.optStringOrNull("I4")?.let { setI4(it) }
|
||||
configData.optStringOrNull("I5")?.let { setI5(it) }
|
||||
configData.optStringOrNull("HeaderProtectionKey")?.trim()?.takeIf { it.isNotEmpty() }
|
||||
?.let { setHeaderProtectionKey(it.base64ToHex()) }
|
||||
configData.optStringOrNull("ContentPaddingAddition")?.trim()?.takeIf { it.isNotEmpty() }
|
||||
?.let { setContentPaddingAddition(it) }
|
||||
configData.optStringOrNull("RekeyAfterTime")?.trim()?.takeIf { it.isNotEmpty() }
|
||||
?.let { setRekeyAfterTime(it) }
|
||||
configData.optStringOrNull("RekeyTimeout")?.trim()?.takeIf { it.isNotEmpty() }
|
||||
?.let { setRekeyTimeout(it) }
|
||||
configData.optStringOrNull("RejectAfterTime")?.trim()?.takeIf { it.isNotEmpty() }
|
||||
?.let { setRejectAfterTime(it) }
|
||||
configData.optStringOrNull("KeepaliveTimeout")?.trim()?.takeIf { it.isNotEmpty() }
|
||||
?.let { setKeepaliveTimeout(it) }
|
||||
configData.optStringOrNull("MaxHandshakeAttempts")?.trim()?.takeIf { it.isNotEmpty() }
|
||||
?.let { setMaxHandshakeAttempts(it) }
|
||||
}
|
||||
|
||||
private fun start(
|
||||
|
||||
@@ -10,7 +10,7 @@ private const val WIREGUARD_DEFAULT_MTU = 1280
|
||||
open class WireguardConfig protected constructor(
|
||||
protocolConfigBuilder: ProtocolConfig.Builder,
|
||||
val endpoint: InetEndpoint,
|
||||
val persistentKeepalive: String?,
|
||||
val persistentKeepalive: Int,
|
||||
val publicKeyHex: String,
|
||||
val preSharedKeyHex: String?,
|
||||
val privateKeyHex: String,
|
||||
@@ -31,13 +31,6 @@ open class WireguardConfig protected constructor(
|
||||
var i3: String?,
|
||||
var i4: String?,
|
||||
var i5: String?,
|
||||
val headerProtectionKeyHex: String?,
|
||||
val contentPaddingAddition: String?,
|
||||
val rekeyAfterTime: String?,
|
||||
val rekeyTimeout: String?,
|
||||
val rejectAfterTime: String?,
|
||||
val keepaliveTimeout: String?,
|
||||
val maxHandshakeAttempts: String?,
|
||||
) : ProtocolConfig(protocolConfigBuilder) {
|
||||
|
||||
protected constructor(builder: Builder) : this(
|
||||
@@ -64,13 +57,6 @@ open class WireguardConfig protected constructor(
|
||||
builder.i3,
|
||||
builder.i4,
|
||||
builder.i5,
|
||||
builder.headerProtectionKeyHex,
|
||||
builder.contentPaddingAddition,
|
||||
builder.rekeyAfterTime,
|
||||
builder.rekeyTimeout,
|
||||
builder.rejectAfterTime,
|
||||
builder.keepaliveTimeout,
|
||||
builder.maxHandshakeAttempts,
|
||||
)
|
||||
|
||||
fun toWgUserspaceString(): String = with(StringBuilder()) {
|
||||
@@ -101,13 +87,6 @@ open class WireguardConfig protected constructor(
|
||||
i4?.let { appendLine("i4=$it") }
|
||||
i5?.let { appendLine("i5=$it") }
|
||||
}
|
||||
headerProtectionKeyHex?.takeIf { it.isNotEmpty() }?.let { appendLine("header_protection_key=$it") }
|
||||
contentPaddingAddition?.takeIf { it.isNotEmpty() }?.let { appendLine("content_padding_addition=$it") }
|
||||
rekeyAfterTime?.takeIf { it.isNotEmpty() }?.let { appendLine("rekey_after_time=$it") }
|
||||
rekeyTimeout?.takeIf { it.isNotEmpty() }?.let { appendLine("rekey_timeout=$it") }
|
||||
rejectAfterTime?.takeIf { it.isNotEmpty() }?.let { appendLine("reject_after_time=$it") }
|
||||
keepaliveTimeout?.takeIf { it.isNotEmpty() }?.let { appendLine("keepalive_timeout=$it") }
|
||||
maxHandshakeAttempts?.takeIf { it.isNotEmpty() }?.let { appendLine("max_handshake_attempts=$it") }
|
||||
}
|
||||
|
||||
private fun validateProtocolExtensionParameters() {
|
||||
@@ -128,7 +107,7 @@ open class WireguardConfig protected constructor(
|
||||
appendLine("allowed_ip=${route.inetNetwork}")
|
||||
}
|
||||
appendLine("endpoint=$endpoint")
|
||||
if (!persistentKeepalive.isNullOrEmpty() && persistentKeepalive != "0")
|
||||
if (persistentKeepalive != 0)
|
||||
appendLine("persistent_keepalive_interval=$persistentKeepalive")
|
||||
if (preSharedKeyHex != null)
|
||||
appendLine("preshared_key=$preSharedKeyHex")
|
||||
@@ -138,7 +117,7 @@ open class WireguardConfig protected constructor(
|
||||
internal lateinit var endpoint: InetEndpoint
|
||||
private set
|
||||
|
||||
internal var persistentKeepalive: String? = null
|
||||
internal var persistentKeepalive: Int = 0
|
||||
private set
|
||||
|
||||
internal lateinit var publicKeyHex: String
|
||||
@@ -170,17 +149,10 @@ open class WireguardConfig protected constructor(
|
||||
internal var i3: String? = null
|
||||
internal var i4: String? = null
|
||||
internal var i5: String? = null
|
||||
internal var headerProtectionKeyHex: String? = null
|
||||
internal var contentPaddingAddition: String? = null
|
||||
internal var rekeyAfterTime: String? = null
|
||||
internal var rekeyTimeout: String? = null
|
||||
internal var rejectAfterTime: String? = null
|
||||
internal var keepaliveTimeout: String? = null
|
||||
internal var maxHandshakeAttempts: String? = null
|
||||
|
||||
fun setEndpoint(endpoint: InetEndpoint) = apply { this.endpoint = endpoint }
|
||||
|
||||
fun setPersistentKeepalive(persistentKeepalive: String) = apply { this.persistentKeepalive = persistentKeepalive }
|
||||
fun setPersistentKeepalive(persistentKeepalive: Int) = apply { this.persistentKeepalive = persistentKeepalive }
|
||||
|
||||
fun setPublicKeyHex(publicKeyHex: String) = apply { this.publicKeyHex = publicKeyHex }
|
||||
|
||||
@@ -206,13 +178,6 @@ open class WireguardConfig protected constructor(
|
||||
fun setI3(i3: String) = apply { this.i3 = i3 }
|
||||
fun setI4(i4: String) = apply { this.i4 = i4 }
|
||||
fun setI5(i5: String) = apply { this.i5 = i5 }
|
||||
fun setHeaderProtectionKey(headerProtectionKeyHex: String) = apply { this.headerProtectionKeyHex = headerProtectionKeyHex }
|
||||
fun setContentPaddingAddition(contentPaddingAddition: String) = apply { this.contentPaddingAddition = contentPaddingAddition }
|
||||
fun setRekeyAfterTime(rekeyAfterTime: String) = apply { this.rekeyAfterTime = rekeyAfterTime }
|
||||
fun setRekeyTimeout(rekeyTimeout: String) = apply { this.rekeyTimeout = rekeyTimeout }
|
||||
fun setRejectAfterTime(rejectAfterTime: String) = apply { this.rejectAfterTime = rejectAfterTime }
|
||||
fun setKeepaliveTimeout(keepaliveTimeout: String) = apply { this.keepaliveTimeout = keepaliveTimeout }
|
||||
fun setMaxHandshakeAttempts(maxHandshakeAttempts: String) = apply { this.maxHandshakeAttempts = maxHandshakeAttempts }
|
||||
|
||||
override fun build(): WireguardConfig = configBuild().run { WireguardConfig(this@Builder) }
|
||||
}
|
||||
|
||||
@@ -2,8 +2,9 @@ set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/..)
|
||||
|
||||
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/Modules;${CMAKE_MODULE_PATH}")
|
||||
|
||||
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/SortFilterProxyModel ${CMAKE_BINARY_DIR}/3rd/SortFilterProxyModel)
|
||||
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/SortFilterProxyModel)
|
||||
set(LIBS ${LIBS} SortFilterProxyModel)
|
||||
include(${CLIENT_ROOT_DIR}/cmake/QSimpleCrypto.cmake)
|
||||
|
||||
include(${CLIENT_ROOT_DIR}/3rd/qrcodegen/qrcodegen.cmake)
|
||||
|
||||
@@ -11,24 +12,20 @@ add_compile_definitions(_WINSOCKAPI_)
|
||||
|
||||
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
|
||||
set(BUILD_WITH_QT6 ON)
|
||||
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtkeychain ${CMAKE_BINARY_DIR}/3rd/qtkeychain EXCLUDE_FROM_ALL)
|
||||
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtkeychain EXCLUDE_FROM_ALL)
|
||||
|
||||
if(ANDROID)
|
||||
# Use qtgamepad from amnezia-vpn/qtgamepad repository
|
||||
# Only if Qt6CorePrivate is available (required by qtgamepad)
|
||||
find_package(Qt6CorePrivate CONFIG QUIET)
|
||||
if(Qt6CorePrivate_FOUND)
|
||||
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtgamepad ${CMAKE_BINARY_DIR}/3rd/qtgamepad)
|
||||
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtgamepad)
|
||||
# Link both the C++ module and QML plugin
|
||||
if(TARGET ${PROJECT})
|
||||
if(TARGET GamepadLegacy)
|
||||
target_link_libraries(${PROJECT} PRIVATE GamepadLegacy)
|
||||
else()
|
||||
list(APPEND LIBS GamepadLegacy)
|
||||
endif()
|
||||
if(TARGET ${PROJECT})
|
||||
if(TARGET GamepadLegacyQuickPrivate)
|
||||
target_link_libraries(${PROJECT} PRIVATE GamepadLegacyQuickPrivate)
|
||||
else()
|
||||
list(APPEND LIBS GamepadLegacyQuickPrivate)
|
||||
endif()
|
||||
message(STATUS "Gamepad support enabled for Android")
|
||||
else()
|
||||
@@ -39,6 +36,7 @@ endif()
|
||||
set(LIBS ${LIBS} qt6keychain)
|
||||
|
||||
include_directories(
|
||||
${CLIENT_ROOT_DIR}/3rd/QSimpleCrypto/src/include
|
||||
${CLIENT_ROOT_DIR}/3rd/qtkeychain/qtkeychain
|
||||
${CMAKE_CURRENT_BINARY_DIR}/3rd/qtkeychain
|
||||
)
|
||||
|
||||
21
client/cmake/QSimpleCrypto.cmake
Normal file
21
client/cmake/QSimpleCrypto.cmake
Normal file
@@ -0,0 +1,21 @@
|
||||
set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/..)
|
||||
set(QSIMPLECRYPTO_DIR ${CLIENT_ROOT_DIR}/3rd/QSimpleCrypto/src)
|
||||
|
||||
include_directories(${QSIMPLECRYPTO_DIR})
|
||||
|
||||
set(HEADERS ${HEADERS}
|
||||
${QSIMPLECRYPTO_DIR}/include/QAead.h
|
||||
${QSIMPLECRYPTO_DIR}/include/QBlockCipher.h
|
||||
${QSIMPLECRYPTO_DIR}/include/QRsa.h
|
||||
${QSIMPLECRYPTO_DIR}/include/QSimpleCrypto_global.h
|
||||
${QSIMPLECRYPTO_DIR}/include/QX509.h
|
||||
${QSIMPLECRYPTO_DIR}/include/QX509Store.h
|
||||
)
|
||||
|
||||
set(SOURCES ${SOURCES}
|
||||
${QSIMPLECRYPTO_DIR}/sources/QAead.cpp
|
||||
${QSIMPLECRYPTO_DIR}/sources/QBlockCipher.cpp
|
||||
${QSIMPLECRYPTO_DIR}/sources/QRsa.cpp
|
||||
${QSIMPLECRYPTO_DIR}/sources/QX509.cpp
|
||||
${QSIMPLECRYPTO_DIR}/sources/QX509Store.cpp
|
||||
)
|
||||
@@ -31,7 +31,6 @@ set(HEADERS ${HEADERS}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ioscontextmenu.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate-C-Interface.h
|
||||
@@ -43,7 +42,6 @@ set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ioscontextmenu.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
|
||||
@@ -51,12 +49,6 @@ set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/AmneziaSceneDelegateHooks.mm
|
||||
)
|
||||
|
||||
# The context menu helper uses ARC-only constructs (weak references); the
|
||||
# rest of the Objective-C++ sources build with manual reference counting.
|
||||
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ioscontextmenu.mm
|
||||
PROPERTIES COMPILE_OPTIONS "-fobjc-arc"
|
||||
)
|
||||
|
||||
|
||||
target_include_directories(${PROJECT} PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS})
|
||||
|
||||
@@ -77,6 +69,7 @@ set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_ATTRIBUTE_PRODUCT_NAME "AmneziaVPN"
|
||||
XCODE_ATTRIBUTE_BUNDLE_INFO_STRING "AmneziaVPN"
|
||||
XCODE_GENERATE_SCHEME TRUE
|
||||
XCODE_ATTRIBUTE_ENABLE_BITCODE "NO"
|
||||
XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME "AppIcon"
|
||||
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2"
|
||||
XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY ON
|
||||
@@ -85,7 +78,7 @@ set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_EMBED_APP_EXTENSIONS networkextension
|
||||
)
|
||||
|
||||
if(DEPLOY)
|
||||
if(DEFINED DEPLOY)
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
|
||||
@@ -128,11 +121,6 @@ target_sources(${PROJECT} PRIVATE
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/StoreKit2Helper.swift
|
||||
)
|
||||
|
||||
set_source_files_properties(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Media.xcassets
|
||||
PROPERTIES MACOSX_PACKAGE_LOCATION Resources
|
||||
)
|
||||
|
||||
target_sources(${PROJECT} PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/AmneziaVPNLaunchScreen.storyboard
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Media.xcassets
|
||||
|
||||
@@ -5,7 +5,6 @@ set_target_properties(${PROJECT} PROPERTIES MACOSX_BUNDLE TRUE)
|
||||
set(APPLE_PROJECT_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||
|
||||
enable_language(OBJC)
|
||||
enable_language(OBJCXX)
|
||||
enable_language(Swift)
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS ShaderTools Widgets)
|
||||
@@ -34,6 +33,7 @@ set(LIBS ${LIBS}
|
||||
set(HEADERS ${HEADERS}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate-C-Interface.h
|
||||
@@ -44,12 +44,19 @@ set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_contro
|
||||
set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
|
||||
)
|
||||
|
||||
set(ICON_FILE ${CMAKE_CURRENT_SOURCE_DIR}/images/app.icns)
|
||||
set(MACOSX_BUNDLE_ICON_FILE app.icns)
|
||||
set_source_files_properties(${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
|
||||
set(SOURCES ${SOURCES} ${ICON_FILE})
|
||||
|
||||
|
||||
target_include_directories(${PROJECT} PRIVATE
|
||||
${Qt6Gui_PRIVATE_INCLUDE_DIRS}
|
||||
${Qt6Widgets_PRIVATE_INCLUDE_DIRS}
|
||||
@@ -62,7 +69,6 @@ set_target_properties(${PROJECT} PROPERTIES
|
||||
MACOSX_BUNDLE_ICON_FILE "AppIcon"
|
||||
MACOSX_BUNDLE_INFO_STRING "AmneziaVPN"
|
||||
MACOSX_BUNDLE_BUNDLE_NAME "AmneziaVPN"
|
||||
MACOSX_BUNDLE_GUI_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}"
|
||||
MACOSX_BUNDLE_BUNDLE_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
|
||||
MACOSX_BUNDLE_LONG_VERSION_STRING "${APPLE_PROJECT_VERSION}-${CMAKE_PROJECT_VERSION_TWEAK}"
|
||||
MACOSX_BUNDLE_SHORT_VERSION_STRING "${APPLE_PROJECT_VERSION}"
|
||||
@@ -73,10 +79,14 @@ set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_ATTRIBUTE_PRODUCT_NAME "AmneziaVPN"
|
||||
XCODE_ATTRIBUTE_BUNDLE_INFO_STRING "AmneziaVPN"
|
||||
XCODE_GENERATE_SCHEME TRUE
|
||||
XCODE_ATTRIBUTE_ENABLE_BITCODE "NO"
|
||||
XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME "AppIcon"
|
||||
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2"
|
||||
XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY "NO"
|
||||
XCODE_EMBED_FRAMEWORKS_REMOVE_HEADERS_ON_COPY "YES"
|
||||
XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET "11.0"
|
||||
|
||||
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
|
||||
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../Frameworks"
|
||||
XCODE_EMBED_APP_EXTENSIONS AmneziaVPNNetworkExtension
|
||||
)
|
||||
@@ -123,11 +133,6 @@ target_sources(${PROJECT} PRIVATE
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/StoreKit2Helper.swift
|
||||
)
|
||||
|
||||
set_source_files_properties(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Images.xcassets
|
||||
PROPERTIES MACOSX_PACKAGE_LOCATION Resources
|
||||
)
|
||||
|
||||
target_sources(${PROJECT} PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Images.xcassets
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
|
||||
|
||||
@@ -52,16 +52,13 @@ set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/core/protocols/qmlRegisterProtocols.h
|
||||
${CLIENT_ROOT_DIR}/ui/utils/pages.h
|
||||
${CLIENT_ROOT_DIR}/ui/utils/qAutoStart.h
|
||||
${CLIENT_ROOT_DIR}/ui/utils/mtProxyPublicHostInput.h
|
||||
${CLIENT_ROOT_DIR}/core/protocols/vpnProtocol.h
|
||||
${CMAKE_CURRENT_BINARY_DIR}/version.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/selfhosted/sshClient.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/networkUtilities.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/payloadSender.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/serialization/serialization.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/serialization/transfer.h
|
||||
${CLIENT_ROOT_DIR}/../common/logger/logger.h
|
||||
${CLIENT_ROOT_DIR}/../common/crypto/cryptoUtils.h
|
||||
${CLIENT_ROOT_DIR}/ui/utils/qmlUtils.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/api/apiUtils.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/osSignalHandler.h
|
||||
@@ -130,11 +127,9 @@ set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/core/repositories/secureServersRepository.cpp
|
||||
${CLIENT_ROOT_DIR}/core/repositories/secureAppSettingsRepository.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/utils/qAutoStart.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/utils/mtProxyPublicHostInput.cpp
|
||||
${CLIENT_ROOT_DIR}/core/protocols/vpnProtocol.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/selfhosted/sshClient.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/networkUtilities.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/payloadSender.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/serialization/outbound.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/serialization/inbound.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/serialization/ss.cpp
|
||||
@@ -144,7 +139,6 @@ set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/core/utils/serialization/vmess.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/serialization/vmess_new.cpp
|
||||
${CLIENT_ROOT_DIR}/../common/logger/logger.cpp
|
||||
${CLIENT_ROOT_DIR}/../common/crypto/cryptoUtils.cpp
|
||||
${CLIENT_ROOT_DIR}/ui/utils/qmlUtils.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/api/apiUtils.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/serverConfigUtils.cpp
|
||||
@@ -207,6 +201,11 @@ file(GLOB CONFIGURATORS_CPP CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/core/configurat
|
||||
file(GLOB_RECURSE CORE_MODELS_H CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/core/models/*.h)
|
||||
file(GLOB_RECURSE CORE_MODELS_CPP CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/core/models/*.cpp)
|
||||
|
||||
if(NOT ANDROID AND NOT IOS)
|
||||
file(GLOB LOCAL_PROXY_H CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/core/local-proxy/*.h)
|
||||
file(GLOB LOCAL_PROXY_CPP CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/core/local-proxy/*.cpp)
|
||||
endif()
|
||||
|
||||
file(GLOB UI_MODELS_H CONFIGURE_DEPENDS
|
||||
${CLIENT_ROOT_DIR}/ui/models/*.h
|
||||
${CLIENT_ROOT_DIR}/ui/models/protocols/*.h
|
||||
@@ -252,6 +251,11 @@ set(SOURCES ${SOURCES}
|
||||
${UI_CONTROLLERS_CPP}
|
||||
)
|
||||
|
||||
if(NOT ANDROID AND NOT IOS)
|
||||
list(APPEND HEADERS ${LOCAL_PROXY_H})
|
||||
list(APPEND SOURCES ${LOCAL_PROXY_CPP})
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/core/protocols/ikev2VpnProtocolWindows.h
|
||||
|
||||
@@ -96,17 +96,13 @@ ProtocolConfig AwgConfigurator::createConfig(const ServerCredentials &credential
|
||||
newClientConfig.specialJunk4 = configMap.value(configKey::specialJunk4);
|
||||
newClientConfig.specialJunk5 = configMap.value(configKey::specialJunk5);
|
||||
|
||||
newClientConfig.cookieReplyPacketJunkSize = configMap.value(configKey::cookieReplyPacketJunkSize);
|
||||
newClientConfig.transportPacketJunkSize = configMap.value(configKey::transportPacketJunkSize);
|
||||
|
||||
newClientConfig.headerProtectionKey = configMap.value(configKey::headerProtectionKey);
|
||||
newClientConfig.contentPaddingAddition = configMap.value(configKey::contentPaddingAddition);
|
||||
newClientConfig.rekeyAfterTime = configMap.value(configKey::rekeyAfterTime);
|
||||
newClientConfig.rekeyTimeout = configMap.value(configKey::rekeyTimeout);
|
||||
newClientConfig.rejectAfterTime = configMap.value(configKey::rejectAfterTime);
|
||||
newClientConfig.keepaliveTimeout = configMap.value(configKey::keepaliveTimeout);
|
||||
newClientConfig.maxHandshakeAttempts = configMap.value(configKey::maxHandshakeAttempts);
|
||||
|
||||
if (container == DockerContainer::Awg2) {
|
||||
newClientConfig.cookieReplyPacketJunkSize = configMap.value(configKey::cookieReplyPacketJunkSize);
|
||||
newClientConfig.transportPacketJunkSize = configMap.value(configKey::transportPacketJunkSize);
|
||||
}
|
||||
|
||||
newClientConfig.isObfuscationEnabled = false;
|
||||
|
||||
protocolConfig.setClientConfig(newClientConfig);
|
||||
|
||||
return protocolConfig;
|
||||
|
||||
@@ -228,20 +228,11 @@ ProtocolConfig WireguardConfigurator::createConfig(const ServerCredentials &cred
|
||||
}
|
||||
}
|
||||
|
||||
const bool isAwg3 = awgServerConfig && awgServerConfig->protocolVersion == protocols::awg::awgV3;
|
||||
|
||||
amnezia::ScriptVars vars = amnezia::genBaseVars(credentials, container, dnsSettings.primaryDns, dnsSettings.secondaryDns);
|
||||
vars.append(amnezia::genProtocolVarsForContainer(container, containerConfig));
|
||||
QString scriptData = amnezia::scriptData(m_configTemplate, container);
|
||||
QString config = m_sshSession->replaceVars(scriptData, vars);
|
||||
|
||||
// The template lists every possible key, but each parameter is optional -
|
||||
// drop the lines whose value came out empty
|
||||
static const QRegularExpression emptyValueLine(R"(^\s*\S+\s*=\s*$)");
|
||||
auto configTemplateLines = config.split("\n");
|
||||
configTemplateLines.removeIf([](const QString &line) { return emptyValueLine.match(line).hasMatch(); });
|
||||
config = configTemplateLines.join("\n");
|
||||
|
||||
ConnectionData connData = prepareWireguardConfig(credentials, container, wireguardServerConfig, awgServerConfig, dnsSettings, errorCode);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return WireGuardProtocolConfig{};
|
||||
@@ -275,8 +266,7 @@ ProtocolConfig WireguardConfigurator::createConfig(const ServerCredentials &cred
|
||||
clientConfig.presharedKey = connData.pskKey;
|
||||
clientConfig.clientId = connData.clientPubKey;
|
||||
clientConfig.allowedIps = QStringList { "0.0.0.0/0", "::/0" };
|
||||
clientConfig.persistentKeepAlive = isAwg3 ? protocols::awg::defaultPersistentKeepAlive
|
||||
: protocols::wireguard::defaultPersistentKeepAlive;
|
||||
clientConfig.persistentKeepAlive = "25";
|
||||
clientConfig.mtu = mtu;
|
||||
clientConfig.isObfuscationEnabled = false;
|
||||
|
||||
|
||||
@@ -85,12 +85,6 @@ namespace {
|
||||
return t.toLower();
|
||||
}
|
||||
|
||||
// xray wants int ranges as "from-to" string, not a {from,to} object.
|
||||
QString makeRangeString(const QString &minV, const QString &maxV)
|
||||
{
|
||||
return minV + QLatin1Char('-') + maxV;
|
||||
}
|
||||
|
||||
void putIntRangeIfAny(QJsonObject &obj, const char *key, QString minV, QString maxV, const char *fallbackMin,
|
||||
const char *fallbackMax)
|
||||
{
|
||||
@@ -100,23 +94,10 @@ namespace {
|
||||
minV = QString::fromLatin1(fallbackMin);
|
||||
if (maxV.isEmpty())
|
||||
maxV = QString::fromLatin1(fallbackMax);
|
||||
obj[QString::fromUtf8(key)] = makeRangeString(minV, maxV);
|
||||
}
|
||||
|
||||
QString effectiveClientFlow(const amnezia::XrayServerConfig &srv)
|
||||
{
|
||||
const bool rawTransport = srv.transport.isEmpty() || srv.transport == QLatin1String("raw");
|
||||
const bool secureFlow =
|
||||
srv.security == QLatin1String("tls") || srv.security == QLatin1String("reality");
|
||||
return (rawTransport && secureFlow) ? srv.flow : QString();
|
||||
}
|
||||
|
||||
QString effectiveSecurity(const amnezia::XrayServerConfig &srv)
|
||||
{
|
||||
if (srv.transport == QLatin1String("mkcp") && srv.security == QLatin1String("reality")) {
|
||||
return QStringLiteral("none");
|
||||
}
|
||||
return srv.security;
|
||||
QJsonObject r;
|
||||
r[QStringLiteral("from")] = minV.toInt();
|
||||
r[QStringLiteral("to")] = maxV.toInt();
|
||||
obj[QString::fromUtf8(key)] = r;
|
||||
}
|
||||
|
||||
// Desktop applies this in XrayProtocol::start(); iOS/Android pass JSON straight to libxray — same fixes here.
|
||||
@@ -216,7 +197,7 @@ QJsonObject XrayConfigurator::mergeStreamSettingsForServerInbound(const XrayServ
|
||||
{
|
||||
QJsonObject streamSettings = buildStreamSettings(srv, QString());
|
||||
|
||||
if (effectiveSecurity(srv) != QLatin1String("reality")) {
|
||||
if (srv.security != QLatin1String("reality")) {
|
||||
return streamSettings;
|
||||
}
|
||||
|
||||
@@ -263,10 +244,10 @@ ErrorCode XrayConfigurator::applyServerSettingsToRemote(const ServerCredentials
|
||||
<< "container=" << static_cast<int>(container) << "host=" << credentials.hostName
|
||||
<< "transport=" << srv.transport << "security=" << srv.security << "port=" << srv.port
|
||||
<< "appendClient=" << appendNewClient;
|
||||
const QString flowValue = effectiveClientFlow(srv);
|
||||
const QString flowValue = srv.flow;
|
||||
QString realityPublicKey;
|
||||
QString realityShortId;
|
||||
if (effectiveSecurity(srv) == QLatin1String("reality")) {
|
||||
if (srv.security == QLatin1String("reality")) {
|
||||
errorCode = readRealityKeyFiles(container, credentials, realityPublicKey, realityShortId);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
logger.error() << "Xray applyServerSettings: readRealityKeyFiles failed, error="
|
||||
@@ -382,129 +363,6 @@ ErrorCode XrayConfigurator::applyServerSettingsToRemote(const ServerCredentials
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode XrayConfigurator::readContainerKeyFile(DockerContainer container, const ServerCredentials &credentials,
|
||||
const QString &path, QString &out) const
|
||||
{
|
||||
out.clear();
|
||||
for (int attempt = 0; attempt < 3; ++attempt) {
|
||||
ErrorCode fileError = ErrorCode::NoError;
|
||||
out = QString::fromUtf8(m_sshSession->getTextFileFromContainer(container, credentials, path, fileError));
|
||||
out.replace(QLatin1Char('\n'), QString());
|
||||
out.replace(QLatin1Char('\r'), QString());
|
||||
if (fileError == ErrorCode::NoError && !out.isEmpty()) {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
if (attempt < 2) {
|
||||
QThread::msleep(500);
|
||||
}
|
||||
}
|
||||
logger.error() << "Xray readContainerKeyFile: failed path=" << path;
|
||||
return ErrorCode::XrayRealityKeysReadFailed;
|
||||
}
|
||||
|
||||
ErrorCode XrayConfigurator::writeServerConfigForSetup(const ServerCredentials &credentials, DockerContainer container,
|
||||
ContainerConfig &containerConfig, const DnsSettings &dnsSettings)
|
||||
{
|
||||
Q_UNUSED(dnsSettings);
|
||||
namespace px = amnezia::protocols::xray;
|
||||
|
||||
const auto *xrayCfg = containerConfig.protocolConfig.as<XrayProtocolConfig>();
|
||||
if (!xrayCfg) {
|
||||
logger.error() << "Xray writeServerConfigForSetup: missing XrayProtocolConfig";
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
const XrayServerConfig &srv = xrayCfg->serverConfig;
|
||||
if (srv.isThirdPartyConfig) {
|
||||
logger.info() << "Xray writeServerConfigForSetup: skipped (third-party/native profile)";
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
logger.info() << "Xray writeServerConfigForSetup: start container=" << static_cast<int>(container)
|
||||
<< "transport=" << srv.transport << "security=" << srv.security << "port=" << srv.port;
|
||||
|
||||
ErrorCode errorCode = ErrorCode::NoError;
|
||||
|
||||
QString clientId;
|
||||
errorCode = readContainerKeyFile(container, credentials, QString::fromLatin1(px::uuidPath), clientId);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
const QString securityEff = effectiveSecurity(srv);
|
||||
|
||||
QString realityPrivateKey;
|
||||
QString realityPublicKey;
|
||||
QString realityShortId;
|
||||
if (securityEff == QLatin1String("reality")) {
|
||||
errorCode = readContainerKeyFile(container, credentials, QString::fromLatin1(px::PrivateKeyPath), realityPrivateKey);
|
||||
if (errorCode != ErrorCode::NoError)
|
||||
return errorCode;
|
||||
errorCode = readContainerKeyFile(container, credentials, QString::fromLatin1(px::PublicKeyPath), realityPublicKey);
|
||||
if (errorCode != ErrorCode::NoError)
|
||||
return errorCode;
|
||||
errorCode = readContainerKeyFile(container, credentials, QString::fromLatin1(px::shortidPath), realityShortId);
|
||||
if (errorCode != ErrorCode::NoError)
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
QJsonObject streamSettings = buildStreamSettings(srv, clientId);
|
||||
if (securityEff == QLatin1String("reality")) {
|
||||
const QString siteEff = srv.site.isEmpty() ? QString::fromLatin1(px::defaultSite) : srv.site;
|
||||
const QString sniEff = srv.sni.isEmpty() ? siteEff : srv.sni;
|
||||
const QString fpEff = srv.fingerprint.isEmpty() ? QString::fromLatin1(px::defaultFingerprint) : srv.fingerprint;
|
||||
QJsonObject rs;
|
||||
rs[QStringLiteral("dest")] = siteEff + QStringLiteral(":443");
|
||||
rs[px::fingerprint] = fpEff;
|
||||
rs[QStringLiteral("privateKey")] = realityPrivateKey;
|
||||
rs[px::serverNames] = QJsonArray { sniEff };
|
||||
rs[QStringLiteral("shortIds")] = QJsonArray { realityShortId };
|
||||
streamSettings[px::realitySettings] = rs;
|
||||
}
|
||||
|
||||
QJsonObject clientEntry;
|
||||
clientEntry[px::id] = clientId;
|
||||
const QString flowValue = effectiveClientFlow(srv);
|
||||
if (!flowValue.isEmpty()) {
|
||||
clientEntry[px::flow] = flowValue;
|
||||
}
|
||||
|
||||
QJsonObject settings;
|
||||
settings[px::clients] = QJsonArray { clientEntry };
|
||||
settings[QStringLiteral("decryption")] = QStringLiteral("none");
|
||||
|
||||
QJsonObject inbound;
|
||||
inbound[px::port] = srv.port.isEmpty() ? QString(px::defaultPort).toInt() : srv.port.toInt();
|
||||
inbound[QStringLiteral("protocol")] = QStringLiteral("vless");
|
||||
inbound[px::settings] = settings;
|
||||
inbound[px::streamSettings] = streamSettings;
|
||||
|
||||
QJsonObject serverConfig;
|
||||
serverConfig[QStringLiteral("log")] = QJsonObject { { QStringLiteral("loglevel"), QStringLiteral("error") } };
|
||||
serverConfig[px::inbounds] = QJsonArray { inbound };
|
||||
serverConfig[px::outbounds] =
|
||||
QJsonArray { QJsonObject { { QStringLiteral("protocol"), QStringLiteral("freedom") } } };
|
||||
|
||||
const QString json = QString::fromUtf8(QJsonDocument(serverConfig).toJson());
|
||||
errorCode = m_sshSession->uploadTextFileToContainer(container, credentials, json,
|
||||
QString::fromLatin1(px::serverConfigPath),
|
||||
libssh::ScpOverwriteMode::ScpOverwriteExisting);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
logger.error() << "Xray writeServerConfigForSetup: upload failed, error=" << static_cast<int>(errorCode);
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
XrayProtocolConfig updated =
|
||||
buildClientProtocolConfig(credentials, container, srv, clientId, errorCode, realityPublicKey, realityShortId);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
logger.error() << "Xray writeServerConfigForSetup: buildClientProtocolConfig failed, error="
|
||||
<< static_cast<int>(errorCode);
|
||||
return errorCode;
|
||||
}
|
||||
containerConfig.protocolConfig = updated;
|
||||
logger.info() << "Xray writeServerConfigForSetup: done, clientId=" << clientId;
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const ContainerConfig &containerConfig,
|
||||
const DnsSettings &dnsSettings,
|
||||
@@ -531,9 +389,7 @@ XrayProtocolConfig XrayConfigurator::buildClientProtocolConfig(const ServerCrede
|
||||
QString xrayPublicKey = prefetchedRealityPublicKey;
|
||||
QString xrayShortId = prefetchedRealityShortId;
|
||||
|
||||
const QString securityEff = effectiveSecurity(srv);
|
||||
|
||||
if (securityEff == QLatin1String("reality")) {
|
||||
if (srv.security == QLatin1String("reality")) {
|
||||
if (xrayPublicKey.isEmpty() || xrayShortId.isEmpty()) {
|
||||
errorCode = readRealityKeyFiles(container, credentials, xrayPublicKey, xrayShortId);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
@@ -545,9 +401,8 @@ XrayProtocolConfig XrayConfigurator::buildClientProtocolConfig(const ServerCrede
|
||||
QJsonObject userObj;
|
||||
userObj[amnezia::protocols::xray::id] = clientId;
|
||||
userObj[amnezia::protocols::xray::encryption] = QStringLiteral("none");
|
||||
const QString flowValue = effectiveClientFlow(srv);
|
||||
if (!flowValue.isEmpty()) {
|
||||
userObj[amnezia::protocols::xray::flow] = flowValue;
|
||||
if (!srv.flow.isEmpty()) {
|
||||
userObj[amnezia::protocols::xray::flow] = srv.flow;
|
||||
}
|
||||
|
||||
QJsonObject vnextEntry;
|
||||
@@ -564,7 +419,7 @@ XrayProtocolConfig XrayConfigurator::buildClientProtocolConfig(const ServerCrede
|
||||
outbound[amnezia::protocols::xray::settings] = outboundSettings;
|
||||
|
||||
QJsonObject streamObj = buildStreamSettings(srv, clientId);
|
||||
if (securityEff == QLatin1String("reality")) {
|
||||
if (srv.security == QLatin1String("reality")) {
|
||||
QJsonObject rs = streamObj[amnezia::protocols::xray::realitySettings].toObject();
|
||||
rs[amnezia::protocols::xray::publicKey] = xrayPublicKey;
|
||||
rs[amnezia::protocols::xray::shortId] = xrayShortId;
|
||||
@@ -613,24 +468,18 @@ QJsonObject XrayConfigurator::buildStreamSettings(const XrayServerConfig &srv, c
|
||||
networkValue = QStringLiteral("kcp");
|
||||
streamSettings[px::network] = networkValue;
|
||||
|
||||
const QString securityEff = effectiveSecurity(srv);
|
||||
streamSettings[px::security] = securityEff;
|
||||
streamSettings[px::security] = srv.security;
|
||||
|
||||
if (securityEff == QLatin1String("tls")) {
|
||||
if (srv.security == QLatin1String("tls")) {
|
||||
QJsonObject tlsSettings;
|
||||
const QString sniEff = srv.sni.isEmpty() ? QString::fromLatin1(px::defaultSni) : srv.sni;
|
||||
tlsSettings[px::serverName] = sniEff;
|
||||
const QString alpnEff = srv.alpn.isEmpty() ? QString::fromLatin1(px::defaultAlpn) : srv.alpn;
|
||||
QJsonArray alpnArray;
|
||||
for (const QString &a : alpnEff.split(QLatin1Char(','))) {
|
||||
QString t = a.trimmed();
|
||||
if (t.isEmpty())
|
||||
continue;
|
||||
if (t.compare(QLatin1String("HTTP/2"), Qt::CaseInsensitive) == 0)
|
||||
t = QStringLiteral("h2");
|
||||
else if (t.compare(QLatin1String("HTTP/1.1"), Qt::CaseInsensitive) == 0)
|
||||
t = QStringLiteral("http/1.1");
|
||||
alpnArray.append(t);
|
||||
const QString t = a.trimmed();
|
||||
if (!t.isEmpty())
|
||||
alpnArray.append(t);
|
||||
}
|
||||
if (!alpnArray.isEmpty())
|
||||
tlsSettings[QStringLiteral("alpn")] = alpnArray;
|
||||
@@ -639,7 +488,7 @@ QJsonObject XrayConfigurator::buildStreamSettings(const XrayServerConfig &srv, c
|
||||
streamSettings[QStringLiteral("tlsSettings")] = tlsSettings;
|
||||
}
|
||||
|
||||
if (securityEff == QLatin1String("reality")) {
|
||||
if (srv.security == QLatin1String("reality")) {
|
||||
QJsonObject realSettings;
|
||||
const QString fpEff = srv.fingerprint.isEmpty() ? QString::fromLatin1(px::defaultFingerprint) : srv.fingerprint;
|
||||
realSettings[px::fingerprint] = fpEff;
|
||||
@@ -655,13 +504,13 @@ QJsonObject XrayConfigurator::buildStreamSettings(const XrayServerConfig &srv, c
|
||||
xo[QStringLiteral("host")] = hostEff;
|
||||
if (!xhttp.path.isEmpty())
|
||||
xo[QStringLiteral("path")] = xhttp.path;
|
||||
QString modeEff = normalizeXhttpMode(xhttp.mode);
|
||||
if (modeEff == QLatin1String("auto") || modeEff == QLatin1String("packet-up")) {
|
||||
modeEff = QStringLiteral("stream-one");
|
||||
}
|
||||
xo[QStringLiteral("mode")] = modeEff;
|
||||
xo[QStringLiteral("mode")] = normalizeXhttpMode(xhttp.mode);
|
||||
|
||||
// No "Host" in headers: xray rejects it when the top-level "host" field is set.
|
||||
if (xhttp.headersTemplate.compare(QLatin1String("HTTP"), Qt::CaseInsensitive) == 0) {
|
||||
QJsonObject headers;
|
||||
headers[QStringLiteral("Host")] = hostEff;
|
||||
xo[QStringLiteral("headers")] = headers;
|
||||
}
|
||||
|
||||
const QString methodEff =
|
||||
xhttp.uplinkMethod.isEmpty() ? QString::fromLatin1(px::defaultXhttpUplinkMethod) : xhttp.uplinkMethod;
|
||||
@@ -672,27 +521,27 @@ QJsonObject XrayConfigurator::buildStreamSettings(const XrayServerConfig &srv, c
|
||||
|
||||
const QString sessPl = normalizeSessionSeqPlacement(xhttp.sessionPlacement);
|
||||
if (!sessPl.isEmpty())
|
||||
xo[QStringLiteral("sessionIDPlacement")] = sessPl;
|
||||
xo[QStringLiteral("sessionPlacement")] = sessPl;
|
||||
const QString seqPl = normalizeSessionSeqPlacement(xhttp.seqPlacement);
|
||||
if (!seqPl.isEmpty())
|
||||
xo[QStringLiteral("seqPlacement")] = seqPl;
|
||||
if (!xhttp.sessionKey.isEmpty())
|
||||
xo[QStringLiteral("sessionIDKey")] = xhttp.sessionKey;
|
||||
xo[QStringLiteral("sessionKey")] = xhttp.sessionKey;
|
||||
if (!xhttp.seqKey.isEmpty())
|
||||
xo[QStringLiteral("seqKey")] = xhttp.seqKey;
|
||||
|
||||
const QString uDataPl = normalizeUplinkDataPlacement(xhttp.uplinkDataPlacement);
|
||||
const bool uDataNeedsPacketUp =
|
||||
uDataPl == QLatin1String("header") || uDataPl == QLatin1String("cookie");
|
||||
if (!(uDataNeedsPacketUp && modeEff != QLatin1String("packet-up")))
|
||||
xo[QStringLiteral("uplinkDataPlacement")] = uDataPl;
|
||||
xo[QStringLiteral("uplinkDataPlacement")] = normalizeUplinkDataPlacement(xhttp.uplinkDataPlacement);
|
||||
if (!xhttp.uplinkDataKey.isEmpty())
|
||||
xo[QStringLiteral("uplinkDataKey")] = xhttp.uplinkDataKey;
|
||||
|
||||
const QString ucs = xhttp.uplinkChunkSize.isEmpty() ? QString::fromLatin1(px::defaultXhttpUplinkChunkSize)
|
||||
: xhttp.uplinkChunkSize;
|
||||
if (!ucs.isEmpty() && ucs != QLatin1String("0")) {
|
||||
xo[QStringLiteral("uplinkChunkSize")] = ucs.toInt();
|
||||
const int v = ucs.toInt();
|
||||
QJsonObject chunkR;
|
||||
chunkR[QStringLiteral("from")] = v;
|
||||
chunkR[QStringLiteral("to")] = v;
|
||||
xo[QStringLiteral("uplinkChunkSize")] = chunkR;
|
||||
}
|
||||
|
||||
if (!xhttp.scMaxBufferedPosts.isEmpty())
|
||||
@@ -709,20 +558,17 @@ QJsonObject XrayConfigurator::buildStreamSettings(const XrayServerConfig &srv, c
|
||||
xo[QStringLiteral("xPaddingObfsMode")] = pad.obfsMode;
|
||||
if (pad.obfsMode) {
|
||||
if (!pad.bytesMin.isEmpty() || !pad.bytesMax.isEmpty()) {
|
||||
const int fromV = pad.bytesMin.isEmpty()
|
||||
? QString::fromLatin1(px::defaultXPaddingBytesMin).toInt()
|
||||
: pad.bytesMin.toInt();
|
||||
int toV = pad.bytesMax.isEmpty()
|
||||
? QString::fromLatin1(px::defaultXPaddingBytesMax).toInt()
|
||||
: pad.bytesMax.toInt();
|
||||
QJsonObject br;
|
||||
const int fromV = pad.bytesMin.isEmpty() ? 1 : pad.bytesMin.toInt();
|
||||
int toV = pad.bytesMax.isEmpty() ? 256 : pad.bytesMax.toInt();
|
||||
if (toV < fromV)
|
||||
toV = fromV;
|
||||
xo[QStringLiteral("xPaddingBytes")] = makeRangeString(QString::number(fromV), QString::number(toV));
|
||||
br[QStringLiteral("from")] = fromV;
|
||||
br[QStringLiteral("to")] = toV;
|
||||
xo[QStringLiteral("xPaddingBytes")] = br;
|
||||
}
|
||||
xo[QStringLiteral("xPaddingKey")] =
|
||||
pad.key.isEmpty() ? QString::fromLatin1(px::defaultXPaddingKey) : pad.key;
|
||||
xo[QStringLiteral("xPaddingHeader")] =
|
||||
pad.header.isEmpty() ? QString::fromLatin1(px::defaultXPaddingHeader) : pad.header;
|
||||
xo[QStringLiteral("xPaddingKey")] = pad.key.isEmpty() ? QStringLiteral("x_padding") : pad.key;
|
||||
xo[QStringLiteral("xPaddingHeader")] = pad.header.isEmpty() ? QStringLiteral("X-Padding") : pad.header;
|
||||
xo[QStringLiteral("xPaddingPlacement")] = normalizeXPaddingPlacement(
|
||||
pad.placement.isEmpty() ? QString::fromLatin1(px::defaultXPaddingPlacement) : pad.placement);
|
||||
xo[QStringLiteral("xPaddingMethod")] = normalizeXPaddingMethod(
|
||||
@@ -733,14 +579,12 @@ QJsonObject XrayConfigurator::buildStreamSettings(const XrayServerConfig &srv, c
|
||||
if (xhttp.xmux.enabled) {
|
||||
QJsonObject mux;
|
||||
auto addMuxRange = [&](const char *key, const QString &a, const QString &b) {
|
||||
// omit empty / 0-0 ranges (xray may reject "0-0")
|
||||
const bool aZero = a.isEmpty() || a == QLatin1String("0");
|
||||
const bool bZero = b.isEmpty() || b == QLatin1String("0");
|
||||
if (aZero && bZero)
|
||||
if (a.isEmpty() && b.isEmpty())
|
||||
return;
|
||||
const QString aV = a.isEmpty() ? QStringLiteral("0") : a;
|
||||
const QString bV = b.isEmpty() ? QStringLiteral("0") : b;
|
||||
mux[QString::fromUtf8(key)] = makeRangeString(aV, bV);
|
||||
QJsonObject r;
|
||||
r[QStringLiteral("from")] = a.isEmpty() ? 0 : a.toInt();
|
||||
r[QStringLiteral("to")] = b.isEmpty() ? 0 : b.toInt();
|
||||
mux[QString::fromUtf8(key)] = r;
|
||||
};
|
||||
addMuxRange("maxConcurrency", xhttp.xmux.maxConcurrencyMin, xhttp.xmux.maxConcurrencyMax);
|
||||
addMuxRange("maxConnections", xhttp.xmux.maxConnectionsMin, xhttp.xmux.maxConnectionsMax);
|
||||
|
||||
@@ -30,16 +30,7 @@ public:
|
||||
bool appendNewClient,
|
||||
QString *outClientId = nullptr);
|
||||
|
||||
amnezia::ErrorCode writeServerConfigForSetup(const amnezia::ServerCredentials &credentials,
|
||||
amnezia::DockerContainer container,
|
||||
amnezia::ContainerConfig &containerConfig,
|
||||
const amnezia::DnsSettings &dnsSettings);
|
||||
|
||||
private:
|
||||
amnezia::ErrorCode readContainerKeyFile(amnezia::DockerContainer container,
|
||||
const amnezia::ServerCredentials &credentials,
|
||||
const QString &path, QString &out) const;
|
||||
|
||||
QString prepareServerConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container, const amnezia::ContainerConfig &containerConfig,
|
||||
const amnezia::DnsSettings &dnsSettings,
|
||||
amnezia::ErrorCode &errorCode);
|
||||
|
||||
@@ -78,8 +78,7 @@ QFuture<QPair<ErrorCode, QJsonArray>> NewsController::fetchNews()
|
||||
m_appSettingsRepository->getGatewayEndpoint(),
|
||||
m_appSettingsRepository->isDevGatewayEnv(),
|
||||
apiDefs::requestTimeoutMsecs,
|
||||
m_appSettingsRepository->isStrictKillSwitchEnabled(),
|
||||
m_appSettingsRepository);
|
||||
m_appSettingsRepository->isStrictKillSwitchEnabled());
|
||||
|
||||
QJsonObject payload;
|
||||
payload.insert("locale", m_appSettingsRepository->getAppLanguage().name().split("_").first());
|
||||
|
||||
@@ -242,7 +242,7 @@ ErrorCode ServicesCatalogController::fillAvailableServices(QJsonObject &services
|
||||
ErrorCode ServicesCatalogController::executeRequest(const QString &endpoint, const QJsonObject &apiPayload, QByteArray &responseBody)
|
||||
{
|
||||
GatewayController gatewayController(m_appSettingsRepository->getGatewayEndpoint(), m_appSettingsRepository->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
|
||||
m_appSettingsRepository->isStrictKillSwitchEnabled(), m_appSettingsRepository);
|
||||
m_appSettingsRepository->isStrictKillSwitchEnabled());
|
||||
return gatewayController.post(endpoint, apiPayload, responseBody);
|
||||
}
|
||||
|
||||
|
||||
@@ -56,38 +56,6 @@ QString getSubscriptionStatusForRenewal(const ApiConfig &apiConfig)
|
||||
|
||||
return QStringLiteral("active");
|
||||
}
|
||||
|
||||
QString normalizeCaptchaSolution(const QString &captchaSolution)
|
||||
{
|
||||
QString normalizedSolution;
|
||||
normalizedSolution.reserve(captchaSolution.size());
|
||||
for (const QChar &ch : captchaSolution) {
|
||||
const ushort u = ch.unicode();
|
||||
if (u >= '0' && u <= '9') {
|
||||
normalizedSolution += ch;
|
||||
} else if (u >= 0xFF10 && u <= 0xFF19) {
|
||||
normalizedSolution += QChar(static_cast<char16_t>(u - 0xFF10 + '0'));
|
||||
}
|
||||
}
|
||||
return normalizedSolution.isEmpty() ? captchaSolution.trimmed() : normalizedSolution;
|
||||
}
|
||||
|
||||
bool fillCaptchaInfoFromResponse(const QByteArray &responseBody, SubscriptionController::CaptchaInfo &captchaInfo)
|
||||
{
|
||||
const QJsonDocument jsonDoc = QJsonDocument::fromJson(responseBody);
|
||||
if (!jsonDoc.isObject()) {
|
||||
return false;
|
||||
}
|
||||
const QJsonObject jsonObj = jsonDoc.object();
|
||||
if (!jsonObj.contains(QStringLiteral("captcha_id")) || !jsonObj.contains(QStringLiteral("captcha_image"))) {
|
||||
return false;
|
||||
}
|
||||
captchaInfo.captchaId = jsonObj.value(QStringLiteral("captcha_id")).toString();
|
||||
captchaInfo.captchaImageBase64 = jsonObj.value(QStringLiteral("captcha_image")).toString();
|
||||
captchaInfo.hint = jsonObj.value(QStringLiteral("hint")).toString();
|
||||
captchaInfo.isRequired = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -188,14 +156,23 @@ ErrorCode SubscriptionController::extractServerConfigJsonFromResponse(const QByt
|
||||
|
||||
// TODO looks like this block can be removed after v1 configs EOL
|
||||
|
||||
const QStringList awgProtocolKeys = configKey::awgProtocolKeys();
|
||||
serverProtocolConfig[configKey::junkPacketCount] = clientProtocolConfig.value(configKey::junkPacketCount);
|
||||
serverProtocolConfig[configKey::junkPacketMinSize] = clientProtocolConfig.value(configKey::junkPacketMinSize);
|
||||
serverProtocolConfig[configKey::junkPacketMaxSize] = clientProtocolConfig.value(configKey::junkPacketMaxSize);
|
||||
serverProtocolConfig[configKey::initPacketJunkSize] = clientProtocolConfig.value(configKey::initPacketJunkSize);
|
||||
serverProtocolConfig[configKey::responsePacketJunkSize] = clientProtocolConfig.value(configKey::responsePacketJunkSize);
|
||||
serverProtocolConfig[configKey::initPacketMagicHeader] = clientProtocolConfig.value(configKey::initPacketMagicHeader);
|
||||
serverProtocolConfig[configKey::responsePacketMagicHeader] = clientProtocolConfig.value(configKey::responsePacketMagicHeader);
|
||||
serverProtocolConfig[configKey::underloadPacketMagicHeader] = clientProtocolConfig.value(configKey::underloadPacketMagicHeader);
|
||||
serverProtocolConfig[configKey::transportPacketMagicHeader] = clientProtocolConfig.value(configKey::transportPacketMagicHeader);
|
||||
|
||||
for (const QString &key : awgProtocolKeys) {
|
||||
const QJsonValue value = clientProtocolConfig.value(key);
|
||||
if (value.isString() && !value.toString().isEmpty()) {
|
||||
serverProtocolConfig[key] = value;
|
||||
}
|
||||
}
|
||||
serverProtocolConfig[configKey::cookieReplyPacketJunkSize] = clientProtocolConfig.value(configKey::cookieReplyPacketJunkSize);
|
||||
serverProtocolConfig[configKey::transportPacketJunkSize] = clientProtocolConfig.value(configKey::transportPacketJunkSize);
|
||||
serverProtocolConfig[configKey::specialJunk1] = clientProtocolConfig.value(configKey::specialJunk1);
|
||||
serverProtocolConfig[configKey::specialJunk2] = clientProtocolConfig.value(configKey::specialJunk2);
|
||||
serverProtocolConfig[configKey::specialJunk3] = clientProtocolConfig.value(configKey::specialJunk3);
|
||||
serverProtocolConfig[configKey::specialJunk4] = clientProtocolConfig.value(configKey::specialJunk4);
|
||||
serverProtocolConfig[configKey::specialJunk5] = clientProtocolConfig.value(configKey::specialJunk5);
|
||||
|
||||
//
|
||||
|
||||
@@ -221,6 +198,9 @@ void SubscriptionController::updateApiConfigInJson(QJsonObject &serverConfigJson
|
||||
|
||||
if (serverConfigJson.value(configKey::configVersion).toInt() == serverConfigUtils::ConfigSource::AmneziaGateway) {
|
||||
QJsonObject responseObj = QJsonDocument::fromJson(apiResponseBody).object();
|
||||
if (responseObj.contains(apiDefs::key::supportedProtocols)) {
|
||||
apiConfig.insert(apiDefs::key::supportedProtocols, responseObj.value(apiDefs::key::supportedProtocols).toArray());
|
||||
}
|
||||
if (responseObj.contains(apiDefs::key::serviceInfo)) {
|
||||
apiConfig.insert(apiDefs::key::serviceInfo, responseObj.value(apiDefs::key::serviceInfo).toObject());
|
||||
}
|
||||
@@ -232,7 +212,7 @@ void SubscriptionController::updateApiConfigInJson(QJsonObject &serverConfigJson
|
||||
ErrorCode SubscriptionController::executeRequest(const QString &endpoint, const QJsonObject &apiPayload, QByteArray &responseBody, bool isTestPurchase)
|
||||
{
|
||||
GatewayController gatewayController(m_appSettingsRepository->getGatewayEndpoint(isTestPurchase), m_appSettingsRepository->isDevGatewayEnv(isTestPurchase), apiDefs::requestTimeoutMsecs,
|
||||
m_appSettingsRepository->isStrictKillSwitchEnabled(), m_appSettingsRepository);
|
||||
m_appSettingsRepository->isStrictKillSwitchEnabled());
|
||||
return gatewayController.post(endpoint, apiPayload, responseBody);
|
||||
}
|
||||
|
||||
@@ -257,7 +237,14 @@ ErrorCode SubscriptionController::importServiceFromGateway(const QString &userCo
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody);
|
||||
|
||||
if (errorCode == ErrorCode::ApiCaptchaRequiredError) {
|
||||
fillCaptchaInfoFromResponse(responseBody, captchaInfo);
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(responseBody);
|
||||
if (jsonDoc.isObject()) {
|
||||
QJsonObject jsonObj = jsonDoc.object();
|
||||
captchaInfo.captchaId = jsonObj.value("captcha_id").toString();
|
||||
captchaInfo.captchaImageBase64 = jsonObj.value("captcha_image").toString();
|
||||
captchaInfo.hint = jsonObj.value("hint").toString();
|
||||
captchaInfo.isRequired = true;
|
||||
}
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
@@ -423,8 +410,7 @@ ErrorCode SubscriptionController::importServiceFromAppStore(const QString &userC
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::updateServiceFromGateway(const QString &serverId, const QString &newCountryCode, bool isConnectEvent,
|
||||
CaptchaInfo *captchaInfoOut, ProtocolData *usedProtocolDataOut)
|
||||
ErrorCode SubscriptionController::updateServiceFromGateway(const QString &serverId, const QString &newCountryCode, bool isConnectEvent)
|
||||
{
|
||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
if (!apiV2.has_value()) {
|
||||
@@ -432,25 +418,8 @@ ErrorCode SubscriptionController::updateServiceFromGateway(const QString &server
|
||||
}
|
||||
const bool isTestPurchase = apiV2->apiConfig.isTestPurchase;
|
||||
QString serviceProtocol = apiV2->serviceProtocol();
|
||||
|
||||
if (!newCountryCode.isEmpty()) {
|
||||
const auto availableCountries = apiV2->apiConfig.availableCountries;
|
||||
for (const auto &country : availableCountries) {
|
||||
const auto countryObject = country.toObject();
|
||||
if (countryObject.value(apiDefs::key::serverCountryCode).toString() != newCountryCode) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto availableProtocols = countryObject.value(apiDefs::key::availableProtocols).toArray();
|
||||
if (!availableProtocols.isEmpty() && !availableProtocols.contains(serviceProtocol)) {
|
||||
serviceProtocol = availableProtocols.first().toString();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ProtocolData protocolData = generateProtocolData(serviceProtocol);
|
||||
|
||||
|
||||
QJsonObject authDataJson = apiV2->authData.toJson();
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
@@ -472,11 +441,6 @@ ErrorCode SubscriptionController::updateServiceFromGateway(const QString &server
|
||||
QByteArray responseBody;
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody, isTestPurchase);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
if (errorCode == ErrorCode::ApiCaptchaRequiredError && captchaInfoOut) {
|
||||
if (fillCaptchaInfoFromResponse(responseBody, *captchaInfoOut) && usedProtocolDataOut) {
|
||||
*usedProtocolDataOut = protocolData;
|
||||
}
|
||||
}
|
||||
if (errorCode == ErrorCode::ApiSubscriptionExpiredError && !apiV2->apiConfig.isInAppPurchase) {
|
||||
ApiV2ServerConfig expiredApiV2 = *apiV2;
|
||||
expiredApiV2.apiConfig.subscriptionExpiredByServer = true;
|
||||
@@ -486,40 +450,29 @@ ErrorCode SubscriptionController::updateServiceFromGateway(const QString &server
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
return applyUpdatedServiceConfig(serverId, serviceProtocol, protocolData, responseBody);
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::applyUpdatedServiceConfig(const QString &serverId, const QString &serviceProtocol,
|
||||
const ProtocolData &protocolData, const QByteArray &responseBody)
|
||||
{
|
||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
if (!apiV2.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
QJsonObject serverConfigJson;
|
||||
ErrorCode errorCode = extractServerConfigJsonFromResponse(responseBody, serviceProtocol, protocolData, serverConfigJson);
|
||||
errorCode = extractServerConfigJsonFromResponse(responseBody, serviceProtocol, protocolData, serverConfigJson);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
|
||||
updateApiConfigInJson(serverConfigJson, apiV2->apiConfig.serviceType, serviceProtocol, apiV2->apiConfig.userCountryCode, responseBody);
|
||||
|
||||
|
||||
if (serverConfigJson.value(configKey::configVersion).toInt() != serverConfigUtils::ConfigSource::AmneziaGateway) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
ApiV2ServerConfig newApiV2Config = ApiV2ServerConfig::fromJson(serverConfigJson);
|
||||
ApiV2ServerConfig* newApiV2 = &newApiV2Config;
|
||||
|
||||
|
||||
newApiV2->apiConfig.vpnKey = apiV2->apiConfig.vpnKey;
|
||||
newApiV2->apiConfig.isTestPurchase = apiV2->apiConfig.isTestPurchase;
|
||||
newApiV2->apiConfig.isInAppPurchase = apiV2->apiConfig.isInAppPurchase;
|
||||
newApiV2->apiConfig.subscriptionExpiredByServer = false;
|
||||
|
||||
|
||||
newApiV2->authData = apiV2->authData;
|
||||
newApiV2->crc = apiV2->crc;
|
||||
|
||||
|
||||
if (apiV2->nameOverriddenByUser) {
|
||||
newApiV2->name = apiV2->name;
|
||||
newApiV2->displayName = apiV2->displayName;
|
||||
@@ -531,59 +484,6 @@ ErrorCode SubscriptionController::applyUpdatedServiceConfig(const QString &serve
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::resolveUpdateServiceCaptcha(const QString &serverId, const QString &newCountryCode,
|
||||
bool isConnectEvent, const ProtocolData &protocolData,
|
||||
const QString &captchaId, const QString &captchaSolution,
|
||||
CaptchaInfo *retryCaptchaOut)
|
||||
{
|
||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
if (!apiV2.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
const bool isTestPurchase = apiV2->apiConfig.isTestPurchase;
|
||||
QString serviceProtocol = apiV2->serviceProtocol();
|
||||
|
||||
QJsonObject authDataJson = apiV2->authData.toJson();
|
||||
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
|
||||
QString(APP_VERSION),
|
||||
m_appSettingsRepository->getAppLanguage().name().split("_").first(),
|
||||
m_appSettingsRepository->getInstallationUuid(true),
|
||||
apiV2->apiConfig.userCountryCode,
|
||||
newCountryCode,
|
||||
apiV2->serviceType(),
|
||||
serviceProtocol,
|
||||
authDataJson };
|
||||
|
||||
QJsonObject apiPayload = gatewayRequestData.toJsonObject();
|
||||
appendProtocolDataToApiPayload(serviceProtocol, protocolData, apiPayload);
|
||||
|
||||
if (isConnectEvent) {
|
||||
apiPayload[apiDefs::key::isConnectEvent] = true;
|
||||
}
|
||||
|
||||
apiPayload["captcha_id"] = captchaId;
|
||||
apiPayload["captcha_solution"] = normalizeCaptchaSolution(captchaSolution);
|
||||
|
||||
QByteArray responseBody;
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody, isTestPurchase);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
if (retryCaptchaOut
|
||||
&& (errorCode == ErrorCode::ApiCaptchaInvalidError || errorCode == ErrorCode::ApiCaptchaRefreshError
|
||||
|| errorCode == ErrorCode::ApiCaptchaRequiredError)) {
|
||||
fillCaptchaInfoFromResponse(responseBody, *retryCaptchaOut);
|
||||
}
|
||||
if (errorCode == ErrorCode::ApiSubscriptionExpiredError && !apiV2->apiConfig.isInAppPurchase) {
|
||||
ApiV2ServerConfig expiredApiV2 = *apiV2;
|
||||
expiredApiV2.apiConfig.subscriptionExpiredByServer = true;
|
||||
m_serversRepository->editServer(serverId, expiredApiV2.toJson(),
|
||||
serverConfigUtils::configTypeFromJson(expiredApiV2.toJson()));
|
||||
}
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
return applyUpdatedServiceConfig(serverId, serviceProtocol, protocolData, responseBody);
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::deactivateDevice(const QString &serverId)
|
||||
{
|
||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
@@ -748,20 +648,19 @@ ErrorCode SubscriptionController::prepareVpnKeyExport(const QString &serverId, Q
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::validateAndUpdateConfig(const QString &serverId, bool hasInstalledContainers,
|
||||
CaptchaInfo *captchaInfoOut, ProtocolData *usedProtocolDataOut)
|
||||
ErrorCode SubscriptionController::validateAndUpdateConfig(const QString &serverId, bool hasInstalledContainers)
|
||||
{
|
||||
if (!m_serversRepository->apiV2Config(serverId).has_value()) {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
if (!hasInstalledContainers) {
|
||||
return updateServiceFromGateway(serverId, "", true, captchaInfoOut, usedProtocolDataOut);
|
||||
return updateServiceFromGateway(serverId, "", true);
|
||||
}
|
||||
|
||||
if (isApiKeyExpired(serverId)) {
|
||||
qDebug() << "attempt to update api config by expires_at event";
|
||||
return updateServiceFromGateway(serverId, "", true, captchaInfoOut, usedProtocolDataOut);
|
||||
return updateServiceFromGateway(serverId, "", true);
|
||||
}
|
||||
|
||||
return ErrorCode::NoError;
|
||||
@@ -853,36 +752,6 @@ bool SubscriptionController::isVlessProtocol(const QString &serverId) const
|
||||
return apiV2.has_value() && apiV2->serviceProtocol() == "vless";
|
||||
}
|
||||
|
||||
QString SubscriptionController::currentProtocol(const QString &serverId) const
|
||||
{
|
||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
return apiV2.has_value() ? apiV2->serviceProtocol() : QString();
|
||||
}
|
||||
|
||||
QStringList SubscriptionController::availableProtocols(const QString &serverId) const
|
||||
{
|
||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
if (!apiV2.has_value()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto currentCountryCode = apiV2->apiConfig.serverCountryCode;
|
||||
const auto availableCountries = apiV2->apiConfig.availableCountries;
|
||||
|
||||
QStringList protocols;
|
||||
for (const auto &country : availableCountries) {
|
||||
const auto countryObject = country.toObject();
|
||||
if (countryObject.value(apiDefs::key::serverCountryCode).toString() != currentCountryCode) {
|
||||
continue;
|
||||
}
|
||||
for (const auto &protocol : countryObject.value(apiDefs::key::availableProtocols).toArray()) {
|
||||
protocols.push_back(protocol.toString());
|
||||
}
|
||||
break;
|
||||
}
|
||||
return protocols;
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::processAppStorePurchase(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol, const QString &productId,
|
||||
int *duplicateServerIndex)
|
||||
@@ -1080,8 +949,7 @@ QFuture<QPair<ErrorCode, QString>> SubscriptionController::getRenewalLink(const
|
||||
auto gatewayController = QSharedPointer<GatewayController>::create(m_appSettingsRepository->getGatewayEndpoint(isTestPurchase),
|
||||
m_appSettingsRepository->isDevGatewayEnv(isTestPurchase),
|
||||
apiDefs::requestTimeoutMsecs,
|
||||
m_appSettingsRepository->isStrictKillSwitchEnabled(),
|
||||
m_appSettingsRepository);
|
||||
m_appSettingsRepository->isStrictKillSwitchEnabled());
|
||||
auto postFuture = gatewayController->postAsync(QString("%1v1/renewal_link"), apiPayload);
|
||||
auto *watcher = new QFutureWatcher<QPair<ErrorCode, QByteArray>>();
|
||||
QObject::connect(watcher, &QFutureWatcher<QPair<ErrorCode, QByteArray>>::finished,
|
||||
@@ -1125,7 +993,17 @@ ErrorCode SubscriptionController::resolveImportServiceCaptcha(const QString &use
|
||||
appendProtocolDataToApiPayload(serviceProtocol, protocolData, apiPayload);
|
||||
|
||||
apiPayload["captcha_id"] = captchaId;
|
||||
apiPayload["captcha_solution"] = normalizeCaptchaSolution(captchaSolution);
|
||||
QString normalizedSolution;
|
||||
normalizedSolution.reserve(captchaSolution.size());
|
||||
for (const QChar &ch : captchaSolution) {
|
||||
const ushort u = ch.unicode();
|
||||
if (u >= '0' && u <= '9') {
|
||||
normalizedSolution += ch;
|
||||
} else if (u >= 0xFF10 && u <= 0xFF19) {
|
||||
normalizedSolution += QChar(static_cast<char16_t>(u - 0xFF10 + '0'));
|
||||
}
|
||||
}
|
||||
apiPayload["captcha_solution"] = normalizedSolution.isEmpty() ? captchaSolution.trimmed() : normalizedSolution;
|
||||
|
||||
QByteArray responseBody;
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody);
|
||||
@@ -1133,7 +1011,16 @@ ErrorCode SubscriptionController::resolveImportServiceCaptcha(const QString &use
|
||||
if (retryCaptchaOut
|
||||
&& (errorCode == ErrorCode::ApiCaptchaInvalidError || errorCode == ErrorCode::ApiCaptchaRefreshError
|
||||
|| errorCode == ErrorCode::ApiCaptchaRequiredError)) {
|
||||
fillCaptchaInfoFromResponse(responseBody, *retryCaptchaOut);
|
||||
const QJsonDocument jsonDoc = QJsonDocument::fromJson(responseBody);
|
||||
if (jsonDoc.isObject()) {
|
||||
const QJsonObject jsonObj = jsonDoc.object();
|
||||
if (jsonObj.contains(QStringLiteral("captcha_id")) && jsonObj.contains(QStringLiteral("captcha_image"))) {
|
||||
retryCaptchaOut->captchaId = jsonObj.value(QStringLiteral("captcha_id")).toString();
|
||||
retryCaptchaOut->captchaImageBase64 = jsonObj.value(QStringLiteral("captcha_image")).toString();
|
||||
retryCaptchaOut->hint = jsonObj.value(QStringLiteral("hint")).toString();
|
||||
retryCaptchaOut->isRequired = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
@@ -66,12 +66,7 @@ public:
|
||||
const QString &transactionId, bool isTestPurchase,
|
||||
int *duplicateServerIndex = nullptr);
|
||||
|
||||
ErrorCode updateServiceFromGateway(const QString &serverId, const QString &newCountryCode, bool isConnectEvent,
|
||||
CaptchaInfo *captchaInfoOut = nullptr, ProtocolData *usedProtocolDataOut = nullptr);
|
||||
|
||||
ErrorCode resolveUpdateServiceCaptcha(const QString &serverId, const QString &newCountryCode, bool isConnectEvent,
|
||||
const ProtocolData &protocolData, const QString &captchaId,
|
||||
const QString &captchaSolution, CaptchaInfo *retryCaptchaOut = nullptr);
|
||||
ErrorCode updateServiceFromGateway(const QString &serverId, const QString &newCountryCode, bool isConnectEvent);
|
||||
|
||||
ErrorCode deactivateDevice(const QString &serverId);
|
||||
|
||||
@@ -83,8 +78,7 @@ public:
|
||||
|
||||
ErrorCode prepareVpnKeyExport(const QString &serverId, QString &vpnKey);
|
||||
|
||||
ErrorCode validateAndUpdateConfig(const QString &serverId, bool hasInstalledContainers,
|
||||
CaptchaInfo *captchaInfoOut = nullptr, ProtocolData *usedProtocolDataOut = nullptr);
|
||||
ErrorCode validateAndUpdateConfig(const QString &serverId, bool hasInstalledContainers);
|
||||
|
||||
void removeApiConfig(const QString &serverId);
|
||||
|
||||
@@ -92,8 +86,6 @@ public:
|
||||
|
||||
void setCurrentProtocol(const QString &serverId, const QString &protocolName);
|
||||
bool isVlessProtocol(const QString &serverId) const;
|
||||
QString currentProtocol(const QString &serverId) const;
|
||||
QStringList availableProtocols(const QString &serverId) const;
|
||||
|
||||
ErrorCode getAccountInfo(const QString &serverId, QJsonObject &accountInfo);
|
||||
QFuture<QPair<ErrorCode, QString>> getRenewalLink(const QString &serverId);
|
||||
@@ -123,10 +115,8 @@ private:
|
||||
ErrorCode executeRequest(const QString &endpoint, const QJsonObject &apiPayload, QByteArray &responseBody, bool isTestPurchase = false);
|
||||
bool isApiKeyExpired(const QString &serverId) const;
|
||||
|
||||
ErrorCode extractServerConfigJsonFromResponse(const QByteArray &apiResponseBody, const QString &protocol,
|
||||
ErrorCode extractServerConfigJsonFromResponse(const QByteArray &apiResponseBody, const QString &protocol,
|
||||
const ProtocolData &protocolData, QJsonObject &serverConfigJson);
|
||||
ErrorCode applyUpdatedServiceConfig(const QString &serverId, const QString &serviceProtocol,
|
||||
const ProtocolData &protocolData, const QByteArray &responseBody);
|
||||
void updateApiConfigInJson(QJsonObject &serverConfigJson, const QString &serviceType,
|
||||
const QString &serviceProtocol, const QString &userCountryCode,
|
||||
const QByteArray &apiResponseBody);
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/protocols/protocolUtils.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "core/utils/payloadSender.h"
|
||||
#include "core/utils/utilities.h"
|
||||
#include "core/utils/serverConfigUtils.h"
|
||||
#include "version.h"
|
||||
@@ -31,6 +30,7 @@ ConnectionController::ConnectionController(SecureServersRepository* serversRepos
|
||||
connect(m_vpnConnection, &VpnConnection::connectionStateChanged, this, &ConnectionController::connectionStateChanged);
|
||||
connect(this, &ConnectionController::openConnectionRequested, m_vpnConnection, &VpnConnection::connectToVpn, Qt::QueuedConnection);
|
||||
connect(this, &ConnectionController::closeConnectionRequested, m_vpnConnection, &VpnConnection::disconnectFromVpn, Qt::QueuedConnection);
|
||||
connect(this, &ConnectionController::setConnectionStateRequested, m_vpnConnection, &VpnConnection::setConnectionState, Qt::QueuedConnection);
|
||||
connect(this, &ConnectionController::killSwitchModeChangedRequested, m_vpnConnection, &VpnConnection::onKillSwitchModeChanged, Qt::QueuedConnection);
|
||||
#ifdef Q_OS_ANDROID
|
||||
connect(this, &ConnectionController::restoreConnectionRequested, m_vpnConnection, &VpnConnection::restoreConnection, Qt::QueuedConnection);
|
||||
@@ -44,7 +44,9 @@ bool ConnectionController::isConnected() const
|
||||
|
||||
void ConnectionController::setConnectionState(Vpn::ConnectionState state)
|
||||
{
|
||||
emit connectionStateChanged(state);
|
||||
if (m_vpnConnection) {
|
||||
emit setConnectionStateRequested(state);
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCode ConnectionController::defaultContainerForServer(const QString &serverId, DockerContainer &container) const
|
||||
@@ -216,10 +218,12 @@ ErrorCode ConnectionController::openConnection(const QString &serverId)
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
const auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
if (apiV2.has_value() && !apiV2->sendPayload.isEmpty()) {
|
||||
PayloadSender::sendAll(apiV2->sendPayload);
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
if (m_appSettingsRepository && m_appSettingsRepository->isLocalProxyHttpEnabled()) {
|
||||
m_appSettingsRepository->setLocalProxyHttpEnabled(false);
|
||||
emit localProxyStoppedBecauseVpnTurnedOn(tr("Local proxy stopped because VPN was turned on"));
|
||||
}
|
||||
#endif
|
||||
|
||||
emit openConnectionRequested(serverId, container, vpnConfiguration);
|
||||
return ErrorCode::NoError;
|
||||
|
||||
@@ -67,7 +67,9 @@ signals:
|
||||
void connectionStateChanged(Vpn::ConnectionState state);
|
||||
void openConnectionRequested(const QString &serverId, DockerContainer container, const QJsonObject &vpnConfiguration);
|
||||
void closeConnectionRequested();
|
||||
void setConnectionStateRequested(Vpn::ConnectionState state);
|
||||
void killSwitchModeChangedRequested(bool enabled);
|
||||
void localProxyStoppedBecauseVpnTurnedOn(const QString &message);
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
void restoreConnectionRequested();
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#include "coreController.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDirIterator>
|
||||
#include <QDebug>
|
||||
#include <QTranslator>
|
||||
#include <QStandardPaths>
|
||||
#include <QTimer>
|
||||
|
||||
#include "core/utils/selfhosted/sshSession.h"
|
||||
@@ -22,8 +25,7 @@
|
||||
#endif
|
||||
|
||||
CoreController::CoreController(const QSharedPointer<VpnConnection> &vpnConnection, SecureQSettings* settings,
|
||||
QQmlApplicationEngine *engine, QObject *parent,
|
||||
bool skipPlatformControllerInit)
|
||||
QQmlApplicationEngine *engine, QObject *parent)
|
||||
: QObject(parent), m_vpnConnection(vpnConnection), m_settings(settings), m_engine(engine)
|
||||
{
|
||||
initRepositories();
|
||||
@@ -32,12 +34,14 @@ CoreController::CoreController(const QSharedPointer<VpnConnection> &vpnConnectio
|
||||
initControllers();
|
||||
initSignalHandlers();
|
||||
|
||||
if (!skipPlatformControllerInit) {
|
||||
initAndroidController();
|
||||
initAppleController();
|
||||
}
|
||||
initAndroidController();
|
||||
initAppleController();
|
||||
initLogging();
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
initLocalProxy();
|
||||
#endif
|
||||
|
||||
m_translator = new QTranslator(this);
|
||||
if (m_appSettingsRepository) {
|
||||
updateTranslator(m_appSettingsRepository->getAppLanguage());
|
||||
@@ -51,6 +55,69 @@ void CoreController::setQmlContextProperty(const QString &name, QObject *value)
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
void CoreController::initLocalProxy()
|
||||
{
|
||||
constexpr quint16 kLocalProxyApiPort = 49490;
|
||||
|
||||
m_proxyServer.reset(new ProxyServer(m_serversRepository, m_appSettingsRepository, this));
|
||||
|
||||
QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, [this]() {
|
||||
if (m_appSettingsRepository && m_appSettingsRepository->isLocalProxyHttpEnabled()) {
|
||||
m_appSettingsRepository->setLocalProxyHttpEnabled(false);
|
||||
}
|
||||
});
|
||||
|
||||
auto syncLocalProxy = [this]() {
|
||||
if (!m_proxyServer) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bool httpEnabled = m_appSettingsRepository->isLocalProxyHttpEnabled();
|
||||
|
||||
if (!httpEnabled) {
|
||||
qInfo() << "Local proxy: HTTP API disabled";
|
||||
m_proxyServer->stop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_proxyServer->start(kLocalProxyApiPort)) {
|
||||
qWarning() << "Local proxy: failed to start on port" << kLocalProxyApiPort;
|
||||
m_appSettingsRepository->setLocalProxyHttpEnabled(false);
|
||||
emit m_appSettingsRepository->localProxyStartFailed(tr("Local proxy failed to start. Check if the port is available."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_proxyServer->syncSettings()) {
|
||||
qWarning() << "Local proxy: failed to start proxy core (Xray)";
|
||||
m_appSettingsRepository->setLocalProxyHttpEnabled(false);
|
||||
emit m_appSettingsRepository->localProxyStartFailed(tr("Couldn’t start the proxy due to an internal error. Try restarting the app."));
|
||||
return;
|
||||
}
|
||||
|
||||
qInfo() << "Local proxy: running on 127.0.0.1:" << kLocalProxyApiPort;
|
||||
};
|
||||
|
||||
syncLocalProxy();
|
||||
|
||||
connect(m_appSettingsRepository, &SecureAppSettingsRepository::localProxySettingsChanged, this, syncLocalProxy);
|
||||
|
||||
connect(m_serversRepository, &SecureServersRepository::serverEdited, this, [this](const QString &serverId) {
|
||||
if (m_appSettingsRepository && m_appSettingsRepository->isLocalProxyHttpEnabled()
|
||||
&& m_appSettingsRepository->localProxyOwnerId() == serverId) {
|
||||
m_appSettingsRepository->bumpLocalProxyRestartToken();
|
||||
}
|
||||
});
|
||||
|
||||
connect(m_serversRepository, &SecureServersRepository::serverRemoved, this, [this](const QString &serverId, int) {
|
||||
if (m_appSettingsRepository && m_appSettingsRepository->localProxyOwnerId() == serverId) {
|
||||
m_appSettingsRepository->setLocalProxyOwnerId(QString());
|
||||
m_appSettingsRepository->setLocalProxyHttpEnabled(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
void CoreController::initModels()
|
||||
{
|
||||
m_containersModel = new ContainersModel(this);
|
||||
@@ -200,6 +267,11 @@ void CoreController::initControllers()
|
||||
m_pageController = new PageController(m_serversController, m_settingsController, this);
|
||||
setQmlContextProperty("PageController", m_pageController);
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
connect(m_connectionController, &ConnectionController::localProxyStoppedBecauseVpnTurnedOn, m_pageController,
|
||||
&PageController::showNotificationMessage);
|
||||
#endif
|
||||
|
||||
m_serversUiController = new ServersUiController(m_serversController, m_settingsController, m_serversModel, m_containersModel, m_defaultServerContainersModel, this);
|
||||
setQmlContextProperty("ServersUiController", m_serversUiController);
|
||||
|
||||
@@ -285,10 +357,6 @@ void CoreController::initSignalHandlers()
|
||||
if (m_serversUiController->hasServersFromGatewayApi()) {
|
||||
m_apiNewsUiController->fetchNews(false);
|
||||
}
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
m_updateController->checkForUpdates();
|
||||
#endif
|
||||
}
|
||||
|
||||
void CoreController::updateTranslator(const QLocale &locale)
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
#include "ui/models/newsModel.h"
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
#include "core/local-proxy/proxyserver.h"
|
||||
#include "ui/utils/notificationHandler.h"
|
||||
#endif
|
||||
|
||||
@@ -90,8 +91,7 @@ class CoreController : public QObject
|
||||
|
||||
public:
|
||||
explicit CoreController(const QSharedPointer<VpnConnection> &vpnConnection, SecureQSettings* settings,
|
||||
QQmlApplicationEngine *engine, QObject *parent = nullptr,
|
||||
bool skipPlatformControllerInit = false);
|
||||
QQmlApplicationEngine *engine, QObject *parent = nullptr);
|
||||
|
||||
PageController* pageController() const;
|
||||
void setQmlRoot();
|
||||
@@ -115,7 +115,7 @@ protected:
|
||||
AppSplitTunnelingModel* appSplitTunnelingModelProtected() const { return m_appSplitTunnelingModel; }
|
||||
IpSplitTunnelingModel* ipSplitTunnelingModelProtected() const { return m_ipSplitTunnelingModel; }
|
||||
LanguageModel* languageModelProtected() const { return m_languageModel; }
|
||||
ConnectionUiController* connectionUiControllerProtected() const { return m_connectionUiController; }
|
||||
|
||||
InstallUiController* installUiControllerProtected() const { return m_installUiController; }
|
||||
ImportController* importCoreControllerProtected() const { return m_importCoreController; }
|
||||
ExportController* exportControllerProtected() const { return m_exportController; }
|
||||
@@ -143,6 +143,9 @@ private:
|
||||
void initAppleController();
|
||||
void initLogging();
|
||||
void initSignalHandlers();
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
void initLocalProxy();
|
||||
#endif
|
||||
void setQmlContextProperty(const QString &name, QObject *value);
|
||||
|
||||
QQmlApplicationEngine *m_engine {}; // TODO use parent child system here?
|
||||
@@ -228,6 +231,10 @@ private:
|
||||
TelemtConfigModel* m_telemtConfigModel;
|
||||
|
||||
CoreSignalHandlers* m_signalHandlers;
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
QScopedPointer<ProxyServer> m_proxyServer;
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // CORECONTROLLER_H
|
||||
|
||||
@@ -205,8 +205,10 @@ void CoreSignalHandlers::initAdminConfigRevokedHandler()
|
||||
{
|
||||
connect(m_coreController->m_installController, &InstallController::clientRevocationRequested, this,
|
||||
[this](const QString &serverId, const ContainerConfig &containerConfig, DockerContainer container) {
|
||||
m_coreController->m_usersController->revokeClient(serverId, containerConfig, container);
|
||||
}, Qt::DirectConnection);
|
||||
QtConcurrent::run([this, serverId, containerConfig, container]() {
|
||||
m_coreController->m_usersController->revokeClient(serverId, containerConfig, container);
|
||||
});
|
||||
});
|
||||
|
||||
connect(m_coreController->m_installController, &InstallController::clientAppendRequested, this,
|
||||
[this](const QString &serverId, const QString &clientId, const QString &clientName, DockerContainer container) {
|
||||
@@ -440,6 +442,9 @@ void CoreSignalHandlers::initNotificationHandler()
|
||||
void CoreSignalHandlers::initUpdateFoundHandler()
|
||||
{
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
connect(m_coreController->m_apiNewsUiController, &ApiNewsUiController::fetchNewsFinished, m_coreController->m_updateUiController,
|
||||
&UpdateUiController::checkForUpdates);
|
||||
|
||||
connect(m_coreController->m_updateUiController, &UpdateUiController::updateFound, this, [this]() {
|
||||
const QString version = m_coreController->m_updateUiController->getVersion();
|
||||
const QString updateId = version.isEmpty() ? QStringLiteral("update") : QStringLiteral("update-%1").arg(version);
|
||||
|
||||
@@ -12,14 +12,14 @@
|
||||
#include <QPromise>
|
||||
#include <QUrl>
|
||||
|
||||
#include <openssl/rsa.h>
|
||||
#include "QBlockCipher.h"
|
||||
#include "QRsa.h"
|
||||
|
||||
#include "amneziaApplication.h"
|
||||
#include "core/repositories/secureAppSettingsRepository.h"
|
||||
#include "core/utils/api/apiUtils.h"
|
||||
#include "core/utils/constants/apiKeys.h"
|
||||
#include "core/utils/networkUtilities.h"
|
||||
#include "cryptoUtils.h"
|
||||
#include "core/utils/utilities.h"
|
||||
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
#include "core/utils/ipcClient.h"
|
||||
@@ -45,75 +45,15 @@ namespace
|
||||
constexpr QLatin1String unprocessableSubscriptionMessage("Failed to retrieve subscription information. Is it activated?");
|
||||
|
||||
constexpr int proxyStorageRequestTimeoutMsecs = 3000;
|
||||
|
||||
QStringList shuffledProxyUrls(const QStringList &proxyUrls)
|
||||
{
|
||||
QStringList shuffled = proxyUrls;
|
||||
std::random_device randomDevice;
|
||||
std::mt19937 generator(randomDevice());
|
||||
std::shuffle(shuffled.begin(), shuffled.end(), generator);
|
||||
return shuffled;
|
||||
}
|
||||
|
||||
QString getProxyUrlsCacheKey(const QString &serviceType, const QString &userCountryCode)
|
||||
{
|
||||
return QStringLiteral("service_%1_country_%2").arg(serviceType, userCountryCode);
|
||||
}
|
||||
|
||||
bool decryptProxyUrlsPayload(const QByteArray &encryptedPayload, bool isDevEnvironment, QByteArray &decryptedPayload)
|
||||
{
|
||||
QByteArray key = isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
||||
if (!isDevEnvironment) {
|
||||
QCryptographicHash hash(QCryptographicHash::Sha512);
|
||||
hash.addData(key);
|
||||
QByteArray h = hash.result().toHex();
|
||||
|
||||
QByteArray decKey = QByteArray::fromHex(h.left(64));
|
||||
QByteArray iv = QByteArray::fromHex(h.mid(64, 32));
|
||||
QByteArray ba = QByteArray::fromBase64(encryptedPayload);
|
||||
|
||||
decryptedPayload = CryptoUtils::decryptAes256Cbc(ba, decKey, iv);
|
||||
if (decryptedPayload.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
decryptedPayload = encryptedPayload;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QStringList readCachedProxyUrls(const QByteArray &cachedProxyUrlsEncrypted, bool isDevEnvironment)
|
||||
{
|
||||
if (cachedProxyUrlsEncrypted.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
QByteArray cachedProxyUrlsDecrypted;
|
||||
if (!decryptProxyUrlsPayload(cachedProxyUrlsEncrypted, isDevEnvironment, cachedProxyUrlsDecrypted)) {
|
||||
qCritical() << "error decrypting cached proxy urls payload";
|
||||
return {};
|
||||
}
|
||||
|
||||
QJsonArray endpointsArray = QJsonDocument::fromJson(cachedProxyUrlsDecrypted).array();
|
||||
QStringList endpoints;
|
||||
endpoints.reserve(endpointsArray.size());
|
||||
for (const QJsonValue &endpoint : endpointsArray) {
|
||||
endpoints.push_back(endpoint.toString());
|
||||
}
|
||||
|
||||
return endpoints;
|
||||
}
|
||||
}
|
||||
|
||||
GatewayController::GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
|
||||
const bool isStrictKillSwitchEnabled, SecureAppSettingsRepository *appSettingsRepository,
|
||||
QObject *parent)
|
||||
const bool isStrictKillSwitchEnabled, QObject *parent)
|
||||
: QObject(parent),
|
||||
m_gatewayEndpoint(gatewayEndpoint),
|
||||
m_isDevEnvironment(isDevEnvironment),
|
||||
m_requestTimeoutMsecs(requestTimeoutMsecs),
|
||||
m_isStrictKillSwitchEnabled(isStrictKillSwitchEnabled),
|
||||
m_appSettingsRepository(appSettingsRepository)
|
||||
m_isStrictKillSwitchEnabled(isStrictKillSwitchEnabled)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -147,29 +87,40 @@ GatewayController::EncryptedRequestData GatewayController::prepareRequest(const
|
||||
}
|
||||
#endif
|
||||
|
||||
encRequestData.key = CryptoUtils::generateRandomBytes(32);
|
||||
encRequestData.iv = CryptoUtils::generateRandomBytes(32);
|
||||
encRequestData.salt = CryptoUtils::generateRandomBytes(8);
|
||||
QSimpleCrypto::QBlockCipher blockCipher;
|
||||
encRequestData.key = blockCipher.generatePrivateSalt(32);
|
||||
encRequestData.iv = blockCipher.generatePrivateSalt(32);
|
||||
encRequestData.salt = blockCipher.generatePrivateSalt(8);
|
||||
|
||||
QJsonObject keyPayload;
|
||||
keyPayload[apiDefs::key::aesKey] = QString(encRequestData.key.toBase64());
|
||||
keyPayload[apiDefs::key::aesIv] = QString(encRequestData.iv.toBase64());
|
||||
keyPayload[apiDefs::key::aesSalt] = QString(encRequestData.salt.toBase64());
|
||||
|
||||
QByteArray rsaKey = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
||||
EVP_PKEY *publicKey = CryptoUtils::loadPublicKeyFromPem(rsaKey);
|
||||
if (publicKey == nullptr) {
|
||||
qCritical() << "error loading public key from environment variables";
|
||||
encRequestData.errorCode = ErrorCode::ApiMissingAgwPublicKey;
|
||||
return encRequestData;
|
||||
}
|
||||
QByteArray encryptedKeyPayload;
|
||||
QByteArray encryptedApiPayload;
|
||||
try {
|
||||
QSimpleCrypto::QRsa rsa;
|
||||
|
||||
QByteArray encryptedKeyPayload = CryptoUtils::rsaEncrypt(QJsonDocument(keyPayload).toJson(), publicKey, RSA_PKCS1_PADDING);
|
||||
EVP_PKEY_free(publicKey);
|
||||
EVP_PKEY *publicKey = nullptr;
|
||||
try {
|
||||
QByteArray rsaKey = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
||||
QSimpleCrypto::QRsa rsa;
|
||||
publicKey = rsa.getPublicKeyFromByteArray(rsaKey);
|
||||
} catch (...) {
|
||||
Utils::logException();
|
||||
qCritical() << "error loading public key from environment variables";
|
||||
encRequestData.errorCode = ErrorCode::ApiMissingAgwPublicKey;
|
||||
return encRequestData;
|
||||
}
|
||||
|
||||
QByteArray encryptedApiPayload = CryptoUtils::encryptAes256Cbc(QJsonDocument(apiPayload).toJson(), encRequestData.key, encRequestData.iv);
|
||||
encryptedKeyPayload = rsa.encrypt(QJsonDocument(keyPayload).toJson(), publicKey, RSA_PKCS1_PADDING);
|
||||
EVP_PKEY_free(publicKey);
|
||||
|
||||
if (encryptedKeyPayload.isEmpty() || encryptedApiPayload.isEmpty()) {
|
||||
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), encRequestData.key, encRequestData.iv,
|
||||
"", encRequestData.salt);
|
||||
} catch (...) {
|
||||
Utils::logException();
|
||||
qCritical() << "error when encrypting the request body";
|
||||
encRequestData.errorCode = ErrorCode::ApiConfigDecryptionError;
|
||||
return encRequestData;
|
||||
@@ -191,11 +142,11 @@ GatewayController::DecryptionResult GatewayController::tryDecryptResponseBody(co
|
||||
result.decryptedBody = encryptedResponseBody;
|
||||
result.isDecryptionSuccessful = false;
|
||||
|
||||
QByteArray decrypted = CryptoUtils::decryptAes256Cbc(encryptedResponseBody, key, iv);
|
||||
if (!decrypted.isEmpty()) {
|
||||
result.decryptedBody = decrypted;
|
||||
try {
|
||||
QSimpleCrypto::QBlockCipher blockCipher;
|
||||
result.decryptedBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt);
|
||||
result.isDecryptionSuccessful = true;
|
||||
} else {
|
||||
} catch (...) {
|
||||
result.decryptedBody = encryptedResponseBody;
|
||||
result.isDecryptionSuccessful = false;
|
||||
}
|
||||
@@ -314,6 +265,7 @@ QFuture<QPair<ErrorCode, QByteArray>> GatewayController::postAsync(const QString
|
||||
}
|
||||
|
||||
if (!decryptionResult.isDecryptionSuccessful) {
|
||||
Utils::logException();
|
||||
qCritical() << "error when decrypting the request body";
|
||||
promise->addResult(qMakePair(ErrorCode::ApiConfigDecryptionError, QByteArray()));
|
||||
promise->finish();
|
||||
@@ -356,9 +308,8 @@ QFuture<QPair<ErrorCode, QByteArray>> GatewayController::postAsync(const QString
|
||||
QStringList proxyStorageUrls;
|
||||
appendStorageUrls(primaryBaseUrls, proxyStorageUrls);
|
||||
appendStorageUrls(fallbackBaseUrls, proxyStorageUrls);
|
||||
const QString proxyUrlsCacheKey = getProxyUrlsCacheKey(serviceType, userCountryCode);
|
||||
|
||||
getProxyUrlsAsync(proxyStorageUrls, 0, proxyUrlsCacheKey, [this, encRequestData, endpoint, processResponse](const QStringList &proxyUrls) {
|
||||
getProxyUrlsAsync(proxyStorageUrls, 0, [this, encRequestData, endpoint, processResponse](const QStringList &proxyUrls) {
|
||||
getProxyUrlAsync(proxyUrls, 0, [this, encRequestData, endpoint, processResponse](const QString &proxyUrl) {
|
||||
bypassProxyAsync(endpoint, proxyUrl, encRequestData,
|
||||
[processResponse, this](const QByteArray &decryptedBody, bool isDecryptionSuccessful,
|
||||
@@ -404,6 +355,8 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
|
||||
std::shuffle(primaryBaseUrls.begin(), primaryBaseUrls.end(), generator);
|
||||
std::shuffle(fallbackBaseUrls.begin(), fallbackBaseUrls.end(), generator);
|
||||
|
||||
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
||||
|
||||
auto appendStorageUrls = [&serviceType, &userCountryCode](const QStringList &baseUrls, QStringList &target) {
|
||||
if (!serviceType.isEmpty()) {
|
||||
for (const auto &baseUrl : baseUrls) {
|
||||
@@ -419,12 +372,10 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
|
||||
QStringList proxyStorageUrls;
|
||||
appendStorageUrls(primaryBaseUrls, proxyStorageUrls);
|
||||
appendStorageUrls(fallbackBaseUrls, proxyStorageUrls);
|
||||
const QString proxyUrlsCacheKey = getProxyUrlsCacheKey(serviceType, userCountryCode);
|
||||
const QByteArray cachedProxyUrlsEncrypted = m_appSettingsRepository->readGatewayProxyUrls(proxyUrlsCacheKey);
|
||||
|
||||
if (proxyStorageUrls.empty()) {
|
||||
qDebug() << "empty storage endpoint list";
|
||||
return readCachedProxyUrls(cachedProxyUrlsEncrypted, m_isDevEnvironment);
|
||||
return {};
|
||||
}
|
||||
|
||||
for (const auto &proxyStorageUrl : proxyStorageUrls) {
|
||||
@@ -439,8 +390,26 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
|
||||
auto encryptedResponseBody = reply->readAll();
|
||||
reply->deleteLater();
|
||||
|
||||
EVP_PKEY *privateKey = nullptr;
|
||||
QByteArray responseBody;
|
||||
if (!decryptProxyUrlsPayload(encryptedResponseBody, m_isDevEnvironment, responseBody)) {
|
||||
try {
|
||||
if (!m_isDevEnvironment) {
|
||||
QCryptographicHash hash(QCryptographicHash::Sha512);
|
||||
hash.addData(key);
|
||||
QByteArray hashResult = hash.result().toHex();
|
||||
|
||||
QByteArray key = QByteArray::fromHex(hashResult.left(64));
|
||||
QByteArray iv = QByteArray::fromHex(hashResult.mid(64, 32));
|
||||
|
||||
QByteArray ba = QByteArray::fromBase64(encryptedResponseBody);
|
||||
|
||||
QSimpleCrypto::QBlockCipher blockCipher;
|
||||
responseBody = blockCipher.decryptAesBlockCipher(ba, key, iv);
|
||||
} else {
|
||||
responseBody = encryptedResponseBody;
|
||||
}
|
||||
} catch (...) {
|
||||
Utils::logException();
|
||||
qCritical() << "error loading private key from environment variables or decrypting payload" << encryptedResponseBody;
|
||||
continue;
|
||||
}
|
||||
@@ -451,8 +420,6 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
|
||||
for (const auto &endpoint : endpointsArray) {
|
||||
endpoints.push_back(endpoint.toString());
|
||||
}
|
||||
m_appSettingsRepository->writeGatewayProxyUrls(proxyUrlsCacheKey, encryptedResponseBody);
|
||||
|
||||
return endpoints;
|
||||
} else {
|
||||
auto replyError = reply->error();
|
||||
@@ -464,7 +431,7 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
|
||||
reply->deleteLater();
|
||||
}
|
||||
}
|
||||
return readCachedProxyUrls(cachedProxyUrlsEncrypted, m_isDevEnvironment);
|
||||
return {};
|
||||
}
|
||||
|
||||
bool GatewayController::shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &decryptedResponseBody,
|
||||
@@ -606,12 +573,10 @@ void GatewayController::bypassProxy(const QString &endpoint, const QString &serv
|
||||
}
|
||||
|
||||
void GatewayController::getProxyUrlsAsync(const QStringList proxyStorageUrls, const int currentProxyStorageIndex,
|
||||
const QString &proxyUrlsCacheKey, std::function<void(const QStringList &)> onComplete)
|
||||
std::function<void(const QStringList &)> onComplete)
|
||||
{
|
||||
const QByteArray cachedProxyUrlsEncrypted = m_appSettingsRepository->readGatewayProxyUrls(proxyUrlsCacheKey);
|
||||
|
||||
if (currentProxyStorageIndex >= proxyStorageUrls.size()) {
|
||||
onComplete(shuffledProxyUrls(readCachedProxyUrls(cachedProxyUrlsEncrypted, m_isDevEnvironment)));
|
||||
onComplete({});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -624,17 +589,33 @@ void GatewayController::getProxyUrlsAsync(const QStringList proxyStorageUrls, co
|
||||
|
||||
// connect(reply, &QNetworkReply::sslErrors, this, [state](const QList<QSslError> &e) { *(state->sslErrors) = e; });
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this,
|
||||
[this, proxyStorageUrls, currentProxyStorageIndex, proxyUrlsCacheKey, onComplete, reply]() {
|
||||
connect(reply, &QNetworkReply::finished, this, [this, proxyStorageUrls, currentProxyStorageIndex, onComplete, reply]() {
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
QByteArray encrypted = reply->readAll();
|
||||
reply->deleteLater();
|
||||
|
||||
QByteArray responseBody;
|
||||
if (!decryptProxyUrlsPayload(encrypted, m_isDevEnvironment, responseBody)) {
|
||||
try {
|
||||
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
||||
if (!m_isDevEnvironment) {
|
||||
QCryptographicHash hash(QCryptographicHash::Sha512);
|
||||
hash.addData(key);
|
||||
QByteArray h = hash.result().toHex();
|
||||
|
||||
QByteArray decKey = QByteArray::fromHex(h.left(64));
|
||||
QByteArray iv = QByteArray::fromHex(h.mid(64, 32));
|
||||
QByteArray ba = QByteArray::fromBase64(encrypted);
|
||||
|
||||
QSimpleCrypto::QBlockCipher cipher;
|
||||
responseBody = cipher.decryptAesBlockCipher(ba, decKey, iv);
|
||||
} else {
|
||||
responseBody = encrypted;
|
||||
}
|
||||
} catch (...) {
|
||||
Utils::logException();
|
||||
qCritical() << "error decrypting payload";
|
||||
QMetaObject::invokeMethod(
|
||||
this, [=]() { getProxyUrlsAsync(proxyStorageUrls, currentProxyStorageIndex + 1, proxyUrlsCacheKey, onComplete); }, Qt::QueuedConnection);
|
||||
this, [=]() { getProxyUrlsAsync(proxyStorageUrls, currentProxyStorageIndex + 1, onComplete); }, Qt::QueuedConnection);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -642,9 +623,13 @@ void GatewayController::getProxyUrlsAsync(const QStringList proxyStorageUrls, co
|
||||
QStringList endpoints;
|
||||
for (const QJsonValue &endpoint : endpointsArray)
|
||||
endpoints.push_back(endpoint.toString());
|
||||
m_appSettingsRepository->writeGatewayProxyUrls(proxyUrlsCacheKey, encrypted);
|
||||
|
||||
onComplete(shuffledProxyUrls(endpoints));
|
||||
QStringList shuffled = endpoints;
|
||||
std::random_device randomDevice;
|
||||
std::mt19937 generator(randomDevice());
|
||||
std::shuffle(shuffled.begin(), shuffled.end(), generator);
|
||||
|
||||
onComplete(shuffled);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -653,7 +638,7 @@ void GatewayController::getProxyUrlsAsync(const QStringList proxyStorageUrls, co
|
||||
qDebug() << "go to the next storage endpoint";
|
||||
reply->deleteLater();
|
||||
QMetaObject::invokeMethod(
|
||||
this, [=]() { getProxyUrlsAsync(proxyStorageUrls, currentProxyStorageIndex + 1, proxyUrlsCacheKey, onComplete); }, Qt::QueuedConnection);
|
||||
this, [=]() { getProxyUrlsAsync(proxyStorageUrls, currentProxyStorageIndex + 1, onComplete); }, Qt::QueuedConnection);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -16,16 +16,13 @@
|
||||
#include "platforms/ios/ios_controller.h"
|
||||
#endif
|
||||
|
||||
class SecureAppSettingsRepository;
|
||||
|
||||
class GatewayController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
|
||||
const bool isStrictKillSwitchEnabled, SecureAppSettingsRepository *appSettingsRepository,
|
||||
QObject *parent = nullptr);
|
||||
const bool isStrictKillSwitchEnabled, QObject *parent = nullptr);
|
||||
|
||||
amnezia::ErrorCode post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody);
|
||||
QFuture<QPair<amnezia::ErrorCode, QByteArray>> postAsync(const QString &endpoint, const QJsonObject apiPayload);
|
||||
@@ -58,7 +55,7 @@ private:
|
||||
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction);
|
||||
|
||||
void getProxyUrlsAsync(const QStringList proxyStorageUrls, const int currentProxyStorageIndex,
|
||||
const QString &proxyUrlsCacheKey, std::function<void(const QStringList &)> onComplete);
|
||||
std::function<void(const QStringList &)> onComplete);
|
||||
void getProxyUrlAsync(const QStringList proxyUrls, const int currentProxyIndex, std::function<void(const QString &)> onComplete);
|
||||
void bypassProxyAsync(
|
||||
const QString &endpoint, const QString &proxyUrl, EncryptedRequestData encRequestData,
|
||||
@@ -68,7 +65,6 @@ private:
|
||||
QString m_gatewayEndpoint;
|
||||
bool m_isDevEnvironment = false;
|
||||
bool m_isStrictKillSwitchEnabled = false;
|
||||
SecureAppSettingsRepository *m_appSettingsRepository = nullptr;
|
||||
|
||||
inline static QString m_proxyUrl;
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "ipSplitTunnelingController.h"
|
||||
#include "core/utils/networkUtilities.h"
|
||||
#include <QJsonObject>
|
||||
#include <QDebug>
|
||||
|
||||
IpSplitTunnelingController::IpSplitTunnelingController(SecureAppSettingsRepository* appSettingsRepository, QObject* parent)
|
||||
: QObject(parent),
|
||||
@@ -15,56 +14,47 @@ IpSplitTunnelingController::IpSplitTunnelingController(SecureAppSettingsReposito
|
||||
fillSites();
|
||||
}
|
||||
|
||||
bool IpSplitTunnelingController::addSiteInternal(const QString &hostname, const QStringList &ips)
|
||||
bool IpSplitTunnelingController::addSiteInternal(const QString &hostname, const QString &ip)
|
||||
{
|
||||
QVariantMap existing = m_appSettingsRepository->vpnSites(m_currentRouteMode);
|
||||
if (existing.contains(hostname) && ips.isEmpty()) {
|
||||
if (existing.contains(hostname) && ip.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < m_sites.size(); i++) {
|
||||
if (m_sites[i].first == hostname) {
|
||||
bool changed = false;
|
||||
for (const QString &ip : ips) {
|
||||
if (!ip.isEmpty() && !m_sites[i].second.contains(ip)) {
|
||||
m_sites[i].second.append(ip);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (!changed) {
|
||||
return false;
|
||||
}
|
||||
m_appSettingsRepository->addVpnSite(m_currentRouteMode, hostname, ips);
|
||||
if (m_sites[i].first == hostname && (m_sites[i].second.isEmpty() && !ip.isEmpty())) {
|
||||
m_sites[i].second = ip;
|
||||
m_appSettingsRepository->addVpnSite(m_currentRouteMode, hostname, ip);
|
||||
return true;
|
||||
} else if (m_sites[i].first == hostname && (m_sites[i].second == ip)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
m_sites.append(qMakePair(hostname, ips));
|
||||
m_appSettingsRepository->addVpnSite(m_currentRouteMode, hostname, ips);
|
||||
m_sites.append(qMakePair(hostname, ip));
|
||||
m_appSettingsRepository->addVpnSite(m_currentRouteMode, hostname, ip);
|
||||
return true;
|
||||
}
|
||||
|
||||
void IpSplitTunnelingController::addSites(const QMap<QString, QStringList> &sites, bool replaceExisting)
|
||||
void IpSplitTunnelingController::addSites(const QMap<QString, QString> &sites, bool replaceExisting)
|
||||
{
|
||||
if (replaceExisting) {
|
||||
m_sites.clear();
|
||||
}
|
||||
for (auto it = sites.constBegin(); it != sites.constEnd(); ++it) {
|
||||
const QString &hostname = it.key();
|
||||
const QStringList &ips = it.value();
|
||||
const QString &ip = it.value();
|
||||
bool found = false;
|
||||
for (int i = 0; i < m_sites.size(); i++) {
|
||||
if (m_sites[i].first == hostname) {
|
||||
for (const QString &ip : ips) {
|
||||
if (!ip.isEmpty() && !m_sites[i].second.contains(ip)) {
|
||||
m_sites[i].second.append(ip);
|
||||
}
|
||||
if (!ip.isEmpty()) {
|
||||
m_sites[i].second = ip;
|
||||
}
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
m_sites.append(qMakePair(hostname, ips));
|
||||
m_sites.append(qMakePair(hostname, ip));
|
||||
}
|
||||
}
|
||||
if (replaceExisting) {
|
||||
@@ -82,11 +72,11 @@ bool IpSplitTunnelingController::addSite(const QString &hostname)
|
||||
}
|
||||
|
||||
if (NetworkUtilities::ipAddressWithSubnetRegExp().exactMatch(normalizedHostname)) {
|
||||
processSite(normalizedHostname, {});
|
||||
processSite(normalizedHostname, "");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (addSiteInternal(normalizedHostname, {})) {
|
||||
if (addSiteInternal(normalizedHostname, "")) {
|
||||
QHostInfo::lookupHost(normalizedHostname, this, SLOT(onHostResolved(QHostInfo)));
|
||||
return true;
|
||||
}
|
||||
@@ -134,7 +124,7 @@ bool IpSplitTunnelingController::isSplitTunnelingEnabled() const
|
||||
return m_appSettingsRepository->isSitesSplitTunnelingEnabled();
|
||||
}
|
||||
|
||||
QVector<QPair<QString, QStringList>> IpSplitTunnelingController::getCurrentSites() const
|
||||
QVector<QPair<QString, QString>> IpSplitTunnelingController::getCurrentSites() const
|
||||
{
|
||||
return m_sites;
|
||||
}
|
||||
@@ -144,7 +134,7 @@ void IpSplitTunnelingController::fillSites()
|
||||
QVariantMap sitesMap = m_appSettingsRepository->vpnSites(m_currentRouteMode);
|
||||
m_sites.clear();
|
||||
for (auto it = sitesMap.begin(); it != sitesMap.end(); ++it) {
|
||||
m_sites.append(qMakePair(it.key(), SecureAppSettingsRepository::siteIpList(it.value())));
|
||||
m_sites.append(qMakePair(it.key(), it.value().toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,40 +164,29 @@ void IpSplitTunnelingController::onHostResolved(const QHostInfo &hostInfo)
|
||||
{
|
||||
const QList<QHostAddress> &addresses = hostInfo.addresses();
|
||||
QString hostname = hostInfo.hostName();
|
||||
|
||||
QStringList allIpv4;
|
||||
|
||||
for (const QHostAddress &addr : addresses) {
|
||||
if (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol) {
|
||||
allIpv4.append(addr.toString());
|
||||
}
|
||||
}
|
||||
allIpv4.removeDuplicates();
|
||||
qDebug() << "[SplitTunneling] Host resolved:" << hostname
|
||||
<< "-> adding all IPv4 addresses to list:" << allIpv4;
|
||||
|
||||
if (!allIpv4.isEmpty()) {
|
||||
processSiteAfterResolve(hostname, allIpv4);
|
||||
}
|
||||
}
|
||||
|
||||
void IpSplitTunnelingController::processSiteAfterResolve(const QString &hostname, const QStringList &ips)
|
||||
{
|
||||
for (int i = 0; i < m_sites.size(); i++) {
|
||||
if (m_sites[i].first == hostname) {
|
||||
for (const QString &ip : ips) {
|
||||
if (!ip.isEmpty() && !m_sites[i].second.contains(ip)) {
|
||||
m_sites[i].second.append(ip);
|
||||
}
|
||||
}
|
||||
processSiteAfterResolve(hostname, addr.toString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
m_appSettingsRepository->addVpnSite(m_currentRouteMode, hostname, ips);
|
||||
}
|
||||
|
||||
void IpSplitTunnelingController::processSite(const QString &hostname, const QStringList &ips)
|
||||
void IpSplitTunnelingController::processSiteAfterResolve(const QString &hostname, const QString &ip)
|
||||
{
|
||||
addSiteInternal(hostname, ips);
|
||||
for (int i = 0; i < m_sites.size(); i++) {
|
||||
if (m_sites[i].first == hostname && m_sites[i].second.isEmpty()) {
|
||||
m_sites[i].second = ip;
|
||||
m_appSettingsRepository->addVpnSite(m_currentRouteMode, hostname, ip);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IpSplitTunnelingController::processSite(const QString &hostname, const QString &ip)
|
||||
{
|
||||
addSiteInternal(hostname, ip);
|
||||
}
|
||||
|
||||
bool IpSplitTunnelingController::importSitesFromJson(const QByteArray& jsonData, bool replaceExisting, QString &errorMessage)
|
||||
@@ -226,25 +205,12 @@ bool IpSplitTunnelingController::importSitesFromJson(const QByteArray& jsonData,
|
||||
}
|
||||
|
||||
QJsonArray jsonArray = jsonDocument.array();
|
||||
QMap<QString, QStringList> sites;
|
||||
QMap<QString, QString> sites;
|
||||
|
||||
for (auto jsonValue : jsonArray) {
|
||||
QJsonObject jsonObject = jsonValue.toObject();
|
||||
QString hostname = jsonObject.value("hostname").toString("");
|
||||
|
||||
QStringList ips;
|
||||
if (jsonObject.value("ips").isArray()) {
|
||||
const QJsonArray ipsArray = jsonObject.value("ips").toArray();
|
||||
for (const auto &ipValue : ipsArray) {
|
||||
ips.append(ipValue.toString());
|
||||
}
|
||||
}
|
||||
const QString singleIp = jsonObject.value("ip").toString("");
|
||||
if (!singleIp.isEmpty()) {
|
||||
ips.append(singleIp);
|
||||
}
|
||||
ips.removeAll(QString());
|
||||
ips.removeDuplicates();
|
||||
QString ip = jsonObject.value("ip").toString("");
|
||||
|
||||
QString normalizedHostname = normalizeHostname(hostname);
|
||||
|
||||
@@ -253,7 +219,7 @@ bool IpSplitTunnelingController::importSitesFromJson(const QByteArray& jsonData,
|
||||
continue;
|
||||
}
|
||||
|
||||
sites.insert(normalizedHostname, ips);
|
||||
sites.insert(normalizedHostname, ip);
|
||||
}
|
||||
|
||||
addSites(sites, replaceExisting);
|
||||
@@ -263,21 +229,13 @@ bool IpSplitTunnelingController::importSitesFromJson(const QByteArray& jsonData,
|
||||
|
||||
QByteArray IpSplitTunnelingController::exportSitesToJson() const
|
||||
{
|
||||
QVector<QPair<QString, QStringList>> sites = getCurrentSites();
|
||||
QVector<QPair<QString, QString>> sites = getCurrentSites();
|
||||
QJsonArray jsonArray;
|
||||
|
||||
for (const auto &site : sites) {
|
||||
QJsonObject jsonObject;
|
||||
jsonObject["hostname"] = site.first;
|
||||
|
||||
QJsonArray ipsArray;
|
||||
for (const QString &ip : site.second) {
|
||||
ipsArray.append(ip);
|
||||
}
|
||||
jsonObject["ips"] = ipsArray;
|
||||
// Keep the legacy "ip" field (first address) for backward compatibility.
|
||||
jsonObject["ip"] = site.second.isEmpty() ? QString() : site.second.first();
|
||||
|
||||
jsonObject["ip"] = site.second;
|
||||
jsonArray.append(jsonObject);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ public:
|
||||
explicit IpSplitTunnelingController(SecureAppSettingsRepository* appSettingsRepository, QObject* parent = nullptr);
|
||||
|
||||
bool addSite(const QString &hostname);
|
||||
void addSites(const QMap<QString, QStringList> &sites, bool replaceExisting);
|
||||
void addSites(const QMap<QString, QString> &sites, bool replaceExisting);
|
||||
bool removeSite(const QString &hostname);
|
||||
void removeSites();
|
||||
void setRouteMode(RouteMode routeMode);
|
||||
@@ -33,7 +33,7 @@ public:
|
||||
|
||||
RouteMode getRouteMode() const;
|
||||
bool isSplitTunnelingEnabled() const;
|
||||
QVector<QPair<QString, QStringList>> getCurrentSites() const;
|
||||
QVector<QPair<QString, QString>> getCurrentSites() const;
|
||||
|
||||
bool importSitesFromJson(const QByteArray& jsonData, bool replaceExisting, QString &errorMessage);
|
||||
QByteArray exportSitesToJson() const;
|
||||
@@ -43,15 +43,15 @@ private slots:
|
||||
|
||||
private:
|
||||
void fillSites();
|
||||
bool addSiteInternal(const QString &hostname, const QStringList &ips);
|
||||
bool addSiteInternal(const QString &hostname, const QString &ip);
|
||||
QString normalizeHostname(const QString &hostname) const;
|
||||
bool validateHostname(const QString &hostname) const;
|
||||
void processSiteAfterResolve(const QString &hostname, const QStringList &ips);
|
||||
void processSite(const QString &hostname, const QStringList &ips);
|
||||
void processSiteAfterResolve(const QString &hostname, const QString &ip);
|
||||
void processSite(const QString &hostname, const QString &ip);
|
||||
|
||||
SecureAppSettingsRepository* m_appSettingsRepository;
|
||||
RouteMode m_currentRouteMode;
|
||||
QVector<QPair<QString, QStringList>> m_sites;
|
||||
QVector<QPair<QString, QString>> m_sites;
|
||||
};
|
||||
|
||||
#endif // IPSPLITTUNNELINGCONTROLLER_H
|
||||
|
||||
@@ -572,19 +572,47 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data, Config
|
||||
lastConfig[configKey::allowedIps] = allowedIpsJsonArray;
|
||||
|
||||
QString protocolName = configKey::wireguard;
|
||||
QString protocolVersion;
|
||||
ConfigTypes detectedType = ConfigTypes::WireGuard;
|
||||
|
||||
const QStringList awgProtocolKeys = configKey::awgProtocolKeys();
|
||||
const QStringList requiredJunkFields = { configKey::junkPacketCount, configKey::junkPacketMinSize,
|
||||
configKey::junkPacketMaxSize, configKey::initPacketJunkSize,
|
||||
configKey::responsePacketJunkSize, configKey::initPacketMagicHeader,
|
||||
configKey::responsePacketMagicHeader, configKey::underloadPacketMagicHeader,
|
||||
configKey::transportPacketMagicHeader };
|
||||
|
||||
bool hasAwgKeys = std::any_of(awgProtocolKeys.begin(), awgProtocolKeys.end(),
|
||||
[&configMap](const QString &field) { return !configMap.value(field).isEmpty(); });
|
||||
if (hasAwgKeys) {
|
||||
for (const QString &key : awgProtocolKeys) {
|
||||
if (!configMap.value(key).isEmpty()) {
|
||||
lastConfig[key] = configMap.value(key);
|
||||
const QStringList optionalJunkFields = { configKey::cookieReplyPacketJunkSize,
|
||||
configKey::transportPacketJunkSize,
|
||||
configKey::specialJunk1, configKey::specialJunk2, configKey::specialJunk3,
|
||||
configKey::specialJunk4, configKey::specialJunk5
|
||||
};
|
||||
|
||||
bool hasAllRequiredFields = std::all_of(requiredJunkFields.begin(), requiredJunkFields.end(),
|
||||
[&configMap](const QString &field) { return !configMap.value(field).isEmpty(); });
|
||||
if (hasAllRequiredFields) {
|
||||
for (const QString &field : requiredJunkFields) {
|
||||
lastConfig[field] = configMap.value(field);
|
||||
}
|
||||
|
||||
for (const QString &field : optionalJunkFields) {
|
||||
if (!configMap.value(field).isEmpty()) {
|
||||
lastConfig[field] = configMap.value(field);
|
||||
}
|
||||
}
|
||||
|
||||
bool hasCookieReplyPacketJunkSize = !configMap.value(configKey::cookieReplyPacketJunkSize).isEmpty();
|
||||
bool hasTransportPacketJunkSize = !configMap.value(configKey::transportPacketJunkSize).isEmpty();
|
||||
bool hasSpecialJunk = !configMap.value(configKey::specialJunk1).isEmpty() ||
|
||||
!configMap.value(configKey::specialJunk2).isEmpty() ||
|
||||
!configMap.value(configKey::specialJunk3).isEmpty() ||
|
||||
!configMap.value(configKey::specialJunk4).isEmpty() ||
|
||||
!configMap.value(configKey::specialJunk5).isEmpty();
|
||||
|
||||
if (hasCookieReplyPacketJunkSize && hasTransportPacketJunkSize) {
|
||||
protocolVersion = "2";
|
||||
} else if (hasSpecialJunk && !hasCookieReplyPacketJunkSize && !hasTransportPacketJunkSize) {
|
||||
protocolVersion = "1.5";
|
||||
}
|
||||
protocolName = configKey::awg;
|
||||
detectedType = ConfigTypes::Awg;
|
||||
}
|
||||
@@ -602,6 +630,9 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data, Config
|
||||
wireguardConfig[configKey::isThirdPartyConfig] = true;
|
||||
wireguardConfig[configKey::port] = port;
|
||||
wireguardConfig[configKey::transportProto] = protocols::openvpn::defaultTransportProto;
|
||||
if (protocolName == configKey::awg && !protocolVersion.isEmpty()) {
|
||||
wireguardConfig[configKey::protocolVersion] = protocolVersion;
|
||||
}
|
||||
|
||||
QJsonObject containers;
|
||||
QString containerName = (protocolName == configKey::awg) ? configKey::amneziaAwg : configKey::amneziaWireguard;
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include "core/models/protocolConfig.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QEventLoop>
|
||||
#include <QFutureWatcher>
|
||||
@@ -11,7 +10,6 @@
|
||||
#include <QtConcurrent>
|
||||
|
||||
#include "core/configurators/configuratorBase.h"
|
||||
#include "core/configurators/xrayConfigurator.h"
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
@@ -22,6 +20,7 @@
|
||||
#include "core/installers/sftpInstaller.h"
|
||||
#include "core/installers/socks5Installer.h"
|
||||
#include "core/installers/mtProxyInstaller.h"
|
||||
#include "core/configurators/xrayConfigurator.h"
|
||||
#include "core/installers/telemtInstaller.h"
|
||||
#include "core/installers/torInstaller.h"
|
||||
#include "core/installers/wireguardInstaller.h"
|
||||
@@ -104,7 +103,7 @@ ErrorCode InstallController::setupContainer(const ServerCredentials &credentials
|
||||
bool isUpdate)
|
||||
{
|
||||
qDebug().noquote() << "InstallController::setupContainer" << ContainerUtils::containerToString(container);
|
||||
SshSession sshSession;
|
||||
SshSession sshSession(this);
|
||||
ErrorCode e = ErrorCode::NoError;
|
||||
|
||||
e = isUserInSudo(credentials, sshSession);
|
||||
@@ -153,15 +152,6 @@ ErrorCode InstallController::setupContainer(const ServerCredentials &credentials
|
||||
return e;
|
||||
qDebug().noquote() << "InstallController::setupContainer configureContainerWorker finished";
|
||||
|
||||
if (container == DockerContainer::Xray || container == DockerContainer::SSXray) {
|
||||
DnsSettings dnsSettings = { m_appSettingsRepository->primaryDns(), m_appSettingsRepository->secondaryDns() };
|
||||
XrayConfigurator xrayConfigurator(&sshSession);
|
||||
e = xrayConfigurator.writeServerConfigForSetup(credentials, container, config, dnsSettings);
|
||||
if (e)
|
||||
return e;
|
||||
qDebug().noquote() << "InstallController::setupContainer xray writeServerConfigForSetup finished";
|
||||
}
|
||||
|
||||
setupServerFirewall(credentials, sshSession);
|
||||
qDebug().noquote() << "InstallController::setupContainer setupServerFirewall finished";
|
||||
|
||||
@@ -178,11 +168,11 @@ ErrorCode InstallController::updateServerConfig(const QString &serverId, DockerC
|
||||
}
|
||||
if (container == DockerContainer::MtProxy) {
|
||||
ServerCredentials credentials = adminConfig->credentials();
|
||||
SshSession sshSession;
|
||||
SshSession sshSession(this);
|
||||
MtProxyInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, newConfig);
|
||||
} else if (container == DockerContainer::Telemt) {
|
||||
ServerCredentials credentials = adminConfig->credentials();
|
||||
SshSession sshSession;
|
||||
SshSession sshSession(this);
|
||||
TelemtInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, newConfig);
|
||||
}
|
||||
adminConfig->updateContainerConfig(container, newConfig);
|
||||
@@ -198,34 +188,43 @@ ErrorCode InstallController::updateServerConfig(const QString &serverId, DockerC
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
SshSession sshSession;
|
||||
SshSession sshSession(this);
|
||||
|
||||
bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig);
|
||||
if (container == DockerContainer::Xray || container == DockerContainer::SSXray) {
|
||||
reinstallRequired = true;
|
||||
}
|
||||
qDebug() << "InstallController::updateServerConfig for container" << container << "reinstall required is" << reinstallRequired;
|
||||
|
||||
bool xrayServerSettingsChanged = false;
|
||||
if (container == DockerContainer::Xray || container == DockerContainer::SSXray) {
|
||||
const auto *oldXrayConfig = oldConfig.getXrayProtocolConfig();
|
||||
const auto *newXrayConfig = newConfig.getXrayProtocolConfig();
|
||||
if (oldXrayConfig && newXrayConfig) {
|
||||
xrayServerSettingsChanged =
|
||||
!oldXrayConfig->serverConfig.hasEqualServerSettings(newXrayConfig->serverConfig);
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCode errorCode = ErrorCode::NoError;
|
||||
if (reinstallRequired) {
|
||||
errorCode = setupContainer(credentials, container, newConfig, true);
|
||||
|
||||
// Reinstall pulls the latest container image, so the server runs the latest protocol version
|
||||
if (errorCode == ErrorCode::NoError && container == DockerContainer::Awg2) {
|
||||
if (auto* awgConfig = newConfig.getAwgProtocolConfig()) {
|
||||
awgConfig->serverConfig.protocolVersion = protocols::awg::awgV3;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
errorCode = configureContainerWorker(credentials, container, newConfig, sshSession);
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
errorCode = startupContainerWorker(credentials, container, newConfig, sshSession);
|
||||
}
|
||||
}
|
||||
|
||||
if (errorCode == ErrorCode::NoError
|
||||
&& (container == DockerContainer::MtProxy || container == DockerContainer::Telemt)) {
|
||||
const QString containerName = ContainerUtils::containerToString(container);
|
||||
errorCode = sshSession.runScript(credentials, "sudo docker restart " + containerName);
|
||||
const bool skipXrayInboundSync =
|
||||
newConfig.getXrayProtocolConfig() && newConfig.getXrayProtocolConfig()->serverConfig.isThirdPartyConfig;
|
||||
|
||||
if (errorCode == ErrorCode::NoError && xrayServerSettingsChanged && !skipXrayInboundSync) {
|
||||
DnsSettings dnsSettings = { m_appSettingsRepository->primaryDns(), m_appSettingsRepository->secondaryDns() };
|
||||
XrayConfigurator xrayConfigurator(&sshSession);
|
||||
qDebug() << "InstallController::updateServerConfig applying Xray server inbound sync, reinstall="
|
||||
<< reinstallRequired;
|
||||
errorCode = xrayConfigurator.applyServerSettingsToRemote(credentials, container, newConfig, dnsSettings, false);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
qDebug() << "InstallController::updateServerConfig Xray inbound sync failed, error="
|
||||
<< static_cast<int>(errorCode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -417,11 +416,6 @@ ErrorCode InstallController::prepareContainerConfig(DockerContainer container, c
|
||||
}
|
||||
|
||||
if (ContainerUtils::containerService(container) != ServiceType::Other) {
|
||||
if ((container == DockerContainer::Xray || container == DockerContainer::SSXray)
|
||||
&& containerConfig.protocolConfig.hasClientConfig()) {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
Proto protocol = ContainerUtils::defaultProtocol(container);
|
||||
|
||||
DnsSettings dnsSettings = {
|
||||
@@ -509,12 +503,6 @@ ErrorCode InstallController::buildContainerWorker(const ServerCredentials &crede
|
||||
if (stdOut.contains("have reached") && stdOut.contains("pull rate limit"))
|
||||
return ErrorCode::DockerPullRateLimit;
|
||||
|
||||
if (stdOut.contains("returned a non-zero code")
|
||||
|| stdOut.contains("failed to solve")
|
||||
|| stdOut.contains("Unable to find image")
|
||||
|| stdOut.contains("Couldn't connect to server"))
|
||||
return ErrorCode::ServerDockerFailedError;
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
@@ -539,8 +527,6 @@ ErrorCode InstallController::runContainerWorker(const ServerCredentials &credent
|
||||
return ErrorCode::ServerPortAlreadyAllocatedError;
|
||||
if (stdOut.contains("invalid publish"))
|
||||
return ErrorCode::ServerDockerFailedError;
|
||||
if (stdOut.contains("Unable to find image") || stdOut.contains("No such image"))
|
||||
return ErrorCode::ServerDockerFailedError;
|
||||
|
||||
return e;
|
||||
}
|
||||
@@ -730,7 +716,13 @@ bool InstallController::isReinstallContainerRequired(DockerContainer container,
|
||||
const auto *newXrayConfig = newConfig.getXrayProtocolConfig();
|
||||
|
||||
if (oldXrayConfig && newXrayConfig) {
|
||||
if (!oldXrayConfig->serverConfig.hasEqualServerSettings(newXrayConfig->serverConfig)) {
|
||||
const QString oldPort = oldXrayConfig->serverConfig.port.isEmpty()
|
||||
? QString(protocols::xray::defaultPort)
|
||||
: oldXrayConfig->serverConfig.port;
|
||||
const QString newPort = newXrayConfig->serverConfig.port.isEmpty()
|
||||
? QString(protocols::xray::defaultPort)
|
||||
: newXrayConfig->serverConfig.port;
|
||||
if (oldPort != newPort) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -747,6 +739,18 @@ bool InstallController::isReinstallContainerRequired(DockerContainer container,
|
||||
if (oldPort != newPort) {
|
||||
return true;
|
||||
}
|
||||
const QString oldTransport = oldMt->transportMode.isEmpty() ? QString(
|
||||
protocols::mtProxy::transportModeStandard)
|
||||
: oldMt->transportMode;
|
||||
const QString newTransport = newMt->transportMode.isEmpty() ? QString(
|
||||
protocols::mtProxy::transportModeStandard)
|
||||
: newMt->transportMode;
|
||||
if (oldTransport != newTransport) {
|
||||
return true;
|
||||
}
|
||||
if (oldMt->tlsDomain != newMt->tlsDomain) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -761,6 +765,39 @@ bool InstallController::isReinstallContainerRequired(DockerContainer container,
|
||||
if (oldPort != newPort) {
|
||||
return true;
|
||||
}
|
||||
const QString oldTransport = oldT->transportMode.isEmpty()
|
||||
? QString(protocols::telemt::transportModeStandard)
|
||||
: oldT->transportMode;
|
||||
const QString newTransport = newT->transportMode.isEmpty()
|
||||
? QString(protocols::telemt::transportModeStandard)
|
||||
: newT->transportMode;
|
||||
if (oldTransport != newTransport) {
|
||||
return true;
|
||||
}
|
||||
if (oldT->tlsDomain != newT->tlsDomain) {
|
||||
return true;
|
||||
}
|
||||
if (oldT->maskEnabled != newT->maskEnabled) {
|
||||
return true;
|
||||
}
|
||||
if (oldT->tlsEmulation != newT->tlsEmulation) {
|
||||
return true;
|
||||
}
|
||||
if (oldT->useMiddleProxy != newT->useMiddleProxy) {
|
||||
return true;
|
||||
}
|
||||
if (oldT->tag != newT->tag) {
|
||||
return true;
|
||||
}
|
||||
const QString oldUser = oldT->userName.isEmpty()
|
||||
? QString::fromUtf8(protocols::telemt::defaultUserName)
|
||||
: oldT->userName;
|
||||
const QString newUser = newT->userName.isEmpty()
|
||||
? QString::fromUtf8(protocols::telemt::defaultUserName)
|
||||
: newT->userName;
|
||||
if (oldUser != newUser) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -800,20 +837,6 @@ ErrorCode InstallController::installDockerWorker(const ServerCredentials &creden
|
||||
|
||||
qDebug().noquote() << "InstallController::installDockerWorker" << stdOut;
|
||||
|
||||
if (container == DockerContainer::MtProxy || container == DockerContainer::Telemt) {
|
||||
QString conntrackOut;
|
||||
auto cbConntrack = [&](const QString &data, libssh::Client &) {
|
||||
conntrackOut += data + "\n";
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
sshSession.runScript(
|
||||
credentials,
|
||||
sshSession.replaceVars(amnezia::scriptData(SharedScriptType::install_conntrack),
|
||||
amnezia::genBaseVars(credentials, DockerContainer::None, QString(), QString())),
|
||||
cbConntrack, cbConntrack);
|
||||
qDebug().noquote() << "InstallController::installDockerWorker install_conntrack:" << conntrackOut;
|
||||
}
|
||||
|
||||
if (container == DockerContainer::Awg2) {
|
||||
QRegularExpression kernelVersionRegex(R"(Linux\s+(\d+)\.(\d+)[^\d]*)");
|
||||
QRegularExpressionMatch match = kernelVersionRegex.match(stdOut);
|
||||
@@ -960,7 +983,7 @@ ErrorCode InstallController::rebootServer(const QString &serverId)
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
SshSession sshSession;
|
||||
SshSession sshSession(this);
|
||||
|
||||
QString script = QString("sudo reboot");
|
||||
|
||||
@@ -988,7 +1011,7 @@ ErrorCode InstallController::removeAllContainers(const QString &serverId)
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
SshSession sshSession;
|
||||
SshSession sshSession(this);
|
||||
ErrorCode errorCode = sshSession.runScript(credentials, amnezia::scriptData(SharedScriptType::remove_all_containers));
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
@@ -1010,7 +1033,7 @@ ErrorCode InstallController::removeContainer(const QString &serverId, DockerCont
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
SshSession sshSession;
|
||||
SshSession sshSession(this);
|
||||
const amnezia::ScriptVars removeContainerVars =
|
||||
amnezia::genBaseVars(credentials, container, QString(), QString());
|
||||
const bool removeDataVolume = (container == DockerContainer::MtProxy || container == DockerContainer::Telemt);
|
||||
@@ -1119,7 +1142,7 @@ ErrorCode InstallController::scanServerForInstalledContainers(const QString &ser
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
SshSession sshSession;
|
||||
SshSession sshSession(this);
|
||||
|
||||
QMap<DockerContainer, ContainerConfig> installedContainers;
|
||||
ErrorCode errorCode = getAlreadyInstalledContainers(credentials, installedContainers, sshSession);
|
||||
@@ -1162,7 +1185,7 @@ ErrorCode InstallController::scanServerForInstalledContainers(const QString &ser
|
||||
ErrorCode InstallController::installServer(const ServerCredentials &credentials, DockerContainer container, int port,
|
||||
TransportProto transportProto, bool &wasContainerInstalled)
|
||||
{
|
||||
SshSession sshSession;
|
||||
SshSession sshSession(this);
|
||||
QMap<DockerContainer, ContainerConfig> installedContainers;
|
||||
ErrorCode errorCode = getAlreadyInstalledContainers(credentials, installedContainers, sshSession);
|
||||
if (errorCode) {
|
||||
@@ -1231,7 +1254,7 @@ ErrorCode InstallController::installContainer(const QString &serverId, DockerCon
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
SshSession sshSession;
|
||||
SshSession sshSession(this);
|
||||
|
||||
QMap<DockerContainer, ContainerConfig> installedContainers;
|
||||
ErrorCode errorCode = getAlreadyInstalledContainers(credentials, installedContainers, sshSession);
|
||||
@@ -1273,7 +1296,7 @@ ErrorCode InstallController::installContainer(const QString &serverId, DockerCon
|
||||
ErrorCode InstallController::checkSshConnection(ServerCredentials &credentials, QString &output,
|
||||
std::function<QString()> passphraseCallback)
|
||||
{
|
||||
SshSession sshSession;
|
||||
SshSession sshSession(this);
|
||||
ErrorCode errorCode = ErrorCode::NoError;
|
||||
|
||||
if (credentials.secretData.contains("BEGIN") && credentials.secretData.contains("PRIVATE KEY")) {
|
||||
@@ -1554,7 +1577,7 @@ ErrorCode InstallController::setDockerContainerEnabledState(const QString &serve
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
const QString containerName = ContainerUtils::containerToString(container);
|
||||
SshSession sshSession;
|
||||
SshSession sshSession(this);
|
||||
const QString script = enabled ? QStringLiteral("sudo docker start %1").arg(containerName)
|
||||
: QStringLiteral("sudo docker stop %1").arg(containerName);
|
||||
const ErrorCode runError = sshSession.runScript(credentials, script);
|
||||
@@ -1594,7 +1617,7 @@ ErrorCode InstallController::queryDockerContainerStatus(const QString &serverId,
|
||||
stdOut += data;
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
SshSession sshSession;
|
||||
SshSession sshSession(this);
|
||||
const QString script = QStringLiteral(
|
||||
"sudo docker inspect --format '{{.State.Status}}' %1 2>/dev/null || echo 'not_found'")
|
||||
.arg(containerName);
|
||||
@@ -1628,7 +1651,7 @@ ErrorCode InstallController::queryMtProxyDiagnostics(const QString &serverId, Do
|
||||
if (!credentials.isValid()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
SshSession sshSession;
|
||||
SshSession sshSession(this);
|
||||
return MtProxyInstaller::queryDiagnostics(sshSession, credentials, container, listenPort, out);
|
||||
}
|
||||
|
||||
@@ -1651,7 +1674,7 @@ QString InstallController::fetchDockerContainerSecret(const QString &serverId, D
|
||||
stdOut += data;
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
SshSession sshSession;
|
||||
SshSession sshSession(this);
|
||||
const QString path = QStringLiteral("/data/secret");
|
||||
const QString cmd = QStringLiteral("sudo docker exec %1 cat %2").arg(containerName, path);
|
||||
const ErrorCode errorCode = sshSession.runScript(credentials, cmd, cbReadStdOut);
|
||||
|
||||
@@ -8,10 +8,19 @@
|
||||
#include "version.h"
|
||||
#include "ui/utils/qAutoStart.h"
|
||||
#include "logger.h"
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
#include "core/local-proxy/portavailabilityhelper.h"
|
||||
#endif
|
||||
#ifdef Q_OS_ANDROID
|
||||
#include "platforms/android/android_controller.h"
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
constexpr int kDefaultProxyPort = 10808;
|
||||
constexpr int kLocalProxyPortMin = 1024;
|
||||
constexpr int kLocalProxyPortMax = 65535;
|
||||
}
|
||||
|
||||
QString getPlatformName()
|
||||
{
|
||||
#if defined(Q_OS_WINDOWS)
|
||||
@@ -38,6 +47,11 @@ SettingsController::SettingsController(SecureServersRepository* serversRepositor
|
||||
{
|
||||
m_appVersion = QString("%1 (%2, %3)").arg(QString(APP_VERSION), __DATE__, GIT_COMMIT_HASH);
|
||||
m_isDevModeEnabled = m_appSettingsRepository->isDevGatewayEnv();
|
||||
|
||||
connect(m_appSettingsRepository, &SecureAppSettingsRepository::localProxySettingsChanged, this,
|
||||
&SettingsController::localProxySettingsUpdated);
|
||||
connect(m_appSettingsRepository, &SecureAppSettingsRepository::localProxyStartFailed, this,
|
||||
&SettingsController::localProxyStartFailed);
|
||||
}
|
||||
|
||||
void SettingsController::toggleAmneziaDns(bool enable)
|
||||
@@ -366,3 +380,114 @@ QString SettingsController::nextAvailableServerName() const
|
||||
return m_serversRepository->nextAvailableServerName();
|
||||
}
|
||||
|
||||
bool SettingsController::isLocalProxySupported() const
|
||||
{
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SettingsController::isLocalProxyHttpEnabled() const
|
||||
{
|
||||
return m_appSettingsRepository->isLocalProxyHttpEnabled();
|
||||
}
|
||||
|
||||
int SettingsController::localProxyPort() const
|
||||
{
|
||||
return static_cast<int>(m_appSettingsRepository->localProxyPort());
|
||||
}
|
||||
|
||||
QString SettingsController::localProxyOwnerId() const
|
||||
{
|
||||
return m_appSettingsRepository->localProxyOwnerId();
|
||||
}
|
||||
|
||||
bool SettingsController::isLocalProxyPortUserDefined() const
|
||||
{
|
||||
return m_appSettingsRepository->isLocalProxyPortUserDefined();
|
||||
}
|
||||
|
||||
bool SettingsController::setLocalProxyPort(int port)
|
||||
{
|
||||
if (port < kLocalProxyPortMin || port > kLocalProxyPortMax) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_appSettingsRepository->localProxyPort() == static_cast<quint16>(port)) {
|
||||
m_appSettingsRepository->setLocalProxyPortUserDefined(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
m_appSettingsRepository->setLocalProxyPort(static_cast<quint16>(port));
|
||||
m_appSettingsRepository->setLocalProxyPortUserDefined(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SettingsController::isLocalProxyPortBusy(int port) const
|
||||
{
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
return !PortAvailabilityHelper::isPortAvailable(port);
|
||||
#else
|
||||
Q_UNUSED(port);
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
int SettingsController::findFirstAvailableLocalProxyPort(int startPort) const
|
||||
{
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
const auto port = PortAvailabilityHelper::findFirstAvailablePort(startPort, kLocalProxyPortMax);
|
||||
return port ? *port : -1;
|
||||
#else
|
||||
Q_UNUSED(startPort);
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SettingsController::enableLocalProxy(const QString &ownerId, int port)
|
||||
{
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
Q_UNUSED(ownerId);
|
||||
Q_UNUSED(port);
|
||||
return false;
|
||||
#else
|
||||
if (port < kLocalProxyPortMin || port > kLocalProxyPortMax || ownerId.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_appSettingsRepository->isLocalProxyHttpEnabled() && m_appSettingsRepository->localProxyOwnerId() != ownerId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int selectedPort = port;
|
||||
|
||||
const bool isUserDefinedPort = m_appSettingsRepository->isLocalProxyPortUserDefined();
|
||||
if (isUserDefinedPort) {
|
||||
if (!PortAvailabilityHelper::isPortAvailable(selectedPort)) {
|
||||
return false;
|
||||
}
|
||||
} else if (selectedPort != kDefaultProxyPort && !PortAvailabilityHelper::isPortAvailable(selectedPort)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_appSettingsRepository->localProxyPort() != static_cast<quint16>(selectedPort)) {
|
||||
m_appSettingsRepository->setLocalProxyPort(static_cast<quint16>(selectedPort));
|
||||
}
|
||||
m_appSettingsRepository->setLocalProxyPortUserDefined(isUserDefinedPort);
|
||||
|
||||
m_appSettingsRepository->setLocalProxyOwnerId(ownerId);
|
||||
m_appSettingsRepository->setLocalProxyHttpEnabled(true);
|
||||
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void SettingsController::disableLocalProxy()
|
||||
{
|
||||
if (m_appSettingsRepository->isLocalProxyHttpEnabled()) {
|
||||
m_appSettingsRepository->setLocalProxyHttpEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -89,6 +89,18 @@ public:
|
||||
QLocale getAppLanguage() const;
|
||||
void setAppLanguage(const QLocale &locale);
|
||||
|
||||
// Local proxy
|
||||
bool isLocalProxySupported() const;
|
||||
bool isLocalProxyHttpEnabled() const;
|
||||
int localProxyPort() const;
|
||||
QString localProxyOwnerId() const;
|
||||
bool isLocalProxyPortUserDefined() const;
|
||||
bool setLocalProxyPort(int port);
|
||||
bool isLocalProxyPortBusy(int port) const;
|
||||
int findFirstAvailableLocalProxyPort(int startPort) const;
|
||||
bool enableLocalProxy(const QString &ownerId, int port);
|
||||
void disableLocalProxy();
|
||||
|
||||
signals:
|
||||
void siteSplitTunnelingRouteModeChanged(RouteMode mode);
|
||||
void siteSplitTunnelingToggled(bool enabled);
|
||||
@@ -96,6 +108,9 @@ signals:
|
||||
void appSplitTunnelingToggled(bool enabled);
|
||||
void appSplitTunnelingClearAppsList();
|
||||
|
||||
void localProxySettingsUpdated();
|
||||
void localProxyStartFailed(const QString &message);
|
||||
|
||||
private:
|
||||
QString getPlatform() const;
|
||||
|
||||
|
||||
@@ -95,8 +95,7 @@ void UpdateController::fetchGatewayUrl()
|
||||
auto gatewayController = QSharedPointer<GatewayController>::create(m_appSettingsRepository->getGatewayEndpoint(),
|
||||
m_appSettingsRepository->isDevGatewayEnv(),
|
||||
7000,
|
||||
m_appSettingsRepository->isStrictKillSwitchEnabled(),
|
||||
m_appSettingsRepository);
|
||||
m_appSettingsRepository->isStrictKillSwitchEnabled());
|
||||
|
||||
QJsonObject apiPayload;
|
||||
apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION);
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
#include "awgInstaller.h"
|
||||
|
||||
#include <QPair>
|
||||
#include <QRandomGenerator>
|
||||
#include <QSet>
|
||||
#include <QStringList>
|
||||
#include <QVector>
|
||||
|
||||
#include "core/configurators/wireguardConfigurator.h"
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
@@ -27,57 +28,109 @@ AwgInstaller::AwgInstaller(QObject *parent)
|
||||
ContainerConfig AwgInstaller::generateConfig(DockerContainer container, int port, TransportProto transportProto)
|
||||
{
|
||||
ContainerConfig config = createBaseConfig(container, port, transportProto);
|
||||
|
||||
bool isAwg2 = (container == DockerContainer::Awg2);
|
||||
|
||||
if (auto* awgConfig = config.getAwgProtocolConfig()) {
|
||||
generateAwgParameters(awgConfig->serverConfig);
|
||||
awgConfig->serverConfig.protocolVersion = protocols::awg::awgV3;
|
||||
generateAwgParameters(awgConfig->serverConfig, isAwg2);
|
||||
|
||||
if (isAwg2) {
|
||||
awgConfig->serverConfig.protocolVersion = "2";
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
void AwgInstaller::generateAwgParameters(AwgServerConfig &serverConfig)
|
||||
void AwgInstaller::generateAwgParameters(AwgServerConfig &serverConfig, bool isAwg2)
|
||||
{
|
||||
QString junkPacketCount = QString::number(QRandomGenerator::global()->bounded(4, 7));
|
||||
QString junkPacketMinSize = QString::number(10);
|
||||
QString junkPacketMaxSize = QString::number(50);
|
||||
|
||||
int s1 = QRandomGenerator::global()->bounded(protocols::awg::junkPacketSizeMin, protocols::awg::initPacketJunkSizeMax);
|
||||
int s2 = QRandomGenerator::global()->bounded(protocols::awg::junkPacketSizeMin, protocols::awg::responsePacketJunkSizeMax);
|
||||
int s3 = QRandomGenerator::global()->bounded(protocols::awg::junkPacketSizeMin, protocols::awg::cookieReplyPacketJunkSizeMax);
|
||||
int s4 = protocols::awg::defaultTransportPacketJunkSize;
|
||||
int s1 = QRandomGenerator::global()->bounded(15, 150);
|
||||
int s2 = QRandomGenerator::global()->bounded(15, 150);
|
||||
int s3 = QRandomGenerator::global()->bounded(0, 64);
|
||||
int s4 = QRandomGenerator::global()->bounded(0, 20);
|
||||
|
||||
// Ensure all values are unique and don't create equal packet sizes
|
||||
QSet<int> usedValues { s1, s4 };
|
||||
QSet<int> usedValues;
|
||||
usedValues.insert(s1);
|
||||
|
||||
while (usedValues.contains(s2) || s1 + amnezia::AwgConstant::messageInitiationSize == s2 + amnezia::AwgConstant::messageResponseSize) {
|
||||
s2 = QRandomGenerator::global()->bounded(protocols::awg::junkPacketSizeMin, protocols::awg::responsePacketJunkSizeMax);
|
||||
s2 = QRandomGenerator::global()->bounded(15, 150);
|
||||
}
|
||||
usedValues.insert(s2);
|
||||
|
||||
while (usedValues.contains(s3) || s1 + amnezia::AwgConstant::messageInitiationSize == s3 + amnezia::AwgConstant::messageCookieReplySize
|
||||
|| s2 + amnezia::AwgConstant::messageResponseSize == s3 + amnezia::AwgConstant::messageCookieReplySize) {
|
||||
s3 = QRandomGenerator::global()->bounded(protocols::awg::junkPacketSizeMin, protocols::awg::cookieReplyPacketJunkSizeMax);
|
||||
s3 = QRandomGenerator::global()->bounded(0, 64);
|
||||
}
|
||||
usedValues.insert(s3);
|
||||
|
||||
while (usedValues.contains(s4)) {
|
||||
s4 = QRandomGenerator::global()->bounded(0, 20);
|
||||
}
|
||||
|
||||
QString initPacketJunkSize = QString::number(s1);
|
||||
QString responsePacketJunkSize = QString::number(s2);
|
||||
QString cookieReplyPacketJunkSize = QString::number(s3);
|
||||
QString transportPacketJunkSize = QString::number(s4);
|
||||
|
||||
QString initPacketMagicHeader;
|
||||
QString responsePacketMagicHeader;
|
||||
QString underloadPacketMagicHeader;
|
||||
QString transportPacketMagicHeader;
|
||||
|
||||
if (isAwg2) {
|
||||
// AWG 2.0: use range format for magic headers
|
||||
QVector<QPair<QString, QString>> headersValue;
|
||||
int min = 5;
|
||||
auto max = (std::numeric_limits<qint32>::max)();
|
||||
while (headersValue.size() != 4) {
|
||||
auto first = QRandomGenerator::global()->bounded(min, max);
|
||||
auto second = QRandomGenerator::global()->bounded(first, max);
|
||||
min = second;
|
||||
headersValue.push_back(QPair<QString, QString>(QString::number(first), QString::number(second)));
|
||||
}
|
||||
|
||||
initPacketMagicHeader = headersValue.at(0).first + "-" + headersValue.at(0).second;
|
||||
responsePacketMagicHeader = headersValue.at(1).first + "-" + headersValue.at(1).second;
|
||||
underloadPacketMagicHeader = headersValue.at(2).first + "-" + headersValue.at(2).second;
|
||||
transportPacketMagicHeader = headersValue.at(3).first + "-" + headersValue.at(3).second;
|
||||
} else {
|
||||
// AWG legacy: use single values for magic headers
|
||||
QSet<QString> headersValue;
|
||||
while (headersValue.size() != 4) {
|
||||
auto max = (std::numeric_limits<qint32>::max)();
|
||||
headersValue.insert(QString::number(QRandomGenerator::global()->bounded(5, max)));
|
||||
}
|
||||
|
||||
auto headersValueList = headersValue.values();
|
||||
initPacketMagicHeader = headersValueList.at(0);
|
||||
responsePacketMagicHeader = headersValueList.at(1);
|
||||
underloadPacketMagicHeader = headersValueList.at(2);
|
||||
transportPacketMagicHeader = headersValueList.at(3);
|
||||
}
|
||||
|
||||
serverConfig.junkPacketCount = junkPacketCount;
|
||||
serverConfig.junkPacketMinSize = junkPacketMinSize;
|
||||
serverConfig.junkPacketMaxSize = junkPacketMaxSize;
|
||||
serverConfig.initPacketJunkSize = QString::number(s1);
|
||||
serverConfig.responsePacketJunkSize = QString::number(s2);
|
||||
serverConfig.cookieReplyPacketJunkSize = QString::number(s3);
|
||||
serverConfig.transportPacketJunkSize = QString::number(s4);
|
||||
serverConfig.initPacketJunkSize = initPacketJunkSize;
|
||||
serverConfig.responsePacketJunkSize = responsePacketJunkSize;
|
||||
serverConfig.initPacketMagicHeader = initPacketMagicHeader;
|
||||
serverConfig.responsePacketMagicHeader = responsePacketMagicHeader;
|
||||
serverConfig.underloadPacketMagicHeader = underloadPacketMagicHeader;
|
||||
serverConfig.transportPacketMagicHeader = transportPacketMagicHeader;
|
||||
|
||||
serverConfig.initPacketMagicHeader = protocols::awg::defaultInitPacketMagicHeader;
|
||||
serverConfig.responsePacketMagicHeader = protocols::awg::defaultResponsePacketMagicHeader;
|
||||
serverConfig.underloadPacketMagicHeader = protocols::awg::defaultUnderloadPacketMagicHeader;
|
||||
serverConfig.transportPacketMagicHeader = protocols::awg::defaultTransportPacketMagicHeader;
|
||||
serverConfig.cookieReplyPacketJunkSize = cookieReplyPacketJunkSize;
|
||||
serverConfig.transportPacketJunkSize = transportPacketJunkSize;
|
||||
|
||||
serverConfig.headerProtectionKey = WireguardConfigurator::genClientKeys().clientPrivKey;
|
||||
serverConfig.contentPaddingAddition = protocols::awg::defaultContentPaddingAddition;
|
||||
serverConfig.rekeyAfterTime = protocols::awg::defaultRekeyAfterTime;
|
||||
serverConfig.rekeyTimeout = protocols::awg::defaultRekeyTimeout;
|
||||
serverConfig.rejectAfterTime = protocols::awg::defaultRejectAfterTime;
|
||||
serverConfig.keepaliveTimeout = protocols::awg::defaultKeepaliveTimeout;
|
||||
serverConfig.maxHandshakeAttempts = protocols::awg::defaultMaxHandshakeAttempts;
|
||||
serverConfig.specialJunk1 = protocols::awg::defaultSpecialJunk1;
|
||||
serverConfig.specialJunk2 = protocols::awg::defaultSpecialJunk2;
|
||||
serverConfig.specialJunk3 = protocols::awg::defaultSpecialJunk3;
|
||||
serverConfig.specialJunk4 = protocols::awg::defaultSpecialJunk4;
|
||||
serverConfig.specialJunk5 = protocols::awg::defaultSpecialJunk5;
|
||||
}
|
||||
|
||||
ErrorCode AwgInstaller::extractConfigFromContainer(DockerContainer container, const ServerCredentials &credentials,
|
||||
@@ -134,20 +187,14 @@ ErrorCode AwgInstaller::extractConfigFromContainer(DockerContainer container, co
|
||||
awgConfig->serverConfig.specialJunk4 = serverConfigMap.value(QString("# ") + configKey::specialJunk4);
|
||||
awgConfig->serverConfig.specialJunk5 = serverConfigMap.value(QString("# ") + configKey::specialJunk5);
|
||||
|
||||
awgConfig->serverConfig.cookieReplyPacketJunkSize = serverConfigMap.value(configKey::cookieReplyPacketJunkSize);
|
||||
awgConfig->serverConfig.transportPacketJunkSize = serverConfigMap.value(configKey::transportPacketJunkSize);
|
||||
|
||||
awgConfig->serverConfig.headerProtectionKey = serverConfigMap.value(configKey::headerProtectionKey);
|
||||
awgConfig->serverConfig.contentPaddingAddition = serverConfigMap.value(configKey::contentPaddingAddition);
|
||||
awgConfig->serverConfig.rekeyAfterTime = serverConfigMap.value(configKey::rekeyAfterTime);
|
||||
awgConfig->serverConfig.rekeyTimeout = serverConfigMap.value(configKey::rekeyTimeout);
|
||||
awgConfig->serverConfig.rejectAfterTime = serverConfigMap.value(configKey::rejectAfterTime);
|
||||
awgConfig->serverConfig.keepaliveTimeout = serverConfigMap.value(configKey::keepaliveTimeout);
|
||||
awgConfig->serverConfig.maxHandshakeAttempts = serverConfigMap.value(configKey::maxHandshakeAttempts);
|
||||
|
||||
awgConfig->serverConfig.protocolVersion = awgConfig->serverProtocolVersion();
|
||||
// AWG 2.0 specific fields
|
||||
if (container == DockerContainer::Awg2) {
|
||||
awgConfig->serverConfig.protocolVersion = "2";
|
||||
awgConfig->serverConfig.cookieReplyPacketJunkSize = serverConfigMap.value(configKey::cookieReplyPacketJunkSize);
|
||||
awgConfig->serverConfig.transportPacketJunkSize = serverConfigMap.value(configKey::transportPacketJunkSize);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ public:
|
||||
SshSession* serverController, amnezia::ContainerConfig &config) override;
|
||||
|
||||
private:
|
||||
void generateAwgParameters(amnezia::AwgServerConfig &serverConfig);
|
||||
void generateAwgParameters(amnezia::AwgServerConfig &serverConfig, bool isAwg2 = false);
|
||||
};
|
||||
|
||||
#endif // AWGINSTALLER_H
|
||||
|
||||
@@ -56,7 +56,6 @@ ContainerConfig InstallerBase::createBaseConfig(DockerContainer container, int p
|
||||
AwgProtocolConfig awgConfig;
|
||||
awgConfig.serverConfig.port = portStr;
|
||||
awgConfig.serverConfig.transportProto = transportProtoStr;
|
||||
awgConfig.serverConfig.subnetAddress = protocols::wireguard::defaultSubnetAddress;
|
||||
config.protocolConfig = awgConfig;
|
||||
break;
|
||||
}
|
||||
@@ -64,7 +63,6 @@ ContainerConfig InstallerBase::createBaseConfig(DockerContainer container, int p
|
||||
WireGuardProtocolConfig wgConfig;
|
||||
wgConfig.serverConfig.port = portStr;
|
||||
wgConfig.serverConfig.transportProto = transportProtoStr;
|
||||
wgConfig.serverConfig.subnetAddress = protocols::wireguard::defaultSubnetAddress;
|
||||
config.protocolConfig = wgConfig;
|
||||
break;
|
||||
}
|
||||
@@ -78,16 +76,8 @@ ContainerConfig InstallerBase::createBaseConfig(DockerContainer container, int p
|
||||
case Proto::Xray:
|
||||
case Proto::SSXray: {
|
||||
XrayProtocolConfig xrayConfig;
|
||||
XrayServerConfig &srv = xrayConfig.serverConfig;
|
||||
srv.port = portStr;
|
||||
srv.transportProto = transportProtoStr;
|
||||
srv.transport = protocols::xray::defaultTransport;
|
||||
srv.security = protocols::xray::defaultSecurity;
|
||||
srv.flow = protocols::xray::defaultFlow;
|
||||
srv.site = protocols::xray::defaultSite;
|
||||
srv.sni = protocols::xray::defaultSni;
|
||||
srv.fingerprint = protocols::xray::defaultFingerprint;
|
||||
srv.alpn = protocols::xray::defaultAlpn;
|
||||
xrayConfig.serverConfig.port = portStr;
|
||||
xrayConfig.serverConfig.transportProto = transportProtoStr;
|
||||
config.protocolConfig = xrayConfig;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
#include "core/utils/protocolEnum.h"
|
||||
#include "core/utils/selfhosted/sshSession.h"
|
||||
#include "core/models/containerConfig.h"
|
||||
@@ -21,8 +20,6 @@ namespace {
|
||||
constexpr QLatin1String kMtProxyClientJsonPath("/data/amnezia-mtproxy-client.json");
|
||||
constexpr QLatin1String kMtProxyClientJsonUploadPath("data/amnezia-mtproxy-client.json");
|
||||
constexpr QLatin1String kMtProxySecretPath("/data/secret");
|
||||
constexpr QLatin1String kMtProxyMetaPath("/data/mtproxy-meta");
|
||||
constexpr QLatin1String kMtProxyStartScriptPath("/opt/amnezia/start.sh");
|
||||
}
|
||||
|
||||
MtProxyInstaller::MtProxyInstaller(QObject *parent)
|
||||
@@ -56,98 +53,14 @@ ErrorCode MtProxyInstaller::extractConfigFromContainer(DockerContainer container
|
||||
}
|
||||
}
|
||||
|
||||
static const QRegularExpression hex32(QStringLiteral("^[0-9a-fA-F]{32}$"));
|
||||
const auto addExtra = [&](const QString &s) {
|
||||
if (hex32.match(s).hasMatch() && !mt->additionalSecrets.contains(s)) {
|
||||
mt->additionalSecrets.append(s);
|
||||
}
|
||||
};
|
||||
|
||||
ErrorCode secretErr = ErrorCode::NoError;
|
||||
const QByteArray secretRaw =
|
||||
sshSession->getTextFileFromContainer(container, credentials, QString(kMtProxySecretPath), secretErr);
|
||||
const QString sec = QString::fromUtf8(secretRaw).trimmed();
|
||||
if (sec.length() == 32 && hex32.match(sec).hasMatch()) {
|
||||
mt->secret = sec;
|
||||
}
|
||||
|
||||
bool metaRestored = false;
|
||||
ErrorCode metaErr = ErrorCode::NoError;
|
||||
const QByteArray metaRaw =
|
||||
sshSession->getTextFileFromContainer(container, credentials, QString(kMtProxyMetaPath), metaErr);
|
||||
if (metaErr == ErrorCode::NoError && !metaRaw.trimmed().isEmpty()) {
|
||||
QString mode, domain, workersMode, workers, natInternal, natExternal;
|
||||
bool natEnabled = false;
|
||||
const QList<QByteArray> lines = metaRaw.split('\n');
|
||||
for (const QByteArray &rawLine : lines) {
|
||||
const QString line = QString::fromUtf8(rawLine).trimmed();
|
||||
const int eq = line.indexOf('=');
|
||||
if (eq < 0) {
|
||||
continue;
|
||||
}
|
||||
const QString key = line.left(eq);
|
||||
const QString val = line.mid(eq + 1).trimmed();
|
||||
if (key == QLatin1String("mode")) mode = val;
|
||||
else if (key == QLatin1String("domain")) domain = val;
|
||||
else if (key == QLatin1String("tag")) { if (mt->tag.isEmpty()) mt->tag = val; }
|
||||
else if (key == QLatin1String("additional")) {
|
||||
for (const QString &s : val.split(',', Qt::SkipEmptyParts)) addExtra(s.trimmed());
|
||||
}
|
||||
else if (key == QLatin1String("workers_mode")) workersMode = val;
|
||||
else if (key == QLatin1String("workers")) workers = val;
|
||||
else if (key == QLatin1String("nat_enabled")) natEnabled = (val == QLatin1String("1"));
|
||||
else if (key == QLatin1String("nat_internal")) natInternal = val;
|
||||
else if (key == QLatin1String("nat_external")) natExternal = val;
|
||||
else if (key == QLatin1String("public_host")) { if (mt->publicHost.isEmpty()) mt->publicHost = val; }
|
||||
}
|
||||
if (!mode.isEmpty()) {
|
||||
mt->transportMode = mode;
|
||||
if (!domain.isEmpty()) mt->tlsDomain = domain;
|
||||
if (!workersMode.isEmpty()) mt->workersMode = workersMode;
|
||||
if (workersMode == QLatin1String(protocols::mtProxy::workersModeManual) && !workers.isEmpty()) {
|
||||
mt->workers = workers;
|
||||
}
|
||||
if (natEnabled) {
|
||||
mt->natEnabled = true;
|
||||
mt->natInternalIp = natInternal;
|
||||
mt->natExternalIp = natExternal;
|
||||
}
|
||||
metaRestored = true;
|
||||
}
|
||||
}
|
||||
if (!metaRestored) {
|
||||
ErrorCode startErr = ErrorCode::NoError;
|
||||
const QByteArray startRaw =
|
||||
sshSession->getTextFileFromContainer(container, credentials, QString(kMtProxyStartScriptPath), startErr);
|
||||
if (startErr == ErrorCode::NoError && !startRaw.trimmed().isEmpty()) {
|
||||
const QString start = QString::fromUtf8(startRaw);
|
||||
static const QRegularExpression modeRe(QStringLiteral("\\[ \"(standard|faketls)\" = \"faketls\" \\]"));
|
||||
const QRegularExpressionMatch m = modeRe.match(start);
|
||||
if (m.hasMatch()) {
|
||||
mt->transportMode = m.captured(1);
|
||||
if (m.captured(1) == QLatin1String(protocols::mtProxy::transportModeFakeTLS)) {
|
||||
static const QRegularExpression domRe(QStringLiteral("--domain ([A-Za-z0-9.\\-]+)"));
|
||||
const QRegularExpressionMatch dm = domRe.match(start);
|
||||
if (dm.hasMatch()) mt->tlsDomain = dm.captured(1);
|
||||
}
|
||||
}
|
||||
static const QRegularExpression tagRe(QStringLiteral("-P ([0-9a-fA-F]{32})"));
|
||||
const QRegularExpressionMatch tm = tagRe.match(start);
|
||||
if (tm.hasMatch() && mt->tag.isEmpty()) mt->tag = tm.captured(1);
|
||||
|
||||
static const QRegularExpression addRe(QStringLiteral("echo \"([0-9a-fA-F,]+)\" \\| tr ',' ' '"));
|
||||
const QRegularExpressionMatch am = addRe.match(start);
|
||||
if (am.hasMatch()) {
|
||||
for (const QString &s : am.captured(1).split(',', Qt::SkipEmptyParts)) addExtra(s.trimmed());
|
||||
}
|
||||
|
||||
static const QRegularExpression natRe(QStringLiteral("NAT_VALUE=\"([0-9.]+):([0-9.]+)\""));
|
||||
const QRegularExpressionMatch nm = natRe.match(start);
|
||||
if (nm.hasMatch()) {
|
||||
mt->natEnabled = true;
|
||||
mt->natInternalIp = nm.captured(1);
|
||||
mt->natExternalIp = nm.captured(2);
|
||||
}
|
||||
if (sec.length() == 32) {
|
||||
static const QRegularExpression hex32(QStringLiteral("^[0-9a-fA-F]{32}$"));
|
||||
if (hex32.match(sec).hasMatch()) {
|
||||
mt->secret = sec;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,62 +71,48 @@ ErrorCode MtProxyInstaller::queryDiagnostics(SshSession &sshSession, const Serve
|
||||
DockerContainer container, int listenPort,
|
||||
MtProxyContainerDiagnostics &out)
|
||||
{
|
||||
out = { };
|
||||
if (container == DockerContainer::MtProxy || container == DockerContainer::Telemt) {
|
||||
const QString containerName = ContainerUtils::containerToString(container);
|
||||
const bool isTelemt = container == DockerContainer::Telemt;
|
||||
|
||||
const QString sportFilter = QString::number(listenPort);
|
||||
const QString peersCmd = QStringLiteral("sudo conntrack -L -p tcp --dport ") + sportFilter
|
||||
+ QStringLiteral(" 2>/dev/null | grep ESTABLISHED | awk '{for(i=1;i<=NF;i++) if($i ~ /^src=/){print "
|
||||
"substr($i,5); break}}'");
|
||||
const QString publicFilter = QStringLiteral(" | grep -vE "
|
||||
"'^(10\\.|127\\.|169\\.254\\.|192\\.168\\.|172\\.(1[6-9]|2[0-9]|3["
|
||||
"01])\\.|::1$|fe80:|f[cd][0-9a-f][0-9a-f]:)'");
|
||||
const QString clientsCmd =
|
||||
QStringLiteral("CLIENTS=$(") + peersCmd + publicFilter + QStringLiteral(" | sort -u | grep -c .); ");
|
||||
const QString confFile =
|
||||
isTelemt ? QStringLiteral("/data/config.toml") : QStringLiteral("/data/proxy-multi.conf");
|
||||
const QString statsUrl = QString();
|
||||
|
||||
const QString script = QStringLiteral("CN=") + containerName + QStringLiteral("; ")
|
||||
+ QStringLiteral("PORT_OK=$(sudo ss -tlnp 2>/dev/null | grep -q :") + QString::number(listenPort)
|
||||
+ QStringLiteral(" && echo yes || echo no); ")
|
||||
+ QStringLiteral("TG_OK=$(curl -s --max-time 5 -o /dev/null -w '%{http_code}' "
|
||||
"https://core.telegram.org/getProxySecret 2>/dev/null | grep -q '200' && echo yes || "
|
||||
"echo no); ")
|
||||
+ clientsCmd + QStringLiteral("CONF_TIME=$(sudo docker exec \"$CN\" sh -c 'stat -c \"%y\" ") + confFile
|
||||
+ QStringLiteral(" 2>/dev/null | cut -d. -f1' 2>/dev/null || echo unknown); ")
|
||||
+ QStringLiteral("echo \"PORT_OK=${PORT_OK}\"; ") + QStringLiteral("echo \"TG_OK=${TG_OK}\"; ")
|
||||
+ QStringLiteral("echo \"CLIENTS=${CLIENTS:-0}\"; ") + QStringLiteral("echo \"CONF_TIME=${CONF_TIME}\"; ")
|
||||
+ QStringLiteral("echo \"STATS=") + statsUrl + QStringLiteral("\";");
|
||||
|
||||
QString stdOut;
|
||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||
stdOut += data;
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
const ErrorCode errorCode = sshSession.runScript(credentials, script, cbReadStdOut);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
for (const QString &line : stdOut.split('\n', Qt::SkipEmptyParts)) {
|
||||
if (line.startsWith(QLatin1String("PORT_OK="))) {
|
||||
out.portReachable = line.mid(8).trimmed() == QLatin1String("yes");
|
||||
} else if (line.startsWith(QLatin1String("TG_OK="))) {
|
||||
out.upstreamReachable = line.mid(6).trimmed() == QLatin1String("yes");
|
||||
} else if (line.startsWith(QLatin1String("CLIENTS="))) {
|
||||
out.clientsConnected = line.mid(8).trimmed().toInt();
|
||||
} else if (line.startsWith(QLatin1String("CONF_TIME="))) {
|
||||
out.lastConfigRefresh = line.mid(10).trimmed();
|
||||
} else if (line.startsWith(QLatin1String("STATS="))) {
|
||||
out.statsEndpoint = line.mid(6).trimmed();
|
||||
}
|
||||
}
|
||||
return ErrorCode::NoError;
|
||||
out = {};
|
||||
if (container != DockerContainer::MtProxy && container != DockerContainer::Telemt) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
const QString containerName = ContainerUtils::containerToString(container);
|
||||
const QString script =
|
||||
QStringLiteral(
|
||||
"PORT_OK=$(sudo docker exec %1 sh -c 'ss -tlnp 2>/dev/null | grep -q :%2 && echo yes || echo no' 2>/dev/null || echo no); "
|
||||
"TG_OK=$(curl -s --max-time 5 -o /dev/null -w '%%{http_code}' https://core.telegram.org/getProxySecret 2>/dev/null | grep -q '200' && echo yes || echo no); "
|
||||
"CLIENTS=$(sudo docker exec amnezia-mtproxy sh -c 'curl -s --max-time 3 http://localhost:2398/stats 2>/dev/null | grep -o \"total_special_connections:[0-9]*\" | cut -d: -f2' 2>/dev/null); "
|
||||
"CONF_TIME=$(sudo docker exec amnezia-mtproxy sh -c 'stat -c \"%%y\" /data/proxy-multi.conf 2>/dev/null | cut -d. -f1' 2>/dev/null || echo unknown); "
|
||||
"echo \"PORT_OK=${PORT_OK}\"; "
|
||||
"echo \"TG_OK=${TG_OK}\"; "
|
||||
"echo \"CLIENTS=${CLIENTS:-0}\"; "
|
||||
"echo \"CONF_TIME=${CONF_TIME}\"; "
|
||||
"echo \"STATS=http://localhost:2398/stats\";")
|
||||
.arg(containerName)
|
||||
.arg(listenPort);
|
||||
|
||||
return ErrorCode::InternalError;
|
||||
QString stdOut;
|
||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||
stdOut += data;
|
||||
return ErrorCode::NoError;
|
||||
};
|
||||
const ErrorCode errorCode = sshSession.runScript(credentials, script, cbReadStdOut);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
for (const QString &line : stdOut.split('\n', Qt::SkipEmptyParts)) {
|
||||
if (line.startsWith(QLatin1String("PORT_OK="))) {
|
||||
out.portReachable = line.mid(8).trimmed() == QLatin1String("yes");
|
||||
} else if (line.startsWith(QLatin1String("TG_OK="))) {
|
||||
out.upstreamReachable = line.mid(6).trimmed() == QLatin1String("yes");
|
||||
} else if (line.startsWith(QLatin1String("CLIENTS="))) {
|
||||
out.clientsConnected = line.mid(8).trimmed().toInt();
|
||||
} else if (line.startsWith(QLatin1String("CONF_TIME="))) {
|
||||
out.lastConfigRefresh = line.mid(10).trimmed();
|
||||
} else if (line.startsWith(QLatin1String("STATS="))) {
|
||||
out.statsEndpoint = line.mid(6).trimmed();
|
||||
}
|
||||
}
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
void MtProxyInstaller::uploadClientSettingsSnapshot(SshSession &sshSession, const ServerCredentials &credentials,
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include "core/utils/containerEnum.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
#include "core/utils/selfhosted/sshSession.h"
|
||||
#include "core/models/containerConfig.h"
|
||||
#include "core/models/protocols/telemtProtocolConfig.h"
|
||||
@@ -20,7 +19,6 @@ namespace {
|
||||
constexpr QLatin1String kTelemtClientJsonPath("/data/amnezia-telemt-client.json");
|
||||
constexpr QLatin1String kTelemtClientJsonUploadPath("data/amnezia-telemt-client.json");
|
||||
constexpr QLatin1String kTelemtSecretPath("/data/secret");
|
||||
constexpr QLatin1String kTelemtConfigTomlPath("/data/config.toml");
|
||||
}
|
||||
|
||||
TelemtInstaller::TelemtInstaller(QObject *parent) : InstallerBase(parent) {}
|
||||
@@ -56,83 +54,10 @@ ErrorCode TelemtInstaller::extractConfigFromContainer(DockerContainer container,
|
||||
const QByteArray secretRaw =
|
||||
sshSession->getTextFileFromContainer(container, credentials, QString(kTelemtSecretPath), secretErr);
|
||||
const QString sec = QString::fromUtf8(secretRaw).trimmed();
|
||||
static const QRegularExpression hex32(QStringLiteral("^[0-9a-fA-F]{32}$"));
|
||||
if (sec.length() == 32 && hex32.match(sec).hasMatch()) {
|
||||
tc->secret = sec;
|
||||
}
|
||||
|
||||
ErrorCode tomlErr = ErrorCode::NoError;
|
||||
const QByteArray tomlRaw =
|
||||
sshSession->getTextFileFromContainer(container, credentials, QString(kTelemtConfigTomlPath), tomlErr);
|
||||
if (tomlErr == ErrorCode::NoError && !tomlRaw.trimmed().isEmpty()) {
|
||||
QString section;
|
||||
bool userNameSet = false;
|
||||
const QList<QByteArray> lines = tomlRaw.split('\n');
|
||||
for (const QByteArray &rawLine : lines) {
|
||||
const QString line = QString::fromUtf8(rawLine).trimmed();
|
||||
if (line.isEmpty() || line.startsWith('#')) {
|
||||
continue;
|
||||
}
|
||||
if (line.startsWith('[')) {
|
||||
section = line;
|
||||
continue;
|
||||
}
|
||||
const int eq = line.indexOf('=');
|
||||
if (eq < 0) {
|
||||
continue;
|
||||
}
|
||||
const QString key = line.left(eq).trimmed();
|
||||
QString val = line.mid(eq + 1).trimmed();
|
||||
if (val.startsWith('"')) {
|
||||
const int last = val.lastIndexOf('"');
|
||||
val = (last > 0) ? val.mid(1, last - 1) : val.mid(1);
|
||||
} else {
|
||||
const int inlineComment = val.indexOf(QLatin1String(" #"));
|
||||
if (inlineComment >= 0) {
|
||||
val = val.left(inlineComment).trimmed();
|
||||
}
|
||||
}
|
||||
|
||||
if (section == QLatin1String("[access.users]")) {
|
||||
if (key.startsWith(QLatin1String("extra"))) {
|
||||
if (hex32.match(val).hasMatch() && !tc->additionalSecrets.contains(val)) {
|
||||
tc->additionalSecrets.append(val);
|
||||
}
|
||||
} else if (!userNameSet) {
|
||||
tc->userName = key;
|
||||
userNameSet = true;
|
||||
if (tc->secret.isEmpty() && hex32.match(val).hasMatch()) {
|
||||
tc->secret = val;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key == QLatin1String("tls") && section == QLatin1String("[general.modes]")) {
|
||||
tc->transportMode = (val == QLatin1String("true"))
|
||||
? QString::fromUtf8(protocols::telemt::transportModeFakeTLS)
|
||||
: QString::fromUtf8(protocols::telemt::transportModeStandard);
|
||||
} else if (key == QLatin1String("tls_domain") && section == QLatin1String("[censorship]")) {
|
||||
tc->tlsDomain = val;
|
||||
} else if (key == QLatin1String("mask") && section == QLatin1String("[censorship]")) {
|
||||
tc->maskEnabled = (val == QLatin1String("true"));
|
||||
} else if (key == QLatin1String("tls_emulation") && section == QLatin1String("[censorship]")) {
|
||||
tc->tlsEmulation = (val == QLatin1String("true"));
|
||||
} else if (key == QLatin1String("use_middle_proxy") && section == QLatin1String("[general]")) {
|
||||
tc->useMiddleProxy = (val == QLatin1String("true"));
|
||||
} else if (key == QLatin1String("middle_proxy_nat_ip") && section == QLatin1String("[general]")) {
|
||||
if (!val.isEmpty()) {
|
||||
tc->natExternalIp = val;
|
||||
tc->natEnabled = true;
|
||||
}
|
||||
} else if (key == QLatin1String("ad_tag") && section == QLatin1String("[general]") && tc->tag.isEmpty()) {
|
||||
tc->tag = val;
|
||||
} else if (key == QLatin1String("public_host") && section == QLatin1String("[general.links]")
|
||||
&& tc->publicHost.isEmpty()) {
|
||||
tc->publicHost = val;
|
||||
} else if (key == QLatin1String("port") && section == QLatin1String("[server]") && tc->port.isEmpty()) {
|
||||
tc->port = val;
|
||||
}
|
||||
if (sec.length() == 32) {
|
||||
static const QRegularExpression hex32(QStringLiteral("^[0-9a-fA-F]{32}$"));
|
||||
if (hex32.match(sec).hasMatch()) {
|
||||
tc->secret = sec;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,31 +26,6 @@ namespace
|
||||
}
|
||||
return fp;
|
||||
}
|
||||
|
||||
// Parse an xray int range: "from-to" string, plain int, or legacy {from,to} object.
|
||||
void parseIntRange(const QJsonValue &v, QString &minOut, QString &maxOut)
|
||||
{
|
||||
if (v.isString()) {
|
||||
const QString s = v.toString().trimmed();
|
||||
const int dash = s.indexOf(QLatin1Char('-'), 1);
|
||||
if (dash > 0) {
|
||||
minOut = s.left(dash).trimmed();
|
||||
maxOut = s.mid(dash + 1).trimmed();
|
||||
} else if (!s.isEmpty()) {
|
||||
minOut = s;
|
||||
maxOut = s;
|
||||
}
|
||||
} else if (v.isDouble()) {
|
||||
minOut = QString::number(v.toInt());
|
||||
maxOut = minOut;
|
||||
} else if (v.isObject()) {
|
||||
const QJsonObject o = v.toObject();
|
||||
if (o.contains(QLatin1String("from")) || o.contains(QLatin1String("to"))) {
|
||||
minOut = QString::number(o.value(QLatin1String("from")).toInt());
|
||||
maxOut = QString::number(o.value(QLatin1String("to")).toInt());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
using namespace amnezia;
|
||||
@@ -150,13 +125,7 @@ ErrorCode XrayInstaller::extractConfigFromContainer(DockerContainer container, c
|
||||
QJsonArray alpnArr = tls.value("alpn").toArray();
|
||||
QStringList alpnList;
|
||||
for (const QJsonValue &v : alpnArr) {
|
||||
QString t = v.toString().trimmed();
|
||||
if (t.compare(QLatin1String("HTTP/2"), Qt::CaseInsensitive) == 0)
|
||||
t = QStringLiteral("h2");
|
||||
else if (t.compare(QLatin1String("HTTP/1.1"), Qt::CaseInsensitive) == 0)
|
||||
t = QStringLiteral("http/1.1");
|
||||
if (!t.isEmpty())
|
||||
alpnList << t;
|
||||
alpnList << v.toString();
|
||||
}
|
||||
srv.alpn = alpnList.join(",");
|
||||
}
|
||||
@@ -190,6 +159,12 @@ ErrorCode XrayInstaller::extractConfigFromContainer(DockerContainer container, c
|
||||
srv.xhttp.host = xhttpObj.value("host").toString();
|
||||
srv.xhttp.path = xhttpObj.value("path").toString();
|
||||
|
||||
{
|
||||
const QJsonObject hdrs = xhttpObj.value("headers").toObject();
|
||||
if (hdrs.contains(QLatin1String("Host")) || !hdrs.isEmpty())
|
||||
srv.xhttp.headersTemplate = QStringLiteral("HTTP");
|
||||
}
|
||||
|
||||
if (xhttpObj.contains(QLatin1String("uplinkHTTPMethod")))
|
||||
srv.xhttp.uplinkMethod = xhttpObj.value("uplinkHTTPMethod").toString();
|
||||
else
|
||||
@@ -209,9 +184,7 @@ ErrorCode XrayInstaller::extractConfigFromContainer(DockerContainer container, c
|
||||
return QStringLiteral("Query");
|
||||
return core;
|
||||
};
|
||||
QString sess = xhttpObj.value("sessionIDPlacement").toString();
|
||||
if (sess.isEmpty())
|
||||
sess = xhttpObj.value("sessionPlacement").toString();
|
||||
QString sess = xhttpObj.value("sessionPlacement").toString();
|
||||
if (sess.isEmpty())
|
||||
sess = xhttpObj.value("scSessionPlacement").toString();
|
||||
srv.xhttp.sessionPlacement = sessionSeqUi(sess);
|
||||
@@ -237,17 +210,14 @@ ErrorCode XrayInstaller::extractConfigFromContainer(DockerContainer container, c
|
||||
udata = xhttpObj.value("scUplinkDataPlacement").toString();
|
||||
srv.xhttp.uplinkDataPlacement = uplinkDataUi(udata);
|
||||
|
||||
srv.xhttp.sessionKey = xhttpObj.value("sessionIDKey").toString();
|
||||
if (srv.xhttp.sessionKey.isEmpty())
|
||||
srv.xhttp.sessionKey = xhttpObj.value("sessionKey").toString();
|
||||
srv.xhttp.sessionKey = xhttpObj.value("sessionKey").toString();
|
||||
srv.xhttp.seqKey = xhttpObj.value("seqKey").toString();
|
||||
srv.xhttp.uplinkDataKey = xhttpObj.value("uplinkDataKey").toString();
|
||||
|
||||
if (xhttpObj.contains(QLatin1String("uplinkChunkSize"))) {
|
||||
QString ucMin, ucMax;
|
||||
parseIntRange(xhttpObj.value("uplinkChunkSize"), ucMin, ucMax);
|
||||
if (!ucMin.isEmpty())
|
||||
srv.xhttp.uplinkChunkSize = ucMin;
|
||||
QJsonObject uc = xhttpObj.value("uplinkChunkSize").toObject();
|
||||
if (!uc.isEmpty())
|
||||
srv.xhttp.uplinkChunkSize = QString::number(uc.value("from").toInt());
|
||||
} else if (xhttpObj.contains(QLatin1String("xhttpUplinkChunkSize"))) {
|
||||
srv.xhttp.uplinkChunkSize = QString::number(xhttpObj.value("xhttpUplinkChunkSize").toInt());
|
||||
}
|
||||
@@ -256,7 +226,11 @@ ErrorCode XrayInstaller::extractConfigFromContainer(DockerContainer container, c
|
||||
}
|
||||
|
||||
auto readRange = [&](const char *key, QString &minOut, QString &maxOut) {
|
||||
parseIntRange(xhttpObj.value(QLatin1String(key)), minOut, maxOut);
|
||||
QJsonObject r = xhttpObj.value(QLatin1String(key)).toObject();
|
||||
if (!r.isEmpty()) {
|
||||
minOut = QString::number(r.value("from").toInt());
|
||||
maxOut = QString::number(r.value("to").toInt());
|
||||
}
|
||||
};
|
||||
readRange("scMaxEachPostBytes", srv.xhttp.scMaxEachPostBytesMin, srv.xhttp.scMaxEachPostBytesMax);
|
||||
readRange("scMinPostsIntervalMs", srv.xhttp.scMinPostsIntervalMsMin, srv.xhttp.scMinPostsIntervalMsMax);
|
||||
@@ -269,11 +243,10 @@ ErrorCode XrayInstaller::extractConfigFromContainer(DockerContainer container, c
|
||||
srv.xhttp.xPadding.header = pad.value("xPaddingHeader").toString();
|
||||
srv.xhttp.xPadding.placement = pad.value("xPaddingPlacement").toString();
|
||||
srv.xhttp.xPadding.method = pad.value("xPaddingMethod").toString();
|
||||
QString bytesMin, bytesMax;
|
||||
parseIntRange(pad.value("xPaddingBytes"), bytesMin, bytesMax);
|
||||
if (!bytesMin.isEmpty()) {
|
||||
srv.xhttp.xPadding.bytesMin = bytesMin;
|
||||
srv.xhttp.xPadding.bytesMax = bytesMax;
|
||||
QJsonObject bytesRange = pad.value("xPaddingBytes").toObject();
|
||||
if (!bytesRange.isEmpty()) {
|
||||
srv.xhttp.xPadding.bytesMin = QString::number(bytesRange.value("from").toInt());
|
||||
srv.xhttp.xPadding.bytesMax = QString::number(bytesRange.value("to").toInt());
|
||||
}
|
||||
QString pl = srv.xhttp.xPadding.placement.toLower();
|
||||
if (pl == QLatin1String("cookie"))
|
||||
@@ -291,7 +264,7 @@ ErrorCode XrayInstaller::extractConfigFromContainer(DockerContainer container, c
|
||||
srv.xhttp.xPadding.method = QStringLiteral("Tokenish");
|
||||
};
|
||||
if (xhttpObj.contains(QLatin1String("xPaddingObfsMode")) || xhttpObj.contains(QLatin1String("xPaddingKey"))
|
||||
|| xhttpObj.contains(QLatin1String("xPaddingBytes"))) {
|
||||
|| !xhttpObj.value("xPaddingBytes").toObject().isEmpty()) {
|
||||
loadPaddingFromObject(xhttpObj);
|
||||
} else if (xhttpObj.contains(QLatin1String("xPadding")) && xhttpObj.value("xPadding").isObject()) {
|
||||
const QJsonObject nested = xhttpObj.value("xPadding").toObject();
|
||||
@@ -307,7 +280,11 @@ ErrorCode XrayInstaller::extractConfigFromContainer(DockerContainer container, c
|
||||
srv.xhttp.xmux.enabled = true;
|
||||
|
||||
auto readMuxRange = [&](const char *key, QString &minOut, QString &maxOut) {
|
||||
parseIntRange(mux.value(QLatin1String(key)), minOut, maxOut);
|
||||
QJsonObject r = mux.value(QLatin1String(key)).toObject();
|
||||
if (!r.isEmpty()) {
|
||||
minOut = QString::number(r.value("from").toInt());
|
||||
maxOut = QString::number(r.value("to").toInt());
|
||||
}
|
||||
};
|
||||
readMuxRange("maxConcurrency", srv.xhttp.xmux.maxConcurrencyMin, srv.xhttp.xmux.maxConcurrencyMax);
|
||||
readMuxRange("maxConnections", srv.xhttp.xmux.maxConnectionsMin, srv.xhttp.xmux.maxConnectionsMax);
|
||||
|
||||
444
client/core/local-proxy/configmanager.cpp
Normal file
444
client/core/local-proxy/configmanager.cpp
Normal file
@@ -0,0 +1,444 @@
|
||||
#include "configmanager.h"
|
||||
|
||||
#include "core/controllers/gatewayController.h"
|
||||
#include "core/repositories/secureAppSettingsRepository.h"
|
||||
#include "core/repositories/secureServersRepository.h"
|
||||
#include "core/utils/api/apiUtils.h"
|
||||
#include "core/utils/constants/apiConstants.h"
|
||||
#include "core/utils/constants/apiKeys.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
#include "portavailabilityhelper.h"
|
||||
#include "proxylogger.h"
|
||||
#include "version.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonParseError>
|
||||
#include <QSaveFile>
|
||||
#include <QSysInfo>
|
||||
#include <QStandardPaths>
|
||||
#include <QUuid>
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
ConfigManager::ConfigManager(SecureServersRepository *serversRepository, SecureAppSettingsRepository *appSettingsRepository)
|
||||
: m_serversRepository(serversRepository), m_appSettingsRepository(appSettingsRepository)
|
||||
{
|
||||
ProxyLogger::getInstance().debug("ConfigManager initialized (repository-backed)");
|
||||
}
|
||||
|
||||
namespace {
|
||||
namespace gateway_key {
|
||||
constexpr char vless[] = "vless";
|
||||
} // namespace gateway_key
|
||||
|
||||
constexpr quint16 kDefaultProxyPort = 10808;
|
||||
constexpr int kProxyPortMin = 1024;
|
||||
constexpr int kProxyPortMax = 65535;
|
||||
|
||||
int resolveProxyPort(SecureAppSettingsRepository *appSettings)
|
||||
{
|
||||
if (!appSettings) {
|
||||
return kDefaultProxyPort;
|
||||
}
|
||||
|
||||
const quint16 port = appSettings->localProxyPort();
|
||||
if (port < kProxyPortMin || port > kProxyPortMax) {
|
||||
return kDefaultProxyPort;
|
||||
}
|
||||
|
||||
return static_cast<int>(port);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ConfigManager::applyProxyPortToConfig(QJsonObject &config, int port) const
|
||||
{
|
||||
if (!config.contains("inbounds") || !config.value("inbounds").isArray()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonArray inbounds = config.value("inbounds").toArray();
|
||||
if (inbounds.isEmpty() || !inbounds.at(0).isObject()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonObject firstInbound = inbounds.at(0).toObject();
|
||||
firstInbound.insert("port", port);
|
||||
inbounds[0] = firstInbound;
|
||||
config.insert("inbounds", inbounds);
|
||||
return true;
|
||||
}
|
||||
|
||||
QString ConfigManager::serializeConfig(const QJsonObject &config) const
|
||||
{
|
||||
return QString::fromUtf8(QJsonDocument(config).toJson(QJsonDocument::Compact));
|
||||
}
|
||||
|
||||
std::optional<ConfigManager::ConfigData> ConfigManager::buildConfig(QString &errorDescription) const
|
||||
{
|
||||
errorDescription.clear();
|
||||
|
||||
if (!m_serversRepository || !m_appSettingsRepository) {
|
||||
const QString message = QStringLiteral("Local proxy repositories are not available");
|
||||
ProxyLogger::getInstance().error(message);
|
||||
errorDescription = message;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const QString ownerId = m_appSettingsRepository->localProxyOwnerId();
|
||||
if (ownerId.isEmpty()) {
|
||||
const QString message = QStringLiteral("Local proxy owner server id is not configured");
|
||||
ProxyLogger::getInstance().warning(message);
|
||||
errorDescription = message;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto ownerServer = m_serversRepository->serverJsonById(ownerId);
|
||||
if (!ownerServer) {
|
||||
const QString message = QStringLiteral("Owner server with id %1 not found").arg(ownerId);
|
||||
ProxyLogger::getInstance().error(message);
|
||||
errorDescription = message;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (!apiUtils::isPremiumServer(*ownerServer)) {
|
||||
const QString message = QStringLiteral("Server %1 is not premium, local proxy is unavailable")
|
||||
.arg(ownerServer->value(configKey::name).toString());
|
||||
ProxyLogger::getInstance().warning(message);
|
||||
errorDescription = message;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto serializedConfig = extractSerializedXrayConfig(*ownerServer);
|
||||
if (!serializedConfig || serializedConfig->isEmpty()) {
|
||||
const QString message = QStringLiteral("Server %1 lacks Xray last_config payload")
|
||||
.arg(ownerServer->value(configKey::name).toString());
|
||||
ProxyLogger::getInstance().error(message);
|
||||
errorDescription = message;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
QJsonParseError parseError;
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(serializedConfig->toUtf8(), &parseError);
|
||||
if (parseError.error != QJsonParseError::NoError || !doc.isObject()) {
|
||||
const QString message = QStringLiteral("Failed to parse Xray config JSON: %1").arg(parseError.errorString());
|
||||
ProxyLogger::getInstance().error(message);
|
||||
errorDescription = message;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
ConfigData data;
|
||||
data.ownerId = ownerId;
|
||||
data.serverName = ownerServer->value(configKey::name).toString();
|
||||
data.parsedConfig = doc.object();
|
||||
const int proxyPort = resolveProxyPort(m_appSettingsRepository);
|
||||
if (applyProxyPortToConfig(data.parsedConfig, proxyPort)) {
|
||||
data.serializedConfig = serializeConfig(data.parsedConfig);
|
||||
} else {
|
||||
ProxyLogger::getInstance().warning(QStringLiteral("Failed to override local proxy inbound port; using original config"));
|
||||
data.serializedConfig = *serializedConfig;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
std::optional<ConfigManager::ConfigData> ConfigManager::buildConfigWithFetch(QString &errorDescription) const
|
||||
{
|
||||
errorDescription.clear();
|
||||
|
||||
if (!m_serversRepository || !m_appSettingsRepository) {
|
||||
const QString message = QStringLiteral("Local proxy repositories are not available");
|
||||
ProxyLogger::getInstance().error(message);
|
||||
errorDescription = message;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const QString ownerId = m_appSettingsRepository->localProxyOwnerId();
|
||||
if (ownerId.isEmpty()) {
|
||||
const QString message = QStringLiteral("Local proxy owner server id is not configured");
|
||||
ProxyLogger::getInstance().warning(message);
|
||||
errorDescription = message;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto ownerServer = m_serversRepository->serverJsonById(ownerId);
|
||||
if (!ownerServer) {
|
||||
const QString message = QStringLiteral("Owner server with id %1 not found").arg(ownerId);
|
||||
ProxyLogger::getInstance().error(message);
|
||||
errorDescription = message;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (!apiUtils::isPremiumServer(*ownerServer)) {
|
||||
const QString message = QStringLiteral("Server %1 is not premium, local proxy is unavailable")
|
||||
.arg(ownerServer->value(configKey::name).toString());
|
||||
ProxyLogger::getInstance().warning(message);
|
||||
errorDescription = message;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto serializedConfig = extractSerializedXrayConfig(*ownerServer);
|
||||
if (!serializedConfig || serializedConfig->isEmpty()) {
|
||||
auto fetchedConfig = fetchSerializedXrayConfigFromGateway(*ownerServer, errorDescription);
|
||||
if (!fetchedConfig || fetchedConfig->isEmpty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
serializedConfig = fetchedConfig;
|
||||
}
|
||||
|
||||
QJsonParseError parseError;
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(serializedConfig->toUtf8(), &parseError);
|
||||
if (parseError.error != QJsonParseError::NoError || !doc.isObject()) {
|
||||
const QString message = QStringLiteral("Failed to parse Xray config JSON: %1").arg(parseError.errorString());
|
||||
ProxyLogger::getInstance().error(message);
|
||||
errorDescription = message;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
ConfigData data;
|
||||
data.ownerId = ownerId;
|
||||
data.serverName = ownerServer->value(configKey::name).toString();
|
||||
data.parsedConfig = doc.object();
|
||||
|
||||
int selectedPort = resolveProxyPort(m_appSettingsRepository);
|
||||
const bool isUserDefinedPort = m_appSettingsRepository->isLocalProxyPortUserDefined();
|
||||
|
||||
if (!PortAvailabilityHelper::isPortAvailable(selectedPort)) {
|
||||
const bool canAutoSelect = !isUserDefinedPort && selectedPort == kDefaultProxyPort;
|
||||
if (canAutoSelect) {
|
||||
const auto freePort = PortAvailabilityHelper::findFirstAvailablePort(kDefaultProxyPort + 1, kProxyPortMax);
|
||||
if (!freePort) {
|
||||
errorDescription = QStringLiteral("No available local proxy port in range %1-%2")
|
||||
.arg(kDefaultProxyPort + 1)
|
||||
.arg(kProxyPortMax);
|
||||
ProxyLogger::getInstance().error(errorDescription);
|
||||
return std::nullopt;
|
||||
}
|
||||
selectedPort = *freePort;
|
||||
} else {
|
||||
errorDescription = QStringLiteral("Local proxy port %1 is already in use")
|
||||
.arg(selectedPort);
|
||||
ProxyLogger::getInstance().error(errorDescription);
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
if (applyProxyPortToConfig(data.parsedConfig, selectedPort)) {
|
||||
data.serializedConfig = serializeConfig(data.parsedConfig);
|
||||
} else {
|
||||
ProxyLogger::getInstance().warning(QStringLiteral("Failed to override local proxy inbound port; using original config"));
|
||||
data.serializedConfig = *serializedConfig;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
bool ConfigManager::writeTempConfig(const QString &serializedConfig, QString &configPath, QString &errorDescription) const
|
||||
{
|
||||
errorDescription.clear();
|
||||
configPath.clear();
|
||||
|
||||
const QString directory = tempDirectory();
|
||||
if (!QDir().mkpath(directory)) {
|
||||
const QString message = QStringLiteral("Failed to create temp config directory: %1").arg(directory);
|
||||
ProxyLogger::getInstance().error(message);
|
||||
errorDescription = message;
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString path = tempConfigPath();
|
||||
QSaveFile file(path);
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
|
||||
const QString message = QStringLiteral("Failed to open temp config file %1: %2").arg(path, file.errorString());
|
||||
ProxyLogger::getInstance().error(message);
|
||||
errorDescription = message;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (file.write(serializedConfig.toUtf8()) == -1) {
|
||||
const QString message = QStringLiteral("Failed to write temp config file %1: %2").arg(path, file.errorString());
|
||||
ProxyLogger::getInstance().error(message);
|
||||
errorDescription = message;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!file.commit()) {
|
||||
const QString message = QStringLiteral("Failed to commit temp config file %1").arg(path);
|
||||
ProxyLogger::getInstance().error(message);
|
||||
errorDescription = message;
|
||||
return false;
|
||||
}
|
||||
|
||||
ProxyLogger::getInstance().info(QStringLiteral("Xray config saved to %1").arg(path));
|
||||
configPath = path;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConfigManager::removeTempConfig() const
|
||||
{
|
||||
const QString path = tempConfigPath();
|
||||
QFile file(path);
|
||||
if (!file.exists()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!file.remove()) {
|
||||
ProxyLogger::getInstance().warning(QStringLiteral("Failed to remove temp config file %1: %2").arg(path, file.errorString()));
|
||||
return false;
|
||||
}
|
||||
|
||||
ProxyLogger::getInstance().debug(QStringLiteral("Removed temp config file %1").arg(path));
|
||||
return true;
|
||||
}
|
||||
|
||||
QString ConfigManager::tempConfigPath() const
|
||||
{
|
||||
return QDir(tempDirectory()).filePath(QStringLiteral("xray_active.json"));
|
||||
}
|
||||
|
||||
std::optional<QString> ConfigManager::extractSerializedXrayConfig(const QJsonObject &server) const
|
||||
{
|
||||
const QJsonArray containers = server.value(configKey::containers).toArray();
|
||||
const QString targetContainer = ContainerUtils::containerToString(DockerContainer::Xray);
|
||||
const QString protoKey = QString(configKey::xray);
|
||||
|
||||
for (const QJsonValue &value : containers) {
|
||||
const QJsonObject container = value.toObject();
|
||||
if (container.value(configKey::container).toString() != targetContainer) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const QJsonObject proto = container.value(protoKey).toObject();
|
||||
const QString serialized = proto.value(configKey::lastConfig).toString();
|
||||
if (!serialized.isEmpty()) {
|
||||
return serialized;
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<QString> ConfigManager::fetchSerializedXrayConfigFromGateway(const QJsonObject &server, QString &errorDescription) const
|
||||
{
|
||||
errorDescription.clear();
|
||||
|
||||
if (!m_appSettingsRepository) {
|
||||
const QString message = QStringLiteral("App settings repository is not available");
|
||||
ProxyLogger::getInstance().error(message);
|
||||
errorDescription = message;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const QJsonObject apiConfig = server.value(apiDefs::key::apiConfig).toObject();
|
||||
if (apiConfig.isEmpty()) {
|
||||
const QString message = QStringLiteral("Server API config is missing");
|
||||
ProxyLogger::getInstance().warning(message);
|
||||
errorDescription = message;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const QString userCountryCode = apiConfig.value(apiDefs::key::userCountryCode).toString();
|
||||
const QString serviceType = apiConfig.value(apiDefs::key::serviceType).toString();
|
||||
if (userCountryCode.isEmpty() || serviceType.isEmpty()) {
|
||||
const QString message = QStringLiteral("Server API config lacks service identifiers");
|
||||
ProxyLogger::getInstance().warning(message);
|
||||
errorDescription = message;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
QJsonObject apiPayload;
|
||||
apiPayload[apiDefs::key::osVersion] = QSysInfo::productType();
|
||||
apiPayload[apiDefs::key::appVersion] = QString(APP_VERSION);
|
||||
|
||||
const QString appLanguage = m_appSettingsRepository->getAppLanguage().name().split("_").first();
|
||||
if (!appLanguage.isEmpty()) {
|
||||
apiPayload[apiDefs::key::appLanguage] = appLanguage;
|
||||
}
|
||||
|
||||
apiPayload[apiDefs::key::uuid] = m_appSettingsRepository->getInstallationUuid(true);
|
||||
apiPayload[apiDefs::key::userCountryCode] = userCountryCode;
|
||||
apiPayload[apiDefs::key::serviceType] = serviceType;
|
||||
apiPayload[apiDefs::key::serviceProtocol] = QString(gateway_key::vless);
|
||||
apiPayload[apiDefs::key::publicKey] = QUuid::createUuid().toString(QUuid::WithoutBraces);
|
||||
|
||||
const QJsonObject authData = server.value(apiDefs::key::authData).toObject();
|
||||
if (!authData.isEmpty()) {
|
||||
apiPayload[apiDefs::key::authData] = authData;
|
||||
}
|
||||
|
||||
GatewayController gatewayController(m_appSettingsRepository->getGatewayEndpoint(), m_appSettingsRepository->isDevGatewayEnv(),
|
||||
apiDefs::requestTimeoutMsecs, m_appSettingsRepository->isStrictKillSwitchEnabled());
|
||||
|
||||
QByteArray responseBody;
|
||||
const amnezia::ErrorCode errorCode = gatewayController.post(QString("%1v1/config"), apiPayload, responseBody);
|
||||
if (errorCode != amnezia::ErrorCode::NoError) {
|
||||
const QString message = QStringLiteral("Gateway request failed with error code %1").arg(static_cast<int>(errorCode));
|
||||
ProxyLogger::getInstance().error(message);
|
||||
errorDescription = message;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
QJsonParseError responseError;
|
||||
const QJsonDocument responseDoc = QJsonDocument::fromJson(responseBody, &responseError);
|
||||
if (responseError.error != QJsonParseError::NoError || !responseDoc.isObject()) {
|
||||
const QString message = QStringLiteral("Failed to parse gateway response: %1").arg(responseError.errorString());
|
||||
ProxyLogger::getInstance().error(message);
|
||||
errorDescription = message;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
QString data = responseDoc.object().value(configKey::config).toString();
|
||||
if (data.isEmpty()) {
|
||||
const QString message = QStringLiteral("Gateway response lacks config payload");
|
||||
ProxyLogger::getInstance().error(message);
|
||||
errorDescription = message;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
data.replace("vpn://", "");
|
||||
QByteArray decoded = QByteArray::fromBase64(data.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
||||
if (decoded.isEmpty()) {
|
||||
const QString message = QStringLiteral("Gateway config payload is empty");
|
||||
ProxyLogger::getInstance().error(message);
|
||||
errorDescription = message;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const QByteArray uncompressed = qUncompress(decoded);
|
||||
if (!uncompressed.isEmpty()) {
|
||||
decoded = uncompressed;
|
||||
}
|
||||
|
||||
QJsonParseError configError;
|
||||
const QJsonDocument configDoc = QJsonDocument::fromJson(decoded, &configError);
|
||||
if (configError.error != QJsonParseError::NoError || !configDoc.isObject()) {
|
||||
const QString message = QStringLiteral("Failed to parse gateway config JSON: %1").arg(configError.errorString());
|
||||
ProxyLogger::getInstance().error(message);
|
||||
errorDescription = message;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto serializedConfig = extractSerializedXrayConfig(configDoc.object());
|
||||
if (!serializedConfig || serializedConfig->isEmpty()) {
|
||||
const QString message = QStringLiteral("Gateway response lacks Xray last_config payload");
|
||||
ProxyLogger::getInstance().error(message);
|
||||
errorDescription = message;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
ProxyLogger::getInstance().info("Fetched Xray config from gateway");
|
||||
return serializedConfig;
|
||||
}
|
||||
|
||||
QString ConfigManager::tempDirectory() const
|
||||
{
|
||||
const QString baseDir = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
|
||||
if (baseDir.isEmpty()) {
|
||||
return QDir::temp().filePath(QStringLiteral("amnezia_local_proxy"));
|
||||
}
|
||||
return QDir(baseDir).filePath(QStringLiteral("local_proxy"));
|
||||
}
|
||||
37
client/core/local-proxy/configmanager.h
Normal file
37
client/core/local-proxy/configmanager.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
|
||||
class SecureServersRepository;
|
||||
class SecureAppSettingsRepository;
|
||||
|
||||
class ConfigManager {
|
||||
public:
|
||||
struct ConfigData {
|
||||
QString ownerId;
|
||||
QString serverName;
|
||||
QString serializedConfig;
|
||||
QJsonObject parsedConfig;
|
||||
};
|
||||
|
||||
ConfigManager(SecureServersRepository *serversRepository, SecureAppSettingsRepository *appSettingsRepository);
|
||||
|
||||
std::optional<ConfigData> buildConfig(QString &errorDescription) const;
|
||||
std::optional<ConfigData> buildConfigWithFetch(QString &errorDescription) const;
|
||||
bool writeTempConfig(const QString &serializedConfig, QString &configPath, QString &errorDescription) const;
|
||||
bool removeTempConfig() const;
|
||||
QString tempConfigPath() const;
|
||||
|
||||
private:
|
||||
std::optional<QString> extractSerializedXrayConfig(const QJsonObject &server) const;
|
||||
std::optional<QString> fetchSerializedXrayConfigFromGateway(const QJsonObject &server, QString &errorDescription) const;
|
||||
QString tempDirectory() const;
|
||||
bool applyProxyPortToConfig(QJsonObject &config, int port) const;
|
||||
QString serializeConfig(const QJsonObject &config) const;
|
||||
|
||||
SecureServersRepository *m_serversRepository;
|
||||
SecureAppSettingsRepository *m_appSettingsRepository;
|
||||
};
|
||||
190
client/core/local-proxy/httpapi.cpp
Normal file
190
client/core/local-proxy/httpapi.cpp
Normal file
@@ -0,0 +1,190 @@
|
||||
#include "httpapi.h"
|
||||
#include "proxylogger.h"
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QHostAddress>
|
||||
#include <optional>
|
||||
|
||||
namespace {
|
||||
|
||||
std::optional<int> extractInboundPort(const QJsonObject &config)
|
||||
{
|
||||
if (!config.contains("inbounds") || !config["inbounds"].isArray()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const QJsonArray inbounds = config["inbounds"].toArray();
|
||||
if (inbounds.isEmpty() || !inbounds.at(0).isObject()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const QJsonObject firstInbound = inbounds.at(0).toObject();
|
||||
if (!firstInbound.contains("port")) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
return firstInbound.value("port").toInt();
|
||||
}
|
||||
|
||||
QJsonValue proxyPortValue(const std::optional<int> &port)
|
||||
{
|
||||
if (port.has_value()) {
|
||||
return QJsonValue(*port);
|
||||
}
|
||||
return QJsonValue::Null;
|
||||
}
|
||||
|
||||
QHttpServerResponse makeServiceUnavailablePingResponse()
|
||||
{
|
||||
QJsonObject payload{
|
||||
{"status", "error"},
|
||||
{"proxyPort", QJsonValue::Null}
|
||||
};
|
||||
return QHttpServerResponse(payload, QHttpServerResponse::StatusCode::ServiceUnavailable);
|
||||
}
|
||||
|
||||
QHttpServerResponse makeServiceUnavailableStatusResponse()
|
||||
{
|
||||
QJsonObject payload{
|
||||
{"status", "error"}
|
||||
};
|
||||
return QHttpServerResponse(payload, QHttpServerResponse::StatusCode::ServiceUnavailable);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
HttpApi::HttpApi(QWeakPointer<IProxyService> service, QObject* parent)
|
||||
: QObject(parent)
|
||||
, m_tcpServer(new QTcpServer(this))
|
||||
, m_service(service)
|
||||
{
|
||||
ProxyLogger::getInstance().debug("HttpApi initialized");
|
||||
}
|
||||
|
||||
HttpApi::~HttpApi()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
bool HttpApi::start(quint16 port)
|
||||
{
|
||||
ProxyLogger::getInstance().info(QString("Starting HTTP API server on port %1").arg(port));
|
||||
|
||||
if (!m_tcpServer->listen(QHostAddress::LocalHost, port)) {
|
||||
ProxyLogger::getInstance().error(QString("Failed to start HTTP API server on port %1").arg(port));
|
||||
return false;
|
||||
}
|
||||
|
||||
setupRoutes();
|
||||
m_server.bind(m_tcpServer.data());
|
||||
|
||||
ProxyLogger::getInstance().info(QString("HTTP API server is running on localhost:%1").arg(m_tcpServer->serverPort()));
|
||||
ProxyLogger::getInstance().debug("Available endpoints:\n"
|
||||
" POST /api/v1/up\n"
|
||||
" POST /api/v1/down\n"
|
||||
" GET /api/v1/ping");
|
||||
return true;
|
||||
}
|
||||
|
||||
void HttpApi::stop()
|
||||
{
|
||||
ProxyLogger::getInstance().info("Stopping HTTP API server");
|
||||
if (m_tcpServer) {
|
||||
m_tcpServer->close();
|
||||
}
|
||||
}
|
||||
|
||||
void HttpApi::setupRoutes()
|
||||
{
|
||||
ProxyLogger::getInstance().debug("Setting up HTTP API routes");
|
||||
|
||||
m_server.route("/api/v1/up", QHttpServerRequest::Method::Post,
|
||||
[this] {
|
||||
ProxyLogger::getInstance().debug("Handling POST /api/v1/up request");
|
||||
return handlePostUp();
|
||||
});
|
||||
|
||||
m_server.route("/api/v1/down", QHttpServerRequest::Method::Post,
|
||||
[this] {
|
||||
ProxyLogger::getInstance().debug("Handling POST /api/v1/down request");
|
||||
return handlePostDown();
|
||||
});
|
||||
|
||||
m_server.route("/api/v1/ping", QHttpServerRequest::Method::Get,
|
||||
[this] {
|
||||
ProxyLogger::getInstance().debug("Handling GET /api/v1/ping request");
|
||||
return handleGetPing();
|
||||
});
|
||||
}
|
||||
|
||||
QHttpServerResponse HttpApi::handlePostUp()
|
||||
{
|
||||
if (auto service = m_service.lock()) {
|
||||
const bool started = service->startXray();
|
||||
QJsonObject response;
|
||||
response["status"] = started ? "ok" : "error";
|
||||
|
||||
const auto port = started ? extractInboundPort(service->getConfig()) : std::optional<int>{};
|
||||
response["proxyPort"] = proxyPortValue(port);
|
||||
|
||||
if (started) {
|
||||
if (port.has_value()) {
|
||||
ProxyLogger::getInstance().info(QString("Xray process started on port %1").arg(*port));
|
||||
} else {
|
||||
ProxyLogger::getInstance().warning("Xray started but inbound port is unknown (local proxy owner may be missing)");
|
||||
}
|
||||
} else {
|
||||
ProxyLogger::getInstance().warning("Failed to start Xray process via HTTP API");
|
||||
}
|
||||
|
||||
return QHttpServerResponse(response);
|
||||
}
|
||||
|
||||
ProxyLogger::getInstance().error("Service unavailable: proxy backend is not initialized");
|
||||
return makeServiceUnavailablePingResponse();
|
||||
}
|
||||
|
||||
QHttpServerResponse HttpApi::handlePostDown()
|
||||
{
|
||||
if (auto service = m_service.lock()) {
|
||||
const bool stopped = service->stopXray();
|
||||
QJsonObject response;
|
||||
response["status"] = stopped ? "ok" : "error";
|
||||
if (!stopped) {
|
||||
ProxyLogger::getInstance().warning("Failed to stop Xray process via HTTP API");
|
||||
} else {
|
||||
ProxyLogger::getInstance().info("Xray process stopped via HTTP API");
|
||||
}
|
||||
|
||||
return QHttpServerResponse(response);
|
||||
}
|
||||
|
||||
ProxyLogger::getInstance().error("Service unavailable: proxy backend is not initialized");
|
||||
return makeServiceUnavailableStatusResponse();
|
||||
}
|
||||
|
||||
QHttpServerResponse HttpApi::handleGetPing() const
|
||||
{
|
||||
if (auto service = m_service.lock()) {
|
||||
QJsonObject response;
|
||||
response["status"] = "ok";
|
||||
const bool isRunning = service->isXrayRunning();
|
||||
if (isRunning) {
|
||||
const auto port = extractInboundPort(service->getConfig());
|
||||
response["proxyPort"] = proxyPortValue(port);
|
||||
if (port.has_value()) {
|
||||
ProxyLogger::getInstance().debug(QString("Xray port: %1").arg(*port));
|
||||
} else {
|
||||
ProxyLogger::getInstance().warning("Unable to detect inbound port while Xray is running");
|
||||
}
|
||||
} else {
|
||||
response["proxyPort"] = QJsonValue::Null;
|
||||
ProxyLogger::getInstance().debug("Xray is not running");
|
||||
}
|
||||
|
||||
return QHttpServerResponse(response);
|
||||
}
|
||||
|
||||
ProxyLogger::getInstance().error("Service unavailable: proxy backend is not initialized");
|
||||
return makeServiceUnavailablePingResponse();
|
||||
}
|
||||
32
client/core/local-proxy/httpapi.h
Normal file
32
client/core/local-proxy/httpapi.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QScopedPointer>
|
||||
#include <QHttpServer>
|
||||
#include <QHttpServerRequest>
|
||||
#include <QHttpServerResponse>
|
||||
#include <QTcpServer>
|
||||
#include <QWeakPointer>
|
||||
#include "iproxyservice.h"
|
||||
|
||||
class HttpApi : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit HttpApi(QWeakPointer<IProxyService> service, QObject* parent = nullptr);
|
||||
~HttpApi();
|
||||
|
||||
bool start(quint16 port);
|
||||
void stop();
|
||||
|
||||
private:
|
||||
void setupRoutes();
|
||||
|
||||
QHttpServerResponse handlePostUp();
|
||||
QHttpServerResponse handlePostDown();
|
||||
QHttpServerResponse handleGetPing() const;
|
||||
|
||||
QHttpServer m_server;
|
||||
QScopedPointer<QTcpServer> m_tcpServer;
|
||||
QWeakPointer<IProxyService> m_service;
|
||||
};
|
||||
17
client/core/local-proxy/iproxyservice.h
Normal file
17
client/core/local-proxy/iproxyservice.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
|
||||
class IProxyService {
|
||||
public:
|
||||
virtual ~IProxyService() = default;
|
||||
|
||||
virtual QJsonObject getConfig() = 0;
|
||||
|
||||
virtual bool startXray() = 0;
|
||||
virtual bool stopXray() = 0;
|
||||
virtual bool isXrayRunning() const = 0;
|
||||
virtual qint64 getXrayProcessId() const = 0;
|
||||
virtual QString getXrayError() const = 0;
|
||||
};
|
||||
43
client/core/local-proxy/portavailabilityhelper.cpp
Normal file
43
client/core/local-proxy/portavailabilityhelper.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#include "portavailabilityhelper.h"
|
||||
|
||||
#include <QHostAddress>
|
||||
#include <QTcpServer>
|
||||
|
||||
namespace {
|
||||
constexpr int kProxyPortMin = 1024;
|
||||
constexpr int kProxyPortMax = 65535;
|
||||
}
|
||||
|
||||
bool PortAvailabilityHelper::isPortAvailable(int port)
|
||||
{
|
||||
if (port < kProxyPortMin || port > kProxyPortMax) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QTcpServer server;
|
||||
const bool success = server.listen(QHostAddress::LocalHost, static_cast<quint16>(port));
|
||||
server.close();
|
||||
return success;
|
||||
}
|
||||
|
||||
std::optional<int> PortAvailabilityHelper::findFirstAvailablePort(int startPort, int endPort)
|
||||
{
|
||||
if (startPort < kProxyPortMin) {
|
||||
startPort = kProxyPortMin;
|
||||
}
|
||||
if (endPort > kProxyPortMax) {
|
||||
endPort = kProxyPortMax;
|
||||
}
|
||||
if (startPort > endPort) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
for (int port = startPort; port <= endPort; ++port) {
|
||||
if (isPortAvailable(port)) {
|
||||
return port;
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
11
client/core/local-proxy/portavailabilityhelper.h
Normal file
11
client/core/local-proxy/portavailabilityhelper.h
Normal file
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
class PortAvailabilityHelper
|
||||
{
|
||||
public:
|
||||
static bool isPortAvailable(int port);
|
||||
static std::optional<int> findFirstAvailablePort(int startPort, int endPort);
|
||||
};
|
||||
|
||||
133
client/core/local-proxy/proxylogger.cpp
Normal file
133
client/core/local-proxy/proxylogger.cpp
Normal file
@@ -0,0 +1,133 @@
|
||||
#include "proxylogger.h"
|
||||
#include <QDir>
|
||||
#include <QTextStream>
|
||||
|
||||
ProxyLogger::ProxyLogger() : m_maxFileSize(0), m_currentLevel(LogLevel::Info)
|
||||
{
|
||||
}
|
||||
|
||||
ProxyLogger::~ProxyLogger()
|
||||
{
|
||||
}
|
||||
|
||||
ProxyLogger& ProxyLogger::getInstance()
|
||||
{
|
||||
static ProxyLogger instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void ProxyLogger::init(const QString& logPath, qint64 maxFileSize)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
m_logPath = logPath;
|
||||
m_maxFileSize = maxFileSize;
|
||||
|
||||
// Create logs directory if it doesn't exist
|
||||
QDir dir = QFileInfo(m_logPath).dir();
|
||||
if (!dir.exists()) {
|
||||
dir.mkpath(".");
|
||||
}
|
||||
}
|
||||
|
||||
void ProxyLogger::setLogLevel(LogLevel level)
|
||||
{
|
||||
m_currentLevel = level;
|
||||
}
|
||||
|
||||
void ProxyLogger::log(LogLevel level, const QString& message)
|
||||
{
|
||||
logInternal(level, message);
|
||||
}
|
||||
|
||||
void ProxyLogger::debug(const QString& message)
|
||||
{
|
||||
logInternal(LogLevel::Debug, message);
|
||||
}
|
||||
|
||||
void ProxyLogger::info(const QString& message)
|
||||
{
|
||||
logInternal(LogLevel::Info, message);
|
||||
}
|
||||
|
||||
void ProxyLogger::warning(const QString& message)
|
||||
{
|
||||
logInternal(LogLevel::Warning, message);
|
||||
}
|
||||
|
||||
void ProxyLogger::error(const QString& message)
|
||||
{
|
||||
logInternal(LogLevel::Error, message);
|
||||
}
|
||||
|
||||
void ProxyLogger::logInternal(LogLevel level, const QString& message)
|
||||
{
|
||||
if (m_logPath.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (level < m_currentLevel) {
|
||||
return;
|
||||
}
|
||||
|
||||
QMutexLocker locker(&m_mutex);
|
||||
|
||||
checkRotation();
|
||||
|
||||
QFile file(m_logPath);
|
||||
if (!openLogFile(file)) {
|
||||
return;
|
||||
}
|
||||
|
||||
QTextStream stream(&file);
|
||||
QString timestamp = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz");
|
||||
stream << QString("[%1] [%2] %3\n").arg(timestamp, levelToString(level), message);
|
||||
stream.flush();
|
||||
file.close();
|
||||
}
|
||||
|
||||
QString ProxyLogger::levelToString(LogLevel level)
|
||||
{
|
||||
switch (level) {
|
||||
case LogLevel::Debug: return "DEBUG";
|
||||
case LogLevel::Info: return "INFO";
|
||||
case LogLevel::Warning: return "WARNING";
|
||||
case LogLevel::Error: return "ERROR";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
qint64 ProxyLogger::getCurrentFileSize() const
|
||||
{
|
||||
QFile file(m_logPath);
|
||||
if (file.exists()) {
|
||||
return file.size();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void ProxyLogger::checkRotation()
|
||||
{
|
||||
if (m_maxFileSize > 0 && getCurrentFileSize() >= m_maxFileSize) {
|
||||
// Delete the oldest file
|
||||
QFile::remove(QString("%1.%2").arg(m_logPath).arg(MAX_BACKUP_FILES));
|
||||
|
||||
// Shift existing files
|
||||
for (int i = MAX_BACKUP_FILES - 1; i >= 1; --i) {
|
||||
QString oldName = QString("%1.%2").arg(m_logPath).arg(i);
|
||||
QString newName = QString("%1.%2").arg(m_logPath).arg(i + 1);
|
||||
QFile::rename(oldName, newName);
|
||||
}
|
||||
|
||||
// Rename current file
|
||||
QFile::rename(m_logPath, m_logPath + ".1");
|
||||
}
|
||||
}
|
||||
|
||||
bool ProxyLogger::openLogFile(QFile& file)
|
||||
{
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
|
||||
qDebug() << "Failed to open log file:" << m_logPath;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
54
client/core/local-proxy/proxylogger.h
Normal file
54
client/core/local-proxy/proxylogger.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#ifndef LOCAL_PROXY_LOGGER_H
|
||||
#define LOCAL_PROXY_LOGGER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QFile>
|
||||
#include <QDateTime>
|
||||
#include <QMutex>
|
||||
#include <QString>
|
||||
|
||||
class ProxyLogger
|
||||
{
|
||||
|
||||
public:
|
||||
enum class LogLevel {
|
||||
Debug,
|
||||
Info,
|
||||
Warning,
|
||||
Error
|
||||
};
|
||||
|
||||
static ProxyLogger& getInstance();
|
||||
|
||||
void init(const QString& logPath, qint64 maxFileSize = 1024 * 1024 * 10); // 10MB by default
|
||||
void setLogLevel(LogLevel level);
|
||||
|
||||
// Main logging method
|
||||
void log(LogLevel level, const QString& message);
|
||||
|
||||
// Helper methods for convenience
|
||||
void debug(const QString& message);
|
||||
void info(const QString& message);
|
||||
void warning(const QString& message);
|
||||
void error(const QString& message);
|
||||
|
||||
private:
|
||||
ProxyLogger();
|
||||
~ProxyLogger();
|
||||
ProxyLogger(const ProxyLogger&) = delete;
|
||||
ProxyLogger& operator=(const ProxyLogger&) = delete;
|
||||
|
||||
void logInternal(LogLevel level, const QString& message);
|
||||
void checkRotation();
|
||||
QString levelToString(LogLevel level);
|
||||
bool openLogFile(QFile& file);
|
||||
qint64 getCurrentFileSize() const;
|
||||
|
||||
QString m_logPath;
|
||||
qint64 m_maxFileSize;
|
||||
LogLevel m_currentLevel;
|
||||
QMutex m_mutex;
|
||||
static const int MAX_BACKUP_FILES = 3;
|
||||
};
|
||||
|
||||
#endif // LOCAL_PROXY_LOGGER_H
|
||||
114
client/core/local-proxy/proxyserver.cpp
Normal file
114
client/core/local-proxy/proxyserver.cpp
Normal file
@@ -0,0 +1,114 @@
|
||||
#include "proxyserver.h"
|
||||
|
||||
#include "core/repositories/secureAppSettingsRepository.h"
|
||||
#include "core/repositories/secureServersRepository.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
ProxyServer::ProxyServer(SecureServersRepository *serversRepository, SecureAppSettingsRepository *appSettingsRepository,
|
||||
QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_appSettingsRepository(appSettingsRepository)
|
||||
, m_service(new ProxyService(serversRepository, appSettingsRepository, this))
|
||||
{
|
||||
m_lastRestartToken = m_appSettingsRepository ? m_appSettingsRepository->localProxyRestartToken() : 0;
|
||||
}
|
||||
|
||||
ProxyServer::~ProxyServer()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
bool ProxyServer::start(quint16 port)
|
||||
{
|
||||
if (m_isRunning) {
|
||||
if (m_currentApiPort == port) {
|
||||
qInfo() << "Local proxy: already running on port" << port;
|
||||
return true;
|
||||
}
|
||||
|
||||
qInfo() << "Local proxy: restarting on new port" << port;
|
||||
stop();
|
||||
}
|
||||
|
||||
m_api.reset(new HttpApi(m_service.toWeakRef()));
|
||||
const bool apiStarted = m_api->start(port);
|
||||
if (!apiStarted) {
|
||||
qWarning() << "Local proxy: port is busy:" << port;
|
||||
m_api.reset();
|
||||
m_isRunning = false;
|
||||
m_currentApiPort = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_isRunning = true;
|
||||
m_currentApiPort = port;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ProxyServer::stop()
|
||||
{
|
||||
stopXrayProcess();
|
||||
if (m_api) {
|
||||
m_api->stop();
|
||||
m_api.reset();
|
||||
}
|
||||
m_isRunning = false;
|
||||
m_currentApiPort = 0;
|
||||
m_currentProxyPort = 0;
|
||||
}
|
||||
|
||||
bool ProxyServer::startXrayProcess()
|
||||
{
|
||||
return m_service->startXray();
|
||||
}
|
||||
|
||||
void ProxyServer::stopXrayProcess()
|
||||
{
|
||||
m_service->stopXray();
|
||||
}
|
||||
|
||||
bool ProxyServer::syncSettings()
|
||||
{
|
||||
if (!m_isRunning) {
|
||||
qDebug() << "Local proxy: syncSettings called but server is not running";
|
||||
return false;
|
||||
}
|
||||
|
||||
const quint16 newProxyPort = m_appSettingsRepository ? m_appSettingsRepository->localProxyPort() : 0;
|
||||
const int restartToken = m_appSettingsRepository ? m_appSettingsRepository->localProxyRestartToken() : 0;
|
||||
const bool xrayRunning = m_service->isXrayRunning();
|
||||
|
||||
if (!xrayRunning) {
|
||||
qInfo() << "Local proxy: starting Xray on port" << newProxyPort;
|
||||
const bool started = startXrayProcess();
|
||||
if (started) {
|
||||
m_currentProxyPort = newProxyPort;
|
||||
m_lastRestartToken = restartToken;
|
||||
}
|
||||
return started;
|
||||
}
|
||||
|
||||
if (m_lastRestartToken != restartToken) {
|
||||
qInfo() << "Local proxy: restarting Xray due to config change token";
|
||||
const bool restarted = m_service->restartXray();
|
||||
if (restarted) {
|
||||
m_currentProxyPort = newProxyPort;
|
||||
m_lastRestartToken = restartToken;
|
||||
}
|
||||
return restarted;
|
||||
}
|
||||
|
||||
if (m_currentProxyPort != newProxyPort) {
|
||||
qInfo() << "Local proxy: proxy port changed from" << m_currentProxyPort << "to" << newProxyPort;
|
||||
const bool restarted = m_service->restartXray();
|
||||
if (restarted) {
|
||||
m_currentProxyPort = newProxyPort;
|
||||
m_lastRestartToken = restartToken;
|
||||
}
|
||||
return restarted;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
37
client/core/local-proxy/proxyserver.h
Normal file
37
client/core/local-proxy/proxyserver.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QScopedPointer>
|
||||
#include <QSharedPointer>
|
||||
|
||||
#include "httpapi.h"
|
||||
#include "proxyservice.h"
|
||||
|
||||
class SecureServersRepository;
|
||||
class SecureAppSettingsRepository;
|
||||
|
||||
class ProxyServer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ProxyServer(SecureServersRepository *serversRepository, SecureAppSettingsRepository *appSettingsRepository,
|
||||
QObject *parent = nullptr);
|
||||
~ProxyServer();
|
||||
|
||||
bool start(quint16 port = 49490);
|
||||
void stop();
|
||||
bool syncSettings();
|
||||
|
||||
private:
|
||||
bool startXrayProcess();
|
||||
void stopXrayProcess();
|
||||
|
||||
SecureAppSettingsRepository *m_appSettingsRepository;
|
||||
QScopedPointer<HttpApi> m_api;
|
||||
QSharedPointer<ProxyService> m_service;
|
||||
bool m_isRunning {false};
|
||||
quint16 m_currentApiPort {0};
|
||||
quint16 m_currentProxyPort {0};
|
||||
int m_lastRestartToken {0};
|
||||
};
|
||||
118
client/core/local-proxy/proxyservice.cpp
Normal file
118
client/core/local-proxy/proxyservice.cpp
Normal file
@@ -0,0 +1,118 @@
|
||||
#include "proxyservice.h"
|
||||
|
||||
#include "proxylogger.h"
|
||||
|
||||
namespace {
|
||||
|
||||
void logConfigError(const QString &errorMessage)
|
||||
{
|
||||
if (!errorMessage.isEmpty()) {
|
||||
ProxyLogger::getInstance().error(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ProxyService::ProxyService(SecureServersRepository *serversRepository, SecureAppSettingsRepository *appSettingsRepository,
|
||||
QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_configManager(new ConfigManager(serversRepository, appSettingsRepository))
|
||||
, m_xrayController(new XrayController())
|
||||
{
|
||||
ProxyLogger::getInstance().debug("ProxyService initialized");
|
||||
}
|
||||
|
||||
QJsonObject ProxyService::getConfig()
|
||||
{
|
||||
if (!m_cachedConfig.isEmpty()) {
|
||||
return m_cachedConfig;
|
||||
}
|
||||
|
||||
QString error;
|
||||
const auto configData = m_configManager->buildConfigWithFetch(error);
|
||||
if (!configData) {
|
||||
logConfigError(error);
|
||||
return {};
|
||||
}
|
||||
|
||||
m_cachedConfig = configData->parsedConfig;
|
||||
return m_cachedConfig;
|
||||
}
|
||||
|
||||
bool ProxyService::startXray()
|
||||
{
|
||||
ProxyLogger::getInstance().info("Starting Xray");
|
||||
|
||||
if (m_xrayController->isXrayRunning()) {
|
||||
ProxyLogger::getInstance().info("Xray is already running");
|
||||
return true;
|
||||
}
|
||||
|
||||
QString error;
|
||||
const auto configData = m_configManager->buildConfigWithFetch(error);
|
||||
if (!configData) {
|
||||
logConfigError(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool success = m_xrayController->start(configData->serializedConfig);
|
||||
if (success) {
|
||||
m_cachedConfig = configData->parsedConfig;
|
||||
ProxyLogger::getInstance().info("Xray started successfully");
|
||||
emit xrayStatusChanged(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
ProxyLogger::getInstance().error(QStringLiteral("Failed to start Xray: %1").arg(m_xrayController->getError()));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ProxyService::stopXray()
|
||||
{
|
||||
ProxyLogger::getInstance().info("Stopping Xray");
|
||||
const bool stopped = m_xrayController->stop();
|
||||
if (stopped) {
|
||||
ProxyLogger::getInstance().info("Xray stopped");
|
||||
emit xrayStatusChanged(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
ProxyLogger::getInstance().warning(QStringLiteral("Failed to stop Xray: %1").arg(m_xrayController->getError()));
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ProxyService::isXrayRunning() const
|
||||
{
|
||||
return m_xrayController->isXrayRunning();
|
||||
}
|
||||
|
||||
qint64 ProxyService::getXrayProcessId() const
|
||||
{
|
||||
return m_xrayController->getProcessId();
|
||||
}
|
||||
|
||||
QString ProxyService::getXrayError() const
|
||||
{
|
||||
return m_xrayController->getError();
|
||||
}
|
||||
|
||||
void ProxyService::clearCache()
|
||||
{
|
||||
m_cachedConfig = QJsonObject();
|
||||
ProxyLogger::getInstance().debug("ProxyService cache cleared");
|
||||
}
|
||||
|
||||
bool ProxyService::restartXray()
|
||||
{
|
||||
ProxyLogger::getInstance().info("Restarting Xray with updated config");
|
||||
clearCache();
|
||||
|
||||
if (m_xrayController->isXrayRunning()) {
|
||||
if (!stopXray()) {
|
||||
ProxyLogger::getInstance().error("Failed to stop Xray during restart, aborting");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return startXray();
|
||||
}
|
||||
39
client/core/local-proxy/proxyservice.h
Normal file
39
client/core/local-proxy/proxyservice.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include "configmanager.h"
|
||||
#include "iproxyservice.h"
|
||||
#include "xraycontroller.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QScopedPointer>
|
||||
#include <QJsonObject>
|
||||
|
||||
class SecureServersRepository;
|
||||
class SecureAppSettingsRepository;
|
||||
|
||||
class ProxyService : public QObject, public IProxyService {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ProxyService(SecureServersRepository *serversRepository, SecureAppSettingsRepository *appSettingsRepository,
|
||||
QObject *parent = nullptr);
|
||||
~ProxyService() = default;
|
||||
|
||||
QJsonObject getConfig() override;
|
||||
bool startXray() override;
|
||||
bool stopXray() override;
|
||||
bool isXrayRunning() const override;
|
||||
qint64 getXrayProcessId() const override;
|
||||
QString getXrayError() const override;
|
||||
|
||||
void clearCache();
|
||||
bool restartXray();
|
||||
|
||||
signals:
|
||||
void xrayStatusChanged(bool running);
|
||||
|
||||
private:
|
||||
QScopedPointer<ConfigManager> m_configManager;
|
||||
QScopedPointer<XrayController> m_xrayController;
|
||||
QJsonObject m_cachedConfig;
|
||||
};
|
||||
105
client/core/local-proxy/xraycontroller.cpp
Normal file
105
client/core/local-proxy/xraycontroller.cpp
Normal file
@@ -0,0 +1,105 @@
|
||||
#include "xraycontroller.h"
|
||||
|
||||
#include "proxylogger.h"
|
||||
#include "core/utils/ipcClient.h"
|
||||
|
||||
namespace {
|
||||
const QString kIpcUnavailableError = QStringLiteral("Failed to communicate with IPC service");
|
||||
}
|
||||
|
||||
XrayController::XrayController(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
ProxyLogger::getInstance().debug("XrayController initialized");
|
||||
}
|
||||
|
||||
XrayController::~XrayController()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
bool XrayController::start(const QString &configJson)
|
||||
{
|
||||
if (m_isRunning) {
|
||||
ProxyLogger::getInstance().info("Xray is already running");
|
||||
return true;
|
||||
}
|
||||
|
||||
ProxyLogger::getInstance().info("Request to start Xray via IPC");
|
||||
|
||||
m_lastError.clear();
|
||||
|
||||
if (configJson.trimmed().isEmpty()) {
|
||||
m_lastError = QStringLiteral("Config content is empty");
|
||||
ProxyLogger::getInstance().error(m_lastError);
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool ipcResult = IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
auto xrayStart = iface->xrayStart(configJson);
|
||||
if (!xrayStart.waitForFinished() || !xrayStart.returnValue()) {
|
||||
ProxyLogger::getInstance().warning("Failed to start Xray via IPC");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}, []() {
|
||||
return false;
|
||||
});
|
||||
|
||||
if (!ipcResult) {
|
||||
m_lastError = kIpcUnavailableError;
|
||||
ProxyLogger::getInstance().error(m_lastError);
|
||||
return false;
|
||||
}
|
||||
|
||||
ProxyLogger::getInstance().info("Xray start command sent to IPC service");
|
||||
m_isRunning = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool XrayController::stop()
|
||||
{
|
||||
if (!m_isRunning) {
|
||||
ProxyLogger::getInstance().debug("Skipping Xray stop via IPC: local proxy Xray is not running");
|
||||
return true;
|
||||
}
|
||||
|
||||
ProxyLogger::getInstance().info("Stopping Xray via IPC");
|
||||
|
||||
const bool ipcResult = IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
auto xrayStop = iface->xrayStop();
|
||||
if (!xrayStop.waitForFinished() || !xrayStop.returnValue()) {
|
||||
ProxyLogger::getInstance().warning("Failed to stop Xray via IPC");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}, []() {
|
||||
return false;
|
||||
});
|
||||
|
||||
if (!ipcResult) {
|
||||
m_lastError = kIpcUnavailableError;
|
||||
ProxyLogger::getInstance().warning(m_lastError);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_isRunning = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool XrayController::isXrayRunning() const
|
||||
{
|
||||
return m_isRunning;
|
||||
}
|
||||
|
||||
qint64 XrayController::getProcessId() const
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
QString XrayController::getError() const
|
||||
{
|
||||
return m_lastError;
|
||||
}
|
||||
22
client/core/local-proxy/xraycontroller.h
Normal file
22
client/core/local-proxy/xraycontroller.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
class XrayController : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit XrayController(QObject* parent = nullptr);
|
||||
~XrayController();
|
||||
|
||||
bool start(const QString& configJson);
|
||||
bool stop();
|
||||
bool isXrayRunning() const;
|
||||
qint64 getProcessId() const;
|
||||
QString getError() const;
|
||||
|
||||
private:
|
||||
bool m_isRunning {false};
|
||||
QString m_lastError;
|
||||
};
|
||||
@@ -144,6 +144,10 @@ QJsonObject ApiConfig::toJson() const
|
||||
obj[apiDefs::key::availableCountries] = availableCountries;
|
||||
}
|
||||
|
||||
if (!supportedProtocols.isEmpty()) {
|
||||
obj[apiDefs::key::supportedProtocols] = supportedProtocols;
|
||||
}
|
||||
|
||||
QJsonObject serviceInfoObj = serviceInfo.toJson();
|
||||
if (!serviceInfoObj.isEmpty()) {
|
||||
obj[apiDefs::key::serviceInfo] = serviceInfoObj;
|
||||
@@ -194,6 +198,7 @@ ApiConfig ApiConfig::fromJson(const QJsonObject& json)
|
||||
config.issuedConfigs = json.value(apiDefs::key::issuedConfigs).toInt(0);
|
||||
|
||||
config.availableCountries = json.value(apiDefs::key::availableCountries).toArray();
|
||||
config.supportedProtocols = json.value(apiDefs::key::supportedProtocols).toArray();
|
||||
|
||||
QJsonObject serviceInfoObj = json.value(apiDefs::key::serviceInfo).toObject();
|
||||
if (!serviceInfoObj.isEmpty()) {
|
||||
|
||||
@@ -34,6 +34,7 @@ struct ApiConfig
|
||||
int maxDeviceCount;
|
||||
int issuedConfigs;
|
||||
QJsonArray availableCountries;
|
||||
QJsonArray supportedProtocols;
|
||||
|
||||
struct ServiceInfo {
|
||||
bool isAdVisible = false;
|
||||
|
||||
@@ -121,11 +121,7 @@ QJsonObject ApiV2ServerConfig::toJson() const
|
||||
if (!dns2.isEmpty()) {
|
||||
obj[configKey::dns2] = dns2;
|
||||
}
|
||||
|
||||
if (!sendPayload.isEmpty()) {
|
||||
obj[configKey::sendPayload] = sendPayload;
|
||||
}
|
||||
|
||||
|
||||
if (crc > 0) {
|
||||
obj[configKey::crc] = crc;
|
||||
}
|
||||
@@ -169,7 +165,6 @@ ApiV2ServerConfig ApiV2ServerConfig::fromJson(const QJsonObject& json)
|
||||
|
||||
config.dns1 = json.value(configKey::dns1).toString();
|
||||
config.dns2 = json.value(configKey::dns2).toString();
|
||||
config.sendPayload = json.value(configKey::sendPayload).toArray();
|
||||
|
||||
config.crc = json.value(configKey::crc).toInt(0);
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#ifndef APIV2SERVERCONFIG_H
|
||||
#define APIV2SERVERCONFIG_H
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QMap>
|
||||
#include <QPair>
|
||||
@@ -29,7 +28,6 @@ struct ApiV2ServerConfig {
|
||||
DockerContainer defaultContainer;
|
||||
QString dns1;
|
||||
QString dns2;
|
||||
QJsonArray sendPayload;
|
||||
|
||||
QString name;
|
||||
bool nameOverriddenByUser = false;
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonArray>
|
||||
#include <QObject>
|
||||
#include <QSet>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "../../../core/utils/protocolEnum.h"
|
||||
#include "../../../core/protocols/protocolUtils.h"
|
||||
@@ -17,41 +13,6 @@ using namespace ProtocolUtils;
|
||||
namespace amnezia
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
template <typename T>
|
||||
QString awgVersionOf(const T &config)
|
||||
{
|
||||
auto hasValue = [](const QString &value) { return !value.trimmed().isEmpty(); };
|
||||
|
||||
const QStringList awg3Params = { config.headerProtectionKey, config.contentPaddingAddition,
|
||||
config.rekeyAfterTime, config.rekeyTimeout,
|
||||
config.rejectAfterTime, config.keepaliveTimeout,
|
||||
config.maxHandshakeAttempts };
|
||||
if (std::any_of(awg3Params.begin(), awg3Params.end(), hasValue)) {
|
||||
return protocols::awg::awgV3;
|
||||
}
|
||||
|
||||
const QStringList junkSizes = { config.cookieReplyPacketJunkSize, config.transportPacketJunkSize };
|
||||
const QStringList magicHeaders = { config.initPacketMagicHeader, config.responsePacketMagicHeader,
|
||||
config.underloadPacketMagicHeader, config.transportPacketMagicHeader };
|
||||
bool hasJunkSizes = std::any_of(junkSizes.begin(), junkSizes.end(), hasValue);
|
||||
bool hasHeaderRanges = std::any_of(magicHeaders.begin(), magicHeaders.end(),
|
||||
[](const QString &header) { return header.contains('-'); });
|
||||
if (hasJunkSizes || hasHeaderRanges) {
|
||||
return protocols::awg::awgV2;
|
||||
}
|
||||
|
||||
const QStringList specialJunk = { config.specialJunk1, config.specialJunk2, config.specialJunk3,
|
||||
config.specialJunk4, config.specialJunk5 };
|
||||
if (std::any_of(specialJunk.begin(), specialJunk.end(), hasValue)) {
|
||||
return protocols::awg::awgV1_5;
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
QJsonObject AwgServerConfig::toJson() const
|
||||
{
|
||||
QJsonObject obj;
|
||||
@@ -113,28 +74,6 @@ QJsonObject AwgServerConfig::toJson() const
|
||||
obj[configKey::specialJunk4] = specialJunk4;
|
||||
obj[configKey::specialJunk5] = specialJunk5;
|
||||
|
||||
if (!headerProtectionKey.isEmpty()) {
|
||||
obj[configKey::headerProtectionKey] = headerProtectionKey;
|
||||
}
|
||||
if (!contentPaddingAddition.isEmpty()) {
|
||||
obj[configKey::contentPaddingAddition] = contentPaddingAddition;
|
||||
}
|
||||
if (!rekeyAfterTime.isEmpty()) {
|
||||
obj[configKey::rekeyAfterTime] = rekeyAfterTime;
|
||||
}
|
||||
if (!rekeyTimeout.isEmpty()) {
|
||||
obj[configKey::rekeyTimeout] = rekeyTimeout;
|
||||
}
|
||||
if (!rejectAfterTime.isEmpty()) {
|
||||
obj[configKey::rejectAfterTime] = rejectAfterTime;
|
||||
}
|
||||
if (!keepaliveTimeout.isEmpty()) {
|
||||
obj[configKey::keepaliveTimeout] = keepaliveTimeout;
|
||||
}
|
||||
if (!maxHandshakeAttempts.isEmpty()) {
|
||||
obj[configKey::maxHandshakeAttempts] = maxHandshakeAttempts;
|
||||
}
|
||||
|
||||
if (isThirdPartyConfig) {
|
||||
obj[configKey::isThirdPartyConfig] = isThirdPartyConfig;
|
||||
}
|
||||
@@ -170,15 +109,7 @@ AwgServerConfig AwgServerConfig::fromJson(const QJsonObject& json)
|
||||
config.specialJunk3 = json.value(configKey::specialJunk3).toString();
|
||||
config.specialJunk4 = json.value(configKey::specialJunk4).toString();
|
||||
config.specialJunk5 = json.value(configKey::specialJunk5).toString();
|
||||
|
||||
config.headerProtectionKey = json.value(configKey::headerProtectionKey).toString();
|
||||
config.contentPaddingAddition = json.value(configKey::contentPaddingAddition).toString();
|
||||
config.rekeyAfterTime = json.value(configKey::rekeyAfterTime).toString();
|
||||
config.rekeyTimeout = json.value(configKey::rekeyTimeout).toString();
|
||||
config.rejectAfterTime = json.value(configKey::rejectAfterTime).toString();
|
||||
config.keepaliveTimeout = json.value(configKey::keepaliveTimeout).toString();
|
||||
config.maxHandshakeAttempts = json.value(configKey::maxHandshakeAttempts).toString();
|
||||
|
||||
|
||||
config.isThirdPartyConfig = json.value(configKey::isThirdPartyConfig).toBool(false);
|
||||
|
||||
return config;
|
||||
@@ -265,44 +196,16 @@ QJsonObject AwgClientConfig::toJson() const
|
||||
obj[configKey::transportPacketMagicHeader] = transportPacketMagicHeader;
|
||||
}
|
||||
|
||||
if (!specialJunk1.isEmpty()) {
|
||||
obj[configKey::specialJunk1] = specialJunk1;
|
||||
obj[configKey::specialJunk1] = specialJunk1;
|
||||
obj[configKey::specialJunk2] = specialJunk2;
|
||||
obj[configKey::specialJunk3] = specialJunk3;
|
||||
obj[configKey::specialJunk4] = specialJunk4;
|
||||
obj[configKey::specialJunk5] = specialJunk5;
|
||||
|
||||
if (isObfuscationEnabled) {
|
||||
obj[configKey::isObfuscationEnabled] = isObfuscationEnabled;
|
||||
}
|
||||
if (!specialJunk2.isEmpty()) {
|
||||
obj[configKey::specialJunk2] = specialJunk2;
|
||||
}
|
||||
if (!specialJunk3.isEmpty()) {
|
||||
obj[configKey::specialJunk3] = specialJunk3;
|
||||
}
|
||||
if (!specialJunk4.isEmpty()) {
|
||||
obj[configKey::specialJunk4] = specialJunk4;
|
||||
}
|
||||
if (!specialJunk5.isEmpty()) {
|
||||
obj[configKey::specialJunk5] = specialJunk5;
|
||||
}
|
||||
|
||||
if (!headerProtectionKey.isEmpty()) {
|
||||
obj[configKey::headerProtectionKey] = headerProtectionKey;
|
||||
}
|
||||
if (!contentPaddingAddition.isEmpty()) {
|
||||
obj[configKey::contentPaddingAddition] = contentPaddingAddition;
|
||||
}
|
||||
if (!rekeyAfterTime.isEmpty()) {
|
||||
obj[configKey::rekeyAfterTime] = rekeyAfterTime;
|
||||
}
|
||||
if (!rekeyTimeout.isEmpty()) {
|
||||
obj[configKey::rekeyTimeout] = rekeyTimeout;
|
||||
}
|
||||
if (!rejectAfterTime.isEmpty()) {
|
||||
obj[configKey::rejectAfterTime] = rejectAfterTime;
|
||||
}
|
||||
if (!keepaliveTimeout.isEmpty()) {
|
||||
obj[configKey::keepaliveTimeout] = keepaliveTimeout;
|
||||
}
|
||||
if (!maxHandshakeAttempts.isEmpty()) {
|
||||
obj[configKey::maxHandshakeAttempts] = maxHandshakeAttempts;
|
||||
}
|
||||
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
@@ -345,26 +248,21 @@ AwgClientConfig AwgClientConfig::fromJson(const QJsonObject& json)
|
||||
config.specialJunk3 = json.value(configKey::specialJunk3).toString();
|
||||
config.specialJunk4 = json.value(configKey::specialJunk4).toString();
|
||||
config.specialJunk5 = json.value(configKey::specialJunk5).toString();
|
||||
|
||||
config.headerProtectionKey = json.value(configKey::headerProtectionKey).toString();
|
||||
config.contentPaddingAddition = json.value(configKey::contentPaddingAddition).toString();
|
||||
config.rekeyAfterTime = json.value(configKey::rekeyAfterTime).toString();
|
||||
config.rekeyTimeout = json.value(configKey::rekeyTimeout).toString();
|
||||
config.rejectAfterTime = json.value(configKey::rejectAfterTime).toString();
|
||||
config.keepaliveTimeout = json.value(configKey::keepaliveTimeout).toString();
|
||||
config.maxHandshakeAttempts = json.value(configKey::maxHandshakeAttempts).toString();
|
||||
|
||||
config.isObfuscationEnabled = json.value(configKey::isObfuscationEnabled).toBool(false);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
QJsonObject AwgProtocolConfig::toJson() const
|
||||
{
|
||||
QJsonObject obj = serverConfig.toJson();
|
||||
|
||||
|
||||
if (clientConfig.has_value()) {
|
||||
QJsonObject clientJson = clientConfig->toJson();
|
||||
obj[configKey::lastConfig] = QString::fromUtf8(QJsonDocument(clientJson).toJson(QJsonDocument::Compact));
|
||||
}
|
||||
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
@@ -385,24 +283,6 @@ AwgProtocolConfig AwgProtocolConfig::fromJson(const QJsonObject& json)
|
||||
return config;
|
||||
}
|
||||
|
||||
QString AwgProtocolConfig::serverProtocolVersion() const
|
||||
{
|
||||
return awgVersionOf(serverConfig);
|
||||
}
|
||||
|
||||
QString AwgProtocolConfig::clientProtocolVersion() const
|
||||
{
|
||||
return clientConfig.has_value() ? awgVersionOf(clientConfig.value()) : QString();
|
||||
}
|
||||
|
||||
QString AwgProtocolConfig::protocolVersionString(const QString &version)
|
||||
{
|
||||
if (version == protocols::awg::awgV3) return QObject::tr(" (version 3)");
|
||||
if (version == protocols::awg::awgV2) return QObject::tr(" (version 2)");
|
||||
if (version == protocols::awg::awgV1_5) return QObject::tr(" (version 1.5)");
|
||||
return "";
|
||||
}
|
||||
|
||||
bool AwgProtocolConfig::hasClientConfig() const
|
||||
{
|
||||
return clientConfig.has_value();
|
||||
@@ -430,31 +310,24 @@ bool AwgServerConfig::hasEqualServerSettings(const AwgServerConfig& other) const
|
||||
transportPacketMagicHeader != other.transportPacketMagicHeader ||
|
||||
specialJunk1 != other.specialJunk1 || specialJunk2 != other.specialJunk2 ||
|
||||
specialJunk3 != other.specialJunk3 || specialJunk4 != other.specialJunk4 ||
|
||||
specialJunk5 != other.specialJunk5 ||
|
||||
cookieReplyPacketJunkSize != other.cookieReplyPacketJunkSize ||
|
||||
transportPacketJunkSize != other.transportPacketJunkSize ||
|
||||
headerProtectionKey != other.headerProtectionKey ||
|
||||
contentPaddingAddition != other.contentPaddingAddition ||
|
||||
rekeyAfterTime != other.rekeyAfterTime || rekeyTimeout != other.rekeyTimeout ||
|
||||
rejectAfterTime != other.rejectAfterTime || keepaliveTimeout != other.keepaliveTimeout ||
|
||||
maxHandshakeAttempts != other.maxHandshakeAttempts) {
|
||||
specialJunk5 != other.specialJunk5) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isV2 = protocolVersion == protocols::awg::awgV2;
|
||||
if (isV2) {
|
||||
if (cookieReplyPacketJunkSize != other.cookieReplyPacketJunkSize ||
|
||||
transportPacketJunkSize != other.transportPacketJunkSize) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AwgProtocolConfig::isHeadersEqual(const QString &h1, const QString &h2, const QString &h3, const QString &h4)
|
||||
{
|
||||
QSet<QString> uniqueHeaders;
|
||||
int filledHeaders = 0;
|
||||
for (const QString &header : { h1, h2, h3, h4 }) {
|
||||
if (!header.trimmed().isEmpty()) {
|
||||
++filledHeaders;
|
||||
uniqueHeaders.insert(header);
|
||||
}
|
||||
}
|
||||
return uniqueHeaders.size() != filledHeaders;
|
||||
return (h1 == h2) || (h1 == h3) || (h1 == h4) || (h2 == h3) || (h2 == h4) || (h3 == h4);
|
||||
}
|
||||
|
||||
bool AwgProtocolConfig::isPacketSizeEqual(int s1, int s2, int s3, int s4)
|
||||
|
||||
@@ -39,13 +39,6 @@ struct AwgServerConfig {
|
||||
QString specialJunk3;
|
||||
QString specialJunk4;
|
||||
QString specialJunk5;
|
||||
QString headerProtectionKey;
|
||||
QString contentPaddingAddition;
|
||||
QString rekeyAfterTime;
|
||||
QString rekeyTimeout;
|
||||
QString rejectAfterTime;
|
||||
QString keepaliveTimeout;
|
||||
QString maxHandshakeAttempts;
|
||||
bool isThirdPartyConfig = false;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
@@ -83,13 +76,8 @@ struct AwgClientConfig {
|
||||
QString specialJunk3;
|
||||
QString specialJunk4;
|
||||
QString specialJunk5;
|
||||
QString headerProtectionKey;
|
||||
QString contentPaddingAddition;
|
||||
QString rekeyAfterTime;
|
||||
QString rekeyTimeout;
|
||||
QString rejectAfterTime;
|
||||
QString keepaliveTimeout;
|
||||
QString maxHandshakeAttempts;
|
||||
bool isObfuscationEnabled = false;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
static AwgClientConfig fromJson(const QJsonObject& json);
|
||||
};
|
||||
@@ -101,10 +89,6 @@ struct AwgProtocolConfig {
|
||||
QJsonObject toJson() const;
|
||||
static AwgProtocolConfig fromJson(const QJsonObject& json);
|
||||
|
||||
QString serverProtocolVersion() const;
|
||||
QString clientProtocolVersion() const;
|
||||
static QString protocolVersionString(const QString &version);
|
||||
|
||||
bool hasClientConfig() const;
|
||||
void setClientConfig(const AwgClientConfig& config);
|
||||
void clearClientConfig();
|
||||
|
||||
@@ -99,6 +99,9 @@ bool TelemtProtocolConfig::equalsDockerDeploymentSettings(const TelemtProtocolCo
|
||||
const auto normTransport = [](const QString &t) {
|
||||
return t.isEmpty() ? QString(protocols::telemt::transportModeStandard) : t;
|
||||
};
|
||||
const auto normWorkersMode = [](const QString &m) {
|
||||
return m.isEmpty() ? QString(protocols::telemt::workersModeAuto) : m;
|
||||
};
|
||||
|
||||
if (normPort(port) != normPort(other.port)) {
|
||||
return false;
|
||||
@@ -130,9 +133,18 @@ bool TelemtProtocolConfig::equalsDockerDeploymentSettings(const TelemtProtocolCo
|
||||
if (userName != other.userName) {
|
||||
return false;
|
||||
}
|
||||
if (normWorkersMode(workersMode) != normWorkersMode(other.workersMode)) {
|
||||
return false;
|
||||
}
|
||||
if (workers != other.workers) {
|
||||
return false;
|
||||
}
|
||||
if (natEnabled != other.natEnabled) {
|
||||
return false;
|
||||
}
|
||||
if (natInternalIp != other.natInternalIp) {
|
||||
return false;
|
||||
}
|
||||
if (natExternalIp != other.natExternalIp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -105,17 +105,11 @@ QJsonObject WireGuardClientConfig::toJson() const
|
||||
if (!mtu.isEmpty()) {
|
||||
obj[configKey::mtu] = mtu;
|
||||
}
|
||||
|
||||
for (auto it = awgParams.constBegin(); it != awgParams.constEnd(); ++it) {
|
||||
if (!it.value().isEmpty()) {
|
||||
obj[it.key()] = it.value();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (isObfuscationEnabled) {
|
||||
obj[configKey::isObfuscationEnabled] = isObfuscationEnabled;
|
||||
}
|
||||
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
@@ -139,16 +133,9 @@ WireGuardClientConfig WireGuardClientConfig::fromJson(const QJsonObject& json)
|
||||
}
|
||||
config.persistentKeepAlive = json.value(configKey::persistentKeepAlive).toString();
|
||||
config.mtu = json.value(configKey::mtu).toString();
|
||||
|
||||
for (const QString &key : configKey::awgProtocolKeys()) {
|
||||
const QString value = json.value(key).toString();
|
||||
if (!value.isEmpty()) {
|
||||
config.awgParams.insert(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
config.isObfuscationEnabled = json.value(configKey::isObfuscationEnabled).toBool(false);
|
||||
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#define WIREGUARDPROTOCOLCONFIG_H
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <optional>
|
||||
@@ -37,10 +36,8 @@ struct WireGuardClientConfig {
|
||||
QStringList allowedIps;
|
||||
QString persistentKeepAlive;
|
||||
QString mtu;
|
||||
|
||||
QMap<QString, QString> awgParams;
|
||||
bool isObfuscationEnabled = false;
|
||||
|
||||
|
||||
QJsonObject toJson() const;
|
||||
static WireGuardClientConfig fromJson(const QJsonObject& json);
|
||||
};
|
||||
|
||||
@@ -81,6 +81,7 @@ QJsonObject XrayXhttpConfig::toJson() const
|
||||
if (!mode.isEmpty()) obj[configKey::xhttpMode] = mode;
|
||||
if (!host.isEmpty()) obj[configKey::xhttpHost] = host;
|
||||
if (!path.isEmpty()) obj[configKey::xhttpPath] = path;
|
||||
if (!headersTemplate.isEmpty()) obj[configKey::xhttpHeadersTemplate] = headersTemplate;
|
||||
if (!uplinkMethod.isEmpty()) obj[configKey::xhttpUplinkMethod] = uplinkMethod;
|
||||
obj[configKey::xhttpDisableGrpc] = disableGrpc;
|
||||
obj[configKey::xhttpDisableSse] = disableSse;
|
||||
@@ -115,6 +116,7 @@ namespace
|
||||
c.mode = QString();
|
||||
c.host = QString();
|
||||
c.path = QString();
|
||||
c.headersTemplate = QString();
|
||||
c.uplinkMethod = QString();
|
||||
c.disableGrpc = false;
|
||||
c.disableSse = false;
|
||||
@@ -153,6 +155,9 @@ XrayXhttpConfig XrayXhttpConfig::fromJson(const QJsonObject &json)
|
||||
if (json.contains(configKey::xhttpPath)) {
|
||||
c.path = json.value(configKey::xhttpPath).toString();
|
||||
}
|
||||
if (json.contains(configKey::xhttpHeadersTemplate)) {
|
||||
c.headersTemplate = json.value(configKey::xhttpHeadersTemplate).toString();
|
||||
}
|
||||
if (json.contains(configKey::xhttpUplinkMethod)) {
|
||||
c.uplinkMethod = json.value(configKey::xhttpUplinkMethod).toString();
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ struct XrayXhttpConfig {
|
||||
QString mode = protocols::xray::defaultXhttpMode; // Auto|Packet-up|Stream-up|Stream-one
|
||||
QString host = protocols::xray::defaultXhttpHost;
|
||||
QString path;
|
||||
QString headersTemplate = protocols::xray::defaultXhttpHeadersTemplate; // HTTP|None
|
||||
QString uplinkMethod = protocols::xray::defaultXhttpUplinkMethod; // POST|PUT|PATCH
|
||||
bool disableGrpc = true;
|
||||
bool disableSse = true;
|
||||
|
||||
@@ -64,11 +64,7 @@ QString getProtocolName(DockerContainer defaultContainer, const QMap<DockerConta
|
||||
const auto it = containers.constFind(defaultContainer);
|
||||
if (it != containers.cend()) {
|
||||
if (const AwgProtocolConfig *awg = it->getAwgProtocolConfig()) {
|
||||
QString version = awg->clientProtocolVersion();
|
||||
if (version.isEmpty()) {
|
||||
version = awg->serverProtocolVersion();
|
||||
}
|
||||
protocolVersion = AwgProtocolConfig::protocolVersionString(version);
|
||||
protocolVersion = ProtocolUtils::getProtocolVersionString(awg->toJson());
|
||||
if (defaultContainer == DockerContainer::Awg && !awg->serverConfig.isThirdPartyConfig) {
|
||||
containerName = QStringLiteral("AmneziaWG Legacy");
|
||||
}
|
||||
|
||||
@@ -222,18 +222,6 @@ ErrorCode OpenVpnProtocol::start()
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
// In "all except sites" mode the config uses redirect-gateway !ipv4, so OpenVPN
|
||||
// never reports net_route_v4_best_gw and m_routeGateway would stay empty
|
||||
const QString winGateway = NetworkUtilities::getGatewayAndIface().first;
|
||||
if (!winGateway.isEmpty()) {
|
||||
m_routeGateway = winGateway;
|
||||
qDebug() << "Set VPN route gateway" << m_routeGateway;
|
||||
} else {
|
||||
qWarning() << "Unable to detect physical default gateway";
|
||||
}
|
||||
#endif
|
||||
|
||||
uint mgmtPort = selectMgmtPort();
|
||||
qDebug() << "OpenVpnProtocol::start mgmt port selected:" << mgmtPort;
|
||||
|
||||
|
||||
@@ -209,3 +209,16 @@ QString ProtocolUtils::key_proto_config_path(Proto p)
|
||||
return protoToString(p) + "_config_path";
|
||||
}
|
||||
|
||||
QString ProtocolUtils::getProtocolVersion(const QJsonObject &protocolConfig)
|
||||
{
|
||||
return protocolConfig.value(configKey::protocolVersion).toString();
|
||||
}
|
||||
|
||||
QString ProtocolUtils::getProtocolVersionString(const QJsonObject &protocolConfig)
|
||||
{
|
||||
auto version = getProtocolVersion(protocolConfig);
|
||||
|
||||
if (version == protocols::awg::awgV2) return QObject::tr(" (version 2)");
|
||||
if (version == protocols::awg::awgV1_5) return QObject::tr(" (version 1.5)");
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -39,6 +39,8 @@ namespace amnezia
|
||||
QString key_proto_config_data(Proto p);
|
||||
QString key_proto_config_path(Proto p);
|
||||
|
||||
QString getProtocolVersion(const QJsonObject &protocolConfig);
|
||||
QString getProtocolVersionString(const QJsonObject &protocolConfig);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -68,6 +68,8 @@ ErrorCode XrayProtocol::start()
|
||||
{
|
||||
qDebug() << "XrayProtocol::start()";
|
||||
|
||||
m_teardownDone = false;
|
||||
|
||||
// Inject SOCKS5 auth into the inbound before starting xray.
|
||||
// Re-uses existing credentials if the config already has them (e.g. imported config).
|
||||
amnezia::serialization::inbounds::InboundCredentials creds;
|
||||
@@ -120,6 +122,17 @@ void XrayProtocol::stop()
|
||||
{
|
||||
qDebug() << "XrayProtocol::stop()";
|
||||
|
||||
// The destructor calls stop() as a safety net, but stop() is also called
|
||||
// explicitly on disconnect. Because stop() spins the event loop via
|
||||
// waitForFinished(), the local proxy may start its own xray on the shared
|
||||
// global engine in between. Running the teardown (xrayStop) twice would then
|
||||
// kill the proxy's just-started xray. Run the engine teardown only once.
|
||||
if (m_teardownDone) {
|
||||
qDebug() << "XrayProtocol::stop() teardown already done, skipping";
|
||||
return;
|
||||
}
|
||||
m_teardownDone = true;
|
||||
|
||||
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
auto disableKillSwitch = iface->disableKillSwitch();
|
||||
if (!disableKillSwitch.waitForFinished() || !disableKillSwitch.returnValue())
|
||||
|
||||
@@ -38,6 +38,11 @@ private:
|
||||
int m_tun2socksRetryCount = 0;
|
||||
static constexpr int maxTun2SocksRetries = 5;
|
||||
static constexpr int tun2socksRetryDelayMs = 400;
|
||||
|
||||
// Guards against tearing down the shared xray engine more than once.
|
||||
// The destructor also calls stop(); without this the second stop() would
|
||||
// call xrayStop() on the global engine that the local proxy may already own.
|
||||
bool m_teardownDone = false;
|
||||
};
|
||||
|
||||
#endif // XRAYPROTOCOL_H
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include <QJsonObject>
|
||||
#include <QUuid>
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "core/utils/errorCodes.h"
|
||||
#include "core/utils/routeModes.h"
|
||||
#include "core/utils/commonStructs.h"
|
||||
@@ -120,60 +122,34 @@ QVariantMap SecureAppSettingsRepository::vpnSites(RouteMode mode) const
|
||||
return value("Conf/" + routeModeString(mode)).toMap();
|
||||
}
|
||||
|
||||
QStringList SecureAppSettingsRepository::siteIpList(const QVariant &value)
|
||||
{
|
||||
// QVariant::toStringList() handles both a QStringList/QVariantList and a single QString
|
||||
// (a single string is returned as a one-element list), which covers the legacy format.
|
||||
QStringList result = value.toStringList();
|
||||
result.removeAll(QString());
|
||||
result.removeDuplicates();
|
||||
return result;
|
||||
}
|
||||
|
||||
void SecureAppSettingsRepository::setVpnSites(RouteMode mode, const QVariantMap &sites)
|
||||
{
|
||||
setValue("Conf/" + routeModeString(mode), sites);
|
||||
}
|
||||
|
||||
bool SecureAppSettingsRepository::addVpnSite(RouteMode mode, const QString &site, const QStringList &ips)
|
||||
bool SecureAppSettingsRepository::addVpnSite(RouteMode mode, const QString &site, const QString &ip)
|
||||
{
|
||||
QVariantMap sites = vpnSites(mode);
|
||||
const bool siteExisted = sites.contains(site);
|
||||
|
||||
if (siteExisted && ips.isEmpty())
|
||||
if (sites.contains(site) && ip.isEmpty())
|
||||
return false;
|
||||
|
||||
QStringList mergedIps = siteIpList(sites.value(site));
|
||||
bool changed = !siteExisted;
|
||||
for (const QString &ip : ips) {
|
||||
if (!ip.isEmpty() && !mergedIps.contains(ip)) {
|
||||
mergedIps.append(ip);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!changed)
|
||||
return false;
|
||||
|
||||
sites.insert(site, mergedIps);
|
||||
sites.insert(site, ip);
|
||||
setVpnSites(mode, sites);
|
||||
emit sitesChanged(mode);
|
||||
return true;
|
||||
}
|
||||
|
||||
void SecureAppSettingsRepository::addVpnSites(RouteMode mode, const QMap<QString, QStringList> &sites)
|
||||
void SecureAppSettingsRepository::addVpnSites(RouteMode mode, const QMap<QString, QString> &sites)
|
||||
{
|
||||
QVariantMap allSites = vpnSites(mode);
|
||||
for (auto i = sites.constBegin(); i != sites.constEnd(); ++i) {
|
||||
const QString &site = i.key();
|
||||
const QString &ip = i.value();
|
||||
|
||||
QStringList mergedIps = siteIpList(allSites.value(site));
|
||||
for (const QString &ip : i.value()) {
|
||||
if (!ip.isEmpty() && !mergedIps.contains(ip))
|
||||
mergedIps.append(ip);
|
||||
}
|
||||
if (allSites.contains(site) && allSites.value(site) == ip)
|
||||
continue;
|
||||
|
||||
allSites.insert(site, mergedIps);
|
||||
allSites.insert(site, ip);
|
||||
}
|
||||
|
||||
setVpnSites(mode, allSites);
|
||||
@@ -306,24 +282,6 @@ void SecureAppSettingsRepository::toggleDevGatewayEnv(bool enabled)
|
||||
setValue("Conf/devGatewayEnv", enabled);
|
||||
}
|
||||
|
||||
QByteArray SecureAppSettingsRepository::readGatewayProxyUrls(const QString &cacheKey) const
|
||||
{
|
||||
if (cacheKey.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return value(QStringLiteral("Conf/proxyUrls/") + cacheKey).toByteArray();
|
||||
}
|
||||
|
||||
void SecureAppSettingsRepository::writeGatewayProxyUrls(const QString &cacheKey, const QByteArray &proxyUrlsEncrypted)
|
||||
{
|
||||
if (cacheKey.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setValue(QStringLiteral("Conf/proxyUrls/") + cacheKey, proxyUrlsEncrypted);
|
||||
}
|
||||
|
||||
bool SecureAppSettingsRepository::isKillSwitchEnabled() const
|
||||
{
|
||||
return value("Conf/killSwitchEnabled", true).toBool();
|
||||
@@ -484,3 +442,59 @@ void SecureAppSettingsRepository::setXraySavedConfigs(const QByteArray &data)
|
||||
{
|
||||
setValue("Xray/savedConfigs", data);
|
||||
}
|
||||
|
||||
QString SecureAppSettingsRepository::localProxyOwnerId() const
|
||||
{
|
||||
return value("Conf/localProxyOwnerId", "").toString();
|
||||
}
|
||||
|
||||
void SecureAppSettingsRepository::setLocalProxyOwnerId(const QString &serverId)
|
||||
{
|
||||
setValue("Conf/localProxyOwnerId", serverId);
|
||||
emit localProxySettingsChanged();
|
||||
}
|
||||
|
||||
quint16 SecureAppSettingsRepository::localProxyPort() const
|
||||
{
|
||||
return static_cast<quint16>(value("Conf/localProxyPort", 10808).toUInt());
|
||||
}
|
||||
|
||||
void SecureAppSettingsRepository::setLocalProxyPort(quint16 port)
|
||||
{
|
||||
setValue("Conf/localProxyPort", port);
|
||||
emit localProxySettingsChanged();
|
||||
}
|
||||
|
||||
bool SecureAppSettingsRepository::isLocalProxyPortUserDefined() const
|
||||
{
|
||||
return value("Conf/localProxyPortUserDefined", false).toBool();
|
||||
}
|
||||
|
||||
void SecureAppSettingsRepository::setLocalProxyPortUserDefined(bool userDefined)
|
||||
{
|
||||
setValue("Conf/localProxyPortUserDefined", userDefined);
|
||||
}
|
||||
|
||||
bool SecureAppSettingsRepository::isLocalProxyHttpEnabled() const
|
||||
{
|
||||
return value("Conf/localProxyHttpEnabled", false).toBool();
|
||||
}
|
||||
|
||||
void SecureAppSettingsRepository::setLocalProxyHttpEnabled(bool enabled)
|
||||
{
|
||||
setValue("Conf/localProxyHttpEnabled", enabled);
|
||||
emit localProxySettingsChanged();
|
||||
}
|
||||
|
||||
int SecureAppSettingsRepository::localProxyRestartToken() const
|
||||
{
|
||||
return value("Conf/localProxyRestartToken", 0).toInt();
|
||||
}
|
||||
|
||||
void SecureAppSettingsRepository::bumpLocalProxyRestartToken()
|
||||
{
|
||||
const int current = localProxyRestartToken();
|
||||
const int next = (current == std::numeric_limits<int>::max()) ? 0 : (current + 1);
|
||||
setValue("Conf/localProxyRestartToken", next);
|
||||
emit localProxySettingsChanged();
|
||||
}
|
||||
|
||||
@@ -38,15 +38,11 @@ public:
|
||||
|
||||
RouteMode routeMode() const;
|
||||
void setRouteMode(RouteMode mode);
|
||||
bool addVpnSite(RouteMode mode, const QString &site, const QStringList &ips = {});
|
||||
void addVpnSites(RouteMode mode, const QMap<QString, QStringList> &sites);
|
||||
bool addVpnSite(RouteMode mode, const QString &site, const QString &ip = "");
|
||||
void addVpnSites(RouteMode mode, const QMap<QString, QString> &sites);
|
||||
void removeVpnSite(RouteMode mode, const QString &site);
|
||||
void removeAllVpnSites(RouteMode mode);
|
||||
QVariantMap vpnSites(RouteMode mode) const;
|
||||
|
||||
// Normalizes a stored vpn site value into a list of IPs.
|
||||
// Supports both the legacy format (a single IP string) and the current one (a list of IPs).
|
||||
static QStringList siteIpList(const QVariant &value);
|
||||
bool isSitesSplitTunnelingEnabled() const;
|
||||
void setSitesSplitTunnelingEnabled(bool enabled);
|
||||
|
||||
@@ -63,9 +59,7 @@ public:
|
||||
void setDevGatewayEndpoint();
|
||||
bool isDevGatewayEnv(bool isTestPurchase = false) const;
|
||||
void toggleDevGatewayEnv(bool enabled);
|
||||
QByteArray readGatewayProxyUrls(const QString &cacheKey) const;
|
||||
void writeGatewayProxyUrls(const QString &cacheKey, const QByteArray &proxyUrlsEncrypted);
|
||||
|
||||
|
||||
bool isKillSwitchEnabled() const;
|
||||
void setKillSwitchEnabled(bool enabled);
|
||||
bool isStrictKillSwitchEnabled() const;
|
||||
@@ -99,6 +93,18 @@ public:
|
||||
QByteArray xraySavedConfigs() const;
|
||||
void setXraySavedConfigs(const QByteArray &data);
|
||||
|
||||
// Local proxy
|
||||
QString localProxyOwnerId() const;
|
||||
void setLocalProxyOwnerId(const QString &serverId);
|
||||
quint16 localProxyPort() const;
|
||||
void setLocalProxyPort(quint16 port);
|
||||
bool isLocalProxyPortUserDefined() const;
|
||||
void setLocalProxyPortUserDefined(bool userDefined);
|
||||
bool isLocalProxyHttpEnabled() const;
|
||||
void setLocalProxyHttpEnabled(bool enabled);
|
||||
int localProxyRestartToken() const;
|
||||
void bumpLocalProxyRestartToken();
|
||||
|
||||
signals:
|
||||
void appLanguageChanged(QLocale locale);
|
||||
void allowedDnsServersChanged(const QStringList &servers);
|
||||
@@ -112,6 +118,8 @@ signals:
|
||||
void saveLogsChanged(bool enabled);
|
||||
void screenshotsEnabledChanged(bool enabled);
|
||||
void settingsCleared();
|
||||
void localProxySettingsChanged();
|
||||
void localProxyStartFailed(const QString &message);
|
||||
|
||||
private:
|
||||
void setVpnSites(RouteMode mode, const QVariantMap &sites);
|
||||
|
||||
@@ -309,6 +309,15 @@ serverConfigUtils::ConfigType SecureServersRepository::serverKind(const QString
|
||||
return serverConfigUtils::configTypeFromJson(withoutStorageServerId(it.value()));
|
||||
}
|
||||
|
||||
std::optional<QJsonObject> SecureServersRepository::serverJsonById(const QString &serverId) const
|
||||
{
|
||||
const auto it = m_serverJsonById.constFind(serverId);
|
||||
if (it == m_serverJsonById.constEnd()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return it.value();
|
||||
}
|
||||
|
||||
std::optional<SelfHostedAdminServerConfig> SecureServersRepository::selfHostedAdminConfig(const QString &serverId) const
|
||||
{
|
||||
const auto it = m_serverJsonById.constFind(serverId);
|
||||
|
||||
@@ -31,6 +31,8 @@ public:
|
||||
void removeServer(const QString &serverId);
|
||||
serverConfigUtils::ConfigType serverKind(const QString &serverId) const;
|
||||
|
||||
std::optional<QJsonObject> serverJsonById(const QString &serverId) const;
|
||||
|
||||
std::optional<SelfHostedAdminServerConfig> selfHostedAdminConfig(const QString &serverId) const;
|
||||
std::optional<SelfHostedUserServerConfig> selfHostedUserConfig(const QString &serverId) const;
|
||||
std::optional<NativeServerConfig> nativeConfig(const QString &serverId) const;
|
||||
|
||||
@@ -18,8 +18,8 @@ namespace apiDefs
|
||||
constexpr QLatin1String stackType("stack_type");
|
||||
constexpr QLatin1String cliVersion("cli_version");
|
||||
constexpr QLatin1String cliName("cli_name");
|
||||
constexpr QLatin1String supportedProtocols("supported_protocols");
|
||||
constexpr QLatin1String availableCountries("available_countries");
|
||||
constexpr QLatin1String availableProtocols("available_protocols");
|
||||
constexpr QLatin1String installationUuid("installation_uuid");
|
||||
constexpr QLatin1String uuid("installation_uuid");
|
||||
constexpr QLatin1String osVersion("os_version");
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#define CONFIGKEYS_H
|
||||
|
||||
#include <QLatin1String>
|
||||
#include <QStringList>
|
||||
|
||||
namespace amnezia
|
||||
{
|
||||
@@ -26,13 +25,6 @@ namespace amnezia
|
||||
constexpr QLatin1String config("config");
|
||||
constexpr QLatin1String configVersion("config_version");
|
||||
|
||||
constexpr QLatin1String sendPayload("send_payload");
|
||||
constexpr QLatin1String sendPayloadEndpoint("endpoint");
|
||||
constexpr QLatin1String sendPayloadTimeoutMs("timeout_ms");
|
||||
constexpr QLatin1String sendPayloadProtocol("protocol");
|
||||
constexpr QLatin1String sendPayloadData("payload");
|
||||
constexpr QLatin1String sendPayloadExpectedResponse("expected_response");
|
||||
|
||||
constexpr QLatin1String containers("containers");
|
||||
constexpr QLatin1String container("container");
|
||||
constexpr QLatin1String defaultContainer("defaultContainer");
|
||||
@@ -71,8 +63,6 @@ namespace amnezia
|
||||
|
||||
constexpr QLatin1String lastConfig("last_config");
|
||||
|
||||
constexpr QLatin1String protocolVersion("protocol_version");
|
||||
|
||||
constexpr QLatin1String isThirdPartyConfig("isThirdPartyConfig");
|
||||
constexpr QLatin1String isObfuscationEnabled("isObfuscationEnabled");
|
||||
|
||||
@@ -93,40 +83,7 @@ namespace amnezia
|
||||
constexpr QLatin1String specialJunk4("I4");
|
||||
constexpr QLatin1String specialJunk5("I5");
|
||||
|
||||
constexpr QLatin1String headerProtectionKey("HeaderProtectionKey");
|
||||
constexpr QLatin1String contentPaddingAddition("ContentPaddingAddition");
|
||||
constexpr QLatin1String rekeyAfterTime("RekeyAfterTime");
|
||||
constexpr QLatin1String rekeyTimeout("RekeyTimeout");
|
||||
constexpr QLatin1String rejectAfterTime("RejectAfterTime");
|
||||
constexpr QLatin1String keepaliveTimeout("KeepaliveTimeout");
|
||||
constexpr QLatin1String maxHandshakeAttempts("MaxHandshakeAttempts");
|
||||
|
||||
inline QStringList awgProtocolKeys()
|
||||
{
|
||||
return { junkPacketCount,
|
||||
junkPacketMinSize,
|
||||
junkPacketMaxSize,
|
||||
initPacketJunkSize,
|
||||
responsePacketJunkSize,
|
||||
cookieReplyPacketJunkSize,
|
||||
transportPacketJunkSize,
|
||||
initPacketMagicHeader,
|
||||
responsePacketMagicHeader,
|
||||
underloadPacketMagicHeader,
|
||||
transportPacketMagicHeader,
|
||||
specialJunk1,
|
||||
specialJunk2,
|
||||
specialJunk3,
|
||||
specialJunk4,
|
||||
specialJunk5,
|
||||
headerProtectionKey,
|
||||
contentPaddingAddition,
|
||||
rekeyAfterTime,
|
||||
rekeyTimeout,
|
||||
rejectAfterTime,
|
||||
keepaliveTimeout,
|
||||
maxHandshakeAttempts };
|
||||
}
|
||||
constexpr QLatin1String protocolVersion("protocol_version");
|
||||
|
||||
constexpr QLatin1String openvpn("openvpn");
|
||||
constexpr QLatin1String wireguard("wireguard");
|
||||
@@ -186,6 +143,7 @@ namespace amnezia
|
||||
constexpr QLatin1String xhttpMode("xhttp_mode"); // Auto | Packet-up | Stream-up | Stream-one
|
||||
constexpr QLatin1String xhttpHost("xhttp_host");
|
||||
constexpr QLatin1String xhttpPath("xhttp_path");
|
||||
constexpr QLatin1String xhttpHeadersTemplate("xhttp_headers_template"); // HTTP | None
|
||||
constexpr QLatin1String xhttpUplinkMethod("xhttp_uplink_method"); // POST | PUT | PATCH
|
||||
constexpr QLatin1String xhttpDisableGrpc("xhttp_disable_grpc"); // bool
|
||||
constexpr QLatin1String xhttpDisableSse("xhttp_disable_sse"); // bool
|
||||
|
||||
@@ -64,13 +64,14 @@ namespace amnezia
|
||||
constexpr char defaultFlow[] = "xtls-rprx-vision";
|
||||
constexpr char defaultTransport[] = "raw";
|
||||
constexpr char defaultFingerprint[] = "chrome";
|
||||
constexpr char defaultSni[] = "www.googletagmanager.com";
|
||||
constexpr char defaultAlpn[] = "h2";
|
||||
constexpr char defaultSni[] = "cdn.example.com";
|
||||
constexpr char defaultAlpn[] = "HTTP/2";
|
||||
|
||||
constexpr char defaultXhttpMode[] = "Auto";
|
||||
constexpr char defaultXhttpHeadersTemplate[] = "HTTP";
|
||||
constexpr char defaultXhttpUplinkMethod[] = "POST";
|
||||
constexpr char defaultXhttpSessionPlacement[] = "Path";
|
||||
constexpr char defaultXhttpSessionKey[] = "";
|
||||
constexpr char defaultXhttpSessionKey[] = "Path";
|
||||
constexpr char defaultXhttpSeqPlacement[] = "Path";
|
||||
constexpr char defaultXhttpUplinkDataPlacement[] = "Body";
|
||||
|
||||
@@ -85,10 +86,6 @@ namespace amnezia
|
||||
|
||||
constexpr char defaultXPaddingPlacement[] = "Cookie";
|
||||
constexpr char defaultXPaddingMethod[] = "Repeat-x";
|
||||
constexpr char defaultXPaddingKey[] = "x_padding";
|
||||
constexpr char defaultXPaddingHeader[] = "X-Padding";
|
||||
constexpr char defaultXPaddingBytesMin[] = "1";
|
||||
constexpr char defaultXPaddingBytesMax[] = "256";
|
||||
|
||||
constexpr char defaultMkcpTti[] = "50";
|
||||
constexpr char defaultMkcpUplinkCapacity[] = "5";
|
||||
@@ -150,7 +147,6 @@ namespace amnezia
|
||||
constexpr char defaultSubnetCidr[] = "24";
|
||||
|
||||
constexpr char defaultPort[] = "51820";
|
||||
constexpr char defaultPersistentKeepAlive[] = "25";
|
||||
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE)
|
||||
constexpr char defaultMtu[] = "1280";
|
||||
@@ -186,16 +182,15 @@ namespace amnezia
|
||||
constexpr char defaultJunkPacketCount[] = "3";
|
||||
constexpr char defaultJunkPacketMinSize[] = "10";
|
||||
constexpr char defaultJunkPacketMaxSize[] = "30";
|
||||
constexpr int junkPacketSizeMin = 12;
|
||||
constexpr int initPacketJunkSizeMax = 150;
|
||||
constexpr int responsePacketJunkSizeMax = 150;
|
||||
constexpr int cookieReplyPacketJunkSizeMax = 64;
|
||||
constexpr int defaultTransportPacketJunkSize = 12;
|
||||
constexpr char defaultInitPacketJunkSize[] = "15";
|
||||
constexpr char defaultResponsePacketJunkSize[] = "18";
|
||||
constexpr char defaultCookieReplyPacketJunkSize[] = "20";
|
||||
constexpr char defaultTransportPacketJunkSize[] = "23";
|
||||
|
||||
constexpr char defaultInitPacketMagicHeader[] = "1";
|
||||
constexpr char defaultResponsePacketMagicHeader[] = "2";
|
||||
constexpr char defaultUnderloadPacketMagicHeader[] = "3";
|
||||
constexpr char defaultTransportPacketMagicHeader[] = "4";
|
||||
constexpr char defaultInitPacketMagicHeader[] = "1020325451";
|
||||
constexpr char defaultResponsePacketMagicHeader[] = "3288052141";
|
||||
constexpr char defaultTransportPacketMagicHeader[] = "2528465083";
|
||||
constexpr char defaultUnderloadPacketMagicHeader[] = "1766607858";
|
||||
constexpr char defaultSpecialJunk1[] = "<r 2><b 0x858000010001000000000669636c6f756403636f6d0000010001c00c000100010000105a00044d583737>";
|
||||
constexpr char defaultSpecialJunk2[] = "";
|
||||
constexpr char defaultSpecialJunk3[] = "";
|
||||
@@ -204,17 +199,6 @@ namespace amnezia
|
||||
|
||||
constexpr char awgV1_5[] = "1.5";
|
||||
constexpr char awgV2[] = "2";
|
||||
constexpr char awgV3[] = "3";
|
||||
|
||||
constexpr char defaultContentPaddingAddition[] = "10-100";
|
||||
constexpr char defaultRekeyAfterTime[] = "100-120";
|
||||
constexpr char defaultRekeyTimeout[] = "3-7";
|
||||
constexpr char defaultRejectAfterTime[] = "150-180";
|
||||
constexpr char defaultKeepaliveTimeout[] = "5-15";
|
||||
constexpr char defaultMaxHandshakeAttempts[] = "15-20";
|
||||
constexpr char defaultPersistentKeepAlive[] = "25-35";
|
||||
|
||||
|
||||
}
|
||||
|
||||
namespace socks5Proxy
|
||||
@@ -250,8 +234,7 @@ namespace amnezia
|
||||
|
||||
constexpr char defaultPort[] = "443";
|
||||
constexpr char defaultWorkers[] = "2";
|
||||
// mtproto-proxy loses connectivity with -M >= 20; keep the cap at the highest known-good value.
|
||||
constexpr int maxWorkers = 19;
|
||||
constexpr int maxWorkers = 32;
|
||||
constexpr int botTagHexLength = 32;
|
||||
constexpr char defaultTlsDomain[] = "googletagmanager.com";
|
||||
}
|
||||
@@ -270,6 +253,7 @@ namespace amnezia
|
||||
constexpr char tlsEmulationKey[] = "telemt_tls_emulation";
|
||||
constexpr char useMiddleProxyKey[] = "telemt_use_middle_proxy";
|
||||
constexpr char userNameKey[] = "telemt_user_name";
|
||||
// Stored for UI only (Telemt server ignores these; same controls as MTProxy page)
|
||||
constexpr char additionalSecretsKey[] = "telemt_additional_secrets";
|
||||
constexpr char workersKey[] = "telemt_workers";
|
||||
constexpr char workersModeKey[] = "telemt_workers_mode";
|
||||
@@ -287,7 +271,6 @@ namespace amnezia
|
||||
constexpr char workersModeAuto[] = "auto";
|
||||
constexpr char workersModeManual[] = "manual";
|
||||
constexpr int maxWorkers = 32;
|
||||
constexpr int botTagHexLength = 32;
|
||||
}
|
||||
|
||||
} // namespace protocols
|
||||
|
||||
@@ -286,7 +286,7 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
||||
return { resGateway, QNetworkInterface::interfaceFromIndex(resIndex) };
|
||||
#endif
|
||||
#ifdef Q_OS_LINUX
|
||||
constexpr int BUFFER_SIZE = 8192;
|
||||
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;
|
||||
@@ -294,7 +294,7 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
||||
// This struct contain route attributes (route type)
|
||||
struct rtattr *route_attribute;
|
||||
char gateway_address[INET_ADDRSTRLEN], interface[IF_NAMESIZE];
|
||||
char msgbuf[100], buffer[BUFFER_SIZE];
|
||||
char msgbuf[BUFFER_SIZE], buffer[BUFFER_SIZE];
|
||||
char *ptr = buffer;
|
||||
struct timeval tv;
|
||||
|
||||
@@ -339,8 +339,8 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
||||
nlh = (struct nlmsghdr *) ptr;
|
||||
|
||||
/* Check if the header is valid */
|
||||
if((NLMSG_OK(nlh, received_bytes) == 0) ||
|
||||
(nlh->nlmsg_type == NLMSG_ERROR))
|
||||
if((NLMSG_OK(nlmsg, received_bytes) == 0) ||
|
||||
(nlmsg->nlmsg_type == NLMSG_ERROR))
|
||||
{
|
||||
perror("Error in received packet");
|
||||
return {};
|
||||
@@ -355,15 +355,13 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
||||
}
|
||||
|
||||
/* Break if its not a multi part message */
|
||||
if ((nlh->nlmsg_flags & NLM_F_MULTI) == 0)
|
||||
if ((nlmsg->nlmsg_flags & NLM_F_MULTI) == 0)
|
||||
break;
|
||||
}
|
||||
while ((nlh->nlmsg_seq != msgseq) || (nlh->nlmsg_pid != getpid()));
|
||||
while ((nlmsg->nlmsg_seq != msgseq) || (nlmsg->nlmsg_pid != getpid()));
|
||||
|
||||
/* parse response */
|
||||
int remaining = msg_len + received_bytes;
|
||||
nlh = (struct nlmsghdr *) buffer;
|
||||
for ( ; NLMSG_OK(nlh, remaining); nlh = NLMSG_NEXT(nlh, remaining))
|
||||
for ( ; NLMSG_OK(nlh, received_bytes); nlh = NLMSG_NEXT(nlh, received_bytes))
|
||||
{
|
||||
/* Get the route data */
|
||||
route_entry = (struct rtmsg *) NLMSG_DATA(nlh);
|
||||
@@ -372,10 +370,6 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
||||
if (route_entry->rtm_table != RT_TABLE_MAIN)
|
||||
continue;
|
||||
|
||||
/* Reset per-route to avoid cross-route state pollution */
|
||||
memset(gateway_address, 0, sizeof(gateway_address));
|
||||
memset(interface, 0, sizeof(interface));
|
||||
|
||||
route_attribute = (struct rtattr *) RTM_RTA(route_entry);
|
||||
route_attribute_len = RTM_PAYLOAD(nlh);
|
||||
|
||||
@@ -401,8 +395,6 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!(*gateway_address) || !(*interface))
|
||||
qDebug() << "getGatewayAndIface: no gateway found";
|
||||
close(sock);
|
||||
return { gateway_address, QNetworkInterface::interfaceFromName(interface) };
|
||||
#endif
|
||||
|
||||
@@ -1,193 +0,0 @@
|
||||
#include "payloadSender.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QEventLoop>
|
||||
#include <QHostAddress>
|
||||
#include <QJsonObject>
|
||||
#include <QRandomGenerator>
|
||||
#include <QRegularExpression>
|
||||
#include <QTcpSocket>
|
||||
#include <QTimer>
|
||||
#include <QUdpSocket>
|
||||
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "core/utils/networkUtilities.h"
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr char protoUdp[] = "udp";
|
||||
constexpr char protoTcp[] = "tcp";
|
||||
|
||||
QByteArray randomBytes(int count)
|
||||
{
|
||||
if (count <= 0) {
|
||||
return {};
|
||||
}
|
||||
QByteArray bytes(count, Qt::Uninitialized);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
bytes[i] = static_cast<char>(QRandomGenerator::global()->bounded(256));
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
bool parseEndpoint(const QString &endpoint, QString &host, quint16 &port)
|
||||
{
|
||||
const int separatorIndex = endpoint.lastIndexOf(QLatin1Char(':'));
|
||||
if (separatorIndex <= 0 || separatorIndex == endpoint.size() - 1) {
|
||||
return false;
|
||||
}
|
||||
host = endpoint.left(separatorIndex);
|
||||
bool ok = false;
|
||||
const uint parsedPort = endpoint.mid(separatorIndex + 1).toUInt(&ok);
|
||||
if (!ok || parsedPort == 0 || parsedPort > 65535) {
|
||||
return false;
|
||||
}
|
||||
port = static_cast<quint16>(parsedPort);
|
||||
return true;
|
||||
}
|
||||
|
||||
enum class ExchangeResult { Sent, Matched, Mismatch, Timeout };
|
||||
|
||||
ExchangeResult waitForResponse(QAbstractSocket &socket, const QByteArray &expectedResponse, int timeoutMs)
|
||||
{
|
||||
if (expectedResponse.isEmpty()) {
|
||||
return ExchangeResult::Sent;
|
||||
}
|
||||
|
||||
QByteArray response;
|
||||
QEventLoop loop;
|
||||
QTimer timer;
|
||||
timer.setSingleShot(true);
|
||||
QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
|
||||
QObject::connect(&socket, &QIODevice::readyRead, &loop, [&]() {
|
||||
response.append(socket.readAll());
|
||||
if (response.size() >= expectedResponse.size()) {
|
||||
loop.quit();
|
||||
}
|
||||
});
|
||||
|
||||
timer.start(timeoutMs);
|
||||
loop.exec();
|
||||
|
||||
if (response.isEmpty()) {
|
||||
return ExchangeResult::Timeout;
|
||||
}
|
||||
return response == expectedResponse ? ExchangeResult::Matched : ExchangeResult::Mismatch;
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray PayloadSender::buildPayload(const QString &tagString)
|
||||
{
|
||||
QByteArray result;
|
||||
if (tagString.isEmpty()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
static const QRegularExpression tagRegExp(QStringLiteral("<([^>]*)>"));
|
||||
QRegularExpressionMatchIterator it = tagRegExp.globalMatch(tagString);
|
||||
while (it.hasNext()) {
|
||||
const QRegularExpressionMatch match = it.next();
|
||||
const QStringList parts = match.captured(1).simplified().split(QLatin1Char(' '), Qt::SkipEmptyParts);
|
||||
if (parts.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const QString tag = parts.at(0).toLower();
|
||||
if (tag == QLatin1String("b")) {
|
||||
if (parts.size() < 2) {
|
||||
continue;
|
||||
}
|
||||
QString hex = parts.at(1);
|
||||
if (hex.startsWith(QLatin1String("0x"), Qt::CaseInsensitive)) {
|
||||
hex = hex.mid(2);
|
||||
}
|
||||
result.append(QByteArray::fromHex(hex.toLatin1()));
|
||||
} else if (tag == QLatin1String("r")) {
|
||||
if (parts.size() < 2) {
|
||||
continue;
|
||||
}
|
||||
bool ok = false;
|
||||
const int count = parts.at(1).toInt(&ok);
|
||||
if (ok) {
|
||||
result.append(randomBytes(count));
|
||||
}
|
||||
} else {
|
||||
qWarning() << "PayloadSender: unknown payload tag" << tag;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void PayloadSender::sendAll(const QJsonArray &sendPayload)
|
||||
{
|
||||
for (int i = 0; i < sendPayload.size(); ++i) {
|
||||
const QJsonValue value = sendPayload.at(i);
|
||||
if (!value.isObject()) {
|
||||
continue;
|
||||
}
|
||||
sendEntry(value.toObject(), i);
|
||||
}
|
||||
}
|
||||
|
||||
void PayloadSender::sendEntry(const QJsonObject &entry, int index)
|
||||
{
|
||||
const QString endpoint = entry.value(configKey::sendPayloadEndpoint).toString();
|
||||
QString host;
|
||||
quint16 port = 0;
|
||||
if (!parseEndpoint(endpoint, host, port)) {
|
||||
qWarning() << "PayloadSender: skipping entry" << index << "- invalid endpoint" << endpoint;
|
||||
return;
|
||||
}
|
||||
|
||||
const QString protocol = entry.value(configKey::sendPayloadProtocol).toString().toLower();
|
||||
const int timeoutMs = entry.value(configKey::sendPayloadTimeoutMs).toInt();
|
||||
const QByteArray payload = buildPayload(entry.value(configKey::sendPayloadData).toString());
|
||||
const QByteArray expectedResponse = buildPayload(entry.value(configKey::sendPayloadExpectedResponse).toString());
|
||||
|
||||
const QString resolvedHost = NetworkUtilities::getIPAddress(host);
|
||||
const QHostAddress hostAddress(resolvedHost.isEmpty() ? host : resolvedHost);
|
||||
|
||||
QUdpSocket udpSocket;
|
||||
QTcpSocket tcpSocket;
|
||||
QAbstractSocket *socket = nullptr;
|
||||
if (protocol == protoUdp) {
|
||||
socket = &udpSocket;
|
||||
if (udpSocket.writeDatagram(payload, hostAddress, port) < 0) {
|
||||
qWarning() << "PayloadSender: entry" << index << "- udp write failed to" << endpoint << udpSocket.errorString();
|
||||
return;
|
||||
}
|
||||
} else if (protocol == protoTcp) {
|
||||
socket = &tcpSocket;
|
||||
tcpSocket.connectToHost(hostAddress, port);
|
||||
if (!tcpSocket.waitForConnected(timeoutMs)) {
|
||||
qWarning() << "PayloadSender: entry" << index << "- tcp connect failed to" << endpoint << tcpSocket.errorString();
|
||||
return;
|
||||
}
|
||||
if (tcpSocket.write(payload) != payload.size()) {
|
||||
qWarning() << "PayloadSender: entry" << index << "- tcp write failed to" << endpoint << tcpSocket.errorString();
|
||||
return;
|
||||
}
|
||||
tcpSocket.flush();
|
||||
} else {
|
||||
qWarning() << "PayloadSender: skipping entry" << index << "- unsupported protocol" << protocol;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (waitForResponse(*socket, expectedResponse, timeoutMs)) {
|
||||
case ExchangeResult::Sent:
|
||||
qInfo() << "PayloadSender: entry" << index << "-" << protocol << "payload sent to" << endpoint;
|
||||
break;
|
||||
case ExchangeResult::Matched:
|
||||
qInfo() << "PayloadSender: entry" << index << "-" << protocol << "expected response received from" << endpoint;
|
||||
break;
|
||||
case ExchangeResult::Mismatch:
|
||||
qWarning() << "PayloadSender: entry" << index << "-" << protocol << "response mismatch from" << endpoint;
|
||||
break;
|
||||
case ExchangeResult::Timeout:
|
||||
qWarning() << "PayloadSender: entry" << index << "-" << protocol << "response timeout from" << endpoint;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
#ifndef PAYLOADSENDER_H
|
||||
#define PAYLOADSENDER_H
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QJsonArray>
|
||||
#include <QString>
|
||||
|
||||
class PayloadSender
|
||||
{
|
||||
public:
|
||||
static void sendAll(const QJsonArray &sendPayload);
|
||||
static QByteArray buildPayload(const QString &tagString);
|
||||
|
||||
private:
|
||||
static void sendEntry(const QJsonObject &entry, int index);
|
||||
};
|
||||
|
||||
#endif // PAYLOADSENDER_H
|
||||
@@ -24,13 +24,6 @@ QList<QString> qrCodeUtils::generateQrCodeImageSeries(const QByteArray &data)
|
||||
return chunks;
|
||||
}
|
||||
|
||||
QString qrCodeUtils::generatePlainQrCodeImage(const QByteArray &data)
|
||||
{
|
||||
qrcodegen::QrCode qr = qrcodegen::QrCode::encodeText(data, qrcodegen::QrCode::Ecc::LOW);
|
||||
QString svg = QString::fromStdString(toSvgString(qr, 1));
|
||||
return svgToBase64(svg);
|
||||
}
|
||||
|
||||
QString qrCodeUtils::svgToBase64(const QString &image)
|
||||
{
|
||||
return "data:image/svg;base64," + QString::fromLatin1(image.toUtf8().toBase64().data());
|
||||
|
||||
@@ -10,7 +10,6 @@ namespace qrCodeUtils
|
||||
constexpr const qint16 qrMagicCode = 1984;
|
||||
|
||||
QList<QString> generateQrCodeImageSeries(const QByteArray &data);
|
||||
QString generatePlainQrCodeImage(const QByteArray &data);
|
||||
qrcodegen::QrCode generateQrCode(const QByteArray &data);
|
||||
QString svgToBase64(const QString &image);
|
||||
};
|
||||
|
||||
@@ -50,7 +50,6 @@ QString amnezia::scriptName(SharedScriptType type)
|
||||
switch (type) {
|
||||
case SharedScriptType::prepare_host: return QLatin1String("prepare_host.sh");
|
||||
case SharedScriptType::install_docker: return QLatin1String("install_docker.sh");
|
||||
case SharedScriptType::install_conntrack: return QLatin1String("install_conntrack.sh");
|
||||
case SharedScriptType::build_container: return QLatin1String("build_container.sh");
|
||||
case SharedScriptType::remove_container: return QLatin1String("remove_container.sh");
|
||||
case SharedScriptType::remove_all_containers: return QLatin1String("remove_all_containers.sh");
|
||||
@@ -254,20 +253,8 @@ amnezia::ScriptVars amnezia::genAwgVars(const ContainerConfig &containerConfig)
|
||||
vars.append({ { "$SPECIAL_JUNK_3", config.specialJunk3 } });
|
||||
vars.append({ { "$SPECIAL_JUNK_4", config.specialJunk4 } });
|
||||
vars.append({ { "$SPECIAL_JUNK_5", config.specialJunk5 } });
|
||||
|
||||
const bool isAwg3 = config.protocolVersion == protocols::awg::awgV3;
|
||||
vars.append({ { "$PERSISTENT_KEEPALIVE", isAwg3 ? QString(protocols::awg::defaultPersistentKeepAlive)
|
||||
: QString(protocols::wireguard::defaultPersistentKeepAlive) } });
|
||||
|
||||
vars.append({ { "$HEADER_PROTECTION_KEY", config.headerProtectionKey } });
|
||||
vars.append({ { "$CONTENT_PADDING_ADDITION", config.contentPaddingAddition } });
|
||||
vars.append({ { "$REKEY_AFTER_TIME", config.rekeyAfterTime } });
|
||||
vars.append({ { "$REKEY_TIMEOUT", config.rekeyTimeout } });
|
||||
vars.append({ { "$REJECT_AFTER_TIME", config.rejectAfterTime } });
|
||||
vars.append({ { "$KEEPALIVE_TIMEOUT", config.keepaliveTimeout } });
|
||||
vars.append({ { "$MAX_HANDSHAKE_ATTEMPTS", config.maxHandshakeAttempts } });
|
||||
}
|
||||
|
||||
|
||||
return vars;
|
||||
}
|
||||
|
||||
@@ -341,7 +328,6 @@ amnezia::ScriptVars amnezia::genMtProxyVars(const ContainerConfig &containerConf
|
||||
workers = (transportMode == QLatin1String(protocols::mtProxy::transportModeFakeTLS)) ? QStringLiteral("0")
|
||||
: QStringLiteral("2");
|
||||
}
|
||||
vars.append({{"$MTPROXY_WORKERS_MODE", workersMode}});
|
||||
vars.append({{"$MTPROXY_WORKERS", workers}});
|
||||
|
||||
vars.append({{"$MTPROXY_NAT_ENABLED", c.natEnabled ? QStringLiteral("1") : QStringLiteral("0")}});
|
||||
@@ -380,20 +366,6 @@ amnezia::ScriptVars amnezia::genTelemtVars(const ContainerConfig &containerConfi
|
||||
vars.append({ { "$TELEMT_USE_MIDDLE_PROXY", c.useMiddleProxy ? QLatin1String("true") : QLatin1String("false") } });
|
||||
vars.append({ { "$TELEMT_MASK", c.maskEnabled ? QLatin1String("true") : QLatin1String("false") } });
|
||||
vars.append({ { "$TELEMT_TLS_EMULATION", c.tlsEmulation ? QLatin1String("true") : QLatin1String("false") } });
|
||||
|
||||
QStringList additionalList;
|
||||
for (const QString &s : c.additionalSecrets) {
|
||||
if (!s.isEmpty()) {
|
||||
additionalList << s;
|
||||
}
|
||||
}
|
||||
vars.append({ { "$TELEMT_ADDITIONAL_SECRETS", additionalList.join(QLatin1Char(',')) } });
|
||||
|
||||
QString middleProxyNatIp;
|
||||
if (c.natEnabled && !c.natExternalIp.isEmpty()) {
|
||||
middleProxyNatIp = c.natExternalIp;
|
||||
}
|
||||
vars.append({ { "$TELEMT_MIDDLE_PROXY_NAT_IP", middleProxyNatIp } });
|
||||
}
|
||||
|
||||
return vars;
|
||||
|
||||
@@ -21,7 +21,6 @@ enum SharedScriptType {
|
||||
// General scripts
|
||||
prepare_host,
|
||||
install_docker,
|
||||
install_conntrack,
|
||||
build_container,
|
||||
remove_container,
|
||||
remove_all_containers,
|
||||
|
||||
@@ -176,8 +176,7 @@ QByteArray SshSession::getTextFileFromContainer(DockerContainer container, const
|
||||
|
||||
errorCode = ErrorCode::NoError;
|
||||
|
||||
QString script = QStringLiteral("sudo docker exec -i %1 sh -c \"xxd -p '%2' 2>/dev/null || od -An -v -tx1 '%2'\"")
|
||||
.arg(ContainerUtils::containerToString(container), path);
|
||||
QString script = QStringLiteral("sudo docker exec -i %1 sh -c \"xxd -p '%2'\"").arg(ContainerUtils::containerToString(container), path);
|
||||
|
||||
QString stdOut;
|
||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||
|
||||
@@ -265,8 +265,6 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
|
||||
#endif
|
||||
}
|
||||
|
||||
config.m_persistentKeepalive = obj.value("persistentKeepalive").toString();
|
||||
|
||||
config.m_deviceIpv4Address = obj.value("deviceIpv4Address").toString();
|
||||
config.m_deviceIpv6Address = obj.value("deviceIpv6Address").toString();
|
||||
if (config.m_deviceIpv4Address.isNull() &&
|
||||
@@ -392,77 +390,55 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
|
||||
|
||||
config.m_killSwitchEnabled = QVariant(obj.value("killSwitchOption").toString()).toBool();
|
||||
|
||||
if (const auto jc = obj.value("Jc"); !jc.isUndefined()) {
|
||||
config.m_junkPacketCount = jc.toString();
|
||||
if (!obj.value("Jc").isNull()) {
|
||||
config.m_junkPacketCount = obj.value("Jc").toString();
|
||||
}
|
||||
if (const auto jmin = obj.value("Jmin"); !jmin.isUndefined()) {
|
||||
config.m_junkPacketMinSize = jmin.toString();
|
||||
if (!obj.value("Jmin").isNull()) {
|
||||
config.m_junkPacketMinSize = obj.value("Jmin").toString();
|
||||
}
|
||||
if (const auto jmax = obj.value("Jmax"); !jmax.isUndefined()) {
|
||||
config.m_junkPacketMaxSize = jmax.toString();
|
||||
if (!obj.value("Jmax").isNull()) {
|
||||
config.m_junkPacketMaxSize = obj.value("Jmax").toString();
|
||||
}
|
||||
if (const auto s1 = obj.value("S1"); !s1.isUndefined()) {
|
||||
config.m_initPacketJunkSize = s1.toString();
|
||||
if (!obj.value("S1").isNull()) {
|
||||
config.m_initPacketJunkSize = obj.value("S1").toString();
|
||||
}
|
||||
if (const auto s2 = obj.value("S2"); !s2.isUndefined()) {
|
||||
config.m_responsePacketJunkSize = s2.toString();
|
||||
if (!obj.value("S2").isNull()) {
|
||||
config.m_responsePacketJunkSize = obj.value("S2").toString();
|
||||
}
|
||||
if (const auto s3 = obj.value("S3"); !s3.isUndefined()) {
|
||||
config.m_cookieReplyPacketJunkSize = s3.toString();
|
||||
if (!obj.value("S3").isNull()) {
|
||||
config.m_cookieReplyPacketJunkSize = obj.value("S3").toString();
|
||||
}
|
||||
if (const auto s4 = obj.value("S4"); !s4.isUndefined()) {
|
||||
config.m_transportPacketJunkSize = s4.toString();
|
||||
if (!obj.value("S4").isNull()) {
|
||||
config.m_transportPacketJunkSize = obj.value("S4").toString();
|
||||
}
|
||||
|
||||
if (const auto h1 = obj.value("H1"); !h1.isUndefined()) {
|
||||
config.m_initPacketMagicHeader = h1.toString();
|
||||
if (!obj.value("H1").isNull()) {
|
||||
config.m_initPacketMagicHeader = obj.value("H1").toString();
|
||||
}
|
||||
if (const auto h2 = obj.value("H2"); !h2.isUndefined()) {
|
||||
config.m_responsePacketMagicHeader = h2.toString();
|
||||
if (!obj.value("H2").isNull()) {
|
||||
config.m_responsePacketMagicHeader = obj.value("H2").toString();
|
||||
}
|
||||
if (const auto h3 = obj.value("H3"); !h3.isUndefined()) {
|
||||
config.m_underloadPacketMagicHeader = h3.toString();
|
||||
if (!obj.value("H3").isNull()) {
|
||||
config.m_underloadPacketMagicHeader = obj.value("H3").toString();
|
||||
}
|
||||
if (const auto h4 = obj.value("H4"); !h4.isUndefined()) {
|
||||
config.m_transportPacketMagicHeader = h4.toString();
|
||||
if (!obj.value("H4").isNull()) {
|
||||
config.m_transportPacketMagicHeader = obj.value("H4").toString();
|
||||
}
|
||||
|
||||
if (const auto i1 = obj.value("I1"); !i1.isUndefined()) {
|
||||
config.m_specialJunk["I1"] = i1.toString();
|
||||
if (!obj.value("I1").isNull()) {
|
||||
config.m_specialJunk["I1"] = obj.value("I1").toString();
|
||||
}
|
||||
if (const auto i2 = obj.value("I2"); !i2.isUndefined()) {
|
||||
config.m_specialJunk["I2"] = i2.toString();
|
||||
if (!obj.value("I2").isNull()) {
|
||||
config.m_specialJunk["I2"] = obj.value("I2").toString();
|
||||
}
|
||||
if (const auto i3 = obj.value("I3"); !i3.isUndefined()) {
|
||||
config.m_specialJunk["I3"] = i3.toString();
|
||||
if (!obj.value("I3").isNull()) {
|
||||
config.m_specialJunk["I3"] = obj.value("I3").toString();
|
||||
}
|
||||
if (const auto i4 = obj.value("I4"); !i4.isUndefined()) {
|
||||
config.m_specialJunk["I4"] = i4.toString();
|
||||
if (!obj.value("I4").isNull()) {
|
||||
config.m_specialJunk["I4"] = obj.value("I4").toString();
|
||||
}
|
||||
if (const auto i5 = obj.value("I5"); !i5.isUndefined()) {
|
||||
config.m_specialJunk["I5"] = i5.toString();
|
||||
}
|
||||
|
||||
if (const auto headerProtectionKey = obj.value("HeaderProtectionKey"); !headerProtectionKey.isUndefined()) {
|
||||
config.m_headerProtectionKey = headerProtectionKey.toString();
|
||||
}
|
||||
if (const auto contentPaddingAddition = obj.value("ContentPaddingAddition"); !contentPaddingAddition.isUndefined()) {
|
||||
config.m_contentPaddingAddition = contentPaddingAddition.toString();
|
||||
}
|
||||
if (const auto rekeyAfterTime = obj.value("RekeyAfterTime"); !rekeyAfterTime.isUndefined()) {
|
||||
config.m_rekeyAfterTime = rekeyAfterTime.toString();
|
||||
}
|
||||
if (const auto rekeyTimeout = obj.value("RekeyTimeout"); !rekeyTimeout.isUndefined()) {
|
||||
config.m_rekeyTimeout = rekeyTimeout.toString();
|
||||
}
|
||||
if (const auto rejectAfterTime = obj.value("RejectAfterTime"); !rejectAfterTime.isUndefined()) {
|
||||
config.m_rejectAfterTime = rejectAfterTime.toString();
|
||||
}
|
||||
if (const auto keepaliveTimeout = obj.value("KeepaliveTimeout"); !keepaliveTimeout.isUndefined()) {
|
||||
config.m_keepaliveTimeout = keepaliveTimeout.toString();
|
||||
}
|
||||
if (const auto maxHandshakeAttempts = obj.value("MaxHandshakeAttempts"); !maxHandshakeAttempts.isUndefined()) {
|
||||
config.m_maxHandshakeAttempts = maxHandshakeAttempts.toString();
|
||||
if (!obj.value("I5").isNull()) {
|
||||
config.m_specialJunk["I5"] = obj.value("I5").toString();
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -24,9 +24,6 @@ QJsonObject InterfaceConfig::toJson() const {
|
||||
json.insert("serverIpv6AddrIn", QJsonValue(m_serverIpv6AddrIn));
|
||||
json.insert("serverPort", QJsonValue((double)m_serverPort));
|
||||
json.insert("deviceMTU", QJsonValue(m_deviceMTU));
|
||||
if (!m_persistentKeepalive.isEmpty()) {
|
||||
json.insert("persistentKeepalive", QJsonValue(m_persistentKeepalive));
|
||||
}
|
||||
if ((m_hopType == InterfaceConfig::MultiHopExit) ||
|
||||
(m_hopType == InterfaceConfig::SingleHop)) {
|
||||
json.insert("serverIpv4Gateway", QJsonValue(m_serverIpv4Gateway));
|
||||
@@ -118,66 +115,42 @@ QString InterfaceConfig::toWgConf(const QMap<QString, QString>& extra) const {
|
||||
out << "DNS = " << dnsServers.join(", ") << "\n";
|
||||
}
|
||||
|
||||
if (!m_junkPacketCount.isEmpty()) {
|
||||
if (!m_junkPacketCount.isNull()) {
|
||||
out << "Jc = " << m_junkPacketCount << "\n";
|
||||
}
|
||||
if (!m_junkPacketMinSize.isEmpty()) {
|
||||
if (!m_junkPacketMinSize.isNull()) {
|
||||
out << "JMin = " << m_junkPacketMinSize << "\n";
|
||||
}
|
||||
if (!m_junkPacketMaxSize.isEmpty()) {
|
||||
if (!m_junkPacketMaxSize.isNull()) {
|
||||
out << "JMax = " << m_junkPacketMaxSize << "\n";
|
||||
}
|
||||
if (!m_initPacketJunkSize.isEmpty()) {
|
||||
if (!m_initPacketJunkSize.isNull()) {
|
||||
out << "S1 = " << m_initPacketJunkSize << "\n";
|
||||
}
|
||||
if (!m_responsePacketJunkSize.isEmpty()) {
|
||||
if (!m_responsePacketJunkSize.isNull()) {
|
||||
out << "S2 = " << m_responsePacketJunkSize << "\n";
|
||||
}
|
||||
if (!m_cookieReplyPacketJunkSize.isEmpty()) {
|
||||
if (!m_cookieReplyPacketJunkSize.isNull()) {
|
||||
out << "S3 = " << m_cookieReplyPacketJunkSize << "\n";
|
||||
}
|
||||
if (!m_transportPacketJunkSize.isEmpty()) {
|
||||
if (!m_transportPacketJunkSize.isNull()) {
|
||||
out << "S4 = " << m_transportPacketJunkSize << "\n";
|
||||
}
|
||||
if (!m_initPacketMagicHeader.isEmpty()) {
|
||||
if (!m_initPacketMagicHeader.isNull()) {
|
||||
out << "H1 = " << m_initPacketMagicHeader << "\n";
|
||||
}
|
||||
if (!m_responsePacketMagicHeader.isEmpty()) {
|
||||
if (!m_responsePacketMagicHeader.isNull()) {
|
||||
out << "H2 = " << m_responsePacketMagicHeader << "\n";
|
||||
}
|
||||
if (!m_underloadPacketMagicHeader.isEmpty()) {
|
||||
if (!m_underloadPacketMagicHeader.isNull()) {
|
||||
out << "H3 = " << m_underloadPacketMagicHeader << "\n";
|
||||
}
|
||||
if (!m_transportPacketMagicHeader.isEmpty()) {
|
||||
if (!m_transportPacketMagicHeader.isNull()) {
|
||||
out << "H4 = " << m_transportPacketMagicHeader << "\n";
|
||||
}
|
||||
|
||||
for (const QString& key : m_specialJunk.keys()) {
|
||||
if (!m_specialJunk[key].isEmpty()) {
|
||||
out << key << " = " << m_specialJunk[key] << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_headerProtectionKey.isEmpty()) {
|
||||
out << "HeaderProtectionKey = " << m_headerProtectionKey << "\n";
|
||||
}
|
||||
if (!m_contentPaddingAddition.isEmpty()) {
|
||||
out << "ContentPaddingAddition = " << m_contentPaddingAddition << "\n";
|
||||
}
|
||||
if (!m_rekeyAfterTime.isEmpty()) {
|
||||
out << "RekeyAfterTime = " << m_rekeyAfterTime << "\n";
|
||||
}
|
||||
if (!m_rekeyTimeout.isEmpty()) {
|
||||
out << "RekeyTimeout = " << m_rekeyTimeout << "\n";
|
||||
}
|
||||
if (!m_rejectAfterTime.isEmpty()) {
|
||||
out << "RejectAfterTime = " << m_rejectAfterTime << "\n";
|
||||
}
|
||||
if (!m_keepaliveTimeout.isEmpty()) {
|
||||
out << "KeepaliveTimeout = " << m_keepaliveTimeout << "\n";
|
||||
}
|
||||
if (!m_maxHandshakeAttempts.isEmpty()) {
|
||||
out << "MaxHandshakeAttempts = " << m_maxHandshakeAttempts << "\n";
|
||||
out << key << " = " << m_specialJunk[key] << "\n";
|
||||
}
|
||||
|
||||
// If any extra config was provided, append it now.
|
||||
@@ -200,9 +173,6 @@ QString InterfaceConfig::toWgConf(const QMap<QString, QString>& extra) const {
|
||||
ranges.append(ip.toString());
|
||||
}
|
||||
out << "AllowedIPs = " << ranges.join(", ") << "\n";
|
||||
if (!m_persistentKeepalive.isEmpty()) {
|
||||
out << "PersistentKeepalive = " << m_persistentKeepalive << "\n";
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
@@ -36,7 +36,6 @@ class InterfaceConfig {
|
||||
QString m_secondaryDnsServer;
|
||||
int m_serverPort = 0;
|
||||
int m_deviceMTU = 1420;
|
||||
QString m_persistentKeepalive;
|
||||
QList<IPAddress> m_allowedIPAddressRanges;
|
||||
QStringList m_excludedAddresses;
|
||||
QStringList m_vpnDisabledApps;
|
||||
@@ -59,14 +58,6 @@ class InterfaceConfig {
|
||||
QString m_transportPacketMagicHeader;
|
||||
QMap<QString, QString> m_specialJunk;
|
||||
|
||||
QString m_headerProtectionKey;
|
||||
QString m_contentPaddingAddition;
|
||||
QString m_rekeyAfterTime;
|
||||
QString m_rekeyTimeout;
|
||||
QString m_rejectAfterTime;
|
||||
QString m_keepaliveTimeout;
|
||||
QString m_maxHandshakeAttempts;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
QString toWgConf(
|
||||
const QMap<QString, QString>& extra = QMap<QString, QString>()) const;
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
constexpr const char* WG_INTERFACE = "amn0";
|
||||
|
||||
constexpr uint16_t WG_KEEPALIVE_PERIOD = 60;
|
||||
|
||||
class WireguardUtils : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17 11L21 7L17 3" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M21 7H9" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M7 21L3 17L7 13" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M15 17H3" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 525 B |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user