Compare commits

..

1 Commits

Author SHA1 Message Date
lunardunno
4edc9eacbb Unblock IPsec Connection for Linux 2025-08-15 09:53:59 +04:00
278 changed files with 3766 additions and 9827 deletions

View File

@@ -10,10 +10,10 @@ env:
jobs:
Build-Linux-Ubuntu:
runs-on: android-runner
runs-on: ubuntu-22.04
env:
QT_VERSION: 6.10.1
QT_VERSION: 6.6.2
QIF_VERSION: 4.7
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
@@ -30,15 +30,13 @@ jobs:
version: ${{ env.QT_VERSION }}
host: 'linux'
target: 'desktop'
arch: 'linux_gcc_64'
arch: 'gcc_64'
modules: 'qtremoteobjects qt5compat qtshadertools'
dir: ${{ runner.temp }}
setup-python: 'true'
tools: 'tools_ifw'
set-env: 'true'
aqtversion: '==3.3.0'
py7zrversion: '==0.22.*'
extra: '--base ${{ env.QT_MIRROR }}'
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
- name: 'Get sources'
uses: actions/checkout@v4
@@ -46,31 +44,24 @@ 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
- name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2
- name: 'Build project'
run: |
sudo apt-get install libxkbcommon-x11-0 libsecret-1-dev
sudo apt-get install libxkbcommon-x11-0
export QT_BIN_DIR=${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/gcc_64/bin
export QIF_BIN_DIR=${{ runner.temp }}/Qt/Tools/QtInstallerFramework/${{ env.QIF_VERSION }}/bin
bash deploy/build_linux.sh
- name: 'Pack installer'
run: cd deploy && tar -cf AmneziaVPN_Linux_Installer.tar AmneziaVPN_Linux_Installer.bin && zip AmneziaVPN_${VERSION}_linux_x64.tar.zip AmneziaVPN_Linux_Installer.tar
run: cd deploy && tar -cf AmneziaVPN_Linux_Installer.tar AmneziaVPN_Linux_Installer.bin
- name: 'Upload installer artifact'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_${{ env.VERSION }}_linux_x64.tar.zip
path: deploy/AmneziaVPN_${{ env.VERSION }}_linux_x64.tar.zip
name: AmneziaVPN_Linux_installer.tar
path: deploy/AmneziaVPN_Linux_Installer.tar
retention-days: 7
- name: 'Upload unpacked artifact'
@@ -93,7 +84,7 @@ jobs:
runs-on: windows-latest
env:
QT_VERSION: 6.10.1
QT_VERSION: 6.6.2
QIF_VERSION: 4.7
BUILD_ARCH: 64
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
@@ -111,16 +102,8 @@ 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
- name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2
- name: 'Install Qt'
uses: jurplel/install-qt-action@v3
@@ -128,62 +111,32 @@ jobs:
version: ${{ env.QT_VERSION }}
host: 'windows'
target: 'desktop'
arch: 'win64_msvc2022_64'
arch: 'win64_msvc2019_64'
modules: 'qtremoteobjects qt5compat qtshadertools'
dir: ${{ runner.temp }}
setup-python: 'true'
tools: 'tools_ifw'
set-env: 'true'
aqtversion: '==3.3.0'
py7zrversion: '==0.22.*'
extra: '--base ${{ env.QT_MIRROR }}'
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
- name: 'Setup mvsc'
uses: ilammy/msvc-dev-cmd@v1
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 }}\\msvc2022_64\\bin"
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_${{ 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
name: AmneziaVPN_Windows_installer
path: AmneziaVPN_x${{ env.BUILD_ARCH }}.exe
retention-days: 7
- name: 'Upload unpacked artifact'
@@ -196,10 +149,10 @@ jobs:
# ------------------------------------------------------
Build-iOS:
runs-on: macos-latest
runs-on: macos-13
env:
QT_VERSION: 6.10.1
QT_VERSION: 6.6.2
CC: cc
CXX: c++
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
@@ -214,7 +167,7 @@ jobs:
- name: 'Setup xcode'
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '26.1'
xcode-version: '15.2'
- name: 'Install desktop Qt'
uses: jurplel/install-qt-action@v3
@@ -258,8 +211,8 @@ jobs:
submodules: 'true'
fetch-depth: 10
# - name: 'Setup ccache'
# uses: hendrikmuhs/ccache-action@v1.2
- name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2
- name: 'Install dependencies'
run: pip install jsonschema jinja2
@@ -350,8 +303,8 @@ jobs:
submodules: 'true'
fetch-depth: 10
# - name: 'Setup ccache'
# uses: hendrikmuhs/ccache-action@v1.2
- name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2
- name: 'Build project'
run: |
@@ -378,7 +331,7 @@ jobs:
runs-on: macos-latest
env:
QT_VERSION: 6.10.1
QT_VERSION: 6.8.0
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
@@ -408,7 +361,7 @@ jobs:
xcode-version: '16.2.0'
- name: 'Install Qt'
uses: jurplel/install-qt-action@v4
uses: jurplel/install-qt-action@v3
with:
version: ${{ env.QT_VERSION }}
host: 'mac'
@@ -418,9 +371,8 @@ jobs:
dir: ${{ runner.temp }}
setup-python: 'true'
set-env: 'true'
aqtversion: '==3.3.0'
py7zrversion: '==0.22.*'
extra: '--base ${{ env.QT_MIRROR }}'
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
- name: 'Get sources'
uses: actions/checkout@v4
@@ -428,32 +380,19 @@ 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
- 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
name: AmneziaVPN_MacOS_installer
path: deploy/build/pkg/AmneziaVPN.pkg
retention-days: 7
- name: 'Upload unpacked artifact'
@@ -467,7 +406,7 @@ jobs:
runs-on: macos-latest
env:
QT_VERSION: 6.10.1
QT_VERSION: 6.8.3
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
@@ -487,31 +426,21 @@ jobs:
- name: 'Setup xcode'
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '26.1'
xcode-version: '16.2.0'
- name: 'Install desktop Qt'
- name: 'Install Qt'
uses: jurplel/install-qt-action@v3
with:
version: ${{ env.QT_VERSION }}
host: 'mac'
target: 'desktop'
modules: 'qtremoteobjects qt5compat qtshadertools qtmultimedia'
arch: 'clang_64'
modules: 'qtremoteobjects qt5compat qtshadertools'
dir: ${{ runner.temp }}
setup-python: 'true'
set-env: 'true'
extra: '--base ${{ env.QT_MIRROR }}'
extra: '--external 7z --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
@@ -519,8 +448,8 @@ jobs:
submodules: 'true'
fetch-depth: 10
# - name: 'Setup ccache'
# uses: hendrikmuhs/ccache-action@v1.2
- name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2
- name: 'Build project'
run: |
@@ -537,11 +466,11 @@ jobs:
# ------------------------------------------------------
Build-Android:
runs-on: android-runner
runs-on: ubuntu-latest
env:
ANDROID_BUILD_PLATFORM: android-36
QT_VERSION: 6.10.1
ANDROID_BUILD_PLATFORM: android-34
QT_VERSION: 6.7.3
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
@@ -622,22 +551,15 @@ 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
- name: 'Setup ccache'
uses: hendrikmuhs/ccache-action@v1.2
- name: 'Setup Java'
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
# cache: 'gradle'
cache: 'gradle'
- name: 'Setup Android NDK'
id: setup-ndk
@@ -662,44 +584,35 @@ 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_${{ env.VERSION }}_android9+_x86_64.apk
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_x86_64.apk
name: AmneziaVPN-android-x86_64
path: deploy/build/AmneziaVPN-x86_64-release.apk
compression-level: 0
retention-days: 7
- name: 'Upload x86 apk'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_${{ env.VERSION }}_android9+_x86.apk
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_x86.apk
name: AmneziaVPN-android-x86
path: deploy/build/AmneziaVPN-x86-release.apk
compression-level: 0
retention-days: 7
- name: 'Upload arm64-v8a apk'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_${{ env.VERSION }}_android9+_arm64-v8a.apk
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_arm64-v8a.apk
name: AmneziaVPN-android-arm64-v8a
path: deploy/build/AmneziaVPN-arm64-v8a-release.apk
compression-level: 0
retention-days: 7
- name: 'Upload armeabi-v7a apk'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_${{ env.VERSION }}_android9+_armeabi-v7a.apk
path: deploy/build/AmneziaVPN_${{ env.VERSION }}_android9+_armeabi-v7a.apk
name: AmneziaVPN-android-armeabi-v7a
path: deploy/build/AmneziaVPN-armeabi-v7a-release.apk
compression-level: 0
retention-days: 7

View File

@@ -24,7 +24,7 @@ jobs:
- name: Verify git tag
run: |
TAG_NAME=${{ inputs.RELEASE_VERSION }}
CMAKE_TAG=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+).*/\1/')
CMAKE_TAG=$(grep 'project.*VERSION' CMakeLists.txt | sed -E 's/.* ([0-9]+.[0-9]+.[0-9]+.[0-9]+)$/\1/')
if [[ "$TAG_NAME" == "$CMAKE_TAG" ]]; then
echo "Git tag ($TAG_NAME) matches CMakeLists.txt version ($CMAKE_TAG)."
else

4
.gitignore vendored
View File

@@ -9,7 +9,6 @@ deploy/build_32/*
deploy/build_64/*
winbuild*.bat
.cache/
.vscode/
# Qt-es
@@ -140,6 +139,3 @@ ios-ne-build.sh
macos-ne-build.sh
macos-signed-build.sh
macos-with-sign-build.sh
DeveloperIdApplicationCertificate.p12
DeveloperIdInstallerCertificate.p12

4
.gitmodules vendored
View File

@@ -14,7 +14,3 @@
[submodule "client/3rd/QSimpleCrypto"]
path = client/3rd/QSimpleCrypto
url = https://github.com/amnezia-vpn/QSimpleCrypto.git
[submodule "client/3rd/qtgamepad"]
path = client/3rd/qtgamepad
url = https://github.com/amnezia-vpn/qtgamepad.git
branch = 6.6

View File

@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN)
set(AMNEZIAVPN_VERSION 4.8.13.0)
set(AMNEZIAVPN_VERSION 4.8.9.2)
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 2106)
set(APP_ANDROID_VERSION_CODE 2092)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux")
@@ -49,36 +49,3 @@ if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
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()

Submodule client/3rd/qtgamepad deleted from f72b3e0c62

View File

@@ -37,10 +37,6 @@ if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
set(PACKAGES ${PACKAGES} Widgets)
endif()
if(LINUX AND NOT ANDROID)
list(APPEND PACKAGES QuickTemplates2 QmlModels OpenGL)
endif()
find_package(Qt6 REQUIRED COMPONENTS ${PACKAGES})
set(LIBS ${LIBS}
@@ -56,28 +52,11 @@ endif()
qt_standard_project_setup()
qt_add_executable(${PROJECT} MANUAL_FINALIZATION)
if(LINUX AND NOT ANDROID)
target_link_options(${PROJECT} PRIVATE "-Wl,--no-as-needed")
target_link_options(${PROJECT} PRIVATE "LINKER:--disable-new-dtags")
set_target_properties(${PROJECT} PROPERTIES
BUILD_RPATH "\$ORIGIN/../lib"
INSTALL_RPATH "\$ORIGIN/../lib"
INSTALL_RPATH_USE_LINK_PATH FALSE
)
set_property(TARGET ${PROJECT} PROPERTY BUILD_WITH_INSTALL_RPATH TRUE)
target_link_libraries(${PROJECT} PRIVATE
Qt6::QuickTemplates2
Qt6::QmlModels
Qt6::OpenGL
)
endif()
target_include_directories(${PROJECT} PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
)
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
if(WIN32 OR (APPLE AND NOT IOS) 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)
@@ -216,17 +195,6 @@ elseif(APPLE)
endif()
target_link_libraries(${PROJECT} PRIVATE ${LIBS})
if(LINUX AND NOT ANDROID)
target_link_libraries(${PROJECT} PRIVATE
"-Wl,--push-state,--no-as-needed"
Qt6::QuickTemplates2
Qt6::QmlModels
Qt6::OpenGL
"-Wl,--pop-state"
)
endif()
target_compile_definitions(${PROJECT} PRIVATE "MZ_$<UPPER_CASE:${MZ_PLATFORM_NAME}>")
# deploy artifacts required to run the application to the debug build folder
@@ -260,13 +228,4 @@ if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
endif()
target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC} ${I18NQRC})
# Finalize the executable so Qt can gather/deploy QML modules and plugins correctly (Android needs this).
if(COMMAND qt_import_qml_plugins)
qt_import_qml_plugins(${PROJECT})
endif()
if(COMMAND qt_finalize_executable)
qt_finalize_executable(${PROJECT})
else()
qt_finalize_target(${PROJECT})
endif()
qt_finalize_target(${PROJECT})

View File

@@ -13,8 +13,6 @@
#include <QTimer>
#include <QTranslator>
#include <QEvent>
#include <QDir>
#include <QSettings>
#include "logger.h"
#include "ui/controllers/pageController.h"
@@ -27,15 +25,8 @@
#include <QtQuick/QQuickWindow> // for QQuickWindow
#include <QWindow> // for qobject_cast<QWindow*>
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"))
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv)
{
setDesktopFileName(QStringLiteral(APPLICATION_NAME));
setQuitOnLastWindowClosed(false);
// Fix config file permissions
@@ -60,40 +51,24 @@ AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_C
AmneziaApplication::~AmneziaApplication()
{
#ifdef AMNEZIA_DESKTOP
if (m_vpnConnection && m_vpnConnectionThread.isRunning()) {
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectSlots", Qt::BlockingQueuedConnection);
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectFromVpn", Qt::BlockingQueuedConnection);
if (m_vpnConnection) {
QMetaObject::invokeMethod(m_vpnConnection.get(), "disconnectFromVpn", Qt::QueuedConnection);
QMetaObject::invokeMethod(m_vpnConnection.get(), "deleteLater", Qt::QueuedConnection);
}
#endif
m_vpnConnectionThread.requestInterruption();
m_vpnConnectionThread.quit();
if (!m_vpnConnectionThread.wait(3000)) {
if (!m_vpnConnectionThread.wait(5000)) {
m_vpnConnectionThread.terminate();
m_vpnConnectionThread.wait(500);
m_vpnConnectionThread.wait();
}
if (m_engine) {
QObject::disconnect(m_engine, 0, 0, 0);
delete m_engine;
}
}
#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;
@@ -129,16 +104,6 @@ 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();
@@ -154,7 +119,7 @@ void AmneziaApplication::init()
Logger::setServiceLogsEnabled(enabled);
#ifdef Q_OS_WIN //TODO
if (m_parser.isSet(m_optAutostart))
if (m_parser.isSet("a"))
m_coreController->pageController()->showOnStartup();
else
emit m_coreController->pageController()->raiseMainWindow();
@@ -178,18 +143,6 @@ 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()
@@ -234,14 +187,15 @@ bool AmneziaApplication::parseCommands()
m_parser.addHelpOption();
m_parser.addVersionOption();
m_parser.addOption(m_optAutostart);
m_parser.addOption(m_optCleanup);
m_parser.addOption(m_optConnect);
m_parser.addOption(m_optImport);
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.process(*this);
if (m_parser.isSet(m_optCleanup)) {
if (m_parser.isSet(c_cleanup)) {
Logger::cleanUp();
QTimer::singleShot(100, this, [this] { quit(); });
exec();
@@ -274,12 +228,8 @@ bool AmneziaApplication::eventFilter(QObject *watched, QEvent *event)
#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();
}
if (m_coreController && m_coreController->pageController()) {
m_coreController->pageController()->hideMainWindow();
}
#endif
return true; // eat the close
@@ -288,12 +238,6 @@ bool AmneziaApplication::eventFilter(QObject *watched, QEvent *event)
return QObject::eventFilter(watched, event);
}
void AmneziaApplication::forceQuit()
{
m_forceQuit = true;
quit();
}
QQmlApplicationEngine *AmneziaApplication::qmlEngine() const
{
return m_engine;

View File

@@ -45,11 +45,7 @@ public:
QNetworkAccessManager *networkManager();
QClipboard *getClipboard();
public slots:
void forceQuit();
private:
static bool m_forceQuit;
QQmlApplicationEngine *m_engine {};
std::shared_ptr<Settings> m_settings;
@@ -60,11 +56,6 @@ private:
QCommandLineParser m_parser;
QCommandLineOption m_optAutostart;
QCommandLineOption m_optCleanup;
QCommandLineOption m_optConnect;
QCommandLineOption m_optImport;
QSharedPointer<VpnConnection> m_vpnConnection;
QThread m_vpnConnectionThread;

View File

@@ -45,8 +45,7 @@
android:configChanges="uiMode|screenSize|smallestScreenSize|screenLayout|orientation|density
|fontScale|layoutDirection|locale|keyboard|keyboardHidden|navigation|mcc|mnc"
android:launchMode="singleInstance"
android:windowSoftInputMode="adjustResize|stateUnchanged"
android:enableOnBackInvokedCallback="false"
android:windowSoftInputMode="stateUnchanged|adjustResize"
android:exported="true">
<intent-filter>
@@ -215,4 +214,4 @@
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/qtprovider_paths" />
</provider>
</application>
</manifest>
</manifest>

View File

@@ -93,7 +93,7 @@ open class OpenVpn : Protocol() {
openVpnClient = null
}
override fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean) {
override fun reconnectVpn(vpnBuilder: Builder) {
openVpnClient?.let {
it.establish = makeEstablish(vpnBuilder)
it.reconnect(0)

View File

@@ -42,7 +42,7 @@ abstract class Protocol {
abstract fun stopVpn()
abstract fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean)
abstract fun reconnectVpn(vpnBuilder: Builder)
protected fun ProtocolConfig.Builder.configSplitTunneling(config: JSONObject) {
if (!allowSplitTunneling) {

View File

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

View File

@@ -26,8 +26,6 @@ import android.os.ParcelFileDescriptor
import android.os.SystemClock
import android.provider.OpenableColumns
import android.provider.Settings
import android.view.InputDevice
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
@@ -37,11 +35,6 @@ 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
@@ -177,9 +170,10 @@ class AmneziaActivity : QtActivity() {
super.onCreate(savedInstanceState)
Log.d(TAG, "Create Amnezia activity")
loadLibs()
// Configure window for edge-to-edge display
configureWindowForEdgeToEdge()
window.apply {
addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
statusBarColor = getColor(R.color.black)
}
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
val proto = mainScope.async(Dispatchers.IO) {
VpnStateStore.getVpnState().vpnProto
@@ -271,136 +265,6 @@ class AmneziaActivity : QtActivity() {
super.onStop()
}
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
Log.d(TAG, "Window focus changed: hasFocus=$hasFocus")
}
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
val deviceId = event.deviceId
val keyCode = event.keyCode
val pressed = event.action == KeyEvent.ACTION_DOWN
val source = event.source
if (deviceId < 0 && pressed) {
when (keyCode) {
KeyEvent.KEYCODE_BUTTON_A,
KeyEvent.KEYCODE_BUTTON_B,
KeyEvent.KEYCODE_BUTTON_X,
KeyEvent.KEYCODE_BUTTON_Y,
KeyEvent.KEYCODE_BUTTON_START,
KeyEvent.KEYCODE_BUTTON_SELECT,
KeyEvent.KEYCODE_DPAD_CENTER -> {
nativeGamepadKeyEvent(0, keyCode, true)
nativeGamepadKeyEvent(0, keyCode, false)
return true
}
}
}
// Real gamepad events (deviceId >= 0)
if (deviceId >= 0) {
val isGamepad = (source and InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD
val isJoystick = (source and InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK
val isDpad = (source and InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD
if (isGamepad || isJoystick || isDpad) {
nativeGamepadKeyEvent(deviceId, keyCode, pressed)
return true
}
}
return super.dispatchKeyEvent(event)
}
private external fun nativeGamepadKeyEvent(deviceId: Int, keyCode: Int, pressed: Boolean)
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)
@@ -802,43 +666,6 @@ 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")

View File

@@ -565,7 +565,7 @@ open class AmneziaVpnService : VpnService() {
protocolState.value = RECONNECTING
connectionJob = connectionScope.launch {
vpnProto?.protocol?.reconnectVpn(Builder(), ::protect)
vpnProto?.protocol?.reconnectVpn(Builder())
}
}

View File

@@ -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) != 0
val icon: Boolean = ai.icon != 0
val isLaunchable: Boolean = pm.getLaunchIntentForPackage(packageName) != null
init {
val name = ai?.loadLabel(pm)?.toString()
this.name = name?.takeIf { it != packageName }
val name = ai.loadLabel(pm).toString()
this.name = if (name != packageName) name else null
}
override fun compareTo(other: App): Int {

View File

@@ -1,10 +1,7 @@
package org.amnezia.vpn
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.result.contract.ActivityResultContracts
@@ -14,25 +11,7 @@ private const val TAG = "TvFilePicker"
class TvFilePicker : ComponentActivity() {
private val fileChooseResultLauncher = registerForActivityResult(object : ActivityResultContracts.OpenDocument() {
override fun createIntent(context: Context, input: Array<String>): Intent {
val intent = super.createIntent(context, input)
val activitiesToResolveIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.packageManager.queryIntentActivities(intent, PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong()))
} else {
@Suppress("DEPRECATION")
context.packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
}
if (activitiesToResolveIntent.all {
val name = it.activityInfo.packageName
name.startsWith("com.google.android.tv.frameworkpackagestubs") || name.startsWith("com.android.tv.frameworkpackagestubs")
}) {
throw ActivityNotFoundException()
}
return intent
}
}) {
private val fileChooseResultLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) {
setResult(RESULT_OK, Intent().apply { data = it })
finish()
}
@@ -52,7 +31,7 @@ class TvFilePicker : ComponentActivity() {
private fun getFile() {
try {
Log.v(TAG, "getFile")
fileChooseResultLauncher.launch(arrayOf("*/*"))
fileChooseResultLauncher.launch("*/*")
} catch (_: ActivityNotFoundException) {
Log.w(TAG, "Activity not found")
setResult(RESULT_CANCELED, Intent().apply { putExtra("activityNotFound", true) })

View File

@@ -28,7 +28,4 @@ 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)
}

View File

@@ -12,7 +12,6 @@ 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
@@ -28,7 +27,6 @@ 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
@@ -63,7 +61,6 @@ 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 {
@@ -125,24 +122,23 @@ 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")?.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("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("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,
stopExistingVpn: Boolean = false
) {
if (!stopExistingVpn && tunnelHandle != -1) {
private fun start(config: WireguardConfig, vpnBuilder: Builder, protect: (Int) -> Boolean) {
if (tunnelHandle != -1) {
Log.w(TAG, "Tunnel already up")
return
}
@@ -150,9 +146,6 @@ 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")
}
@@ -209,25 +202,20 @@ open class Wireguard : Protocol() {
return lastHandshake
}
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()
statusJob?.cancel()
statusJob = null
val handleToClose = tunnelHandle
tunnelHandle = -1
GoBackend.awgTurnOff(handleToClose)
state.value = DISCONNECTED
}
override fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean) {
val config = this.config ?: throw VpnException("Reconnect config is empty")
start(config, vpnBuilder, protect, true)
override fun reconnectVpn(vpnBuilder: Builder) {
state.value = CONNECTED
}
}

View File

@@ -22,15 +22,19 @@ open class WireguardConfig protected constructor(
val s2: Int?,
val s3: Int?,
val s4: Int?,
val h1: String?,
val h2: String?,
val h3: String?,
val h4: String?,
val h1: Long?,
val h2: Long?,
val h3: Long?,
val h4: Long?,
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(
@@ -57,6 +61,10 @@ open class WireguardConfig protected constructor(
builder.i3,
builder.i4,
builder.i5,
builder.j1,
builder.j2,
builder.j3,
builder.itime
)
fun toWgUserspaceString(): String = with(StringBuilder()) {
@@ -86,6 +94,10 @@ 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") }
}
}
@@ -140,15 +152,19 @@ open class WireguardConfig protected constructor(
internal var s2: Int? = null
internal var s3: Int? = null
internal var s4: Int? = null
internal var h1: String? = null
internal var h2: String? = null
internal var h3: String? = null
internal var h4: String? = null
internal var h1: Long? = null
internal var h2: Long? = null
internal var h3: Long? = null
internal var h4: Long? = 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 }
@@ -169,15 +185,19 @@ 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: 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 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 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) }
}

View File

@@ -157,7 +157,7 @@ class Xray : Protocol() {
state.value = DISCONNECTED
}
override fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean) {
override fun reconnectVpn(vpnBuilder: Builder) {
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 = "warn"
logLevel = "warning"
}
LibXray.startTun2Socks(tun2SocksConfig, fd.toLong()).isNotNullOrBlank { err ->
throw VpnStartException("Failed to start tun2socks: $err")

View File

@@ -38,7 +38,7 @@ elseif(APPLE AND NOT IOS)
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")
@@ -62,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})
@@ -83,26 +83,6 @@ add_compile_definitions(_WINSOCKAPI_)
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
set(BUILD_WITH_QT6 ON)
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtkeychain)
if(ANDROID)
# Use qtgamepad from amnezia-vpn/qtgamepad repository
# Only if Qt6CorePrivate is available (required by qtgamepad)
find_package(Qt6CorePrivate CONFIG QUIET)
if(Qt6CorePrivate_FOUND)
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtgamepad)
# Link both the C++ module and QML plugin
if(TARGET GamepadLegacy)
target_link_libraries(${PROJECT} PRIVATE GamepadLegacy)
endif()
if(TARGET GamepadLegacyQuickPrivate)
target_link_libraries(${PROJECT} PRIVATE GamepadLegacyQuickPrivate)
endif()
message(STATUS "Gamepad support enabled for Android")
else()
message(STATUS "Qt6CorePrivate not found. Gamepad support disabled for Android.")
endif()
endif()
set(LIBS ${LIBS} qt6keychain)
include_directories(

View File

@@ -1,6 +1,6 @@
message("Client android ${CMAKE_ANDROID_ARCH_ABI} build")
set(APP_ANDROID_MIN_SDK 28)
set(APP_ANDROID_MIN_SDK 26)
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 36
QT_ANDROID_SDK_BUILD_TOOLS_REVISION 36.0.0
QT_ANDROID_TARGET_SDK_VERSION 34
QT_ANDROID_SDK_BUILD_TOOLS_REVISION 34.0.0
QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android
)
@@ -20,11 +20,7 @@ set(QT_ANDROID_MULTI_ABI_FORWARD_VARS "QT_NO_GLOBAL_APK_TARGET_PART_OF_ALL;CMAKE
# We need to include qtprivate api's
# As QAndroidBinder is not yet implemented with a public api
# 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)
set(LIBS ${LIBS} Qt6::CorePrivate -ljnigraphics)
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/platforms/android)

View File

@@ -34,7 +34,6 @@ 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)
@@ -47,8 +46,6 @@ 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
)

View File

@@ -35,7 +35,6 @@ set(HEADERS ${HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate-C-Interface.h
)
@@ -46,7 +45,6 @@ set(SOURCES ${SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/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

View File

@@ -28,7 +28,6 @@ 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
@@ -37,6 +36,7 @@ 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 AND NOT MACOS_NE)
@@ -79,7 +79,6 @@ 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
@@ -87,6 +86,7 @@ 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 AND NOT MACOS_NE)
@@ -175,7 +175,7 @@ if(WIN32)
)
endif()
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
message("Client desktop build")
add_compile_definitions(AMNEZIA_DESKTOP)
@@ -189,13 +189,11 @@ if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) 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
@@ -205,14 +203,3 @@ if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) 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()

View File

@@ -41,16 +41,18 @@ 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);
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::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::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::mtu] =
containerConfig.value(ProtocolProps::protoToString(Proto::Awg)).toObject().value(config_key::mtu).toString(protocols::awg::defaultMtu);

View File

@@ -83,30 +83,12 @@ 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", sanitizeStaticKey(connData.taKey));
config.replace("$OPENVPN_TA_KEY", connData.taKey);
} else {
config.replace("<tls-auth>", "");
config.replace("</tls-auth>", "");
@@ -135,7 +117,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);

View File

@@ -103,11 +103,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
return connData;
}
QString configPath = m_serverConfigPath;
if (container == DockerContainer::Awg) {
configPath = amnezia::protocols::awg::serverLegacyConfigPath;
}
QString getIpsScript = QString("cat %1 | grep AllowedIPs").arg(configPath);
QString getIpsScript = QString("cat %1 | grep AllowedIPs").arg(m_serverConfigPath);
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
@@ -165,18 +161,15 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
"AllowedIPs = %3/32\n\n")
.arg(connData.clientPubKey, connData.pskKey, connData.clientIP);
errorCode = m_serverController->uploadTextFileToContainer(container, credentials, configPart, configPath,
errorCode = m_serverController->uploadTextFileToContainer(container, credentials, configPart, m_serverConfigPath,
libssh::ScpOverwriteMode::ScpAppendToExisting);
if (errorCode != ErrorCode::NoError) {
return connData;
}
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);
QString script = QString("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip %1)'")
.arg(m_serverConfigPath);
errorCode = m_serverController->runScript(
credentials,

View File

@@ -28,10 +28,7 @@ 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));
@@ -44,10 +41,7 @@ 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));
@@ -77,8 +71,6 @@ 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) };
}
}
@@ -102,7 +94,6 @@ 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"},
@@ -129,9 +120,6 @@ QMap<DockerContainer, QString> ContainerProps::containerDescriptions()
{ DockerContainer::Awg,
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::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,
QObject::tr("XRay with REALITY masks VPN traffic as web traffic and protects against active probing. "
"It is highly resistant to detection and offers high speed.") },
@@ -194,7 +182,7 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
"* Minimal configuration required\n"
"* Easily detected by DPI systems (susceptible to blocking)\n"
"* Operates over UDP protocol") },
{ DockerContainer::Awg2,
{ DockerContainer::Awg,
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, "
@@ -254,7 +242,6 @@ 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;
@@ -268,15 +255,6 @@ 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
@@ -287,7 +265,6 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
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;
@@ -301,7 +278,6 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
// 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;
@@ -324,7 +300,6 @@ 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;
@@ -333,10 +308,7 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
}
#elif defined(Q_OS_LINUX)
switch (c) {
case DockerContainer::Ipsec: return false;
default: return true;
}
return true;
#else
return false;
@@ -354,7 +326,7 @@ QStringList ContainerProps::fixedPortsForContainer(DockerContainer c)
bool ContainerProps::isEasySetupContainer(DockerContainer container)
{
switch (container) {
case DockerContainer::Awg2: return true;
case DockerContainer::Awg: return true;
default: return false;
}
}
@@ -362,7 +334,7 @@ bool ContainerProps::isEasySetupContainer(DockerContainer container)
QString ContainerProps::easySetupHeader(DockerContainer container)
{
switch (container) {
case DockerContainer::Awg2: return tr("Automatic");
case DockerContainer::Awg: return tr("Automatic");
default: return "";
}
}
@@ -370,7 +342,7 @@ QString ContainerProps::easySetupHeader(DockerContainer container)
QString ContainerProps::easySetupDescription(DockerContainer container)
{
switch (container) {
case DockerContainer::Awg2: return tr("AmneziaWG protocol will be installed. "
case DockerContainer::Awg: 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 "";
}
@@ -379,7 +351,7 @@ QString ContainerProps::easySetupDescription(DockerContainer container)
int ContainerProps::easySetupOrder(DockerContainer container)
{
switch (container) {
case DockerContainer::Awg2: return 1;
case DockerContainer::Awg: return 1;
default: return 0;
}
}
@@ -395,12 +367,6 @@ 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))
@@ -418,7 +384,7 @@ int ContainerProps::installPageOrder(DockerContainer container)
case DockerContainer::Cloak: return 5;
case DockerContainer::ShadowSocks: return 6;
case DockerContainer::WireGuard: return 2;
case DockerContainer::Awg2: return 1;
case DockerContainer::Awg: return 1;
case DockerContainer::Xray: return 3;
case DockerContainer::Ipsec: return 7;
case DockerContainer::SSXray: return 8;

View File

@@ -17,7 +17,6 @@ namespace amnezia
enum DockerContainer {
None = 0,
Awg,
Awg2,
WireGuard,
OpenVpn,
Cloak,
@@ -46,7 +45,6 @@ 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();
@@ -73,9 +71,6 @@ 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);

View File

@@ -47,14 +47,12 @@ 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");
@@ -66,17 +64,6 @@ namespace apiDefs
constexpr QLatin1String id("id");
constexpr QLatin1String orderId("order_id");
constexpr QLatin1String migrationCode("migration_code");
constexpr QLatin1String transactionId("transaction_id");
constexpr QLatin1String isTestPurchase("is_test_purchase");
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

View File

@@ -1,7 +1,6 @@
#include "apiUtils.h"
#include <QDateTime>
#include <QJsonDocument>
#include <QJsonObject>
namespace
@@ -24,7 +23,7 @@ namespace
bool apiUtils::isSubscriptionExpired(const QString &subscriptionEndDate)
{
QDateTime now = QDateTime::currentDateTimeUtc();
QDateTime now = QDateTime::currentDateTime();
QDateTime endDate = QDateTime::fromString(subscriptionEndDate, Qt::ISODateWithMs);
return endDate < now;
}
@@ -83,45 +82,34 @@ 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, const QString &replyErrorString,
const QNetworkReply::NetworkError &replyError, const int httpStatusCode,
const QByteArray &responseBody)
amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList<QSslError> &sslErrors, QNetworkReply *reply)
{
const int httpStatusCodeConflict = 409;
const int httpStatusCodeNotFound = 404;
const int httpStatusCodeNotImplemented = 501;
if (!sslErrors.empty()) {
qDebug().noquote() << sslErrors;
return amnezia::ErrorCode::ApiConfigSslError;
} else if (replyError == QNetworkReply::NoError) {
} else if (reply->error() == QNetworkReply::NoError) {
return amnezia::ErrorCode::NoError;
} else if (replyError == QNetworkReply::NetworkError::OperationCanceledError
|| replyError == QNetworkReply::NetworkError::TimeoutError) {
qDebug() << replyError;
} else if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
qDebug() << reply->error();
return amnezia::ErrorCode::ApiConfigTimeoutError;
} else if (replyError == QNetworkReply::NetworkError::OperationNotImplementedError) {
qDebug() << replyError;
} else if (reply->error() == QNetworkReply::NetworkError::OperationNotImplementedError) {
qDebug() << reply->error();
return amnezia::ErrorCode::ApiUpdateRequestError;
} else {
qDebug() << QString::fromUtf8(responseBody);
qDebug() << replyError;
qDebug() << replyErrorString;
QString err = reply->errorString();
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
qDebug() << QString::fromUtf8(reply->readAll());
qDebug() << reply->error();
qDebug() << err;
qDebug() << httpStatusCode;
int httpStatusFromBody = -1;
QJsonDocument jsonDoc = QJsonDocument::fromJson(responseBody);
if (jsonDoc.isObject()) {
QJsonObject jsonObj = jsonDoc.object();
httpStatusFromBody = jsonObj.value("http_status").toInt(-1);
}
if (httpStatusFromBody == httpStatusCodeConflict) {
if (httpStatusCode == httpStatusCodeConflict) {
return amnezia::ErrorCode::ApiConfigLimitError;
} else if (httpStatusFromBody == httpStatusCodeNotFound) {
} else if (httpStatusCode == httpStatusCodeNotFound) {
return amnezia::ErrorCode::ApiNotFoundError;
} else if (httpStatusFromBody == httpStatusCodeNotImplemented) {
return amnezia::ErrorCode::ApiUpdateRequestError;
}
return amnezia::ErrorCode::ApiConfigDownloadError;
}
@@ -174,51 +162,3 @@ 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;
}

View File

@@ -18,12 +18,9 @@ namespace apiUtils
apiDefs::ConfigType getConfigType(const QJsonObject &serverConfigObject);
apiDefs::ConfigSource getConfigSource(const QJsonObject &serverConfigObject);
amnezia::ErrorCode checkNetworkReplyErrors(const QList<QSslError> &sslErrors, const QString &replyErrorString,
const QNetworkReply::NetworkError &replyError, const int httpStatusCode,
const QByteArray &responseBody);
amnezia::ErrorCode checkNetworkReplyErrors(const QList<QSslError> &sslErrors, QNetworkReply *reply);
QString getPremiumV1VpnKey(const QJsonObject &serverConfigObject);
QString getPremiumV2VpnKey(const QJsonObject &serverConfigObject);
}
#endif // APIUTILS_H

View File

@@ -26,8 +26,9 @@ CoreController::CoreController(const QSharedPointer<VpnConnection> &vpnConnectio
initNotificationHandler();
auto locale = m_settings->getAppLanguage();
m_translator.reset(new QTranslator());
updateTranslator(m_settings->getAppLanguage());
updateTranslator(locale);
}
void CoreController::initModels()
@@ -99,9 +100,6 @@ 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()
@@ -154,8 +152,8 @@ void CoreController::initControllers()
m_apiConfigsController.reset(new ApiConfigsController(m_serversModel, m_apiServicesModel, m_settings));
m_engine->rootContext()->setContextProperty("ApiConfigsController", m_apiConfigsController.get());
m_apiNewsController.reset(new ApiNewsController(m_newsModel, m_settings, m_serversModel, this));
m_engine->rootContext()->setContextProperty("ApiNewsController", m_apiNewsController.get());
m_apiPremV1MigrationController.reset(new ApiPremV1MigrationController(m_serversModel, m_settings, this));
m_engine->rootContext()->setContextProperty("ApiPremV1MigrationController", m_apiPremV1MigrationController.get());
}
void CoreController::initAndroidController()
@@ -228,12 +226,14 @@ void CoreController::initSignalHandlers()
initAutoConnectHandler();
initAmneziaDnsToggledHandler();
initPrepareConfigHandler();
initImportPremiumV2VpnKeyHandler();
initShowMigrationDrawerHandler();
initStrictKillSwitchHandler();
}
void CoreController::initNotificationHandler()
{
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#ifndef Q_OS_ANDROID
m_notificationHandler.reset(NotificationHandler::create(nullptr));
connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(),
@@ -317,11 +317,6 @@ 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();
}
@@ -377,6 +372,25 @@ void CoreController::initPrepareConfigHandler()
});
}
void CoreController::initImportPremiumV2VpnKeyHandler()
{
connect(m_apiPremV1MigrationController.get(), &ApiPremV1MigrationController::importPremiumV2VpnKey, this, [this](const QString &vpnKey) {
m_importController->extractConfigFromData(vpnKey);
m_importController->importConfig();
emit m_apiPremV1MigrationController->migrationFinished();
});
}
void CoreController::initShowMigrationDrawerHandler()
{
QTimer::singleShot(1000, this, [this]() {
if (m_apiPremV1MigrationController->isPremV1MigrationReminderActive() && m_apiPremV1MigrationController->hasConfigsToMigration()) {
m_apiPremV1MigrationController->showMigrationDrawer();
}
});
}
void CoreController::initStrictKillSwitchHandler()
{
connect(m_settingsController.get(), &SettingsController::strictKillSwitchEnabledChanged, m_vpnConnection.get(),
@@ -387,22 +401,3 @@ 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();
}
}

View File

@@ -5,13 +5,13 @@
#include <QQmlContext>
#include <QThread>
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#ifndef Q_OS_ANDROID
#include "ui/systemtray_notificationhandler.h"
#endif
#include "ui/controllers/api/apiConfigsController.h"
#include "ui/controllers/api/apiSettingsController.h"
#include "ui/controllers/api/apiNewsController.h"
#include "ui/controllers/api/apiPremV1MigrationController.h"
#include "ui/controllers/appSplitTunnelingController.h"
#include "ui/controllers/allowedDnsController.h"
#include "ui/controllers/connectionController.h"
@@ -47,9 +47,8 @@
#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)
#ifndef Q_OS_ANDROID
#include "ui/notificationhandler.h"
#endif
@@ -64,9 +63,6 @@ public:
QSharedPointer<PageController> pageController() const;
void setQmlRoot();
void openConnectionByIndex(int serverIndex);
void importConfigFromData(const QString &data);
signals:
void translationsUpdated();
void websiteUrlChanged(const QString &newUrl);
@@ -92,6 +88,8 @@ private:
void initAutoConnectHandler();
void initAmneziaDnsToggledHandler();
void initPrepareConfigHandler();
void initImportPremiumV2VpnKeyHandler();
void initShowMigrationDrawerHandler();
void initStrictKillSwitchHandler();
QQmlApplicationEngine *m_engine {}; // TODO use parent child system here?
@@ -99,7 +97,7 @@ private:
QSharedPointer<VpnConnection> m_vpnConnection;
QSharedPointer<QTranslator> m_translator;
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#ifndef Q_OS_ANDROID
QScopedPointer<NotificationHandler> m_notificationHandler;
#endif
@@ -119,7 +117,7 @@ private:
QScopedPointer<ApiSettingsController> m_apiSettingsController;
QScopedPointer<ApiConfigsController> m_apiConfigsController;
QScopedPointer<ApiNewsController> m_apiNewsController;
QScopedPointer<ApiPremV1MigrationController> m_apiPremV1MigrationController;
QSharedPointer<ContainersModel> m_containersModel;
QSharedPointer<ContainersModel> m_defaultServerContainersModel;
@@ -127,7 +125,6 @@ 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;

View File

@@ -1,15 +1,12 @@
#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"
@@ -41,11 +38,6 @@ namespace
constexpr QLatin1String errorResponsePattern3("Account not found.");
constexpr QLatin1String updateRequestResponsePattern("client version update is required");
constexpr int httpStatusCodeNotFound = 404;
constexpr int httpStatusCodeConflict = 409;
constexpr int httpStatusCodeNotImplemented = 501;
}
GatewayController::GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
@@ -58,45 +50,101 @@ GatewayController::GatewayController(const QString &gatewayEndpoint, const bool
{
}
GatewayController::EncryptedRequestData GatewayController::prepareRequest(const QString &endpoint, const QJsonObject &apiPayload)
ErrorCode GatewayController::get(const QString &endpoint, QByteArray &responseBody)
{
EncryptedRequestData encRequestData;
encRequestData.errorCode = ErrorCode::NoError;
#ifdef Q_OS_IOS
IosController::Instance()->requestInetAccess();
QThread::msleep(10);
#endif
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));
QNetworkRequest request;
request.setTransferTimeout(m_requestTimeoutMsecs);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setUrl(QString(endpoint).arg(m_gatewayEndpoint));
// bypass killSwitch exceptions for API-gateway
#ifdef AMNEZIA_DESKTOP
if (m_isStrictKillSwitchEnabled) {
QString host = QUrl(encRequestData.request.url()).host();
QString host = QUrl(request.url()).host();
QString ip = NetworkUtilities::getIPAddress(host);
if (!ip.isEmpty()) {
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";
});
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 });
}
}
#endif
QSimpleCrypto::QBlockCipher blockCipher;
encRequestData.key = blockCipher.generatePrivateSalt(32);
encRequestData.iv = blockCipher.generatePrivateSalt(32);
encRequestData.salt = blockCipher.generatePrivateSalt(8);
QByteArray key = blockCipher.generatePrivateSalt(32);
QByteArray iv = blockCipher.generatePrivateSalt(32);
QByteArray salt = blockCipher.generatePrivateSalt(8);
QJsonObject keyPayload;
keyPayload[configKey::aesKey] = QString(encRequestData.key.toBase64());
keyPayload[configKey::aesIv] = QString(encRequestData.iv.toBase64());
keyPayload[configKey::aesSalt] = QString(encRequestData.salt.toBase64());
keyPayload[configKey::aesKey] = QString(key.toBase64());
keyPayload[configKey::aesIv] = QString(iv.toBase64());
keyPayload[configKey::aesSalt] = QString(salt.toBase64());
QByteArray encryptedKeyPayload;
QByteArray encryptedApiPayload;
@@ -111,217 +159,71 @@ GatewayController::EncryptedRequestData GatewayController::prepareRequest(const
} catch (...) {
Utils::logException();
qCritical() << "error loading public key from environment variables";
encRequestData.errorCode = ErrorCode::ApiMissingAgwPublicKey;
return encRequestData;
return ErrorCode::ApiMissingAgwPublicKey;
}
encryptedKeyPayload = rsa.encrypt(QJsonDocument(keyPayload).toJson(), publicKey, RSA_PKCS1_PADDING);
EVP_PKEY_free(publicKey);
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), encRequestData.key, encRequestData.iv,
"", encRequestData.salt);
} catch (...) {
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), key, iv, "", salt);
} catch (...) { // todo change error handling in QSimpleCrypto?
Utils::logException();
qCritical() << "error when encrypting the request body";
encRequestData.errorCode = ErrorCode::ApiConfigDecryptionError;
return encRequestData;
return ErrorCode::ApiConfigDecryptionError;
}
QJsonObject requestBody;
requestBody[configKey::keyPayload] = QString(encryptedKeyPayload.toBase64());
requestBody[configKey::apiPayload] = QString(encryptedApiPayload.toBase64());
encRequestData.requestBody = QJsonDocument(requestBody).toJson();
return encRequestData;
}
GatewayController::DecryptionResult GatewayController::tryDecryptResponseBody(const QByteArray &encryptedResponseBody,
QNetworkReply::NetworkError replyError, const QByteArray &key,
const QByteArray &iv, const QByteArray &salt)
{
DecryptionResult result;
result.decryptedBody = encryptedResponseBody;
result.isDecryptionSuccessful = false;
try {
QSimpleCrypto::QBlockCipher blockCipher;
result.decryptedBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt);
result.isDecryptionSuccessful = true;
} catch (...) {
result.decryptedBody = encryptedResponseBody;
result.isDecryptionSuccessful = false;
}
return result;
}
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);
QNetworkReply *reply = amnApp->networkManager()->post(request, QJsonDocument(requestBody).toJson());
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(QEventLoop::ExcludeUserInputEvents);
wait.exec();
QByteArray encryptedResponseBody = reply->readAll();
QString replyErrorString = reply->errorString();
auto replyError = reply->error();
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
reply->deleteLater();
auto decryptionResult =
tryDecryptResponseBody(encryptedResponseBody, replyError, encRequestData.key, encRequestData.iv, encRequestData.salt);
if (sslErrors.isEmpty() && shouldBypassProxy(replyError, decryptionResult.decryptedBody, decryptionResult.isDecryptionSuccessful)) {
auto requestFunction = [&encRequestData, &encryptedResponseBody](const QString &url) {
encRequestData.request.setUrl(url);
return amnApp->networkManager()->post(encRequestData.request, encRequestData.requestBody);
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());
};
auto replyProcessingFunction = [&encryptedResponseBody, &replyErrorString, &replyError, &httpStatusCode, &sslErrors, &encRequestData,
&decryptionResult, this](QNetworkReply *reply, const QList<QSslError> &nestedSslErrors) {
encryptedResponseBody = reply->readAll();
replyErrorString = reply->errorString();
replyError = reply->error();
httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
decryptionResult =
tryDecryptResponseBody(encryptedResponseBody, replyError, encRequestData.key, encRequestData.iv, encRequestData.salt);
if (!sslErrors.isEmpty()
|| shouldBypassProxy(replyError, decryptionResult.decryptedBody, decryptionResult.isDecryptionSuccessful)) {
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)) {
sslErrors = nestedSslErrors;
return false;
}
return true;
};
auto serviceType = apiPayload.value(apiDefs::key::serviceType).toString("");
auto userCountryCode = apiPayload.value(apiDefs::key::userCountryCode).toString("");
bypassProxy(endpoint, serviceType, userCountryCode, requestFunction, replyProcessingFunction);
bypassProxy(endpoint, reply, requestFunction, replyProcessingFunction);
}
auto errorCode =
apiUtils::checkNetworkReplyErrors(sslErrors, replyErrorString, replyError, httpStatusCode, decryptionResult.decryptedBody);
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, reply);
reply->deleteLater();
if (errorCode) {
return errorCode;
}
if (!decryptionResult.isDecryptionSuccessful) {
try {
responseBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt);
return ErrorCode::NoError;
} catch (...) { // todo change error handling in QSimpleCrypto?
Utils::logException();
qCritical() << "error when decrypting the request body";
return ErrorCode::ApiConfigDecryptionError;
}
responseBody = decryptionResult.decryptedBody;
return ErrorCode::NoError;
}
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 decryptionResult =
tryDecryptResponseBody(encryptedResponseBody, replyError, encRequestData.key, encRequestData.iv, encRequestData.salt);
auto processResponse = [promise, encRequestData](const GatewayController::DecryptionResult &decryptionResult,
const QList<QSslError> &sslErrors, QNetworkReply::NetworkError replyError,
const QString &replyErrorString, int httpStatusCode) {
auto errorCode = apiUtils::checkNetworkReplyErrors(sslErrors, replyErrorString, replyError, httpStatusCode,
decryptionResult.decryptedBody);
if (errorCode) {
promise->addResult(qMakePair(errorCode, QByteArray()));
promise->finish();
return;
}
if (!decryptionResult.isDecryptionSuccessful) {
Utils::logException();
qCritical() << "error when decrypting the request body";
promise->addResult(qMakePair(ErrorCode::ApiConfigDecryptionError, QByteArray()));
promise->finish();
return;
}
promise->addResult(qMakePair(ErrorCode::NoError, decryptionResult.decryptedBody));
promise->finish();
};
if (sslErrors->isEmpty() && shouldBypassProxy(replyError, decryptionResult.decryptedBody, decryptionResult.isDecryptionSuccessful)) {
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 &proxyUrl) {
bypassProxyAsync(endpoint, proxyUrl, encRequestData,
[processResponse, this](const QByteArray &decryptedBody, bool isDecryptionSuccessful,
const QList<QSslError> &sslErrors, QNetworkReply::NetworkError replyError,
const QString &replyErrorString, int httpStatusCode) {
GatewayController::DecryptionResult result;
result.decryptedBody = decryptedBody;
result.isDecryptionSuccessful = isDecryptionSuccessful;
processResponse(result, sslErrors, replyError, replyErrorString, httpStatusCode);
});
});
});
} else {
processResponse(decryptionResult, *sslErrors, replyError, replyErrorString, httpStatusCode);
}
});
return promise->future();
}
QStringList GatewayController::getProxyUrls(const QString &serviceType, const QString &userCountryCode)
QStringList GatewayController::getProxyUrls()
{
QNetworkRequest request;
request.setTransferTimeout(m_requestTimeoutMsecs);
@@ -331,33 +233,22 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
QList<QSslError> sslErrors;
QNetworkReply *reply;
QStringList baseUrls;
QStringList proxyStorageUrls;
if (m_isDevEnvironment) {
baseUrls = QString(DEV_S3_ENDPOINT).split(", ");
proxyStorageUrls = QString(DEV_S3_ENDPOINT).split(", ");
} else {
baseUrls = QString(PROD_S3_ENDPOINT).split(", ");
proxyStorageUrls = 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(QEventLoop::ExcludeUserInputEvents);
wait.exec();
if (reply->error() == QNetworkReply::NetworkError::NoError) {
auto encryptedResponseBody = reply->readAll();
@@ -395,10 +286,7 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
}
return endpoints;
} else {
auto replyError = reply->error();
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
qDebug() << replyError;
qDebug() << httpStatusCode;
apiUtils::checkNetworkReplyErrors(sslErrors, reply);
qDebug() << "go to the next storage endpoint";
reply->deleteLater();
@@ -407,259 +295,70 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
return {};
}
bool GatewayController::shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &decryptedResponseBody,
bool isDecryptionSuccessful)
bool GatewayController::shouldBypassProxy(QNetworkReply *reply, const QByteArray &responseBody, bool checkEncryption, const QByteArray &key,
const QByteArray &iv, const QByteArray &salt)
{
const QByteArray &responseBody = decryptedResponseBody;
int httpStatus = -1;
if (isDecryptionSuccessful) {
QJsonDocument jsonDoc = QJsonDocument::fromJson(responseBody);
if (jsonDoc.isObject()) {
QJsonObject jsonObj = jsonDoc.object();
httpStatus = jsonObj.value("http_status").toInt(-1);
}
} else {
qDebug() << "failed to decrypt the data";
return true;
}
if (replyError == QNetworkReply::NetworkError::OperationCanceledError || replyError == QNetworkReply::NetworkError::TimeoutError) {
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError || reply->error() == QNetworkReply::NetworkError::TimeoutError) {
qDebug() << "timeout occurred";
qDebug() << replyError;
qDebug() << reply->error();
return true;
} else if (responseBody.contains("html")) {
qDebug() << "the response contains an html tag";
return true;
} else if (httpStatus == httpStatusCodeNotFound) {
} else if (reply->error() == QNetworkReply::NetworkError::ContentNotFoundError) {
if (responseBody.contains(errorResponsePattern1) || responseBody.contains(errorResponsePattern2)
|| responseBody.contains(errorResponsePattern3)) {
return false;
} else {
qDebug() << replyError;
qDebug() << reply->error();
return true;
}
} else if (httpStatus == httpStatusCodeNotImplemented) {
} else if (reply->error() == QNetworkReply::NetworkError::OperationNotImplementedError) {
if (responseBody.contains(updateRequestResponsePattern)) {
return false;
} else {
qDebug() << replyError;
qDebug() << reply->error();
return true;
}
} else if (httpStatus == httpStatusCodeConflict) {
return false;
} else if (replyError != QNetworkReply::NetworkError::NoError) {
qDebug() << replyError;
} else if (reply->error() != QNetworkReply::NetworkError::NoError) {
qDebug() << reply->error();
return true;
} else if (checkEncryption) {
try {
QSimpleCrypto::QBlockCipher blockCipher;
static_cast<void>(blockCipher.decryptAesBlockCipher(responseBody, key, iv, "", salt));
} catch (...) {
qDebug() << "failed to decrypt the data";
return true;
}
}
return false;
}
void GatewayController::bypassProxy(const QString &endpoint, const QString &serviceType, const QString &userCountryCode,
void GatewayController::bypassProxy(const QString &endpoint, QNetworkReply *reply,
std::function<QNetworkReply *(const QString &url)> requestFunction,
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction)
{
QStringList proxyUrls = getProxyUrls(serviceType, userCountryCode);
QStringList proxyUrls = getProxyUrls();
std::random_device randomDevice;
std::mt19937 generator(randomDevice());
std::shuffle(proxyUrls.begin(), proxyUrls.end(), generator);
QEventLoop wait;
QList<QSslError> sslErrors;
QByteArray responseBody;
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;
for (const QString &proxyUrl : proxyUrls) {
qDebug() << "go to the next proxy endpoint";
QNetworkReply *reply = requestFunction(endpoint.arg(proxyUrl));
reply->deleteLater(); // delete the previous reply
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(QEventLoop::ExcludeUserInputEvents);
wait.exec();
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;
if (replyProcessingFunction(reply, sslErrors)) {
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 &, bool, const QList<QSslError> &, QNetworkReply::NetworkError, const QString &, int)> onComplete)
{
auto sslErrors = QSharedPointer<QList<QSslError>>::create();
if (proxyUrl.isEmpty()) {
onComplete(QByteArray(), false, *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, encRequestData, reply, this]() {
QByteArray encryptedResponseBody = reply->readAll();
QString replyErrorString = reply->errorString();
auto replyError = reply->error();
int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
reply->deleteLater();
auto decryptionResult =
tryDecryptResponseBody(encryptedResponseBody, replyError, encRequestData.key, encRequestData.iv, encRequestData.salt);
onComplete(decryptionResult.decryptedBody, decryptionResult.isDecryptionSuccessful, *sslErrors, replyError, replyErrorString,
httpStatusCode);
});
}

View File

@@ -1,12 +1,8 @@
#ifndef GATEWAYCONTROLLER_H
#define GATEWAYCONTROLLER_H
#include <QFuture>
#include <QNetworkReply>
#include <QObject>
#include <QPair>
#include <QPromise>
#include <QSharedPointer>
#include "core/defs.h"
@@ -22,49 +18,20 @@ 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:
struct EncryptedRequestData
{
QNetworkRequest request;
QByteArray requestBody;
QByteArray key;
QByteArray iv;
QByteArray salt;
amnezia::ErrorCode errorCode;
};
struct DecryptionResult
{
QByteArray decryptedBody;
bool isDecryptionSuccessful;
};
EncryptedRequestData prepareRequest(const QString &endpoint, const QJsonObject &apiPayload);
DecryptionResult tryDecryptResponseBody(const QByteArray &encryptedResponseBody, QNetworkReply::NetworkError replyError,
const QByteArray &key, const QByteArray &iv, const QByteArray &salt);
QStringList getProxyUrls(const QString &serviceType, const QString &userCountryCode);
bool shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &decryptedResponseBody, bool isDecryptionSuccessful);
void bypassProxy(const QString &endpoint, const QString &serviceType, const QString &userCountryCode,
std::function<QNetworkReply *(const QString &url)> requestFunction,
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,
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 &, bool, 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

View File

@@ -345,7 +345,7 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
return true;
}
if (ContainerProps::isAwgContainer(container)) {
if (container == DockerContainer::Awg) {
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;
}
@@ -419,18 +419,6 @@ ErrorCode ServerController::installDockerWorker(const ServerCredentials &credent
cbReadStdOut, cbReadStdErr);
qDebug().noquote() << "ServerController::installDockerWorker" << stdOut;
if (container == DockerContainer::Awg2) {
QRegularExpression regex(R"(Linux\s+(\d+)\.(\d+)[^\d]*)");
QRegularExpressionMatch match = regex.match(stdOut);
if (match.hasMatch()) {
int majorVersion = match.captured(1).toInt();
int minorVersion = match.captured(2).toInt();
if (majorVersion < 4 || (majorVersion == 4 && minorVersion < 14)) {
return ErrorCode::ServerLinuxKernelTooOld;
}
}
}
if (stdOut.contains("lock"))
return ErrorCode::ServerPacketManagerError;
if (stdOut.contains("command not found"))
@@ -660,11 +648,6 @@ 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) } });
@@ -674,8 +657,7 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
vars.append({ { "$SOCKS5_USER", socks5user } });
vars.append({ { "$SOCKS5_AUTH_TYPE", socks5user.isEmpty() ? "none" : "strong" } });
QString serverIp = (!ContainerProps::isAwgContainer(container) &&
container != DockerContainer::WireGuard && container != DockerContainer::Xray)
QString serverIp = (container != DockerContainer::Awg && container != DockerContainer::WireGuard && container != DockerContainer::Xray)
? NetworkUtilities::getIPAddress(credentials.hostName)
: credentials.hostName;
if (!serverIp.isEmpty()) {

View File

@@ -99,12 +99,11 @@ QJsonObject VpnConfigurationsController::createVpnConfiguration(const QPair<QStr
protocolConfigString = configurator->processConfigWithLocalSettings(dns, isApiConfig, protocolConfigString);
QJsonObject vpnConfigData = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
if (ContainerProps::isAwgContainer(container) || container == DockerContainer::WireGuard) {
if (container == DockerContainer::Awg || container == DockerContainer::WireGuard) {
// add mtu for old configs
if (vpnConfigData[config_key::mtu].toString().isEmpty()) {
vpnConfigData[config_key::mtu] =
ContainerProps::isAwgContainer(container) ? protocols::awg::defaultMtu :
protocols::wireguard::defaultMtu;
container == DockerContainer::Awg ? protocols::awg::defaultMtu : protocols::wireguard::defaultMtu;
}
}

View File

@@ -61,7 +61,6 @@ namespace amnezia
ServerDockerOnCgroupsV2 = 211,
ServerCgroupMountpoint = 212,
DockerPullRateLimit = 213,
ServerLinuxKernelTooOld = 214,
// Ssh connection errors
SshRequestDeniedError = 300,
@@ -121,8 +120,6 @@ namespace amnezia
ApiNotFoundError = 1109,
ApiMigrationError = 1110,
ApiUpdateRequestError = 1111,
ApiSubscriptionExpiredError = 1112,
ApiPurchaseError = 1113,
// QFile errors
OpenError = 1200,

View File

@@ -29,7 +29,6 @@ QString errorString(ErrorCode code) {
case(ErrorCode::ServerDockerOnCgroupsV2): errorMessage = QObject::tr("Docker error: runc doesn't work on cgroups v2"); break;
case(ErrorCode::ServerCgroupMountpoint): errorMessage = QObject::tr("Server error: cgroup mountpoint does not exist"); break;
case(ErrorCode::DockerPullRateLimit): errorMessage = QObject::tr("Docker error: The pull rate limit has been reached"); break;
case(ErrorCode::ServerLinuxKernelTooOld): errorMessage = QObject::tr("Server error: Linux kernel is too old"); break;
// Libssh errors
case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break;
@@ -78,8 +77,6 @@ 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;

View File

@@ -1,71 +1,108 @@
#include "ipcclient.h"
#include "ipc.h"
#include <QRemoteObjectNode>
#include <QtNetwork/qlocalsocket.h>
IpcClient *IpcClient::m_instance = nullptr;
IpcClient::IpcClient(QObject *parent) : QObject(parent)
{
m_node.connectToNode(QUrl("local:" + amnezia::getIpcServiceUrl()));
m_interface.reset(m_node.acquire<IpcInterfaceReplica>());
m_tun2socks.reset(m_node.acquire<IpcProcessTun2SocksReplica>());
}
IpcClient& IpcClient::Instance()
IpcClient::~IpcClient()
{
thread_local IpcClient ipcClient;
return ipcClient;
if (m_localSocket)
m_localSocket->close();
}
bool IpcClient::isSocketConnected() const
{
return m_isSocketConnected;
}
IpcClient *IpcClient::Instance()
{
return m_instance;
}
QSharedPointer<IpcInterfaceReplica> IpcClient::Interface()
{
QSharedPointer<IpcInterfaceReplica> rep = Instance().m_interface;
if (rep.isNull()) {
qCritical() << "IpcClient::Interface(): Failed to acquire replica";
if (!Instance())
return nullptr;
}
if (!rep->waitForSource(1000)) {
qCritical() << "IpcClient::Interface(): Failed to initialize replica";
return nullptr;
}
if (!rep->isReplicaValid()) {
qWarning() << "IpcClient::Interface(): Replica is invalid";
}
return rep;
return Instance()->m_ipcClient;
}
QSharedPointer<IpcProcessTun2SocksReplica> IpcClient::InterfaceTun2Socks()
{
QSharedPointer<IpcProcessTun2SocksReplica> rep = Instance().m_tun2socks;
if (rep.isNull()) {
qCritical() << "IpcClient::InterfaceTun2Socks: Replica is undefined";
if (!Instance())
return nullptr;
return Instance()->m_Tun2SocksClient;
}
bool IpcClient::init(IpcClient *instance)
{
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;
}
if (!rep->waitForSource(1000)) {
qCritical() << "IpcClient::InterfaceTun2Socks: Failed to initialize replica";
return nullptr;
}
if (!rep->isReplicaValid()) {
qWarning() << "IpcClient::InterfaceTun2Socks(): Replica is invalid";
}
return rep;
qDebug() << "IpcClient::init succeed";
return (Instance()->m_ipcClient->isReplicaValid() && Instance()->m_Tun2SocksClient->isReplicaValid());
}
QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
{
QSharedPointer<IpcInterfaceReplica> rep = Interface();
if (!rep) {
qCritical() << "IpcClient::createPrivilegedProcess: Replica is invalid";
if (!Instance()->m_ipcClient || !Instance()->m_ipcClient->isReplicaValid()) {
qWarning() << "IpcClient::createPrivilegedProcess : IpcClient IpcClient replica is not valid";
return nullptr;
}
QRemoteObjectPendingReply<int> pidReply = rep->createPrivilegedProcess();
if (!pidReply.waitForFinished(5000)){
qCritical() << "IpcClient::createPrivilegedProcess: Failed to execute RO createPrivilegedProcess call";
return nullptr;
}
QRemoteObjectPendingReply<int> futureResult = Instance()->m_ipcClient->createPrivilegedProcess();
futureResult.waitForFinished(5000);
int pid = pidReply.returnValue();
QSharedPointer<ProcessDescriptor> pd(new ProcessDescriptor());
int pid = futureResult.returnValue();
auto pd = QSharedPointer<ProcessDescriptor>(new ProcessDescriptor());
Instance()->m_processNodes.insert(pid, pd);
pd->localSocket.reset(new QLocalSocket(pd->replicaNode.data()));
@@ -73,7 +110,6 @@ 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) {
@@ -88,12 +124,8 @@ QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
[pd]() { pd->replicaNode->deleteLater(); });
}
});
pd->localSocket->connectToServer(amnezia::getIpcProcessUrl(pid));
if (!pd->localSocket->waitForConnected()) {
qCritical() << "IpcClient::createPrivilegedProcess: Failed to connect to process' socket";
return nullptr;
}
pd->localSocket->waitForConnected();
auto processReplica = QSharedPointer<PrivilegedProcess>(pd->ipcProcess);
return processReplica;

View File

@@ -4,6 +4,7 @@
#include <QLocalSocket>
#include <QObject>
#include "ipc.h"
#include "rep_ipc_interface_replica.h"
#include "rep_ipc_process_tun2socks_replica.h"
@@ -13,48 +14,27 @@ class IpcClient : public QObject
{
Q_OBJECT
public:
explicit IpcClient(QObject *parent = nullptr);
explicit IpcClient(QObject *parent = nullptr);
static IpcClient& Instance();
static IpcClient *Instance();
static bool init(IpcClient *instance);
static QSharedPointer<IpcInterfaceReplica> Interface();
static QSharedPointer<IpcProcessTun2SocksReplica> InterfaceTun2Socks();
static QSharedPointer<PrivilegedProcess> CreatePrivilegedProcess();
static QSharedPointer<IpcInterfaceReplica> Interface();
static QSharedPointer<IpcProcessTun2SocksReplica> InterfaceTun2Socks();
static QSharedPointer<PrivilegedProcess> CreatePrivilegedProcess();
bool isSocketConnected() const;
template <typename Func>
static auto withInterface(Func func)
{
QSharedPointer<IpcInterfaceReplica> iface = Instance().m_interface;
using ReturnType = decltype(func(std::declval<QSharedPointer<IpcInterfaceReplica>>()));
if (iface.isNull() || !iface->waitForSource(1000) || !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_interface;
if (iface.isNull() || !iface->waitForSource(1000) || !iface->isReplicaValid()) {
return onFailure();
}
return onSuccess(iface);
}
signals:
private:
QRemoteObjectNode m_node;
QSharedPointer<IpcInterfaceReplica> m_interface;
QSharedPointer<IpcProcessTun2SocksReplica> m_tun2socks;
~IpcClient() override;
QRemoteObjectNode m_ClientNode;
QRemoteObjectNode m_Tun2SocksNode;
QSharedPointer<IpcInterfaceReplica> m_ipcClient;
QPointer<QLocalSocket> m_localSocket;
QPointer<QLocalSocket> m_tun2socksSocket;
QSharedPointer<IpcProcessTun2SocksReplica> m_Tun2SocksClient;
struct ProcessDescriptor {
ProcessDescriptor () {
@@ -66,6 +46,11 @@ private:
QSharedPointer<QRemoteObjectNode> replicaNode;
QSharedPointer<QLocalSocket> localSocket;
};
QMap<int, QSharedPointer<ProcessDescriptor>> m_processNodes;
bool m_isSocketConnected {false};
static IpcClient *m_instance;
};
#endif // IPCCLIENT_H

View File

@@ -1,12 +1,11 @@
#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>
@@ -31,15 +30,6 @@
#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>
@@ -180,7 +170,7 @@ int NetworkUtilities::AdapterIndexTo(const QHostAddress& dst) {
#ifdef Q_OS_WIN
qDebug() << "Getting Current Internet Adapter that routes to"
<< dst.toString();
quint32 ipBigEndian;
quint32_be ipBigEndian;
quint32 ip = dst.toIPv4Address();
qToBigEndian(ip, &ipBigEndian);
_MIB_IPFORWARDROW routeInfo;
@@ -249,14 +239,12 @@ DWORD GetAdaptersAddressesWrapper(const ULONG Family,
}
#endif
QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
QString NetworkUtilities::getGatewayAndIface()
{
#ifdef Q_OS_WIN
constexpr int BUFF_LEN = 100;
char buff[BUFF_LEN] = {'\0'};
QString resGateway;
int resIndex = -1;
QString result;
PIP_ADAPTER_ADDRESSES pAdapterAddresses = nullptr;
DWORD dwRetVal =
@@ -264,7 +252,7 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
if (dwRetVal != NO_ERROR) {
qDebug() << "ipv4 stack detect GetAdaptersAddresses failed.";
return {};
return "";
}
PIP_ADAPTER_ADDRESSES pCurAddress = pAdapterAddresses;
@@ -279,9 +267,7 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
struct sockaddr_in addr;
if (inet_pton(AF_INET, buff, &addr.sin_addr) == 1) {
qDebug() << "this is true v4 !";
resGateway = gw;
resIndex = pCurAddress->IfIndex;
result = gw;
}
}
}
@@ -289,7 +275,7 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
}
free(pAdapterAddresses);
return { resGateway, QNetworkInterface::interfaceFromIndex(resIndex) };
return result;
#endif
#ifdef Q_OS_LINUX
constexpr int BUFFER_SIZE = 100;
@@ -306,7 +292,7 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
if ((sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) {
perror("socket failed");
return {};
return "";
}
memset(msgbuf, 0, sizeof(msgbuf));
@@ -330,7 +316,7 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
/* send msg */
if (send(sock, nlmsg, nlmsg->nlmsg_len, 0) < 0) {
perror("send failed");
return {};
return "";
}
/* receive response */
@@ -339,7 +325,7 @@ QPair<QString, QNetworkInterface> 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;
@@ -349,7 +335,7 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
(nlmsg->nlmsg_type == NLMSG_ERROR))
{
perror("Error in received packet");
return {};
return "";
}
/* If we received all data break */
@@ -402,12 +388,10 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
}
}
close(sock);
return { gateway_address, QNetworkInterface::interfaceFromName(interface) };
return gateway_address;
#endif
#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};
@@ -417,17 +401,17 @@ QPair<QString, QNetworkInterface> 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 {};
return gateway;
}
struct rt_msghdr* rt;
@@ -465,10 +449,7 @@ QPair<QString, QNetworkInterface> 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;
}
}
@@ -482,10 +463,7 @@ QPair<QString, QNetworkInterface> 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;
}
}
@@ -494,6 +472,6 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
free(buf);
}
return { gateway, QNetworkInterface::interfaceFromIndex(index) };
return gateway;
#endif
}

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 QPair<QString, QNetworkInterface> getGatewayAndIface();
static QString getGatewayAndIface();
// Returns the Interface Index that could Route to dst
static int AdapterIndexTo(const QHostAddress& dst);

View File

@@ -1,194 +0,0 @@
#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(); });
}

View File

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

View File

@@ -11,8 +11,7 @@ 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::Awg2: return QLatin1String("awg");
case DockerContainer::Awg: return QLatin1String("awg_legacy");
case DockerContainer::Awg: return QLatin1String("awg");
case DockerContainer::Ipsec: return QLatin1String("ipsec");
case DockerContainer::Xray: return QLatin1String("xray");

View File

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

View File

@@ -42,25 +42,6 @@ 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
{

View File

@@ -252,65 +252,5 @@ QJsonObject Deserialize(const QString &str, QString *alias, QString *errMessage)
root["inbounds"] = QJsonArray { inbound };
return root;
}
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);
}
}
} // namespace amnezia::serialization::vless

View File

@@ -440,6 +440,18 @@ 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;
}

View File

@@ -101,10 +101,10 @@ QString InterfaceConfig::toWgConf(const QMap<QString, QString>& extra) const {
out << "MTU = " << m_deviceMTU << "\n";
}
if (!m_primaryDnsServer.isEmpty()) {
if (!m_primaryDnsServer.isNull()) {
QStringList dnsServers;
dnsServers.append(m_primaryDnsServer);
if (!m_secondaryDnsServer.isEmpty()) {
if (!m_secondaryDnsServer.isNull()) {
dnsServers.append(m_secondaryDnsServer);
}
// If the DNS is not the Gateway, it's a user defined DNS
@@ -152,6 +152,12 @@ 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()) {

View File

@@ -8,7 +8,7 @@
#include <QList>
#include <QMap>
#include <QString>
#include <QMap>
#include "ipaddress.h"
class QJsonObject;
@@ -57,6 +57,8 @@ 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(

View File

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

Before

Width:  |  Height:  |  Size: 982 B

View File

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

Before

Width:  |  Height:  |  Size: 410 B

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.9 KiB

View File

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

Before

Width:  |  Height:  |  Size: 188 B

View File

@@ -32,41 +32,17 @@
<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>
<false/>
<true/>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationPortrait</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<array/>
<key>UIUserInterfaceStyle</key>
<string>Light</string>
<key>com.wireguard.ios.app_group_id</key>

View File

@@ -2,7 +2,6 @@
#include <QTimer>
#include "amnezia_application.h"
#include "core/osSignalHandler.h"
#include "migrations.h"
#include "version.h"
@@ -45,7 +44,6 @@ int main(int argc, char *argv[])
#endif
AmneziaApplication app(argc, argv);
OsSignalHandler::setup();
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
if (isAnotherInstanceRunning()) {

View File

@@ -5,9 +5,6 @@
#include <stdint.h>
#include <QCoreApplication>
#include <QDateTime>
#include <QDebug>
#include <QDir>
#include <QFileInfo>
#include <QHostAddress>
@@ -15,13 +12,12 @@
#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"
@@ -119,6 +115,7 @@ void LocalSocketController::daemonConnected() {
}
void LocalSocketController::activate(const QJsonObject &rawConfig) {
QString protocolName = rawConfig.value("protocol").toString();
int splitTunnelType = rawConfig.value("splitTunnelType").toInt();
@@ -135,16 +132,13 @@ 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
// simply "dead::1" is globally-routable, don't use it
json.insert("deviceIpv6Address", "fd58:baa6:dead::1");
json.insert("deviceIpv6Address", "fd58:baa6:dead::1"); // simply "dead::1" is globally-routable, don't use it
json.insert("serverPublicKey", wgConfig.value(amnezia::config_key::server_pub_key));
json.insert("serverPskKey", wgConfig.value(amnezia::config_key::psk_key));
@@ -226,6 +220,7 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
json.insert("allowedIPAddressRanges", jsAllowedIPAddesses);
QJsonArray jsExcludedAddresses;
jsExcludedAddresses.append(wgConfig.value(amnezia::config_key::hostName));
if (splitTunnelType == 2) {
@@ -260,6 +255,10 @@ 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()
@@ -270,7 +269,16 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
&& !wgConfig.value(amnezia::config_key::initPacketMagicHeader).isUndefined()
&& !wgConfig.value(amnezia::config_key::responsePacketMagicHeader).isUndefined()
&& !wgConfig.value(amnezia::config_key::underloadPacketMagicHeader).isUndefined()
&& !wgConfig.value(amnezia::config_key::transportPacketMagicHeader).isUndefined()) {
&& !wgConfig.value(amnezia::config_key::transportPacketMagicHeader).isUndefined()
&& !wgConfig.value(amnezia::config_key::specialJunk1).isUndefined()
&& !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()) {
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));
@@ -287,6 +295,10 @@ 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);
@@ -437,7 +449,6 @@ void LocalSocketController::parseCommand(const QByteArray& command) {
}
if (type == "status") {
QJsonValue serverIpv4Gateway = obj.value("serverIpv4Gateway");
if (!serverIpv4Gateway.isString()) {
logger.error() << "Unexpected serverIpv4Gateway value";
@@ -482,11 +493,6 @@ 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;
}

View File

@@ -12,7 +12,6 @@
#include "controllerimpl.h"
class QJsonObject;
class LocalSocketController final : public ControllerImpl {
@@ -59,7 +58,6 @@ class LocalSocketController final : public ControllerImpl {
QByteArray m_buffer;
QString m_deviceIpv4;
std::function<void(const QString&)> m_logCallback = nullptr;
QTimer m_initializingTimer;

View File

@@ -11,6 +11,7 @@
#include "logger.h"
//#include "mozillavpn.h"
#include "networkwatcherimpl.h"
#include "platforms/dummy/dummynetworkwatcher.h"
//#include "settingsholder.h"
#ifdef MZ_WINDOWS
@@ -50,7 +51,7 @@ NetworkWatcher::NetworkWatcher() { MZ_COUNT_CTOR(NetworkWatcher); }
NetworkWatcher::~NetworkWatcher() { MZ_COUNT_DTOR(NetworkWatcher); }
void NetworkWatcher::initialize() {
logger.debug() << "Initialize NetworkWatcher";
logger.debug() << "Initialize";
#if defined(MZ_WINDOWS)
m_impl = new WindowsNetworkWatcher(this);
@@ -68,45 +69,59 @@ 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();
// 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 {
// 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) {
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()";
}
m_active = true;
m_reportUnsecuredNetwork = false; // Disable unsecured network alerts for Amnezia
connect(settingsHolder, &SettingsHolder::unsecuredNetworkAlertChanged, this,
&NetworkWatcher::settingsChanged);
connect(settingsHolder, &SettingsHolder::captivePortalAlertChanged, this,
&NetworkWatcher::settingsChanged);
#endif
}
void NetworkWatcher::settingsChanged() {
// For Amnezia: Keep NetworkWatcher always active for sleep/wake monitoring
logger.debug() << "NetworkWatcher settings changed - keeping sleep monitoring active";
}
// TODO: IMPL FOR AMNEZIA
#if 0
SettingsHolder* settingsHolder = SettingsHolder::instance();
m_active = settingsHolder->unsecuredNetworkAlert() ||
settingsHolder->captivePortalAlert();
m_reportUnsecuredNetwork = settingsHolder->unsecuredNetworkAlert();
void NetworkWatcher::onSleepMode()
{
logger.debug() << "Resumed from sleep mode";
emit sleepMode();
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::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";

View File

@@ -29,13 +29,10 @@ public:
// false to restore.
void simulateDisconnection(bool simulatedDisconnection);
void onSleepMode();
QNetworkInformation::Reachability getReachability();
signals:
void networkChange();
void sleepMode();
private:
void settingsChanged();

View File

@@ -41,8 +41,6 @@ signals:
// TODO: Only windows-networkwatcher has this, the other plattforms should
// too.
void networkChanged(QString newBSSID);
void sleepMode();
private:
bool m_active = false;

View File

@@ -41,7 +41,6 @@ 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
@@ -54,10 +53,8 @@ void PingHelper::start(const QString& serverIpv4Gateway,
connect(m_pingSender, &PingSender::recvPing, this, &PingHelper::pingReceived,
Qt::QueuedConnection);
connect(m_pingSender, &PingSender::criticalPingError, this, [this]() {
logger.info() << "Encountered Unrecoverable ping error";
emit connectionLose();
});
connect(m_pingSender, &PingSender::criticalPingError, this,
[]() { logger.info() << "Encountered Unrecoverable ping error"; });
// Reset the ping statistics
m_sequence = 0;

View File

@@ -33,8 +33,6 @@ class PingHelper final : public QObject {
signals:
void pingSentAndReceived(qint64 msec);
void connectionLose();
private:
void nextPing();

View File

@@ -5,26 +5,27 @@
#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_WASM) || defined(UNIT_TEST)
# include "platforms/dummy/dummypingsender.h"
# include "platforms/windows/windowspingsender.h"
#elif defined(MZ_DUMMY) || 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 new LinuxPingSender(source, parent);
return nullptr;
// 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
}

View File

@@ -10,10 +10,9 @@ 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

View File

@@ -99,9 +99,7 @@ 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)},
{"onImeInsetsChanged", "(I)V", reinterpret_cast<void *>(onImeInsetsChanged)},
{"onSystemBarsInsetsChanged", "(II)V", reinterpret_cast<void *>(onSystemBarsInsetsChanged)}
{"decodeQrCode", "(Ljava/lang/String;)Z", reinterpret_cast<bool *>(decodeQrCode)}
};
QJniEnvironment env;
@@ -204,21 +202,6 @@ 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");
@@ -538,23 +521,3 @@ 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);
}

View File

@@ -39,9 +39,6 @@ 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);
@@ -73,8 +70,6 @@ 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;
@@ -103,8 +98,6 @@ 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);

View File

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

View File

@@ -2,8 +2,7 @@ import Foundation
import os.log
struct Log {
private static let subsystemIdentifier = Bundle.main.bundleIdentifier ?? "org.amnezia.AmneziaVPN"
static let osLog = Logger(subsystem: subsystemIdentifier, category: "App")
static let osLog = Logger()
private static let IsLoggingEnabledKey = "IsLoggingEnabled"
static var isLoggingEnabled: Bool {
@@ -78,41 +77,10 @@ struct Log {
static func log(_ type: OSLogType, title: String = "", message: String, url: URL = neLogURL) {
NSLog("\(title) \(message)")
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)")
}
}
guard isLoggingEnabled else { return }
osLog.log(level: type, "\(title) \(message)")
let date = Date()
let level = Record.Level(from: type)
let messages = message.split(whereSeparator: \.isNewline)

View File

@@ -1,76 +1,22 @@
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) {
let stringMessage = String(describing: staticMessage)
logToSystem(wireGuardSystemLogger, type: type, prefix: "WG", title: title, message: stringMessage)
neLog(type, title: "WG: \(title)", message: stringMessage)
neLog(type, title: "WG: \(title)", message: "\(staticMessage)")
}
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)
}

View File

@@ -1,7 +1,6 @@
import Foundation
import NetworkExtension
import OpenVPNAdapter
import CryptoKit
struct OpenVPNConfig: Decodable {
let config: String
@@ -28,83 +27,26 @@ extension PacketTunnelProvider {
let ovpnConfiguration = Data(openVPNConfig.config.utf8)
setupAndlaunchOpenVPN(withConfig: ovpnConfiguration, completionHandler: completionHandler)
} catch {
ovpnLog(.error, message: "Can't parse OpenVPN config: \(error.localizedDescription)")
ovpnLog(.error, message: "Can't parse config: \(error.localizedDescription)")
if let underlyingError = (error as NSError).userInfo[NSUnderlyingErrorKey] as? NSError {
ovpnLog(.error, message: "Can't parse config: \(underlyingError.localizedDescription)")
}
return
}
}
private func 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")
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 str = String(decoding: ovpnConfiguration, as: UTF8.self)
let configuration = OpenVPNConfiguration()
configuration.fileContent = sanitizedData
if configString.contains("cloak") {
configuration.fileContent = ovpnConfiguration
if str.contains("cloak") {
configuration.setPTCloak()
}
@@ -115,8 +57,6 @@ 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
}
@@ -131,7 +71,7 @@ extension PacketTunnelProvider {
}
startHandler = completionHandler
ovpnAdapter?.connect(using: openVPNPacketFlow())
ovpnAdapter?.connect(using: packetFlow)
}
func handleOpenVPNStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
@@ -153,7 +93,7 @@ extension PacketTunnelProvider {
}
func stopOpenVPN(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
ovpnLog(.info, message: "Stopping tunnel: reason: \(reason.amneziaDescription)")
ovpnLog(.info, message: "Stopping tunnel: reason: \(reason.description)")
stopHandler = completionHandler
if vpnReachability.isTracking {
@@ -268,11 +208,8 @@ 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 = nsError.userInfo[OpenVPNAdapterErrorFatalKey] as? Bool,
guard let fatal = (error as NSError).userInfo[OpenVPNAdapterErrorFatalKey] as? Bool,
fatal == true else { return }
if vpnReachability.isTracking {
@@ -293,3 +230,5 @@ extension PacketTunnelProvider: OpenVPNAdapterDelegate {
ovpnLog(.info, message: logMessage)
}
}
extension NEPacketTunnelFlow: OpenVPNAdapterPacketFlow {}

View File

@@ -94,24 +94,15 @@ extension PacketTunnelProvider {
}
} catch {
wg_log(.error, message: "Can't parse WG config: \(error.localizedDescription)")
errorNotifier.notify(PacketTunnelProviderError.savedProtocolConfigurationIsInvalid)
completionHandler(PacketTunnelProviderError.savedProtocolConfigurationIsInvalid)
completionHandler(nil)
return
}
}
func handleWireguardStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
guard let completionHandler = completionHandler else { return }
guard let wgAdapter = wgAdapter else {
completionHandler(nil)
return
}
wgAdapter.getRuntimeConfiguration { settings in
guard let settings = settings else {
completionHandler(nil)
return
}
let components = settings.components(separatedBy: "\n")
wgAdapter?.getRuntimeConfiguration { settings in
let components = settings!.components(separatedBy: "\n")
var settingsDictionary: [String: String] = [:]
for component in components {
@@ -140,7 +131,7 @@ extension PacketTunnelProvider {
}
}
func handleWireguardAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
private func handleWireguardAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
guard let completionHandler = completionHandler else { return }
if messageData.count == 1 && messageData[0] == 0 {
wgAdapter?.getRuntimeConfiguration { settings in
@@ -185,7 +176,7 @@ extension PacketTunnelProvider {
}
func stopWireguard(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
wg_log(.info, message: "Stopping tunnel: reason: \(reason.amneziaDescription)")
wg_log(.info, message: "Stopping tunnel: reason: \(reason.description)")
wgAdapter?.stop { error in
ErrorNotifier.removeLastErrorFile()

View File

@@ -1,5 +1,6 @@
import Foundation
import NetworkExtension
import WireGuardKitGo
enum XrayErrors: Error {
case noXrayConfig
@@ -107,8 +108,6 @@ extension PacketTunnelProvider {
return
}
self?.updateActiveInterfaceIndexForCurrentPath()
// Launch xray
self?.setupAndStartXray(configData: updatedData) { xrayError in
if let xrayError {
@@ -135,15 +134,6 @@ 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
@@ -153,17 +143,6 @@ 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)

View File

@@ -1,6 +1,5 @@
import Foundation
import NetworkExtension
import Network
import os
import Darwin
import OpenVPNAdapter
@@ -39,12 +38,6 @@ 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]?
@@ -54,90 +47,8 @@ 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, let proto = self.protoType else { return }
// WireGuard/AWG manages network changes internally; avoid restarting the tunnel here.
if proto == .wireguard {
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) {
if messageData.count == 1 && messageData[0] == 0 {
guard let completionHandler else { return }
if protoType == .wireguard {
handleWireguardAppMessage(messageData, completionHandler: completionHandler)
} else {
completionHandler(nil)
}
return
}
guard let message = String(data: messageData, encoding: .utf8) else {
if let completionHandler {
completionHandler(nil)
@@ -148,10 +59,6 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
neLog(.info, title: "App said: ", message: message)
guard let message = try? JSONSerialization.jsonObject(with: messageData, options: []) as? [String: Any] else {
if protoType == .wireguard {
handleWireguardAppMessage(messageData, completionHandler: completionHandler)
return
}
neLog(.error, message: "Failed to serialize message from app")
return
}
@@ -197,9 +104,6 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
return
}
didReceiveInitialPathUpdate = false
updateActiveInterfaceIndexForCurrentPath()
switch protoType {
case .wireguard:
startWireguard(activationAttemptId: activationAttemptId,
@@ -253,63 +157,28 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
of object: Any?,
change: [NSKeyValueChangeKey: Any]?,
context: UnsafeMutableRawPointer?) {
guard Constants.kDefaultPathKey == keyPath else {
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 {
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: Network.NWPath, completion: @escaping (Error?) -> Void) {
updateActiveInterfaceIndex(for: changePath)
private func handle(networkChange changePath: NWPath, completion: @escaping (Error?) -> Void) {
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 {
@@ -321,27 +190,8 @@ extension WireGuardLogLevel {
}
}
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 {
extension NEProviderStopReason: CustomStringConvertible {
public var description: String {
switch self {
case .none:
return "No specific reason"
@@ -373,8 +223,6 @@ extension NEProviderStopReason {
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:

View File

@@ -11,7 +11,13 @@ class ScreenProtection {
import UIKit
public func toggleScreenshots(_ isEnabled: Bool) {
ScreenProtection.shared.setScreenshotsEnabled(isEnabled)
let window = UIApplication.shared.keyWindows.first!
if isEnabled {
ScreenProtection.shared.disable(for: window.rootViewController!.view)
} else {
ScreenProtection.shared.enable(for: window.rootViewController!.view)
}
}
extension UIApplication {
@@ -39,45 +45,6 @@ class ScreenProtection {
private var blurView: UIVisualEffectView?
private var recordingObservation: NSKeyValueObservation?
private var desiredScreenshotsEnabled: Bool?
private var retryCount = 0
private var retryWorkItem: DispatchWorkItem?
public func setScreenshotsEnabled(_ isEnabled: Bool) {
DispatchQueue.main.async {
self.desiredScreenshotsEnabled = isEnabled
self.applyScreenshotsSettingOrRetry()
}
}
private func applyScreenshotsSettingOrRetry() {
assert(Thread.isMainThread)
guard let desiredScreenshotsEnabled else { return }
guard let window = UIApplication.shared.keyWindows.first,
let rootView = window.rootViewController?.view else {
retryCount += 1
guard retryCount <= 50 else { return } // ~5s total
retryWorkItem?.cancel()
let item = DispatchWorkItem { [weak self] in
self?.applyScreenshotsSettingOrRetry()
}
retryWorkItem = item
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: item)
return
}
retryWorkItem?.cancel()
retryWorkItem = nil
retryCount = 0
if desiredScreenshotsEnabled {
disable(for: rootView)
} else {
enable(for: rootView)
}
}
public func enable(for view: UIView) {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {

View File

@@ -1,39 +0,0 @@
/* 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/. */
#ifndef STOREKITCONTROLLER_H
#define STOREKITCONTROLLER_H
#import <Foundation/Foundation.h>
#import <StoreKit/StoreKit.h>
@class Product;
@class Transaction;
@class VerificationResult;
API_AVAILABLE(ios(15.0), macos(12.0))
@interface StoreKitController : NSObject
+ (instancetype)sharedInstance;
- (void)purchaseProduct:(NSString *)productIdentifier
completion:(void (^)(BOOL success,
NSString *_Nullable transactionId,
NSString *_Nullable productId,
NSString *_Nullable originalTransactionId,
NSError *_Nullable error))completion;
- (void)restorePurchasesWithCompletion:(void (^)(BOOL success,
NSArray<NSDictionary *> *_Nullable restoredTransactions,
NSError *_Nullable error))completion;
// Fetch product information for a set of identifiers without initiating a purchase
- (void)fetchProductsWithIdentifiers:(NSSet<NSString *> *)productIdentifiers
completion:(void (^)(NSArray<NSDictionary *> *products,
NSArray<NSString *> *invalidIdentifiers,
NSError *_Nullable error))completion;
@end
#endif // STOREKITCONTROLLER_H

View File

@@ -1,264 +0,0 @@
/* 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/. */
#import "StoreKitController.h"
#import <StoreKit/StoreKit.h>
#include <QtCore/QDebug>
#include <QtCore/QString>
API_AVAILABLE(ios(15.0), macos(12.0))
@interface StoreKitController () <SKProductsRequestDelegate, SKPaymentTransactionObserver>
@property (nonatomic, copy) void (^purchaseCompletion)(BOOL success,
NSString *_Nullable transactionId,
NSString *_Nullable productId,
NSString *_Nullable originalTransactionId,
NSError *_Nullable error);
@property (nonatomic, copy) void (^restoreCompletion)(BOOL success,
NSArray<NSDictionary *> *_Nullable restoredTransactions,
NSError *_Nullable error);
@property (nonatomic, copy) void (^productsFetchCompletion)(NSArray<NSDictionary *> *products,
NSArray<NSString *> *invalidIdentifiers,
NSError *_Nullable error);
@property (nonatomic, strong) SKProductsRequest *productsRequest;
@property (nonatomic, strong) NSMutableArray<NSDictionary *> *restoredTransactions;
@end
@implementation StoreKitController
+ (instancetype)sharedInstance
{
static dispatch_once_t onceToken;
static StoreKitController *instance;
dispatch_once(&onceToken, ^{
if (@available(iOS 15.0, macOS 12.0, *)) {
instance = [[StoreKitController alloc] init];
}
});
return instance;
}
- (instancetype)init API_AVAILABLE(ios(15.0), macos(12.0))
{
self = [super init];
if (self) {
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}
return self;
}
- (void)dealloc
{
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
}
- (void)purchaseProduct:(NSString *)productIdentifier
completion:(void (^)(BOOL success,
NSString *_Nullable transactionId,
NSString *_Nullable productId,
NSString *_Nullable originalTransactionId,
NSError *_Nullable error))completion API_AVAILABLE(ios(15.0), macos(12.0))
{
self.purchaseCompletion = completion;
qInfo().noquote() << "[IAP][StoreKit] Starting purchase for" << QString::fromUtf8(productIdentifier.UTF8String);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self performPurchaseAsync:productIdentifier];
});
}
- (void)performPurchaseAsync:(NSString *)productIdentifier API_AVAILABLE(ios(15.0), macos(12.0))
{
dispatch_async(dispatch_get_main_queue(), ^{
@try {
SKProductsRequest *request = [[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithObject:productIdentifier]];
request.delegate = self;
[request start];
} @catch (NSException *exception) {
NSError *error = [NSError errorWithDomain:@"StoreKitController"
code:1
userInfo:@{ NSLocalizedDescriptionKey : exception.reason ?: @"Purchase failed" }];
if (self.purchaseCompletion) {
self.purchaseCompletion(NO, nil, nil, nil, error);
self.purchaseCompletion = nil;
}
}
});
}
- (void)restorePurchasesWithCompletion:(void (^)(BOOL success,
NSArray<NSDictionary *> *_Nullable restoredTransactions,
NSError *_Nullable error))completion API_AVAILABLE(ios(15.0), macos(12.0))
{
self.restoreCompletion = completion;
self.restoredTransactions = [NSMutableArray array];
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}
- (void)fetchProductsWithIdentifiers:(NSSet<NSString *> *)productIdentifiers
completion:(void (^)(NSArray<NSDictionary *> *products,
NSArray<NSString *> *invalidIdentifiers,
NSError *_Nullable error))completion API_AVAILABLE(ios(15.0), macos(12.0))
{
self.productsFetchCompletion = completion;
self.productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];
self.productsRequest.delegate = self;
[self.productsRequest start];
}
#pragma mark - SKProductsRequestDelegate / SKRequestDelegate
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
if (self.purchaseCompletion) {
SKProduct *product = response.products.firstObject;
if (!product) {
NSError *error = [NSError errorWithDomain:@"StoreKitController"
code:0
userInfo:@{ NSLocalizedDescriptionKey : @"Product not found" }];
self.purchaseCompletion(NO, nil, nil, nil, error);
self.purchaseCompletion = nil;
self.productsRequest = nil;
return;
}
NSString *currencyCode = [product.priceLocale objectForKey:NSLocaleCurrencyCode] ?: @"";
NSString *priceString = [product.price stringValue] ?: @"";
qInfo().noquote() << "[IAP][StoreKit] Received product" << QString::fromUtf8(product.productIdentifier.UTF8String)
<< "price=" << QString::fromUtf8(priceString.UTF8String)
<< "currency=" << QString::fromUtf8(currencyCode.UTF8String);
SKPayment *payment = [SKPayment paymentWithProduct:product];
[[SKPaymentQueue defaultQueue] addPayment:payment];
self.productsRequest = nil;
return;
}
if (self.productsFetchCompletion) {
NSMutableArray<NSDictionary *> *productDicts = [NSMutableArray array];
for (SKProduct *p in response.products) {
NSDictionary *productDict = @{
@"productId": p.productIdentifier,
@"title": p.localizedTitle,
@"description": p.localizedDescription,
@"price": p.price.stringValue,
@"currencyCode": [p.priceLocale objectForKey:NSLocaleCurrencyCode] ?: @""
};
[productDicts addObject:productDict];
NSString *productCurrency = [p.priceLocale objectForKey:NSLocaleCurrencyCode] ?: @"";
NSString *productPrice = [p.price stringValue] ?: @"";
qInfo().noquote() << "[IAP][StoreKit] Fetched product info" << QString::fromUtf8(p.productIdentifier.UTF8String)
<< "price=" << QString::fromUtf8(productPrice.UTF8String)
<< "currency=" << QString::fromUtf8(productCurrency.UTF8String);
}
self.productsFetchCompletion(productDicts, response.invalidProductIdentifiers, nil);
self.productsFetchCompletion = nil;
self.productsRequest = nil;
return;
}
}
- (void)request:(SKRequest *)request didFailWithError:(NSError *)error
{
if (self.purchaseCompletion) {
self.purchaseCompletion(NO, nil, nil, nil, error);
self.purchaseCompletion = nil;
}
if (self.productsFetchCompletion) {
self.productsFetchCompletion(@[], @[], error);
self.productsFetchCompletion = nil;
}
self.productsRequest = nil;
}
#pragma mark - SKPaymentTransactionObserver
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray<SKPaymentTransaction *> *)transactions
{
for (SKPaymentTransaction *transaction in transactions) {
switch (transaction.transactionState) {
case SKPaymentTransactionStatePurchased: {
NSString *originalTransactionId = transaction.originalTransaction.transactionIdentifier ?: transaction.transactionIdentifier;
qInfo().noquote() << "[IAP][StoreKit] Transaction purchased" << QString::fromUtf8(transaction.transactionIdentifier.UTF8String)
<< "original=" << QString::fromUtf8((originalTransactionId ?: @"").UTF8String)
<< "product=" << QString::fromUtf8(transaction.payment.productIdentifier.UTF8String);
if (self.purchaseCompletion) {
self.purchaseCompletion(YES,
transaction.transactionIdentifier,
transaction.payment.productIdentifier,
originalTransactionId,
nil);
self.purchaseCompletion = nil;
}
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
}
case SKPaymentTransactionStateFailed:
qInfo().noquote() << "[IAP][StoreKit] Transaction failed" << QString::fromUtf8(transaction.transactionIdentifier.UTF8String)
<< "product=" << QString::fromUtf8(transaction.payment.productIdentifier.UTF8String)
<< "error=" << QString::fromUtf8(transaction.error.localizedDescription.UTF8String);
if (self.purchaseCompletion) {
self.purchaseCompletion(NO,
transaction.transactionIdentifier,
transaction.payment.productIdentifier,
nil,
transaction.error);
self.purchaseCompletion = nil;
}
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
case SKPaymentTransactionStateRestored: {
if (self.restoreCompletion) {
NSString *transactionId = transaction.transactionIdentifier ?: @"";
NSString *originalTransactionId = transaction.originalTransaction.transactionIdentifier ?: transactionId;
NSString *productId = transaction.payment.productIdentifier ?: @"";
qInfo().noquote() << "[IAP][StoreKit] Transaction restored"
<< QString::fromUtf8(transactionId.UTF8String)
<< "original="
<< QString::fromUtf8((originalTransactionId ?: @"").UTF8String)
<< "product="
<< QString::fromUtf8((productId ?: @"").UTF8String);
NSDictionary *info = @{
@"transactionId": transactionId,
@"originalTransactionId": originalTransactionId ?: @"",
@"productId": productId ?: @""
};
if (!self.restoredTransactions) {
self.restoredTransactions = [NSMutableArray array];
}
[self.restoredTransactions addObject:info];
}
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
break;
}
case SKPaymentTransactionStatePurchasing:
case SKPaymentTransactionStateDeferred:
break;
}
}
}
- (void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue
{
if (self.restoreCompletion) {
NSArray<NSDictionary *> *transactions = [self.restoredTransactions copy];
self.restoreCompletion(YES, transactions, nil);
self.restoreCompletion = nil;
self.restoredTransactions = nil;
}
}
- (void)paymentQueue:(SKPaymentQueue *)queue restoreCompletedTransactionsFailedWithError:(NSError *)error
{
if (self.restoreCompletion) {
self.restoreCompletion(NO, nil, error);
self.restoreCompletion = nil;
self.restoredTransactions = nil;
}
}
@end

View File

@@ -6,6 +6,8 @@ struct WGConfig: Decodable {
let junkPacketCount, junkPacketMinSize, junkPacketMaxSize: String?
let initPacketJunkSize, responsePacketJunkSize, cookieReplyPacketJunkSize, transportPacketJunkSize: String?
let specialJunk1, specialJunk2, specialJunk3, specialJunk4, specialJunk5: String?
let controlledJunk1, controlledJunk2, controlledJunk3: String?
let specialHandshakeTimeout: String?
let dns1: String
let dns2: String
let mtu: String
@@ -26,6 +28,8 @@ struct WGConfig: Decodable {
case junkPacketCount = "Jc", junkPacketMinSize = "Jmin", junkPacketMaxSize = "Jmax"
case initPacketJunkSize = "S1", responsePacketJunkSize = "S2", cookieReplyPacketJunkSize = "S3", transportPacketJunkSize = "S4"
case specialJunk1 = "I1", specialJunk2 = "I2", specialJunk3 = "I3", specialJunk4 = "I4", specialJunk5 = "I5"
case controlledJunk1 = "J1", controlledJunk2 = "J2", controlledJunk3 = "J3"
case specialHandshakeTimeout = "Itime"
case dns1
case dns2
case mtu
@@ -42,64 +46,58 @@ struct WGConfig: Decodable {
}
var settings: String {
func trimmed(_ value: String?) -> String? {
guard let value = value?.trimmingCharacters(in: .whitespacesAndNewlines),
!value.isEmpty else {
return nil
}
return value
}
guard
let junkPacketCount = trimmed(junkPacketCount),
let junkPacketMinSize = trimmed(junkPacketMinSize),
let junkPacketMaxSize = trimmed(junkPacketMaxSize),
let initPacketJunkSize = trimmed(initPacketJunkSize),
let responsePacketJunkSize = trimmed(responsePacketJunkSize),
let initPacketMagicHeader = trimmed(initPacketMagicHeader),
let responsePacketMagicHeader = trimmed(responsePacketMagicHeader),
let underloadPacketMagicHeader = trimmed(underloadPacketMagicHeader),
let transportPacketMagicHeader = trimmed(transportPacketMagicHeader)
else { return "" }
guard junkPacketCount != nil else { return "" }
var settingsLines: [String] = []
// Required parameters when junkPacketCount is present
settingsLines.append("Jc = \(junkPacketCount)")
settingsLines.append("Jmin = \(junkPacketMinSize)")
settingsLines.append("Jmax = \(junkPacketMaxSize)")
settingsLines.append("S1 = \(initPacketJunkSize)")
settingsLines.append("S2 = \(responsePacketJunkSize)")
settingsLines.append("H1 = \(initPacketMagicHeader)")
settingsLines.append("H2 = \(responsePacketMagicHeader)")
settingsLines.append("H3 = \(underloadPacketMagicHeader)")
settingsLines.append("H4 = \(transportPacketMagicHeader)")
settingsLines.append("Jc = \(junkPacketCount!)")
settingsLines.append("Jmin = \(junkPacketMinSize!)")
settingsLines.append("Jmax = \(junkPacketMaxSize!)")
settingsLines.append("S1 = \(initPacketJunkSize!)")
settingsLines.append("S2 = \(responsePacketJunkSize!)")
settingsLines.append("H1 = \(initPacketMagicHeader!)")
settingsLines.append("H2 = \(responsePacketMagicHeader!)")
settingsLines.append("H3 = \(underloadPacketMagicHeader!)")
settingsLines.append("H4 = \(transportPacketMagicHeader!)")
// Optional parameters - only add if not nil and not empty
if let s3 = trimmed(cookieReplyPacketJunkSize) {
if let s3 = cookieReplyPacketJunkSize, !s3.isEmpty {
settingsLines.append("S3 = \(s3)")
}
if let s4 = trimmed(transportPacketJunkSize) {
if let s4 = transportPacketJunkSize, !s4.isEmpty {
settingsLines.append("S4 = \(s4)")
}
if let i1 = trimmed(specialJunk1) {
if let i1 = specialJunk1, !i1.isEmpty {
settingsLines.append("I1 = \(i1)")
}
if let i2 = trimmed(specialJunk2) {
if let i2 = specialJunk2, !i2.isEmpty {
settingsLines.append("I2 = \(i2)")
}
if let i3 = trimmed(specialJunk3) {
if let i3 = specialJunk3, !i3.isEmpty {
settingsLines.append("I3 = \(i3)")
}
if let i4 = trimmed(specialJunk4) {
if let i4 = specialJunk4, !i4.isEmpty {
settingsLines.append("I4 = \(i4)")
}
if let i5 = trimmed(specialJunk5) {
if let i5 = specialJunk5, !i5.isEmpty {
settingsLines.append("I5 = \(i5)")
}
if let j1 = controlledJunk1, !j1.isEmpty {
settingsLines.append("J1 = \(j1)")
}
if let j2 = controlledJunk2, !j2.isEmpty {
settingsLines.append("J2 = \(j2)")
}
if let j3 = controlledJunk3, !j3.isEmpty {
settingsLines.append("J3 = \(j3)")
}
if let itime = specialHandshakeTimeout, !itime.isEmpty {
settingsLines.append("Itime = \(itime)")
}
return settingsLines.joined(separator: "\n")
}

View File

@@ -2,13 +2,6 @@
#define IOS_CONTROLLER_H
#include "protocols/vpnprotocol.h"
#include <functional>
#include <QVariant>
#include <QVariantMap>
#include <QStringList>
#include <QList>
#include <QElapsedTimer>
#include <atomic>
#ifdef __OBJC__
#import <Foundation/Foundation.h>
@@ -62,24 +55,7 @@ public:
bool shareText(const QStringList &filesToSend);
QString openFile();
void purchaseProduct(const QString &productId,
std::function<void(bool success,
const QString &transactionId,
const QString &purchasedProductId,
const QString &originalTransactionId,
const QString &errorString)> &&callback);
void restorePurchases(std::function<void(bool success,
const QList<QVariantMap> &transactions,
const QString &errorString)> &&callback);
// Fetch product info for given product identifiers and return basic fields for logging
void fetchProducts(const QStringList &productIds,
std::function<void(const QList<QVariantMap> &products,
const QStringList &invalidIds,
const QString &errorString)> &&callback);
void requestInetAccess();
bool isTestFlight();
signals:
void connectionStateChanged(Vpn::ConnectionState state);
void bytesChanged(quint64 receivedBytes, quint64 sentBytes);
@@ -105,7 +81,6 @@ private:
bool startXray(const QString &jsonConfig);
void startTunnel();
void emitConnectionStateIfChanged(Vpn::ConnectionState state);
private:
void *m_iosControllerWrapper {};
@@ -119,13 +94,8 @@ private:
amnezia::Proto m_proto;
QJsonObject m_rawConfig;
QString m_tunnelId;
uint64_t m_txBytes = 0;
uint64_t m_rxBytes = 0;
bool m_handshakeAwaiting = false;
bool m_handshakeConfirmed = false;
QElapsedTimer m_handshakeTimer;
Vpn::ConnectionState m_lastEmittedState = Vpn::ConnectionState::Unknown;
std::atomic_bool m_statusRequestInFlight { false };
uint64_t m_txBytes;
uint64_t m_rxBytes;
};
#endif // IOS_CONTROLLER_H

View File

@@ -10,7 +10,6 @@
#include "../protocols/vpnprotocol.h"
#import "ios_controller_wrapper.h"
#import "StoreKitController.h"
const char* Action::start = "start";
const char* Action::restart = "restart";
@@ -30,46 +29,12 @@ const char* MessageKey::SplitTunnelSites = "SplitTunnelSites";
#if !MACOS_NE
static UIViewController* getViewController() {
UIApplication *application = [UIApplication sharedApplication];
if (@available(iOS 13.0, *)) {
for (UIScene *scene in application.connectedScenes) {
if (scene.activationState != UISceneActivationStateForegroundActive) {
continue;
}
if (![scene isKindOfClass:[UIWindowScene class]]) {
continue;
}
UIWindowScene *windowScene = (UIWindowScene *)scene;
for (UIWindow *window in windowScene.windows) {
if (window.isKeyWindow && window.rootViewController) {
return window.rootViewController;
}
}
for (UIWindow *window in windowScene.windows) {
if (!window.isHidden && window.rootViewController) {
return window.rootViewController;
}
}
}
}
for (UIWindow *window in application.windows) {
if (window.isKeyWindow && window.rootViewController) {
NSArray *windows = [[UIApplication sharedApplication]windows];
for (UIWindow *window in windows) {
if (window.isKeyWindow) {
return window.rootViewController;
}
}
for (UIWindow *window in application.windows) {
if (window.rootViewController) {
return window.rootViewController;
}
}
return nil;
}
#endif
@@ -93,48 +58,6 @@ Vpn::ConnectionState iosStatusToState(NEVPNStatus status) {
}
}
namespace {
constexpr int kHandshakeTimeoutMs = 12000;
constexpr uint64_t kHandshakeRxThreshold = 4096;
bool isWireGuardBasedProto(amnezia::Proto proto) {
return proto == amnezia::Proto::WireGuard || proto == amnezia::Proto::Awg;
}
uint64_t uint64FromResponse(NSDictionary *response, NSString *key, uint64_t fallback = 0) {
id value = response[key];
if (!value || value == [NSNull null]) {
return fallback;
}
if ([value isKindOfClass:[NSNumber class]]) {
return [(NSNumber *)value unsignedLongLongValue];
}
if ([value isKindOfClass:[NSString class]]) {
const char *str = [(NSString *)value UTF8String];
if (str && *str) {
return strtoull(str, nullptr, 10);
}
}
return fallback;
}
long long int64FromResponse(NSDictionary *response, NSString *key, long long fallback = 0) {
id value = response[key];
if (!value || value == [NSNull null]) {
return fallback;
}
if ([value isKindOfClass:[NSNumber class]]) {
return [(NSNumber *)value longLongValue];
}
if ([value isKindOfClass:[NSString class]]) {
const char *str = [(NSString *)value UTF8String];
if (str && *str) {
return strtoll(str, nullptr, 10);
}
}
return fallback;
}
}
namespace {
IosController* s_instance = nullptr;
}
@@ -144,9 +67,6 @@ IosController::IosController() : QObject()
s_instance = this;
m_iosControllerWrapper = [[IosControllerWrapper alloc] initWithCppController:this];
// Initialize StoreKitController early to start observing the payment queue
[StoreKitController sharedInstance];
[[NSNotificationCenter defaultCenter]
removeObserver: (__bridge NSObject *)m_iosControllerWrapper];
[[NSNotificationCenter defaultCenter]
@@ -156,15 +76,6 @@ IosController::IosController() : QObject()
}
void IosController::emitConnectionStateIfChanged(Vpn::ConnectionState state)
{
if (m_lastEmittedState == state) {
return;
}
m_lastEmittedState = state;
emit connectionStateChanged(state);
}
IosController* IosController::Instance() {
if (!s_instance) {
s_instance = new IosController();
@@ -331,65 +242,33 @@ void IosController::disconnectVpn()
void IosController::checkStatus()
{
if (!m_currentTunnel) {
return;
}
if (m_currentTunnel.connection.status != NEVPNStatusConnected) {
return;
}
if (m_statusRequestInFlight.exchange(true)) {
return;
}
NSString *actionKey = [NSString stringWithUTF8String:MessageKey::action];
NSString *actionValue = [NSString stringWithUTF8String:Action::getStatus];
NSString *tunnelIdKey = [NSString stringWithUTF8String:MessageKey::tunnelId];
NSString *tunnelIdValue = !m_tunnelId.isEmpty() ? m_tunnelId.toNSString() : @"";
NSDictionary* message = @{actionKey: actionValue, tunnelIdKey: tunnelIdValue};
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sendVpnExtensionMessage(message, [&](NSDictionary* response){
if (!response) {
QMetaObject::invokeMethod(this, [this]() {
m_statusRequestInFlight = false;
}, Qt::QueuedConnection);
return;
uint64_t txBytes = [response[@"tx_bytes"] intValue];
uint64_t rxBytes = [response[@"rx_bytes"] intValue];
uint64_t last_handshake_time_sec = 0;
#if !MACOS_NE
if (response[@"last_handshake_time_sec"] && ![response[@"last_handshake_time_sec"] isKindOfClass:[NSNull class]]) {
last_handshake_time_sec = [response[@"last_handshake_time_sec"] intValue];
} else {
qDebug() << "Key last_handshake_time_sec is missing or null";
}
const uint64_t txBytes = uint64FromResponse(response, @"tx_bytes");
const uint64_t rxBytes = uint64FromResponse(response, @"rx_bytes");
const long long last_handshake_time_sec = int64FromResponse(response, @"last_handshake_time_sec");
if (last_handshake_time_sec < 0) {
disconnectVpn();
qDebug() << "Invalid handshake time, disconnecting VPN.";
}
#endif
QMetaObject::invokeMethod(this, [this, txBytes, rxBytes, last_handshake_time_sec]() {
if (isWireGuardBasedProto(m_proto) && m_handshakeAwaiting) {
const bool hasHandshakeData = (last_handshake_time_sec >= 0);
const bool hasFreshHandshake = hasHandshakeData &&
((last_handshake_time_sec > 0) ||
(rxBytes >= kHandshakeRxThreshold) ||
(txBytes >= kHandshakeRxThreshold));
if (hasFreshHandshake) {
m_handshakeConfirmed = true;
m_handshakeAwaiting = false;
m_handshakeTimer.invalidate();
qDebug() << "IosController::checkStatus : handshake confirmed";
emitConnectionStateIfChanged(Vpn::ConnectionState::Connected);
} else if (m_handshakeTimer.isValid() &&
m_handshakeTimer.elapsed() > kHandshakeTimeoutMs) {
m_handshakeTimer.restart();
qDebug() << "IosController::checkStatus : handshake timed out, keeping tunnel alive";
emitConnectionStateIfChanged(Vpn::ConnectionState::Reconnecting);
}
}
emit bytesChanged(rxBytes - m_rxBytes, txBytes - m_txBytes);
m_rxBytes = rxBytes;
m_txBytes = txBytes;
m_statusRequestInFlight = false;
}, Qt::QueuedConnection);
});
emit bytesChanged(rxBytes - m_rxBytes, txBytes - m_txBytes);
m_rxBytes = rxBytes;
m_txBytes = txBytes;
});
}
@@ -496,22 +375,7 @@ void IosController::vpnStatusDidChange(void *pNotification)
}
}
Vpn::ConnectionState nextState = iosStatusToState(session.status);
if (session.status == NEVPNStatusConnected && isWireGuardBasedProto(m_proto)) {
if (!m_handshakeConfirmed) {
nextState = Vpn::ConnectionState::Connecting;
if (!m_handshakeAwaiting) {
m_handshakeAwaiting = true;
m_handshakeTimer.restart();
}
}
} else if (session.status != NEVPNStatusConnected) {
m_handshakeAwaiting = false;
m_handshakeConfirmed = false;
m_handshakeTimer.invalidate();
m_statusRequestInFlight = false;
}
emitConnectionStateIfChanged(nextState);
emit connectionStateChanged(iosStatusToState(session.status));
}
}
@@ -772,6 +636,10 @@ bool IosController::setupAwg()
wgConfig.insert(config_key::specialJunk3, config[config_key::specialJunk3]);
wgConfig.insert(config_key::specialJunk4, config[config_key::specialJunk4]);
wgConfig.insert(config_key::specialJunk5, config[config_key::specialJunk5]);
wgConfig.insert(config_key::controlledJunk1, config[config_key::controlledJunk1]);
wgConfig.insert(config_key::controlledJunk2, config[config_key::controlledJunk2]);
wgConfig.insert(config_key::controlledJunk3, config[config_key::controlledJunk3]);
wgConfig.insert(config_key::specialHandshakeTimeout, config[config_key::specialHandshakeTimeout]);
QJsonDocument wgConfigDoc(wgConfig);
QString wgConfigDocStr(wgConfigDoc.toJson(QJsonDocument::Compact));
@@ -897,9 +765,6 @@ void IosController::sendVpnExtensionMessage(NSDictionary* message, std::function
{
if (!m_currentTunnel) {
qDebug() << "Cannot set an extension callback without a tunnel manager";
if (callback) {
callback(nil);
}
return;
}
@@ -909,9 +774,6 @@ void IosController::sendVpnExtensionMessage(NSDictionary* message, std::function
if (!data || error) {
qDebug() << "Failed to serialize message to VpnExtension as JSON. Error:"
<< [error.localizedDescription UTF8String];
if (callback) {
callback(nil);
}
return;
}
@@ -942,18 +804,11 @@ void IosController::sendVpnExtensionMessage(NSDictionary* message, std::function
[session sendProviderMessage:data returnError:&sendError responseHandler:completionHandler];
} else {
qDebug() << "Method sendProviderMessage:responseHandler:error: does not exist";
if (callback) {
callback(nil);
}
return;
}
if (sendError) {
qDebug() << "Failed to send message to VpnExtension. Error:"
<< [sendError.localizedDescription UTF8String];
if (callback) {
callback(nil);
}
}
}
@@ -1024,135 +879,6 @@ QString IosController::openFile() {
return filePath;
}
void IosController::purchaseProduct(const QString &productId,
std::function<void(bool success,
const QString &transactionId,
const QString &purchasedProductId,
const QString &originalTransactionId,
const QString &errorString)> &&callback)
{
qInfo().noquote() << "[IAP][IosController] purchaseProduct called" << productId;
if (@available(iOS 15.0, macOS 12.0, *)) {
StoreKitController *controller = [StoreKitController sharedInstance];
__block auto cb = std::move(callback);
[controller purchaseProduct:productId.toNSString() completion:^(BOOL s,
NSString * _Nullable transactionId,
NSString * _Nullable prodId,
NSString * _Nullable originalTxId,
NSError * _Nullable error) {
const QString txId = QString::fromUtf8((transactionId ?: @"").UTF8String);
const QString pId = QString::fromUtf8((prodId ?: @"").UTF8String);
const QString origTxId = QString::fromUtf8((originalTxId ?: @"").UTF8String);
const QString err = QString::fromUtf8((error.localizedDescription ?: @"").UTF8String);
qInfo().noquote() << "[IAP][IosController] purchase completion" << "success=" << s
<< "transactionId=" << txId << "originalTransactionId=" << origTxId
<< "productId=" << pId << "error=" << err;
if (cb) {
cb(s, txId, pId, origTxId, err);
}
}];
} else {
if (callback) {
callback(false, QString(), QString(), QString(), "StoreKit 2 requires iOS 15.0 or later");
}
}
}
void IosController::restorePurchases(std::function<void(bool success,
const QList<QVariantMap> &transactions,
const QString &errorString)> &&callback)
{
if (@available(iOS 15.0, macOS 12.0, *)) {
StoreKitController *controller = [StoreKitController sharedInstance];
__block auto cb = std::move(callback);
[controller restorePurchasesWithCompletion:^(BOOL s,
NSArray<NSDictionary *> * _Nullable restoredTransactions,
NSError * _Nullable error) {
QString err;
if (error) {
err = QString::fromUtf8(error.localizedDescription.UTF8String);
}
QList<QVariantMap> transactions;
for (NSDictionary *dict in restoredTransactions ?: @[]) {
QVariantMap transaction;
NSString *transactionId = dict[@"transactionId"];
NSString *productId = dict[@"productId"];
NSString *originalTransactionId = dict[@"originalTransactionId"];
if (transactionId) {
transaction.insert(QStringLiteral("transactionId"), QString::fromUtf8(transactionId.UTF8String));
}
if (productId) {
transaction.insert(QStringLiteral("productId"), QString::fromUtf8(productId.UTF8String));
}
if (originalTransactionId) {
transaction.insert(QStringLiteral("originalTransactionId"),
QString::fromUtf8(originalTransactionId.UTF8String));
}
transactions.push_back(transaction);
}
if (cb) {
cb(s, transactions, err);
}
}];
} else {
if (callback) {
callback(false, QList<QVariantMap>(), "StoreKit 2 requires iOS 15.0 or later");
}
}
}
void IosController::fetchProducts(const QStringList &productIds,
std::function<void(const QList<QVariantMap> &products,
const QStringList &invalidIds,
const QString &errorString)> &&callback)
{
if (@available(iOS 15.0, macOS 12.0, *)) {
StoreKitController *controller = [StoreKitController sharedInstance];
NSMutableSet<NSString *> *ids = [NSMutableSet setWithCapacity:productIds.size()];
for (const auto &pid : productIds) {
[ids addObject:pid.toNSString()];
}
__block auto cb = std::move(callback);
[controller fetchProductsWithIdentifiers:ids
completion:^(NSArray<NSDictionary *> * _Nonnull products,
NSArray<NSString *> * _Nonnull invalidIdentifiers,
NSError * _Nullable error) {
QList<QVariantMap> outProducts;
for (NSDictionary *p in products) {
QVariantMap m;
m["productId"] = QString::fromUtf8([p[@"productId"] UTF8String]);
m["title"] = QString::fromUtf8([p[@"title"] UTF8String]);
m["description"] = QString::fromUtf8([p[@"description"] UTF8String]);
m["price"] = QString::fromUtf8([p[@"price"] UTF8String]);
m["currencyCode"] = QString::fromUtf8([p[@"currencyCode"] UTF8String]);
outProducts.push_back(m);
}
QStringList invalid;
for (NSString *inv in invalidIdentifiers) {
invalid.push_back(QString::fromUtf8(inv.UTF8String));
}
QString err;
if (error) {
err = QString::fromUtf8(error.localizedDescription.UTF8String);
}
if (cb) {
cb(outProducts, invalid, err);
}
}];
} else {
if (callback) {
callback(QList<QVariantMap>(), QStringList(), "StoreKit 2 requires iOS 15.0 or later");
}
}
}
void IosController::requestInetAccess() {
NSURL *url = [NSURL URLWithString:@"http://captive.apple.com/generate_204"];
if (!url) {
@@ -1171,8 +897,3 @@ void IosController::requestInetAccess() {
}];
[task resume];
}
bool IosController::isTestFlight() {
NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
return receiptURL && [[receiptURL lastPathComponent] isEqualToString:@"sandboxReceipt"];
}

View File

@@ -34,9 +34,6 @@ void IOSNetworkWatcher::initialize() {
});
nw_path_monitor_start(m_networkMonitor);
// Call start() to initialize sleep/wake monitoring (will call MacOSNetworkWatcher::start() if this is macOS)
this->start();
//TODO IMPL FOR AMNEZIA
}

View File

@@ -165,7 +165,7 @@ bool LinuxRouteMonitor::rtmSendRoute(int action, int flags, int type,
if (rtm->rtm_type == RTN_THROW) {
struct in_addr ip4;
inet_pton(AF_INET, NetworkUtilities::getGatewayAndIface().first.toUtf8(), &ip4);
inet_pton(AF_INET, NetworkUtilities::getGatewayAndIface().toUtf8(), &ip4);
nlmsg_append_attr(nlmsg, sizeof(buf), RTA_GATEWAY, &ip4, sizeof(ip4));
nlmsg_append_attr32(nlmsg, sizeof(buf), RTA_PRIORITY, 0);
rtm->rtm_type = RTN_UNICAST;

View File

@@ -143,6 +143,12 @@ bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) {
for (const QString& key : config.m_specialJunk.keys()) {
out << key.toLower() << "=" << config.m_specialJunk.value(key) << "\n";
}
for (const QString& key : config.m_controlledJunk.keys()) {
out << key.toLower() << "=" << config.m_controlledJunk.value(key) << "\n";
}
if (!config.m_specialHandshakeTimeout.isEmpty()) {
out << "itime=" << config.m_specialHandshakeTimeout << "\n";
}
int err = uapiErrno(uapiCommand(message));
if (err != 0) {

View File

@@ -41,9 +41,6 @@ void LinuxNetworkWatcher::initialize() {
connect(m_worker, &LinuxNetworkWatcherWorker::unsecuredNetwork, this,
&LinuxNetworkWatcher::unsecuredNetwork);
connect(m_worker, &LinuxNetworkWatcherWorker::sleepMode, this,
&NetworkWatcherImpl::sleepMode);
// Let's wait a few seconds to allow the UI to be fully loaded and shown.
// This is not strictly needed, but it's better for user experience because
// it makes the UI faster to appear, plus it gives a bit of delay between the

View File

@@ -33,21 +33,7 @@
#define NM_802_11_AP_SEC_WEAK_CRYPTO \
(NM_802_11_AP_SEC_PAIR_WEP40 | NM_802_11_AP_SEC_PAIR_WEP104)
enum NMState {
NM_STATE_UNKNOWN = 0,
NM_STATE_ASLEEP = 10,
NM_STATE_DISCONNECTED = 20,
NM_STATE_DISCONNECTING = 30,
NM_STATE_CONNECTING = 40,
NM_STATE_CONNECTED_LOCAL = 50,
NM_STATE_CONNECTED_SITE = 60,
NM_STATE_CONNECTED_GLOBAL = 70
};
constexpr const char* DBUS_NETWORKMANAGER = "org.freedesktop.NetworkManager";
constexpr const char* DBUS_NETWORKMANAGER_PATH = "/org/freedesktop/NetworkManager";
namespace {
Logger logger("LinuxNetworkWatcherWorker");
@@ -87,7 +73,7 @@ void LinuxNetworkWatcherWorker::initialize() {
// documentation:
// https://developer.gnome.org/NetworkManager/stable/gdbus-org.freedesktop.NetworkManager.html
QDBusInterface nm(DBUS_NETWORKMANAGER, DBUS_NETWORKMANAGER_PATH,
QDBusInterface nm(DBUS_NETWORKMANAGER, "/org/freedesktop/NetworkManager",
DBUS_NETWORKMANAGER, QDBusConnection::systemBus());
if (!nm.isValid()) {
logger.error()
@@ -122,12 +108,6 @@ void LinuxNetworkWatcherWorker::initialize() {
SLOT(propertyChanged(QString, QVariantMap, QStringList)));
}
QDBusConnection::systemBus().connect(DBUS_NETWORKMANAGER,
DBUS_NETWORKMANAGER_PATH,
DBUS_NETWORKMANAGER,
"StateChanged",
this, SLOT(NMStateChanged(quint32)));
if (m_devicePaths.isEmpty()) {
logger.warning() << "No wifi devices found";
return;
@@ -193,16 +173,5 @@ void LinuxNetworkWatcherWorker::checkDevices() {
emit unsecuredNetwork(ssid, bssid);
break;
}
}
}
void LinuxNetworkWatcherWorker::NMStateChanged(quint32 state)
{
if (state == NM_STATE_ASLEEP) {
emit sleepMode();
}
logger.debug() << "NMStateChanged " << state;
}

View File

@@ -23,7 +23,6 @@ class LinuxNetworkWatcherWorker final : public QObject {
signals:
void unsecuredNetwork(const QString& networkName, const QString& networkId);
void sleepMode();
public slots:
void initialize();
@@ -31,7 +30,6 @@ class LinuxNetworkWatcherWorker final : public QObject {
private slots:
void propertyChanged(QString interface, QVariantMap properties,
QStringList list);
void NMStateChanged(quint32 state);
private:
// We collect the list of DBus wifi network device paths during the

View File

@@ -1,185 +0,0 @@
/* 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 "linuxpingsender.h"
#include <arpa/inet.h>
#include <errno.h>
#include <linux/filter.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <sys/socket.h>
#include <unistd.h>
#include <QSocketNotifier>
#include <QtEndian>
#include "leakdetector.h"
#include "logger.h"
#include "qhostaddress.h"
namespace {
Logger logger("LinuxPingSender");
}
int LinuxPingSender::createSocket() {
// Try creating an ICMP socket. This would be the ideal choice, but it can
// fail depending on the kernel config (see: sys.net.ipv4.ping_group_range)
m_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
if (m_socket >= 0) {
m_ident = 0;
return m_socket;
}
if ((errno != EPERM) && (errno != EACCES)) {
return -1;
}
// As a fallback, create a raw socket, which requires root permissions
// or CAP_NET_RAW to be granted to the VPN client.
m_socket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (m_socket < 0) {
return -1;
}
m_ident = getpid() & 0xffff;
// Attach a BPF filter to discard everything but replies to our echo.
struct sock_filter bpf_prog[] = {
BPF_STMT(BPF_LDX | BPF_B | BPF_MSH, 0), /* Skip IP header. */
BPF_STMT(BPF_LD | BPF_H | BPF_IND, 4), /* Load icmp echo ident */
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, m_ident, 1, 0), /* Ours? */
BPF_STMT(BPF_RET | BPF_K, 0), /* Unexpected identifier. Reject. */
BPF_STMT(BPF_LD | BPF_B | BPF_IND, 0), /* Load icmp type */
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ICMP_ECHOREPLY, 1, 0), /* Echo? */
BPF_STMT(BPF_RET | BPF_K, 0), /* Unexpected type. Reject. */
BPF_STMT(BPF_RET | BPF_K, ~0U), /* Packet passes the filter. */
};
struct sock_fprog filter = {
.len = sizeof(bpf_prog) / sizeof(struct sock_filter),
.filter = bpf_prog,
};
setsockopt(m_socket, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter));
return m_socket;
}
LinuxPingSender::LinuxPingSender(const QHostAddress& source, QObject* parent)
: PingSender(parent) {
MZ_COUNT_CTOR(LinuxPingSender);
logger.debug() << "LinuxPingSender(" + logger.sensitive(source.toString()) +
") created";
m_socket = createSocket();
if (m_socket < 0) {
logger.error() << "Socket creation error: " << strerror(errno);
return;
}
quint32 ipv4addr = INADDR_ANY;
if (!source.isNull()) {
ipv4addr = source.toIPv4Address();
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof addr);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = qToBigEndian<quint32>(ipv4addr);
if (bind(m_socket, (struct sockaddr*)&addr, sizeof(addr)) != 0) {
close(m_socket);
m_socket = -1;
logger.error() << "bind error:" << strerror(errno);
return;
}
m_notifier = new QSocketNotifier(m_socket, QSocketNotifier::Read, this);
if (m_ident) {
connect(m_notifier, &QSocketNotifier::activated, this,
&LinuxPingSender::rawSocketReady);
} else {
connect(m_notifier, &QSocketNotifier::activated, this,
&LinuxPingSender::icmpSocketReady);
}
}
LinuxPingSender::~LinuxPingSender() {
MZ_COUNT_DTOR(LinuxPingSender);
if (m_socket >= 0) {
close(m_socket);
}
}
void LinuxPingSender::sendPing(const QHostAddress& dest, quint16 sequence) {
quint32 ipv4dest = dest.toIPv4Address();
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = qToBigEndian<quint32>(ipv4dest);
struct icmphdr packet;
memset(&packet, 0, sizeof(packet));
packet.type = ICMP_ECHO;
packet.un.echo.id = htons(m_ident);
packet.un.echo.sequence = htons(sequence);
packet.checksum = inetChecksum(&packet, sizeof(packet));
int rc = sendto(m_socket, &packet, sizeof(packet), 0, (struct sockaddr*)&addr,
sizeof(addr));
if (rc < 0) {
logger.error() << "failed to send:" << strerror(errno);
if (errno == ENETUNREACH) {
emit criticalPingError();
}
}
}
void LinuxPingSender::icmpSocketReady() {
socklen_t slen = 0;
unsigned char data[2048];
int rc = recvfrom(m_socket, data, sizeof(data), MSG_DONTWAIT, NULL, &slen);
if (rc <= 0) {
logger.error() << "recvfrom failed:" << strerror(errno);
return;
}
struct icmphdr packet;
if (rc >= (int)sizeof(packet)) {
memcpy(&packet, data, sizeof(packet));
if (packet.type == ICMP_ECHOREPLY) {
emit recvPing(htons(packet.un.echo.sequence));
}
}
}
void LinuxPingSender::rawSocketReady() {
socklen_t slen = 0;
unsigned char data[2048];
int rc = recvfrom(m_socket, data, sizeof(data), MSG_DONTWAIT, NULL, &slen);
if (rc <= 0) {
logger.error() << "recvfrom failed:" << strerror(errno);
return;
}
// Check the IP header
const struct iphdr* ip = (struct iphdr*)data;
int iphdrlen = ip->ihl * 4;
if (rc < iphdrlen || iphdrlen < (int)sizeof(struct iphdr)) {
logger.error() << "malformed IP packet:" << strerror(errno);
return;
}
// Check the ICMP packet
struct icmphdr packet;
if (inetChecksum(data + iphdrlen, rc - iphdrlen) != 0) {
logger.warning() << "invalid checksum";
return;
}
if (rc >= (iphdrlen + (int)sizeof(packet))) {
memcpy(&packet, data + iphdrlen, sizeof(packet));
quint16 id = htons(m_ident);
if ((packet.type == ICMP_ECHOREPLY) && (packet.un.echo.id == id)) {
emit recvPing(htons(packet.un.echo.sequence));
}
}
}

View File

@@ -1,39 +0,0 @@
/* 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/. */
#ifndef LINUXPINGSENDER_H
#define LINUXPINGSENDER_H
#include <QObject>
#include "../../mozilla/pingsender.h"
class QSocketNotifier;
class LinuxPingSender final : public PingSender {
Q_OBJECT
Q_DISABLE_COPY_MOVE(LinuxPingSender)
public:
LinuxPingSender(const QHostAddress& source, QObject* parent = nullptr);
~LinuxPingSender();
bool isValid() override { return (m_socket >= 0); };
void sendPing(const QHostAddress& dest, quint16 sequence) override;
private:
int createSocket();
private slots:
void rawSocketReady();
void icmpSocketReady();
private:
QSocketNotifier* m_notifier = nullptr;
int m_socket = -1;
quint16 m_ident = 0;
};
#endif // LINUXPINGSENDER_H

View File

@@ -141,6 +141,12 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) {
for (const QString& key : config.m_specialJunk.keys()) {
out << key.toLower() << "=" << config.m_specialJunk.value(key) << "\n";
}
for (const QString& key : config.m_controlledJunk.keys()) {
out << key.toLower() << "=" << config.m_controlledJunk.value(key) << "\n";
}
if (!config.m_specialHandshakeTimeout.isEmpty()) {
out << "itime=" << config.m_specialHandshakeTimeout << "\n";
}
int err = uapiErrno(uapiCommand(message));
if (err != 0) {

View File

@@ -10,31 +10,8 @@
#include "../ios/iosnetworkwatcher.h"
#include "networkwatcherimpl.h"
#include <IOKit/pwr_mgt/IOPMLib.h>
#include <IOKit/IOMessage.h>
class QString;
// Inspired by https://ladydebug.com/blog/2020/05/21/programmatically-capture-energy-saver-event-on-mac/
class PowerNotificationsListener
{
public:
PowerNotificationsListener(class MacOSNetworkWatcher* watcher) : m_watcher(watcher) {}
void registerForNotifications();
void cleanup();
private:
static void sleepWakeupCallBack(void *refParam, io_service_t service, natural_t messageType, void *messageArgument);
private:
class MacOSNetworkWatcher* m_watcher = nullptr;
IONotificationPortRef notifyPortRef = nullptr; // notification port allocated by IORegisterForSystemPower
io_object_t notifierObj = IO_OBJECT_NULL; // notifier object, used to deregister later
io_connect_t rootPowerDomain = IO_OBJECT_NULL; // a reference to the Root Power Domain IOService
};
class MacOSNetworkWatcher final : public IOSNetworkWatcher {
public:
MacOSNetworkWatcher(QObject* parent);
@@ -48,7 +25,6 @@ class MacOSNetworkWatcher final : public IOSNetworkWatcher {
private:
void* m_delegate = nullptr;
PowerNotificationsListener m_powerlistener;
};
#endif // MACOSNETWORKWATCHER_H

View File

@@ -6,11 +6,6 @@
#include "leakdetector.h"
#include "logger.h"
#include <QProcess>
#include <QMetaObject>
#include <pthread.h>
#include <iostream>
#import <CoreWLAN/CoreWLAN.h>
#import <Network/Network.h>
@@ -18,37 +13,6 @@ namespace {
Logger logger("MacOSNetworkWatcher");
}
// Global variables for CFRunLoop thread
static pthread_t g_powerThread;
static CFRunLoopRef g_powerRunLoop = nullptr;
static bool g_shouldStopPowerThread = false;
static PowerNotificationsListener* g_powerListener = nullptr;
// Thread function for dedicated CFRunLoop
void* powerMonitoringThread(void* arg) {
logger.debug() << "Power monitoring thread started";
PowerNotificationsListener* listener = static_cast<PowerNotificationsListener*>(arg);
// Get the runloop for this thread
g_powerRunLoop = CFRunLoopGetCurrent();
// Register for power notifications in this thread
listener->registerForNotifications();
// Run the CFRunLoop - this will block until CFRunLoopStop is called
while (!g_shouldStopPowerThread) {
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0, true);
}
// Cleanup
listener->cleanup();
g_powerRunLoop = nullptr;
logger.debug() << "Power monitoring thread finished";
return nullptr;
}
@interface MacOSNetworkWatcherDelegate : NSObject <CWEventDelegate> {
MacOSNetworkWatcher* m_watcher;
}
@@ -69,145 +33,17 @@ void* powerMonitoringThread(void* arg) {
if (m_watcher) {
m_watcher->checkInterface();
// Emit networkChanged signal when BSSID changes
emit m_watcher->networkChanged(QString::fromNSString(interfaceName));
}
}
@end
void PowerNotificationsListener::registerForNotifications()
{
logger.debug() << "Registering for system power notifications in dedicated thread";
rootPowerDomain = IORegisterForSystemPower(this, &notifyPortRef, sleepWakeupCallBack, &notifierObj);
if (rootPowerDomain == IO_OBJECT_NULL) {
logger.error() << "Failed to register for system power notifications!";
return;
}
// Add the notification port to the current runloop (dedicated thread)
CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(notifyPortRef), kCFRunLoopCommonModes);
logger.debug() << "Power notifications registered successfully";
}
void PowerNotificationsListener::cleanup()
{
if (notifyPortRef != nullptr) {
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(notifyPortRef), kCFRunLoopCommonModes);
IONotificationPortDestroy(notifyPortRef);
notifyPortRef = nullptr;
}
if (notifierObj != IO_OBJECT_NULL) {
IODeregisterForSystemPower(&notifierObj);
notifierObj = IO_OBJECT_NULL;
}
if (rootPowerDomain != IO_OBJECT_NULL) {
IOServiceClose(rootPowerDomain);
rootPowerDomain = IO_OBJECT_NULL;
}
}
void PowerNotificationsListener::sleepWakeupCallBack(void *refParam, io_service_t service, natural_t messageType, void *messageArgument)
{
Q_UNUSED(service)
auto listener = static_cast<PowerNotificationsListener *>(refParam);
logger.debug() << "Power callback received, messageType:" << messageType;
switch (messageType) {
case kIOMessageCanSystemSleep:
/* Idle sleep is about to kick in. This message will not be sent for forced sleep.
* Applications have a chance to prevent sleep by calling IOCancelPowerChange.
* Most applications should not prevent idle sleep. Power Management waits up to
* 30 seconds for you to either allow or deny idle sleep. If you dont acknowledge
* this power change by calling either IOAllowPowerChange or IOCancelPowerChange,
* the system will wait 30 seconds then go to sleep.
*/
logger.debug() << "System power message: can system sleep?";
// Uncomment to cancel idle sleep
// IOCancelPowerChange(thiz->rootPowerDomain, reinterpret_cast<long>(messageArgument));
// Allow idle sleep
IOAllowPowerChange(listener->rootPowerDomain, reinterpret_cast<long>(messageArgument));
break;
case kIOMessageSystemWillNotSleep:
/* Announces that the system has retracted a previous attempt to sleep; it
* follows `kIOMessageCanSystemSleep`.
*/
logger.debug() << "System power message: system will NOT sleep.";
break;
case kIOMessageSystemWillSleep:
/* The system WILL go to sleep. If you do not call IOAllowPowerChange or
* IOCancelPowerChange to acknowledge this message, sleep will be delayed by
* 30 seconds.
*
* NOTE: If you call IOCancelPowerChange to deny sleep it returns kIOReturnSuccess,
* however the system WILL still go to sleep.
*/
logger.debug() << "System power message: system WILL sleep";
IOAllowPowerChange(listener->rootPowerDomain, reinterpret_cast<long>(messageArgument));
break;
case kIOMessageSystemWillPowerOn:
/* Announces that the system is beginning to power the device tree; most devices
* are still unavailable at this point.
*/
/* From the documentation:
*
* - kIOMessageSystemWillPowerOn is delivered at early wakeup time, before most hardware
* has been powered on. Be aware that any attempts to access disk, network, the display,
* etc. may result in errors or blocking your process until those resources become
* available.
*
* So we do NOT log this event.
*/
break;
case kIOMessageSystemHasPoweredOn:
/* Announces that the system and its devices have woken up. */
logger.debug() << "System has powered on - emitting sleepMode signal from dedicated CFRunLoop thread";
if (listener->m_watcher) {
// Use QMetaObject::invokeMethod for thread-safe signal emission
QMetaObject::invokeMethod(listener->m_watcher, "sleepMode", Qt::QueuedConnection);
}
break;
default:
logger.debug() << "System power message: other event: " << messageType;
/* Not a system sleep and wake notification. */
break;
}
}
MacOSNetworkWatcher::MacOSNetworkWatcher(QObject* parent) : IOSNetworkWatcher(parent), m_powerlistener(this) {
MacOSNetworkWatcher::MacOSNetworkWatcher(QObject* parent) : IOSNetworkWatcher(parent) {
MZ_COUNT_CTOR(MacOSNetworkWatcher);
}
MacOSNetworkWatcher::~MacOSNetworkWatcher() {
MZ_COUNT_DTOR(MacOSNetworkWatcher);
// Stop the dedicated power monitoring thread
if (g_powerListener) {
logger.debug() << "Stopping dedicated power monitoring thread";
g_shouldStopPowerThread = true;
if (g_powerRunLoop) {
CFRunLoopStop(g_powerRunLoop);
}
// Wait for thread to finish
pthread_join(g_powerThread, nullptr);
g_powerListener = nullptr;
}
if (m_delegate) {
CWWiFiClient* client = CWWiFiClient.sharedWiFiClient;
if (!client) {
@@ -230,20 +66,6 @@ void MacOSNetworkWatcher::start() {
logger.debug() << "Delegate already registered";
return;
}
// Start dedicated power monitoring thread with CFRunLoop
if (!g_powerListener) {
g_powerListener = &m_powerlistener;
g_shouldStopPowerThread = false;
int result = pthread_create(&g_powerThread, nullptr, powerMonitoringThread, &m_powerlistener);
if (result != 0) {
logger.error() << "Failed to create power monitoring thread:" << result;
g_powerListener = nullptr;
} else {
logger.debug() << "Power monitoring enabled";
}
}
CWWiFiClient* client = CWWiFiClient.sharedWiFiClient;
if (!client) {
@@ -255,8 +77,6 @@ void MacOSNetworkWatcher::start() {
m_delegate = [[MacOSNetworkWatcherDelegate alloc] initWithObject:this];
[client setDelegate:static_cast<MacOSNetworkWatcherDelegate*>(m_delegate)];
[client startMonitoringEventWithType:CWEventTypeBSSIDDidChange error:nullptr];
logger.debug() << "MacOSNetworkWatcher started successfully";
}
void MacOSNetworkWatcher::checkInterface() {
@@ -267,70 +87,42 @@ void MacOSNetworkWatcher::checkInterface() {
return;
}
// Use wdutil to get reliable WiFi information
QProcess process;
process.start("wdutil", QStringList() << "info");
process.waitForFinished(5000);
QString output = process.readAllStandardOutput();
QString errorOutput = process.readAllStandardError();
logger.debug() << "wdutil exit code:" << process.exitCode();
if (process.exitCode() != 0) {
logger.debug() << "wdutil failed with exit code:" << process.exitCode();
CWWiFiClient* client = CWWiFiClient.sharedWiFiClient;
if (!client) {
logger.debug() << "Unable to retrieve the CWWiFiClient shared instance";
return;
}
// Parse wdutil output to find WiFi connection info
QStringList lines = output.split('\n');
QString ssid, interfaceName, security;
bool wifiSectionFound = false;
for (int i = 0; i < lines.size(); i++) {
QString trimmedLine = lines[i].trimmed();
if (trimmedLine == "WIFI") {
wifiSectionFound = true;
continue;
}
if (wifiSectionFound) {
// Stop parsing when we reach next section header (all caps after separator line)
if (trimmedLine.startsWith("————————")) {
if (i + 1 < lines.size()) {
QString nextLine = lines[i + 1].trimmed();
if (!nextLine.isEmpty() && nextLine.length() > 2 && nextLine.toUpper() == nextLine && nextLine != "WIFI") {
break;
}
}
continue; // Skip separator lines
}
if (trimmedLine.startsWith("Interface Name")) {
QStringList parts = trimmedLine.split(":");
if (parts.size() >= 2) {
interfaceName = parts[1].trimmed();
}
} else if (trimmedLine.startsWith("SSID")) {
QStringList parts = trimmedLine.split(":");
if (parts.size() >= 2) {
ssid = parts[1].trimmed();
}
} else if (trimmedLine.startsWith("Security")) {
QStringList parts = trimmedLine.split(":");
if (parts.size() >= 2) {
security = parts[1].trimmed();
}
}
}
CWInterface* interface = [client interface];
if (!interface) {
logger.debug() << "No default wifi interface";
return;
}
if (!ssid.isEmpty() && !interfaceName.isEmpty()) {
logger.debug() << "Found active WiFi connection on" << interfaceName
<< "SSID:" << ssid << "Security:" << security;
} else {
logger.debug() << "No active WiFi connection found";
if (![interface powerOn]) {
logger.debug() << "The interface is off";
return;
}
NSString* ssidNS = [interface ssid];
if (!ssidNS) {
logger.debug() << "WiFi is not in used";
return;
}
QString ssid = QString::fromNSString(ssidNS);
if (ssid.isEmpty()) {
logger.debug() << "WiFi doesn't have a valid SSID";
return;
}
CWSecurity security = [interface security];
if (security == kCWSecurityNone || security == kCWSecurityWEP) {
logger.debug() << "Unsecured network found!";
emit unsecuredNetwork(ssid, ssid);
return;
}
logger.debug() << "Secure WiFi interface";
}

View File

@@ -22,6 +22,7 @@
#include "logger.h"
#include "platforms/windows/daemon/windowsfirewall.h"
#include "platforms/windows/daemon/windowssplittunnel.h"
#include "platforms/windows/windowscommons.h"
#include "windowsfirewall.h"
#include "core/networkUtilities.h"

View File

@@ -32,28 +32,9 @@ WindowsNetworkWatcher::~WindowsNetworkWatcher() {
}
}
LRESULT WindowsNetworkWatcher::PowerWndProcCallback(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
auto obj = reinterpret_cast<WindowsNetworkWatcher*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
if (!obj){
logger.debug() << "obj not casted";
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
switch (uMsg) {
case WM_POWERBROADCAST:
if (wParam == PBT_APMRESUMESUSPEND) {
emit obj->sleepMode();
}
break;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
}
void WindowsNetworkWatcher::initialize() {
logger.debug() << "initialize";
DWORD negotiatedVersion;
if (WlanOpenHandle(2, nullptr, &negotiatedVersion, &m_wlanHandle) !=
ERROR_SUCCESS) {
@@ -70,25 +51,6 @@ void WindowsNetworkWatcher::initialize() {
return;
}
const wchar_t* className = L"PowerMonitorClass";
WNDCLASS wc = { 0 };
wc.lpfnWndProc = &WindowsNetworkWatcher::PowerWndProcCallback;
wc.hInstance = GetModuleHandle(NULL);
wc.lpszClassName = className;
wc.cbWndExtra = sizeof(WindowsNetworkWatcher*);
if (!RegisterClass(&wc)) {
logger.debug() << "Failed to register window class in createPowerMonitorWindow.";
return;
}
HWND hwnd = CreateWindowEx(0, className, L"Power Monitor", 0, 0, 0, 0, 0, NULL, NULL, GetModuleHandle(NULL), static_cast<LPVOID>(this));
if (!hwnd) {
logger.debug() << "Failed to create window in createPowerMonitorWindow.";
return;
}
SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
logger.debug() << "callback registered";
}
@@ -175,4 +137,4 @@ void WindowsNetworkWatcher::processWlan(PWLAN_NOTIFICATION_DATA data) {
logger.debug() << "Unsecure network:" << logger.sensitive(ssid)
<< "id:" << logger.sensitive(bssid);
emit unsecuredNetwork(ssid, bssid);
}
}

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