Compare commits

..

22 Commits

Author SHA1 Message Date
NickVs2015
cbcdf0f221 fix: remove comments 2026-06-26 16:48:47 +03:00
NickVs2015
79e28c55df fix: suppress linux firewall errors and add missing 400.allowPIA anchor 2026-06-26 16:17:08 +03:00
NickVs2015
80238a0620 fix: linux DNS set on connect and cleanup on disconnect for AWG/WireGuard
- Set DNS resolvers via systemd-resolved after flushDns on connect (vpnConnection.cpp)
- RevertLink after systemd-resolved restart on disconnect (DnsUtilsLinux)
- Add scheduleRetry() to retry full DNS setup on D-Bus transient failures
- Reduce D-Bus call timeout to 5s; use systemctl restart for flushDns
- Guard maybeUpdateResolvers with #ifndef Q_OS_LINUX to preserve macOS/Windows DNS
2026-06-26 13:08:39 +03:00
NickVs2015
2c68aa4185 fix: control resetIpStack when destroy DnsUtilsLinux 2026-06-25 12:39:40 +03:00
NickVs2015
b6f15b4b49 fix: linux reconnect, DNS rewrite, dbus async, killswitch and NM fixes 2026-06-25 12:39:40 +03:00
NickVs2015
e3e6b15ff1 fix: extend IPC security validation to macOS firewall 2026-06-25 11:00:04 +03:00
NickVs2015
f75b239d69 fix: resolve critical IPC security vulnerabilities
- Validate IP/CIDR values from IPC before passing to Linux firewall
- Replace shell interpolation with direct execve in firewall update functions
- Block dangerous OpenVPN/WireGuard arguments in sanitizeArguments()
- Add programId bounds check in IpcServerProcess::setProgram()
- Add SO_PEERCRED peer authentication for IPC connections on Linux
2026-06-24 12:00:40 +03:00
yp
5d16645b84 fix: android icons (#2725)
* update icons android

* remove comment
2026-06-24 00:08:21 +08:00
Yaroslav Gurov
203a092dc9 fix: blobs for macos-ne and codesigning of qt blobs (#2754) 2026-06-24 00:07:42 +08:00
yp
d8b8590bc4 fix: XRay validation audit (#2749)
* flow default and config fixes

* host/SNI/path validation backend + flow-default flag

* input validation, numeric limits and live Save on settings pages

* MinMaxRowType clamping + DropDownType fit-content drawer
2026-06-24 00:07:26 +08:00
yp
9b8bfaa6f8 fix: regression testing vs 4.8.15.4 (#2730)
* fixed revoke

* fixed async update xray/mtproxy/telemt

* fixed connect premium config

* fixed autostart app hide

* fixed clear profile

* (6) fixed xtls-rprx-vision→empty

* (7) fixed appendClient abort & fix restore admin

* (8) fixed async|clientsUpdated

* fixed increment name server N

* remove comment & reset file

* chore: add tr to nextAvailableServerName

---------

Co-authored-by: vkamn <vk@amnezia.org>
2026-06-23 23:05:58 +08:00
Yaroslav Gurov
890103a16a fix: update amneziawg (#2743)
* chore(conan): update amneziawg

* fix(conan): use cmake 4.2+ to support MSVC26

* fix(ci/cd): use the latest cmake generator available on windows
2026-06-17 19:56:53 +07:00
yp
56ab82f87f fix: Use shared OpenSSL on Android (#2736) 2026-06-16 10:57:32 +07:00
lunardunno
3984acbb44 feat: updating install_docker.sh script (#2661)
* Updating install_docker.sh script

Implementing a Docker service status check.
The Docker reinstall step has been removed due to the implementation of Docker service checking.
Implementing locale checking and assignment.
Implementation of execution of some actions through commands with sudo, to reduce delays caused by differences in the values ​​of the PATH variable for the root user and the user included in the sudo group.
Implementation of a verification step for the install containerization app to avoid installing unsupported podman-docker applications.

* adding message handling to install controller

Adding handling for "Containerization app is not supported" and "Service status not active" messages to the controller.

* Error Codes added

Error Codes added for ServerContainerizationNotSupported & DockerServiceNotActive

* Adding extended descriptions of new errors

* fix last line in errorCodes.h

* fix last line in errorStrings.cpp

* Changing the names of errors

* various changes in the script

The messages output for processing by the server controller have been changed: "Container runtime is not supported" and "Container runtime service is not running."
The redundant check and output of the "Packet manager not found" message, as well as the interruption of script execution, have been eliminated, as this situation is handled by the server controller at an earlier stage (check_server_is_busy.sh) and only there.
Added installation of the whish package if it is missing from the OS, for subsequent re-execution of the install_docker.sh and check_server_is_busy.sh scripts.
Implemented an alternative method for detecting the package manager if the whish package is initially missing from the OS.
The algorithm for setting the $pm variable (package manager) has been changed.

* processed phrases have been changed

The phrases processed by the server controller have been changed.

* Attempting to use "command -v"

Switching to using "command -v" instead of "which".

* "which" as main, "command" as backup.

* "which" as main, "command" as backup for check user

* which  LOCK_CMD with sudo

Run the "which" with sudo to check the $LOCK_CMD variable in case the user's PATH variable has incorrect values ​​if the user is not root and is only a member of the sudo group.

* suppressing sudo password prompt

* suppressing sudo password prompt

* suppressing sudo password prompt install_docker.sh

* Changing the phrase for check stdout

"sudo:" with "not found" instead of "command not found"

* Changing phrases for check stdout check_user_in_sudo.sh‎

* sudo|docker and not found, in one line

* check only sudoers
2026-06-15 22:28:38 +07:00
yp
cc404378f9 fix: remove only amnezia- prefixed docker volumes (#2728) 2026-06-15 13:12:19 +07:00
yp
594635e5cf fix: script remove docker volume (#2686)
* move sudo docker volume rm -f

* fix: remove unnecessary function

---------

Co-authored-by: vkamn <vk@amnezia.org>
2026-06-04 22:58:39 +08:00
vkamn
f9b106cf5b fix: various fixes (#2693)
* fix: fixed country model update

* fix: fixed context menu crush on ios

* fix: fixed passphrase dialog freeze

* fix: fixed country switch

* fix: fixed start minimized

* fix: fixed black screen after remove container

* refactor: return cloak and ss only for view

* fix: fixed default server change after improt while connected

* fix: divider visibility

* fix: fixed revoke admin user

* fix: fixed language restore after backup

* fix: link hover for tor settings page

* fix: fixed openvpn connecntion status

* fix: fixed free color status

* fix: fixed client config update

* chore: bump version
2026-06-04 22:45:53 +08:00
yp
a9861d18b7 fix: wrong index on xray pages (#2669)
* test crash xray

* fixed save config xray

* reset file

* fixed text port & reset file

* fixed textFieldWithHeaderType.textField
2026-06-01 12:22:54 +08:00
lunardunno
c14138f031 fix: deleting volumes when cleaning the server (#2673)
* Deleting volumes when cleaning the server

* force the remove volumes
2026-06-01 11:54:34 +08:00
yyy-amnezia
60686fde24 fix: link OpenVPNAdapter statically (#2645)
* fix(ios): link OpenVPNAdapter statically

* chore(conan): simplify openvpnadapter merge process and make everything via XCrun

---------

Co-authored-by: Yaroslav Gurov <ygurov@proton.me>
2026-05-30 13:59:35 +08:00
Yaroslav Gurov
bd0747296e fix: networkextension proper framework linking (#2668) 2026-05-28 23:09:49 +08:00
Yaroslav Gurov
ba61019a50 fix: enable bundled openssl for every platform except NE-based ones (#2660)
* fix: enable bundled openssl for every platform except NE-based ones

* fix(conan): trigger CI/CD on crutial cmake changes

* fix: install dylibs/dlls from conan and use proper RPATH

* fix: adjust windows runtime deps
2026-05-28 19:17:27 +08:00
134 changed files with 1924 additions and 750 deletions

View File

@@ -23,6 +23,9 @@ jobs:
- 'recipes/**'
- 'conanfile.py'
- '.github/workflows/deploy.yml'
- 'cmake/conan_provider.cmake'
- 'cmake/platform_settings.cmake'
- 'cmake/recipes_bootstrap.cmake'
Bake-Prebuilts-Linux:
runs-on: ubuntu-latest
@@ -154,7 +157,7 @@ jobs:
run: pip install "conan==2.28.0"
- name: 'Build dependencies'
run: cmake -S . -B build -G "Visual Studio 17 2022" -DPREBUILTS_ONLY=1
run: cmake -S . -B build -DPREBUILTS_ONLY=1
- name: 'Authorize in remote'
if: github.ref == 'refs/heads/dev'

View File

@@ -4,9 +4,7 @@ set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(PROJECT AmneziaVPN)
if(NOT AMNEZIAVPN_VERSION)
set(AMNEZIAVPN_VERSION 4.9.0.1 CACHE STRING "Client app version")
endif()
set(AMNEZIAVPN_VERSION 4.9.0.2)
set(QT_CREATOR_SKIP_PACKAGE_MANAGER_SETUP ON CACHE BOOL "" FORCE)
set(CMAKE_PROJECT_TOP_LEVEL_INCLUDES
@@ -20,9 +18,9 @@ project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
HOMEPAGE_URL "https://amnezia.org/"
)
# trigger conan to kick off `conan install` globally
find_package(OpenSSL REQUIRED)
if (PREBUILTS_ONLY)
# trigger conan to kick off `conan install`
find_package(OpenSSL REQUIRED)
return()
endif()
@@ -30,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 2122)
set(APP_ANDROID_VERSION_CODE 2123)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux")

View File

@@ -1,8 +1,6 @@
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
include(${CMAKE_CURRENT_LIST_DIR}/cmake/clientBrandingDefaults.cmake)
set(PROJECT ${CLIENT_TARGET_NAME})
set(PROJECT AmneziaVPN)
project(${PROJECT})
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
@@ -58,9 +56,6 @@ qt_add_executable(${PROJECT} MANUAL_FINALIZATION)
target_include_directories(${PROJECT} PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
)
target_compile_definitions(${PROJECT} PRIVATE
CLIENT_KEYCHAIN_NAME_STR=\"${CLIENT_KEYCHAIN_NAME}\"
)
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep)
@@ -71,26 +66,21 @@ qt6_add_resources(QRC ${QRC}
${CMAKE_CURRENT_LIST_DIR}/images/images.qrc
${CMAKE_CURRENT_LIST_DIR}/images/flagKit.qrc
${CMAKE_CURRENT_LIST_DIR}/client_scripts/clientScripts.qrc
${CLIENT_QML_QRC_FILE}
${CMAKE_CURRENT_LIST_DIR}/ui/qml/qml.qrc
${CMAKE_CURRENT_LIST_DIR}/server_scripts/serverScripts.qrc
${CLIENT_EXTRA_QRC}
)
# -- i18n begin
if(CLIENT_TS_FILES)
set(AMNEZIAVPN_TS_FILES ${CLIENT_TS_FILES})
else()
set(AMNEZIAVPN_TS_FILES
${CMAKE_CURRENT_LIST_DIR}/translations/${CLIENT_TS_PREFIX}_ru_RU.ts
${CMAKE_CURRENT_LIST_DIR}/translations/${CLIENT_TS_PREFIX}_zh_CN.ts
${CMAKE_CURRENT_LIST_DIR}/translations/${CLIENT_TS_PREFIX}_fa_IR.ts
${CMAKE_CURRENT_LIST_DIR}/translations/${CLIENT_TS_PREFIX}_ar_EG.ts
${CMAKE_CURRENT_LIST_DIR}/translations/${CLIENT_TS_PREFIX}_my_MM.ts
${CMAKE_CURRENT_LIST_DIR}/translations/${CLIENT_TS_PREFIX}_uk_UA.ts
${CMAKE_CURRENT_LIST_DIR}/translations/${CLIENT_TS_PREFIX}_ur_PK.ts
${CMAKE_CURRENT_LIST_DIR}/translations/${CLIENT_TS_PREFIX}_hi_IN.ts
)
endif()
set(AMNEZIAVPN_TS_FILES
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ru_RU.ts
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_zh_CN.ts
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_fa_IR.ts
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ar_EG.ts
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_my_MM.ts
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_uk_UA.ts
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ur_PK.ts
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_hi_IN.ts
)
qt6_add_translations(${PROJECT}
TS_FILES ${AMNEZIAVPN_TS_FILES}
@@ -131,17 +121,6 @@ include_directories(mozilla/shared)
include_directories(mozilla/models)
configure_file(${CMAKE_CURRENT_LIST_DIR}/../version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/core/utils)
configure_file(
${CMAKE_CURRENT_LIST_DIR}/core/utils/appUiConfig.h.in
${CMAKE_CURRENT_BINARY_DIR}/core/utils/appUiConfig.h
@ONLY
)
configure_file(
${CMAKE_CURRENT_LIST_DIR}/core/utils/swiftInteropConfig.h.in
${CMAKE_CURRENT_BINARY_DIR}/core/utils/swiftInteropConfig.h
@ONLY
)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_definitions(${PROJECT} PRIVATE "MZ_DEBUG")
@@ -233,11 +212,32 @@ endif()
install(TARGETS ${PROJECT}
DESTINATION ${CMAKE_INSTALL_BINDIR}
RUNTIME_DEPENDENCY_SET client_deps
COMPONENT AmneziaVPN
)
install(FILES $<TARGET_RUNTIME_DLLS:${PROJECT}>
DESTINATION ${CMAKE_INSTALL_BINDIR}
if(APPLE)
set(RUNTIME_DEPS_DIR ${CMAKE_INSTALL_BINDIR}/AmneziaVPN.app/Contents/Frameworks)
else()
set(RUNTIME_DEPS_DIR ${CMAKE_INSTALL_BINDIR})
endif()
install(RUNTIME_DEPENDENCY_SET client_deps
PRE_EXCLUDE_REGEXES
[[api-ms-win-.*]]
[[ext-ms-.*]]
[[kernel32\.dll]]
[[hvsifiletrust\.dll]]
[[libc\.so\..*]] [[libgcc_s\.so\..*]] [[libm\.so\..*]] [[libstdc\+\+\.so\..*]]
[[.*\.framework]]
[[^[Qq]t.*]]
POST_EXCLUDE_REGEXES
[[^.*[\\/]system32[\\/].*\.dll$]]
[[^/lib.*]]
[[^/usr/lib.*]]
DIRECTORIES ${CONAN_RUNTIME_LIB_DIRS}
COMPONENT AmneziaVPN
DESTINATION "${RUNTIME_DEPS_DIR}"
)
set(deploy_tool_options "")

View File

@@ -23,7 +23,6 @@
#include "ui/controllers/qml/pageController.h"
#include "ui/models/installedAppsModel.h"
#include "version.h"
#include "core/utils/appUiConfig.h"
#include "platforms/ios/QRCodeReaderBase.h"
@@ -99,7 +98,7 @@ void AmneziaApplication::init()
{
m_engine = new QQmlApplicationEngine;
const QUrl url(QStringLiteral(APP_QML_ENTRYPOINT));
const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml"));
QObject::connect(
m_engine, &QQmlApplicationEngine::objectCreated, this,
[this, url](QObject *obj, const QUrl &objUrl) {
@@ -120,7 +119,13 @@ void AmneziaApplication::init()
win->setPersistentSceneGraph(true);
win->setPersistentGraphics(true);
#endif
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
win->show();
#else
if (!m_coreController || !m_coreController->pageController()->shouldStartMinimized()) {
win->show();
}
#endif
}
},
Qt::QueuedConnection);
@@ -139,7 +144,7 @@ void AmneziaApplication::init()
m_coreController.reset(new CoreController(m_vpnConnection, m_settings, m_engine));
m_engine->addImportPath(QStringLiteral(APP_QML_IMPORT_PATH));
m_engine->addImportPath("qrc:/ui/qml/Modules/");
if (m_parser.isSet(m_optImport)) {
const QString data = m_parser.value(m_optImport);
@@ -225,7 +230,7 @@ void AmneziaApplication::loadFonts()
{
QQuickStyle::setStyle("Basic");
QFontDatabase::addApplicationFont(QStringLiteral(APP_UI_FONT_RESOURCE));
QFontDatabase::addApplicationFont(":/fonts/pt-root-ui_vf.ttf");
}
bool AmneziaApplication::parseCommands()
@@ -252,7 +257,7 @@ bool AmneziaApplication::parseCommands()
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
void AmneziaApplication::startLocalServer() {
const QString serverName(APP_INSTANCE_NAME);
const QString serverName("AmneziaVPNInstance");
QLocalServer::removeServer(serverName);
QLocalServer *server = new QLocalServer(this);

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:type="linear"
android:angle="135"
android:startColor="#2A2A2E"
android:centerColor="#17171A"
android:endColor="#0E0E11" />
</shape>

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_amnezia_round"
android:insetLeft="19.5%"
android:insetTop="19.5%"
android:insetRight="19.5%"
android:insetBottom="19.5%" />

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_monochrome" />
</adaptive-icon>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
<monochrome android:drawable="@drawable/ic_launcher_monochrome" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="NoActionBar">
<item name="android:windowBackground">@color/black</item>
<item name="android:colorBackground">@color/black</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:enforceNavigationBarContrast">false</item>
<item name="android:enforceStatusBarContrast">false</item>
<item name="android:windowSplashScreenBackground">@color/ic_launcher_background</item>
<item name="android:windowSplashScreenIconBackgroundColor">@color/ic_launcher_background</item>
<item name="android:windowSplashScreenAnimatedIcon">@mipmap/icon</item>
</style>
</resources>

View File

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

View File

@@ -1,96 +0,0 @@
if(NOT CLIENT_TARGET_NAME)
set(CLIENT_TARGET_NAME "AmneziaVPN" CACHE STRING "Client executable target name")
endif()
if(NOT CLIENT_APPLICATION_NAME)
set(CLIENT_APPLICATION_NAME "AmneziaVPN" CACHE STRING "Application display and executable name")
endif()
if(NOT CLIENT_SERVICE_NAME)
set(CLIENT_SERVICE_NAME "AmneziaVPN-service" CACHE STRING "Service executable name")
endif()
if(NOT CLIENT_ORGANIZATION_NAME)
set(CLIENT_ORGANIZATION_NAME "AmneziaVPN.ORG" CACHE STRING "QSettings organization name")
endif()
if(NOT CLIENT_APP_INSTANCE_NAME)
set(CLIENT_APP_INSTANCE_NAME "AmneziaVPNInstance" CACHE STRING "Single-instance local server name")
endif()
if(NOT CLIENT_KEYCHAIN_NAME)
set(CLIENT_KEYCHAIN_NAME "${CLIENT_APPLICATION_NAME}-Keychain" CACHE STRING "QtKeychain service name used for encrypted settings keys")
endif()
if(NOT CLIENT_QML_ENTRYPOINT)
set(CLIENT_QML_ENTRYPOINT "qrc:/ui/qml/main2.qml" CACHE STRING "Main QML entrypoint URL")
endif()
if(NOT CLIENT_QML_IMPORT_PATH)
set(CLIENT_QML_IMPORT_PATH "qrc:/ui/qml/Modules/" CACHE STRING "Additional QQmlEngine import path")
endif()
if(NOT CLIENT_QML_PAGES_PREFIX)
set(CLIENT_QML_PAGES_PREFIX "qrc:/ui/qml/Pages2/" CACHE STRING "PageController QML pages prefix")
endif()
if(NOT CLIENT_UI_FONT_RESOURCE)
set(CLIENT_UI_FONT_RESOURCE ":/fonts/pt-root-ui_vf.ttf" CACHE STRING "UI font resource path")
endif()
if(NOT CLIENT_SWIFT_MODULE_NAME)
set(CLIENT_SWIFT_MODULE_NAME "${CLIENT_APPLICATION_NAME}" CACHE STRING "Swift module name for Objective-C interop header")
endif()
if(NOT CLIENT_SWIFT_OBJC_HEADER_NAME)
set(CLIENT_SWIFT_OBJC_HEADER_NAME "${CLIENT_SWIFT_MODULE_NAME}-Swift.h" CACHE STRING "Generated Swift Objective-C header name")
endif()
if(NOT CLIENT_NETWORK_EXTENSION_NAME)
set(CLIENT_NETWORK_EXTENSION_NAME "${CLIENT_APPLICATION_NAME}NetworkExtension" CACHE STRING "Display name for Apple network extension targets")
endif()
if(NOT CLIENT_IOS_NE_TARGET_NAME)
set(CLIENT_IOS_NE_TARGET_NAME "networkextension" CACHE STRING "iOS network extension build target name")
endif()
if(NOT CLIENT_MACOS_NE_TARGET_NAME)
set(CLIENT_MACOS_NE_TARGET_NAME "${CLIENT_NETWORK_EXTENSION_NAME}" CACHE STRING "macOS network extension build target name")
endif()
if(NOT CLIENT_IOS_NE_ENTITLEMENTS_FILE)
set(CLIENT_IOS_NE_ENTITLEMENTS_FILE "AmneziaVPNNetworkExtension.entitlements" CACHE STRING "iOS network extension entitlements filename")
endif()
if(NOT CLIENT_MACOS_NE_ENTITLEMENTS_FILE)
set(CLIENT_MACOS_NE_ENTITLEMENTS_FILE "AmneziaVPNNetworkExtension.entitlements" CACHE STRING "macOS network extension entitlements filename")
endif()
if(NOT CLIENT_IOS_LAUNCHSCREEN_FILE)
set(CLIENT_IOS_LAUNCHSCREEN_FILE "AmneziaVPNLaunchScreen.storyboard" CACHE STRING "iOS launch screen storyboard filename")
endif()
if(NOT CLIENT_IOS_APP_ENTITLEMENTS_PATH)
set(CLIENT_IOS_APP_ENTITLEMENTS_PATH "${CMAKE_CURRENT_LIST_DIR}/../ios/app/main.entitlements" CACHE FILEPATH "iOS app entitlements file path")
endif()
if(NOT CLIENT_MACOS_APP_ENTITLEMENTS_PATH)
set(CLIENT_MACOS_APP_ENTITLEMENTS_PATH "${CMAKE_CURRENT_LIST_DIR}/../macos/app/app.entitlements" CACHE FILEPATH "macOS app entitlements file path")
endif()
if(NOT CLIENT_IOS_LAUNCHSCREEN_PATH)
set(CLIENT_IOS_LAUNCHSCREEN_PATH "${CMAKE_CURRENT_LIST_DIR}/../ios/app/${CLIENT_IOS_LAUNCHSCREEN_FILE}" CACHE FILEPATH "iOS launch screen storyboard path")
endif()
if(NOT CLIENT_IOS_MEDIA_ASSETS_PATH)
set(CLIENT_IOS_MEDIA_ASSETS_PATH "${CMAKE_CURRENT_LIST_DIR}/../ios/app/Media.xcassets" CACHE FILEPATH "iOS app Media.xcassets path")
endif()
if(NOT CLIENT_IOS_NE_ENTITLEMENTS_PATH)
set(CLIENT_IOS_NE_ENTITLEMENTS_PATH "${CMAKE_CURRENT_LIST_DIR}/../ios/networkextension/${CLIENT_IOS_NE_ENTITLEMENTS_FILE}" CACHE FILEPATH "iOS network extension entitlements file path")
endif()
if(NOT CLIENT_MACOS_NE_ENTITLEMENTS_PATH)
set(CLIENT_MACOS_NE_ENTITLEMENTS_PATH "${CMAKE_CURRENT_LIST_DIR}/../macos/networkextension/${CLIENT_MACOS_NE_ENTITLEMENTS_FILE}" CACHE FILEPATH "macOS network extension entitlements file path")
endif()
if(NOT CLIENT_QML_QRC_FILE)
set(CLIENT_QML_QRC_FILE "${CMAKE_CURRENT_LIST_DIR}/../ui/qml/qml.qrc" CACHE FILEPATH "QML resources manifest")
endif()
if(NOT CLIENT_EXTRA_QRC)
set(CLIENT_EXTRA_QRC "" CACHE STRING "Optional semicolon-separated extra QRC files")
endif()
if(NOT CLIENT_TS_PREFIX)
set(CLIENT_TS_PREFIX "amneziavpn" CACHE STRING "Translation filename prefix")
endif()
if(NOT CLIENT_TS_FILES)
set(CLIENT_TS_FILES "" CACHE STRING "Optional semicolon-separated absolute TS file paths")
endif()
if(NOT CLIENT_IOS_PROVISIONING_PROFILE_SPECIFIER)
set(CLIENT_IOS_PROVISIONING_PROFILE_SPECIFIER "distr ios.org.amnezia.AmneziaVPN" CACHE STRING "iOS app provisioning profile specifier for release")
endif()
if(NOT CLIENT_IOS_PROVISIONING_PROFILE_SPECIFIER_DEBUG)
set(CLIENT_IOS_PROVISIONING_PROFILE_SPECIFIER_DEBUG "dev ios.org.amnezia.AmneziaVPN" CACHE STRING "iOS app provisioning profile specifier for debug")
endif()
if(NOT CLIENT_IOS_NE_PROVISIONING_PROFILE_SPECIFIER)
set(CLIENT_IOS_NE_PROVISIONING_PROFILE_SPECIFIER "distr ios.org.amnezia.AmneziaVPN" CACHE STRING "iOS network extension provisioning profile specifier for release")
endif()
if(NOT CLIENT_IOS_NE_PROVISIONING_PROFILE_SPECIFIER_DEBUG)
set(CLIENT_IOS_NE_PROVISIONING_PROFILE_SPECIFIER_DEBUG "dev ios.org.amnezia.AmneziaVPN" CACHE STRING "iOS network extension provisioning profile specifier for debug")
endif()

View File

@@ -54,21 +54,20 @@ target_include_directories(${PROJECT} PRIVATE ${Qt6Gui_PRIVATE_INCLUDE_DIRS})
set_target_properties(${PROJECT} PROPERTIES
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Info.plist.in
MACOSX_BUNDLE_ICON_FILE "AppIcon"
MACOSX_BUNDLE_INFO_STRING "${CLIENT_APPLICATION_NAME}"
MACOSX_BUNDLE_BUNDLE_NAME "${CLIENT_APPLICATION_NAME}"
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}"
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}"
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CLIENT_IOS_APP_ENTITLEMENTS_PATH}"
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/ios/app/main.entitlements"
XCODE_ATTRIBUTE_MARKETING_VERSION "${APPLE_PROJECT_VERSION}"
XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
XCODE_ATTRIBUTE_PRODUCT_NAME "${CLIENT_APPLICATION_NAME}"
XCODE_ATTRIBUTE_BUNDLE_INFO_STRING "${CLIENT_APPLICATION_NAME}"
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"
@@ -76,7 +75,7 @@ set_target_properties(${PROJECT} PROPERTIES
XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY ON
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/Frameworks"
XCODE_EMBED_APP_EXTENSIONS ${CLIENT_IOS_NE_TARGET_NAME}
XCODE_EMBED_APP_EXTENSIONS networkextension
)
if(DEFINED DEPLOY)
@@ -84,8 +83,8 @@ if(DEFINED DEPLOY)
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "${CLIENT_IOS_PROVISIONING_PROFILE_SPECIFIER}"
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "${CLIENT_IOS_PROVISIONING_PROFILE_SPECIFIER_DEBUG}"
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "distr ios.org.amnezia.AmneziaVPN"
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "dev ios.org.amnezia.AmneziaVPN"
)
else()
set_target_properties(${PROJECT} PROPERTIES
@@ -97,11 +96,11 @@ set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES"
XCODE_ATTRIBUTE_SWIFT_PRECOMPILE_BRIDGING_HEADER "NO"
XCODE_ATTRIBUTE_SWIFT_OBJC_INTERFACE_HEADER_NAME "${CLIENT_SWIFT_OBJC_HEADER_NAME}"
XCODE_ATTRIBUTE_SWIFT_OBJC_INTERFACE_HEADER_NAME "AmneziaVPN-Swift.h"
XCODE_ATTRIBUTE_SWIFT_OBJC_INTEROP_MODE "objcxx"
)
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "${BUILD_VPN_DEVELOPMENT_TEAM}"
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "X7UJ388FXK"
)
target_include_directories(${PROJECT} PRIVATE ${CMAKE_CURRENT_LIST_DIR})
target_compile_options(${PROJECT} PRIVATE
@@ -123,15 +122,15 @@ target_sources(${PROJECT} PRIVATE
)
target_sources(${PROJECT} PRIVATE
${CLIENT_IOS_LAUNCHSCREEN_PATH}
${CLIENT_IOS_MEDIA_ASSETS_PATH}
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/AmneziaVPNLaunchScreen.storyboard
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Media.xcassets
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
)
set_property(TARGET ${PROJECT} APPEND PROPERTY RESOURCE
${CLIENT_IOS_LAUNCHSCREEN_PATH}
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/AmneziaVPNLaunchScreen.storyboard
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
)
add_subdirectory(ios/networkextension)
add_dependencies(${PROJECT} ${CLIENT_IOS_NE_TARGET_NAME})
add_dependencies(${PROJECT} networkextension)

View File

@@ -67,17 +67,17 @@ set_target_properties(${PROJECT} PROPERTIES
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Info.plist.in
MACOSX_BUNDLE_ICON_FILE "AppIcon"
MACOSX_BUNDLE_INFO_STRING "${CLIENT_APPLICATION_NAME}"
MACOSX_BUNDLE_BUNDLE_NAME "${CLIENT_APPLICATION_NAME}"
MACOSX_BUNDLE_INFO_STRING "AmneziaVPN"
MACOSX_BUNDLE_BUNDLE_NAME "AmneziaVPN"
MACOSX_BUNDLE_BUNDLE_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
MACOSX_BUNDLE_LONG_VERSION_STRING "${APPLE_PROJECT_VERSION}-${CMAKE_PROJECT_VERSION_TWEAK}"
MACOSX_BUNDLE_SHORT_VERSION_STRING "${APPLE_PROJECT_VERSION}"
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}"
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CLIENT_MACOS_APP_ENTITLEMENTS_PATH}"
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/macos/app/app.entitlements"
XCODE_ATTRIBUTE_MARKETING_VERSION "${APPLE_PROJECT_VERSION}"
XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
XCODE_ATTRIBUTE_PRODUCT_NAME "${CLIENT_APPLICATION_NAME}"
XCODE_ATTRIBUTE_BUNDLE_INFO_STRING "${CLIENT_APPLICATION_NAME}"
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"
@@ -88,7 +88,7 @@ set_target_properties(${PROJECT} PROPERTIES
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../Frameworks"
XCODE_EMBED_APP_EXTENSIONS ${CLIENT_MACOS_NE_TARGET_NAME}
XCODE_EMBED_APP_EXTENSIONS AmneziaVPNNetworkExtension
)
if(DEPLOY)
@@ -109,11 +109,11 @@ set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES"
XCODE_ATTRIBUTE_SWIFT_PRECOMPILE_BRIDGING_HEADER "NO"
XCODE_ATTRIBUTE_SWIFT_OBJC_INTERFACE_HEADER_NAME "${CLIENT_SWIFT_OBJC_HEADER_NAME}"
XCODE_ATTRIBUTE_SWIFT_OBJC_INTERFACE_HEADER_NAME "AmneziaVPN-Swift.h"
XCODE_ATTRIBUTE_SWIFT_OBJC_INTEROP_MODE "objcxx"
)
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "${BUILD_VPN_DEVELOPMENT_TEAM}"
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "X7UJ388FXK"
)
target_include_directories(${PROJECT} PRIVATE ${CMAKE_CURRENT_LIST_DIR})
target_compile_options(${PROJECT} PRIVATE
@@ -143,7 +143,7 @@ set_property(TARGET ${PROJECT} APPEND PROPERTY RESOURCE
)
add_subdirectory(macos/networkextension)
add_dependencies(${PROJECT} ${CLIENT_MACOS_NE_TARGET_NAME})
add_dependencies(${PROJECT} AmneziaVPNNetworkExtension)
get_target_property(QtCore_location Qt6::Core LOCATION)
message("QtCore_location")
@@ -152,5 +152,5 @@ message(${QtCore_location})
get_filename_component(QT_BIN_DIR_DETECTED "${QtCore_location}/../../../../../bin" ABSOLUTE)
add_custom_command(TARGET ${PROJECT} POST_BUILD
COMMAND ${QT_BIN_DIR_DETECTED}/macdeployqt $<TARGET_BUNDLE_DIR:${PROJECT}> -appstore-compliant -qmldir=${CMAKE_CURRENT_SOURCE_DIR}
COMMAND ${QT_BIN_DIR_DETECTED}/macdeployqt $<TARGET_BUNDLE_DIR:AmneziaVPN> -appstore-compliant -qmldir=${CMAKE_CURRENT_SOURCE_DIR} -no-codesign
)

View File

@@ -62,7 +62,6 @@ set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/ui/utils/qmlUtils.h
${CLIENT_ROOT_DIR}/core/utils/api/apiUtils.h
${CLIENT_ROOT_DIR}/core/utils/osSignalHandler.h
${CLIENT_ROOT_DIR}/core/utils/swiftInterop.h
${CLIENT_ROOT_DIR}/core/utils/utilities.h
${CLIENT_ROOT_DIR}/core/utils/managementServer.h
${CLIENT_ROOT_DIR}/core/utils/constants.h

View File

@@ -244,11 +244,7 @@ ErrorCode XrayConfigurator::applyServerSettingsToRemote(const ServerCredentials
<< "container=" << static_cast<int>(container) << "host=" << credentials.hostName
<< "transport=" << srv.transport << "security=" << srv.security << "port=" << srv.port
<< "appendClient=" << appendNewClient;
QString flowValue = srv.flow;
if (flowValue.isEmpty() && srv.security == QLatin1String("reality")) {
flowValue = QStringLiteral("xtls-rprx-vision");
}
const QString flowValue = srv.flow;
QString realityPublicKey;
QString realityShortId;
if (srv.security == QLatin1String("reality")) {
@@ -563,9 +559,12 @@ QJsonObject XrayConfigurator::buildStreamSettings(const XrayServerConfig &srv, c
if (pad.obfsMode) {
if (!pad.bytesMin.isEmpty() || !pad.bytesMax.isEmpty()) {
QJsonObject br;
br[QStringLiteral("from")] = pad.bytesMin.isEmpty() ? 1 : pad.bytesMin.toInt();
br[QStringLiteral("to")] = pad.bytesMax.isEmpty() ? (pad.bytesMin.isEmpty() ? 256 : pad.bytesMin.toInt())
: pad.bytesMax.toInt();
const int fromV = pad.bytesMin.isEmpty() ? 1 : pad.bytesMin.toInt();
int toV = pad.bytesMax.isEmpty() ? 256 : pad.bytesMax.toInt();
if (toV < fromV)
toV = fromV;
br[QStringLiteral("from")] = fromV;
br[QStringLiteral("to")] = toV;
xo[QStringLiteral("xPaddingBytes")] = br;
}
xo[QStringLiteral("xPaddingKey")] = pad.key.isEmpty() ? QStringLiteral("x_padding") : pad.key;

View File

@@ -32,7 +32,7 @@
#if defined(Q_OS_IOS) || defined(MACOS_NE)
#include "platforms/ios/ios_controller.h"
#include "core/utils/swiftInterop.h"
#include <AmneziaVPN-Swift.h>
#endif
using namespace amnezia;
@@ -681,7 +681,7 @@ void SubscriptionController::removeApiConfig(const QString &serverId)
.arg(hostName)
.arg("");
SWIFT_INTEROP_NAMESPACE::removeVPNC(vpncName.toStdString());
AmneziaVPN::removeVPNC(vpncName.toStdString());
#endif
apiV2->dns1.clear();

View File

@@ -49,14 +49,96 @@ void ConnectionController::setConnectionState(Vpn::ConnectionState state)
}
}
ErrorCode ConnectionController::prepareConnection(const QString &serverId,
QJsonObject& vpnConfiguration,
DockerContainer& container)
ErrorCode ConnectionController::defaultContainerForServer(const QString &serverId, DockerContainer &container) const
{
const auto kind = m_serversRepository->serverKind(serverId);
switch (kind) {
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
const auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
if (!cfg.has_value()) {
return ErrorCode::InternalError;
}
container = cfg->defaultContainer;
return ErrorCode::NoError;
}
case serverConfigUtils::ConfigType::SelfHostedUser: {
const auto cfg = m_serversRepository->selfHostedUserConfig(serverId);
if (!cfg.has_value()) {
return ErrorCode::InternalError;
}
container = cfg->defaultContainer;
return ErrorCode::NoError;
}
case serverConfigUtils::ConfigType::Native: {
const auto cfg = m_serversRepository->nativeConfig(serverId);
if (!cfg.has_value()) {
return ErrorCode::InternalError;
}
container = cfg->defaultContainer;
return ErrorCode::NoError;
}
case serverConfigUtils::ConfigType::AmneziaPremiumV2:
case serverConfigUtils::ConfigType::AmneziaFreeV3:
case serverConfigUtils::ConfigType::ExternalPremium: {
const auto cfg = m_serversRepository->apiV2Config(serverId);
if (!cfg.has_value()) {
return ErrorCode::InternalError;
}
container = cfg->defaultContainer;
return ErrorCode::NoError;
}
case serverConfigUtils::ConfigType::AmneziaPremiumV1:
case serverConfigUtils::ConfigType::AmneziaFreeV2:
return ErrorCode::LegacyApiV1NotSupportedError;
case serverConfigUtils::ConfigType::Invalid:
default:
return ErrorCode::InternalError;
}
}
ErrorCode ConnectionController::isConnectionSupported(const QString &serverId) const
{
if (serverId.isEmpty()) {
return ErrorCode::InternalError;
}
if (!isServiceReady()) {
return ErrorCode::AmneziaServiceNotRunning;
}
const serverConfigUtils::ConfigType kind = m_serversRepository->serverKind(serverId);
if (serverConfigUtils::isLegacyApiSubscription(kind)) {
return ErrorCode::LegacyApiV1NotSupportedError;
}
DockerContainer container = DockerContainer::None;
const ErrorCode errorCode = defaultContainerForServer(serverId, container);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
if (container == DockerContainer::None) {
if (serverConfigUtils::isApiV2Subscription(kind)) {
return ErrorCode::NoError;
}
return ErrorCode::NoInstalledContainersError;
}
if (ContainerUtils::isUnsupportedContainer(container)) {
return ErrorCode::LegacyContainerNotSupportedError;
}
if (!isContainerSupported(container)) {
return ErrorCode::NotSupportedOnThisPlatform;
}
return ErrorCode::NoError;
}
ErrorCode ConnectionController::prepareConnection(const QString &serverId,
QJsonObject& vpnConfiguration,
DockerContainer& container)
{
ContainerConfig containerConfigModel;
QPair<QString, QString> dns;
QString hostName;
@@ -120,10 +202,6 @@ ErrorCode ConnectionController::prepareConnection(const QString &serverId,
return ErrorCode::InternalError;
}
if (!isContainerSupported(container)) {
return ErrorCode::NotSupportedOnThisPlatform;
}
vpnConfiguration = createConnectionConfiguration(dns, isApiConfig, hostName, description, configVersion,
containerConfigModel, container);

View File

@@ -34,6 +34,8 @@ public:
QJsonObject& vpnConfiguration,
DockerContainer& container);
ErrorCode isConnectionSupported(const QString &serverId) const;
ErrorCode openConnection(const QString &serverId);
void closeConnection();
@@ -73,6 +75,8 @@ signals:
#endif
private:
ErrorCode defaultContainerForServer(const QString &serverId, DockerContainer &container) const;
SecureServersRepository* m_serversRepository;
SecureAppSettingsRepository* m_appSettingsRepository;
VpnConnection* m_vpnConnection;

View File

@@ -18,7 +18,7 @@
#if defined(Q_OS_IOS)
#include "platforms/ios/ios_controller.h"
#include "core/utils/swiftInterop.h"
#include <AmneziaVPN-Swift.h>
#endif
CoreController::CoreController(const QSharedPointer<VpnConnection> &vpnConnection, SecureQSettings* settings,
@@ -191,7 +191,7 @@ void CoreController::initControllers()
m_languageUiController = new LanguageUiController(m_settingsController, m_languageModel, this);
setQmlContextProperty("LanguageUiController", m_languageUiController);
m_settingsUiController = new SettingsUiController(m_settingsController, m_serversController, m_languageUiController, this);
m_settingsUiController = new SettingsUiController(m_settingsController, m_serversController, this);
setQmlContextProperty("SettingsController", m_settingsUiController);
m_pageController = new PageController(m_serversController, m_settingsController, this);
@@ -255,7 +255,7 @@ void CoreController::initAppleController()
{
#ifdef Q_OS_IOS
IosController::Instance()->initialize();
QTimer::singleShot(0, this, [this]() { SWIFT_INTEROP_NAMESPACE::toggleScreenshots(m_appSettingsRepository->isScreenshotsEnabled()); });
QTimer::singleShot(0, this, [this]() { AmneziaVPN::toggleScreenshots(m_appSettingsRepository->isScreenshotsEnabled()); });
#endif
}

View File

@@ -1,6 +1,7 @@
#include "coreSignalHandlers.h"
#include <QTimer>
#include <QtConcurrent>
#include "core/utils/selfhosted/sshSession.h"
#include "core/utils/errorCodes.h"
@@ -33,7 +34,6 @@
#include "core/controllers/connectionController.h"
#include "ui/models/clientManagementModel.h"
#include "ui/controllers/api/apiNewsUiController.h"
#include "ui/models/api/apiCountryModel.h"
#include "ui/models/containersModel.h"
#include "core/utils/containerEnum.h"
@@ -48,7 +48,7 @@
#ifdef Q_OS_IOS
#include "platforms/ios/ios_controller.h"
#include "core/utils/swiftInterop.h"
#include <AmneziaVPN-Swift.h>
#endif
CoreSignalHandlers::CoreSignalHandlers(CoreController* coreController, QObject* parent)
@@ -145,7 +145,9 @@ void CoreSignalHandlers::initExportControllerHandler()
});
connect(m_coreController->m_exportController, &ExportController::revokeClientRequested, this,
[this](const QString &serverId, int row, DockerContainer container) {
m_coreController->m_usersController->revokeClient(serverId, row, container);
QtConcurrent::run([this, serverId, row, container]() {
m_coreController->m_usersController->revokeClient(serverId, row, container);
});
});
connect(m_coreController->m_exportController, &ExportController::renameClientRequested, this,
[this](const QString &serverId, int row, const QString &clientName, DockerContainer container) {
@@ -156,15 +158,17 @@ void CoreSignalHandlers::initExportControllerHandler()
void CoreSignalHandlers::initImportControllerHandler()
{
connect(m_coreController->m_importCoreController, &ImportController::importFinished, this, [this]() {
if (!m_coreController->m_connectionController->isConnected()) {
int newServerIndex = m_coreController->m_serversController->getServersCount() - 1;
const QString serverId = m_coreController->m_serversController->getServerId(newServerIndex);
if (!serverId.isEmpty()) {
m_coreController->m_serversController->setDefaultServer(serverId);
}
if (m_coreController->m_serversUiController) {
m_coreController->m_serversUiController->setProcessedServerId(serverId);
}
if (m_coreController->m_connectionUiController->isConnected()) {
return;
}
const int newServerIndex = m_coreController->m_serversController->getServersCount() - 1;
const QString serverId = m_coreController->m_serversController->getServerId(newServerIndex);
if (!serverId.isEmpty()) {
m_coreController->m_serversController->setDefaultServer(serverId);
}
if (m_coreController->m_serversUiController) {
m_coreController->m_serversUiController->setProcessedServerId(serverId);
}
});
}
@@ -176,17 +180,14 @@ void CoreSignalHandlers::initApiCountryModelUpdateHandler()
if (processedServerId.isEmpty()) {
return;
}
QJsonArray availableCountries;
QString serverCountryCode;
const auto apiV2 = m_coreController->m_serversRepository->apiV2Config(processedServerId);
if (apiV2.has_value()) {
availableCountries = apiV2->apiConfig.availableCountries;
serverCountryCode = apiV2->apiConfig.serverCountryCode;
if (!apiV2.has_value()) {
return;
}
m_coreController->m_apiCountryModel->updateModel(availableCountries, serverCountryCode);
m_coreController->m_apiCountryModel->updateModel(apiV2->apiConfig.availableCountries,
apiV2->apiConfig.serverCountryCode);
});
}
@@ -204,13 +205,15 @@ 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);
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) {
m_coreController->m_usersController->appendClient(serverId, clientId, clientName, container);
});
}, Qt::DirectConnection);
connect(m_coreController->m_usersController, &UsersController::adminConfigRevoked, m_coreController->m_installController,
&InstallController::clearCachedProfile);
@@ -237,13 +240,16 @@ void CoreSignalHandlers::initLanguageHandler()
connect(m_coreController->m_settingsUiController, &SettingsUiController::resetLanguageToSystem, m_coreController->m_languageUiController, [this]() {
m_coreController->m_languageUiController->changeLanguage(m_coreController->m_languageUiController->getSystemLanguageEnum());
});
connect(m_coreController->m_settingsUiController, &SettingsUiController::appLanguageChanged, m_coreController->m_languageUiController, [this]() {
m_coreController->m_languageUiController->onAppLanguageChanged(m_coreController->m_settingsController->getAppLanguage());
});
}
void CoreSignalHandlers::initAutoConnectHandler()
{
if (m_coreController->m_settingsUiController->isAutoConnectEnabled()
&& !m_coreController->m_serversController->getDefaultServerId().isEmpty()) {
QTimer::singleShot(1000, this, [this]() { m_coreController->m_connectionUiController->openConnection(); });
QTimer::singleShot(1000, this, [this]() { m_coreController->m_connectionUiController->toggleConnection(); });
}
}
@@ -284,6 +290,8 @@ void CoreSignalHandlers::initClientManagementModelUpdateHandler()
m_coreController->m_clientManagementModel, &ClientManagementModel::updateModel);
connect(m_coreController->m_usersController, &UsersController::clientRenamed,
m_coreController->m_clientManagementModel, &ClientManagementModel::updateClientName);
connect(m_coreController->m_usersController, &UsersController::revokeFinished,
m_coreController->m_exportController, &ExportController::revokeFinished);
}
void CoreSignalHandlers::initSitesModelUpdateHandler()
@@ -348,6 +356,9 @@ void CoreSignalHandlers::initUnsupportedConnectDrawerHandler()
{
connect(m_coreController->m_subscriptionUiController, &SubscriptionUiController::unsupportedConnectDrawerRequested,
m_coreController->m_pageController, &PageController::unsupportedConnectDrawerRequested);
connect(m_coreController->m_connectionUiController, &ConnectionUiController::unsupportedConnectDrawerRequested,
m_coreController->m_pageController, &PageController::unsupportedConnectDrawerRequested);
}
void CoreSignalHandlers::initStrictKillSwitchHandler()
@@ -404,7 +415,7 @@ void CoreSignalHandlers::initIosImportHandler()
void CoreSignalHandlers::initIosSettingsHandler()
{
#ifdef Q_OS_IOS
connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::screenshotsEnabledChanged, [](bool enabled) { SWIFT_INTEROP_NAMESPACE::toggleScreenshots(enabled); });
connect(m_coreController->m_appSettingsRepository, &SecureAppSettingsRepository::screenshotsEnabledChanged, [](bool enabled) { AmneziaVPN::toggleScreenshots(enabled); });
#endif
}

View File

@@ -48,6 +48,7 @@ signals:
void appendClientRequested(const QString &serverId, const QString &clientId, const QString &clientName, DockerContainer container);
void updateClientsRequested(const QString &serverId, DockerContainer container);
void revokeClientRequested(const QString &serverId, int row, DockerContainer container);
void revokeFinished(ErrorCode errorCode);
void renameClientRequested(const QString &serverId, int row, const QString &clientName, DockerContainer container);
public slots:

View File

@@ -72,6 +72,16 @@ namespace
}
return false;
}
QString buildRemoveContainerScript(const amnezia::ScriptVars &vars, bool removeDataVolume)
{
QString script = SshSession::replaceVars(amnezia::scriptData(SharedScriptType::remove_container), vars);
if (removeDataVolume) {
script += QLatin1String("\nsudo docker volume rm -f $CONTAINER_NAME-data 2>/dev/null || true");
script = SshSession::replaceVars(script, vars);
}
return script;
}
}
InstallController::InstallController(SecureServersRepository *serversRepository,
@@ -120,14 +130,10 @@ ErrorCode InstallController::setupContainer(const ServerCredentials &credentials
return e;
qDebug().noquote() << "InstallController::setupContainer prepareHostWorker finished";
amnezia::ScriptVars removeContainerVars =
const amnezia::ScriptVars removeContainerVars =
amnezia::genBaseVars(credentials, container, QString(), QString());
if (!isUpdate) {
removeContainerVars.append({ { "$REMOVE_CONTAINER_DATA", QStringLiteral("1") } });
}
sshSession.runScript(credentials,
sshSession.replaceVars(amnezia::scriptData(SharedScriptType::remove_container),
removeContainerVars));
const bool removeDataVolume = !isUpdate && (container == DockerContainer::MtProxy || container == DockerContainer::Telemt);
sshSession.runScript(credentials, buildRemoveContainerScript(removeContainerVars, removeDataVolume));
qDebug().noquote() << "InstallController::setupContainer removeContainer finished";
qDebug().noquote() << "buildContainerWorker start";
@@ -152,8 +158,8 @@ ErrorCode InstallController::setupContainer(const ServerCredentials &credentials
return startupContainerWorker(credentials, container, config, sshSession);
}
ErrorCode InstallController::updateContainer(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig,
ContainerConfig &newConfig)
ErrorCode InstallController::updateServerConfig(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig,
ContainerConfig &newConfig)
{
if (!isUpdateDockerContainerRequired(container, oldConfig, newConfig)) {
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
@@ -185,7 +191,7 @@ ErrorCode InstallController::updateContainer(const QString &serverId, DockerCont
SshSession sshSession(this);
bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig);
qDebug() << "InstallController::updateContainer for container" << container << "reinstall required is" << reinstallRequired;
qDebug() << "InstallController::updateServerConfig for container" << container << "reinstall required is" << reinstallRequired;
bool xrayServerSettingsChanged = false;
if (container == DockerContainer::Xray || container == DockerContainer::SSXray) {
@@ -213,11 +219,11 @@ ErrorCode InstallController::updateContainer(const QString &serverId, DockerCont
if (errorCode == ErrorCode::NoError && xrayServerSettingsChanged && !skipXrayInboundSync) {
DnsSettings dnsSettings = { m_appSettingsRepository->primaryDns(), m_appSettingsRepository->secondaryDns() };
XrayConfigurator xrayConfigurator(&sshSession);
qDebug() << "InstallController::updateContainer applying Xray server inbound sync, reinstall="
qDebug() << "InstallController::updateServerConfig applying Xray server inbound sync, reinstall="
<< reinstallRequired;
errorCode = xrayConfigurator.applyServerSettingsToRemote(credentials, container, newConfig, dnsSettings, false);
if (errorCode != ErrorCode::NoError) {
qDebug() << "InstallController::updateContainer Xray inbound sync failed, error="
qDebug() << "InstallController::updateServerConfig Xray inbound sync failed, error="
<< static_cast<int>(errorCode);
}
}
@@ -228,7 +234,9 @@ ErrorCode InstallController::updateContainer(const QString &serverId, DockerCont
} else if (container == DockerContainer::Telemt) {
TelemtInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, newConfig);
}
clearCachedProfile(serverId, container);
if (reinstallRequired) {
clearCachedProfile(serverId, container);
}
adminConfig->updateContainerConfig(container, newConfig);
m_serversRepository->editServer(serverId, adminConfig->toJson(), serverConfigUtils::ConfigType::SelfHostedAdmin);
}
@@ -236,6 +244,41 @@ ErrorCode InstallController::updateContainer(const QString &serverId, DockerCont
return errorCode;
}
ErrorCode InstallController::updateClientConfig(const QString &serverId, DockerContainer container, ContainerConfig &newConfig)
{
switch (m_serversRepository->serverKind(serverId)) {
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
auto config = m_serversRepository->selfHostedAdminConfig(serverId);
if (!config.has_value()) {
return ErrorCode::InternalError;
}
config->updateContainerConfig(container, newConfig);
m_serversRepository->editServer(serverId, config->toJson(), serverConfigUtils::ConfigType::SelfHostedAdmin);
return ErrorCode::NoError;
}
case serverConfigUtils::ConfigType::SelfHostedUser: {
auto config = m_serversRepository->selfHostedUserConfig(serverId);
if (!config.has_value()) {
return ErrorCode::InternalError;
}
config->updateContainerConfig(container, newConfig);
m_serversRepository->editServer(serverId, config->toJson(), serverConfigUtils::ConfigType::SelfHostedUser);
return ErrorCode::NoError;
}
case serverConfigUtils::ConfigType::Native: {
auto config = m_serversRepository->nativeConfig(serverId);
if (!config.has_value()) {
return ErrorCode::InternalError;
}
config->updateContainerConfig(container, newConfig);
m_serversRepository->editServer(serverId, config->toJson(), serverConfigUtils::ConfigType::Native);
return ErrorCode::NoError;
}
default:
return ErrorCode::InternalError;
}
}
void InstallController::clearCachedProfile(const QString &serverId, DockerContainer container)
{
if (ContainerUtils::containerService(container) == ServiceType::Other) {
@@ -795,8 +838,8 @@ ErrorCode InstallController::installDockerWorker(const ServerCredentials &creden
qDebug().noquote() << "InstallController::installDockerWorker" << stdOut;
if (container == DockerContainer::Awg2) {
QRegularExpression regex(R"(Linux\s+(\d+)\.(\d+)[^\d]*)");
QRegularExpressionMatch match = regex.match(stdOut);
QRegularExpression kernelVersionRegex(R"(Linux\s+(\d+)\.(\d+)[^\d]*)");
QRegularExpressionMatch match = kernelVersionRegex.match(stdOut);
if (match.hasMatch()) {
int majorVersion = match.captured(1).toInt();
int minorVersion = match.captured(2).toInt();
@@ -809,8 +852,19 @@ ErrorCode InstallController::installDockerWorker(const ServerCredentials &creden
if (stdOut.contains("lock"))
return ErrorCode::ServerPacketManagerError;
if (stdOut.contains("command not found"))
if (stdOut.contains("Container runtime is not supported"))
return ErrorCode::ServerContainerRuntimeNotSupported;
QRegularExpression notFoundRegex(
R"(^.*(?:sudo:|docker:).*not found.*$)",
QRegularExpression::MultilineOption);
if (notFoundRegex.match(stdOut).hasMatch()) {
return ErrorCode::ServerDockerFailedError;
}
if (stdOut.contains("Container runtime service not running"))
return ErrorCode::ContainerRuntimeServiceNotRunning;
return error;
}
@@ -847,7 +901,7 @@ ErrorCode InstallController::isUserInSudo(const ServerCredentials &credentials,
return ErrorCode::ServerUserNotInSudo;
if (stdOut.contains("can't cd to") || stdOut.contains("Permission denied") || stdOut.contains("No such file or directory"))
return ErrorCode::ServerUserDirectoryNotAccessible;
if (stdOut.contains("sudoers") || stdOut.contains("is not allowed to run sudo on"))
if (stdOut.contains(QRegularExpression(R"(\bsudoers\b)")) || stdOut.contains("is not allowed to") || stdOut.contains("can't do that"))
return ErrorCode::ServerUserNotAllowedInSudoers;
if (stdOut.contains("password is required") || stdOut.contains("authentication is required"))
return ErrorCode::ServerUserPasswordRequired;
@@ -980,12 +1034,11 @@ ErrorCode InstallController::removeContainer(const QString &serverId, DockerCont
return ErrorCode::InternalError;
}
SshSession sshSession(this);
amnezia::ScriptVars removeContainerVars =
const amnezia::ScriptVars removeContainerVars =
amnezia::genBaseVars(credentials, container, QString(), QString());
removeContainerVars.append({ { "$REMOVE_CONTAINER_DATA", QStringLiteral("1") } });
ErrorCode errorCode = sshSession.runScript(
credentials,
sshSession.replaceVars(amnezia::scriptData(SharedScriptType::remove_container), removeContainerVars));
const bool removeDataVolume = (container == DockerContainer::MtProxy || container == DockerContainer::Telemt);
ErrorCode errorCode =
sshSession.runScript(credentials, buildRemoveContainerScript(removeContainerVars, removeDataVolume));
if (errorCode == ErrorCode::NoError) {
QMap<DockerContainer, ContainerConfig> containers = adminConfig->containers;
@@ -1463,7 +1516,7 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
QString transportProtoStr = containerAndPortMatch.captured(3);
DockerContainer container = ContainerUtils::containerFromString(name);
if (container == DockerContainer::None) {
if (container == DockerContainer::None || ContainerUtils::isUnsupportedContainer(container)) {
continue;
}
@@ -1488,7 +1541,7 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
QString transportProtoStr = torOrDnsRegMatch.captured(3);
DockerContainer container = ContainerUtils::containerFromString(name);
if (container == DockerContainer::None) {
if (container == DockerContainer::None || ContainerUtils::isUnsupportedContainer(container)) {
continue;
}

View File

@@ -34,7 +34,12 @@ public:
~InstallController();
ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, ContainerConfig &config, bool isUpdate = false);
ErrorCode updateContainer(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig, ContainerConfig &newConfig);
// Updates server-side container settings (admin self-hosted only): reconfigures the container over SSH.
ErrorCode updateServerConfig(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig, ContainerConfig &newConfig);
// Updates client-local settings only: rewrites the stored container config for any self-hosted/native server. No SSH.
ErrorCode updateClientConfig(const QString &serverId, DockerContainer container, ContainerConfig &newConfig);
ErrorCode rebootServer(const QString &serverId);
ErrorCode removeAllContainers(const QString &serverId);

View File

@@ -698,7 +698,7 @@ ErrorCode UsersController::revokeXray(const int row,
QString restartScript = QString("sudo docker restart $CONTAINER_NAME");
error = sshSession->runScript(
credentials,
credentials,
sshSession->replaceVars(restartScript, amnezia::genBaseVars(credentials, container, QString(), QString()))
);
if (error != ErrorCode::NoError) {
@@ -758,14 +758,17 @@ ErrorCode UsersController::revokeClient(const QString &serverId, const int index
ContainerConfig containerCfg = adminConfig->containerConfig(container);
QString containerClientId = containerCfg.protocolConfig.clientId();
if (!clientId.isEmpty() && !containerClientId.isEmpty() && containerClientId.contains(clientId)) {
const bool isAdminMatch = !clientId.isEmpty() && !containerClientId.isEmpty() && containerClientId.contains(clientId);
if (isAdminMatch) {
emit adminConfigRevoked(serverId, container);
}
emit clientRevoked(index);
emit clientsUpdated(m_clientsTable);
}
emit clientsUpdated(m_clientsTable);
emit revokeFinished(errorCode);
return errorCode;
}

View File

@@ -37,6 +37,7 @@ signals:
void clientAdded(const QJsonObject &client);
void clientRenamed(int row, const QString &newName);
void clientRevoked(int row);
void revokeFinished(ErrorCode errorCode);
void adminConfigRevoked(const QString &serverId, DockerContainer container);
public slots:

View File

@@ -8,7 +8,7 @@
#include "core/models/serverDescription.h"
#if defined(Q_OS_IOS) || defined(MACOS_NE)
#include "core/utils/swiftInterop.h"
#include <AmneziaVPN-Swift.h>
#endif

View File

@@ -32,7 +32,7 @@ XrayXPaddingConfig XrayXPaddingConfig::fromJson(const QJsonObject &json)
c.bytesMin = json.value(configKey::xPaddingBytesMin).toString();
c.bytesMax = json.value(configKey::xPaddingBytesMax).toString();
c.obfsMode = json.value(configKey::xPaddingObfsMode).toBool(true);
c.key = json.value(configKey::xPaddingKey).toString(protocols::xray::defaultSite);
c.key = json.value(configKey::xPaddingKey).toString();
c.header = json.value(configKey::xPaddingHeader).toString();
c.placement = json.value(configKey::xPaddingPlacement).toString(protocols::xray::defaultXPaddingPlacement);
c.method = json.value(configKey::xPaddingMethod).toString(protocols::xray::defaultXPaddingMethod);
@@ -365,6 +365,8 @@ XrayServerConfig XrayServerConfig::fromJson(const QJsonObject &json)
bool XrayServerConfig::hasEqualServerSettings(const XrayServerConfig &other) const
{
return port == other.port
&& transportProto == other.transportProto
&& subnetAddress == other.subnetAddress
&& site == other.site
&& security == other.security
&& flow == other.flow
@@ -466,6 +468,17 @@ XrayProtocolConfig XrayProtocolConfig::fromJson(const QJsonObject &json)
}
}
}
const QJsonArray outbounds = parsed.value(protocols::xray::outbounds).toArray();
if (!outbounds.isEmpty()) {
const QJsonObject settings = outbounds[0].toObject().value(protocols::xray::settings).toObject();
const QJsonArray vnext = settings.value(protocols::xray::vnext).toArray();
if (!vnext.isEmpty()) {
const QJsonArray users = vnext[0].toObject().value(protocols::xray::users).toArray();
if (!users.isEmpty()) {
clientCfg.id = users[0].toObject().value(protocols::xray::id).toString();
}
}
}
c.clientConfig = clientCfg;
} else {
c.clientConfig = XrayClientConfig::fromJson(parsed);

View File

@@ -29,6 +29,11 @@ ContainerConfig NativeServerConfig::containerConfig(DockerContainer container) c
return containers.value(container);
}
void NativeServerConfig::updateContainerConfig(DockerContainer container, const ContainerConfig &config)
{
containers[container] = config;
}
QPair<QString, QString> NativeServerConfig::getDnsPair(const QString &primaryDns, const QString &secondaryDns) const
{
QString d1 = dns1;

View File

@@ -27,6 +27,8 @@ struct NativeServerConfig {
bool hasContainers() const;
ContainerConfig containerConfig(DockerContainer container) const;
void updateContainerConfig(DockerContainer container, const ContainerConfig &config);
QPair<QString, QString> getDnsPair(const QString &primaryDns, const QString &secondaryDns) const;
QJsonObject toJson() const;

View File

@@ -43,6 +43,11 @@ ContainerConfig SelfHostedUserServerConfig::containerConfig(DockerContainer cont
return containers.value(container);
}
void SelfHostedUserServerConfig::updateContainerConfig(DockerContainer container, const ContainerConfig &config)
{
containers[container] = config;
}
QPair<QString, QString> SelfHostedUserServerConfig::getDnsPair(const QString &primaryDns,
const QString &secondaryDns) const
{

View File

@@ -32,6 +32,8 @@ struct SelfHostedUserServerConfig {
bool hasContainers() const;
ContainerConfig containerConfig(DockerContainer container) const;
void updateContainerConfig(DockerContainer container, const ContainerConfig &config);
QPair<QString, QString> getDnsPair(const QString &primaryDns, const QString &secondaryDns) const;
QJsonObject toJson() const;

View File

@@ -39,33 +39,44 @@ QString OpenVpnProtocol::defaultConfigPath()
return p;
}
void OpenVpnProtocol::stop()
void OpenVpnProtocol::cleanupResources()
{
qDebug() << "OpenVpnProtocol::stop()";
setConnectionState(Vpn::ConnectionState::Disconnecting);
// TODO: need refactoring
// sendTermSignal() will even return true while server connected ???
if ((m_connectionState == Vpn::ConnectionState::Preparing) || (m_connectionState == Vpn::ConnectionState::Connecting)
|| (m_connectionState == Vpn::ConnectionState::Connected)
|| (m_connectionState == Vpn::ConnectionState::Reconnecting)) {
if (m_openVpnProcess || openVpnProcessIsRunning()) {
if (!sendTermSignal()) {
killOpenVpnProcess();
}
QThread::msleep(10);
m_managementServer.stop();
}
m_managementServer.stop();
#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
QRemoteObjectPendingReply<bool> reply = iface->disableKillSwitch();
if (!reply.waitForFinished(1000) && !reply.returnValue()) {
qWarning() << "OpenVpnProtocol::stop(): Failed to disable killswitch";
qWarning() << "OpenVpnProtocol::cleanupResources(): Failed to disable killswitch";
}
});
#endif
}
setConnectionState(Vpn::ConnectionState::Disconnected);
void OpenVpnProtocol::stop()
{
qDebug() << "OpenVpnProtocol::stop()";
const bool wasActive = m_connectionState == Vpn::ConnectionState::Preparing
|| m_connectionState == Vpn::ConnectionState::Connecting
|| m_connectionState == Vpn::ConnectionState::Connected
|| m_connectionState == Vpn::ConnectionState::Reconnecting;
if (wasActive) {
setConnectionState(Vpn::ConnectionState::Disconnecting);
}
cleanupResources();
if (wasActive || m_connectionState == Vpn::ConnectionState::Disconnecting) {
setConnectionState(Vpn::ConnectionState::Disconnected);
}
}
ErrorCode OpenVpnProtocol::prepare()
@@ -168,7 +179,7 @@ void OpenVpnProtocol::updateRouteGateway(QString line)
ErrorCode OpenVpnProtocol::start()
{
OpenVpnProtocol::stop();
cleanupResources();
if (!QFileInfo::exists(configPath())) {
setLastError(ErrorCode::OpenVpnConfigMissing);

View File

@@ -29,6 +29,7 @@ protected slots:
void onReadyReadDataFromManagementServer();
private:
void cleanupResources();
QString configPath() const;
bool openVpnProcessIsRunning() const;
bool sendTermSignal();

View File

@@ -208,8 +208,8 @@ QString SecureServersRepository::nextAvailableServerName() const
int i = 0;
QString candidate;
do {
i++;
candidate = QStringLiteral("Server %1").arg(i);
++i;
candidate = tr("Server") + QLatin1Char(' ') + QString::number(i);
} while (usedNames.contains(candidate));
return candidate;

View File

@@ -1,10 +0,0 @@
#ifndef APP_UI_CONFIG_H
#define APP_UI_CONFIG_H
#define APP_INSTANCE_NAME "@CLIENT_APP_INSTANCE_NAME@"
#define APP_QML_ENTRYPOINT "@CLIENT_QML_ENTRYPOINT@"
#define APP_QML_IMPORT_PATH "@CLIENT_QML_IMPORT_PATH@"
#define APP_QML_PAGES_PREFIX "@CLIENT_QML_PAGES_PREFIX@"
#define APP_UI_FONT_RESOURCE "@CLIENT_UI_FONT_RESOURCE@"
#endif

View File

@@ -15,6 +15,8 @@ namespace amnezia
Awg2,
WireGuard,
OpenVpn,
Cloak,
ShadowSocks,
Ipsec,
Xray,
SSXray,

View File

@@ -21,6 +21,8 @@ QString ContainerUtils::containerToString(DockerContainer c)
{
if (c == DockerContainer::None)
return "none";
if (c == DockerContainer::Cloak)
return "amnezia-openvpn-cloak";
if (c == DockerContainer::Awg)
return "amnezia-awg";
if (c == DockerContainer::Awg2)
@@ -62,6 +64,8 @@ QMap<DockerContainer, QString> ContainerUtils::containerHumanNames()
{
return { { DockerContainer::None, "Not installed" },
{ DockerContainer::OpenVpn, "OpenVPN" },
{ DockerContainer::ShadowSocks, "OpenVPN over SS" },
{ DockerContainer::Cloak, "OpenVPN over Cloak" },
{ DockerContainer::WireGuard, "WireGuard" },
{ DockerContainer::Awg, "AmneziaWG" },
{ DockerContainer::Awg2, "AmneziaWG" },
@@ -83,6 +87,10 @@ QMap<DockerContainer, QString> ContainerUtils::containerDescriptions()
return { { DockerContainer::OpenVpn,
QObject::tr("OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its "
"own security protocol with SSL/TLS for key exchange.") },
{ DockerContainer::ShadowSocks,
QObject::tr("This protocol is no longer supported.") },
{ DockerContainer::Cloak,
QObject::tr("This protocol is no longer supported.") },
{ DockerContainer::WireGuard,
QObject::tr("WireGuard - popular VPN protocol with high performance, high speed and low power "
"consumption.") },
@@ -194,6 +202,9 @@ QMap<DockerContainer, QString> ContainerUtils::containerDetailedDescriptions()
ServiceType ContainerUtils::containerService(DockerContainer c)
{
if (isUnsupportedContainer(c)) {
return ServiceType::Vpn;
}
return ProtocolUtils::protocolService(defaultProtocol(c));
}
@@ -202,6 +213,8 @@ Proto ContainerUtils::defaultProtocol(DockerContainer c)
switch (c) {
case DockerContainer::None: return Proto::Unknown;
case DockerContainer::OpenVpn: return Proto::OpenVpn;
case DockerContainer::Cloak:
case DockerContainer::ShadowSocks: return Proto::Unknown;
case DockerContainer::WireGuard: return Proto::WireGuard;
case DockerContainer::Awg2: return Proto::Awg;
case DockerContainer::Awg: return Proto::Awg;
@@ -252,6 +265,8 @@ bool ContainerUtils::isSupportedByCurrentPlatform(DockerContainer c)
// macOS build using Network Extension allow OpenVPN for parity with iOS.
switch (c) {
case DockerContainer::OpenVpn: return true;
case DockerContainer::Cloak: return false;
case DockerContainer::ShadowSocks: return false;
case DockerContainer::WireGuard: return true;
case DockerContainer::Awg2: return true;
case DockerContainer::Awg: return true;
@@ -336,6 +351,10 @@ int ContainerUtils::easySetupOrder(DockerContainer container)
bool ContainerUtils::isShareable(DockerContainer container)
{
if (isUnsupportedContainer(container)) {
return false;
}
switch (container) {
case DockerContainer::TorWebSite: return false;
case DockerContainer::Dns: return false;
@@ -352,6 +371,11 @@ bool ContainerUtils::isAwgContainer(DockerContainer container)
return container == DockerContainer::Awg || container == DockerContainer::Awg2;
}
bool ContainerUtils::isUnsupportedContainer(DockerContainer container)
{
return container == DockerContainer::Cloak || container == DockerContainer::ShadowSocks;
}
QJsonObject ContainerUtils::getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig)
{
QString protocolConfigString = containerConfig.value(ProtocolUtils::protoToString(protocol))

View File

@@ -45,6 +45,8 @@ namespace amnezia
bool isAwgContainer(DockerContainer container);
bool isUnsupportedContainer(DockerContainer container);
QJsonObject getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig);
int installPageOrder(DockerContainer container);

View File

@@ -38,6 +38,8 @@ namespace amnezia
XrayServerConfigInvalid = 215,
XrayServerNoVlessClients = 216,
XrayRealityKeysReadFailed = 217,
ServerContainerRuntimeNotSupported = 218,
ContainerRuntimeServiceNotRunning = 219,
// Ssh connection errors
SshRequestDeniedError = 300,
@@ -79,6 +81,7 @@ namespace amnezia
ImportBackupFileUseRestoreInstead = 903,
RestoreBackupInvalidError = 904,
LegacyApiV1NotSupportedError = 905,
LegacyContainerNotSupportedError = 906,
// Android errors
AndroidError = 1000,
@@ -123,5 +126,3 @@ namespace amnezia
Q_DECLARE_METATYPE(amnezia::ErrorCode)
#endif // ERRORCODES_H

View File

@@ -39,6 +39,8 @@ QString errorString(ErrorCode code) {
case(ErrorCode::XrayRealityKeysReadFailed):
errorMessage = QObject::tr("Server error: failed to read XRay Reality keys from the server");
break;
case(ErrorCode::ServerContainerRuntimeNotSupported): errorMessage = QObject::tr("Server error: The default container runtime available for installation on this server is not supported.\n Install Docker Engine on the server manually and try again."); break;
case(ErrorCode::ContainerRuntimeServiceNotRunning): errorMessage = QObject::tr("Container runtime error: The container runtime service is not running.\n Check the container runtime service on the server, or wait about a minute and try again."); break;
// Libssh errors
case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break;
@@ -69,6 +71,7 @@ QString errorString(ErrorCode code) {
case (ErrorCode::ImportBackupFileUseRestoreInstead): errorMessage = QObject::tr("Backup files cannot be imported here. Use 'Restore from backup' instead."); break;
case (ErrorCode::RestoreBackupInvalidError): errorMessage = QObject::tr("Backup file is corrupted or has invalid format"); break;
case (ErrorCode::LegacyApiV1NotSupportedError): errorMessage = QObject::tr("This legacy Amnezia subscription format is no longer supported"); break;
case (ErrorCode::LegacyContainerNotSupportedError): errorMessage = QObject::tr("This protocol is no longer supported. Please select another protocol or remove this container from the server settings."); break;
case (ErrorCode::ImportOpenConfigError): errorMessage = QObject::tr("Unable to open config file"); break;
case (ErrorCode::NoInstalledContainersError): errorMessage = QObject::tr("VPN Protocols is not installed.\n Please install VPN container at first"); break;

View File

@@ -286,7 +286,7 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
return { resGateway, QNetworkInterface::interfaceFromIndex(resIndex) };
#endif
#ifdef Q_OS_LINUX
constexpr int BUFFER_SIZE = 100;
constexpr int BUFFER_SIZE = 8192;
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[BUFFER_SIZE], buffer[BUFFER_SIZE];
char msgbuf[100], 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(nlmsg, received_bytes) == 0) ||
(nlmsg->nlmsg_type == NLMSG_ERROR))
if((NLMSG_OK(nlh, received_bytes) == 0) ||
(nlh->nlmsg_type == NLMSG_ERROR))
{
perror("Error in received packet");
return {};
@@ -355,13 +355,15 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
}
/* Break if its not a multi part message */
if ((nlmsg->nlmsg_flags & NLM_F_MULTI) == 0)
if ((nlh->nlmsg_flags & NLM_F_MULTI) == 0)
break;
}
while ((nlmsg->nlmsg_seq != msgseq) || (nlmsg->nlmsg_pid != getpid()));
while ((nlh->nlmsg_seq != msgseq) || (nlh->nlmsg_pid != getpid()));
/* parse response */
for ( ; NLMSG_OK(nlh, received_bytes); nlh = NLMSG_NEXT(nlh, received_bytes))
int remaining = msg_len + received_bytes;
nlh = (struct nlmsghdr *) buffer;
for ( ; NLMSG_OK(nlh, remaining); nlh = NLMSG_NEXT(nlh, remaining))
{
/* Get the route data */
route_entry = (struct rtmsg *) NLMSG_DATA(nlh);
@@ -370,6 +372,10 @@ 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);
@@ -395,6 +401,8 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
break;
}
}
if (!(*gateway_address) || !(*interface))
qDebug() << "getGatewayAndIface: no gateway found";
close(sock);
return { gateway_address, QNetworkInterface::interfaceFromName(interface) };
#endif

View File

@@ -1,25 +0,0 @@
#ifndef SWIFTINTEROP_H
#define SWIFTINTEROP_H
#if defined(Q_OS_IOS) || defined(MACOS_NE)
#include "core/utils/swiftInteropConfig.h"
#ifndef SWIFT_INTEROP_OBJC_HEADER
#ifdef AMNEZIA_SWIFT_OBJC_HEADER
#define SWIFT_INTEROP_OBJC_HEADER AMNEZIA_SWIFT_OBJC_HEADER
#else
#define SWIFT_INTEROP_OBJC_HEADER "AmneziaVPN-Swift.h"
#endif
#endif
#ifndef SWIFT_INTEROP_NAMESPACE
#ifdef AMNEZIA_SWIFT_NAMESPACE
#define SWIFT_INTEROP_NAMESPACE AMNEZIA_SWIFT_NAMESPACE
#else
#define SWIFT_INTEROP_NAMESPACE AmneziaVPN
#endif
#endif
#include SWIFT_INTEROP_OBJC_HEADER
#endif
#endif

View File

@@ -1,7 +0,0 @@
#ifndef SWIFT_INTEROP_CONFIG_H
#define SWIFT_INTEROP_CONFIG_H
#define SWIFT_INTEROP_OBJC_HEADER "@CLIENT_SWIFT_OBJC_HEADER_NAME@"
#define SWIFT_INTEROP_NAMESPACE @CLIENT_SWIFT_MODULE_NAME@
#endif

View File

@@ -78,14 +78,6 @@ bool Daemon::activate(const InterfaceConfig& config) {
return false;
}
if (!dnsutils()->restoreResolvers()) {
return false;
}
if (!maybeUpdateResolvers(config)) {
return false;
}
bool status = run(Switch, config);
logger.debug() << "Connection status:" << status;
if (status) {
@@ -142,10 +134,6 @@ bool Daemon::activate(const InterfaceConfig& config) {
return false;
}
if (!maybeUpdateResolvers(config)) {
return false;
}
// set routing
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
if (!wgutils()->updateRoutePrefix(ip)) {
@@ -154,6 +142,12 @@ bool Daemon::activate(const InterfaceConfig& config) {
}
}
#ifndef Q_OS_LINUX
if (!maybeUpdateResolvers(config)) {
return false;
}
#endif
bool status = run(Up, config);
logger.debug() << "Connection status:" << status;
if (status) {
@@ -168,15 +162,20 @@ bool Daemon::activate(const InterfaceConfig& config) {
bool Daemon::maybeUpdateResolvers(const InterfaceConfig& config) {
if ((config.m_hopType == InterfaceConfig::MultiHopExit) ||
(config.m_hopType == InterfaceConfig::SingleHop)) {
if (!dnsutils()) {
logger.error() << "dnsutils is null, cannot update resolvers";
return false;
}
QList<QHostAddress> resolvers;
resolvers.append(QHostAddress(config.m_primaryDnsServer));
if (!config.m_secondaryDnsServer.isEmpty()) {
resolvers.append(QHostAddress(config.m_secondaryDnsServer));
}
// If the DNS is not the Gateway, it's a user defined DNS
// thus, not add any other :)
if (config.m_primaryDnsServer == config.m_serverIpv4Gateway) {
// If the DNS is the Gateway, also add IPv6 gateway (only if non-empty)
if (config.m_primaryDnsServer == config.m_serverIpv4Gateway &&
!config.m_serverIpv6Gateway.isEmpty()) {
resolvers.append(QHostAddress(config.m_serverIpv6Gateway));
}
@@ -613,7 +612,7 @@ void Daemon::checkHandshake() {
pendingHandshakes++;
}
}
// Check again if there were connections that haven't completed a handshake.
if (pendingHandshakes > 0) {
m_handshakeTimer.start(HANDSHAKE_POLL_MSEC);

View File

@@ -2,47 +2,49 @@ enable_language(Swift)
set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../..)
add_executable(${CLIENT_IOS_NE_TARGET_NAME})
set_target_properties(${CLIENT_IOS_NE_TARGET_NAME} PROPERTIES
add_executable(networkextension)
set_target_properties(networkextension PROPERTIES
XCODE_PRODUCT_TYPE com.apple.product-type.app-extension
BUNDLE_EXTENSION appex
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in
MACOSX_BUNDLE_INFO_STRING "${CLIENT_NETWORK_EXTENSION_NAME}"
MACOSX_BUNDLE_BUNDLE_NAME "${CLIENT_NETWORK_EXTENSION_NAME}"
MACOSX_BUNDLE_INFO_STRING "AmneziaVPNNetworkExtension"
MACOSX_BUNDLE_BUNDLE_NAME "AmneziaVPNNetworkExtension"
MACOSX_BUNDLE_GUI_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}.network-extension"
MACOSX_BUNDLE_BUNDLE_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
MACOSX_BUNDLE_LONG_VERSION_STRING "${APPLE_PROJECT_VERSION}-${CMAKE_PROJECT_VERSION_TWEAK}"
MACOSX_BUNDLE_SHORT_VERSION_STRING "${APPLE_PROJECT_VERSION}"
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}.network-extension"
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS ${CLIENT_IOS_NE_ENTITLEMENTS_PATH}
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS ${CMAKE_CURRENT_SOURCE_DIR}/AmneziaVPNNetworkExtension.entitlements
XCODE_ATTRIBUTE_MARKETING_VERSION "${APP_MAJOR_VERSION}"
XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION "${BUILD_ID}"
XCODE_ATTRIBUTE_PRODUCT_NAME "${CLIENT_NETWORK_EXTENSION_NAME}"
XCODE_ATTRIBUTE_PRODUCT_NAME "AmneziaVPNNetworkExtension"
XCODE_ATTRIBUTE_APPLICATION_EXTENSION_API_ONLY "YES"
XCODE_ATTRIBUTE_ENABLE_BITCODE "NO"
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2"
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../../Frameworks"
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
)
if(DEPLOY)
set_target_properties(${CLIENT_IOS_NE_TARGET_NAME} PROPERTIES
set_target_properties(networkextension PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "${CLIENT_IOS_NE_PROVISIONING_PROFILE_SPECIFIER}"
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "${CLIENT_IOS_NE_PROVISIONING_PROFILE_SPECIFIER_DEBUG}"
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "distr ios.org.amnezia.AmneziaVPN"
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "dev ios.org.amnezia.AmneziaVPN"
)
else()
set_target_properties(${CLIENT_IOS_NE_TARGET_NAME} PROPERTIES
set_target_properties(networkextension PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
)
endif()
set_target_properties(${CLIENT_IOS_NE_TARGET_NAME} PROPERTIES
set_target_properties(networkextension PROPERTIES
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES"
XCODE_ATTRIBUTE_SWIFT_OBJC_BRIDGING_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/WireGuardNetworkExtension-Bridging-Header.h"
@@ -50,8 +52,8 @@ set_target_properties(${CLIENT_IOS_NE_TARGET_NAME} PROPERTIES
XCODE_ATTRIBUTE_SWIFT_PRECOMPILE_BRIDGING_HEADER "NO"
)
set_target_properties("${CLIENT_IOS_NE_TARGET_NAME}" PROPERTIES
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "${BUILD_VPN_DEVELOPMENT_TEAM}"
set_target_properties("networkextension" PROPERTIES
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "X7UJ388FXK"
)
find_library(FW_ASSETS_LIBRARY AssetsLibrary)
@@ -59,17 +61,17 @@ find_library(FW_MOBILE_CORE MobileCoreServices)
find_library(FW_UI_KIT UIKit)
find_library(FW_LIBRESOLV libresolv.9.tbd)
target_link_libraries(${CLIENT_IOS_NE_TARGET_NAME} PRIVATE ${FW_ASSETS_LIBRARY})
target_link_libraries(${CLIENT_IOS_NE_TARGET_NAME} PRIVATE ${FW_MOBILE_CORE})
target_link_libraries(${CLIENT_IOS_NE_TARGET_NAME} PRIVATE ${FW_UI_KIT})
target_link_libraries(${CLIENT_IOS_NE_TARGET_NAME} PRIVATE ${FW_LIBRESOLV})
target_link_libraries(networkextension PRIVATE ${FW_ASSETS_LIBRARY})
target_link_libraries(networkextension PRIVATE ${FW_MOBILE_CORE})
target_link_libraries(networkextension PRIVATE ${FW_UI_KIT})
target_link_libraries(networkextension PRIVATE ${FW_LIBRESOLV})
target_compile_options(${CLIENT_IOS_NE_TARGET_NAME} PRIVATE -DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\")
target_compile_options(${CLIENT_IOS_NE_TARGET_NAME} PRIVATE -DNETWORK_EXTENSION=1)
target_compile_options(networkextension PRIVATE -DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\")
target_compile_options(networkextension PRIVATE -DNETWORK_EXTENSION=1)
set(WG_APPLE_SOURCE_DIR ${CLIENT_ROOT_DIR}/3rd/amneziawg-apple/Sources)
target_sources(${CLIENT_IOS_NE_TARGET_NAME} PRIVATE
target_sources(networkextension PRIVATE
${WG_APPLE_SOURCE_DIR}/WireGuardKit/WireGuardAdapter.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/PacketTunnelSettingsGenerator.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/DNSResolver.swift
@@ -102,22 +104,32 @@ target_sources(${CLIENT_IOS_NE_TARGET_NAME} PRIVATE
${CLIENT_ROOT_DIR}/platforms/ios/iosglue.mm
)
target_sources(${CLIENT_IOS_NE_TARGET_NAME} PRIVATE
target_sources(networkextension PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/PrivacyInfo.xcprivacy
)
set_property(TARGET ${CLIENT_IOS_NE_TARGET_NAME} APPEND PROPERTY RESOURCE
set_property(TARGET networkextension APPEND PROPERTY RESOURCE
${CMAKE_CURRENT_SOURCE_DIR}/PrivacyInfo.xcprivacy
)
target_include_directories(${CLIENT_IOS_NE_TARGET_NAME} PRIVATE ${CLIENT_ROOT_DIR})
target_include_directories(${CLIENT_IOS_NE_TARGET_NAME} PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
target_include_directories(networkextension PRIVATE ${CLIENT_ROOT_DIR})
target_include_directories(networkextension PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
find_package(openvpnadapter REQUIRED)
target_link_libraries(${CLIENT_IOS_NE_TARGET_NAME} PRIVATE amnezia::openvpnadapter)
# FIXME(ygurov): https://github.com/conan-io/conan/issues/20034
set_property(TARGET amnezia::openvpnadapter APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
set_property(TARGET amnezia::openvpnadapter APPEND PROPERTY IMPORTED_CONFIGURATIONS MINSIZEREL)
set_property(TARGET amnezia::openvpnadapter APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
set_property(TARGET amnezia::openvpnadapter APPEND PROPERTY IMPORTED_CONFIGURATIONS RELWITHDEBINFO)
target_link_libraries(networkextension PRIVATE amnezia::openvpnadapter)
find_package(awg-apple REQUIRED)
target_link_libraries(${CLIENT_IOS_NE_TARGET_NAME} PRIVATE amnezia::awg-apple)
target_link_libraries(networkextension PRIVATE amnezia::awg-apple)
find_package(hev-socks5-tunnel REQUIRED)
target_link_libraries(${CLIENT_IOS_NE_TARGET_NAME} PRIVATE heiher::hev-socks5-tunnel)
# FIXME(ygurov): https://github.com/conan-io/conan/issues/20034
set_property(TARGET heiher::hev-socks5-tunnel APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
set_property(TARGET heiher::hev-socks5-tunnel APPEND PROPERTY IMPORTED_CONFIGURATIONS MINSIZEREL)
set_property(TARGET heiher::hev-socks5-tunnel APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
set_property(TARGET heiher::hev-socks5-tunnel APPEND PROPERTY IMPORTED_CONFIGURATIONS RELWITHDEBINFO)
target_link_libraries(networkextension PRIVATE heiher::hev-socks5-tunnel)

View File

@@ -1,23 +1,23 @@
enable_language(Swift)
message("Client message >> macos build >> ${CLIENT_MACOS_NE_TARGET_NAME}")
message("Client message >> macos build >> AmneziaVPNNetworkExtension")
set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../..)
add_executable(${CLIENT_MACOS_NE_TARGET_NAME})
add_executable(AmneziaVPNNetworkExtension)
message("executable_path is: @executable_path/../../Frameworks")
set_target_properties(${CLIENT_MACOS_NE_TARGET_NAME} PROPERTIES
set_target_properties(AmneziaVPNNetworkExtension PROPERTIES
XCODE_PRODUCT_TYPE com.apple.product-type.app-extension
# MACOSX_BUNDLE YES
BUNDLE_EXTENSION appex
MACOSX_BUNDLE_SHORT_VERSION_STRING "${APPLE_PROJECT_VERSION}"
MACOSX_BUNDLE_INFO_STRING "${CLIENT_NETWORK_EXTENSION_NAME}"
MACOSX_BUNDLE_BUNDLE_NAME "${CLIENT_NETWORK_EXTENSION_NAME}"
MACOSX_BUNDLE_INFO_STRING "AmneziaVPNNetworkExtension"
MACOSX_BUNDLE_BUNDLE_NAME "AmneziaVPNNetworkExtension"
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}.network-extension"
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_NAME "${BUILD_IOS_APP_IDENTIFIER}.network-extension"
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS ${CLIENT_MACOS_NE_ENTITLEMENTS_PATH}
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS ${CMAKE_CURRENT_SOURCE_DIR}/AmneziaVPNNetworkExtension.entitlements
XCODE_ATTRIBUTE_MARKETING_VERSION "${APP_MAJOR_VERSION}"
XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
XCODE_ATTRIBUTE_PRODUCT_NAME "${CLIENT_NETWORK_EXTENSION_NAME}"
XCODE_ATTRIBUTE_PRODUCT_NAME "AmneziaVPNNetworkExtension"
XCODE_ATTRIBUTE_APPLICATION_EXTENSION_API_ONLY "YES"
XCODE_ATTRIBUTE_ENABLE_BITCODE "NO"
@@ -25,11 +25,13 @@ set_target_properties(${CLIENT_MACOS_NE_TARGET_NAME} PROPERTIES
XCODE_ATTRIBUTE_INFOPLIST_FILE ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../../../../Frameworks @loader_path/../../../../Frameworks"
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
)
if(DEPLOY)
message("DEPLOY is ON")
set_target_properties(${CLIENT_MACOS_NE_TARGET_NAME} PROPERTIES
set_target_properties(AmneziaVPNNetworkExtension PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
@@ -37,12 +39,12 @@ if(DEPLOY)
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "dev macos.org.amnezia.amneziaVPN.NE"
)
else()
set_target_properties(${CLIENT_MACOS_NE_TARGET_NAME} PROPERTIES
set_target_properties(AmneziaVPNNetworkExtension PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
)
endif()
set_target_properties(${CLIENT_MACOS_NE_TARGET_NAME} PROPERTIES
set_target_properties(AmneziaVPNNetworkExtension PROPERTIES
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES"
XCODE_ATTRIBUTE_SWIFT_OBJC_BRIDGING_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/WireGuardNetworkExtension-Bridging-Header.h"
@@ -50,8 +52,8 @@ set_target_properties(${CLIENT_MACOS_NE_TARGET_NAME} PROPERTIES
XCODE_ATTRIBUTE_SWIFT_PRECOMPILE_BRIDGING_HEADER "NO"
)
set_target_properties("${CLIENT_MACOS_NE_TARGET_NAME}" PROPERTIES
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "${BUILD_VPN_DEVELOPMENT_TEAM}"
set_target_properties("AmneziaVPNNetworkExtension" PROPERTIES
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "X7UJ388FXK"
)
find_library(FW_ASSETS_LIBRARY AssetsLibrary)
@@ -63,17 +65,17 @@ find_library(FW_LIBRESOLV libresolv.9.tbd)
# Set the root directory
set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../..)
target_link_libraries(${CLIENT_MACOS_NE_TARGET_NAME} PRIVATE ${FW_LIBRESOLV})
target_link_libraries(AmneziaVPNNetworkExtension PRIVATE ${FW_LIBRESOLV})
target_compile_options(${CLIENT_MACOS_NE_TARGET_NAME} PRIVATE -DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\")
target_compile_options(${CLIENT_MACOS_NE_TARGET_NAME} PRIVATE -DNETWORK_EXTENSION=1)
target_compile_options(AmneziaVPNNetworkExtension PRIVATE -DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\")
target_compile_options(AmneziaVPNNetworkExtension PRIVATE -DNETWORK_EXTENSION=1)
set(WG_APPLE_SOURCE_DIR ${CLIENT_ROOT_DIR}/3rd/amneziawg-apple/Sources)
message("WG_APPLE_SOURCE_DIR is: ${WG_APPLE_SOURCE_DIR}")
message("CLIENT_ROOT_DIR is: ${CLIENT_ROOT_DIR}")
target_sources(${CLIENT_MACOS_NE_TARGET_NAME} PRIVATE
target_sources(AmneziaVPNNetworkExtension PRIVATE
${WG_APPLE_SOURCE_DIR}/WireGuardKit/WireGuardAdapter.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/PacketTunnelSettingsGenerator.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/DNSResolver.swift
@@ -106,22 +108,32 @@ target_sources(${CLIENT_MACOS_NE_TARGET_NAME} PRIVATE
${CLIENT_ROOT_DIR}/platforms/ios/XrayConfig.swift
)
target_sources(${CLIENT_MACOS_NE_TARGET_NAME} PRIVATE
target_sources(AmneziaVPNNetworkExtension PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/PrivacyInfo.xcprivacy
)
set_property(TARGET ${CLIENT_MACOS_NE_TARGET_NAME} APPEND PROPERTY RESOURCE
set_property(TARGET AmneziaVPNNetworkExtension APPEND PROPERTY RESOURCE
${CMAKE_CURRENT_SOURCE_DIR}/PrivacyInfo.xcprivacy
)
target_include_directories(${CLIENT_MACOS_NE_TARGET_NAME} PRIVATE ${CLIENT_ROOT_DIR})
target_include_directories(${CLIENT_MACOS_NE_TARGET_NAME} PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
target_include_directories(AmneziaVPNNetworkExtension PRIVATE ${CLIENT_ROOT_DIR})
target_include_directories(AmneziaVPNNetworkExtension PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
find_package(openvpnadapter REQUIRED)
target_link_libraries(${CLIENT_MACOS_NE_TARGET_NAME} PRIVATE amnezia::openvpnadapter)
# FIXME(ygurov): https://github.com/conan-io/conan/issues/20034
set_property(TARGET amnezia::openvpnadapter APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
set_property(TARGET amnezia::openvpnadapter APPEND PROPERTY IMPORTED_CONFIGURATIONS MINSIZEREL)
set_property(TARGET amnezia::openvpnadapter APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
set_property(TARGET amnezia::openvpnadapter APPEND PROPERTY IMPORTED_CONFIGURATIONS RELWITHDEBINFO)
target_link_libraries(AmneziaVPNNetworkExtension PRIVATE amnezia::openvpnadapter)
find_package(awg-apple REQUIRED)
target_link_libraries(${CLIENT_MACOS_NE_TARGET_NAME} PRIVATE amnezia::awg-apple)
target_link_libraries(AmneziaVPNNetworkExtension PRIVATE amnezia::awg-apple)
find_package(hev-socks5-tunnel REQUIRED)
target_link_libraries(${CLIENT_MACOS_NE_TARGET_NAME} PRIVATE heiher::hev-socks5-tunnel)
# FIXME(ygurov): https://github.com/conan-io/conan/issues/20034
set_property(TARGET heiher::hev-socks5-tunnel APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
set_property(TARGET heiher::hev-socks5-tunnel APPEND PROPERTY IMPORTED_CONFIGURATIONS MINSIZEREL)
set_property(TARGET heiher::hev-socks5-tunnel APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
set_property(TARGET heiher::hev-socks5-tunnel APPEND PROPERTY IMPORTED_CONFIGURATIONS RELWITHDEBINFO)
target_link_libraries(AmneziaVPNNetworkExtension PRIVATE heiher::hev-socks5-tunnel)

View File

@@ -5,7 +5,6 @@
#include "amneziaApplication.h"
#include "core/utils/osSignalHandler.h"
#include "core/utils/migrations.h"
#include "core/utils/appUiConfig.h"
#include "version.h"
#ifdef Q_OS_WIN
@@ -20,9 +19,9 @@
bool isAnotherInstanceRunning()
{
QLocalSocket socket;
socket.connectToServer(APP_INSTANCE_NAME);
socket.connectToServer("AmneziaVPNInstance");
if (socket.waitForConnected(500)) {
qWarning() << APPLICATION_NAME << "is already running";
qWarning() << "AmneziaVPN is already running";
return true;
}
return false;

View File

@@ -4,8 +4,7 @@
#import "StoreKitController.h"
#import <StoreKit/StoreKit.h>
#include <QtCore/qglobal.h>
#import "core/utils/swiftInterop.h"
#import <AmneziaVPN-Swift.h>
#include <QtCore/QDebug>
#include <QtCore/QString>

View File

@@ -7,8 +7,11 @@
#include <net/if.h>
#include <QDBusVariant>
#include <QNetworkInterface>
#include <QTimer>
#include <QtDBus/QtDBus>
#include "core/utils/networkUtilities.h"
#include "leakdetector.h"
#include "logger.h"
@@ -27,24 +30,78 @@ DnsUtilsLinux::DnsUtilsLinux(QObject* parent) : DnsUtils(parent) {
logger.debug() << "DnsUtilsLinux created.";
QDBusConnection conn = QDBusConnection::systemBus();
m_resolver = new QDBusInterface(DBUS_RESOLVE_SERVICE, DBUS_RESOLVE_PATH,
DBUS_RESOLVE_MANAGER, conn, this);
auto* watcher = new QDBusServiceWatcher(
DBUS_RESOLVE_SERVICE, conn,
QDBusServiceWatcher::WatchForRegistration |
QDBusServiceWatcher::WatchForUnregistration, this);
connect(watcher, &QDBusServiceWatcher::serviceRegistered,
this, &DnsUtilsLinux::onResolverRegistered);
connect(watcher, &QDBusServiceWatcher::serviceUnregistered,
this, &DnsUtilsLinux::onResolverUnregistered);
if (conn.interface()->isServiceRegistered(DBUS_RESOLVE_SERVICE)) {
onResolverRegistered();
}
}
void DnsUtilsLinux::onResolverRegistered() {
m_resolver.reset(new QDBusInterface(DBUS_RESOLVE_SERVICE, DBUS_RESOLVE_PATH,
DBUS_RESOLVE_MANAGER,
QDBusConnection::systemBus()));
logger.debug() << "systemd-resolved available, DNS resolver initialized";
if (m_revertAfterRegister > 0) {
logger.debug() << "Calling RevertLink after restart for ifindex" << m_revertAfterRegister;
QDBusMessage msg = QDBusMessage::createMethodCall(
DBUS_RESOLVE_SERVICE, DBUS_RESOLVE_PATH, DBUS_RESOLVE_MANAGER, "RevertLink");
msg.setArguments({QVariant::fromValue(m_revertAfterRegister)});
QDBusPendingReply<> reply = QDBusConnection::systemBus().asyncCall(msg, 5000);
int savedIdx = m_revertAfterRegister;
m_revertAfterRegister = 0;
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this,
[this, savedIdx](QDBusPendingCallWatcher* w) {
QDBusPendingReply<> r = *w;
if (r.isError()) {
logger.debug() << "RevertLink after restart failed for ifindex" << savedIdx
<< ":" << r.error().message();
} else {
logger.debug() << "RevertLink after restart succeeded for ifindex" << savedIdx;
}
w->deleteLater();
});
}
if (!m_pendingIfname.isEmpty()) {
logger.debug() << "Re-applying DNS configuration for" << m_pendingIfname;
updateResolvers(m_pendingIfname, m_pendingResolvers);
}
}
void DnsUtilsLinux::onResolverUnregistered() {
logger.debug() << "systemd-resolved disappeared, dropping DNS resolver";
m_resolver.reset();
}
DnsUtilsLinux::~DnsUtilsLinux() {
MZ_COUNT_DTOR(DnsUtilsLinux);
for (auto iterator = m_linkDomains.constBegin();
iterator != m_linkDomains.constEnd(); ++iterator) {
QList<QVariant> argumentList;
argumentList << QVariant::fromValue(iterator.key());
argumentList << QVariant::fromValue(iterator.value());
m_resolver->asyncCallWithArgumentList(QStringLiteral("SetLinkDomains"),
argumentList);
}
if (m_revertOnDestroy && m_resolver) {
if (m_gatewayIfindex > 0)
setLinkDefaultRoute(m_gatewayIfindex, true);
if (m_ifindex > 0) {
m_resolver->asyncCall(QStringLiteral("RevertLink"), m_ifindex);
for (auto iterator = m_linkDomains.constBegin();
iterator != m_linkDomains.constEnd(); ++iterator) {
QList<QVariant> argumentList;
argumentList << QVariant::fromValue(iterator.key());
argumentList << QVariant::fromValue(iterator.value());
m_resolver->asyncCallWithArgumentList(QStringLiteral("SetLinkDomains"),
argumentList);
}
if (m_ifindex > 0) {
m_resolver->asyncCall(QStringLiteral("RevertLink"), m_ifindex);
}
}
logger.debug() << "DnsUtilsLinux destroyed.";
@@ -52,12 +109,37 @@ DnsUtilsLinux::~DnsUtilsLinux() {
bool DnsUtilsLinux::updateResolvers(const QString& ifname,
const QList<QHostAddress>& resolvers) {
m_revertAfterRegister = 0;
if (m_gatewayIfindex > 0) {
setLinkDefaultRoute(m_gatewayIfindex, true);
m_gatewayIfindex = 0;
}
m_ifindex = if_nametoindex(qPrintable(ifname));
if (m_ifindex <= 0) {
logger.error() << "Unable to resolve ifindex for" << ifname;
return false;
}
// Reset retry counter only when called externally (not from scheduleRetry)
if (ifname != m_pendingIfname || resolvers != m_pendingResolvers)
m_domainRetries = 0;
m_pendingIfname = ifname;
m_pendingResolvers = resolvers;
if (!m_resolver) {
logger.debug() << "systemd-resolved not ready, queuing DNS configuration";
return true;
}
const int gwIdx = NetworkUtilities::getGatewayAndIface().second.index();
if (gwIdx > 0 && gwIdx != m_ifindex && gwIdx != m_gatewayIfindex) {
m_gatewayIfindex = gwIdx;
setLinkDefaultRoute(gwIdx, false);
}
setLinkDNS(m_ifindex, resolvers);
setLinkDefaultRoute(m_ifindex, true);
updateLinkDomains();
@@ -65,38 +147,54 @@ bool DnsUtilsLinux::updateResolvers(const QString& ifname,
}
bool DnsUtilsLinux::restoreResolvers() {
m_revertOnDestroy = true;
m_pendingIfname.clear();
m_pendingResolvers.clear();
if (m_gatewayIfindex > 0) {
setLinkDefaultRoute(m_gatewayIfindex, true);
m_gatewayIfindex = 0;
}
for (auto iterator = m_linkDomains.constBegin();
iterator != m_linkDomains.constEnd(); ++iterator) {
setLinkDomains(iterator.key(), iterator.value());
}
m_linkDomains.clear();
/* Revert the VPN interface's DNS configuration */
if (m_ifindex > 0) {
QList<QVariant> argumentList = {QVariant::fromValue(m_ifindex)};
QDBusPendingReply<> reply = m_resolver->asyncCallWithArgumentList(
QStringLiteral("RevertLink"), argumentList);
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
SLOT(dnsCallCompleted(QDBusPendingCallWatcher*)));
m_revertAfterRegister = m_ifindex;
m_ifindex = 0;
}
return true;
}
void DnsUtilsLinux::scheduleRetry() {
if (m_pendingIfname.isEmpty() || m_retryPending || m_domainRetries >= 5)
return;
m_retryPending = true;
++m_domainRetries;
logger.debug() << "Retrying full DNS setup (" << m_domainRetries << "/5)";
QTimer::singleShot(1000, this, [this]() {
m_retryPending = false;
if (!m_pendingIfname.isEmpty())
updateResolvers(m_pendingIfname, m_pendingResolvers);
});
}
void DnsUtilsLinux::dnsCallCompleted(QDBusPendingCallWatcher* call) {
QDBusPendingReply<> reply = *call;
if (reply.isError()) {
logger.error() << "Error received from the DBus service";
logger.debug() << "DBus call failed (may be transient after systemd-resolved restart)";
scheduleRetry();
}
delete call;
}
void DnsUtilsLinux::setLinkDNS(int ifindex,
const QList<QHostAddress>& resolvers) {
if (!m_resolver) return;
QList<DnsResolver> resolverList;
char ifnamebuf[IF_NAMESIZE];
const char* ifname = if_indextoname(ifindex, ifnamebuf);
@@ -111,8 +209,10 @@ void DnsUtilsLinux::setLinkDNS(int ifindex,
QList<QVariant> argumentList;
argumentList << QVariant::fromValue(ifindex);
argumentList << QVariant::fromValue(resolverList);
QDBusPendingReply<> reply = m_resolver->asyncCallWithArgumentList(
QStringLiteral("SetLinkDNS"), argumentList);
QDBusMessage msg = QDBusMessage::createMethodCall(
DBUS_RESOLVE_SERVICE, DBUS_RESOLVE_PATH, DBUS_RESOLVE_MANAGER, "SetLinkDNS");
msg.setArguments(argumentList);
QDBusPendingReply<> reply = QDBusConnection::systemBus().asyncCall(msg, 5000);
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
@@ -121,6 +221,7 @@ void DnsUtilsLinux::setLinkDNS(int ifindex,
void DnsUtilsLinux::setLinkDomains(int ifindex,
const QList<DnsLinkDomain>& domains) {
if (!m_resolver) return;
char ifnamebuf[IF_NAMESIZE];
const char* ifname = if_indextoname(ifindex, ifnamebuf);
if (ifname) {
@@ -135,8 +236,10 @@ void DnsUtilsLinux::setLinkDomains(int ifindex,
QList<QVariant> argumentList;
argumentList << QVariant::fromValue(ifindex);
argumentList << QVariant::fromValue(domains);
QDBusPendingReply<> reply = m_resolver->asyncCallWithArgumentList(
QStringLiteral("SetLinkDomains"), argumentList);
QDBusMessage msg = QDBusMessage::createMethodCall(
DBUS_RESOLVE_SERVICE, DBUS_RESOLVE_PATH, DBUS_RESOLVE_MANAGER, "SetLinkDomains");
msg.setArguments(argumentList);
QDBusPendingReply<> reply = QDBusConnection::systemBus().asyncCall(msg, 5000);
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
@@ -144,11 +247,14 @@ void DnsUtilsLinux::setLinkDomains(int ifindex,
}
void DnsUtilsLinux::setLinkDefaultRoute(int ifindex, bool enable) {
if (!m_resolver) return;
QList<QVariant> argumentList;
argumentList << QVariant::fromValue(ifindex);
argumentList << QVariant::fromValue(enable);
QDBusPendingReply<> reply = m_resolver->asyncCallWithArgumentList(
QStringLiteral("SetLinkDefaultRoute"), argumentList);
QDBusMessage msg = QDBusMessage::createMethodCall(
DBUS_RESOLVE_SERVICE, DBUS_RESOLVE_PATH, DBUS_RESOLVE_MANAGER, "SetLinkDefaultRoute");
msg.setArguments(argumentList);
QDBusPendingReply<> reply = QDBusConnection::systemBus().asyncCall(msg, 5000);
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
@@ -156,6 +262,7 @@ void DnsUtilsLinux::setLinkDefaultRoute(int ifindex, bool enable) {
}
void DnsUtilsLinux::updateLinkDomains() {
if (!m_resolver) return;
/* Get the list of search domains, and remove any others that might conspire
* to satisfy DNS resolution. Unfortunately, this is a pain because Qt doesn't
* seem to be able to demarshall complex property types.
@@ -165,7 +272,7 @@ void DnsUtilsLinux::updateLinkDomains() {
message << QString(DBUS_RESOLVE_MANAGER);
message << QString("Domains");
QDBusPendingReply<QVariant> reply =
m_resolver->connection().asyncCall(message);
m_resolver->connection().asyncCall(message, 5000);
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
@@ -174,11 +281,13 @@ void DnsUtilsLinux::updateLinkDomains() {
void DnsUtilsLinux::dnsDomainsReceived(QDBusPendingCallWatcher* call) {
QDBusPendingReply<QVariant> reply = *call;
call->deleteLater();
if (reply.isError()) {
logger.error() << "Error retrieving the DNS domains from the DBus service";
delete call;
logger.debug() << "DBus Domains call failed (may be transient after systemd-resolved restart)";
scheduleRetry();
return;
}
m_domainRetries = 0;
/* Update the state of the DNS domains */
m_linkDomains.clear();
@@ -204,9 +313,17 @@ void DnsUtilsLinux::dnsDomainsReceived(QDBusPendingCallWatcher* call) {
}
/* Add a root search domain for the new interface. */
QList<DnsLinkDomain> newlist = {root};
setLinkDomains(m_ifindex, newlist);
delete call;
if (m_ifindex > 0) {
setLinkDomains(m_ifindex, {root});
/* Disable DefaultRoute on the physical gateway so systemd-resolved
* routes all DNS through the VPN interface. */
const int gwIdx = NetworkUtilities::getGatewayAndIface().second.index();
if (gwIdx > 0 && gwIdx != m_ifindex && gwIdx != m_gatewayIfindex) {
m_gatewayIfindex = gwIdx;
setLinkDefaultRoute(gwIdx, false);
}
}
}
static DnsMetatypeRegistrationProxy s_dnsMetatypeProxy;

View File

@@ -6,7 +6,12 @@
#define DNSUTILSLINUX_H
#include <QDBusInterface>
#include <QScopedPointer>
#include <QDBusPendingCallWatcher>
#include <QDBusServiceWatcher>
#include <QHostAddress>
#include <QList>
#include <QString>
#include "daemon/dnsutils.h"
#include "dbustypeslinux.h"
@@ -29,13 +34,25 @@ class DnsUtilsLinux final : public DnsUtils {
void updateLinkDomains();
private slots:
void onResolverRegistered();
void onResolverUnregistered();
void dnsCallCompleted(QDBusPendingCallWatcher*);
void dnsDomainsReceived(QDBusPendingCallWatcher*);
private:
void scheduleRetry();
private:
int m_ifindex = 0;
int m_gatewayIfindex = 0;
int m_domainRetries = 0;
bool m_revertOnDestroy = false;
bool m_retryPending = false;
int m_revertAfterRegister = 0;
QMap<int, DnsLinkDomainList> m_linkDomains;
QDBusInterface* m_resolver = nullptr;
QScopedPointer<QDBusInterface> m_resolver;
QString m_pendingIfname;
QList<QHostAddress> m_pendingResolvers;
};
#endif // DNSUTILSLINUX_H

View File

@@ -33,6 +33,7 @@
#include "linuxfirewall.h"
#include "logger.h"
#include "xray_defs.h"
#include <QFileInfo>
#include <QProcess>
#define BRAND_CODE "amn"
@@ -102,14 +103,7 @@ int LinuxFirewall::linkChain(LinuxFirewall::IPVersion ip, const QString& chain,
const QString cmd = getCommand(ip);
if (mustBeFirst)
{
// This monster shell script does the following:
// 1. Check if a rule with the appropriate target exists at the top of the parent chain
// 2. If not, insert a jump rule at the top of the parent chain
// 3. Look for and delete a single rule with the designated target at an index > 1
// (we can't safely delete all rules at once since rule numbers change)
// TODO: occasionally this script results in warnings in logs "Bad rule (does a matching rule exist in the chain?)" - this happens when
// the e.g OUTPUT chain is empty but this script attempts to delete things from it anyway. It doesn't cause any problems, but we should still fix at some point..
return execute(QStringLiteral("if ! %1 -L %2 -n --line-numbers -t %4 2> /dev/null | awk 'int($1) == 1 && $2 == \"%3\" { found=1 } END { if(found==1) { exit 0 } else { exit 1 } }' ; then %1 -I %2 -j %3 -t %4 && %1 -L %2 -n --line-numbers -t %4 2> /dev/null | awk 'int($1) > 1 && $2 == \"%3\" { print $1; exit }' | xargs %1 -t %4 -D %2 ; fi").arg(cmd, parent, chain, tableName));
return execute(QStringLiteral("if ! %1 -L %2 -n --line-numbers -t %4 2> /dev/null | awk 'int($1) == 1 && $2 == \"%3\" { found=1 } END { if(found==1) { exit 0 } else { exit 1 } }' ; then %1 -I %2 -j %3 -t %4 && %1 -L %2 -n --line-numbers -t %4 2> /dev/null | awk 'int($1) > 1 && $2 == \"%3\" { print $1; exit }' | xargs -r %1 -t %4 -D %2 ; fi").arg(cmd, parent, chain, tableName));
}
else
return execute(QStringLiteral("if ! %1 -C %2 -j %3 -t %4 2> /dev/null ; then %1 -A %2 -j %3 -t %4; fi").arg(cmd, parent, chain, tableName));
@@ -291,6 +285,8 @@ void LinuxFirewall::install()
installAnchor(IPv4, QStringLiteral("110.allowNets"), {});
installAnchor(Both, QStringLiteral("400.allowPIA"), {});
installAnchor(Both, QStringLiteral("100.blockAll"), {
QStringLiteral("-j REJECT"),
});
@@ -454,16 +450,33 @@ void LinuxFirewall::updateDNSServers(const QStringList& servers)
static QStringList existingServers {};
existingServers = servers;
execute(QStringLiteral("iptables -F %1.320.allowDNS").arg(kAnchorName));
for (const QString& rule : getDNSRules(servers))
execute(QStringLiteral("iptables -A %1.320.allowDNS %2").arg(kAnchorName, rule));
const QString chain = QStringLiteral("%1.320.allowDNS").arg(kAnchorName);
executeIptables(QStringLiteral("iptables"), {QStringLiteral("-F"), chain});
const QStringList ifaces = {
QStringLiteral("amn0+"), QStringLiteral("tun0+"), QStringLiteral("tun2+")
};
for (const QString& server : servers) {
for (const QString& iface : ifaces) {
executeIptables(QStringLiteral("iptables"),
{QStringLiteral("-A"), chain, QStringLiteral("-o"), iface,
QStringLiteral("-d"), server, QStringLiteral("-p"), QStringLiteral("udp"),
QStringLiteral("--dport"), QStringLiteral("53"), QStringLiteral("-j"), QStringLiteral("ACCEPT")});
executeIptables(QStringLiteral("iptables"),
{QStringLiteral("-A"), chain, QStringLiteral("-o"), iface,
QStringLiteral("-d"), server, QStringLiteral("-p"), QStringLiteral("tcp"),
QStringLiteral("--dport"), QStringLiteral("53"), QStringLiteral("-j"), QStringLiteral("ACCEPT")});
}
}
}
void LinuxFirewall::updateAllowNets(const QStringList& servers)
{
execute(QStringLiteral("iptables -F %1.110.allowNets").arg(kAnchorName));
for (const QString& rule : getAllowRule(servers))
execute(QStringLiteral("iptables -A %1.110.allowNets %2").arg(kAnchorName, rule));
const QString chain = QStringLiteral("%1.110.allowNets").arg(kAnchorName);
executeIptables(QStringLiteral("iptables"), {QStringLiteral("-F"), chain});
for (const QString& server : servers)
executeIptables(QStringLiteral("iptables"),
{QStringLiteral("-A"), chain, QStringLiteral("-d"), server,
QStringLiteral("-j"), QStringLiteral("ACCEPT")});
}
void LinuxFirewall::updateBlockNets(const QStringList& servers)
@@ -471,9 +484,12 @@ void LinuxFirewall::updateBlockNets(const QStringList& servers)
static QStringList existingServers {};
existingServers = servers;
execute(QStringLiteral("iptables -F %1.120.blockNets").arg(kAnchorName));
for (const QString& rule : getBlockRule(servers))
execute(QStringLiteral("iptables -A %1.120.blockNets %2").arg(kAnchorName, rule));
const QString chain = QStringLiteral("%1.120.blockNets").arg(kAnchorName);
executeIptables(QStringLiteral("iptables"), {QStringLiteral("-F"), chain});
for (const QString& server : servers)
executeIptables(QStringLiteral("iptables"),
{QStringLiteral("-A"), chain, QStringLiteral("-d"), server,
QStringLiteral("-j"), QStringLiteral("REJECT")});
}
int waitForExitCode(QProcess& process)
@@ -506,10 +522,39 @@ int LinuxFirewall::execute(const QString &command, bool ignoreErrors)
return exitCode;
}
int LinuxFirewall::executeIptables(const QString &program, const QStringList &args, bool ignoreErrors)
{
QProcess p;
p.start(program, args, QProcess::ReadOnly);
p.closeWriteChannel();
int exitCode = waitForExitCode(p);
auto out = p.readAllStandardOutput().trimmed();
auto err = p.readAllStandardError().trimmed();
if ((exitCode != 0 || !err.isEmpty()) && !ignoreErrors)
logger.warning() << "(" << exitCode << ") $ " << program << args.join(QLatin1Char(' '));
if (!out.isEmpty())
logger.info() << out;
if (!err.isEmpty())
logger.warning() << err;
return exitCode;
}
void LinuxFirewall::setupTrafficSplitting()
{
const QString cgroupBase = QStringLiteral("/sys/fs/cgroup/net_cls");
if (!QFileInfo::exists(cgroupBase)) {
logger.warning() << "net_cls cgroup v1 not available, traffic splitting disabled";
return;
}
execute(QStringLiteral(
"if ! grep -qE '^[0-9]+[[:space:]]+%1$' /etc/iproute2/rt_tables 2>/dev/null ; then "
"echo '200 %1' >> /etc/iproute2/rt_tables ; fi"
).arg(kRtableName));
auto cGroupDir = "/sys/fs/cgroup/net_cls/" BRAND_CODE "vpnexclusions/";
logger.info() << "Should be setting up cgroup in" << cGroupDir << "for traffic splitting";
logger.info() << "Setting up cgroup in" << cGroupDir << "for traffic splitting";
execute(QStringLiteral("if [ ! -d %1 ] ; then mkdir %1 ; sleep 0.1 ; echo %2 > %1/net_cls.classid ; fi").arg(cGroupDir).arg(kCGroupId));
// Set a rule with priority 100 (lower priority than local but higher than main/default, 0 is highest priority)
execute(QStringLiteral("if ! ip rule list | grep -q %1 ; then ip rule add from all fwmark %1 lookup %2 pri 100 ; fi").arg(kPacketTag, kRtableName));
@@ -518,7 +563,7 @@ void LinuxFirewall::setupTrafficSplitting()
void LinuxFirewall::teardownTrafficSplitting()
{
logger.info() << "Tearing down cgroup and routing rules";
execute(QStringLiteral("if ip rule list | grep -q %1; then ip rule del from all fwmark %1 lookup %2 2> /dev/null ; fi").arg(kPacketTag, kRtableName));
execute(QStringLiteral("ip route flush table %1").arg(kRtableName));
execute(QStringLiteral("if ip rule list | grep -q %1; then ip rule del from all fwmark %1 lookup %2 2>/dev/null ; fi").arg(kPacketTag, kRtableName));
execute(QStringLiteral("ip route flush table %1 2>/dev/null || true").arg(kRtableName));
execute(QStringLiteral("ip route flush cache"));
}

View File

@@ -85,6 +85,7 @@ private:
static void setupTrafficSplitting();
static void teardownTrafficSplitting();
static int execute(const QString& command, bool ignoreErrors = false);
static int executeIptables(const QString& program, const QStringList& args, bool ignoreErrors = false);
private:
// Chain names
static QString kOutputChain, kRootChain, kPostRoutingChain, kPreRoutingChain;

View File

@@ -237,7 +237,11 @@ bool WireguardUtilsLinux::updatePeer(const InterfaceConfig& config) {
// Exclude the server address, except for multihop exit servers.
if ((config.m_hopType != InterfaceConfig::MultiHopExit) &&
(m_rtmonitor != nullptr)) {
m_rtmonitor->addExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
if (!config.m_serverIpv4AddrIn.isEmpty() &&
!m_rtmonitor->addExclusionRoute(IPAddress(config.m_serverIpv4AddrIn))) {
logger.error() << "No gateway — cannot add server exclusion route";
return false;
}
m_rtmonitor->addExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
}

View File

@@ -18,10 +18,7 @@ using namespace QKeychain;
namespace {
constexpr const char *settingsKeyTag = "settingsKeyTag";
constexpr const char *settingsIvTag = "settingsIvTag";
#ifndef CLIENT_KEYCHAIN_NAME_STR
#define CLIENT_KEYCHAIN_NAME_STR "AmneziaVPN-Keychain"
#endif
constexpr const char *keyChainName = CLIENT_KEYCHAIN_NAME_STR;
constexpr const char *keyChainName = "AmneziaVPN-Keychain";
}
SecureQSettings::SecureQSettings(const QString &organization, const QString &application, QObject *parent, bool enableEncryption)

View File

@@ -1,7 +1,8 @@
if which apt-get > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/lib/dpkg/lock-frontend";\
elif which dnf > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/cache/dnf/* /var/run/dnf/* /var/lib/dnf/* /var/lib/rpm/*";\
elif which yum > /dev/null 2>&1; then LOCK_CMD="cat"; LOCK_FILE="/var/run/yum.pid";\
elif which zypper > /dev/null 2>&1; then LOCK_CMD="cat"; LOCK_FILE="/var/run/zypp.pid";\
elif which pacman > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/lib/pacman/db.lck";\
else echo "Packet manager not found"; echo "Internal error"; exit 1; fi;\
if command -v $LOCK_CMD > /dev/null 2>&1; then sudo $LOCK_CMD $LOCK_FILE 2>/dev/null; else echo "$LOCK_CMD not installed"; fi
if which apt-get > /dev/null 2>&1 || command -v apt-get > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/lib/dpkg/lock-frontend";\
elif which dnf > /dev/null 2>&1 || command -v dnf > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/cache/dnf/* /var/run/dnf/* /var/lib/dnf/* /var/lib/rpm/*";\
elif which yum > /dev/null 2>&1 || command -v yum > /dev/null 2>&1; then LOCK_CMD="cat"; LOCK_FILE="/var/run/yum.pid";\
elif which zypper > /dev/null 2>&1 || command -v zypper > /dev/null 2>&1; then LOCK_CMD="cat"; LOCK_FILE="/var/run/zypp.pid";\
elif which pacman > /dev/null 2>&1 || command -v pacman > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/lib/pacman/db.lck";\
else echo "Packet manager not found"; echo "Internal error"; exit 1;\
fi;\
if sudo -n which $LOCK_CMD > /dev/null 2>&1 || command -v $LOCK_CMD > /dev/null 2>&1; then sudo -n $LOCK_CMD $LOCK_FILE 2>/dev/null; else echo "$LOCK_CMD not installed"; fi

View File

@@ -1,8 +1,8 @@
if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); opt="--version";\
elif which dnf > /dev/null 2>&1; then pm=$(which dnf); opt="--version";\
elif which yum > /dev/null 2>&1; then pm=$(which yum); opt="--version";\
elif which zypper > /dev/null 2>&1; then pm=$(which zypper); opt="--version";\
elif which pacman > /dev/null 2>&1; then pm=$(which pacman); opt="--version";\
if pm=$(which apt-get 2>/dev/null || command -v apt-get 2>/dev/null); then opt="--version";\
elif pm=$(which dnf 2>/dev/null || command -v dnf 2>/dev/null); then opt="--version";\
elif pm=$(which yum 2>/dev/null || command -v yum 2>/dev/null); then opt="--version";\
elif pm=$(which zypper 2>/dev/null || command -v zypper 2>/dev/null); then opt="--version";\
elif pm=$(which pacman 2>/dev/null || command -v pacman 2>/dev/null); then opt="--version";\
else pm="uname"; opt="-a";\
fi;\
CUR_USER=$(whoami 2>/dev/null || echo $HOME | sed 's/.*\///');\

View File

@@ -1,25 +1,34 @@
if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); silent_inst="-yq install --install-recommends"; check_pkgs="-yq update"; docker_pkg="docker.io"; dist="debian";\
elif which dnf > /dev/null 2>&1; then pm=$(which dnf); silent_inst="-yq install"; check_pkgs="-yq check-update"; docker_pkg="docker"; dist="fedora";\
elif which yum > /dev/null 2>&1; then pm=$(which yum); silent_inst="-y -q install"; check_pkgs="-y -q check-update"; docker_pkg="docker"; dist="centos";\
elif which zypper > /dev/null 2>&1; then pm=$(which zypper); silent_inst="-nq install"; check_pkgs="-nq refresh"; docker_pkg="docker"; dist="opensuse";\
elif which pacman > /dev/null 2>&1; then pm=$(which pacman); silent_inst="-S --noconfirm --noprogressbar --quiet"; check_pkgs="-Sup"; docker_pkg="docker"; dist="archlinux";\
else echo "Packet manager not found"; exit 1; fi;\
echo "Dist: $dist, Packet manager: $pm, Install command: $silent_inst, Check pkgs command: $check_pkgs, Docker pkg: $docker_pkg";\
if pm=$(which apt-get 2>/dev/null || command -v apt-get 2>/dev/null); then silent_inst="-yq install --install-recommends"; what_pkg="-s install"; check_pkgs="-yq update"; docker_pkg="docker.io"; dist="debian";\
elif pm=$(which dnf 2>/dev/null || command -v dnf 2>/dev/null); then silent_inst="-yq install"; what_pkg="--assumeno install --setopt=tsflags=test"; check_pkgs="-yq check-update"; docker_pkg="docker"; dist="fedora";\
elif pm=$(which yum 2>/dev/null || command -v yum 2>/dev/null); then silent_inst="-y -q install"; what_pkg="--assumeno install --setopt=tsflags=test"; check_pkgs="-y -q check-update"; docker_pkg="docker"; dist="centos";\
elif pm=$(which zypper 2>/dev/null || command -v zypper 2>/dev/null); then silent_inst="-nq install"; what_pkg="--dry-run install"; check_pkgs="-nq refresh"; docker_pkg="docker"; dist="suse";\
elif pm=$(which pacman 2>/dev/null || command -v pacman 2>/dev/null); then silent_inst="-S --noconfirm --noprogressbar --quiet"; what_pkg="-Sp"; check_pkgs="-Sup"; docker_pkg="docker"; dist="archlinux";\
fi;\
echo "Dist: $dist, Packet manager: $pm, Install command: $silent_inst, What pkg command: $what_pkg, Check pkgs command: $check_pkgs, Docker pkg: $docker_pkg, Language: $LANG";\
echo $LANG | grep -qE '^(en_US.UTF-8|C.UTF-8|C)$' || export LC_ALL=C;\
if [ "$dist" = "debian" ]; then export DEBIAN_FRONTEND=noninteractive; fi;\
if ! command -v sudo > /dev/null 2>&1; then $pm $check_pkgs; $pm $silent_inst sudo; fi;\
if ! command -v fuser > /dev/null 2>&1; then sudo $pm $check_pkgs; sudo $pm $silent_inst psmisc; fi;\
if ! command -v lsof > /dev/null 2>&1; then sudo $pm $check_pkgs; sudo $pm $silent_inst lsof; fi;\
if ! command -v docker > /dev/null 2>&1; then \
sudo $pm $check_pkgs; sudo $pm $silent_inst $docker_pkg;\
sleep 5; sudo systemctl enable --now docker; sleep 5;\
if ! sudo -n sh -c 'command -v which > /dev/null 2>&1'; then sudo -n $pm $check_pkgs; sudo -n $pm $silent_inst which; fi;\
if ! sudo -n sh -c 'command -v fuser > /dev/null 2>&1'; then sudo -n $pm $check_pkgs; sudo -n $pm $silent_inst psmisc; fi;\
if ! sudo -n sh -c 'command -v lsof > /dev/null 2>&1'; then sudo -n $pm $check_pkgs; sudo -n $pm $silent_inst lsof; fi;\
if ! sudo -n sh -c 'command -v docker > /dev/null 2>&1'; then \
sudo -n $pm $check_pkgs;\
if ! sudo -n $pm $what_pkg $docker_pkg 2>/dev/null | grep -qi podman; then \
sudo -n $pm $silent_inst $docker_pkg;\
sleep 5; sudo -n systemctl enable --now docker; sleep 5;\
else \
echo "Container runtime is not supported";\
exit 1;\
fi;\
fi;\
if [ "$(cat /sys/module/apparmor/parameters/enabled 2>/dev/null)" = "Y" ]; then \
if ! command -v apparmor_parser > /dev/null 2>&1; then sudo $pm $check_pkgs; sudo $pm $silent_inst apparmor; fi;\
if [ "$(sudo -n cat /sys/module/apparmor/parameters/enabled 2>/dev/null)" = "Y" ]; then \
if ! sudo -n sh -c 'command -v apparmor_parser > /dev/null 2>&1'; then \
sudo -n $pm $check_pkgs; sudo -n $pm $silent_inst apparmor;\
fi;\
fi;\
if [ "$(systemctl is-active docker)" != "active" ]; then \
sudo $pm $check_pkgs; sudo $pm $silent_inst $docker_pkg;\
sleep 5; sudo systemctl start docker; sleep 5;\
if [ "$(sudo -n systemctl is-active docker)" != "active" ]; then \
sleep 5; sudo -n systemctl start docker; sleep 5;\
if [ "$(sudo -n systemctl is-active docker)" != "active" ]; then echo "Container runtime service not running"; fi;\
fi;\
if ! command -v sudo > /dev/null 2>&1; then echo "Failed to install sudo, command not found"; exit 1; fi;\
docker --version;\
sudo -n docker --version || docker --version;\
uname -sr

View File

@@ -1,5 +1,6 @@
sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker stop;\
sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker rm -fv;\
sudo docker images -a --format table | grep amnezia | awk '{print $3, $1 ":" $2}' | xargs sudo docker rmi;\
sudo docker volume ls --format '{{.Name}}' | grep '^amnezia-' | xargs -r sudo docker volume rm -f;\
sudo docker network ls | grep amnezia-dns-net | awk '{print $1}' | xargs sudo docker network rm;\
sudo rm -frd /opt/amnezia

View File

@@ -1,4 +1,3 @@
sudo docker stop $CONTAINER_NAME;\
sudo docker rm -fv $CONTAINER_NAME;\
sudo docker rmi $CONTAINER_NAME;\
test "$REMOVE_CONTAINER_DATA" = "1" && sudo docker volume rm -f ${CONTAINER_NAME}-data 2>/dev/null || true
sudo docker rmi $CONTAINER_NAME;

View File

@@ -475,8 +475,7 @@ bool SubscriptionUiController::deactivateExternalDevice(const QString &serverId,
void SubscriptionUiController::validateConfig()
{
const QString serverId = m_serversController->getDefaultServerId();
if (!serverId.isEmpty() && m_serversController->isLegacyApiV1Server(serverId)) {
emit unsupportedConnectDrawerRequested();
if (serverId.isEmpty()) {
emit configValidated(false);
return;
}

View File

@@ -8,6 +8,8 @@
#include "amneziaApplication.h"
#include "core/controllers/serversController.h"
#include "core/models/containerConfig.h"
#include "core/utils/containerEnum.h"
ConnectionUiController::ConnectionUiController(ConnectionController* connectionController,
ServersController* serversController,
@@ -33,7 +35,7 @@ void ConnectionUiController::openConnection()
ErrorCode errorCode = m_connectionController->openConnection(serverId);
if (errorCode != ErrorCode::NoError) {
emit connectionErrorOccurred(errorCode);
notifyConnectionBlocked(errorCode);
return;
}
}
@@ -130,10 +132,36 @@ void ConnectionUiController::toggleConnection()
} else if (isConnected()) {
closeConnection();
} else {
const QString serverId = m_serversController->getDefaultServerId();
if (serverId.isEmpty()) {
return;
}
const ErrorCode errorCode = m_connectionController->isConnectionSupported(serverId);
if (errorCode != ErrorCode::NoError) {
notifyConnectionBlocked(errorCode);
return;
}
emit prepareConfig();
}
}
void ConnectionUiController::notifyConnectionBlocked(ErrorCode errorCode)
{
if (errorCode == ErrorCode::LegacyApiV1NotSupportedError) {
emit unsupportedConnectDrawerRequested();
return;
}
if (errorCode == ErrorCode::NoInstalledContainersError) {
emit noInstalledContainers();
return;
}
emit connectionErrorOccurred(errorCode);
}
bool ConnectionUiController::isConnectionInProgress() const
{
return m_isConnectionInProgress;
@@ -143,3 +171,32 @@ bool ConnectionUiController::isConnected() const
{
return m_isConnected;
}
bool ConnectionUiController::isRevokeBlockedDuringActiveConnection(const QString &serverId, int containerIndex,
const QString &clientId) const
{
if (clientId.isEmpty() || (!isConnected() && !isConnectionInProgress())) {
return false;
}
if (m_serversController->getDefaultServerId() != serverId) {
return false;
}
if (static_cast<int>(m_serversController->getDefaultContainer(serverId)) != containerIndex) {
return false;
}
const auto adminConfig = m_serversController->selfHostedAdminConfig(serverId);
if (!adminConfig.has_value()) {
return false;
}
const QString connectionClientId =
adminConfig->containerConfig(static_cast<DockerContainer>(containerIndex)).protocolConfig.clientId();
if (connectionClientId.isEmpty()) {
return false;
}
return connectionClientId == clientId || connectionClientId.contains(clientId);
}

View File

@@ -35,6 +35,8 @@ public slots:
void openConnection();
void closeConnection();
bool isRevokeBlockedDuringActiveConnection(const QString &serverId, int containerIndex, const QString &clientId) const;
ErrorCode getLastConnectionError();
void onConnectionStateChanged(Vpn::ConnectionState state);
@@ -48,9 +50,12 @@ signals:
void connectButtonClicked();
void preparingConfig();
void prepareConfig();
void unsupportedConnectDrawerRequested();
void noInstalledContainers();
private:
Vpn::ConnectionState getCurrentConnectionState();
void notifyConnectionBlocked(ErrorCode errorCode);
ConnectionController* m_connectionController;
ServersController* m_serversController;

View File

@@ -2,7 +2,6 @@
#include "ui/utils/converter.h"
#include "core/utils/errorStrings.h"
#include "core/utils/appUiConfig.h"
#if defined(MACOS_NE)
#include "platforms/ios/ios_controller.h"
#endif
@@ -64,7 +63,7 @@ QString PageController::getPagePath(PageLoader::PageEnum page)
{
QMetaEnum metaEnum = QMetaEnum::fromType<PageLoader::PageEnum>();
QString pageName = metaEnum.valueToKey(static_cast<int>(page));
return QStringLiteral(APP_QML_PAGES_PREFIX) + pageName + QStringLiteral(".qml");
return "qrc:/ui/qml/Pages2/" + pageName + ".qml";
}
void PageController::closeWindow()
@@ -129,6 +128,11 @@ void PageController::showOnStartup()
}
}
bool PageController::shouldStartMinimized() const
{
return m_settingsController->isStartMinimizedEnabled();
}
bool PageController::isTriggeredByConnectButton()
{
return m_isTriggeredByConnectButton;

View File

@@ -123,6 +123,7 @@ public slots:
void updateNavigationBarColor(const int color);
void showOnStartup();
bool shouldStartMinimized() const;
bool isTriggeredByConnectButton();
void setTriggeredByConnectButton(bool trigger);

View File

@@ -9,6 +9,13 @@ ExportUiController::ExportUiController(ExportController* exportController, QObje
: QObject(parent),
m_exportController(exportController)
{
connect(m_exportController, &ExportController::revokeFinished, this, [this](ErrorCode errorCode) {
if (errorCode == ErrorCode::NoError) {
emit revokeConfigFinished();
} else {
emit exportErrorOccurred(errorCode);
}
});
}
void ExportUiController::generateFullAccessConfig(const QString &serverId)
@@ -92,7 +99,6 @@ void ExportUiController::updateClientManagementModel(const QString &serverId, in
void ExportUiController::revokeConfig(int row, const QString &serverId, int containerIndex)
{
m_exportController->revokeConfig(row, serverId, containerIndex);
emit revokeConfigFinished();
}
void ExportUiController::renameClient(int row, const QString &clientName, const QString &serverId, int containerIndex)

View File

@@ -75,13 +75,7 @@ InstallUiController::InstallUiController(InstallController *installController,
m_connectionController(connectionController)
{
connect(m_installController, &InstallController::configValidated, this, &InstallUiController::configValidated);
connect(m_installController, &InstallController::validationErrorOccurred, this, [this](ErrorCode errorCode) {
if (errorCode == ErrorCode::NoInstalledContainersError) {
emit noInstalledContainers();
} else {
emit installationErrorOccurred(errorCode);
}
});
connect(m_installController, &InstallController::validationErrorOccurred, this, &InstallUiController::installationErrorOccurred);
}
InstallUiController::~InstallUiController()
@@ -217,15 +211,13 @@ void InstallUiController::scanServerForInstalledContainers(const QString &server
emit installationErrorOccurred(errorCode);
}
void InstallUiController::updateContainer(const QString &serverId, int containerIndex, int protocolIndex, bool closePage)
bool InstallUiController::buildContainerConfigFromModel(int containerIndex, int protocolIndex, ContainerConfig &containerConfig)
{
DockerContainer container = static_cast<DockerContainer>(containerIndex);
Proto protocolType = static_cast<Proto>(protocolIndex);
ContainerConfig containerConfig;
containerConfig.container = container;
switch (protocolType) {
case Proto::Awg: {
containerConfig.protocolConfig = m_awgConfigModel->getProtocolConfig();
@@ -271,6 +263,41 @@ void InstallUiController::updateContainer(const QString &serverId, int container
}
#endif
default:
return false;
}
return true;
}
void InstallUiController::updateClientConfig(const QString &serverId, int containerIndex, int protocolIndex, bool closePage)
{
DockerContainer container = static_cast<DockerContainer>(containerIndex);
Proto protocolType = static_cast<Proto>(protocolIndex);
ContainerConfig containerConfig;
if (!buildContainerConfigFromModel(containerIndex, protocolIndex, containerConfig)) {
return;
}
ErrorCode errorCode = m_installController->updateClientConfig(serverId, container, containerConfig);
if (errorCode == ErrorCode::NoError) {
ContainerConfig updatedConfig = m_serversController->getContainerConfig(serverId, container);
m_protocolModel->updateModel(updatedConfig);
updateProtocolConfigModel(serverId, static_cast<int>(container), static_cast<int>(protocolType));
emit updateContainerFinished(tr("Settings updated successfully"), closePage);
return;
}
emit installationErrorOccurred(errorCode);
}
void InstallUiController::updateServerConfig(const QString &serverId, int containerIndex, int protocolIndex, bool closePage)
{
DockerContainer container = static_cast<DockerContainer>(containerIndex);
Proto protocolType = static_cast<Proto>(protocolIndex);
ContainerConfig containerConfig;
if (!buildContainerConfigFromModel(containerIndex, protocolIndex, containerConfig)) {
return;
}
ContainerConfig oldContainerConfig = m_serversController->getContainerConfig(serverId, container);
@@ -279,14 +306,17 @@ void InstallUiController::updateContainer(const QString &serverId, int container
|| container == DockerContainer::Xray || container == DockerContainer::SSXray;
if (asyncUpdate) {
emit serverIsBusy(true);
const bool emitBusy = container == DockerContainer::MtProxy || container == DockerContainer::Telemt;
if (emitBusy)
emit serverIsBusy(true);
auto *watcher = new QFutureWatcher<ErrorCode>(this);
const Proto protocolTypeCopy = protocolType;
QObject::connect(watcher, &QFutureWatcher<ErrorCode>::finished, this,
[this, watcher, serverId, container, closePage, protocolTypeCopy]() {
[this, watcher, serverId, container, closePage, protocolTypeCopy, emitBusy]() {
const ErrorCode errorCode = watcher->result();
watcher->deleteLater();
emit serverIsBusy(false);
if (emitBusy)
emit serverIsBusy(false);
if (errorCode == ErrorCode::NoError) {
const ContainerConfig updatedConfig =
@@ -305,13 +335,13 @@ void InstallUiController::updateContainer(const QString &serverId, int container
QFuture<ErrorCode> future =
QtConcurrent::run([installController, serverId, container, oldConfigCopy,
newConfigCopy]() mutable -> ErrorCode {
return installController->updateContainer(serverId, container, oldConfigCopy, newConfigCopy);
return installController->updateServerConfig(serverId, container, oldConfigCopy, newConfigCopy);
});
watcher->setFuture(future);
return;
}
ErrorCode errorCode = m_installController->updateContainer(serverId, container, oldContainerConfig, containerConfig);
ErrorCode errorCode = m_installController->updateServerConfig(serverId, container, oldContainerConfig, containerConfig);
if (errorCode == ErrorCode::NoError) {
ContainerConfig updatedConfig = m_serversController->getContainerConfig(serverId, container);

View File

@@ -64,7 +64,8 @@ public slots:
void scanServerForInstalledContainers(const QString &serverId);
void updateContainer(const QString &serverId, int containerIndex, int protocolIndex, bool closePage = true);
void updateServerConfig(const QString &serverId, int containerIndex, int protocolIndex, bool closePage = true);
void updateClientConfig(const QString &serverId, int containerIndex, int protocolIndex, bool closePage = true);
void removeServer(const QString &serverId);
void rebootServer(const QString &serverId);
@@ -132,7 +133,6 @@ signals:
void cachedProfileCleared(const QString &message);
void apiConfigRemoved(const QString &message);
void noInstalledContainers();
void configValidated(bool isValid);
private:
@@ -162,6 +162,8 @@ private:
QString m_privateKeyPassphrase;
void updateProtocolConfigModel(const QString &serverId, int containerIndex, int protocolIndex);
bool buildContainerConfigFromModel(int containerIndex, int protocolIndex, ContainerConfig &containerConfig);
};
#endif // INSTALLUICONTROLLER_H

View File

@@ -156,7 +156,17 @@ void ServersUiController::updateModel()
m_serversModel->updateModel(m_orderedServerDescriptions, defaultServerId);
updateContainersModel();
if (!m_processedServerId.isEmpty()) {
if (isServerFromApi(m_processedServerId)) {
const auto &description = serverDescriptionById(m_processedServerId);
if (description.isApiV2 && description.isCountrySelectionAvailable
&& !description.apiAvailableCountries.isEmpty()) {
emit updateApiCountryModel();
}
} else {
updateContainersModel();
}
}
updateDefaultServerContainersModel();
if (hadServersFromGatewayBefore != hasServersFromGatewayNow) {
@@ -350,19 +360,14 @@ void ServersUiController::setProcessedServerId(const QString &serverId)
m_processedServerId = normalizedServerId;
if (newIndex >= 0) {
updateContainersModel();
for (const auto &description : m_orderedServerDescriptions) {
if (description.serverId != normalizedServerId) {
continue;
if (isServerFromApi(m_processedServerId)) {
const auto &description = serverDescriptionById(m_processedServerId);
if (description.isApiV2 && description.isCountrySelectionAvailable
&& !description.apiAvailableCountries.isEmpty()) {
emit updateApiCountryModel();
}
if (description.isApiV2) {
if (description.isCountrySelectionAvailable && !description.apiAvailableCountries.isEmpty()) {
emit updateApiCountryModel();
}
emit updateApiServicesModel();
}
break;
} else {
updateContainersModel();
}
}

View File

@@ -113,7 +113,6 @@ signals:
void processedContainerIndexChanged(int index);
void hasServersFromGatewayApiChanged();
void updateApiCountryModel();
void updateApiServicesModel();
public:
void updateModel();

View File

@@ -17,17 +17,15 @@
#endif
#if defined(Q_OS_IOS) || defined(MACOS_NE)
#include "core/utils/swiftInterop.h"
#include <AmneziaVPN-Swift.h>
#endif
SettingsUiController::SettingsUiController(SettingsController* settingsController,
ServersController* serversController,
LanguageUiController* languageUiController,
QObject *parent)
: QObject(parent),
m_settingsController(settingsController),
m_serversController(serversController),
m_languageUiController(languageUiController)
m_serversController(serversController)
{
#ifdef Q_OS_ANDROID
connect(AndroidController::instance(), &AndroidController::notificationStateChanged, this, &SettingsUiController::onNotificationStateChanged);
@@ -83,7 +81,7 @@ void SettingsUiController::toggleLogging(bool enable)
{
m_settingsController->toggleLogging(enable);
#if defined(Q_OS_IOS)
SWIFT_INTEROP_NAMESPACE::toggleLogging(enable);
AmneziaVPN::toggleLogging(enable);
#endif
if (enable == true) {
qInfo().noquote() << QString("Logging has enabled on %1 version %2 %3").arg(APPLICATION_NAME, APP_VERSION, GIT_COMMIT_HASH);
@@ -157,13 +155,13 @@ void SettingsUiController::restoreAppConfigFromData(const QByteArray &data)
{
ErrorCode errorCode = m_settingsController->restoreAppConfigFromData(data);
if (errorCode == ErrorCode::NoError) {
emit appLanguageChanged(
static_cast<LanguageSettings::AvailableLanguageEnum>(m_languageUiController->getCurrentLanguageIndex()));
emit appLanguageChanged();
bool amneziaDnsEnabled = m_settingsController->isAmneziaDnsEnabled();
emit amneziaDnsToggled(amneziaDnsEnabled);
emit restoreBackupFinished();
emit autoStartChanged();
emit startMinimizedChanged();
} else {
emit errorOccurred(errorCode);
@@ -178,13 +176,14 @@ QString SettingsUiController::getAppVersion()
void SettingsUiController::clearSettings()
{
m_settingsController->clearSettings();
emit autoStartChanged();
emit startMinimizedChanged();
emit resetLanguageToSystem();
emit changeSettingsFinished(tr("All settings have been reset to default values"));
#if defined(Q_OS_IOS) || defined(MACOS_NE)
SWIFT_INTEROP_NAMESPACE::clearSettings();
AmneziaVPN::clearSettings();
#endif
}
@@ -206,9 +205,8 @@ bool SettingsUiController::isAutoStartEnabled()
void SettingsUiController::toggleAutoStart(bool enable)
{
m_settingsController->toggleAutoStart(enable);
if (!enable) {
emit startMinimizedChanged();
}
emit autoStartChanged();
emit startMinimizedChanged();
}
bool SettingsUiController::isStartMinimizedEnabled()

View File

@@ -5,8 +5,6 @@
#include "core/controllers/settingsController.h"
#include "core/controllers/serversController.h"
#include "ui/controllers/languageUiController.h"
#include "ui/models/languageModel.h"
#include "core/utils/errorCodes.h"
#include "core/utils/routeModes.h"
#include "core/utils/commonStructs.h"
@@ -17,7 +15,6 @@ class SettingsUiController : public QObject
public:
explicit SettingsUiController(SettingsController* settingsController,
ServersController* serversController,
LanguageUiController* languageUiController,
QObject *parent = nullptr);
Q_PROPERTY(QString primaryDns READ getPrimaryDns WRITE setPrimaryDns NOTIFY primaryDnsChanged)
@@ -32,6 +29,7 @@ public:
Q_PROPERTY(bool isDevGatewayEnv READ isDevGatewayEnv WRITE toggleDevGatewayEnv NOTIFY devGatewayEnvChanged)
Q_PROPERTY(bool isHomeAdLabelVisible READ isHomeAdLabelVisible NOTIFY isHomeAdLabelVisibleChanged)
Q_PROPERTY(bool autoStartEnabled READ isAutoStartEnabled NOTIFY autoStartChanged)
Q_PROPERTY(bool startMinimized READ isStartMinimizedEnabled NOTIFY startMinimizedChanged)
public slots:
@@ -122,7 +120,7 @@ signals:
void loggingDisableByWatcher();
void appLanguageChanged(const LanguageSettings::AvailableLanguageEnum language);
void appLanguageChanged();
void resetLanguageToSystem();
void onNotificationStateChanged();
@@ -135,12 +133,12 @@ signals:
void activityResumed();
void isHomeAdLabelVisibleChanged(bool visible);
void autoStartChanged();
void startMinimizedChanged();
private:
SettingsController* m_settingsController;
ServersController* m_serversController;
LanguageUiController* m_languageUiController;
};
#endif

View File

@@ -30,7 +30,7 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
switch (role) {
case SubscriptionStatusRole: {
if (m_accountInfoData.configType == serverConfigUtils::ConfigType::AmneziaFreeV3) {
return tr("Active");
return QStringLiteral("<p><a style=\"color: #28c840;\">%1</a>").arg(tr("Active"));
}
return apiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate)

View File

@@ -27,6 +27,7 @@ QVariant ClientManagementModel::data(const QModelIndex &index, int role) const
auto userData = client.value(configKey::userData).toObject();
switch (role) {
case ClientIdRole: return client.value(configKey::clientId).toString();
case ClientNameRole: return userData.value(configKey::clientName).toString();
case CreationDateRole: return userData.value(configKey::creationDate).toString();
case LatestHandshakeRole: return userData.value(configKey::latestHandshake).toString();
@@ -62,6 +63,7 @@ void ClientManagementModel::updateClientName(int row, const QString &newName)
QHash<int, QByteArray> ClientManagementModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[ClientIdRole] = "clientId";
roles[ClientNameRole] = "clientName";
roles[CreationDateRole] = "creationDate";
roles[LatestHandshakeRole] = "latestHandshake";

View File

@@ -10,7 +10,8 @@ class ClientManagementModel : public QAbstractListModel
public:
enum Roles {
ClientNameRole = Qt::UserRole + 1,
ClientIdRole = Qt::UserRole + 1,
ClientNameRole,
CreationDateRole,
LatestHandshakeRole,
DataReceivedRole,

View File

@@ -23,6 +23,10 @@ public:
Q_INVOKABLE int containerFromString(const QString &container) const {
return static_cast<int>(amnezia::ContainerUtils::containerFromString(container));
}
Q_INVOKABLE bool isUnsupportedContainer(int containerIndex) const {
return amnezia::ContainerUtils::isUnsupportedContainer(static_cast<amnezia::DockerContainer>(containerIndex));
}
};
#endif // CONTAINERPROPS_H

View File

@@ -67,6 +67,7 @@ QVariant ContainersModel::data(const QModelIndex &index, int role) const
case IsCurrentlyProcessedRole: return container == static_cast<DockerContainer>(m_processedContainerIndex);
case IsSupportedRole: return ContainerUtils::isSupportedByCurrentPlatform(container);
case IsShareableRole: return ContainerUtils::isShareable(container);
case IsUnsupportedContainerRole: return ContainerUtils::isUnsupportedContainer(container);
case IsVpnContainerRole: return ContainerUtils::containerService(container) == ServiceType::Vpn;
case IsServiceContainerRole: return ContainerUtils::containerService(container) == ServiceType::Other;
case IsIpsecRole: return container == DockerContainer::Ipsec;
@@ -142,7 +143,8 @@ bool ContainersModel::hasInstalledProtocols()
bool ContainersModel::isInstallationAllowed(DockerContainer container)
{
return container != DockerContainer::Awg;
return container != DockerContainer::Awg
&& !ContainerUtils::isUnsupportedContainer(container);
}
void ContainersModel::openContainerSettings(int containerIndex)
@@ -176,6 +178,7 @@ QHash<int, QByteArray> ContainersModel::roleNames() const
roles[IsCurrentlyProcessedRole] = "isCurrentlyProcessed";
roles[IsSupportedRole] = "isSupported";
roles[IsShareableRole] = "isShareable";
roles[IsUnsupportedContainerRole] = "isUnsupportedContainer";
roles[IsInstallationAllowedRole] = "isInstallationAllowed";
roles[InstallPageOrderRole] = "installPageOrder";

View File

@@ -39,6 +39,8 @@ public:
IsSupportedRole,
IsShareableRole,
IsUnsupportedContainerRole,
InstallPageOrderRole,
// Container type check roles

View File

@@ -4,6 +4,10 @@
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/utils/networkUtilities.h"
#include <QHostAddress>
#include <QRegularExpression>
using namespace amnezia;
using namespace ProtocolUtils;
@@ -272,7 +276,7 @@ void XrayConfigModel::updateModel(amnezia::DockerContainer container, const amne
}
if (!m_protocolConfig.serverConfig.isThirdPartyConfig) {
applyDefaultsToServerConfig(m_protocolConfig.serverConfig);
applyDefaultsToServerConfig(m_protocolConfig.serverConfig, false);
}
m_originalProtocolConfig = m_protocolConfig;
@@ -283,7 +287,7 @@ void XrayConfigModel::updateModel(amnezia::DockerContainer container, const amne
}
}
void XrayConfigModel::applyDefaultsToServerConfig(amnezia::XrayServerConfig &config)
void XrayConfigModel::applyDefaultsToServerConfig(amnezia::XrayServerConfig &config, bool fillFlowDefault)
{
if (config.port.isEmpty()) {
config.port = protocols::xray::defaultPort;
@@ -306,7 +310,7 @@ void XrayConfigModel::applyDefaultsToServerConfig(amnezia::XrayServerConfig &con
config.security = protocols::xray::defaultSecurity;
}
if (config.flow.isEmpty()) {
if (fillFlowDefault && config.flow.isEmpty()) {
config.flow = protocols::xray::defaultFlow;
}
@@ -585,3 +589,87 @@ QString XrayConfigModel::mkcpDefaultWriteBufferSize()
{
return QString::fromLatin1(protocols::xray::defaultMkcpWriteBufferSize);
}
namespace {
bool isValidSingleHost(const QString &t)
{
if (t.isEmpty() || t.length() > 253) {
return false;
}
QHostAddress a(t);
if (a.protocol() == QHostAddress::IPv4Protocol) {
return NetworkUtilities::checkIPv4Format(t);
}
if (a.protocol() == QHostAddress::IPv6Protocol) {
return true;
}
static const QRegularExpression onlyDigits(QStringLiteral(R"(^\d+$)"));
if (onlyDigits.match(t).hasMatch()) {
return false;
}
QRegExp re = NetworkUtilities::domainRegExp();
re.setCaseSensitivity(Qt::CaseInsensitive);
return re.exactMatch(t);
}
}
bool XrayConfigModel::isValidHost(const QString &host)
{
const QString t = host.trimmed();
if (t.isEmpty()) {
return true;
}
return isValidSingleHost(t);
}
bool XrayConfigModel::isValidSni(const QString &sni)
{
const QString t = sni.trimmed();
if (t.isEmpty()) {
return true;
}
if (t.startsWith(QLatin1String("*."))) {
return isValidSingleHost(t.mid(2));
}
return isValidSingleHost(t);
}
bool XrayConfigModel::isValidPath(const QString &path)
{
const QString t = path.trimmed();
if (t.isEmpty()) {
return true;
}
return t.startsWith(QLatin1Char('/'));
}
QStringList XrayConfigModel::validationErrors() const
{
QStringList errs;
const auto &srv = m_protocolConfig.serverConfig;
if (!srv.port.isEmpty()) {
bool ok = false;
const int p = srv.port.toInt(&ok);
if (!ok || p < 1 || p > 65535) {
errs << tr("Port must be in the range of 1 to 65535");
}
}
if (srv.security == QLatin1String("tls") || srv.security == QLatin1String("reality")) {
if (!isValidSni(srv.sni)) {
errs << tr("SNI: enter a valid IP address or domain name");
}
}
if (srv.transport == QLatin1String("xhttp")) {
if (!isValidHost(srv.xhttp.host)) {
errs << tr("Host: enter a valid IP address or domain name");
}
if (!isValidPath(srv.xhttp.path)) {
errs << tr("Path must start with \"/\"");
}
}
return errs;
}

View File

@@ -118,6 +118,11 @@ public:
Q_INVOKABLE static QString mkcpDefaultReadBufferSize();
Q_INVOKABLE static QString mkcpDefaultWriteBufferSize();
Q_INVOKABLE static bool isValidHost(const QString &host);
Q_INVOKABLE static bool isValidSni(const QString &sni);
Q_INVOKABLE static bool isValidPath(const QString &path);
Q_INVOKABLE QStringList validationErrors() const;
public slots:
void updateModel(amnezia::DockerContainer container, const amnezia::XrayProtocolConfig& protocolConfig);
amnezia::XrayProtocolConfig getProtocolConfig();
@@ -137,7 +142,7 @@ private:
amnezia::XrayProtocolConfig m_protocolConfig;
amnezia::XrayProtocolConfig m_originalProtocolConfig;
void applyDefaultsToServerConfig(amnezia::XrayServerConfig& config);
void applyDefaultsToServerConfig(amnezia::XrayServerConfig& config, bool fillFlowDefault = true);
};
#endif // XRAYCONFIGMODEL_H

View File

@@ -13,7 +13,7 @@
#include "core/utils/networkUtilities.h"
#if defined(Q_OS_IOS) || defined(MACOS_NE)
#include "core/utils/swiftInterop.h"
#include <AmneziaVPN-Swift.h>
#endif
#include "core/utils/api/apiUtils.h"

View File

@@ -56,14 +56,17 @@ ListViewType {
return
}
if (checked) {
containersDropDown.closeTriggered()
ServersUiController.setDefaultContainer(ServersUiController.defaultServerId, proxyDefaultServerContainersModel.mapToSource(index))
} else {
ServersUiController.processedContainerIndex = proxyDefaultServerContainersModel.mapToSource(index)
var containerIndex = proxyDefaultServerContainersModel.mapToSource(index)
if (!isInstalled) {
ServersUiController.processedContainerIndex = containerIndex
PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings)
containersDropDown.closeTriggered()
return
}
containersDropDown.closeTriggered()
ServersUiController.setDefaultContainer(ServersUiController.defaultServerId, containerIndex)
}
MouseArea {

View File

@@ -5,7 +5,6 @@ import QtQuick.Layouts
import SortFilterProxyModel 0.2
import PageEnum 1.0
import ContainerProps 1.0
import "../Controls2"
import "../Controls2/TextTypes"

View File

@@ -6,8 +6,36 @@ Menu {
popupType: Popup.Native
onAboutToShow: blocker.enabled = true
onClosed: blocker.enabled = false
property Item inputBlocker: null
Component {
id: inputBlockerComponent
MouseArea {
anchors.fill: parent
preventStealing: true
}
}
onAboutToShow: {
if (!textObj || !textObj.window) {
return
}
const contentItem = textObj.window.contentItem
if (!inputBlocker) {
inputBlocker = inputBlockerComponent.createObject(contentItem)
} else {
inputBlocker.parent = contentItem
}
}
onClosed: {
if (inputBlocker) {
inputBlocker.destroy()
inputBlocker = null
}
}
MenuItem {
text: qsTr("C&ut")
@@ -31,11 +59,4 @@ Menu {
enabled: textObj.length > 0
onTriggered: textObj.selectAll()
}
MouseArea {
id: blocker
z: 2
enabled: false
preventStealing: true
}
}

View File

@@ -42,6 +42,7 @@ Item {
property int rootButtonTextBottomMargin: 16
property real drawerHeight: 0.9
property bool fitContent: false
property Item drawerParent
property Component listView
@@ -219,12 +220,20 @@ Item {
parent: drawerParent
anchors.fill: parent
expandedHeight: drawerParent.height * drawerHeight
property real measuredContentHeight: 0
expandedHeight: (root.fitContent && measuredContentHeight > 0)
? Math.min(measuredContentHeight, drawerParent.height * root.drawerHeight)
: drawerParent.height * root.drawerHeight
expandedStateContent: Item {
id: container
implicitHeight: menu.expandedHeight
property real fitHeight: backButton.implicitHeight + titleLabel.implicitHeight
+ (listViewLoader.item ? listViewLoader.item.contentHeight : 0) + 48
onFitHeightChanged: menu.measuredContentHeight = fitHeight
Component.onCompleted: menu.measuredContentHeight = fitHeight
ColumnLayout {
id: header
@@ -238,6 +247,7 @@ Item {
}
Header2Type {
id: titleLabel
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.bottomMargin: 16

View File

@@ -12,8 +12,8 @@ import "../Controls2/TextTypes"
// MinMaxRowType {
// minValue: "0"
// maxValue: "0"
// onMinChanged: someProperty = val
// onMaxChanged: someProperty = val
// onMinChanged: function(val) { someProperty = val }
// onMaxChanged: function(val) { someProperty = val }
// }
Item {
id: root
@@ -21,41 +21,128 @@ Item {
property string minValue: "0"
property string maxValue: "0"
property int minLimit: 0
property int maxLimit: 2147483647
property string hintText: root.minLimit > 0
? (root.minLimit + "" + root.maxLimit)
: ("≤ " + root.maxLimit)
signal minChanged(string val)
signal maxChanged(string val)
signal edited()
implicitHeight: row.implicitHeight
implicitWidth: row.implicitWidth
implicitHeight: col.implicitHeight
implicitWidth: col.implicitWidth
RowLayout {
id: row
function clampValue(text) {
if (text === "")
return ""
var n = parseInt(text, 10)
if (isNaN(n))
return ""
if (n < root.minLimit)
n = root.minLimit
if (n > root.maxLimit)
n = root.maxLimit
return String(n)
}
function capEdit(tf, holder) {
if (tf.text !== "" && parseInt(tf.text, 10) > root.maxLimit) {
tf.text = holder.lastValid
tf.cursorPosition = tf.text.length
} else {
holder.lastValid = tf.text
}
}
ColumnLayout {
id: col
anchors.fill: parent
spacing: 10
spacing: 4
// Min field
TextFieldWithHeaderType {
RowLayout {
id: row
Layout.fillWidth: true
headerText: qsTr("Min")
textField.text: root.minValue
textField.validator: IntValidator { bottom: 0 }
textField.onEditingFinished: {
if (textField.text !== root.minValue) {
root.minChanged(textField.text)
spacing: 10
// Min field
TextFieldWithHeaderType {
id: minField
property string lastValid: ""
Layout.fillWidth: true
headerText: qsTr("Min")
textField.maximumLength: 10
textField.validator: RegularExpressionValidator { regularExpression: /^\d*$/ }
textField.onActiveFocusChanged: {
if (minField.textField.activeFocus)
minField.lastValid = minField.textField.text
}
textField.onTextEdited: { root.capEdit(minField.textField, minField); root.edited() }
textField.onEditingFinished: {
var v = root.clampValue(minField.textField.text)
if (v !== "" && root.maxValue !== "") {
var mx = parseInt(root.maxValue, 10)
if (!isNaN(mx) && parseInt(v, 10) > mx)
root.maxChanged(v)
}
if (v !== root.minValue)
root.minChanged(v)
else if (minField.textField.text !== v)
minField.textField.text = v
}
Binding {
target: minField.textField
property: "text"
value: root.minValue
when: !minField.textField.activeFocus
restoreMode: Binding.RestoreNone
}
}
// Max field
TextFieldWithHeaderType {
id: maxField
property string lastValid: ""
Layout.fillWidth: true
headerText: qsTr("Max")
textField.maximumLength: 10
textField.validator: RegularExpressionValidator { regularExpression: /^\d*$/ }
textField.onActiveFocusChanged: {
if (maxField.textField.activeFocus)
maxField.lastValid = maxField.textField.text
}
textField.onTextEdited: { root.capEdit(maxField.textField, maxField); root.edited() }
textField.onEditingFinished: {
var v = root.clampValue(maxField.textField.text)
if (v !== "" && root.minValue !== "") {
var mn = parseInt(root.minValue, 10)
if (!isNaN(mn) && parseInt(v, 10) < mn)
v = String(mn)
}
if (v !== root.maxValue)
root.maxChanged(v)
else if (maxField.textField.text !== v)
maxField.textField.text = v
}
Binding {
target: maxField.textField
property: "text"
value: root.maxValue
when: !maxField.textField.activeFocus
restoreMode: Binding.RestoreNone
}
}
}
// Max field
TextFieldWithHeaderType {
SmallTextType {
visible: root.hintText !== ""
text: root.hintText
color: AmneziaStyle.color.mutedGray
Layout.fillWidth: true
headerText: qsTr("Max")
textField.text: root.maxValue
textField.validator: IntValidator { bottom: 0 }
textField.onEditingFinished: {
if (textField.text !== root.maxValue) {
root.maxChanged(textField.text)
}
}
}
}
}

View File

@@ -25,8 +25,8 @@ PageType {
filters: [
ValueFilter {
roleName: "isCurrentlyProcessed"
value: true
roleName: "serverId"
value: ServersUiController.processedServerId
}
]
}

View File

@@ -440,8 +440,7 @@ PageType {
return
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Awg)
InstallController.updateClientConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Awg)
}
var noButtonFunction = function() {}

View File

@@ -561,7 +561,7 @@ PageType {
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Awg)
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Awg)
}
var noButtonFunction = function() {}

View File

@@ -434,7 +434,7 @@ PageType {
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.OpenVpn)
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.OpenVpn)
}
var noButtonFunction = function() {
if (!GC.isMobile()) {

View File

@@ -128,8 +128,7 @@ PageType {
return
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.WireGuard)
InstallController.updateClientConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.WireGuard)
}
var noButtonFunction = function() {}
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)

View File

@@ -129,7 +129,7 @@ PageType {
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.WireGuard)
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.WireGuard)
}
var noButtonFunction = function() {
if (!GC.isMobile()) {

View File

@@ -112,7 +112,7 @@ PageType {
return
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
}
var noButtonFunction = function () {
if (typeof GC !== "undefined" && !GC.isMobile()) {

View File

@@ -15,6 +15,8 @@ import "../Components"
PageType {
id: root
property bool editDirty: false
BackButtonType {
id: backButton
anchors.top: parent.top
@@ -90,6 +92,7 @@ PageType {
DropDownType {
id: tlsAlpnDropDown
fitContent: true
Layout.fillWidth: true
Layout.topMargin: 16
Layout.leftMargin: 16
@@ -133,6 +136,7 @@ PageType {
DropDownType {
id: tlsFingerprintDropDown
fitContent: true
Layout.fillWidth: true
Layout.topMargin: 8
Layout.leftMargin: 16
@@ -175,14 +179,21 @@ PageType {
}
TextFieldWithHeaderType {
id: sniFieldTls
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 8
headerText: qsTr("Server Name (SNI)")
textField.text: sni
textField.validator: RegularExpressionValidator { regularExpression: /^[A-Za-z0-9.*_-]*$/ }
textField.onTextEdited: root.editDirty = (textField.text !== sni)
textField.onEditingFinished: {
if (textField.text !== sni) sni = textField.text
var v = textField.text.trim()
if (v !== sni) sni = v
else if (textField.text !== v) textField.text = v
sniFieldTls.errorText = XrayConfigModel.isValidSni(v) ? "" : qsTr("Enter a valid IP address or domain name")
root.editDirty = false
}
}
}
@@ -195,6 +206,7 @@ PageType {
DropDownType {
id: realityFingerprintDropDown
fitContent: true
Layout.fillWidth: true
Layout.topMargin: 16
Layout.leftMargin: 16
@@ -237,14 +249,21 @@ PageType {
}
TextFieldWithHeaderType {
id: sniFieldReality
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 8
headerText: qsTr("Server Name (SNI)")
textField.text: sni
textField.validator: RegularExpressionValidator { regularExpression: /^[A-Za-z0-9.*_-]*$/ }
textField.onTextEdited: root.editDirty = (textField.text !== sni)
textField.onEditingFinished: {
if (textField.text !== sni) sni = textField.text
var v = textField.text.trim()
if (v !== sni) sni = v
else if (textField.text !== v) textField.text = v
sniFieldReality.errorText = XrayConfigModel.isValidSni(v) ? "" : qsTr("Enter a valid IP address or domain name")
root.editDirty = false
}
}
}
@@ -265,10 +284,15 @@ PageType {
anchors.rightMargin: 16
anchors.bottomMargin: 16 + PageController.safeAreaBottomMargin
visible: listView.enabled && XrayConfigModel.hasUnsavedChanges
visible: listView.enabled && (XrayConfigModel.hasUnsavedChanges || root.editDirty)
enabled: visible
text: qsTr("Save")
clickedFunc: function () {
var errs = XrayConfigModel.validationErrors()
if (errs.length > 0) {
PageController.showErrorMessage(errs.join("\n"))
return
}
var headerText = qsTr("Save settings?")
var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.")
var yesButtonText = qsTr("Continue")
@@ -279,7 +303,7 @@ PageType {
return
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
}
var noButtonFunction = function () {
if (typeof GC !== "undefined" && !GC.isMobile()) {

View File

@@ -17,6 +17,10 @@ import "../Components"
PageType {
id: root
enableTimer: false
property bool portDirty: false
function formatTransport(value) {
if (value === "raw") return "RAW (TCP)"
if (value === "xhttp") return "XHTTP"
@@ -39,8 +43,8 @@ PageType {
anchors.right: parent.right
anchors.topMargin: 20 + PageController.safeAreaTopMargin
onFocusChanged: {
if (this.activeFocus) {
onActiveFocusChanged: {
if (backButton.enabled && backButton.activeFocus) {
listView.positionViewAtBeginning()
}
}
@@ -60,8 +64,6 @@ PageType {
delegate: ColumnLayout {
width: listView.width
property alias focusItemId: textFieldWithHeaderType.textField
spacing: 0
Text {
@@ -107,13 +109,43 @@ PageType {
Layout.rightMargin: 16
enabled: listView.enabled
headerText: qsTr("Port")
textField.text: port
subtitleText: qsTr("165535")
Binding {
target: textFieldWithHeaderType.textField
property: "text"
value: port
when: !textFieldWithHeaderType.textField.activeFocus
restoreMode: Binding.RestoreNone
}
textField.maximumLength: 5
textField.validator: IntValidator {
bottom: 1; top: 65535
textField.validator: RegularExpressionValidator {
regularExpression: /^(|\d{1,4}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$/
}
textField.onActiveFocusChanged: {
if (textField.activeFocus && textField.text === "" && port !== "") {
textField.text = port
}
}
textField.onTextChanged: {
root.portDirty = (textField.text !== port)
}
textField.onEditingFinished: {
if (textField.text !== port) port = textField.text
var v = textFieldWithHeaderType.textField.text
if (v !== "") {
var n = parseInt(v, 10)
if (isNaN(n) || n < 1)
n = 1
if (n > 65535)
n = 65535
v = String(n)
if (textFieldWithHeaderType.textField.text !== v)
textFieldWithHeaderType.textField.text = v
}
if (v !== port)
port = v
root.portDirty = false
}
checkEmptyText: true
}
@@ -172,12 +204,16 @@ PageType {
Layout.leftMargin: 16
Layout.rightMargin: 16
visible: listView.enabled
&& (XrayConfigModel.hasUnsavedChanges
|| textFieldWithHeaderType.textField.text !== port)
enabled: visible && textFieldWithHeaderType.errorText === ""
&& (XrayConfigModel.hasUnsavedChanges || root.portDirty)
enabled: visible && textFieldWithHeaderType.textField.text !== ""
text: qsTr("Save")
onClicked: function() {
forceActiveFocus()
var errs = XrayConfigModel.validationErrors()
if (errs.length > 0) {
PageController.showErrorMessage(errs.join("\n"))
return
}
var headerText = qsTr("Save settings?")
var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.")
var yesButtonText = qsTr("Continue")
@@ -193,7 +229,7 @@ PageType {
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
}
var noButtonFunction = function() {
if (!GC.isMobile()) saveButton.forceActiveFocus()

View File

@@ -15,6 +15,21 @@ import "../Components"
PageType {
id: root
property bool editDirty: false
function clampInt(text, lo, hi) {
if (text === "")
return ""
var n = parseInt(text, 10)
if (isNaN(n))
return ""
if (n < lo)
n = lo
if (n > hi)
n = hi
return String(n)
}
BackButtonType {
id: backButton
anchors.top: parent.top
@@ -108,10 +123,16 @@ PageType {
Layout.rightMargin: 16
Layout.topMargin: 8
headerText: qsTr("TTI")
subtitleText: qsTr("Default: %1 ms", "mKCP TTI").arg(XrayConfigModel.mkcpDefaultTti())
subtitleText: qsTr("Range 10100, default %1 ms", "mKCP TTI").arg(XrayConfigModel.mkcpDefaultTti())
textField.text: mkcpTti
textField.maximumLength: 3
textField.validator: RegularExpressionValidator { regularExpression: /^(|\d{1,2}|100)$/ }
textField.onTextEdited: root.editDirty = (textField.text !== mkcpTti)
textField.onEditingFinished: {
if (textField.text !== mkcpTti) mkcpTti = textField.text
var v = root.clampInt(textField.text, 10, 100)
if (v !== mkcpTti) mkcpTti = v
else if (textField.text !== v) textField.text = v
root.editDirty = false
}
}
@@ -121,10 +142,16 @@ PageType {
Layout.rightMargin: 16
Layout.topMargin: 8
headerText: qsTr("uplinkCapacity")
subtitleText: qsTr("Default: %1 Mbit/s", "mKCP uplink").arg(XrayConfigModel.mkcpDefaultUplinkCapacity())
subtitleText: qsTr("≥ 0, default %1 MB/s", "mKCP uplink").arg(XrayConfigModel.mkcpDefaultUplinkCapacity())
textField.text: mkcpUplinkCapacity
textField.maximumLength: 10
textField.validator: RegularExpressionValidator { regularExpression: /^\d*$/ }
textField.onTextEdited: root.editDirty = (textField.text !== mkcpUplinkCapacity)
textField.onEditingFinished: {
if (textField.text !== mkcpUplinkCapacity) mkcpUplinkCapacity = textField.text
var v = root.clampInt(textField.text, 0, 2147483647)
if (v !== mkcpUplinkCapacity) mkcpUplinkCapacity = v
else if (textField.text !== v) textField.text = v
root.editDirty = false
}
}
@@ -134,10 +161,16 @@ PageType {
Layout.rightMargin: 16
Layout.topMargin: 8
headerText: qsTr("downlinkCapacity")
subtitleText: qsTr("Default: %1 Mbit/s", "mKCP downlink").arg(XrayConfigModel.mkcpDefaultDownlinkCapacity())
subtitleText: qsTr("≥ 0, default %1 MB/s", "mKCP downlink").arg(XrayConfigModel.mkcpDefaultDownlinkCapacity())
textField.text: mkcpDownlinkCapacity
textField.maximumLength: 10
textField.validator: RegularExpressionValidator { regularExpression: /^\d*$/ }
textField.onTextEdited: root.editDirty = (textField.text !== mkcpDownlinkCapacity)
textField.onEditingFinished: {
if (textField.text !== mkcpDownlinkCapacity) mkcpDownlinkCapacity = textField.text
var v = root.clampInt(textField.text, 0, 2147483647)
if (v !== mkcpDownlinkCapacity) mkcpDownlinkCapacity = v
else if (textField.text !== v) textField.text = v
root.editDirty = false
}
}
@@ -147,10 +180,16 @@ PageType {
Layout.rightMargin: 16
Layout.topMargin: 8
headerText: qsTr("readBufferSize")
subtitleText: qsTr("Default: %1 MiB").arg(XrayConfigModel.mkcpDefaultReadBufferSize())
subtitleText: qsTr("≥ 1, default %1 MB").arg(XrayConfigModel.mkcpDefaultReadBufferSize())
textField.text: mkcpReadBufferSize
textField.maximumLength: 10
textField.validator: RegularExpressionValidator { regularExpression: /^\d*$/ }
textField.onTextEdited: root.editDirty = (textField.text !== mkcpReadBufferSize)
textField.onEditingFinished: {
if (textField.text !== mkcpReadBufferSize) mkcpReadBufferSize = textField.text
var v = root.clampInt(textField.text, 1, 2147483647)
if (v !== mkcpReadBufferSize) mkcpReadBufferSize = v
else if (textField.text !== v) textField.text = v
root.editDirty = false
}
}
@@ -160,10 +199,16 @@ PageType {
Layout.rightMargin: 16
Layout.topMargin: 8
headerText: qsTr("writeBufferSize")
subtitleText: qsTr("Default: %1 MiB").arg(XrayConfigModel.mkcpDefaultWriteBufferSize())
subtitleText: qsTr("≥ 1, default %1 MB").arg(XrayConfigModel.mkcpDefaultWriteBufferSize())
textField.text: mkcpWriteBufferSize
textField.maximumLength: 10
textField.validator: RegularExpressionValidator { regularExpression: /^\d*$/ }
textField.onTextEdited: root.editDirty = (textField.text !== mkcpWriteBufferSize)
textField.onEditingFinished: {
if (textField.text !== mkcpWriteBufferSize) mkcpWriteBufferSize = textField.text
var v = root.clampInt(textField.text, 1, 2147483647)
if (v !== mkcpWriteBufferSize) mkcpWriteBufferSize = v
else if (textField.text !== v) textField.text = v
root.editDirty = false
}
}
@@ -187,6 +232,7 @@ PageType {
DropDownType {
id: modeDropDown
fitContent: true
Layout.fillWidth: true
Layout.topMargin: 16
Layout.leftMargin: 16
@@ -239,31 +285,46 @@ PageType {
}
TextFieldWithHeaderType {
id: hostField
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 8
headerText: qsTr("Host")
textField.text: xhttpHost
textField.validator: RegularExpressionValidator { regularExpression: /^[A-Za-z0-9._:,-]*$/ }
textField.onTextEdited: root.editDirty = (textField.text !== xhttpHost)
textField.onEditingFinished: {
if (textField.text !== xhttpHost) xhttpHost = textField.text
var v = textField.text.trim()
if (v !== xhttpHost) xhttpHost = v
else if (textField.text !== v) textField.text = v
hostField.errorText = XrayConfigModel.isValidHost(v) ? "" : qsTr("Enter a valid IP address or domain name")
root.editDirty = false
}
}
TextFieldWithHeaderType {
id: pathField
Layout.fillWidth: true
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.topMargin: 8
headerText: qsTr("Path")
textField.text: xhttpPath
textField.validator: RegularExpressionValidator { regularExpression: /^[A-Za-z0-9\-._~:\/?#\[\]@!$&'()*+,;=%]*$/ }
textField.onTextEdited: root.editDirty = (textField.text !== xhttpPath)
textField.onEditingFinished: {
if (textField.text !== xhttpPath) xhttpPath = textField.text
var v = textField.text.trim()
if (v !== xhttpPath) xhttpPath = v
else if (textField.text !== v) textField.text = v
pathField.errorText = XrayConfigModel.isValidPath(v) ? "" : qsTr("Path must start with \"/\"")
root.editDirty = false
}
}
DropDownType {
id: headersDropDown
fitContent: true
Layout.fillWidth: true
Layout.topMargin: 8
Layout.leftMargin: 16
@@ -307,6 +368,7 @@ PageType {
DropDownType {
id: uplinkMethodDropDown
fitContent: true
Layout.fillWidth: true
Layout.topMargin: 8
Layout.leftMargin: 16
@@ -386,6 +448,7 @@ PageType {
DropDownType {
id: sessionPlacementDropDown
fitContent: true
Layout.fillWidth: true
Layout.topMargin: 8
Layout.leftMargin: 16
@@ -429,6 +492,7 @@ PageType {
DropDownType {
id: sessionKeyDropDown
fitContent: true
Layout.fillWidth: true
Layout.topMargin: 8
Layout.leftMargin: 16
@@ -472,6 +536,7 @@ PageType {
DropDownType {
id: seqPlacementDropDown
fitContent: true
Layout.fillWidth: true
Layout.topMargin: 8
Layout.leftMargin: 16
@@ -520,13 +585,19 @@ PageType {
Layout.topMargin: 8
headerText: qsTr("SeqKey")
textField.text: xhttpSeqKey
textField.validator: RegularExpressionValidator { regularExpression: /^[A-Za-z0-9_-]*$/ }
textField.onTextEdited: root.editDirty = (textField.text !== xhttpSeqKey)
textField.onEditingFinished: {
if (textField.text !== xhttpSeqKey) xhttpSeqKey = textField.text
var v = textField.text.trim()
if (v !== xhttpSeqKey) xhttpSeqKey = v
else if (textField.text !== v) textField.text = v
root.editDirty = false
}
}
DropDownType {
id: uplinkDataPlacementDropDown
fitContent: true
Layout.fillWidth: true
Layout.topMargin: 8
Layout.leftMargin: 16
@@ -575,8 +646,13 @@ PageType {
Layout.topMargin: 8
headerText: qsTr("UplinkDataKey")
textField.text: xhttpUplinkDataKey
textField.validator: RegularExpressionValidator { regularExpression: /^[A-Za-z0-9_-]*$/ }
textField.onTextEdited: root.editDirty = (textField.text !== xhttpUplinkDataKey)
textField.onEditingFinished: {
if (textField.text !== xhttpUplinkDataKey) xhttpUplinkDataKey = textField.text
var v = textField.text.trim()
if (v !== xhttpUplinkDataKey) xhttpUplinkDataKey = v
else if (textField.text !== v) textField.text = v
root.editDirty = false
}
}
@@ -597,12 +673,16 @@ PageType {
Layout.rightMargin: 16
Layout.topMargin: 8
headerText: qsTr("UplinkChunkSize")
subtitleText: qsTr("≥ 0 (0 = off)")
textField.text: xhttpUplinkChunkSize
textField.validator: IntValidator {
bottom: 0
}
textField.maximumLength: 10
textField.validator: RegularExpressionValidator { regularExpression: /^\d*$/ }
textField.onTextEdited: root.editDirty = (textField.text !== xhttpUplinkChunkSize)
textField.onEditingFinished: {
if (textField.text !== xhttpUplinkChunkSize) xhttpUplinkChunkSize = textField.text
var v = root.clampInt(textField.text, 0, 2147483647)
if (v !== xhttpUplinkChunkSize) xhttpUplinkChunkSize = v
else if (textField.text !== v) textField.text = v
root.editDirty = false
}
}
@@ -612,9 +692,16 @@ PageType {
Layout.rightMargin: 16
Layout.topMargin: 8
headerText: qsTr("scMaxBufferedPosts")
subtitleText: qsTr("≥ 0")
textField.text: xhttpScMaxBufferedPosts
textField.maximumLength: 10
textField.validator: RegularExpressionValidator { regularExpression: /^\d*$/ }
textField.onTextEdited: root.editDirty = (textField.text !== xhttpScMaxBufferedPosts)
textField.onEditingFinished: {
if (textField.text !== xhttpScMaxBufferedPosts) xhttpScMaxBufferedPosts = textField.text
var v = root.clampInt(textField.text, 0, 2147483647)
if (v !== xhttpScMaxBufferedPosts) xhttpScMaxBufferedPosts = v
else if (textField.text !== v) textField.text = v
root.editDirty = false
}
}
@@ -633,8 +720,9 @@ PageType {
Layout.rightMargin: 16
minValue: xhttpScMaxEachPostBytesMin
maxValue: xhttpScMaxEachPostBytesMax
onMinChanged: xhttpScMaxEachPostBytesMin = val
onMaxChanged: xhttpScMaxEachPostBytesMax = val
onMinChanged: function(val) { xhttpScMaxEachPostBytesMin = val; root.editDirty = false }
onMaxChanged: function(val) { xhttpScMaxEachPostBytesMax = val; root.editDirty = false }
onEdited: root.editDirty = true
}
CaptionTextType {
@@ -652,8 +740,9 @@ PageType {
Layout.rightMargin: 16
minValue: xhttpScStreamUpServerSecsMin
maxValue: xhttpScStreamUpServerSecsMax
onMinChanged: xhttpScStreamUpServerSecsMin = val
onMaxChanged: xhttpScStreamUpServerSecsMax = val
onMinChanged: function(val) { xhttpScStreamUpServerSecsMin = val; root.editDirty = false }
onMaxChanged: function(val) { xhttpScStreamUpServerSecsMax = val; root.editDirty = false }
onEdited: root.editDirty = true
}
CaptionTextType {
@@ -671,8 +760,9 @@ PageType {
Layout.rightMargin: 16
minValue: xhttpScMinPostsIntervalMsMin
maxValue: xhttpScMinPostsIntervalMsMax
onMinChanged: xhttpScMinPostsIntervalMsMin = val
onMaxChanged: xhttpScMinPostsIntervalMsMax = val
onMinChanged: function(val) { xhttpScMinPostsIntervalMsMin = val; root.editDirty = false }
onMaxChanged: function(val) { xhttpScMinPostsIntervalMsMax = val; root.editDirty = false }
onEdited: root.editDirty = true
}
// ── Padding and multiplexing ──────────────────────────
@@ -728,10 +818,15 @@ PageType {
anchors.rightMargin: 16
anchors.bottomMargin: 16 + PageController.safeAreaBottomMargin
visible: listView.enabled && XrayConfigModel.hasUnsavedChanges
visible: listView.enabled && (XrayConfigModel.hasUnsavedChanges || root.editDirty)
enabled: visible
text: qsTr("Save")
clickedFunc: function () {
var errs = XrayConfigModel.validationErrors()
if (errs.length > 0) {
PageController.showErrorMessage(errs.join("\n"))
return
}
var headerText = qsTr("Save settings?")
var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.")
var yesButtonText = qsTr("Continue")
@@ -742,7 +837,7 @@ PageType {
return
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
}
var noButtonFunction = function () {
if (typeof GC !== "undefined" && !GC.isMobile()) {

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