Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c226265e33 | ||
|
|
35b65484e7 | ||
|
|
c60b04ca46 | ||
|
|
051f4ca6ef | ||
|
|
69d87e70ac | ||
|
|
b7ea6c498d | ||
|
|
8ad00d533e | ||
|
|
b4c5c381ba | ||
|
|
3c25dfce55 | ||
|
|
d8fa1f5161 | ||
|
|
6960287ef5 | ||
|
|
1386336f10 | ||
|
|
c482fa51a7 | ||
|
|
f49fa1ac60 | ||
|
|
34101a91d9 | ||
|
|
972b4e1274 | ||
|
|
df47fdcae0 | ||
|
|
bc5ae6e410 | ||
|
|
0d38b22162 | ||
|
|
4773fb0821 | ||
|
|
a91e1a20f0 | ||
|
|
24b74fcb3e | ||
|
|
ed3deef2dd | ||
|
|
185c85e183 | ||
|
|
bb7cf69cfc | ||
|
|
5ba704ffcf | ||
|
|
1b47d79f26 | ||
|
|
a5cb5679ae | ||
|
|
6aefc6be47 | ||
|
|
378b30f553 | ||
|
|
812ee905f4 | ||
|
|
b16d603435 | ||
|
|
c107c01cab | ||
|
|
80ff2e0676 | ||
|
|
4b6f09d55f | ||
|
|
c6958d8b70 |
No files matched your search
@@ -88,7 +88,7 @@ jobs:
|
||||
host: 'linux'
|
||||
target: 'desktop'
|
||||
arch: 'linux_gcc_64'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools qthttpserver qtwebsockets'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools'
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
tools: 'tools_ifw'
|
||||
@@ -209,7 +209,7 @@ jobs:
|
||||
host: 'windows'
|
||||
target: 'desktop'
|
||||
arch: 'win64_msvc2022_64'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools qthttpserver qtwebsockets'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools'
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
tools: 'tools_ifw'
|
||||
@@ -324,7 +324,7 @@ jobs:
|
||||
# ------------------------------------------------------
|
||||
|
||||
Build-iOS:
|
||||
runs-on: macos-26
|
||||
runs-on: macos-latest
|
||||
|
||||
needs: Bake-Prebuilts-iOS
|
||||
if: ${{ always() }}
|
||||
@@ -472,7 +472,7 @@ jobs:
|
||||
# ------------------------------------------------------
|
||||
|
||||
Build-MacOS:
|
||||
runs-on: macos-26
|
||||
runs-on: macos-15
|
||||
|
||||
needs: Bake-Prebuilts-MacOS
|
||||
if: ${{ always() }}
|
||||
@@ -528,7 +528,7 @@ jobs:
|
||||
host: 'mac'
|
||||
target: 'desktop'
|
||||
arch: 'clang_64'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools qthttpserver qtwebsockets'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools'
|
||||
dir: ${{ runner.temp }}
|
||||
setup-python: 'true'
|
||||
set-env: 'true'
|
||||
@@ -671,7 +671,7 @@ jobs:
|
||||
version: ${{ env.QT_VERSION }}
|
||||
host: 'mac'
|
||||
target: 'desktop'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools qtmultimedia qthttpserver qtwebsockets'
|
||||
modules: 'qtremoteobjects qt5compat qtshadertools qtmultimedia'
|
||||
arch: 'clang_64'
|
||||
dir: ${{ runner.temp }}
|
||||
set-env: 'true'
|
||||
@@ -1049,19 +1049,22 @@ jobs:
|
||||
- SettingsSignalsTest
|
||||
- UiServersModelAndControllerTest
|
||||
- XraySerializationTest
|
||||
- XrayServerInboundTest
|
||||
- XrayGeneratorMatrixTest
|
||||
- XrayConfigModelTest
|
||||
- XrayCoreAcceptsTest
|
||||
- UiLanguageModelAndControllerTest
|
||||
- UiIpModelAndControllerTest
|
||||
- UiAppSTModelAndControllerTest
|
||||
- UiAllowedDnsModelAndControllerTest
|
||||
- BackupExportImportTest
|
||||
- FavoriteLocationsRestoreTest
|
||||
- ConnectionTest
|
||||
- OpenSSLLibraryTest
|
||||
- GatewayServiceImportTest
|
||||
- UiNewsModelAndControllerTest
|
||||
- UiServicesModelAndControllerTest
|
||||
- UiSubscriptionControllerTest
|
||||
- UiApiAccountInfoModelTest
|
||||
- ApiBenefitsModelTest
|
||||
- ApiCountryModelTest
|
||||
- ApiCountryListModelTest
|
||||
- ApiDevicesModelTest
|
||||
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
@@ -1082,6 +1085,7 @@ jobs:
|
||||
THIRD_PARTY_VLESS_IMPORT_DATA: ${{ secrets.THIRD_PARTY_VLESS_IMPORT_DATA }}
|
||||
THIRD_PARTY_SHADOWSOCKS_IMPORT_DATA: ${{ secrets.THIRD_PARTY_SHADOWSOCKS_IMPORT_DATA }}
|
||||
THIRD_PARTY_SHADOWSOCKS_SUBSCRIPTION_IMPORT_DATA: ${{ secrets.THIRD_PARTY_SHADOWSOCKS_SUBSCRIPTION_IMPORT_DATA }}
|
||||
AMNEZIA_TEST_API_KEY: ${{ secrets.AMNEZIA_TEST_API_KEY }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -1110,7 +1114,7 @@ jobs:
|
||||
host: linux
|
||||
target: desktop
|
||||
arch: linux_gcc_64
|
||||
modules: qtremoteobjects qt5compat qtshadertools qthttpserver qtwebsockets
|
||||
modules: qtremoteobjects qt5compat qtshadertools
|
||||
dir: ${{ runner.temp }}
|
||||
set-env: true
|
||||
aqtversion: '==3.3.0'
|
||||
@@ -1137,17 +1141,6 @@ jobs:
|
||||
-name "test_*" \
|
||||
-exec chmod +x {} \;
|
||||
|
||||
- name: Download Xray core
|
||||
if: matrix.filter == 'XrayCoreAcceptsTest'
|
||||
shell: bash
|
||||
run: |
|
||||
XRAY_RELEASE=$(sed -n 's/^ARG XRAY_RELEASE="\(.*\)"/\1/p' client/server_scripts/xray/Dockerfile)
|
||||
curl -fsSL -o "$RUNNER_TEMP/xray.zip" \
|
||||
"https://github.com/XTLS/Xray-core/releases/download/${XRAY_RELEASE}/Xray-linux-64.zip"
|
||||
unzip -q -o "$RUNNER_TEMP/xray.zip" xray -d "$RUNNER_TEMP/xray"
|
||||
chmod +x "$RUNNER_TEMP/xray/xray"
|
||||
echo "XRAY_BIN=$RUNNER_TEMP/xray/xray" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Run tests
|
||||
env:
|
||||
AMNEZIA_TEST_API_KEY: ${{ secrets.AMNEZIA_TEST_API_KEY }}
|
||||
@@ -1197,7 +1190,7 @@ jobs:
|
||||
host: linux
|
||||
target: desktop
|
||||
arch: linux_gcc_64
|
||||
modules: qtremoteobjects qt5compat qtshadertools qthttpserver qtwebsockets
|
||||
modules: qtremoteobjects qt5compat qtshadertools
|
||||
dir: ${{ runner.temp }}
|
||||
set-env: true
|
||||
aqtversion: '==3.3.0'
|
||||
@@ -1224,16 +1217,6 @@ jobs:
|
||||
-name "test_*" \
|
||||
-exec chmod +x {} \;
|
||||
|
||||
- name: Download Xray core
|
||||
shell: bash
|
||||
run: |
|
||||
XRAY_RELEASE=$(sed -n 's/^ARG XRAY_RELEASE="\(.*\)"/\1/p' client/server_scripts/xray/Dockerfile)
|
||||
curl -fsSL -o "$RUNNER_TEMP/xray.zip" \
|
||||
"https://github.com/XTLS/Xray-core/releases/download/${XRAY_RELEASE}/Xray-linux-64.zip"
|
||||
unzip -q -o "$RUNNER_TEMP/xray.zip" xray -d "$RUNNER_TEMP/xray"
|
||||
chmod +x "$RUNNER_TEMP/xray/xray"
|
||||
echo "XRAY_BIN=$RUNNER_TEMP/xray/xray" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Run tests
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -1243,8 +1226,7 @@ jobs:
|
||||
export QT_QPA_PLATFORM=offscreen
|
||||
|
||||
ctest --test-dir deploy/build/client/tests --output-on-failure -VV \
|
||||
--timeout 3600 \
|
||||
-R "^(SelfHostedServerSetupTest|MultipleExportsTest|AdminSelfHostedExportTest|XrayVpsMatrixTest)$"
|
||||
-R "^(SelfHostedServerSetupTest|MultipleExportsTest|AdminSelfHostedExportTest)$"
|
||||
|
||||
# ------------------------------------------------------
|
||||
|
||||
@@ -1266,19 +1248,22 @@ jobs:
|
||||
- SettingsSignalsTest
|
||||
- UiServersModelAndControllerTest
|
||||
- XraySerializationTest
|
||||
- XrayServerInboundTest
|
||||
- XrayGeneratorMatrixTest
|
||||
- XrayConfigModelTest
|
||||
- XrayCoreAcceptsTest
|
||||
- UiLanguageModelAndControllerTest
|
||||
- UiIpModelAndControllerTest
|
||||
- UiAppSTModelAndControllerTest
|
||||
- UiAllowedDnsModelAndControllerTest
|
||||
- BackupExportImportTest
|
||||
- FavoriteLocationsRestoreTest
|
||||
- ConnectionTest
|
||||
- OpenSSLLibraryTest
|
||||
- GatewayServiceImportTest
|
||||
- UiNewsModelAndControllerTest
|
||||
- UiServicesModelAndControllerTest
|
||||
- UiSubscriptionControllerTest
|
||||
- UiApiAccountInfoModelTest
|
||||
- ApiBenefitsModelTest
|
||||
- ApiCountryModelTest
|
||||
- ApiCountryListModelTest
|
||||
- ApiDevicesModelTest
|
||||
|
||||
runs-on: windows-latest
|
||||
|
||||
@@ -1298,7 +1283,8 @@ jobs:
|
||||
THIRD_PARTY_TROJAN_IMPORT_DATA: ${{ secrets.THIRD_PARTY_TROJAN_IMPORT_DATA }}
|
||||
THIRD_PARTY_VLESS_IMPORT_DATA: ${{ secrets.THIRD_PARTY_VLESS_IMPORT_DATA }}
|
||||
THIRD_PARTY_SHADOWSOCKS_IMPORT_DATA: ${{ secrets.THIRD_PARTY_SHADOWSOCKS_IMPORT_DATA }}
|
||||
THIRD_PARTY_SHADOWSOCKS_SUBSCRIPTION_IMPORT_DATA: ${{ secrets.THIRD_PARTY_SHADOWSOCKS_SUBSCRIPTION_IMPORT_DATA }}
|
||||
THIRD_PARTY_SHADOWSOCKS_SUBSCRIPTION_IMPORT_DATA: ${{ secrets.THIRD_PARTY_SHADOWSOCKS_SUBSCRIPTION_IMPORT_DATA }}|
|
||||
AMNEZIA_TEST_API_KEY: ${{ secrets.AMNEZIA_TEST_API_KEY }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -1335,7 +1321,7 @@ jobs:
|
||||
host: windows
|
||||
target: desktop
|
||||
arch: win64_msvc2022_64
|
||||
modules: qtremoteobjects qt5compat qtshadertools qthttpserver qtwebsockets
|
||||
modules: qtremoteobjects qt5compat qtshadertools
|
||||
dir: ${{ runner.temp }}
|
||||
set-env: true
|
||||
|
||||
@@ -1344,17 +1330,6 @@ jobs:
|
||||
with:
|
||||
arch: x64
|
||||
|
||||
- name: Download Xray core
|
||||
if: matrix.filter == 'XrayCoreAcceptsTest'
|
||||
shell: pwsh
|
||||
run: |
|
||||
$release = (Select-String -Path client/server_scripts/xray/Dockerfile -Pattern '^ARG XRAY_RELEASE="(.*)"').Matches[0].Groups[1].Value
|
||||
$zip = Join-Path $env:RUNNER_TEMP "xray.zip"
|
||||
$dir = Join-Path $env:RUNNER_TEMP "xray"
|
||||
Invoke-WebRequest -Uri "https://github.com/XTLS/Xray-core/releases/download/$release/Xray-windows-64.zip" -OutFile $zip
|
||||
Expand-Archive -Path $zip -DestinationPath $dir -Force
|
||||
"XRAY_BIN=$(Join-Path $dir 'xray.exe')" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
||||
|
||||
- name: Run tests
|
||||
env:
|
||||
AMNEZIA_TEST_API_KEY: ${{ secrets.AMNEZIA_TEST_API_KEY }}
|
||||
@@ -1394,10 +1369,17 @@ jobs:
|
||||
- UiAppSTModelAndControllerTest
|
||||
- UiAllowedDnsModelAndControllerTest
|
||||
- BackupExportImportTest
|
||||
- FavoriteLocationsRestoreTest
|
||||
- ConnectionTest
|
||||
- OpenSSLLibraryTest
|
||||
- GatewayServiceImportTest
|
||||
- UiNewsModelAndControllerTest
|
||||
- UiServicesModelAndControllerTest
|
||||
- UiSubscriptionControllerTest
|
||||
- UiApiAccountInfoModelTest
|
||||
- ApiBenefitsModelTest
|
||||
- ApiCountryModelTest
|
||||
- ApiCountryListModelTest
|
||||
- ApiDevicesModelTest
|
||||
|
||||
runs-on: macos-15
|
||||
|
||||
@@ -1418,6 +1400,7 @@ jobs:
|
||||
THIRD_PARTY_VLESS_IMPORT_DATA: ${{ secrets.THIRD_PARTY_VLESS_IMPORT_DATA }}
|
||||
THIRD_PARTY_SHADOWSOCKS_IMPORT_DATA: ${{ secrets.THIRD_PARTY_SHADOWSOCKS_IMPORT_DATA }}
|
||||
THIRD_PARTY_SHADOWSOCKS_SUBSCRIPTION_IMPORT_DATA: ${{ secrets.THIRD_PARTY_SHADOWSOCKS_SUBSCRIPTION_IMPORT_DATA }}
|
||||
AMNEZIA_TEST_API_KEY: ${{ secrets.AMNEZIA_TEST_API_KEY }}
|
||||
|
||||
steps:
|
||||
- name: Setup Xcode
|
||||
@@ -1457,7 +1440,7 @@ jobs:
|
||||
host: mac
|
||||
target: desktop
|
||||
arch: clang_64
|
||||
modules: qtremoteobjects qt5compat qtshadertools qthttpserver qtwebsockets
|
||||
modules: qtremoteobjects qt5compat qtshadertools
|
||||
dir: ${{ runner.temp }}
|
||||
set-env: true
|
||||
|
||||
@@ -1552,98 +1535,16 @@ jobs:
|
||||
# KEYSTORE_BASE64: ${{ secrets.ANDROID_RELEASE_KEYSTORE_BASE64 }}
|
||||
# run: echo $KEYSTORE_BASE64 | base64 --decode > android.keystore
|
||||
|
||||
- name: 'Configure (Android x86_64)'
|
||||
env:
|
||||
QT_ANDROID: ${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/android_x86_64
|
||||
QT_HOST: ${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/gcc_64
|
||||
CMAKE_ANDROID_NDK: ${{ env.ANDROID_HOME }}/ndk/${{ env.NDK_VERSION }}
|
||||
run: |
|
||||
cmake -S . -B build -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DCMAKE_TOOLCHAIN_FILE=$QT_ANDROID/lib/cmake/Qt6/qt.toolchain.cmake \
|
||||
-DANDROID_PLATFORM=${{ env.ANDROID_PLATFORM }} \
|
||||
-DCMAKE_ANDROID_NDK=$CMAKE_ANDROID_NDK \
|
||||
-DQT_HOST_PATH=$QT_HOST \
|
||||
-DANDROID_SDK_ROOT=$ANDROID_HOME \
|
||||
-DAMNEZIA_BUILD_TESTS=ON
|
||||
|
||||
- name: 'Build Android tests'
|
||||
# env:
|
||||
# QT_ANDROID_KEYSTORE_PATH: ${{ github.workspace }}/android.keystore
|
||||
# QT_ANDROID_KEYSTORE_ALIAS: ${{ secrets.ANDROID_RELEASE_KEYSTORE_KEY_ALIAS }}
|
||||
# QT_ANDROID_KEYSTORE_STORE_PASS: ${{ secrets.ANDROID_RELEASE_KEYSTORE_KEY_PASS }}
|
||||
run: |
|
||||
cmake --build build --target test_android_openssl_make_apk
|
||||
cmake --build build --target test_android_restore_backup_make_apk
|
||||
|
||||
- name: 'Write Android test runner script'
|
||||
run: |
|
||||
cat > ci_run_android_tests.sh << 'EOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ADB=$(which adb)
|
||||
|
||||
echo ">>> Waiting for package manager to be ready..."
|
||||
"$ADB" shell 'while ! pm list packages > /dev/null 2>&1; do sleep 2; done'
|
||||
echo ">>> Package manager ready."
|
||||
|
||||
run_test() {
|
||||
local target="$1"
|
||||
local apk="build/client/tests/android-build/${target}.apk"
|
||||
local pkg_name="org.qtproject.example.${target}"
|
||||
local activity="${pkg_name}/org.qtproject.qt.android.bindings.QtActivity"
|
||||
|
||||
echo ">>> Installing ${target} APK..."
|
||||
timeout 120 "$ADB" install -r "$apk"
|
||||
|
||||
"$ADB" shell "run-as $pkg_name sh -c 'mkdir -p files && : > files/stdout.txt'" 2>/dev/null || true
|
||||
"$ADB" shell "run-as $pkg_name sh -c 'rm -f files/qtest_last_exit_code'" 2>/dev/null || true
|
||||
|
||||
echo ">>> Launching ${target}..."
|
||||
"$ADB" shell am start -n "$activity"
|
||||
|
||||
echo ">>> Waiting for test to finish (up to 120s)..."
|
||||
local elapsed=0 exit_code=""
|
||||
while [[ $elapsed -lt 120 ]]; do
|
||||
sleep 2
|
||||
elapsed=$((elapsed + 2))
|
||||
exit_code=$("$ADB" shell \
|
||||
"run-as $pkg_name cat files/qtest_last_exit_code 2>/dev/null" \
|
||||
| tr -d '[:space:]' || true)
|
||||
[[ -n "$exit_code" ]] && break
|
||||
done
|
||||
|
||||
local stdout_content
|
||||
stdout_content=$("$ADB" shell \
|
||||
"run-as $pkg_name cat files/stdout.txt 2>/dev/null" || true)
|
||||
if [[ -n "$stdout_content" ]]; then
|
||||
echo ">>> Test output:"
|
||||
echo "$stdout_content"
|
||||
fi
|
||||
|
||||
if [[ -z "$exit_code" ]]; then
|
||||
echo ">>> TIMEOUT: ${target} did not complete within 120s."
|
||||
exit 1
|
||||
elif [[ "$exit_code" != "0" ]]; then
|
||||
echo ">>> ${target} FAILED (exit code: $exit_code)"
|
||||
exit 1
|
||||
else
|
||||
echo ">>> ${target} PASSED"
|
||||
fi
|
||||
}
|
||||
|
||||
run_test test_android_openssl
|
||||
run_test test_android_restore_backup
|
||||
EOF
|
||||
chmod +x ci_run_android_tests.sh
|
||||
|
||||
- name: 'Run Android tests on emulator'
|
||||
uses: reactivecircus/android-emulator-runner@v2
|
||||
env:
|
||||
QT_ROOT: ${{ runner.temp }}/Qt/${{ env.QT_VERSION }}
|
||||
NDK_VERSION: ${{ env.NDK_VERSION }}
|
||||
ANDROID_PLATFORM: ${{ env.ANDROID_PLATFORM }}
|
||||
with:
|
||||
api-level: 28
|
||||
arch: x86_64
|
||||
emulator-options: -no-snapshot -no-window -no-audio -no-boot-anim -accel on
|
||||
disable-animations: true
|
||||
script: bash ci_run_android_tests.sh
|
||||
script: bash client/tests/run_android_tests.sh --wait-boot
|
||||
|
||||
@@ -83,6 +83,7 @@ client/.DS_Store
|
||||
._.DS_Store
|
||||
._*
|
||||
*.dmg
|
||||
deploy/data/macos/pf/amn.400.allowPIA.conf
|
||||
|
||||
# tmp files
|
||||
*.*~
|
||||
|
||||
@@ -11,7 +11,3 @@
|
||||
path = client/3rd/qtgamepad
|
||||
url = https://github.com/amnezia-vpn/qtgamepad.git
|
||||
branch = 6.6
|
||||
[submodule "cmake/cmake-conan"]
|
||||
path = cmake/cmake-conan
|
||||
url = https://github.com/amnezia-vpn/cmake-conan
|
||||
branch = develop2
|
||||
@@ -1,219 +0,0 @@
|
||||
# AGENTS.md
|
||||
|
||||
Operating guide for this repository. Read it before changing anything.
|
||||
|
||||
## Project Overview
|
||||
|
||||
AmneziaVPN is a cross-platform VPN client built with C++ and Qt Quick (QML) for
|
||||
Windows, macOS, Linux, Android and iOS. It has three kinds of servers, each
|
||||
with its own config model (`serverConfigUtils::ConfigType`):
|
||||
|
||||
- **Self-hosted.** The admin connects the app to their own server over SSH; the
|
||||
app installs protocols as Docker containers (AmneziaWG, WireGuard, OpenVPN,
|
||||
XRay, IPsec and others) and generates client configs (`SelfHostedAdmin`).
|
||||
Access is shared with other users as `vpn://` keys, which import without
|
||||
server management rights (`SelfHostedUser`).
|
||||
- **Amnezia Premium and Free.** Configs come from the Amnezia gateway API. The
|
||||
gateway passes the backend's config through; the client does not invent
|
||||
server-side values.
|
||||
- **Native protocol configs.** Third-party configs imported as they are:
|
||||
WireGuard and AmneziaWG `.conf`, OpenVPN `.ovpn`, XRay and Shadowsocks links
|
||||
(`Native`). The app only connects with them; there is no server to manage.
|
||||
|
||||
A change to shared code (protocol configs, import, connection) must keep
|
||||
working for all three.
|
||||
|
||||
The desktop builds run a privileged service next to the GUI; the GUI talks to
|
||||
it over Qt Remote Objects. Android, iOS and the macOS App Store build
|
||||
(`MACOS_NE`) run the tunnel in the platform's VPN extension instead.
|
||||
|
||||
## Repository Layout
|
||||
|
||||
- `client/core/` — business logic with no UI: controllers, configurators
|
||||
(client config generation per protocol), installers (server-side setup over
|
||||
SSH), protocol and server config models, repositories (encrypted settings
|
||||
storage).
|
||||
- `client/ui/controllers/` — `*UiController` classes exposed to QML. Thin
|
||||
adapters over `core/`: they translate between QML and core types and hold no
|
||||
business logic of their own.
|
||||
- `client/ui/models/` — list and property models for QML.
|
||||
- `client/ui/qml/` — the interface:
|
||||
- `Pages2/` — screens, one file per page.
|
||||
- `Components/` — composite pieces of screens.
|
||||
- `Controls2/` — design system controls, named `<Name>Type.qml`; text styles
|
||||
live in `Controls2/TextTypes/`.
|
||||
- `Modules/Style/AmneziaStyle.qml` — design tokens (colours and the like).
|
||||
- `Config/` — `GlobalConfig`, `Dialogs` and other singletons.
|
||||
- `client/platforms/`, `client/android/`, `client/ios/`, `client/macos/` —
|
||||
platform code: VPN extensions, Kotlin, Swift, Objective-C++.
|
||||
- `client/daemon/`, `client/mozilla/`, `service/`, `ipc/` — the privileged
|
||||
service and its IPC.
|
||||
- `client/server_scripts/` — the shell scripts and Dockerfiles run on the
|
||||
user's server.
|
||||
- `client/translations/` — Qt `.ts` files.
|
||||
- `client/tests/` — Qt Test suites, run through `ctest`.
|
||||
- `recipes/` — Conan recipes for native dependencies (protocol libraries,
|
||||
OpenSSL, libssh and others).
|
||||
- `deploy/` — build and installer scripts. `.github/workflows/` — CI.
|
||||
- `client/3rd/` — vendored third-party code. Do not edit it.
|
||||
|
||||
## Essential Commands
|
||||
|
||||
Run from the repository root.
|
||||
|
||||
```bash
|
||||
deploy/build.sh -h # build options
|
||||
deploy/build.sh # build for the host platform
|
||||
deploy/build.sh -t android --aab # Android APK and AAB
|
||||
cmake -S . -B build -DCMAKE_PREFIX_PATH=<Qt>/<platform> -DAMNEZIA_BUILD_TESTS=ON
|
||||
cmake --build build --parallel
|
||||
ctest --test-dir build/client/tests --output-on-failure
|
||||
```
|
||||
|
||||
Qt 6.10 or later and Conan are required; native dependencies are built by Conan
|
||||
from `recipes/` on the first configure.
|
||||
|
||||
## Repository Rules
|
||||
|
||||
These three come first. Each one has been asked for repeatedly.
|
||||
|
||||
### 1. Clean, self-documenting code, no comments
|
||||
|
||||
- Names and structure carry the meaning. Code must be readable without help.
|
||||
- No explanatory comments in code: no `//` notes in QML or C++, no doc blocks.
|
||||
- The one exception is a hack that looks wrong on purpose: a workaround for a
|
||||
platform or Qt bug, an ordering that must not be "fixed". Then a short comment
|
||||
next to it says why.
|
||||
- Explanations of a change go into the chat summary or the commit message, not
|
||||
into the code.
|
||||
- Leave existing comments alone unless the change removes the code they
|
||||
describe.
|
||||
|
||||
### 2. Reusable code (DRY), without abstraction for its own sake
|
||||
|
||||
- **QML.** Every reusable component lives in its own file. A piece of a page
|
||||
goes into its own file in `Components/` even when it is used once, if that
|
||||
keeps the page short and readable. No inline `component` definitions or
|
||||
`Component` blocks inside pages. Before writing a new control, look in
|
||||
`Controls2/` and `Components/` for one that already does the job, and extend
|
||||
it if it almost does.
|
||||
- **C++.** Logic shared by several places is extracted once and reused:
|
||||
duplicated parsing, validation or config building is a bug waiting to diverge.
|
||||
- **No fanaticism.** Do not create a base class, interface, helper or file
|
||||
that only renames a single call, has one trivial user, or exists "for the
|
||||
future". Decompose when it makes the code shorter, clearer or removes
|
||||
duplication; otherwise keep it direct.
|
||||
|
||||
### 3. Do not add what was not asked for
|
||||
|
||||
- Implement exactly the request. No extra screens, buttons, settings,
|
||||
refactors, renames, translations or "while I'm here" fixes.
|
||||
- Ideas, improvements and problems noticed along the way are reported to the
|
||||
user as suggestions. Nothing is implemented without the user agreeing first.
|
||||
- When a request is ambiguous, ask instead of guessing big.
|
||||
- Keep the diff small. If a fix grows beyond the obvious change, say so and
|
||||
explain why before making it.
|
||||
|
||||
### Other rules
|
||||
|
||||
- **Committing is the user's call.** Prepare the change and a commit message;
|
||||
do not commit or push unless asked. Commit messages use a conventional prefix
|
||||
with a scope (`fix(ui): …`, `feat(macos): …`, `chore: …`): a one-line
|
||||
subject plus one or two short body lines saying what was wrong and what
|
||||
changed. Pull requests target `dev`.
|
||||
- **Format C++ with `.clang-format`.** Format the lines you touch; do not
|
||||
reformat files the change does not otherwise touch.
|
||||
- **Delete unused code** instead of keeping it "for later".
|
||||
- **Do not edit `client/3rd/`.** Fix vendored code through its upstream or a
|
||||
Conan recipe.
|
||||
|
||||
## UI And Design System
|
||||
|
||||
- **Figma is the source of truth.** Screens follow the "Amnezia APP VPN" file;
|
||||
components follow the "Amnezia DS Library". Where the two disagree, the app
|
||||
screens win.
|
||||
- **Build UI from the design system controls** in `Controls2/`
|
||||
(`ButtonType`, `ListItemType`, `TextFieldType`, `SheetType`,
|
||||
`NavigationBarType` and the others) and the text types in
|
||||
`Controls2/TextTypes/`. Do not style raw Qt Quick Controls on a page.
|
||||
- **No hex colours in QML.** Every colour is a semantic token from
|
||||
`AmneziaStyle.color`. A colour the tokens do not have is added to
|
||||
`AmneziaStyle` first, with the name the design system gives it.
|
||||
- **Icons come from Figma**, exported from the DS Library. Never draw an icon
|
||||
by hand. Exports include page background rectangles — strip them and keep
|
||||
the `0 0 24 24` viewBox.
|
||||
- **Restyling a component never redesigns a screen.** A design system update
|
||||
changes components; screen layout and behaviour stay unless the user asks.
|
||||
- **No Qt5Compat.GraphicalEffects.** It overflows the interpreted JS stack on
|
||||
iOS. Use `QtQuick.Effects` (`MultiEffect`), and create effects only while
|
||||
they are visible: each instance is expensive to create.
|
||||
|
||||
### QML pitfalls
|
||||
|
||||
- A vertical `ListView` resets the delegate's `x`. Put insets on the
|
||||
ListView's `leftMargin`/`rightMargin`, or on a child inside the delegate.
|
||||
- `lineHeightMode: Text.FixedHeight` puts all the extra leading below the text,
|
||||
while Figma splits it evenly. The text types already compensate with
|
||||
`halfLeading` padding; a new text type must do the same.
|
||||
- QML is loaded from `qml.qrc`, not compiled ahead of time: a QML error shows
|
||||
up only at runtime. When moving QML between files, compare the `import`
|
||||
lines — types registered from C++ under their own URIs fail only when the
|
||||
page is opened.
|
||||
|
||||
## Translations
|
||||
|
||||
- Every user-visible string goes through `qsTr()` in QML or `tr()` in C++.
|
||||
- The `.ts` files contain only finished, non-empty translations: no
|
||||
`vanished`, `obsolete` or `unfinished` entries, and every language holds the
|
||||
same set of strings. `lrelease` must report `N finished and 0 unfinished` for
|
||||
every file.
|
||||
- Refresh `.ts` files only when the task is about translations. `lupdate` run
|
||||
through `qt6_add_translations` misses QML, so run it over an explicit file
|
||||
list that skips `client/3rd` (from `client/translations`):
|
||||
|
||||
```bash
|
||||
find .. -path ../3rd -prune -o -path ../3rd-prebuilt -prune -o \
|
||||
\( -name '*.qml' -o -name '*.cpp' -o -name '*.h' -o -name '*.mm' \) -print \
|
||||
| sort > /tmp/src.lst
|
||||
<Qt>/bin/lupdate @/tmp/src.lst -ts amneziavpn_*.ts
|
||||
```
|
||||
|
||||
- `lupdate` output differs between macOS and Windows (keychain strings swap
|
||||
places), so a large `.ts` diff after a refresh is often only that.
|
||||
- Technical terms stay in English in every language: protocol parameter names
|
||||
and their descriptions (`Jc`, `S1`, `H1`…), config keys, transport and
|
||||
algorithm names. Ordinary UI words are translated.
|
||||
- Renaming a QML file changes its translation context, so its strings have to
|
||||
be carried over.
|
||||
|
||||
## Which Check When
|
||||
|
||||
- **Touched C++** — build the affected targets, and run `ctest` when the change
|
||||
touches anything the tests cover (servers, imports, configs, settings,
|
||||
split tunneling, XRay).
|
||||
- **Touched QML** — build, then open every page you changed. If the app cannot
|
||||
be launched, at least load the changed files through the QML engine so that
|
||||
syntax, imports and type errors surface, and say what was not checked.
|
||||
- **Touched a protocol config, import or connection path** — check self-hosted,
|
||||
Premium/Free and native configs, and every platform that consumes the field
|
||||
(desktop service, Android, iOS/macOS NE).
|
||||
- **Touched CMake, recipes or the app icon** — reconfigure from a clean build
|
||||
directory as well: branding paths are CMake cache variables and keep stale
|
||||
values in an existing build directory.
|
||||
- **Touched translations** — `lrelease` on every `.ts` file, see above.
|
||||
|
||||
## Privacy
|
||||
|
||||
- Never commit or repeat private keys, `vpn://` keys, server addresses,
|
||||
passwords or tokens — including ones found in pasted configs, logs or test
|
||||
output.
|
||||
- Logs must not contain secrets or full config bodies.
|
||||
- Local absolute paths (`/Users/...`, `C:\...`) do not belong in repository
|
||||
files.
|
||||
|
||||
## Verification Culture
|
||||
|
||||
- Verify your own work before reporting it. "Should work" is not a result;
|
||||
say exactly what was built, run or opened, and what was not.
|
||||
- Hypotheses are settled by an experiment, not by reasoning about the code.
|
||||
- After a wrong diagnosis, remove the code it produced.
|
||||
@@ -1,6 +0,0 @@
|
||||
# CLAUDE.md
|
||||
|
||||
The operating guide for this repository lives in [AGENTS.md](AGENTS.md), so
|
||||
every agent runtime reads the same file.
|
||||
|
||||
@AGENTS.md
|
||||
@@ -1,18 +1,18 @@
|
||||
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
set(PROJECT AmneziaVPN)
|
||||
if(NOT AMNEZIAVPN_VERSION)
|
||||
set(AMNEZIAVPN_VERSION 5.1.0.4 CACHE STRING "Client app version")
|
||||
set(AMNEZIAVPN_VERSION 5.1.0.0 CACHE STRING "Client app version")
|
||||
endif()
|
||||
|
||||
set(QT_CREATOR_SKIP_PACKAGE_MANAGER_SETUP ON CACHE BOOL "" FORCE)
|
||||
include(${CMAKE_SOURCE_DIR}/cmake/cmake-conan/conan_bootstrap.cmake)
|
||||
set(CMAKE_PROJECT_TOP_LEVEL_INCLUDES
|
||||
${CMAKE_SOURCE_DIR}/cmake/platform_settings.cmake
|
||||
${CMAKE_SOURCE_DIR}/cmake/cmake-conan/conan_provider.cmake
|
||||
${CMAKE_SOURCE_DIR}/cmake/recipes_bootstrap.cmake
|
||||
${CMAKE_SOURCE_DIR}/cmake/conan_provider.cmake
|
||||
CACHE STRING "" FORCE)
|
||||
|
||||
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
|
||||
@@ -34,13 +34,48 @@ set(RELEASE_DATE "${CURRENT_DATE}")
|
||||
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||
|
||||
# bump by 2 on every release, because we're releasing two versions on the play store
|
||||
set(APP_ANDROID_VERSION_CODE 2186)
|
||||
set(APP_ANDROID_VERSION_CODE 2173)
|
||||
if(DEFINED APP_ANDROID_VERSION_CODE_OFFSET)
|
||||
math(EXPR APP_ANDROID_VERSION_CODE "${APP_ANDROID_VERSION_CODE} + ${APP_ANDROID_VERSION_CODE_OFFSET}")
|
||||
endif()
|
||||
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||
set(MZ_PLATFORM_NAME "linux")
|
||||
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
|
||||
set(MZ_PLATFORM_NAME "windows")
|
||||
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
|
||||
set(MZ_PLATFORM_NAME "macos")
|
||||
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Android")
|
||||
set(MZ_PLATFORM_NAME "android")
|
||||
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "iOS")
|
||||
set(MZ_PLATFORM_NAME "ios")
|
||||
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Emscripten")
|
||||
set(MZ_PLATFORM_NAME "wasm")
|
||||
endif()
|
||||
|
||||
set(QT_BUILD_TOOLS_WHEN_CROSS_COMPILING ON)
|
||||
|
||||
if(APPLE AND NOT IOS)
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
set(AMN_PF_RULE_IDENTITY "user { root }")
|
||||
else()
|
||||
set(AMN_PF_RULE_IDENTITY "group { amnvpn }")
|
||||
endif()
|
||||
|
||||
configure_file(
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/deploy/data/pf-templates/amn.400.allowPIA.conf.in"
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/amn.400.allowPIA.conf"
|
||||
@ONLY
|
||||
)
|
||||
|
||||
file(COPY_FILE
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/amn.400.allowPIA.conf"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/deploy/data/macos/pf/amn.400.allowPIA.conf"
|
||||
ONLY_IF_DIFFERENT
|
||||
)
|
||||
endif()
|
||||
|
||||
|
||||
add_subdirectory(client)
|
||||
|
||||
if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
|
||||
|
||||
@@ -37,7 +37,7 @@ add_definitions(-DFREE_V2_ENDPOINT="$ENV{FREE_V2_ENDPOINT}")
|
||||
add_definitions(-DPREM_V1_ENDPOINT="$ENV{PREM_V1_ENDPOINT}")
|
||||
|
||||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
set(PACKAGES ${PACKAGES} Widgets HttpServer)
|
||||
set(PACKAGES ${PACKAGES} Widgets)
|
||||
endif()
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS ${PACKAGES})
|
||||
@@ -60,7 +60,7 @@ if(TARGET Qt6::CorePrivate)
|
||||
endif()
|
||||
|
||||
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
||||
set(LIBS ${LIBS} Qt6::Widgets Qt6::HttpServer)
|
||||
set(LIBS ${LIBS} Qt6::Widgets)
|
||||
endif()
|
||||
|
||||
qt_standard_project_setup()
|
||||
@@ -74,6 +74,7 @@ target_compile_definitions(${PROJECT} PRIVATE
|
||||
|
||||
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
|
||||
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep)
|
||||
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_interface.rep)
|
||||
endif()
|
||||
|
||||
qt6_add_resources(QRC ${QRC}
|
||||
@@ -103,7 +104,6 @@ else()
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/${CLIENT_TS_PREFIX}_ko_KR.ts
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/${CLIENT_TS_PREFIX}_fr_FR.ts
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/${CLIENT_TS_PREFIX}_th_TH.ts
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/${CLIENT_TS_PREFIX}_id_ID.ts
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -150,6 +150,10 @@ else()
|
||||
message("MACOS_NE is OFF")
|
||||
endif()
|
||||
|
||||
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)
|
||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/core/utils)
|
||||
configure_file(
|
||||
@@ -172,6 +176,10 @@ configure_file(
|
||||
@ONLY
|
||||
)
|
||||
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
target_compile_definitions(${PROJECT} PRIVATE "MZ_DEBUG")
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
configure_file(
|
||||
${CMAKE_CURRENT_LIST_DIR}/platforms/windows/amneziavpn.rc.in
|
||||
@@ -218,10 +226,6 @@ if(ANDROID)
|
||||
include(cmake/android.cmake)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
include(cmake/appleAppIcon.cmake)
|
||||
endif()
|
||||
|
||||
if(IOS)
|
||||
include(cmake/ios.cmake)
|
||||
include(cmake/ios-arch-fixup.cmake)
|
||||
@@ -235,6 +239,7 @@ endif()
|
||||
list(APPEND SOURCES ${CMAKE_CURRENT_LIST_DIR}/main.cpp)
|
||||
|
||||
target_link_libraries(${PROJECT} PRIVATE ${LIBS})
|
||||
target_compile_definitions(${PROJECT} PRIVATE "MZ_$<UPPER_CASE:${MZ_PLATFORM_NAME}>")
|
||||
|
||||
target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC} ${I18NQRC})
|
||||
|
||||
@@ -294,11 +299,32 @@ install(SCRIPT ${QT_DEPLOY_SCRIPT}
|
||||
)
|
||||
|
||||
if (APPLE AND NOT IOS AND NOT MACOS_NE)
|
||||
list(APPEND OVPN_SCRIPTS "${CMAKE_SOURCE_DIR}/deploy/data/macos/update-resolv-conf.sh")
|
||||
set_target_properties(${PROJECT} PROPERTIES
|
||||
XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO"
|
||||
XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO"
|
||||
)
|
||||
endif()
|
||||
if (LINUX AND NOT ANDROID)
|
||||
list(APPEND OVPN_SCRIPTS "${CMAKE_SOURCE_DIR}/deploy/data/linux/update-resolv-conf.sh")
|
||||
endif()
|
||||
|
||||
if(OVPN_SCRIPTS)
|
||||
add_custom_command(TARGET ${PROJECT} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
${OVPN_SCRIPTS}
|
||||
"$<TARGET_FILE_DIR:${PROJECT}>"
|
||||
)
|
||||
|
||||
install(FILES ${OVPN_SCRIPTS}
|
||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
COMPONENT AmneziaVPN
|
||||
PERMISSIONS
|
||||
OWNER_READ OWNER_EXECUTE
|
||||
GROUP_READ GROUP_EXECUTE
|
||||
WORLD_READ WORLD_EXECUTE
|
||||
)
|
||||
endif()
|
||||
|
||||
if(AMNEZIA_BUILD_TESTS)
|
||||
add_subdirectory(tests)
|
||||
|
||||
@@ -129,11 +129,6 @@ void AmneziaApplication::init()
|
||||
win->setPersistentSceneGraph(true);
|
||||
win->setPersistentGraphics(true);
|
||||
#endif
|
||||
#ifdef Q_OS_IOS
|
||||
auto pageController = m_coreController->pageController();
|
||||
pageController->setSafeAreaMargins(win->safeAreaMargins());
|
||||
QObject::connect(win, &QWindow::safeAreaMarginsChanged, pageController, &PageController::setSafeAreaMargins);
|
||||
#endif
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
win->show();
|
||||
#else
|
||||
|
||||
@@ -49,23 +49,15 @@ set(SOURCES ${SOURCES}
|
||||
|
||||
|
||||
find_package(awg-android REQUIRED)
|
||||
set_property(TARGET ${PROJECT} APPEND PROPERTY QT_ANDROID_EXTRA_LIBS
|
||||
$<TARGET_FILE:amnezia::awg-android>
|
||||
${AMNEZIA_ANDROID_LIBWG_PATH}
|
||||
${AMNEZIA_ANDROID_LIBWG_QUICK_PATH}
|
||||
)
|
||||
set(LIBS ${LIBS} amnezia::awg-android)
|
||||
set_property(TARGET ${PROJECT} APPEND PROPERTY QT_ANDROID_EXTRA_LIBS ${AMNEZIA_ANDROID_LIBWG_PATH} ${AMNEZIA_ANDROID_LIBWG_QUICK_PATH})
|
||||
|
||||
find_package(amnezia-libxray REQUIRED)
|
||||
file(COPY ${AMNEZIA_LIBXRAY_PATH} DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/android/xray/libXray)
|
||||
|
||||
find_package(openvpn-pt-android REQUIRED)
|
||||
get_filename_component(OPENVPN_PT_ANDROID_LIB_DIR ${OPENVPN_PT_ANDROID_LIBCK_OVPN_PLUGIN_PATH} DIRECTORY)
|
||||
set_property(TARGET ${PROJECT} APPEND PROPERTY QT_ANDROID_EXTRA_LIBS
|
||||
${OPENVPN_PT_ANDROID_LIBCK_OVPN_PLUGIN_PATH}
|
||||
${OPENVPN_PT_ANDROID_LIB_DIR}/libovpn3.so
|
||||
${OPENVPN_PT_ANDROID_LIB_DIR}/libovpnutil.so
|
||||
${OPENVPN_PT_ANDROID_LIB_DIR}/librsapss.so
|
||||
)
|
||||
set(LIBS ${LIBS} amnezia::openvpn-pt-android)
|
||||
set_property(TARGET ${PROJECT} APPEND PROPERTY QT_ANDROID_EXTRA_LIBS ${OPENVPN_PT_ANDROID_LIBCK_OVPN_PLUGIN_PATH})
|
||||
|
||||
set(APP_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android)
|
||||
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
function(apple_icon_composer_supported OUT_VAR)
|
||||
set(${OUT_VAR} FALSE PARENT_SCOPE)
|
||||
|
||||
execute_process(
|
||||
COMMAND xcrun actool --version --output-format human-readable-text
|
||||
OUTPUT_VARIABLE ACTOOL_VERSION_OUTPUT
|
||||
ERROR_QUIET
|
||||
)
|
||||
string(REGEX MATCH "short-bundle-version: ([0-9]+)" ACTOOL_VERSION_MATCH "${ACTOOL_VERSION_OUTPUT}")
|
||||
if(NOT ACTOOL_VERSION_MATCH OR CMAKE_MATCH_1 LESS 26)
|
||||
return()
|
||||
endif()
|
||||
|
||||
execute_process(
|
||||
COMMAND sw_vers -productVersion
|
||||
OUTPUT_VARIABLE HOST_MACOS_VERSION
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_QUIET
|
||||
)
|
||||
if(HOST_MACOS_VERSION VERSION_LESS 26)
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(${OUT_VAR} TRUE PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(check_apple_app_icon ICON_PATH)
|
||||
if(NOT EXISTS "${ICON_PATH}/icon.json")
|
||||
message(FATAL_ERROR "App icon not found: ${ICON_PATH} (expected an Icon Composer .icon bundle)")
|
||||
endif()
|
||||
|
||||
apple_icon_composer_supported(ICON_COMPOSER_SUPPORTED)
|
||||
if(NOT ICON_COMPOSER_SUPPORTED)
|
||||
message(FATAL_ERROR "Xcode 26 and macOS 26 or later are required to build the app icon ${ICON_PATH}")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(select_macos_app_icon ICON_PATH ICNS_PATH OUT_USE_ICON_COMPOSER)
|
||||
apple_icon_composer_supported(ICON_COMPOSER_SUPPORTED)
|
||||
if(ICON_COMPOSER_SUPPORTED)
|
||||
check_apple_app_icon(${ICON_PATH})
|
||||
set(${OUT_USE_ICON_COMPOSER} TRUE PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(NOT EXISTS "${ICNS_PATH}")
|
||||
message(FATAL_ERROR "Xcode 26 and macOS 26 are required for ${ICON_PATH}, and the fallback icon ${ICNS_PATH} was not found")
|
||||
endif()
|
||||
message(STATUS "Icon Composer needs Xcode 26 and macOS 26, using the fallback app icon ${ICNS_PATH}")
|
||||
set(${OUT_USE_ICON_COMPOSER} FALSE PARENT_SCOPE)
|
||||
endfunction()
|
||||
@@ -19,8 +19,8 @@ endif()
|
||||
if(NOT CLIENT_IOS_LAUNCHSCREEN_PATH)
|
||||
set(CLIENT_IOS_LAUNCHSCREEN_PATH "${_CLIENT_SRC_DIR}/ios/app/${CLIENT_IOS_LAUNCHSCREEN_FILE}" CACHE FILEPATH "iOS launch screen storyboard path")
|
||||
endif()
|
||||
if(NOT CLIENT_IOS_APP_ICON_PATH)
|
||||
set(CLIENT_IOS_APP_ICON_PATH "${_CLIENT_SRC_DIR}/images/AppIcon.icon" CACHE PATH "iOS app icon (Icon Composer .icon)")
|
||||
if(NOT CLIENT_IOS_MEDIA_ASSETS_PATH)
|
||||
set(CLIENT_IOS_MEDIA_ASSETS_PATH "${_CLIENT_SRC_DIR}/ios/app/Media.xcassets" CACHE FILEPATH "iOS app Media.xcassets path")
|
||||
endif()
|
||||
|
||||
if(NOT CLIENT_IOS_BUNDLE_ID)
|
||||
|
||||
@@ -11,11 +11,11 @@ endif()
|
||||
if(NOT CLIENT_MACOS_NE_ENTITLEMENTS_PATH)
|
||||
set(CLIENT_MACOS_NE_ENTITLEMENTS_PATH "${_CLIENT_SRC_DIR}/macos/networkextension/${CLIENT_MACOS_NE_ENTITLEMENTS_FILE}" CACHE FILEPATH "macOS network extension entitlements file path")
|
||||
endif()
|
||||
if(NOT CLIENT_MACOS_APP_ICON_PATH)
|
||||
set(CLIENT_MACOS_APP_ICON_PATH "${_CLIENT_SRC_DIR}/images/AppIcon.icon" CACHE PATH "macOS app icon (Icon Composer .icon)")
|
||||
if(NOT CLIENT_MACOS_MEDIA_ASSETS_PATH)
|
||||
set(CLIENT_MACOS_MEDIA_ASSETS_PATH "${_CLIENT_SRC_DIR}/macos/app/Images.xcassets" CACHE FILEPATH "macOS app Images.xcassets path")
|
||||
endif()
|
||||
if(NOT CLIENT_MACOS_APP_ICNS_PATH)
|
||||
set(CLIENT_MACOS_APP_ICNS_PATH "${_CLIENT_SRC_DIR}/macos/app/AppIcon.icns" CACHE FILEPATH "macOS app icon (.icns) for builds without Xcode 26 and macOS 26")
|
||||
set(CLIENT_MACOS_APP_ICNS_PATH "${_CLIENT_SRC_DIR}/images/app.icns" CACHE FILEPATH "macOS app icon (.icns) used by non-App Store builds")
|
||||
endif()
|
||||
|
||||
if(NOT CLIENT_MACOS_EXTRA_PLIST_ENTRIES)
|
||||
|
||||
@@ -127,18 +127,14 @@ target_sources(${PROJECT} PRIVATE
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/StoreKit2Helper.swift
|
||||
)
|
||||
|
||||
check_apple_app_icon(${CLIENT_IOS_APP_ICON_PATH})
|
||||
|
||||
set_source_files_properties(
|
||||
${CLIENT_IOS_APP_ICON_PATH}
|
||||
PROPERTIES
|
||||
MACOSX_PACKAGE_LOCATION Resources
|
||||
XCODE_LAST_KNOWN_FILE_TYPE folder.iconcomposer.icon
|
||||
${CLIENT_IOS_MEDIA_ASSETS_PATH}
|
||||
PROPERTIES MACOSX_PACKAGE_LOCATION Resources
|
||||
)
|
||||
|
||||
target_sources(${PROJECT} PRIVATE
|
||||
${CLIENT_IOS_LAUNCHSCREEN_PATH}
|
||||
${CLIENT_IOS_APP_ICON_PATH}
|
||||
${CLIENT_IOS_MEDIA_ASSETS_PATH}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
|
||||
)
|
||||
|
||||
|
||||
@@ -34,42 +34,10 @@ set(SOURCES ${SOURCES}
|
||||
|
||||
|
||||
|
||||
select_macos_app_icon(${CLIENT_MACOS_APP_ICON_PATH} ${CLIENT_MACOS_APP_ICNS_PATH} USE_ICON_COMPOSER)
|
||||
|
||||
set(APP_ICON_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/appicon)
|
||||
set(MACOSX_BUNDLE_ICON_FILE AppIcon)
|
||||
|
||||
if(USE_ICON_COMPOSER)
|
||||
set(APP_ICON_FILES ${APP_ICON_OUTPUT_DIR}/Assets.car ${APP_ICON_OUTPUT_DIR}/AppIcon.icns)
|
||||
file(GLOB_RECURSE APP_ICON_INPUTS CONFIGURE_DEPENDS "${CLIENT_MACOS_APP_ICON_PATH}/*")
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${APP_ICON_FILES}
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${APP_ICON_OUTPUT_DIR}
|
||||
COMMAND xcrun actool --output-format human-readable-text --warnings --errors
|
||||
--platform macosx
|
||||
--minimum-deployment-target ${CMAKE_OSX_DEPLOYMENT_TARGET}
|
||||
--app-icon AppIcon
|
||||
--output-partial-info-plist ${APP_ICON_OUTPUT_DIR}/partial.plist
|
||||
--compile ${APP_ICON_OUTPUT_DIR}
|
||||
${CLIENT_MACOS_APP_ICON_PATH}
|
||||
DEPENDS ${APP_ICON_INPUTS}
|
||||
COMMENT "Compiling macOS app icon"
|
||||
VERBATIM
|
||||
)
|
||||
set_source_files_properties(${APP_ICON_FILES} PROPERTIES GENERATED TRUE)
|
||||
|
||||
add_custom_command(TARGET ${PROJECT} POST_BUILD
|
||||
COMMAND plutil -replace CFBundleIconName -string AppIcon $<TARGET_BUNDLE_CONTENT_DIR:${PROJECT}>/Info.plist
|
||||
VERBATIM
|
||||
)
|
||||
else()
|
||||
set(APP_ICON_FILES ${APP_ICON_OUTPUT_DIR}/AppIcon.icns)
|
||||
configure_file(${CLIENT_MACOS_APP_ICNS_PATH} ${APP_ICON_FILES} COPYONLY)
|
||||
endif()
|
||||
|
||||
set_source_files_properties(${APP_ICON_FILES} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
|
||||
set(SOURCES ${SOURCES} ${APP_ICON_FILES})
|
||||
set(ICON_FILE ${CLIENT_MACOS_APP_ICNS_PATH})
|
||||
get_filename_component(MACOSX_BUNDLE_ICON_FILE "${CLIENT_MACOS_APP_ICNS_PATH}" NAME)
|
||||
set_source_files_properties(${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
|
||||
set(SOURCES ${SOURCES} ${ICON_FILE})
|
||||
|
||||
target_compile_options(${PROJECT} PRIVATE
|
||||
-DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\"
|
||||
|
||||
@@ -122,24 +122,13 @@ target_sources(${PROJECT} PRIVATE
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/StoreKit2Helper.swift
|
||||
)
|
||||
|
||||
select_macos_app_icon(${CLIENT_MACOS_APP_ICON_PATH} ${CLIENT_MACOS_APP_ICNS_PATH} USE_ICON_COMPOSER)
|
||||
|
||||
if(USE_ICON_COMPOSER)
|
||||
set(APP_ICON_SOURCE ${CLIENT_MACOS_APP_ICON_PATH})
|
||||
set_source_files_properties(
|
||||
${APP_ICON_SOURCE}
|
||||
PROPERTIES
|
||||
MACOSX_PACKAGE_LOCATION Resources
|
||||
XCODE_LAST_KNOWN_FILE_TYPE folder.iconcomposer.icon
|
||||
)
|
||||
else()
|
||||
set(APP_ICON_SOURCE ${CMAKE_CURRENT_BINARY_DIR}/appicon/AppIcon.icns)
|
||||
configure_file(${CLIENT_MACOS_APP_ICNS_PATH} ${APP_ICON_SOURCE} COPYONLY)
|
||||
set_source_files_properties(${APP_ICON_SOURCE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
|
||||
endif()
|
||||
set_source_files_properties(
|
||||
${CLIENT_MACOS_MEDIA_ASSETS_PATH}
|
||||
PROPERTIES MACOSX_PACKAGE_LOCATION Resources
|
||||
)
|
||||
|
||||
target_sources(${PROJECT} PRIVATE
|
||||
${APP_ICON_SOURCE}
|
||||
${CLIENT_MACOS_MEDIA_ASSETS_PATH}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
|
||||
)
|
||||
|
||||
|
||||
@@ -40,6 +40,64 @@ function(osx_bundle_files TARGET)
|
||||
endforeach()
|
||||
endfunction(osx_bundle_files)
|
||||
|
||||
## A helper to bundle an asset catalog.
|
||||
function(osx_bundle_assetcatalog TARGET)
|
||||
cmake_parse_arguments(XCASSETS
|
||||
""
|
||||
"CATALOG;PLATFORM"
|
||||
"DEVICES"
|
||||
${ARGN})
|
||||
|
||||
if(XCASSETS_PLATFORM)
|
||||
set(XCASSETS_TARGET_ARGS --platform ${XCASSETS_PLATFORM})
|
||||
elseif(IOS)
|
||||
set(XCASSETS_TARGET_ARGS --platform iphoneos)
|
||||
else()
|
||||
set(XCASSETS_TARGET_ARGS --platform macosx)
|
||||
endif()
|
||||
foreach(DEVNAME ${XCASSETS_DEVICES})
|
||||
list(APPEND XCASSETS_TARGET_ARGS --target-device ${DEVNAME})
|
||||
endforeach()
|
||||
list(APPEND XCASSETS_TARGET_ARGS --minimum-deployment-target ${CMAKE_OSX_DEPLOYMENT_TARGET})
|
||||
|
||||
## Compile the asset catalog
|
||||
set(XCASSETS_GEN_PLIST ${CMAKE_CURRENT_BINARY_DIR}/xcassets_generated_info.plist)
|
||||
add_custom_command(
|
||||
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/xcassets/Assets.car ${XCASSETS_GEN_PLIST}
|
||||
MAIN_DEPENDENCY ${XCASSETS_CATALOG}/Contents.json
|
||||
COMMENT "Building asset catalog"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_BINARY_DIR}/xcassets
|
||||
COMMAND actool --output-format human-readable-text --notices --warnings
|
||||
${XCASSETS_TARGET_ARGS}
|
||||
--app-icon AppIcon
|
||||
--output-partial-info-plist ${XCASSETS_GEN_PLIST}
|
||||
--development-region en --enable-on-demand-resources NO
|
||||
--compile ${CMAKE_CURRENT_BINARY_DIR}/xcassets ${XCASSETS_CATALOG}
|
||||
)
|
||||
|
||||
## Patch the asset catalog into the target bundle.
|
||||
if(NOT IOS)
|
||||
set(XCASSETS_RESOURCE_DIR "Resources")
|
||||
endif()
|
||||
add_custom_command(TARGET ${TARGET} POST_BUILD
|
||||
COMMENT "Bundling asset catalog"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_BINARY_DIR}/xcassets $<TARGET_BUNDLE_CONTENT_DIR:${TARGET}>/${XCASSETS_RESOURCE_DIR}
|
||||
COMMAND ${CLIENT_ROOT_DIR}/scripts/macos/merge_plist.py ${XCASSETS_GEN_PLIST} -o $<TARGET_BUNDLE_CONTENT_DIR:${TARGET}>/Info.plist
|
||||
)
|
||||
|
||||
target_sources(${TARGET} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/xcassets/Assets.car)
|
||||
set_source_files_properties(
|
||||
${CMAKE_CURRENT_BINARY_DIR}/xcassets/Assets.car
|
||||
${XCASSETS_GEN_PLIST}
|
||||
PROPERTIES
|
||||
GENERATED TRUE
|
||||
HEADER_FILE_ONLY TRUE
|
||||
)
|
||||
|
||||
target_sources(${TARGET} PRIVATE ${XCASSETS_GEN_PLIST})
|
||||
set_source_files_properties(${XCASSETS_GEN_PLIST} PROPERTIES GENERATED TRUE)
|
||||
endfunction()
|
||||
|
||||
## A helper to code-sign an executable.
|
||||
function(osx_codesign_target TARGET)
|
||||
## Xcode should perform automatic code-signing for us.
|
||||
|
||||
@@ -44,7 +44,6 @@ set(HEADERS ${HEADERS}
|
||||
${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/localProxyController.h
|
||||
${CLIENT_ROOT_DIR}/core/controllers/logsController.h
|
||||
${CLIENT_ROOT_DIR}/core/controllers/api/servicesCatalogController.h
|
||||
${CLIENT_ROOT_DIR}/core/controllers/api/storePurchaseController.h
|
||||
@@ -70,12 +69,19 @@ set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/core/utils/api/apiUtils.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/api/countryCatalog.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/api/gatewayPayloadBuilder.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/localProxy/localProxyConstants.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/localProxy/portUtils.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/localProxy/xrayConfigUtils.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/osSignalHandler.h
|
||||
${CLIENT_ROOT_DIR}/core/utils/swiftBridge.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)
|
||||
@@ -124,7 +130,6 @@ set(SOURCES ${SOURCES}
|
||||
${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/localProxyController.cpp
|
||||
${CLIENT_ROOT_DIR}/core/controllers/logsController.cpp
|
||||
${CLIENT_ROOT_DIR}/core/controllers/api/servicesCatalogController.cpp
|
||||
${CLIENT_ROOT_DIR}/core/controllers/api/storePurchaseController.cpp
|
||||
@@ -152,11 +157,17 @@ set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/core/utils/api/apiUtils.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/api/countryCatalog.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/api/gatewayPayloadBuilder.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/localProxy/portUtils.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/localProxy/xrayConfigUtils.cpp
|
||||
${CLIENT_ROOT_DIR}/core/utils/serverConfigUtils.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)
|
||||
@@ -188,11 +199,13 @@ 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)
|
||||
@@ -250,6 +263,14 @@ set(SOURCES ${SOURCES}
|
||||
)
|
||||
|
||||
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
|
||||
)
|
||||
@@ -262,21 +283,21 @@ if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
|
||||
set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/core/utils/ipcClient.h
|
||||
${CLIENT_ROOT_DIR}/ui/utils/systemTrayNotificationHandler.h
|
||||
${CLIENT_ROOT_DIR}/xtunVpnConnection.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}/xtunVpnConnection.cpp
|
||||
)
|
||||
else()
|
||||
set(HEADERS ${HEADERS}
|
||||
${CLIENT_ROOT_DIR}/vpnConnection.h
|
||||
)
|
||||
|
||||
set(SOURCES ${SOURCES}
|
||||
${CLIENT_ROOT_DIR}/vpnConnection.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()
|
||||
|
||||
|
||||
@@ -129,7 +129,7 @@ ProtocolConfig OpenVpnConfigurator::createConfig(const ServerCredentials &creden
|
||||
config.replace("</tls-auth>", "");
|
||||
}
|
||||
|
||||
#ifndef Q_OS_WIN
|
||||
#ifndef MZ_WINDOWS
|
||||
config.replace("block-outside-dns", "");
|
||||
#endif
|
||||
|
||||
@@ -177,10 +177,17 @@ ProtocolConfig OpenVpnConfigurator::processConfigWithLocalSettings(const Connect
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef Q_OS_WIN
|
||||
#ifndef MZ_WINDOWS
|
||||
config.replace("block-outside-dns", "");
|
||||
#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()));
|
||||
#endif
|
||||
|
||||
protocolConfig.setNativeConfig(config);
|
||||
return protocolConfig;
|
||||
}
|
||||
|
||||
@@ -24,9 +24,9 @@
|
||||
namespace {
|
||||
Logger logger("XrayConfigurator");
|
||||
|
||||
QString effectiveServerFlow(const amnezia::XrayServerConfig &srv)
|
||||
QString effectiveClientFlow(const amnezia::XrayServerConfig &srv)
|
||||
{
|
||||
return amnezia::xrayEffective::serverFlow(srv);
|
||||
return amnezia::xrayEffective::clientFlow(srv);
|
||||
}
|
||||
|
||||
QString effectiveSecurity(const amnezia::XrayServerConfig &srv)
|
||||
@@ -139,7 +139,7 @@ ErrorCode XrayConfigurator::applyServerSettingsToRemote(const ServerCredentials
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
const QString flowValue = effectiveServerFlow(srv);
|
||||
const QString flowValue = effectiveClientFlow(srv);
|
||||
QString realityPublicKey;
|
||||
QString realityShortId;
|
||||
if (effectiveSecurity(srv) == QLatin1String("reality")) {
|
||||
@@ -314,7 +314,7 @@ ErrorCode XrayConfigurator::writeServerConfigForSetup(const ServerCredentials &c
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
const QString flowValue = effectiveServerFlow(srv);
|
||||
const QString flowValue = effectiveClientFlow(srv);
|
||||
const QJsonArray clients = collectServerClients(credentials, container, flowValue, clientId, errorCode);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
logger.error() << "Xray writeServerConfigForSetup: upload failed, error=" << static_cast<int>(errorCode);
|
||||
|
||||
@@ -362,33 +362,6 @@ ErrorCode SubscriptionController::updateServiceFromGateway(const QString &server
|
||||
return applyUpdatedServiceConfig(serverId, serviceProtocol, protocolData, responseBody);
|
||||
}
|
||||
|
||||
ErrorCode SubscriptionController::fetchServiceConfig(const QString &serverId, const QString &serviceProtocol,
|
||||
QJsonObject &serverConfigJson)
|
||||
{
|
||||
auto apiV2 = m_serversRepository->apiV2Config(serverId);
|
||||
if (!apiV2.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
ProtocolData protocolData = generateProtocolData(serviceProtocol);
|
||||
|
||||
QJsonObject apiPayload = GatewayPayloadBuilder(m_appSettingsRepository)
|
||||
.addField(apiDefs::key::userCountryCode, apiV2->apiConfig.userCountryCode)
|
||||
.addField(apiDefs::key::serviceType, apiV2->serviceType())
|
||||
.addField(apiDefs::key::serviceProtocol, serviceProtocol)
|
||||
.addField(apiDefs::key::publicKey, publicKeyForProtocol(serviceProtocol, protocolData))
|
||||
.addField(apiDefs::key::authData, apiV2->authData.toJson())
|
||||
.build();
|
||||
|
||||
QByteArray responseBody;
|
||||
ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody, apiV2->apiConfig.isTestPurchase);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
return extractServerConfigJsonFromResponse(responseBody, serviceProtocol, protocolData, serverConfigJson);
|
||||
}
|
||||
|
||||
void SubscriptionController::markSubscriptionExpiredByServer(const QString &serverId, const ApiV2ServerConfig &apiV2)
|
||||
{
|
||||
ApiV2ServerConfig expiredApiV2 = apiV2;
|
||||
|
||||
@@ -45,8 +45,6 @@ public:
|
||||
ErrorCode importTrialFromGateway(const QString &userCountryCode, const QString &serviceType,
|
||||
const QString &serviceProtocol, const QString &email);
|
||||
|
||||
ErrorCode fetchServiceConfig(const QString &serverId, const QString &serviceProtocol, QJsonObject &serverConfigJson);
|
||||
|
||||
ErrorCode updateServiceFromGateway(const QString &serverId, const QString &newCountryCode, bool isConnectEvent,
|
||||
CaptchaInfo *captchaInfoOut = nullptr, ProtocolData *usedProtocolDataOut = nullptr);
|
||||
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
@startuml
|
||||
participant AmneziaVPN
|
||||
participant "VPN Backend\n(Gateway API)" as Backend
|
||||
participant Worker
|
||||
|
||||
AmneziaVPN -> AmneziaVPN : Нажатие кнопки «Подключить»
|
||||
activate AmneziaVPN
|
||||
|
||||
AmneziaVPN -> AmneziaVPN : Проверка наличия вторичного\nконфига
|
||||
|
||||
alt Конфиг отсутствует
|
||||
AmneziaVPN -> Backend : POST v1/config\n(запрос вторичного конфига)
|
||||
Backend --> AmneziaVPN : Конфиг сервера
|
||||
AmneziaVPN -> AmneziaVPN : Сохранение конфига
|
||||
else Конфиг есть, но срок жизни истёк (expires_at)
|
||||
AmneziaVPN -> Backend : POST v1/config\n(обновление конфига)
|
||||
Backend --> AmneziaVPN : Новый конфиг сервера
|
||||
AmneziaVPN -> AmneziaVPN : Обновление конфига
|
||||
else Конфиг актуален
|
||||
note right : Пропуск запроса к бекенду
|
||||
end
|
||||
|
||||
AmneziaVPN -> Worker : Подключение по VPN протоколу
|
||||
activate Worker
|
||||
Worker --> AmneziaVPN : Туннель установлен
|
||||
deactivate Worker
|
||||
|
||||
deactivate AmneziaVPN
|
||||
@enduml
|
||||
@@ -179,7 +179,6 @@ void CoreController::initCoreControllers()
|
||||
m_importCoreController = new ImportController(m_serversRepository, m_appSettingsRepository, this);
|
||||
m_connectionController = new ConnectionController(m_serversRepository, m_appSettingsRepository, m_vpnConnection.get(), this);
|
||||
m_settingsController = new SettingsController(m_serversRepository, m_appSettingsRepository, this);
|
||||
m_localProxyController = new LocalProxyController(m_serversRepository, m_appSettingsRepository, m_subscriptionController, this);
|
||||
}
|
||||
|
||||
void CoreController::initControllers()
|
||||
@@ -213,9 +212,6 @@ void CoreController::initControllers()
|
||||
m_settingsUiController = new SettingsUiController(m_settingsController, m_serversController, this);
|
||||
setQmlContextProperty("SettingsController", m_settingsUiController);
|
||||
|
||||
m_localProxyUiController = new LocalProxyUiController(m_localProxyController, this);
|
||||
setQmlContextProperty("LocalProxyController", m_localProxyUiController);
|
||||
|
||||
m_logsUiController = new LogsUiController(m_settingsController, this);
|
||||
setQmlContextProperty("LogsController", m_logsUiController);
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include "ui/controllers/selfhosted/installUiController.h"
|
||||
#include "ui/controllers/qml/pageController.h"
|
||||
#include "ui/controllers/settingsUiController.h"
|
||||
#include "ui/controllers/localProxyUiController.h"
|
||||
#include "ui/controllers/logsUiController.h"
|
||||
#include "ui/controllers/serversUiController.h"
|
||||
#include "ui/controllers/ipSplitTunnelingUiController.h"
|
||||
@@ -45,7 +44,6 @@
|
||||
#include "core/controllers/api/newsController.h"
|
||||
#include "core/controllers/selfhosted/installController.h"
|
||||
#include "core/controllers/settingsController.h"
|
||||
#include "core/controllers/localProxyController.h"
|
||||
#include "core/controllers/connectionController.h"
|
||||
#include "core/controllers/updateController.h"
|
||||
|
||||
@@ -143,9 +141,6 @@ protected:
|
||||
ServicesCatalogUiController* servicesCatalogUiControllerProtected() const { return m_servicesCatalogUiController; }
|
||||
ApiNewsUiController* apiNewsUiControllerProtected() const { return m_apiNewsUiController; }
|
||||
SubscriptionController* subscriptionControllerProtected() const { return m_subscriptionController; }
|
||||
ApiCountryModel* apiCountryModelProtected() const { return m_apiCountryModel; }
|
||||
ApiCountryListModel* apiCountryListModelProtected() const { return m_apiCountryListModel; }
|
||||
ApiCountryListModel* apiConfigsCountryListModelProtected() const { return m_apiConfigsCountryListModel; }
|
||||
|
||||
private:
|
||||
void initRepositories();
|
||||
@@ -173,14 +168,13 @@ private:
|
||||
QMetaObject::Connection m_reloadConfigErrorOccurredConnection;
|
||||
|
||||
ConnectionUiController* m_connectionUiController;
|
||||
FocusController* m_focusController = nullptr;
|
||||
FocusController* m_focusController;
|
||||
PageController* m_pageController;
|
||||
InstallUiController* m_installUiController;
|
||||
ImportUiController* m_importController;
|
||||
ImportController* m_importCoreController;
|
||||
ExportUiController* m_exportUiController;
|
||||
SettingsUiController* m_settingsUiController;
|
||||
LocalProxyUiController* m_localProxyUiController;
|
||||
LogsUiController* m_logsUiController;
|
||||
ServersUiController* m_serversUiController;
|
||||
IpSplitTunnelingUiController* m_ipSplitTunnelingUiController;
|
||||
@@ -210,7 +204,6 @@ private:
|
||||
ExportController* m_exportController;
|
||||
ConnectionController* m_connectionController;
|
||||
SettingsController* m_settingsController;
|
||||
LocalProxyController* m_localProxyController;
|
||||
|
||||
ContainersModel* m_containersModel;
|
||||
ContainersModel* m_defaultServerContainersModel;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "coreSignalHandlers.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QTimer>
|
||||
#include <QtConcurrent>
|
||||
|
||||
@@ -12,7 +11,6 @@
|
||||
#include "core/utils/serverConfigUtils.h"
|
||||
#include "core/repositories/secureAppSettingsRepository.h"
|
||||
#include "vpnConnection.h"
|
||||
#include "ui/controllers/qml/focusController.h"
|
||||
#include "ui/controllers/qml/pageController.h"
|
||||
#include "ui/controllers/connectionUiController.h"
|
||||
#include "ui/controllers/settingsUiController.h"
|
||||
@@ -34,7 +32,6 @@
|
||||
#include "core/controllers/selfhosted/installController.h"
|
||||
#include "core/controllers/selfhosted/exportController.h"
|
||||
#include "core/controllers/connectionController.h"
|
||||
#include "core/controllers/localProxyController.h"
|
||||
#include "ui/models/clientManagementModel.h"
|
||||
#include "ui/controllers/api/apiNewsUiController.h"
|
||||
#include "ui/models/containersModel.h"
|
||||
@@ -84,7 +81,6 @@ void CoreSignalHandlers::initAllHandlers()
|
||||
initAppSplitTunnelingModelUpdateHandler();
|
||||
initPrepareConfigHandler();
|
||||
initUnsupportedConnectDrawerHandler();
|
||||
initBackNavigationHandler();
|
||||
initStrictKillSwitchHandler();
|
||||
initAndroidSettingsHandler();
|
||||
initAndroidConnectionHandler();
|
||||
@@ -92,7 +88,6 @@ void CoreSignalHandlers::initAllHandlers()
|
||||
initIosSettingsHandler();
|
||||
initNotificationHandler();
|
||||
initUpdateFoundHandler();
|
||||
initLocalProxyHandler();
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initErrorMessagesHandler()
|
||||
@@ -187,21 +182,12 @@ void CoreSignalHandlers::initCountryListSortModeHandler()
|
||||
const QVector<ApiCountryListModel *> models { m_coreController->m_apiCountryListModel,
|
||||
m_coreController->m_apiConfigsCountryListModel };
|
||||
|
||||
const auto reloadSortModes = [repository, models]() {
|
||||
for (ApiCountryListModel *model : models) {
|
||||
model->setSortMode(repository->countryListSortMode(model->listId()));
|
||||
}
|
||||
};
|
||||
reloadSortModes();
|
||||
|
||||
for (ApiCountryListModel *model : models) {
|
||||
model->setSortMode(repository->countryListSortMode(model->listId()));
|
||||
connect(model, &ApiCountryListModel::sortModeChanged, this, [repository, model]() {
|
||||
repository->setCountryListSortMode(model->listId(), model->sortMode());
|
||||
});
|
||||
}
|
||||
|
||||
connect(m_coreController->m_settingsUiController, &SettingsUiController::restoreBackupFinished, this, reloadSortModes);
|
||||
connect(repository, &SecureAppSettingsRepository::settingsCleared, this, reloadSortModes);
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initCountryListStateHandler()
|
||||
@@ -211,45 +197,10 @@ void CoreSignalHandlers::initCountryListStateHandler()
|
||||
const QVector<ApiCountryListModel *> models { m_coreController->m_apiCountryListModel,
|
||||
m_coreController->m_apiConfigsCountryListModel };
|
||||
|
||||
ApiCountryModel *countryModel = m_coreController->m_apiCountryModel;
|
||||
SecureServersRepository *serversRepository = m_coreController->m_serversRepository;
|
||||
|
||||
const auto reloadFavorites = [repository, models, countryModel]() {
|
||||
const QStringList codes = repository->favoriteLocations(countryModel->serverId());
|
||||
for (ApiCountryListModel *model : models) {
|
||||
model->setFavorites(codes);
|
||||
}
|
||||
};
|
||||
const auto migrateLegacyFavorites = [repository, serversRepository]() {
|
||||
QStringList apiServerIds;
|
||||
for (const QString &serverId : serversRepository->orderedServerIds()) {
|
||||
if (serversRepository->apiV2Config(serverId).has_value()) {
|
||||
apiServerIds.append(serverId);
|
||||
}
|
||||
}
|
||||
repository->migrateLegacyFavoriteLocations(apiServerIds);
|
||||
};
|
||||
migrateLegacyFavorites();
|
||||
reloadFavorites();
|
||||
|
||||
connect(countryModel, &ApiCountryModel::serverIdChanged, this, reloadFavorites);
|
||||
connect(m_coreController->m_settingsUiController, &SettingsUiController::restoreBackupFinished, this,
|
||||
[migrateLegacyFavorites, reloadFavorites]() {
|
||||
migrateLegacyFavorites();
|
||||
reloadFavorites();
|
||||
});
|
||||
connect(repository, &SecureAppSettingsRepository::settingsCleared, this, reloadFavorites);
|
||||
connect(serversRepository, &SecureServersRepository::serverRemoved, this,
|
||||
[repository, countryModel](const QString &serverId) {
|
||||
repository->removeFavoriteLocations(serverId);
|
||||
if (countryModel->serverId() == serverId) {
|
||||
countryModel->setServerId(QString());
|
||||
}
|
||||
});
|
||||
|
||||
for (ApiCountryListModel *model : models) {
|
||||
connect(model, &ApiCountryListModel::favoritesChanged, this, [repository, models, model, countryModel](const QStringList &codes) {
|
||||
repository->setFavoriteLocations(countryModel->serverId(), codes);
|
||||
model->setFavorites(repository->favoriteLocations());
|
||||
connect(model, &ApiCountryListModel::favoritesChanged, this, [repository, models, model](const QStringList &codes) {
|
||||
repository->setFavoriteLocations(codes);
|
||||
for (ApiCountryListModel *other : models) {
|
||||
if (other != model) {
|
||||
other->setFavorites(codes);
|
||||
@@ -272,7 +223,6 @@ void CoreSignalHandlers::initApiCountryModelUpdateHandler()
|
||||
return;
|
||||
}
|
||||
|
||||
m_coreController->m_apiCountryModel->setServerId(processedServerId);
|
||||
m_coreController->m_apiCountryModel->updateModel(apiV2->apiConfig.availableCountries,
|
||||
apiV2->apiConfig.serverCountryCode,
|
||||
apiV2->apiConfig.userCountryCode);
|
||||
@@ -459,16 +409,6 @@ void CoreSignalHandlers::initUnsupportedConnectDrawerHandler()
|
||||
m_coreController->m_pageController, &PageController::unsupportedConnectDrawerRequested);
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initBackNavigationHandler()
|
||||
{
|
||||
if (!m_coreController->m_focusController) {
|
||||
return;
|
||||
}
|
||||
|
||||
connect(m_coreController->m_focusController, &FocusController::backRequested,
|
||||
m_coreController->m_pageController, &PageController::escapePressed);
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initStrictKillSwitchHandler()
|
||||
{
|
||||
connect(m_coreController->m_settingsUiController, &SettingsUiController::strictKillSwitchEnabledChanged, m_coreController->m_connectionController,
|
||||
@@ -554,12 +494,3 @@ void CoreSignalHandlers::initUpdateFoundHandler()
|
||||
});
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initLocalProxyHandler()
|
||||
{
|
||||
connect(m_coreController->m_connectionController, &ConnectionController::openConnectionRequested, m_coreController->m_localProxyController,
|
||||
&LocalProxyController::onVpnConnectionRequested);
|
||||
connect(m_coreController->m_localProxyController, &LocalProxyController::stoppedByVpn, m_coreController->m_pageController,
|
||||
&PageController::showNotificationMessage);
|
||||
connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, m_coreController->m_localProxyController,
|
||||
&LocalProxyController::disable);
|
||||
}
|
||||
@@ -36,7 +36,6 @@ private:
|
||||
void initAppSplitTunnelingModelUpdateHandler();
|
||||
void initPrepareConfigHandler();
|
||||
void initUnsupportedConnectDrawerHandler();
|
||||
void initBackNavigationHandler();
|
||||
void initStrictKillSwitchHandler();
|
||||
void initAndroidSettingsHandler();
|
||||
void initAndroidConnectionHandler();
|
||||
@@ -44,7 +43,6 @@ private:
|
||||
void initIosSettingsHandler();
|
||||
void initNotificationHandler();
|
||||
void initUpdateFoundHandler();
|
||||
void initLocalProxyHandler();
|
||||
|
||||
CoreController* m_coreController;
|
||||
};
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
@startuml
|
||||
start
|
||||
|
||||
partition "**1. Request payload formation for Gateway**" {
|
||||
:Generate session keys for AES encryption (key, iv, salt);
|
||||
:Encrypt request data with AES;
|
||||
:Encrypt session keys with Gateway public key (RSA);
|
||||
:Assemble request payload:\nencrypted keys + encrypted data;
|
||||
}
|
||||
|
||||
:Send HTTP POST to Gateway;
|
||||
|
||||
if (Response successful?) then (yes)
|
||||
:Decode response using session keys;
|
||||
:Return data to caller;
|
||||
stop
|
||||
else (error)
|
||||
:Decode response body;
|
||||
|
||||
partition "**2. Error analysis**" {
|
||||
note right
|
||||
Response body (when decrypted successfully)
|
||||
contains http_status and error message.
|
||||
|
||||
**Handled errors**:
|
||||
• 404 + "No active configuration found for"
|
||||
• 404 + "No non-revoked public key found for"
|
||||
• 404 + "Account not found."
|
||||
• 501 + "client version update is required"
|
||||
• 409 Conflict
|
||||
end note
|
||||
|
||||
if (Handled error?) then (no)
|
||||
else (yes)
|
||||
:Return error to user;
|
||||
stop
|
||||
endif
|
||||
}
|
||||
|
||||
partition "**3. Obtaining proxy list**" {
|
||||
:Build proxy storage list;
|
||||
note right
|
||||
**Storage list formation:**
|
||||
For each baseUrl:
|
||||
• baseUrl + base64url("endpoints-{service_type}-{user_country_code}") + ".json"
|
||||
• baseUrl + "endpoints.json" (generic list)
|
||||
Specific first, then generic.
|
||||
end note
|
||||
while (Proxy list obtained?) is (no)
|
||||
:HTTPS GET to storage;
|
||||
if (Encrypted list received?) then (yes)
|
||||
note right
|
||||
**Proxy list decryption:**
|
||||
Data encrypted with AES.
|
||||
Key and IV derived from hash
|
||||
of Gateway public key (SHA512):
|
||||
first 64 hex chars — key, next 32 — IV.
|
||||
end note
|
||||
:Decrypt AES proxy list;
|
||||
else (no)
|
||||
if (All storages exhausted) then (no)
|
||||
:Next storage;
|
||||
else (yes)
|
||||
:Return original error to user;
|
||||
stop
|
||||
endif
|
||||
endif
|
||||
endwhile (yes)
|
||||
}
|
||||
|
||||
partition "**4. Bypass via proxy**" {
|
||||
while (Proxy in list) is (yes)
|
||||
:Check proxy availability (/lmbd-health);
|
||||
if (Proxy responds?) then (yes)
|
||||
:Send request via proxy\n(same payload);
|
||||
:Decode response;
|
||||
if (Response successful) then (yes)
|
||||
:Return data to caller;
|
||||
stop
|
||||
endif
|
||||
endif
|
||||
:Next proxy;
|
||||
endwhile (all exhausted)
|
||||
}
|
||||
|
||||
:Return error with last failed request to user;
|
||||
stop
|
||||
endif
|
||||
|
||||
@enduml
|
||||
@@ -5,13 +5,11 @@
|
||||
#include <QFutureWatcher>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QMetaObject>
|
||||
#include <QThread>
|
||||
#include <QUrl>
|
||||
#include <QtConcurrent/QtConcurrentRun>
|
||||
|
||||
#include "core/repositories/secureAppSettingsRepository.h"
|
||||
#include "core/utils/constants/apiConstants.h"
|
||||
#include "core/utils/constants/apiKeys.h"
|
||||
#include "core/utils/api/apiUtils.h"
|
||||
|
||||
@@ -34,9 +32,6 @@ namespace
|
||||
constexpr QLatin1String s3FallbackEndpoints("s3_fallback_endpoints");
|
||||
constexpr QLatin1String isDevEnvironment("is_dev_environment");
|
||||
constexpr QLatin1String requestTimeoutMsecs("request_timeout_msecs");
|
||||
constexpr QLatin1String proxyRequestTimeoutMsecs("proxy_request_timeout_msecs");
|
||||
constexpr QLatin1String proxyStorageTimeoutMsecs("proxy_storage_timeout_msecs");
|
||||
constexpr QLatin1String proxyHealthTimeoutMsecs("proxy_health_timeout_msecs");
|
||||
}
|
||||
|
||||
namespace agwOptionsKey
|
||||
@@ -45,11 +40,6 @@ namespace
|
||||
constexpr QLatin1String userCountryCode("user_country_code");
|
||||
}
|
||||
|
||||
namespace agwStateKey
|
||||
{
|
||||
constexpr QLatin1String workingProxy("working_proxy");
|
||||
}
|
||||
|
||||
constexpr QLatin1String agwStateCacheKey("agw_state");
|
||||
constexpr QLatin1String agwTracePrefix("agw trace:");
|
||||
constexpr QLatin1String agwTraceFailoverMarker("suspicious");
|
||||
@@ -81,7 +71,6 @@ GatewayController::GatewayController(const QString &gatewayEndpoint, const bool
|
||||
const bool isStrictKillSwitchEnabled, SecureAppSettingsRepository *appSettingsRepository,
|
||||
QObject *parent)
|
||||
: QObject(parent),
|
||||
m_gatewayEndpoint(gatewayEndpoint),
|
||||
m_isStrictKillSwitchEnabled(isStrictKillSwitchEnabled),
|
||||
m_appSettingsRepository(appSettingsRepository)
|
||||
{
|
||||
@@ -104,11 +93,13 @@ GatewayController::GatewayController(const QString &gatewayEndpoint, const bool
|
||||
config[agwConfigKey::s3FallbackEndpoints] = QJsonArray::fromStringList(fallbackEndpoints);
|
||||
config[agwConfigKey::isDevEnvironment] = isDevEnvironment;
|
||||
config[agwConfigKey::requestTimeoutMsecs] = requestTimeoutMsecs;
|
||||
config[agwConfigKey::proxyRequestTimeoutMsecs] = apiDefs::proxyRequestTimeoutMsecs;
|
||||
config[agwConfigKey::proxyStorageTimeoutMsecs] = apiDefs::proxyStorageTimeoutMsecs;
|
||||
config[agwConfigKey::proxyHealthTimeoutMsecs] = apiDefs::proxyHealthTimeoutMsecs;
|
||||
|
||||
m_client = agw_client_create(QJsonDocument(config).toJson(QJsonDocument::Compact).constData(), nullptr);
|
||||
agw_callbacks callbacks {};
|
||||
callbacks.struct_size = sizeof(agw_callbacks);
|
||||
callbacks.on_before_request = &GatewayController::onBeforeRequest;
|
||||
callbacks.on_before_request_user_data = this;
|
||||
|
||||
m_client = agw_client_create(QJsonDocument(config).toJson(QJsonDocument::Compact).constData(), &callbacks);
|
||||
if (m_client == 0) {
|
||||
qCritical() << "GatewayController: failed to create gateway client (missing key or endpoint?)";
|
||||
return;
|
||||
@@ -146,7 +137,6 @@ amnezia::ErrorCode GatewayController::post(const QString &endpoint, const QJsonO
|
||||
|
||||
QFuture<QPair<amnezia::ErrorCode, QByteArray>> GatewayController::postAsync(const QString &endpoint, const QJsonObject apiPayload)
|
||||
{
|
||||
prepareRequest();
|
||||
return QtConcurrent::run(
|
||||
[this, endpoint, apiPayload]() -> QPair<amnezia::ErrorCode, QByteArray> { return executePost(endpoint, apiPayload); });
|
||||
}
|
||||
@@ -200,56 +190,44 @@ amnezia::ErrorCode GatewayController::mapResultCode(const int code, const QByteA
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayController::prepareRequest()
|
||||
void GatewayController::onBeforeRequest(const char *host, void *userData)
|
||||
{
|
||||
auto *controller = static_cast<GatewayController *>(userData);
|
||||
const QString hostString = QString::fromUtf8(host);
|
||||
if (QThread::currentThread() == controller->thread()) {
|
||||
controller->handleBeforeRequest(hostString);
|
||||
} else {
|
||||
QMetaObject::invokeMethod(
|
||||
controller, [controller, hostString]() { controller->handleBeforeRequest(hostString); },
|
||||
Qt::BlockingQueuedConnection);
|
||||
}
|
||||
}
|
||||
|
||||
void GatewayController::handleBeforeRequest(const QString &host)
|
||||
{
|
||||
#ifdef Q_OS_IOS
|
||||
Q_UNUSED(host);
|
||||
IosController::Instance()->requestInetAccess();
|
||||
QThread::msleep(10);
|
||||
#endif
|
||||
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
if (!m_isStrictKillSwitchEnabled) {
|
||||
return;
|
||||
}
|
||||
QStringList ips;
|
||||
for (const QString &host : requestHosts()) {
|
||||
if (m_isStrictKillSwitchEnabled) {
|
||||
const QString ip = NetworkUtilities::getIPAddress(host);
|
||||
if (!ip.isEmpty()) {
|
||||
ips.append(ip);
|
||||
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
QRemoteObjectPendingReply<bool> reply = iface->addKillSwitchAllowedRange(QStringList { ip });
|
||||
if (!reply.waitForFinished(1000) || !reply.returnValue()) {
|
||||
qWarning() << "GatewayController::handleBeforeRequest(): failed to add killswitch exception for" << host;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if (ips.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
QRemoteObjectPendingReply<bool> reply = iface->addKillSwitchAllowedRanges(ips);
|
||||
if (!reply.waitForFinished(1000) || !reply.returnValue()) {
|
||||
qWarning()
|
||||
<< "GatewayController::prepareRequest(): Failed to execute remote addKillSwitchAllowedRanges call";
|
||||
}
|
||||
});
|
||||
#else
|
||||
Q_UNUSED(host);
|
||||
#endif
|
||||
}
|
||||
|
||||
QStringList GatewayController::requestHosts() const
|
||||
{
|
||||
QStringList hosts;
|
||||
if (m_client != 0) {
|
||||
char *state = agw_export_state(m_client);
|
||||
if (state != nullptr) {
|
||||
const QString workingProxy = QJsonDocument::fromJson(QByteArray(state)).object().value(agwStateKey::workingProxy).toString();
|
||||
agw_string_free(state);
|
||||
if (!workingProxy.isEmpty()) {
|
||||
hosts.append(QUrl(workingProxy).host());
|
||||
}
|
||||
}
|
||||
}
|
||||
hosts.append(QUrl(m_gatewayEndpoint).host());
|
||||
hosts.removeAll(QString());
|
||||
hosts.removeDuplicates();
|
||||
return hosts;
|
||||
}
|
||||
|
||||
void GatewayController::persistState()
|
||||
{
|
||||
if (m_client == 0 || m_appSettingsRepository == nullptr) {
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include <QObject>
|
||||
#include <QPair>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
#include "core/utils/errorCodes.h"
|
||||
|
||||
@@ -29,14 +28,13 @@ public:
|
||||
QFuture<QPair<amnezia::ErrorCode, QByteArray>> postAsync(const QString &endpoint, const QJsonObject apiPayload);
|
||||
|
||||
private:
|
||||
void prepareRequest();
|
||||
QStringList requestHosts() const;
|
||||
static void onBeforeRequest(const char *host, void *userData);
|
||||
void handleBeforeRequest(const QString &host);
|
||||
|
||||
QPair<amnezia::ErrorCode, QByteArray> executePost(const QString &endpoint, const QJsonObject &apiPayload);
|
||||
static amnezia::ErrorCode mapResultCode(const int code, const QByteArray &responseBody);
|
||||
void persistState();
|
||||
|
||||
QString m_gatewayEndpoint;
|
||||
bool m_isStrictKillSwitchEnabled = false;
|
||||
SecureAppSettingsRepository *m_appSettingsRepository = nullptr;
|
||||
|
||||
|
||||
@@ -1,525 +0,0 @@
|
||||
#include "localProxyController.h"
|
||||
|
||||
#include "core/controllers/api/subscriptionController.h"
|
||||
#include "core/repositories/secureAppSettingsRepository.h"
|
||||
#include "core/repositories/secureServersRepository.h"
|
||||
#include "core/utils/api/apiUtils.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "core/utils/localProxy/localProxyConstants.h"
|
||||
#include "core/utils/localProxy/portUtils.h"
|
||||
#include "core/utils/localProxy/xrayConfigUtils.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonParseError>
|
||||
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
#include "core/utils/ipcClient.h"
|
||||
|
||||
#include <QHostAddress>
|
||||
#include <QHttpServer>
|
||||
#include <QHttpServerResponse>
|
||||
#include <QTcpServer>
|
||||
#endif
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
LocalProxyController::LocalProxyController(SecureServersRepository *serversRepository,
|
||||
SecureAppSettingsRepository *appSettingsRepository,
|
||||
SubscriptionController *subscriptionController, QObject *parent)
|
||||
: QObject(parent),
|
||||
m_serversRepository(serversRepository),
|
||||
m_appSettingsRepository(appSettingsRepository),
|
||||
m_subscriptionController(subscriptionController)
|
||||
{
|
||||
connect(m_serversRepository, &SecureServersRepository::serverEdited, this, &LocalProxyController::onServerEdited);
|
||||
connect(m_serversRepository, &SecureServersRepository::serverRemoved, this,
|
||||
[this](const QString &serverId, int) { onServerRemoved(serverId); });
|
||||
}
|
||||
|
||||
LocalProxyController::~LocalProxyController()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
bool LocalProxyController::isEnabled() const
|
||||
{
|
||||
return m_isEnabled;
|
||||
}
|
||||
|
||||
int LocalProxyController::port() const
|
||||
{
|
||||
return m_appSettingsRepository->localProxyPort();
|
||||
}
|
||||
|
||||
QString LocalProxyController::ownerId() const
|
||||
{
|
||||
return m_appSettingsRepository->localProxyOwnerId();
|
||||
}
|
||||
|
||||
bool LocalProxyController::isPortUserDefined() const
|
||||
{
|
||||
return m_appSettingsRepository->isLocalProxyPortUserDefined();
|
||||
}
|
||||
|
||||
int LocalProxyController::activePort() const
|
||||
{
|
||||
return m_activePort;
|
||||
}
|
||||
|
||||
bool LocalProxyController::setPort(int port)
|
||||
{
|
||||
if (port < localProxy::proxyPortMin || port > localProxy::proxyPortMax) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool isPortChanged = m_appSettingsRepository->localProxyPort() != static_cast<quint16>(port);
|
||||
if (isPortChanged) {
|
||||
m_appSettingsRepository->setLocalProxyPort(static_cast<quint16>(port));
|
||||
m_lastProxyPort = 0;
|
||||
}
|
||||
m_appSettingsRepository->setLocalProxyPortUserDefined(true);
|
||||
emit stateChanged();
|
||||
|
||||
if (isPortChanged && m_isEnabled) {
|
||||
start();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LocalProxyController::isPortBusy(int port) const
|
||||
{
|
||||
return !portUtils::isPortAvailable(port);
|
||||
}
|
||||
|
||||
bool LocalProxyController::enable(const QString &ownerId, int port)
|
||||
{
|
||||
if (!isSupported() || ownerId.isEmpty() || port < localProxy::proxyPortMin || port > localProxy::proxyPortMax) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_isEnabled && m_appSettingsRepository->localProxyOwnerId() != ownerId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!portUtils::isPortAvailable(port) && m_appSettingsRepository->isLocalProxyPortUserDefined()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_appSettingsRepository->localProxyPort() != static_cast<quint16>(port)) {
|
||||
m_appSettingsRepository->setLocalProxyPort(static_cast<quint16>(port));
|
||||
}
|
||||
|
||||
m_appSettingsRepository->setLocalProxyOwnerId(ownerId);
|
||||
m_isEnabled = true;
|
||||
emit stateChanged();
|
||||
|
||||
start();
|
||||
return true;
|
||||
}
|
||||
|
||||
void LocalProxyController::disable()
|
||||
{
|
||||
if (!m_isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "Local proxy is disabled";
|
||||
m_isEnabled = false;
|
||||
stop();
|
||||
emit stateChanged();
|
||||
}
|
||||
|
||||
void LocalProxyController::start()
|
||||
{
|
||||
if (!startApi(localProxy::apiPort)) {
|
||||
disableWithError(tr("Local proxy failed to start. Check if the port is available"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!syncXray()) {
|
||||
disableWithError(tr("Couldn’t start the proxy due to an internal error. Try restarting the app"));
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "Local proxy is running on 127.0.0.1:" << m_apiPort;
|
||||
}
|
||||
|
||||
void LocalProxyController::onServerEdited(const QString &serverId)
|
||||
{
|
||||
if (m_apiPort == 0 || m_appSettingsRepository->localProxyOwnerId() != serverId) {
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "Owner server edited, restarting Xray";
|
||||
if (restartXray()) {
|
||||
m_appliedProxyPort = m_appSettingsRepository->localProxyPort();
|
||||
} else {
|
||||
disableWithError(tr("Couldn’t start the proxy due to an internal error. Try restarting the app"));
|
||||
}
|
||||
}
|
||||
|
||||
void LocalProxyController::onServerRemoved(const QString &serverId)
|
||||
{
|
||||
if (m_appSettingsRepository->localProxyOwnerId() != serverId) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_appSettingsRepository->setLocalProxyOwnerId(QString());
|
||||
if (m_isEnabled) {
|
||||
m_isEnabled = false;
|
||||
stop();
|
||||
}
|
||||
emit stateChanged();
|
||||
}
|
||||
|
||||
void LocalProxyController::onVpnConnectionRequested()
|
||||
{
|
||||
if (!m_isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
disable();
|
||||
emit stoppedByVpn(tr("Local proxy stopped because VPN was turned on"));
|
||||
}
|
||||
|
||||
void LocalProxyController::stop()
|
||||
{
|
||||
stopXray();
|
||||
stopApi();
|
||||
m_appliedProxyPort = 0;
|
||||
}
|
||||
|
||||
bool LocalProxyController::syncXray()
|
||||
{
|
||||
const quint16 proxyPort = m_appSettingsRepository->localProxyPort();
|
||||
|
||||
bool synced;
|
||||
if (!isXrayEngineRunning()) {
|
||||
synced = startXray();
|
||||
} else if (m_appliedProxyPort != proxyPort) {
|
||||
qDebug() << "Proxy port changed from" << m_appliedProxyPort << "to" << proxyPort;
|
||||
synced = restartXray();
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (synced) {
|
||||
m_appliedProxyPort = proxyPort;
|
||||
}
|
||||
return synced;
|
||||
}
|
||||
|
||||
bool LocalProxyController::startXray()
|
||||
{
|
||||
if (isXrayEngineRunning()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto config = buildXrayConfig();
|
||||
if (!config) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!startXrayEngine(config->serializedConfig)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
qDebug() << "Xray started on port" << config->proxyPort;
|
||||
m_lastProxyPort = config->proxyPort;
|
||||
setActivePort(config->proxyPort);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LocalProxyController::stopXray()
|
||||
{
|
||||
if (!stopXrayEngine()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
setActivePort(0);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LocalProxyController::restartXray()
|
||||
{
|
||||
qDebug() << "Restarting Xray with updated config";
|
||||
|
||||
if (!stopXray()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return startXray();
|
||||
}
|
||||
|
||||
void LocalProxyController::disableWithError(const QString &message)
|
||||
{
|
||||
qWarning() << message;
|
||||
|
||||
m_isEnabled = false;
|
||||
stop();
|
||||
emit stateChanged();
|
||||
emit startFailed(message);
|
||||
}
|
||||
|
||||
void LocalProxyController::setActivePort(int port)
|
||||
{
|
||||
if (m_activePort == port) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_activePort = port;
|
||||
emit stateChanged();
|
||||
}
|
||||
|
||||
int LocalProxyController::runningProxyPort() const
|
||||
{
|
||||
return isXrayEngineRunning() ? m_activePort : 0;
|
||||
}
|
||||
|
||||
std::optional<LocalProxyController::XrayConfig> LocalProxyController::buildXrayConfig() const
|
||||
{
|
||||
const QString ownerId = m_appSettingsRepository->localProxyOwnerId();
|
||||
if (ownerId.isEmpty()) {
|
||||
qWarning() << "Local proxy owner server id is not configured";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto ownerServer = m_serversRepository->serverJsonById(ownerId);
|
||||
if (!ownerServer) {
|
||||
qWarning() << "Owner server with id" << ownerId << "not found";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (!apiUtils::isPremiumServer(*ownerServer)) {
|
||||
qWarning() << "Server" << ownerServer->value(configKey::name).toString() << "is not premium, local proxy is unavailable";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto serializedConfig = xrayConfigUtils::extractLastConfig(*ownerServer);
|
||||
if (!serializedConfig) {
|
||||
serializedConfig = fetchXrayConfigFromGateway(ownerId);
|
||||
if (!serializedConfig) {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
QJsonParseError parseError;
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(serializedConfig->toUtf8(), &parseError);
|
||||
if (parseError.error != QJsonParseError::NoError || !doc.isObject()) {
|
||||
qWarning() << "Failed to parse Xray config JSON:" << parseError.errorString();
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto proxyPort = selectProxyPort();
|
||||
if (!proxyPort) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
QJsonObject xrayConfig = doc.object();
|
||||
if (!xrayConfigUtils::setInboundPort(xrayConfig, *proxyPort)) {
|
||||
qWarning() << "Failed to override local proxy inbound port, using original config";
|
||||
return XrayConfig { *serializedConfig, 0 };
|
||||
}
|
||||
|
||||
return XrayConfig { QString::fromUtf8(QJsonDocument(xrayConfig).toJson(QJsonDocument::Compact)), *proxyPort };
|
||||
}
|
||||
|
||||
std::optional<int> LocalProxyController::selectProxyPort() const
|
||||
{
|
||||
int basePort = m_appSettingsRepository->localProxyPort();
|
||||
if (basePort < localProxy::proxyPortMin || basePort > localProxy::proxyPortMax) {
|
||||
basePort = localProxy::defaultProxyPort;
|
||||
}
|
||||
|
||||
if (m_lastProxyPort == 0) {
|
||||
if (portUtils::waitForPort(basePort, localProxy::portReleaseWaitMs)) {
|
||||
return basePort;
|
||||
}
|
||||
|
||||
if (m_appSettingsRepository->isLocalProxyPortUserDefined()) {
|
||||
qWarning() << "Local proxy port" << basePort << "is already in use";
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
// Every restart moves to a new port so that clients notice the change and reconnect.
|
||||
const int from = (m_lastProxyPort > 0 ? m_lastProxyPort : basePort) + 1;
|
||||
auto freePort = portUtils::findFirstAvailablePort(from, localProxy::proxyPortMax);
|
||||
if (!freePort && from > basePort + 1) {
|
||||
freePort = portUtils::findFirstAvailablePort(basePort, from - 2);
|
||||
}
|
||||
|
||||
if (!freePort) {
|
||||
qWarning() << "No available local proxy port in range" << basePort << "-" << localProxy::proxyPortMax;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
qDebug() << "Local proxy port selected:" << *freePort << "(previous" << m_lastProxyPort << ")";
|
||||
return freePort;
|
||||
}
|
||||
|
||||
std::optional<QString> LocalProxyController::fetchXrayConfigFromGateway(const QString &serverId) const
|
||||
{
|
||||
QJsonObject serverConfig;
|
||||
const ErrorCode errorCode = m_subscriptionController->fetchServiceConfig(serverId, configKey::vless, serverConfig);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
qWarning() << "Gateway request failed with error code" << static_cast<int>(errorCode);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto serializedConfig = xrayConfigUtils::extractLastConfig(serverConfig);
|
||||
if (!serializedConfig) {
|
||||
qWarning() << "Gateway response lacks Xray last_config payload";
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
qDebug() << "Fetched Xray config from gateway";
|
||||
return serializedConfig;
|
||||
}
|
||||
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
|
||||
bool LocalProxyController::isSupported() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LocalProxyController::startApi(quint16 port)
|
||||
{
|
||||
if (m_httpServer && m_apiPort == port) {
|
||||
return true;
|
||||
}
|
||||
|
||||
stopApi();
|
||||
|
||||
const auto proxyPortValue = [this]() {
|
||||
const int port = runningProxyPort();
|
||||
return port > 0 ? QJsonValue(port) : QJsonValue::Null;
|
||||
};
|
||||
|
||||
auto httpServer = new QHttpServer(this);
|
||||
httpServer->route("/api/v1/up", QHttpServerRequest::Method::Post, [this, proxyPortValue] {
|
||||
const bool started = startXray();
|
||||
return QHttpServerResponse(QJsonObject { { "status", started ? "ok" : "error" }, { "proxyPort", proxyPortValue() } });
|
||||
});
|
||||
httpServer->route("/api/v1/down", QHttpServerRequest::Method::Post, [this] {
|
||||
const bool stopped = stopXray();
|
||||
return QHttpServerResponse(QJsonObject { { "status", stopped ? "ok" : "error" } });
|
||||
});
|
||||
httpServer->route("/api/v1/ping", QHttpServerRequest::Method::Get, [proxyPortValue] {
|
||||
return QHttpServerResponse(QJsonObject { { "status", "ok" }, { "proxyPort", proxyPortValue() } });
|
||||
});
|
||||
|
||||
auto tcpServer = new QTcpServer(httpServer);
|
||||
if (!tcpServer->listen(QHostAddress::LocalHost, port) || !httpServer->bind(tcpServer)) {
|
||||
qWarning() << "Failed to start HTTP API server on port" << port;
|
||||
delete httpServer;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_httpServer = httpServer;
|
||||
m_apiPort = port;
|
||||
qDebug() << "HTTP API server is running on localhost:" << port;
|
||||
return true;
|
||||
}
|
||||
|
||||
void LocalProxyController::stopApi()
|
||||
{
|
||||
delete m_httpServer;
|
||||
m_httpServer = nullptr;
|
||||
m_apiPort = 0;
|
||||
}
|
||||
|
||||
bool LocalProxyController::startXrayEngine(const QString &config)
|
||||
{
|
||||
if (config.trimmed().isEmpty()) {
|
||||
qWarning() << "Xray config content is empty";
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool started = IpcClient::withInterface(
|
||||
[&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
auto localProxyUp = iface->localProxyUp(config);
|
||||
return localProxyUp.waitForFinished() && localProxyUp.returnValue();
|
||||
},
|
||||
[]() { return false; });
|
||||
|
||||
if (!started) {
|
||||
qWarning() << "Failed to start local proxy via IPC";
|
||||
return false;
|
||||
}
|
||||
|
||||
m_isXrayEngineStarted = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LocalProxyController::stopXrayEngine()
|
||||
{
|
||||
if (!m_isXrayEngineStarted) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const bool stopped = IpcClient::withInterface(
|
||||
[](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
auto localProxyDown = iface->localProxyDown();
|
||||
return localProxyDown.waitForFinished() && localProxyDown.returnValue();
|
||||
},
|
||||
[]() { return false; });
|
||||
|
||||
if (!stopped) {
|
||||
qWarning() << "Failed to stop local proxy via IPC";
|
||||
return false;
|
||||
}
|
||||
|
||||
m_isXrayEngineStarted = false;
|
||||
qDebug() << "Local proxy stopped";
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LocalProxyController::isXrayEngineRunning() const
|
||||
{
|
||||
if (!m_isXrayEngineStarted) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return IpcClient::withInterface(
|
||||
[](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
auto isRunning = iface->isLocalProxyRunning();
|
||||
return isRunning.waitForFinished() && isRunning.returnValue();
|
||||
},
|
||||
[]() { return false; });
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
bool LocalProxyController::isSupported() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LocalProxyController::startApi(quint16)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void LocalProxyController::stopApi()
|
||||
{
|
||||
}
|
||||
|
||||
bool LocalProxyController::startXrayEngine(const QString &)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LocalProxyController::stopXrayEngine()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LocalProxyController::isXrayEngineRunning() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,85 +0,0 @@
|
||||
#ifndef LOCALPROXYCONTROLLER_H
|
||||
#define LOCALPROXYCONTROLLER_H
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
class QHttpServer;
|
||||
class SecureServersRepository;
|
||||
class SecureAppSettingsRepository;
|
||||
class SubscriptionController;
|
||||
|
||||
class LocalProxyController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
LocalProxyController(SecureServersRepository *serversRepository, SecureAppSettingsRepository *appSettingsRepository,
|
||||
SubscriptionController *subscriptionController, QObject *parent = nullptr);
|
||||
~LocalProxyController();
|
||||
|
||||
bool isSupported() const;
|
||||
bool isEnabled() const;
|
||||
int port() const;
|
||||
QString ownerId() const;
|
||||
bool isPortUserDefined() const;
|
||||
int activePort() const;
|
||||
|
||||
bool setPort(int port);
|
||||
bool isPortBusy(int port) const;
|
||||
bool enable(const QString &ownerId, int port);
|
||||
void disable();
|
||||
|
||||
public slots:
|
||||
void onServerEdited(const QString &serverId);
|
||||
void onServerRemoved(const QString &serverId);
|
||||
void onVpnConnectionRequested();
|
||||
|
||||
signals:
|
||||
void stateChanged();
|
||||
void startFailed(const QString &message);
|
||||
void stoppedByVpn(const QString &message);
|
||||
|
||||
private:
|
||||
struct XrayConfig
|
||||
{
|
||||
QString serializedConfig;
|
||||
int proxyPort = 0;
|
||||
};
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
bool syncXray();
|
||||
bool startXray();
|
||||
bool stopXray();
|
||||
bool restartXray();
|
||||
void disableWithError(const QString &message);
|
||||
void setActivePort(int port);
|
||||
int runningProxyPort() const;
|
||||
|
||||
std::optional<XrayConfig> buildXrayConfig() const;
|
||||
std::optional<int> selectProxyPort() const;
|
||||
std::optional<QString> fetchXrayConfigFromGateway(const QString &serverId) const;
|
||||
|
||||
bool startApi(quint16 port);
|
||||
void stopApi();
|
||||
bool startXrayEngine(const QString &config);
|
||||
bool stopXrayEngine();
|
||||
bool isXrayEngineRunning() const;
|
||||
|
||||
SecureServersRepository *m_serversRepository;
|
||||
SecureAppSettingsRepository *m_appSettingsRepository;
|
||||
SubscriptionController *m_subscriptionController;
|
||||
|
||||
bool m_isEnabled = false;
|
||||
QHttpServer *m_httpServer = nullptr;
|
||||
quint16 m_apiPort = 0;
|
||||
bool m_isXrayEngineStarted = false;
|
||||
quint16 m_appliedProxyPort = 0;
|
||||
int m_activePort = 0;
|
||||
int m_lastProxyPort = 0;
|
||||
};
|
||||
|
||||
#endif // LOCALPROXYCONTROLLER_H
|
||||
@@ -779,11 +779,8 @@ void ImportController::processAmneziaConfig(QJsonObject &config) const
|
||||
}
|
||||
|
||||
QJsonObject jsonConfig = QJsonDocument::fromJson(protocolConfig.toUtf8()).object();
|
||||
if (jsonConfig.value(configKey::mtu).toString().trimmed().isEmpty()) {
|
||||
jsonConfig[configKey::mtu] = ContainerUtils::isAwgContainer(dockerContainer)
|
||||
? protocols::awg::defaultMtu
|
||||
: protocols::wireguard::defaultMtu;
|
||||
}
|
||||
jsonConfig[configKey::mtu] =
|
||||
ContainerUtils::isAwgContainer(dockerContainer) ? protocols::awg::defaultMtu : protocols::wireguard::defaultMtu;
|
||||
|
||||
containerConfig[configKey::lastConfig] = QString(QJsonDocument(jsonConfig).toJson());
|
||||
|
||||
@@ -793,3 +790,4 @@ void ImportController::processAmneziaConfig(QJsonObject &config) const
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,7 +296,7 @@ ErrorCode InstallController::updateServerConfig(const QString &serverId, DockerC
|
||||
portReinstall = isReinstallContainerRequired(container, oldConfig, newConfig);
|
||||
reinstallRequired = portReinstall;
|
||||
}
|
||||
if (container == DockerContainer::Xray && serverSettingsChanged) {
|
||||
if (container == DockerContainer::Xray) {
|
||||
if (credentials.isValid()) {
|
||||
binaryMismatch = xrayServerBinaryMismatchesExpected(credentials, sshSession);
|
||||
if (binaryMismatch) {
|
||||
@@ -1244,7 +1244,7 @@ ErrorCode InstallController::isServerDpkgBusy(const ServerCredentials &credentia
|
||||
if (stdOut.isEmpty()) {
|
||||
return ErrorCode::NoError;
|
||||
} else {
|
||||
#ifdef QT_DEBUG
|
||||
#ifdef MZ_DEBUG
|
||||
qDebug().noquote() << stdOut;
|
||||
#endif
|
||||
emit serverIsBusy(true);
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkReply>
|
||||
#include <QStandardPaths>
|
||||
#include <QTemporaryDir>
|
||||
#include <QTimer>
|
||||
#include <QUrl>
|
||||
|
||||
@@ -15,7 +13,6 @@
|
||||
#include "core/controllers/gatewayController.h"
|
||||
#include "core/utils/api/gatewayPayloadBuilder.h"
|
||||
#include "core/utils/appUiConfig.h"
|
||||
#include "core/utils/constants/apiConstants.h"
|
||||
#include "core/utils/constants/apiKeys.h"
|
||||
#include "core/utils/selfhosted/scriptsRegistry.h"
|
||||
|
||||
@@ -143,7 +140,7 @@ void UpdateController::checkForUpdates()
|
||||
|
||||
auto gatewayController = QSharedPointer<GatewayController>::create(m_appSettingsRepository->getGatewayEndpoint(),
|
||||
m_appSettingsRepository->isDevGatewayEnv(),
|
||||
apiDefs::requestTimeoutMsecs,
|
||||
7000,
|
||||
m_appSettingsRepository->isStrictKillSwitchEnabled(),
|
||||
m_appSettingsRepository);
|
||||
|
||||
|
||||
@@ -518,14 +518,6 @@ namespace xrayEffective
|
||||
return (rawTransport && secureFlow) ? srv.flow : QString();
|
||||
}
|
||||
|
||||
QString serverFlow(const XrayServerConfig &srv)
|
||||
{
|
||||
QString flow = clientFlow(srv);
|
||||
if (flow.endsWith(QLatin1String("-udp443")))
|
||||
flow.chop(int(qstrlen("-udp443")));
|
||||
return flow;
|
||||
}
|
||||
|
||||
QString network(const XrayServerConfig &srv)
|
||||
{
|
||||
namespace px = protocols::xray;
|
||||
@@ -584,7 +576,7 @@ QJsonObject XrayServerConfig::streamSettingsJson(XrayStreamSide side) const
|
||||
}
|
||||
if (!alpnArray.isEmpty())
|
||||
tlsSettings[px::alpn] = alpnArray;
|
||||
if (clientSide && transport != QLatin1String(px::transportMkcp))
|
||||
if (clientSide)
|
||||
tlsSettings[px::fingerprint] = fingerprintEff;
|
||||
if (!clientSide && !isThirdPartyConfig) {
|
||||
QJsonObject cert;
|
||||
@@ -605,11 +597,9 @@ QJsonObject XrayServerConfig::streamSettingsJson(XrayStreamSide side) const
|
||||
if (transport == QLatin1String(px::transportXhttp)) {
|
||||
QJsonObject xo;
|
||||
xo[px::xhttpHost] = xhttp.host.isEmpty() ? QString::fromLatin1(px::defaultXhttpHost) : xhttp.host;
|
||||
QString pathEff = xhttp.path.trimmed().isEmpty()
|
||||
const QString pathEff = xhttp.path.trimmed().isEmpty()
|
||||
? QString::fromLatin1(px::defaultXhttpPath)
|
||||
: xhttp.path.trimmed();
|
||||
if (!pathEff.startsWith(QLatin1Char('/')))
|
||||
pathEff.prepend(QLatin1Char('/'));
|
||||
: xhttp.path;
|
||||
xo[px::xhttpPath] = pathEff;
|
||||
const QString modeEff = xrayEffective::xhttpModeSent(*this);
|
||||
xo[px::xhttpMode] = modeEff;
|
||||
@@ -1196,10 +1186,7 @@ XrayServerJsonStatus XrayServerConfig::fromServerInboundJson(const QJsonObject &
|
||||
if (inbound.contains(px::settings)) {
|
||||
const QJsonArray clients = inbound.value(px::settings).toObject().value(px::clients).toArray();
|
||||
if (!clients.isEmpty()) {
|
||||
const QString inboundFlow = clients[0].toObject().value(px::flow).toString();
|
||||
if (srv.flow != inboundFlow + QLatin1String("-udp443")) {
|
||||
srv.flow = inboundFlow;
|
||||
}
|
||||
srv.flow = clients[0].toObject().value(px::flow).toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1318,7 +1305,7 @@ QJsonObject XrayServerConfig::serverView() const
|
||||
view[QStringLiteral("port")] = port.isEmpty() ? QString::fromLatin1(px::defaultPort) : port;
|
||||
view[QStringLiteral("transportProto")] = transportProto;
|
||||
|
||||
view[px::flow] = xrayEffective::serverFlow(*this);
|
||||
view[px::flow] = xrayEffective::clientFlow(*this);
|
||||
|
||||
view[px::streamSettings] = serverStreamSettings();
|
||||
|
||||
|
||||
@@ -187,7 +187,6 @@ namespace xrayEffective
|
||||
|
||||
QString security(const XrayServerConfig &srv);
|
||||
QString clientFlow(const XrayServerConfig &srv);
|
||||
QString serverFlow(const XrayServerConfig &srv);
|
||||
QString network(const XrayServerConfig &srv);
|
||||
QString xhttpModeSent(const XrayServerConfig &srv);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
#include "awgProtocol.h"
|
||||
|
||||
Awg::Awg(const QJsonObject &configuration, QObject *parent)
|
||||
: WireguardProtocol(configuration, parent)
|
||||
{
|
||||
}
|
||||
|
||||
Awg::~Awg()
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#ifndef AWGPROTOCOL_H
|
||||
#define AWGPROTOCOL_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "wireGuardProtocol.h"
|
||||
|
||||
class Awg : public WireguardProtocol
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit Awg(const QJsonObject &configuration, QObject *parent = nullptr);
|
||||
virtual ~Awg() override;
|
||||
};
|
||||
|
||||
#endif // AWGPROTOCOL_H
|
||||
@@ -0,0 +1,332 @@
|
||||
#include <QCoreApplication>
|
||||
#include <QFileInfo>
|
||||
#include <QProcess>
|
||||
|
||||
#include <QThread>
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include "ipc.h"
|
||||
#include "logger.h"
|
||||
#include "ikev2VpnProtocolWindows.h"
|
||||
#include "core/utils/utilities.h"
|
||||
#include "core/protocols/protocolUtils.h"
|
||||
|
||||
|
||||
static Ikev2Protocol* self = nullptr;
|
||||
static std::mutex rasDialFuncMutex;
|
||||
|
||||
extern "C" {
|
||||
static void WINAPI RasDialFuncCallback(UINT unMsg,
|
||||
RASCONNSTATE rasconnstate,
|
||||
DWORD dwError );
|
||||
}
|
||||
|
||||
Ikev2Protocol::Ikev2Protocol(const QJsonObject &configuration, QObject* parent) :
|
||||
VpnProtocol(configuration, parent)
|
||||
{
|
||||
self = this;
|
||||
readIkev2Configuration(configuration);
|
||||
}
|
||||
|
||||
Ikev2Protocol::~Ikev2Protocol()
|
||||
{
|
||||
qDebug() << "IpsecProtocol::~IpsecProtocol()";
|
||||
Ikev2Protocol::stop();
|
||||
}
|
||||
|
||||
void Ikev2Protocol::stop()
|
||||
{
|
||||
setConnectionState(Vpn::ConnectionState::Disconnecting);
|
||||
{
|
||||
if (!disconnect_vpn()){
|
||||
qDebug()<<"We don't disconnect";
|
||||
setConnectionState(Vpn::ConnectionState::Error);
|
||||
}
|
||||
else {
|
||||
setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Ikev2Protocol::newConnectionStateEventReceived(UINT unMsg, tagRASCONNSTATE rasconnstate, DWORD dwError)
|
||||
{
|
||||
Q_UNUSED(unMsg);
|
||||
qDebug()<<"Receive the new event "<<static_cast<int>(rasconnstate);
|
||||
switch (rasconnstate)
|
||||
{
|
||||
case RASCS_OpenPort:
|
||||
//qDebug()<<__FUNCTION__ << __LINE__;
|
||||
setConnectionState(Vpn::ConnectionState::Preparing);
|
||||
break;
|
||||
case RASCS_PortOpened:
|
||||
//qDebug()<<__FUNCTION__ << __LINE__;
|
||||
setConnectionState(Vpn::ConnectionState::Preparing);
|
||||
break;
|
||||
case RASCS_ConnectDevice:
|
||||
//qDebug()<<__FUNCTION__ << __LINE__;
|
||||
setConnectionState(Vpn::ConnectionState::Preparing);
|
||||
break;
|
||||
case RASCS_DeviceConnected:
|
||||
//qDebug()<<__FUNCTION__ << __LINE__;
|
||||
setConnectionState(Vpn::ConnectionState::Preparing);
|
||||
break;
|
||||
case RASCS_AllDevicesConnected:
|
||||
//qDebug()<<__FUNCTION__ << __LINE__;
|
||||
setConnectionState(Vpn::ConnectionState::Preparing);
|
||||
break;
|
||||
case RASCS_Authenticate:
|
||||
//qDebug()<<__FUNCTION__ << __LINE__;
|
||||
setConnectionState(Vpn::ConnectionState::Preparing);
|
||||
break;
|
||||
case RASCS_AuthNotify:
|
||||
//qDebug()<<__FUNCTION__ << __LINE__;
|
||||
if (dwError != 0) {
|
||||
qDebug() << "have error" << dwError;
|
||||
setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||
} else {
|
||||
qDebug() << "RASCS_AuthNotify but no error" << dwError;
|
||||
}
|
||||
break;
|
||||
case RASCS_AuthRetry:
|
||||
//qDebug()<<__FUNCTION__ << __LINE__;
|
||||
setConnectionState(Vpn::ConnectionState::Preparing);
|
||||
break;
|
||||
case RASCS_AuthCallback:
|
||||
qDebug()<<__FUNCTION__ << __LINE__;
|
||||
break;
|
||||
case RASCS_AuthChangePassword:
|
||||
qDebug()<<__FUNCTION__ << __LINE__;
|
||||
break;
|
||||
case RASCS_AuthProject:
|
||||
qDebug()<<__FUNCTION__ << __LINE__;
|
||||
break;
|
||||
case RASCS_AuthLinkSpeed:
|
||||
qDebug()<<__FUNCTION__ << __LINE__;
|
||||
break;
|
||||
case RASCS_AuthAck:
|
||||
qDebug()<<__FUNCTION__ << __LINE__;
|
||||
break;
|
||||
case RASCS_ReAuthenticate:
|
||||
//qDebug()<<__FUNCTION__ << __LINE__;
|
||||
break;
|
||||
case RASCS_Authenticated:
|
||||
//qDebug()<<__FUNCTION__ << __LINE__;
|
||||
break;
|
||||
case RASCS_PrepareForCallback:
|
||||
qDebug()<<__FUNCTION__ << __LINE__;
|
||||
break;
|
||||
case RASCS_WaitForModemReset:
|
||||
qDebug()<<__FUNCTION__ << __LINE__;
|
||||
break;
|
||||
case RASCS_WaitForCallback:
|
||||
qDebug()<<__FUNCTION__ << __LINE__;
|
||||
break;
|
||||
case RASCS_Projected:
|
||||
qDebug()<<__FUNCTION__ << __LINE__;
|
||||
break;
|
||||
#if (WINVER >= 0x400)
|
||||
case RASCS_StartAuthentication: // Windows 95 only
|
||||
qDebug()<<__FUNCTION__ << __LINE__;
|
||||
break;
|
||||
case RASCS_CallbackComplete: // Windows 95 only
|
||||
qDebug()<<__FUNCTION__ << __LINE__;
|
||||
break;
|
||||
case RASCS_LogonNetwork: // Windows 95 only
|
||||
qDebug()<<__FUNCTION__ << __LINE__;
|
||||
break;
|
||||
#endif
|
||||
case RASCS_SubEntryConnected:
|
||||
qDebug()<<__FUNCTION__ << __LINE__;
|
||||
break;
|
||||
case RASCS_SubEntryDisconnected:
|
||||
qDebug()<<__FUNCTION__ << __LINE__;
|
||||
break;
|
||||
//PAUSED STATES:
|
||||
case RASCS_Interactive:
|
||||
qDebug()<<__FUNCTION__ << __LINE__;
|
||||
break;
|
||||
case RASCS_RetryAuthentication:
|
||||
qDebug()<<__FUNCTION__ << __LINE__;
|
||||
break;
|
||||
case RASCS_CallbackSetByCaller:
|
||||
qDebug()<<__FUNCTION__ << __LINE__;
|
||||
break;
|
||||
case RASCS_PasswordExpired:
|
||||
setConnectionState(Vpn::ConnectionState::Error);
|
||||
qDebug()<<__FUNCTION__ << __LINE__;
|
||||
break;
|
||||
|
||||
case RASCS_Connected: // = RASCS_DONE:
|
||||
setConnectionState(Vpn::ConnectionState::Connected);
|
||||
//qDebug()<<__FUNCTION__ << __LINE__;
|
||||
break;
|
||||
case RASCS_Disconnected:
|
||||
setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||
//qDebug()<<__FUNCTION__ << __LINE__;
|
||||
break;
|
||||
default:
|
||||
//qDebug()<<__FUNCTION__ << __LINE__;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Ikev2Protocol::readIkev2Configuration(const QJsonObject &configuration)
|
||||
{
|
||||
m_config = configuration.value(ProtocolUtils::key_proto_config_data(Proto::Ikev2)).toObject();
|
||||
}
|
||||
|
||||
ErrorCode Ikev2Protocol::start()
|
||||
{
|
||||
QByteArray cert = QByteArray::fromBase64(m_config[configKey::cert].toString().toUtf8());
|
||||
setConnectionState(Vpn::ConnectionState::Connecting);
|
||||
|
||||
QTemporaryFile * certFile = new QTemporaryFile;
|
||||
certFile->setAutoRemove(false);
|
||||
certFile->open();
|
||||
QString m_filename = certFile->fileName();
|
||||
certFile->write(cert);
|
||||
certFile->close();
|
||||
delete certFile;
|
||||
|
||||
{
|
||||
auto certInstallProcess = IpcClient::CreatePrivilegedProcess();
|
||||
|
||||
if (!certInstallProcess) {
|
||||
setLastError(ErrorCode::AmneziaServiceConnectionFailed);
|
||||
return ErrorCode::AmneziaServiceConnectionFailed;
|
||||
}
|
||||
|
||||
certInstallProcess->waitForSource();
|
||||
if (!certInstallProcess->isInitialized()) {
|
||||
qWarning() << "IpcProcess replica is not connected!";
|
||||
setLastError(ErrorCode::AmneziaServiceConnectionFailed);
|
||||
return ErrorCode::AmneziaServiceConnectionFailed;
|
||||
}
|
||||
certInstallProcess->setProgram(PermittedProcess::CertUtil);
|
||||
|
||||
QStringList arguments({"-f", "-importpfx", "-p", m_config[configKey::password].toString(),
|
||||
QDir::toNativeSeparators(m_filename), "NoExport"
|
||||
});
|
||||
|
||||
certInstallProcess->setArguments(arguments);
|
||||
certInstallProcess->start();
|
||||
}
|
||||
// /*
|
||||
{
|
||||
if ( disconnect_vpn()){
|
||||
qDebug()<<"VPN was disconnected";
|
||||
}
|
||||
if ( delete_vpn_connection (tunnelName())){
|
||||
qDebug()<<"VPN was deleted";
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
{
|
||||
if ( !create_new_vpn(tunnelName(), m_config[configKey::hostName].toString())){
|
||||
qDebug() <<"Can't create the VPN connect";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
QProcess adapterConfigProcess;
|
||||
adapterConfigProcess.setProgram("powershell");
|
||||
QString arguments = QString("-command \"Set-VpnConnectionIPsecConfiguration\" "
|
||||
"-ConnectionName '%1' "
|
||||
"-AuthenticationTransformConstants GCMAES128 "
|
||||
"-CipherTransformConstants GCMAES128 "
|
||||
"-EncryptionMethod AES256 "
|
||||
"-IntegrityCheckMethod SHA256 "
|
||||
"-PfsGroup PFS2048 "
|
||||
"-DHGroup Group14 "
|
||||
"-PassThru -Force\"")
|
||||
.arg(tunnelName());
|
||||
|
||||
adapterConfigProcess.setNativeArguments(arguments);
|
||||
|
||||
adapterConfigProcess.start();
|
||||
adapterConfigProcess.waitForFinished(5000);
|
||||
}
|
||||
//*/
|
||||
{
|
||||
if (!connect_to_vpn(tunnelName())) {
|
||||
qDebug()<<"We can't connect to VPN";
|
||||
}
|
||||
}
|
||||
//setConnectionState(Connecting);
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
bool Ikev2Protocol::create_new_vpn(const QString & vpn_name,
|
||||
const QString & serv_addr){
|
||||
|
||||
if ( RasValidateEntryName(nullptr, vpn_name.toStdWString().c_str()) != ERROR_SUCCESS)
|
||||
return false;
|
||||
DWORD size = 0;
|
||||
::RasGetEntryProperties(nullptr, L"", nullptr, &size, nullptr, nullptr);
|
||||
LPRASENTRY pras = static_cast<LPRASENTRY>(malloc(size));
|
||||
memset(pras, 0, size);
|
||||
pras->dwSize = size;
|
||||
pras->dwType = RASET_Vpn;
|
||||
pras->dwRedialCount = 1;
|
||||
pras->dwRedialPause = 60;
|
||||
pras->dwfNetProtocols = RASNP_Ip|RASNP_Ipv6;
|
||||
pras->dwEncryptionType = ET_RequireMax;
|
||||
wcscpy_s(pras->szLocalPhoneNumber, serv_addr.toStdWString().c_str());
|
||||
wcscpy_s(pras->szDeviceType, RASDT_Vpn);
|
||||
pras->dwfOptions = RASEO_RemoteDefaultGateway;
|
||||
pras->dwfOptions |= RASEO_RequireDataEncryption;
|
||||
pras->dwfOptions2 |= RASEO2_RequireMachineCertificates;
|
||||
pras->dwVpnStrategy = VS_Ikev2Only;
|
||||
const auto nRet = ::RasSetEntryProperties(nullptr, vpn_name.toStdWString().c_str(), pras, pras->dwSize, NULL, 0);
|
||||
free(pras);
|
||||
if (nRet == ERROR_SUCCESS)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
bool Ikev2Protocol::delete_vpn_connection(const QString &vpn_name){
|
||||
|
||||
if ( RasDeleteEntry(nullptr, vpn_name.toStdWString().c_str()) == ERROR_SUCCESS){
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
bool Ikev2Protocol::connect_to_vpn(const QString & vpn_name){
|
||||
RASDIALPARAMS RasDialParams;
|
||||
memset(&RasDialParams, 0x0, sizeof(RASDIALPARAMS));
|
||||
RasDialParams.dwSize = sizeof(RASDIALPARAMS);
|
||||
wcscpy_s(RasDialParams.szEntryName, vpn_name.toStdWString().c_str());
|
||||
auto ret = RasDial(NULL, NULL, &RasDialParams, 0,
|
||||
&RasDialFuncCallback,
|
||||
&hRasConn);
|
||||
|
||||
if (ret == ERROR_SUCCESS){
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
bool Ikev2Protocol::disconnect_vpn(){
|
||||
if ( hRasConn != nullptr ){
|
||||
auto ret = RasHangUp(hRasConn);
|
||||
qDebug() << "RasHangUp " << ret;
|
||||
if (ret != ERROR_SUCCESS)
|
||||
return false;
|
||||
}
|
||||
QThread::msleep(3000);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void WINAPI RasDialFuncCallback(UINT unMsg,
|
||||
RASCONNSTATE rasconnstate,
|
||||
DWORD dwError ){
|
||||
std::lock_guard<std::mutex> guard(rasDialFuncMutex);
|
||||
if (self) {
|
||||
self->newConnectionStateEventReceived(unMsg, rasconnstate, dwError);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
#ifndef IKEV2_VPN_PROTOCOL_WINDOWS_H
|
||||
#define IKEV2_VPN_PROTOCOL_WINDOWS_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QProcess>
|
||||
#include <QString>
|
||||
#include <QTemporaryFile>
|
||||
#include <QTimer>
|
||||
|
||||
#include "vpnProtocol.h"
|
||||
#include "core/utils/ipcClient.h"
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <windows.h>
|
||||
#include <Ras.h>
|
||||
#include <raserror.h>
|
||||
#include <shlwapi.h>
|
||||
|
||||
#include <wincrypt.h>
|
||||
|
||||
#pragma comment(lib, "shlwapi.lib")
|
||||
#pragma comment(lib, "rasapi32.lib")
|
||||
#pragma comment(lib, "Crypt32.lib")
|
||||
|
||||
class Ikev2Protocol : public VpnProtocol
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit Ikev2Protocol(const QJsonObject& configuration, QObject* parent = nullptr);
|
||||
virtual ~Ikev2Protocol() override;
|
||||
|
||||
ErrorCode start() override;
|
||||
void stop() override;
|
||||
|
||||
static QString tunnelName() { return "AmneziaVPN IKEv2"; }
|
||||
|
||||
public:
|
||||
void newConnectionStateEventReceived(UINT unMsg,
|
||||
RASCONNSTATE rasconnstate,
|
||||
DWORD dwError);
|
||||
|
||||
private:
|
||||
void readIkev2Configuration(const QJsonObject &configuration);
|
||||
|
||||
private:
|
||||
QJsonObject m_config;
|
||||
|
||||
//RAS functions and parameters
|
||||
HRASCONN hRasConn{nullptr};
|
||||
bool create_new_vpn(const QString & vpn_name,
|
||||
const QString & serv_addr);
|
||||
bool delete_vpn_connection(const QString &vpn_name);
|
||||
|
||||
bool connect_to_vpn(const QString & vpn_name);
|
||||
bool disconnect_vpn();
|
||||
};
|
||||
|
||||
DWORD CALLBACK rasCallback(UINT msg, RASCONNSTATE rascs, DWORD err);
|
||||
|
||||
#endif // IKEV2_VPN_PROTOCOL_WINDOWS_H
|
||||
@@ -0,0 +1,406 @@
|
||||
#include <QCoreApplication>
|
||||
#include <QFileInfo>
|
||||
#include <QProcess>
|
||||
#include <QRandomGenerator>
|
||||
#include <QTcpServer>
|
||||
#include <QTcpSocket>
|
||||
#include <QNetworkInterface>
|
||||
|
||||
#include "core/utils/networkUtilities.h"
|
||||
#include "ipc.h"
|
||||
#include "openVpnProtocol.h"
|
||||
#include "core/utils/utilities.h"
|
||||
#include "core/protocols/protocolUtils.h"
|
||||
#include "version.h"
|
||||
|
||||
OpenVpnProtocol::OpenVpnProtocol(const QJsonObject &configuration, QObject *parent) : VpnProtocol(configuration, parent)
|
||||
{
|
||||
readOpenVpnConfiguration(configuration);
|
||||
connect(&m_managementServer, &ManagementServer::readyRead, this,
|
||||
&OpenVpnProtocol::onReadyReadDataFromManagementServer);
|
||||
}
|
||||
|
||||
OpenVpnProtocol::~OpenVpnProtocol()
|
||||
{
|
||||
OpenVpnProtocol::stop();
|
||||
QThread::msleep(200);
|
||||
}
|
||||
|
||||
QString OpenVpnProtocol::defaultConfigFileName()
|
||||
{
|
||||
return defaultConfigPath() + QString("/%1.ovpn").arg(APPLICATION_NAME);
|
||||
}
|
||||
|
||||
QString OpenVpnProtocol::defaultConfigPath()
|
||||
{
|
||||
QString p = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/config";
|
||||
Utils::initializePath(p);
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
void OpenVpnProtocol::cleanupResources()
|
||||
{
|
||||
if (m_openVpnProcess || openVpnProcessIsRunning()) {
|
||||
if (!sendTermSignal()) {
|
||||
killOpenVpnProcess();
|
||||
}
|
||||
QThread::msleep(10);
|
||||
}
|
||||
m_managementServer.stop();
|
||||
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
|
||||
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
QRemoteObjectPendingReply<bool> reply = iface->disableKillSwitch();
|
||||
if (!reply.waitForFinished(1000) && !reply.returnValue()) {
|
||||
qWarning() << "OpenVpnProtocol::cleanupResources(): Failed to disable killswitch";
|
||||
}
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenVpnProtocol::stop()
|
||||
{
|
||||
qDebug() << "OpenVpnProtocol::stop()";
|
||||
|
||||
const bool wasActive = m_connectionState == Vpn::ConnectionState::Preparing
|
||||
|| m_connectionState == Vpn::ConnectionState::Connecting
|
||||
|| m_connectionState == Vpn::ConnectionState::Connected
|
||||
|| m_connectionState == Vpn::ConnectionState::Reconnecting;
|
||||
|
||||
if (wasActive) {
|
||||
setConnectionState(Vpn::ConnectionState::Disconnecting);
|
||||
}
|
||||
|
||||
cleanupResources();
|
||||
|
||||
if (wasActive || m_connectionState == Vpn::ConnectionState::Disconnecting) {
|
||||
setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCode OpenVpnProtocol::prepare()
|
||||
{
|
||||
return IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
QRemoteObjectPendingReply<QStringList> listReply = iface->getTapList();
|
||||
if (!listReply.waitForFinished(1000)) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
QStringList list = listReply.returnValue();
|
||||
if (list.empty()) {
|
||||
QRemoteObjectPendingReply<bool> installReply = iface->checkAndInstallDriver();
|
||||
if (!installReply.waitForFinished() || !installReply.returnValue()) {
|
||||
return ErrorCode::OpenVpnTapAdapterError;
|
||||
}
|
||||
}
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}, [] () {
|
||||
return ErrorCode::AmneziaServiceConnectionFailed;
|
||||
});
|
||||
}
|
||||
|
||||
void OpenVpnProtocol::killOpenVpnProcess()
|
||||
{
|
||||
if (m_openVpnProcess) {
|
||||
m_openVpnProcess->close();
|
||||
}
|
||||
}
|
||||
|
||||
void OpenVpnProtocol::readOpenVpnConfiguration(const QJsonObject &configuration)
|
||||
{
|
||||
if (configuration.contains(ProtocolUtils::key_proto_config_data(Proto::OpenVpn))) {
|
||||
m_configData = configuration;
|
||||
QJsonObject jConfig = configuration.value(ProtocolUtils::key_proto_config_data(Proto::OpenVpn)).toObject();
|
||||
|
||||
m_configFile.open();
|
||||
m_configFile.write(jConfig.value(configKey::config).toString().toUtf8());
|
||||
m_configFile.close();
|
||||
m_configFileName = m_configFile.fileName();
|
||||
qDebug().noquote() << QString("Set config data") << m_configFileName;
|
||||
}
|
||||
}
|
||||
|
||||
bool OpenVpnProtocol::openVpnProcessIsRunning() const
|
||||
{
|
||||
return Utils::processIsRunning("openvpn");
|
||||
}
|
||||
|
||||
void OpenVpnProtocol::disconnectFromManagementServer()
|
||||
{
|
||||
m_managementServer.stop();
|
||||
}
|
||||
|
||||
QString OpenVpnProtocol::configPath() const
|
||||
{
|
||||
return m_configFileName;
|
||||
}
|
||||
|
||||
void OpenVpnProtocol::sendManagementCommand(const QString &command)
|
||||
{
|
||||
QIODevice *device = dynamic_cast<QIODevice *>(m_managementServer.socket().data());
|
||||
if (device) {
|
||||
QTextStream stream(device);
|
||||
stream << command << Qt::endl;
|
||||
}
|
||||
}
|
||||
|
||||
uint OpenVpnProtocol::selectMgmtPort()
|
||||
{
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
quint32 port = QRandomGenerator::global()->generate();
|
||||
port = (double)(65000 - 15001) * port / UINT32_MAX + 15001;
|
||||
|
||||
QTcpServer s;
|
||||
bool ok = s.listen(QHostAddress::LocalHost, port);
|
||||
if (ok)
|
||||
return port;
|
||||
}
|
||||
return m_managementPort;
|
||||
}
|
||||
|
||||
void OpenVpnProtocol::updateRouteGateway(QString line)
|
||||
{
|
||||
if (line.contains("net_route_v4_best_gw")) {
|
||||
QStringList params = line.split(" ");
|
||||
if (params.size() == 6) {
|
||||
m_routeGateway = params.at(3);
|
||||
}
|
||||
} else {
|
||||
line = line.split("ROUTE_GATEWAY", Qt::SkipEmptyParts).at(1);
|
||||
if (!line.contains("/"))
|
||||
return;
|
||||
m_routeGateway = line.split("/", Qt::SkipEmptyParts).first();
|
||||
m_routeGateway.replace(" ", "");
|
||||
}
|
||||
qDebug() << "Set VPN route gateway" << m_routeGateway;
|
||||
}
|
||||
|
||||
ErrorCode OpenVpnProtocol::start()
|
||||
{
|
||||
cleanupResources();
|
||||
|
||||
if (!QFileInfo::exists(configPath())) {
|
||||
setLastError(ErrorCode::OpenVpnConfigMissing);
|
||||
return lastError();
|
||||
}
|
||||
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
const ErrorCode res = IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
QString ip = NetworkUtilities::getIPAddress(m_configData.value(amnezia::configKey::hostName).toString());
|
||||
QRemoteObjectPendingReply<bool> reply = iface->addKillSwitchAllowedRange(QStringList(ip));
|
||||
if (!reply.waitForFinished(1000) || !reply.returnValue()) {
|
||||
return ErrorCode::AmneziaServiceConnectionFailed;
|
||||
}
|
||||
return ErrorCode::NoError;
|
||||
});
|
||||
if (res != ErrorCode::NoError) {
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Detect default gateway
|
||||
#ifdef Q_OS_MAC
|
||||
QProcess p;
|
||||
p.setProcessChannelMode(QProcess::MergedChannels);
|
||||
|
||||
p.start("route",
|
||||
QStringList() << "-n"
|
||||
<< "get"
|
||||
<< "default");
|
||||
p.waitForFinished();
|
||||
QString s = p.readAll();
|
||||
|
||||
QRegularExpression rx(R"(gateway:\s*(\d+\.\d+\.\d+\.\d+))");
|
||||
QRegularExpressionMatch match = rx.match(s);
|
||||
if (match.hasMatch()) {
|
||||
m_routeGateway = match.captured(1);
|
||||
qDebug() << "Set VPN route gateway" << m_routeGateway;
|
||||
} else {
|
||||
qWarning() << "Unable to set VPN route gateway, output:\n" << s;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
// In "all except sites" mode the config uses redirect-gateway !ipv4, so OpenVPN
|
||||
// never reports net_route_v4_best_gw and m_routeGateway would stay empty
|
||||
const QString winGateway = NetworkUtilities::getGatewayAndIface().first;
|
||||
if (!winGateway.isEmpty()) {
|
||||
m_routeGateway = winGateway;
|
||||
qDebug() << "Set VPN route gateway" << m_routeGateway;
|
||||
} else {
|
||||
qWarning() << "Unable to detect physical default gateway";
|
||||
}
|
||||
#endif
|
||||
|
||||
uint mgmtPort = selectMgmtPort();
|
||||
qDebug() << "OpenVpnProtocol::start mgmt port selected:" << mgmtPort;
|
||||
|
||||
if (!m_managementServer.start(m_managementHost, mgmtPort)) {
|
||||
setLastError(ErrorCode::OpenVpnManagementServerError);
|
||||
return lastError();
|
||||
}
|
||||
|
||||
setConnectionState(Vpn::ConnectionState::Connecting);
|
||||
|
||||
m_openVpnProcess = IpcClient::CreatePrivilegedProcess();
|
||||
|
||||
if (!m_openVpnProcess) {
|
||||
setLastError(ErrorCode::AmneziaServiceConnectionFailed);
|
||||
return ErrorCode::AmneziaServiceConnectionFailed;
|
||||
}
|
||||
|
||||
m_openVpnProcess->setProgram(PermittedProcess::OpenVPN);
|
||||
QStringList arguments({
|
||||
"--config", configPath(), "--management", m_managementHost, QString::number(mgmtPort),
|
||||
"--management-client" /*, "--log", vpnLogFileNamePath */
|
||||
});
|
||||
m_openVpnProcess->setArguments(arguments);
|
||||
|
||||
qDebug() << arguments.join(" ");
|
||||
connect(m_openVpnProcess.data(), &IpcProcessInterfaceReplica::errorOccurred,
|
||||
[&](QProcess::ProcessError error) { qDebug() << "PrivilegedProcess errorOccurred" << error; });
|
||||
|
||||
connect(m_openVpnProcess.data(), &IpcProcessInterfaceReplica::stateChanged,
|
||||
[&](QProcess::ProcessState newState) { qDebug() << "PrivilegedProcess stateChanged" << newState; });
|
||||
|
||||
connect(m_openVpnProcess.data(), &IpcProcessInterfaceReplica::finished, this,
|
||||
[&]() { setConnectionState(Vpn::ConnectionState::Disconnected); });
|
||||
|
||||
m_openVpnProcess->start();
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
bool OpenVpnProtocol::sendTermSignal()
|
||||
{
|
||||
return m_managementServer.writeCommand("signal SIGTERM");
|
||||
}
|
||||
|
||||
void OpenVpnProtocol::sendByteCount()
|
||||
{
|
||||
m_managementServer.writeCommand("bytecount 1");
|
||||
}
|
||||
|
||||
void OpenVpnProtocol::sendInitialData()
|
||||
{
|
||||
m_managementServer.writeCommand("state on");
|
||||
m_managementServer.writeCommand("log on");
|
||||
}
|
||||
|
||||
void OpenVpnProtocol::onReadyReadDataFromManagementServer()
|
||||
{
|
||||
for (;;) {
|
||||
QString line = m_managementServer.readLine().simplified();
|
||||
|
||||
if (line.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!line.contains(">BYTECOUNT")) {
|
||||
qDebug().noquote() << line;
|
||||
}
|
||||
|
||||
if (line.contains(">INFO:OpenVPN Management Interface")) {
|
||||
sendInitialData();
|
||||
} else if (line.startsWith(">STATE")) {
|
||||
if (line.contains("CONNECTED,SUCCESS")) {
|
||||
sendByteCount();
|
||||
stopTimeoutTimer();
|
||||
setConnectionState(Vpn::ConnectionState::Connected);
|
||||
continue;
|
||||
} else if (line.contains("EXITING,SIGTER")) {
|
||||
// openVpnStateSigTermHandler();
|
||||
setConnectionState(Vpn::ConnectionState::Disconnecting);
|
||||
continue;
|
||||
} else if (line.contains("RECONNECTING")) {
|
||||
setConnectionState(Vpn::ConnectionState::Reconnecting);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (line.contains("ROUTE_GATEWAY") || line.contains("net_route_v4_best_gw")) {
|
||||
updateRouteGateway(line);
|
||||
}
|
||||
|
||||
if (line.contains("PUSH: Received control message")) {
|
||||
updateVpnGateway(line);
|
||||
}
|
||||
|
||||
if (line.contains("FATAL")) {
|
||||
if (line.contains("tap-windows6 adapters on this system are currently in use or disabled")) {
|
||||
emit protocolError(ErrorCode::OpenVpnAdaptersInUseError);
|
||||
} else {
|
||||
emit protocolError(ErrorCode::OpenVpnUnknownError);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
QByteArray data(line.toStdString().c_str());
|
||||
if (data.contains(">BYTECOUNT:")) {
|
||||
int beg = data.lastIndexOf(">BYTECOUNT:");
|
||||
int end = data.indexOf("\n", beg);
|
||||
|
||||
beg += sizeof(">BYTECOUNT:") - 1;
|
||||
QList<QByteArray> count = data.mid(beg, end - beg + 1).split(',');
|
||||
|
||||
quint64 r = static_cast<quint64>(count.at(0).trimmed().toULongLong());
|
||||
quint64 s = static_cast<quint64>(count.at(1).trimmed().toULongLong());
|
||||
|
||||
setBytesChanged(r, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OpenVpnProtocol::updateVpnGateway(const QString &line)
|
||||
{
|
||||
// line looks like
|
||||
// PUSH: Received control message: 'PUSH_REPLY,route 10.8.0.1,topology net30,ping 10,ping-restart
|
||||
// 120,ifconfig 10.8.0.6 10.8.0.5,peer-id 0,cipher AES-256-GCM'
|
||||
QStringList params = line.split(",");
|
||||
for (const QString &l : params) {
|
||||
if (l.contains("ifconfig")) {
|
||||
if (l.split(" ").size() == 3) {
|
||||
m_vpnLocalAddress = l.split(" ").at(1);
|
||||
m_vpnGateway = l.split(" ").at(2);
|
||||
#ifdef Q_OS_WIN
|
||||
QThread::msleep(300);
|
||||
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
QList<QNetworkInterface> netInterfaces = QNetworkInterface::allInterfaces();
|
||||
for (int i = 0; i < netInterfaces.size(); i++) {
|
||||
for (int j=0; j < netInterfaces.at(i).addressEntries().size(); j++)
|
||||
{
|
||||
// killSwitch toggle
|
||||
if (m_vpnLocalAddress == netInterfaces.at(i).addressEntries().at(j).ip().toString()) {
|
||||
if (QVariant(m_configData.value(configKey::killSwitchOption).toString()).toBool()) {
|
||||
iface->enableKillSwitch(m_configData, netInterfaces.at(i).index());
|
||||
}
|
||||
m_configData.insert("vpnAdapterIndex", netInterfaces.at(i).index());
|
||||
m_configData.insert("vpnGateway", m_vpnGateway);
|
||||
m_configData.insert("vpnServer",
|
||||
NetworkUtilities::getIPAddress(m_configData.value(amnezia::configKey::hostName).toString()));
|
||||
iface->enablePeerTraffic(m_configData);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
#endif
|
||||
#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
|
||||
// killSwitch toggle
|
||||
if (QVariant(m_configData.value(configKey::killSwitchOption).toString()).toBool()) {
|
||||
m_configData.insert("vpnServer",
|
||||
NetworkUtilities::getIPAddress(m_configData.value(amnezia::configKey::hostName).toString()));
|
||||
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
QRemoteObjectPendingReply<bool> reply = iface->enableKillSwitch(m_configData, 0);
|
||||
if (!reply.waitForFinished(1000) || !reply.returnValue()) {
|
||||
qWarning() << "OpenVpnProtocol::updateVpnGateway(): Failed to enable killswitch";
|
||||
}
|
||||
});
|
||||
}
|
||||
#endif
|
||||
qDebug() << QString("Set vpn local address %1, gw %2").arg(m_vpnLocalAddress).arg(vpnGateway());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
#ifndef OPENVPNPROTOCOL_H
|
||||
#define OPENVPNPROTOCOL_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QTimer>
|
||||
|
||||
#include "core/utils/managementServer.h"
|
||||
#include "vpnProtocol.h"
|
||||
|
||||
#include "core/utils/ipcClient.h"
|
||||
|
||||
class OpenVpnProtocol : public VpnProtocol
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit OpenVpnProtocol(const QJsonObject& configuration, QObject* parent = nullptr);
|
||||
virtual ~OpenVpnProtocol() override;
|
||||
|
||||
ErrorCode start() override;
|
||||
void stop() override;
|
||||
|
||||
ErrorCode prepare() override;
|
||||
static QString defaultConfigFileName();
|
||||
static QString defaultConfigPath();
|
||||
|
||||
protected slots:
|
||||
void onReadyReadDataFromManagementServer();
|
||||
|
||||
private:
|
||||
void cleanupResources();
|
||||
QString configPath() const;
|
||||
bool openVpnProcessIsRunning() const;
|
||||
bool sendTermSignal();
|
||||
void readOpenVpnConfiguration(const QJsonObject &configuration);
|
||||
void disconnectFromManagementServer();
|
||||
void killOpenVpnProcess();
|
||||
void sendByteCount();
|
||||
void sendInitialData();
|
||||
void sendManagementCommand(const QString& command);
|
||||
|
||||
const QString m_managementHost = "127.0.0.1";
|
||||
const unsigned int m_managementPort = 57775;
|
||||
|
||||
ManagementServer m_managementServer;
|
||||
QString m_configFileName;
|
||||
QJsonObject m_configData;
|
||||
QTemporaryFile m_configFile;
|
||||
|
||||
uint selectMgmtPort();
|
||||
|
||||
private:
|
||||
void updateRouteGateway(QString line);
|
||||
void updateVpnGateway(const QString &line);
|
||||
|
||||
QSharedPointer<IpcProcessInterfaceReplica> m_openVpnProcess;
|
||||
};
|
||||
|
||||
#endif // OPENVPNPROTOCOL_H
|
||||
@@ -4,6 +4,16 @@
|
||||
#include "core/utils/errorStrings.h"
|
||||
#include "vpnProtocol.h"
|
||||
|
||||
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) and !defined MACOS_NE || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
|
||||
#include "openVpnProtocol.h"
|
||||
#include "wireGuardProtocol.h"
|
||||
#include "xrayProtocol.h"
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WINDOWS
|
||||
#include "ikev2VpnProtocolWindows.h"
|
||||
#endif
|
||||
|
||||
VpnProtocol::VpnProtocol(const QJsonObject &configuration, QObject *parent)
|
||||
: QObject(parent),
|
||||
m_connectionState(Vpn::ConnectionState::Unknown),
|
||||
@@ -17,20 +27,6 @@ VpnProtocol::VpnProtocol(const QJsonObject &configuration, QObject *parent)
|
||||
connect(m_timeoutTimer, &QTimer::timeout, this, &VpnProtocol::onTimeout);
|
||||
}
|
||||
|
||||
bool VpnProtocol::guardStop()
|
||||
{
|
||||
if (m_stopped) {
|
||||
return true;
|
||||
}
|
||||
m_stopped = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void VpnProtocol::resetStopGuard()
|
||||
{
|
||||
m_stopped = false;
|
||||
}
|
||||
|
||||
void VpnProtocol::setLastError(ErrorCode lastError)
|
||||
{
|
||||
m_lastError = lastError;
|
||||
@@ -111,6 +107,24 @@ QString VpnProtocol::vpnLocalAddress() const
|
||||
return m_vpnLocalAddress;
|
||||
}
|
||||
|
||||
VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject &configuration)
|
||||
{
|
||||
switch (container) {
|
||||
#if defined(Q_OS_WINDOWS)
|
||||
case DockerContainer::Ipsec: return new Ikev2Protocol(configuration);
|
||||
#endif
|
||||
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) and !defined MACOS_NE || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
|
||||
case DockerContainer::OpenVpn: return new OpenVpnProtocol(configuration);
|
||||
case DockerContainer::WireGuard: return new WireguardProtocol(configuration);
|
||||
case DockerContainer::Awg2: return new WireguardProtocol(configuration);
|
||||
case DockerContainer::Awg: return new WireguardProtocol(configuration);
|
||||
case DockerContainer::Xray: return new XrayProtocol(configuration);
|
||||
case DockerContainer::SSXray: return new XrayProtocol(configuration);
|
||||
#endif
|
||||
default: return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
QString VpnProtocol::routeGateway() const
|
||||
{
|
||||
return m_routeGateway;
|
||||
|
||||
@@ -70,6 +70,8 @@ public:
|
||||
QString vpnGateway() const;
|
||||
QString vpnLocalAddress() const;
|
||||
|
||||
static VpnProtocol* factory(amnezia::DockerContainer container, const QJsonObject &configuration);
|
||||
|
||||
signals:
|
||||
void bytesChanged(quint64 receivedBytes, quint64 sentBytes);
|
||||
void connectionStateChanged(Vpn::ConnectionState state);
|
||||
@@ -87,11 +89,6 @@ protected:
|
||||
void startTimeoutTimer();
|
||||
void stopTimeoutTimer();
|
||||
|
||||
bool guardStop();
|
||||
void resetStopGuard();
|
||||
|
||||
bool m_stopped = false;
|
||||
|
||||
Vpn::ConnectionState m_connectionState;
|
||||
|
||||
QString m_routeGateway;
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
#include <QCoreApplication>
|
||||
#include <QFileInfo>
|
||||
#include <QProcess>
|
||||
#include <QTcpSocket>
|
||||
#include <QThread>
|
||||
|
||||
#include "wireGuardProtocol.h"
|
||||
#include "core/utils/networkUtilities.h"
|
||||
|
||||
#include "mozilla/localsocketcontroller.h"
|
||||
|
||||
WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *parent)
|
||||
: VpnProtocol(configuration, parent)
|
||||
{
|
||||
m_impl.reset(new LocalSocketController());
|
||||
connect(m_impl.get(), &ControllerImpl::connected, this,
|
||||
[this](const QString &pubkey, const QDateTime &connectionTimestamp) {
|
||||
setConnectionState(Vpn::ConnectionState::Connected);
|
||||
});
|
||||
connect(m_impl.get(), &ControllerImpl::statusUpdated, this,
|
||||
[this](const QString& serverIpv4Gateway,
|
||||
const QString& deviceIpv4Address, uint64_t txBytes,
|
||||
uint64_t rxBytes) {
|
||||
const QString previousGateway = m_vpnGateway;
|
||||
const QString previousLocal = m_vpnLocalAddress;
|
||||
|
||||
if (!serverIpv4Gateway.isEmpty()) {
|
||||
m_vpnGateway = serverIpv4Gateway;
|
||||
}
|
||||
if (!deviceIpv4Address.isEmpty()) {
|
||||
m_vpnLocalAddress = deviceIpv4Address;
|
||||
}
|
||||
|
||||
if ((!m_vpnGateway.isEmpty() && m_vpnGateway != previousGateway) ||
|
||||
(!m_vpnLocalAddress.isEmpty() && m_vpnLocalAddress != previousLocal)) {
|
||||
emit tunnelAddressesUpdated(m_vpnGateway, m_vpnLocalAddress);
|
||||
}
|
||||
});
|
||||
|
||||
connect(m_impl.get(), &ControllerImpl::disconnected, this,
|
||||
[this]() { setConnectionState(Vpn::ConnectionState::Disconnected); });
|
||||
m_impl->initialize(nullptr, nullptr);
|
||||
}
|
||||
|
||||
WireguardProtocol::~WireguardProtocol()
|
||||
{
|
||||
WireguardProtocol::stop();
|
||||
QThread::msleep(200);
|
||||
}
|
||||
|
||||
void WireguardProtocol::stop()
|
||||
{
|
||||
stopMzImpl();
|
||||
return;
|
||||
}
|
||||
|
||||
ErrorCode WireguardProtocol::startMzImpl()
|
||||
{
|
||||
QString protocolName = m_rawConfig.value("protocol").toString();
|
||||
QJsonObject vpnConfigData = m_rawConfig.value(protocolName + "_config_data").toObject();
|
||||
vpnConfigData[configKey::hostName] = NetworkUtilities::getIPAddress(vpnConfigData.value(configKey::hostName).toString());
|
||||
m_rawConfig.insert(protocolName + "_config_data", vpnConfigData);
|
||||
m_rawConfig[configKey::hostName] = NetworkUtilities::getIPAddress(m_rawConfig[configKey::hostName].toString());
|
||||
|
||||
m_impl->activate(m_rawConfig);
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode WireguardProtocol::stopMzImpl()
|
||||
{
|
||||
m_impl->deactivate();
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
|
||||
ErrorCode WireguardProtocol::start()
|
||||
{
|
||||
return startMzImpl();
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
#ifndef WIREGUARDPROTOCOL_H
|
||||
#define WIREGUARDPROTOCOL_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QProcess>
|
||||
#include <QString>
|
||||
#include <QTemporaryFile>
|
||||
#include <QTimer>
|
||||
|
||||
#include "vpnProtocol.h"
|
||||
|
||||
#include "mozilla/controllerimpl.h"
|
||||
|
||||
class WireguardProtocol : public VpnProtocol
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit WireguardProtocol(const QJsonObject& configuration, QObject* parent = nullptr);
|
||||
virtual ~WireguardProtocol() override;
|
||||
|
||||
ErrorCode start() override;
|
||||
void stop() override;
|
||||
|
||||
ErrorCode startMzImpl();
|
||||
ErrorCode stopMzImpl();
|
||||
|
||||
private:
|
||||
|
||||
QScopedPointer<ControllerImpl> m_impl;
|
||||
};
|
||||
|
||||
#endif // WIREGUARDPROTOCOL_H
|
||||
@@ -0,0 +1,319 @@
|
||||
#include "xrayProtocol.h"
|
||||
|
||||
#include "core/models/protocols/xrayProtocolConfig.h"
|
||||
#include "core/protocols/protocolUtils.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
#include "core/utils/ipcClient.h"
|
||||
#include "core/utils/networkUtilities.h"
|
||||
#include "core/utils/serialization/serialization.h"
|
||||
#include "ipc.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QJsonDocument>
|
||||
#include <QTimer>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkInterface>
|
||||
#include <QtCore/qlogging.h>
|
||||
#include <QtCore/qobjectdefs.h>
|
||||
#include <QtCore/qprocess.h>
|
||||
|
||||
#include <exception>
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
static const QString tunName = "utun22";
|
||||
#else
|
||||
static const QString tunName = "tun2";
|
||||
#endif
|
||||
|
||||
XrayProtocol::XrayProtocol(const QJsonObject &configuration, QObject *parent) : VpnProtocol(configuration, parent)
|
||||
{
|
||||
m_vpnGateway = amnezia::protocols::xray::defaultLocalAddr;
|
||||
m_vpnLocalAddress = amnezia::protocols::xray::defaultLocalAddr;
|
||||
m_routeGateway = NetworkUtilities::getGatewayAndIface().first;
|
||||
|
||||
m_routeMode = static_cast<amnezia::RouteMode>(configuration.value(amnezia::configKey::splitTunnelType).toInt());
|
||||
m_remoteAddress = NetworkUtilities::getIPAddress(m_rawConfig.value(amnezia::configKey::hostName).toString());
|
||||
|
||||
const QString primaryDns = configuration.value(amnezia::configKey::dns1).toString();
|
||||
m_dnsServers.push_back(QHostAddress(primaryDns));
|
||||
if (primaryDns != amnezia::protocols::dns::amneziaDnsIp) {
|
||||
const QString secondaryDns = configuration.value(amnezia::configKey::dns2).toString();
|
||||
m_dnsServers.push_back(QHostAddress(secondaryDns));
|
||||
}
|
||||
|
||||
QJsonObject xrayConfiguration = configuration.value(ProtocolUtils::key_proto_config_data(Proto::Xray)).toObject();
|
||||
if (xrayConfiguration.isEmpty()) {
|
||||
xrayConfiguration = configuration.value(ProtocolUtils::key_proto_config_data(Proto::SSXray)).toObject();
|
||||
}
|
||||
|
||||
if (xrayConfiguration.isEmpty()) {
|
||||
qWarning() << "Xray config wrapper is empty";
|
||||
m_xrayConfig = {};
|
||||
}
|
||||
|
||||
m_xrayConfig = QJsonDocument::fromJson(xrayConfiguration.value(amnezia::configKey::config).toString().toUtf8()).object();
|
||||
if (m_xrayConfig.isEmpty()) {
|
||||
qWarning() << "Xray config string is not a valid JSON object";
|
||||
m_xrayConfig = {};
|
||||
}
|
||||
}
|
||||
|
||||
XrayProtocol::~XrayProtocol()
|
||||
{
|
||||
qDebug() << "XrayProtocol::~XrayProtocol()";
|
||||
XrayProtocol::stop();
|
||||
}
|
||||
|
||||
ErrorCode XrayProtocol::start()
|
||||
{
|
||||
qDebug() << "XrayProtocol::start()";
|
||||
|
||||
// Inject SOCKS5 auth into the inbound before starting xray.
|
||||
// Re-uses existing credentials if the config already has them (e.g. imported config).
|
||||
amnezia::serialization::inbounds::InboundCredentials creds;
|
||||
try {
|
||||
creds = amnezia::serialization::inbounds::EnsureInboundAuth(m_xrayConfig);
|
||||
} catch (const std::exception &e) {
|
||||
qCritical() << "EnsureInboundAuth failed:" << e.what();
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
m_socksUser = creds.username;
|
||||
m_socksPassword = creds.password;
|
||||
m_socksPort = creds.port;
|
||||
|
||||
QString xrayConfigStr = QJsonDocument(m_xrayConfig).toJson(QJsonDocument::Compact);
|
||||
if (xrayConfigStr.isEmpty()) {
|
||||
qCritical() << "Xray config is empty";
|
||||
return ErrorCode::XrayExecutableCrashed;
|
||||
}
|
||||
|
||||
xrayConfigStr = amnezia::sanitizeNativeConfig(xrayConfigStr);
|
||||
|
||||
return IpcClient::withInterface(
|
||||
[&](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
auto xrayStart = iface->xrayStart(xrayConfigStr);
|
||||
if (!xrayStart.waitForFinished() || !xrayStart.returnValue()) {
|
||||
qCritical() << "Failed to start xray";
|
||||
return ErrorCode::XrayExecutableCrashed;
|
||||
}
|
||||
return startTun2Socks();
|
||||
},
|
||||
[]() { return ErrorCode::AmneziaServiceConnectionFailed; });
|
||||
}
|
||||
|
||||
void XrayProtocol::stop()
|
||||
{
|
||||
qDebug() << "XrayProtocol::stop()";
|
||||
|
||||
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
auto disableKillSwitch = iface->disableKillSwitch();
|
||||
if (!disableKillSwitch.waitForFinished() || !disableKillSwitch.returnValue())
|
||||
qWarning() << "Failed to disable killswitch";
|
||||
|
||||
auto StartRoutingIpv6 = iface->StartRoutingIpv6();
|
||||
if (!StartRoutingIpv6.waitForFinished() || !StartRoutingIpv6.returnValue())
|
||||
qWarning() << "Failed to start routing ipv6";
|
||||
|
||||
auto restoreResolvers = iface->restoreResolvers();
|
||||
if (!restoreResolvers.waitForFinished() || !restoreResolvers.returnValue())
|
||||
qWarning() << "Failed to restore resolvers";
|
||||
|
||||
auto deleteTun = iface->deleteTun(tunName);
|
||||
if (!deleteTun.waitForFinished() || !deleteTun.returnValue())
|
||||
qWarning() << "Failed to delete tun";
|
||||
|
||||
auto xrayStop = iface->xrayStop();
|
||||
if (!xrayStop.waitForFinished() || !xrayStop.returnValue())
|
||||
qWarning() << "Failed to stop xray";
|
||||
});
|
||||
|
||||
if (m_tun2socksProcess) {
|
||||
m_tun2socksProcess->blockSignals(true);
|
||||
|
||||
#ifndef Q_OS_WIN
|
||||
m_tun2socksProcess->terminate();
|
||||
auto waitForFinished = m_tun2socksProcess->waitForFinished(1000);
|
||||
if (!waitForFinished.waitForFinished() || !waitForFinished.returnValue()) {
|
||||
qWarning() << "Failed to terminate tun2socks. Killing the process...";
|
||||
m_tun2socksProcess->kill();
|
||||
}
|
||||
#else
|
||||
// terminate does not do anything useful on Windows
|
||||
// so just kill the process
|
||||
m_tun2socksProcess->kill();
|
||||
#endif
|
||||
|
||||
m_tun2socksProcess->close();
|
||||
m_tun2socksProcess.reset();
|
||||
}
|
||||
|
||||
setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||
}
|
||||
|
||||
ErrorCode XrayProtocol::startTun2Socks()
|
||||
{
|
||||
m_tun2socksProcess = IpcClient::CreatePrivilegedProcess();
|
||||
if (!m_tun2socksProcess->waitForSource()) {
|
||||
return ErrorCode::AmneziaServiceConnectionFailed;
|
||||
}
|
||||
|
||||
const QString proxyUrl = QString("socks5://%1:%2@127.0.0.1:%3").arg(m_socksUser, m_socksPassword, QString::number(m_socksPort));
|
||||
|
||||
m_tun2socksProcess->setProgram(PermittedProcess::Tun2Socks);
|
||||
m_tun2socksProcess->setArguments({ "-device", QString("tun://%1").arg(tunName), "-proxy", proxyUrl });
|
||||
|
||||
connect(
|
||||
m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::readyReadStandardError, this,
|
||||
[this]() {
|
||||
auto readAllStandardError = m_tun2socksProcess->readAllStandardError();
|
||||
if (!readAllStandardError.waitForFinished()) {
|
||||
qWarning() << "Failed to read output from tun2socks";
|
||||
return;
|
||||
}
|
||||
|
||||
const QString line = readAllStandardError.returnValue();
|
||||
|
||||
if (!line.contains("[TCP]") && !line.contains("[UDP]"))
|
||||
qDebug() << "[tun2socks]:" << line;
|
||||
|
||||
if (line.contains("[STACK] tun://") && line.contains("<-> socks5://")) {
|
||||
disconnect(m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::readyReadStandardOutput, this, nullptr);
|
||||
|
||||
if (ErrorCode res = setupRouting(); res != ErrorCode::NoError) {
|
||||
stop();
|
||||
setLastError(res);
|
||||
} else {
|
||||
setConnectionState(Vpn::ConnectionState::Connected);
|
||||
}
|
||||
}
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
|
||||
connect(
|
||||
m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::finished, this,
|
||||
[this](int exitCode, QProcess::ExitStatus exitStatus) {
|
||||
// Check stdout for "resource busy" — the TUN device was not yet released
|
||||
// by the previous tun2socks instance. Retry after a short delay.
|
||||
bool resourceBusy = false;
|
||||
if (m_tun2socksProcess) {
|
||||
auto readOut = m_tun2socksProcess->readAllStandardOutput();
|
||||
if (readOut.waitForFinished()) {
|
||||
resourceBusy = readOut.returnValue().contains("resource busy");
|
||||
}
|
||||
}
|
||||
|
||||
if (resourceBusy && m_tun2socksRetryCount < maxTun2SocksRetries) {
|
||||
m_tun2socksRetryCount++;
|
||||
qWarning() << QString("Tun2socks: TUN resource busy, retrying (%1/%2) in %3ms...")
|
||||
.arg(m_tun2socksRetryCount)
|
||||
.arg(maxTun2SocksRetries)
|
||||
.arg(tun2socksRetryDelayMs);
|
||||
QTimer::singleShot(tun2socksRetryDelayMs, this, [this]() {
|
||||
if (ErrorCode err = startTun2Socks(); err != ErrorCode::NoError) {
|
||||
stop();
|
||||
setLastError(err);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
m_tun2socksRetryCount = 0;
|
||||
|
||||
if (exitStatus == QProcess::ExitStatus::CrashExit) {
|
||||
qCritical() << "Tun2socks process crashed!";
|
||||
} else {
|
||||
qCritical() << QString("Tun2socks process was closed with %1 exit code").arg(exitCode);
|
||||
}
|
||||
stop();
|
||||
setLastError(ErrorCode::Tun2SockExecutableCrashed);
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
|
||||
m_tun2socksProcess->start();
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode XrayProtocol::setupRouting()
|
||||
{
|
||||
return IpcClient::withInterface(
|
||||
[this](QSharedPointer<IpcInterfaceReplica> iface) -> ErrorCode {
|
||||
#ifdef Q_OS_WIN
|
||||
const int inetAdapterIndex = NetworkUtilities::AdapterIndexTo(QHostAddress(m_remoteAddress));
|
||||
#endif
|
||||
auto createTun = iface->createTun(tunName, amnezia::protocols::xray::defaultLocalAddr);
|
||||
if (!createTun.waitForFinished() || !createTun.returnValue()) {
|
||||
qCritical() << "Failed to assign IP address for TUN";
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
auto updateResolvers = iface->updateResolvers(tunName, m_dnsServers);
|
||||
if (!updateResolvers.waitForFinished() || !updateResolvers.returnValue()) {
|
||||
qCritical() << "Failed to set DNS resolvers for TUN";
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
int vpnAdapterIndex = -1;
|
||||
QList<QNetworkInterface> netInterfaces = QNetworkInterface::allInterfaces();
|
||||
for (auto &netInterface : netInterfaces) {
|
||||
for (auto &address : netInterface.addressEntries()) {
|
||||
if (m_vpnLocalAddress == address.ip().toString())
|
||||
vpnAdapterIndex = netInterface.index();
|
||||
}
|
||||
}
|
||||
#else
|
||||
static const int vpnAdapterIndex = 0;
|
||||
#endif
|
||||
const bool killSwitchEnabled = QVariant(m_rawConfig.value(configKey::killSwitchOption).toString()).toBool();
|
||||
if (killSwitchEnabled) {
|
||||
if (vpnAdapterIndex != -1) {
|
||||
QJsonObject config = m_rawConfig;
|
||||
config.insert("vpnServer", m_remoteAddress);
|
||||
|
||||
auto enableKillSwitch = IpcClient::Interface()->enableKillSwitch(config, vpnAdapterIndex);
|
||||
if (!enableKillSwitch.waitForFinished() || !enableKillSwitch.returnValue()) {
|
||||
qCritical() << "Failed to enable killswitch";
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
} else
|
||||
qWarning() << "Failed to get vpnAdapterIndex. Killswitch disabled";
|
||||
}
|
||||
|
||||
if (m_routeMode == amnezia::RouteMode::VpnAllSites) {
|
||||
static const QStringList subnets = { "1.0.0.0/8", "2.0.0.0/7", "4.0.0.0/6", "8.0.0.0/5",
|
||||
"16.0.0.0/4", "32.0.0.0/3", "64.0.0.0/2", "128.0.0.0/1" };
|
||||
|
||||
auto routeAddList = iface->routeAddList(m_vpnGateway, subnets);
|
||||
if (!routeAddList.waitForFinished() || routeAddList.returnValue() != subnets.count()) {
|
||||
qCritical() << "Failed to set routes for TUN";
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
}
|
||||
|
||||
auto StopRoutingIpv6 = iface->StopRoutingIpv6();
|
||||
if (!StopRoutingIpv6.waitForFinished() || !StopRoutingIpv6.returnValue()) {
|
||||
qCritical() << "Failed to disable IPv6 routing";
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
if (inetAdapterIndex != -1 && vpnAdapterIndex != -1) {
|
||||
QJsonObject config = m_rawConfig;
|
||||
config.insert("inetAdapterIndex", inetAdapterIndex);
|
||||
config.insert("vpnAdapterIndex", vpnAdapterIndex);
|
||||
config.insert("vpnGateway", m_vpnGateway);
|
||||
config.insert("vpnServer", m_remoteAddress);
|
||||
|
||||
auto enablePeerTraffic = iface->enablePeerTraffic(config);
|
||||
if (!enablePeerTraffic.waitForFinished() || !enablePeerTraffic.returnValue()) {
|
||||
qCritical() << "Failed to enable peer traffic";
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
} else
|
||||
qWarning() << "Failed to get adapter indexes. Split-tunneling disabled";
|
||||
#endif
|
||||
return ErrorCode::NoError;
|
||||
},
|
||||
[]() { return ErrorCode::AmneziaServiceConnectionFailed; });
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
#ifndef XRAYPROTOCOL_H
|
||||
#define XRAYPROTOCOL_H
|
||||
|
||||
#include "QProcess"
|
||||
#include <QtCore/qsharedpointer.h>
|
||||
#include <QHostAddress>
|
||||
#include <QList>
|
||||
|
||||
#include "core/utils/errorCodes.h"
|
||||
#include "core/utils/routeModes.h"
|
||||
#include "core/utils/commonStructs.h"
|
||||
#include "core/utils/ipcClient.h"
|
||||
#include "vpnProtocol.h"
|
||||
|
||||
class XrayProtocol : public VpnProtocol
|
||||
{
|
||||
public:
|
||||
XrayProtocol(const QJsonObject &configuration, QObject *parent = nullptr);
|
||||
virtual ~XrayProtocol() override;
|
||||
|
||||
ErrorCode start() override;
|
||||
void stop() override;
|
||||
|
||||
private:
|
||||
ErrorCode setupRouting();
|
||||
ErrorCode startTun2Socks();
|
||||
|
||||
QJsonObject m_xrayConfig;
|
||||
amnezia::RouteMode m_routeMode;
|
||||
QList<QHostAddress> m_dnsServers;
|
||||
QString m_remoteAddress;
|
||||
|
||||
QString m_socksUser;
|
||||
QString m_socksPassword;
|
||||
int m_socksPort = 10808;
|
||||
|
||||
QSharedPointer<IpcProcessInterfaceReplica> m_tun2socksProcess;
|
||||
int m_tun2socksRetryCount = 0;
|
||||
static constexpr int maxTun2SocksRetries = 5;
|
||||
static constexpr int tun2socksRetryDelayMs = 400;
|
||||
};
|
||||
|
||||
#endif // XRAYPROTOCOL_H
|
||||
@@ -385,57 +385,14 @@ void SecureAppSettingsRepository::setCountryListSortMode(const QString &listId,
|
||||
setValue("Conf/countryListSortMode/" + listId, mode);
|
||||
}
|
||||
|
||||
QStringList SecureAppSettingsRepository::favoriteLocations(const QString &serverId) const
|
||||
QStringList SecureAppSettingsRepository::favoriteLocations() const
|
||||
{
|
||||
QStringList codes = value("Conf/favoriteLocations").toMap().value(serverId).toStringList();
|
||||
codes.removeAll(QString());
|
||||
codes.removeDuplicates();
|
||||
codes.sort();
|
||||
return codes;
|
||||
return value("Conf/favoriteLocations").toStringList();
|
||||
}
|
||||
|
||||
void SecureAppSettingsRepository::setFavoriteLocations(const QString &serverId, const QStringList &codes)
|
||||
void SecureAppSettingsRepository::setFavoriteLocations(const QStringList &codes)
|
||||
{
|
||||
if (serverId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
QVariantMap favorites = value("Conf/favoriteLocations").toMap();
|
||||
if (codes.isEmpty()) {
|
||||
favorites.remove(serverId);
|
||||
} else {
|
||||
favorites.insert(serverId, codes);
|
||||
}
|
||||
setValue("Conf/favoriteLocations", favorites);
|
||||
}
|
||||
|
||||
void SecureAppSettingsRepository::removeFavoriteLocations(const QString &serverId)
|
||||
{
|
||||
QVariantMap favorites = value("Conf/favoriteLocations").toMap();
|
||||
if (favorites.remove(serverId) > 0) {
|
||||
setValue("Conf/favoriteLocations", favorites);
|
||||
}
|
||||
}
|
||||
|
||||
void SecureAppSettingsRepository::migrateLegacyFavoriteLocations(const QStringList &serverIds)
|
||||
{
|
||||
// Before per-subscription favorites the key held one list shared by all servers
|
||||
const QVariant stored = value("Conf/favoriteLocations");
|
||||
const int type = stored.typeId();
|
||||
if (type != QMetaType::QStringList && type != QMetaType::QVariantList && type != QMetaType::QString) {
|
||||
return;
|
||||
}
|
||||
|
||||
QStringList codes = stored.toStringList();
|
||||
codes.removeAll(QString());
|
||||
codes.removeDuplicates();
|
||||
|
||||
QVariantMap favorites;
|
||||
if (!codes.isEmpty()) {
|
||||
for (const QString &serverId : serverIds) {
|
||||
favorites.insert(serverId, codes);
|
||||
}
|
||||
}
|
||||
setValue("Conf/favoriteLocations", favorites);
|
||||
setValue("Conf/favoriteLocations", codes);
|
||||
}
|
||||
|
||||
bool SecureAppSettingsRepository::isNewsNotifications() const
|
||||
@@ -547,33 +504,3 @@ void SecureAppSettingsRepository::setXraySavedConfigs(const QByteArray &data)
|
||||
{
|
||||
setValue("Xray/savedConfigs", data);
|
||||
}
|
||||
|
||||
QString SecureAppSettingsRepository::localProxyOwnerId() const
|
||||
{
|
||||
return value("Conf/localProxyOwnerId", "").toString();
|
||||
}
|
||||
|
||||
void SecureAppSettingsRepository::setLocalProxyOwnerId(const QString &serverId)
|
||||
{
|
||||
setValue("Conf/localProxyOwnerId", serverId);
|
||||
}
|
||||
|
||||
quint16 SecureAppSettingsRepository::localProxyPort() const
|
||||
{
|
||||
return static_cast<quint16>(value("Conf/localProxyPort", 10808).toUInt());
|
||||
}
|
||||
|
||||
void SecureAppSettingsRepository::setLocalProxyPort(quint16 port)
|
||||
{
|
||||
setValue("Conf/localProxyPort", port);
|
||||
}
|
||||
|
||||
bool SecureAppSettingsRepository::isLocalProxyPortUserDefined() const
|
||||
{
|
||||
return value("Conf/localProxyPortUserDefined", false).toBool();
|
||||
}
|
||||
|
||||
void SecureAppSettingsRepository::setLocalProxyPortUserDefined(bool userDefined)
|
||||
{
|
||||
setValue("Conf/localProxyPortUserDefined", userDefined);
|
||||
}
|
||||
@@ -80,10 +80,8 @@ public:
|
||||
|
||||
int countryListSortMode(const QString &listId) const;
|
||||
void setCountryListSortMode(const QString &listId, int mode);
|
||||
QStringList favoriteLocations(const QString &serverId) const;
|
||||
void setFavoriteLocations(const QString &serverId, const QStringList &codes);
|
||||
void removeFavoriteLocations(const QString &serverId);
|
||||
void migrateLegacyFavoriteLocations(const QStringList &serverIds);
|
||||
QStringList favoriteLocations() const;
|
||||
void setFavoriteLocations(const QStringList &codes);
|
||||
bool isNewsNotifications() const;
|
||||
void setNewsNotifications(bool enabled);
|
||||
|
||||
@@ -107,13 +105,6 @@ public:
|
||||
QByteArray xraySavedConfigs() const;
|
||||
void setXraySavedConfigs(const QByteArray &data);
|
||||
|
||||
QString localProxyOwnerId() const;
|
||||
void setLocalProxyOwnerId(const QString &serverId);
|
||||
quint16 localProxyPort() const;
|
||||
void setLocalProxyPort(quint16 port);
|
||||
bool isLocalProxyPortUserDefined() const;
|
||||
void setLocalProxyPortUserDefined(bool userDefined);
|
||||
|
||||
signals:
|
||||
void appLanguageChanged(QLocale locale);
|
||||
void allowedDnsServersChanged(const QStringList &servers);
|
||||
|
||||
@@ -149,9 +149,6 @@ void SecureServersRepository::loadFromStorage()
|
||||
QJsonDocument::fromJson(value(QStringLiteral("Servers/serversList"), QByteArray()).toByteArray())
|
||||
.array();
|
||||
|
||||
QJsonArray persistedArray;
|
||||
bool hasGeneratedIds = false;
|
||||
|
||||
for (int i = 0; i < serversArray.size(); ++i) {
|
||||
const QJsonObject json = serversArray.at(i).toObject();
|
||||
const QString candidateId = readStorageServerId(json);
|
||||
@@ -161,25 +158,15 @@ void SecureServersRepository::loadFromStorage()
|
||||
qWarning() << "Skipping stored server config with unsupported format version"
|
||||
<< serverConfigUtils::configFormatVersion(strippedJson);
|
||||
++m_unsupportedFormatConfigsCount;
|
||||
persistedArray.append(json);
|
||||
continue;
|
||||
}
|
||||
const serverConfigUtils::ConfigType kind = serverConfigUtils::configTypeFromJson(strippedJson);
|
||||
|
||||
if (m_serverJsonById.contains(serverId) || kind == serverConfigUtils::ConfigType::Invalid) {
|
||||
persistedArray.append(json);
|
||||
continue;
|
||||
}
|
||||
m_serverJsonById.insert(serverId, embedStorageServerId(serverId, strippedJson));
|
||||
m_orderedServerIds.append(serverId);
|
||||
persistedArray.append(m_serverJsonById.value(serverId));
|
||||
hasGeneratedIds = hasGeneratedIds || serverId != candidateId;
|
||||
}
|
||||
|
||||
// Per-server settings are keyed by id, so a generated one must survive restarts.
|
||||
// Unlike syncToStorage(), this keeps entries that were skipped above
|
||||
if (hasGeneratedIds) {
|
||||
setValue(QStringLiteral("Servers/serversList"), QJsonDocument(persistedArray).toJson());
|
||||
}
|
||||
|
||||
updateDefaultServerFromStorage();
|
||||
@@ -336,15 +323,6 @@ serverConfigUtils::ConfigType SecureServersRepository::serverKind(const QString
|
||||
return serverConfigUtils::configTypeFromJson(withoutStorageServerId(it.value()));
|
||||
}
|
||||
|
||||
std::optional<QJsonObject> SecureServersRepository::serverJsonById(const QString &serverId) const
|
||||
{
|
||||
const auto it = m_serverJsonById.constFind(serverId);
|
||||
if (it == m_serverJsonById.constEnd()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return it.value();
|
||||
}
|
||||
|
||||
std::optional<SelfHostedAdminServerConfig> SecureServersRepository::selfHostedAdminConfig(const QString &serverId) const
|
||||
{
|
||||
const auto it = m_serverJsonById.constFind(serverId);
|
||||
|
||||
@@ -31,8 +31,6 @@ public:
|
||||
void removeServer(const QString &serverId);
|
||||
serverConfigUtils::ConfigType serverKind(const QString &serverId) const;
|
||||
|
||||
std::optional<QJsonObject> serverJsonById(const QString &serverId) const;
|
||||
|
||||
std::optional<SelfHostedAdminServerConfig> selfHostedAdminConfig(const QString &serverId) const;
|
||||
std::optional<SelfHostedUserServerConfig> selfHostedUserConfig(const QString &serverId) const;
|
||||
std::optional<NativeServerConfig> nativeConfig(const QString &serverId) const;
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef CONSTANTS_H
|
||||
#define CONSTANTS_H
|
||||
|
||||
#include "version.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <QObject>
|
||||
|
||||
namespace Constants {
|
||||
|
||||
inline QString versionString() { return QStringLiteral(APP_VERSION); }
|
||||
|
||||
constexpr const char* PLATFORM_NAME =
|
||||
#if defined(MZ_IOS)
|
||||
"ios"
|
||||
#elif defined(MZ_MACOS)
|
||||
"macos"
|
||||
#elif defined(MZ_LINUX)
|
||||
"linux"
|
||||
#elif defined(MZ_ANDROID)
|
||||
"android"
|
||||
#elif defined(MZ_WINDOWS)
|
||||
"windows"
|
||||
#elif defined(UNIT_TEST) || defined(MZ_DUMMY)
|
||||
"dummy"
|
||||
#else
|
||||
# error "Unsupported platform"
|
||||
#endif
|
||||
;
|
||||
}
|
||||
#endif // CONSTANTS_H
|
||||
@@ -4,10 +4,7 @@
|
||||
namespace apiDefs
|
||||
{
|
||||
|
||||
constexpr int requestTimeoutMsecs = 6 * 1000;
|
||||
constexpr int proxyRequestTimeoutMsecs = 6 * 1000;
|
||||
constexpr int proxyStorageTimeoutMsecs = 6 * 1000;
|
||||
constexpr int proxyHealthTimeoutMsecs = 1000;
|
||||
constexpr int requestTimeoutMsecs = 12 * 1000; // 12 secs
|
||||
|
||||
} // namespace apiDefs
|
||||
|
||||
|
||||
@@ -94,8 +94,8 @@ namespace amnezia
|
||||
constexpr char defaultXPaddingMethod[] = "Repeat-x";
|
||||
constexpr char defaultXPaddingKey[] = "x_padding";
|
||||
constexpr char defaultXPaddingHeader[] = "X-Padding";
|
||||
constexpr char defaultXPaddingBytesMin[] = "100";
|
||||
constexpr char defaultXPaddingBytesMax[] = "1000";
|
||||
constexpr char defaultXPaddingBytesMin[] = "1";
|
||||
constexpr char defaultXPaddingBytesMax[] = "256";
|
||||
|
||||
constexpr char defaultMkcpTti[] = "50";
|
||||
constexpr char defaultMkcpUplinkCapacity[] = "5";
|
||||
|
||||
@@ -31,3 +31,44 @@ QSharedPointer<IpcInterfaceReplica> IpcClient::Interface()
|
||||
}
|
||||
return rep;
|
||||
}
|
||||
|
||||
QSharedPointer<IpcProcessInterfaceReplica> IpcClient::CreatePrivilegedProcess()
|
||||
{
|
||||
return withInterface([](QSharedPointer<IpcInterfaceReplica> &iface) -> QSharedPointer<IpcProcessInterfaceReplica> {
|
||||
auto createPrivilegedProcess = iface->createPrivilegedProcess();
|
||||
if (!createPrivilegedProcess.waitForFinished()) {
|
||||
qCritical() << "Failed to create privileged process";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const int pid = createPrivilegedProcess.returnValue();
|
||||
|
||||
auto* node = new QRemoteObjectNode();
|
||||
node->connectToNode(QUrl(QString("local:%1").arg(amnezia::getIpcProcessUrl(pid))));
|
||||
|
||||
QSharedPointer<IpcProcessInterfaceReplica> rep(
|
||||
node->acquire<IpcProcessInterfaceReplica>(),
|
||||
[node] (IpcProcessInterfaceReplica *ptr) {
|
||||
delete ptr;
|
||||
node->deleteLater();
|
||||
}
|
||||
);
|
||||
if (rep.isNull()) {
|
||||
qCritical() << "IpcClient::CreatePrivilegedProcess(): Failed to acquire replica";
|
||||
return nullptr;
|
||||
}
|
||||
if (!rep->waitForSource()) {
|
||||
qCritical() << "IpcClient::CreatePrivilegedProcess(): Failed to initialize replica";
|
||||
return nullptr;
|
||||
}
|
||||
if (!rep->isReplicaValid()) {
|
||||
qCritical() << "IpcClient::CreatePrivilegedProcess(): Replica is invalid";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return rep;
|
||||
},
|
||||
[]() -> QSharedPointer<IpcProcessInterfaceReplica> {
|
||||
return nullptr;
|
||||
});
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <QObject>
|
||||
|
||||
#include "rep_ipc_interface_replica.h"
|
||||
#include "rep_ipc_process_interface_replica.h"
|
||||
|
||||
class IpcClient : public QObject
|
||||
{
|
||||
@@ -15,6 +16,7 @@ public:
|
||||
static IpcClient& Instance();
|
||||
|
||||
static QSharedPointer<IpcInterfaceReplica> Interface();
|
||||
static QSharedPointer<IpcProcessInterfaceReplica> CreatePrivilegedProcess();
|
||||
|
||||
template <typename Func>
|
||||
static auto withInterface(Func func)
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
#ifndef LOCALPROXYDEFS_H
|
||||
#define LOCALPROXYDEFS_H
|
||||
|
||||
#include <QtGlobal>
|
||||
|
||||
namespace amnezia::localProxy
|
||||
{
|
||||
constexpr int defaultProxyPort = 10808;
|
||||
constexpr int proxyPortMin = 1024;
|
||||
constexpr int proxyPortMax = 65535;
|
||||
constexpr quint16 apiPort = 49490;
|
||||
constexpr int portReleaseWaitMs = 800;
|
||||
}
|
||||
|
||||
#endif // LOCALPROXYDEFS_H
|
||||
@@ -1,51 +0,0 @@
|
||||
#include "portUtils.h"
|
||||
|
||||
#include "localProxyConstants.h"
|
||||
|
||||
#include <QElapsedTimer>
|
||||
#include <QHostAddress>
|
||||
#include <QTcpServer>
|
||||
#include <QThread>
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
bool portUtils::isPortAvailable(int port)
|
||||
{
|
||||
if (port < localProxy::proxyPortMin || port > localProxy::proxyPortMax) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QTcpServer server;
|
||||
const bool success = server.listen(QHostAddress::LocalHost, static_cast<quint16>(port));
|
||||
server.close();
|
||||
return success;
|
||||
}
|
||||
|
||||
bool portUtils::waitForPort(int port, int timeoutMs)
|
||||
{
|
||||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
|
||||
while (!isPortAvailable(port)) {
|
||||
if (timer.elapsed() >= timeoutMs) {
|
||||
return false;
|
||||
}
|
||||
QThread::msleep(200);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<int> portUtils::findFirstAvailablePort(int startPort, int endPort)
|
||||
{
|
||||
startPort = qMax(startPort, localProxy::proxyPortMin);
|
||||
endPort = qMin(endPort, localProxy::proxyPortMax);
|
||||
|
||||
for (int port = startPort; port <= endPort; ++port) {
|
||||
if (isPortAvailable(port)) {
|
||||
return port;
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
#ifndef PORTUTILS_H
|
||||
#define PORTUTILS_H
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace portUtils
|
||||
{
|
||||
bool isPortAvailable(int port);
|
||||
bool waitForPort(int port, int timeoutMs);
|
||||
std::optional<int> findFirstAvailablePort(int startPort, int endPort);
|
||||
}
|
||||
|
||||
#endif // PORTUTILS_H
|
||||
@@ -1,47 +0,0 @@
|
||||
#include "xrayConfigUtils.h"
|
||||
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "core/utils/containers/containerUtils.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
|
||||
using namespace amnezia;
|
||||
|
||||
std::optional<QString> xrayConfigUtils::extractLastConfig(const QJsonObject &serverConfig)
|
||||
{
|
||||
const QJsonArray containers = serverConfig.value(configKey::containers).toArray();
|
||||
const QString targetContainer = ContainerUtils::containerToString(DockerContainer::Xray);
|
||||
|
||||
for (const QJsonValue &value : containers) {
|
||||
const QJsonObject container = value.toObject();
|
||||
if (container.value(configKey::container).toString() != targetContainer) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const QJsonObject proto = container.value(QString(configKey::xray)).toObject();
|
||||
const QString serialized = proto.value(configKey::lastConfig).toString();
|
||||
if (!serialized.isEmpty()) {
|
||||
return serialized;
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool xrayConfigUtils::setInboundPort(QJsonObject &xrayConfig, int port)
|
||||
{
|
||||
if (!xrayConfig.value(QLatin1String("inbounds")).isArray()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonArray inbounds = xrayConfig.value(QLatin1String("inbounds")).toArray();
|
||||
if (inbounds.isEmpty() || !inbounds.at(0).isObject()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonObject firstInbound = inbounds.at(0).toObject();
|
||||
firstInbound.insert(QLatin1String("port"), port);
|
||||
inbounds[0] = firstInbound;
|
||||
xrayConfig.insert(QLatin1String("inbounds"), inbounds);
|
||||
return true;
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
#ifndef XRAYCONFIGUTILS_H
|
||||
#define XRAYCONFIGUTILS_H
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
|
||||
namespace xrayConfigUtils
|
||||
{
|
||||
std::optional<QString> extractLastConfig(const QJsonObject &serverConfig);
|
||||
bool setInboundPort(QJsonObject &xrayConfig, int port);
|
||||
}
|
||||
|
||||
#endif // XRAYCONFIGUTILS_H
|
||||
@@ -0,0 +1,112 @@
|
||||
#include <QDebug>
|
||||
#include <QTcpServer>
|
||||
#include <QTcpSocket>
|
||||
|
||||
#include "core/utils/managementServer.h"
|
||||
|
||||
ManagementServer::ManagementServer(QObject *parent) : QObject(parent),
|
||||
m_tcpServer(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
ManagementServer::~ManagementServer()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool ManagementServer::isOpen() const
|
||||
{
|
||||
return (m_socket && m_socket->isOpen());
|
||||
}
|
||||
|
||||
void ManagementServer::stop()
|
||||
{
|
||||
if (m_tcpServer) m_tcpServer->close();
|
||||
}
|
||||
|
||||
void ManagementServer::onAcceptError(QAbstractSocket::SocketError socketError)
|
||||
{
|
||||
qDebug().noquote() << QString("Accept error: %1").arg(socketError);
|
||||
}
|
||||
|
||||
qint64 ManagementServer::writeCommand(const QString& message)
|
||||
{
|
||||
if (!isOpen()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const QString command = message + "\n";
|
||||
qint64 bytesWritten = m_socket->write(command.toStdString().c_str());
|
||||
m_socket->flush();
|
||||
return bytesWritten;
|
||||
}
|
||||
|
||||
void ManagementServer::onNewConnection()
|
||||
{
|
||||
qDebug() << "New incoming connection";
|
||||
|
||||
m_socket = QPointer<QTcpSocket>(m_tcpServer->nextPendingConnection());
|
||||
if (m_tcpServer) m_tcpServer->close();
|
||||
|
||||
QObject::connect(m_socket.data(), &QTcpSocket::disconnected, this, &ManagementServer::onSocketDisconnected);
|
||||
QObject::connect(m_socket.data(), &QTcpSocket::errorOccurred, this, &ManagementServer::onSocketError);
|
||||
QObject::connect(m_socket.data(), &QTcpSocket::readyRead, this, &ManagementServer::onReadyRead);
|
||||
}
|
||||
|
||||
void ManagementServer::onSocketError(QAbstractSocket::SocketError socketError)
|
||||
{
|
||||
Q_UNUSED(socketError)
|
||||
qDebug().noquote() << QString("Management server error: %1").arg(m_socket->errorString());
|
||||
}
|
||||
|
||||
void ManagementServer::onSocketDisconnected()
|
||||
{
|
||||
if (m_socket) m_socket->deleteLater();
|
||||
}
|
||||
|
||||
QPointer<QTcpSocket> ManagementServer::socket() const
|
||||
{
|
||||
if (!isOpen()) {
|
||||
return nullptr;
|
||||
}
|
||||
return m_socket;
|
||||
}
|
||||
|
||||
void ManagementServer::onReadyRead()
|
||||
{
|
||||
emit readyRead();
|
||||
}
|
||||
|
||||
bool ManagementServer::start(const QString& host, unsigned int port)
|
||||
{
|
||||
if (m_tcpServer) m_tcpServer->close();
|
||||
|
||||
m_tcpServer = QSharedPointer<QTcpServer>(new QTcpServer(this), [](QTcpServer *s){
|
||||
if (s) s->deleteLater();
|
||||
});
|
||||
m_tcpServer->setMaxPendingConnections(1);
|
||||
|
||||
connect(m_tcpServer.data(), SIGNAL(acceptError(QAbstractSocket::SocketError)), this, SLOT(onAcceptError(QAbstractSocket::SocketError)));
|
||||
connect(m_tcpServer.data(), SIGNAL(newConnection()), this, SLOT(onNewConnection()));
|
||||
|
||||
if (m_tcpServer->listen(QHostAddress(host), port)) {
|
||||
emit serverStarted();
|
||||
return true;
|
||||
}
|
||||
|
||||
qDebug().noquote() << QString("Can't start TCP server, %1,%2")
|
||||
.arg(m_tcpServer->serverError())
|
||||
.arg(m_tcpServer->errorString());
|
||||
return false;
|
||||
}
|
||||
|
||||
QString ManagementServer::readLine()
|
||||
{
|
||||
if (!isOpen()) {
|
||||
qDebug() << "Socket is not opened";
|
||||
return QString();
|
||||
}
|
||||
|
||||
return m_socket->readLine();
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#ifndef MANAGEMENTSERVER_H
|
||||
#define MANAGEMENTSERVER_H
|
||||
|
||||
#include <QAbstractSocket>
|
||||
#include <QPointer>
|
||||
#include <QSharedPointer>
|
||||
#include <QString>
|
||||
|
||||
class QTcpServer;
|
||||
class QTcpSocket;
|
||||
|
||||
class ManagementServer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ManagementServer(QObject *parent = nullptr);
|
||||
~ManagementServer();
|
||||
|
||||
bool start(const QString& host, unsigned int port);
|
||||
void stop();
|
||||
bool isOpen() const;
|
||||
|
||||
QString readLine();
|
||||
qint64 writeCommand(const QString& message);
|
||||
|
||||
QPointer<QTcpSocket> socket() const;
|
||||
|
||||
signals:
|
||||
void readyRead();
|
||||
void serverStarted();
|
||||
|
||||
protected slots:
|
||||
void onAcceptError(QAbstractSocket::SocketError socketError);
|
||||
void onNewConnection();
|
||||
void onReadyRead();
|
||||
void onSocketDisconnected();
|
||||
void onSocketError(QAbstractSocket::SocketError socketError);
|
||||
|
||||
protected:
|
||||
QSharedPointer<QTcpServer> m_tcpServer;
|
||||
QPointer<QTcpSocket> m_socket;
|
||||
};
|
||||
|
||||
#endif // MANAGEMENTSERVER_H
|
||||
@@ -1,8 +1,46 @@
|
||||
#include "networkUtilities.h"
|
||||
#include <QtNetwork/qnetworkinterface.h>
|
||||
#include <cstddef>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <windows.h>
|
||||
#include <Ipexport.h>
|
||||
#include <Ws2tcpip.h>
|
||||
#include <ws2ipdef.h>
|
||||
#include <Iphlpapi.h>
|
||||
#include <Iptypes.h>
|
||||
#include <WinSock2.h>
|
||||
#include <winsock.h>
|
||||
#include <QNetworkInterface>
|
||||
#include "qendian.h"
|
||||
#include <QSettings>
|
||||
#endif
|
||||
#ifdef Q_OS_LINUX
|
||||
#include <arpa/inet.h>
|
||||
#include <linux/netlink.h>
|
||||
#include <linux/rtnetlink.h>
|
||||
#include <net/if.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||
#include <sys/param.h>
|
||||
#include <sys/sysctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <net/route.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <net/if.h>
|
||||
#include <net/if_dl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <unistd.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <net/if.h>
|
||||
#endif
|
||||
|
||||
#include <QHostAddress>
|
||||
#include <QHostInfo>
|
||||
@@ -36,6 +74,32 @@ QRegExp NetworkUtilities::domainRegExp()
|
||||
"9\\-]{1,30})\\.[a-z]{2,}");
|
||||
}
|
||||
|
||||
QString NetworkUtilities::netMaskFromIpWithSubnet(const QString ip)
|
||||
{
|
||||
if (!ip.contains("/"))
|
||||
return "255.255.255.255";
|
||||
|
||||
const QStringList parts = ip.split("/");
|
||||
if (parts.size() < 2)
|
||||
return "255.255.255.255";
|
||||
|
||||
bool ok;
|
||||
int prefix = parts.at(1).toInt(&ok);
|
||||
if (!ok || prefix < 0 || prefix > 32)
|
||||
return "255.255.255.255";
|
||||
|
||||
const quint32 mask = prefix == 0 ? 0 : 0xFFFFFFFFu << (32 - prefix);
|
||||
|
||||
return QString("%1.%2.%3.%4").arg(mask >> 24).arg((mask >> 16) & 0xFF).arg((mask >> 8) & 0xFF).arg(mask & 0xFF);
|
||||
}
|
||||
|
||||
QString NetworkUtilities::ipAddressFromIpWithSubnet(const QString ip)
|
||||
{
|
||||
if (ip.count(".") != 3)
|
||||
return "";
|
||||
return ip.split("/").first();
|
||||
}
|
||||
|
||||
QStringList NetworkUtilities::summarizeRoutes(const QStringList &ips, const QString cidr)
|
||||
{
|
||||
// QMap<int, int>
|
||||
@@ -110,6 +174,27 @@ bool NetworkUtilities::checkIpSubnetFormat(const QString &ip)
|
||||
return false;
|
||||
}
|
||||
|
||||
// static
|
||||
int NetworkUtilities::AdapterIndexTo(const QHostAddress& dst) {
|
||||
#ifdef Q_OS_WIN
|
||||
qDebug() << "Getting Current Internet Adapter that routes to"
|
||||
<< dst.toString();
|
||||
quint32 ipBigEndian;
|
||||
quint32 ip = dst.toIPv4Address();
|
||||
qToBigEndian(ip, &ipBigEndian);
|
||||
_MIB_IPFORWARDROW routeInfo;
|
||||
auto result = GetBestRoute(ipBigEndian, 0, &routeInfo);
|
||||
if (result != NO_ERROR) {
|
||||
return -1;
|
||||
}
|
||||
auto adapter =
|
||||
QNetworkInterface::interfaceFromIndex(routeInfo.dwForwardIfIndex);
|
||||
qDebug() << "Internet Adapter:" << adapter.name();
|
||||
return routeInfo.dwForwardIfIndex;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool NetworkUtilities::checkIpv6Enabled() {
|
||||
#ifdef Q_OS_WIN
|
||||
QSettings RegHLM("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Tcpip6\\Parameters",
|
||||
@@ -120,3 +205,302 @@ bool NetworkUtilities::checkIpv6Enabled() {
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
DWORD GetAdaptersAddressesWrapper(const ULONG Family,
|
||||
const ULONG Flags,
|
||||
const PVOID Reserved,
|
||||
_Out_ PIP_ADAPTER_ADDRESSES& pAdapterAddresses) {
|
||||
DWORD dwRetVal = 0;
|
||||
int iter = 0;
|
||||
constexpr int max_iter = 3;
|
||||
ULONG AdapterAddressesLen = 15000;
|
||||
do {
|
||||
// xassert2(pAdapterAddresses == nullptr);
|
||||
pAdapterAddresses = (IP_ADAPTER_ADDRESSES*)malloc(AdapterAddressesLen);
|
||||
if (pAdapterAddresses == nullptr) {
|
||||
qDebug() << "can not malloc" << AdapterAddressesLen << "bytes";
|
||||
return ERROR_OUTOFMEMORY;
|
||||
}
|
||||
|
||||
dwRetVal = GetAdaptersAddresses(Family, Flags, NULL, pAdapterAddresses, &AdapterAddressesLen);
|
||||
|
||||
if (dwRetVal == ERROR_BUFFER_OVERFLOW) {
|
||||
free(pAdapterAddresses);
|
||||
pAdapterAddresses = nullptr;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
iter++;
|
||||
} while ((dwRetVal == ERROR_BUFFER_OVERFLOW) && (iter < max_iter));
|
||||
|
||||
if (dwRetVal != NO_ERROR) {
|
||||
qDebug() << "Family: " << Family << ", Flags: " << Flags << " AdapterAddressesLen: " << AdapterAddressesLen <<
|
||||
", dwRetVal:" << dwRetVal << ", iter: " << iter;
|
||||
if (pAdapterAddresses) {
|
||||
free(pAdapterAddresses);
|
||||
pAdapterAddresses = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return dwRetVal;
|
||||
}
|
||||
#endif
|
||||
|
||||
QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
constexpr int BUFF_LEN = 100;
|
||||
char buff[BUFF_LEN] = {'\0'};
|
||||
|
||||
QString resGateway;
|
||||
int resIndex = -1;
|
||||
|
||||
PIP_ADAPTER_ADDRESSES pAdapterAddresses = nullptr;
|
||||
DWORD dwRetVal =
|
||||
GetAdaptersAddressesWrapper(AF_INET, GAA_FLAG_INCLUDE_GATEWAYS, NULL, pAdapterAddresses);
|
||||
|
||||
if (dwRetVal != NO_ERROR) {
|
||||
qDebug() << "ipv4 stack detect GetAdaptersAddresses failed.";
|
||||
return {};
|
||||
}
|
||||
|
||||
PIP_ADAPTER_ADDRESSES pCurAddress = pAdapterAddresses;
|
||||
while (pCurAddress) {
|
||||
PIP_ADAPTER_GATEWAY_ADDRESS_LH gateway = pCurAddress->FirstGatewayAddress;
|
||||
if (gateway) {
|
||||
SOCKET_ADDRESS gateway_address = gateway->Address;
|
||||
if (gateway->Address.lpSockaddr->sa_family == AF_INET) {
|
||||
sockaddr_in* sa_in = (sockaddr_in*)gateway->Address.lpSockaddr;
|
||||
QString gw = inet_ntop(AF_INET, &(sa_in->sin_addr), buff, BUFF_LEN);
|
||||
qDebug() << "gateway IPV4:" << gw;
|
||||
struct sockaddr_in addr;
|
||||
if (inet_pton(AF_INET, buff, &addr.sin_addr) == 1) {
|
||||
qDebug() << "this is true v4 !";
|
||||
|
||||
resGateway = gw;
|
||||
resIndex = pCurAddress->IfIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
pCurAddress = pCurAddress->Next;
|
||||
}
|
||||
|
||||
free(pAdapterAddresses);
|
||||
return { resGateway, QNetworkInterface::interfaceFromIndex(resIndex) };
|
||||
#endif
|
||||
#ifdef Q_OS_LINUX
|
||||
constexpr int BUFFER_SIZE = 8192;
|
||||
int received_bytes = 0, msg_len = 0, route_attribute_len = 0;
|
||||
int sock = -1, msgseq = 0;
|
||||
struct nlmsghdr *nlh, *nlmsg;
|
||||
struct rtmsg *route_entry;
|
||||
// This struct contain route attributes (route type)
|
||||
struct rtattr *route_attribute;
|
||||
char gateway_address[INET_ADDRSTRLEN], interface[IF_NAMESIZE];
|
||||
char msgbuf[100], buffer[BUFFER_SIZE];
|
||||
char *ptr = buffer;
|
||||
struct timeval tv;
|
||||
|
||||
if ((sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) {
|
||||
perror("socket failed");
|
||||
return {};
|
||||
}
|
||||
|
||||
memset(msgbuf, 0, sizeof(msgbuf));
|
||||
memset(gateway_address, 0, sizeof(gateway_address));
|
||||
memset(interface, 0, sizeof(interface));
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
|
||||
/* point the header and the msg structure pointers into the buffer */
|
||||
nlmsg = (struct nlmsghdr *)msgbuf;
|
||||
|
||||
/* Fill in the nlmsg header*/
|
||||
nlmsg->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
|
||||
nlmsg->nlmsg_type = RTM_GETROUTE; // Get the routes from kernel routing table .
|
||||
nlmsg->nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST; // The message is a request for dump.
|
||||
nlmsg->nlmsg_seq = msgseq++; // Sequence of the message packet.
|
||||
nlmsg->nlmsg_pid = getpid(); // PID of process sending the request.
|
||||
|
||||
/* 1 Sec Timeout to avoid stall */
|
||||
tv.tv_sec = 1;
|
||||
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (struct timeval *)&tv, sizeof(struct timeval));
|
||||
/* send msg */
|
||||
if (send(sock, nlmsg, nlmsg->nlmsg_len, 0) < 0) {
|
||||
perror("send failed");
|
||||
return {};
|
||||
}
|
||||
|
||||
/* receive response */
|
||||
do
|
||||
{
|
||||
received_bytes = recv(sock, ptr, sizeof(buffer) - msg_len, 0);
|
||||
if (received_bytes < 0) {
|
||||
perror("Error in recv");
|
||||
return {};
|
||||
}
|
||||
|
||||
nlh = (struct nlmsghdr *) ptr;
|
||||
|
||||
/* Check if the header is valid */
|
||||
if((NLMSG_OK(nlh, received_bytes) == 0) ||
|
||||
(nlh->nlmsg_type == NLMSG_ERROR))
|
||||
{
|
||||
perror("Error in received packet");
|
||||
return {};
|
||||
}
|
||||
|
||||
/* If we received all data break */
|
||||
if (nlh->nlmsg_type == NLMSG_DONE)
|
||||
break;
|
||||
else {
|
||||
ptr += received_bytes;
|
||||
msg_len += received_bytes;
|
||||
}
|
||||
|
||||
/* Break if its not a multi part message */
|
||||
if ((nlh->nlmsg_flags & NLM_F_MULTI) == 0)
|
||||
break;
|
||||
}
|
||||
while ((nlh->nlmsg_seq != msgseq) || (nlh->nlmsg_pid != getpid()));
|
||||
|
||||
/* parse response */
|
||||
int remaining = msg_len + received_bytes;
|
||||
nlh = (struct nlmsghdr *) buffer;
|
||||
for ( ; NLMSG_OK(nlh, remaining); nlh = NLMSG_NEXT(nlh, remaining))
|
||||
{
|
||||
/* Get the route data */
|
||||
route_entry = (struct rtmsg *) NLMSG_DATA(nlh);
|
||||
|
||||
/* We are just interested in main routing table */
|
||||
if (route_entry->rtm_table != RT_TABLE_MAIN)
|
||||
continue;
|
||||
|
||||
/* Reset per-route to avoid cross-route state pollution */
|
||||
memset(gateway_address, 0, sizeof(gateway_address));
|
||||
memset(interface, 0, sizeof(interface));
|
||||
|
||||
route_attribute = (struct rtattr *) RTM_RTA(route_entry);
|
||||
route_attribute_len = RTM_PAYLOAD(nlh);
|
||||
|
||||
/* Loop through all attributes */
|
||||
for ( ; RTA_OK(route_attribute, route_attribute_len);
|
||||
route_attribute = RTA_NEXT(route_attribute, route_attribute_len))
|
||||
{
|
||||
switch(route_attribute->rta_type) {
|
||||
case RTA_OIF:
|
||||
if_indextoname(*(int *)RTA_DATA(route_attribute), interface);
|
||||
break;
|
||||
case RTA_GATEWAY:
|
||||
inet_ntop(AF_INET, RTA_DATA(route_attribute),
|
||||
gateway_address, sizeof(gateway_address));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ((*gateway_address) && (*interface)) {
|
||||
qDebug() << "Gateway " << gateway_address << " for interface " << interface;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!(*gateway_address) || !(*interface))
|
||||
qDebug() << "getGatewayAndIface: no gateway found";
|
||||
close(sock);
|
||||
return { gateway_address, QNetworkInterface::interfaceFromName(interface) };
|
||||
#endif
|
||||
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
|
||||
QString gateway;
|
||||
int index = -1;
|
||||
|
||||
int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_FLAGS, RTF_GATEWAY};
|
||||
int afinet_type[] = {AF_INET, AF_INET6};
|
||||
|
||||
for (int ip_type = 0; ip_type <= 1; ip_type++)
|
||||
{
|
||||
mib[3] = afinet_type[ip_type];
|
||||
|
||||
size_t needed = 0;
|
||||
if (sysctl(mib, sizeof(mib) / sizeof(int), nullptr, &needed, nullptr, 0) < 0)
|
||||
return {};
|
||||
|
||||
char* buf;
|
||||
if ((buf = new char[needed]) == 0)
|
||||
return {};
|
||||
|
||||
if (sysctl(mib, sizeof(mib) / sizeof(int), buf, &needed, nullptr, 0) < 0)
|
||||
{
|
||||
qDebug() << "sysctl: net.route.0.0.dump";
|
||||
delete[] buf;
|
||||
return {};
|
||||
}
|
||||
|
||||
struct rt_msghdr* rt;
|
||||
for (char* p = buf; p < buf + needed; p += rt->rtm_msglen)
|
||||
{
|
||||
rt = reinterpret_cast<struct rt_msghdr*>(p);
|
||||
struct sockaddr* sa = reinterpret_cast<struct sockaddr*>(rt + 1);
|
||||
struct sockaddr* sa_tab[RTAX_MAX];
|
||||
for (int i = 0; i < RTAX_MAX; i++)
|
||||
{
|
||||
if (rt->rtm_addrs & (1 << i))
|
||||
{
|
||||
sa_tab[i] = sa;
|
||||
sa = reinterpret_cast<struct sockaddr*>(
|
||||
reinterpret_cast<char*>(sa) +
|
||||
((sa->sa_len) > 0 ? (1 + (((sa->sa_len) - 1) | (sizeof(long) - 1))) : sizeof(long)));
|
||||
}
|
||||
else
|
||||
{
|
||||
sa_tab[i] = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (((rt->rtm_addrs & (RTA_DST | RTA_GATEWAY)) == (RTA_DST | RTA_GATEWAY)) &&
|
||||
sa_tab[RTAX_DST]->sa_family == afinet_type[ip_type] &&
|
||||
sa_tab[RTAX_GATEWAY]->sa_family == afinet_type[ip_type])
|
||||
{
|
||||
if (afinet_type[ip_type] == AF_INET)
|
||||
{
|
||||
if ((reinterpret_cast<struct sockaddr_in*>(sa_tab[RTAX_DST]))->sin_addr.s_addr == 0)
|
||||
{
|
||||
char dstStr4[INET_ADDRSTRLEN];
|
||||
char srcStr4[INET_ADDRSTRLEN];
|
||||
memcpy(srcStr4,
|
||||
&(reinterpret_cast<struct sockaddr_in*>(sa_tab[RTAX_GATEWAY]))->sin_addr,
|
||||
sizeof(struct in_addr));
|
||||
if (inet_ntop(AF_INET, srcStr4, dstStr4, INET_ADDRSTRLEN) != nullptr)
|
||||
{
|
||||
gateway = dstStr4;
|
||||
index = rt->rtm_index;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (afinet_type[ip_type] == AF_INET6)
|
||||
{
|
||||
if ((reinterpret_cast<struct sockaddr_in*>(sa_tab[RTAX_DST]))->sin_addr.s_addr == 0)
|
||||
{
|
||||
char dstStr6[INET6_ADDRSTRLEN];
|
||||
char srcStr6[INET6_ADDRSTRLEN];
|
||||
memcpy(srcStr6,
|
||||
&(reinterpret_cast<struct sockaddr_in6*>(sa_tab[RTAX_GATEWAY]))->sin6_addr,
|
||||
sizeof(struct in6_addr));
|
||||
if (inet_ntop(AF_INET6, srcStr6, dstStr6, INET6_ADDRSTRLEN) != nullptr)
|
||||
{
|
||||
gateway = dstStr6;
|
||||
index = rt->rtm_index;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
free(buf);
|
||||
}
|
||||
|
||||
return { gateway, QNetworkInterface::interfaceFromIndex(index) };
|
||||
#endif
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <QString>
|
||||
#include <QHostAddress>
|
||||
#include <QNetworkReply>
|
||||
#include <QtNetwork/qnetworkinterface.h>
|
||||
|
||||
class NetworkUtilities : public QObject
|
||||
{
|
||||
@@ -16,6 +17,9 @@ public:
|
||||
static bool checkIPv4Format(const QString &ip);
|
||||
static bool checkIpSubnetFormat(const QString &ip);
|
||||
static bool checkIpv6Enabled();
|
||||
static QPair<QString, QNetworkInterface> getGatewayAndIface();
|
||||
// Returns the Interface Index that could Route to dst
|
||||
static int AdapterIndexTo(const QHostAddress& dst);
|
||||
|
||||
static QRegularExpression ipAddressRegExp();
|
||||
static QRegExp ipAddressWithSubnetRegExp();
|
||||
@@ -23,6 +27,8 @@ public:
|
||||
static QRegExp ipPortRegExp();
|
||||
static QRegExp domainRegExp();
|
||||
|
||||
static QString netMaskFromIpWithSubnet(const QString ip);
|
||||
static QString ipAddressFromIpWithSubnet(const QString ip);
|
||||
static QStringList summarizeRoutes(const QStringList &ips, const QString cidr);
|
||||
};
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include <QMetaObject>
|
||||
#include <QSocketNotifier>
|
||||
|
||||
#include "amneziaApplication.h"
|
||||
#include "../amneziaApplication.h"
|
||||
|
||||
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
|
||||
#include <pthread.h>
|
||||
|
||||
@@ -12,6 +12,30 @@
|
||||
#include "core/utils/utilities.h"
|
||||
|
||||
#ifdef Q_OS_WINDOWS
|
||||
QString printErrorMessage(DWORD errorCode) {
|
||||
LPVOID lpMsgBuf;
|
||||
|
||||
DWORD dwFlags = FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
||||
FORMAT_MESSAGE_FROM_SYSTEM |
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS;
|
||||
|
||||
DWORD dwLanguageId = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US);
|
||||
|
||||
FormatMessageW(
|
||||
dwFlags,
|
||||
NULL,
|
||||
errorCode,
|
||||
dwLanguageId,
|
||||
(LPWSTR)&lpMsgBuf,
|
||||
0,
|
||||
NULL
|
||||
);
|
||||
|
||||
QString errorMsg = QString::fromWCharArray((LPCWSTR)lpMsgBuf);
|
||||
LocalFree(lpMsgBuf);
|
||||
return errorMsg.trimmed();
|
||||
}
|
||||
|
||||
QString Utils::getNextDriverLetter()
|
||||
{
|
||||
DWORD drivesBitmask = GetLogicalDrives();
|
||||
@@ -189,6 +213,83 @@ bool Utils::processIsRunning(const QString &fileName, const bool fullFlag)
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Utils::killProcessByName(const QString &name)
|
||||
{
|
||||
qDebug().noquote() << "Kill process" << name;
|
||||
#ifdef Q_OS_WIN
|
||||
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
if (hSnapshot == INVALID_HANDLE_VALUE)
|
||||
return false;
|
||||
|
||||
PROCESSENTRY32W pe32;
|
||||
pe32.dwSize = sizeof(PROCESSENTRY32W);
|
||||
|
||||
bool success = false;
|
||||
|
||||
if (Process32FirstW(hSnapshot, &pe32)) {
|
||||
do {
|
||||
QString exeFile = QString::fromWCharArray(pe32.szExeFile);
|
||||
|
||||
if (exeFile.compare(name, Qt::CaseInsensitive) == 0) {
|
||||
HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pe32.th32ProcessID);
|
||||
if (hProcess != NULL) {
|
||||
if (TerminateProcess(hProcess, 0)) {
|
||||
success = true;
|
||||
} else {
|
||||
DWORD error = GetLastError();
|
||||
qCritical() << "Can't terminate process" << exeFile << "(PID:" << pe32.th32ProcessID << "). Error:" << printErrorMessage(error);
|
||||
}
|
||||
CloseHandle(hProcess);
|
||||
} else {
|
||||
DWORD error = GetLastError();
|
||||
qCritical() << "Can't open process for termination" << exeFile << "(PID:" << pe32.th32ProcessID << "). Error:" << printErrorMessage(error);
|
||||
}
|
||||
}
|
||||
} while (Process32NextW(hSnapshot, &pe32));
|
||||
}
|
||||
|
||||
CloseHandle(hSnapshot);
|
||||
return success;
|
||||
#elif defined Q_OS_IOS || defined(Q_OS_ANDROID)
|
||||
return false;
|
||||
#else
|
||||
return QProcess::execute("pkill", { name }) == 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
QString Utils::openVpnExecPath()
|
||||
{
|
||||
return Utils::executable("openvpn", true);
|
||||
}
|
||||
|
||||
QString Utils::wireguardExecPath()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
return Utils::executable("wireguard/wireguard-service", true);
|
||||
#elif defined Q_OS_LINUX
|
||||
return Utils::usrExecutable("wg-quick");
|
||||
#elif defined Q_OS_MAC
|
||||
return Utils::executable("/wireguard", true);
|
||||
#else
|
||||
return {};
|
||||
#endif
|
||||
}
|
||||
|
||||
QString Utils::certUtilPath()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
QString winPath = QString::fromUtf8(qgetenv("windir"));
|
||||
return winPath + "\\system32\\certutil.exe";
|
||||
#else
|
||||
return "";
|
||||
#endif
|
||||
}
|
||||
|
||||
QString Utils::tun2socksPath()
|
||||
{
|
||||
return Utils::executable("tun2socks", true);
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
// Inspired from http://stackoverflow.com/a/15281070/1529139
|
||||
// and http://stackoverflow.com/q/40059902/1529139
|
||||
|
||||
@@ -28,6 +28,12 @@ public:
|
||||
static bool initializePath(const QString &path);
|
||||
|
||||
static bool processIsRunning(const QString &fileName, const bool fullFlag = false);
|
||||
static bool killProcessByName(const QString &name);
|
||||
|
||||
static QString openVpnExecPath();
|
||||
static QString wireguardExecPath();
|
||||
static QString certUtilPath();
|
||||
static QString tun2socksPath();
|
||||
|
||||
static void logException(const std::exception &e);
|
||||
static void logException(const std::exception_ptr &eptr = std::current_exception());
|
||||
|
||||
@@ -0,0 +1,651 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "daemon.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
#include <QMetaEnum>
|
||||
#include <QTimer>
|
||||
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
|
||||
constexpr const char* JSON_ALLOWEDIPADDRESSRANGES = "allowedIPAddressRanges";
|
||||
constexpr int HANDSHAKE_POLL_MSEC = 250;
|
||||
|
||||
namespace {
|
||||
|
||||
Logger logger("Daemon");
|
||||
|
||||
Daemon* s_daemon = nullptr;
|
||||
|
||||
} // namespace
|
||||
|
||||
Daemon::Daemon(QObject* parent) : QObject(parent) {
|
||||
MZ_COUNT_CTOR(Daemon);
|
||||
|
||||
logger.debug() << "Daemon created";
|
||||
|
||||
Q_ASSERT(s_daemon == nullptr);
|
||||
s_daemon = this;
|
||||
|
||||
m_handshakeTimer.setSingleShot(true);
|
||||
connect(&m_handshakeTimer, &QTimer::timeout, this, &Daemon::checkHandshake);
|
||||
}
|
||||
|
||||
Daemon::~Daemon() {
|
||||
MZ_COUNT_DTOR(Daemon);
|
||||
|
||||
logger.debug() << "Daemon released";
|
||||
|
||||
Q_ASSERT(s_daemon == this);
|
||||
s_daemon = nullptr;
|
||||
}
|
||||
|
||||
// static
|
||||
Daemon* Daemon::instance() {
|
||||
Q_ASSERT(s_daemon);
|
||||
return s_daemon;
|
||||
}
|
||||
|
||||
bool Daemon::activate(const InterfaceConfig& config) {
|
||||
Q_ASSERT(wgutils() != nullptr);
|
||||
|
||||
// There are 3 possible scenarios in which this method is called:
|
||||
//
|
||||
// 1. the VPN is off: the method tries to enable the VPN.
|
||||
// 2. the VPN is on and the platform doesn't support the server-switching:
|
||||
// this method calls deactivate() and then it continues as 1.
|
||||
// 3. the VPN is on and the platform supports the server-switching: this
|
||||
// method calls switchServer().
|
||||
//
|
||||
// At the end, if the activation succeds, the `connected` signal is emitted.
|
||||
// If the activation abort's for any reason `the `activationFailure` signal is
|
||||
// emitted.
|
||||
logger.debug() << "Activating interface";
|
||||
auto emit_failure_guard = qScopeGuard([this] { emit activationFailure(); });
|
||||
|
||||
if (m_connections.contains(config.m_hopType)) {
|
||||
if (supportServerSwitching(config)) {
|
||||
logger.debug() << "Already connected. Server switching supported.";
|
||||
|
||||
if (!switchServer(config)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!dnsutils()->restoreResolvers()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!maybeUpdateResolvers(config)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool status = run(Switch, config);
|
||||
logger.debug() << "Connection status:" << status;
|
||||
if (status) {
|
||||
m_connections[config.m_hopType] = ConnectionState(config);
|
||||
m_handshakeTimer.start(HANDSHAKE_POLL_MSEC);
|
||||
emit_failure_guard.dismiss();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
logger.warning() << "Already connected. Server switching not supported.";
|
||||
if (!deactivate(false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Q_ASSERT(!m_connections.contains(config.m_hopType));
|
||||
if (activate(config)) {
|
||||
emit_failure_guard.dismiss();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
prepareActivation(config);
|
||||
|
||||
// Bring up the wireguard interface if not already done.
|
||||
if (!wgutils()->interfaceExists()) {
|
||||
// Create the interface.
|
||||
if (!wgutils()->addInterface(config)) {
|
||||
logger.error() << "Interface creation failed.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Bring the interface up.
|
||||
if (supportIPUtils()) {
|
||||
if (!iputils()->addInterfaceIPs(config)) {
|
||||
return false;
|
||||
}
|
||||
if (!iputils()->setMTUAndUp(config)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Configure routing for excluded addresses.
|
||||
for (const QString& i : config.m_excludedAddresses) {
|
||||
addExclusionRoute(IPAddress(i));
|
||||
}
|
||||
|
||||
// Add the peer to this interface.
|
||||
if (!wgutils()->updatePeer(config)) {
|
||||
logger.error() << "Peer creation failed.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!maybeUpdateResolvers(config)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// set routing
|
||||
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
|
||||
if (!wgutils()->updateRoutePrefix(ip)) {
|
||||
logger.debug() << "Routing configuration failed for" << ip.toString();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool status = run(Up, config);
|
||||
logger.debug() << "Connection status:" << status;
|
||||
if (status) {
|
||||
m_connections[config.m_hopType] = ConnectionState(config);
|
||||
m_handshakeTimer.start(HANDSHAKE_POLL_MSEC);
|
||||
emit_failure_guard.dismiss();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Daemon::maybeUpdateResolvers(const InterfaceConfig& config) {
|
||||
if ((config.m_hopType == InterfaceConfig::MultiHopExit) ||
|
||||
(config.m_hopType == InterfaceConfig::SingleHop)) {
|
||||
QList<QHostAddress> resolvers;
|
||||
resolvers.append(QHostAddress(config.m_primaryDnsServer));
|
||||
if (!config.m_secondaryDnsServer.isEmpty()) {
|
||||
resolvers.append(QHostAddress(config.m_secondaryDnsServer));
|
||||
}
|
||||
|
||||
// If the DNS is not the Gateway, it's a user defined DNS
|
||||
// thus, not add any other :)
|
||||
if (config.m_primaryDnsServer == config.m_serverIpv4Gateway) {
|
||||
resolvers.append(QHostAddress(config.m_serverIpv6Gateway));
|
||||
}
|
||||
|
||||
if (!dnsutils()->updateResolvers(wgutils()->interfaceName(), resolvers)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// static
|
||||
bool Daemon::parseStringList(const QJsonObject& obj, const QString& name,
|
||||
QStringList& list) {
|
||||
if (obj.contains(name)) {
|
||||
QJsonValue value = obj.value(name);
|
||||
if (!value.isArray()) {
|
||||
logger.error() << name << "is not an array";
|
||||
return false;
|
||||
}
|
||||
QJsonArray array = value.toArray();
|
||||
for (const QJsonValue& i : array) {
|
||||
if (!i.isString()) {
|
||||
logger.error() << name << "must contain only strings";
|
||||
return false;
|
||||
}
|
||||
list.append(i.toString());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Daemon::addExclusionRoute(const IPAddress& prefix) {
|
||||
if (m_excludedAddrSet.contains(prefix)) {
|
||||
m_excludedAddrSet[prefix]++;
|
||||
return true;
|
||||
}
|
||||
if (!wgutils()->addExclusionRoute(prefix)) {
|
||||
return false;
|
||||
}
|
||||
m_excludedAddrSet[prefix] = 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Daemon::delExclusionRoute(const IPAddress& prefix) {
|
||||
Q_ASSERT(m_excludedAddrSet.contains(prefix));
|
||||
if (m_excludedAddrSet[prefix] > 1) {
|
||||
m_excludedAddrSet[prefix]--;
|
||||
return true;
|
||||
}
|
||||
m_excludedAddrSet.remove(prefix);
|
||||
return wgutils()->deleteExclusionRoute(prefix);
|
||||
}
|
||||
|
||||
// static
|
||||
bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
|
||||
#define GETVALUE(name, where, jsontype) \
|
||||
if (!obj.contains(name)) { \
|
||||
logger.debug() << name << " missing in the jsonConfig input"; \
|
||||
return false; \
|
||||
} else { \
|
||||
QJsonValue value = obj.value(name); \
|
||||
if (value.type() != QJsonValue::jsontype) { \
|
||||
logger.error() << name << " is not a " #jsontype; \
|
||||
return false; \
|
||||
} \
|
||||
where = value.to##jsontype(); \
|
||||
}
|
||||
|
||||
GETVALUE("privateKey", config.m_privateKey, String);
|
||||
GETVALUE("serverPublicKey", config.m_serverPublicKey, String);
|
||||
GETVALUE("serverPort", config.m_serverPort, Double);
|
||||
|
||||
config.m_serverPskKey = obj.value("serverPskKey").toString();
|
||||
|
||||
if (!obj.contains("deviceMTU") || obj.value("deviceMTU").toString().toInt() == 0)
|
||||
{
|
||||
config.m_deviceMTU = 1420;
|
||||
} else {
|
||||
config.m_deviceMTU = obj.value("deviceMTU").toString().toInt();
|
||||
#ifdef Q_OS_WINDOWS
|
||||
// For Windows min MTU value is 1280 (the smallest MTU legal with IPv6).
|
||||
if (config.m_deviceMTU < 1280) {
|
||||
config.m_deviceMTU = 1280;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
config.m_persistentKeepalive = obj.value("persistentKeepalive").toString();
|
||||
|
||||
config.m_deviceIpv4Address = obj.value("deviceIpv4Address").toString();
|
||||
config.m_deviceIpv6Address = obj.value("deviceIpv6Address").toString();
|
||||
if (config.m_deviceIpv4Address.isNull() &&
|
||||
config.m_deviceIpv6Address.isNull()) {
|
||||
logger.warning() << "no device addresses found in jsonConfig input";
|
||||
return false;
|
||||
}
|
||||
config.m_serverIpv4AddrIn = obj.value("serverIpv4AddrIn").toString();
|
||||
config.m_serverIpv6AddrIn = obj.value("serverIpv6AddrIn").toString();
|
||||
if (config.m_serverIpv4AddrIn.isNull() &&
|
||||
config.m_serverIpv6AddrIn.isNull()) {
|
||||
logger.error() << "no server addresses found in jsonConfig input";
|
||||
return false;
|
||||
}
|
||||
config.m_serverIpv4Gateway = obj.value("serverIpv4Gateway").toString();
|
||||
config.m_serverIpv6Gateway = obj.value("serverIpv6Gateway").toString();
|
||||
|
||||
if (!obj.contains("primaryDnsServer")) {
|
||||
config.m_primaryDnsServer = QString();
|
||||
} else {
|
||||
QJsonValue value = obj.value("primaryDnsServer");
|
||||
if (!value.isString()) {
|
||||
logger.error() << "dnsServer is not a string";
|
||||
return false;
|
||||
}
|
||||
config.m_primaryDnsServer = value.toString();
|
||||
}
|
||||
|
||||
if (!obj.contains("secondaryDnsServer")) {
|
||||
config.m_secondaryDnsServer = QString();
|
||||
} else {
|
||||
QJsonValue value = obj.value("secondaryDnsServer");
|
||||
if (!value.isString()) {
|
||||
logger.error() << "dnsServer is not a string";
|
||||
return false;
|
||||
}
|
||||
config.m_secondaryDnsServer = value.toString();
|
||||
}
|
||||
|
||||
if (!obj.contains("hopType")) {
|
||||
config.m_hopType = InterfaceConfig::SingleHop;
|
||||
} else {
|
||||
QJsonValue value = obj.value("hopType");
|
||||
if (!value.isString()) {
|
||||
logger.error() << "hopType is not a string";
|
||||
return false;
|
||||
}
|
||||
|
||||
bool okay;
|
||||
QByteArray vdata = value.toString().toUtf8();
|
||||
QMetaEnum meta = QMetaEnum::fromType<InterfaceConfig::HopType>();
|
||||
config.m_hopType =
|
||||
InterfaceConfig::HopType(meta.keyToValue(vdata.constData(), &okay));
|
||||
if (!okay) {
|
||||
logger.error() << "hopType" << value.toString() << "is not valid";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!obj.contains(JSON_ALLOWEDIPADDRESSRANGES)) {
|
||||
logger.error() << JSON_ALLOWEDIPADDRESSRANGES
|
||||
<< "missing in the jsonconfig input";
|
||||
return false;
|
||||
} else {
|
||||
QJsonValue value = obj.value(JSON_ALLOWEDIPADDRESSRANGES);
|
||||
if (!value.isArray()) {
|
||||
logger.error() << JSON_ALLOWEDIPADDRESSRANGES << "is not an array";
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonArray array = value.toArray();
|
||||
for (const QJsonValue& i : array) {
|
||||
if (!i.isObject()) {
|
||||
logger.error() << JSON_ALLOWEDIPADDRESSRANGES
|
||||
<< "must contain only objects";
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonObject ipObj = i.toObject();
|
||||
|
||||
QJsonValue address = ipObj.value("address");
|
||||
if (!address.isString()) {
|
||||
logger.error() << JSON_ALLOWEDIPADDRESSRANGES
|
||||
<< "objects must have a string address";
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonValue range = ipObj.value("range");
|
||||
if (!range.isDouble()) {
|
||||
logger.error() << JSON_ALLOWEDIPADDRESSRANGES
|
||||
<< "object must have a numberic range";
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonValue isIpv6 = ipObj.value("isIpv6");
|
||||
if (!isIpv6.isBool()) {
|
||||
logger.error() << JSON_ALLOWEDIPADDRESSRANGES
|
||||
<< "object must have a boolean isIpv6";
|
||||
return false;
|
||||
}
|
||||
|
||||
config.m_allowedIPAddressRanges.append(
|
||||
IPAddress(QHostAddress(address.toString()), range.toInt()));
|
||||
}
|
||||
|
||||
// Sort allowed IPs by decreasing prefix length.
|
||||
std::sort(config.m_allowedIPAddressRanges.begin(),
|
||||
config.m_allowedIPAddressRanges.end(),
|
||||
[&](const IPAddress& a, const IPAddress& b) -> bool {
|
||||
return a.prefixLength() > b.prefixLength();
|
||||
});
|
||||
}
|
||||
|
||||
if (!parseStringList(obj, "excludedAddresses", config.m_excludedAddresses)) {
|
||||
return false;
|
||||
}
|
||||
if (!parseStringList(obj, "vpnDisabledApps", config.m_vpnDisabledApps)) {
|
||||
return false;
|
||||
}
|
||||
if (!parseStringList(obj, "allowedDnsServers", config.m_allowedDnsServers)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
config.m_killSwitchEnabled = QVariant(obj.value("killSwitchOption").toString()).toBool();
|
||||
|
||||
if (const auto jc = obj.value("Jc"); !jc.isUndefined()) {
|
||||
config.m_junkPacketCount = jc.toString();
|
||||
}
|
||||
if (const auto jmin = obj.value("Jmin"); !jmin.isUndefined()) {
|
||||
config.m_junkPacketMinSize = jmin.toString();
|
||||
}
|
||||
if (const auto jmax = obj.value("Jmax"); !jmax.isUndefined()) {
|
||||
config.m_junkPacketMaxSize = jmax.toString();
|
||||
}
|
||||
if (const auto s1 = obj.value("S1"); !s1.isUndefined()) {
|
||||
config.m_initPacketJunkSize = s1.toString();
|
||||
}
|
||||
if (const auto s2 = obj.value("S2"); !s2.isUndefined()) {
|
||||
config.m_responsePacketJunkSize = s2.toString();
|
||||
}
|
||||
if (const auto s3 = obj.value("S3"); !s3.isUndefined()) {
|
||||
config.m_cookieReplyPacketJunkSize = s3.toString();
|
||||
}
|
||||
if (const auto s4 = obj.value("S4"); !s4.isUndefined()) {
|
||||
config.m_transportPacketJunkSize = s4.toString();
|
||||
}
|
||||
|
||||
if (const auto h1 = obj.value("H1"); !h1.isUndefined()) {
|
||||
config.m_initPacketMagicHeader = h1.toString();
|
||||
}
|
||||
if (const auto h2 = obj.value("H2"); !h2.isUndefined()) {
|
||||
config.m_responsePacketMagicHeader = h2.toString();
|
||||
}
|
||||
if (const auto h3 = obj.value("H3"); !h3.isUndefined()) {
|
||||
config.m_underloadPacketMagicHeader = h3.toString();
|
||||
}
|
||||
if (const auto h4 = obj.value("H4"); !h4.isUndefined()) {
|
||||
config.m_transportPacketMagicHeader = h4.toString();
|
||||
}
|
||||
|
||||
if (const auto i1 = obj.value("I1"); !i1.isUndefined()) {
|
||||
config.m_specialJunk["I1"] = i1.toString();
|
||||
}
|
||||
if (const auto i2 = obj.value("I2"); !i2.isUndefined()) {
|
||||
config.m_specialJunk["I2"] = i2.toString();
|
||||
}
|
||||
if (const auto i3 = obj.value("I3"); !i3.isUndefined()) {
|
||||
config.m_specialJunk["I3"] = i3.toString();
|
||||
}
|
||||
if (const auto i4 = obj.value("I4"); !i4.isUndefined()) {
|
||||
config.m_specialJunk["I4"] = i4.toString();
|
||||
}
|
||||
if (const auto i5 = obj.value("I5"); !i5.isUndefined()) {
|
||||
config.m_specialJunk["I5"] = i5.toString();
|
||||
}
|
||||
|
||||
if (const auto headerProtectionKey = obj.value("HeaderProtectionKey"); !headerProtectionKey.isUndefined()) {
|
||||
config.m_headerProtectionKey = headerProtectionKey.toString();
|
||||
}
|
||||
if (const auto contentPaddingAddition = obj.value("ContentPaddingAddition"); !contentPaddingAddition.isUndefined()) {
|
||||
config.m_contentPaddingAddition = contentPaddingAddition.toString();
|
||||
}
|
||||
if (const auto rekeyAfterTime = obj.value("RekeyAfterTime"); !rekeyAfterTime.isUndefined()) {
|
||||
config.m_rekeyAfterTime = rekeyAfterTime.toString();
|
||||
}
|
||||
if (const auto rekeyTimeout = obj.value("RekeyTimeout"); !rekeyTimeout.isUndefined()) {
|
||||
config.m_rekeyTimeout = rekeyTimeout.toString();
|
||||
}
|
||||
if (const auto rejectAfterTime = obj.value("RejectAfterTime"); !rejectAfterTime.isUndefined()) {
|
||||
config.m_rejectAfterTime = rejectAfterTime.toString();
|
||||
}
|
||||
if (const auto keepaliveTimeout = obj.value("KeepaliveTimeout"); !keepaliveTimeout.isUndefined()) {
|
||||
config.m_keepaliveTimeout = keepaliveTimeout.toString();
|
||||
}
|
||||
if (const auto maxHandshakeAttempts = obj.value("MaxHandshakeAttempts"); !maxHandshakeAttempts.isUndefined()) {
|
||||
config.m_maxHandshakeAttempts = maxHandshakeAttempts.toString();
|
||||
}
|
||||
if (const auto randomTrailers = obj.value("RandomTrailers"); !randomTrailers.isUndefined()) {
|
||||
config.m_randomTrailers = randomTrailers.toString();
|
||||
}
|
||||
if (const auto disableCookies = obj.value("DisableCookies"); !disableCookies.isUndefined()) {
|
||||
config.m_disableCookies = disableCookies.toString();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Daemon::deactivate(bool emitSignals) {
|
||||
Q_ASSERT(wgutils() != nullptr);
|
||||
|
||||
// Deactivate the main interface.
|
||||
if (!m_connections.isEmpty()) {
|
||||
const ConnectionState& state = m_connections.first();
|
||||
if (!run(Down, state.m_config)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (emitSignals) {
|
||||
emit disconnected();
|
||||
}
|
||||
|
||||
// Cleanup DNS
|
||||
if (!dnsutils()->restoreResolvers()) {
|
||||
logger.warning() << "Failed to restore DNS resolvers.";
|
||||
}
|
||||
|
||||
// Cleanup peers and routing
|
||||
for (const ConnectionState& state : m_connections) {
|
||||
const InterfaceConfig& config = state.m_config;
|
||||
logger.debug() << "Deleting routes for" << config.m_hopType;
|
||||
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
|
||||
wgutils()->deleteRoutePrefix(ip);
|
||||
}
|
||||
wgutils()->deletePeer(config);
|
||||
}
|
||||
|
||||
// Cleanup routing for excluded addresses.
|
||||
for (auto iterator = m_excludedAddrSet.constBegin();
|
||||
iterator != m_excludedAddrSet.constEnd(); ++iterator) {
|
||||
wgutils()->deleteExclusionRoute(iterator.key());
|
||||
}
|
||||
m_excludedAddrSet.clear();
|
||||
|
||||
m_connections.clear();
|
||||
// Delete the interface
|
||||
return wgutils()->deleteInterface();
|
||||
}
|
||||
|
||||
QString Daemon::logs() {
|
||||
return {};
|
||||
}
|
||||
|
||||
void Daemon::cleanLogs() { }
|
||||
|
||||
bool Daemon::supportServerSwitching(const InterfaceConfig& config) const {
|
||||
if (!m_connections.contains(config.m_hopType)) {
|
||||
return false;
|
||||
}
|
||||
const InterfaceConfig& current =
|
||||
m_connections.value(config.m_hopType).m_config;
|
||||
|
||||
return current.m_privateKey == config.m_privateKey &&
|
||||
current.m_deviceIpv4Address == config.m_deviceIpv4Address &&
|
||||
current.m_deviceIpv6Address == config.m_deviceIpv6Address &&
|
||||
current.m_serverIpv4Gateway == config.m_serverIpv4Gateway &&
|
||||
current.m_serverIpv6Gateway == config.m_serverIpv6Gateway;
|
||||
}
|
||||
|
||||
bool Daemon::switchServer(const InterfaceConfig& config) {
|
||||
Q_ASSERT(wgutils() != nullptr);
|
||||
|
||||
logger.debug() << "Switching server for" << config.m_hopType;
|
||||
|
||||
Q_ASSERT(m_connections.contains(config.m_hopType));
|
||||
const InterfaceConfig& lastConfig =
|
||||
m_connections.value(config.m_hopType).m_config;
|
||||
|
||||
// Configure routing for new excluded addresses.
|
||||
for (const QString& i : config.m_excludedAddresses) {
|
||||
addExclusionRoute(IPAddress(i));
|
||||
}
|
||||
|
||||
// Activate the new peer and its routes.
|
||||
if (!wgutils()->updatePeer(config)) {
|
||||
logger.error() << "Server switch failed to update the wireguard interface";
|
||||
return false;
|
||||
}
|
||||
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
|
||||
if (!wgutils()->updateRoutePrefix(ip)) {
|
||||
logger.error() << "Server switch failed to update the routing table";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Remove routing entries for the old peer.
|
||||
for (const QString& i : lastConfig.m_excludedAddresses) {
|
||||
delExclusionRoute(QHostAddress(i));
|
||||
}
|
||||
for (const IPAddress& ip : lastConfig.m_allowedIPAddressRanges) {
|
||||
if (!config.m_allowedIPAddressRanges.contains(ip)) {
|
||||
wgutils()->deleteRoutePrefix(ip);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the old peer if it is no longer necessary.
|
||||
if (config.m_serverPublicKey != lastConfig.m_serverPublicKey) {
|
||||
if (!wgutils()->deletePeer(lastConfig)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
m_connections[config.m_hopType] = ConnectionState(config);
|
||||
return true;
|
||||
}
|
||||
|
||||
QJsonObject Daemon::getStatus() {
|
||||
Q_ASSERT(wgutils() != nullptr);
|
||||
QJsonObject json;
|
||||
logger.debug() << "Status request";
|
||||
|
||||
if (!wgutils()->interfaceExists() || m_connections.isEmpty()) {
|
||||
json.insert("connected", QJsonValue(false));
|
||||
return json;
|
||||
}
|
||||
|
||||
const ConnectionState& connection = m_connections.first();
|
||||
QList<WireguardUtils::PeerStatus> peers = wgutils()->getPeerStatus();
|
||||
for (const WireguardUtils::PeerStatus& status : peers) {
|
||||
if (status.m_pubkey != connection.m_config.m_serverPublicKey) {
|
||||
continue;
|
||||
}
|
||||
json.insert("connected", QJsonValue(true));
|
||||
json.insert("serverIpv4Gateway",
|
||||
QJsonValue(connection.m_config.m_serverIpv4Gateway));
|
||||
json.insert("deviceIpv4Address",
|
||||
QJsonValue(connection.m_config.m_deviceIpv4Address));
|
||||
json.insert("date", connection.m_date.toString());
|
||||
json.insert("txBytes", QJsonValue(status.m_txBytes));
|
||||
json.insert("rxBytes", QJsonValue(status.m_rxBytes));
|
||||
return json;
|
||||
}
|
||||
|
||||
json.insert("connected", QJsonValue(false));
|
||||
return json;
|
||||
}
|
||||
|
||||
void Daemon::checkHandshake() {
|
||||
Q_ASSERT(wgutils() != nullptr);
|
||||
|
||||
logger.debug() << "Checking for handshake...";
|
||||
|
||||
int pendingHandshakes = 0;
|
||||
QList<WireguardUtils::PeerStatus> peers = wgutils()->getPeerStatus();
|
||||
for (ConnectionState& connection : m_connections) {
|
||||
const InterfaceConfig& config = connection.m_config;
|
||||
if (connection.m_date.isValid()) {
|
||||
continue;
|
||||
}
|
||||
logger.debug() << "awaiting" << config.m_serverPublicKey;
|
||||
|
||||
// Check if the handshake has completed.
|
||||
for (const WireguardUtils::PeerStatus& status : peers) {
|
||||
if (config.m_serverPublicKey != status.m_pubkey) {
|
||||
continue;
|
||||
}
|
||||
if (status.m_handshake != 0) {
|
||||
connection.m_date.setMSecsSinceEpoch(status.m_handshake);
|
||||
emit connected(status.m_pubkey);
|
||||
}
|
||||
}
|
||||
|
||||
if (!connection.m_date.isValid()) {
|
||||
pendingHandshakes++;
|
||||
}
|
||||
}
|
||||
|
||||
// Check again if there were connections that haven't completed a handshake.
|
||||
if (pendingHandshakes > 0) {
|
||||
m_handshakeTimer.start(HANDSHAKE_POLL_MSEC);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef DAEMON_H
|
||||
#define DAEMON_H
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QTimer>
|
||||
|
||||
#include "daemon/daemonerrors.h"
|
||||
#include "daemonerrors.h"
|
||||
#include "dnsutils.h"
|
||||
#include "interfaceconfig.h"
|
||||
#include "iputils.h"
|
||||
#include "wireguardutils.h"
|
||||
|
||||
class Daemon : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum Op {
|
||||
Up,
|
||||
Down,
|
||||
Switch,
|
||||
};
|
||||
|
||||
explicit Daemon(QObject* parent);
|
||||
~Daemon();
|
||||
|
||||
static Daemon* instance();
|
||||
|
||||
static bool parseConfig(const QJsonObject& obj, InterfaceConfig& config);
|
||||
|
||||
virtual bool activate(const InterfaceConfig& config);
|
||||
virtual bool deactivate(bool emitSignals = true);
|
||||
virtual QJsonObject getStatus();
|
||||
|
||||
// Callback before any Activating measure is done
|
||||
virtual void prepareActivation(const InterfaceConfig& config, int inetAdapterIndex = 0) {
|
||||
Q_UNUSED(config) };
|
||||
virtual void activateSplitTunnel(const InterfaceConfig& config, int vpnAdapterIndex = 0) {
|
||||
Q_UNUSED(config) };
|
||||
|
||||
QString logs();
|
||||
void cleanLogs();
|
||||
|
||||
signals:
|
||||
void connected(const QString& pubkey);
|
||||
/**
|
||||
* Can be fired if a call to activate() was unsucessfull
|
||||
* and connected systems should rollback
|
||||
*/
|
||||
void activationFailure();
|
||||
void disconnected();
|
||||
void backendFailure(DaemonError reason = DaemonError::ERROR_FATAL);
|
||||
|
||||
private:
|
||||
bool maybeUpdateResolvers(const InterfaceConfig& config);
|
||||
bool addExclusionRoute(const IPAddress& address);
|
||||
bool delExclusionRoute(const IPAddress& address);
|
||||
|
||||
protected:
|
||||
virtual bool run(Op op, const InterfaceConfig& config) {
|
||||
Q_UNUSED(op);
|
||||
Q_UNUSED(config);
|
||||
return true;
|
||||
}
|
||||
virtual bool supportServerSwitching(const InterfaceConfig& config) const;
|
||||
virtual bool switchServer(const InterfaceConfig& config);
|
||||
virtual WireguardUtils* wgutils() const = 0;
|
||||
virtual bool supportIPUtils() const { return false; }
|
||||
virtual IPUtils* iputils() { return nullptr; }
|
||||
virtual DnsUtils* dnsutils() { return nullptr; }
|
||||
|
||||
static bool parseStringList(const QJsonObject& obj, const QString& name,
|
||||
QStringList& list);
|
||||
|
||||
void checkHandshake();
|
||||
|
||||
class ConnectionState {
|
||||
public:
|
||||
ConnectionState(){};
|
||||
ConnectionState(const InterfaceConfig& config) { m_config = config; }
|
||||
QDateTime m_date;
|
||||
InterfaceConfig m_config;
|
||||
};
|
||||
QMap<InterfaceConfig::HopType, ConnectionState> m_connections;
|
||||
QHash<IPAddress, int> m_excludedAddrSet;
|
||||
QTimer m_handshakeTimer;
|
||||
};
|
||||
|
||||
#endif // DAEMON_H
|
||||
@@ -0,0 +1,17 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
enum class DaemonError : uint8_t {
|
||||
ERROR_NONE = 0u,
|
||||
ERROR_FATAL = 1u,
|
||||
ERROR_SPLIT_TUNNEL_INIT_FAILURE = 2u,
|
||||
ERROR_SPLIT_TUNNEL_START_FAILURE = 3u,
|
||||
ERROR_SPLIT_TUNNEL_EXCLUDE_FAILURE = 4u,
|
||||
|
||||
DAEMON_ERROR_MAX = 5u,
|
||||
};
|
||||
@@ -0,0 +1,97 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "daemonlocalserver.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QLocalSocket>
|
||||
|
||||
#include "daemonlocalserverconnection.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
|
||||
#if defined(MZ_MACOS) || defined(MZ_LINUX)
|
||||
# include <sys/stat.h>
|
||||
# include <sys/types.h>
|
||||
# include <unistd.h>
|
||||
|
||||
constexpr const char* TMP_PATH = "/tmp/amneziavpn.socket";
|
||||
constexpr const char* VAR_PATH = "/var/run/amneziavpn/daemon.socket";
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
Logger logger("DaemonLocalServer");
|
||||
} // namespace
|
||||
|
||||
DaemonLocalServer::DaemonLocalServer(QObject* parent) : QObject(parent) {
|
||||
MZ_COUNT_CTOR(DaemonLocalServer);
|
||||
}
|
||||
|
||||
DaemonLocalServer::~DaemonLocalServer() { MZ_COUNT_DTOR(DaemonLocalServer); }
|
||||
|
||||
bool DaemonLocalServer::initialize() {
|
||||
m_server.setSocketOptions(QLocalServer::WorldAccessOption);
|
||||
|
||||
QString path = daemonPath();
|
||||
logger.debug() << "Server path:" << path;
|
||||
|
||||
if (QFileInfo::exists(path)) {
|
||||
QFile::remove(path);
|
||||
}
|
||||
|
||||
if (!m_server.listen(path)) {
|
||||
logger.error() << "Failed to listen the daemon path";
|
||||
return false;
|
||||
}
|
||||
|
||||
connect(&m_server, &QLocalServer::newConnection, [&] {
|
||||
logger.debug() << "New connection received";
|
||||
|
||||
if (!m_server.hasPendingConnections()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QLocalSocket* socket = m_server.nextPendingConnection();
|
||||
Q_ASSERT(socket);
|
||||
|
||||
DaemonLocalServerConnection* connection =
|
||||
new DaemonLocalServerConnection(&m_server, socket);
|
||||
connect(socket, &QLocalSocket::disconnected, connection,
|
||||
&DaemonLocalServerConnection::deleteLater);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QString DaemonLocalServer::daemonPath() const {
|
||||
#if defined(MZ_WINDOWS)
|
||||
return "\\\\.\\pipe\\amneziavpn";
|
||||
#endif
|
||||
#if defined(MZ_MACOS) || defined(MZ_LINUX)
|
||||
QDir dir("/var/run");
|
||||
if (!dir.exists()) {
|
||||
logger.warning() << "/var/run doesn't exist. Fallback /tmp.";
|
||||
return TMP_PATH;
|
||||
}
|
||||
|
||||
if (dir.exists("amneziavpn")) {
|
||||
logger.debug() << "/var/run/amneziavpn seems to be usable";
|
||||
return VAR_PATH;
|
||||
}
|
||||
|
||||
if (!dir.mkdir("amneziavpn")) {
|
||||
logger.warning() << "Failed to create /var/run/amneziavpn";
|
||||
return TMP_PATH;
|
||||
}
|
||||
|
||||
if (chmod("/var/run/amneziavpn", S_IRWXU | S_IRWXG | S_IRWXO) < 0) {
|
||||
logger.warning()
|
||||
<< "Failed to set the right permissions to /var/run/amneziavpn";
|
||||
return TMP_PATH;
|
||||
}
|
||||
|
||||
return VAR_PATH;
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef DAEMONLOCALSERVER_H
|
||||
#define DAEMONLOCALSERVER_H
|
||||
|
||||
#include <QLocalServer>
|
||||
|
||||
class DaemonLocalServer final : public QObject {
|
||||
Q_DISABLE_COPY_MOVE(DaemonLocalServer)
|
||||
|
||||
public:
|
||||
explicit DaemonLocalServer(QObject* parent);
|
||||
~DaemonLocalServer();
|
||||
|
||||
bool initialize();
|
||||
|
||||
private:
|
||||
QString daemonPath() const;
|
||||
|
||||
private:
|
||||
QLocalServer m_server;
|
||||
};
|
||||
|
||||
#endif // DAEMONLOCALSERVER_H
|
||||
@@ -0,0 +1,172 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "daemonlocalserverconnection.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
#include <QLocalSocket>
|
||||
|
||||
#include "daemon.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
|
||||
namespace {
|
||||
Logger logger("DaemonLocalServerConnection");
|
||||
}
|
||||
|
||||
DaemonLocalServerConnection::DaemonLocalServerConnection(QObject* parent,
|
||||
QLocalSocket* socket)
|
||||
: QObject(parent) {
|
||||
MZ_COUNT_CTOR(DaemonLocalServerConnection);
|
||||
|
||||
logger.debug() << "Connection created";
|
||||
|
||||
Q_ASSERT(socket);
|
||||
m_socket = socket;
|
||||
|
||||
connect(m_socket, &QLocalSocket::readyRead, this,
|
||||
&DaemonLocalServerConnection::readData);
|
||||
|
||||
Daemon* daemon = Daemon::instance();
|
||||
connect(daemon, &Daemon::connected, this,
|
||||
&DaemonLocalServerConnection::connected);
|
||||
connect(daemon, &Daemon::disconnected, this,
|
||||
&DaemonLocalServerConnection::disconnected);
|
||||
connect(daemon, &Daemon::backendFailure, this,
|
||||
&DaemonLocalServerConnection::backendFailure);
|
||||
}
|
||||
|
||||
DaemonLocalServerConnection::~DaemonLocalServerConnection() {
|
||||
MZ_COUNT_DTOR(DaemonLocalServerConnection);
|
||||
|
||||
logger.debug() << "Connection released";
|
||||
}
|
||||
|
||||
void DaemonLocalServerConnection::readData() {
|
||||
logger.debug() << "Read Data";
|
||||
|
||||
Q_ASSERT(m_socket);
|
||||
|
||||
while (true) {
|
||||
int pos = m_buffer.indexOf("\n");
|
||||
if (pos == -1) {
|
||||
QByteArray input = m_socket->readAll();
|
||||
if (input.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
m_buffer.append(input);
|
||||
continue;
|
||||
}
|
||||
|
||||
QByteArray line = m_buffer.left(pos);
|
||||
m_buffer.remove(0, pos + 1);
|
||||
|
||||
QByteArray command(line);
|
||||
command = command.trimmed();
|
||||
|
||||
if (command.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
parseCommand(command);
|
||||
}
|
||||
}
|
||||
|
||||
void DaemonLocalServerConnection::parseCommand(const QByteArray& data) {
|
||||
QJsonDocument json = QJsonDocument::fromJson(data);
|
||||
if (!json.isObject()) {
|
||||
logger.error() << "Invalid input";
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject obj = json.object();
|
||||
QJsonValue typeValue = obj.value("type");
|
||||
if (!typeValue.isString()) {
|
||||
logger.warning() << "No type command. Ignoring request.";
|
||||
return;
|
||||
}
|
||||
QString type = typeValue.toString();
|
||||
|
||||
logger.debug() << "Command received:" << type;
|
||||
|
||||
// It is expected that sometimes the client will request backend logs
|
||||
// before the first authentication. In these cases we just return empty
|
||||
// logs.
|
||||
if (type == "logs") {
|
||||
QJsonObject obj;
|
||||
obj.insert("type", "logs");
|
||||
obj.insert("logs", "");
|
||||
write(obj);
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == "activate") {
|
||||
InterfaceConfig config;
|
||||
if (!Daemon::parseConfig(obj, config)) {
|
||||
logger.error() << "Invalid configuration";
|
||||
emit disconnected();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Daemon::instance()->activate(config)) {
|
||||
logger.error() << "Failed to activate the interface";
|
||||
emit disconnected();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == "deactivate") {
|
||||
Daemon::instance()->deactivate(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == "status") {
|
||||
QJsonObject obj = Daemon::instance()->getStatus();
|
||||
obj.insert("type", "status");
|
||||
write(obj);
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == "logs") {
|
||||
QJsonObject obj;
|
||||
obj.insert("type", "logs");
|
||||
obj.insert("logs", Daemon::instance()->logs().replace("\n", "|"));
|
||||
write(obj);
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == "cleanlogs") {
|
||||
Daemon::instance()->cleanLogs();
|
||||
return;
|
||||
}
|
||||
|
||||
logger.warning() << "Invalid command:" << type;
|
||||
}
|
||||
|
||||
void DaemonLocalServerConnection::connected(const QString& pubkey) {
|
||||
QJsonObject obj;
|
||||
obj.insert("type", "connected");
|
||||
obj.insert("pubkey", QJsonValue(pubkey));
|
||||
write(obj);
|
||||
}
|
||||
|
||||
void DaemonLocalServerConnection::disconnected() {
|
||||
QJsonObject obj;
|
||||
obj.insert("type", "disconnected");
|
||||
write(obj);
|
||||
}
|
||||
|
||||
void DaemonLocalServerConnection::backendFailure(DaemonError err) {
|
||||
QJsonObject obj;
|
||||
obj.insert("type", "backendFailure");
|
||||
obj.insert("errorCode", static_cast<int>(err));
|
||||
write(obj);
|
||||
}
|
||||
|
||||
void DaemonLocalServerConnection::write(const QJsonObject& obj) {
|
||||
m_socket->write(QJsonDocument(obj).toJson(QJsonDocument::Compact));
|
||||
m_socket->write("\n");
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef DAEMONLOCALSERVERCONNECTION_H
|
||||
#define DAEMONLOCALSERVERCONNECTION_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "daemonerrors.h"
|
||||
|
||||
class QLocalSocket;
|
||||
|
||||
class DaemonLocalServerConnection final : public QObject {
|
||||
Q_DISABLE_COPY_MOVE(DaemonLocalServerConnection)
|
||||
|
||||
public:
|
||||
DaemonLocalServerConnection(QObject* parent, QLocalSocket* socket);
|
||||
~DaemonLocalServerConnection();
|
||||
|
||||
private:
|
||||
void readData();
|
||||
|
||||
void parseCommand(const QByteArray& json);
|
||||
|
||||
void connected(const QString& pubkey);
|
||||
void disconnected();
|
||||
void backendFailure(DaemonError err);
|
||||
|
||||
void write(const QJsonObject& obj);
|
||||
|
||||
private:
|
||||
QLocalSocket* m_socket = nullptr;
|
||||
|
||||
QByteArray m_buffer;
|
||||
};
|
||||
|
||||
#endif // DAEMONLOCALSERVERCONNECTION_H
|
||||
@@ -0,0 +1,34 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef DNSUTILS_H
|
||||
#define DNSUTILS_H
|
||||
|
||||
#include <QHostAddress>
|
||||
#include <QString>
|
||||
|
||||
#include "dnsutils.h"
|
||||
|
||||
class DnsUtils : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DnsUtils(QObject* parent) : QObject(parent){};
|
||||
virtual ~DnsUtils() = default;
|
||||
|
||||
virtual bool updateResolvers(const QString& ifname,
|
||||
const QList<QHostAddress>& resolvers) {
|
||||
Q_UNUSED(ifname);
|
||||
Q_UNUSED(resolvers);
|
||||
qFatal("Have you forgotten to implement DnsUtils::updateResolvers?");
|
||||
return false;
|
||||
};
|
||||
|
||||
virtual bool restoreResolvers() {
|
||||
qFatal("Have you forgotten to implement DnsUtils::restoreResolvers?");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // DNSUTILS_H
|
||||
@@ -0,0 +1,224 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "interfaceconfig.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
#include <QMetaEnum>
|
||||
|
||||
QJsonObject InterfaceConfig::toJson() const {
|
||||
QJsonObject json;
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<HopType>();
|
||||
|
||||
json.insert("hopType", QJsonValue(metaEnum.valueToKey(m_hopType)));
|
||||
json.insert("privateKey", QJsonValue(m_privateKey));
|
||||
json.insert("deviceIpv4Address", QJsonValue(m_deviceIpv4Address));
|
||||
json.insert("deviceIpv6Address", QJsonValue(m_deviceIpv6Address));
|
||||
json.insert("serverPublicKey", QJsonValue(m_serverPublicKey));
|
||||
json.insert("serverPskKey", QJsonValue(m_serverPskKey));
|
||||
json.insert("serverIpv4AddrIn", QJsonValue(m_serverIpv4AddrIn));
|
||||
json.insert("serverIpv6AddrIn", QJsonValue(m_serverIpv6AddrIn));
|
||||
json.insert("serverPort", QJsonValue((double)m_serverPort));
|
||||
json.insert("deviceMTU", QJsonValue(m_deviceMTU));
|
||||
if (!m_persistentKeepalive.isEmpty()) {
|
||||
json.insert("persistentKeepalive", QJsonValue(m_persistentKeepalive));
|
||||
}
|
||||
if ((m_hopType == InterfaceConfig::MultiHopExit) ||
|
||||
(m_hopType == InterfaceConfig::SingleHop)) {
|
||||
json.insert("serverIpv4Gateway", QJsonValue(m_serverIpv4Gateway));
|
||||
json.insert("serverIpv6Gateway", QJsonValue(m_serverIpv6Gateway));
|
||||
json.insert("primaryDnsServer", QJsonValue(m_primaryDnsServer));
|
||||
json.insert("secondaryDnsServer", QJsonValue(m_secondaryDnsServer));
|
||||
}
|
||||
|
||||
QJsonArray allowedIPAddesses;
|
||||
for (const IPAddress& i : m_allowedIPAddressRanges) {
|
||||
QJsonObject range;
|
||||
range.insert("address", QJsonValue(i.address().toString()));
|
||||
range.insert("range", QJsonValue((double)i.prefixLength()));
|
||||
range.insert("isIpv6",
|
||||
QJsonValue(i.type() == QAbstractSocket::IPv6Protocol));
|
||||
allowedIPAddesses.append(range);
|
||||
};
|
||||
json.insert("allowedIPAddressRanges", allowedIPAddesses);
|
||||
|
||||
QJsonArray jsExcludedAddresses;
|
||||
for (const QString& i : m_excludedAddresses) {
|
||||
jsExcludedAddresses.append(QJsonValue(i));
|
||||
}
|
||||
json.insert("excludedAddresses", jsExcludedAddresses);
|
||||
|
||||
|
||||
QJsonArray jsAllowedDnsServers;
|
||||
for (const QString& i : m_allowedDnsServers) {
|
||||
jsAllowedDnsServers.append(QJsonValue(i));
|
||||
}
|
||||
json.insert("allowedDnsServers", jsAllowedDnsServers);
|
||||
|
||||
QJsonArray disabledApps;
|
||||
for (const QString& i : m_vpnDisabledApps) {
|
||||
disabledApps.append(QJsonValue(i));
|
||||
}
|
||||
json.insert("vpnDisabledApps", disabledApps);
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
QString InterfaceConfig::toWgConf(const QMap<QString, QString>& extra) const {
|
||||
#define VALIDATE(x) \
|
||||
if (x.contains("\n")) return "";
|
||||
|
||||
VALIDATE(m_privateKey);
|
||||
VALIDATE(m_deviceIpv4Address);
|
||||
VALIDATE(m_deviceIpv6Address);
|
||||
VALIDATE(m_serverIpv4Gateway);
|
||||
VALIDATE(m_serverIpv6Gateway);
|
||||
VALIDATE(m_serverPublicKey);
|
||||
VALIDATE(m_serverIpv4AddrIn);
|
||||
VALIDATE(m_serverIpv6AddrIn);
|
||||
#undef VALIDATE
|
||||
|
||||
QString content;
|
||||
QTextStream out(&content);
|
||||
out << "[Interface]\n";
|
||||
out << "PrivateKey = " << m_privateKey << "\n";
|
||||
|
||||
QStringList addresses;
|
||||
if (!m_deviceIpv4Address.isNull()) {
|
||||
addresses.append(m_deviceIpv4Address);
|
||||
}
|
||||
if (!m_deviceIpv6Address.isNull()) {
|
||||
addresses.append(m_deviceIpv6Address);
|
||||
}
|
||||
if (addresses.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
out << "Address = " << addresses.join(", ") << "\n";
|
||||
|
||||
if (m_deviceMTU) {
|
||||
out << "MTU = " << m_deviceMTU << "\n";
|
||||
}
|
||||
|
||||
if (!m_primaryDnsServer.isEmpty()) {
|
||||
QStringList dnsServers;
|
||||
dnsServers.append(m_primaryDnsServer);
|
||||
if (!m_secondaryDnsServer.isEmpty()) {
|
||||
dnsServers.append(m_secondaryDnsServer);
|
||||
}
|
||||
// If the DNS is not the Gateway, it's a user defined DNS
|
||||
// thus, not add any other :)
|
||||
if (m_primaryDnsServer == m_serverIpv4Gateway) {
|
||||
dnsServers.append(m_serverIpv6Gateway);
|
||||
}
|
||||
out << "DNS = " << dnsServers.join(", ") << "\n";
|
||||
}
|
||||
|
||||
if (!m_junkPacketCount.isEmpty()) {
|
||||
out << "Jc = " << m_junkPacketCount << "\n";
|
||||
}
|
||||
if (!m_junkPacketMinSize.isEmpty()) {
|
||||
out << "JMin = " << m_junkPacketMinSize << "\n";
|
||||
}
|
||||
if (!m_junkPacketMaxSize.isEmpty()) {
|
||||
out << "JMax = " << m_junkPacketMaxSize << "\n";
|
||||
}
|
||||
if (!m_initPacketJunkSize.isEmpty()) {
|
||||
out << "S1 = " << m_initPacketJunkSize << "\n";
|
||||
}
|
||||
if (!m_responsePacketJunkSize.isEmpty()) {
|
||||
out << "S2 = " << m_responsePacketJunkSize << "\n";
|
||||
}
|
||||
if (!m_cookieReplyPacketJunkSize.isEmpty()) {
|
||||
out << "S3 = " << m_cookieReplyPacketJunkSize << "\n";
|
||||
}
|
||||
if (!m_transportPacketJunkSize.isEmpty()) {
|
||||
out << "S4 = " << m_transportPacketJunkSize << "\n";
|
||||
}
|
||||
if (!m_initPacketMagicHeader.isEmpty()) {
|
||||
out << "H1 = " << m_initPacketMagicHeader << "\n";
|
||||
}
|
||||
if (!m_responsePacketMagicHeader.isEmpty()) {
|
||||
out << "H2 = " << m_responsePacketMagicHeader << "\n";
|
||||
}
|
||||
if (!m_underloadPacketMagicHeader.isEmpty()) {
|
||||
out << "H3 = " << m_underloadPacketMagicHeader << "\n";
|
||||
}
|
||||
if (!m_transportPacketMagicHeader.isEmpty()) {
|
||||
out << "H4 = " << m_transportPacketMagicHeader << "\n";
|
||||
}
|
||||
|
||||
for (const QString& key : m_specialJunk.keys()) {
|
||||
if (!m_specialJunk[key].isEmpty()) {
|
||||
out << key << " = " << m_specialJunk[key] << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_headerProtectionKey.isEmpty()) {
|
||||
out << "HeaderProtectionKey = " << m_headerProtectionKey << "\n";
|
||||
}
|
||||
if (!m_contentPaddingAddition.isEmpty()) {
|
||||
out << "ContentPaddingAddition = " << m_contentPaddingAddition << "\n";
|
||||
}
|
||||
if (!m_rekeyAfterTime.isEmpty()) {
|
||||
out << "RekeyAfterTime = " << m_rekeyAfterTime << "\n";
|
||||
}
|
||||
if (!m_rekeyTimeout.isEmpty()) {
|
||||
out << "RekeyTimeout = " << m_rekeyTimeout << "\n";
|
||||
}
|
||||
if (!m_rejectAfterTime.isEmpty()) {
|
||||
out << "RejectAfterTime = " << m_rejectAfterTime << "\n";
|
||||
}
|
||||
if (!m_keepaliveTimeout.isEmpty()) {
|
||||
out << "KeepaliveTimeout = " << m_keepaliveTimeout << "\n";
|
||||
}
|
||||
if (!m_maxHandshakeAttempts.isEmpty()) {
|
||||
out << "MaxHandshakeAttempts = " << m_maxHandshakeAttempts << "\n";
|
||||
}
|
||||
if (!m_randomTrailers.isEmpty()) {
|
||||
out << "RandomTrailers = " << m_randomTrailers << "\n";
|
||||
}
|
||||
if (!m_disableCookies.isEmpty()) {
|
||||
out << "DisableCookies = " << m_disableCookies << "\n";
|
||||
}
|
||||
|
||||
// If any extra config was provided, append it now.
|
||||
for (const QString& key : extra.keys()) {
|
||||
out << key << " = " << extra[key] << "\n";
|
||||
}
|
||||
|
||||
out << "\n[Peer]\n";
|
||||
out << "PublicKey = " << m_serverPublicKey << "\n";
|
||||
out << "Endpoint = " << m_serverIpv4AddrIn.toUtf8() << ":" << m_serverPort
|
||||
<< "\n";
|
||||
|
||||
/* In theory, we should use the ipv6 endpoint, but wireguard doesn't seem
|
||||
* to be happy if there are 2 endpoints.
|
||||
out << "Endpoint = [" << config.m_serverIpv6AddrIn << "]:"
|
||||
<< config.m_serverPort << "\n";
|
||||
*/
|
||||
QStringList ranges;
|
||||
for (const IPAddress& ip : m_allowedIPAddressRanges) {
|
||||
ranges.append(ip.toString());
|
||||
}
|
||||
out << "AllowedIPs = " << ranges.join(", ") << "\n";
|
||||
if (!m_persistentKeepalive.isEmpty()) {
|
||||
out << "PersistentKeepalive = " << m_persistentKeepalive << "\n";
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
QString InterfaceConfig::awgBoolToUapi(const QString& value) {
|
||||
const QString v = value.trimmed().toLower();
|
||||
if (v == QLatin1String("on") || v == QLatin1String("1") ||
|
||||
v == QLatin1String("true") || v == QLatin1String("t") ||
|
||||
v == QLatin1String("yes")) {
|
||||
return QStringLiteral("1");
|
||||
}
|
||||
return QStringLiteral("0");
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef INTERFACECONFIG_H
|
||||
#define INTERFACECONFIG_H
|
||||
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
#include <QMap>
|
||||
#include "ipaddress.h"
|
||||
|
||||
class QJsonObject;
|
||||
|
||||
class InterfaceConfig {
|
||||
Q_GADGET
|
||||
|
||||
public:
|
||||
InterfaceConfig() {}
|
||||
|
||||
enum HopType { SingleHop, MultiHopEntry, MultiHopExit };
|
||||
Q_ENUM(HopType)
|
||||
|
||||
HopType m_hopType;
|
||||
QString m_privateKey;
|
||||
QString m_deviceIpv4Address;
|
||||
QString m_deviceIpv6Address;
|
||||
QString m_serverIpv4Gateway;
|
||||
QString m_serverIpv6Gateway;
|
||||
QString m_serverPublicKey;
|
||||
QString m_serverIpv4AddrIn;
|
||||
QString m_serverPskKey;
|
||||
QString m_serverIpv6AddrIn;
|
||||
QString m_primaryDnsServer;
|
||||
QString m_secondaryDnsServer;
|
||||
int m_serverPort = 0;
|
||||
int m_deviceMTU = 1420;
|
||||
QString m_persistentKeepalive;
|
||||
QList<IPAddress> m_allowedIPAddressRanges;
|
||||
QStringList m_excludedAddresses;
|
||||
QStringList m_vpnDisabledApps;
|
||||
QStringList m_allowedDnsServers;
|
||||
bool m_killSwitchEnabled;
|
||||
#if defined(MZ_ANDROID) || defined(MZ_IOS)
|
||||
QString m_installationId;
|
||||
#endif
|
||||
|
||||
QString m_junkPacketCount;
|
||||
QString m_junkPacketMinSize;
|
||||
QString m_junkPacketMaxSize;
|
||||
QString m_initPacketJunkSize;
|
||||
QString m_responsePacketJunkSize;
|
||||
QString m_cookieReplyPacketJunkSize;
|
||||
QString m_transportPacketJunkSize;
|
||||
QString m_initPacketMagicHeader;
|
||||
QString m_responsePacketMagicHeader;
|
||||
QString m_underloadPacketMagicHeader;
|
||||
QString m_transportPacketMagicHeader;
|
||||
QMap<QString, QString> m_specialJunk;
|
||||
|
||||
QString m_headerProtectionKey;
|
||||
QString m_contentPaddingAddition;
|
||||
QString m_rekeyAfterTime;
|
||||
QString m_rekeyTimeout;
|
||||
QString m_rejectAfterTime;
|
||||
QString m_keepaliveTimeout;
|
||||
QString m_maxHandshakeAttempts;
|
||||
QString m_randomTrailers;
|
||||
QString m_disableCookies;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
QString toWgConf(
|
||||
const QMap<QString, QString>& extra = QMap<QString, QString>()) const;
|
||||
|
||||
// Converts awg-quick on/off (and 0/1/true/false) to UAPI 1/0.
|
||||
// amneziawg-go uses strconv.ParseBool and rejects "on"/"off".
|
||||
static QString awgBoolToUapi(const QString& value);
|
||||
};
|
||||
|
||||
#endif // INTERFACECONFIG_H
|
||||
@@ -0,0 +1,31 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef IPUTILS_H
|
||||
#define IPUTILS_H
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QObject>
|
||||
|
||||
#include "interfaceconfig.h"
|
||||
|
||||
class IPUtils : public QObject {
|
||||
public:
|
||||
explicit IPUtils(QObject* parent) : QObject(parent){};
|
||||
virtual ~IPUtils() = default;
|
||||
|
||||
virtual bool addInterfaceIPs(const InterfaceConfig& config) {
|
||||
Q_UNUSED(config);
|
||||
qFatal("Have you forgotten to implement IPUtils::addInterfaceIPs?");
|
||||
return false;
|
||||
};
|
||||
|
||||
virtual bool setMTUAndUp(const InterfaceConfig& config) {
|
||||
Q_UNUSED(config);
|
||||
qFatal("Have you forgotten to implement IPUtils::setMTUAndUp?");
|
||||
return false;
|
||||
};
|
||||
};
|
||||
|
||||
#endif // IPUTILS_H
|
||||
@@ -0,0 +1,53 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef WIREGUARDUTILS_H
|
||||
#define WIREGUARDUTILS_H
|
||||
|
||||
#define _WINSOCKAPI_
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QHostAddress>
|
||||
#include <QObject>
|
||||
#include <QStringList>
|
||||
|
||||
#include "interfaceconfig.h"
|
||||
|
||||
constexpr const char* WG_INTERFACE = "amn0";
|
||||
|
||||
class WireguardUtils : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
class PeerStatus {
|
||||
public:
|
||||
PeerStatus(const QString& pubkey = QString()) { m_pubkey = pubkey; }
|
||||
QString m_pubkey;
|
||||
qint64 m_handshake = 0;
|
||||
qint64 m_rxBytes = 0;
|
||||
qint64 m_txBytes = 0;
|
||||
};
|
||||
|
||||
explicit WireguardUtils(QObject* parent) : QObject(parent){};
|
||||
virtual ~WireguardUtils() = default;
|
||||
|
||||
virtual bool interfaceExists() = 0;
|
||||
virtual QString interfaceName() { return WG_INTERFACE; }
|
||||
virtual bool addInterface(const InterfaceConfig& config) = 0;
|
||||
virtual bool deleteInterface() = 0;
|
||||
|
||||
virtual bool updatePeer(const InterfaceConfig& config) = 0;
|
||||
virtual bool deletePeer(const InterfaceConfig& config) = 0;
|
||||
virtual QList<PeerStatus> getPeerStatus() = 0;
|
||||
|
||||
virtual bool updateRoutePrefix(const IPAddress& prefix) = 0;
|
||||
virtual bool deleteRoutePrefix(const IPAddress& prefix) = 0;
|
||||
|
||||
virtual bool addExclusionRoute(const IPAddress& prefix) = 0;
|
||||
virtual bool deleteExclusionRoute(const IPAddress& prefix) = 0;
|
||||
|
||||
virtual bool excludeLocalNetworks(const QList<IPAddress>& addresses) = 0;
|
||||
};
|
||||
|
||||
#endif // WIREGUARDUTILS_H
|
||||
@@ -1,35 +0,0 @@
|
||||
<svg width="1024" height="1024" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg x="94.969" y="72.212" width="840" height="809.053" viewBox="0 0 840 809.053" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<mask id="mask0_5470_4284" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="841" height="810">
|
||||
<path d="M838.697 289.781C839.128 290.057 839.092 290.7 838.633 290.929C835.038 292.719 831.485 294.6 827.806 296.232C799.555 308.776 772.521 323.383 749.551 344.372C741.699 351.545 734.598 359.689 727.949 368.025C721.643 375.923 719.536 385.597 719.631 395.64C719.651 397.39 719.936 399.14 720.232 401.108C720.434 402.455 721.585 403.47 722.947 403.47H754.934C756.426 403.47 757.641 404.682 757.573 406.173C757.006 418.719 752.651 429.513 745.064 438.533C734.995 450.489 730.891 464.7 726.144 478.966C719.221 499.763 718.756 521.202 720.398 542.478C722.108 564.655 713.899 582.337 700.656 598.83C690.259 611.783 680.19 625.011 669.847 638.019C659.244 651.355 655.291 666.414 656.426 683.344C657.89 705.043 662.979 725.963 667.727 747.088C671.598 764.332 672.871 782.151 675.21 799.723C675.317 800.514 675.272 801.319 675.174 802.406C675.006 804.292 672.993 805.496 671.237 804.786C667.425 803.244 663.961 801.983 661.064 799.914C647.862 790.5 639.736 777.081 633.087 762.665C629.836 755.612 626.686 748.509 623.528 741.499C623.236 740.851 622.7 740.346 622.038 740.086C612.637 736.39 610.069 734.129 607.429 727.667C607.012 726.646 606.042 725.945 604.94 725.931C590.847 725.747 576.823 727.121 563.328 731.798C539.442 740.065 515.514 748.249 491.969 757.391C465.73 767.585 438.573 769.648 411.239 766.355C385.068 763.198 359.197 757.514 333.176 753.1C324.448 751.625 315.61 750.75 306.841 749.425C304.666 749.097 302.504 748.249 300.507 747.279C284.746 739.6 269.178 731.511 253.267 724.159C236.015 716.193 222.69 721.877 216.301 739.559C213.675 746.828 212.115 754.467 209.57 761.764C205.685 772.9 201.622 784.009 197.148 794.913C193.962 802.692 187.887 806.658 178.915 807.795C177.278 808.003 175.934 806.595 176.072 804.951C176.687 797.655 177.055 790.615 178.241 783.722C180.676 769.607 184.781 755.724 186.272 741.527C187.749 727.343 187.024 712.913 186.86 698.607C186.833 696.53 184.918 694.125 183.289 692.458C171.62 680.597 159.95 668.736 147.993 657.176C135.338 644.933 123.039 632.28 114.585 616.812C106.896 602.751 94.8842 600.743 81.5728 603.339C60.1486 607.52 38.9845 612.959 17.7108 617.905C12.3942 619.143 7.11331 620.525 1.82366 621.848C1.1673 622.013 0.484093 621.706 0.169055 621.108C-0.123688 620.551 -0.0299188 619.87 0.401333 619.413C2.93984 616.719 5.31074 614.184 8.16162 611.811C24.2639 598.42 40.7356 585.452 56.6464 571.842C66.1408 563.712 74.9923 554.803 83.7754 545.894C85.5402 544.104 86.6484 540.674 86.4705 538.105C82.0653 474.457 96.8679 414.73 123.327 357.407C134.518 333.152 146.142 309.094 157.878 284.322C158.345 283.335 159.369 282.751 160.46 282.687C170.461 282.106 171.998 271.978 175.957 264.599C178.669 259.543 180.643 254.095 182.891 248.386C183.584 246.625 185.831 246.144 187.204 247.447C188.931 249.086 190.564 250.638 192.338 252.327C193.394 253.332 195.059 253.317 196.13 252.327C199.595 249.128 203.201 246.202 205.753 242.545C215.658 228.389 225.399 214.096 234.415 199.379C238.574 192.588 243.198 188.325 251.16 186.097C277.787 178.649 304.16 170.335 330.424 162.392C331.068 162.197 331.64 161.784 332.011 161.223C332.138 161.03 332.228 160.859 332.246 160.682C334.189 145.022 335.461 143.71 351.303 141.114C368.172 138.354 378.802 129.336 381.647 112.282C384.479 95.2702 385.314 77.9165 386.969 60.6994C388.351 46.2015 389.377 31.6626 391.087 17.2057C392.145 8.24311 394.608 3.97571 400.598 0.355299C401.894 -0.42824 403.557 0.142784 404.243 1.49332C407.282 7.4772 410.711 13.3289 412.744 19.6243C418.257 36.7184 422.635 54.1678 427.998 71.3166C432.608 86.0468 437.465 100.736 443.047 115.125C450.517 134.405 465.798 142.576 485.854 142.918C493.611 143.055 501.368 143.328 509.111 142.986C518.592 142.576 527.403 144.653 534.995 150.092C555.53 164.822 576.257 179.32 596.012 195.047C610.924 206.928 624.546 220.435 639.396 233.918C640.083 234.542 641.043 234.751 641.954 234.576C649.443 233.136 656.045 237.059 662.73 241.666C663.822 242.419 664.193 243.86 663.612 245.053C661.428 249.534 659.268 253.957 656.99 258.64C656.297 260.065 656.98 261.773 658.432 262.408C673.511 269.004 684.03 280.872 695.731 291.217C699.822 294.825 704.213 298.118 708.55 301.438C716.718 307.697 725.76 308.995 735.693 306.494C754.901 301.657 774.095 296.751 793.439 292.556C808.443 289.3 822.647 287.5 838.432 289.683C838.526 289.696 838.617 289.729 838.697 289.781ZM432.82 707.072C432.825 707.068 432.831 707.065 432.837 707.065C448.553 707.065 464.269 707.243 479.985 706.956C484.185 706.874 488.809 706.259 492.489 704.401C512.19 694.466 532.013 684.724 551.07 673.656C570.73 662.246 572.385 656.575 566.557 634.343C561.304 614.325 551.604 596.22 541.343 578.511C529.208 557.55 519.741 536.083 519.139 511.268C518.825 498.246 515.076 485.757 508.879 474.074C503.393 463.73 494.897 458.838 483.446 458.688C451.775 458.278 420.761 462.063 390.595 472.27C312.614 498.683 265.114 562.824 263.801 642.911C263.486 662.232 271.859 675.35 288.755 683.002C300.534 688.345 312.806 692.786 325.173 696.585C360.261 707.365 396.485 708.363 432.804 707.079C432.81 707.079 432.Line truncated
|
||||
</mask>
|
||||
<g mask="url(#mask0_5470_4284)">
|
||||
<path d="M313.469 658.228C312.707 668.769 309.913 679.69 311.564 689.723C315.755 715.122 296.832 713.09 282.736 714.487C267.37 716.011 254.543 731.378 237.526 722.615C238.288 721.599 238.923 720.71 239.685 719.694C259.623 705.979 270.417 681.087 296.832 674.357C301.912 673.214 301.658 657.974 313.469 658.228Z" fill="#FEDAA5"/>
|
||||
<path d="M-8.20716 410.326C-8.20716 267.947 -8.20716 125.569 -8.32803 -16.8094C-8.32803 -20.9187 -7.48198 -21.7648 -3.37258 -21.7648C281.747 -21.6439 566.987 -21.6439 852.106 -21.7648C856.216 -21.7648 857.062 -20.9187 857.062 -16.8094C856.941 268.31 856.941 553.55 857.062 838.669C857.062 842.779 856.216 843.625 852.106 843.625C566.987 843.504 281.747 843.504 -3.37258 843.625C-7.48198 843.625 -8.32803 842.779 -8.32803 838.669C-8.20716 695.808 -8.20716 553.067 -8.20716 410.326ZM-0.350972 449.607C-0.350972 573.493 -0.471837 697.379 -0.471837 821.144C1.94545 825.858 -6.87765 835.89 4.72534 835.89C258.057 836.131 511.389 836.131 764.721 836.01C766.897 836.01 771.127 832.989 770.885 832.264C766.413 812.563 778.742 813.892 791.674 814.497C805.694 815.222 819.835 814.376 833.977 814.738C839.053 814.859 840.262 813.409 840.262 808.332C840.02 751.043 840.141 693.874 840.141 636.584C840.02 421.929 840.02 207.273 839.899 -7.38192C838.932 -8.22797 838.086 -9.67835 837.119 -9.67835C811.133 -9.67835 785.268 -9.67835 759.282 -9.31575C757.953 -9.31575 755.415 -5.20636 755.415 -3.15166C755.173 69.7296 755.415 142.49 756.382 215.25C756.624 232.655 747.075 239.544 733.418 243.654C728.825 244.862 724.715 245.467 720.122 246.675C702.597 246.675 684.467 241.236 677.095 267.343C672.381 283.66 657.152 296.955 646.637 311.579C641.681 315.205 637.209 319.798 631.77 322.336C606.751 334.423 583.062 315.568 556.351 319.315C526.86 323.424 507.486 331.521 506.519 362.583C506.519 364.033 521.526 393.84 493.742 376.753C492.533 376.028 490.842 376.725 489.271 376.725C469.087 363.309 444.914 377.088 424.246 366.935C423.4 365.364 422.554 363.793 421.587 362.222C421.708 354.124 422.433 346.026 421.708 338.049C420.62 326.808 422.191 320.403 436.212 322.82C444.551 324.27 458.088 327.05 461.11 317.864C472.108 284.627 488.667 254.29 502.929 222.744C511.148 204.494 492.172 186.968 489.875 168.355C487.579 150.467 472.834 135.238 478.514 116.142C486.491 89.7931 505.467 69.9714 520.817 47.974C522.388 46.4028 523.959 44.8315 525.651 43.1394C538.826 23.5594 554.175 6.63836 577.865 -0.371785C581.491 -2.30562 589.468 1.19945 588.259 -6.41501C587.292 -12.9417 580.403 -9.55748 576.535 -9.55748C391.371 -9.43662 206.206 -9.07402 20.9212 -10.1618C-0.713563 -10.2827 -4.09777 -2.78907 -0.592699 14.7363C-0.592699 154.818 -0.592699 295.021 -0.592699 435.103C-0.471835 439.938 -0.350972 444.772 -0.350972 449.607ZM640.352 95.8363C640.11 95.8363 639.868 95.8363 639.506 95.8363C639.506 64.049 639.506 32.2616 639.506 0.474274C639.506 -3.0308 641.077 -7.50278 634.429 -7.38192C627.782 -7.26105 629.716 -2.54734 629.716 0.715998C629.232 62.7195 628.87 124.723 628.386 186.726C628.386 193.616 631.287 198.088 637.572 200.505C638.055 200.747 640.231 198.692 640.231 197.846C640.352 163.762 640.352 129.799 640.352 95.8363Z" fill="#FCE8EC"/>
|
||||
<path d="M839.683 -7.14046C839.803 207.515 839.803 422.17 839.924 636.825C839.924 694.115 839.924 751.284 840.045 808.574C840.045 813.529 838.836 815.101 833.76 814.98C819.619 814.617 805.599 815.342 791.458 814.738C778.404 814.134 766.076 812.804 770.669 832.505C770.911 833.351 766.68 836.252 764.505 836.252C511.173 836.373 257.841 836.373 4.50893 836.131C-7.09406 836.131 1.60818 826.099 -0.688248 821.386C0.641262 820.056 1.97077 818.727 3.17942 817.397C16.3536 802.772 29.5279 788.148 42.5812 773.523C89.3558 772.798 136.251 772.556 183.026 770.381C188.465 770.139 189.673 787.664 196.321 772.919C198.617 767.843 202.848 763.733 206.111 759.14C205.386 762.524 202.968 766.876 204.177 769.172C207.803 775.82 210.583 768.326 212.396 766.996C224.12 757.569 234.151 768.205 243.941 770.622C250.347 772.194 254.578 783.313 259.412 790.323C263.642 796.487 267.268 803.135 271.136 809.541C271.378 804.223 271.861 798.905 271.982 793.708C272.103 788.873 271.499 784.038 272.103 779.204C274.037 763.975 270.773 749.713 290.233 738.231C305.824 729.045 329.151 716.959 324.921 689.039C323.108 677.194 324.921 664.866 325.162 652.659C337.249 643.956 349.456 635.133 361.543 626.431C378.584 611.806 398.285 602.379 419.558 595.852C420.162 595.852 420.766 595.852 421.371 595.852C422.337 595.49 423.425 595.006 424.392 594.644C448.323 597.061 445.543 577.36 449.774 563.702C480.594 551.979 515.645 574.58 545.257 551.616C553.475 545.21 564.474 541.826 571.122 534.453C582.241 522.125 593.844 512.818 610.886 509.918C620.313 508.346 635.542 514.511 638.201 502.424C640.981 489.854 636.026 475.713 634.455 462.176C640.256 454.924 645.937 447.793 651.738 440.542C656.573 425.554 666.363 437.036 673.615 436.795C697.908 435.707 701.776 413.468 713.137 399.448C734.047 373.583 746.617 342.762 756.89 311.821C766.68 282.693 757.495 251.872 757.495 221.656C757.616 147.566 757.495 73.597 757.615 -0.492919C757.615 -2.42675 758.582 -4.48145 759.066 -6.41528C760.516 -5.69009 761.967 -5.08577 763.296 -4.36058C762.329 -3.99798 761.241 -3.6354 760.275 -3.27281C760.637 -2.91021 760.879 -2.54762 761.241 -2.06416C761.846 -2.78935 762.571 -3.51453 763.175 -4.36058C788.919 -5.56922 814.301 -6.41528 839.683 -7.14046Z" fill="#F18928" stroke-width="1.34889" stroke="#F18928"/>
|
||||
<path d="M-0.807763 435.101C-0.807763 295.019 -0.807763 154.816 -0.807763 14.7345C-0.203441 14.7345 0.521746 14.6137 1.12607 14.6137C74.6117 14.6137 148.097 15.3388 221.583 14.1302C241.646 13.7676 255.546 19.3274 268.962 33.952C281.773 47.7305 297.969 58.3666 312.715 70.3322C325.768 81.8143 319.725 98.7353 319.362 110.943C318.395 146.114 318.395 181.89 307.034 216.216C304.254 224.797 304.617 234.587 301.716 243.168C295.673 261.298 306.551 281.482 292.651 299.129C281.532 313.27 273.675 330.07 264.369 345.661C256.15 358.836 244.184 369.955 242.613 386.634C241.767 386.876 240.921 386.997 240.075 387.239C222.187 383.734 200.794 403.193 186.17 378.657C186.17 378.657 185.203 379.02 185.082 379.262C180.731 392.919 166.469 393.403 157.162 395.82C116.068 406.577 73.5239 406.094 31.5839 403.072C4.51027 401.138 -4.0711 409.599 0.884341 434.98C0.400883 435.101 -0.203441 435.101 -0.807763 435.101Z" fill="#80C9C1" stroke-width="1.34889" stroke="#80C9C1"/>
|
||||
<path d="M264.488 345.543C273.794 329.951 281.65 313.151 292.77 299.01C306.669 281.364 295.792 261.179 301.835 243.05C304.736 234.468 304.252 224.799 307.153 216.097C318.393 181.892 318.393 145.996 319.481 110.824C319.844 98.4958 325.766 81.6956 312.833 70.2135C330.48 78.3114 354.169 68.2796 367.102 92.5734C377.979 112.999 393.934 87.3762 407.833 92.2108C410.371 83.992 417.381 78.3114 417.623 68.0379C417.865 59.94 420.161 42.1729 429.951 44.7111C460.288 52.6881 490.625 42.8981 520.479 48.337C505.129 70.3343 486.032 90.1561 478.176 116.505C472.495 135.48 487.241 150.83 489.537 168.718C491.834 187.331 510.809 204.856 502.591 223.107C488.329 254.653 471.77 285.111 460.772 318.227C457.75 327.413 444.213 324.633 435.874 323.183C421.732 320.766 420.282 327.05 421.37 338.412C422.095 346.389 421.37 354.487 421.249 362.585C404.207 361.739 387.165 361.376 370.244 359.805C365.168 359.321 360.333 355.937 355.378 353.882C357.795 349.048 355.499 341.433 363.717 339.862C364.563 339.741 366.014 336.72 365.651 336.115C360.938 329.589 355.982 323.304 350.906 317.14C350.785 317.019 347.884 318.59 346.796 319.799C324.799 345.18 299.659 361.497 264.488 345.543Z" fill="#95C7DB" stroke-width="1.34889" stroke="#95C7DB"/>
|
||||
<path d="M520.601 48.3367C490.747 43.0186 460.41 52.6878 430.073 44.7107C420.162 42.1726 417.987 59.9397 417.745 68.0376C417.382 78.3111 410.493 83.9917 407.955 92.2105C394.056 87.3759 378.101 112.999 367.224 92.5731C354.291 68.2793 330.602 78.3111 312.955 70.2132C298.21 58.2476 281.893 47.6115 269.203 33.8329C255.787 19.2083 241.766 13.6486 221.824 14.0112C148.096 15.3407 74.6107 14.6155 1.12514 14.6155C4.9928 7.84708 -7.33537 -6.77753 8.25614 -6.8984C190.641 -7.50272 373.146 -7.38185 555.53 -6.8984C562.903 -6.8984 570.155 -2.4264 577.528 -0.0091138C553.838 6.88016 538.489 23.9221 525.314 43.5021C523.743 45.0733 522.172 46.6446 520.601 48.3367Z" fill="#87ADD4" stroke-width="1.34889" stroke="#87ADD4"/>
|
||||
<path d="M498.602 502.547C499.81 490.098 505.854 483.934 519.028 482.967C532.806 482 524.104 468.947 527.73 461.937C536.07 445.62 541.75 427.611 540.904 408.515C546.222 406.581 551.903 405.372 556.617 402.471C573.054 392.319 584.778 376.969 605.929 371.168C617.532 367.904 629.377 368.75 640.255 365.003C660.197 358.114 659.231 336.479 668.054 321.613C678.931 312.911 683.887 300.945 690.172 288.859C695.973 277.86 687.15 256.83 709.268 255.742C715.795 255.379 718.212 252.116 719.3 246.556C723.893 245.348 728.585 244.232 733.178 243.023C744.056 296.445 721.476 339.984 682.316 369.838C655.242 390.385 656.088 414.437 651.737 440.302C645.935 447.554 640.255 454.685 634.453 461.937C628.289 473.661 617.291 467.738 608.347 468.826C600.37 469.793 592.392 467.496 585.02 473.661C560.243 494.57 531.477 505.085 498.602 502.547Z" fill="#FEDAA5" stroke-width="1.34889" stroke="#FEDAA5"/>
|
||||
<path d="M668.424 321.413C659.601 336.279 660.396 357.987 640.454 364.998C629.576 368.744 617.68 367.868 606.077 371.131C584.926 376.933 573.171 392.323 556.733 402.475C552.02 405.376 546.124 406.608 540.926 408.542C523.643 398.027 506.368 387.192 488.963 376.676C490.534 376.676 492.589 375.951 493.677 376.676C520.509 393.235 506.126 363.986 506.126 362.535C507.093 331.473 526.673 323.375 556.043 319.266C582.754 315.519 606.322 334.374 631.462 322.287C636.901 319.628 641.373 315.156 646.329 311.53C653.701 314.794 661.172 318.149 668.424 321.413Z" fill="#FFF1CF" stroke-width="1.34889" stroke-linejoin="bevel" stroke="#FFF1CF"/>
|
||||
<path d="M449.73 563.404C445.372 577.206 448.278 596.82 424.305 594.399C423.787 592.976 421.607 594.497 421.278 595.731C420.672 595.731 419.762 595.3 419.156 595.3C415.524 526.166 418.493 456.979 407.112 388.329C408.081 377.554 414.861 371.258 424.063 366.778C444.766 377.069 468.981 363.267 489.201 376.585C506.515 387.119 523.481 397.969 540.916 408.503C541.763 427.633 536.42 445.235 528.066 461.701C524.434 468.723 533.151 481.799 519.349 482.768C506.151 483.615 500.098 489.911 498.887 502.382C482.3 522.722 465.954 543.063 449.73 563.404Z" fill="#A29EA1" stroke-width="1.34889" stroke="#A29EA1"/>
|
||||
<path d="M-0.807763 435.103C-0.203441 435.103 0.400882 434.982 1.0052 434.982C75.216 437.642 149.427 441.147 223.517 432.686C202.003 460.122 180.731 482.966 139.032 479.34C93.95 475.472 48.0215 480.79 2.57644 482.241C1.48866 471.363 0.521747 460.485 -0.566033 449.607C-0.686898 444.773 -0.807763 439.938 -0.807763 435.103Z" fill="#797AAA" stroke-width="1.34889" stroke="#797AAA"/>
|
||||
<path d="M651.736 440.3C656.088 414.435 655.242 390.383 682.315 369.836C721.475 339.982 744.077 296.471 733.078 243.049C746.736 238.94 755.68 233.017 755.438 215.613C754.471 142.853 755.076 69.9712 755.196 -2.7892C755.196 -4.96476 757.614 -8.95329 759.064 -8.95329C785.05 -9.43674 810.915 -9.43674 836.901 -9.31588C837.868 -9.31588 838.714 -7.86551 839.681 -7.01946C814.299 -6.29427 788.918 -5.44822 763.536 -4.72303C762.086 -5.44822 760.635 -6.05253 759.306 -6.77772C758.822 -4.84389 757.855 -2.7892 757.855 -0.855369C757.735 73.2346 757.855 147.204 757.735 221.294C757.614 251.389 766.92 282.33 757.13 311.458C746.736 342.4 734.287 373.22 713.377 399.085C702.016 413.105 698.148 435.465 673.855 436.432C666.361 436.795 656.571 425.313 651.736 440.3Z" fill="#F8B570"/>
|
||||
<path d="M424.42 366.647C415.355 371.118 408.194 377.935 407.106 388.692C402.271 391.955 397.316 395.339 392.481 398.603C368.309 397.877 344.257 397.031 320.084 396.306C315.249 395.581 310.414 394.735 305.58 394.01C285.637 382.528 263.761 388.208 242.73 386.516C244.302 369.837 256.267 358.717 264.486 345.543C299.658 361.376 324.676 345.06 346.916 319.678C348.003 318.469 350.904 316.898 351.025 317.019C356.101 323.183 361.057 329.468 365.77 335.995C366.133 336.599 364.803 339.5 363.837 339.742C355.618 341.313 358.035 348.927 355.497 353.762C360.452 355.816 365.287 359.201 370.363 359.684C387.284 361.135 404.806 361.362 421.848 362.208C422.694 363.78 423.574 365.075 424.42 366.647Z" fill="#87ADD4"/>
|
||||
<path d="M640.578 95.6923C640.578 129.796 640.398 164.837 640.276 198.941C640.276 199.912 638.092 201.854 637.606 201.611C631.416 199.184 628.382 194.814 628.382 187.775C628.868 125.514 629.072 63.1936 629.558 0.932391C629.558 -2.34452 627.727 -7.33012 634.402 -7.45149C640.956 -7.57285 639.703 -3.06418 639.824 0.455463C639.824 32.375 639.728 63.6514 639.728 95.5709C639.971 95.6923 640.214 95.6923 640.578 95.6923Z" fill="#FCE8EC"/>
|
||||
<path d="M668.056 321.732C660.804 318.468 653.311 314.601 646.059 311.337C656.574 296.713 671.561 283.539 676.275 267.222C683.769 241.236 702.922 246.407 720.327 246.286C719.239 251.846 716.039 255.498 709.513 255.74C687.395 256.828 696.58 277.979 690.779 288.977C684.615 301.185 678.934 313.03 668.056 321.732Z" fill="#F8C9D1" stroke-width="1.34889" stroke="#F8C9D1"/>
|
||||
<path d="M577.525 -0.0101719C570.153 -2.42746 562.901 -6.89945 555.528 -6.89945C373.144 -7.26205 190.638 -7.50377 8.25367 -6.89945C-7.33785 -6.89945 4.86947 7.72515 1.12267 14.6144C0.518345 14.6144 -0.206842 14.7353 -0.811164 14.7353C-4.4371 -2.79006 -1.05289 -10.2837 20.5818 -10.1628C205.746 -9.07501 391.031 -9.55847 576.196 -9.31674C580.184 -9.31674 586.953 -12.7009 587.92 -6.17426C589.249 1.56107 581.272 -1.944 577.525 -0.0101719Z" fill="#95C7DB" stroke-width="1.34889" stroke="#95C7DB"/>
|
||||
<path d="M-0.686898 449.607C0.400883 460.485 1.3678 471.363 2.45558 482.24C1.97212 489.613 1.0052 496.986 1.0052 504.359C0.88434 606.368 0.88434 708.378 1.0052 810.388C1.0052 812.684 2.33472 814.98 3.0599 817.277C1.73039 818.606 0.400882 819.936 -0.807763 821.265C-0.807763 697.379 -0.686898 573.493 -0.686898 449.607Z" fill="#ADA0CC"/>
|
||||
<path d="M520.6 48.339C522.171 46.7677 523.743 45.1965 525.435 43.5044C523.743 45.0756 522.171 46.6469 520.6 48.339Z" fill="#95C7DB"/>
|
||||
<path d="M3.05605 817.275C2.33087 814.979 1.00135 812.682 1.00135 810.386C0.88049 708.376 0.88049 606.367 1.00135 504.357C1.00135 496.984 1.96827 489.612 2.45173 482.239C48.0176 480.909 93.8253 475.471 138.908 479.338C180.606 482.964 201.878 460.121 223.392 432.684C223.996 432.564 224.722 432.443 225.326 432.201C233.786 429.059 237.775 433.893 245.148 438.244C273.793 454.924 304.976 446.342 332.412 433.047C350.783 424.224 363.353 429.421 369.759 443.079C386.438 479.217 379.186 518.982 383.537 557.054C388.493 599.84 362.628 589.567 340.51 589.687C325.764 589.808 311.14 589.929 296.394 589.567C293.252 589.446 289.626 588.116 287.209 586.061C270.892 573.008 251.674 588.841 234.995 580.623C217.107 571.799 205.141 594.039 188.704 589.204C183.628 587.754 179.881 577.964 175.53 578.084C149.423 578.568 123.074 578.689 100.956 596.818C91.408 604.675 75.333 606.367 74.6078 622.079C73.3992 646.373 73.6409 670.787 73.7618 695.081C73.8826 717.441 74.1244 717.441 86.936 722.034C87.6612 722.276 87.842 724.11 88.2046 725.198C72.9757 741.273 57.8077 757.326 42.5787 773.281C29.4045 788.026 16.2303 802.651 3.05605 817.275Z" fill="#6E5EA6" stroke-width="1.34889" stroke="#6E5EA6"/>
|
||||
<path d="M88.1444 725.295C87.7818 724.208 87.6609 722.395 86.9357 722.153C74.1241 717.56 73.8824 717.681 73.7615 695.2C73.6406 670.906 73.5198 646.492 74.6076 622.198C75.3327 606.486 91.4077 604.793 100.956 596.937C123.074 578.808 149.423 578.566 175.529 578.203C179.76 578.082 183.627 587.872 188.704 589.323C205.141 594.157 217.107 571.918 234.995 580.741C251.553 588.96 270.891 573.127 287.208 586.18C289.625 588.114 293.251 589.565 296.394 589.685C311.139 590.048 325.764 589.927 340.509 589.806C362.628 589.685 388.372 599.959 383.537 557.173C379.186 519.101 386.438 479.336 369.759 443.198C363.474 429.54 350.783 424.343 332.411 433.166C304.975 446.461 273.792 455.042 245.147 438.363C237.775 434.012 233.786 429.177 225.326 432.32C230.281 417.333 235.222 402.117 240.177 387.13C241.023 386.888 241.895 386.753 242.741 386.511C263.771 388.203 285.629 382.493 305.571 393.976C310.406 394.701 315.274 395.559 320.108 396.284C323.371 401.844 386.47 403.709 392.414 398.626C397.249 395.363 402.287 391.866 407.121 388.603C418.482 457.133 415.687 526.836 419.313 595.85C398.041 602.255 378.219 611.683 361.298 626.428C340.993 625.582 320.204 626.791 300.382 623.286C272.1 618.33 250.345 630.538 228.468 644.437C219.403 650.118 209.492 651.568 199.944 648.788C167.19 639.482 137.336 659.424 139.028 692.541C140.237 714.78 137.336 727.954 110.746 720.461C105.307 718.89 98.6596 721.428 92.6164 722.032C91.166 722.999 89.5948 724.087 88.1444 725.295Z" fill="#797AAA" stroke-width="1.34889" stroke="#797AAA"/>
|
||||
<path d="M92.7403 721.915C98.7835 721.311 105.431 718.773 110.87 720.344C137.581 727.838 140.361 714.784 139.152 692.424C137.46 659.307 167.193 639.365 200.068 648.671C209.616 651.451 219.527 650.001 228.592 644.32C250.468 630.542 272.224 618.213 300.506 623.169C320.328 626.674 341.117 625.465 361.422 626.311C349.336 635.014 337.128 643.837 325.042 652.539C320.812 655.077 316.702 657.615 312.472 660.033C301.352 659.791 301.473 674.295 296.518 675.503C271.378 681.909 261.105 705.599 242.129 718.652C240.195 715.147 238.865 714.18 234.273 716.718C227.142 720.586 218.923 724.091 210.946 724.453C174.445 726.025 138.064 726.508 101.563 726.871C98.5418 726.992 95.641 723.607 92.7403 721.915Z" fill="#A29EA1" stroke-width="1.34889" stroke="#A29EA1"/>
|
||||
<path d="M498.602 502.543C531.477 505.202 560.243 494.566 585.141 473.656C592.514 467.492 600.491 469.789 608.468 468.822C617.412 467.734 628.41 473.656 634.575 461.933C636.146 475.469 640.98 489.611 638.321 502.18C635.662 514.388 620.433 508.103 611.006 509.674C593.964 512.575 582.361 521.761 571.242 534.21C564.473 541.703 553.475 545.087 545.377 551.372C515.765 574.457 480.714 551.735 449.894 563.459C465.969 543.154 482.285 522.848 498.602 502.543Z" fill="#F8B570"/>
|
||||
<path d="M92.7394 721.915C95.7085 723.67 98.6479 726.973 101.549 726.852C138.05 726.369 174.551 725.895 210.931 724.444C218.908 724.082 227.104 720.615 234.235 716.626C238.828 714.088 240.339 715.075 242.273 718.58C241.548 719.547 240.798 720.585 240.073 721.552C228.712 734.001 217.472 746.571 206.11 759.02C202.726 763.613 198.617 767.722 196.32 772.799C189.673 787.544 188.585 770.019 183.025 770.261C136.251 772.557 89.2649 772.636 42.4903 773.362C57.7193 757.287 72.8736 741.239 88.1025 725.285C89.5529 724.076 91.1681 723.003 92.7394 721.915Z" fill="#B59259" stroke-width="1.34889" stroke="#B59259"/>
|
||||
<path d="M206.107 758.899C217.469 746.45 228.709 733.88 240.07 721.431C256.266 729.771 268.473 715.146 283.098 713.696C296.514 712.366 314.523 714.3 310.534 690.127C308.963 680.458 311.622 670.185 312.347 660.153C316.577 657.615 320.687 655.077 324.917 652.659C324.796 664.746 322.862 677.195 324.675 689.04C328.906 716.959 305.579 729.046 289.987 738.231C270.528 749.714 273.791 763.975 271.858 779.204C271.253 783.918 271.858 788.874 271.737 793.708C271.616 799.026 271.132 804.344 270.891 809.541C267.023 803.136 263.397 796.488 259.167 790.324C254.332 783.193 250.102 772.194 243.696 770.623C233.906 768.206 223.874 757.691 212.15 766.997C210.458 768.327 207.679 775.82 203.932 769.173C202.965 766.755 205.382 762.404 206.107 758.899Z" fill="#F8B570"/>
|
||||
<path d="M240.193 387.12C235.238 402.107 230.403 417.215 225.448 432.202C224.843 432.323 224.118 432.444 223.514 432.686C149.424 441.267 75.2133 437.641 1.00251 434.982C-3.95294 409.721 4.74931 401.261 31.7021 403.074C73.642 405.975 116.186 406.458 157.28 395.822C166.587 393.405 180.849 392.8 185.2 379.264C185.321 378.901 186.288 378.659 186.288 378.659C200.912 403.074 222.426 383.736 240.193 387.12Z" fill="#A29EA1" stroke-width="1.34889" stroke="#A29EA1"/>
|
||||
<path d="M392.483 398.6C387.285 403.918 323.469 401.863 320.085 396.304C344.258 397.029 368.431 397.875 392.483 398.6Z" fill="#7D89C1" stroke-width="1.34889" stroke="#7D89C1"/>
|
||||
<path d="M320.079 396.306C315.245 395.581 310.41 394.735 305.575 394.01C310.41 394.735 315.245 395.581 320.079 396.306Z" fill="#7D89C1"/>
|
||||
</g>
|
||||
</svg>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 28 KiB |
@@ -1,5 +0,0 @@
|
||||
<svg width="1024" height="1024" viewBox="0 0 1024 1024" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg x="94.969" y="72.212" width="840" height="809.053" viewBox="0 0 840 809.053" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M838.697 289.781C839.129 290.058 839.092 290.701 838.633 290.929C835.039 292.719 831.485 294.6 827.806 296.232C799.555 308.776 772.522 323.383 749.552 344.372C741.699 351.546 734.598 359.69 727.95 368.025C721.643 375.923 719.536 385.597 719.632 395.64C719.651 397.39 719.936 399.14 720.232 401.108C720.435 402.455 721.585 403.47 722.947 403.47H754.934C756.426 403.47 757.641 404.683 757.574 406.173C757.007 418.72 752.651 429.514 745.064 438.533C734.995 450.489 730.891 464.7 726.144 478.966C719.221 499.763 718.756 521.203 720.398 542.478C722.108 564.655 713.899 582.337 700.656 598.83C690.259 611.784 680.19 625.011 669.847 638.019C659.245 651.356 655.291 666.414 656.426 683.344C657.89 705.043 662.979 725.963 667.727 747.088C671.598 764.333 672.871 782.151 675.21 799.723C675.317 800.514 675.272 801.319 675.175 802.406C675.006 804.292 672.993 805.496 671.237 804.786C667.425 803.245 663.961 801.983 661.064 799.915C647.862 790.5 639.736 777.082 633.087 762.666C629.837 755.612 626.686 748.51 623.528 741.499C623.236 740.852 622.7 740.346 622.039 740.087C612.637 736.391 610.069 734.129 607.43 727.667C607.013 726.646 606.042 725.945 604.94 725.931C590.847 725.748 576.824 727.122 563.328 731.798C539.442 740.065 515.514 748.25 491.969 757.391C465.73 767.585 438.573 769.648 411.239 766.355C385.068 763.199 359.197 757.514 333.176 753.101C324.448 751.625 315.61 750.75 306.841 749.425C304.666 749.097 302.504 748.25 300.507 747.28C284.746 739.6 269.178 731.511 253.267 724.159C236.015 716.193 222.69 721.877 216.301 739.559C213.675 746.829 212.115 754.467 209.57 761.764C205.685 772.9 201.622 784.009 197.148 794.914C193.962 802.692 187.887 806.658 178.915 807.795C177.278 808.003 175.934 806.595 176.072 804.951C176.687 797.655 177.055 790.615 178.241 783.722C180.676 769.607 184.781 755.724 186.272 741.527C187.749 727.343 187.024 712.914 186.86 698.607C186.833 696.53 184.917 694.125 183.289 692.458C171.62 680.597 159.95 668.737 147.993 657.177C135.338 644.933 123.039 632.28 114.584 616.812C106.896 602.751 94.8841 600.743 81.5727 603.339C60.1486 607.52 38.9844 612.959 17.7107 617.905C12.3941 619.143 7.11322 620.525 1.82357 621.849C1.16721 622.013 0.484001 621.707 0.168963 621.108C-0.12378 620.551 -0.0300104 619.87 0.401242 619.413C2.93975 616.72 5.31065 614.184 8.16154 611.811C24.2638 598.42 40.7355 585.452 56.6463 571.843C66.1408 563.712 74.9923 554.803 83.7753 545.894C85.5401 544.104 86.6483 540.674 86.4704 538.105C82.0652 474.457 96.8679 414.73 123.327 357.408C134.518 333.153 146.142 309.094 157.878 284.322C158.345 283.335 159.369 282.751 160.459 282.687C170.461 282.106 171.998 271.978 175.957 264.599C178.669 259.543 180.643 254.095 182.891 248.386C183.584 246.625 185.831 246.144 187.204 247.447C188.931 249.086 190.564 250.638 192.338 252.327C193.394 253.332 195.059 253.317 196.13 252.327C199.595 249.128 203.201 246.202 205.753 242.545C215.658 228.389 225.399 214.096 234.415 199.379C238.574 192.588 243.198 188.325 251.16 186.097C277.787 178.649 304.16 170.335 330.424 162.392C331.068 162.197 331.64 161.784 332.011 161.223C332.138 161.03 332.228 160.859 332.246 160.682C334.189 145.022 335.461 143.71 351.303 141.114C368.172 138.354 378.802 129.336 381.647 112.282C384.479 95.2703 385.314 77.9165 386.969 60.6994C388.351 46.2015 389.377 31.6626 391.087 17.2057C392.145 8.24311 394.608 3.97571 400.598 0.355299C401.894 -0.42824 403.557 0.142784 404.243 1.49332C407.282 7.4772 410.711 13.3289 412.744 19.6243C418.257 36.7184 422.635 54.1678 427.998 71.3166C432.608 86.0468 437.465 100.736 443.047 115.125C450.517 134.405 465.798 142.576 485.854 142.918C493.611 143.055 501.368 143.328 509.111 142.986C518.592 142.576 527.403 144.653 534.996 150.092C555.53 164.822 576.257 179.32 596.012 195.048C610.924 206.928 624.546 220.435 639.396 233.918C640.083 234.542 641.043 234.751 641.954 234.576C649.443 233.136 656.045 237.059 662.73 241.666C663.822 242.419 664.193 243.86 663.612 245.053C661.429 249.534 659.269 253.957 656.99 258.64C656.297 260.065 656.98 261.773 658.432 262.408C673.511 269.004 684.03 280.872 695.731 291.217C699.822 294.825 704.213 298.118 708.55 301.438C716.718 307.697 725.761 308.995 735.693 306.494C754.901 301.657 774.095 296.751 793.44 292.556C808.443 289.301 822.647 287.5 838.432 289.683C838.526 289.696 838.617 289.73 838.697 289.781ZM432.82 707.072C432.825 707.068 432.831 707.065 432.837 707.065C448.553 707.065 464.269 707.243 479.985 706.956C484.185 706.874 488.809 706.259 492.489 704.401C512.19 694.467 532.013 684.724 551.07 673.656C570.73 662.246 572.385 656.575 566.557 634.344C561.304 614.325 551.604 596.22 541.343 578.511C529.209 557.55 519.741 536.083 519.139 511.268C518.825 498.246 515.076 485.757 508.879 474.074C503.393 463.73 494.897 458.838 483.446 458.688C451.775 458.278 420.761 462.063 390.595 472.27C312.614 498.684 265.114 562.824 263.801 642.911C263.486 662.232 271.859 675.35 288.755 683.002C300.534 688.345 312.806 692.786 325.173 696.585C360.261 707.365 396.485 708.363 432.804 707.079C432.81 707.079 432.81Line truncated
|
||||
</svg>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 8.0 KiB |
@@ -1,50 +0,0 @@
|
||||
{
|
||||
"fill" : {
|
||||
"linear-gradient" : [
|
||||
"srgb:0.24706,0.24706,0.27451,1.00000",
|
||||
"srgb:0.03529,0.03529,0.04314,1.00000"
|
||||
],
|
||||
"orientation" : {
|
||||
"start" : {
|
||||
"x" : 0.5,
|
||||
"y" : 0
|
||||
},
|
||||
"stop" : {
|
||||
"x" : 0.5,
|
||||
"y" : 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"groups" : [
|
||||
{
|
||||
"layers" : [
|
||||
{
|
||||
"image-name-specializations" : [
|
||||
{
|
||||
"value" : "01-mark.svg"
|
||||
},
|
||||
{
|
||||
"appearance" : "tinted",
|
||||
"value" : "02-mark-mono.svg"
|
||||
}
|
||||
],
|
||||
"name" : "01-mark"
|
||||
}
|
||||
],
|
||||
"shadow" : {
|
||||
"kind" : "neutral",
|
||||
"opacity" : 0.5
|
||||
},
|
||||
"translucency" : {
|
||||
"enabled" : true,
|
||||
"value" : 0.5
|
||||
}
|
||||
}
|
||||
],
|
||||
"supported-platforms" : {
|
||||
"circles" : [
|
||||
"watchOS"
|
||||
],
|
||||
"squares" : "shared"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<svg width="24" height="24" viewBox="0 0 74 74" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_4_34)">
|
||||
<path d="M55.5 12.3333H18.5C15.0942 12.3333 12.3333 15.0943 12.3333 18.5V55.5C12.3333 58.9058 15.0942 61.6667 18.5 61.6667H55.5C58.9057 61.6667 61.6666 58.9058 61.6666 55.5V18.5C61.6666 15.0943 58.9057 12.3333 55.5 12.3333Z" stroke="#CBCAC8" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M21.5833 24.6667H52.4167" stroke="#CBCAC8" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M21.5833 37H52.4167" stroke="#CBCAC8" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M21.5833 49.3333H40.0833" stroke="#CBCAC8" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<circle cx="61.5" cy="12.5" r="15" fill="#FBB36B" stroke="#1C1D21" stroke-width="5"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_4_34">
|
||||
<rect width="74" height="74" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 982 B |
@@ -1,3 +1,8 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M20 1C20.7957 1 21.5585 1.3163 22.1211 1.87891C22.6837 2.44152 23 3.20435 23 4V20C23 20.7957 22.6837 21.5585 22.1211 22.1211C21.5585 22.6837 20.7957 23 20 23H4C3.9647 23 3.92987 22.9977 3.89551 22.9941C3.13785 22.9677 2.41681 22.659 1.87891 22.1211C1.3163 21.5585 1 20.7957 1 20V11C1 9.34772 2.34772 8 4 8H5V4C5 3.20435 5.3163 2.44152 5.87891 1.87891C6.44152 1.3163 7.20435 1 8 1H20ZM4 10C3.45228 10 3 10.4523 3 11V20C3 20.2652 3.10543 20.5195 3.29297 20.707C3.48051 20.8946 3.73478 21 4 21C4.26522 21 4.51949 20.8946 4.70703 20.707C4.89457 20.5195 5 20.2652 5 20V10H4ZM8 3C7.73478 3 7.4805 3.10543 7.29297 3.29297C7.10543 3.4805 7 3.73478 7 4V20C7 20.3444 6.93703 20.6814 6.82422 21H20C20.2652 21 20.5195 20.8946 20.707 20.707C20.8946 20.5195 21 20.2652 21 20V4C21 3.73478 20.8946 3.48051 20.707 3.29297C20.5195 3.10543 20.2652 3 20 3H8ZM15 17C15.5523 17 16 17.4477 16 18C16 18.5523 15.5523 19 15 19H10C9.44772 19 9 18.5523 9 18C9 17.4477 9.44771 17 10 17H15ZM18 13C18.5523 13 19 13.4477 19 14C19 14.5523 18.5523 15 18 15H10C9.44771 15 9 14.5523 9 14C9 13.4477 9.44771 13 10 13H18ZM18 5C18.5523 5 19 5.44772 19 6V10C19 10.5523 18.5523 11 18 11H10C9.44771 11 9 10.5523 9 10V6C9 5.44772 9.44771 5 10 5H18ZM11 9H17V7H11V9Z" fill="#E4E4E7"/>
|
||||
</svg>
|
||||
<svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" fill="none" stroke="#CBCAC8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<!-- Основа газеты -->
|
||||
<rect x="4" y="4" width="16" height="16" rx="2"/>
|
||||
<!-- Линии текста -->
|
||||
<line x1="7" y1="8" x2="17" y2="8"/>
|
||||
<line x1="7" y1="12" x2="17" y2="12"/>
|
||||
<line x1="7" y1="16" x2="13" y2="16"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 410 B |
@@ -48,6 +48,7 @@
|
||||
<file>controls/map-pin.svg</file>
|
||||
<file>controls/more-vertical.svg</file>
|
||||
<file>controls/news.svg</file>
|
||||
<file>controls/news-unread.svg</file>
|
||||
<file>controls/unread-dot.svg</file>
|
||||
<file>controls/plus.svg</file>
|
||||
<file>controls/qr-code.svg</file>
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
<false/>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
@@ -67,7 +68,7 @@
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIUserInterfaceStyle</key>
|
||||
<string>Dark</string>
|
||||
<string>Light</string>
|
||||
<key>com.wireguard.ios.app_group_id</key>
|
||||
<string>${BUILD_IOS_GROUP_IDENTIFIER}</string>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
|
||||
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 6.7 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 767 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.7 KiB |