Merge branch 'dev' into feature/local-proxy-integration

This commit is contained in:
aiamnezia
2025-12-19 14:18:26 +04:00
296 changed files with 10462 additions and 3415 deletions
+178 -30
View File
@@ -44,6 +44,13 @@ jobs:
submodules: 'true'
fetch-depth: 10
- name: 'Get version from CMakeLists.txt'
id: get_version
run: |
VERSION=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\)/\1/')
echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "Version: $VERSION"
- name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2
@@ -55,13 +62,13 @@ jobs:
bash deploy/build_linux.sh
- name: 'Pack installer'
run: cd deploy && tar -cf AmneziaVPN_Linux_Installer.tar AmneziaVPN_Linux_Installer.bin
run: cd deploy && tar -cf AmneziaVPN_Linux_Installer.tar AmneziaVPN_Linux_Installer.bin && zip AmneziaVPN_${VERSION}_linux_x64.tar.zip AmneziaVPN_Linux_Installer.tar
- name: 'Upload installer artifact'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_Linux_installer.tar
path: deploy/AmneziaVPN_Linux_Installer.tar
name: AmneziaVPN_${{ env.VERSION }}_linux_x64.tar.zip
path: deploy/AmneziaVPN_${{ env.VERSION }}_linux_x64.tar.zip
retention-days: 7
- name: 'Upload unpacked artifact'
@@ -102,6 +109,14 @@ jobs:
submodules: 'true'
fetch-depth: 10
- name: 'Get version from CMakeLists.txt'
id: get_version
shell: bash
run: |
VERSION=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\)/\1/')
echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "Version: $VERSION"
- name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2
@@ -124,19 +139,47 @@ jobs:
with:
arch: 'x64'
- name: 'Setup .NET SDK'
uses: actions/setup-dotnet@v4
with:
dotnet-version: '8.0.x'
- name: 'Install WiX Toolset'
shell: powershell
run: |
dotnet tool install --global wix --version 4.0.6
wix extension add -g WixToolset.UI.wixext/4.0.6
wix extension add -g WixToolset.Util.wixext/4.0.6
wix extension list -g
$wixBinDir = Join-Path $env:USERPROFILE ".dotnet\tools"
echo "WIX_BIN_DIR=$wixBinDir" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
- name: 'Build project'
shell: cmd
run: |
set BUILD_ARCH=${{ env.BUILD_ARCH }}
set QT_BIN_DIR="${{ runner.temp }}\\Qt\\${{ env.QT_VERSION }}\\msvc2019_64\\bin"
set QIF_BIN_DIR="${{ runner.temp }}\\Qt\\Tools\\QtInstallerFramework\\${{ env.QIF_VERSION }}\\bin"
set WIX_BIN_DIR=%USERPROFILE%\.dotnet\tools
call deploy\\build_windows.bat
- name: 'Rename Windows installer'
shell: cmd
run: |
copy AmneziaVPN_x${{ env.BUILD_ARCH }}.exe AmneziaVPN_%VERSION%_x64.exe
- name: 'Upload installer artifact'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_Windows_installer
path: AmneziaVPN_x${{ env.BUILD_ARCH }}.exe
name: AmneziaVPN_${{ env.VERSION }}_x64.exe
path: AmneziaVPN_${{ env.VERSION }}_x64.exe
retention-days: 7
- name: 'Upload MSI installer artifact'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_Windows_MSI_installer
path: AmneziaVPN_x${{ env.BUILD_ARCH }}.msi
retention-days: 7
- name: 'Upload unpacked artifact'
@@ -149,10 +192,10 @@ jobs:
# ------------------------------------------------------
Build-iOS:
runs-on: macos-13
runs-on: macos-latest
env:
QT_VERSION: 6.6.2
QT_VERSION: 6.9.2
CC: cc
CXX: c++
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
@@ -167,7 +210,7 @@ jobs:
- name: 'Setup xcode'
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '15.2'
xcode-version: '26.1'
- name: 'Install desktop Qt'
uses: jurplel/install-qt-action@v3
@@ -331,7 +374,7 @@ jobs:
runs-on: macos-latest
env:
QT_VERSION: 6.8.0
QT_VERSION: 6.8.3
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
@@ -358,10 +401,10 @@ jobs:
- name: 'Setup xcode'
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '15.4.0'
xcode-version: '16.2.0'
- name: 'Install Qt'
uses: jurplel/install-qt-action@v3
uses: jurplel/install-qt-action@v4
with:
version: ${{ env.QT_VERSION }}
host: 'mac'
@@ -369,10 +412,106 @@ jobs:
arch: 'clang_64'
modules: 'qtremoteobjects qt5compat qtshadertools qthttpserver qtwebsockets'
dir: ${{ runner.temp }}
#setup-python: 'true'
#set-env: 'true'
#extra: '--external 7z --base ${{ env.QT_MIRROR }}'
setup-python: 'true'
set-env: 'true'
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
aqtversion: '==3.3.0'
py7zrversion: '==0.22.*'
extra: '--base ${{ env.QT_MIRROR }}'
cache: 'true'
- name: 'Get sources'
uses: actions/checkout@v4
with:
submodules: 'true'
fetch-depth: 10
- name: 'Get version from CMakeLists.txt'
id: get_version
run: |
VERSION=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\)/\1/')
echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "Version: $VERSION"
- name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2
- name: 'Build project'
run: |
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin"
bash deploy/build_macos.sh -n
- name: 'Pack macOS installer'
run: |
cd deploy/build/pkg
zip -r ../../AmneziaVPN_${VERSION}_macos.zip AmneziaVPN.pkg
cd ../../..
- name: 'Upload installer artifact'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_${{ env.VERSION }}_macos.zip
path: deploy/AmneziaVPN_${{ env.VERSION }}_macos.zip
retention-days: 7
- name: 'Upload unpacked artifact'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_MacOS_unpacked
path: deploy/build/client/AmneziaVPN.app
retention-days: 7
Build-MacOS-NE:
runs-on: macos-latest
env:
QT_VERSION: 6.9.2
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
MAC_APP_CERT_CERT: ${{ secrets.MAC_APP_CERT_CERT }}
MAC_SIGNER_ID: ${{ secrets.MAC_SIGNER_ID }}
MAC_APP_CERT_PW: ${{ secrets.MAC_APP_CERT_PW }}
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
steps:
- name: 'Setup xcode'
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '26.1'
- name: 'Install desktop Qt'
uses: jurplel/install-qt-action@v3
with:
version: ${{ env.QT_VERSION }}
host: 'mac'
target: 'desktop'
modules: 'qtremoteobjects qt5compat qtshadertools qtmultimedia'
arch: 'clang_64'
dir: ${{ runner.temp }}
set-env: 'true'
extra: '--base ${{ env.QT_MIRROR }}'
- name: 'Install go'
uses: actions/setup-go@v5
with:
go-version: '1.24'
cache: false
- name: 'Setup gomobile'
run: |
export PATH=$PATH:~/go/bin
go install golang.org/x/mobile/cmd/gomobile@latest
gomobile init
- name: 'Get sources'
uses: actions/checkout@v4
@@ -386,14 +525,7 @@ jobs:
- name: 'Build project'
run: |
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin"
bash deploy/build_macos.sh -n
- name: 'Upload installer artifact'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_MacOS_installer
path: deploy/build/pkg/AmneziaVPN.pkg
retention-days: 7
bash deploy/build_macos_ne.sh
- name: 'Upload unpacked artifact'
uses: actions/upload-artifact@v4
@@ -408,8 +540,8 @@ jobs:
runs-on: ubuntu-latest
env:
ANDROID_BUILD_PLATFORM: android-34
QT_VERSION: 6.7.3
ANDROID_BUILD_PLATFORM: android-36
QT_VERSION: 6.10.1
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools qthttpserver qtwebsockets'
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
@@ -490,6 +622,13 @@ jobs:
with:
submodules: 'true'
- name: 'Get version from CMakeLists.txt'
id: get_version
run: |
VERSION=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\)/\1/')
echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "Version: $VERSION"
- name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2
@@ -523,35 +662,44 @@ jobs:
shell: bash
run: ./deploy/build_android.sh --aab --apk all --build-platform ${{ env.ANDROID_BUILD_PLATFORM }}
- name: 'Rename Android APKs'
run: |
cd deploy/build
mv AmneziaVPN-x86_64-release.apk AmneziaVPN_${VERSION}_android9+_x86_64.apk
mv AmneziaVPN-x86-release.apk AmneziaVPN_${VERSION}_android9+_x86.apk
mv AmneziaVPN-arm64-v8a-release.apk AmneziaVPN_${VERSION}_android9+_arm64-v8a.apk
mv AmneziaVPN-armeabi-v7a-release.apk AmneziaVPN_${VERSION}_android9+_armeabi-v7a.apk
cd ../..
- name: 'Upload x86_64 apk'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN-android-x86_64
path: deploy/build/AmneziaVPN-x86_64-release.apk
name: AmneziaVPN_${{ env.VERSION }}_android9+_x86_64.apk
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_x86_64.apk
compression-level: 0
retention-days: 7
- name: 'Upload x86 apk'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN-android-x86
path: deploy/build/AmneziaVPN-x86-release.apk
name: AmneziaVPN_${{ env.VERSION }}_android9+_x86.apk
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_x86.apk
compression-level: 0
retention-days: 7
- name: 'Upload arm64-v8a apk'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN-android-arm64-v8a
path: deploy/build/AmneziaVPN-arm64-v8a-release.apk
name: AmneziaVPN_${{ env.VERSION }}_android9+_arm64-v8a.apk
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_arm64-v8a.apk
compression-level: 0
retention-days: 7
- name: 'Upload armeabi-v7a apk'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN-android-armeabi-v7a
path: deploy/build/AmneziaVPN-armeabi-v7a-release.apk
name: AmneziaVPN_${{ env.VERSION }}_android9+_armeabi-v7a.apk
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_armeabi-v7a.apk
compression-level: 0
retention-days: 7
+1
View File
@@ -9,6 +9,7 @@ deploy/build_32/*
deploy/build_64/*
winbuild*.bat
.cache/
.vscode/
# Qt-es
+44 -5
View File
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN)
set(AMNEZIAVPN_VERSION 4.8.9.2)
set(AMNEZIAVPN_VERSION 4.8.12.0)
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
DESCRIPTION "AmneziaVPN"
@@ -12,7 +12,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 2092)
set(APP_ANDROID_VERSION_CODE 2101)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux")
@@ -32,14 +32,53 @@ set(QT_BUILD_TOOLS_WHEN_CROSS_COMPILING ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(APPLE AND NOT IOS)
set(CMAKE_OSX_ARCHITECTURES "x86_64")
if(APPLE)
if(IOS)
set(CMAKE_OSX_ARCHITECTURES "arm64")
elseif(MACOS_NE)
set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64")
else()
set(CMAKE_OSX_ARCHITECTURES "x86_64")
endif()
endif()
add_subdirectory(client)
if(NOT IOS AND NOT ANDROID)
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
add_subdirectory(service)
include(${CMAKE_SOURCE_DIR}/deploy/installer/config.cmake)
endif()
set(AMNEZIA_STAGE_DIR "${CMAKE_BINARY_DIR}/stage")
if(WIN32 AND NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
file(TO_CMAKE_PATH "${AMNEZIA_STAGE_DIR}" AMNEZIA_STAGE_DIR_CMAKE)
set(CPACK_GENERATOR "WIX")
set(CPACK_WIX_VERSION 4)
set(CPACK_PACKAGE_NAME "AmneziaVPN")
set(CPACK_PACKAGE_VENDOR "AmneziaVPN")
set(CPACK_PACKAGE_VERSION ${AMNEZIAVPN_VERSION})
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "AmneziaVPN client")
set(CPACK_PACKAGE_INSTALL_DIRECTORY "AmneziaVPN")
set(CPACK_PACKAGE_DIRECTORY "${CMAKE_BINARY_DIR}")
set(CPACK_PACKAGE_EXECUTABLES "AmneziaVPN" "AmneziaVPN")
set(CPACK_WIX_UPGRADE_GUID "{2D55AC62-96D6-4692-8C05-0D85BBF95485}")
set(CPACK_WIX_PRODUCT_ICON "${CMAKE_SOURCE_DIR}/client/images/app.ico")
# WiX patches
set(_AMNEZIA_WIX_PATCH_SERVICE "${CMAKE_SOURCE_DIR}/deploy/installer/wix/service_install_patch.xml")
set(_AMNEZIA_WIX_PATCH_CLOSE_APP "${CMAKE_SOURCE_DIR}/deploy/installer/wix/close_client_patch.xml")
file(TO_CMAKE_PATH "${_AMNEZIA_WIX_PATCH_SERVICE}" _AMNEZIA_WIX_PATCH_SERVICE_CMAKE)
file(TO_CMAKE_PATH "${_AMNEZIA_WIX_PATCH_CLOSE_APP}" _AMNEZIA_WIX_PATCH_CLOSE_APP_CMAKE)
set(CPACK_WIX_PATCH_FILE "${_AMNEZIA_WIX_PATCH_SERVICE_CMAKE};${_AMNEZIA_WIX_PATCH_CLOSE_APP_CMAKE}")
# WiX v4 Util extension for CloseApplication + namespace for util
set(CPACK_WIX_EXTENSIONS "${CPACK_WIX_EXTENSIONS};WixToolset.Util.wixext")
set(CPACK_WIX_CUSTOM_XMLNS "util=http://wixtoolset.org/schemas/v4/wxs/util")
set(CPACK_INSTALLED_DIRECTORIES "${AMNEZIA_STAGE_DIR_CMAKE};/")
include(CPack)
endif()
+19 -9
View File
@@ -3,7 +3,6 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN)
project(${PROJECT})
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set_property(GLOBAL PROPERTY AUTOGEN_TARGETS_FOLDER "Autogen")
set_property(GLOBAL PROPERTY AUTOMOC_TARGETS_FOLDER "Autogen")
@@ -53,8 +52,11 @@ endif()
qt_standard_project_setup()
qt_add_executable(${PROJECT} MANUAL_FINALIZATION)
target_include_directories(${PROJECT} PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
)
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
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)
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_interface.rep)
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_tun2socks.rep)
@@ -110,6 +112,15 @@ include_directories(
${CMAKE_CURRENT_BINARY_DIR}
)
if(MACOS_NE)
message("MACOS_NE is ON")
add_definitions(-DQ_OS_MAC)
add_definitions(-DMACOS_NE)
message("Add macros for MacOS Network Extension")
else()
message("MACOS_NE is OFF")
endif()
include_directories(mozilla)
include_directories(mozilla/shared)
include_directories(mozilla/models)
@@ -139,7 +150,7 @@ if(WIN32)
endif()
if(APPLE)
cmake_policy(SET CMP0099 OLD)
cmake_policy(SET CMP0099 NEW)
cmake_policy(SET CMP0114 NEW)
if(NOT BUILD_OSX_APP_IDENTIFIER)
@@ -158,7 +169,6 @@ if(APPLE)
set(CMAKE_XCODE_GENERATE_SCHEME FALSE)
set(CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM ${BUILD_VPN_DEVELOPMENT_TEAM})
set(CMAKE_XCODE_ATTRIBUTE_GROUP_ID_IOS ${BUILD_IOS_GROUP_IDENTIFIER})
endif()
if(LINUX AND NOT ANDROID)
@@ -166,8 +176,7 @@ if(LINUX AND NOT ANDROID)
link_directories(${CMAKE_CURRENT_LIST_DIR}/platforms/linux)
endif()
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
message("Client desktop build")
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
add_compile_definitions(AMNEZIA_DESKTOP)
endif()
@@ -178,7 +187,9 @@ endif()
if(IOS)
include(cmake/ios.cmake)
include(cmake/ios-arch-fixup.cmake)
elseif(APPLE AND NOT IOS)
elseif(APPLE AND MACOS_NE)
include(cmake/macos_ne.cmake)
elseif(APPLE)
include(cmake/osxtools.cmake)
include(cmake/macos.cmake)
endif()
@@ -199,7 +210,7 @@ elseif(APPLE AND NOT IOS)
set(DEPLOY_PLATFORM_PATH "macos")
endif()
if(NOT IOS AND NOT ANDROID)
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
add_custom_command(
TARGET ${PROJECT} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E $<IF:$<CONFIG:Debug>,copy_directory,true>
@@ -214,7 +225,6 @@ if(NOT IOS AND NOT ANDROID)
$<TARGET_FILE_DIR:${PROJECT}>
COMMAND_EXPAND_LISTS
)
endif()
target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC} ${I18NQRC})
+113 -19
View File
@@ -12,6 +12,9 @@
#include <QTextDocument>
#include <QTimer>
#include <QTranslator>
#include <QEvent>
#include <QDir>
#include <QSettings>
#include "logger.h"
#include "ui/controllers/pageController.h"
@@ -21,8 +24,16 @@
#include "platforms/ios/QRCodeReaderBase.h"
#include "protocols/qml_register_protocols.h"
#include <QtQuick/QQuickWindow> // for QQuickWindow
#include <QWindow> // for qobject_cast<QWindow*>
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv)
bool AmneziaApplication::m_forceQuit = false;
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv),
m_optAutostart({QStringLiteral("a"), QStringLiteral("autostart")}, QStringLiteral("System autostart")),
m_optCleanup ({QStringLiteral("c"), QStringLiteral("cleanup")}, QStringLiteral("Cleanup logs")),
m_optConnect ({QStringLiteral("connect")}, QStringLiteral("Connect to server by index on startup"), QStringLiteral("index")),
m_optImport ({QStringLiteral("import")}, QStringLiteral("Import configuration from data string"), QStringLiteral("data"))
{
setQuitOnLastWindowClosed(false);
@@ -48,8 +59,19 @@ AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_C
AmneziaApplication::~AmneziaApplication()
{
if (m_vpnConnection) {
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectSlots", Qt::QueuedConnection);
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectFromVpn", Qt::QueuedConnection);
QThread::msleep(2000);
}
m_vpnConnectionThread.requestInterruption();
m_vpnConnectionThread.quit();
m_vpnConnectionThread.wait(3000);
if (!m_vpnConnectionThread.wait(3000)) {
m_vpnConnectionThread.terminate();
m_vpnConnectionThread.wait(500);
}
if (m_engine) {
QObject::disconnect(m_engine, 0, 0, 0);
@@ -57,21 +79,47 @@ AmneziaApplication::~AmneziaApplication()
}
}
#ifdef Q_OS_ANDROID
namespace {
static void clearQtCaches()
{
const QString cacheRoot = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
if (!cacheRoot.isEmpty()) {
QDir(cacheRoot + "/QtShaderCache").removeRecursively();
QDir(cacheRoot + "/qmlcache").removeRecursively();
}
}
}
#endif
void AmneziaApplication::init()
{
m_engine = new QQmlApplicationEngine;
const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml"));
QObject::connect(
m_engine, &QQmlApplicationEngine::objectCreated, this,
[url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
},
Qt::QueuedConnection);
m_engine, &QQmlApplicationEngine::objectCreated, this,
[this, url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl) {
QCoreApplication::exit(-1);
return;
}
// install filter on main window
if (auto win = qobject_cast<QQuickWindow*>(obj)) {
win->installEventFilter(this);
win->show();
}
},
Qt::QueuedConnection);
m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance());
#ifdef MACOS_NE
m_engine->rootContext()->setContextProperty("IsMacOsNeBuild", true);
#else
m_engine->rootContext()->setContextProperty("IsMacOsNeBuild", false);
#endif
m_vpnConnection.reset(new VpnConnection(m_settings));
m_vpnConnection->moveToThread(&m_vpnConnectionThread);
m_vpnConnectionThread.start();
@@ -79,6 +127,16 @@ void AmneziaApplication::init()
m_coreController.reset(new CoreController(m_vpnConnection, m_settings, m_engine));
m_engine->addImportPath("qrc:/ui/qml/Modules/");
if (m_parser.isSet(m_optImport)) {
const QString data = m_parser.value(m_optImport);
if (!data.isEmpty()) {
if (m_coreController) {
m_coreController->importConfigFromData(data);
}
}
}
m_engine->load(url);
m_coreController->setQmlRoot();
@@ -94,7 +152,7 @@ void AmneziaApplication::init()
Logger::setServiceLogsEnabled(enabled);
#ifdef Q_OS_WIN //TODO
if (m_parser.isSet("a"))
if (m_parser.isSet(m_optAutostart))
m_coreController->pageController()->showOnStartup();
else
emit m_coreController->pageController()->raiseMainWindow();
@@ -118,6 +176,18 @@ void AmneziaApplication::init()
}
});
#endif
if (m_parser.isSet(m_optConnect)) {
bool ok = false;
int idx = m_parser.value(m_optConnect).toInt(&ok);
if (ok) {
QTimer::singleShot(0, this, [this, idx]() {
if (m_coreController) {
m_coreController->openConnectionByIndex(idx);
}
});
}
}
}
void AmneziaApplication::registerTypes()
@@ -162,15 +232,14 @@ bool AmneziaApplication::parseCommands()
m_parser.addHelpOption();
m_parser.addVersionOption();
QCommandLineOption c_autostart { { "a", "autostart" }, "System autostart" };
m_parser.addOption(c_autostart);
QCommandLineOption c_cleanup { { "c", "cleanup" }, "Cleanup logs" };
m_parser.addOption(c_cleanup);
m_parser.addOption(m_optAutostart);
m_parser.addOption(m_optCleanup);
m_parser.addOption(m_optConnect);
m_parser.addOption(m_optImport);
m_parser.process(*this);
if (m_parser.isSet(c_cleanup)) {
if (m_parser.isSet(m_optCleanup)) {
Logger::cleanUp();
QTimer::singleShot(100, this, [this] { quit(); });
exec();
@@ -179,9 +248,8 @@ bool AmneziaApplication::parseCommands()
return true;
}
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
void AmneziaApplication::startLocalServer()
{
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
void AmneziaApplication::startLocalServer() {
const QString serverName("AmneziaVPNInstance");
QLocalServer::removeServer(serverName);
@@ -198,6 +266,32 @@ void AmneziaApplication::startLocalServer()
}
#endif
bool AmneziaApplication::eventFilter(QObject *watched, QEvent *event)
{
if (event->type() == QEvent::Close) {
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
quit();
#else
if (m_forceQuit) {
quit();
} else {
if (m_coreController && m_coreController->pageController()) {
m_coreController->pageController()->hideMainWindow();
}
}
#endif
return true; // eat the close
}
// call base QObject::eventFilter
return QObject::eventFilter(watched, event);
}
void AmneziaApplication::forceQuit()
{
m_forceQuit = true;
quit();
}
QQmlApplicationEngine *AmneziaApplication::qmlEngine() const
{
return m_engine;
+16 -5
View File
@@ -7,9 +7,9 @@
#include <QQmlContext>
#include <QThread>
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#include <QGuiApplication>
#include <QGuiApplication>
#else
#include <QApplication>
#include <QApplication>
#endif
#include <QClipboard>
@@ -20,9 +20,9 @@
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#define AMNEZIA_BASE_CLASS QGuiApplication
#define AMNEZIA_BASE_CLASS QGuiApplication
#else
#define AMNEZIA_BASE_CLASS QApplication
#define AMNEZIA_BASE_CLASS QApplication
#endif
class AmneziaApplication : public AMNEZIA_BASE_CLASS
@@ -37,7 +37,7 @@ public:
void loadFonts();
bool parseCommands();
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
void startLocalServer();
#endif
@@ -45,7 +45,11 @@ public:
QNetworkAccessManager *networkManager();
QClipboard *getClipboard();
public slots:
void forceQuit();
private:
static bool m_forceQuit;
QQmlApplicationEngine *m_engine {};
std::shared_ptr<Settings> m_settings;
@@ -56,10 +60,17 @@ private:
QCommandLineParser m_parser;
QCommandLineOption m_optAutostart;
QCommandLineOption m_optCleanup;
QCommandLineOption m_optConnect;
QCommandLineOption m_optImport;
QSharedPointer<VpnConnection> m_vpnConnection;
QThread m_vpnConnectionThread;
QNetworkAccessManager *m_nam;
protected:
bool eventFilter(QObject *watched, QEvent *event) override;
};
#endif // AMNEZIA_APPLICATION_H
+3 -2
View File
@@ -45,7 +45,8 @@
android:configChanges="uiMode|screenSize|smallestScreenSize|screenLayout|orientation|density
|fontScale|layoutDirection|locale|keyboard|keyboardHidden|navigation|mcc|mnc"
android:launchMode="singleInstance"
android:windowSoftInputMode="stateUnchanged|adjustResize"
android:windowSoftInputMode="adjustResize|stateUnchanged"
android:enableOnBackInvokedCallback="false"
android:exported="true">
<intent-filter>
@@ -214,4 +215,4 @@
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/qtprovider_paths" />
</provider>
</application>
</manifest>
</manifest>
@@ -93,7 +93,7 @@ open class OpenVpn : Protocol() {
openVpnClient = null
}
override fun reconnectVpn(vpnBuilder: Builder) {
override fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean) {
openVpnClient?.let {
it.establish = makeEstablish(vpnBuilder)
it.reconnect(0)
@@ -42,7 +42,7 @@ abstract class Protocol {
abstract fun stopVpn()
abstract fun reconnectVpn(vpnBuilder: Builder)
abstract fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean)
protected fun ProtocolConfig.Builder.configSplitTunneling(config: JSONObject) {
if (!allowSplitTunneling) {
+3
View File
@@ -6,6 +6,9 @@
<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>
</style>
<style name="Translucent" parent="NoActionBar">
<item name="android:windowBackground">@android:color/transparent</item>
@@ -35,6 +35,11 @@ import android.widget.Toast
import androidx.annotation.MainThread
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import androidx.core.graphics.Insets
import androidx.core.view.OnApplyWindowInsetsListener
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import java.io.IOException
import kotlin.LazyThreadSafetyMode.NONE
import kotlin.coroutines.CoroutineContext
@@ -170,10 +175,9 @@ class AmneziaActivity : QtActivity() {
super.onCreate(savedInstanceState)
Log.d(TAG, "Create Amnezia activity")
loadLibs()
window.apply {
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
statusBarColor = getColor(R.color.black)
}
// Configure window for edge-to-edge display
configureWindowForEdgeToEdge()
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
val proto = mainScope.async(Dispatchers.IO) {
VpnStateStore.getVpnState().vpnProto
@@ -265,6 +269,98 @@ class AmneziaActivity : QtActivity() {
super.onStop()
}
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
Log.d(TAG, "Window focus changed: hasFocus=$hasFocus")
}
override fun onPause() {
super.onPause()
Log.d(TAG, "Pause Amnezia activity")
}
override fun onResume() {
super.onResume()
/* if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
window.decorView.apply {
invalidate()
postDelayed({
sendTouch(1f, 1f)
}, 100)
postDelayed({
sendTouch(2f, 2f)
}, 200)
postDelayed({
requestLayout()
invalidate()
}, 250)
}
} */
Log.d(TAG, "Resume Amnezia activity")
}
private fun configureWindowForEdgeToEdge() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
window.apply {
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
addFlags(LayoutParams.FLAG_LAYOUT_NO_LIMITS)
statusBarColor = android.graphics.Color.TRANSPARENT
navigationBarColor = android.graphics.Color.TRANSPARENT
}
WindowInsetsControllerCompat(window, window.decorView).apply {
isAppearanceLightStatusBars = false
isAppearanceLightNavigationBars = false
}
// Workaround for Android 14 (API 34+) IME adjustResize bug
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
setupImeInsetsListener()
}
} else {
window.apply {
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
statusBarColor = getColor(R.color.black)
}
WindowInsetsControllerCompat(window, window.decorView).apply {
isAppearanceLightStatusBars = false
isAppearanceLightNavigationBars = false
}
}
}
private fun setupImeInsetsListener() {
ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { view, windowInsets ->
val imeInsets = windowInsets.getInsets(WindowInsetsCompat.Type.ime())
val imeVisible = windowInsets.isVisible(WindowInsetsCompat.Type.ime())
val imeHeight = if (imeVisible) imeInsets.bottom else 0
val density = resources.displayMetrics.density
val imeHeightDp = (imeHeight / density).toInt()
// Also track system bars (navigation bar, status bar) changes
val systemBarsInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val navBarHeight = systemBarsInsets.bottom
val navBarHeightDp = (navBarHeight / density).toInt()
val statusBarHeight = systemBarsInsets.top
val statusBarHeightDp = (statusBarHeight / density).toInt()
mainScope.launch {
qtInitialized.await()
QtAndroidController.onImeInsetsChanged(imeHeightDp)
QtAndroidController.onSystemBarsInsetsChanged(navBarHeightDp, statusBarHeightDp)
}
// Return windowInsets instead of CONSUMED to allow proper handling
windowInsets
}
}
override fun onDestroy() {
Log.d(TAG, "Destroy Amnezia activity")
unregisterBroadcastReceiver(notificationStateReceiver)
@@ -666,6 +762,43 @@ class AmneziaActivity : QtActivity() {
@Suppress("unused")
fun isOnTv(): Boolean = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
@Suppress("unused")
fun isEdgeToEdgeEnabled(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
@Suppress("unused")
fun getStatusBarHeight(): Int {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) return 0
val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android")
val heightPx = if (resourceId > 0) {
resources.getDimensionPixelSize(resourceId)
} else {
0
}
// Convert physical pixels to device-independent pixels for QML
val density = resources.displayMetrics.density
val heightDp = (heightPx / density).toInt()
return heightDp
}
@Suppress("unused")
fun getNavigationBarHeight(): Int {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) return 0
val resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android")
val heightPx = if (resourceId > 0) {
resources.getDimensionPixelSize(resourceId)
} else {
0
}
// Convert physical pixels to device-independent pixels for QML
val density = resources.displayMetrics.density
val heightDp = (heightPx / density).toInt()
return heightDp
}
@Suppress("unused")
fun startQrCodeReader() {
Log.v(TAG, "Start camera")
@@ -565,7 +565,7 @@ open class AmneziaVpnService : VpnService() {
protocolState.value = RECONNECTING
connectionJob = connectionScope.launch {
vpnProto?.protocol?.reconnectVpn(Builder())
vpnProto?.protocol?.reconnectVpn(Builder(), ::protect)
}
}
@@ -38,15 +38,15 @@ object AppListProvider {
}
}
private class App(pi: PackageInfo, pm: PackageManager, ai: ApplicationInfo = pi.applicationInfo) : Comparable<App> {
private class App(pi: PackageInfo, pm: PackageManager, ai: ApplicationInfo? = pi.applicationInfo) : Comparable<App> {
val name: String?
val packageName: String = pi.packageName
val icon: Boolean = ai.icon != 0
val icon: Boolean = (ai?.icon ?: 0) != 0
val isLaunchable: Boolean = pm.getLaunchIntentForPackage(packageName) != null
init {
val name = ai.loadLabel(pm).toString()
this.name = if (name != packageName) name else null
val name = ai?.loadLabel(pm)?.toString()
this.name = name?.takeIf { it != packageName }
}
override fun compareTo(other: App): Int {
@@ -28,4 +28,7 @@ object QtAndroidController {
external fun onAuthResult(result: Boolean)
external fun decodeQrCode(data: String): Boolean
external fun onImeInsetsChanged(heightDp: Int)
external fun onSystemBarsInsetsChanged(navBarHeightDp: Int, statusBarHeightDp: Int)
}
@@ -12,6 +12,7 @@ import org.amnezia.vpn.protocol.Protocol
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
import org.amnezia.vpn.protocol.Statistics
import org.amnezia.vpn.protocol.VpnException
import org.amnezia.vpn.protocol.VpnStartException
import org.amnezia.vpn.util.LibraryLoader.loadSharedLibrary
import org.amnezia.vpn.util.Log
@@ -27,6 +28,7 @@ private const val TAG = "Wireguard"
open class Wireguard : Protocol() {
private var tunnelHandle: Int = -1
private var config: WireguardConfig? = null // save config for reconnect
protected open val ifName: String = "amn0"
private lateinit var scope: CoroutineScope
private var statusJob: Job? = null
@@ -61,6 +63,7 @@ open class Wireguard : Protocol() {
override suspend fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) {
val wireguardConfig = parseConfig(config)
start(wireguardConfig, vpnBuilder, protect)
this.config = wireguardConfig
}
protected open fun parseConfig(config: JSONObject): WireguardConfig {
@@ -122,23 +125,24 @@ open class Wireguard : Protocol() {
configData.optStringOrNull("S2")?.let { setS2(it.toInt()) }
configData.optStringOrNull("S3")?.let { setS3(it.toInt()) }
configData.optStringOrNull("S4")?.let { setS4(it.toInt()) }
configData.optStringOrNull("H1")?.let { setH1(it.toLong()) }
configData.optStringOrNull("H2")?.let { setH2(it.toLong()) }
configData.optStringOrNull("H3")?.let { setH3(it.toLong()) }
configData.optStringOrNull("H4")?.let { setH4(it.toLong()) }
configData.optStringOrNull("H1")?.trim()?.let { if (it.isNotEmpty()) setH1(it) }
configData.optStringOrNull("H2")?.trim()?.let { if (it.isNotEmpty()) setH2(it) }
configData.optStringOrNull("H3")?.trim()?.let { if (it.isNotEmpty()) setH3(it) }
configData.optStringOrNull("H4")?.trim()?.let { if (it.isNotEmpty()) setH4(it) }
configData.optStringOrNull("I1")?.let { setI1(it) }
configData.optStringOrNull("I2")?.let { setI2(it) }
configData.optStringOrNull("I3")?.let { setI3(it) }
configData.optStringOrNull("I4")?.let { setI4(it) }
configData.optStringOrNull("I5")?.let { setI5(it) }
configData.optStringOrNull("J1")?.let { setJ1(it) }
configData.optStringOrNull("J2")?.let { setJ2(it) }
configData.optStringOrNull("J3")?.let { setJ3(it) }
configData.optStringOrNull("Itime")?.let { setItime(it.toInt()) }
}
private fun start(config: WireguardConfig, vpnBuilder: Builder, protect: (Int) -> Boolean) {
if (tunnelHandle != -1) {
private fun start(
config: WireguardConfig,
vpnBuilder: Builder,
protect: (Int) -> Boolean,
stopExistingVpn: Boolean = false
) {
if (!stopExistingVpn && tunnelHandle != -1) {
Log.w(TAG, "Tunnel already up")
return
}
@@ -146,6 +150,9 @@ open class Wireguard : Protocol() {
buildVpnInterface(config, vpnBuilder)
vpnBuilder.establish().use { tunFd ->
if (stopExistingVpn && tunnelHandle != -1) {
turnOffVpn()
}
if (tunFd == null) {
throw VpnStartException("Create VPN interface: permission not granted or revoked")
}
@@ -202,20 +209,25 @@ open class Wireguard : Protocol() {
return lastHandshake
}
override fun stopVpn() {
if (tunnelHandle == -1) {
Log.w(TAG, "Tunnel already down")
return
}
private fun turnOffVpn() {
statusJob?.cancel()
statusJob = null
val handleToClose = tunnelHandle
tunnelHandle = -1
GoBackend.awgTurnOff(handleToClose)
}
override fun stopVpn() {
if (tunnelHandle == -1) {
Log.w(TAG, "Tunnel already down")
return
}
turnOffVpn()
state.value = DISCONNECTED
}
override fun reconnectVpn(vpnBuilder: Builder) {
state.value = CONNECTED
override fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean) {
val config = this.config ?: throw VpnException("Reconnect config is empty")
start(config, vpnBuilder, protect, true)
}
}
@@ -22,19 +22,15 @@ open class WireguardConfig protected constructor(
val s2: Int?,
val s3: Int?,
val s4: Int?,
val h1: Long?,
val h2: Long?,
val h3: Long?,
val h4: Long?,
val h1: String?,
val h2: String?,
val h3: String?,
val h4: String?,
var i1: String?,
var i2: String?,
var i3: String?,
var i4: String?,
var i5: String?,
var j1: String?,
var j2: String?,
var j3: String?,
var itime: Int?
) : ProtocolConfig(protocolConfigBuilder) {
protected constructor(builder: Builder) : this(
@@ -61,10 +57,6 @@ open class WireguardConfig protected constructor(
builder.i3,
builder.i4,
builder.i5,
builder.j1,
builder.j2,
builder.j3,
builder.itime
)
fun toWgUserspaceString(): String = with(StringBuilder()) {
@@ -94,10 +86,6 @@ open class WireguardConfig protected constructor(
i3?.let { appendLine("i3=$it") }
i4?.let { appendLine("i4=$it") }
i5?.let { appendLine("i5=$it") }
j1?.let { appendLine("j1=$it") }
j2?.let { appendLine("j2=$it") }
j3?.let { appendLine("j3=$it") }
itime?.let { appendLine("itime=$it") }
}
}
@@ -152,19 +140,15 @@ open class WireguardConfig protected constructor(
internal var s2: Int? = null
internal var s3: Int? = null
internal var s4: Int? = null
internal var h1: Long? = null
internal var h2: Long? = null
internal var h3: Long? = null
internal var h4: Long? = null
internal var h1: String? = null
internal var h2: String? = null
internal var h3: String? = null
internal var h4: String? = null
internal var i1: String? = null
internal var i2: String? = null
internal var i3: String? = null
internal var i4: String? = null
internal var i5: String? = null
internal var j1: String? = null
internal var j2: String? = null
internal var j3: String? = null
internal var itime: Int? = null
fun setEndpoint(endpoint: InetEndpoint) = apply { this.endpoint = endpoint }
@@ -185,19 +169,15 @@ open class WireguardConfig protected constructor(
fun setS2(s2: Int) = apply { this.s2 = s2 }
fun setS3(s3: Int) = apply { this.s3 = s3 }
fun setS4(s4: Int) = apply { this.s4 = s4 }
fun setH1(h1: Long) = apply { this.h1 = h1 }
fun setH2(h2: Long) = apply { this.h2 = h2 }
fun setH3(h3: Long) = apply { this.h3 = h3 }
fun setH4(h4: Long) = apply { this.h4 = h4 }
fun setH1(h1: String) = apply { this.h1 = h1 }
fun setH2(h2: String) = apply { this.h2 = h2 }
fun setH3(h3: String) = apply { this.h3 = h3 }
fun setH4(h4: String) = apply { this.h4 = h4 }
fun setI1(i1: String) = apply { this.i1 = i1 }
fun setI2(i2: String) = apply { this.i2 = i2 }
fun setI3(i3: String) = apply { this.i3 = i3 }
fun setI4(i4: String) = apply { this.i4 = i4 }
fun setI5(i5: String) = apply { this.i5 = i5 }
fun setJ1(j1: String) = apply { this.j1 = j1 }
fun setJ2(j2: String) = apply { this.j2 = j2 }
fun setJ3(j3: String) = apply { this.j3 = j3 }
fun setItime(itime: Int) = apply { this.itime = itime }
override fun build(): WireguardConfig = configBuild().run { WireguardConfig(this@Builder) }
}
+2 -2
View File
@@ -157,7 +157,7 @@ class Xray : Protocol() {
state.value = DISCONNECTED
}
override fun reconnectVpn(vpnBuilder: Builder) {
override fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean) {
state.value = CONNECTED
}
@@ -166,7 +166,7 @@ class Xray : Protocol() {
mtu = config.mtu.toLong()
proxy = "socks5://127.0.0.1:${config.socksPort}"
device = "fd://$fd"
logLevel = "warning"
logLevel = "warn"
}
LibXray.startTun2Socks(tun2SocksConfig, fd.toLong()).isNotNullOrBlank { err ->
throw VpnStartException("Failed to start tun2socks: $err")
+11 -5
View File
@@ -27,12 +27,18 @@ if(WIN32)
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/windows/win32/libcrypto.lib")
endif()
elseif(APPLE AND NOT IOS)
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libssh.a")
set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libz.a")
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/macos/x86_64")
if(MACOS_NE)
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/universal2/libssh.a")
set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/universal2/libz.a")
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/macos/universal2")
else()
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libssh.a")
set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libz.a")
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/macos/x86_64")
endif()
set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/macos/include")
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libssl.a")
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libcrypto.a")
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libcrypto.a")
elseif(IOS)
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/ios/arm64")
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/ios/arm64/libssh.a")
@@ -56,7 +62,7 @@ elseif(LINUX)
set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/linux/x86_64/libssl.a")
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/linux/x86_64/libcrypto.a")
endif()
file(COPY ${OPENSSL_LIB_SSL_PATH} ${OPENSSL_LIB_CRYPTO_PATH}
DESTINATION ${OPENSSL_LIBRARIES_DIR})
+8 -4
View File
@@ -1,6 +1,6 @@
message("Client android ${CMAKE_ANDROID_ARCH_ABI} build")
set(APP_ANDROID_MIN_SDK 26)
set(APP_ANDROID_MIN_SDK 28)
set(ANDROID_PLATFORM "android-${APP_ANDROID_MIN_SDK}" CACHE STRING
"The minimum API level supported by the application or library" FORCE)
@@ -11,8 +11,8 @@ set_target_properties(${PROJECT} PROPERTIES
QT_ANDROID_VERSION_NAME ${CMAKE_PROJECT_VERSION}
QT_ANDROID_VERSION_CODE ${APP_ANDROID_VERSION_CODE}
QT_ANDROID_MIN_SDK_VERSION ${APP_ANDROID_MIN_SDK}
QT_ANDROID_TARGET_SDK_VERSION 34
QT_ANDROID_SDK_BUILD_TOOLS_REVISION 34.0.0
QT_ANDROID_TARGET_SDK_VERSION 36
QT_ANDROID_SDK_BUILD_TOOLS_REVISION 36.0.0
QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android
)
@@ -20,7 +20,11 @@ set(QT_ANDROID_MULTI_ABI_FORWARD_VARS "QT_NO_GLOBAL_APK_TARGET_PART_OF_ALL;CMAKE
# We need to include qtprivate api's
# As QAndroidBinder is not yet implemented with a public api
set(LIBS ${LIBS} Qt6::CorePrivate -ljnigraphics)
# Check if Qt6::CorePrivate is available (may not be in all Qt versions/configurations)
if(TARGET Qt6::CorePrivate)
set(LIBS ${LIBS} Qt6::CorePrivate)
endif()
set(LIBS ${LIBS} -ljnigraphics)
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/platforms/android)
+3
View File
@@ -34,6 +34,7 @@ set(HEADERS ${HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate-C-Interface.h
)
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h PROPERTIES OBJECTIVE_CPP_HEADER TRUE)
@@ -46,6 +47,8 @@ set(SOURCES ${SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/AmneziaSceneDelegateHooks.mm
)
+3 -2
View File
@@ -14,7 +14,7 @@ set(LIBS ${LIBS}
${FW_SECURITY}
${FW_COREWLAN}
${FW_NETWORK}
${FW_USERNOTIFICATIONS}
${FW_USER_NOTIFICATIONS}
${FW_NETWORK_EXTENSION}
)
@@ -35,6 +35,8 @@ set(SOURCES ${SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/ui/macos_util.mm
)
set(ICON_FILE ${CMAKE_CURRENT_SOURCE_DIR}/images/app.icns)
set(MACOSX_BUNDLE_ICON_FILE app.icns)
set_source_files_properties(${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
@@ -53,4 +55,3 @@ execute_process(
)
message("OSX_SDK_PATH is: ${OSX_SDK_PATH}")
+170
View File
@@ -0,0 +1,170 @@
message("Client ==> MacOS NE build")
set_target_properties(${PROJECT} PROPERTIES MACOSX_BUNDLE TRUE)
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15)
set(APPLE_PROJECT_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
enable_language(OBJC)
enable_language(Swift)
find_package(Qt6 REQUIRED COMPONENTS ShaderTools Widgets)
# Link Qt Widgets for QWidget, QMenu, QAction etc.
set(LIBS ${LIBS} Qt6::ShaderTools Qt6::Widgets)
find_library(FW_AUTHENTICATIONSERVICES AuthenticationServices)
find_library(FW_AVFOUNDATION AVFoundation)
find_library(FW_FOUNDATION Foundation)
find_library(FW_STOREKIT StoreKit)
find_library(FW_SERVICEMGMT ServiceManagement)
find_library(FW_USERNOTIFICATIONS UserNotifications)
find_library(FW_NETWORKEXTENSION NetworkExtension)
set(LIBS ${LIBS}
${FW_AUTHENTICATIONSERVICES}
${FW_AVFOUNDATION}
${FW_FOUNDATION}
${FW_STOREKIT}
${FW_SERVICEMGMT}
${FW_USERNOTIFICATIONS}
${FW_NETWORKEXTENSION}
)
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate-C-Interface.h
)
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h PROPERTIES OBJECTIVE_CPP_HEADER TRUE)
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
)
set(ICON_FILE ${CMAKE_CURRENT_SOURCE_DIR}/images/app.icns)
set(MACOSX_BUNDLE_ICON_FILE app.icns)
set_source_files_properties(${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
set(SOURCES ${SOURCES} ${ICON_FILE})
target_include_directories(${PROJECT} PRIVATE
${Qt6Gui_PRIVATE_INCLUDE_DIRS}
${Qt6Widgets_PRIVATE_INCLUDE_DIRS}
)
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 "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 "${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 "AmneziaVPN"
XCODE_ATTRIBUTE_BUNDLE_INFO_STRING "AmneziaVPN"
XCODE_GENERATE_SCHEME TRUE
XCODE_ATTRIBUTE_ENABLE_BITCODE "NO"
XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME "AppIcon"
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2"
XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY "NO"
XCODE_EMBED_FRAMEWORKS_REMOVE_HEADERS_ON_COPY "YES"
XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET "11.0"
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../Frameworks"
XCODE_EMBED_APP_EXTENSIONS AmneziaVPNNetworkExtension
)
if(DEPLOY)
set_target_properties(${PROJECT} 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 "distr macos.org.amnezia.AmneziaVPN"
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "dev macos.org.amnezia.AmneziaVPN"
)
else()
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
)
endif()
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 "AmneziaVPN-Swift.h"
XCODE_ATTRIBUTE_SWIFT_OBJC_INTEROP_MODE "objcxx"
)
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "X7UJ388FXK"
)
target_include_directories(${PROJECT} PRIVATE ${CMAKE_CURRENT_LIST_DIR})
target_compile_options(${PROJECT} PRIVATE
-DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\"
-DVPN_NE_BUNDLEID=\"${BUILD_IOS_APP_IDENTIFIER}.network-extension\"
)
set(WG_APPLE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rd/amneziawg-apple/Sources)
target_sources(${PROJECT} PRIVATE
${WG_APPLE_SOURCE_DIR}/WireGuardKitC/x25519.c
${CLIENT_ROOT_DIR}/platforms/ios/LogController.swift
${CLIENT_ROOT_DIR}/platforms/ios/Log.swift
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
${CLIENT_ROOT_DIR}/platforms/ios/ScreenProtection.swift
${CLIENT_ROOT_DIR}/platforms/ios/VPNCController.swift
)
target_sources(${PROJECT} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Images.xcassets
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
)
set_property(TARGET ${PROJECT} APPEND PROPERTY RESOURCE
${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Images.xcassets
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
)
add_subdirectory(macos/networkextension)
add_dependencies(${PROJECT} AmneziaVPNNetworkExtension)
get_target_property(QtCore_location Qt6::Core LOCATION)
message("QtCore_location")
message(${QtCore_location})
get_filename_component(QT_BIN_DIR_DETECTED "${QtCore_location}/../../../../../bin" ABSOLUTE)
set_property(TARGET ${PROJECT} PROPERTY XCODE_EMBED_FRAMEWORKS
"${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-macos/OpenVPNAdapter.framework"
)
set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-macos)
target_link_libraries("AmneziaVPNNetworkExtension" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-macos/OpenVPNAdapter.framework")
add_custom_command(TARGET ${PROJECT} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory
$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks
COMMAND /usr/bin/find "$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework" -name "*.sha256" -delete
COMMAND /usr/bin/codesign --force --sign "Apple Distribution"
"$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework/Versions/Current/OpenVPNAdapter"
COMMAND ${QT_BIN_DIR_DETECTED}/macdeployqt $<TARGET_BUNDLE_DIR:AmneziaVPN> -appstore-compliant -qmldir=${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Signing OpenVPNAdapter framework"
)
+32 -5
View File
@@ -28,6 +28,7 @@ set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/../common/logger/logger.h
${CLIENT_ROOT_DIR}/utils/qmlUtils.h
${CLIENT_ROOT_DIR}/core/api/apiUtils.h
${CLIENT_ROOT_DIR}/core/osSignalHandler.h
)
# Mozilla headres
@@ -36,10 +37,9 @@ set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/mozilla/shared/ipaddress.h
${CLIENT_ROOT_DIR}/mozilla/shared/leakdetector.h
${CLIENT_ROOT_DIR}/mozilla/controllerimpl.h
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.h
)
if(NOT IOS)
if(NOT IOS AND NOT MACOS_NE)
set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/platforms/ios/QRCodeReaderBase.h
)
@@ -79,6 +79,7 @@ set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/../common/logger/logger.cpp
${CLIENT_ROOT_DIR}/utils/qmlUtils.cpp
${CLIENT_ROOT_DIR}/core/api/apiUtils.cpp
${CLIENT_ROOT_DIR}/core/osSignalHandler.cpp
)
# Mozilla sources
@@ -86,15 +87,28 @@ set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/mozilla/models/server.cpp
${CLIENT_ROOT_DIR}/mozilla/shared/ipaddress.cpp
${CLIENT_ROOT_DIR}/mozilla/shared/leakdetector.cpp
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.cpp
)
if(NOT IOS)
if(NOT IOS AND NOT MACOS_NE)
set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/platforms/ios/QRCodeReaderBase.cpp
)
endif()
# Include native macOS platform helpers (dock/status-item)
if(APPLE AND NOT IOS)
list(APPEND HEADERS
${CLIENT_ROOT_DIR}/platforms/macos/macosutils.h
${CLIENT_ROOT_DIR}/platforms/macos/macosstatusicon.h
${CLIENT_ROOT_DIR}/ui/macos_util.h
)
list(APPEND SOURCES
${CLIENT_ROOT_DIR}/platforms/macos/macosutils.mm
${CLIENT_ROOT_DIR}/platforms/macos/macosstatusicon.mm
${CLIENT_ROOT_DIR}/ui/macos_util.mm
)
endif()
if(NOT ANDROID)
set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/ui/notificationhandler.cpp
@@ -166,7 +180,7 @@ if(WIN32)
)
endif()
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
message("Client desktop build")
add_compile_definitions(AMNEZIA_DESKTOP)
@@ -180,11 +194,13 @@ if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
${CLIENT_ROOT_DIR}/protocols/wireguardprotocol.h
${CLIENT_ROOT_DIR}/protocols/xrayprotocol.h
${CLIENT_ROOT_DIR}/protocols/awgprotocol.h
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.h
)
set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/core/ipcclient.cpp
${CLIENT_ROOT_DIR}/core/privileged_process.cpp
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.cpp
${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.cpp
${CLIENT_ROOT_DIR}/protocols/openvpnprotocol.cpp
${CLIENT_ROOT_DIR}/protocols/openvpnovercloakprotocol.cpp
@@ -194,3 +210,14 @@ if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
${CLIENT_ROOT_DIR}/protocols/awgprotocol.cpp
)
endif()
if(APPLE AND MACOS_NE)
# Include only the tray notification handler in NE builds
set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.h
)
set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.cpp
)
endif()
+9 -11
View File
@@ -41,18 +41,16 @@ QString AwgConfigurator::createConfig(const ServerCredentials &credentials, Dock
jsonConfig[config_key::underloadPacketMagicHeader] = configMap.value(config_key::underloadPacketMagicHeader);
jsonConfig[config_key::transportPacketMagicHeader] = configMap.value(config_key::transportPacketMagicHeader);
// jsonConfig[config_key::cookieReplyPacketJunkSize] = configMap.value(config_key::cookieReplyPacketJunkSize);
// jsonConfig[config_key::transportPacketJunkSize] = configMap.value(config_key::transportPacketJunkSize);
if (container == DockerContainer::Awg2) {
jsonConfig[config_key::cookieReplyPacketJunkSize] = configMap.value(config_key::cookieReplyPacketJunkSize);
jsonConfig[config_key::transportPacketJunkSize] = configMap.value(config_key::transportPacketJunkSize);
}
// jsonConfig[config_key::specialJunk1] = configMap.value(amnezia::config_key::specialJunk1);
// jsonConfig[config_key::specialJunk2] = configMap.value(amnezia::config_key::specialJunk2);
// jsonConfig[config_key::specialJunk3] = configMap.value(amnezia::config_key::specialJunk3);
// jsonConfig[config_key::specialJunk4] = configMap.value(amnezia::config_key::specialJunk4);
// jsonConfig[config_key::specialJunk5] = configMap.value(amnezia::config_key::specialJunk5);
// jsonConfig[config_key::controlledJunk1] = configMap.value(amnezia::config_key::controlledJunk1);
// jsonConfig[config_key::controlledJunk2] = configMap.value(amnezia::config_key::controlledJunk2);
// jsonConfig[config_key::controlledJunk3] = configMap.value(amnezia::config_key::controlledJunk3);
// jsonConfig[config_key::specialHandshakeTimeout] = configMap.value(amnezia::config_key::specialHandshakeTimeout);
jsonConfig[config_key::specialJunk1] = configMap.value(amnezia::config_key::specialJunk1);
jsonConfig[config_key::specialJunk2] = configMap.value(amnezia::config_key::specialJunk2);
jsonConfig[config_key::specialJunk3] = configMap.value(amnezia::config_key::specialJunk3);
jsonConfig[config_key::specialJunk4] = configMap.value(amnezia::config_key::specialJunk4);
jsonConfig[config_key::specialJunk5] = configMap.value(amnezia::config_key::specialJunk5);
jsonConfig[config_key::mtu] =
containerConfig.value(ProtocolProps::protoToString(Proto::Awg)).toObject().value(config_key::mtu).toString(protocols::awg::defaultMtu);
+21 -3
View File
@@ -83,12 +83,30 @@ QString OpenVpnConfigurator::createConfig(const ServerCredentials &credentials,
return "";
}
auto sanitizeStaticKey = [](const QString &key) {
QStringList lines = key.split('\n');
QStringList filtered;
filtered.reserve(lines.size());
for (const QString &line : lines) {
const QString trimmed = line.trimmed();
if (trimmed.startsWith('#')) {
continue;
}
filtered.append(line);
}
QString result = filtered.join('\n');
if (!result.endsWith('\n')) {
result.append('\n');
}
return result;
};
config.replace("$OPENVPN_CA_CERT", connData.caCert);
config.replace("$OPENVPN_CLIENT_CERT", connData.clientCert);
config.replace("$OPENVPN_PRIV_KEY", connData.privKey);
if (config.contains("$OPENVPN_TA_KEY")) {
config.replace("$OPENVPN_TA_KEY", connData.taKey);
config.replace("$OPENVPN_TA_KEY", sanitizeStaticKey(connData.taKey));
} else {
config.replace("<tls-auth>", "");
config.replace("</tls-auth>", "");
@@ -117,7 +135,7 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPair<QString,
if (!isApiConfig) {
QRegularExpression regex("redirect-gateway.*");
config.replace(regex, "");
// We don't use secondary DNS if primary DNS is AmneziaDNS
if (dns.first.contains(protocols::dns::amneziaDnsIp)) {
QRegularExpression dnsRegex("dhcp-option DNS " + dns.second);
@@ -131,7 +149,7 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPair<QString,
// no redirect-gateway
} else if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n");
// Prevent ipv6 leak
#endif
+5 -4
View File
@@ -8,7 +8,7 @@
#include <QTemporaryFile>
#include <QThread>
#include <qtimer.h>
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(MACOS_NE)
#include <QGuiApplication>
#else
#include <QApplication>
@@ -24,7 +24,7 @@ SshConfigurator::SshConfigurator(std::shared_ptr<Settings> settings, const QShar
QString SshConfigurator::convertOpenSShKey(const QString &key)
{
#ifndef Q_OS_IOS
#if !defined(Q_OS_IOS) && !defined(MACOS_NE)
QProcess p;
p.setProcessChannelMode(QProcess::MergedChannels);
@@ -67,9 +67,10 @@ QString SshConfigurator::convertOpenSShKey(const QString &key)
#endif
}
// DEAD CODE.
void SshConfigurator::openSshTerminal(const ServerCredentials &credentials)
{
#ifndef Q_OS_IOS
#if !defined(Q_OS_IOS) && !defined(MACOS_NE)
QProcess *p = new QProcess();
p->setProcessChannelMode(QProcess::SeparateChannels);
@@ -101,7 +102,7 @@ QProcessEnvironment SshConfigurator::prepareEnv()
pathEnvVar.clear();
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\cygwin;");
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\openvpn;");
#elif defined(Q_OS_MACX)
#elif defined(Q_OS_MACX) && !defined(MACOS_NE)
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "/Contents/MacOS");
#endif
@@ -103,7 +103,11 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
return connData;
}
QString getIpsScript = QString("cat %1 | grep AllowedIPs").arg(m_serverConfigPath);
QString configPath = m_serverConfigPath;
if (container == DockerContainer::Awg) {
configPath = amnezia::protocols::awg::serverLegacyConfigPath;
}
QString getIpsScript = QString("cat %1 | grep AllowedIPs").arg(configPath);
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
@@ -161,15 +165,18 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
"AllowedIPs = %3/32\n\n")
.arg(connData.clientPubKey, connData.pskKey, connData.clientIP);
errorCode = m_serverController->uploadTextFileToContainer(container, credentials, configPart, m_serverConfigPath,
errorCode = m_serverController->uploadTextFileToContainer(container, credentials, configPart, configPath,
libssh::ScpOverwriteMode::ScpAppendToExisting);
if (errorCode != ErrorCode::NoError) {
return connData;
}
QString script = QString("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip %1)'")
.arg(m_serverConfigPath);
bool isAwg = (container == DockerContainer::Awg2);
QString bin = isAwg ? QStringLiteral("awg") : QStringLiteral("wg");
QString iface = isAwg ? QStringLiteral("awg0") : QStringLiteral("wg0");
QString script = QString(
"sudo docker exec -i $CONTAINER_NAME bash -c '%1 syncconf %2 <(%1-quick strip %3)'").arg(bin, iface, configPath);
errorCode = m_serverController->runScript(
credentials,
+56 -9
View File
@@ -28,7 +28,10 @@ QString ContainerProps::containerToString(amnezia::DockerContainer c)
return "none";
if (c == DockerContainer::Cloak)
return "amnezia-openvpn-cloak";
if (c == DockerContainer::Awg)
return "amnezia-awg";
if (c == DockerContainer::Awg2)
return "amnezia-awg2";
QMetaEnum metaEnum = QMetaEnum::fromType<DockerContainer>();
QString containerKey = metaEnum.valueToKey(static_cast<int>(c));
@@ -41,7 +44,10 @@ QString ContainerProps::containerTypeToString(amnezia::DockerContainer c)
return "none";
if (c == DockerContainer::Ipsec)
return "ikev2";
if (c == DockerContainer::Awg)
return "awg";
if (c == DockerContainer::Awg2)
return "awg";
QMetaEnum metaEnum = QMetaEnum::fromType<DockerContainer>();
QString containerKey = metaEnum.valueToKey(static_cast<int>(c));
@@ -71,6 +77,8 @@ QVector<amnezia::Proto> ContainerProps::protocolsForContainer(amnezia::DockerCon
case DockerContainer::Socks5Proxy: return { Proto::Socks5Proxy };
case DockerContainer::Awg: return { Proto::Awg };
case DockerContainer::Awg2: return { Proto::Awg };
default: return { defaultProtocol(container) };
}
}
@@ -94,6 +102,7 @@ QMap<DockerContainer, QString> ContainerProps::containerHumanNames()
{ DockerContainer::Cloak, "OpenVPN over Cloak" },
{ DockerContainer::WireGuard, "WireGuard" },
{ DockerContainer::Awg, "AmneziaWG" },
{ DockerContainer::Awg2, "AmneziaWG" },
{ DockerContainer::Xray, "XRay" },
{ DockerContainer::Ipsec, QObject::tr("IPsec") },
{ DockerContainer::SSXray, "Shadowsocks"},
@@ -118,6 +127,8 @@ QMap<DockerContainer, QString> ContainerProps::containerDescriptions()
QObject::tr("WireGuard - popular VPN protocol with high performance, high speed and low power "
"consumption.") },
{ DockerContainer::Awg,
QObject::tr("AmneziaWG Legacy is a outdated version of AmneziaWG protocol. To upgrade, install AmneziaWG and recreate users.") },
{ DockerContainer::Awg2,
QObject::tr("AmneziaWG is a special protocol from Amnezia based on WireGuard. "
"It provides high connection speed and ensures stable operation even in the most challenging network conditions.") },
{ DockerContainer::Xray,
@@ -182,7 +193,7 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
"* Minimal configuration required\n"
"* Easily detected by DPI systems (susceptible to blocking)\n"
"* Operates over UDP protocol") },
{ DockerContainer::Awg,
{ DockerContainer::Awg2,
QObject::tr("AmneziaWG is a modern VPN protocol based on WireGuard, "
"combining simplified architecture with high performance across all devices. "
"It addresses WireGuard's main vulnerability (easy detection by DPI systems) through advanced obfuscation techniques, "
@@ -242,6 +253,7 @@ Proto ContainerProps::defaultProtocol(DockerContainer c)
case DockerContainer::Cloak: return Proto::Cloak;
case DockerContainer::ShadowSocks: return Proto::ShadowSocks;
case DockerContainer::WireGuard: return Proto::WireGuard;
case DockerContainer::Awg2: return Proto::Awg;
case DockerContainer::Awg: return Proto::Awg;
case DockerContainer::Xray: return Proto::Xray;
case DockerContainer::Ipsec: return Proto::Ikev2;
@@ -255,21 +267,49 @@ Proto ContainerProps::defaultProtocol(DockerContainer c)
}
}
QString ContainerProps::containerTypeToProtocolString(DockerContainer c)
{
if (c == DockerContainer::None)
return "none";
Proto p = defaultProtocol(c);
return ProtocolProps::protoToString(p);
}
bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
{
#ifdef Q_OS_WINDOWS
return true;
#elif defined(Q_OS_IOS)
// Standard iOS build (without Network Extension limitations)
switch (c) {
case DockerContainer::WireGuard: return true;
case DockerContainer::OpenVpn: return true;
case DockerContainer::Awg2: return true;
case DockerContainer::Awg: return true;
case DockerContainer::Xray: return true;
case DockerContainer::Cloak: return true;
case DockerContainer::SSXray: return true;
// case DockerContainer::ShadowSocks: return true;
default: return false;
default:
return false;
}
#elif defined(MACOS_NE)
// macOS build using Network Extension hide OpenVPN-based containers
switch (c) {
case DockerContainer::WireGuard: return true;
case DockerContainer::Awg2: return true;
case DockerContainer::Awg: return true;
case DockerContainer::Xray: return true;
case DockerContainer::SSXray: return true;
case DockerContainer::OpenVpn:
case DockerContainer::Cloak:
case DockerContainer::ShadowSocks:
return false;
default:
return false;
}
#elif defined(Q_OS_MAC)
switch (c) {
@@ -283,6 +323,7 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
case DockerContainer::WireGuard: return true;
case DockerContainer::OpenVpn: return true;
case DockerContainer::ShadowSocks: return false;
case DockerContainer::Awg2: return true;
case DockerContainer::Awg: return true;
case DockerContainer::Cloak: return true;
case DockerContainer::Xray: return true;
@@ -312,7 +353,7 @@ QStringList ContainerProps::fixedPortsForContainer(DockerContainer c)
bool ContainerProps::isEasySetupContainer(DockerContainer container)
{
switch (container) {
case DockerContainer::Awg: return true;
case DockerContainer::Awg2: return true;
default: return false;
}
}
@@ -320,7 +361,7 @@ bool ContainerProps::isEasySetupContainer(DockerContainer container)
QString ContainerProps::easySetupHeader(DockerContainer container)
{
switch (container) {
case DockerContainer::Awg: return tr("Automatic");
case DockerContainer::Awg2: return tr("Automatic");
default: return "";
}
}
@@ -328,7 +369,7 @@ QString ContainerProps::easySetupHeader(DockerContainer container)
QString ContainerProps::easySetupDescription(DockerContainer container)
{
switch (container) {
case DockerContainer::Awg: return tr("AmneziaWG protocol will be installed. "
case DockerContainer::Awg2: return tr("AmneziaWG protocol will be installed. "
"It provides high connection speed and ensures stable operation even in the most challenging network conditions.");
default: return "";
}
@@ -337,7 +378,7 @@ QString ContainerProps::easySetupDescription(DockerContainer container)
int ContainerProps::easySetupOrder(DockerContainer container)
{
switch (container) {
case DockerContainer::Awg: return 1;
case DockerContainer::Awg2: return 1;
default: return 0;
}
}
@@ -353,6 +394,12 @@ bool ContainerProps::isShareable(DockerContainer container)
}
}
bool ContainerProps::isAwgContainer(DockerContainer container)
{
return container == DockerContainer::Awg || container == DockerContainer::Awg2;
}
QJsonObject ContainerProps::getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig)
{
QString protocolConfigString = containerConfig.value(ProtocolProps::protoToString(protocol))
@@ -370,7 +417,7 @@ int ContainerProps::installPageOrder(DockerContainer container)
case DockerContainer::Cloak: return 5;
case DockerContainer::ShadowSocks: return 6;
case DockerContainer::WireGuard: return 2;
case DockerContainer::Awg: return 1;
case DockerContainer::Awg2: return 1;
case DockerContainer::Xray: return 3;
case DockerContainer::Ipsec: return 7;
case DockerContainer::SSXray: return 8;
+5
View File
@@ -17,6 +17,7 @@ namespace amnezia
enum DockerContainer {
None = 0,
Awg,
Awg2,
WireGuard,
OpenVpn,
Cloak,
@@ -45,6 +46,7 @@ namespace amnezia
Q_INVOKABLE static amnezia::DockerContainer containerFromString(const QString &container);
Q_INVOKABLE static QString containerToString(amnezia::DockerContainer container);
Q_INVOKABLE static QString containerTypeToString(amnezia::DockerContainer c);
Q_INVOKABLE static QString containerTypeToProtocolString(amnezia::DockerContainer c);
Q_INVOKABLE static QList<amnezia::DockerContainer> allContainers();
@@ -71,6 +73,9 @@ namespace amnezia
static bool isShareable(amnezia::DockerContainer container);
static bool isAwgContainer(amnezia::DockerContainer container);
static QJsonObject getProtocolConfigFromContainer(const amnezia::Proto protocol, const QJsonObject &containerConfig);
static int installPageOrder(amnezia::DockerContainer container);
+12
View File
@@ -47,12 +47,14 @@ namespace apiDefs
constexpr QLatin1String serverCountryName("server_country_name");
constexpr QLatin1String osVersion("os_version");
constexpr QLatin1String appLanguage("app_language");
constexpr QLatin1String availableCountries("available_countries");
constexpr QLatin1String activeDeviceCount("active_device_count");
constexpr QLatin1String maxDeviceCount("max_device_count");
constexpr QLatin1String subscriptionEndDate("subscription_end_date");
constexpr QLatin1String issuedConfigs("issued_configs");
constexpr QLatin1String subscriptionDescription("subscription_description");
constexpr QLatin1String supportInfo("support_info");
constexpr QLatin1String email("email");
@@ -64,6 +66,16 @@ namespace apiDefs
constexpr QLatin1String id("id");
constexpr QLatin1String orderId("order_id");
constexpr QLatin1String migrationCode("migration_code");
constexpr QLatin1String transactionId("transaction_id");
constexpr QLatin1String userCountryCode("user_country_code");
constexpr QLatin1String serviceInfo("service_info");
constexpr QLatin1String isAdVisible("is_ad_visible");
constexpr QLatin1String adHeader("ad_header");
constexpr QLatin1String adDescription("ad_description");
constexpr QLatin1String adEndpoint("ad_endpoint");
}
const int requestTimeoutMsecs = 12 * 1000; // 12 secs
+61 -13
View File
@@ -23,7 +23,7 @@ namespace
bool apiUtils::isSubscriptionExpired(const QString &subscriptionEndDate)
{
QDateTime now = QDateTime::currentDateTime();
QDateTime now = QDateTime::currentDateTimeUtc();
QDateTime endDate = QDateTime::fromString(subscriptionEndDate, Qt::ISODateWithMs);
return endDate < now;
}
@@ -82,7 +82,9 @@ apiDefs::ConfigSource apiUtils::getConfigSource(const QJsonObject &serverConfigO
return static_cast<apiDefs::ConfigSource>(serverConfigObject.value(apiDefs::key::configVersion).toInt());
}
amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &sslErrors, QNetworkReply *reply)
amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &sslErrors, const QString &replyErrorString,
const QNetworkReply::NetworkError &replyError, const int httpStatusCode,
const QByteArray &responseBody)
{
const int httpStatusCodeConflict = 409;
const int httpStatusCodeNotFound = 404;
@@ -90,21 +92,19 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &ssl
if (!sslErrors.empty()) {
qDebug().noquote() << sslErrors;
return amnezia::ErrorCode::ApiConfigSslError;
} else if (reply->error() == QNetworkReply::NoError) {
} else if (replyError == QNetworkReply::NoError) {
return amnezia::ErrorCode::NoError;
} else if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
qDebug() << reply->error();
} else if (replyError == QNetworkReply::NetworkError::OperationCanceledError
|| replyError == QNetworkReply::NetworkError::TimeoutError) {
qDebug() << replyError;
return amnezia::ErrorCode::ApiConfigTimeoutError;
} else if (reply->error() == QNetworkReply::NetworkError::OperationNotImplementedError) {
qDebug() << reply->error();
} else if (replyError == QNetworkReply::NetworkError::OperationNotImplementedError) {
qDebug() << replyError;
return amnezia::ErrorCode::ApiUpdateRequestError;
} else {
QString err = reply->errorString();
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
qDebug() << QString::fromUtf8(reply->readAll());
qDebug() << reply->error();
qDebug() << err;
qDebug() << QString::fromUtf8(responseBody);
qDebug() << replyError;
qDebug() << replyErrorString;
qDebug() << httpStatusCode;
if (httpStatusCode == httpStatusCodeConflict) {
return amnezia::ErrorCode::ApiConfigLimitError;
@@ -162,3 +162,51 @@ QString apiUtils::getPremiumV1VpnKey(const QJsonObject &serverConfigObject)
return QString("vpn://%1").arg(QString(signedData.toBase64(QByteArray::Base64UrlEncoding)));
}
QString apiUtils::getPremiumV2VpnKey(const QJsonObject &serverConfigObject)
{
if (apiUtils::getConfigType(serverConfigObject) != apiDefs::ConfigType::AmneziaPremiumV2) {
return {};
}
QString vpnKeyText = "";
auto apiConfig = serverConfigObject.value(apiDefs::key::apiConfig).toObject();
auto authData = serverConfigObject.value(QLatin1String("auth_data")).toObject();
const QString name = serverConfigObject.value(apiDefs::key::name).toString();
const QString description = serverConfigObject.value(apiDefs::key::description).toString();
const double configVersion = serverConfigObject.value(apiDefs::key::configVersion).toDouble();
const QString serviceType = apiConfig.value(apiDefs::key::serviceType).toString();
const QString serviceProtocol = apiConfig.value(QLatin1String("service_protocol")).toString();
const QString userCountryCode = apiConfig.value(QLatin1String("user_country_code")).toString();
const QString apiKey = authData.value(apiDefs::key::apiKey).toString();
QString vpnKeyStr = "{";
vpnKeyStr += "\"" + QString(apiDefs::key::name) + "\": \"" + name + "\", ";
vpnKeyStr += "\"" + QString(apiDefs::key::description) + "\": \"" + description + "\", ";
vpnKeyStr += "\"" + QString(apiDefs::key::configVersion) + "\": " + QString::number(static_cast<int>(configVersion)) + ", ";
vpnKeyStr += "\"" + QString(apiDefs::key::apiConfig) + "\": {";
vpnKeyStr += "\"" + QString(apiDefs::key::serviceType) + "\": \"" + serviceType + "\", ";
vpnKeyStr += "\"service_protocol\": \"" + serviceProtocol + "\", ";
vpnKeyStr += "\"user_country_code\": \"" + userCountryCode + "\"";
vpnKeyStr += "}, ";
vpnKeyStr += "\"auth_data\": {";
vpnKeyStr += "\"" + QString(apiDefs::key::apiKey) + "\": \"" + apiKey + "\"";
vpnKeyStr += "}";
vpnKeyStr += "}";
QByteArray vpnKeyCompressed = escapeUnicode(vpnKeyStr).toUtf8();
vpnKeyCompressed = qCompress(vpnKeyCompressed, 6);
vpnKeyCompressed = vpnKeyCompressed.mid(4);
QByteArray signedData = AMNEZIA_CONFIG_SIGNATURE + vpnKeyCompressed;
vpnKeyText = QString("vpn://%1").arg(QString(signedData.toBase64(QByteArray::Base64UrlEncoding)));
return vpnKeyText;
}
+4 -1
View File
@@ -18,9 +18,12 @@ namespace apiUtils
apiDefs::ConfigType getConfigType(const QJsonObject &serverConfigObject);
apiDefs::ConfigSource getConfigSource(const QJsonObject &serverConfigObject);
amnezia::ErrorCode checkNetworkReplyErrors(const QList<QSslError> &sslErrors, QNetworkReply *reply);
amnezia::ErrorCode checkNetworkReplyErrors(const QList<QSslError> &sslErrors, const QString &replyErrorString,
const QNetworkReply::NetworkError &replyError, const int httpStatusCode,
const QByteArray &responseBody);
QString getPremiumV1VpnKey(const QJsonObject &serverConfigObject);
QString getPremiumV2VpnKey(const QJsonObject &serverConfigObject);
}
#endif // APIUTILS_H
+32 -2
View File
@@ -31,7 +31,7 @@ CoreController::CoreController(const QSharedPointer<VpnConnection> &vpnConnectio
auto locale = m_settings->getAppLanguage();
m_translator.reset(new QTranslator());
updateTranslator(locale);
updateTranslator(m_settings->getAppLanguage());
}
void CoreController::initLocalProxy()
@@ -116,6 +116,9 @@ void CoreController::initModels()
m_apiDevicesModel.reset(new ApiDevicesModel(m_settings, this));
m_engine->rootContext()->setContextProperty("ApiDevicesModel", m_apiDevicesModel.get());
m_newsModel.reset(new NewsModel(m_settings, this));
m_engine->rootContext()->setContextProperty("NewsModel", m_newsModel.get());
}
void CoreController::initControllers()
@@ -170,6 +173,9 @@ void CoreController::initControllers()
m_apiPremV1MigrationController.reset(new ApiPremV1MigrationController(m_serversModel, m_settings, this));
m_engine->rootContext()->setContextProperty("ApiPremV1MigrationController", m_apiPremV1MigrationController.get());
m_apiNewsController.reset(new ApiNewsController(m_newsModel, m_settings, m_serversModel, this));
m_engine->rootContext()->setContextProperty("ApiNewsController", m_apiNewsController.get());
}
void CoreController::initAndroidController()
@@ -249,7 +255,7 @@ void CoreController::initSignalHandlers()
void CoreController::initNotificationHandler()
{
#ifndef Q_OS_ANDROID
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
m_notificationHandler.reset(NotificationHandler::create(nullptr));
connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(),
@@ -333,6 +339,11 @@ void CoreController::initContainerModelUpdateHandler()
connect(m_serversModel.get(), &ServersModel::containersUpdated, m_containersModel.get(), &ContainersModel::updateModel);
connect(m_serversModel.get(), &ServersModel::defaultServerContainersUpdated, m_defaultServerContainersModel.get(),
&ContainersModel::updateModel);
connect(m_serversModel.get(), &ServersModel::gatewayStacksExpanded, this, [this]() {
if (m_serversModel->hasServersFromGatewayApi()) {
m_apiNewsController->fetchNews(false);
}
});
m_serversModel->resetModel();
}
@@ -417,3 +428,22 @@ QSharedPointer<PageController> CoreController::pageController() const
{
return m_pageController;
}
void CoreController::openConnectionByIndex(int serverIndex)
{
if (m_serversModel) {
m_serversModel->setProcessedServerIndex(serverIndex);
m_serversModel->setDefaultServerIndex(serverIndex);
}
m_connectionController->toggleConnection();
}
void CoreController::importConfigFromData(const QString &data)
{
if (!m_importController)
return;
if (m_importController->extractConfigFromData(data)) {
m_importController->importConfig();
}
}
+10 -6
View File
@@ -5,13 +5,14 @@
#include <QQmlContext>
#include <QThread>
#ifndef Q_OS_ANDROID
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#include "ui/systemtray_notificationhandler.h"
#endif
#include "ui/controllers/api/apiConfigsController.h"
#include "ui/controllers/api/apiSettingsController.h"
#include "ui/controllers/api/apiPremV1MigrationController.h"
#include "ui/controllers/api/apiNewsController.h"
#include "ui/controllers/appSplitTunnelingController.h"
#include "ui/controllers/allowedDnsController.h"
#include "ui/controllers/connectionController.h"
@@ -47,14 +48,12 @@
#include "ui/models/services/sftpConfigModel.h"
#include "ui/models/services/socks5ProxyConfigModel.h"
#include "ui/models/sites_model.h"
#include "ui/models/newsModel.h"
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#include "core/local-proxy/proxyserver.h"
#include "core/local-proxy/proxylogger.h"
#endif
#ifndef Q_OS_ANDROID
#include "ui/notificationhandler.h"
#include "ui/notificationhandler.h"
#endif
class CoreController : public QObject
@@ -68,6 +67,9 @@ public:
QSharedPointer<PageController> pageController() const;
void setQmlRoot();
void openConnectionByIndex(int serverIndex);
void importConfigFromData(const QString &data);
signals:
void translationsUpdated();
void websiteUrlChanged(const QString &newUrl);
@@ -103,7 +105,7 @@ private:
QSharedPointer<VpnConnection> m_vpnConnection;
QSharedPointer<QTranslator> m_translator;
#ifndef Q_OS_ANDROID
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
QScopedPointer<NotificationHandler> m_notificationHandler;
#endif
@@ -124,6 +126,7 @@ private:
QScopedPointer<ApiSettingsController> m_apiSettingsController;
QScopedPointer<ApiConfigsController> m_apiConfigsController;
QScopedPointer<ApiPremV1MigrationController> m_apiPremV1MigrationController;
QScopedPointer<ApiNewsController> m_apiNewsController;
QSharedPointer<ContainersModel> m_containersModel;
QSharedPointer<ContainersModel> m_defaultServerContainersModel;
@@ -131,6 +134,7 @@ private:
QSharedPointer<LanguageModel> m_languageModel;
QSharedPointer<ProtocolsModel> m_protocolsModel;
QSharedPointer<SitesModel> m_sitesModel;
QSharedPointer<NewsModel> m_newsModel;
QSharedPointer<AllowedDnsModel> m_allowedDnsModel;
QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel;
QSharedPointer<ClientManagementModel> m_clientManagementModel;
+370 -120
View File
@@ -1,12 +1,15 @@
#include "gatewayController.h"
#include <algorithm>
#include <functional>
#include <random>
#include <QCryptographicHash>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkReply>
#include <QPromise>
#include <QUrl>
#include "QBlockCipher.h"
@@ -50,101 +53,45 @@ GatewayController::GatewayController(const QString &gatewayEndpoint, const bool
{
}
ErrorCode GatewayController::get(const QString &endpoint, QByteArray &responseBody)
GatewayController::EncryptedRequestData GatewayController::prepareRequest(const QString &endpoint, const QJsonObject &apiPayload)
{
EncryptedRequestData encRequestData;
encRequestData.errorCode = ErrorCode::NoError;
#ifdef Q_OS_IOS
IosController::Instance()->requestInetAccess();
QThread::msleep(10);
#endif
QNetworkRequest request;
request.setTransferTimeout(m_requestTimeoutMsecs);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setUrl(QString(endpoint).arg(m_gatewayEndpoint));
encRequestData.request.setTransferTimeout(m_requestTimeoutMsecs);
encRequestData.request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
encRequestData.request.setRawHeader(QString("X-Client-Request-ID").toUtf8(), QUuid::createUuid().toString(QUuid::WithoutBraces).toUtf8());
encRequestData.request.setUrl(endpoint.arg(m_proxyUrl.isEmpty() ? m_gatewayEndpoint : m_proxyUrl));
// bypass killSwitch exceptions for API-gateway
#ifdef AMNEZIA_DESKTOP
if (m_isStrictKillSwitchEnabled) {
QString host = QUrl(request.url()).host();
QString host = QUrl(encRequestData.request.url()).host();
QString ip = NetworkUtilities::getIPAddress(host);
if (!ip.isEmpty()) {
IpcClient::Interface()->addKillSwitchAllowedRange(QStringList { ip });
}
}
#endif
QNetworkReply *reply;
reply = amnApp->networkManager()->get(request);
QEventLoop wait;
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
QList<QSslError> sslErrors;
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
wait.exec();
responseBody = reply->readAll();
if (sslErrors.isEmpty() && shouldBypassProxy(reply, responseBody, false)) {
auto requestFunction = [&request, &responseBody](const QString &url) {
request.setUrl(url);
return amnApp->networkManager()->get(request);
};
auto replyProcessingFunction = [&responseBody, &reply, &sslErrors, this](QNetworkReply *nestedReply,
const QList<QSslError> &nestedSslErrors) {
responseBody = nestedReply->readAll();
if (!sslErrors.isEmpty() || !shouldBypassProxy(nestedReply, responseBody, false)) {
sslErrors = nestedSslErrors;
reply = nestedReply;
return true;
}
return false;
};
bypassProxy(endpoint, reply, requestFunction, replyProcessingFunction);
}
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, reply);
reply->deleteLater();
return errorCode;
}
ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody)
{
#ifdef Q_OS_IOS
IosController::Instance()->requestInetAccess();
QThread::msleep(10);
#endif
QNetworkRequest request;
request.setTransferTimeout(m_requestTimeoutMsecs);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setUrl(endpoint.arg(m_gatewayEndpoint));
// bypass killSwitch exceptions for API-gateway
#ifdef AMNEZIA_DESKTOP
if (m_isStrictKillSwitchEnabled) {
QString host = QUrl(request.url()).host();
QString ip = NetworkUtilities::getIPAddress(host);
if (!ip.isEmpty()) {
IpcClient::Interface()->addKillSwitchAllowedRange(QStringList { ip });
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
QRemoteObjectPendingReply<bool> reply = iface->addKillSwitchAllowedRange(QStringList { ip });
if (!reply.waitForFinished(1000) || !reply.returnValue())
qWarning() << "GatewayController::prepareRequest(): Failed to execute remote addKillSwitchAllowedRange call";
});
}
}
#endif
QSimpleCrypto::QBlockCipher blockCipher;
QByteArray key = blockCipher.generatePrivateSalt(32);
QByteArray iv = blockCipher.generatePrivateSalt(32);
QByteArray salt = blockCipher.generatePrivateSalt(8);
encRequestData.key = blockCipher.generatePrivateSalt(32);
encRequestData.iv = blockCipher.generatePrivateSalt(32);
encRequestData.salt = blockCipher.generatePrivateSalt(8);
QJsonObject keyPayload;
keyPayload[configKey::aesKey] = QString(key.toBase64());
keyPayload[configKey::aesIv] = QString(iv.toBase64());
keyPayload[configKey::aesSalt] = QString(salt.toBase64());
keyPayload[configKey::aesKey] = QString(encRequestData.key.toBase64());
keyPayload[configKey::aesIv] = QString(encRequestData.iv.toBase64());
keyPayload[configKey::aesSalt] = QString(encRequestData.salt.toBase64());
QByteArray encryptedKeyPayload;
QByteArray encryptedApiPayload;
@@ -159,62 +106,88 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
} catch (...) {
Utils::logException();
qCritical() << "error loading public key from environment variables";
return ErrorCode::ApiMissingAgwPublicKey;
encRequestData.errorCode = ErrorCode::ApiMissingAgwPublicKey;
return encRequestData;
}
encryptedKeyPayload = rsa.encrypt(QJsonDocument(keyPayload).toJson(), publicKey, RSA_PKCS1_PADDING);
EVP_PKEY_free(publicKey);
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), key, iv, "", salt);
} catch (...) { // todo change error handling in QSimpleCrypto?
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), encRequestData.key, encRequestData.iv,
"", encRequestData.salt);
} catch (...) {
Utils::logException();
qCritical() << "error when encrypting the request body";
return ErrorCode::ApiConfigDecryptionError;
encRequestData.errorCode = ErrorCode::ApiConfigDecryptionError;
return encRequestData;
}
QJsonObject requestBody;
requestBody[configKey::keyPayload] = QString(encryptedKeyPayload.toBase64());
requestBody[configKey::apiPayload] = QString(encryptedApiPayload.toBase64());
QNetworkReply *reply = amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson());
encRequestData.requestBody = QJsonDocument(requestBody).toJson();
return encRequestData;
}
ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody)
{
EncryptedRequestData encRequestData = prepareRequest(endpoint, apiPayload);
if (encRequestData.errorCode != ErrorCode::NoError) {
return encRequestData.errorCode;
}
QNetworkReply *reply = amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
QEventLoop wait;
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
QList<QSslError> sslErrors;
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
wait.exec();
wait.exec(QEventLoop::ExcludeUserInputEvents);
QByteArray encryptedResponseBody = reply->readAll();
QString replyErrorString = reply->errorString();
auto replyError = reply->error();
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (sslErrors.isEmpty() && shouldBypassProxy(reply, encryptedResponseBody, true, key, iv, salt)) {
auto requestFunction = [&request, &encryptedResponseBody, &requestBody](const QString &url) {
request.setUrl(url);
return amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson());
reply->deleteLater();
if (sslErrors.isEmpty()
&& shouldBypassProxy(replyError, encryptedResponseBody, true, encRequestData.key, encRequestData.iv, encRequestData.salt)) {
auto requestFunction = [&encRequestData, &encryptedResponseBody](const QString &url) {
encRequestData.request.setUrl(url);
return amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
};
auto replyProcessingFunction = [&encryptedResponseBody, &reply, &sslErrors, &key, &iv, &salt,
this](QNetworkReply *nestedReply, const QList<QSslError> &nestedSslErrors) {
encryptedResponseBody = nestedReply->readAll();
reply = nestedReply;
if (!sslErrors.isEmpty() || shouldBypassProxy(nestedReply, encryptedResponseBody, true, key, iv, salt)) {
auto replyProcessingFunction = [&encryptedResponseBody, &replyErrorString, &replyError, &httpStatusCode, &sslErrors,
&encRequestData, this](QNetworkReply *reply, const QList<QSslError> &nestedSslErrors) {
encryptedResponseBody = reply->readAll();
replyErrorString = reply->errorString();
replyError = reply->error();
httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (!sslErrors.isEmpty()
|| shouldBypassProxy(replyError, encryptedResponseBody, true, encRequestData.key, encRequestData.iv, encRequestData.salt)) {
sslErrors = nestedSslErrors;
return false;
}
return true;
};
bypassProxy(endpoint, reply, requestFunction, replyProcessingFunction);
auto serviceType = apiPayload.value(apiDefs::key::serviceType).toString("");
auto userCountryCode = apiPayload.value(apiDefs::key::userCountryCode).toString("");
bypassProxy(endpoint, serviceType, userCountryCode, requestFunction, replyProcessingFunction);
}
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, reply);
reply->deleteLater();
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, replyErrorString, replyError, httpStatusCode, encryptedResponseBody);
if (errorCode) {
return errorCode;
}
try {
responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt);
QSimpleCrypto::QBlockCipher blockCipher;
responseBody =
blockCipher.decryptAesBlockCipher(encryptedResponseBody, encRequestData.key, encRequestData.iv, "", encRequestData.salt);
return ErrorCode::NoError;
} catch (...) { // todo change error handling in QSimpleCrypto?
Utils::logException();
@@ -223,7 +196,94 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api
}
}
QStringList GatewayController::getProxyUrls()
QFuture<QPair<ErrorCode, QByteArray>> GatewayController::postAsync(const QString &endpoint, const QJsonObject apiPayload)
{
auto promise = QSharedPointer<QPromise<QPair<ErrorCode, QByteArray>>>::create();
promise->start();
EncryptedRequestData encRequestData = prepareRequest(endpoint, apiPayload);
if (encRequestData.errorCode != ErrorCode::NoError) {
promise->addResult(qMakePair(encRequestData.errorCode, QByteArray()));
promise->finish();
return promise->future();
}
QNetworkReply *reply = amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
auto sslErrors = QSharedPointer<QList<QSslError>>::create();
connect(reply, &QNetworkReply::sslErrors, [sslErrors](const QList<QSslError> &errors) { *sslErrors = errors; });
connect(reply, &QNetworkReply::finished, reply, [promise, sslErrors, encRequestData, endpoint, apiPayload, reply, this]() mutable {
QByteArray encryptedResponseBody = reply->readAll();
QString replyErrorString = reply->errorString();
auto replyError = reply->error();
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
reply->deleteLater();
auto processResponse = [promise, encRequestData](const QByteArray &ecryptedResponseBody, const QList<QSslError> &sslErrors,
QNetworkReply::NetworkError replyError, const QString &replyErrorString,
int httpStatusCode) {
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, replyErrorString, replyError, httpStatusCode, ecryptedResponseBody);
if (errorCode) {
promise->addResult(qMakePair(errorCode, QByteArray()));
promise->finish();
return;
}
QSimpleCrypto::QBlockCipher blockCipher;
try {
QByteArray responseBody = blockCipher.decryptAesBlockCipher(ecryptedResponseBody, encRequestData.key, encRequestData.iv, "",
encRequestData.salt);
promise->addResult(qMakePair(ErrorCode::NoError, responseBody));
promise->finish();
} catch (...) {
Utils::logException();
qCritical() << "error when decrypting the request body";
promise->addResult(qMakePair(ErrorCode::ApiConfigDecryptionError, QByteArray()));
promise->finish();
}
};
if (sslErrors->isEmpty()
&& shouldBypassProxy(replyError, encryptedResponseBody, true, encRequestData.key, encRequestData.iv, encRequestData.salt)) {
auto serviceType = apiPayload.value(apiDefs::key::serviceType).toString("");
auto userCountryCode = apiPayload.value(apiDefs::key::userCountryCode).toString("");
QStringList baseUrls;
if (m_isDevEnvironment) {
baseUrls = QString(DEV_S3_ENDPOINT).split(", ");
} else {
baseUrls = QString(PROD_S3_ENDPOINT).split(", ");
}
QStringList proxyStorageUrls;
if (!serviceType.isEmpty()) {
for (const auto &baseUrl : baseUrls) {
QByteArray path = ("endpoints-" + serviceType + "-" + userCountryCode).toUtf8();
proxyStorageUrls.push_back(baseUrl + path.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)
+ ".json");
}
}
for (const auto &baseUrl : baseUrls)
proxyStorageUrls.push_back(baseUrl + "endpoints.json");
getProxyUrlsAsync(proxyStorageUrls, 0, [this, encRequestData, endpoint, processResponse](const QStringList &proxyUrls) {
getProxyUrlAsync(proxyUrls, 0, [this, encRequestData, endpoint, processResponse](const QString &proxyUrls) {
bypassProxyAsync(endpoint, proxyUrls, encRequestData, processResponse);
});
});
} else {
processResponse(encryptedResponseBody, *sslErrors, replyError, replyErrorString, httpStatusCode);
}
});
return promise->future();
}
QStringList GatewayController::getProxyUrls(const QString &serviceType, const QString &userCountryCode)
{
QNetworkRequest request;
request.setTransferTimeout(m_requestTimeoutMsecs);
@@ -233,22 +293,33 @@ QStringList GatewayController::getProxyUrls()
QList<QSslError> sslErrors;
QNetworkReply *reply;
QStringList proxyStorageUrls;
QStringList baseUrls;
if (m_isDevEnvironment) {
proxyStorageUrls = QString(DEV_S3_ENDPOINT).split(", ");
baseUrls = QString(DEV_S3_ENDPOINT).split(", ");
} else {
proxyStorageUrls = QString(PROD_S3_ENDPOINT).split(", ");
baseUrls = QString(PROD_S3_ENDPOINT).split(", ");
}
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
QStringList proxyStorageUrls;
if (!serviceType.isEmpty()) {
for (const auto &baseUrl : baseUrls) {
QByteArray path = ("endpoints-" + serviceType + "-" + userCountryCode).toUtf8();
proxyStorageUrls.push_back(baseUrl + path.toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals) + ".json");
}
}
for (const auto &baseUrl : baseUrls) {
proxyStorageUrls.push_back(baseUrl + "endpoints.json");
}
for (const auto &proxyStorageUrl : proxyStorageUrls) {
request.setUrl(proxyStorageUrl);
reply = amnApp->networkManager()->get(request);
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
wait.exec();
wait.exec(QEventLoop::ExcludeUserInputEvents);
if (reply->error() == QNetworkReply::NetworkError::NoError) {
auto encryptedResponseBody = reply->readAll();
@@ -286,7 +357,10 @@ QStringList GatewayController::getProxyUrls()
}
return endpoints;
} else {
apiUtils::checkNetworkReplyErrors(sslErrors, reply);
auto replyError = reply->error();
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
qDebug() << replyError;
qDebug() << httpStatusCode;
qDebug() << "go to the next storage endpoint";
reply->deleteLater();
@@ -295,33 +369,33 @@ QStringList GatewayController::getProxyUrls()
return {};
}
bool GatewayController::shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key,
const QByteArray &iv, const QByteArray &salt)
bool GatewayController::shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &responseBody,
bool checkEncryption, const QByteArray &key, const QByteArray &iv, const QByteArray &salt)
{
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError || reply->error() == QNetworkReply::NetworkError::TimeoutError) {
if (replyError == QNetworkReply::NetworkError::OperationCanceledError || replyError == QNetworkReply::NetworkError::TimeoutError) {
qDebug() << "timeout occurred";
qDebug() << reply->error();
qDebug() << replyError;
return true;
} else if (responseBody.contains("html")) {
qDebug() << "the response contains an html tag";
return true;
} else if (reply->error() == QNetworkReply::NetworkError::ContentNotFoundError) {
} else if (replyError == QNetworkReply::NetworkError::ContentNotFoundError) {
if (responseBody.contains(errorResponsePattern1) || responseBody.contains(errorResponsePattern2)
|| responseBody.contains(errorResponsePattern3)) {
return false;
} else {
qDebug() << reply->error();
qDebug() << replyError;
return true;
}
} else if (reply->error() == QNetworkReply::NetworkError::OperationNotImplementedError) {
} else if (replyError == QNetworkReply::NetworkError::OperationNotImplementedError) {
if (responseBody.contains(updateRequestResponsePattern)) {
return false;
} else {
qDebug() << reply->error();
qDebug() << replyError;
return true;
}
} else if (reply->error() != QNetworkReply::NetworkError::NoError) {
qDebug() << reply->error();
} else if (replyError != QNetworkReply::NetworkError::NoError) {
qDebug() << replyError;
return true;
} else if (checkEncryption) {
try {
@@ -335,30 +409,206 @@ bool GatewayController::shouldBypassProxy(QNetworkReply *reply, const QByteArray
return false;
}
void GatewayController::bypassProxy(const QString &endpoint, QNetworkReply *reply,
void GatewayController::bypassProxy(const QString &endpoint, const QString &serviceType, const QString &userCountryCode,
std::function<QNetworkReply *(const QString &url)> requestFunction,
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction)
{
QStringList proxyUrls = getProxyUrls();
QStringList proxyUrls = getProxyUrls(serviceType, userCountryCode);
std::random_device randomDevice;
std::mt19937 generator(randomDevice());
std::shuffle(proxyUrls.begin(), proxyUrls.end(), generator);
QEventLoop wait;
QList<QSslError> sslErrors;
QByteArray responseBody;
for (const QString &proxyUrl : proxyUrls) {
auto bypassFunction = [this](const QString &endpoint, const QString &proxyUrl,
std::function<QNetworkReply *(const QString &url)> requestFunction,
std::function<bool(QNetworkReply * reply, const QList<QSslError> &sslErrors)> replyProcessingFunction) {
QEventLoop wait;
QList<QSslError> sslErrors;
qDebug() << "go to the next proxy endpoint";
reply->deleteLater(); // delete the previous reply
reply = requestFunction(endpoint.arg(proxyUrl));
QNetworkReply *reply = requestFunction(endpoint.arg(proxyUrl));
QObject::connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
wait.exec();
wait.exec(QEventLoop::ExcludeUserInputEvents);
if (replyProcessingFunction(reply, sslErrors)) {
auto result = replyProcessingFunction(reply, sslErrors);
reply->deleteLater();
return result;
};
if (m_proxyUrl.isEmpty()) {
QNetworkRequest request;
request.setTransferTimeout(1000);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QEventLoop wait;
QList<QSslError> sslErrors;
QNetworkReply *reply;
for (const QString &proxyUrl : proxyUrls) {
request.setUrl(proxyUrl + "lmbd-health");
reply = amnApp->networkManager()->get(request);
connect(reply, &QNetworkReply::finished, &wait, &QEventLoop::quit);
connect(reply, &QNetworkReply::sslErrors, [this, &sslErrors](const QList<QSslError> &errors) { sslErrors = errors; });
wait.exec(QEventLoop::ExcludeUserInputEvents);
if (reply->error() == QNetworkReply::NetworkError::NoError) {
reply->deleteLater();
m_proxyUrl = proxyUrl;
if (!m_proxyUrl.isEmpty()) {
break;
}
} else {
reply->deleteLater();
}
}
}
if (!m_proxyUrl.isEmpty()) {
if (bypassFunction(endpoint, m_proxyUrl, requestFunction, replyProcessingFunction)) {
return;
}
}
for (const QString &proxyUrl : proxyUrls) {
if (bypassFunction(endpoint, proxyUrl, requestFunction, replyProcessingFunction)) {
m_proxyUrl = proxyUrl;
break;
}
}
}
void GatewayController::getProxyUrlsAsync(const QStringList proxyStorageUrls, const int currentProxyStorageIndex,
std::function<void(const QStringList &)> onComplete)
{
if (currentProxyStorageIndex >= proxyStorageUrls.size()) {
onComplete({});
return;
}
QNetworkRequest request;
request.setTransferTimeout(m_requestTimeoutMsecs);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setUrl(proxyStorageUrls[currentProxyStorageIndex]);
QNetworkReply *reply = amnApp->networkManager()->get(request);
// connect(reply, &QNetworkReply::sslErrors, this, [state](const QList<QSslError> &e) { *(state->sslErrors) = e; });
connect(reply, &QNetworkReply::finished, this, [this, proxyStorageUrls, currentProxyStorageIndex, onComplete, reply]() {
if (reply->error() == QNetworkReply::NoError) {
QByteArray encrypted = reply->readAll();
reply->deleteLater();
QByteArray responseBody;
try {
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
if (!m_isDevEnvironment) {
QCryptographicHash hash(QCryptographicHash::Sha512);
hash.addData(key);
QByteArray h = hash.result().toHex();
QByteArray decKey = QByteArray::fromHex(h.left(64));
QByteArray iv = QByteArray::fromHex(h.mid(64, 32));
QByteArray ba = QByteArray::fromBase64(encrypted);
QSimpleCrypto::QBlockCipher cipher;
responseBody = cipher.decryptAesBlockCipher(ba, decKey, iv);
} else {
responseBody = encrypted;
}
} catch (...) {
Utils::logException();
qCritical() << "error decrypting payload";
QMetaObject::invokeMethod(
this, [=]() { getProxyUrlsAsync(proxyStorageUrls, currentProxyStorageIndex + 1, onComplete); }, Qt::QueuedConnection);
return;
}
QJsonArray endpointsArray = QJsonDocument::fromJson(responseBody).array();
QStringList endpoints;
for (const QJsonValue &endpoint : endpointsArray)
endpoints.push_back(endpoint.toString());
QStringList shuffled = endpoints;
std::random_device randomDevice;
std::mt19937 generator(randomDevice());
std::shuffle(shuffled.begin(), shuffled.end(), generator);
onComplete(shuffled);
return;
}
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
qDebug() << httpStatusCode;
qDebug() << "go to the next storage endpoint";
reply->deleteLater();
QMetaObject::invokeMethod(
this, [=]() { getProxyUrlsAsync(proxyStorageUrls, currentProxyStorageIndex + 1, onComplete); }, Qt::QueuedConnection);
});
}
void GatewayController::getProxyUrlAsync(const QStringList proxyUrls, const int currentProxyIndex, std::function<void(const QString &)> onComplete)
{
if (currentProxyIndex >= proxyUrls.size()) {
onComplete("");
return;
}
QNetworkRequest request;
request.setTransferTimeout(1000);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setUrl(proxyUrls[currentProxyIndex] + "lmbd-health");
QNetworkReply *reply = amnApp->networkManager()->get(request);
// connect(reply, &QNetworkReply::sslErrors, this, [state](const QList<QSslError> &e) {
// *(state->sslErrors) = e;
// });
connect(reply, &QNetworkReply::finished, this, [this, proxyUrls, currentProxyIndex, onComplete, reply]() {
reply->deleteLater();
if (reply->error() == QNetworkReply::NoError) {
m_proxyUrl = proxyUrls[currentProxyIndex];
onComplete(m_proxyUrl);
return;
}
qDebug() << "go to the next proxy endpoint";
QMetaObject::invokeMethod(this, [=]() { getProxyUrlAsync(proxyUrls, currentProxyIndex + 1, onComplete); }, Qt::QueuedConnection);
});
}
void GatewayController::bypassProxyAsync(
const QString &endpoint, const QString &proxyUrl, EncryptedRequestData encRequestData,
std::function<void(const QByteArray &, const QList<QSslError> &, QNetworkReply::NetworkError, const QString &, int)> onComplete)
{
auto sslErrors = QSharedPointer<QList<QSslError>>::create();
if (proxyUrl.isEmpty()) {
onComplete(QByteArray(), *sslErrors, QNetworkReply::InternalServerError, "empty proxy url", 0);
return;
}
QNetworkRequest request = encRequestData.request;
request.setUrl(endpoint.arg(proxyUrl));
QNetworkReply *reply = amnApp->networkManager()->post(request, encRequestData.requestBody);
connect(reply, &QNetworkReply::sslErrors, this, [sslErrors](const QList<QSslError> &errors) { *sslErrors = errors; });
connect(reply, &QNetworkReply::finished, this, [sslErrors, onComplete, reply]() {
QByteArray encryptedResponseBody = reply->readAll();
QString replyErrorString = reply->errorString();
auto replyError = reply->error();
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
reply->deleteLater();
onComplete(encryptedResponseBody, *sslErrors, replyError, replyErrorString, httpStatusCode);
});
}
+31 -5
View File
@@ -1,8 +1,12 @@
#ifndef GATEWAYCONTROLLER_H
#define GATEWAYCONTROLLER_H
#include <QFuture>
#include <QNetworkReply>
#include <QObject>
#include <QPair>
#include <QPromise>
#include <QSharedPointer>
#include "core/defs.h"
@@ -18,20 +22,42 @@ public:
explicit GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
const bool isStrictKillSwitchEnabled, QObject *parent = nullptr);
amnezia::ErrorCode get(const QString &endpoint, QByteArray &responseBody);
amnezia::ErrorCode post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody);
QFuture<QPair<amnezia::ErrorCode, QByteArray>> postAsync(const QString &endpoint, const QJsonObject apiPayload);
private:
QStringList getProxyUrls();
bool shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key = "",
const QByteArray &iv = "", const QByteArray &salt = "");
void bypassProxy(const QString &endpoint, QNetworkReply *reply, std::function<QNetworkReply *(const QString &url)> requestFunction,
struct EncryptedRequestData
{
QNetworkRequest request;
QByteArray requestBody;
QByteArray key;
QByteArray iv;
QByteArray salt;
amnezia::ErrorCode errorCode;
};
EncryptedRequestData prepareRequest(const QString &endpoint, const QJsonObject &apiPayload);
QStringList getProxyUrls(const QString &serviceType, const QString &userCountryCode);
bool shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &responseBody, bool checkEncryption,
const QByteArray &key = "", const QByteArray &iv = "", const QByteArray &salt = "");
void bypassProxy(const QString &endpoint, const QString &serviceType, const QString &userCountryCode,
std::function<QNetworkReply *(const QString &url)> requestFunction,
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction);
void getProxyUrlsAsync(const QStringList proxyStorageUrls, const int currentProxyStorageIndex,
std::function<void(const QStringList &)> onComplete);
void getProxyUrlAsync(const QStringList proxyUrls, const int currentProxyIndex, std::function<void(const QString &)> onComplete);
void bypassProxyAsync(
const QString &endpoint, const QString &proxyUrl, EncryptedRequestData encRequestData,
std::function<void(const QByteArray &, const QList<QSslError> &, QNetworkReply::NetworkError, const QString &, int)> onComplete);
int m_requestTimeoutMsecs;
QString m_gatewayEndpoint;
bool m_isDevEnvironment = false;
bool m_isStrictKillSwitchEnabled = false;
inline static QString m_proxyUrl;
};
#endif // GATEWAYCONTROLLER_H
+13 -7
View File
@@ -345,7 +345,7 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
return true;
}
if (container == DockerContainer::Awg) {
if (ContainerProps::isAwgContainer(container)) {
if ((oldProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress)
!= newProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress))
|| (oldProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort)
@@ -367,11 +367,11 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
|| (oldProtoConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader)
!= newProtoConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader))
|| (oldProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader))
!= newProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader))
// || (oldProtoConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize)
// != newProtoConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize))
// || (oldProtoConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize)
// != newProtoConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize))
!= newProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader)
|| (oldProtoConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize)
!= newProtoConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize))
|| (oldProtoConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize)
!= newProtoConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize)))
return true;
}
@@ -648,6 +648,11 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
vars.append({ { "$COOKIE_REPLY_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::cookieReplyPacketJunkSize).toString() } });
vars.append({ { "$TRANSPORT_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::transportPacketJunkSize).toString() } });
vars.append({ { "$SPECIAL_JUNK_1", amneziaWireguarConfig.value(config_key::specialJunk1).toString() } });
vars.append({ { "$SPECIAL_JUNK_2", amneziaWireguarConfig.value(config_key::specialJunk2).toString() } });
vars.append({ { "$SPECIAL_JUNK_3", amneziaWireguarConfig.value(config_key::specialJunk3).toString() } });
vars.append({ { "$SPECIAL_JUNK_4", amneziaWireguarConfig.value(config_key::specialJunk4).toString() } });
vars.append({ { "$SPECIAL_JUNK_5", amneziaWireguarConfig.value(config_key::specialJunk5).toString() } });
// Socks5 proxy vars
vars.append({ { "$SOCKS5_PROXY_PORT", socks5ProxyConfig.value(config_key::port).toString(protocols::socks5Proxy::defaultPort) } });
@@ -657,7 +662,8 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
vars.append({ { "$SOCKS5_USER", socks5user } });
vars.append({ { "$SOCKS5_AUTH_TYPE", socks5user.isEmpty() ? "none" : "strong" } });
QString serverIp = (container != DockerContainer::Awg && container != DockerContainer::WireGuard && container != DockerContainer::Xray)
QString serverIp = (!ContainerProps::isAwgContainer(container) &&
container != DockerContainer::WireGuard && container != DockerContainer::Xray)
? NetworkUtilities::getIPAddress(credentials.hostName)
: credentials.hostName;
if (!serverIp.isEmpty()) {
@@ -99,11 +99,12 @@ QJsonObject VpnConfigurationsController::createVpnConfiguration(const QPair<QStr
protocolConfigString = configurator->processConfigWithLocalSettings(dns, isApiConfig, protocolConfigString);
QJsonObject vpnConfigData = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
if (container == DockerContainer::Awg || container == DockerContainer::WireGuard) {
if (ContainerProps::isAwgContainer(container) || container == DockerContainer::WireGuard) {
// add mtu for old configs
if (vpnConfigData[config_key::mtu].toString().isEmpty()) {
vpnConfigData[config_key::mtu] =
container == DockerContainer::Awg ? protocols::awg::defaultMtu : protocols::wireguard::defaultMtu;
ContainerProps::isAwgContainer(container) ? protocols::awg::defaultMtu :
protocols::wireguard::defaultMtu;
}
}
+2
View File
@@ -120,6 +120,8 @@ namespace amnezia
ApiNotFoundError = 1109,
ApiMigrationError = 1110,
ApiUpdateRequestError = 1111,
ApiSubscriptionExpiredError = 1112,
ApiPurchaseError = 1113,
// QFile errors
OpenError = 1200,
+2
View File
@@ -77,6 +77,8 @@ QString errorString(ErrorCode code) {
case (ErrorCode::ApiNotFoundError): errorMessage = QObject::tr("Error when retrieving configuration from API"); break;
case (ErrorCode::ApiMigrationError): errorMessage = QObject::tr("A migration error has occurred. Please contact our technical support"); break;
case (ErrorCode::ApiUpdateRequestError): errorMessage = QObject::tr("Please update the application to use this feature"); break;
case (ErrorCode::ApiSubscriptionExpiredError): errorMessage = QObject::tr("Your Amnezia Premium subscription has expired.\n Please check your email for renewal instructions.\n If you haven't received an email, please contact our support."); break;
case (ErrorCode::ApiPurchaseError): errorMessage = QObject::tr("Unable to process purchase"); break;
// QFile errors
case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;
+67 -74
View File
@@ -1,108 +1,96 @@
#include "ipcclient.h"
#include "ipc.h"
#include <QRemoteObjectNode>
#include <QtNetwork/qlocalsocket.h>
IpcClient *IpcClient::m_instance = nullptr;
namespace
{
thread_local IpcClient ipcClient;
}
IpcClient::IpcClient(QObject *parent) : QObject(parent)
{
}
connect(&m_localSocket, &QLocalSocket::connected, this, [this]() {
m_ClientNode.reset(new QRemoteObjectNode);
m_ClientNode->addClientSideConnection(&m_localSocket);
m_ipcClient.reset(m_ClientNode->acquire<IpcInterfaceReplica>());
m_Tun2SocksClient.reset(m_ClientNode->acquire<IpcProcessTun2SocksReplica>());
m_isSocketConnected = true;
});
IpcClient::~IpcClient()
{
if (m_localSocket)
m_localSocket->close();
}
bool IpcClient::isSocketConnected() const
{
return m_isSocketConnected;
connect(&m_localSocket, &QLocalSocket::disconnected, this, [this]() {
m_ClientNode.clear();
m_ipcClient.clear();
m_Tun2SocksClient.clear();
m_isSocketConnected = false;
});
}
IpcClient *IpcClient::Instance()
{
return m_instance;
if (!ipcClient.m_isSocketConnected) {
ipcClient.establishConnection();
}
return &ipcClient;
}
QSharedPointer<IpcInterfaceReplica> IpcClient::Interface()
{
if (!Instance())
QSharedPointer<IpcInterfaceReplica> rep = Instance()->m_ipcClient;
if (rep.isNull()) {
qCritical() << "IpcClient::Interface(): Failed to acquire replica";
return nullptr;
return Instance()->m_ipcClient;
}
if (!rep->waitForSource(1000)) {
qCritical() << "IpcClient::Interface(): Failed to initialize replica";
return nullptr;
}
if (!rep->isReplicaValid()) {
qWarning() << "IpcClient::Interface(): Replica is invalid";
}
return rep;
}
QSharedPointer<IpcProcessTun2SocksReplica> IpcClient::InterfaceTun2Socks()
{
if (!Instance())
QSharedPointer<IpcProcessTun2SocksReplica> rep = Instance()->m_Tun2SocksClient;
if (rep.isNull()) {
qCritical() << "IpcClient::InterfaceTun2Socks: Replica is undefined";
return nullptr;
return Instance()->m_Tun2SocksClient;
}
if (!rep->waitForSource(1000)) {
qCritical() << "IpcClient::InterfaceTun2Socks: Failed to initialize replica";
return nullptr;
}
if (!rep->isReplicaValid()) {
qWarning() << "IpcClient::InterfaceTun2Socks(): Replica is invalid";
}
return rep;
}
bool IpcClient::init(IpcClient *instance)
bool IpcClient::establishConnection()
{
m_instance = instance;
Instance()->m_localSocket = new QLocalSocket(Instance());
connect(Instance()->m_localSocket.data(), &QLocalSocket::connected, &Instance()->m_ClientNode, []() {
Instance()->m_ClientNode.addClientSideConnection(Instance()->m_localSocket.data());
auto cliNode = Instance()->m_ClientNode.acquire<IpcInterfaceReplica>();
cliNode->waitForSource(5000);
Instance()->m_ipcClient.reset(cliNode);
if (!Instance()->m_ipcClient) {
qWarning() << "IpcClient is not ready!";
}
Instance()->m_ipcClient->waitForSource(1000);
if (!Instance()->m_ipcClient->isReplicaValid()) {
qWarning() << "IpcClient replica is not connected!";
}
auto t2sNode = Instance()->m_ClientNode.acquire<IpcProcessTun2SocksReplica>();
t2sNode->waitForSource(5000);
Instance()->m_Tun2SocksClient.reset(t2sNode);
if (!Instance()->m_Tun2SocksClient) {
qWarning() << "IpcClient::m_Tun2SocksClient is not ready!";
}
Instance()->m_Tun2SocksClient->waitForSource(1000);
if (!Instance()->m_Tun2SocksClient->isReplicaValid()) {
qWarning() << "IpcClient::m_Tun2SocksClient replica is not connected!";
}
});
connect(Instance()->m_localSocket, &QLocalSocket::disconnected,
[instance]() { instance->m_isSocketConnected = false; });
Instance()->m_localSocket->connectToServer(amnezia::getIpcServiceUrl());
Instance()->m_localSocket->waitForConnected();
if (!Instance()->m_ipcClient) {
qDebug() << "IpcClient::init failed";
return false;
}
qDebug() << "IpcClient::init succeed";
return (Instance()->m_ipcClient->isReplicaValid() && Instance()->m_Tun2SocksClient->isReplicaValid());
m_localSocket.connectToServer(amnezia::getIpcServiceUrl());
return m_localSocket.waitForConnected();
}
QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
{
if (!Instance()->m_ipcClient || !Instance()->m_ipcClient->isReplicaValid()) {
qWarning() << "IpcClient::createPrivilegedProcess : IpcClient IpcClient replica is not valid";
QSharedPointer<IpcInterfaceReplica> rep = Interface();
if (!rep) {
qCritical() << "IpcClient::createPrivilegedProcess: Replica is invalid";
return nullptr;
}
QRemoteObjectPendingReply<int> futureResult = Instance()->m_ipcClient->createPrivilegedProcess();
futureResult.waitForFinished(5000);
QRemoteObjectPendingReply<int> pidReply = rep->createPrivilegedProcess();
if (!pidReply.waitForFinished(5000)){
qCritical() << "IpcClient::createPrivilegedProcess: Failed to execute RO createPrivilegedProcess call";
return nullptr;
}
int pid = futureResult.returnValue();
auto pd = QSharedPointer<ProcessDescriptor>(new ProcessDescriptor());
Instance()->m_processNodes.insert(pid, pd);
int pid = pidReply.returnValue();
QSharedPointer<ProcessDescriptor> pd(new ProcessDescriptor());
pd->localSocket.reset(new QLocalSocket(pd->replicaNode.data()));
@@ -110,6 +98,7 @@ QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
pd->replicaNode->addClientSideConnection(pd->localSocket.data());
IpcProcessInterfaceReplica *repl = pd->replicaNode->acquire<IpcProcessInterfaceReplica>();
// TODO: rework the unsafe cast below
PrivilegedProcess *priv = static_cast<PrivilegedProcess *>(repl);
pd->ipcProcess.reset(priv);
if (!pd->ipcProcess) {
@@ -124,8 +113,12 @@ QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
[pd]() { pd->replicaNode->deleteLater(); });
}
});
pd->localSocket->connectToServer(amnezia::getIpcProcessUrl(pid));
pd->localSocket->waitForConnected();
if (!pd->localSocket->waitForConnected()) {
qCritical() << "IpcClient::createPrivilegedProcess: Failed to connect to process' socket";
return nullptr;
}
auto processReplica = QSharedPointer<PrivilegedProcess>(pd->ipcProcess);
return processReplica;
+39 -16
View File
@@ -4,7 +4,6 @@
#include <QLocalSocket>
#include <QObject>
#include "ipc.h"
#include "rep_ipc_interface_replica.h"
#include "rep_ipc_process_tun2socks_replica.h"
@@ -14,26 +13,53 @@ class IpcClient : public QObject
{
Q_OBJECT
public:
explicit IpcClient(QObject *parent = nullptr);
explicit IpcClient(QObject *parent = nullptr);
static IpcClient *Instance();
static bool init(IpcClient *instance);
static QSharedPointer<IpcInterfaceReplica> Interface();
static QSharedPointer<IpcProcessTun2SocksReplica> InterfaceTun2Socks();
static QSharedPointer<PrivilegedProcess> CreatePrivilegedProcess();
static IpcClient *Instance();
bool isSocketConnected() const;
static QSharedPointer<IpcInterfaceReplica> Interface();
static QSharedPointer<IpcProcessTun2SocksReplica> InterfaceTun2Socks();
static QSharedPointer<PrivilegedProcess> CreatePrivilegedProcess();
template <typename Func>
static auto withInterface(Func func)
{
QSharedPointer<IpcInterfaceReplica> iface = Instance()->m_ipcClient;
using ReturnType = decltype(func(std::declval<QSharedPointer<IpcInterfaceReplica>>()));
if (iface.isNull() || !iface->isReplicaValid()) {
qWarning() << "IpcClient::withInterface(): Service is not running";
if constexpr (std::is_void_v<ReturnType>)
return;
else
return ReturnType{};
}
return func(iface);
}
template <typename OnSuccess, typename OnFailure>
static auto withInterface(OnSuccess onSuccess, OnFailure onFailure)
{
QSharedPointer<IpcInterfaceReplica> iface = Instance()->m_ipcClient;
if (iface.isNull() || !iface->isReplicaValid()) {
return onFailure();
}
return onSuccess(iface);
}
bool isSocketConnected() const;
signals:
private:
~IpcClient() override;
bool establishConnection();
QRemoteObjectNode m_ClientNode;
QRemoteObjectNode m_Tun2SocksNode;
QLocalSocket m_localSocket;
QSharedPointer<QRemoteObjectNode> m_ClientNode;
QSharedPointer<IpcInterfaceReplica> m_ipcClient;
QPointer<QLocalSocket> m_localSocket;
QPointer<QLocalSocket> m_tun2socksSocket;
QSharedPointer<IpcProcessTun2SocksReplica> m_Tun2SocksClient;
struct ProcessDescriptor {
@@ -47,10 +73,7 @@ private:
QSharedPointer<QLocalSocket> localSocket;
};
QMap<int, QSharedPointer<ProcessDescriptor>> m_processNodes;
bool m_isSocketConnected {false};
static IpcClient *m_instance;
};
#endif // IPCCLIENT_H
+40 -18
View File
@@ -1,11 +1,12 @@
#include "networkUtilities.h"
#include <QtNetwork/qnetworkinterface.h>
#include <cstddef>
#ifdef Q_OS_WIN
#include <windows.h>
#include <Ipexport.h>
#include <Ws2tcpip.h>
#include <ws2ipdef.h>
#include <stdint.h>
#include <Iphlpapi.h>
#include <Iptypes.h>
#include <WinSock2.h>
@@ -23,13 +24,22 @@
#include <sys/socket.h>
#include <unistd.h>
#endif
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS)
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
#include <sys/param.h>
#include <sys/sysctl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/route.h>
#include <ifaddrs.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <ifaddrs.h>
#include <net/if.h>
#endif
#include <QHostAddress>
@@ -170,7 +180,7 @@ int NetworkUtilities::AdapterIndexTo(const QHostAddress& dst) {
#ifdef Q_OS_WIN
qDebug() << "Getting Current Internet Adapter that routes to"
<< dst.toString();
quint32_be ipBigEndian;
quint32 ipBigEndian;
quint32 ip = dst.toIPv4Address();
qToBigEndian(ip, &ipBigEndian);
_MIB_IPFORWARDROW routeInfo;
@@ -239,12 +249,14 @@ DWORD GetAdaptersAddressesWrapper(const ULONG Family,
}
#endif
QString NetworkUtilities::getGatewayAndIface()
QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
{
#ifdef Q_OS_WIN
constexpr int BUFF_LEN = 100;
char buff[BUFF_LEN] = {'\0'};
QString result;
QString resGateway;
int resIndex = -1;
PIP_ADAPTER_ADDRESSES pAdapterAddresses = nullptr;
DWORD dwRetVal =
@@ -252,7 +264,7 @@ QString NetworkUtilities::getGatewayAndIface()
if (dwRetVal != NO_ERROR) {
qDebug() << "ipv4 stack detect GetAdaptersAddresses failed.";
return "";
return {};
}
PIP_ADAPTER_ADDRESSES pCurAddress = pAdapterAddresses;
@@ -267,7 +279,9 @@ QString NetworkUtilities::getGatewayAndIface()
struct sockaddr_in addr;
if (inet_pton(AF_INET, buff, &addr.sin_addr) == 1) {
qDebug() << "this is true v4 !";
result = gw;
resGateway = gw;
resIndex = pCurAddress->IfIndex;
}
}
}
@@ -275,7 +289,7 @@ QString NetworkUtilities::getGatewayAndIface()
}
free(pAdapterAddresses);
return result;
return { resGateway, QNetworkInterface::interfaceFromIndex(resIndex) };
#endif
#ifdef Q_OS_LINUX
constexpr int BUFFER_SIZE = 100;
@@ -292,7 +306,7 @@ QString NetworkUtilities::getGatewayAndIface()
if ((sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) {
perror("socket failed");
return "";
return {};
}
memset(msgbuf, 0, sizeof(msgbuf));
@@ -316,7 +330,7 @@ QString NetworkUtilities::getGatewayAndIface()
/* send msg */
if (send(sock, nlmsg, nlmsg->nlmsg_len, 0) < 0) {
perror("send failed");
return "";
return {};
}
/* receive response */
@@ -325,7 +339,7 @@ QString NetworkUtilities::getGatewayAndIface()
received_bytes = recv(sock, ptr, sizeof(buffer) - msg_len, 0);
if (received_bytes < 0) {
perror("Error in recv");
return "";
return {};
}
nlh = (struct nlmsghdr *) ptr;
@@ -335,7 +349,7 @@ QString NetworkUtilities::getGatewayAndIface()
(nlmsg->nlmsg_type == NLMSG_ERROR))
{
perror("Error in received packet");
return "";
return {};
}
/* If we received all data break */
@@ -388,10 +402,12 @@ QString NetworkUtilities::getGatewayAndIface()
}
}
close(sock);
return gateway_address;
return { gateway_address, QNetworkInterface::interfaceFromName(interface) };
#endif
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS)
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
QString gateway;
int index = -1;
int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_FLAGS, RTF_GATEWAY};
int afinet_type[] = {AF_INET, AF_INET6};
@@ -401,17 +417,17 @@ QString NetworkUtilities::getGatewayAndIface()
size_t needed = 0;
if (sysctl(mib, sizeof(mib) / sizeof(int), nullptr, &needed, nullptr, 0) < 0)
return "";
return {};
char* buf;
if ((buf = new char[needed]) == 0)
return "";
return {};
if (sysctl(mib, sizeof(mib) / sizeof(int), buf, &needed, nullptr, 0) < 0)
{
qDebug() << "sysctl: net.route.0.0.dump";
delete[] buf;
return gateway;
return {};
}
struct rt_msghdr* rt;
@@ -449,7 +465,10 @@ QString NetworkUtilities::getGatewayAndIface()
&(reinterpret_cast<struct sockaddr_in*>(sa_tab[RTAX_GATEWAY]))->sin_addr,
sizeof(struct in_addr));
if (inet_ntop(AF_INET, srcStr4, dstStr4, INET_ADDRSTRLEN) != nullptr)
{
gateway = dstStr4;
index = rt->rtm_index;
}
break;
}
}
@@ -463,7 +482,10 @@ QString NetworkUtilities::getGatewayAndIface()
&(reinterpret_cast<struct sockaddr_in6*>(sa_tab[RTAX_GATEWAY]))->sin6_addr,
sizeof(struct in6_addr));
if (inet_ntop(AF_INET6, srcStr6, dstStr6, INET6_ADDRSTRLEN) != nullptr)
{
gateway = dstStr6;
index = rt->rtm_index;
}
break;
}
}
@@ -472,6 +494,6 @@ QString NetworkUtilities::getGatewayAndIface()
free(buf);
}
return gateway;
return { gateway, QNetworkInterface::interfaceFromIndex(index) };
#endif
}
+2 -2
View File
@@ -6,7 +6,7 @@
#include <QString>
#include <QHostAddress>
#include <QNetworkReply>
#include <QtNetwork/qnetworkinterface.h>
class NetworkUtilities : public QObject
{
@@ -17,7 +17,7 @@ public:
static bool checkIPv4Format(const QString &ip);
static bool checkIpSubnetFormat(const QString &ip);
static bool checkIpv6Enabled();
static QString getGatewayAndIface();
static QPair<QString, QNetworkInterface> getGatewayAndIface();
// Returns the Interface Index that could Route to dst
static int AdapterIndexTo(const QHostAddress& dst);
+194
View File
@@ -0,0 +1,194 @@
#include "osSignalHandler.h"
#include <QCoreApplication>
#include <QMetaObject>
#include <QSocketNotifier>
#include "../amnezia_application.h"
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
#include <pthread.h>
#include <signal.h>
#include <sys/signalfd.h>
#include <unistd.h>
#elif defined(Q_OS_MACOS)
#include <fcntl.h>
#include <signal.h>
#include <unistd.h>
#endif
#ifdef Q_OS_WIN
#include <QAbstractNativeEventFilter>
#include <windows.h>
#endif
namespace
{
static bool initialized = false;
#ifdef Q_OS_WIN
class WindowsCloseFilter : public QAbstractNativeEventFilter
{
public:
bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result) override
{
MSG *msg = static_cast<MSG *>(message);
switch (msg->message) {
case WM_CLOSE: {
const HWND active = GetActiveWindow();
const HWND self = msg->hwnd;
if (active != self) {
AmneziaApplication *app = qobject_cast<AmneziaApplication *>(QCoreApplication::instance());
if (app) {
QMetaObject::invokeMethod(app, "forceQuit", Qt::QueuedConnection);
}
}
}
}
return false;
};
};
static WindowsCloseFilter *windowsFilter = nullptr;
#endif
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
static int signalFd = -1;
static QSocketNotifier *socketNotifier = nullptr;
static void setupUnixSignalHandler()
{
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGINT);
sigaddset(&set, SIGTERM);
pthread_sigmask(SIG_BLOCK, &set, nullptr);
signalFd = signalfd(-1, &set, SFD_NONBLOCK | SFD_CLOEXEC);
if (signalFd < 0)
return;
socketNotifier = new QSocketNotifier(signalFd, QSocketNotifier::Read, QCoreApplication::instance());
QObject::connect(socketNotifier, &QSocketNotifier::activated, QCoreApplication::instance(), [](int) {
signalfd_siginfo fdsi;
::read(signalFd, &fdsi, sizeof(fdsi));
if (fdsi.ssi_signo == SIGINT || fdsi.ssi_signo == SIGTERM) {
QCoreApplication::quit();
}
});
}
#elif defined(Q_OS_MACOS)
static int signalPipe[2] = { -1, -1 };
static QSocketNotifier *socketNotifier = nullptr;
static void macSignalHandler(int)
{
if (signalPipe[1] >= 0) {
const char ch = 1;
::write(signalPipe[1], &ch, sizeof(ch));
}
}
static void setupUnixSignalHandler()
{
if (::pipe(signalPipe) != 0)
return;
::fcntl(signalPipe[0], F_SETFL, O_NONBLOCK);
::fcntl(signalPipe[1], F_SETFL, O_NONBLOCK);
socketNotifier = new QSocketNotifier(signalPipe[0], QSocketNotifier::Read, QCoreApplication::instance());
QObject::connect(socketNotifier, &QSocketNotifier::activated, QCoreApplication::instance(), [](int) {
char buf[16];
::read(signalPipe[0], buf, sizeof(buf));
QCoreApplication::quit();
});
struct sigaction sa {};
sa.sa_handler = macSignalHandler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, nullptr);
sigaction(SIGTERM, &sa, nullptr);
}
#endif
static void cleanupUnixSignalHandler()
{
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
if (socketNotifier) {
socketNotifier->setEnabled(false);
socketNotifier->deleteLater();
socketNotifier = nullptr;
}
if (signalFd >= 0) {
::close(signalFd);
signalFd = -1;
}
#elif defined(Q_OS_MACOS)
struct sigaction sa {};
sa.sa_handler = SIG_DFL;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGINT, &sa, nullptr);
sigaction(SIGTERM, &sa, nullptr);
if (socketNotifier) {
socketNotifier->setEnabled(false);
socketNotifier->deleteLater();
socketNotifier = nullptr;
}
if (signalPipe[0] >= 0) {
::close(signalPipe[0]);
signalPipe[0] = -1;
}
if (signalPipe[1] >= 0) {
::close(signalPipe[1]);
signalPipe[1] = -1;
}
#endif
#ifdef Q_OS_WIN
if (windowsFilter) {
QCoreApplication::instance()->removeNativeEventFilter(windowsFilter);
delete windowsFilter;
windowsFilter = nullptr;
}
#endif
}
}
OsSignalHandler::OsSignalHandler(QObject *parent) : QObject(parent)
{
}
void OsSignalHandler::setup()
{
if (initialized)
return;
initialized = true;
#if (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) || defined(Q_OS_MACOS)
setupUnixSignalHandler();
#endif
#ifdef Q_OS_WIN
windowsFilter = new WindowsCloseFilter();
QCoreApplication::instance()->installNativeEventFilter(windowsFilter);
#endif
QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, [] { cleanupUnixSignalHandler(); });
}
+17
View File
@@ -0,0 +1,17 @@
#ifndef OSSIGNALHANDLER_H
#define OSSIGNALHANDLER_H
#include <QObject>
class OsSignalHandler : public QObject
{
Q_OBJECT
public:
static void setup();
private:
explicit OsSignalHandler(QObject *parent = nullptr);
static void handleSignal(int signal);
};
#endif // OSSIGNALHANDLER_H
+2 -1
View File
@@ -11,7 +11,8 @@ QString amnezia::scriptFolder(amnezia::DockerContainer container)
case DockerContainer::Cloak: return QLatin1String("openvpn_cloak");
case DockerContainer::ShadowSocks: return QLatin1String("openvpn_shadowsocks");
case DockerContainer::WireGuard: return QLatin1String("wireguard");
case DockerContainer::Awg: return QLatin1String("awg");
case DockerContainer::Awg2: return QLatin1String("awg");
case DockerContainer::Awg: return QLatin1String("awg_legacy");
case DockerContainer::Ipsec: return QLatin1String("ipsec");
case DockerContainer::Xray: return QLatin1String("xray");
@@ -21,6 +21,7 @@ namespace amnezia::serialization
namespace vless
{
QJsonObject Deserialize(const QString &vless, QString *alias, QString *errMessage);
const QString Serialize(const VlessServerObject &server, const QString &alias);
} // namespace vless
namespace ss
+19
View File
@@ -42,6 +42,25 @@ struct VMessServerObject
};
struct VlessServerObject
{
QString address;
QString id; // UUID
int port;
QString flow = "xtls-rprx-vision";
QString encryption = "none";
QString network = "tcp";
QString security = "reality";
QString serverName; // SNI
QString publicKey;
QString shortId;
QString fingerprint = "chrome";
QString spiderX = "";
JSONSTRUCT_COMPARE(VlessServerObject, address, id, port, flow, encryption)
JSONSTRUCT_REGISTER(VlessServerObject, F(address, id, port, flow, encryption, network, security, serverName, publicKey, shortId, fingerprint, spiderX))
};
namespace transfer
{
+61 -1
View File
@@ -252,5 +252,65 @@ QJsonObject Deserialize(const QString &str, QString *alias, QString *errMessage)
root["inbounds"] = QJsonArray { inbound };
return root;
}
} // namespace amnezia::serialization::vless
const QString Serialize(const VlessServerObject &server, const QString &alias)
{
QUrl url;
// Set basic URL components
url.setScheme("vless");
url.setUserInfo(server.id);
url.setHost(server.address);
url.setPort(server.port);
QUrlQuery query;
if (!server.network.isEmpty() && server.network != "tcp") {
query.addQueryItem("type", server.network);
}
if (!server.encryption.isEmpty()) {
query.addQueryItem("encryption", server.encryption);
}
if (!server.security.isEmpty() && server.security != "none") {
query.addQueryItem("security", server.security);
}
if (!server.flow.isEmpty() && (server.security == "xtls" || server.security == "reality")) {
query.addQueryItem("flow", server.flow);
}
if (!server.serverName.isEmpty()) {
query.addQueryItem("sni", server.serverName);
}
if (server.security == "reality") {
if (!server.fingerprint.isEmpty()) {
query.addQueryItem("fp", server.fingerprint);
}
if (!server.publicKey.isEmpty()) {
query.addQueryItem("pbk", server.publicKey);
}
if (!server.shortId.isEmpty()) {
query.addQueryItem("sid", server.shortId);
}
if (!server.spiderX.isEmpty()) {
query.addQueryItem("spiderX", server.spiderX);
}
}
url.setQuery(query);
if (!alias.isEmpty()) {
url.setFragment(alias);
}
return url.toString(QUrl::ComponentFormattingOption::FullyEncoded);
}
}
-12
View File
@@ -440,18 +440,6 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
if (!obj.value("I5").isNull()) {
config.m_specialJunk["I5"] = obj.value("I5").toString();
}
if (!obj.value("J1").isNull()) {
config.m_controlledJunk["J1"] = obj.value("J1").toString();
}
if (!obj.value("J2").isNull()) {
config.m_controlledJunk["J2"] = obj.value("J2").toString();
}
if (!obj.value("J3").isNull()) {
config.m_controlledJunk["J3"] = obj.value("J3").toString();
}
if (!obj.value("Itime").isNull()) {
config.m_specialHandshakeTimeout = obj.value("Itime").toString();
}
return true;
}
+2 -8
View File
@@ -101,10 +101,10 @@ QString InterfaceConfig::toWgConf(const QMap<QString, QString>& extra) const {
out << "MTU = " << m_deviceMTU << "\n";
}
if (!m_primaryDnsServer.isNull()) {
if (!m_primaryDnsServer.isEmpty()) {
QStringList dnsServers;
dnsServers.append(m_primaryDnsServer);
if (!m_secondaryDnsServer.isNull()) {
if (!m_secondaryDnsServer.isEmpty()) {
dnsServers.append(m_secondaryDnsServer);
}
// If the DNS is not the Gateway, it's a user defined DNS
@@ -152,12 +152,6 @@ QString InterfaceConfig::toWgConf(const QMap<QString, QString>& extra) const {
for (const QString& key : m_specialJunk.keys()) {
out << key << " = " << m_specialJunk[key] << "\n";
}
for (const QString& key : m_controlledJunk.keys()) {
out << key << " = " << m_controlledJunk[key] << "\n";
}
if (!m_specialHandshakeTimeout.isNull()) {
out << "Itime = " << m_specialHandshakeTimeout << "\n";
}
// If any extra config was provided, append it now.
for (const QString& key : extra.keys()) {
+1 -3
View File
@@ -8,7 +8,7 @@
#include <QList>
#include <QMap>
#include <QString>
#include <QMap>
#include "ipaddress.h"
class QJsonObject;
@@ -57,8 +57,6 @@ class InterfaceConfig {
QString m_underloadPacketMagicHeader;
QString m_transportPacketMagicHeader;
QMap<QString, QString> m_specialJunk;
QMap<QString, QString> m_controlledJunk;
QString m_specialHandshakeTimeout;
QJsonObject toJson() const;
QString toWgConf(
+14
View File
@@ -0,0 +1,14 @@
<svg width="24" height="24" viewBox="0 0 74 74" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_4_34)">
<path d="M55.5 12.3333H18.5C15.0942 12.3333 12.3333 15.0943 12.3333 18.5V55.5C12.3333 58.9058 15.0942 61.6667 18.5 61.6667H55.5C58.9057 61.6667 61.6666 58.9058 61.6666 55.5V18.5C61.6666 15.0943 58.9057 12.3333 55.5 12.3333Z" stroke="#CBCAC8" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M21.5833 24.6667H52.4167" stroke="#CBCAC8" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M21.5833 37H52.4167" stroke="#CBCAC8" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M21.5833 49.3333H40.0833" stroke="#CBCAC8" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
<circle cx="61.5" cy="12.5" r="15" fill="#FBB36B" stroke="#1C1D21" stroke-width="5"/>
</g>
<defs>
<clipPath id="clip0_4_34">
<rect width="74" height="74" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 982 B

+8
View File
@@ -0,0 +1,8 @@
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="#CBCAC8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<!-- Основа газеты -->
<rect x="4" y="4" width="16" height="16" rx="2"/>
<!-- Линии текста -->
<line x1="7" y1="8" x2="17" y2="8"/>
<line x1="7" y1="12" x2="17" y2="12"/>
<line x1="7" y1="16" x2="13" y2="16"/>
</svg>

After

Width:  |  Height:  |  Size: 410 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.9 KiB

+3
View File
@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 35 35" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="17.5" cy="17.5" r="15" fill="#FBB36B" stroke="#1C1D21" stroke-width="5"/>
</svg>

After

Width:  |  Height:  |  Size: 188 B

+26 -2
View File
@@ -32,17 +32,41 @@
<false/>
<key>UILaunchStoryboardName</key>
<string>AmneziaVPNLaunchScreen</string>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<true/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneClassName</key>
<string>UIWindowScene</string>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>QIOSWindowSceneDelegate</string>
</dict>
</array>
</dict>
</dict>
<key>UIRequiredDeviceCapabilities</key>
<array/>
<key>UIRequiresFullScreen</key>
<true/>
<false/>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationPortrait</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array/>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIUserInterfaceStyle</key>
<string>Light</string>
<key>com.wireguard.ios.app_group_id</key>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 336 B

After

Width:  |  Height:  |  Size: 682 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 593 B

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 593 B

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

+65 -3
View File
@@ -1,6 +1,68 @@
{
"info" : {
"author" : "xcode",
"version" : 1
"images": [
{
"idiom": "mac",
"size": "16x16",
"scale": "1x",
"filename": "16.png"
},
{
"idiom": "mac",
"size": "16x16",
"scale": "2x",
"filename": "16@2x.png"
},
{
"idiom": "mac",
"size": "32x32",
"scale": "1x",
"filename": "32.png"
},
{
"idiom": "mac",
"size": "32x32",
"scale": "2x",
"filename": "32@2x.png"
},
{
"idiom": "mac",
"size": "128x128",
"scale": "1x",
"filename": "128.png"
},
{
"idiom": "mac",
"size": "128x128",
"scale": "2x",
"filename": "128@2x.png"
},
{
"idiom": "mac",
"size": "256x256",
"scale": "1x",
"filename": "256.png"
},
{
"idiom": "mac",
"size": "256x256",
"scale": "2x",
"filename": "256@2x.png"
},
{
"idiom": "mac",
"size": "512x512",
"scale": "1x",
"filename": "512.png"
},
{
"idiom": "mac",
"size": "512x512",
"scale": "2x",
"filename": "512@2x.png"
}
],
"info": {
"version": 1,
"author": "xcode"
}
}
+172
View File
@@ -0,0 +1,172 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleAllowMixedLocalizations</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>${QT_INTERNAL_DOLLAR_VAR}{PRODUCT_NAME}</string>
<key>CFBundleExecutable</key>
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
<key>CFBundleVersion</key>
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
<key>NSHumanReadableCopyright</key>
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSApplicationCategoryType</key>
<string>public.app-category.utilities</string>
<key>LSMinimumSystemVersion</key>
<string>${MACOSX_DEPLOYMENT_TARGET}</string>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<key>com.wireguard.ios.app_group_id</key>
<string>group.org.amnezia.AmneziaVPN</string>
<key>NSCameraUsageDescription</key>
<string>Amnezia VPN needs access to the camera for reading QR-codes.</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<false/>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
<key>CFBundleIcons</key>
<dict/>
<key>UTImportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>Amnezia VPN config</string>
<key>UTTypeIconFiles</key>
<array/>
<key>UTTypeIdentifier</key>
<string>org.amnezia.AmneziaVPN.amnezia-config</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>vpn</string>
</array>
<key>public.mime-type</key>
<array>
<string>text/plain</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>WireGuard config</string>
<key>UTTypeIconFiles</key>
<array/>
<key>UTTypeIdentifier</key>
<string>org.amnezia.AmneziaVPN.wireguard-config</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>conf</string>
<string>cfg</string>
</array>
<key>public.mime-type</key>
<array>
<string>text/plain</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>OpenVPN config</string>
<key>UTTypeIconFiles</key>
<array/>
<key>UTTypeIdentifier</key>
<string>org.amnezia.AmneziaVPN.openvpn-config</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>ovpn</string>
</array>
<key>public.mime-type</key>
<array>
<string>text/plain</string>
</array>
</dict>
</dict>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>AmneziaVPN backup file</string>
<key>UTTypeIconFiles</key>
<array/>
<key>UTTypeIdentifier</key>
<string>org.amnezia.AmneziaVPN.backup-config</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>backup</string>
</array>
<key>public.mime-type</key>
<array>
<string>text/plain</string>
</array>
</dict>
</dict>
</array>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeName</key>
<string>Amnezia VPN config</string>
<key>LSHandlerRank</key>
<string>Alternate</string>
<key>LSItemContentTypes</key>
<array>
<string>org.amnezia.AmneziaVPN.amnezia-config</string>
<string>org.amnezia.AmneziaVPN.wireguard-config</string>
<string>org.amnezia.AmneziaVPN.openvpn-config</string>
<string>org.amnezia.AmneziaVPN.backup-config</string>
</array>
</dict>
</array>
<key>NSExtensions</key>
<array>
<dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.networkextension.packet-tunnel</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).PacketTunnelProvider</string>
</dict>
</array>
</dict>
</plist>
+27 -21
View File
@@ -2,34 +2,40 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.application-identifier</key>
<string>$(DEVELOPMENT_TEAM).$(APP_ID_MACOS)</string>
<key>com.apple.developer.networking.custom-protocol</key>
<true/>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>app-proxy-provider</string>
<string>packet-tunnel-provider</string>
<string>dns-settings</string>
<string>relay</string>
<string>content-filter-provider</string>
<string>dns-proxy</string>
</array>
<key>com.apple.developer.system-extension.install</key>
<true/>
<key>com.apple.developer.networking.vpn.api</key>
<array>
<string>allow-vpn</string>
</array>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>group.org.amnezia.AmneziaVPN</string>
</array>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>keychain-access-groups</key>
<array>
<string>$(DEVELOPMENT_TEAM).*</string>
</array>
<key>com.apple.developer.team-identifier</key>
<string>$(DEVELOPMENT_TEAM)</string>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>$(DEVELOPMENT_TEAM).$(GROUP_ID_MACOS)</string>
</array>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
</dict>
</plist>
@@ -2,41 +2,30 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.application-identifier</key>
<string>$(DEVELOPMENT_TEAM).$(NETEXT_ID_MACOS)</string>
<key>com.apple.developer.networking.custom-protocol</key>
<true/>
<key>com.apple.developer.networking.networkextension</key>
<array>
<string>dns-settings</string>
<string>relay</string>
<string>packet-tunnel-provider</string>
<string>content-filter-provider</string>
<string>dns-proxy</string>
<string>app-proxy-provider</string>
</array>
<key>keychain-access-groups</key>
<key>com.apple.developer.networking.vpn.api</key>
<array>
<string>$(DEVELOPMENT_TEAM).*</string>
<string>allow-vpn</string>
</array>
<key>com.apple.developer.team-identifier</key>
<string>$(DEVELOPMENT_TEAM)</string>
<key>com.apple.developer.system-extension.install</key>
<true/>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.application-groups</key>
<array>
<string>$(DEVELOPMENT_TEAM).$(GROUP_ID_MACOS)</string>
<string>group.org.amnezia.AmneziaVPN</string>
</array>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.private.network.socket-delegate</key>
<true/>
</dict>
</plist>
@@ -0,0 +1,138 @@
enable_language(Swift)
message("Client message >> macos build >> AmneziaVPNNetworkExtension")
set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../..)
add_executable(AmneziaVPNNetworkExtension)
message("executable_path is: @executable_path/../../Frameworks")
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 "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 ${CMAKE_CURRENT_SOURCE_DIR}/AmneziaVPNNetworkExtension.entitlements
XCODE_ATTRIBUTE_MARKETING_VERSION "${APP_MAJOR_VERSION}"
XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION "${BUILD_ID}"
XCODE_ATTRIBUTE_PRODUCT_NAME "AmneziaVPNNetworkExtension"
XCODE_ATTRIBUTE_APPLICATION_EXTENSION_API_ONLY "YES"
XCODE_ATTRIBUTE_ENABLE_BITCODE "NO"
XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET "11.0"
XCODE_ATTRIBUTE_INFOPLIST_FILE ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../../../../Frameworks @loader_path/../../../../Frameworks"
)
if(DEPLOY)
message("DEPLOY is ON")
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
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "distr macos.org.amnezia.amneziaVPN.NE"
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "dev macos.org.amnezia.amneziaVPN.NE"
)
else()
set_target_properties(AmneziaVPNNetworkExtension PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
)
endif()
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"
XCODE_ATTRIBUTE_SWIFT_OPTIMIZATION_LEVEL "-Onone"
XCODE_ATTRIBUTE_SWIFT_PRECOMPILE_BRIDGING_HEADER "NO"
)
set_target_properties("AmneziaVPNNetworkExtension" PROPERTIES
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "X7UJ388FXK"
)
find_library(FW_ASSETS_LIBRARY AssetsLibrary)
find_library(FW_MOBILE_CORE MobileCoreServices)
find_library(FW_UI_KIT UIKit)
find_library(FW_LIBRESOLV libresolv.9.tbd)
# Set the root directory
set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../..)
target_link_libraries(AmneziaVPNNetworkExtension PRIVATE ${FW_LIBRESOLV})
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(AmneziaVPNNetworkExtension PRIVATE
${WG_APPLE_SOURCE_DIR}/WireGuardKit/WireGuardAdapter.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/PacketTunnelSettingsGenerator.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/DNSResolver.swift
${WG_APPLE_SOURCE_DIR}/WireGuardNetworkExtension/ErrorNotifier.swift
${WG_APPLE_SOURCE_DIR}/Shared/Keychain.swift
${WG_APPLE_SOURCE_DIR}/Shared/Model/TunnelConfiguration+WgQuickConfig.swift
${WG_APPLE_SOURCE_DIR}/Shared/Model/NETunnelProviderProtocol+Extension.swift
${WG_APPLE_SOURCE_DIR}/Shared/Model/String+ArrayConversion.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/TunnelConfiguration.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/IPAddressRange.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/Endpoint.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/DNSServer.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/InterfaceConfiguration.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/PeerConfiguration.swift
${WG_APPLE_SOURCE_DIR}/Shared/FileManager+Extension.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKitC/x25519.c
${WG_APPLE_SOURCE_DIR}/WireGuardKit/Array+ConcurrentMap.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/IPAddress+AddrInfo.swift
${WG_APPLE_SOURCE_DIR}/WireGuardKit/PrivateKey.swift
${CLIENT_ROOT_DIR}/platforms/ios/HevSocksTunnel.swift
${CLIENT_ROOT_DIR}/platforms/ios/NELogController.swift
${CLIENT_ROOT_DIR}/platforms/ios/Log.swift
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider.swift
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+WireGuard.swift
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+OpenVPN.swift
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+Xray.swift
${CLIENT_ROOT_DIR}/platforms/ios/WGConfig.swift
${CLIENT_ROOT_DIR}/platforms/ios/iosglue.mm
${CLIENT_ROOT_DIR}/platforms/ios/XrayConfig.swift
)
target_sources(AmneziaVPNNetworkExtension PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/PrivacyInfo.xcprivacy
)
set_property(TARGET AmneziaVPNNetworkExtension APPEND PROPERTY RESOURCE
${CMAKE_CURRENT_SOURCE_DIR}/PrivacyInfo.xcprivacy
)
## Build wireguard-go-version.h
execute_process(
COMMAND go list -m golang.zx2c4.com/wireguard
WORKING_DIRECTORY ${CLIENT_ROOT_DIR}/3rd/wireguard-apple/Sources/WireGuardKitGo
OUTPUT_VARIABLE WG_VERSION_FULL
)
string(REGEX REPLACE ".*v\([0-9.]*\).*" "\\1" WG_VERSION_STRING 1.1.1)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/wireguard-go-version.h.in
${CMAKE_CURRENT_BINARY_DIR}/wireguard-go-version.h)
target_sources(AmneziaVPNNetworkExtension PRIVATE
${CMAKE_CURRENT_BINARY_DIR}/wireguard-go-version.h)
target_include_directories(AmneziaVPNNetworkExtension PRIVATE ${CLIENT_ROOT_DIR})
target_include_directories(AmneziaVPNNetworkExtension PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
target_link_libraries(AmneziaVPNNetworkExtension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/wireguard/macos/universal2/libwg-go.a)
message(${CLIENT_ROOT_DIR})
message(${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/xray/HevSocks5Tunnel.xcframework/macos-arm64_x86_64/libhev-socks5-tunnel.a)
target_link_libraries(AmneziaVPNNetworkExtension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/xray/HevSocks5Tunnel.xcframework/macos-arm64_x86_64/libhev-socks5-tunnel.a)
target_include_directories(AmneziaVPNNetworkExtension PRIVATE ${CLIENT_ROOT_DIR}/3rd-prebuilt/3rd-prebuilt/xray/HevSocks5Tunnel.xcframework/macos-arm64_x86_64/Headers)
@@ -3,27 +3,32 @@
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>AmneziaVPNNetworkExtension</string>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<string>AmneziaVPNNetworkExtension</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<string>org.amnezia.AmneziaVPN.network-extension</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<string>AmneziaVPNNetworkExtension</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<string>${APPLE_PROJECT_VERSION}</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<string>${CMAKE_PROJECT_VERSION_TWEAK}</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<string>${CMAKE_OSX_DEPLOYMENT_TARGET}</string>
<key>CFBundleDisplayName</key>
<string>AmneziaVPNNetworkExtension</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
@@ -31,5 +36,11 @@
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).PacketTunnelProvider</string>
</dict>
<key>com.wireguard.ios.app_group_id</key>
<string>group.org.amnezia.AmneziaVPN</string>
<key>com.wireguard.macos.app_group_id</key>
<string>${BUILD_VPN_DEVELOPMENT_TEAM}.group.org.amnezia.AmneziaVPN</string>
</dict>
</plist>
@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>1C8F.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
</array>
</dict>
</plist>
@@ -1,10 +1,10 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "macos/gobridge/wireguard.h"
#include "wireguard-go-version.h"
#include "3rd/awg-apple/Sources/WireGuardKitC/WireGuardKitC.h"
#include "3rd/amneziawg-apple/Sources/WireGuardKitGo/wireguard.h"
#include "3rd/amneziawg-apple/Sources/WireGuardKitC/WireGuardKitC.h"
#include <stdbool.h>
#include <stdint.h>
@@ -23,3 +23,8 @@ bool key_from_hex(uint8_t key[WG_KEY_LEN], const char* hex);
bool key_eq(const uint8_t key1[WG_KEY_LEN], const uint8_t key2[WG_KEY_LEN]);
void write_msg_to_log(const char* tag, const char* msg);
// init function definition in C
void hev_socks5_tunnel_quit(void);
// Updated function definition in C
int hev_socks5_tunnel_main(const char* configFile, int fd);
@@ -0,0 +1,3 @@
#ifndef WIREGUARD_GO_VERSION
#define WIREGUARD_GO_VERSION "@WG_VERSION_STRING@"
#endif // WIREGUARD_GO_VERSION
+4 -2
View File
@@ -2,6 +2,7 @@
#include <QTimer>
#include "amnezia_application.h"
#include "core/osSignalHandler.h"
#include "migrations.h"
#include "version.h"
@@ -15,7 +16,7 @@
#include "platforms/ios/QtAppDelegate-C-Interface.h"
#endif
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
bool isAnotherInstanceRunning()
{
QLocalSocket socket;
@@ -44,8 +45,9 @@ int main(int argc, char *argv[])
#endif
AmneziaApplication app(argc, argv);
OsSignalHandler::setup();
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
if (isAnotherInstanceRunning()) {
QTimer::singleShot(1000, &app, [&]() { app.quit(); });
return app.exec();
+17 -18
View File
@@ -5,6 +5,9 @@
#include <stdint.h>
#include <QCoreApplication>
#include <QDateTime>
#include <QDebug>
#include <QDir>
#include <QFileInfo>
#include <QHostAddress>
@@ -12,12 +15,13 @@
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include <QLocalSocket>
#include <QObject>
#include <QStandardPaths>
#include <QTimer>
#include "ipaddress.h"
#include "leakdetector.h"
#include "logger.h"
#include "models/server.h"
#include "daemon/daemonerrors.h"
#include "protocols/protocols_defs.h"
@@ -115,7 +119,6 @@ void LocalSocketController::daemonConnected() {
}
void LocalSocketController::activate(const QJsonObject &rawConfig) {
QString protocolName = rawConfig.value("protocol").toString();
int splitTunnelType = rawConfig.value("splitTunnelType").toInt();
@@ -132,13 +135,16 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
// json.insert("hopindex", QJsonValue((double)hop.m_hopindex));
json.insert("privateKey", wgConfig.value(amnezia::config_key::client_priv_key));
json.insert("deviceIpv4Address", wgConfig.value(amnezia::config_key::client_ip));
m_deviceIpv4 = wgConfig.value(amnezia::config_key::client_ip).toString();
// set up IPv6 unique-local-address, ULA, with "fd00::/8" prefix, not globally routable.
// this will be default IPv6 gateway, OS recognizes that IPv6 link is local and switches to IPv4.
// Otherwise some OSes (Linux) try IPv6 forever and hang.
// https://en.wikipedia.org/wiki/Unique_local_address (RFC 4193)
// https://man7.org/linux/man-pages/man5/gai.conf.5.html
json.insert("deviceIpv6Address", "fd58:baa6:dead::1"); // simply "dead::1" is globally-routable, don't use it
// simply "dead::1" is globally-routable, don't use it
json.insert("deviceIpv6Address", "fd58:baa6:dead::1");
json.insert("serverPublicKey", wgConfig.value(amnezia::config_key::server_pub_key));
json.insert("serverPskKey", wgConfig.value(amnezia::config_key::psk_key));
@@ -220,7 +226,6 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
json.insert("allowedIPAddressRanges", jsAllowedIPAddesses);
QJsonArray jsExcludedAddresses;
jsExcludedAddresses.append(wgConfig.value(amnezia::config_key::hostName));
if (splitTunnelType == 2) {
@@ -255,10 +260,6 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
json.insert(amnezia::config_key::specialJunk3, wgConfig.value(amnezia::config_key::specialJunk3));
json.insert(amnezia::config_key::specialJunk4, wgConfig.value(amnezia::config_key::specialJunk4));
json.insert(amnezia::config_key::specialJunk5, wgConfig.value(amnezia::config_key::specialJunk5));
json.insert(amnezia::config_key::controlledJunk1, wgConfig.value(amnezia::config_key::controlledJunk1));
json.insert(amnezia::config_key::controlledJunk2, wgConfig.value(amnezia::config_key::controlledJunk2));
json.insert(amnezia::config_key::controlledJunk3, wgConfig.value(amnezia::config_key::controlledJunk3));
json.insert(amnezia::config_key::specialHandshakeTimeout, wgConfig.value(amnezia::config_key::specialHandshakeTimeout));
} else if (!wgConfig.value(amnezia::config_key::junkPacketCount).isUndefined()
&& !wgConfig.value(amnezia::config_key::junkPacketMinSize).isUndefined()
&& !wgConfig.value(amnezia::config_key::junkPacketMaxSize).isUndefined()
@@ -274,11 +275,7 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
&& !wgConfig.value(amnezia::config_key::specialJunk2).isUndefined()
&& !wgConfig.value(amnezia::config_key::specialJunk3).isUndefined()
&& !wgConfig.value(amnezia::config_key::specialJunk4).isUndefined()
&& !wgConfig.value(amnezia::config_key::specialJunk5).isUndefined()
&& !wgConfig.value(amnezia::config_key::controlledJunk1).isUndefined()
&& !wgConfig.value(amnezia::config_key::controlledJunk2).isUndefined()
&& !wgConfig.value(amnezia::config_key::controlledJunk3).isUndefined()
&& !wgConfig.value(amnezia::config_key::specialHandshakeTimeout).isUndefined()) {
&& !wgConfig.value(amnezia::config_key::specialJunk5).isUndefined()) {
json.insert(amnezia::config_key::junkPacketCount, wgConfig.value(amnezia::config_key::junkPacketCount));
json.insert(amnezia::config_key::junkPacketMinSize, wgConfig.value(amnezia::config_key::junkPacketMinSize));
json.insert(amnezia::config_key::junkPacketMaxSize, wgConfig.value(amnezia::config_key::junkPacketMaxSize));
@@ -295,10 +292,6 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
json.insert(amnezia::config_key::specialJunk3, wgConfig.value(amnezia::config_key::specialJunk3));
json.insert(amnezia::config_key::specialJunk4, wgConfig.value(amnezia::config_key::specialJunk4));
json.insert(amnezia::config_key::specialJunk5, wgConfig.value(amnezia::config_key::specialJunk5));
json.insert(amnezia::config_key::controlledJunk1, wgConfig.value(amnezia::config_key::controlledJunk1));
json.insert(amnezia::config_key::controlledJunk2, wgConfig.value(amnezia::config_key::controlledJunk2));
json.insert(amnezia::config_key::controlledJunk3, wgConfig.value(amnezia::config_key::controlledJunk3));
json.insert(amnezia::config_key::specialHandshakeTimeout, wgConfig.value(amnezia::config_key::specialHandshakeTimeout));
}
write(json);
@@ -449,6 +442,7 @@ void LocalSocketController::parseCommand(const QByteArray& command) {
}
if (type == "status") {
QJsonValue serverIpv4Gateway = obj.value("serverIpv4Gateway");
if (!serverIpv4Gateway.isString()) {
logger.error() << "Unexpected serverIpv4Gateway value";
@@ -493,6 +487,11 @@ void LocalSocketController::parseCommand(const QByteArray& command) {
logger.debug() << "Handshake completed with:"
<< pubkey.toString();
checkStatus();
emit statusUpdated("", m_deviceIpv4, 0, 0);
emit connected(pubkey.toString());
return;
}
+2
View File
@@ -12,6 +12,7 @@
#include "controllerimpl.h"
class QJsonObject;
class LocalSocketController final : public ControllerImpl {
@@ -58,6 +59,7 @@ class LocalSocketController final : public ControllerImpl {
QByteArray m_buffer;
QString m_deviceIpv4;
std::function<void(const QString&)> m_logCallback = nullptr;
QTimer m_initializingTimer;
+22 -37
View File
@@ -11,7 +11,6 @@
#include "logger.h"
//#include "mozillavpn.h"
#include "networkwatcherimpl.h"
#include "platforms/dummy/dummynetworkwatcher.h"
//#include "settingsholder.h"
#ifdef MZ_WINDOWS
@@ -51,7 +50,7 @@ NetworkWatcher::NetworkWatcher() { MZ_COUNT_CTOR(NetworkWatcher); }
NetworkWatcher::~NetworkWatcher() { MZ_COUNT_DTOR(NetworkWatcher); }
void NetworkWatcher::initialize() {
logger.debug() << "Initialize";
logger.debug() << "Initialize NetworkWatcher";
#if defined(MZ_WINDOWS)
m_impl = new WindowsNetworkWatcher(this);
@@ -69,59 +68,45 @@ void NetworkWatcher::initialize() {
m_impl = new DummyNetworkWatcher(this);
#endif
connect(m_impl, &NetworkWatcherImpl::unsecuredNetwork, this,
&NetworkWatcher::unsecuredNetwork);
connect(m_impl, &NetworkWatcherImpl::networkChanged, this,
&NetworkWatcher::networkChange);
connect(m_impl, &NetworkWatcherImpl::sleepMode, this,
&NetworkWatcher::onSleepMode);
m_impl->initialize();
// TODO: IMPL FOR AMNEZIA
#if 0
SettingsHolder* settingsHolder = SettingsHolder::instance();
Q_ASSERT(settingsHolder);
m_active = settingsHolder->unsecuredNetworkAlert() ||
settingsHolder->captivePortalAlert();
m_reportUnsecuredNetwork = settingsHolder->unsecuredNetworkAlert();
if (m_active) {
// Enable sleep/wake monitoring for VPN auto-reconnection
logger.debug() << "Starting NetworkWatcher for sleep/wake monitoring";
logger.debug() << "About to call m_impl->start()";
try {
m_impl->start();
logger.debug() << "m_impl->start() completed successfully";
} catch (const std::exception& e) {
logger.error() << "Exception in m_impl->start():" << e.what();
} catch (...) {
logger.error() << "Unknown exception in m_impl->start()";
}
connect(settingsHolder, &SettingsHolder::unsecuredNetworkAlertChanged, this,
&NetworkWatcher::settingsChanged);
connect(settingsHolder, &SettingsHolder::captivePortalAlertChanged, this,
&NetworkWatcher::settingsChanged);
#endif
m_active = true;
m_reportUnsecuredNetwork = false; // Disable unsecured network alerts for Amnezia
}
void NetworkWatcher::settingsChanged() {
// TODO: IMPL FOR AMNEZIA
#if 0
SettingsHolder* settingsHolder = SettingsHolder::instance();
m_active = settingsHolder->unsecuredNetworkAlert() ||
settingsHolder->captivePortalAlert();
m_reportUnsecuredNetwork = settingsHolder->unsecuredNetworkAlert();
// For Amnezia: Keep NetworkWatcher always active for sleep/wake monitoring
logger.debug() << "NetworkWatcher settings changed - keeping sleep monitoring active";
}
if (m_active) {
logger.debug()
<< "Starting Network Watcher; Reporting of Unsecured Networks: "
<< m_reportUnsecuredNetwork;
m_impl->start();
} else {
logger.debug() << "Stopping Network Watcher";
m_impl->stop();
}
#endif
void NetworkWatcher::onSleepMode()
{
logger.debug() << "Resumed from sleep mode";
emit sleepMode();
}
void NetworkWatcher::unsecuredNetwork(const QString& networkName,
const QString& networkId) {
logger.debug() << "Unsecured network:" << logger.sensitive(networkName)
<< "id:" << logger.sensitive(networkId);
#ifndef UNIT_TEST
if (!m_reportUnsecuredNetwork) {
logger.debug() << "Disabled. Ignoring unsecured network";
+3
View File
@@ -29,10 +29,13 @@ public:
// false to restore.
void simulateDisconnection(bool simulatedDisconnection);
void onSleepMode();
QNetworkInformation::Reachability getReachability();
signals:
void networkChange();
void sleepMode();
private:
void settingsChanged();
+2
View File
@@ -41,6 +41,8 @@ signals:
// TODO: Only windows-networkwatcher has this, the other plattforms should
// too.
void networkChanged(QString newBSSID);
void sleepMode();
private:
bool m_active = false;
+5 -2
View File
@@ -41,6 +41,7 @@ void PingHelper::start(const QString& serverIpv4Gateway,
m_gateway = QHostAddress(serverIpv4Gateway);
m_source = QHostAddress(deviceIpv4Address.section('/', 0, 0));
m_pingSender = PingSenderFactory::create(m_source, this);
// Some platforms require root access to send and receive ICMP pings. If
@@ -53,8 +54,10 @@ void PingHelper::start(const QString& serverIpv4Gateway,
connect(m_pingSender, &PingSender::recvPing, this, &PingHelper::pingReceived,
Qt::QueuedConnection);
connect(m_pingSender, &PingSender::criticalPingError, this,
[]() { logger.info() << "Encountered Unrecoverable ping error"; });
connect(m_pingSender, &PingSender::criticalPingError, this, [this]() {
logger.info() << "Encountered Unrecoverable ping error";
emit connectionLose();
});
// Reset the ping statistics
m_sequence = 0;
+2
View File
@@ -33,6 +33,8 @@ class PingHelper final : public QObject {
signals:
void pingSentAndReceived(qint64 msec);
void connectionLose();
private:
void nextPing();
+10 -11
View File
@@ -5,27 +5,26 @@
#include "pingsenderfactory.h"
#if defined(MZ_LINUX) || defined(MZ_ANDROID)
//# include "platforms/linux/linuxpingsender.h"
# include "platforms/linux/linuxpingsender.h"
#elif defined(MZ_MACOS) || defined(MZ_IOS)
# include "platforms/macos/macospingsender.h"
# include "platforms/macos/macospingsender.h"
#elif defined(MZ_WINDOWS)
# include "platforms/windows/windowspingsender.h"
#elif defined(MZ_DUMMY) || defined(UNIT_TEST)
# include "platforms/dummy/dummypingsender.h"
# include "platforms/windows/windowspingsender.h"
#elif defined(MZ_WASM) || defined(UNIT_TEST)
# include "platforms/dummy/dummypingsender.h"
#else
# error "Unsupported platform"
# error "Unsupported platform"
#endif
PingSender* PingSenderFactory::create(const QHostAddress& source,
QObject* parent) {
#if defined(MZ_LINUX) || defined(MZ_ANDROID)
return nullptr;
// return new LinuxPingSender(source, parent);
return new LinuxPingSender(source, parent);
#elif defined(MZ_MACOS) || defined(MZ_IOS)
return new MacOSPingSender(source, parent);
return new MacOSPingSender(source, parent);
#elif defined(MZ_WINDOWS)
return new WindowsPingSender(source, parent);
return new WindowsPingSender(source, parent);
#else
return new DummyPingSender(source, parent);
return new DummyPingSender(source, parent);
#endif
}
+4 -3
View File
@@ -10,9 +10,10 @@ class QHostAddress;
class QObject;
class PingSenderFactory final {
public:
PingSenderFactory() = delete;
static PingSender* create(const QHostAddress& source, QObject* parent);
public:
PingSenderFactory() = delete;
static PingSender* create(const QHostAddress& source, QObject* parent);
};
#endif // PINGSENDERFACTORY_H
@@ -99,7 +99,9 @@ bool AndroidController::initialize()
{"onFileOpened", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onFileOpened)},
{"onConfigImported", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onConfigImported)},
{"onAuthResult", "(Z)V", reinterpret_cast<void *>(onAuthResult)},
{"decodeQrCode", "(Ljava/lang/String;)Z", reinterpret_cast<bool *>(decodeQrCode)}
{"decodeQrCode", "(Ljava/lang/String;)Z", reinterpret_cast<bool *>(decodeQrCode)},
{"onImeInsetsChanged", "(I)V", reinterpret_cast<void *>(onImeInsetsChanged)},
{"onSystemBarsInsetsChanged", "(II)V", reinterpret_cast<void *>(onSystemBarsInsetsChanged)}
};
QJniEnvironment env;
@@ -202,6 +204,21 @@ bool AndroidController::isOnTv()
return callActivityMethod<jboolean>("isOnTv", "()Z");
}
bool AndroidController::isEdgeToEdgeEnabled()
{
return callActivityMethod<jboolean>("isEdgeToEdgeEnabled", "()Z");
}
int AndroidController::getStatusBarHeight()
{
return callActivityMethod<jint>("getStatusBarHeight", "()I");
}
int AndroidController::getNavigationBarHeight()
{
return callActivityMethod<jint>("getNavigationBarHeight", "()I");
}
void AndroidController::startQrReaderActivity()
{
callActivityMethod("startQrCodeReader", "()V");
@@ -521,3 +538,23 @@ bool AndroidController::decodeQrCode(JNIEnv *env, jobject thiz, jstring data)
return ImportController::decodeQrCode(AndroidUtils::convertJString(env, data));
}
// static
void AndroidController::onImeInsetsChanged(JNIEnv *env, jobject thiz, jint heightDp)
{
Q_UNUSED(env);
Q_UNUSED(thiz);
qDebug() << "Android IME insets changed: height =" << heightDp << "dp";
emit AndroidController::instance()->imeInsetsChanged(heightDp);
}
// static
void AndroidController::onSystemBarsInsetsChanged(JNIEnv *env, jobject thiz, jint navBarHeightDp, jint statusBarHeightDp)
{
Q_UNUSED(env);
Q_UNUSED(thiz);
qDebug() << "Android system bars insets changed: nav bar =" << navBarHeightDp << "dp, status bar =" << statusBarHeightDp << "dp";
emit AndroidController::instance()->systemBarsInsetsChanged(navBarHeightDp, statusBarHeightDp);
}
@@ -39,6 +39,9 @@ public:
QString getFileName(const QString &uri);
bool isCameraPresent();
bool isOnTv();
bool isEdgeToEdgeEnabled();
int getStatusBarHeight();
int getNavigationBarHeight();
void startQrReaderActivity();
void setSaveLogs(bool enabled);
void exportLogsFile(const QString &fileName);
@@ -70,6 +73,8 @@ signals:
void importConfigFromOutside(QString config);
void initConnectionState(Vpn::ConnectionState state);
void authenticationResult(bool result);
void imeInsetsChanged(int heightDp);
void systemBarsInsetsChanged(int navBarHeightDp, int statusBarHeightDp);
private:
bool isWaitingStatus = true;
@@ -98,6 +103,8 @@ private:
static void onFileOpened(JNIEnv *env, jobject thiz, jstring uri);
static void onAuthResult(JNIEnv *env, jobject thiz, jboolean result);
static bool decodeQrCode(JNIEnv *env, jobject thiz, jstring data);
static void onImeInsetsChanged(JNIEnv *env, jobject thiz, jint heightDp);
static void onSystemBarsInsetsChanged(JNIEnv *env, jobject thiz, jint navBarHeightDp, jint statusBarHeightDp);
template <typename Ret, typename ...Args>
static auto callActivityMethod(const char *methodName, const char *signature, Args &&...args);
@@ -0,0 +1,82 @@
#import <UIKit/UIKit.h>
#import <objc/runtime.h>
#include <dispatch/dispatch.h>
#include <QByteArray>
#include <QFile>
#include <QString>
#include "ios_controller.h"
using SceneOpenURLContexts = void (*)(id, SEL, UIScene *, NSSet<UIOpenURLContext *> *);
static SceneOpenURLContexts g_originalSceneOpenURLContexts = nullptr;
static void amnezia_handleURL(NSURL *url)
{
if (!url || !url.isFileURL) {
return;
}
QString filePath(url.path.UTF8String);
if (filePath.isEmpty()) {
return;
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (filePath.contains("backup")) {
IosController::Instance()->importBackupFromOutside(filePath);
return;
}
QFile file(filePath);
if (!file.open(QIODevice::ReadOnly)) {
return;
}
const QByteArray data = file.readAll();
IosController::Instance()->importConfigFromOutside(QString::fromUtf8(data));
});
}
static void amnezia_scene_openURLContexts(id self, SEL _cmd, UIScene *scene, NSSet<UIOpenURLContext *> *contexts)
{
if (g_originalSceneOpenURLContexts) {
g_originalSceneOpenURLContexts(self, _cmd, scene, contexts);
}
if (!contexts || contexts.count == 0) {
return;
}
if (@available(iOS 13.0, *)) {
for (UIOpenURLContext *context in contexts) {
amnezia_handleURL(context.URL);
}
}
}
@interface AmneziaSceneDelegateHooks : NSObject
@end
@implementation AmneziaSceneDelegateHooks
+ (void)load
{
Class cls = objc_getClass("QIOSWindowSceneDelegate");
if (!cls) {
return;
}
SEL selector = @selector(scene:openURLContexts:);
Method method = class_getInstanceMethod(cls, selector);
if (method) {
g_originalSceneOpenURLContexts = reinterpret_cast<SceneOpenURLContexts>(method_getImplementation(method));
method_setImplementation(method, reinterpret_cast<IMP>(amnezia_scene_openURLContexts));
} else {
const char *types = "v@:@@";
class_addMethod(cls, selector, reinterpret_cast<IMP>(amnezia_scene_openURLContexts), types);
}
}
@end
+35 -3
View File
@@ -2,7 +2,8 @@ import Foundation
import os.log
struct Log {
static let osLog = Logger()
private static let subsystemIdentifier = Bundle.main.bundleIdentifier ?? "org.amnezia.AmneziaVPN"
static let osLog = Logger(subsystem: subsystemIdentifier, category: "App")
private static let IsLoggingEnabledKey = "IsLoggingEnabled"
static var isLoggingEnabled: Bool {
@@ -77,9 +78,40 @@ struct Log {
static func log(_ type: OSLogType, title: String = "", message: String, url: URL = neLogURL) {
NSLog("\(title) \(message)")
guard isLoggingEnabled else { return }
switch type {
case .debug:
if title.isEmpty {
osLog.debug("\(message, privacy: .public)")
} else {
osLog.debug("\(title, privacy: .public) \(message, privacy: .public)")
}
case .info:
if title.isEmpty {
osLog.info("\(message, privacy: .public)")
} else {
osLog.info("\(title, privacy: .public) \(message, privacy: .public)")
}
case .error:
if title.isEmpty {
osLog.error("\(message, privacy: .public)")
} else {
osLog.error("\(title, privacy: .public) \(message, privacy: .public)")
}
case .fault:
if title.isEmpty {
osLog.fault("\(message, privacy: .public)")
} else {
osLog.fault("\(title, privacy: .public) \(message, privacy: .public)")
}
default:
if title.isEmpty {
osLog.log("\(message, privacy: .public)")
} else {
osLog.log("\(title, privacy: .public) \(message, privacy: .public)")
}
}
osLog.log(level: type, "\(title) \(message)")
guard isLoggingEnabled else { return }
let date = Date()
let level = Record.Level(from: type)
+55 -1
View File
@@ -1,22 +1,76 @@
import Foundation
import os.log
private let subsystemIdentifier = Bundle.main.bundleIdentifier ?? "org.amnezia.AmneziaVPN"
private let wireGuardSystemLogger = Logger(subsystem: subsystemIdentifier, category: "WireGuard")
private let openVPNSystemLogger = Logger(subsystem: subsystemIdentifier, category: "OpenVPN")
private let xraySystemLogger = Logger(subsystem: subsystemIdentifier, category: "Xray")
private let networkExtensionLogger = Logger(subsystem: subsystemIdentifier, category: "NetworkExtension")
private func logToSystem(_ logger: Logger, type: OSLogType, prefix: String, title: String, message: String) {
let combinedTitle: String
if title.isEmpty {
combinedTitle = prefix
} else {
combinedTitle = "\(prefix): \(title)"
}
switch type {
case .debug:
if combinedTitle.isEmpty {
logger.debug("\(message, privacy: .public)")
} else {
logger.debug("\(combinedTitle, privacy: .public) \(message, privacy: .public)")
}
case .info:
if combinedTitle.isEmpty {
logger.info("\(message, privacy: .public)")
} else {
logger.info("\(combinedTitle, privacy: .public) \(message, privacy: .public)")
}
case .error:
if combinedTitle.isEmpty {
logger.error("\(message, privacy: .public)")
} else {
logger.error("\(combinedTitle, privacy: .public) \(message, privacy: .public)")
}
case .fault:
if combinedTitle.isEmpty {
logger.fault("\(message, privacy: .public)")
} else {
logger.fault("\(combinedTitle, privacy: .public) \(message, privacy: .public)")
}
default:
if combinedTitle.isEmpty {
logger.log("\(message, privacy: .public)")
} else {
logger.log("\(combinedTitle, privacy: .public) \(message, privacy: .public)")
}
}
}
public func wg_log(_ type: OSLogType, title: String = "", staticMessage: StaticString) {
neLog(type, title: "WG: \(title)", message: "\(staticMessage)")
let stringMessage = String(describing: staticMessage)
logToSystem(wireGuardSystemLogger, type: type, prefix: "WG", title: title, message: stringMessage)
neLog(type, title: "WG: \(title)", message: stringMessage)
}
public func wg_log(_ type: OSLogType, title: String = "", message: String) {
logToSystem(wireGuardSystemLogger, type: type, prefix: "WG", title: title, message: message)
neLog(type, title: "WG: \(title)", message: message)
}
public func ovpnLog(_ type: OSLogType, title: String = "", message: String) {
logToSystem(openVPNSystemLogger, type: type, prefix: "OVPN", title: title, message: message)
neLog(type, title: "OVPN: \(title)", message: message)
}
public func xrayLog(_ type: OSLogType, title: String = "", message: String) {
logToSystem(xraySystemLogger, type: type, prefix: "XRAY", title: title, message: message)
neLog(type, title: "XRAY: \(title)", message: message)
}
public func neLog(_ type: OSLogType, title: String = "", message: String) {
logToSystem(networkExtensionLogger, type: type, prefix: "NE", title: title, message: message)
Log.log(type, title: "NE: \(title)", message: message)
}
@@ -1,6 +1,7 @@
import Foundation
import NetworkExtension
import OpenVPNAdapter
import CryptoKit
struct OpenVPNConfig: Decodable {
let config: String
@@ -27,26 +28,83 @@ extension PacketTunnelProvider {
let ovpnConfiguration = Data(openVPNConfig.config.utf8)
setupAndlaunchOpenVPN(withConfig: ovpnConfiguration, completionHandler: completionHandler)
} catch {
ovpnLog(.error, message: "Can't parse config: \(error.localizedDescription)")
if let underlyingError = (error as NSError).userInfo[NSUnderlyingErrorKey] as? NSError {
ovpnLog(.error, message: "Can't parse config: \(underlyingError.localizedDescription)")
}
ovpnLog(.error, message: "Can't parse OpenVPN config: \(error.localizedDescription)")
return
}
}
private func logOpenVPNError(_ error: NSError) {
let fatalFlag = (error.userInfo[OpenVPNAdapterErrorFatalKey] as? Bool) ?? false
var lines: [String] = []
lines.append("domain=\(error.domain) code=\(error.code) fatal=\(fatalFlag)")
if let adapterMessage = error.userInfo[OpenVPNAdapterErrorMessageKey] as? String, !adapterMessage.isEmpty {
lines.append("message=\(adapterMessage)")
}
let userInfoKeys = error.userInfo.keys.map { String(describing: $0) }.sorted()
if !userInfoKeys.isEmpty {
lines.append("userInfoKeys=[\(userInfoKeys.joined(separator: ","))]")
}
if let underlying = error.userInfo[NSUnderlyingErrorKey] as? NSError {
lines.append("underlying=\(underlying.domain)#\(underlying.code) fatal=\((underlying.userInfo[OpenVPNAdapterErrorFatalKey] as? Bool) ?? false)")
if let underlyingMessage = underlying.userInfo[OpenVPNAdapterErrorMessageKey] as? String, !underlyingMessage.isEmpty {
lines.append("underlyingMessage=\(underlyingMessage)")
} else if !underlying.localizedDescription.isEmpty {
lines.append("underlyingLocalized=\(underlying.localizedDescription)")
}
} else if let underlying = error.userInfo[NSUnderlyingErrorKey] {
lines.append("underlyingRaw=\(underlying)")
}
let formatted = lines.joined(separator: "\n ")
ovpnLog(.error, title: "Error", message: formatted)
}
private func setupAndlaunchOpenVPN(withConfig ovpnConfiguration: Data,
withShadowSocks viaSS: Bool = false,
completionHandler: @escaping (Error?) -> Void) {
ovpnLog(.info, message: "Setup and launch")
let str = String(decoding: ovpnConfiguration, as: UTF8.self)
var configString = String(decoding: ovpnConfiguration, as: UTF8.self)
let digest = SHA256.hash(data: ovpnConfiguration)
let digestString = digest.map { String(format: "%02x", $0) }.joined()
ovpnLog(.info, title: "ConfigDigest", message: digestString)
let hasTlsAuthOpen = configString.contains("<tls-auth>")
let hasTlsAuthClose = configString.contains("</tls-auth>")
ovpnLog(.info, title: "ConfigFlags", message: "tls-auth open=\(hasTlsAuthOpen) close=\(hasTlsAuthClose)")
let lines = configString.split(separator: "\n")
let head = lines.prefix(10).joined(separator: "\n")
let tail = lines.suffix(10).joined(separator: "\n")
ovpnLog(.debug, title: "ConfigHead", message: head)
ovpnLog(.debug, title: "ConfigTail", message: tail)
if let start = configString.range(of: "<tls-auth>"),
let end = configString.range(of: "</tls-auth>", range: start.upperBound..<configString.endIndex) {
let keyBody = String(configString[start.upperBound..<end.lowerBound])
ovpnLog(.debug, title: "TLSAuthInline", message: keyBody)
let sanitizedLines = keyBody
.split(whereSeparator: { $0.isNewline })
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
.filter { !$0.isEmpty }
.filter { !$0.hasPrefix("#") }
let sanitizedKey = sanitizedLines.joined(separator: "\n")
ovpnLog(.debug, title: "TLSAuthSanitized", message: sanitizedKey)
let sanitizedBlock = "<tls-auth>\n\(sanitizedKey)\n</tls-auth>"
configString.replaceSubrange(start.lowerBound..<end.upperBound, with: sanitizedBlock)
}
let normalizedConfig = configString.replacingOccurrences(of: "\r\n", with: "\n")
let sanitizedData = Data(normalizedConfig.utf8)
let configuration = OpenVPNConfiguration()
configuration.fileContent = ovpnConfiguration
if str.contains("cloak") {
configuration.fileContent = sanitizedData
if configString.contains("cloak") {
configuration.setPTCloak()
}
@@ -57,6 +115,8 @@ extension PacketTunnelProvider {
evaluation = try ovpnAdapter?.apply(configuration: configuration)
} catch {
let nsError = error as NSError
ovpnLog(.error, title: "ApplyConfig", message: "domain=\(nsError.domain) code=\(nsError.code) info=\(nsError.userInfo)")
completionHandler(error)
return
}
@@ -71,9 +131,9 @@ extension PacketTunnelProvider {
}
startHandler = completionHandler
ovpnAdapter?.connect(using: packetFlow)
ovpnAdapter?.connect(using: openVPNPacketFlow())
}
func handleOpenVPNStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
guard let completionHandler = completionHandler else { return }
let bytesin = ovpnAdapter?.transportStatistics.bytesIn
@@ -93,7 +153,7 @@ extension PacketTunnelProvider {
}
func stopOpenVPN(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
ovpnLog(.info, message: "Stopping tunnel: reason: \(reason.description)")
ovpnLog(.info, message: "Stopping tunnel: reason: \(reason.amneziaDescription)")
stopHandler = completionHandler
if vpnReachability.isTracking {
@@ -208,8 +268,11 @@ extension PacketTunnelProvider: OpenVPNAdapterDelegate {
// Handle errors thrown by the OpenVPN library
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleError error: Error) {
let nsError = error as NSError
logOpenVPNError(nsError)
// Handle only fatal errors
guard let fatal = (error as NSError).userInfo[OpenVPNAdapterErrorFatalKey] as? Bool,
guard let fatal = nsError.userInfo[OpenVPNAdapterErrorFatalKey] as? Bool,
fatal == true else { return }
if vpnReachability.isTracking {
@@ -230,5 +293,3 @@ extension PacketTunnelProvider: OpenVPNAdapterDelegate {
ovpnLog(.info, message: logMessage)
}
}
extension NEPacketTunnelFlow: OpenVPNAdapterPacketFlow {}
@@ -112,9 +112,19 @@ extension PacketTunnelProvider {
}
}
let lastHandshakeString = settingsDictionary["last_handshake_time_sec"]
let lastHandshake: Int64
if let lastHandshakeValue = lastHandshakeString, let handshakeValue = Int64(lastHandshakeValue) {
lastHandshake = handshakeValue
} else {
lastHandshake = -2 // Return an error if there is no value for `last_handshake_time_sec`
}
let response: [String: Any] = [
"rx_bytes": settingsDictionary["rx_bytes"] ?? "0",
"tx_bytes": settingsDictionary["tx_bytes"] ?? "0"
"tx_bytes": settingsDictionary["tx_bytes"] ?? "0",
"last_handshake_time_sec": lastHandshake
]
completionHandler(try? JSONSerialization.data(withJSONObject: response, options: []))
@@ -166,7 +176,7 @@ extension PacketTunnelProvider {
}
func stopWireguard(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
wg_log(.info, message: "Stopping tunnel: reason: \(reason.description)")
wg_log(.info, message: "Stopping tunnel: reason: \(reason.amneziaDescription)")
wgAdapter?.stop { error in
ErrorNotifier.removeLastErrorFile()
@@ -1,6 +1,5 @@
import Foundation
import NetworkExtension
import WireGuardKitGo
enum XrayErrors: Error {
case noXrayConfig
@@ -108,6 +107,8 @@ extension PacketTunnelProvider {
return
}
self?.updateActiveInterfaceIndexForCurrentPath()
// Launch xray
self?.setupAndStartXray(configData: updatedData) { xrayError in
if let xrayError {
@@ -134,6 +135,15 @@ extension PacketTunnelProvider {
completionHandler()
}
func sockCallback(fd: uintptr_t) {
if activeIfaceIdx != 0 {
withUnsafePointer(to: activeIfaceIdx) { ptr in
setsockopt(Int32(fd), IPPROTO_IP, IP_BOUND_IF, ptr, socklen_t(MemoryLayout<UInt32>.size))
setsockopt(Int32(fd), IPPROTO_IPV6, IPV6_BOUND_IF, ptr, socklen_t(MemoryLayout<UInt32>.size))
}
}
}
private func setupAndStartXray(configData: Data,
completionHandler: @escaping (Error?) -> Void) {
let path = Constants.cachesDirectory.appendingPathComponent("config.json", isDirectory: false).path
@@ -143,6 +153,17 @@ extension PacketTunnelProvider {
return
}
updateActiveInterfaceIndexForCurrentPath()
let ctx = Unmanaged.passUnretained(self).toOpaque()
let cb: libxray_sockcallback = { (fd, ctx) in
guard let ctx = ctx else { return }
let instance = Unmanaged<PacketTunnelProvider>.fromOpaque(ctx).takeUnretainedValue()
instance.sockCallback(fd: fd)
}
LibXraySetSockCallback(cb, ctx)
LibXrayRunXray(nil,
path,
Int64.max)
+148 -15
View File
@@ -1,5 +1,6 @@
import Foundation
import NetworkExtension
import Network
import os
import Darwin
import OpenVPNAdapter
@@ -38,6 +39,12 @@ struct Constants {
class PacketTunnelProvider: NEPacketTunnelProvider {
var wgAdapter: WireGuardAdapter?
var ovpnAdapter: OpenVPNAdapter?
private lazy var openVPNPacketFlowAdapter = PacketTunnelFlowAdapter(flow: packetFlow)
private let pathMonitorQueue = DispatchQueue(label: Constants.processQueueName + ".path-monitor")
private let pathMonitor = NWPathMonitor()
private var didReceiveInitialPathUpdate = false
private var currentPath: Network.NWPath?
private var currentPathSignature: String?
var splitTunnelType: Int?
var splitTunnelSites: [String]?
@@ -47,6 +54,73 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
var startHandler: ((Error?) -> Void)?
var stopHandler: (() -> Void)?
var protoType: TunnelProtoType?
var activeIfaceIdx: UInt32 = 0
func openVPNPacketFlow() -> OpenVPNAdapterPacketFlow {
openVPNPacketFlowAdapter
}
override init() {
super.init()
pathMonitor.pathUpdateHandler = { [weak self] path in
guard let self else { return }
self.currentPath = path
let signature = self.pathSignature(for: path)
let hasMeaningfulChange = self.currentPathSignature != signature
self.currentPathSignature = signature
self.updateActiveInterfaceIndex(for: path)
guard self.didReceiveInitialPathUpdate else {
self.didReceiveInitialPathUpdate = true
return
}
guard hasMeaningfulChange, self.protoType != nil else { return }
DispatchQueue.main.async {
self.handle(networkChange: path) { _ in }
}
}
pathMonitor.start(queue: pathMonitorQueue)
currentPath = pathMonitor.currentPath
currentPathSignature = pathSignature(for: pathMonitor.currentPath)
}
func updateActiveInterfaceIndex(for path: Network.NWPath?) {
guard let path else {
activeIfaceIdx = 0
return
}
let preferredTypes: [NWInterface.InterfaceType] = [.wiredEthernet, .wifi, .cellular, .other]
let nonLoopbackInterfaces = path.availableInterfaces.filter { $0.type != .loopback }
let activeInterfaces = nonLoopbackInterfaces.filter { path.usesInterfaceType($0.type) }
let candidate = preferredTypes.compactMap { type in
activeInterfaces.first { $0.type == type }
}.first ?? activeInterfaces.first ?? nonLoopbackInterfaces.first
if let candidate {
activeIfaceIdx = UInt32(candidate.index)
} else {
activeIfaceIdx = 0
}
}
func updateActiveInterfaceIndexForCurrentPath() {
if let currentPath {
currentPathSignature = pathSignature(for: currentPath)
updateActiveInterfaceIndex(for: currentPath)
return
}
currentPath = pathMonitor.currentPath
currentPathSignature = pathSignature(for: pathMonitor.currentPath)
updateActiveInterfaceIndex(for: pathMonitor.currentPath)
}
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
guard let message = String(data: messageData, encoding: .utf8) else {
@@ -104,6 +178,9 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
return
}
didReceiveInitialPathUpdate = false
updateActiveInterfaceIndexForCurrentPath()
switch protoType {
case .wireguard:
startWireguard(activationAttemptId: activationAttemptId,
@@ -157,28 +234,63 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
of object: Any?,
change: [NSKeyValueChangeKey: Any]?,
context: UnsafeMutableRawPointer?) {
guard Constants.kDefaultPathKey != keyPath else { return }
// Since iOS 11, we have observed that this KVO event fires repeatedly when connecting over Wifi,
// even though the underlying network has not changed (i.e. `isEqualToPath` returns false),
// leading to "wakeup crashes" due to excessive network activity. Guard against false positives by
// comparing the paths' string description, which includes properties not exposed by the class
guard let lastPath: NWPath = change?[.oldKey] as? NWPath,
let defPath = defaultPath,
lastPath != defPath || lastPath.description != defPath.description else {
guard Constants.kDefaultPathKey == keyPath else {
return
}
DispatchQueue.main.async { [weak self] in
guard let self, self.defaultPath != nil else { return }
self.handle(networkChange: self.defaultPath!) { _ in }
}
}
private func handle(networkChange changePath: NWPath, completion: @escaping (Error?) -> Void) {
private func handle(networkChange changePath: Network.NWPath, completion: @escaping (Error?) -> Void) {
updateActiveInterfaceIndex(for: changePath)
wg_log(.info, message: "Tunnel restarted.")
startTunnel(options: nil, completionHandler: completion)
}
}
private extension PacketTunnelProvider {
func pathSignature(for path: Network.NWPath) -> String {
var signatureComponents = [String(describing: path.status)]
signatureComponents.append(path.isExpensive ? "exp" : "noexp")
signatureComponents.append(path.isConstrained ? "con" : "nocon")
let preferredTypes: [NWInterface.InterfaceType] = [.wiredEthernet, .wifi, .cellular, .loopback, .other]
let sortedInterfaces = path.availableInterfaces.sorted { lhs, rhs in
if lhs.type == rhs.type {
return lhs.index < rhs.index
}
let lhsOrder = preferredTypes.firstIndex(of: lhs.type) ?? preferredTypes.count
let rhsOrder = preferredTypes.firstIndex(of: rhs.type) ?? preferredTypes.count
if lhsOrder == rhsOrder {
return lhs.index < rhs.index
}
return lhsOrder < rhsOrder
}
for interface in sortedInterfaces {
let typeName: String
switch interface.type {
case .wiredEthernet: typeName = "ethernet"
case .wifi: typeName = "wifi"
case .cellular: typeName = "cellular"
case .loopback: typeName = "loopback"
case .other: typeName = "other"
@unknown default: typeName = "unknown"
}
signatureComponents.append("\(typeName):\(interface.index)")
}
// Include currently used interface preference ordering
for type in preferredTypes {
let usesType = path.usesInterfaceType(type)
signatureComponents.append("uses-\(type):\(usesType)")
}
return signatureComponents.joined(separator: "|")
}
}
extension WireGuardLogLevel {
var osLogLevel: OSLogType {
switch self {
@@ -190,8 +302,27 @@ extension WireGuardLogLevel {
}
}
extension NEProviderStopReason: CustomStringConvertible {
public var description: String {
final class PacketTunnelFlowAdapter: NSObject, OpenVPNAdapterPacketFlow {
private let flow: NEPacketTunnelFlow
init(flow: NEPacketTunnelFlow) {
self.flow = flow
super.init()
}
@objc(readPacketsWithCompletionHandler:)
func readPackets(completionHandler: @escaping ([Data], [NSNumber]) -> Void) {
flow.readPackets(completionHandler: completionHandler)
}
@objc(writePackets:withProtocols:)
func writePackets(_ packets: [Data], withProtocols protocols: [NSNumber]) -> Bool {
flow.writePackets(packets, withProtocols: protocols)
}
}
extension NEProviderStopReason {
var amneziaDescription: String {
switch self {
case .none:
return "No specific reason"
@@ -223,6 +354,8 @@ extension NEProviderStopReason: CustomStringConvertible {
return "The current console user changed"
case .connectionFailed:
return "The connection failed"
case .internalError:
return "The network extension reported an internal error"
case .sleep:
return "A stop reason indicating the VPNC enabled disconnect on sleep and the device went to sleep"
case .appUpdate:

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