Compare commits

..

2 Commits

Author SHA1 Message Date
vladimir.kuznetsov
b45f609531 Merge branch 'dev' of github.com:amnezia-vpn/amnezia-client into dev 2024-09-29 22:29:17 +08:00
vladimir.kuznetsov
ce01523a88 chore: bump version to 4.8.1.8
- fixed m_isDevGatewayEnv initialization in Settings class
2024-09-29 22:27:49 +08:00
765 changed files with 33706 additions and 65239 deletions

View File

@@ -1,39 +0,0 @@
BasedOnStyle: WebKit
AccessModifierOffset: '-4'
AlignAfterOpenBracket: Align
AlignConsecutiveMacros: 'true'
AlignTrailingComments: 'true'
AllowAllArgumentsOnNextLine: 'true'
AllowAllParametersOfDeclarationOnNextLine: 'true'
AllowShortBlocksOnASingleLine: 'false'
AllowShortCaseLabelsOnASingleLine: 'true'
AllowShortEnumsOnASingleLine: 'false'
AllowShortFunctionsOnASingleLine: None
AlwaysBreakTemplateDeclarations: 'No'
BreakBeforeBinaryOperators: NonAssignment
BreakBeforeBraces: Custom
BraceWrapping:
AfterClass: true
AfterControlStatement: false
AfterEnum: false
AfterFunction: true
AfterNamespace: true
AfterObjCDeclaration: false
AfterStruct: true
AfterUnion: false
BeforeCatch: false
BeforeElse: false
IndentBraces: false
BreakConstructorInitializers: BeforeColon
ColumnLimit: '120'
CommentPragmas: '"^!|^:"'
ConstructorInitializerAllOnOneLineOrOnePerLine: 'true'
ConstructorInitializerIndentWidth: '4'
ContinuationIndentWidth: '8'
IndentPPDirectives: BeforeHash
NamespaceIndentation: All
PenaltyExcessCharacter: '10'
PointerAlignment: Right
SortIncludes: 'true'
SpaceAfterTemplateKeyword: 'false'
Standard: Auto

View File

@@ -1,20 +0,0 @@
/client/3rd
/client/3rd-prebuild
/client/android
/client/cmake
/client/core/utils/serialization
/client/daemon
/client/fonts
/client/images
/client/ios
/client/mozilla
/client/platforms/dummy
/client/platforms/linux
/client/platforms/macos
/client/platforms/windows
/client/server_scripts
/client/translations
/deploy
/docs
/metadata
/service/src

View File

@@ -10,19 +10,13 @@ env:
jobs:
Build-Linux-Ubuntu:
runs-on: android-runner
runs-on: ubuntu-20.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 }}
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
steps:
- name: 'Install Qt'
@@ -31,15 +25,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
@@ -47,31 +39,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'
@@ -94,17 +79,11 @@ 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 }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
steps:
- name: 'Get sources'
@@ -113,16 +92,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
@@ -130,62 +101,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'
@@ -198,26 +139,20 @@ 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 }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
steps:
- name: 'Setup xcode'
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '26.1'
xcode-version: '15.2'
- name: 'Install desktop Qt'
uses: jurplel/install-qt-action@v3
@@ -246,7 +181,7 @@ jobs:
- name: 'Install go'
uses: actions/setup-go@v5
with:
go-version: '1.24'
go-version: '1.22.1'
cache: false
- name: 'Setup gomobile'
@@ -261,8 +196,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
@@ -273,11 +208,7 @@ jobs:
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/ios/bin"
export QT_MACOS_ROOT_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos"
export PATH=$PATH:~/go/bin
sh deploy/build_ios.sh | \
sed -e '/-Xcc -DPROD_AGW_PUBLIC_KEY/,/-Xcc/ { /-Xcc/!d; }' -e '/-Xcc -DPROD_AGW_PUBLIC_KEY/d' | \
sed -e '/-Xcc -DDEV_AGW_PUBLIC_KEY/,/-Xcc/ { /-Xcc/!d; }' -e '/-Xcc -DDEV_AGW_PUBLIC_KEY/d' | \
sed -e '/-DPROD_AGW_PUBLIC_KEY/,/-D/ { /-D/!d; }' -e '/-DPROD_AGW_PUBLIC_KEY/d' | \
sed -e '/-DDEV_AGW_PUBLIC_KEY/,/-D/ { /-D/!d; }' -e '/-DDEV_AGW_PUBLIC_KEY/d'
sh deploy/build_ios.sh
env:
IOS_TRUST_CERT_BASE64: ${{ secrets.IOS_TRUST_CERT_BASE64 }}
IOS_SIGNING_CERT_BASE64: ${{ secrets.IOS_SIGNING_CERT_BASE64 }}
@@ -299,40 +230,21 @@ jobs:
# ------------------------------------------------------
Build-MacOS-old:
Build-MacOS:
runs-on: macos-latest
env:
# Keep compat with MacOS 10.15 aka Catalina by Qt 6.4
QT_VERSION: 6.4.3
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
MAC_APP_CERT_CERT: ${{ secrets.MAC_APP_CERT_CERT }}
MAC_SIGNER_ID: ${{ secrets.MAC_SIGNER_ID }}
MAC_APP_CERT_PW: ${{ secrets.MAC_APP_CERT_PW }}
MAC_INSTALLER_SIGNER_CERT: ${{ secrets.MAC_INSTALLER_SIGNER_CERT }}
MAC_INSTALLER_SIGNER_ID: ${{ secrets.MAC_INSTALLER_SIGNER_ID }}
MAC_INSTALL_CERT_PW: ${{ secrets.MAC_INSTALL_CERT_PW }}
APPLE_DEV_EMAIL: ${{ secrets.APPLE_DEV_EMAIL }}
APPLE_DEV_PASSWORD: ${{ secrets.APPLE_DEV_PASSWORD }}
QIF_VERSION: 4.6
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
steps:
- name: 'Setup xcode'
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '15.4.0'
xcode-version: '14.3.1'
- name: 'Install Qt'
uses: jurplel/install-qt-action@v3
@@ -347,6 +259,11 @@ jobs:
set-env: 'true'
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
- name: 'Install Qt Installer Framework ${{ env.QIF_VERSION }}'
run: |
mkdir -pv ${{ runner.temp }}/Qt/Tools/QtInstallerFramework
wget https://qt.amzsvc.com/tools/ifw/${{ env.QIF_VERSION }}.zip
unzip ${{ env.QIF_VERSION }}.zip -d ${{ runner.temp }}/Qt/Tools/QtInstallerFramework/
- name: 'Get sources'
uses: actions/checkout@v4
@@ -354,185 +271,22 @@ 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: |
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin"
bash deploy/build_macos.sh -n
export QIF_BIN_DIR="${{ runner.temp }}/Qt/Tools/QtInstallerFramework/${{ env.QIF_VERSION }}/bin"
bash deploy/build_macos.sh
- name: 'Upload installer artifact'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_MacOS_old_installer
path: deploy/build/pkg/AmneziaVPN.pkg
name: AmneziaVPN_MacOS_installer
path: AmneziaVPN.dmg
retention-days: 7
- name: 'Upload unpacked artifact'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_MacOS_old_unpacked
path: deploy/build/client/AmneziaVPN.app
retention-days: 7
# ------------------------------------------------------
Build-MacOS:
runs-on: macos-latest
env:
QT_VERSION: 6.10.1
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
MAC_APP_CERT_CERT: ${{ secrets.MAC_APP_CERT_CERT }}
MAC_SIGNER_ID: ${{ secrets.MAC_SIGNER_ID }}
MAC_APP_CERT_PW: ${{ secrets.MAC_APP_CERT_PW }}
MAC_INSTALLER_SIGNER_CERT: ${{ secrets.MAC_INSTALLER_SIGNER_CERT }}
MAC_INSTALLER_SIGNER_ID: ${{ secrets.MAC_INSTALLER_SIGNER_ID }}
MAC_INSTALL_CERT_PW: ${{ secrets.MAC_INSTALL_CERT_PW }}
APPLE_DEV_EMAIL: ${{ secrets.APPLE_DEV_EMAIL }}
APPLE_DEV_PASSWORD: ${{ secrets.APPLE_DEV_PASSWORD }}
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
steps:
- name: 'Setup xcode'
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '16.2.0'
- name: 'Install Qt'
uses: jurplel/install-qt-action@v4
with:
version: ${{ env.QT_VERSION }}
host: 'mac'
target: 'desktop'
arch: 'clang_64'
modules: 'qtremoteobjects qt5compat qtshadertools'
dir: ${{ runner.temp }}
setup-python: 'true'
set-env: 'true'
aqtversion: '==3.3.0'
py7zrversion: '==0.22.*'
extra: '--base ${{ env.QT_MIRROR }}'
- name: 'Get sources'
uses: actions/checkout@v4
with:
submodules: 'true'
fetch-depth: 10
- name: 'Get version from CMakeLists.txt'
id: get_version
run: |
VERSION=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+.[0-9]+.[0-9]+.[0-9]+)\)/\1/')
echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "Version: $VERSION"
# - name: 'Setup ccache'
# uses: hendrikmuhs/ccache-action@v1.2
- name: 'Build project'
run: |
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin"
bash deploy/build_macos.sh -n
- name: 'Pack macOS installer'
run: |
cd deploy/build/pkg
zip -r ../../AmneziaVPN_${VERSION}_macos.zip AmneziaVPN.pkg
cd ../../..
- name: 'Upload installer artifact'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_${{ env.VERSION }}_macos.zip
path: deploy/AmneziaVPN_${{ env.VERSION }}_macos.zip
retention-days: 7
- name: 'Upload unpacked artifact'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN_MacOS_unpacked
path: deploy/build/client/AmneziaVPN.app
retention-days: 7
Build-MacOS-NE:
runs-on: macos-latest
env:
QT_VERSION: 6.10.1
MAC_TEAM_ID: ${{ secrets.MAC_TEAM_ID }}
MAC_APP_CERT_CERT: ${{ secrets.MAC_APP_CERT_CERT }}
MAC_SIGNER_ID: ${{ secrets.MAC_SIGNER_ID }}
MAC_APP_CERT_PW: ${{ secrets.MAC_APP_CERT_PW }}
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
steps:
- name: 'Setup xcode'
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '26.1'
- name: 'Install desktop Qt'
uses: jurplel/install-qt-action@v3
with:
version: ${{ env.QT_VERSION }}
host: 'mac'
target: 'desktop'
modules: 'qtremoteobjects qt5compat qtshadertools qtmultimedia'
arch: 'clang_64'
dir: ${{ runner.temp }}
set-env: 'true'
extra: '--base ${{ env.QT_MIRROR }}'
- name: 'Install go'
uses: actions/setup-go@v5
with:
go-version: '1.24'
cache: false
- name: 'Setup gomobile'
run: |
export PATH=$PATH:~/go/bin
go install golang.org/x/mobile/cmd/gomobile@latest
gomobile init
- name: 'Get sources'
uses: actions/checkout@v4
with:
submodules: 'true'
fetch-depth: 10
# - 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_ne.sh
- name: 'Upload unpacked artifact'
uses: actions/upload-artifact@v4
with:
@@ -543,20 +297,14 @@ 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.2
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
steps:
- name: 'Install desktop Qt'
@@ -568,8 +316,7 @@ jobs:
arch: 'linux_gcc_64'
modules: ${{ env.QT_MODULES }}
dir: ${{ runner.temp }}
py7zrversion: '==0.22.*'
extra: '--base ${{ env.QT_MIRROR }}'
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
- name: 'Install android_x86_64 Qt'
uses: jurplel/install-qt-action@v4
@@ -580,8 +327,7 @@ jobs:
arch: 'android_x86_64'
modules: ${{ env.QT_MODULES }}
dir: ${{ runner.temp }}
py7zrversion: '==0.22.*'
extra: '--base ${{ env.QT_MIRROR }}'
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
- name: 'Install android_x86 Qt'
uses: jurplel/install-qt-action@v4
@@ -592,8 +338,7 @@ jobs:
arch: 'android_x86'
modules: ${{ env.QT_MODULES }}
dir: ${{ runner.temp }}
py7zrversion: '==0.22.*'
extra: '--base ${{ env.QT_MIRROR }}'
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
- name: 'Install android_armv7 Qt'
uses: jurplel/install-qt-action@v4
@@ -604,8 +349,7 @@ jobs:
arch: 'android_armv7'
modules: ${{ env.QT_MODULES }}
dir: ${{ runner.temp }}
py7zrversion: '==0.22.*'
extra: '--base ${{ env.QT_MIRROR }}'
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
- name: 'Install android_arm64_v8a Qt'
uses: jurplel/install-qt-action@v4
@@ -616,8 +360,7 @@ jobs:
arch: 'android_arm64_v8a'
modules: ${{ env.QT_MODULES }}
dir: ${{ runner.temp }}
py7zrversion: '==0.22.*'
extra: '--base ${{ env.QT_MIRROR }}'
extra: '--external 7z --base ${{ env.QT_MIRROR }}'
- name: 'Grant execute permission for qt-cmake'
shell: bash
@@ -629,22 +372,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
@@ -669,44 +405,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

@@ -16,13 +16,7 @@ jobs:
QT_VERSION: 6.4.1
QIF_VERSION: 4.5
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
FALLBACK_S3_ENDPOINT: ${{ secrets.FALLBACK_S3_ENDPOINT }}
DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }}
DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }}
DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }}
FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }}
PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }}
steps:
- name: 'Install desktop Qt'

View File

@@ -1,41 +1,64 @@
name: 'Upload a new version'
on:
workflow_dispatch:
inputs:
RELEASE_VERSION:
description: 'Release version (e.g. 1.2.3.4)'
required: true
type: string
push:
tags:
- '[0-9]+.[0-9]+.[0-9]+.[0-9]+'
jobs:
Upload-S3:
upload:
runs-on: ubuntu-latest
name: upload
steps:
- name: Checkout
- name: Checkout CMakeLists.txt
uses: actions/checkout@v4
with:
ref: ${{ inputs.RELEASE_VERSION }}
ref: ${{ github.ref_name }}
sparse-checkout: |
CMakeLists.txt
deploy/deploy_s3.sh
sparse-checkout-cone-mode: false
- 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/')
if [[ "$TAG_NAME" == "$CMAKE_TAG" ]]; then
echo "Git tag ($TAG_NAME) matches CMakeLists.txt version ($CMAKE_TAG)."
GIT_TAG=${{ github.ref_name }}
CMAKE_TAG=$(grep 'project.*VERSION' CMakeLists.txt | sed -E 's/.* ([0-9]+.[0-9]+.[0-9]+.[0-9]+)$/\1/')
if [[ "$GIT_TAG" == "$CMAKE_TAG" ]]; then
echo "Git tag ($GIT_TAG) and version in CMakeLists.txt ($CMAKE_TAG) are the same. Continuing..."
else
echo "::error::Mismatch: Git tag ($TAG_NAME) != CMakeLists.txt version ($CMAKE_TAG). Exiting with error..."
echo "Git tag ($GIT_TAG) and version in CMakeLists.txt ($CMAKE_TAG) are not the same! Cancelling..."
exit 1
fi
- name: Setup Rclone
uses: AnimMouse/setup-rclone@v1
- name: Download artifacts from the "${{ github.ref_name }}" tag
uses: robinraju/release-downloader@v1.8
with:
rclone_config: ${{ secrets.RCLONE_CONFIG }}
tag: ${{ github.ref_name }}
fileName: "AmneziaVPN_(Linux_|)${{ github.ref_name }}*"
out-file-path: ${{ github.ref_name }}
- name: Send dist to S3
run: bash deploy/deploy_s3.sh ${{ inputs.RELEASE_VERSION }}
- name: Upload beta version
uses: jakejarvis/s3-sync-action@master
if: contains(github.event.base_ref, 'dev')
with:
args: --include "AmneziaVPN*" --delete
env:
AWS_S3_BUCKET: updates
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
AWS_S3_ENDPOINT: https://${{ vars.CF_ACCOUNT_ID }}.r2.cloudflarestorage.com
SOURCE_DIR: ${{ github.ref_name }}
DEST_DIR: beta/${{ github.ref_name }}
- name: Upload stable version
uses: jakejarvis/s3-sync-action@master
if: contains(github.event.base_ref, 'master')
with:
args: --include "AmneziaVPN*" --delete
env:
AWS_S3_BUCKET: updates
AWS_ACCESS_KEY_ID: ${{ secrets.CF_R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_R2_SECRET_ACCESS_KEY }}
AWS_S3_ENDPOINT: https://${{ vars.CF_ACCOUNT_ID }}.r2.cloudflarestorage.com
SOURCE_DIR: ${{ github.ref_name }}
DEST_DIR: stable/${{ github.ref_name }}

11
.gitignore vendored
View File

@@ -9,7 +9,6 @@ deploy/build_32/*
deploy/build_64/*
winbuild*.bat
.cache/
.vscode/
# Qt-es
@@ -134,12 +133,4 @@ client/3rd/ShadowSocks/ss_ios.xcconfig
out/
# CMake files
CMakeFiles/
ios-ne-build.sh
macos-ne-build.sh
macos-signed-build.sh
macos-with-sign-build.sh
DeveloperIdApplicationCertificate.p12
DeveloperIdInstallerCertificate.p12
CMakeFiles/

8
.gitmodules vendored
View File

@@ -1,3 +1,6 @@
[submodule "client/3rd/OpenVPNAdapter"]
path = client/3rd/OpenVPNAdapter
url = https://github.com/amnezia-vpn/OpenVPNAdapter.git
[submodule "client/3rd/qtkeychain"]
path = client/3rd/qtkeychain
url = https://github.com/frankosterfeld/qtkeychain.git
@@ -7,14 +10,9 @@
[submodule "client/3rd-prebuilt"]
path = client/3rd-prebuilt
url = https://github.com/amnezia-vpn/3rd-prebuilt
branch = feature/special-handshake
[submodule "client/3rd/amneziawg-apple"]
path = client/3rd/amneziawg-apple
url = https://github.com/amnezia-vpn/amneziawg-apple
[submodule "client/3rd/QSimpleCrypto"]
path = client/3rd/QSimpleCrypto
url = https://github.com/amnezia-vpn/QSimpleCrypto.git
[submodule "client/3rd/qtgamepad"]
path = client/3rd/qtgamepad
url = https://github.com/amnezia-vpn/qtgamepad.git
branch = 6.6

View File

@@ -1,9 +1,8 @@
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN)
set(AMNEZIAVPN_VERSION 4.8.15.4)
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
project(${PROJECT} VERSION 4.8.1.8
DESCRIPTION "AmneziaVPN"
HOMEPAGE_URL "https://amnezia.org/"
)
@@ -12,7 +11,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 2120)
set(APP_ANDROID_VERSION_CODE 63)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux")
@@ -32,56 +31,14 @@ set(QT_BUILD_TOOLS_WHEN_CROSS_COMPILING ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(APPLE)
if(IOS)
set(CMAKE_OSX_ARCHITECTURES "arm64")
elseif(MACOS_NE)
set(CMAKE_OSX_ARCHITECTURES "arm64;x86_64")
else()
set(CMAKE_OSX_ARCHITECTURES "x86_64")
endif()
if(APPLE AND NOT IOS)
set(CMAKE_OSX_ARCHITECTURES "x86_64")
endif()
add_subdirectory(client)
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
if(NOT IOS AND NOT ANDROID)
add_subdirectory(service)
include(${CMAKE_SOURCE_DIR}/deploy/installer/config.cmake)
endif()
set(AMNEZIA_STAGE_DIR "${CMAKE_BINARY_DIR}/stage")
if(WIN32 AND NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
file(TO_CMAKE_PATH "${AMNEZIA_STAGE_DIR}" AMNEZIA_STAGE_DIR_CMAKE)
set(CPACK_GENERATOR "WIX")
set(CPACK_WIX_VERSION 4)
set(CPACK_PACKAGE_NAME "AmneziaVPN")
set(CPACK_PACKAGE_VENDOR "AmneziaVPN")
set(CPACK_PACKAGE_VERSION ${AMNEZIAVPN_VERSION})
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "AmneziaVPN client")
set(AMNEZIA_LICENSE_TXT "${CMAKE_BINARY_DIR}/LICENSE.txt")
configure_file("${CMAKE_SOURCE_DIR}/LICENSE" "${AMNEZIA_LICENSE_TXT}" COPYONLY)
set(CPACK_RESOURCE_FILE_LICENSE "${AMNEZIA_LICENSE_TXT}")
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()

View File

@@ -1,31 +1,30 @@
# Amnezia VPN
### _The best client for self-hosted VPN_
## _The best client for self-hosted VPN_
[![Build Status](https://github.com/amnezia-vpn/amnezia-client/actions/workflows/deploy.yml/badge.svg?branch=dev)](https://github.com/amnezia-vpn/amnezia-client/actions/workflows/deploy.yml?query=branch:dev)
[![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/amnezia-vpn/amnezia-client)
### [English]([https://github.com/amnezia-vpn/amnezia-client/blob/dev/README_RU.md](https://github.com/amnezia-vpn/amnezia-client/tree/dev?tab=readme-ov-file#)) | [Русский](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README_RU.md)
Amnezia is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
![Image](https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/uipic4.png)
[Amnezia](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en) is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
<br>
[![Image](https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/uipic4.png)](https://amnezia.org)
<a href="https://github.com/amnezia-vpn/amnezia-client/releases/download/4.7.0.0/AmneziaVPN_4.7.0.0_x64.exe"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/win.png" width="150" style="max-width: 100%;"></a>
<a href="https://github.com/amnezia-vpn/amnezia-client/releases/download/4.7.0.0/AmneziaVPN_4.7.0.0.dmg"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/mac.png" width="150" style="max-width: 100%;"></a>
<a href="https://github.com/amnezia-vpn/amnezia-client/releases/download/4.7.0.0/AmneziaVPN_Linux_4.7.0.0.tar.zip"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/lin.png" width="150" style="max-width: 100%;"></a>
<a href="https://github.com/amnezia-vpn/amnezia-client/releases/tag/4.7.0.0"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/andr.png" width="150" style="max-width: 100%;"></a>
### [Website](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en) | [Alt website link](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en-mirror) | [Documentation](https://docs.amnezia.org) | [Troubleshooting](https://docs.amnezia.org/troubleshooting)
<br>
> [!TIP]
> If the [Amnezia website](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en) is blocked in your region, you can use an [Alternative website link](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-en-mirror).
<a href="https://play.google.com/store/search?q=amnezia+vpn&c=apps"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/play.png" width="150" style="max-width: 100%;"></a>
<a href="https://apps.apple.com/us/app/amneziavpn/id1600529900"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/apl.png" width="150" style="max-width: 100%;"></a>
<a href="https://amnezia.org/en/downloads?utm_source=github&utm_campaign=amnezia_button-readme-en"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
<a href="https://storage.googleapis.com/amnezia/amnezia.org?m-path=/en/downloads&utm_source=github&utm_campaign=amnezia_button-readme-en-mirrow"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-alt.svg" width="150" style="max-width: 100%;"></a>
[All releases](https://github.com/amnezia-vpn/amnezia-client/releases)
<br/>
<br>
<a href="https://www.testiny.io"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/testiny.png" height="28px"></a>
## Features
@@ -38,8 +37,7 @@
## Links
- [https://amnezia.org](https://amnezia.org) - Project website | [Alternative link (mirror)](https://storage.googleapis.com/kldscp/amnezia.org)
- [https://docs.amnezia.org](https://docs.amnezia.org) - Documentation
- [https://amnezia.org](https://amnezia.org) - project website
- [https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit
- [https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Telegram support channel (English)
- [https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Telegram support channel (Farsi)
@@ -179,17 +177,17 @@ You may face compiling issues in QT Creator after you've worked in Android Studi
## License
This project is licensed under the GNU General Public License v3.0 (see LICENSE) and also includes third-party components distributed under their own terms (see THIRD_PARTY_LICENSES.md).
GPL v3.0
## Donate
Patreon: [https://www.patreon.com/amneziavpn](https://www.patreon.com/amneziavpn)
Bitcoin: bc1qmhtgcf9637rl3kqyy22r2a8wa8laka4t9rx2mf <br>
Bitcoin: bc1q26eevjcg9j0wuyywd2e3uc9cs2w58lpkpjxq6p <br>
USDT BEP20: 0x6abD576765a826f87D1D95183438f9408C901bE4 <br>
USDT TRC20: TELAitazF1MZGmiNjTcnxDjEiH5oe7LC9d <br>
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3 <br>
TON: UQDpU1CyKRmg7L8mNScKk9FRc2SlESuI7N-Hby4nX-CcVmns
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3
## Acknowledgments
This project is tested with BrowserStack.

View File

@@ -1,181 +0,0 @@
# Amnezia VPN
### _Лучший клиент для создания VPN на собственном сервере_
[![Build Status](https://github.com/amnezia-vpn/amnezia-client/actions/workflows/deploy.yml/badge.svg?branch=dev)](https://github.com/amnezia-vpn/amnezia-client/actions/workflows/deploy.yml?query=branch:dev)
[![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/amnezia-vpn/amnezia-client)
### [English](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README.md) | Русский
[AmneziaVPN](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru) — это open source VPN-клиент, ключевая особенность которого заключается в возможности развернуть собственный VPN на вашем сервере.
[![Image](https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/uipic4.png)](https://amnezia.org)
### [Сайт](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru) | [Зеркало сайта](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru-mirror) | [Документация](https://docs.amnezia.org) | [Решение проблем](https://docs.amnezia.org/troubleshooting)
> [!TIP]
> Если [сайт Amnezia](https://amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/amnezia/amnezia.org?utm_source=github&utm_campaign=amnezia_website-readme-ru-mirror).
<a href="https://storage.googleapis.com/amnezia/amnezia.org?m-path=/ru/downloads&utm_source=github&utm_campaign=amnezia_button-readme-ru-mirror"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/download-website-ru.svg" width="150" style="max-width: 100%; margin-right: 10px"></a>
[Все релизы](https://github.com/amnezia-vpn/amnezia-client/releases)
<br/>
<a href="https://www.testiny.io"><img src="https://github.com/amnezia-vpn/amnezia-client/blob/dev/metadata/img-readme/testiny.png" height="28px"></a>
## Особенности
- Простой в использовании — введите IP-адрес, SSH-логин и пароль, и Amnezia автоматически установит VPN-контейнеры Docker на ваш сервер и подключится к VPN.
- Классические VPN-протоколы: OpenVPN, WireGuard и IKEv2.
- Протоколы с маскировкой трафика (обфускацией): OpenVPN с плагином [Cloak](https://github.com/cbeuw/Cloak), Shadowsocks (OpenVPN over Shadowsocks), [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/) and XRay.
- Поддержка Split Tunneling — добавляйте любые сайты или приложения в список, чтобы включить VPN только для них.
- Поддерживает платформы: Windows, macOS, Linux, Android, iOS.
- Поддержка конфигурации протокола AmneziaWG на [бета-прошивке Keenetic](https://docs.keenetic.com/ua/air/kn-1611/en/6319-latest-development-release.html#UUID-186c4108-5afd-c10b-f38a-cdff6c17fab3_section-idm33192196168192-improved).
## Ссылки
- [https://amnezia.org](https://amnezia.org) - Веб-сайт проекта | [Альтернативная ссылка (зеркало)](https://storage.googleapis.com/kldscp/amnezia.org)
- [https://docs.amnezia.org](https://docs.amnezia.org) - Документация
- [https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit
- [https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Канал поддержки в Telegram (Английский)
- [https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Канал поддержки в Telegram (Фарси)
- [https://t.me/amnezia_vpn_mm](https://t.me/amnezia_vpn_mm) - Канал поддержки в Telegram (Мьянма)
- [https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - Канал поддержки в Telegram (Русский)
- [https://vpnpay.io/en/amnezia-premium/](https://vpnpay.io/en/amnezia-premium/) - Amnezia Premium | [Зеркало](https://storage.googleapis.com/kldscp/vpnpay.io/ru/amnezia-premium\)
## Технологии
AmneziaVPN использует несколько проектов с открытым исходным кодом:
- [OpenSSL](https://www.openssl.org/)
- [OpenVPN](https://openvpn.net/)
- [Shadowsocks](https://shadowsocks.org/)
- [Qt](https://www.qt.io/)
- [LibSsh](https://libssh.org)
- и другие...
## Проверка исходного кода
После клонирования репозитория обязательно загрузите все подмодули.
```bash
git submodule update --init --recursive
```
## Разработка
Хотите внести свой вклад? Добро пожаловать!
### Помощь с переводами
Загрузите самые актуальные файлы перевода.
Перейдите на [вкладку "Actions"](https://github.com/amnezia-vpn/amnezia-client/actions?query=is%3Asuccess+branch%3Adev), нажмите на первую строку. Затем прокрутите вниз до раздела "Artifacts" и скачайте "AmneziaVPN_translations".
Распакуйте этот файл. Каждый файл с расширением *.ts содержит строки для соответствующего языка.
Переведите или исправьте строки в одном или нескольких файлах *.ts и загрузите их обратно в этот репозиторий в папку ``client/translations``. Это можно сделать через веб-интерфейс или любым другим знакомым вам способом.
### Сборка исходного кода и деплой
Проверьте папку deploy для скриптов сборки.
### Как собрать iOS-приложение из исходного кода на MacOS
1. Убедитесь, что у вас установлен Xcode версии 14 или выше.
2. Для генерации проекта Xcode используется QT. Требуется версия QT 6.6.2. Установите QT для MacOS здесь или через QT Online Installer. Необходимые модули:
- MacOS
- iOS
- Модуль совместимости с Qt 5
- Qt Shader Tools
- Дополнительные библиотеки:
- Qt Image Formats
- Qt Multimedia
- Qt Remote Objects
3. Установите CMake, если это необходимо. Рекомендуемая версия — 3.25. Скачать CMake можно здесь.
4. Установите Go версии >= v1.16. Если Go ещё не установлен, скачайте его с [официального сайта](https://golang.org/dl/) или используйте Homebrew. Установите gomobile:
```bash
export PATH=$PATH:~/go/bin
go install golang.org/x/mobile/cmd/gomobile@latest
gomobile init
```
5. Соберите проект:
```bash
export QT_BIN_DIR="<PATH-TO-QT-FOLDER>/Qt/<QT-VERSION>/ios/bin"
export QT_MACOS_ROOT_DIR="<PATH-TO-QT-FOLDER>/Qt/<QT-VERSION>/macos"
export QT_IOS_BIN=$QT_BIN_DIR
export PATH=$PATH:~/go/bin
mkdir build-ios
$QT_IOS_BIN/qt-cmake . -B build-ios -GXcode -DQT_HOST_PATH=$QT_MACOS_ROOT_DIR
```
Замените <PATH-TO-QT-FOLDER> и <QT-VERSION> на ваши значения.
Если появляется ошибка gomobile: command not found, убедитесь, что PATH настроен на папку bin, где установлен gomobile:
```bash
export PATH=$(PATH):/path/to/GOPATH/bin
```
6. Откройте проект в Xcode. Теперь вы можете тестировать, архивировать или публиковать приложение.
Если сборка завершится с ошибкой:
```
make: ***
[$(PROJECTDIR)/client/build/AmneziaVPN.build/Debug-iphoneos/wireguard-go-bridge/goroot/.prepared]
Error 1
```
Добавьте пользовательскую переменную PATH в настройки сборки для целей AmneziaVPN и WireGuardNetworkExtension с ключом `PATH` и значением `${PATH}/path/to/bin/folder/with/go/executable`, e.g. `${PATH}:/usr/local/go/bin`.
Если ошибка повторяется на Mac с M1, установите версию CMake для архитектуры ARM:
```
arch -arm64 brew install cmake
```
При первой попытке сборка может завершиться с ошибкой source files not found. Это происходит из-за параллельной компиляции зависимостей в XCode. Просто перезапустите сборку.
## Как собрать Android-приложение
Сборка тестировалась на MacOS. Требования:
- JDK 11
- Android SDK 33
- CMake 3.25.0
Установите QT, QT Creator и Android Studio.
Настройте QT Creator:
- В меню QT Creator перейдите в `QT Creator` -> `Preferences` -> `Devices` ->`Android`.
- Укажите путь к JDK 11.
- Укажите путь к Android SDK (`$ANDROID_HOME`)
Если вы сталкиваетесь с ошибками, связанными с отсутствием SDK или сообщением «SDK manager not running», их нельзя исправить просто корректировкой путей. Если у вас есть несколько свободных гигабайт на диске, вы можете позволить Qt Creator установить все необходимые компоненты, выбрав пустую папку для расположения Android SDK и нажав кнопку **Set Up SDK**. Учтите: это установит второй Android SDK и NDK на вашем компьютере!
Убедитесь, что настроена правильная версия CMake: перейдите в **Qt Creator -> Preferences** и в боковом меню выберите пункт **Kits**. В центральной части окна, на вкладке **Kits**, найдите запись для инструмента **CMake Tool**. Если выбранная по умолчанию версия CMake ниже 3.25.0, установите на свою систему CMake версии 3.25.0 или выше, а затем выберите опцию **System CMake at <путь>** из выпадающего списка. Если этот пункт отсутствует, это может означать, что вы еще не установили CMake, или Qt Creator не смог найти путь к нему. В таком случае в окне **Preferences** перейдите в боковое меню **CMake**, затем во вкладку **Tools** в центральной части окна и нажмите кнопку **Add**, чтобы указать путь к установленному CMake.
Убедитесь, что для вашего проекта выбрана Android Platform SDK 33: в главном окне на боковой панели выберите пункт **Projects**, и слева вы увидите раздел **Build & Run**, показывающий различные целевые Android-платформы. Вы можете выбрать любую из них, так как настройка проекта Amnezia VPN разработана таким образом, чтобы все Android-цели могли быть собраны. Перейдите в подраздел **Build** и прокрутите центральную часть окна до раздела **Build Steps**. Нажмите **Details** в заголовке **Build Android APK** (кнопка **Details** может быть скрыта, если окно Qt Creator не запущено в полноэкранном режиме!). Вот здесь выберите **android-33** в качестве Android Build Platform SDK.
### Разработка Android-компонентов
После сборки QT Creator копирует проект в отдельную папку, например, `build-amnezia-client-Android_Qt_<version>_Clang_<architecture>-<BuildType>`. Для разработки Android-компонентов откройте сгенерированный проект в Android Studio, указав папку `build-amnezia-client-Android_Qt_<version>_Clang_<architecture>-<BuildType>/client/android-build` в качестве корневой.
Изменения в сгенерированном проекте нужно вручную перенести в репозиторий. После этого можно коммитить изменения.
Если возникают проблемы со сборкой в QT Creator после работы в Android Studio, выполните команду `./gradlew clean` в корневой папке сгенерированного проекта (`<path>/client/android-build/.`).
## Лицензия
GPL v3.0
## Донаты
Patreon: [https://www.patreon.com/amneziavpn](https://www.patreon.com/amneziavpn)
Bitcoin: bc1qmhtgcf9637rl3kqyy22r2a8wa8laka4t9rx2mf <br>
USDT BEP20: 0x6abD576765a826f87D1D95183438f9408C901bE4 <br>
USDT TRC20: TELAitazF1MZGmiNjTcnxDjEiH5oe7LC9d <br>
XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3 <br>
TON: UQDpU1CyKRmg7L8mNScKk9FRc2SlESuI7N-Hby4nX-CcVmns
## Благодарности
Этот проект тестируется с помощью BrowserStack.
Мы выражаем благодарность [BrowserStack](https://www.browserstack.com) за поддержку нашего проекта.

View File

@@ -1,149 +0,0 @@
# Third-Party Licenses
This project is licensed under the GNU General Public License v3.0.
This file lists third-party software components used by this repository.
Each component is distributed under its own license as linked below.
---
## QtKeychain
- Source: https://github.com/frankosterfeld/qtkeychain
- License: BSD License
- License Text: https://www.gnu.org/licenses/license-list.html#ModifiedBSD
---
## QSimpleCrypto
- Source: https://github.com/n1flh31mur/QSimpleCrypto
- License: Apache License 2.0
- License Text: https://github.com/n1flh31mur/QSimpleCrypto/blob/master/LICENSE
---
## SortFilterProxyModel
- Source: https://github.com/oKcerG/SortFilterProxyModel
- License: MIT License
- License Text: https://github.com/oKcerG/SortFilterProxyModel/blob/master/LICENSE
---
## QJsonStruct
- Source: https://github.com/Qv2ray/QJsonStruct
- License: MIT License
- License Text: https://github.com/Qv2ray/QJsonStruct/blob/master/LICENSE
---
## QR Code Generator (qrcodegen)
- Source: https://github.com/nayuki/QR-Code-generator
- License: MIT License
- License Text: https://www.nayuki.io/page/qr-code-generator-library
---
## Qt Gamepad
- Source: https://github.com/qt/qtgamepad
- License: GNU General Public License v3.0 (GPL-3.0)
- License Text: https://www.gnu.org/licenses/gpl-3.0.en.html
---
## AmneziaWG Apple (WireGuard)
- Source: https://github.com/amnezia-vpn/amneziawg-apple
- License: MIT License
- License Text: https://github.com/amnezia-vpn/amneziawg-apple/blob/master/COPYING
---
## AmneziaWG Android
- Source: https://github.com/amnezia-vpn/amneziawg-go
- License: MIT License
- License Text: https://github.com/amnezia-vpn/amneziawg-go/blob/master/LICENSE
---
## Xray Core
- Source: https://github.com/XTLS/Xray-core
- License: Mozilla Public License 2.0 (MPL-2.0)
- License Text: https://github.com/XTLS/Xray-core/blob/main/LICENSE
---
## Cloak
- Source: https://github.com/cbeuw/Cloak
- License: GNU General Public License v3.0 (GPL-3.0)
- License Text: https://github.com/cbeuw/Cloak/blob/master/LICENSE
---
## Shadowsocks
- Source: https://github.com/shadowsocks/shadowsocks-libev
- License: GPL-3.0-or-later
- License Text: http://www.gnu.org/licenses/
---
## OpenSSL
- Source: https://github.com/openssl/openssl
- License: Apache License 2.0
- License Text: https://www.openssl.org/source/license.html
---
## libssh
- Source: https://www.libssh.org/
- License: GNU Lesser General Public License (LGPL)
- License Text: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
---
## OpenVPNAdapter
- Source: https://github.com/ss-abramchuk/OpenVPNAdapter
- License: GNU Affero General Public License v3.0 (AGPL-3.0)
- License Text: https://github.com/ss-abramchuk/OpenVPNAdapter/blob/master/LICENSE
---
## Wintun
- Source: https://www.wintun.net/
- License: Prebuilt Binaries License
- License Text: https://github.com/WireGuard/wintun/blob/master/prebuilt-binaries-license.txt
---
## Mullvad Split Tunnel Driver
- Source: https://github.com/mullvad/win-split-tunnel
- License: GNU General Public License v3.0 (GPL-3.0) and Mozilla Public License Version 2.0
- License Text: https://github.com/mullvad/win-split-tunnel/blob/master/LICENSE-GPL.md https://github.com/mullvad/win-split-tunnel/blob/master/LICENSE-MPL.txt
---
## tun2socks
- Source: https://github.com/eycorsican/go-tun2socks
- License: MIT License
- License Text: https://github.com/eycorsican/go-tun2socks/blob/master/LICENSE
---
## TAP-Windows Driver
- Source: https://github.com/OpenVPN/tap-windows6
- License: tap-windows6 license
- License Text: https://github.com/OpenVPN/tap-windows6/blob/master/COPYING

1
client/3rd/OpenVPNAdapter vendored Submodule

View File

@@ -0,0 +1,25 @@
include_directories(${CMAKE_CURRENT_LIST_DIR})
find_package(Qt6 REQUIRED COMPONENTS
Core Network
)
set(LIBS ${LIBS} Qt6::Core Qt6::Network)
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/singleapplication.h
${CMAKE_CURRENT_LIST_DIR}/singleapplication_p.h
)
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_LIST_DIR}/singleapplication.cpp
${CMAKE_CURRENT_LIST_DIR}/singleapplication_p.cpp
)
if(WIN32)
if(MSVC)
set(LIBS ${LIBS} Advapi32.lib)
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
set(LIBS ${LIBS} advapi32)
endif()
endif()

View File

@@ -0,0 +1,274 @@
// The MIT License (MIT)
//
// Copyright (c) Itay Grudev 2015 - 2020
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#include <QtCore/QElapsedTimer>
#include <QtCore/QByteArray>
#include <QtCore/QSharedMemory>
#include "singleapplication.h"
#include "singleapplication_p.h"
/**
* @brief Constructor. Checks and fires up LocalServer or closes the program
* if another instance already exists
* @param argc
* @param argv
* @param allowSecondary Whether to enable secondary instance support
* @param options Optional flags to toggle specific behaviour
* @param timeout Maximum time blocking functions are allowed during app load
*/
SingleApplication::SingleApplication( int &argc, char *argv[], bool allowSecondary, Options options, int timeout, const QString &userData )
: app_t( argc, argv ), d_ptr( new SingleApplicationPrivate( this ) )
{
Q_D( SingleApplication );
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
// On Android and iOS since the library is not supported fallback to
// standard QApplication behaviour by simply returning at this point.
qWarning() << "SingleApplication is not supported on Android and iOS systems.";
return;
#endif
// Store the current mode of the program
d->options = options;
// Add any unique user data
if ( ! userData.isEmpty() )
d->addAppData( userData );
// Generating an application ID used for identifying the shared memory
// block and QLocalServer
d->genBlockServerName();
// To mitigate QSharedMemory issues with large amount of processes
// attempting to attach at the same time
SingleApplicationPrivate::randomSleep();
#ifdef Q_OS_UNIX
// By explicitly attaching it and then deleting it we make sure that the
// memory is deleted even after the process has crashed on Unix.
d->memory = new QSharedMemory( d->blockServerName );
d->memory->attach();
delete d->memory;
#endif
// Guarantee thread safe behaviour with a shared memory block.
d->memory = new QSharedMemory( d->blockServerName );
// Create a shared memory block
if( d->memory->create( sizeof( InstancesInfo ) )){
// Initialize the shared memory block
if( ! d->memory->lock() ){
qCritical() << "SingleApplication: Unable to lock memory block after create.";
abortSafely();
}
d->initializeMemoryBlock();
} else {
if( d->memory->error() == QSharedMemory::AlreadyExists ){
// Attempt to attach to the memory segment
if( ! d->memory->attach() ){
qCritical() << "SingleApplication: Unable to attach to shared memory block.";
abortSafely();
}
if( ! d->memory->lock() ){
qCritical() << "SingleApplication: Unable to lock memory block after attach.";
abortSafely();
}
} else {
qCritical() << "SingleApplication: Unable to create block.";
abortSafely();
}
}
auto *inst = static_cast<InstancesInfo*>( d->memory->data() );
QElapsedTimer time;
time.start();
// Make sure the shared memory block is initialised and in consistent state
while( true ){
// If the shared memory block's checksum is valid continue
if( d->blockChecksum() == inst->checksum ) break;
// If more than 5s have elapsed, assume the primary instance crashed and
// assume it's position
if( time.elapsed() > 5000 ){
qWarning() << "SingleApplication: Shared memory block has been in an inconsistent state from more than 5s. Assuming primary instance failure.";
d->initializeMemoryBlock();
}
// Otherwise wait for a random period and try again. The random sleep here
// limits the probability of a collision between two racing apps and
// allows the app to initialise faster
if( ! d->memory->unlock() ){
qDebug() << "SingleApplication: Unable to unlock memory for random wait.";
qDebug() << d->memory->errorString();
}
SingleApplicationPrivate::randomSleep();
if( ! d->memory->lock() ){
qCritical() << "SingleApplication: Unable to lock memory after random wait.";
abortSafely();
}
}
if( inst->primary == false ){
d->startPrimary();
if( ! d->memory->unlock() ){
qDebug() << "SingleApplication: Unable to unlock memory after primary start.";
qDebug() << d->memory->errorString();
}
return;
}
// Check if another instance can be started
if( allowSecondary ){
d->startSecondary();
if( d->options & Mode::SecondaryNotification ){
d->connectToPrimary( timeout, SingleApplicationPrivate::SecondaryInstance );
}
if( ! d->memory->unlock() ){
qDebug() << "SingleApplication: Unable to unlock memory after secondary start.";
qDebug() << d->memory->errorString();
}
return;
}
if( ! d->memory->unlock() ){
qDebug() << "SingleApplication: Unable to unlock memory at end of execution.";
qDebug() << d->memory->errorString();
}
d->connectToPrimary( timeout, SingleApplicationPrivate::NewInstance );
delete d;
::exit( EXIT_SUCCESS );
}
SingleApplication::~SingleApplication()
{
Q_D( SingleApplication );
delete d;
}
/**
* Checks if the current application instance is primary.
* @return Returns true if the instance is primary, false otherwise.
*/
bool SingleApplication::isPrimary() const
{
Q_D( const SingleApplication );
return d->server != nullptr;
}
/**
* Checks if the current application instance is secondary.
* @return Returns true if the instance is secondary, false otherwise.
*/
bool SingleApplication::isSecondary() const
{
Q_D( const SingleApplication );
return d->server == nullptr;
}
/**
* Allows you to identify an instance by returning unique consecutive instance
* ids. It is reset when the first (primary) instance of your app starts and
* only incremented afterwards.
* @return Returns a unique instance id.
*/
quint32 SingleApplication::instanceId() const
{
Q_D( const SingleApplication );
return d->instanceNumber;
}
/**
* Returns the OS PID (Process Identifier) of the process running the primary
* instance. Especially useful when SingleApplication is coupled with OS.
* specific APIs.
* @return Returns the primary instance PID.
*/
qint64 SingleApplication::primaryPid() const
{
Q_D( const SingleApplication );
return d->primaryPid();
}
/**
* Returns the username the primary instance is running as.
* @return Returns the username the primary instance is running as.
*/
QString SingleApplication::primaryUser() const
{
Q_D( const SingleApplication );
return d->primaryUser();
}
/**
* Returns the username the current instance is running as.
* @return Returns the username the current instance is running as.
*/
QString SingleApplication::currentUser() const
{
return SingleApplicationPrivate::getUsername();
}
/**
* Sends message to the Primary Instance.
* @param message The message to send.
* @param timeout the maximum timeout in milliseconds for blocking functions.
* @return true if the message was sent successfuly, false otherwise.
*/
bool SingleApplication::sendMessage( const QByteArray &message, int timeout )
{
Q_D( SingleApplication );
// Nobody to connect to
if( isPrimary() ) return false;
// Make sure the socket is connected
if( ! d->connectToPrimary( timeout, SingleApplicationPrivate::Reconnect ) )
return false;
d->socket->write( message );
bool dataWritten = d->socket->waitForBytesWritten( timeout );
d->socket->flush();
return dataWritten;
}
/**
* Cleans up the shared memory block and exits with a failure.
* This function halts program execution.
*/
void SingleApplication::abortSafely()
{
Q_D( SingleApplication );
qCritical() << "SingleApplication: " << d->memory->error() << d->memory->errorString();
delete d;
::exit( EXIT_FAILURE );
}
QStringList SingleApplication::userData() const
{
Q_D( const SingleApplication );
return d->appData();
}

View File

@@ -0,0 +1,154 @@
// The MIT License (MIT)
//
// Copyright (c) Itay Grudev 2015 - 2018
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#ifndef SINGLE_APPLICATION_H
#define SINGLE_APPLICATION_H
#include <QtCore/QtGlobal>
#include <QtNetwork/QLocalSocket>
#ifndef QAPPLICATION_CLASS
#define QAPPLICATION_CLASS QApplication
#endif
#include QT_STRINGIFY(QAPPLICATION_CLASS)
class SingleApplicationPrivate;
/**
* @brief The SingleApplication class handles multiple instances of the same
* Application
* @see QCoreApplication
*/
class SingleApplication : public QAPPLICATION_CLASS
{
Q_OBJECT
using app_t = QAPPLICATION_CLASS;
public:
/**
* @brief Mode of operation of SingleApplication.
* Whether the block should be user-wide or system-wide and whether the
* primary instance should be notified when a secondary instance had been
* started.
* @note Operating system can restrict the shared memory blocks to the same
* user, in which case the User/System modes will have no effect and the
* block will be user wide.
* @enum
*/
enum Mode {
User = 1 << 0,
System = 1 << 1,
SecondaryNotification = 1 << 2,
ExcludeAppVersion = 1 << 3,
ExcludeAppPath = 1 << 4
};
Q_DECLARE_FLAGS(Options, Mode)
/**
* @brief Intitializes a SingleApplication instance with argc command line
* arguments in argv
* @arg {int &} argc - Number of arguments in argv
* @arg {const char *[]} argv - Supplied command line arguments
* @arg {bool} allowSecondary - Whether to start the instance as secondary
* if there is already a primary instance.
* @arg {Mode} mode - Whether for the SingleApplication block to be applied
* User wide or System wide.
* @arg {int} timeout - Timeout to wait in milliseconds.
* @note argc and argv may be changed as Qt removes arguments that it
* recognizes
* @note Mode::SecondaryNotification only works if set on both the primary
* instance and the secondary instance.
* @note The timeout is just a hint for the maximum time of blocking
* operations. It does not guarantee that the SingleApplication
* initialisation will be completed in given time, though is a good hint.
* Usually 4*timeout would be the worst case (fail) scenario.
* @see See the corresponding QAPPLICATION_CLASS constructor for reference
*/
explicit SingleApplication( int &argc, char *argv[], bool allowSecondary = false, Options options = Mode::User, int timeout = 1000, const QString &userData = {} );
~SingleApplication() override;
/**
* @brief Returns if the instance is the primary instance
* @returns {bool}
*/
bool isPrimary() const;
/**
* @brief Returns if the instance is a secondary instance
* @returns {bool}
*/
bool isSecondary() const;
/**
* @brief Returns a unique identifier for the current instance
* @returns {qint32}
*/
quint32 instanceId() const;
/**
* @brief Returns the process ID (PID) of the primary instance
* @returns {qint64}
*/
qint64 primaryPid() const;
/**
* @brief Returns the username of the user running the primary instance
* @returns {QString}
*/
QString primaryUser() const;
/**
* @brief Returns the username of the current user
* @returns {QString}
*/
QString currentUser() const;
/**
* @brief Sends a message to the primary instance. Returns true on success.
* @param {int} timeout - Timeout for connecting
* @returns {bool}
* @note sendMessage() will return false if invoked from the primary
* instance.
*/
bool sendMessage( const QByteArray &message, int timeout = 100 );
/**
* @brief Get the set user data.
* @returns {QStringList}
*/
QStringList userData() const;
Q_SIGNALS:
void instanceStarted();
void receivedMessage( quint32 instanceId, QByteArray message );
private:
SingleApplicationPrivate *d_ptr;
Q_DECLARE_PRIVATE(SingleApplication)
void abortSafely();
};
Q_DECLARE_OPERATORS_FOR_FLAGS(SingleApplication::Options)
#endif // SINGLE_APPLICATION_H

View File

@@ -0,0 +1,15 @@
QT += core network
CONFIG += c++11
HEADERS += \
$$PWD/singleapplication.h \
$$PWD/singleapplication_p.h
SOURCES += $$PWD/singleapplication.cpp \
$$PWD/singleapplication_p.cpp
INCLUDEPATH += $$PWD
win32 {
msvc:LIBS += Advapi32.lib
gcc:LIBS += -ladvapi32
}

View File

@@ -0,0 +1,486 @@
// The MIT License (MIT)
//
// Copyright (c) Itay Grudev 2015 - 2020
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// W A R N I N G !!!
// -----------------
//
// This file is not part of the SingleApplication API. It is used purely as an
// implementation detail. This header file may change from version to
// version without notice, or may even be removed.
//
#include <cstdlib>
#include <cstddef>
#include <QtCore/QDir>
#include <QtCore/QThread>
#include <QtCore/QByteArray>
#include <QtCore/QDataStream>
#include <QtCore/QElapsedTimer>
#include <QtCore/QCryptographicHash>
#include <QtNetwork/QLocalServer>
#include <QtNetwork/QLocalSocket>
#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0)
#include <QtCore/QRandomGenerator>
#else
#include <QtCore/QDateTime>
#endif
#include "singleapplication.h"
#include "singleapplication_p.h"
#ifdef Q_OS_UNIX
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#endif
#ifdef Q_OS_WIN
#ifndef NOMINMAX
#define NOMINMAX 1
#endif
#include <windows.h>
#include <lmcons.h>
#endif
SingleApplicationPrivate::SingleApplicationPrivate( SingleApplication *q_ptr )
: q_ptr( q_ptr )
{
server = nullptr;
socket = nullptr;
memory = nullptr;
instanceNumber = 0;
}
SingleApplicationPrivate::~SingleApplicationPrivate()
{
if( socket != nullptr ){
socket->close();
delete socket;
}
if( memory != nullptr ){
memory->lock();
auto *inst = static_cast<InstancesInfo*>(memory->data());
if( server != nullptr ){
server->close();
delete server;
inst->primary = false;
inst->primaryPid = -1;
inst->primaryUser[0] = '\0';
inst->checksum = blockChecksum();
}
memory->unlock();
delete memory;
}
}
QString SingleApplicationPrivate::getUsername()
{
#ifdef Q_OS_WIN
wchar_t username[UNLEN + 1];
// Specifies size of the buffer on input
DWORD usernameLength = UNLEN + 1;
if( GetUserNameW( username, &usernameLength ) )
return QString::fromWCharArray( username );
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
return QString::fromLocal8Bit( qgetenv( "USERNAME" ) );
#else
return qEnvironmentVariable( "USERNAME" );
#endif
#endif
#ifdef Q_OS_UNIX
QString username;
uid_t uid = geteuid();
struct passwd *pw = getpwuid( uid );
if( pw )
username = QString::fromLocal8Bit( pw->pw_name );
if ( username.isEmpty() ){
#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0)
username = QString::fromLocal8Bit( qgetenv( "USER" ) );
#else
username = qEnvironmentVariable( "USER" );
#endif
}
return username;
#endif
}
void SingleApplicationPrivate::genBlockServerName()
{
QCryptographicHash appData( QCryptographicHash::Sha256 );
appData.addData( "SingleApplication", 17 );
appData.addData( SingleApplication::app_t::applicationName().toUtf8() );
appData.addData( SingleApplication::app_t::organizationName().toUtf8() );
appData.addData( SingleApplication::app_t::organizationDomain().toUtf8() );
if ( ! appDataList.isEmpty() )
appData.addData( appDataList.join( "" ).toUtf8() );
if( ! (options & SingleApplication::Mode::ExcludeAppVersion) ){
appData.addData( SingleApplication::app_t::applicationVersion().toUtf8() );
}
if( ! (options & SingleApplication::Mode::ExcludeAppPath) ){
#ifdef Q_OS_WIN
appData.addData( SingleApplication::app_t::applicationFilePath().toLower().toUtf8() );
#else
appData.addData( SingleApplication::app_t::applicationFilePath().toUtf8() );
#endif
}
// User level block requires a user specific data in the hash
if( options & SingleApplication::Mode::User ){
appData.addData( getUsername().toUtf8() );
}
// Replace the backslash in RFC 2045 Base64 [a-zA-Z0-9+/=] to comply with
// server naming requirements.
blockServerName = appData.result().toBase64().replace("/", "_");
}
void SingleApplicationPrivate::initializeMemoryBlock() const
{
auto *inst = static_cast<InstancesInfo*>( memory->data() );
inst->primary = false;
inst->secondary = 0;
inst->primaryPid = -1;
inst->primaryUser[0] = '\0';
inst->checksum = blockChecksum();
}
void SingleApplicationPrivate::startPrimary()
{
// Reset the number of connections
auto *inst = static_cast <InstancesInfo*>( memory->data() );
inst->primary = true;
inst->primaryPid = QCoreApplication::applicationPid();
qstrncpy( inst->primaryUser, getUsername().toUtf8().data(), sizeof(inst->primaryUser) );
inst->checksum = blockChecksum();
instanceNumber = 0;
// Successful creation means that no main process exists
// So we start a QLocalServer to listen for connections
QLocalServer::removeServer( blockServerName );
server = new QLocalServer();
// Restrict access to the socket according to the
// SingleApplication::Mode::User flag on User level or no restrictions
if( options & SingleApplication::Mode::User ){
server->setSocketOptions( QLocalServer::UserAccessOption );
} else {
server->setSocketOptions( QLocalServer::WorldAccessOption );
}
server->listen( blockServerName );
QObject::connect(
server,
&QLocalServer::newConnection,
this,
&SingleApplicationPrivate::slotConnectionEstablished
);
}
void SingleApplicationPrivate::startSecondary()
{
auto *inst = static_cast <InstancesInfo*>( memory->data() );
inst->secondary += 1;
inst->checksum = blockChecksum();
instanceNumber = inst->secondary;
}
bool SingleApplicationPrivate::connectToPrimary( int msecs, ConnectionType connectionType )
{
QElapsedTimer time;
time.start();
// Connect to the Local Server of the Primary Instance if not already
// connected.
if( socket == nullptr ){
socket = new QLocalSocket();
}
if( socket->state() == QLocalSocket::ConnectedState ) return true;
if( socket->state() != QLocalSocket::ConnectedState ){
while( true ){
randomSleep();
if( socket->state() != QLocalSocket::ConnectingState )
socket->connectToServer( blockServerName );
if( socket->state() == QLocalSocket::ConnectingState ){
socket->waitForConnected( static_cast<int>(msecs - time.elapsed()) );
}
// If connected break out of the loop
if( socket->state() == QLocalSocket::ConnectedState ) break;
// If elapsed time since start is longer than the method timeout return
if( time.elapsed() >= msecs ) return false;
}
}
// Initialisation message according to the SingleApplication protocol
QByteArray initMsg;
QDataStream writeStream(&initMsg, QIODevice::WriteOnly);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
writeStream.setVersion(QDataStream::Qt_5_6);
#endif
writeStream << blockServerName.toLatin1();
writeStream << static_cast<quint8>(connectionType);
writeStream << instanceNumber;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
quint16 checksum = qChecksum(QByteArray(initMsg, static_cast<quint32>(initMsg.length())));
#else
quint16 checksum = qChecksum(initMsg.constData(), static_cast<quint32>(initMsg.length()));
#endif
writeStream << checksum;
// The header indicates the message length that follows
QByteArray header;
QDataStream headerStream(&header, QIODevice::WriteOnly);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
headerStream.setVersion(QDataStream::Qt_5_6);
#endif
headerStream << static_cast <quint64>( initMsg.length() );
socket->write( header );
socket->write( initMsg );
bool result = socket->waitForBytesWritten( static_cast<int>(msecs - time.elapsed()) );
socket->flush();
return result;
}
quint16 SingleApplicationPrivate::blockChecksum() const
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
quint16 checksum = qChecksum(QByteArray(static_cast<const char*>(memory->constData()), offsetof(InstancesInfo, checksum)));
#else
quint16 checksum = qChecksum(static_cast<const char*>(memory->constData()), offsetof(InstancesInfo, checksum));
#endif
return checksum;
}
qint64 SingleApplicationPrivate::primaryPid() const
{
qint64 pid;
memory->lock();
auto *inst = static_cast<InstancesInfo*>( memory->data() );
pid = inst->primaryPid;
memory->unlock();
return pid;
}
QString SingleApplicationPrivate::primaryUser() const
{
QByteArray username;
memory->lock();
auto *inst = static_cast<InstancesInfo*>( memory->data() );
username = inst->primaryUser;
memory->unlock();
return QString::fromUtf8( username );
}
/**
* @brief Executed when a connection has been made to the LocalServer
*/
void SingleApplicationPrivate::slotConnectionEstablished()
{
QLocalSocket *nextConnSocket = server->nextPendingConnection();
connectionMap.insert(nextConnSocket, ConnectionInfo());
QObject::connect(nextConnSocket, &QLocalSocket::aboutToClose,
[nextConnSocket, this](){
auto &info = connectionMap[nextConnSocket];
Q_EMIT this->slotClientConnectionClosed( nextConnSocket, info.instanceId );
}
);
QObject::connect(nextConnSocket, &QLocalSocket::disconnected, nextConnSocket, &QLocalSocket::deleteLater);
QObject::connect(nextConnSocket, &QLocalSocket::destroyed,
[nextConnSocket, this](){
connectionMap.remove(nextConnSocket);
}
);
QObject::connect(nextConnSocket, &QLocalSocket::readyRead,
[nextConnSocket, this](){
auto &info = connectionMap[nextConnSocket];
switch(info.stage){
case StageHeader:
readInitMessageHeader(nextConnSocket);
break;
case StageBody:
readInitMessageBody(nextConnSocket);
break;
case StageConnected:
Q_EMIT this->slotDataAvailable( nextConnSocket, info.instanceId );
break;
default:
break;
};
}
);
}
void SingleApplicationPrivate::readInitMessageHeader( QLocalSocket *sock )
{
if (!connectionMap.contains( sock )){
return;
}
if( sock->bytesAvailable() < ( qint64 )sizeof( quint64 ) ){
return;
}
QDataStream headerStream( sock );
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
headerStream.setVersion( QDataStream::Qt_5_6 );
#endif
// Read the header to know the message length
quint64 msgLen = 0;
headerStream >> msgLen;
ConnectionInfo &info = connectionMap[sock];
info.stage = StageBody;
info.msgLen = msgLen;
if ( sock->bytesAvailable() >= (qint64) msgLen ){
readInitMessageBody( sock );
}
}
void SingleApplicationPrivate::readInitMessageBody( QLocalSocket *sock )
{
Q_Q(SingleApplication);
if (!connectionMap.contains( sock )){
return;
}
ConnectionInfo &info = connectionMap[sock];
if( sock->bytesAvailable() < ( qint64 )info.msgLen ){
return;
}
// Read the message body
QByteArray msgBytes = sock->read(info.msgLen);
QDataStream readStream(msgBytes);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 6, 0))
readStream.setVersion( QDataStream::Qt_5_6 );
#endif
// server name
QByteArray latin1Name;
readStream >> latin1Name;
// connection type
ConnectionType connectionType = InvalidConnection;
quint8 connTypeVal = InvalidConnection;
readStream >> connTypeVal;
connectionType = static_cast <ConnectionType>( connTypeVal );
// instance id
quint32 instanceId = 0;
readStream >> instanceId;
// checksum
quint16 msgChecksum = 0;
readStream >> msgChecksum;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
const quint16 actualChecksum = qChecksum(QByteArray(msgBytes, static_cast<quint32>(msgBytes.length() - sizeof(quint16))));
#else
const quint16 actualChecksum = qChecksum(msgBytes.constData(), static_cast<quint32>(msgBytes.length() - sizeof(quint16)));
#endif
bool isValid = readStream.status() == QDataStream::Ok &&
QLatin1String(latin1Name) == blockServerName &&
msgChecksum == actualChecksum;
if( !isValid ){
sock->close();
return;
}
info.instanceId = instanceId;
info.stage = StageConnected;
if( connectionType == NewInstance ||
( connectionType == SecondaryInstance &&
options & SingleApplication::Mode::SecondaryNotification ) )
{
Q_EMIT q->instanceStarted();
}
if (sock->bytesAvailable() > 0){
Q_EMIT this->slotDataAvailable( sock, instanceId );
}
}
void SingleApplicationPrivate::slotDataAvailable( QLocalSocket *dataSocket, quint32 instanceId )
{
Q_Q(SingleApplication);
Q_EMIT q->receivedMessage( instanceId, dataSocket->readAll() );
}
void SingleApplicationPrivate::slotClientConnectionClosed( QLocalSocket *closedSocket, quint32 instanceId )
{
if( closedSocket->bytesAvailable() > 0 )
Q_EMIT slotDataAvailable( closedSocket, instanceId );
}
void SingleApplicationPrivate::randomSleep()
{
#if QT_VERSION >= QT_VERSION_CHECK( 5, 10, 0 )
QThread::msleep( QRandomGenerator::global()->bounded( 8u, 18u ));
#else
qsrand( QDateTime::currentMSecsSinceEpoch() % std::numeric_limits<uint>::max() );
QThread::msleep( 8 + static_cast <unsigned long>( static_cast <float>( qrand() ) / RAND_MAX * 10 ));
#endif
}
void SingleApplicationPrivate::addAppData(const QString &data)
{
appDataList.push_back(data);
}
QStringList SingleApplicationPrivate::appData() const
{
return appDataList;
}

View File

@@ -0,0 +1,104 @@
// The MIT License (MIT)
//
// Copyright (c) Itay Grudev 2015 - 2020
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// W A R N I N G !!!
// -----------------
//
// This file is not part of the SingleApplication API. It is used purely as an
// implementation detail. This header file may change from version to
// version without notice, or may even be removed.
//
#ifndef SINGLEAPPLICATION_P_H
#define SINGLEAPPLICATION_P_H
#include <QtCore/QSharedMemory>
#include <QtNetwork/QLocalServer>
#include <QtNetwork/QLocalSocket>
#include "singleapplication.h"
struct InstancesInfo {
bool primary;
quint32 secondary;
qint64 primaryPid;
char primaryUser[128];
quint16 checksum; // Must be the last field
};
struct ConnectionInfo {
qint64 msgLen = 0;
quint32 instanceId = 0;
quint8 stage = 0;
};
class SingleApplicationPrivate : public QObject {
Q_OBJECT
public:
enum ConnectionType : quint8 {
InvalidConnection = 0,
NewInstance = 1,
SecondaryInstance = 2,
Reconnect = 3
};
enum ConnectionStage : quint8 {
StageHeader = 0,
StageBody = 1,
StageConnected = 2,
};
Q_DECLARE_PUBLIC(SingleApplication)
SingleApplicationPrivate( SingleApplication *q_ptr );
~SingleApplicationPrivate() override;
static QString getUsername();
void genBlockServerName();
void initializeMemoryBlock() const;
void startPrimary();
void startSecondary();
bool connectToPrimary( int msecs, ConnectionType connectionType );
quint16 blockChecksum() const;
qint64 primaryPid() const;
QString primaryUser() const;
void readInitMessageHeader(QLocalSocket *socket);
void readInitMessageBody(QLocalSocket *socket);
static void randomSleep();
void addAppData(const QString &data);
QStringList appData() const;
SingleApplication *q_ptr;
QSharedMemory *memory;
QLocalSocket *socket;
QLocalServer *server;
quint32 instanceNumber;
QString blockServerName;
SingleApplication::Options options;
QMap<QLocalSocket*, ConnectionInfo> connectionMap;
QStringList appDataList;
public Q_SLOTS:
void slotConnectionEstablished();
void slotDataAvailable( QLocalSocket*, quint32 );
void slotClientConnectionClosed( QLocalSocket*, quint32 );
};
#endif // SINGLEAPPLICATION_P_H

Submodule client/3rd/qtgamepad deleted from f72b3e0c62

View File

@@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN)
project(${PROJECT})
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
set_property(GLOBAL PROPERTY AUTOGEN_TARGETS_FOLDER "Autogen")
set_property(GLOBAL PROPERTY AUTOMOC_TARGETS_FOLDER "Autogen")
@@ -24,15 +25,14 @@ execute_process(
add_definitions(-DGIT_COMMIT_HASH="${GIT_COMMIT_HASH}")
add_definitions(-DPROD_AGW_PUBLIC_KEY="$ENV{PROD_AGW_PUBLIC_KEY}")
add_definitions(-DPROD_S3_ENDPOINT="$ENV{PROD_S3_ENDPOINT}")
add_definitions(-DFALLBACK_S3_ENDPOINT="$ENV{FALLBACK_S3_ENDPOINT}")
add_definitions(-DPROD_PROXY_STORAGE_KEY="$ENV{PROD_PROXY_STORAGE_KEY}")
add_definitions(-DDEV_AGW_PUBLIC_KEY="$ENV{DEV_AGW_PUBLIC_KEY}")
add_definitions(-DDEV_AGW_ENDPOINT="$ENV{DEV_AGW_ENDPOINT}")
add_definitions(-DDEV_S3_ENDPOINT="$ENV{DEV_S3_ENDPOINT}")
add_definitions(-DFREE_V2_ENDPOINT="$ENV{FREE_V2_ENDPOINT}")
add_definitions(-DPREM_V1_ENDPOINT="$ENV{PREM_V1_ENDPOINT}")
if(IOS)
set(PACKAGES ${PACKAGES} Multimedia)
endif()
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
set(PACKAGES ${PACKAGES} Widgets)
@@ -47,27 +47,24 @@ set(LIBS ${LIBS}
Qt6::Core5Compat Qt6::Concurrent
)
if(IOS)
set(LIBS ${LIBS} Qt6::Multimedia)
endif()
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
set(LIBS ${LIBS} Qt6::Widgets)
endif()
qt_standard_project_setup()
qt_add_executable(${PROJECT} MANUAL_FINALIZATION)
target_include_directories(${PROJECT} PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
)
if(WIN32 OR (APPLE AND NOT IOS 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)
endif()
qt6_add_resources(QRC ${QRC}
${CMAKE_CURRENT_LIST_DIR}/images/images.qrc
${CMAKE_CURRENT_LIST_DIR}/images/flagKit.qrc
${CMAKE_CURRENT_LIST_DIR}/ui/qml/qml.qrc
${CMAKE_CURRENT_LIST_DIR}/server_scripts/serverScripts.qrc
)
qt6_add_resources(QRC ${QRC} ${CMAKE_CURRENT_LIST_DIR}/resources.qrc)
# -- i18n begin
set(CMAKE_AUTORCC ON)
@@ -84,7 +81,6 @@ set(AMNEZIAVPN_TS_FILES
)
file(GLOB_RECURSE AMNEZIAVPN_TS_SOURCES *.qrc *.cpp *.h *.ui)
list(FILTER AMNEZIAVPN_TS_SOURCES EXCLUDE REGEX "qtgamepad/examples")
qt_create_translation(AMNEZIAVPN_QM_FILES ${AMNEZIAVPN_TS_SOURCES} ${AMNEZIAVPN_TS_FILES})
@@ -99,6 +95,11 @@ configure_file(${CMAKE_CURRENT_LIST_DIR}/translations/translations.qrc.in ${CMAK
qt6_add_resources(QRC ${I18NQRC} ${CMAKE_CURRENT_BINARY_DIR}/translations.qrc)
# -- i18n end
if(IOS)
execute_process(COMMAND bash ${CMAKE_CURRENT_LIST_DIR}/ios/scripts/openvpn.sh args
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR})
endif()
set(IS_CI ${CI})
if(IS_CI)
message("Detected CI env")
@@ -108,8 +109,8 @@ if(IS_CI)
endif()
endif()
include(${CMAKE_CURRENT_LIST_DIR}/cmake/3rdparty.cmake)
include(${CMAKE_CURRENT_LIST_DIR}/cmake/sources.cmake)
include_directories(
${CMAKE_CURRENT_LIST_DIR}/../ipc
@@ -118,31 +119,165 @@ include_directories(
${CMAKE_CURRENT_BINARY_DIR}
)
if(MACOS_NE)
message("MACOS_NE is ON")
add_definitions(-DQ_OS_MAC)
add_definitions(-DMACOS_NE)
message("Add macros for MacOS Network Extension")
else()
message("MACOS_NE is OFF")
endif()
configure_file(${CMAKE_CURRENT_LIST_DIR}/../version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h)
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/migrations.h
${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc.h
${CMAKE_CURRENT_LIST_DIR}/amnezia_application.h
${CMAKE_CURRENT_LIST_DIR}/containers/containers_defs.h
${CMAKE_CURRENT_LIST_DIR}/core/defs.h
${CMAKE_CURRENT_LIST_DIR}/core/errorstrings.h
${CMAKE_CURRENT_LIST_DIR}/core/scripts_registry.h
${CMAKE_CURRENT_LIST_DIR}/core/server_defs.h
${CMAKE_CURRENT_LIST_DIR}/core/controllers/apiController.h
${CMAKE_CURRENT_LIST_DIR}/core/controllers/serverController.h
${CMAKE_CURRENT_LIST_DIR}/core/controllers/vpnConfigurationController.h
${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.h
${CMAKE_CURRENT_LIST_DIR}/protocols/qml_register_protocols.h
${CMAKE_CURRENT_LIST_DIR}/ui/pages.h
${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.h
${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.h
${CMAKE_CURRENT_BINARY_DIR}/version.h
${CMAKE_CURRENT_LIST_DIR}/core/sshclient.h
${CMAKE_CURRENT_LIST_DIR}/core/networkUtilities.h
${CMAKE_CURRENT_LIST_DIR}/core/serialization/serialization.h
${CMAKE_CURRENT_LIST_DIR}/core/serialization/transfer.h
${CMAKE_CURRENT_LIST_DIR}/core/enums/apiEnums.h
${CMAKE_CURRENT_LIST_DIR}/../common/logger/logger.h
)
# Mozilla headres
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/mozilla/models/server.h
${CMAKE_CURRENT_LIST_DIR}/mozilla/shared/ipaddress.h
${CMAKE_CURRENT_LIST_DIR}/mozilla/shared/leakdetector.h
${CMAKE_CURRENT_LIST_DIR}/mozilla/controllerimpl.h
${CMAKE_CURRENT_LIST_DIR}/mozilla/localsocketcontroller.h
)
include_directories(mozilla)
include_directories(mozilla/shared)
include_directories(mozilla/models)
configure_file(${CMAKE_CURRENT_LIST_DIR}/../version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h)
if(NOT IOS)
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.h
)
endif()
if(NOT ANDROID)
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.h
)
endif()
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_LIST_DIR}/migrations.cpp
${CMAKE_CURRENT_LIST_DIR}/amnezia_application.cpp
${CMAKE_CURRENT_LIST_DIR}/containers/containers_defs.cpp
${CMAKE_CURRENT_LIST_DIR}/core/errorstrings.cpp
${CMAKE_CURRENT_LIST_DIR}/core/scripts_registry.cpp
${CMAKE_CURRENT_LIST_DIR}/core/server_defs.cpp
${CMAKE_CURRENT_LIST_DIR}/core/controllers/apiController.cpp
${CMAKE_CURRENT_LIST_DIR}/core/controllers/serverController.cpp
${CMAKE_CURRENT_LIST_DIR}/core/controllers/vpnConfigurationController.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.cpp
${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.cpp
${CMAKE_CURRENT_LIST_DIR}/core/sshclient.cpp
${CMAKE_CURRENT_LIST_DIR}/core/networkUtilities.cpp
${CMAKE_CURRENT_LIST_DIR}/core/serialization/outbound.cpp
${CMAKE_CURRENT_LIST_DIR}/core/serialization/inbound.cpp
${CMAKE_CURRENT_LIST_DIR}/core/serialization/ss.cpp
${CMAKE_CURRENT_LIST_DIR}/core/serialization/ssd.cpp
${CMAKE_CURRENT_LIST_DIR}/core/serialization/vless.cpp
${CMAKE_CURRENT_LIST_DIR}/core/serialization/trojan.cpp
${CMAKE_CURRENT_LIST_DIR}/core/serialization/vmess.cpp
${CMAKE_CURRENT_LIST_DIR}/core/serialization/vmess_new.cpp
${CMAKE_CURRENT_LIST_DIR}/../common/logger/logger.cpp
)
# Mozilla sources
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_LIST_DIR}/mozilla/models/server.cpp
${CMAKE_CURRENT_LIST_DIR}/mozilla/shared/ipaddress.cpp
${CMAKE_CURRENT_LIST_DIR}/mozilla/shared/leakdetector.cpp
${CMAKE_CURRENT_LIST_DIR}/mozilla/localsocketcontroller.cpp
)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_definitions(${PROJECT} PRIVATE "MZ_DEBUG")
endif()
if(NOT IOS)
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.cpp
)
endif()
if(NOT ANDROID)
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.cpp
)
endif()
file(GLOB COMMON_FILES_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/*.h)
file(GLOB COMMON_FILES_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/*.cpp)
file(GLOB_RECURSE PAGE_LOGIC_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/pages_logic/*.h)
file(GLOB_RECURSE PAGE_LOGIC_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/pages_logic/*.cpp)
file(GLOB CONFIGURATORS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/configurators/*.h)
file(GLOB CONFIGURATORS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/configurators/*.cpp)
file(GLOB UI_MODELS_H CONFIGURE_DEPENDS
${CMAKE_CURRENT_LIST_DIR}/ui/models/*.h
${CMAKE_CURRENT_LIST_DIR}/ui/models/protocols/*.h
${CMAKE_CURRENT_LIST_DIR}/ui/models/services/*.h
)
file(GLOB UI_MODELS_CPP CONFIGURE_DEPENDS
${CMAKE_CURRENT_LIST_DIR}/ui/models/*.cpp
${CMAKE_CURRENT_LIST_DIR}/ui/models/protocols/*.cpp
${CMAKE_CURRENT_LIST_DIR}/ui/models/services/*.cpp
)
file(GLOB UI_CONTROLLERS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/controllers/*.h)
file(GLOB UI_CONTROLLERS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/controllers/*.cpp)
set(HEADERS ${HEADERS}
${COMMON_FILES_H}
${PAGE_LOGIC_H}
${CONFIGURATORS_H}
${UI_MODELS_H}
${UI_CONTROLLERS_H}
)
set(SOURCES ${SOURCES}
${COMMON_FILES_CPP}
${PAGE_LOGIC_CPP}
${CONFIGURATORS_CPP}
${UI_MODELS_CPP}
${UI_CONTROLLERS_CPP}
)
if(WIN32)
configure_file(
${CMAKE_CURRENT_LIST_DIR}/platforms/windows/amneziavpn.rc.in
${CMAKE_CURRENT_BINARY_DIR}/amneziavpn.rc
)
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/protocols/ikev2_vpn_protocol_windows.h
)
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_LIST_DIR}/protocols/ikev2_vpn_protocol_windows.cpp
)
set(RESOURCES ${RESOURCES}
${CMAKE_CURRENT_BINARY_DIR}/amneziavpn.rc
)
set(LIBS ${LIBS}
user32
rasapi32
@@ -156,7 +291,7 @@ if(WIN32)
endif()
if(APPLE)
cmake_policy(SET CMP0099 NEW)
cmake_policy(SET CMP0099 OLD)
cmake_policy(SET CMP0114 NEW)
if(NOT BUILD_OSX_APP_IDENTIFIER)
@@ -175,6 +310,7 @@ if(APPLE)
set(CMAKE_XCODE_GENERATE_SCHEME FALSE)
set(CMAKE_XCODE_ATTRIBUTE_DEVELOPMENT_TEAM ${BUILD_VPN_DEVELOPMENT_TEAM})
set(CMAKE_XCODE_ATTRIBUTE_GROUP_ID_IOS ${BUILD_IOS_GROUP_IDENTIFIER})
endif()
if(LINUX AND NOT ANDROID)
@@ -182,8 +318,33 @@ if(LINUX AND NOT ANDROID)
link_directories(${CMAKE_CURRENT_LIST_DIR}/platforms/linux)
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)
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/core/ipcclient.h
${CMAKE_CURRENT_LIST_DIR}/core/privileged_process.h
${CMAKE_CURRENT_LIST_DIR}/ui/systemtray_notificationhandler.h
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnprotocol.h
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.h
${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.h
${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.h
${CMAKE_CURRENT_LIST_DIR}/protocols/xrayprotocol.h
${CMAKE_CURRENT_LIST_DIR}/protocols/awgprotocol.h
)
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_LIST_DIR}/core/ipcclient.cpp
${CMAKE_CURRENT_LIST_DIR}/core/privileged_process.cpp
${CMAKE_CURRENT_LIST_DIR}/ui/systemtray_notificationhandler.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnprotocol.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/xrayprotocol.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/awgprotocol.cpp
)
endif()
if(ANDROID)
@@ -193,9 +354,7 @@ endif()
if(IOS)
include(cmake/ios.cmake)
include(cmake/ios-arch-fixup.cmake)
elseif(APPLE AND MACOS_NE)
include(cmake/macos_ne.cmake)
elseif(APPLE)
elseif(APPLE AND NOT IOS)
include(cmake/osxtools.cmake)
include(cmake/macos.cmake)
endif()
@@ -216,7 +375,7 @@ elseif(APPLE AND NOT IOS)
set(DEPLOY_PLATFORM_PATH "macos")
endif()
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
if(NOT IOS AND NOT ANDROID)
add_custom_command(
TARGET ${PROJECT} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E $<IF:$<CONFIG:Debug>,copy_directory,true>
@@ -231,22 +390,8 @@ if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
$<TARGET_FILE_DIR:${PROJECT}>
COMMAND_EXPAND_LISTS
)
endif()
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
add_subdirectory(tests)
endif()
list(APPEND SOURCES ${CMAKE_CURRENT_LIST_DIR}/main.cpp)
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

@@ -1,309 +0,0 @@
#include "amneziaApplication.h"
#include <QClipboard>
#include <QFontDatabase>
#include <QLocalServer>
#include <QLocalSocket>
#include <QMimeData>
#include <QQuickItem>
#include <QQuickStyle>
#include <QResource>
#include <QStandardPaths>
#include <QTextDocument>
#include <QTimer>
#include <QTranslator>
#include <QEvent>
#include <QDir>
#include <QSettings>
#include <QtQuick/QQuickWindow>
#include <QWindow>
#include "core/protocols/qmlRegisterProtocols.h"
#include "logger.h"
#include "ui/controllers/qml/pageController.h"
#include "ui/models/installedAppsModel.h"
#include "version.h"
#include "platforms/ios/QRCodeReaderBase.h"
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"))
{
setDesktopFileName(QStringLiteral(APPLICATION_NAME));
setQuitOnLastWindowClosed(false);
// Fix config file permissions
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
{
QSettings s(ORGANIZATION_NAME, APPLICATION_NAME);
s.setValue("permFixed", true);
}
QString configLoc1 = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/" + ORGANIZATION_NAME + "/"
+ APPLICATION_NAME + ".conf";
QFile::setPermissions(configLoc1, QFileDevice::ReadOwner | QFileDevice::WriteOwner);
QString configLoc2 = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/" + ORGANIZATION_NAME + "/"
+ APPLICATION_NAME + "/" + APPLICATION_NAME + ".conf";
QFile::setPermissions(configLoc2, QFileDevice::ReadOwner | QFileDevice::WriteOwner);
#endif
m_settings = new SecureQSettings(ORGANIZATION_NAME, APPLICATION_NAME, this);
m_nam = new QNetworkAccessManager(this);
}
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);
}
#endif
m_vpnConnectionThread.requestInterruption();
m_vpnConnectionThread.quit();
if (!m_vpnConnectionThread.wait(3000)) {
m_vpnConnectionThread.terminate();
m_vpnConnectionThread.wait(500);
}
if (m_engine) {
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;
const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml"));
QObject::connect(
m_engine, &QQmlApplicationEngine::objectCreated, this,
[this, url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl) {
QCoreApplication::exit(-1);
return;
}
// install filter on main window
if (auto win = qobject_cast<QQuickWindow*>(obj)) {
win->installEventFilter(this);
#ifdef Q_OS_ANDROID
QObject::connect(win, &QQuickWindow::sceneGraphError,
[](QQuickWindow::SceneGraphError, const QString &msg) {
qWarning() << "Scene graph error (suppressed):" << msg;
});
// Keep graphics context alive across hide/show cycles to avoid
// eglSwapBuffers/makeCurrent being called on a context Android has reclaimed.
win->setPersistentSceneGraph(true);
win->setPersistentGraphics(true);
#endif
win->show();
}
},
Qt::QueuedConnection);
m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance());
#ifdef MACOS_NE
m_engine->rootContext()->setContextProperty("IsMacOsNeBuild", true);
#else
m_engine->rootContext()->setContextProperty("IsMacOsNeBuild", false);
#endif
m_vpnConnection.reset(new VpnConnection(nullptr, nullptr));
m_vpnConnection->moveToThread(&m_vpnConnectionThread);
m_vpnConnectionThread.start();
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();
#ifdef Q_OS_WIN //TODO
if (m_parser.isSet(m_optAutostart))
m_coreController->pageController()->showOnStartup();
else
emit m_coreController->pageController()->raiseMainWindow();
#else
m_coreController->pageController()->showOnStartup();
#endif
// Android TextArea clipboard workaround
// Text from TextArea always has "text/html" mime-type:
// /qt/6.6.1/Src/qtdeclarative/src/quick/items/qquicktextcontrol.cpp:1865
// Next, html is created for this mime-type:
// /qt/6.6.1/Src/qtdeclarative/src/quick/items/qquicktextcontrol.cpp:1885
// And this html goes to the Androids clipboard, i.e. text from TextArea is always copied as richText:
// /qt/6.6.1/Src/qtbase/src/plugins/platforms/android/androidjniclipboard.cpp:46
// So we catch all the copies to the clipboard and clear them from "text/html"
#ifdef Q_OS_ANDROID
connect(QGuiApplication::clipboard(), &QClipboard::dataChanged, []() {
auto clipboard = QGuiApplication::clipboard();
if (clipboard->mimeData()->hasHtml()) {
clipboard->setText(clipboard->text());
}
});
#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()
{
qRegisterMetaType<ServerCredentials>("ServerCredentials");
qRegisterMetaType<DockerContainer>("DockerContainer");
using namespace amnezia::ProtocolEnumNS;
qRegisterMetaType<TransportProto>("TransportProto");
qRegisterMetaType<Proto>("Proto");
qRegisterMetaType<ServiceType>("ServiceType");
qmlRegisterType<QRCodeReader>("QRCodeReader", 1, 0, "QRCodeReader");
m_containerProps.reset(new ContainerProps());
qmlRegisterSingletonInstance("ContainerProps", 1, 0, "ContainerProps", m_containerProps.get());
m_protocolProps.reset(new ProtocolProps());
qmlRegisterSingletonInstance("ProtocolProps", 1, 0, "ProtocolProps", m_protocolProps.get());
qmlRegisterSingletonType(QUrl("qrc:/ui/qml/Filters/ContainersModelFilters.qml"), "ContainersModelFilters", 1, 0,
"ContainersModelFilters");
qmlRegisterType<InstalledAppsModel>("InstalledAppsModel", 1, 0, "InstalledAppsModel");
amnezia::declareQmlProtocolEnum();
Vpn::declareQmlVpnConnectionStateEnum();
PageLoader::declareQmlPageEnum();
}
void AmneziaApplication::loadFonts()
{
QQuickStyle::setStyle("Basic");
QFontDatabase::addApplicationFont(":/fonts/pt-root-ui_vf.ttf");
}
bool AmneziaApplication::parseCommands()
{
m_parser.setApplicationDescription(APPLICATION_NAME);
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);
m_parser.process(*this);
if (m_parser.isSet(m_optCleanup)) {
Logger::cleanUp();
QTimer::singleShot(100, this, [this] { quit(); });
exec();
return false;
}
return true;
}
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
void AmneziaApplication::startLocalServer() {
const QString serverName("AmneziaVPNInstance");
QLocalServer::removeServer(serverName);
QLocalServer *server = new QLocalServer(this);
server->listen(serverName);
QObject::connect(server, &QLocalServer::newConnection, this, [server, this]() {
if (server) {
QLocalSocket *clientConnection = server->nextPendingConnection();
clientConnection->deleteLater();
}
emit m_coreController->pageController()->raiseMainWindow(); //TODO
});
}
#endif
bool AmneziaApplication::eventFilter(QObject *watched, QEvent *event)
{
if (event->type() == QEvent::Close) {
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
quit();
#else
if (m_forceQuit) {
quit();
} else {
if (m_coreController && m_coreController->pageController()) {
m_coreController->pageController()->hideMainWindow();
}
}
#endif
return true; // eat the close
}
// call base QObject::eventFilter
return QObject::eventFilter(watched, event);
}
void AmneziaApplication::forceQuit()
{
m_forceQuit = true;
quit();
}
QQmlApplicationEngine *AmneziaApplication::qmlEngine() const
{
return m_engine;
}
QNetworkAccessManager *AmneziaApplication::networkManager()
{
return m_nam;
}
QClipboard *AmneziaApplication::getClipboard()
{
return this->clipboard();
}

View File

@@ -1,78 +0,0 @@
#ifndef AMNEZIA_APPLICATION_H
#define AMNEZIA_APPLICATION_H
#include <QCommandLineParser>
#include <QNetworkAccessManager>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QThread>
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#include <QGuiApplication>
#else
#include <QApplication>
#endif
#include <QClipboard>
#include "core/controllers/coreController.h"
#include "secureQSettings.h"
#include "vpnConnection.h"
#include "ui/models/containerProps.h"
#include "ui/models/protocolProps.h"
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#define AMNEZIA_BASE_CLASS QGuiApplication
#else
#define AMNEZIA_BASE_CLASS QApplication
#endif
class AmneziaApplication : public AMNEZIA_BASE_CLASS
{
Q_OBJECT
public:
AmneziaApplication(int &argc, char *argv[]);
virtual ~AmneziaApplication();
void init();
void registerTypes();
void loadFonts();
bool parseCommands();
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
void startLocalServer();
#endif
QQmlApplicationEngine *qmlEngine() const;
QNetworkAccessManager *networkManager();
QClipboard *getClipboard();
public slots:
void forceQuit();
private:
static bool m_forceQuit;
QQmlApplicationEngine *m_engine {};
SecureQSettings* m_settings;
QScopedPointer<CoreController> m_coreController;
QSharedPointer<ContainerProps> m_containerProps;
QSharedPointer<ProtocolProps> m_protocolProps;
QCommandLineParser m_parser;
QCommandLineOption m_optAutostart;
QCommandLineOption m_optCleanup;
QCommandLineOption m_optConnect;
QCommandLineOption m_optImport;
QSharedPointer<VpnConnection> m_vpnConnection;
QThread m_vpnConnectionThread;
QNetworkAccessManager *m_nam;
protected:
bool eventFilter(QObject *watched, QEvent *event) override;
};
#endif // AMNEZIA_APPLICATION_H

View File

@@ -0,0 +1,458 @@
#include "amnezia_application.h"
#include <QClipboard>
#include <QFontDatabase>
#include <QMimeData>
#include <QQuickItem>
#include <QQuickStyle>
#include <QResource>
#include <QStandardPaths>
#include <QTextDocument>
#include <QTimer>
#include <QTranslator>
#include "logger.h"
#include "ui/models/installedAppsModel.h"
#include "version.h"
#include "platforms/ios/QRCodeReaderBase.h"
#if defined(Q_OS_ANDROID)
#include "core/installedAppsImageProvider.h"
#include "platforms/android/android_controller.h"
#endif
#include "protocols/qml_register_protocols.h"
#if defined(Q_OS_IOS)
#include "platforms/ios/ios_controller.h"
#include <AmneziaVPN-Swift.h>
#endif
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv)
#else
AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecondary, SingleApplication::Options options, int timeout,
const QString &userData)
: SingleApplication(argc, argv, allowSecondary, options, timeout, userData)
#endif
{
setQuitOnLastWindowClosed(false);
// Fix config file permissions
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
{
QSettings s(ORGANIZATION_NAME, APPLICATION_NAME);
s.setValue("permFixed", true);
}
QString configLoc1 = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/" + ORGANIZATION_NAME + "/"
+ APPLICATION_NAME + ".conf";
QFile::setPermissions(configLoc1, QFileDevice::ReadOwner | QFileDevice::WriteOwner);
QString configLoc2 = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/" + ORGANIZATION_NAME + "/"
+ APPLICATION_NAME + "/" + APPLICATION_NAME + ".conf";
QFile::setPermissions(configLoc2, QFileDevice::ReadOwner | QFileDevice::WriteOwner);
#endif
m_settings = std::shared_ptr<Settings>(new Settings);
m_nam = new QNetworkAccessManager(this);
}
AmneziaApplication::~AmneziaApplication()
{
m_vpnConnectionThread.quit();
m_vpnConnectionThread.wait(3000);
if (m_engine) {
QObject::disconnect(m_engine, 0, 0, 0);
delete m_engine;
}
}
void AmneziaApplication::init()
{
m_engine = new QQmlApplicationEngine;
const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml"));
QObject::connect(
m_engine, &QQmlApplicationEngine::objectCreated, this,
[url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
},
Qt::QueuedConnection);
m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance());
m_vpnConnection.reset(new VpnConnection(m_settings));
m_vpnConnection->moveToThread(&m_vpnConnectionThread);
m_vpnConnectionThread.start();
initModels();
loadTranslator();
initControllers();
#ifdef Q_OS_ANDROID
if (!AndroidController::initLogging()) {
qFatal("Android logging initialization failed");
}
AndroidController::instance()->setSaveLogs(m_settings->isSaveLogs());
connect(m_settings.get(), &Settings::saveLogsChanged, AndroidController::instance(), &AndroidController::setSaveLogs);
AndroidController::instance()->setScreenshotsEnabled(m_settings->isScreenshotsEnabled());
connect(m_settings.get(), &Settings::screenshotsEnabledChanged, AndroidController::instance(), &AndroidController::setScreenshotsEnabled);
connect(m_settings.get(), &Settings::serverRemoved, AndroidController::instance(), &AndroidController::resetLastServer);
connect(m_settings.get(), &Settings::settingsCleared, []() { AndroidController::instance()->resetLastServer(-1); });
connect(AndroidController::instance(), &AndroidController::initConnectionState, this, [this](Vpn::ConnectionState state) {
m_connectionController->onConnectionStateChanged(state);
if (m_vpnConnection)
m_vpnConnection->restoreConnection();
});
if (!AndroidController::instance()->initialize()) {
qFatal("Android controller initialization failed");
}
connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, [this](QString data) {
m_pageController->goToPageHome();
m_importController->extractConfigFromData(data);
m_pageController->goToPageViewConfig();
});
m_engine->addImageProvider(QLatin1String("installedAppImage"), new InstalledAppsImageProvider);
#endif
#ifdef Q_OS_IOS
IosController::Instance()->initialize();
connect(IosController::Instance(), &IosController::importConfigFromOutside, [this](QString data) {
m_pageController->goToPageHome();
m_importController->extractConfigFromData(data);
m_pageController->goToPageViewConfig();
});
connect(IosController::Instance(), &IosController::importBackupFromOutside, [this](QString filePath) {
m_pageController->goToPageHome();
m_pageController->goToPageSettingsBackup();
m_settingsController->importBackupFromOutside(filePath);
});
QTimer::singleShot(0, this, [this]() { AmneziaVPN::toggleScreenshots(m_settings->isScreenshotsEnabled()); });
connect(m_settings.get(), &Settings::screenshotsEnabledChanged, [](bool enabled) { AmneziaVPN::toggleScreenshots(enabled); });
#endif
#ifndef Q_OS_ANDROID
m_notificationHandler.reset(NotificationHandler::create(nullptr));
connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(),
&NotificationHandler::setConnectionState);
connect(m_notificationHandler.get(), &NotificationHandler::raiseRequested, m_pageController.get(), &PageController::raiseMainWindow);
connect(m_notificationHandler.get(), &NotificationHandler::connectRequested, m_connectionController.get(),
static_cast<void (ConnectionController::*)()>(&ConnectionController::openConnection));
connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(),
&ConnectionController::closeConnection);
connect(this, &AmneziaApplication::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated);
#endif
m_engine->addImportPath("qrc:/ui/qml/Modules/");
m_engine->load(url);
m_systemController->setQmlRoot(m_engine->rootObjects().value(0));
bool enabled = m_settings->isSaveLogs();
#ifndef Q_OS_ANDROID
if (enabled) {
if (!Logger::init(false)) {
qWarning() << "Initialization of debug subsystem failed";
}
}
#endif
Logger::setServiceLogsEnabled(enabled);
#ifdef Q_OS_WIN
if (m_parser.isSet("a"))
m_pageController->showOnStartup();
else
emit m_pageController->raiseMainWindow();
#else
m_pageController->showOnStartup();
#endif
// TODO - fix
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
if (isPrimary()) {
QObject::connect(this, &SingleApplication::instanceStarted, m_pageController.get(), [this]() {
qDebug() << "Secondary instance started, showing this window instead";
emit m_pageController->raiseMainWindow();
});
}
#endif
// Android TextArea clipboard workaround
// Text from TextArea always has "text/html" mime-type:
// /qt/6.6.1/Src/qtdeclarative/src/quick/items/qquicktextcontrol.cpp:1865
// Next, html is created for this mime-type:
// /qt/6.6.1/Src/qtdeclarative/src/quick/items/qquicktextcontrol.cpp:1885
// And this html goes to the Androids clipboard, i.e. text from TextArea is always copied as richText:
// /qt/6.6.1/Src/qtbase/src/plugins/platforms/android/androidjniclipboard.cpp:46
// So we catch all the copies to the clipboard and clear them from "text/html"
#ifdef Q_OS_ANDROID
connect(QGuiApplication::clipboard(), &QClipboard::dataChanged, []() {
auto clipboard = QGuiApplication::clipboard();
if (clipboard->mimeData()->hasHtml()) {
clipboard->setText(clipboard->text());
}
});
#endif
}
void AmneziaApplication::registerTypes()
{
qRegisterMetaType<ServerCredentials>("ServerCredentials");
qRegisterMetaType<DockerContainer>("DockerContainer");
qRegisterMetaType<TransportProto>("TransportProto");
qRegisterMetaType<Proto>("Proto");
qRegisterMetaType<ServiceType>("ServiceType");
declareQmlProtocolEnum();
declareQmlContainerEnum();
qmlRegisterType<QRCodeReader>("QRCodeReader", 1, 0, "QRCodeReader");
m_containerProps.reset(new ContainerProps());
qmlRegisterSingletonInstance("ContainerProps", 1, 0, "ContainerProps", m_containerProps.get());
m_protocolProps.reset(new ProtocolProps());
qmlRegisterSingletonInstance("ProtocolProps", 1, 0, "ProtocolProps", m_protocolProps.get());
qmlRegisterSingletonType(QUrl("qrc:/ui/qml/Filters/ContainersModelFilters.qml"), "ContainersModelFilters", 1, 0,
"ContainersModelFilters");
qmlRegisterType<InstalledAppsModel>("InstalledAppsModel", 1, 0, "InstalledAppsModel");
Vpn::declareQmlVpnConnectionStateEnum();
PageLoader::declareQmlPageEnum();
}
void AmneziaApplication::loadFonts()
{
QQuickStyle::setStyle("Basic");
QFontDatabase::addApplicationFont(":/fonts/pt-root-ui_vf.ttf");
}
void AmneziaApplication::loadTranslator()
{
auto locale = m_settings->getAppLanguage();
m_translator.reset(new QTranslator());
updateTranslator(locale);
}
void AmneziaApplication::updateTranslator(const QLocale &locale)
{
if (!m_translator->isEmpty()) {
QCoreApplication::removeTranslator(m_translator.get());
}
QString strFileName = QString(":/translations/amneziavpn") + QLatin1String("_") + locale.name() + ".qm";
if (m_translator->load(strFileName)) {
if (QCoreApplication::installTranslator(m_translator.get())) {
m_settings->setAppLanguage(locale);
}
} else {
m_settings->setAppLanguage(QLocale::English);
}
m_engine->retranslate();
emit translationsUpdated();
}
bool AmneziaApplication::parseCommands()
{
m_parser.setApplicationDescription(APPLICATION_NAME);
m_parser.addHelpOption();
m_parser.addVersionOption();
QCommandLineOption c_autostart { { "a", "autostart" }, "System autostart" };
m_parser.addOption(c_autostart);
QCommandLineOption c_cleanup { { "c", "cleanup" }, "Cleanup logs" };
m_parser.addOption(c_cleanup);
m_parser.process(*this);
if (m_parser.isSet(c_cleanup)) {
Logger::cleanUp();
QTimer::singleShot(100, this, [this] { quit(); });
exec();
return false;
}
return true;
}
QQmlApplicationEngine *AmneziaApplication::qmlEngine() const
{
return m_engine;
}
void AmneziaApplication::initModels()
{
m_containersModel.reset(new ContainersModel(this));
m_engine->rootContext()->setContextProperty("ContainersModel", m_containersModel.get());
m_defaultServerContainersModel.reset(new ContainersModel(this));
m_engine->rootContext()->setContextProperty("DefaultServerContainersModel", m_defaultServerContainersModel.get());
m_serversModel.reset(new ServersModel(m_settings, this));
m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get());
connect(m_serversModel.get(), &ServersModel::containersUpdated, m_containersModel.get(), &ContainersModel::updateModel);
connect(m_serversModel.get(), &ServersModel::defaultServerContainersUpdated, m_defaultServerContainersModel.get(),
&ContainersModel::updateModel);
m_serversModel->resetModel();
m_languageModel.reset(new LanguageModel(m_settings, this));
m_engine->rootContext()->setContextProperty("LanguageModel", m_languageModel.get());
connect(m_languageModel.get(), &LanguageModel::updateTranslations, this, &AmneziaApplication::updateTranslator);
connect(this, &AmneziaApplication::translationsUpdated, m_languageModel.get(), &LanguageModel::translationsUpdated);
m_sitesModel.reset(new SitesModel(m_settings, this));
m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get());
m_appSplitTunnelingModel.reset(new AppSplitTunnelingModel(m_settings, this));
m_engine->rootContext()->setContextProperty("AppSplitTunnelingModel", m_appSplitTunnelingModel.get());
m_protocolsModel.reset(new ProtocolsModel(m_settings, this));
m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get());
m_openVpnConfigModel.reset(new OpenVpnConfigModel(this));
m_engine->rootContext()->setContextProperty("OpenVpnConfigModel", m_openVpnConfigModel.get());
m_shadowSocksConfigModel.reset(new ShadowSocksConfigModel(this));
m_engine->rootContext()->setContextProperty("ShadowSocksConfigModel", m_shadowSocksConfigModel.get());
m_cloakConfigModel.reset(new CloakConfigModel(this));
m_engine->rootContext()->setContextProperty("CloakConfigModel", m_cloakConfigModel.get());
m_wireGuardConfigModel.reset(new WireGuardConfigModel(this));
m_engine->rootContext()->setContextProperty("WireGuardConfigModel", m_wireGuardConfigModel.get());
m_awgConfigModel.reset(new AwgConfigModel(this));
m_engine->rootContext()->setContextProperty("AwgConfigModel", m_awgConfigModel.get());
m_xrayConfigModel.reset(new XrayConfigModel(this));
m_engine->rootContext()->setContextProperty("XrayConfigModel", m_xrayConfigModel.get());
#ifdef Q_OS_WINDOWS
m_ikev2ConfigModel.reset(new Ikev2ConfigModel(this));
m_engine->rootContext()->setContextProperty("Ikev2ConfigModel", m_ikev2ConfigModel.get());
#endif
m_sftpConfigModel.reset(new SftpConfigModel(this));
m_engine->rootContext()->setContextProperty("SftpConfigModel", m_sftpConfigModel.get());
m_socks5ConfigModel.reset(new Socks5ProxyConfigModel(this));
m_engine->rootContext()->setContextProperty("Socks5ProxyConfigModel", m_socks5ConfigModel.get());
m_clientManagementModel.reset(new ClientManagementModel(m_settings, this));
m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get());
connect(m_clientManagementModel.get(), &ClientManagementModel::adminConfigRevoked, m_serversModel.get(),
&ServersModel::clearCachedProfile);
m_apiServicesModel.reset(new ApiServicesModel(this));
m_engine->rootContext()->setContextProperty("ApiServicesModel", m_apiServicesModel.get());
m_apiCountryModel.reset(new ApiCountryModel(this));
m_engine->rootContext()->setContextProperty("ApiCountryModel", m_apiCountryModel.get());
connect(m_serversModel.get(), &ServersModel::updateApiLanguageModel, this, [this]() {
m_apiCountryModel->updateModel(m_serversModel->getProcessedServerData("apiAvailableCountries").toJsonArray(),
m_serversModel->getProcessedServerData("apiServerCountryCode").toString());
});
connect(m_serversModel.get(), &ServersModel::updateApiServicesModel, this,
[this]() { m_apiServicesModel->updateModel(m_serversModel->getProcessedServerData("apiConfig").toJsonObject()); });
}
void AmneziaApplication::initControllers()
{
m_connectionController.reset(
new ConnectionController(m_serversModel, m_containersModel, m_clientManagementModel, m_vpnConnection, m_settings));
m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get());
connect(m_connectionController.get(), qOverload<const QString &>(&ConnectionController::connectionErrorOccurred), this,
[this](const QString &errorMessage) {
emit m_pageController->showErrorMessage(errorMessage);
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
});
connect(m_connectionController.get(), qOverload<ErrorCode>(&ConnectionController::connectionErrorOccurred), this,
[this](ErrorCode errorCode) {
emit m_pageController->showErrorMessage(errorCode);
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
});
connect(m_connectionController.get(), &ConnectionController::connectButtonClicked, m_connectionController.get(),
&ConnectionController::toggleConnection, Qt::QueuedConnection);
m_pageController.reset(new PageController(m_serversModel, m_settings));
m_engine->rootContext()->setContextProperty("PageController", m_pageController.get());
m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_clientManagementModel,
m_apiServicesModel, m_settings));
m_engine->rootContext()->setContextProperty("InstallController", m_installController.get());
connect(m_installController.get(), &InstallController::passphraseRequestStarted, m_pageController.get(),
&PageController::showPassphraseRequestDrawer);
connect(m_pageController.get(), &PageController::passphraseRequestDrawerClosed, m_installController.get(),
&InstallController::setEncryptedPassphrase);
connect(m_installController.get(), &InstallController::currentContainerUpdated, m_connectionController.get(),
&ConnectionController::onCurrentContainerUpdated);
connect(m_installController.get(), &InstallController::updateServerFromApiFinished, this, [this]() {
disconnect(m_reloadConfigErrorOccurredConnection);
emit m_connectionController->configFromApiUpdated();
});
connect(m_connectionController.get(), &ConnectionController::updateApiConfigFromGateway, this, [this]() {
m_reloadConfigErrorOccurredConnection = connect(
m_installController.get(), qOverload<ErrorCode>(&InstallController::installationErrorOccurred), this,
[this]() { emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); },
static_cast<Qt::ConnectionType>(Qt::AutoConnection || Qt::SingleShotConnection));
m_installController->updateServiceFromApi(m_serversModel->getDefaultServerIndex(), "", "");
});
connect(m_connectionController.get(), &ConnectionController::updateApiConfigFromTelegram, this, [this]() {
m_reloadConfigErrorOccurredConnection = connect(
m_installController.get(), qOverload<ErrorCode>(&InstallController::installationErrorOccurred), this,
[this]() { emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected); },
static_cast<Qt::ConnectionType>(Qt::AutoConnection || Qt::SingleShotConnection));
m_serversModel->removeApiConfig(m_serversModel->getDefaultServerIndex());
m_installController->updateServiceFromTelegram(m_serversModel->getDefaultServerIndex());
});
connect(this, &AmneziaApplication::translationsUpdated, m_connectionController.get(), &ConnectionController::onTranslationsUpdated);
m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings));
m_engine->rootContext()->setContextProperty("ImportController", m_importController.get());
m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_clientManagementModel, m_settings));
m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get());
m_settingsController.reset(
new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, m_appSplitTunnelingModel, m_settings));
m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get());
if (m_settingsController->isAutoConnectEnabled() && m_serversModel->getDefaultServerIndex() >= 0) {
QTimer::singleShot(1000, this, [this]() { m_connectionController->openConnection(); });
}
connect(m_settingsController.get(), &SettingsController::amneziaDnsToggled, m_serversModel.get(), &ServersModel::toggleAmneziaDns);
m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel));
m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get());
m_appSplitTunnelingController.reset(new AppSplitTunnelingController(m_settings, m_appSplitTunnelingModel));
m_engine->rootContext()->setContextProperty("AppSplitTunnelingController", m_appSplitTunnelingController.get());
m_systemController.reset(new SystemController(m_settings));
m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get());
}

View File

@@ -0,0 +1,145 @@
#ifndef AMNEZIA_APPLICATION_H
#define AMNEZIA_APPLICATION_H
#include <QCommandLineParser>
#include <QNetworkAccessManager>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QThread>
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#include <QGuiApplication>
#else
#include <QApplication>
#endif
#include "settings.h"
#include "vpnconnection.h"
#include "ui/controllers/connectionController.h"
#include "ui/controllers/exportController.h"
#include "ui/controllers/importController.h"
#include "ui/controllers/installController.h"
#include "ui/controllers/pageController.h"
#include "ui/controllers/settingsController.h"
#include "ui/controllers/sitesController.h"
#include "ui/controllers/systemController.h"
#include "ui/controllers/appSplitTunnelingController.h"
#include "ui/models/containers_model.h"
#include "ui/models/languageModel.h"
#include "ui/models/protocols/cloakConfigModel.h"
#ifndef Q_OS_ANDROID
#include "ui/notificationhandler.h"
#endif
#ifdef Q_OS_WINDOWS
#include "ui/models/protocols/ikev2ConfigModel.h"
#endif
#include "ui/models/protocols/awgConfigModel.h"
#include "ui/models/protocols/openvpnConfigModel.h"
#include "ui/models/protocols/shadowsocksConfigModel.h"
#include "ui/models/protocols/wireguardConfigModel.h"
#include "ui/models/protocols/xrayConfigModel.h"
#include "ui/models/protocols_model.h"
#include "ui/models/servers_model.h"
#include "ui/models/services/sftpConfigModel.h"
#include "ui/models/services/socks5ProxyConfigModel.h"
#include "ui/models/sites_model.h"
#include "ui/models/clientManagementModel.h"
#include "ui/models/appSplitTunnelingModel.h"
#include "ui/models/apiServicesModel.h"
#include "ui/models/apiCountryModel.h"
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#define AMNEZIA_BASE_CLASS QGuiApplication
#else
#define AMNEZIA_BASE_CLASS SingleApplication
#define QAPPLICATION_CLASS QApplication
#include "singleapplication.h"
#endif
class AmneziaApplication : public AMNEZIA_BASE_CLASS
{
Q_OBJECT
public:
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
AmneziaApplication(int &argc, char *argv[]);
#else
AmneziaApplication(int &argc, char *argv[], bool allowSecondary = false,
SingleApplication::Options options = SingleApplication::User, int timeout = 1000,
const QString &userData = {});
#endif
virtual ~AmneziaApplication();
void init();
void registerTypes();
void loadFonts();
void loadTranslator();
void updateTranslator(const QLocale &locale);
bool parseCommands();
QQmlApplicationEngine *qmlEngine() const;
QNetworkAccessManager *manager() { return m_nam; }
signals:
void translationsUpdated();
private:
void initModels();
void initControllers();
QQmlApplicationEngine *m_engine {};
std::shared_ptr<Settings> m_settings;
QSharedPointer<ContainerProps> m_containerProps;
QSharedPointer<ProtocolProps> m_protocolProps;
QSharedPointer<QTranslator> m_translator;
QCommandLineParser m_parser;
QSharedPointer<ContainersModel> m_containersModel;
QSharedPointer<ContainersModel> m_defaultServerContainersModel;
QSharedPointer<ServersModel> m_serversModel;
QSharedPointer<LanguageModel> m_languageModel;
QSharedPointer<ProtocolsModel> m_protocolsModel;
QSharedPointer<SitesModel> m_sitesModel;
QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel;
QSharedPointer<ClientManagementModel> m_clientManagementModel;
QSharedPointer<ApiServicesModel> m_apiServicesModel;
QSharedPointer<ApiCountryModel> m_apiCountryModel;
QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel;
QScopedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel;
QScopedPointer<CloakConfigModel> m_cloakConfigModel;
QScopedPointer<XrayConfigModel> m_xrayConfigModel;
QScopedPointer<WireGuardConfigModel> m_wireGuardConfigModel;
QScopedPointer<AwgConfigModel> m_awgConfigModel;
#ifdef Q_OS_WINDOWS
QScopedPointer<Ikev2ConfigModel> m_ikev2ConfigModel;
#endif
QScopedPointer<SftpConfigModel> m_sftpConfigModel;
QScopedPointer<Socks5ProxyConfigModel> m_socks5ConfigModel;
QSharedPointer<VpnConnection> m_vpnConnection;
QThread m_vpnConnectionThread;
#ifndef Q_OS_ANDROID
QScopedPointer<NotificationHandler> m_notificationHandler;
#endif
QScopedPointer<ConnectionController> m_connectionController;
QScopedPointer<PageController> m_pageController;
QScopedPointer<InstallController> m_installController;
QScopedPointer<ImportController> m_importController;
QScopedPointer<ExportController> m_exportController;
QScopedPointer<SettingsController> m_settingsController;
QScopedPointer<SitesController> m_sitesController;
QScopedPointer<SystemController> m_systemController;
QScopedPointer<AppSplitTunnelingController> m_appSplitTunnelingController;
QNetworkAccessManager *m_nam;
QMetaObject::Connection m_reloadConfigErrorOccurredConnection;
};
#endif // AMNEZIA_APPLICATION_H

View File

@@ -20,7 +20,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<!-- To request network state -->
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
@@ -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>
@@ -92,13 +91,6 @@
android:exported="false"
android:theme="@style/Translucent" />
<activity android:name=".TvFilePicker"
android:excludeFromRecents="true"
android:launchMode="singleTask"
android:taskAffinity=""
android:exported="false"
android:theme="@style/Translucent" />
<activity
android:name=".ImportConfigActivity"
android:excludeFromRecents="true"
@@ -215,4 +207,4 @@
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/qtprovider_paths" />
</provider>
</application>
</manifest>
</manifest>

View File

@@ -111,6 +111,7 @@ dependencies {
implementation(project(":wireguard"))
implementation(project(":awg"))
implementation(project(":openvpn"))
implementation(project(":cloak"))
implementation(project(":xray"))
implementation(libs.androidx.core)
implementation(libs.androidx.activity)

View File

@@ -0,0 +1,18 @@
plugins {
id(libs.plugins.android.library.get().pluginId)
id(libs.plugins.kotlin.android.get().pluginId)
}
kotlin {
jvmToolchain(17)
}
android {
namespace = "org.amnezia.vpn.protocol.cloak"
}
dependencies {
compileOnly(project(":utils"))
compileOnly(project(":protocolApi"))
implementation(project(":openvpn"))
}

View File

@@ -0,0 +1,45 @@
package org.amnezia.vpn.protocol.cloak
import android.util.Base64
import net.openvpn.ovpn3.ClientAPI_Config
import org.amnezia.vpn.protocol.openvpn.OpenVpn
import org.amnezia.vpn.util.LibraryLoader.loadSharedLibrary
import org.json.JSONObject
class Cloak : OpenVpn() {
override fun internalInit() {
super.internalInit()
if (!isInitialized) loadSharedLibrary(context, "ck-ovpn-plugin")
}
override fun parseConfig(config: JSONObject): ClientAPI_Config {
val openVpnConfig = ClientAPI_Config()
val openVpnConfigStr = config.getJSONObject("openvpn_config_data").getString("config")
val cloakConfigJson = checkCloakJson(config.getJSONObject("cloak_config_data"))
val cloakConfigStr = Base64.encodeToString(cloakConfigJson.toString().toByteArray(), Base64.DEFAULT)
val configStr = "$openVpnConfigStr\n<cloak>\n$cloakConfigStr\n</cloak>\n"
openVpnConfig.usePluggableTransports = true
openVpnConfig.content = configStr
return openVpnConfig
}
private fun checkCloakJson(cloakConfigJson: JSONObject): JSONObject {
cloakConfigJson.put("NumConn", 1)
cloakConfigJson.put("ProxyMethod", "openvpn")
if (cloakConfigJson.has("port")) {
val port = cloakConfigJson["port"]
cloakConfigJson.remove("port")
cloakConfigJson.put("RemotePort", port)
}
if (cloakConfigJson.has("remote")) {
val remote = cloakConfigJson["remote"]
cloakConfigJson.remove("remote")
cloakConfigJson.put("RemoteHost", remote)
}
return cloakConfigJson
}
}

View File

@@ -33,7 +33,7 @@ android.library.defaults.buildfeatures.androidresources=false
# For development copy and set local values for these parameters in local.properties
#androidCompileSdkVersion=android-34
#androidBuildToolsVersion=34.0.0
#qtMinSdkVersion=26
#qtMinSdkVersion=24
#qtTargetSdkVersion=34
#androidNdkVersion=26.1.10909125
#qtTargetAbiList=x86_64

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

@@ -1,5 +1,6 @@
package org.amnezia.vpn.protocol
import android.annotation.SuppressLint
import android.content.Context
import android.net.IpPrefix
import android.net.VpnService
@@ -7,6 +8,9 @@ import android.net.VpnService.Builder
import android.os.Build
import android.system.OsConstants
import androidx.annotation.RequiresApi
import java.io.File
import java.io.FileOutputStream
import java.util.zip.ZipFile
import kotlinx.coroutines.flow.MutableStateFlow
import org.amnezia.vpn.util.Log
import org.amnezia.vpn.util.net.InetNetwork
@@ -42,7 +46,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

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_banner_background"/>
<foreground android:drawable="@mipmap/ic_banner_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -23,6 +23,4 @@
<string name="notificationSettingsDialogTitle">Настройки уведомлений</string>
<string name="notificationSettingsDialogMessage">Для показа уведомлений необходимо включить уведомления в системных настройках</string>
<string name="openNotificationSettings">Открыть настройки уведомлений</string>
<string name="tvNoFileBrowser">Пожалуйста, установите приложение для просмотра файлов</string>
</resources>

View File

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

View File

@@ -23,6 +23,4 @@
<string name="notificationSettingsDialogTitle">Notification settings</string>
<string name="notificationSettingsDialogMessage">To show notifications, you must enable notifications in the system settings</string>
<string name="openNotificationSettings">Open notification settings</string>
<string name="tvNoFileBrowser">Please install a file management utility to browse files</string>
</resources>

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

@@ -35,6 +35,7 @@ include(":protocolApi")
include(":wireguard")
include(":awg")
include(":openvpn")
include(":cloak")
include(":xray")
include(":xray:libXray")

View File

@@ -4,7 +4,6 @@ import android.Manifest
import android.annotation.SuppressLint
import android.app.AlertDialog
import android.app.NotificationManager
import android.content.ActivityNotFoundException
import android.content.BroadcastReceiver
import android.content.ComponentName
import android.content.Intent
@@ -13,7 +12,6 @@ import android.content.Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
import android.content.ServiceConnection
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.net.Uri
import android.net.VpnService
import android.os.Build
import android.os.Bundle
@@ -22,29 +20,15 @@ import android.os.IBinder
import android.os.Looper
import android.os.Message
import android.os.Messenger
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
import android.view.WindowManager.LayoutParams
import android.webkit.MimeTypeMap
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
import kotlin.text.RegexOption.IGNORE_CASE
import AppListProvider
import kotlinx.coroutines.CompletableDeferred
@@ -75,8 +59,6 @@ private const val OPEN_FILE_ACTION_CODE = 3
private const val CHECK_NOTIFICATION_PERMISSION_ACTION_CODE = 4
private const val PREFS_NOTIFICATION_PERMISSION_ASKED = "NOTIFICATION_PERMISSION_ASKED"
private const val OPEN_FILE_AFTER_RESUME_DELAY_MS = 400L
private const val KEY_PENDING_OPEN_FILE_URI = "pending_open_file_uri"
class AmneziaActivity : QtActivity() {
@@ -88,17 +70,10 @@ class AmneziaActivity : QtActivity() {
private var isInBoundState = false
private var notificationStateReceiver: BroadcastReceiver? = null
private lateinit var vpnServiceMessenger: IpcMessenger
private var pfd: ParcelFileDescriptor? = null
private val actionResultHandlers = mutableMapOf<Int, ActivityResultHandler>()
private val permissionRequestHandlers = mutableMapOf<Int, PermissionRequestHandler>()
private var isActivityResumed = false
private var hasWindowFocus = false
private val resumeHandler = Handler(Looper.getMainLooper())
private var pendingOpenFileUri: String? = null
private var openFileDeliveryScheduled = false
private val vpnServiceEventHandler: Handler by lazy(NONE) {
object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
@@ -183,11 +158,12 @@ class AmneziaActivity : QtActivity() {
*/
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d(TAG, "Create Amnezia activity")
Log.d(TAG, "Create Amnezia activity: $intent")
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
@@ -200,18 +176,11 @@ class AmneziaActivity : QtActivity() {
doBindService()
}
)
pendingOpenFileUri = savedInstanceState?.getString(KEY_PENDING_OPEN_FILE_URI)
openFileDeliveryScheduled = false
registerBroadcastReceivers()
intent?.let(::processIntent)
runBlocking { vpnProto = proto.await() }
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
pendingOpenFileUri?.let { outState.putString(KEY_PENDING_OPEN_FILE_URI, it) }
}
private fun loadLibs() {
listOf(
"rsapss",
@@ -231,7 +200,7 @@ class AmneziaActivity : QtActivity() {
NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED
)
) {
Log.v(
Log.d(
TAG, "Notification state changed: ${it?.action}, blocked = " +
"${it?.getBooleanExtra(NotificationManager.EXTRA_BLOCKED_STATE, false)}"
)
@@ -245,7 +214,7 @@ class AmneziaActivity : QtActivity() {
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
Log.v(TAG, "onNewIntent: $intent")
Log.d(TAG, "onNewIntent: $intent")
intent?.let(::processIntent)
}
@@ -277,11 +246,6 @@ class AmneziaActivity : QtActivity() {
}
override fun onStop() {
isActivityResumed = false
hasWindowFocus = false
// Cancel all pending operations when activity stops
resumeHandler.removeCallbacksAndMessages(null)
openFileDeliveryScheduled = false
Log.d(TAG, "Stop Amnezia activity")
doUnbindService()
mainScope.launch {
@@ -291,197 +255,7 @@ class AmneziaActivity : QtActivity() {
super.onStop()
}
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
hasWindowFocus = hasFocus
Log.d(TAG, "Window focus changed: hasFocus=$hasFocus")
if (!hasFocus) {
// Cancel pending operations if window loses focus
resumeHandler.removeCallbacksAndMessages(null)
} else if (isActivityResumed && Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
window.decorView.apply {
invalidate()
resumeHandler.postDelayed({
if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
sendTouch(1f, 1f)
}
}, 50)
resumeHandler.postDelayed({
if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
sendTouch(2f, 2f)
requestLayout()
invalidate()
}
}, 150)
}
}
}
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
val keyCode = event.keyCode
val pressed = event.action == KeyEvent.ACTION_DOWN
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 -> {
nativeGamepadKeyEvent(0, keyCode, pressed)
return true
}
KeyEvent.KEYCODE_DPAD_CENTER,
KeyEvent.KEYCODE_DPAD_UP,
KeyEvent.KEYCODE_DPAD_DOWN,
KeyEvent.KEYCODE_DPAD_LEFT,
KeyEvent.KEYCODE_DPAD_RIGHT -> {
val syntheticKeyCode = if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) KeyEvent.KEYCODE_ENTER else keyCode
val synthetic = KeyEvent(
event.downTime, event.eventTime, event.action, syntheticKeyCode,
event.repeatCount, event.metaState, -1, event.scanCode,
event.flags, InputDevice.SOURCE_KEYBOARD
)
return super.dispatchKeyEvent(synthetic)
}
}
return super.dispatchKeyEvent(event)
}
private external fun nativeGamepadKeyEvent(deviceId: Int, keyCode: Int, pressed: Boolean)
override fun onPause() {
// Notify Qt to stop rendering BEFORE super.onPause() destroys the EGL surface.
// Using a coroutine here would be too late — the surface is gone by the time
// the coroutine runs. A direct synchronous call gives Qt's render thread the
// best chance to process visible=false before surface destruction.
if (qtInitialized.isCompleted) {
QtAndroidController.onActivityPaused()
}
super.onPause()
isActivityResumed = false
// Cancel all pending operations when activity pauses
resumeHandler.removeCallbacksAndMessages(null)
openFileDeliveryScheduled = false
Log.d(TAG, "Pause Amnezia activity")
}
override fun onResume() {
super.onResume()
isActivityResumed = true
Log.d(TAG, "Resume Amnezia activity")
if (qtInitialized.isCompleted) {
QtAndroidController.onActivityResumed()
}
if (pendingOpenFileUri != null && !openFileDeliveryScheduled) {
val uri = pendingOpenFileUri!!
openFileDeliveryScheduled = true
resumeHandler.postDelayed({
if (!isFinishing && !isDestroyed) {
pendingOpenFileUri = null
openFileDeliveryScheduled = false
mainScope.launch {
qtInitialized.await()
QtAndroidController.onFileOpened(uri)
}
}
}, OPEN_FILE_AFTER_RESUME_DELAY_MS)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
window.decorView.apply {
invalidate()
resumeHandler.postDelayed({
// Check if activity is still resumed and has focus before executing
if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
sendTouch(1f, 1f)
}
}, 100)
resumeHandler.postDelayed({
if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
sendTouch(2f, 2f)
}
}, 200)
resumeHandler.postDelayed({
if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
requestLayout()
invalidate()
}
}, 250)
}
}
}
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() {
isActivityResumed = false
hasWindowFocus = false
// Cancel all pending operations when activity is destroyed
resumeHandler.removeCallbacksAndMessages(null)
Log.d(TAG, "Destroy Amnezia activity")
unregisterBroadcastReceiver(notificationStateReceiver)
notificationStateReceiver = null
@@ -629,7 +403,7 @@ class AmneziaActivity : QtActivity() {
@MainThread
private fun startVpn(vpnConfig: String) {
getVpnProto(vpnConfig)?.let { proto ->
Log.v(TAG, "Proto from config: $proto, current proto: $vpnProto")
Log.d(TAG, "Proto from config: $proto, current proto: $vpnProto")
if (isServiceConnected) {
if (proto.serviceClass == vpnProto?.serviceClass) {
vpnProto = proto
@@ -739,25 +513,21 @@ class AmneziaActivity : QtActivity() {
type = "text/*"
putExtra(Intent.EXTRA_TITLE, fileName)
}.also {
try {
startActivityForResult(it, CREATE_FILE_ACTION_CODE, ActivityResultHandler(
onSuccess = {
it?.data?.let { uri ->
Log.v(TAG, "Save file to $uri")
try {
contentResolver.openOutputStream(uri)?.use { os ->
os.bufferedWriter().use { it.write(data) }
}
} catch (e: IOException) {
Log.e(TAG, "Failed to save file $uri: $e")
// todo: send error to Qt
startActivityForResult(it, CREATE_FILE_ACTION_CODE, ActivityResultHandler(
onSuccess = {
it?.data?.let { uri ->
Log.d(TAG, "Save file to $uri")
try {
contentResolver.openOutputStream(uri)?.use { os ->
os.bufferedWriter().use { it.write(data) }
}
} catch (e: IOException) {
Log.e(TAG, "Failed to save file $uri: $e")
// todo: send error to Qt
}
}
))
} catch (_: ActivityNotFoundException) {
Toast.makeText(this@AmneziaActivity, "Unsupported", Toast.LENGTH_LONG).show()
}
}
))
}
}
}
@@ -766,119 +536,46 @@ class AmneziaActivity : QtActivity() {
fun openFile(filter: String?) {
Log.v(TAG, "Open file with filter: $filter")
mainScope.launch {
val intent = if (!isOnTv()) {
val mimeTypes = if (!filter.isNullOrEmpty()) {
val extensionRegex = "\\*\\.([a-z0-9]+)".toRegex(IGNORE_CASE)
val mime = MimeTypeMap.getSingleton()
extensionRegex.findAll(filter).map {
it.groups[1]?.value?.let { mime.getMimeTypeFromExtension(it) } ?: "*/*"
}.toSet()
} else emptySet()
val mimeTypes = if (!filter.isNullOrEmpty()) {
val extensionRegex = "\\*\\.([a-z0-9]+)".toRegex(IGNORE_CASE)
val mime = MimeTypeMap.getSingleton()
extensionRegex.findAll(filter).map {
it.groups[1]?.value?.let { mime.getMimeTypeFromExtension(it) } ?: "*/*"
}.toSet()
} else emptySet()
Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
Log.v(TAG, "File mimyType filter: $mimeTypes")
if ("*/*" in mimeTypes) {
type = "*/*"
} else {
when (mimeTypes.size) {
1 -> type = mimeTypes.first()
Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
Log.v(TAG, "File mimyType filter: $mimeTypes")
if ("*/*" in mimeTypes) {
type = "*/*"
} else {
when (mimeTypes.size) {
1 -> type = mimeTypes.first()
in 2..Int.MAX_VALUE -> {
type = "*/*"
putExtra(EXTRA_MIME_TYPES, mimeTypes.toTypedArray())
}
else -> type = "*/*"
in 2..Int.MAX_VALUE -> {
type = "*/*"
putExtra(EXTRA_MIME_TYPES, mimeTypes.toTypedArray())
}
else -> type = "*/*"
}
}
} else {
Intent(this@AmneziaActivity, TvFilePicker::class.java)
}
try {
startActivityForResult(intent, OPEN_FILE_ACTION_CODE, ActivityResultHandler(
}.also {
startActivityForResult(it, OPEN_FILE_ACTION_CODE, ActivityResultHandler(
onAny = {
if (isOnTv() && it?.hasExtra("activityNotFound") == true) {
showNoFileBrowserAlertDialog()
}
val uri = it?.data?.apply {
grantUriPermission(packageName, this, Intent.FLAG_GRANT_READ_URI_PERMISSION)
}?.toString() ?: ""
Log.v(TAG, "Open file: $uri")
if (uri.isNotEmpty()) {
pendingOpenFileUri = uri
} else {
mainScope.launch {
qtInitialized.await()
QtAndroidController.onFileOpened(uri)
}
val uri = it?.data?.toString() ?: ""
Log.d(TAG, "Open file: $uri")
mainScope.launch {
qtInitialized.await()
QtAndroidController.onFileOpened(uri)
}
}
))
} catch (_: ActivityNotFoundException) {
showNoFileBrowserAlertDialog()
mainScope.launch {
qtInitialized.await()
QtAndroidController.onFileOpened("")
}
}
}
}
private fun showNoFileBrowserAlertDialog() {
AlertDialog.Builder(this)
.setMessage(R.string.tvNoFileBrowser)
.setCancelable(false)
.setPositiveButton(android.R.string.ok) { _, _ ->
try {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("market://webstoreredirect")))
} catch (_: Throwable) {}
}
.show()
}
@Suppress("unused")
fun getFd(fileName: String): Int {
Log.v(TAG, "Get fd for $fileName")
return blockingCall(Dispatchers.IO) {
try {
pfd = contentResolver.openFileDescriptor(Uri.parse(fileName), "r")
pfd?.fd ?: -1
} catch (e: Exception) {
Log.e(TAG, "Failed to get fd: $e")
-1
}
}
}
@Suppress("unused")
fun closeFd() {
Log.v(TAG, "Close fd")
mainScope.launch {
pfd?.close()
pfd = null
}
}
@Suppress("unused")
fun getFileName(uri: String): String {
Log.v(TAG, "Get file name for uri: $uri")
return blockingCall {
try {
contentResolver.query(Uri.parse(uri), arrayOf(OpenableColumns.DISPLAY_NAME), null, null, null)?.use { cursor ->
if (cursor.moveToFirst() && !cursor.isNull(0)) {
return@blockingCall cursor.getString(0) ?: ""
}
}
} catch (e: Exception) {
Log.e(TAG, "Failed to get file name: $e")
}
""
}
}
@Suppress("unused")
@SuppressLint("UnsupportedChromeOsCameraSystemFeature")
fun isCameraPresent(): Boolean = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)
@@ -886,43 +583,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")
@@ -1060,121 +720,9 @@ class AmneziaActivity : QtActivity() {
}
}
// method to workaround Qt's problem with calling the keyboard on TVs
@Suppress("unused")
fun sendTouch(x: Float, y: Float) {
Log.v(TAG, "Send touch: $x, $y")
blockingCall {
findQtWindow(window.decorView)?.let {
Log.v(TAG, "Send touch to $it")
it.dispatchTouchEvent(createEvent(x, y, SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN))
it.dispatchTouchEvent(createEvent(x, y, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP))
}
}
}
private fun findQtWindow(view: View): View? {
Log.v(TAG, "findQtWindow: process $view")
if (view::class.simpleName == "QtWindow") return view
else if (view is ViewGroup) {
for (i in 0 until view.childCount) {
val result = findQtWindow(view.getChildAt(i))
if (result != null) return result
}
return null
} else return null
}
private fun createEvent(x: Float, y: Float, eventTime: Long, action: Int): MotionEvent =
MotionEvent.obtain(
eventTime,
eventTime,
action,
1,
arrayOf(MotionEvent.PointerProperties().apply {
id = 0
toolType = MotionEvent.TOOL_TYPE_FINGER
}),
arrayOf(MotionEvent.PointerCoords().apply {
this.x = x
this.y = y
pressure = 1f
size = 1f
}),
0, 0, 1.0f, 1.0f, 0, 0, 0,0
)
// workaround for a bug in Qt that causes the mouse click event not to be handled
// also disable right-click, as it causes the application to crash
private var lastButtonState = 0
private fun MotionEvent.fixCopy(): MotionEvent = MotionEvent.obtain(
downTime,
eventTime,
action,
pointerCount,
(0 until pointerCount).map { i ->
MotionEvent.PointerProperties().apply {
getPointerProperties(i, this)
}
}.toTypedArray(),
(0 until pointerCount).map { i ->
MotionEvent.PointerCoords().apply {
getPointerCoords(i, this)
}
}.toTypedArray(),
metaState,
MotionEvent.BUTTON_PRIMARY,
xPrecision,
yPrecision,
deviceId,
edgeFlags,
source,
flags
)
private fun handleMouseEvent(ev: MotionEvent, superDispatch: (MotionEvent?) -> Boolean): Boolean {
when (ev.action) {
MotionEvent.ACTION_DOWN -> {
lastButtonState = ev.buttonState
if (ev.buttonState == MotionEvent.BUTTON_SECONDARY) return true
}
MotionEvent.ACTION_UP -> {
when (lastButtonState) {
MotionEvent.BUTTON_SECONDARY -> return true
MotionEvent.BUTTON_PRIMARY -> {
val modEvent = ev.fixCopy()
return superDispatch(modEvent).apply { modEvent.recycle() }
}
}
}
}
return superDispatch(ev)
}
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
Log.v(TAG, "dispatchTouch: $ev")
if (ev != null && ev.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE) {
return handleMouseEvent(ev) { super.dispatchTouchEvent(it) }
}
return super.dispatchTouchEvent(ev)
}
override fun dispatchTrackballEvent(ev: MotionEvent?): Boolean {
ev?.let { return handleMouseEvent(ev) { super.dispatchTrackballEvent(it) }}
return super.dispatchTrackballEvent(ev)
}
/**
* Utils methods
*/
private fun <T> blockingCall(
context: CoroutineContext = Dispatchers.Main.immediate,
block: suspend () -> T
) = runBlocking {
mainScope.async(context) { block() }.await()
}
companion object {
private fun actionCodeToString(actionCode: Int): String =
when (actionCode) {

View File

@@ -22,7 +22,6 @@ import androidx.annotation.MainThread
import androidx.core.app.ServiceCompat
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import java.net.UnknownHostException
import java.util.concurrent.ConcurrentHashMap
import kotlin.LazyThreadSafetyMode.NONE
import kotlinx.coroutines.CoroutineExceptionHandler
@@ -128,8 +127,6 @@ open class AmneziaVpnService : VpnService() {
is LoadLibraryException -> onError("${e.message}. Caused: ${e.cause?.message}")
is UnknownHostException -> onError("Unknown host")
else -> throw e
}
}
@@ -300,7 +297,7 @@ open class AmneziaVpnService : VpnService() {
arrayOf(ACTION_CONNECT, ACTION_DISCONNECT), ContextCompat.RECEIVER_NOT_EXPORTED
) {
it?.action?.let { action ->
Log.v(TAG, "Broadcast request received: $action")
Log.d(TAG, "Broadcast request received: $action")
when (action) {
ACTION_CONNECT -> connect()
ACTION_DISCONNECT -> disconnect()
@@ -317,7 +314,7 @@ open class AmneziaVpnService : VpnService() {
)
) {
val state = it?.getBooleanExtra(NotificationManager.EXTRA_BLOCKED_STATE, false)
Log.v(TAG, "Notification state changed: ${it?.action}, blocked = $state")
Log.d(TAG, "Notification state changed: ${it?.action}, blocked = $state")
if (state == false) {
enableNotification()
} else {
@@ -450,7 +447,7 @@ open class AmneziaVpnService : VpnService() {
serviceNotification.isNotificationEnabled() &&
getSystemService<PowerManager>()?.isInteractive != false
) {
Log.v(TAG, "Launch traffic stats update")
Log.d(TAG, "Launch traffic stats update")
trafficStats.reset()
startTrafficStatsUpdateJob()
}
@@ -565,7 +562,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

@@ -66,7 +66,7 @@ class AuthActivity : FragmentActivity() {
object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationSucceeded(result: AuthenticationResult) {
super.onAuthenticationSucceeded(result)
Log.v(TAG, "Authentication succeeded")
Log.d(TAG, "Authentication succeeded")
QtAndroidController.onAuthResult(true)
finish()
}

View File

@@ -29,20 +29,20 @@ class ImportConfigActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.v(TAG, "Create Import Config Activity: $intent")
Log.d(TAG, "Create Import Config Activity: $intent")
intent?.let(::readConfig)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
Log.v(TAG, "onNewIntent: $intent")
Log.d(TAG, "onNewIntent: $intent")
intent.let(::readConfig)
}
private fun readConfig(intent: Intent) {
when (intent.action) {
ACTION_SEND -> {
Log.v(TAG, "Process SEND action, type: ${intent.type}")
Log.d(TAG, "Process SEND action, type: ${intent.type}")
when (intent.type) {
"application/octet-stream" -> {
intent.getUriCompat()?.let { uri ->
@@ -60,7 +60,7 @@ class ImportConfigActivity : ComponentActivity() {
}
ACTION_VIEW -> {
Log.v(TAG, "Process VIEW action, scheme: ${intent.scheme}")
Log.d(TAG, "Process VIEW action, scheme: ${intent.scheme}")
when (intent.scheme) {
"file", "content" -> {
intent.data?.let { uri ->

View File

@@ -62,7 +62,7 @@ class ServiceNotification(private val context: Context) {
fun buildNotification(serverName: String?, protocol: String?, state: ProtocolState): Notification {
val speedString = if (state == CONNECTED) zeroSpeed else null
Log.v(TAG, "Build notification: $serverName, $state")
Log.d(TAG, "Build notification: $serverName, $state")
return notificationBuilder
.setSmallIcon(R.drawable.ic_amnezia_round)
@@ -88,15 +88,17 @@ class ServiceNotification(private val context: Context) {
fun isNotificationEnabled(): Boolean {
if (!context.isNotificationPermissionGranted()) return false
if (!notificationManager.areNotificationsEnabled()) return false
return notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID)?.let {
it.importance != NotificationManager.IMPORTANCE_NONE
} ?: true
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
return notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID)
?.let { it.importance != NotificationManager.IMPORTANCE_NONE } ?: true
}
return true
}
@SuppressLint("MissingPermission")
fun updateNotification(serverName: String?, protocol: String?, state: ProtocolState) {
if (context.isNotificationPermissionGranted()) {
Log.v(TAG, "Update notification: $serverName, $state")
Log.d(TAG, "Update notification: $serverName, $state")
notificationManager.notify(NOTIFICATION_ID, buildNotification(serverName, protocol, state))
}
}

View File

@@ -1,69 +0,0 @@
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
import org.amnezia.vpn.util.Log
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
}
}) {
setResult(RESULT_OK, Intent().apply {
data = it
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
})
finish()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.v(TAG, "onCreate")
getFile()
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
Log.v(TAG, "onNewIntent")
getFile()
}
private fun getFile() {
try {
Log.v(TAG, "getFile")
fileChooseResultLauncher.launch(arrayOf("*/*"))
} catch (_: ActivityNotFoundException) {
Log.w(TAG, "Activity not found")
setResult(RESULT_CANCELED, Intent().apply { putExtra("activityNotFound", true) })
finish()
} catch (e: Exception) {
Log.e(TAG, "Failed to get file: $e")
setResult(RESULT_CANCELED)
finish()
}
}
}

View File

@@ -2,6 +2,7 @@ package org.amnezia.vpn
import org.amnezia.vpn.protocol.Protocol
import org.amnezia.vpn.protocol.awg.Awg
import org.amnezia.vpn.protocol.cloak.Cloak
import org.amnezia.vpn.protocol.openvpn.OpenVpn
import org.amnezia.vpn.protocol.wireguard.Wireguard
import org.amnezia.vpn.protocol.xray.Xray
@@ -35,6 +36,14 @@ enum class VpnProto(
override fun createProtocol(): Protocol = OpenVpn()
},
CLOAK(
"Cloak",
"org.amnezia.vpn:amneziaOpenVpnService",
OpenVpnService::class.java
) {
override fun createProtocol(): Protocol = Cloak()
},
XRAY(
"XRay",
"org.amnezia.vpn:amneziaXrayService",
@@ -63,4 +72,4 @@ enum class VpnProto(
companion object {
fun get(protocolName: String): VpnProto = VpnProto.valueOf(protocolName.uppercase())
}
}
}

View File

@@ -28,10 +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)
external fun onActivityPaused()
external fun onActivityResumed()
}

View File

@@ -46,7 +46,7 @@ object LibraryLoader {
System.loadLibrary(libraryName)
return
} catch (_: UnsatisfiedLinkError) {
Log.w(TAG, "Failed to load library, try to extract it from apk")
Log.d(TAG, "Failed to load library, try to extract it from apk")
}
var tempFile: File? = null
try {

View File

@@ -1,6 +1,8 @@
package org.amnezia.vpn.util
import android.content.Context
import android.icu.text.DateFormat
import android.icu.text.SimpleDateFormat
import android.os.Build
import android.os.Process
import java.io.File
@@ -10,8 +12,8 @@ import java.nio.channels.FileChannel
import java.nio.channels.FileLock
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.time.ZonedDateTime
import java.time.ZoneOffset
import java.util.Date
import java.util.Locale
import java.util.concurrent.locks.ReentrantLock
import org.amnezia.vpn.util.Log.Priority.D
import org.amnezia.vpn.util.Log.Priority.E
@@ -39,7 +41,11 @@ private const val LOG_MAX_FILE_SIZE = 1024 * 1024
* | | | create a report and/or terminate the process |
*/
object Log {
private val dateTimeFormat: DateTimeFormatter = DateTimeFormatter.ofPattern(DATE_TIME_PATTERN)
private val dateTimeFormat: Any =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) DateTimeFormatter.ofPattern(DATE_TIME_PATTERN)
else object : ThreadLocal<DateFormat>() {
override fun initialValue(): DateFormat = SimpleDateFormat(DATE_TIME_PATTERN, Locale.US)
}
private lateinit var logDir: File
private val logFile: File by lazy { File(logDir, LOG_FILE_NAME) }
@@ -137,8 +143,13 @@ object Log {
}
private fun formatLogMsg(tag: String, msg: String, priority: Priority): String {
val utcDate = ZonedDateTime.now(ZoneOffset.UTC).format(dateTimeFormat)
return "${utcDate}Z ${Process.myPid()} ${Process.myTid()} $priority [${Thread.currentThread().name}] " +
val date = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
LocalDateTime.now().format(dateTimeFormat as DateTimeFormatter)
} else {
@Suppress("UNCHECKED_CAST")
(dateTimeFormat as ThreadLocal<DateFormat>).get()?.format(Date())
}
return "$date ${Process.myPid()} ${Process.myTid()} $priority [${Thread.currentThread().name}] " +
"$tag: $msg\n"
}

View File

@@ -42,12 +42,18 @@ class NetworkState(
private val networkCallback: NetworkCallback by lazy(NONE) {
object : NetworkCallback() {
override fun onAvailable(network: Network) {
Log.v(TAG, "onAvailable: $network")
Log.d(TAG, "onAvailable: $network")
}
override fun onCapabilitiesChanged(network: Network, networkCapabilities: NetworkCapabilities) {
Log.v(TAG, "onCapabilitiesChanged: $network, $networkCapabilities")
checkNetworkState(network, networkCapabilities)
Log.d(TAG, "onCapabilitiesChanged: $network, $networkCapabilities")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
checkNetworkState(network, networkCapabilities)
} else {
handler.post {
checkNetworkState(network, networkCapabilities)
}
}
}
private fun checkNetworkState(network: Network, networkCapabilities: NetworkCapabilities) {
@@ -67,11 +73,11 @@ class NetworkState(
}
override fun onBlockedStatusChanged(network: Network, blocked: Boolean) {
Log.v(TAG, "onBlockedStatusChanged: $network, $blocked")
Log.d(TAG, "onBlockedStatusChanged: $network, $blocked")
}
override fun onLost(network: Network) {
Log.v(TAG, "onLost: $network")
Log.d(TAG, "onLost: $network")
}
}
}
@@ -81,7 +87,7 @@ class NetworkState(
Log.d(TAG, "Bind network listener")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
connectivityManager.registerBestMatchingNetworkCallback(networkRequest, networkCallback, handler)
} else {
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val numberAttempts = 300
var attemptCount = 0
while(true) {
@@ -102,6 +108,8 @@ class NetworkState(
}
}
}
} else {
connectivityManager.requestNetwork(networkRequest, networkCallback)
}
isListenerBound = true
}

View File

@@ -1,18 +1,16 @@
package org.amnezia.vpn.protocol.wireguard
import android.net.VpnService.Builder
import kotlinx.coroutines.CoroutineScope
import java.io.IOException
import java.util.Locale
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.amnezia.awg.GoBackend
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,10 +26,7 @@ private const val TAG = "Wireguard"
open class Wireguard : Protocol() {
private var tunnelHandle: Int = -1
private var config: WireguardConfig? = null // save config for reconnect
protected open val ifName: String = "amn0"
private lateinit var scope: CoroutineScope
private var statusJob: Job? = null
override val statistics: Statistics
get() {
@@ -54,18 +49,46 @@ open class Wireguard : Protocol() {
override fun internalInit() {
if (!isInitialized) loadSharedLibrary(context, "wg-go")
if (this::scope.isInitialized) {
scope.cancel()
}
scope = CoroutineScope(Dispatchers.IO)
}
override suspend fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) {
val wireguardConfig = parseConfig(config)
val startTime = System.currentTimeMillis()
start(wireguardConfig, vpnBuilder, protect)
this.config = wireguardConfig
waitForConnection(startTime)
state.value = CONNECTED
}
private suspend fun waitForConnection(startTime: Long) {
Log.d(TAG, "Waiting for connection")
withContext(Dispatchers.IO) {
val time = String.format(Locale.ROOT,"%.3f", startTime / 1000.0)
try {
delay(1000)
var log = getLogcat(time)
Log.d(TAG, "First waiting log: $log")
// check that there is a connection log,
// to avoid infinite connection
if (!log.contains("Attaching to interface")) {
Log.w(TAG, "Logs do not contain a connection log")
return@withContext
}
while (!log.contains("Received handshake response")) {
delay(1000)
log = getLogcat(time)
}
} catch (e: IOException) {
Log.e(TAG, "Failed to get logcat: $e")
}
}
}
private fun getLogcat(time: String): String =
ProcessBuilder("logcat", "--buffer=main", "--format=raw", "*:S AmneziaWG/awg0", "-t", time)
.redirectErrorStream(true)
.start()
.inputStream.reader().readText()
protected open fun parseConfig(config: JSONObject): WireguardConfig {
val configData = config.getJSONObject("wireguard_config_data")
return WireguardConfig.build {
@@ -123,26 +146,14 @@ open class Wireguard : Protocol() {
configData.optStringOrNull("Jmax")?.let { setJmax(it.toInt()) }
configData.optStringOrNull("S1")?.let { setS1(it.toInt()) }
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("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("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()) }
}
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 +161,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")
}
@@ -170,51 +178,6 @@ open class Wireguard : Protocol() {
tunnelHandle = -1
throw VpnStartException("Protect VPN interface: permission not granted or revoked")
}
launchStatusJob()
}
private fun launchStatusJob() {
Log.d(TAG, "Launch status job")
statusJob = scope.launch {
while (true) {
val lastHandshake = getLastHandshake()
Log.v(TAG, "lastHandshake=$lastHandshake")
if (lastHandshake == 0L) {
delay(1000)
continue
}
if (lastHandshake == -2L || lastHandshake > 0L) state.value = CONNECTED
else if (lastHandshake == -1L) state.value = DISCONNECTED
statusJob = null
break
}
}
}
private fun getLastHandshake(): Long {
if (tunnelHandle == -1) {
Log.e(TAG, "Trying to get config of a non-existent tunnel")
return -1
}
val config = GoBackend.awgGetConfig(tunnelHandle)
if (config == null) {
Log.e(TAG, "Failed to get tunnel config")
return -2
}
val lastHandshake = config.lines().find { it.startsWith("last_handshake_time_sec=") }?.substring(24)?.toLong()
if (lastHandshake == null) {
Log.e(TAG, "Failed to get last_handshake_time_sec")
return -2
}
return lastHandshake
}
private fun turnOffVpn() {
statusJob?.cancel()
statusJob = null
val handleToClose = tunnelHandle
tunnelHandle = -1
GoBackend.awgTurnOff(handleToClose)
}
override fun stopVpn() {
@@ -222,12 +185,13 @@ open class Wireguard : Protocol() {
Log.w(TAG, "Tunnel already down")
return
}
turnOffVpn()
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

@@ -20,17 +20,10 @@ open class WireguardConfig protected constructor(
val jmax: Int?,
val s1: Int?,
val s2: Int?,
val s3: Int?,
val s4: Int?,
val h1: String?,
val h2: String?,
val h3: String?,
val h4: String?,
var i1: String?,
var i2: String?,
var i3: String?,
var i4: String?,
var i5: String?,
val h1: Long?,
val h2: Long?,
val h3: Long?,
val h4: Long?
) : ProtocolConfig(protocolConfigBuilder) {
protected constructor(builder: Builder) : this(
@@ -46,17 +39,10 @@ open class WireguardConfig protected constructor(
builder.jmax,
builder.s1,
builder.s2,
builder.s3,
builder.s4,
builder.h1,
builder.h2,
builder.h3,
builder.h4,
builder.i1,
builder.i2,
builder.i3,
builder.i4,
builder.i5,
builder.h4
)
fun toWgUserspaceString(): String = with(StringBuilder()) {
@@ -75,17 +61,10 @@ open class WireguardConfig protected constructor(
appendLine("jmax=$jmax")
appendLine("s1=$s1")
appendLine("s2=$s2")
s3?.let { appendLine("s3=$it") }
s4?.let { appendLine("s4=$it") }
appendLine("h1=$h1")
appendLine("h2=$h2")
appendLine("h3=$h3")
appendLine("h4=$h4")
i1?.let { appendLine("i1=$it") }
i2?.let { appendLine("i2=$it") }
i3?.let { appendLine("i3=$it") }
i4?.let { appendLine("i4=$it") }
i5?.let { appendLine("i5=$it") }
}
}
@@ -138,17 +117,10 @@ open class WireguardConfig protected constructor(
internal var jmax: Int? = null
internal var s1: Int? = null
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 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 h1: Long? = null
internal var h2: Long? = null
internal var h3: Long? = null
internal var h4: Long? = null
fun setEndpoint(endpoint: InetEndpoint) = apply { this.endpoint = endpoint }
@@ -167,17 +139,10 @@ open class WireguardConfig protected constructor(
fun setJmax(jmax: Int) = apply { this.jmax = jmax }
fun setS1(s1: Int) = apply { this.s1 = s1 }
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 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 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 }
override fun build(): WireguardConfig = configBuild().run { WireguardConfig(this@Builder) }
}

View File

@@ -4,9 +4,6 @@ import android.content.Context
import android.net.VpnService.Builder
import java.io.File
import java.io.IOException
import java.net.InetAddress
import java.net.ServerSocket
import java.util.UUID
import go.Seq
import org.amnezia.vpn.protocol.BadConfigException
import org.amnezia.vpn.protocol.Protocol
@@ -22,32 +19,11 @@ import org.amnezia.vpn.util.Log
import org.amnezia.vpn.util.net.InetNetwork
import org.amnezia.vpn.util.net.ip
import org.amnezia.vpn.util.net.parseInetAddress
import org.json.JSONArray
import org.json.JSONObject
private const val TAG = "Xray"
private const val LIBXRAY_TAG = "libXray"
private fun findSocksInboundIndex(inbounds: JSONArray): Int {
for (i in 0 until inbounds.length()) {
val o = inbounds.optJSONObject(i) ?: continue
if (o.optString("protocol").equals("socks", ignoreCase = true)) {
return i
}
}
return -1
}
private fun acquireFreeLocalPort(): Int {
try {
ServerSocket(0, 1, InetAddress.getByName("127.0.0.1")).use { return it.localPort }
} catch (e: Exception) {
throw VpnStartException(
"Failed to acquire free TCP port on 127.0.0.1 for SOCKS inbound: ${e.message}"
)
}
}
class Xray : Protocol() {
private var isRunning: Boolean = false
@@ -77,13 +53,9 @@ class Xray : Protocol() {
return
}
val xrayConfigData = config.optJSONObject("xray_config_data")
val xrayJsonConfig = config.optJSONObject("xray_config_data")
?: config.optJSONObject("ssxray_config_data")
?: throw BadConfigException("config_data not found")
val xrayJsonConfig = JSONObject(xrayConfigData.optString("config"))
// Inject SOCKS5 auth before starting xray. Re-uses existing credentials if present.
ensureInboundAuth(xrayJsonConfig)
val xrayConfig = parseConfig(config, xrayJsonConfig)
(xrayJsonConfig.optJSONObject("log") ?: JSONObject().also { xrayJsonConfig.put("log", it) })
@@ -125,22 +97,9 @@ class Xray : Protocol() {
if (it.isNotBlank()) setMtu(it.toInt())
}
val inbounds = xrayJsonConfig.getJSONArray("inbounds")
val socksIdx = findSocksInboundIndex(inbounds)
if (socksIdx < 0) {
throw BadConfigException("socks inbound not found")
}
val socksConfig = inbounds.getJSONObject(socksIdx)
val socksConfig = xrayJsonConfig.getJSONArray("inbounds")[0] as JSONObject
socksConfig.getInt("port").let { setSocksPort(it) }
val socksSettings = socksConfig.optJSONObject("settings")
val accounts = socksSettings?.optJSONArray("accounts")
if (accounts != null && accounts.length() > 0) {
val account = accounts.getJSONObject(0)
setSocksUser(account.optString("user"))
setSocksPass(account.optString("pass"))
}
configSplitTunneling(config)
configAppSplitTunneling(config)
}
@@ -171,8 +130,8 @@ class Xray : Protocol() {
LibXray.initXray(assetsPath)
val geoDir = File(assetsPath, "geo").absolutePath
val configPath = File(context.cacheDir, "config.json")
Log.v(TAG, "xray.location.asset: $geoDir")
Log.v(TAG, "config: $configPath")
Log.d(TAG, "xray.location.asset: $geoDir")
Log.d(TAG, "config: $configPath")
try {
configPath.writeText(configJson)
} catch (e: IOException) {
@@ -198,54 +157,22 @@ class Xray : Protocol() {
state.value = DISCONNECTED
}
override fun reconnectVpn(vpnBuilder: Builder, protect: (Int) -> Boolean) {
override fun reconnectVpn(vpnBuilder: Builder) {
state.value = CONNECTED
}
private fun runTun2Socks(config: XrayConfig, fd: Int) {
val proxyUrl = "socks5://${config.socksUser}:${config.socksPass}@127.0.0.1:${config.socksPort}"
val tun2SocksConfig = Tun2SocksConfig().apply {
mtu = config.mtu.toLong()
proxy = proxyUrl
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")
}
}
// Ensures SOCKS5 auth is present on the socks inbound settings.
// Re-uses existing credentials if already configured; otherwise generates random ones.
private fun ensureInboundAuth(xrayConfig: JSONObject) {
val inbounds = xrayConfig.optJSONArray("inbounds") ?: return
val socksIdx = findSocksInboundIndex(inbounds)
if (socksIdx < 0) return
val inbound = inbounds.getJSONObject(socksIdx)
inbound.put("port", acquireFreeLocalPort())
val settings = inbound.optJSONObject("settings") ?: JSONObject().also { inbound.put("settings", it) }
val accounts = settings.optJSONArray("accounts")
if (accounts != null && accounts.length() > 0) {
val account = accounts.getJSONObject(0)
if (account.optString("user").isNotEmpty() && account.optString("pass").isNotEmpty()) {
// Ensure auth mode is enforced even for imported configs that had accounts
// but auth: "noauth" (or no auth field).
settings.put("auth", "password")
inbound.put("settings", settings)
inbounds.put(socksIdx, inbound)
return
}
}
val user = UUID.randomUUID().toString().replace("-", "").substring(0, 16)
val pass = UUID.randomUUID().toString().replace("-", "")
settings.put("auth", "password")
settings.put("accounts", JSONArray().put(JSONObject().put("user", user).put("pass", pass)))
inbound.put("settings", settings)
inbounds.put(socksIdx, inbound)
}
companion object {
val instance: Xray by lazy { Xray() }
}

View File

@@ -9,16 +9,12 @@ private const val XRAY_DEFAULT_MAX_MEMORY: Long = 50 shl 20 // 50 MB
class XrayConfig protected constructor(
protocolConfigBuilder: ProtocolConfig.Builder,
val socksPort: Int,
val socksUser: String,
val socksPass: String,
val maxMemory: Long,
) : ProtocolConfig(protocolConfigBuilder) {
protected constructor(builder: Builder) : this(
builder,
builder.socksPort,
builder.socksUser,
builder.socksPass,
builder.maxMemory
)
@@ -26,12 +22,6 @@ class XrayConfig protected constructor(
internal var socksPort: Int = 0
private set
internal var socksUser: String = ""
private set
internal var socksPass: String = ""
private set
internal var maxMemory: Long = XRAY_DEFAULT_MAX_MEMORY
private set
@@ -39,10 +29,6 @@ class XrayConfig protected constructor(
fun setSocksPort(port: Int) = apply { socksPort = port }
fun setSocksUser(user: String) = apply { socksUser = user }
fun setSocksPass(pass: String) = apply { socksPass = pass }
fun setMaxMemory(maxMemory: Long) = apply { this.maxMemory = maxMemory }
override fun build(): XrayConfig = configBuild().run { XrayConfig(this@Builder) }

View File

@@ -2,6 +2,10 @@ set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/..)
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/Modules;${CMAKE_MODULE_PATH}")
if(NOT IOS AND NOT ANDROID)
include(${CLIENT_ROOT_DIR}/3rd/SingleApplication/singleapplication.cmake)
endif()
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/SortFilterProxyModel)
set(LIBS ${LIBS} SortFilterProxyModel)
include(${CLIENT_ROOT_DIR}/cmake/QSimpleCrypto.cmake)
@@ -27,18 +31,12 @@ if(WIN32)
set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/windows/win32/libcrypto.lib")
endif()
elseif(APPLE AND NOT IOS)
if(MACOS_NE)
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/universal2/libssh.a")
set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/universal2/libz.a")
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/macos/universal2")
else()
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libssh.a")
set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libz.a")
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/macos/x86_64")
endif()
set(LIBSSH_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libssh.a")
set(ZLIB_LIB_PATH "${LIBSSH_ROOT_DIR}/macos/x86_64/libz.a")
set(LIBSSH_INCLUDE_DIR "${LIBSSH_ROOT_DIR}/macos/x86_64")
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 +60,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 +81,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 24)
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,26 +20,22 @@ 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)
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.h
${CMAKE_CURRENT_SOURCE_DIR}/core/protocols/androidVpnProtocol.h
${CMAKE_CURRENT_SOURCE_DIR}/core/utils/installedAppsImageProvider.h
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.h
${CMAKE_CURRENT_SOURCE_DIR}/core/installedAppsImageProvider.h
)
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.cpp
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.cpp
${CMAKE_CURRENT_SOURCE_DIR}/core/protocols/androidVpnProtocol.cpp
${CMAKE_CURRENT_SOURCE_DIR}/core/utils/installedAppsImageProvider.cpp
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.cpp
${CMAKE_CURRENT_SOURCE_DIR}/core/installedAppsImageProvider.cpp
)
foreach(abi IN ITEMS ${QT_ANDROID_ABIS})

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
)
@@ -79,22 +76,12 @@ set_target_properties(${PROJECT} PROPERTIES
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/Frameworks"
XCODE_EMBED_APP_EXTENSIONS networkextension
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "match AppStore org.amnezia.AmneziaVPN"
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "match Development org.amnezia.AmneziaVPN"
)
if(DEFINED DEPLOY)
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "distr ios.org.amnezia.AmneziaVPN"
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "dev ios.org.amnezia.AmneziaVPN"
)
else()
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
)
endif()
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES"
@@ -121,7 +108,6 @@ target_sources(${PROJECT} PRIVATE
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
${CLIENT_ROOT_DIR}/platforms/ios/ScreenProtection.swift
${CLIENT_ROOT_DIR}/platforms/ios/VPNCController.swift
${CLIENT_ROOT_DIR}/platforms/ios/StoreKit2Helper.swift
)
target_sources(${PROJECT} PRIVATE
@@ -140,9 +126,9 @@ add_subdirectory(ios/networkextension)
add_dependencies(${PROJECT} networkextension)
set_property(TARGET ${PROJECT} PROPERTY XCODE_EMBED_FRAMEWORKS
"${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/OpenVPNAdapter.framework"
"${CMAKE_CURRENT_SOURCE_DIR}/3rd/OpenVPNAdapter/build/Release-iphoneos/OpenVPNAdapter.framework"
)
set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/)
target_link_libraries("networkextension" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-ios/OpenVPNAdapter.framework")
set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/3rd/OpenVPNAdapter/build/Release-iphoneos)
target_link_libraries("networkextension" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/3rd/OpenVPNAdapter/build/Release-iphoneos/OpenVPNAdapter.framework")

View File

@@ -14,29 +14,23 @@ set(LIBS ${LIBS}
${FW_SECURITY}
${FW_COREWLAN}
${FW_NETWORK}
${FW_USER_NOTIFICATIONS}
${FW_USERNOTIFICATIONS}
${FW_NETWORK_EXTENSION}
)
set_target_properties(${PROJECT} PROPERTIES
MACOSX_BUNDLE TRUE
MACOSX_BUNDLE_SHORT_VERSION_STRING "${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}"
MACOSX_BUNDLE_BUNDLE_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
)
set_target_properties(${PROJECT} PROPERTIES MACOSX_BUNDLE TRUE)
set(CMAKE_OSX_ARCHITECTURES "x86_64" CACHE INTERNAL "" FORCE)
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15)
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/ui/utils/macosUtil.h
${CMAKE_CURRENT_SOURCE_DIR}/ui/macos_util.h
)
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/ui/utils/macosUtil.mm
${CMAKE_CURRENT_SOURCE_DIR}/ui/macos_util.mm
)
set(ICON_FILE ${CMAKE_CURRENT_SOURCE_DIR}/images/app.icns)
set(MACOSX_BUNDLE_ICON_FILE app.icns)
set_source_files_properties(${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
@@ -55,3 +49,4 @@ execute_process(
)
message("OSX_SDK_PATH is: ${OSX_SDK_PATH}")

View File

@@ -1,171 +0,0 @@
message("Client ==> MacOS NE build")
set_target_properties(${PROJECT} PROPERTIES MACOSX_BUNDLE TRUE)
set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15)
set(APPLE_PROJECT_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
enable_language(OBJC)
enable_language(Swift)
find_package(Qt6 REQUIRED COMPONENTS ShaderTools Widgets)
# Link Qt Widgets for QWidget, QMenu, QAction etc.
set(LIBS ${LIBS} Qt6::ShaderTools Qt6::Widgets)
find_library(FW_AUTHENTICATIONSERVICES AuthenticationServices)
find_library(FW_AVFOUNDATION AVFoundation)
find_library(FW_FOUNDATION Foundation)
find_library(FW_STOREKIT StoreKit)
find_library(FW_SERVICEMGMT ServiceManagement)
find_library(FW_USERNOTIFICATIONS UserNotifications)
find_library(FW_NETWORKEXTENSION NetworkExtension)
set(LIBS ${LIBS}
${FW_AUTHENTICATIONSERVICES}
${FW_AVFOUNDATION}
${FW_FOUNDATION}
${FW_STOREKIT}
${FW_SERVICEMGMT}
${FW_USERNOTIFICATIONS}
${FW_NETWORKEXTENSION}
)
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate-C-Interface.h
)
set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.h PROPERTIES OBJECTIVE_CPP_HEADER TRUE)
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ios_controller_wrapper.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosnotificationhandler.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/StoreKitController.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
)
set(ICON_FILE ${CMAKE_CURRENT_SOURCE_DIR}/images/app.icns)
set(MACOSX_BUNDLE_ICON_FILE app.icns)
set_source_files_properties(${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
set(SOURCES ${SOURCES} ${ICON_FILE})
target_include_directories(${PROJECT} PRIVATE
${Qt6Gui_PRIVATE_INCLUDE_DIRS}
${Qt6Widgets_PRIVATE_INCLUDE_DIRS}
)
set_target_properties(${PROJECT} PROPERTIES
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Info.plist.in
MACOSX_BUNDLE_ICON_FILE "AppIcon"
MACOSX_BUNDLE_INFO_STRING "AmneziaVPN"
MACOSX_BUNDLE_BUNDLE_NAME "AmneziaVPN"
MACOSX_BUNDLE_BUNDLE_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
MACOSX_BUNDLE_LONG_VERSION_STRING "${APPLE_PROJECT_VERSION}-${CMAKE_PROJECT_VERSION_TWEAK}"
MACOSX_BUNDLE_SHORT_VERSION_STRING "${APPLE_PROJECT_VERSION}"
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${BUILD_IOS_APP_IDENTIFIER}"
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/macos/app/app.entitlements"
XCODE_ATTRIBUTE_MARKETING_VERSION "${APPLE_PROJECT_VERSION}"
XCODE_ATTRIBUTE_CURRENT_PROJECT_VERSION "${CMAKE_PROJECT_VERSION_TWEAK}"
XCODE_ATTRIBUTE_PRODUCT_NAME "AmneziaVPN"
XCODE_ATTRIBUTE_BUNDLE_INFO_STRING "AmneziaVPN"
XCODE_GENERATE_SCHEME TRUE
XCODE_ATTRIBUTE_ENABLE_BITCODE "NO"
XCODE_ATTRIBUTE_ASSETCATALOG_COMPILER_APPICON_NAME "AppIcon"
XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2"
XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY "NO"
XCODE_EMBED_FRAMEWORKS_REMOVE_HEADERS_ON_COPY "YES"
XCODE_ATTRIBUTE_MACOSX_DEPLOYMENT_TARGET "11.0"
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../Frameworks"
XCODE_EMBED_APP_EXTENSIONS AmneziaVPNNetworkExtension
)
if(DEPLOY)
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution"
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development"
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "distr macos.org.amnezia.AmneziaVPN"
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "dev macos.org.amnezia.AmneziaVPN"
)
else()
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic
)
endif()
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_SWIFT_VERSION "5.0"
XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES"
XCODE_ATTRIBUTE_SWIFT_PRECOMPILE_BRIDGING_HEADER "NO"
XCODE_ATTRIBUTE_SWIFT_OBJC_INTERFACE_HEADER_NAME "AmneziaVPN-Swift.h"
XCODE_ATTRIBUTE_SWIFT_OBJC_INTEROP_MODE "objcxx"
)
set_target_properties(${PROJECT} PROPERTIES
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "X7UJ388FXK"
)
target_include_directories(${PROJECT} PRIVATE ${CMAKE_CURRENT_LIST_DIR})
target_compile_options(${PROJECT} PRIVATE
-DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\"
-DVPN_NE_BUNDLEID=\"${BUILD_IOS_APP_IDENTIFIER}.network-extension\"
)
set(WG_APPLE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rd/amneziawg-apple/Sources)
target_sources(${PROJECT} PRIVATE
${WG_APPLE_SOURCE_DIR}/WireGuardKitC/x25519.c
${CLIENT_ROOT_DIR}/platforms/ios/LogController.swift
${CLIENT_ROOT_DIR}/platforms/ios/Log.swift
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
${CLIENT_ROOT_DIR}/platforms/ios/ScreenProtection.swift
${CLIENT_ROOT_DIR}/platforms/ios/VPNCController.swift
${CLIENT_ROOT_DIR}/platforms/ios/StoreKit2Helper.swift
)
target_sources(${PROJECT} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Images.xcassets
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
)
set_property(TARGET ${PROJECT} APPEND PROPERTY RESOURCE
${CMAKE_CURRENT_SOURCE_DIR}/macos/app/Images.xcassets
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
)
add_subdirectory(macos/networkextension)
add_dependencies(${PROJECT} AmneziaVPNNetworkExtension)
get_target_property(QtCore_location Qt6::Core LOCATION)
message("QtCore_location")
message(${QtCore_location})
get_filename_component(QT_BIN_DIR_DETECTED "${QtCore_location}/../../../../../bin" ABSOLUTE)
set_property(TARGET ${PROJECT} PROPERTY XCODE_EMBED_FRAMEWORKS
"${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-macos/OpenVPNAdapter.framework"
)
set(CMAKE_XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-macos)
target_link_libraries("AmneziaVPNNetworkExtension" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/apple/OpenVPNAdapter-macos/OpenVPNAdapter.framework")
add_custom_command(TARGET ${PROJECT} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory
$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks
COMMAND /usr/bin/find "$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework" -name "*.sha256" -delete
COMMAND /usr/bin/codesign --force --sign "Apple Distribution: Privacy Technologies OU"
"$<TARGET_BUNDLE_DIR:AmneziaVPN>/Contents/Frameworks/OpenVPNAdapter.framework/Versions/Current/OpenVPNAdapter"
COMMAND ${QT_BIN_DIR_DETECTED}/macdeployqt $<TARGET_BUNDLE_DIR:AmneziaVPN> -appstore-compliant -qmldir=${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Signing OpenVPNAdapter framework"
)

View File

@@ -1,289 +0,0 @@
set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/..)
set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/core/utils/migrations.h
${CLIENT_ROOT_DIR}/../ipc/ipc.h
${CLIENT_ROOT_DIR}/amneziaApplication.h
${CLIENT_ROOT_DIR}/core/utils/errorCodes.h
${CLIENT_ROOT_DIR}/core/utils/routeModes.h
${CLIENT_ROOT_DIR}/core/utils/commonStructs.h
${CLIENT_ROOT_DIR}/core/utils/containerEnum.h
${CLIENT_ROOT_DIR}/core/utils/protocolEnum.h
${CLIENT_ROOT_DIR}/core/utils/containers/containerUtils.h
${CLIENT_ROOT_DIR}/core/protocols/protocolUtils.h
${CLIENT_ROOT_DIR}/core/utils/constants/configKeys.h
${CLIENT_ROOT_DIR}/core/utils/constants/protocolConstants.h
${CLIENT_ROOT_DIR}/core/utils/constants/apiKeys.h
${CLIENT_ROOT_DIR}/core/utils/constants/apiConstants.h
${CLIENT_ROOT_DIR}/core/utils/api/apiEnums.h
${CLIENT_ROOT_DIR}/core/utils/errorStrings.h
${CLIENT_ROOT_DIR}/core/utils/selfhosted/scriptsRegistry.h
${CLIENT_ROOT_DIR}/core/utils/qrCodeUtils.h
${CLIENT_ROOT_DIR}/core/controllers/coreController.h
${CLIENT_ROOT_DIR}/core/controllers/coreSignalHandlers.h
${CLIENT_ROOT_DIR}/core/controllers/gatewayController.h
${CLIENT_ROOT_DIR}/core/utils/selfhosted/sshSession.h
${CLIENT_ROOT_DIR}/core/controllers/serversController.h
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/usersController.h
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/installController.h
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/exportController.h
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/importController.h
${CLIENT_ROOT_DIR}/core/installers/installerBase.h
${CLIENT_ROOT_DIR}/core/installers/awgInstaller.h
${CLIENT_ROOT_DIR}/core/installers/wireguardInstaller.h
${CLIENT_ROOT_DIR}/core/installers/openvpnInstaller.h
${CLIENT_ROOT_DIR}/core/installers/xrayInstaller.h
${CLIENT_ROOT_DIR}/core/installers/torInstaller.h
${CLIENT_ROOT_DIR}/core/installers/sftpInstaller.h
${CLIENT_ROOT_DIR}/core/installers/socks5Installer.h
${CLIENT_ROOT_DIR}/core/controllers/appSplitTunnelingController.h
${CLIENT_ROOT_DIR}/core/controllers/ipSplitTunnelingController.h
${CLIENT_ROOT_DIR}/core/controllers/allowedDnsController.h
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/exportController.h
${CLIENT_ROOT_DIR}/core/controllers/connectionController.h
${CLIENT_ROOT_DIR}/core/controllers/settingsController.h
${CLIENT_ROOT_DIR}/core/controllers/api/servicesCatalogController.h
${CLIENT_ROOT_DIR}/core/controllers/api/subscriptionController.h
${CLIENT_ROOT_DIR}/core/controllers/api/newsController.h
${CLIENT_ROOT_DIR}/core/repositories/secureServersRepository.h
${CLIENT_ROOT_DIR}/core/repositories/secureAppSettingsRepository.h
${CLIENT_ROOT_DIR}/core/protocols/qmlRegisterProtocols.h
${CLIENT_ROOT_DIR}/ui/utils/pages.h
${CLIENT_ROOT_DIR}/ui/utils/qAutoStart.h
${CLIENT_ROOT_DIR}/core/protocols/vpnProtocol.h
${CMAKE_CURRENT_BINARY_DIR}/version.h
${CLIENT_ROOT_DIR}/core/utils/selfhosted/sshClient.h
${CLIENT_ROOT_DIR}/core/utils/networkUtilities.h
${CLIENT_ROOT_DIR}/core/utils/serialization/serialization.h
${CLIENT_ROOT_DIR}/core/utils/serialization/transfer.h
${CLIENT_ROOT_DIR}/../common/logger/logger.h
${CLIENT_ROOT_DIR}/ui/utils/qmlUtils.h
${CLIENT_ROOT_DIR}/core/utils/api/apiUtils.h
${CLIENT_ROOT_DIR}/core/utils/osSignalHandler.h
${CLIENT_ROOT_DIR}/core/utils/utilities.h
${CLIENT_ROOT_DIR}/core/utils/managementServer.h
${CLIENT_ROOT_DIR}/core/utils/constants.h
)
# Mozilla headres
set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/mozilla/models/server.h
${CLIENT_ROOT_DIR}/mozilla/shared/ipaddress.h
${CLIENT_ROOT_DIR}/mozilla/shared/leakdetector.h
${CLIENT_ROOT_DIR}/mozilla/controllerimpl.h
)
if(NOT IOS AND NOT MACOS_NE)
set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/platforms/ios/QRCodeReaderBase.h
)
endif()
if(NOT ANDROID)
set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/ui/utils/notificationHandler.h
)
endif()
set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/core/utils/migrations.cpp
${CLIENT_ROOT_DIR}/amneziaApplication.cpp
${CLIENT_ROOT_DIR}/core/utils/errorStrings.cpp
${CLIENT_ROOT_DIR}/core/utils/containers/containerUtils.cpp
${CLIENT_ROOT_DIR}/core/protocols/protocolUtils.cpp
${CLIENT_ROOT_DIR}/core/utils/selfhosted/scriptsRegistry.cpp
${CLIENT_ROOT_DIR}/core/utils/qrCodeUtils.cpp
${CLIENT_ROOT_DIR}/core/controllers/coreController.cpp
${CLIENT_ROOT_DIR}/core/controllers/coreSignalHandlers.cpp
${CLIENT_ROOT_DIR}/core/controllers/gatewayController.cpp
${CLIENT_ROOT_DIR}/core/utils/selfhosted/sshSession.cpp
${CLIENT_ROOT_DIR}/core/controllers/serversController.cpp
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/usersController.cpp
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/installController.cpp
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/exportController.cpp
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/importController.cpp
${CLIENT_ROOT_DIR}/core/installers/installerBase.cpp
${CLIENT_ROOT_DIR}/core/installers/awgInstaller.cpp
${CLIENT_ROOT_DIR}/core/installers/wireguardInstaller.cpp
${CLIENT_ROOT_DIR}/core/installers/openvpnInstaller.cpp
${CLIENT_ROOT_DIR}/core/installers/xrayInstaller.cpp
${CLIENT_ROOT_DIR}/core/installers/torInstaller.cpp
${CLIENT_ROOT_DIR}/core/installers/sftpInstaller.cpp
${CLIENT_ROOT_DIR}/core/installers/socks5Installer.cpp
${CLIENT_ROOT_DIR}/core/controllers/appSplitTunnelingController.cpp
${CLIENT_ROOT_DIR}/core/controllers/ipSplitTunnelingController.cpp
${CLIENT_ROOT_DIR}/core/controllers/allowedDnsController.cpp
${CLIENT_ROOT_DIR}/core/controllers/selfhosted/exportController.cpp
${CLIENT_ROOT_DIR}/core/controllers/connectionController.cpp
${CLIENT_ROOT_DIR}/core/controllers/settingsController.cpp
${CLIENT_ROOT_DIR}/core/controllers/api/servicesCatalogController.cpp
${CLIENT_ROOT_DIR}/core/controllers/api/subscriptionController.cpp
${CLIENT_ROOT_DIR}/core/controllers/api/newsController.cpp
${CLIENT_ROOT_DIR}/core/repositories/secureServersRepository.cpp
${CLIENT_ROOT_DIR}/core/repositories/secureAppSettingsRepository.cpp
${CLIENT_ROOT_DIR}/ui/utils/qAutoStart.cpp
${CLIENT_ROOT_DIR}/core/protocols/vpnProtocol.cpp
${CLIENT_ROOT_DIR}/core/utils/selfhosted/sshClient.cpp
${CLIENT_ROOT_DIR}/core/utils/networkUtilities.cpp
${CLIENT_ROOT_DIR}/core/utils/serialization/outbound.cpp
${CLIENT_ROOT_DIR}/core/utils/serialization/inbound.cpp
${CLIENT_ROOT_DIR}/core/utils/serialization/ss.cpp
${CLIENT_ROOT_DIR}/core/utils/serialization/ssd.cpp
${CLIENT_ROOT_DIR}/core/utils/serialization/vless.cpp
${CLIENT_ROOT_DIR}/core/utils/serialization/trojan.cpp
${CLIENT_ROOT_DIR}/core/utils/serialization/vmess.cpp
${CLIENT_ROOT_DIR}/core/utils/serialization/vmess_new.cpp
${CLIENT_ROOT_DIR}/../common/logger/logger.cpp
${CLIENT_ROOT_DIR}/ui/utils/qmlUtils.cpp
${CLIENT_ROOT_DIR}/core/utils/api/apiUtils.cpp
${CLIENT_ROOT_DIR}/core/utils/osSignalHandler.cpp
${CLIENT_ROOT_DIR}/core/utils/utilities.cpp
${CLIENT_ROOT_DIR}/core/utils/managementServer.cpp
)
# Mozilla sources
set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/mozilla/models/server.cpp
${CLIENT_ROOT_DIR}/mozilla/shared/ipaddress.cpp
${CLIENT_ROOT_DIR}/mozilla/shared/leakdetector.cpp
)
if(NOT IOS AND NOT MACOS_NE)
set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/platforms/ios/QRCodeReaderBase.cpp
)
endif()
# Include native macOS platform helpers (dock/status-item)
if(APPLE AND NOT IOS)
list(APPEND HEADERS
${CLIENT_ROOT_DIR}/platforms/macos/macosutils.h
${CLIENT_ROOT_DIR}/platforms/macos/macosstatusicon.h
${CLIENT_ROOT_DIR}/ui/utils/macosUtil.h
)
list(APPEND SOURCES
${CLIENT_ROOT_DIR}/platforms/macos/macosutils.mm
${CLIENT_ROOT_DIR}/platforms/macos/macosstatusicon.mm
${CLIENT_ROOT_DIR}/ui/utils/macosUtil.mm
)
endif()
if(NOT ANDROID)
set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/ui/utils/notificationHandler.cpp
)
endif()
set(COMMON_FILES_H
${CLIENT_ROOT_DIR}/amneziaApplication.h
${CLIENT_ROOT_DIR}/secureQSettings.h
${CLIENT_ROOT_DIR}/vpnConnection.h
)
set(COMMON_FILES_CPP
${CLIENT_ROOT_DIR}/amneziaApplication.cpp
${CLIENT_ROOT_DIR}/secureQSettings.cpp
${CLIENT_ROOT_DIR}/vpnConnection.cpp
)
file(GLOB_RECURSE PAGE_LOGIC_H CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/ui/pages_logic/*.h)
file(GLOB_RECURSE PAGE_LOGIC_CPP CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/ui/pages_logic/*.cpp)
file(GLOB CONFIGURATORS_H CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/core/configurators/*.h)
file(GLOB CONFIGURATORS_CPP CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/core/configurators/*.cpp)
file(GLOB_RECURSE CORE_MODELS_H CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/core/models/*.h)
file(GLOB_RECURSE CORE_MODELS_CPP CONFIGURE_DEPENDS ${CLIENT_ROOT_DIR}/core/models/*.cpp)
file(GLOB UI_MODELS_H CONFIGURE_DEPENDS
${CLIENT_ROOT_DIR}/ui/models/*.h
${CLIENT_ROOT_DIR}/ui/models/protocols/*.h
${CLIENT_ROOT_DIR}/ui/models/services/*.h
${CLIENT_ROOT_DIR}/ui/models/api/*.h
)
file(GLOB UI_MODELS_CPP CONFIGURE_DEPENDS
${CLIENT_ROOT_DIR}/ui/models/*.cpp
${CLIENT_ROOT_DIR}/ui/models/protocols/*.cpp
${CLIENT_ROOT_DIR}/ui/models/services/*.cpp
${CLIENT_ROOT_DIR}/ui/models/api/*.cpp
)
file(GLOB UI_CONTROLLERS_H CONFIGURE_DEPENDS
${CLIENT_ROOT_DIR}/ui/controllers/*.h
${CLIENT_ROOT_DIR}/ui/controllers/api/*.h
${CLIENT_ROOT_DIR}/ui/controllers/qml/*.h
${CLIENT_ROOT_DIR}/ui/controllers/selfhosted/*.h
)
file(GLOB UI_CONTROLLERS_CPP CONFIGURE_DEPENDS
${CLIENT_ROOT_DIR}/ui/controllers/*.cpp
${CLIENT_ROOT_DIR}/ui/controllers/api/*.cpp
${CLIENT_ROOT_DIR}/ui/controllers/qml/*.cpp
${CLIENT_ROOT_DIR}/ui/controllers/selfhosted/*.cpp
)
set(HEADERS ${HEADERS}
${COMMON_FILES_H}
${PAGE_LOGIC_H}
${CONFIGURATORS_H}
${CORE_MODELS_H}
${UI_MODELS_H}
${UI_CONTROLLERS_H}
)
set(SOURCES ${SOURCES}
${COMMON_FILES_CPP}
${PAGE_LOGIC_CPP}
${CONFIGURATORS_CPP}
${CORE_MODELS_CPP}
${UI_MODELS_CPP}
${UI_CONTROLLERS_CPP}
)
if(WIN32)
set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/core/protocols/ikev2VpnProtocolWindows.h
)
set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/core/protocols/ikev2VpnProtocolWindows.cpp
)
set(RESOURCES ${RESOURCES}
${CMAKE_CURRENT_BINARY_DIR}/amneziavpn.rc
)
endif()
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
message("Client desktop build")
add_compile_definitions(AMNEZIA_DESKTOP)
set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/core/utils/ipcClient.h
${CLIENT_ROOT_DIR}/ui/utils/systemTrayNotificationHandler.h
${CLIENT_ROOT_DIR}/core/protocols/openVpnProtocol.h
${CLIENT_ROOT_DIR}/core/protocols/wireGuardProtocol.h
${CLIENT_ROOT_DIR}/core/protocols/xrayProtocol.h
${CLIENT_ROOT_DIR}/core/protocols/awgProtocol.h
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.h
)
set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/core/utils/ipcClient.cpp
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.cpp
${CLIENT_ROOT_DIR}/ui/utils/systemTrayNotificationHandler.cpp
${CLIENT_ROOT_DIR}/core/protocols/openVpnProtocol.cpp
${CLIENT_ROOT_DIR}/core/protocols/wireGuardProtocol.cpp
${CLIENT_ROOT_DIR}/core/protocols/xrayProtocol.cpp
${CLIENT_ROOT_DIR}/core/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/utils/systemTrayNotificationHandler.h
)
set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/ui/utils/systemTrayNotificationHandler.cpp
)
endif()

View File

@@ -0,0 +1,46 @@
#include "awg_configurator.h"
#include <QJsonDocument>
#include <QJsonObject>
AwgConfigurator::AwgConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent)
: WireguardConfigurator(settings, serverController, true, parent)
{
}
QString AwgConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
ErrorCode &errorCode)
{
QString config = WireguardConfigurator::createConfig(credentials, container, containerConfig, errorCode);
QJsonObject jsonConfig = QJsonDocument::fromJson(config.toUtf8()).object();
QString awgConfig = jsonConfig.value(config_key::config).toString();
QMap<QString, QString> configMap;
auto configLines = awgConfig.split("\n");
for (auto &line : configLines) {
auto trimmedLine = line.trimmed();
if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) {
continue;
} else {
QStringList parts = trimmedLine.split(" = ");
if (parts.count() == 2) {
configMap.insert(parts[0].trimmed(), parts[1].trimmed());
}
}
}
jsonConfig[config_key::junkPacketCount] = configMap.value(config_key::junkPacketCount);
jsonConfig[config_key::junkPacketMinSize] = configMap.value(config_key::junkPacketMinSize);
jsonConfig[config_key::junkPacketMaxSize] = configMap.value(config_key::junkPacketMaxSize);
jsonConfig[config_key::initPacketJunkSize] = configMap.value(config_key::initPacketJunkSize);
jsonConfig[config_key::responsePacketJunkSize] = configMap.value(config_key::responsePacketJunkSize);
jsonConfig[config_key::initPacketMagicHeader] = configMap.value(config_key::initPacketMagicHeader);
jsonConfig[config_key::responsePacketMagicHeader] = configMap.value(config_key::responsePacketMagicHeader);
jsonConfig[config_key::underloadPacketMagicHeader] = configMap.value(config_key::underloadPacketMagicHeader);
jsonConfig[config_key::transportPacketMagicHeader] = configMap.value(config_key::transportPacketMagicHeader);
jsonConfig[config_key::mtu] =
containerConfig.value(ProtocolProps::protoToString(Proto::Awg)).toObject().value(config_key::mtu).toString(protocols::awg::defaultMtu);
return QJsonDocument(jsonConfig).toJson();
}

View File

@@ -0,0 +1,18 @@
#ifndef AWGCONFIGURATOR_H
#define AWGCONFIGURATOR_H
#include <QObject>
#include "wireguard_configurator.h"
class AwgConfigurator : public WireguardConfigurator
{
Q_OBJECT
public:
AwgConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode &errorCode);
};
#endif // AWGCONFIGURATOR_H

View File

@@ -0,0 +1,51 @@
#include "cloak_configurator.h"
#include <QFile>
#include <QJsonDocument>
#include <QJsonObject>
#include "containers/containers_defs.h"
#include "core/controllers/serverController.h"
CloakConfigurator::CloakConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent)
: ConfiguratorBase(settings, serverController, parent)
{
}
QString CloakConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
ErrorCode &errorCode)
{
QString cloakPublicKey =
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::cloak::ckPublicKeyPath, errorCode);
cloakPublicKey.replace("\n", "");
if (errorCode != ErrorCode::NoError) {
return "";
}
QString cloakBypassUid =
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::cloak::ckBypassUidKeyPath, errorCode);
cloakBypassUid.replace("\n", "");
if (errorCode != ErrorCode::NoError) {
return "";
}
QJsonObject config;
config.insert("Transport", "direct");
config.insert("ProxyMethod", "openvpn");
config.insert("EncryptionMethod", "aes-gcm");
config.insert("UID", cloakBypassUid);
config.insert("PublicKey", cloakPublicKey);
config.insert("ServerName", "$FAKE_WEB_SITE_ADDRESS");
config.insert("NumConn", 1);
config.insert("BrowserSig", "chrome");
config.insert("StreamTimeout", 300);
config.insert("RemoteHost", credentials.hostName);
config.insert("RemotePort", "$CLOAK_SERVER_PORT");
QString textCfg = m_serverController->replaceVars(QJsonDocument(config).toJson(),
m_serverController->genVarsForScript(credentials, container, containerConfig));
return textCfg;
}

View File

@@ -0,0 +1,20 @@
#ifndef CLOAK_CONFIGURATOR_H
#define CLOAK_CONFIGURATOR_H
#include <QObject>
#include "configurator_base.h"
using namespace amnezia;
class CloakConfigurator : public ConfiguratorBase
{
Q_OBJECT
public:
CloakConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode &errorCode);
};
#endif // CLOAK_CONFIGURATOR_H

View File

@@ -0,0 +1,26 @@
#include "configurator_base.h"
ConfiguratorBase::ConfiguratorBase(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent)
: QObject { parent }, m_settings(settings), m_serverController(serverController)
{
}
QString ConfiguratorBase::processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString)
{
processConfigWithDnsSettings(dns, protocolConfigString);
return protocolConfigString;
}
QString ConfiguratorBase::processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString)
{
processConfigWithDnsSettings(dns, protocolConfigString);
return protocolConfigString;
}
void ConfiguratorBase::processConfigWithDnsSettings(const QPair<QString, QString> &dns, QString &protocolConfigString)
{
protocolConfigString.replace("$PRIMARY_DNS", dns.first);
protocolConfigString.replace("$SECONDARY_DNS", dns.second);
}

View File

@@ -0,0 +1,33 @@
#ifndef CONFIGURATORBASE_H
#define CONFIGURATORBASE_H
#include <QObject>
#include "containers/containers_defs.h"
#include "core/defs.h"
#include "core/controllers/serverController.h"
#include "settings.h"
class ConfiguratorBase : public QObject
{
Q_OBJECT
public:
explicit ConfiguratorBase(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
virtual QString createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode &errorCode) = 0;
virtual QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString);
virtual QString processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString);
protected:
void processConfigWithDnsSettings(const QPair<QString, QString> &dns, QString &protocolConfigString);
std::shared_ptr<Settings> m_settings;
QSharedPointer<ServerController> m_serverController;
};
#endif // CONFIGURATORBASE_H

View File

@@ -1,4 +1,4 @@
#include "ikev2Configurator.h"
#include "ikev2_configurator.h"
#include <QDebug>
#include <QJsonDocument>
@@ -8,16 +8,14 @@
#include <QTemporaryFile>
#include <QUuid>
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/selfhosted/sshSession.h"
#include "core/utils/selfhosted/scriptsRegistry.h"
#include "core/utils/utilities.h"
#include "core/models/protocols/ikev2ProtocolConfig.h"
#include "containers/containers_defs.h"
#include "core/controllers/serverController.h"
#include "core/scripts_registry.h"
#include "core/server_defs.h"
#include "utilities.h"
Ikev2Configurator::Ikev2Configurator(SshSession* sshSession, QObject *parent)
: ConfiguratorBase(sshSession, parent)
Ikev2Configurator::Ikev2Configurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent)
: ConfiguratorBase(settings, serverController, parent)
{
}
@@ -27,6 +25,7 @@ Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const Se
Ikev2Configurator::ConnectionData connData;
connData.host = credentials.hostName;
connData.clientId = Utils::getRandomString(16);
connData.password = Utils::getRandomString(16);
connData.password = "";
QString certFileName = "/opt/amnezia/ikev2/clients/" + connData.clientId + ".p12";
@@ -40,14 +39,14 @@ Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const Se
"--extKeyUsage serverAuth,clientAuth -8 \"%1\"")
.arg(connData.clientId);
errorCode = m_sshSession->runContainerScript(credentials, container, scriptCreateCert);
errorCode = m_serverController->runContainerScript(credentials, container, scriptCreateCert);
QString scriptExportCert =
QString("pk12util -W \"%1\" -d sql:/etc/ipsec.d -n \"%2\" -o \"%3\"").arg(connData.password).arg(connData.clientId).arg(certFileName);
errorCode = m_sshSession->runContainerScript(credentials, container, scriptExportCert);
errorCode = m_serverController->runContainerScript(credentials, container, scriptExportCert);
connData.clientCert = m_sshSession->getTextFileFromContainer(container, credentials, certFileName, errorCode);
connData.caCert = m_sshSession->getTextFileFromContainer(container, credentials, "/etc/ipsec.d/ca_cert_base64.p12", errorCode);
connData.clientCert = m_serverController->getTextFileFromContainer(container, credentials, certFileName, errorCode);
connData.caCert = m_serverController->getTextFileFromContainer(container, credentials, "/etc/ipsec.d/ca_cert_base64.p12", errorCode);
qDebug() << "Ikev2Configurator::ConnectionData client cert size:" << connData.clientCert.size();
qDebug() << "Ikev2Configurator::ConnectionData ca cert size:" << connData.caCert.size();
@@ -55,51 +54,26 @@ Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const Se
return connData;
}
ProtocolConfig Ikev2Configurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const ContainerConfig &containerConfig,
const DnsSettings &dnsSettings,
ErrorCode &errorCode)
QString Ikev2Configurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
ErrorCode &errorCode)
{
const Ikev2ServerConfig* serverConfig = nullptr;
if (auto* ikev2Config = containerConfig.protocolConfig.as<Ikev2ProtocolConfig>()) {
serverConfig = &ikev2Config->serverConfig;
}
Q_UNUSED(containerConfig)
ConnectionData connData = prepareIkev2Config(credentials, container, errorCode);
if (errorCode != ErrorCode::NoError) {
return Ikev2ProtocolConfig{};
return "";
}
QString configJson = genIkev2Config(connData);
QJsonDocument doc = QJsonDocument::fromJson(configJson.toUtf8());
QJsonObject configObj = doc.object();
Ikev2ProtocolConfig protocolConfig;
if (serverConfig) {
protocolConfig.serverConfig = *serverConfig;
} else {
protocolConfig.serverConfig.hostName = connData.host;
}
Ikev2ClientConfig clientConfig;
clientConfig.nativeConfig = configJson;
clientConfig.hostName = connData.host;
clientConfig.userName = connData.clientId;
clientConfig.cert = QString(connData.clientCert.toBase64());
clientConfig.password = connData.password;
clientConfig.clientId = connData.clientId;
protocolConfig.setClientConfig(clientConfig);
return protocolConfig;
return genIkev2Config(connData);
}
QString Ikev2Configurator::genIkev2Config(const ConnectionData &connData)
{
QJsonObject config;
config[configKey::hostName] = connData.host;
config[configKey::userName] = connData.clientId;
config[configKey::cert] = QString(connData.clientCert.toBase64());
config[configKey::password] = connData.password;
config[config_key::hostName] = connData.host;
config[config_key::userName] = connData.clientId;
config[config_key::cert] = QString(connData.clientCert.toBase64());
config[config_key::password] = connData.password;
return QJsonDocument(config).toJson();
}

View File

@@ -0,0 +1,35 @@
#ifndef IKEV2_CONFIGURATOR_H
#define IKEV2_CONFIGURATOR_H
#include <QObject>
#include <QProcessEnvironment>
#include "configurator_base.h"
#include "core/defs.h"
class Ikev2Configurator : public ConfiguratorBase
{
Q_OBJECT
public:
Ikev2Configurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
struct ConnectionData {
QByteArray clientCert; // p12 client cert
QByteArray caCert; // p12 server cert
QString clientId;
QString password; // certificate password
QString host; // host ip
};
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode &errorCode);
QString genIkev2Config(const ConnectionData &connData);
QString genMobileConfig(const ConnectionData &connData);
QString genStrongSwanConfig(const ConnectionData &connData);
ConnectionData prepareIkev2Config(const ServerCredentials &credentials,
DockerContainer container, ErrorCode &errorCode);
};
#endif // IKEV2_CONFIGURATOR_H

View File

@@ -1,9 +1,8 @@
#include "openVpnConfigurator.h"
#include "openvpn_configurator.h"
#include <QDebug>
#include <QJsonDocument>
#include <QJsonObject>
#include <QRegularExpression>
#include <QProcess>
#include <QString>
#include <QTemporaryDir>
@@ -14,34 +13,25 @@
#include <QApplication>
#endif
#include "core/utils/errorCodes.h"
#include "core/utils/routeModes.h"
#include "core/utils/commonStructs.h"
#include "core/utils/networkUtilities.h"
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/selfhosted/sshSession.h"
#include "core/utils/selfhosted/scriptsRegistry.h"
#include "core/utils/utilities.h"
#include "core/models/protocols/openVpnProtocolConfig.h"
using namespace amnezia;
#include "containers/containers_defs.h"
#include "core/controllers/serverController.h"
#include "core/scripts_registry.h"
#include "core/server_defs.h"
#include "settings.h"
#include "utilities.h"
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <openssl/x509.h>
OpenVpnConfigurator::OpenVpnConfigurator(SshSession* sshSession, QObject *parent)
: ConfiguratorBase(sshSession, parent)
OpenVpnConfigurator::OpenVpnConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController,
QObject *parent)
: ConfiguratorBase(settings, serverController, parent)
{
}
OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(const ServerCredentials &credentials,
DockerContainer container,
const DnsSettings &dnsSettings,
ErrorCode &errorCode)
DockerContainer container, ErrorCode &errorCode)
{
OpenVpnConfigurator::ConnectionData connData = OpenVpnConfigurator::createCertRequest();
connData.host = credentials.hostName;
@@ -53,26 +43,26 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(co
QString reqFileName = QString("%1/%2.req").arg(amnezia::protocols::openvpn::clientsDirPath).arg(connData.clientId);
errorCode = m_sshSession->uploadTextFileToContainer(container, credentials, connData.request, reqFileName);
errorCode = m_serverController->uploadTextFileToContainer(container, credentials, connData.request, reqFileName);
if (errorCode != ErrorCode::NoError) {
return connData;
}
errorCode = signCert(container, credentials, dnsSettings, connData.clientId);
errorCode = signCert(container, credentials, connData.clientId);
if (errorCode != ErrorCode::NoError) {
return connData;
}
connData.caCert =
m_sshSession->getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::caCertPath, errorCode);
connData.clientCert = m_sshSession->getTextFileFromContainer(
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::caCertPath, errorCode);
connData.clientCert = m_serverController->getTextFileFromContainer(
container, credentials, QString("%1/%2.crt").arg(amnezia::protocols::openvpn::clientCertPath).arg(connData.clientId), errorCode);
if (errorCode != ErrorCode::NoError) {
return connData;
}
connData.taKey = m_sshSession->getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::taKeyPath, errorCode);
connData.taKey = m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::taKeyPath, errorCode);
if (connData.caCert.isEmpty() || connData.clientCert.isEmpty() || connData.taKey.isEmpty()) {
errorCode = ErrorCode::SshScpFailureError;
@@ -81,49 +71,23 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(co
return connData;
}
ProtocolConfig OpenVpnConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
const ContainerConfig &containerConfig,
const DnsSettings &dnsSettings,
ErrorCode &errorCode)
QString OpenVpnConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode &errorCode)
{
const OpenVpnServerConfig* serverConfig = nullptr;
if (auto* openVpnProtocolConfig = containerConfig.getOpenVpnProtocolConfig()) {
serverConfig = &openVpnProtocolConfig->serverConfig;
}
amnezia::ScriptVars vars = amnezia::genBaseVars(credentials, container, dnsSettings.primaryDns, dnsSettings.secondaryDns);
vars.append(amnezia::genProtocolVarsForContainer(container, containerConfig));
QString config = m_sshSession->replaceVars(amnezia::scriptData(ProtocolScriptType::openvpn_template, container), vars);
QString config = m_serverController->replaceVars(amnezia::scriptData(ProtocolScriptType::openvpn_template, container),
m_serverController->genVarsForScript(credentials, container, containerConfig));
ConnectionData connData = prepareOpenVpnConfig(credentials, container, dnsSettings, errorCode);
ConnectionData connData = prepareOpenVpnConfig(credentials, container, errorCode);
if (errorCode != ErrorCode::NoError) {
return OpenVpnProtocolConfig{};
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>", "");
@@ -133,45 +97,42 @@ ProtocolConfig OpenVpnConfigurator::createConfig(const ServerCredentials &creden
config.replace("block-outside-dns", "");
#endif
OpenVpnProtocolConfig protocolConfig;
if (serverConfig) {
protocolConfig.serverConfig = *serverConfig;
}
OpenVpnClientConfig clientConfig;
clientConfig.nativeConfig = config;
clientConfig.clientId = connData.clientId;
clientConfig.blockOutsideDns = false;
protocolConfig.setClientConfig(clientConfig);
return protocolConfig;
QJsonObject jConfig;
jConfig[config_key::config] = config;
jConfig[config_key::clientId] = connData.clientId;
return QJsonDocument(jConfig).toJson();
}
ProtocolConfig OpenVpnConfigurator::processConfigWithLocalSettings(const ConnectionSettings &settings,
ProtocolConfig protocolConfig)
QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString)
{
applyDnsToNativeConfig(settings.dns, protocolConfig);
processConfigWithDnsSettings(dns, protocolConfigString);
QString config = protocolConfig.nativeConfig();
QJsonObject json = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
QString config = json[config_key::config].toString();
if (!settings.isApiConfig) {
if (!isApiConfig) {
QRegularExpression regex("redirect-gateway.*");
config.replace(regex, "");
if (settings.dns.primaryDns.contains(protocols::dns::amneziaDnsIp)) {
QRegularExpression dnsRegex("dhcp-option DNS " + settings.dns.secondaryDns);
config.replace(dnsRegex, "");
}
if (!settings.splitTunneling.isSitesSplitTunnelingEnabled) {
if (!m_settings->isSitesSplitTunnelingEnabled()) {
config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n");
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
// Prevent ipv6 leak
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
#endif
config.append("block-ipv6\n");
} else if (settings.splitTunneling.routeMode == RouteMode::VpnOnlyForwardSites) {
} else if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
// no redirect-gateway
} else if (settings.splitTunneling.routeMode == RouteMode::VpnAllExceptSites) {
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
} else if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n");
// Prevent ipv6 leak
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
#endif
config.append("block-ipv6\n");
}
@@ -182,57 +143,59 @@ ProtocolConfig OpenVpnConfigurator::processConfigWithLocalSettings(const Connect
#endif
#if (defined(MZ_MACOS) || defined(MZ_LINUX))
config.append(QString("\nscript-security 2\n"
"up %1/update-resolv-conf.sh\n"
"down %1/update-resolv-conf.sh\n")
.arg(qApp->applicationDirPath()));
QString dnsConf = QString("\nscript-security 2\n"
"up %1/update-resolv-conf.sh\n"
"down %1/update-resolv-conf.sh\n")
.arg(qApp->applicationDirPath());
config.append(dnsConf);
#endif
protocolConfig.setNativeConfig(config);
return protocolConfig;
json[config_key::config] = config;
return QJsonDocument(json).toJson();
}
ProtocolConfig OpenVpnConfigurator::processConfigWithExportSettings(const ExportSettings &settings,
ProtocolConfig protocolConfig)
QString OpenVpnConfigurator::processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString)
{
applyDnsToNativeConfig(settings.dns, protocolConfig);
processConfigWithDnsSettings(dns, protocolConfigString);
QString config = protocolConfig.nativeConfig();
QJsonObject json = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
QString config = json[config_key::config].toString();
QRegularExpression regex("redirect-gateway.*");
config.replace(regex, "");
if (settings.dns.primaryDns.contains(protocols::dns::amneziaDnsIp)) {
QRegularExpression dnsRegex("dhcp-option DNS " + settings.dns.secondaryDns);
config.replace(dnsRegex, "");
}
config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n");
// Prevent ipv6 leak
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
config.append("block-ipv6\n");
// remove block-outside-dns for all exported configs
config.replace("block-outside-dns", "");
protocolConfig.setNativeConfig(config);
return protocolConfig;
json[config_key::config] = config;
return QJsonDocument(json).toJson();
}
ErrorCode OpenVpnConfigurator::signCert(DockerContainer container, const ServerCredentials &credentials,
const DnsSettings &dnsSettings, QString clientId)
ErrorCode OpenVpnConfigurator::signCert(DockerContainer container, const ServerCredentials &credentials, QString clientId)
{
QString script_import = QString("sudo docker exec -i %1 bash -c \"cd /opt/amnezia/openvpn && "
"easyrsa import-req %2/%3.req %3\"")
.arg(ContainerUtils::containerToString(container))
.arg(ContainerProps::containerToString(container))
.arg(amnezia::protocols::openvpn::clientsDirPath)
.arg(clientId);
QString script_sign = QString("sudo docker exec -i %1 bash -c \"export EASYRSA_BATCH=1; cd /opt/amnezia/openvpn && "
"easyrsa sign-req client %2\"")
.arg(ContainerUtils::containerToString(container))
.arg(ContainerProps::containerToString(container))
.arg(clientId);
QStringList scriptList { script_import, script_sign };
QString script = m_sshSession->replaceVars(scriptList.join("\n"), amnezia::genBaseVars(credentials, container, dnsSettings.primaryDns, dnsSettings.secondaryDns));
QString script = m_serverController->replaceVars(scriptList.join("\n"), m_serverController->genVarsForScript(credentials, container));
return m_sshSession->runScript(credentials, script);
return m_serverController->runScript(credentials, script);
}
OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest()

View File

@@ -0,0 +1,43 @@
#ifndef OPENVPN_CONFIGURATOR_H
#define OPENVPN_CONFIGURATOR_H
#include <QObject>
#include <QProcessEnvironment>
#include "configurator_base.h"
#include "core/defs.h"
class OpenVpnConfigurator : public ConfiguratorBase
{
Q_OBJECT
public:
OpenVpnConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
struct ConnectionData
{
QString clientId;
QString request; // certificate request
QString privKey; // client private key
QString clientCert; // client signed certificate
QString caCert; // server certificate
QString taKey; // tls-auth key
QString host; // host ip
};
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode &errorCode);
QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString);
QString processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString);
static ConnectionData createCertRequest();
private:
ConnectionData prepareOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container,
ErrorCode &errorCode);
ErrorCode signCert(DockerContainer container, const ServerCredentials &credentials, QString clientId);
};
#endif // OPENVPN_CONFIGURATOR_H

View File

@@ -0,0 +1,40 @@
#include "shadowsocks_configurator.h"
#include <QFile>
#include <QJsonDocument>
#include <QJsonObject>
#include "containers/containers_defs.h"
#include "core/controllers/serverController.h"
ShadowSocksConfigurator::ShadowSocksConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController,
QObject *parent)
: ConfiguratorBase(settings, serverController, parent)
{
}
QString ShadowSocksConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode &errorCode)
{
QString ssKey =
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::shadowsocks::ssKeyPath, errorCode);
ssKey.replace("\n", "");
if (errorCode != ErrorCode::NoError) {
return "";
}
QJsonObject config;
config.insert("server", credentials.hostName);
config.insert("server_port", "$SHADOWSOCKS_SERVER_PORT");
config.insert("local_port", "$SHADOWSOCKS_LOCAL_PORT");
config.insert("password", ssKey);
config.insert("timeout", 60);
config.insert("method", "$SHADOWSOCKS_CIPHER");
QString textCfg = m_serverController->replaceVars(QJsonDocument(config).toJson(),
m_serverController->genVarsForScript(credentials, container, containerConfig));
// qDebug().noquote() << textCfg;
return textCfg;
}

View File

@@ -0,0 +1,19 @@
#ifndef SHADOWSOCKS_CONFIGURATOR_H
#define SHADOWSOCKS_CONFIGURATOR_H
#include <QObject>
#include "configurator_base.h"
#include "core/defs.h"
class ShadowSocksConfigurator : public ConfiguratorBase
{
Q_OBJECT
public:
ShadowSocksConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode &errorCode);
};
#endif // SHADOWSOCKS_CONFIGURATOR_H

View File

@@ -0,0 +1,111 @@
#include "ssh_configurator.h"
#include <QDebug>
#include <QObject>
#include <QProcess>
#include <QString>
#include <QTemporaryDir>
#include <QTemporaryFile>
#include <QThread>
#include <qtimer.h>
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#include <QGuiApplication>
#else
#include <QApplication>
#endif
#include "core/server_defs.h"
#include "utilities.h"
SshConfigurator::SshConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent)
: ConfiguratorBase(settings, serverController, parent)
{
}
QString SshConfigurator::convertOpenSShKey(const QString &key)
{
#ifndef Q_OS_IOS
QProcess p;
p.setProcessChannelMode(QProcess::MergedChannels);
QTemporaryFile tmp;
#ifdef QT_DEBUG
tmp.setAutoRemove(false);
#endif
tmp.open();
tmp.write(key.toUtf8());
tmp.close();
// ssh-keygen -p -P "" -N "" -m pem -f id_ssh
#ifdef Q_OS_WIN
p.setProcessEnvironment(prepareEnv());
p.setProgram("cmd.exe");
p.setNativeArguments(QString("/C \"ssh-keygen.exe -p -P \"\" -N \"\" -m pem -f \"%1\"\"").arg(tmp.fileName()));
#else
p.setProgram("ssh-keygen");
p.setArguments(QStringList() << "-p"
<< "-P"
<< ""
<< "-N"
<< ""
<< "-m"
<< "pem"
<< "-f" << tmp.fileName());
#endif
p.start();
p.waitForFinished();
qDebug().noquote() << "OpenVpnConfigurator::convertOpenSShKey" << p.exitCode() << p.exitStatus() << p.readAll();
tmp.open();
return tmp.readAll();
#else
return key;
#endif
}
void SshConfigurator::openSshTerminal(const ServerCredentials &credentials)
{
#ifndef Q_OS_IOS
QProcess *p = new QProcess();
p->setProcessChannelMode(QProcess::SeparateChannels);
#ifdef Q_OS_WIN
p->setProcessEnvironment(prepareEnv());
p->setProgram(qApp->applicationDirPath() + "\\cygwin\\putty.exe");
if (credentials.secretData.contains("PRIVATE KEY")) {
// todo: connect by key
// p->setNativeArguments(QString("%1@%2")
// .arg(credentials.userName).arg(credentials.hostName).arg(credentials.secretData));
} else {
p->setNativeArguments(QString("%1@%2 -pw %3").arg(credentials.userName).arg(credentials.hostName).arg(credentials.secretData));
}
#else
p->setProgram("/bin/bash");
#endif
p->startDetached();
#endif
}
QProcessEnvironment SshConfigurator::prepareEnv()
{
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
QString pathEnvVar = env.value("PATH");
#ifdef Q_OS_WIN
pathEnvVar.clear();
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\cygwin;");
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "\\openvpn;");
#elif defined(Q_OS_MACX)
pathEnvVar.prepend(QDir::toNativeSeparators(QApplication::applicationDirPath()) + "/Contents/MacOS");
#endif
env.insert("PATH", pathEnvVar);
// qDebug().noquote() << "ENV PATH" << pathEnvVar;
return env;
}

View File

@@ -0,0 +1,22 @@
#ifndef SSH_CONFIGURATOR_H
#define SSH_CONFIGURATOR_H
#include <QObject>
#include <QProcessEnvironment>
#include "configurator_base.h"
#include "core/defs.h"
class SshConfigurator : ConfiguratorBase
{
Q_OBJECT
public:
SshConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
QProcessEnvironment prepareEnv();
QString convertOpenSShKey(const QString &key);
void openSshTerminal(const ServerCredentials &credentials);
};
#endif // SSH_CONFIGURATOR_H

View File

@@ -0,0 +1,225 @@
#include "wireguard_configurator.h"
#include <QDebug>
#include <QJsonDocument>
#include <QProcess>
#include <QString>
#include <QTemporaryDir>
#include <QTemporaryFile>
#include <openssl/pem.h>
#include <openssl/rand.h>
#include <openssl/rsa.h>
#include <openssl/x509.h>
#include "containers/containers_defs.h"
#include "core/controllers/serverController.h"
#include "core/scripts_registry.h"
#include "core/server_defs.h"
#include "settings.h"
#include "utilities.h"
WireguardConfigurator::WireguardConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController,
bool isAwg, QObject *parent)
: ConfiguratorBase(settings, serverController, parent), m_isAwg(isAwg)
{
m_serverConfigPath = m_isAwg ? amnezia::protocols::awg::serverConfigPath : amnezia::protocols::wireguard::serverConfigPath;
m_serverPublicKeyPath = m_isAwg ? amnezia::protocols::awg::serverPublicKeyPath : amnezia::protocols::wireguard::serverPublicKeyPath;
m_serverPskKeyPath = m_isAwg ? amnezia::protocols::awg::serverPskKeyPath : amnezia::protocols::wireguard::serverPskKeyPath;
m_configTemplate = m_isAwg ? ProtocolScriptType::awg_template : ProtocolScriptType::wireguard_template;
m_protocolName = m_isAwg ? config_key::awg : config_key::wireguard;
m_defaultPort = m_isAwg ? protocols::wireguard::defaultPort : protocols::awg::defaultPort;
}
WireguardConfigurator::ConnectionData WireguardConfigurator::genClientKeys()
{
// TODO review
constexpr size_t EDDSA_KEY_LENGTH = 32;
ConnectionData connData;
unsigned char buff[EDDSA_KEY_LENGTH];
int ret = RAND_priv_bytes(buff, EDDSA_KEY_LENGTH);
if (ret <= 0)
return connData;
EVP_PKEY *pKey = EVP_PKEY_new();
q_check_ptr(pKey);
pKey = EVP_PKEY_new_raw_private_key(EVP_PKEY_X25519, NULL, &buff[0], EDDSA_KEY_LENGTH);
size_t keySize = EDDSA_KEY_LENGTH;
// save private key
unsigned char priv[EDDSA_KEY_LENGTH];
EVP_PKEY_get_raw_private_key(pKey, priv, &keySize);
connData.clientPrivKey = QByteArray::fromRawData((char *)priv, keySize).toBase64();
// save public key
unsigned char pub[EDDSA_KEY_LENGTH];
EVP_PKEY_get_raw_public_key(pKey, pub, &keySize);
connData.clientPubKey = QByteArray::fromRawData((char *)pub, keySize).toBase64();
return connData;
}
WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardConfig(const ServerCredentials &credentials,
DockerContainer container,
const QJsonObject &containerConfig, ErrorCode &errorCode)
{
WireguardConfigurator::ConnectionData connData = WireguardConfigurator::genClientKeys();
connData.host = credentials.hostName;
connData.port = containerConfig.value(m_protocolName).toObject().value(config_key::port).toString(m_defaultPort);
if (connData.clientPrivKey.isEmpty() || connData.clientPubKey.isEmpty()) {
errorCode = ErrorCode::InternalError;
return connData;
}
// Get list of already created clients (only IP addresses)
QString nextIpNumber;
{
QString script = QString("cat %1 | grep AllowedIPs").arg(m_serverConfigPath);
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};
errorCode = m_serverController->runContainerScript(credentials, container, script, cbReadStdOut);
if (errorCode != ErrorCode::NoError) {
return connData;
}
stdOut.replace("AllowedIPs = ", "");
stdOut.replace("/32", "");
QStringList ips = stdOut.split("\n", Qt::SkipEmptyParts);
// remove extra IPs from each line for case when user manually edited the wg0.conf
// and added there more IPs for route his itnernal networks, like:
// ...
// AllowedIPs = 10.8.1.6/32, 192.168.1.0/24, 192.168.2.0/24, ...
// ...
// without this code - next IP would be 1 if last item in 'ips' has format above
QStringList vpnIps;
for (const auto &ip : ips) {
vpnIps.append(ip.split(",", Qt::SkipEmptyParts).first().trimmed());
}
ips = vpnIps;
// Calc next IP address
if (ips.isEmpty()) {
nextIpNumber = "2";
} else {
int next = ips.last().split(".").last().toInt() + 1;
if (next > 254) {
errorCode = ErrorCode::AddressPoolError;
return connData;
}
nextIpNumber = QString::number(next);
}
}
QString subnetIp = containerConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress);
{
QStringList l = subnetIp.split(".", Qt::SkipEmptyParts);
if (l.isEmpty()) {
errorCode = ErrorCode::AddressPoolError;
return connData;
}
l.removeLast();
l.append(nextIpNumber);
connData.clientIP = l.join(".");
}
// Get keys
connData.serverPubKey = m_serverController->getTextFileFromContainer(container, credentials, m_serverPublicKeyPath, errorCode);
connData.serverPubKey.replace("\n", "");
if (errorCode != ErrorCode::NoError) {
return connData;
}
connData.pskKey = m_serverController->getTextFileFromContainer(container, credentials, m_serverPskKeyPath, errorCode);
connData.pskKey.replace("\n", "");
if (errorCode != ErrorCode::NoError) {
return connData;
}
// Add client to config
QString configPart = QString("[Peer]\n"
"PublicKey = %1\n"
"PresharedKey = %2\n"
"AllowedIPs = %3/32\n\n")
.arg(connData.clientPubKey, connData.pskKey, connData.clientIP);
errorCode = m_serverController->uploadTextFileToContainer(container, credentials, configPart, m_serverConfigPath,
libssh::ScpOverwriteMode::ScpAppendToExisting);
if (errorCode != ErrorCode::NoError) {
return connData;
}
QString script = QString("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip %1)'").arg(m_serverConfigPath);
errorCode = m_serverController->runScript(
credentials, m_serverController->replaceVars(script, m_serverController->genVarsForScript(credentials, container)));
return connData;
}
QString WireguardConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode &errorCode)
{
QString scriptData = amnezia::scriptData(m_configTemplate, container);
QString config =
m_serverController->replaceVars(scriptData, m_serverController->genVarsForScript(credentials, container, containerConfig));
ConnectionData connData = prepareWireguardConfig(credentials, container, containerConfig, errorCode);
if (errorCode != ErrorCode::NoError) {
return "";
}
config.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", connData.clientPrivKey);
config.replace("$WIREGUARD_CLIENT_IP", connData.clientIP);
config.replace("$WIREGUARD_SERVER_PUBLIC_KEY", connData.serverPubKey);
config.replace("$WIREGUARD_PSK", connData.pskKey);
const QJsonObject &wireguarConfig = containerConfig.value(ProtocolProps::protoToString(Proto::WireGuard)).toObject();
QJsonObject jConfig;
jConfig[config_key::config] = config;
jConfig[config_key::hostName] = connData.host;
jConfig[config_key::port] = connData.port.toInt();
jConfig[config_key::client_priv_key] = connData.clientPrivKey;
jConfig[config_key::client_ip] = connData.clientIP;
jConfig[config_key::client_pub_key] = connData.clientPubKey;
jConfig[config_key::psk_key] = connData.pskKey;
jConfig[config_key::server_pub_key] = connData.serverPubKey;
jConfig[config_key::mtu] = wireguarConfig.value(config_key::mtu).toString(protocols::wireguard::defaultMtu);
jConfig[config_key::persistent_keep_alive] = "25";
QJsonArray allowedIps { "0.0.0.0/0", "::/0" };
jConfig[config_key::allowed_ips] = allowedIps;
jConfig[config_key::clientId] = connData.clientPubKey;
return QJsonDocument(jConfig).toJson();
}
QString WireguardConfigurator::processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString)
{
processConfigWithDnsSettings(dns, protocolConfigString);
return protocolConfigString;
}
QString WireguardConfigurator::processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString)
{
processConfigWithDnsSettings(dns, protocolConfigString);
return protocolConfigString;
}

View File

@@ -0,0 +1,50 @@
#ifndef WIREGUARD_CONFIGURATOR_H
#define WIREGUARD_CONFIGURATOR_H
#include <QObject>
#include <QProcessEnvironment>
#include "configurator_base.h"
#include "core/defs.h"
#include "core/scripts_registry.h"
class WireguardConfigurator : public ConfiguratorBase
{
Q_OBJECT
public:
WireguardConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, bool isAwg,
QObject *parent = nullptr);
struct ConnectionData
{
QString clientPrivKey; // client private key
QString clientPubKey; // client public key
QString clientIP; // internal client IP address
QString serverPubKey; // tls-auth key
QString pskKey; // preshared key
QString host; // host ip
QString port;
};
QString createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
ErrorCode &errorCode);
QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig, QString &protocolConfigString);
QString processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig, QString &protocolConfigString);
static ConnectionData genClientKeys();
private:
ConnectionData prepareWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode &errorCode);
bool m_isAwg;
QString m_serverConfigPath;
QString m_serverPublicKeyPath;
QString m_serverPskKeyPath;
amnezia::ProtocolScriptType m_configTemplate;
QString m_protocolName;
QString m_defaultPort;
};
#endif // WIREGUARD_CONFIGURATOR_H

View File

@@ -0,0 +1,42 @@
#include "xray_configurator.h"
#include <QFile>
#include <QJsonDocument>
#include <QJsonObject>
#include "containers/containers_defs.h"
#include "core/controllers/serverController.h"
#include "core/scripts_registry.h"
XrayConfigurator::XrayConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent)
: ConfiguratorBase(settings, serverController, parent)
{
}
QString XrayConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
ErrorCode &errorCode)
{
QString config = m_serverController->replaceVars(amnezia::scriptData(ProtocolScriptType::xray_template, container),
m_serverController->genVarsForScript(credentials, container, containerConfig));
QString xrayPublicKey =
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::PublicKeyPath, errorCode);
xrayPublicKey.replace("\n", "");
QString xrayUuid = m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::uuidPath, errorCode);
xrayUuid.replace("\n", "");
QString xrayShortId =
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::shortidPath, errorCode);
xrayShortId.replace("\n", "");
if (errorCode != ErrorCode::NoError) {
return "";
}
config.replace("$XRAY_CLIENT_ID", xrayUuid);
config.replace("$XRAY_PUBLIC_KEY", xrayPublicKey);
config.replace("$XRAY_SHORT_ID", xrayShortId);
return config;
}

View File

@@ -0,0 +1,19 @@
#ifndef XRAY_CONFIGURATOR_H
#define XRAY_CONFIGURATOR_H
#include <QObject>
#include "configurator_base.h"
#include "core/defs.h"
class XrayConfigurator : public ConfiguratorBase
{
Q_OBJECT
public:
XrayConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
QString createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
ErrorCode &errorCode);
};
#endif // XRAY_CONFIGURATOR_H

View File

@@ -0,0 +1,407 @@
#include "containers_defs.h"
#include "QJsonObject"
#include "QJsonDocument"
QDebug operator<<(QDebug debug, const amnezia::DockerContainer &c)
{
QDebugStateSaver saver(debug);
debug.nospace() << ContainerProps::containerToString(c);
return debug;
}
amnezia::DockerContainer ContainerProps::containerFromString(const QString &container)
{
QMetaEnum metaEnum = QMetaEnum::fromType<DockerContainer>();
for (int i = 0; i < metaEnum.keyCount(); ++i) {
DockerContainer c = static_cast<DockerContainer>(i);
if (container == containerToString(c))
return c;
}
return DockerContainer::None;
}
QString ContainerProps::containerToString(amnezia::DockerContainer c)
{
if (c == DockerContainer::None)
return "none";
if (c == DockerContainer::Cloak)
return "amnezia-openvpn-cloak";
QMetaEnum metaEnum = QMetaEnum::fromType<DockerContainer>();
QString containerKey = metaEnum.valueToKey(static_cast<int>(c));
return "amnezia-" + containerKey.toLower();
}
QString ContainerProps::containerTypeToString(amnezia::DockerContainer c)
{
if (c == DockerContainer::None)
return "none";
if (c == DockerContainer::Ipsec)
return "ikev2";
QMetaEnum metaEnum = QMetaEnum::fromType<DockerContainer>();
QString containerKey = metaEnum.valueToKey(static_cast<int>(c));
return containerKey.toLower();
}
QVector<amnezia::Proto> ContainerProps::protocolsForContainer(amnezia::DockerContainer container)
{
switch (container) {
case DockerContainer::None: return {};
case DockerContainer::OpenVpn: return { Proto::OpenVpn };
case DockerContainer::ShadowSocks: return { Proto::OpenVpn, Proto::ShadowSocks };
case DockerContainer::Cloak: return { Proto::OpenVpn, Proto::ShadowSocks, Proto::Cloak };
case DockerContainer::Ipsec: return { Proto::Ikev2 /*, Protocol::L2tp */ };
case DockerContainer::Xray: return { Proto::Xray };
case DockerContainer::SSXray: return { Proto::SSXray };
case DockerContainer::Dns: return { Proto::Dns };
case DockerContainer::Sftp: return { Proto::Sftp };
case DockerContainer::Socks5Proxy: return { Proto::Socks5Proxy };
default: return { defaultProtocol(container) };
}
}
QList<DockerContainer> ContainerProps::allContainers()
{
QMetaEnum metaEnum = QMetaEnum::fromType<DockerContainer>();
QList<DockerContainer> all;
for (int i = 0; i < metaEnum.keyCount(); ++i) {
all.append(static_cast<DockerContainer>(i));
}
return all;
}
QMap<DockerContainer, QString> ContainerProps::containerHumanNames()
{
return { { DockerContainer::None, "Not installed" },
{ DockerContainer::OpenVpn, "OpenVPN" },
{ DockerContainer::ShadowSocks, "OpenVPN over SS" },
{ DockerContainer::Cloak, "OpenVPN over Cloak" },
{ DockerContainer::WireGuard, "WireGuard" },
{ DockerContainer::Awg, "AmneziaWG" },
{ DockerContainer::Xray, "XRay" },
{ DockerContainer::Ipsec, QObject::tr("IPsec") },
{ DockerContainer::SSXray, "Shadowsocks"},
{ DockerContainer::TorWebSite, QObject::tr("Website in Tor network") },
{ DockerContainer::Dns, QObject::tr("AmneziaDNS") },
{ DockerContainer::Sftp, QObject::tr("SFTP file sharing service") },
{ DockerContainer::Socks5Proxy, QObject::tr("SOCKS5 proxy server") } };
}
QMap<DockerContainer, QString> ContainerProps::containerDescriptions()
{
return { { DockerContainer::OpenVpn,
QObject::tr("OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its "
"own security protocol with SSL/TLS for key exchange.") },
{ DockerContainer::ShadowSocks,
QObject::tr("Shadowsocks - masks VPN traffic, making it similar to normal web traffic, but it "
"may be recognized by analysis systems in some highly censored regions.") },
{ DockerContainer::Cloak,
QObject::tr("OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against "
"active-probing detection. Ideal for bypassing blocking in regions with the highest levels "
"of censorship.") },
{ DockerContainer::WireGuard,
QObject::tr("WireGuard - New popular VPN protocol with high performance, high speed and low power "
"consumption. Recommended for regions with low levels of censorship.") },
{ DockerContainer::Awg,
QObject::tr("AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, "
"but very resistant to blockages. "
"Recommended for regions with high levels of censorship.") },
{ DockerContainer::Xray,
QObject::tr("XRay with REALITY - Suitable for countries with the highest level of internet censorship. "
"Traffic masking as web traffic at the TLS level, and protection against detection by active probing methods.") },
{ DockerContainer::Ipsec,
QObject::tr("IKEv2/IPsec - Modern stable protocol, a bit faster than others, restores connection after "
"signal loss. It has native support on the latest versions of Android and iOS.") },
{ DockerContainer::TorWebSite, QObject::tr("Deploy a WordPress site on the Tor network in two clicks.") },
{ DockerContainer::Dns,
QObject::tr("Replace the current DNS server with your own. This will increase your privacy level.") },
{ DockerContainer::Sftp,
QObject::tr("Create a file vault on your server to securely store and transfer files.") },
{ DockerContainer::Socks5Proxy,
QObject::tr("") } };
}
QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
{
return {
{ DockerContainer::OpenVpn,
QObject::tr(
"OpenVPN stands as one of the most popular and time-tested VPN protocols available.\n"
"It employs its unique security protocol, "
"leveraging the strength of SSL/TLS for encryption and key exchange. "
"Furthermore, OpenVPN's support for a multitude of authentication methods makes it versatile and adaptable, "
"catering to a wide range of devices and operating systems. "
"Due to its open-source nature, OpenVPN benefits from extensive scrutiny by the global community, "
"which continually reinforces its security. "
"With a strong balance of performance, security, and compatibility, "
"OpenVPN remains a top choice for privacy-conscious individuals and businesses alike.\n\n"
"* Available in the AmneziaVPN across all platforms\n"
"* Normal power consumption on mobile devices\n"
"* Flexible customisation to suit user needs to work with different operating systems and devices\n"
"* Recognised by DPI analysis systems and therefore susceptible to blocking\n"
"* Can operate over both TCP and UDP network protocols.") },
{ DockerContainer::ShadowSocks,
QObject::tr("Shadowsocks, inspired by the SOCKS5 protocol, safeguards the connection using the AEAD cipher. "
"Although Shadowsocks is designed to be discreet and challenging to identify, it isn't identical to a standard HTTPS connection."
"However, certain traffic analysis systems might still detect a Shadowsocks connection. "
"Due to limited support in Amnezia, it's recommended to use AmneziaWG protocol.\n\n"
"* Available in the AmneziaVPN only on desktop platforms\n"
"* Configurable encryption protocol\n"
"* Detectable by some DPI systems\n"
"* Works over TCP network protocol.") },
{ DockerContainer::Cloak,
QObject::tr("This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for "
"protecting against blocking.\n\n"
"OpenVPN provides a secure VPN connection by encrypting all internet traffic between the client "
"and the server.\n\n"
"Cloak protects OpenVPN from detection and blocking. \n\n"
"Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, "
"and also protects the VPN from detection by Active Probing. This makes it very resistant to "
"being detected\n\n"
"Immediately after receiving the first data packet, Cloak authenticates the incoming connection. "
"If authentication fails, the plugin masks the server as a fake website and your VPN becomes "
"invisible to analysis systems.\n\n"
"If there is a extreme level of Internet censorship in your region, we advise you to use only "
"OpenVPN over Cloak from the first connection\n\n"
"* Available in the AmneziaVPN across all platforms\n"
"* High power consumption on mobile devices\n"
"* Flexible settings\n"
"* Not recognised by DPI analysis systems\n"
"* Works over TCP network protocol, 443 port.\n") },
{ DockerContainer::WireGuard,
QObject::tr("A relatively new popular VPN protocol with a simplified architecture.\n"
"WireGuard provides stable VPN connection and high performance on all devices. It uses hard-coded encryption "
"settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput.\n"
"WireGuard is very susceptible to blocking due to its distinct packet signatures. "
"Unlike some other VPN protocols that employ obfuscation techniques, "
"the consistent signature patterns of WireGuard packets can be more easily identified and "
"thus blocked by advanced Deep Packet Inspection (DPI) systems and other network monitoring tools.\n\n"
"* Available in the AmneziaVPN across all platforms\n"
"* Low power consumption\n"
"* Minimum number of settings\n"
"* Easily recognised by DPI analysis systems, susceptible to blocking\n"
"* Works over UDP network protocol.") },
{ DockerContainer::Awg,
QObject::tr("A modern iteration of the popular VPN protocol, "
"AmneziaWG builds upon the foundation set by WireGuard, "
"retaining its simplified architecture and high-performance capabilities across devices.\n"
"While WireGuard is known for its efficiency, "
"it had issues with being easily detected due to its distinct packet signatures. "
"AmneziaWG solves this problem by using better obfuscation methods, "
"making its traffic blend in with regular internet traffic.\n"
"This means that AmneziaWG keeps the fast performance of the original "
"while adding an extra layer of stealth, "
"making it a great choice for those wanting a fast and discreet VPN connection.\n\n"
"* Available in the AmneziaVPN across all platforms\n"
"* Low power consumption\n"
"* Minimum number of settings\n"
"* Not recognised by DPI analysis systems, resistant to blocking\n"
"* Works over UDP network protocol.") },
{ DockerContainer::Xray,
QObject::tr("The REALITY protocol, a pioneering development by the creators of XRay, "
"is specifically designed to counteract the highest levels of internet censorship through its novel approach to evasion.\n"
"It uniquely identifies censors during the TLS handshake phase, seamlessly operating as a proxy for legitimate clients while diverting censors to genuine websites like google.com, "
"thus presenting an authentic TLS certificate and data. \n"
"This advanced capability differentiates REALITY from similar technologies by its ability to disguise web traffic as coming from random, "
"legitimate sites without the need for specific configurations. \n"
"Unlike older protocols such as VMess, VLESS, and the XTLS-Vision transport, "
"REALITY's innovative \"friend or foe\" recognition at the TLS handshake enhances security and circumvents detection by sophisticated DPI systems employing active probing techniques. "
"This makes REALITY a robust solution for maintaining internet freedom in environments with stringent censorship.")
},
{ DockerContainer::Ipsec,
QObject::tr("IKEv2, paired with the IPSec encryption layer, stands as a modern and stable VPN protocol.\n"
"One of its distinguishing features is its ability to swiftly switch between networks and devices, "
"making it particularly adaptive in dynamic network environments. \n"
"While it offers a blend of security, stability, and speed, "
"it's essential to note that IKEv2 can be easily detected and is susceptible to blocking.\n\n"
"* Available in the AmneziaVPN only on Windows\n"
"* Low power consumption, on mobile devices\n"
"* Minimal configuration\n"
"* Recognised by DPI analysis systems\n"
"* Works over UDP network protocol, ports 500 and 4500.") },
{ DockerContainer::TorWebSite, QObject::tr("Website in Tor network") },
{ DockerContainer::Dns, QObject::tr("DNS Service") },
{ DockerContainer::Sftp,
QObject::tr("After installation, Amnezia will create a\n\n file storage on your server. "
"You will be able to access it using\n FileZilla or other SFTP clients, "
"as well as mount the disk on your device to access\n it directly from your device.\n\n"
"For more detailed information, you can\n find it in the support section under \"Create SFTP file storage.\" ") },
{ DockerContainer::Socks5Proxy, QObject::tr("SOCKS5 proxy server") }
};
}
amnezia::ServiceType ContainerProps::containerService(DockerContainer c)
{
return ProtocolProps::protocolService(defaultProtocol(c));
}
Proto ContainerProps::defaultProtocol(DockerContainer c)
{
switch (c) {
case DockerContainer::None: return Proto::Any;
case DockerContainer::OpenVpn: return Proto::OpenVpn;
case DockerContainer::Cloak: return Proto::Cloak;
case DockerContainer::ShadowSocks: return Proto::ShadowSocks;
case DockerContainer::WireGuard: return Proto::WireGuard;
case DockerContainer::Awg: return Proto::Awg;
case DockerContainer::Xray: return Proto::Xray;
case DockerContainer::Ipsec: return Proto::Ikev2;
case DockerContainer::SSXray: return Proto::SSXray;
case DockerContainer::TorWebSite: return Proto::TorWebSite;
case DockerContainer::Dns: return Proto::Dns;
case DockerContainer::Sftp: return Proto::Sftp;
case DockerContainer::Socks5Proxy: return Proto::Socks5Proxy;
default: return Proto::Any;
}
}
bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
{
#ifdef Q_OS_WINDOWS
return true;
#elif defined(Q_OS_IOS)
switch (c) {
case DockerContainer::WireGuard: return true;
case DockerContainer::OpenVpn: return true;
case DockerContainer::Awg: return true;
case DockerContainer::Xray: return true;
case DockerContainer::Cloak: return true;
case DockerContainer::SSXray: return true;
// case DockerContainer::ShadowSocks: return true;
default: return false;
}
#elif defined(Q_OS_MAC)
switch (c) {
case DockerContainer::WireGuard: return true;
case DockerContainer::Ipsec: return false;
default: return true;
}
#elif defined(Q_OS_ANDROID)
switch (c) {
case DockerContainer::WireGuard: return true;
case DockerContainer::OpenVpn: return true;
case DockerContainer::ShadowSocks: return false;
case DockerContainer::Awg: return true;
case DockerContainer::Cloak: return true;
case DockerContainer::Xray: return true;
case DockerContainer::SSXray: return true;
default: return false;
}
#elif defined(Q_OS_LINUX)
switch (c) {
case DockerContainer::Ipsec: return false;
default: return true;
}
#else
return false;
#endif
}
QStringList ContainerProps::fixedPortsForContainer(DockerContainer c)
{
switch (c) {
case DockerContainer::Ipsec: return QStringList { "500", "4500" };
default: return {};
}
}
bool ContainerProps::isEasySetupContainer(DockerContainer container)
{
switch (container) {
case DockerContainer::WireGuard: return true;
case DockerContainer::Awg: return true;
// case DockerContainer::Cloak: return true;
default: return false;
}
}
QString ContainerProps::easySetupHeader(DockerContainer container)
{
switch (container) {
case DockerContainer::WireGuard: return tr("Low");
case DockerContainer::Awg: return tr("High");
// case DockerContainer::Cloak: return tr("Extreme");
default: return "";
}
}
QString ContainerProps::easySetupDescription(DockerContainer container)
{
switch (container) {
case DockerContainer::WireGuard: return tr("I just want to increase the level of my privacy.");
case DockerContainer::Awg: return tr("I want to bypass censorship. This option recommended in most cases.");
// case DockerContainer::Cloak:
// return tr("Most VPN protocols are blocked. Recommended if other options are not working.");
default: return "";
}
}
int ContainerProps::easySetupOrder(DockerContainer container)
{
switch (container) {
case DockerContainer::WireGuard: return 3;
case DockerContainer::Awg: return 2;
// case DockerContainer::Cloak: return 1;
default: return 0;
}
}
bool ContainerProps::isShareable(DockerContainer container)
{
switch (container) {
case DockerContainer::TorWebSite: return false;
case DockerContainer::Dns: return false;
case DockerContainer::Sftp: return false;
case DockerContainer::Socks5Proxy: return false;
default: return true;
}
}
QJsonObject ContainerProps::getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig)
{
QString protocolConfigString = containerConfig.value(ProtocolProps::protoToString(protocol))
.toObject()
.value(config_key::last_config)
.toString();
return QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
}
int ContainerProps::installPageOrder(DockerContainer container)
{
switch (container) {
case DockerContainer::OpenVpn: return 4;
case DockerContainer::Cloak: return 5;
case DockerContainer::ShadowSocks: return 6;
case DockerContainer::WireGuard: return 2;
case DockerContainer::Awg: return 1;
case DockerContainer::Xray: return 3;
case DockerContainer::Ipsec: return 7;
case DockerContainer::SSXray: return 8;
default: return 0;
}
}

View File

@@ -0,0 +1,89 @@
#ifndef CONTAINERS_DEFS_H
#define CONTAINERS_DEFS_H
#include <QObject>
#include <QQmlEngine>
#include "../protocols/protocols_defs.h"
using namespace amnezia;
namespace amnezia
{
namespace ContainerEnumNS
{
Q_NAMESPACE
enum DockerContainer {
None = 0,
Awg,
WireGuard,
OpenVpn,
Cloak,
ShadowSocks,
Ipsec,
Xray,
SSXray,
// non-vpn
TorWebSite,
Dns,
Sftp,
Socks5Proxy
};
Q_ENUM_NS(DockerContainer)
} // namespace ContainerEnumNS
using namespace ContainerEnumNS;
using namespace ProtocolEnumNS;
class ContainerProps : public QObject
{
Q_OBJECT
public:
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 QList<amnezia::DockerContainer> allContainers();
Q_INVOKABLE static QMap<amnezia::DockerContainer, QString> containerHumanNames();
Q_INVOKABLE static QMap<amnezia::DockerContainer, QString> containerDescriptions();
Q_INVOKABLE static QMap<amnezia::DockerContainer, QString> containerDetailedDescriptions();
// these protocols will be displayed in container settings
Q_INVOKABLE static QVector<amnezia::Proto> protocolsForContainer(amnezia::DockerContainer container);
Q_INVOKABLE static amnezia::ServiceType containerService(amnezia::DockerContainer c);
// binding between Docker container and main protocol of given container
// it may be changed fot future containers :)
Q_INVOKABLE static amnezia::Proto defaultProtocol(amnezia::DockerContainer c);
Q_INVOKABLE static bool isSupportedByCurrentPlatform(amnezia::DockerContainer c);
Q_INVOKABLE static QStringList fixedPortsForContainer(amnezia::DockerContainer c);
static bool isEasySetupContainer(amnezia::DockerContainer container);
static QString easySetupHeader(amnezia::DockerContainer container);
static QString easySetupDescription(amnezia::DockerContainer container);
static int easySetupOrder(amnezia::DockerContainer container);
static bool isShareable(amnezia::DockerContainer container);
static QJsonObject getProtocolConfigFromContainer(const amnezia::Proto protocol, const QJsonObject &containerConfig);
static int installPageOrder(amnezia::DockerContainer container);
};
static void declareQmlContainerEnum()
{
qmlRegisterUncreatableMetaObject(ContainerEnumNS::staticMetaObject, "ContainerEnum", 1, 0, "ContainerEnum",
"Error: only enums");
}
} // namespace amnezia
QDebug operator<<(QDebug debug, const amnezia::DockerContainer &c);
#endif // CONTAINERS_DEFS_H

View File

@@ -1,109 +0,0 @@
#include "awgConfigurator.h"
#include "core/utils/protocolEnum.h"
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/models/containerConfig.h"
#include "core/models/protocols/awgProtocolConfig.h"
#include <QJsonDocument>
#include <QJsonObject>
using namespace amnezia;
AwgConfigurator::AwgConfigurator(SshSession* sshSession, QObject *parent)
: WireguardConfigurator(sshSession, true, parent)
{
}
ProtocolConfig AwgConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const ContainerConfig &containerConfig,
const DnsSettings &dnsSettings,
ErrorCode &errorCode)
{
const AwgServerConfig* serverConfig = nullptr;
const AwgClientConfig* clientConfig = nullptr;
if (auto* awgProtocolConfig = containerConfig.getAwgProtocolConfig()) {
serverConfig = &awgProtocolConfig->serverConfig;
if (awgProtocolConfig->clientConfig.has_value()) {
clientConfig = &awgProtocolConfig->clientConfig.value();
}
}
ProtocolConfig wireguardConfig = WireguardConfigurator::createConfig(credentials, container, containerConfig, dnsSettings, errorCode);
if (errorCode != ErrorCode::NoError) {
return AwgProtocolConfig{};
}
WireGuardProtocolConfig* wgConfig = wireguardConfig.as<WireGuardProtocolConfig>();
if (!wgConfig || !wgConfig->clientConfig.has_value()) {
errorCode = ErrorCode::InternalError;
return AwgProtocolConfig{};
}
QString awgConfig = wgConfig->clientConfig->nativeConfig;
QMap<QString, QString> configMap;
auto configLines = awgConfig.split("\n");
for (auto &line : configLines) {
auto trimmedLine = line.trimmed();
if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) {
continue;
} else {
QStringList parts = trimmedLine.split(" = ");
if (parts.count() == 2) {
configMap.insert(parts[0].trimmed(), parts[1].trimmed());
}
}
}
AwgProtocolConfig protocolConfig;
if (serverConfig) {
protocolConfig.serverConfig = *serverConfig;
}
AwgClientConfig newClientConfig;
newClientConfig.nativeConfig = awgConfig;
newClientConfig.hostName = wgConfig->clientConfig->hostName;
newClientConfig.port = wgConfig->clientConfig->port;
newClientConfig.clientIp = wgConfig->clientConfig->clientIp;
newClientConfig.clientPrivateKey = wgConfig->clientConfig->clientPrivateKey;
newClientConfig.clientPublicKey = wgConfig->clientConfig->clientPublicKey;
newClientConfig.serverPublicKey = wgConfig->clientConfig->serverPublicKey;
newClientConfig.presharedKey = wgConfig->clientConfig->presharedKey;
newClientConfig.clientId = wgConfig->clientConfig->clientId;
newClientConfig.allowedIps = wgConfig->clientConfig->allowedIps;
newClientConfig.persistentKeepAlive = wgConfig->clientConfig->persistentKeepAlive;
QString mtu = protocols::awg::defaultMtu;
if (clientConfig && !clientConfig->mtu.isEmpty()) {
mtu = clientConfig->mtu;
}
newClientConfig.mtu = mtu;
newClientConfig.junkPacketCount = configMap.value(configKey::junkPacketCount);
newClientConfig.junkPacketMinSize = configMap.value(configKey::junkPacketMinSize);
newClientConfig.junkPacketMaxSize = configMap.value(configKey::junkPacketMaxSize);
newClientConfig.initPacketJunkSize = configMap.value(configKey::initPacketJunkSize);
newClientConfig.responsePacketJunkSize = configMap.value(configKey::responsePacketJunkSize);
newClientConfig.initPacketMagicHeader = configMap.value(configKey::initPacketMagicHeader);
newClientConfig.responsePacketMagicHeader = configMap.value(configKey::responsePacketMagicHeader);
newClientConfig.underloadPacketMagicHeader = configMap.value(configKey::underloadPacketMagicHeader);
newClientConfig.transportPacketMagicHeader = configMap.value(configKey::transportPacketMagicHeader);
newClientConfig.specialJunk1 = configMap.value(configKey::specialJunk1);
newClientConfig.specialJunk2 = configMap.value(configKey::specialJunk2);
newClientConfig.specialJunk3 = configMap.value(configKey::specialJunk3);
newClientConfig.specialJunk4 = configMap.value(configKey::specialJunk4);
newClientConfig.specialJunk5 = configMap.value(configKey::specialJunk5);
if (container == DockerContainer::Awg2) {
newClientConfig.cookieReplyPacketJunkSize = configMap.value(configKey::cookieReplyPacketJunkSize);
newClientConfig.transportPacketJunkSize = configMap.value(configKey::transportPacketJunkSize);
}
newClientConfig.isObfuscationEnabled = false;
protocolConfig.setClientConfig(newClientConfig);
return protocolConfig;
}

View File

@@ -1,20 +0,0 @@
#ifndef AWGCONFIGURATOR_H
#define AWGCONFIGURATOR_H
#include <QObject>
#include "wireguardConfigurator.h"
class AwgConfigurator : public WireguardConfigurator
{
Q_OBJECT
public:
AwgConfigurator(SshSession* sshSession, QObject *parent = nullptr);
amnezia::ProtocolConfig createConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container,
const amnezia::ContainerConfig &containerConfig,
const amnezia::DnsSettings &dnsSettings,
amnezia::ErrorCode &errorCode) override;
};
#endif // AWGCONFIGURATOR_H

View File

@@ -1,50 +0,0 @@
#include "configuratorBase.h"
#include "core/configurators/awgConfigurator.h"
#include "core/configurators/ikev2Configurator.h"
#include "core/configurators/openVpnConfigurator.h"
#include "core/configurators/wireguardConfigurator.h"
#include "core/configurators/xrayConfigurator.h"
using namespace amnezia;
ConfiguratorBase::ConfiguratorBase(SshSession* sshSession, QObject *parent)
: QObject { parent }, m_sshSession(sshSession)
{
}
QScopedPointer<ConfiguratorBase> ConfiguratorBase::create(Proto protocol,
SshSession* sshSession)
{
switch (protocol) {
case Proto::OpenVpn: return QScopedPointer<ConfiguratorBase>(new OpenVpnConfigurator(sshSession));
case Proto::WireGuard: return QScopedPointer<ConfiguratorBase>(new WireguardConfigurator(sshSession, false));
case Proto::Awg: return QScopedPointer<ConfiguratorBase>(new AwgConfigurator(sshSession));
case Proto::Ikev2: return QScopedPointer<ConfiguratorBase>(new Ikev2Configurator(sshSession));
case Proto::Xray: return QScopedPointer<ConfiguratorBase>(new XrayConfigurator(sshSession));
case Proto::SSXray: return QScopedPointer<ConfiguratorBase>(new XrayConfigurator(sshSession));
default: return QScopedPointer<ConfiguratorBase>();
}
}
ProtocolConfig ConfiguratorBase::processConfigWithLocalSettings(const ConnectionSettings &settings,
ProtocolConfig protocolConfig)
{
applyDnsToNativeConfig(settings.dns, protocolConfig);
return protocolConfig;
}
ProtocolConfig ConfiguratorBase::processConfigWithExportSettings(const ExportSettings &settings,
ProtocolConfig protocolConfig)
{
applyDnsToNativeConfig(settings.dns, protocolConfig);
return protocolConfig;
}
void ConfiguratorBase::applyDnsToNativeConfig(const DnsSettings &dns, ProtocolConfig &protocolConfig)
{
QString config = protocolConfig.nativeConfig();
config.replace("$PRIMARY_DNS", dns.primaryDns);
config.replace("$SECONDARY_DNS", dns.secondaryDns);
protocolConfig.setNativeConfig(config);
}

View File

@@ -1,43 +0,0 @@
#ifndef CONFIGURATORBASE_H
#define CONFIGURATORBASE_H
#include <QObject>
#include <QScopedPointer>
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/errorCodes.h"
#include "core/utils/routeModes.h"
#include "core/utils/commonStructs.h"
#include "core/models/containerConfig.h"
#include "core/models/protocolConfig.h"
class SshSession;
class ConfiguratorBase : public QObject
{
Q_OBJECT
public:
explicit ConfiguratorBase(SshSession* sshSession, QObject *parent = nullptr);
static QScopedPointer<ConfiguratorBase> create(amnezia::Proto protocol,
SshSession* sshSession);
virtual amnezia::ProtocolConfig createConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container,
const amnezia::ContainerConfig &containerConfig,
const amnezia::DnsSettings &dnsSettings,
amnezia::ErrorCode &errorCode) = 0;
virtual amnezia::ProtocolConfig processConfigWithLocalSettings(const amnezia::ConnectionSettings &settings,
amnezia::ProtocolConfig protocolConfig);
virtual amnezia::ProtocolConfig processConfigWithExportSettings(const amnezia::ExportSettings &settings,
amnezia::ProtocolConfig protocolConfig);
protected:
void applyDnsToNativeConfig(const amnezia::DnsSettings &dns, amnezia::ProtocolConfig &protocolConfig);
SshSession* m_sshSession;
};
#endif // CONFIGURATORBASE_H

View File

@@ -1,39 +0,0 @@
#ifndef IKEV2_CONFIGURATOR_H
#define IKEV2_CONFIGURATOR_H
#include <QObject>
#include <QProcessEnvironment>
#include "configuratorBase.h"
#include "core/utils/errorCodes.h"
#include "core/utils/routeModes.h"
#include "core/utils/commonStructs.h"
class Ikev2Configurator : public ConfiguratorBase
{
Q_OBJECT
public:
Ikev2Configurator(SshSession* sshSession, QObject *parent = nullptr);
struct ConnectionData {
QByteArray clientCert; // p12 client cert
QByteArray caCert; // p12 server cert
QString clientId;
QString password; // certificate password
QString host; // host ip
};
amnezia::ProtocolConfig createConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container,
const amnezia::ContainerConfig &containerConfig,
const amnezia::DnsSettings &dnsSettings,
amnezia::ErrorCode &errorCode) override;
QString genIkev2Config(const ConnectionData &connData);
QString genMobileConfig(const ConnectionData &connData);
QString genStrongSwanConfig(const ConnectionData &connData);
ConnectionData prepareIkev2Config(const amnezia::ServerCredentials &credentials,
amnezia::DockerContainer container, amnezia::ErrorCode &errorCode);
};
#endif // IKEV2_CONFIGURATOR_H

View File

@@ -1,49 +0,0 @@
#ifndef OPENVPN_CONFIGURATOR_H
#define OPENVPN_CONFIGURATOR_H
#include <QObject>
#include <QProcessEnvironment>
#include "configuratorBase.h"
#include "core/utils/errorCodes.h"
#include "core/utils/routeModes.h"
#include "core/utils/commonStructs.h"
class OpenVpnConfigurator : public ConfiguratorBase
{
Q_OBJECT
public:
OpenVpnConfigurator(SshSession* sshSession, QObject *parent = nullptr);
struct ConnectionData
{
QString clientId;
QString request; // certificate request
QString privKey; // client private key
QString clientCert; // client signed certificate
QString caCert; // server certificate
QString taKey; // tls-auth key
QString host; // host ip
};
amnezia::ProtocolConfig createConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container,
const amnezia::ContainerConfig &containerConfig,
const amnezia::DnsSettings &dnsSettings,
amnezia::ErrorCode &errorCode) override;
amnezia::ProtocolConfig processConfigWithLocalSettings(const amnezia::ConnectionSettings &settings,
amnezia::ProtocolConfig protocolConfig) override;
amnezia::ProtocolConfig processConfigWithExportSettings(const amnezia::ExportSettings &settings,
amnezia::ProtocolConfig protocolConfig) override;
static ConnectionData createCertRequest();
private:
ConnectionData prepareOpenVpnConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container,
const amnezia::DnsSettings &dnsSettings,
amnezia::ErrorCode &errorCode);
amnezia::ErrorCode signCert(amnezia::DockerContainer container, const amnezia::ServerCredentials &credentials,
const amnezia::DnsSettings &dnsSettings, QString clientId);
};
#endif // OPENVPN_CONFIGURATOR_H

View File

@@ -1,288 +0,0 @@
#include "wireguardConfigurator.h"
#include <QDebug>
#include <QJsonDocument>
#include <QProcess>
#include <QRegularExpression>
#include <QString>
#include <QTemporaryDir>
#include <QTemporaryFile>
#include <openssl/pem.h>
#include <openssl/rand.h>
#include <openssl/rsa.h>
#include <openssl/x509.h>
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/selfhosted/sshSession.h"
#include "core/utils/selfhosted/scriptsRegistry.h"
#include "core/utils/protocolEnum.h"
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/utils/utilities.h"
#include "core/models/containerConfig.h"
#include "core/models/protocols/wireGuardProtocolConfig.h"
#include "core/models/protocols/awgProtocolConfig.h"
#include <QJsonArray>
using namespace amnezia;
WireguardConfigurator::WireguardConfigurator(SshSession* sshSession, bool isAwg,
QObject *parent)
: ConfiguratorBase(sshSession, parent), m_isAwg(isAwg)
{
m_serverConfigPath =
m_isAwg ? amnezia::protocols::awg::serverConfigPath : amnezia::protocols::wireguard::serverConfigPath;
m_serverPublicKeyPath =
m_isAwg ? amnezia::protocols::awg::serverPublicKeyPath : amnezia::protocols::wireguard::serverPublicKeyPath;
m_serverPskKeyPath =
m_isAwg ? amnezia::protocols::awg::serverPskKeyPath : amnezia::protocols::wireguard::serverPskKeyPath;
m_configTemplate = m_isAwg ? ProtocolScriptType::awg_template : ProtocolScriptType::wireguard_template;
m_protocolName = m_isAwg ? configKey::awg : configKey::wireguard;
m_defaultPort = m_isAwg ? protocols::awg::defaultPort : protocols::wireguard::defaultPort;
}
WireguardConfigurator::ConnectionData WireguardConfigurator::genClientKeys()
{
// TODO review
constexpr size_t EDDSA_KEY_LENGTH = 32;
ConnectionData connData;
unsigned char buff[EDDSA_KEY_LENGTH];
int ret = RAND_priv_bytes(buff, EDDSA_KEY_LENGTH);
if (ret <= 0)
return connData;
EVP_PKEY *pKey = EVP_PKEY_new();
q_check_ptr(pKey);
pKey = EVP_PKEY_new_raw_private_key(EVP_PKEY_X25519, NULL, &buff[0], EDDSA_KEY_LENGTH);
size_t keySize = EDDSA_KEY_LENGTH;
// save private key
unsigned char priv[EDDSA_KEY_LENGTH];
EVP_PKEY_get_raw_private_key(pKey, priv, &keySize);
connData.clientPrivKey = QByteArray::fromRawData((char *)priv, keySize).toBase64();
// save public key
unsigned char pub[EDDSA_KEY_LENGTH];
EVP_PKEY_get_raw_public_key(pKey, pub, &keySize);
connData.clientPubKey = QByteArray::fromRawData((char *)pub, keySize).toBase64();
return connData;
}
QList<QHostAddress> WireguardConfigurator::getIpsFromConf(const QString &input)
{
QRegularExpression regex("AllowedIPs = (\\d+\\.\\d+\\.\\d+\\.\\d+)");
QRegularExpressionMatchIterator matchIterator = regex.globalMatch(input);
QList<QHostAddress> ips;
while (matchIterator.hasNext()) {
QRegularExpressionMatch match = matchIterator.next();
const QString address_string { match.captured(1) };
const QHostAddress address { address_string };
if (address.isNull()) {
qWarning() << "Couldn't recognize the ip address: " << address_string;
} else {
ips << address;
}
}
return ips;
}
WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardConfig(const ServerCredentials &credentials,
DockerContainer container,
const WireGuardServerConfig* serverConfig,
const AwgServerConfig* awgServerConfig,
const DnsSettings &dnsSettings,
ErrorCode &errorCode)
{
WireguardConfigurator::ConnectionData connData = WireguardConfigurator::genClientKeys();
connData.host = credentials.hostName;
QString portStr = m_defaultPort;
if (serverConfig && !serverConfig->port.isEmpty()) {
portStr = serverConfig->port;
} else if (awgServerConfig && !awgServerConfig->port.isEmpty()) {
portStr = awgServerConfig->port;
}
connData.port = portStr;
if (connData.clientPrivKey.isEmpty() || connData.clientPubKey.isEmpty()) {
errorCode = ErrorCode::InternalError;
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 stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};
errorCode = m_sshSession->runContainerScript(credentials, container, getIpsScript, cbReadStdOut);
if (errorCode != ErrorCode::NoError) {
return connData;
}
auto ips = getIpsFromConf(stdOut);
QHostAddress nextIp = [&] {
QHostAddress result;
QHostAddress lastIp;
QString subnetAddress = protocols::wireguard::defaultSubnetAddress;
if (serverConfig && !serverConfig->subnetAddress.isEmpty()) {
subnetAddress = serverConfig->subnetAddress;
} else if (awgServerConfig && !awgServerConfig->subnetAddress.isEmpty()) {
subnetAddress = awgServerConfig->subnetAddress;
}
if (ips.empty()) {
lastIp.setAddress(subnetAddress);
} else {
lastIp = ips.last();
}
quint8 lastOctet = static_cast<quint8>(lastIp.toIPv4Address());
switch (lastOctet) {
case 254: result.setAddress(lastIp.toIPv4Address() + 3); break;
case 255: result.setAddress(lastIp.toIPv4Address() + 2); break;
default: result.setAddress(lastIp.toIPv4Address() + 1); break;
}
return result;
}();
connData.clientIP = nextIp.toString();
// Get keys
connData.serverPubKey =
m_sshSession->getTextFileFromContainer(container, credentials, m_serverPublicKeyPath, errorCode);
connData.serverPubKey.replace("\n", "");
if (errorCode != ErrorCode::NoError) {
return connData;
}
connData.pskKey = m_sshSession->getTextFileFromContainer(container, credentials, m_serverPskKeyPath, errorCode);
connData.pskKey.replace("\n", "");
if (errorCode != ErrorCode::NoError) {
return connData;
}
// Add client to config
QString configPart = QString("[Peer]\n"
"PublicKey = %1\n"
"PresharedKey = %2\n"
"AllowedIPs = %3/32\n\n")
.arg(connData.clientPubKey, connData.pskKey, connData.clientIP);
errorCode = m_sshSession->uploadTextFileToContainer(container, credentials, configPart, configPath,
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);
errorCode = m_sshSession->runScript(
credentials,
m_sshSession->replaceVars(script, amnezia::genBaseVars(credentials, container, dnsSettings.primaryDns, dnsSettings.secondaryDns)));
return connData;
}
ProtocolConfig WireguardConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
const ContainerConfig &containerConfig,
const DnsSettings &dnsSettings,
ErrorCode &errorCode)
{
const WireGuardServerConfig* wireguardServerConfig = nullptr;
const WireGuardClientConfig* wireguardClientConfig = nullptr;
const AwgServerConfig* awgServerConfig = nullptr;
const AwgClientConfig* awgClientConfig = nullptr;
if (auto* wireGuardProtocolConfig = containerConfig.getWireGuardProtocolConfig()) {
wireguardServerConfig = &wireGuardProtocolConfig->serverConfig;
if (wireGuardProtocolConfig->clientConfig.has_value()) {
wireguardClientConfig = &wireGuardProtocolConfig->clientConfig.value();
}
} else if (auto* awgProtocolConfig = containerConfig.getAwgProtocolConfig()) {
awgServerConfig = &awgProtocolConfig->serverConfig;
if (awgProtocolConfig->clientConfig.has_value()) {
awgClientConfig = &awgProtocolConfig->clientConfig.value();
}
}
amnezia::ScriptVars vars = amnezia::genBaseVars(credentials, container, dnsSettings.primaryDns, dnsSettings.secondaryDns);
vars.append(amnezia::genProtocolVarsForContainer(container, containerConfig));
QString scriptData = amnezia::scriptData(m_configTemplate, container);
QString config = m_sshSession->replaceVars(scriptData, vars);
ConnectionData connData = prepareWireguardConfig(credentials, container, wireguardServerConfig, awgServerConfig, dnsSettings, errorCode);
if (errorCode != ErrorCode::NoError) {
return WireGuardProtocolConfig{};
}
config.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", connData.clientPrivKey);
config.replace("$WIREGUARD_CLIENT_IP", connData.clientIP);
config.replace("$WIREGUARD_SERVER_PUBLIC_KEY", connData.serverPubKey);
config.replace("$WIREGUARD_PSK", connData.pskKey);
QString mtu = protocols::wireguard::defaultMtu;
if (wireguardClientConfig && !wireguardClientConfig->mtu.isEmpty()) {
mtu = wireguardClientConfig->mtu;
} else if (awgClientConfig && !awgClientConfig->mtu.isEmpty()) {
mtu = awgClientConfig->mtu;
}
WireGuardProtocolConfig protocolConfig;
if (wireguardServerConfig) {
protocolConfig.serverConfig = *wireguardServerConfig;
}
WireGuardClientConfig clientConfig;
clientConfig.nativeConfig = config;
clientConfig.hostName = connData.host;
clientConfig.port = connData.port.toInt();
clientConfig.clientIp = connData.clientIP;
clientConfig.clientPrivateKey = connData.clientPrivKey;
clientConfig.clientPublicKey = connData.clientPubKey;
clientConfig.serverPublicKey = connData.serverPubKey;
clientConfig.presharedKey = connData.pskKey;
clientConfig.clientId = connData.clientPubKey;
clientConfig.allowedIps = QStringList { "0.0.0.0/0", "::/0" };
clientConfig.persistentKeepAlive = "25";
clientConfig.mtu = mtu;
clientConfig.isObfuscationEnabled = false;
protocolConfig.setClientConfig(clientConfig);
return protocolConfig;
}
ProtocolConfig WireguardConfigurator::processConfigWithLocalSettings(const ConnectionSettings &settings,
ProtocolConfig protocolConfig)
{
return ConfiguratorBase::processConfigWithLocalSettings(settings, protocolConfig);
}
ProtocolConfig WireguardConfigurator::processConfigWithExportSettings(const ExportSettings &settings,
ProtocolConfig protocolConfig)
{
return ConfiguratorBase::processConfigWithExportSettings(settings, protocolConfig);
}

View File

@@ -1,61 +0,0 @@
#ifndef WIREGUARD_CONFIGURATOR_H
#define WIREGUARD_CONFIGURATOR_H
#include <QHostAddress>
#include <QObject>
#include <QProcessEnvironment>
#include "configuratorBase.h"
#include "core/utils/errorCodes.h"
#include "core/utils/routeModes.h"
#include "core/utils/commonStructs.h"
#include "core/utils/selfhosted/scriptsRegistry.h"
class WireguardConfigurator : public ConfiguratorBase
{
Q_OBJECT
public:
WireguardConfigurator(SshSession* sshSession,
bool isAwg, QObject *parent = nullptr);
struct ConnectionData
{
QString clientPrivKey; // client private key
QString clientPubKey; // client public key
QString clientIP; // internal client IP address
QString serverPubKey; // tls-auth key
QString pskKey; // preshared key
QString host; // host ip
QString port;
};
amnezia::ProtocolConfig createConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container,
const amnezia::ContainerConfig &containerConfig,
const amnezia::DnsSettings &dnsSettings,
amnezia::ErrorCode &errorCode) override;
amnezia::ProtocolConfig processConfigWithLocalSettings(const amnezia::ConnectionSettings &settings,
amnezia::ProtocolConfig protocolConfig) override;
amnezia::ProtocolConfig processConfigWithExportSettings(const amnezia::ExportSettings &settings,
amnezia::ProtocolConfig protocolConfig) override;
static ConnectionData genClientKeys();
private:
QList<QHostAddress> getIpsFromConf(const QString &input);
ConnectionData prepareWireguardConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container,
const amnezia::WireGuardServerConfig* serverConfig,
const amnezia::AwgServerConfig* awgServerConfig,
const amnezia::DnsSettings &dnsSettings,
amnezia::ErrorCode &errorCode);
bool m_isAwg;
QString m_serverConfigPath;
QString m_serverPublicKeyPath;
QString m_serverPskKeyPath;
amnezia::ProtocolScriptType m_configTemplate;
QString m_protocolName;
QString m_defaultPort;
};
#endif // WIREGUARD_CONFIGURATOR_H

View File

@@ -1,202 +0,0 @@
#include "xrayConfigurator.h"
#include <QFile>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QUuid>
#include "logger.h"
#include "core/utils/containerEnum.h"
#include "core/utils/containers/containerUtils.h"
#include "core/utils/protocolEnum.h"
#include "core/utils/selfhosted/sshSession.h"
#include "core/utils/selfhosted/scriptsRegistry.h"
#include "core/utils/protocolEnum.h"
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/constants/protocolConstants.h"
#include "core/models/containerConfig.h"
#include "core/models/protocols/xrayProtocolConfig.h"
namespace {
Logger logger("XrayConfigurator");
}
XrayConfigurator::XrayConfigurator(SshSession* sshSession, QObject *parent)
: ConfiguratorBase(sshSession, parent)
{
}
QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentials, DockerContainer container,
const ContainerConfig &containerConfig,
const DnsSettings &dnsSettings,
ErrorCode &errorCode)
{
// Generate new UUID for client
QString clientId = QUuid::createUuid().toString(QUuid::WithoutBraces);
// Get current server config
QString currentConfig = m_sshSession->getTextFileFromContainer(
container, credentials, amnezia::protocols::xray::serverConfigPath, errorCode);
if (errorCode != ErrorCode::NoError) {
logger.error() << "Failed to get server config file";
return "";
}
// Parse current config as JSON
QJsonDocument doc = QJsonDocument::fromJson(currentConfig.toUtf8());
if (doc.isNull() || !doc.isObject()) {
logger.error() << "Failed to parse server config JSON";
errorCode = ErrorCode::InternalError;
return "";
}
QJsonObject serverConfig = doc.object();
// Validate server config structure
if (!serverConfig.contains(amnezia::protocols::xray::inbounds)) {
logger.error() << "Server config missing 'inbounds' field";
errorCode = ErrorCode::InternalError;
return "";
}
QJsonArray inbounds = serverConfig[amnezia::protocols::xray::inbounds].toArray();
if (inbounds.isEmpty()) {
logger.error() << "Server config has empty 'inbounds' array";
errorCode = ErrorCode::InternalError;
return "";
}
QJsonObject inbound = inbounds[0].toObject();
if (!inbound.contains(amnezia::protocols::xray::settings)) {
logger.error() << "Inbound missing 'settings' field";
errorCode = ErrorCode::InternalError;
return "";
}
QJsonObject settings = inbound[amnezia::protocols::xray::settings].toObject();
if (!settings.contains(amnezia::protocols::xray::clients)) {
logger.error() << "Settings missing 'clients' field";
errorCode = ErrorCode::InternalError;
return "";
}
QJsonArray clients = settings[amnezia::protocols::xray::clients].toArray();
// Create configuration for new client
QJsonObject clientConfig {
{amnezia::protocols::xray::id, clientId},
{amnezia::protocols::xray::flow, "xtls-rprx-vision"}
};
clients.append(clientConfig);
// Update config
settings[amnezia::protocols::xray::clients] = clients;
inbound[amnezia::protocols::xray::settings] = settings;
inbounds[0] = inbound;
serverConfig[amnezia::protocols::xray::inbounds] = inbounds;
// Save updated config to server
QString updatedConfig = QJsonDocument(serverConfig).toJson();
errorCode = m_sshSession->uploadTextFileToContainer(
container,
credentials,
updatedConfig,
amnezia::protocols::xray::serverConfigPath,
libssh::ScpOverwriteMode::ScpOverwriteExisting
);
if (errorCode != ErrorCode::NoError) {
logger.error() << "Failed to upload updated config";
return "";
}
// Restart container
QString restartScript = QString("sudo docker restart $CONTAINER_NAME");
errorCode = m_sshSession->runScript(
credentials,
m_sshSession->replaceVars(restartScript, amnezia::genBaseVars(credentials, container, dnsSettings.primaryDns, dnsSettings.secondaryDns))
);
if (errorCode != ErrorCode::NoError) {
logger.error() << "Failed to restart container";
return "";
}
return clientId;
}
ProtocolConfig XrayConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
const ContainerConfig &containerConfig,
const DnsSettings &dnsSettings,
ErrorCode &errorCode)
{
const XrayServerConfig* serverConfig = nullptr;
if (auto* xrayConfig = containerConfig.protocolConfig.as<XrayProtocolConfig>()) {
serverConfig = &xrayConfig->serverConfig;
}
QString xrayClientId = prepareServerConfig(credentials, container, containerConfig, dnsSettings, errorCode);
if (errorCode != ErrorCode::NoError || xrayClientId.isEmpty()) {
logger.error() << "Failed to prepare server config";
errorCode = ErrorCode::InternalError;
return XrayProtocolConfig{};
}
amnezia::ScriptVars vars = amnezia::genBaseVars(credentials, container, dnsSettings.primaryDns, dnsSettings.secondaryDns);
vars.append(amnezia::genProtocolVarsForContainer(container, containerConfig));
QString config = m_sshSession->replaceVars(amnezia::scriptData(ProtocolScriptType::xray_template, container), vars);
if (config.isEmpty()) {
logger.error() << "Failed to get config template";
errorCode = ErrorCode::InternalError;
return XrayProtocolConfig{};
}
QString xrayPublicKey =
m_sshSession->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::PublicKeyPath, errorCode);
if (errorCode != ErrorCode::NoError || xrayPublicKey.isEmpty()) {
logger.error() << "Failed to get public key";
errorCode = ErrorCode::InternalError;
return XrayProtocolConfig{};
}
xrayPublicKey.replace("\n", "");
QString xrayShortId =
m_sshSession->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::shortidPath, errorCode);
if (errorCode != ErrorCode::NoError || xrayShortId.isEmpty()) {
logger.error() << "Failed to get short ID";
errorCode = ErrorCode::InternalError;
return XrayProtocolConfig{};
}
xrayShortId.replace("\n", "");
if (!config.contains("$XRAY_CLIENT_ID") || !config.contains("$XRAY_PUBLIC_KEY") || !config.contains("$XRAY_SHORT_ID")) {
logger.error() << "Config template missing required variables:"
<< "XRAY_CLIENT_ID:" << !config.contains("$XRAY_CLIENT_ID")
<< "XRAY_PUBLIC_KEY:" << !config.contains("$XRAY_PUBLIC_KEY")
<< "XRAY_SHORT_ID:" << !config.contains("$XRAY_SHORT_ID");
errorCode = ErrorCode::InternalError;
return XrayProtocolConfig{};
}
config.replace("$XRAY_CLIENT_ID", xrayClientId);
config.replace("$XRAY_PUBLIC_KEY", xrayPublicKey);
config.replace("$XRAY_SHORT_ID", xrayShortId);
XrayProtocolConfig protocolConfig;
if (serverConfig) {
protocolConfig.serverConfig = *serverConfig;
}
XrayClientConfig clientConfig;
clientConfig.nativeConfig = config;
clientConfig.localPort = "";
clientConfig.id = xrayClientId;
protocolConfig.setClientConfig(clientConfig);
return protocolConfig;
}

View File

@@ -1,27 +0,0 @@
#ifndef XRAY_CONFIGURATOR_H
#define XRAY_CONFIGURATOR_H
#include <QObject>
#include "configuratorBase.h"
#include "core/utils/errorCodes.h"
#include "core/utils/routeModes.h"
#include "core/utils/commonStructs.h"
class XrayConfigurator : public ConfiguratorBase
{
Q_OBJECT
public:
XrayConfigurator(SshSession* sshSession, QObject *parent = nullptr);
amnezia::ProtocolConfig createConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container, const amnezia::ContainerConfig &containerConfig,
const amnezia::DnsSettings &dnsSettings,
amnezia::ErrorCode &errorCode) override;
private:
QString prepareServerConfig(const amnezia::ServerCredentials &credentials, amnezia::DockerContainer container, const amnezia::ContainerConfig &containerConfig,
const amnezia::DnsSettings &dnsSettings,
amnezia::ErrorCode &errorCode);
};
#endif // XRAY_CONFIGURATOR_H

View File

@@ -1,54 +0,0 @@
#include "allowedDnsController.h"
AllowedDnsController::AllowedDnsController(SecureAppSettingsRepository* appSettingsRepository)
: m_appSettingsRepository(appSettingsRepository)
{
fillDnsServers();
}
bool AllowedDnsController::addDns(const QString &ip)
{
if (m_dnsServers.contains(ip)) {
return false;
}
m_dnsServers.append(ip);
m_appSettingsRepository->setAllowedDnsServers(m_dnsServers);
return true;
}
void AllowedDnsController::addDnsList(const QStringList &dnsServers, bool replaceExisting)
{
if (replaceExisting) {
m_dnsServers.clear();
}
for (const QString &ip : dnsServers) {
if (!m_dnsServers.contains(ip)) {
m_dnsServers.append(ip);
}
}
m_appSettingsRepository->setAllowedDnsServers(m_dnsServers);
}
void AllowedDnsController::removeDns(int index)
{
if (index < 0 || index >= m_dnsServers.size()) {
return;
}
m_dnsServers.removeAt(index);
m_appSettingsRepository->setAllowedDnsServers(m_dnsServers);
}
QStringList AllowedDnsController::getCurrentDnsServers() const
{
return m_dnsServers;
}
void AllowedDnsController::fillDnsServers()
{
m_dnsServers = m_appSettingsRepository->getAllowedDnsServers();
}

View File

@@ -1,26 +0,0 @@
#ifndef ALLOWEDDNSCONTROLLER_H
#define ALLOWEDDNSCONTROLLER_H
#include <QStringList>
#include "core/repositories/secureAppSettingsRepository.h"
class AllowedDnsController
{
public:
explicit AllowedDnsController(SecureAppSettingsRepository* appSettingsRepository);
bool addDns(const QString &ip);
void addDnsList(const QStringList &dnsServers, bool replaceExisting);
void removeDns(int index);
QStringList getCurrentDnsServers() const;
private:
void fillDnsServers();
SecureAppSettingsRepository* m_appSettingsRepository;
QStringList m_dnsServers;
};
#endif // ALLOWEDDNSCONTROLLER_H

View File

@@ -1,72 +0,0 @@
#include "newsController.h"
#include "core/controllers/gatewayController.h"
#include "core/utils/api/apiEnums.h"
#include "core/utils/constants/apiKeys.h"
#include "core/utils/constants/apiConstants.h"
#include "core/utils/constants/configKeys.h"
#include <QtConcurrent/QtConcurrent>
#include <QJsonDocument>
#include <QJsonObject>
#include <QSharedPointer>
using namespace amnezia;
NewsController::NewsController(SecureAppSettingsRepository* appSettingsRepository,
ServersController* serversController)
: m_appSettingsRepository(appSettingsRepository), m_serversController(serversController)
{
}
QFuture<QPair<ErrorCode, QJsonArray>> NewsController::fetchNews()
{
if (!m_serversController) {
qWarning() << "ServersController is null, skip fetchNews";
return QtFuture::makeReadyFuture(qMakePair(ErrorCode::InternalError, QJsonArray()));
}
const auto stacks = m_serversController->gatewayStacks();
if (stacks.isEmpty()) {
qDebug() << "No Gateway stacks, skip fetchNews";
return QtFuture::makeReadyFuture(qMakePair(ErrorCode::NoError, QJsonArray()));
}
auto gatewayController = QSharedPointer<GatewayController>::create(
m_appSettingsRepository->getGatewayEndpoint(),
m_appSettingsRepository->isDevGatewayEnv(),
apiDefs::requestTimeoutMsecs,
m_appSettingsRepository->isStrictKillSwitchEnabled());
QJsonObject payload;
payload.insert("locale", m_appSettingsRepository->getAppLanguage().name().split("_").first());
const QJsonObject stacksJson = stacks.toJson();
if (stacksJson.contains(apiDefs::key::userCountryCode)) {
payload.insert(apiDefs::key::userCountryCode, stacksJson.value(apiDefs::key::userCountryCode));
}
if (stacksJson.contains(apiDefs::key::serviceType)) {
payload.insert(apiDefs::key::serviceType, stacksJson.value(apiDefs::key::serviceType));
}
auto future = gatewayController->postAsync(QString("%1v1/news"), payload);
return future.then([gatewayController](QPair<ErrorCode, QByteArray> result) -> QPair<ErrorCode, QJsonArray> {
auto [errorCode, responseBody] = result;
if (errorCode != ErrorCode::NoError) {
return qMakePair(errorCode, QJsonArray());
}
QJsonDocument doc = QJsonDocument::fromJson(responseBody);
QJsonArray newsArray;
if (doc.isArray()) {
newsArray = doc.array();
} else if (doc.isObject()) {
QJsonObject obj = doc.object();
if (obj.value("news").isArray()) {
newsArray = obj.value("news").toArray();
}
}
return qMakePair(ErrorCode::NoError, newsArray);
});
}

View File

@@ -1,28 +0,0 @@
#ifndef NEWSCONTROLLER_H
#define NEWSCONTROLLER_H
#include <QFuture>
#include <QJsonArray>
#include <QPair>
#include "core/utils/errorCodes.h"
#include "core/utils/routeModes.h"
#include "core/utils/commonStructs.h"
#include "core/repositories/secureAppSettingsRepository.h"
#include "core/controllers/serversController.h"
class NewsController
{
public:
explicit NewsController(SecureAppSettingsRepository* appSettingsRepository,
ServersController* serversController);
QFuture<QPair<ErrorCode, QJsonArray>> fetchNews();
private:
SecureAppSettingsRepository* m_appSettingsRepository;
ServersController* m_serversController;
};
#endif // NEWSCONTROLLER_H

View File

@@ -1,248 +0,0 @@
#include "servicesCatalogController.h"
#include <QJsonDocument>
#include <QSysInfo>
#include <QJsonArray>
#include <QEventLoop>
#include <QDebug>
#include <QCoreApplication>
#include <QHash>
#include <QSet>
#include <limits>
#include "core/controllers/gatewayController.h"
#include "core/utils/api/apiEnums.h"
#include "core/utils/constants/apiKeys.h"
#include "core/utils/constants/apiConstants.h"
#include "version.h"
#if defined(Q_OS_IOS) || defined(MACOS_NE)
#include "platforms/ios/ios_controller.h"
#endif
namespace
{
namespace configKey
{
constexpr char serviceDescription[] = "service_description";
constexpr char subscriptionPlans[] = "subscription_plans";
constexpr char storeProductId[] = "store_product_id";
constexpr char priceLabel[] = "price_label";
constexpr char subtitle[] = "subtitle";
constexpr char isTrial[] = "is_trial";
constexpr char minPriceLabel[] = "min_price_label";
}
namespace serviceType
{
constexpr char amneziaPremium[] = "amnezia-premium";
}
#if defined(Q_OS_IOS) || defined(MACOS_NE)
struct StoreKitPlanQuote {
QString displayPrice;
double priceAmount = 0.0;
double subscriptionBillingMonths = 0.0;
QString displayPricePerMonth;
};
constexpr double oneMonthThreshold = 1.0 + 1e-6;
constexpr double monthsFallbackThreshold = 1e-6;
constexpr double monthlyPriceEpsilon = 1e-9;
QStringList collectPremiumStoreProductIds(const QJsonArray &services)
{
QStringList productIds;
QSet<QString> seenProductIds;
for (const QJsonValue &serviceValue : services) {
const QJsonObject serviceObject = serviceValue.toObject();
if (serviceObject.value(apiDefs::key::serviceType).toString() != serviceType::amneziaPremium) {
continue;
}
const QJsonArray subscriptionPlans =
serviceObject.value(configKey::serviceDescription).toObject().value(configKey::subscriptionPlans).toArray();
for (const QJsonValue &planValue : subscriptionPlans) {
if (!planValue.isObject()) {
continue;
}
const QString storeProductId = planValue.toObject().value(configKey::storeProductId).toString();
if (storeProductId.isEmpty() || seenProductIds.contains(storeProductId)) {
continue;
}
seenProductIds.insert(storeProductId);
productIds.append(storeProductId);
}
}
return productIds;
}
QHash<QString, StoreKitPlanQuote> buildStoreKitQuoteMap(const QList<QVariantMap> &fetchedProducts)
{
QHash<QString, StoreKitPlanQuote> quotesByProductId;
quotesByProductId.reserve(fetchedProducts.size());
for (const QVariantMap &productInfo : fetchedProducts) {
const QString productId = productInfo.value(QStringLiteral("productId")).toString();
if (productId.isEmpty()) {
continue;
}
QString displayPrice = productInfo.value(QStringLiteral("displayPrice")).toString();
if (displayPrice.isEmpty()) {
const QString price = productInfo.value(QStringLiteral("price")).toString();
const QString currencyCode = productInfo.value(QStringLiteral("currencyCode")).toString();
displayPrice = currencyCode.isEmpty() ? price : (price + QLatin1Char(' ') + currencyCode);
}
StoreKitPlanQuote quote;
quote.displayPrice = displayPrice;
quote.priceAmount = productInfo.value(QStringLiteral("priceAmount")).toDouble();
quote.subscriptionBillingMonths = productInfo.value(QStringLiteral("subscriptionBillingMonths")).toDouble();
quote.displayPricePerMonth = productInfo.value(QStringLiteral("displayPricePerMonth")).toString();
quotesByProductId.insert(productId, quote);
}
return quotesByProductId;
}
void mergeStoreKitPricesIntoPremiumPlans(QJsonObject &data)
{
QJsonArray services = data.value(apiDefs::key::services).toArray();
if (services.isEmpty()) {
return;
}
const QStringList productIds = collectPremiumStoreProductIds(services);
if (productIds.isEmpty()) {
qInfo().noquote() << "[IAP] No store_product_id in premium plans; skip StoreKit merge into services payload";
return;
}
QList<QVariantMap> fetchedProducts;
QEventLoop loop;
IosController::Instance()->fetchProducts(productIds,
[&](const QList<QVariantMap> &products, const QStringList &invalidIds,
const QString &errorString) {
if (!errorString.isEmpty()) {
qWarning().noquote() << "[IAP] StoreKit merge fetch:" << errorString;
}
if (!invalidIds.isEmpty()) {
qWarning().noquote() << "[IAP] Unknown App Store product ids:" << invalidIds;
}
fetchedProducts = products;
loop.quit();
});
loop.exec();
const QHash<QString, StoreKitPlanQuote> quotesByProductId = buildStoreKitQuoteMap(fetchedProducts);
for (int serviceIndex = 0; serviceIndex < services.size(); ++serviceIndex) {
QJsonObject serviceObject = services.at(serviceIndex).toObject();
if (serviceObject.value(apiDefs::key::serviceType).toString() != serviceType::amneziaPremium) {
continue;
}
QJsonObject descriptionObject = serviceObject.value(configKey::serviceDescription).toObject();
const QJsonArray sourcePlans = descriptionObject.value(configKey::subscriptionPlans).toArray();
QJsonArray mergedPlans;
double minMonthlyAmount = std::numeric_limits<double>::infinity();
QString minMonthlyDisplay;
for (const QJsonValue &planValue : sourcePlans) {
if (!planValue.isObject()) {
continue;
}
QJsonObject planObject = planValue.toObject();
const QString storeProductId = planObject.value(configKey::storeProductId).toString();
if (storeProductId.isEmpty()) {
continue;
}
const auto quoteIterator = quotesByProductId.constFind(storeProductId);
if (quoteIterator == quotesByProductId.cend()) {
continue;
}
const bool isTrialPlan = planObject.value(configKey::isTrial).toBool();
const StoreKitPlanQuote &quote = *quoteIterator;
planObject.insert(configKey::priceLabel, quote.displayPrice);
const double months = quote.subscriptionBillingMonths;
if (!isTrialPlan && months > oneMonthThreshold && !quote.displayPricePerMonth.isEmpty()) {
planObject.insert(
configKey::subtitle,
QCoreApplication::translate("ServicesCatalogController", "%1/mo",
"IAP: price per month in plan subtitle")
.arg(quote.displayPricePerMonth));
}
if (!isTrialPlan && quote.priceAmount > 0.0) {
const double monthsForMin = months > monthsFallbackThreshold ? months : 1.0;
const double monthly = quote.priceAmount / monthsForMin;
if (monthly < minMonthlyAmount - monthlyPriceEpsilon) {
minMonthlyAmount = monthly;
minMonthlyDisplay = !quote.displayPricePerMonth.isEmpty() ? quote.displayPricePerMonth : quote.displayPrice;
}
}
mergedPlans.append(planObject);
}
descriptionObject.insert(configKey::subscriptionPlans, mergedPlans);
if (minMonthlyAmount < std::numeric_limits<double>::infinity() && !minMonthlyDisplay.isEmpty()) {
descriptionObject.insert(configKey::minPriceLabel,
QCoreApplication::translate("ServicesCatalogController", "from %1 per month",
"IAP: card footer minimum monthly price from StoreKit")
.arg(minMonthlyDisplay));
}
serviceObject.insert(configKey::serviceDescription, descriptionObject);
services.replace(serviceIndex, serviceObject);
}
data.insert(apiDefs::key::services, services);
}
#endif
}
ServicesCatalogController::ServicesCatalogController(SecureAppSettingsRepository* appSettingsRepository)
: m_appSettingsRepository(appSettingsRepository)
{
}
ErrorCode ServicesCatalogController::fillAvailableServices(QJsonObject &servicesData)
{
QJsonObject apiPayload;
apiPayload[apiDefs::key::osVersion] = QSysInfo::productType();
apiPayload[apiDefs::key::appVersion] = QString(APP_VERSION);
apiPayload[apiDefs::key::cliName] = QString(APPLICATION_NAME);
apiPayload[apiDefs::key::appLanguage] = m_appSettingsRepository->getAppLanguage().name().split("_").first();
QByteArray responseBody;
ErrorCode errorCode = executeRequest(QString("%1v1/services"), apiPayload, responseBody);
if (errorCode == ErrorCode::NoError) {
if (!responseBody.contains(apiDefs::key::services.data())) {
errorCode = ErrorCode::ApiServicesMissingError;
}
}
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
servicesData = QJsonDocument::fromJson(responseBody).object();
#if defined(Q_OS_IOS) || defined(MACOS_NE)
mergeStoreKitPricesIntoPremiumPlans(servicesData);
#endif
return ErrorCode::NoError;
}
ErrorCode ServicesCatalogController::executeRequest(const QString &endpoint, const QJsonObject &apiPayload, QByteArray &responseBody)
{
GatewayController gatewayController(m_appSettingsRepository->getGatewayEndpoint(), m_appSettingsRepository->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
m_appSettingsRepository->isStrictKillSwitchEnabled());
return gatewayController.post(endpoint, apiPayload, responseBody);
}

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