diff --git a/.clang-format b/.clang-format
new file mode 100644
index 00000000..5c459fd2
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,39 @@
+BasedOnStyle: WebKit
+AccessModifierOffset: '-4'
+AlignAfterOpenBracket: Align
+AlignConsecutiveMacros: 'true'
+AlignTrailingComments: 'true'
+AllowAllArgumentsOnNextLine: 'true'
+AllowAllParametersOfDeclarationOnNextLine: 'true'
+AllowShortBlocksOnASingleLine: 'false'
+AllowShortCaseLabelsOnASingleLine: 'true'
+AllowShortEnumsOnASingleLine: 'false'
+AllowShortFunctionsOnASingleLine: None
+AlwaysBreakTemplateDeclarations: 'No'
+BreakBeforeBinaryOperators: NonAssignment
+BreakBeforeBraces: Custom
+BraceWrapping:
+ AfterClass: true
+ AfterControlStatement: false
+ AfterEnum: false
+ AfterFunction: true
+ AfterNamespace: true
+ AfterObjCDeclaration: false
+ AfterStruct: true
+ AfterUnion: false
+ BeforeCatch: false
+ BeforeElse: false
+ IndentBraces: false
+BreakConstructorInitializers: BeforeColon
+ColumnLimit: '120'
+CommentPragmas: '"^!|^:"'
+ConstructorInitializerAllOnOneLineOrOnePerLine: 'true'
+ConstructorInitializerIndentWidth: '4'
+ContinuationIndentWidth: '8'
+IndentPPDirectives: BeforeHash
+NamespaceIndentation: All
+PenaltyExcessCharacter: '10'
+PointerAlignment: Right
+SortIncludes: 'true'
+SpaceAfterTemplateKeyword: 'false'
+Standard: Auto
diff --git a/.clang-format-ignore b/.clang-format-ignore
new file mode 100644
index 00000000..4019357f
--- /dev/null
+++ b/.clang-format-ignore
@@ -0,0 +1,20 @@
+/client/3rd
+/client/3rd-prebuild
+/client/android
+/client/cmake
+/client/core/serialization
+/client/daemon
+/client/fonts
+/client/images
+/client/ios
+/client/mozilla
+/client/platforms/dummy
+/client/platforms/linux
+/client/platforms/macos
+/client/platforms/windows
+/client/server_scripts
+/client/translations
+/deploy
+/docs
+/metadata
+/service/src
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 370c069d..1f270cd1 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -217,7 +217,11 @@ jobs:
export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/ios/bin"
export QT_MACOS_ROOT_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos"
export PATH=$PATH:~/go/bin
- sh deploy/build_ios.sh
+ sh deploy/build_ios.sh | \
+ sed -e '/-Xcc -DPROD_AGW_PUBLIC_KEY/,/-Xcc/ { /-Xcc/!d; }' -e '/-Xcc -DPROD_AGW_PUBLIC_KEY/d' | \
+ sed -e '/-Xcc -DDEV_AGW_PUBLIC_KEY/,/-Xcc/ { /-Xcc/!d; }' -e '/-Xcc -DDEV_AGW_PUBLIC_KEY/d' | \
+ sed -e '/-DPROD_AGW_PUBLIC_KEY/,/-D/ { /-D/!d; }' -e '/-DPROD_AGW_PUBLIC_KEY/d' | \
+ sed -e '/-DDEV_AGW_PUBLIC_KEY/,/-D/ { /-D/!d; }' -e '/-DDEV_AGW_PUBLIC_KEY/d'
env:
IOS_TRUST_CERT_BASE64: ${{ secrets.IOS_TRUST_CERT_BASE64 }}
IOS_SIGNING_CERT_BASE64: ${{ secrets.IOS_SIGNING_CERT_BASE64 }}
@@ -256,7 +260,7 @@ jobs:
- name: 'Setup xcode'
uses: maxim-lobanov/setup-xcode@v1
with:
- xcode-version: '14.3.1'
+ xcode-version: '15.4.0'
- name: 'Install Qt'
uses: jurplel/install-qt-action@v3
@@ -331,7 +335,8 @@ jobs:
arch: 'gcc_64'
modules: ${{ env.QT_MODULES }}
dir: ${{ runner.temp }}
- extra: '--external 7z --base ${{ env.QT_MIRROR }}'
+ py7zrversion: '==0.22.*'
+ extra: '--base ${{ env.QT_MIRROR }}'
- name: 'Install android_x86_64 Qt'
uses: jurplel/install-qt-action@v4
@@ -342,7 +347,8 @@ jobs:
arch: 'android_x86_64'
modules: ${{ env.QT_MODULES }}
dir: ${{ runner.temp }}
- extra: '--external 7z --base ${{ env.QT_MIRROR }}'
+ py7zrversion: '==0.22.*'
+ extra: '--base ${{ env.QT_MIRROR }}'
- name: 'Install android_x86 Qt'
uses: jurplel/install-qt-action@v4
@@ -353,7 +359,8 @@ jobs:
arch: 'android_x86'
modules: ${{ env.QT_MODULES }}
dir: ${{ runner.temp }}
- extra: '--external 7z --base ${{ env.QT_MIRROR }}'
+ py7zrversion: '==0.22.*'
+ extra: '--base ${{ env.QT_MIRROR }}'
- name: 'Install android_armv7 Qt'
uses: jurplel/install-qt-action@v4
@@ -364,7 +371,8 @@ jobs:
arch: 'android_armv7'
modules: ${{ env.QT_MODULES }}
dir: ${{ runner.temp }}
- extra: '--external 7z --base ${{ env.QT_MIRROR }}'
+ py7zrversion: '==0.22.*'
+ extra: '--base ${{ env.QT_MIRROR }}'
- name: 'Install android_arm64_v8a Qt'
uses: jurplel/install-qt-action@v4
@@ -375,7 +383,8 @@ jobs:
arch: 'android_arm64_v8a'
modules: ${{ env.QT_MODULES }}
dir: ${{ runner.temp }}
- extra: '--external 7z --base ${{ env.QT_MIRROR }}'
+ py7zrversion: '==0.22.*'
+ extra: '--base ${{ env.QT_MIRROR }}'
- name: 'Grant execute permission for qt-cmake'
shell: bash
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 45c923e0..1c670911 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN)
-project(${PROJECT} VERSION 4.8.2.4
+project(${PROJECT} VERSION 4.8.3.0
DESCRIPTION "AmneziaVPN"
HOMEPAGE_URL "https://amnezia.org/"
)
@@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
set(RELEASE_DATE "${CURRENT_DATE}")
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
-set(APP_ANDROID_VERSION_CODE 1071)
+set(APP_ANDROID_VERSION_CODE 1073)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux")
diff --git a/README.md b/README.md
index eed800f5..8f887808 100644
--- a/README.md
+++ b/README.md
@@ -1,24 +1,29 @@
# Amnezia VPN
-## _The best client for self-hosted VPN_
+
+### _The best client for self-hosted VPN_
+
[](https://github.com/amnezia-vpn/amnezia-client/actions/workflows/deploy.yml?query=branch:dev)
[](https://gitpod.io/#https://github.com/amnezia-vpn/amnezia-client)
-Amnezia is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
+### [English]([https://github.com/amnezia-vpn/amnezia-client/blob/dev/README_RU.md](https://github.com/amnezia-vpn/amnezia-client/tree/dev?tab=readme-ov-file#)) | [Русский](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README_RU.md)
-
-
+[Amnezia](https://amnezia.org) is an open-source VPN client, with a key feature that enables you to deploy your own VPN server on your server.
-
-
-
+[](https://amnezia.org)
-[Alternative download link (mirror)](https://storage.googleapis.com/kldscp/amnezia.org/downloads)
+### [Website](https://amnezia.org) | [Alt website link](https://storage.googleapis.com/kldscp/amnezia.org) | [Documentation](https://docs.amnezia.org) | [Troubleshooting](https://docs.amnezia.org/troubleshooting)
+
+> [!TIP]
+> If the [Amnezia website](https://amnezia.org) is blocked in your region, you can use an [Alternative website link](https://storage.googleapis.com/kldscp/amnezia.org).
+
+
+
[All releases](https://github.com/amnezia-vpn/amnezia-client/releases)
-
+
@@ -33,7 +38,8 @@ Amnezia is an open-source VPN client, with a key feature that enables you to dep
## Links
-- [https://amnezia.org](https://amnezia.org) - project website | [Alternative link (mirror)](https://storage.googleapis.com/kldscp/amnezia.org)
+- [https://amnezia.org](https://amnezia.org) - Project website | [Alternative link (mirror)](https://storage.googleapis.com/kldscp/amnezia.org)
+- [https://docs.amnezia.org](https://docs.amnezia.org) - Documentation
- [https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit
- [https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Telegram support channel (English)
- [https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Telegram support channel (Farsi)
@@ -182,8 +188,8 @@ Patreon: [https://www.patreon.com/amneziavpn](https://www.patreon.com/amneziavpn
Bitcoin: bc1q26eevjcg9j0wuyywd2e3uc9cs2w58lpkpjxq6p
USDT BEP20: 0x6abD576765a826f87D1D95183438f9408C901bE4
USDT TRC20: TELAitazF1MZGmiNjTcnxDjEiH5oe7LC9d
-XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3
-
+XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3
+TON: UQDpU1CyKRmg7L8mNScKk9FRc2SlESuI7N-Hby4nX-CcVmns
## Acknowledgments
This project is tested with BrowserStack.
diff --git a/README_RU.md b/README_RU.md
new file mode 100644
index 00000000..59518f4b
--- /dev/null
+++ b/README_RU.md
@@ -0,0 +1,181 @@
+# Amnezia VPN
+
+### _Лучший клиент для создания VPN на собственном сервере_
+
+[](https://github.com/amnezia-vpn/amnezia-client/actions/workflows/deploy.yml?query=branch:dev)
+[](https://gitpod.io/#https://github.com/amnezia-vpn/amnezia-client)
+
+### [English](https://github.com/amnezia-vpn/amnezia-client/blob/dev/README.md) | Русский
+[AmneziaVPN](https://amnezia.org) — это open sourse VPN-клиент, ключевая особенность которого заключается в возможности развернуть собственный VPN на вашем сервере.
+
+[](https://amnezia.org)
+
+### [Сайт](https://amnezia.org) | [Зеркало на сайт](https://storage.googleapis.com/kldscp/amnezia.org) | [Документация](https://docs.amnezia.org) | [Решение проблем](https://docs.amnezia.org/troubleshooting)
+
+> [!TIP]
+> Если [сайт Amnezia](https://amnezia.org) заблокирован в вашем регионе, вы можете воспользоваться [ссылкой на зеркало](https://storage.googleapis.com/kldscp/amnezia.org).
+
+
+
+
+[Все релизы](https://github.com/amnezia-vpn/amnezia-client/releases)
+
+
+
+
+
+## Особенности
+
+- Простой в использовании — введите IP-адрес, SSH-логин и пароль, и Amnezia автоматически установит VPN-контейнеры Docker на ваш сервер и подключится к VPN.
+- Классические VPN-протоколы: OpenVPN, WireGuard и IKEv2.
+- Протоколы с маскировкой трафика (обфускацией): OpenVPN с плагином [Cloak](https://github.com/cbeuw/Cloak), Shadowsocks (OpenVPN over Shadowsocks), [AmneziaWG](https://docs.amnezia.org/documentation/amnezia-wg/) and XRay.
+- Поддержка Split Tunneling — добавляйте любые сайты или приложения в список, чтобы включить VPN только для них.
+- Поддерживает платформы: Windows, MacOS, Linux, Android, iOS.
+- Поддержка конфигурации протокола AmneziaWG на [бета-прошивке Keenetic](https://docs.keenetic.com/ua/air/kn-1611/en/6319-latest-development-release.html#UUID-186c4108-5afd-c10b-f38a-cdff6c17fab3_section-idm33192196168192-improved).
+
+## Ссылки
+
+- [https://amnezia.org](https://amnezia.org) - Веб-сайт проекта | [Альтернативная ссылка (зеркало)](https://storage.googleapis.com/kldscp/amnezia.org)
+- [https://docs.amnezia.org](https://docs.amnezia.org) - Документация
+- [https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit
+- [https://t.me/amnezia_vpn_en](https://t.me/amnezia_vpn_en) - Канал поддржки в Telegram (Английский)
+- [https://t.me/amnezia_vpn_ir](https://t.me/amnezia_vpn_ir) - Канал поддржки в Telegram (Фарси)
+- [https://t.me/amnezia_vpn_mm](https://t.me/amnezia_vpn_mm) - Канал поддржки в Telegram (Мьянма)
+- [https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - Канал поддржки в Telegram (Русский)
+- [https://vpnpay.io/en/amnezia-premium/](https://vpnpay.io/en/amnezia-premium/) - Amnezia Premium | [Зеркало](https://storage.googleapis.com/kldscp/vpnpay.io/ru/amnezia-premium\)
+
+## Технологии
+
+AmneziaVPN использует несколько проектов с открытым исходным кодом:
+
+- [OpenSSL](https://www.openssl.org/)
+- [OpenVPN](https://openvpn.net/)
+- [Shadowsocks](https://shadowsocks.org/)
+- [Qt](https://www.qt.io/)
+- [LibSsh](https://libssh.org)
+- и другие...
+
+## Проверка исходного кода
+После клонирования репозитория обязательно загрузите все подмодули.
+
+```bash
+git submodule update --init --recursive
+```
+
+
+## Разработка
+Хотите внести свой вклад? Добро пожаловать!
+
+### Помощь с переводами
+
+Загрузите самые актуальные файлы перевода.
+
+Перейдите на [вкладку "Actions"](https://github.com/amnezia-vpn/amnezia-client/actions?query=is%3Asuccess+branch%3Adev), нажмите на первую строку. Затем прокрутите вниз до раздела "Artifacts" и скачайте "AmneziaVPN_translations".
+
+Распакуйте этот файл. Каждый файл с расширением *.ts содержит строки для соответствующего языка.
+
+Переведите или исправьте строки в одном или нескольких файлах *.ts и загрузите их обратно в этот репозиторий в папку ``client/translations``. Это можно сделать через веб-интерфейс или любым другим знакомым вам способом.
+
+### Сборка исходного кода и деплой
+Проверьте папку deploy для скриптов сборки.
+
+### Как собрать iOS-приложение из исходного кода на MacOS
+1. Убедитесь, что у вас установлен XCode версии 14 или выше.
+2. Для генерации проекта XCode используется QT. Требуется версия QT 6.6.2. Установите QT для MacOS здесь или через QT Online Installer. Необходимые модули:
+- MacOS
+- iOS
+- Модуль совместимости с Qt 5
+- Qt Shader Tools
+- Дополнительные библиотеки:
+ - Qt Image Formats
+ - Qt Multimedia
+ - Qt Remote Objects
+
+
+3. Установите CMake, если это необходимо. Рекомендуемая версия — 3.25. Скачать CMake можно здесь.
+4. Установите Go версии >= v1.16. Если Go ещё не установлен, скачайте его с [официального сайта](https://golang.org/dl/) или используйте Homebrew. Установите gomobile:
+
+```bash
+export PATH=$PATH:~/go/bin
+go install golang.org/x/mobile/cmd/gomobile@latest
+gomobile init
+```
+
+5. Соберите проект:
+```bash
+export QT_BIN_DIR="/Qt//ios/bin"
+export QT_MACOS_ROOT_DIR="/Qt//macos"
+export QT_IOS_BIN=$QT_BIN_DIR
+export PATH=$PATH:~/go/bin
+mkdir build-ios
+$QT_IOS_BIN/qt-cmake . -B build-ios -GXcode -DQT_HOST_PATH=$QT_MACOS_ROOT_DIR
+```
+Замените и на ваши значения.
+
+Если появляется ошибка gomobile: command not found, убедитесь, что PATH настроен на папку bin, где установлен gomobile:
+```bash
+export PATH=$(PATH):/path/to/GOPATH/bin
+```
+
+6. Откройте проект в XCode. Теперь вы можете тестировать, архивировать или публиковать приложение.
+
+Если сборка завершится с ошибкой:
+```
+make: ***
+[$(PROJECTDIR)/client/build/AmneziaVPN.build/Debug-iphoneos/wireguard-go-bridge/goroot/.prepared]
+Error 1
+```
+Добавьте пользовательскую переменную PATH в настройки сборки для целей AmneziaVPN и WireGuardNetworkExtension с ключом `PATH` и значением `${PATH}/path/to/bin/folder/with/go/executable`, e.g. `${PATH}:/usr/local/go/bin`.
+
+Если ошибка повторяется на Mac с M1, установите версию CMake для архитектуры ARM:
+```
+arch -arm64 brew install cmake
+```
+
+ При первой попытке сборка может завершиться с ошибкой source files not found. Это происходит из-за параллельной компиляции зависимостей в XCode. Просто перезапустите сборку.
+
+
+## Как собрать Android-приложение
+Сборка тестировалась на MacOS. Требования:
+- JDK 11
+- Android SDK 33
+- CMake 3.25.0
+
+Установите QT, QT Creator и Android Studio.
+Настройте QT Creator:
+
+- В меню QT Creator перейдите в `QT Creator` -> `Preferences` -> `Devices` ->`Android`.
+- Укажите путь к JDK 11.
+- Укажите путь к Android SDK (`$ANDROID_HOME`)
+
+Если вы сталкиваетесь с ошибками, связанными с отсутствием SDK или сообщением «SDK manager not running», их нельзя исправить просто корректировкой путей. Если у вас есть несколько свободных гигабайт на диске, вы можете позволить Qt Creator установить все необходимые компоненты, выбрав пустую папку для расположения Android SDK и нажав кнопку **Set Up SDK**. Учтите: это установит второй Android SDK и NDK на вашем компьютере!
+
+Убедитесь, что настроена правильная версия CMake: перейдите в **Qt Creator -> Preferences** и в боковом меню выберите пункт **Kits**. В центральной части окна, на вкладке **Kits**, найдите запись для инструмента **CMake Tool**. Если выбранная по умолчанию версия CMake ниже 3.25.0, установите на свою систему CMake версии 3.25.0 или выше, а затем выберите опцию **System CMake at <путь>** из выпадающего списка. Если этот пункт отсутствует, это может означать, что вы еще не установили CMake, или Qt Creator не смог найти путь к нему. В таком случае в окне **Preferences** перейдите в боковое меню **CMake**, затем во вкладку **Tools** в центральной части окна и нажмите кнопку **Add**, чтобы указать путь к установленному CMake.
+
+Убедитесь, что для вашего проекта выбрана Android Platform SDK 33: в главном окне на боковой панели выберите пункт **Projects**, и слева вы увидите раздел **Build & Run**, показывающий различные целевые Android-платформы. Вы можете выбрать любую из них, так как настройка проекта Amnezia VPN разработана таким образом, чтобы все Android-цели могли быть собраны. Перейдите в подраздел **Build** и прокрутите центральную часть окна до раздела **Build Steps**. Нажмите **Details** в заголовке **Build Android APK** (кнопка **Details** может быть скрыта, если окно Qt Creator не запущено в полноэкранном режиме!). Вот здесь выберите **android-33** в качестве Android Build Platform SDK.
+
+### Разработка Android-компонентов
+
+После сборки QT Creator копирует проект в отдельную папку, например, `build-amnezia-client-Android_Qt__Clang_-`. Для разработки Android-компонентов откройте сгенерированный проект в Android Studio, указав папку `build-amnezia-client-Android_Qt__Clang_-/client/android-build` в качестве корневой.
+Изменения в сгенерированном проекте нужно вручную перенести в репозиторий. После этого можно коммитить изменения.
+Если возникают проблемы со сборкой в QT Creator после работы в Android Studio, выполните команду `./gradlew clean` в корневой папке сгенерированного проекта (`/client/android-build/.`).
+
+
+## Лицензия
+
+GPL v3.0
+
+## Донаты
+
+Patreon: [https://www.patreon.com/amneziavpn](https://www.patreon.com/amneziavpn)
+
+Bitcoin: bc1q26eevjcg9j0wuyywd2e3uc9cs2w58lpkpjxq6p
+USDT BEP20: 0x6abD576765a826f87D1D95183438f9408C901bE4
+USDT TRC20: TELAitazF1MZGmiNjTcnxDjEiH5oe7LC9d
+XMR: 48spms39jt1L2L5vyw2RQW6CXD6odUd4jFu19GZcDyKKQV9U88wsJVjSbL4CfRys37jVMdoaWVPSvezCQPhHXUW5UKLqUp3
+TON: UQDpU1CyKRmg7L8mNScKk9FRc2SlESuI7N-Hby4nX-CcVmns
+
+## Благодарности
+
+Этот проект тестируется с помощью BrowserStack.
+Мы выражаем благодарность [BrowserStack](https://www.browserstack.com) за поддержку нашего проекта.
diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt
index 05f9f17c..3ef92385 100644
--- a/client/CMakeLists.txt
+++ b/client/CMakeLists.txt
@@ -146,6 +146,7 @@ set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/core/serialization/transfer.h
${CMAKE_CURRENT_LIST_DIR}/core/enums/apiEnums.h
${CMAKE_CURRENT_LIST_DIR}/../common/logger/logger.h
+ ${CMAKE_CURRENT_LIST_DIR}/utils/qmlUtils.h
)
# Mozilla headres
@@ -197,6 +198,7 @@ set(SOURCES ${SOURCES}
${CMAKE_CURRENT_LIST_DIR}/core/serialization/vmess.cpp
${CMAKE_CURRENT_LIST_DIR}/core/serialization/vmess_new.cpp
${CMAKE_CURRENT_LIST_DIR}/../common/logger/logger.cpp
+ ${CMAKE_CURRENT_LIST_DIR}/utils/qmlUtils.cpp
)
# Mozilla sources
diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp
index 4e25097d..aeed439b 100644
--- a/client/amnezia_application.cpp
+++ b/client/amnezia_application.cpp
@@ -404,6 +404,9 @@ void AmneziaApplication::initControllers()
m_pageController.reset(new PageController(m_serversModel, m_settings));
m_engine->rootContext()->setContextProperty("PageController", m_pageController.get());
+ m_focusController.reset(new FocusController(m_engine, this));
+ m_engine->rootContext()->setContextProperty("FocusController", m_focusController.get());
+
m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_clientManagementModel,
m_apiServicesModel, m_settings));
m_engine->rootContext()->setContextProperty("InstallController", m_installController.get());
diff --git a/client/amnezia_application.h b/client/amnezia_application.h
index 64566216..cfeac0d1 100644
--- a/client/amnezia_application.h
+++ b/client/amnezia_application.h
@@ -19,6 +19,7 @@
#include "ui/controllers/exportController.h"
#include "ui/controllers/importController.h"
#include "ui/controllers/installController.h"
+#include "ui/controllers/focusController.h"
#include "ui/controllers/pageController.h"
#include "ui/controllers/settingsController.h"
#include "ui/controllers/sitesController.h"
@@ -124,6 +125,7 @@ private:
#endif
QScopedPointer m_connectionController;
+ QScopedPointer m_focusController;
QScopedPointer m_pageController;
QScopedPointer m_installController;
QScopedPointer m_importController;
diff --git a/client/android/AndroidManifest.xml b/client/android/AndroidManifest.xml
index 085ff5a0..314df0de 100644
--- a/client/android/AndroidManifest.xml
+++ b/client/android/AndroidManifest.xml
@@ -97,6 +97,13 @@
android:exported="false"
android:theme="@style/Translucent" />
+
+
-
-
-
-
\ No newline at end of file
diff --git a/client/android/res/mipmap-hdpi/ic_banner.png b/client/android/res/mipmap-hdpi/ic_banner.png
new file mode 100644
index 00000000..a444777f
Binary files /dev/null and b/client/android/res/mipmap-hdpi/ic_banner.png differ
diff --git a/client/android/res/mipmap-mdpi/ic_banner.png b/client/android/res/mipmap-mdpi/ic_banner.png
new file mode 100644
index 00000000..b9ad1db7
Binary files /dev/null and b/client/android/res/mipmap-mdpi/ic_banner.png differ
diff --git a/client/android/res/mipmap-xhdpi/ic_banner_foreground.png b/client/android/res/mipmap-xhdpi/ic_banner_foreground.png
deleted file mode 100644
index 1c21902e..00000000
Binary files a/client/android/res/mipmap-xhdpi/ic_banner_foreground.png and /dev/null differ
diff --git a/client/android/res/values-ru/strings.xml b/client/android/res/values-ru/strings.xml
index 8bdabfc0..5e35bba5 100644
--- a/client/android/res/values-ru/strings.xml
+++ b/client/android/res/values-ru/strings.xml
@@ -23,4 +23,6 @@
Настройки уведомленийДля показа уведомлений необходимо включить уведомления в системных настройкахОткрыть настройки уведомлений
+
+ Пожалуйста, установите приложение для просмотра файлов
\ No newline at end of file
diff --git a/client/android/res/values/ic_banner_background.xml b/client/android/res/values/ic_banner_background.xml
deleted file mode 100644
index fa6f91c7..00000000
--- a/client/android/res/values/ic_banner_background.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
- #1E1E1F
-
\ No newline at end of file
diff --git a/client/android/res/values/strings.xml b/client/android/res/values/strings.xml
index 5251403b..bf8d76d1 100644
--- a/client/android/res/values/strings.xml
+++ b/client/android/res/values/strings.xml
@@ -23,4 +23,6 @@
Notification settingsTo show notifications, you must enable notifications in the system settingsOpen notification settings
+
+ Please install a file management utility to browse files
\ No newline at end of file
diff --git a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt
index 30bf09a3..ad958204 100644
--- a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt
+++ b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt
@@ -3,6 +3,7 @@ package org.amnezia.vpn
import android.Manifest
import android.annotation.SuppressLint
import android.app.AlertDialog
+import android.content.ActivityNotFoundException
import android.content.ComponentName
import android.content.Intent
import android.content.Intent.EXTRA_MIME_TYPES
@@ -10,6 +11,7 @@ import android.content.Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
import android.content.ServiceConnection
import android.content.pm.PackageManager
import android.graphics.Bitmap
+import android.net.Uri
import android.net.VpnService
import android.os.Build
import android.os.Bundle
@@ -18,7 +20,13 @@ import android.os.IBinder
import android.os.Looper
import android.os.Message
import android.os.Messenger
+import android.os.ParcelFileDescriptor
+import android.os.SystemClock
+import android.provider.OpenableColumns
import android.provider.Settings
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewGroup
import android.view.WindowManager.LayoutParams
import android.webkit.MimeTypeMap
import android.widget.Toast
@@ -27,6 +35,7 @@ import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat
import java.io.IOException
import kotlin.LazyThreadSafetyMode.NONE
+import kotlin.coroutines.CoroutineContext
import kotlin.text.RegexOption.IGNORE_CASE
import AppListProvider
import kotlinx.coroutines.CompletableDeferred
@@ -67,6 +76,7 @@ class AmneziaActivity : QtActivity() {
private var isServiceConnected = false
private var isInBoundState = false
private lateinit var vpnServiceMessenger: IpcMessenger
+ private var pfd: ParcelFileDescriptor? = null
private val actionResultHandlers = mutableMapOf()
private val permissionRequestHandlers = mutableMapOf()
@@ -487,21 +497,25 @@ class AmneziaActivity : QtActivity() {
type = "text/*"
putExtra(Intent.EXTRA_TITLE, fileName)
}.also {
- startActivityForResult(it, CREATE_FILE_ACTION_CODE, ActivityResultHandler(
- onSuccess = {
- it?.data?.let { uri ->
- Log.v(TAG, "Save file to $uri")
- try {
- contentResolver.openOutputStream(uri)?.use { os ->
- os.bufferedWriter().use { it.write(data) }
+ try {
+ startActivityForResult(it, CREATE_FILE_ACTION_CODE, ActivityResultHandler(
+ onSuccess = {
+ it?.data?.let { uri ->
+ Log.v(TAG, "Save file to $uri")
+ try {
+ contentResolver.openOutputStream(uri)?.use { os ->
+ os.bufferedWriter().use { it.write(data) }
+ }
+ } catch (e: IOException) {
+ Log.e(TAG, "Failed to save file $uri: $e")
+ // todo: send error to Qt
}
- } catch (e: IOException) {
- Log.e(TAG, "Failed to save file $uri: $e")
- // todo: send error to Qt
}
}
- }
- ))
+ ))
+ } catch (_: ActivityNotFoundException) {
+ Toast.makeText(this@AmneziaActivity, "Unsupported", Toast.LENGTH_LONG).show()
+ }
}
}
}
@@ -510,35 +524,46 @@ class AmneziaActivity : QtActivity() {
fun openFile(filter: String?) {
Log.v(TAG, "Open file with filter: $filter")
mainScope.launch {
- val mimeTypes = if (!filter.isNullOrEmpty()) {
- val extensionRegex = "\\*\\.([a-z0-9]+)".toRegex(IGNORE_CASE)
- val mime = MimeTypeMap.getSingleton()
- extensionRegex.findAll(filter).map {
- it.groups[1]?.value?.let { mime.getMimeTypeFromExtension(it) } ?: "*/*"
- }.toSet()
- } else emptySet()
+ val intent = if (!isOnTv()) {
+ val mimeTypes = if (!filter.isNullOrEmpty()) {
+ val extensionRegex = "\\*\\.([a-z0-9]+)".toRegex(IGNORE_CASE)
+ val mime = MimeTypeMap.getSingleton()
+ extensionRegex.findAll(filter).map {
+ it.groups[1]?.value?.let { mime.getMimeTypeFromExtension(it) } ?: "*/*"
+ }.toSet()
+ } else emptySet()
- Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
- addCategory(Intent.CATEGORY_OPENABLE)
- Log.v(TAG, "File mimyType filter: $mimeTypes")
- if ("*/*" in mimeTypes) {
- type = "*/*"
- } else {
- when (mimeTypes.size) {
- 1 -> type = mimeTypes.first()
+ Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
+ addCategory(Intent.CATEGORY_OPENABLE)
+ Log.v(TAG, "File mimyType filter: $mimeTypes")
+ if ("*/*" in mimeTypes) {
+ type = "*/*"
+ } else {
+ when (mimeTypes.size) {
+ 1 -> type = mimeTypes.first()
- in 2..Int.MAX_VALUE -> {
- type = "*/*"
- putExtra(EXTRA_MIME_TYPES, mimeTypes.toTypedArray())
+ in 2..Int.MAX_VALUE -> {
+ type = "*/*"
+ putExtra(EXTRA_MIME_TYPES, mimeTypes.toTypedArray())
+ }
+
+ else -> type = "*/*"
}
-
- else -> type = "*/*"
}
}
- }.also {
- startActivityForResult(it, OPEN_FILE_ACTION_CODE, ActivityResultHandler(
+ } else {
+ Intent(this@AmneziaActivity, TvFilePicker::class.java)
+ }
+
+ try {
+ startActivityForResult(intent, OPEN_FILE_ACTION_CODE, ActivityResultHandler(
onAny = {
- val uri = it?.data?.toString() ?: ""
+ if (isOnTv() && it?.hasExtra("activityNotFound") == true) {
+ showNoFileBrowserAlertDialog()
+ }
+ val uri = it?.data?.apply {
+ grantUriPermission(packageName, this, Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ }?.toString() ?: ""
Log.v(TAG, "Open file: $uri")
mainScope.launch {
qtInitialized.await()
@@ -546,10 +571,68 @@ class AmneziaActivity : QtActivity() {
}
}
))
+ } catch (_: ActivityNotFoundException) {
+ showNoFileBrowserAlertDialog()
+ mainScope.launch {
+ qtInitialized.await()
+ QtAndroidController.onFileOpened("")
+ }
}
}
}
+ private fun showNoFileBrowserAlertDialog() {
+ AlertDialog.Builder(this)
+ .setMessage(R.string.tvNoFileBrowser)
+ .setCancelable(false)
+ .setPositiveButton(android.R.string.ok) { _, _ ->
+ try {
+ startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("market://webstoreredirect")))
+ } catch (_: Throwable) {}
+ }
+ .show()
+ }
+
+ @Suppress("unused")
+ fun getFd(fileName: String): Int {
+ Log.v(TAG, "Get fd for $fileName")
+ return blockingCall {
+ try {
+ pfd = contentResolver.openFileDescriptor(Uri.parse(fileName), "r")
+ pfd?.fd ?: -1
+ } catch (e: Exception) {
+ Log.e(TAG, "Failed to get fd: $e")
+ -1
+ }
+ }
+ }
+
+ @Suppress("unused")
+ fun closeFd() {
+ Log.v(TAG, "Close fd")
+ mainScope.launch {
+ pfd?.close()
+ pfd = null
+ }
+ }
+
+ @Suppress("unused")
+ fun getFileName(uri: String): String {
+ Log.v(TAG, "Get file name for uri: $uri")
+ return blockingCall {
+ try {
+ contentResolver.query(Uri.parse(uri), arrayOf(OpenableColumns.DISPLAY_NAME), null, null, null)?.use { cursor ->
+ if (cursor.moveToFirst() && !cursor.isNull(0)) {
+ return@blockingCall cursor.getString(0) ?: ""
+ }
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "Failed to get file name: $e")
+ }
+ ""
+ }
+ }
+
@Suppress("unused")
@SuppressLint("UnsupportedChromeOsCameraSystemFeature")
fun isCameraPresent(): Boolean = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)
@@ -694,9 +777,60 @@ class AmneziaActivity : QtActivity() {
}
}
+ // method to workaround Qt's problem with calling the keyboard on TVs
+ @Suppress("unused")
+ fun sendTouch(x: Float, y: Float) {
+ Log.v(TAG, "Send touch: $x, $y")
+ blockingCall {
+ findQtWindow(window.decorView)?.let {
+ Log.v(TAG, "Send touch to $it")
+ it.dispatchTouchEvent(createEvent(x, y, SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN))
+ it.dispatchTouchEvent(createEvent(x, y, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP))
+ }
+ }
+ }
+
+ private fun findQtWindow(view: View): View? {
+ Log.v(TAG, "findQtWindow: process $view")
+ if (view::class.simpleName == "QtWindow") return view
+ else if (view is ViewGroup) {
+ for (i in 0 until view.childCount) {
+ val result = findQtWindow(view.getChildAt(i))
+ if (result != null) return result
+ }
+ return null
+ } else return null
+ }
+
+ private fun createEvent(x: Float, y: Float, eventTime: Long, action: Int): MotionEvent =
+ MotionEvent.obtain(
+ eventTime,
+ eventTime,
+ action,
+ 1,
+ arrayOf(MotionEvent.PointerProperties().apply {
+ id = 0
+ toolType = MotionEvent.TOOL_TYPE_FINGER
+ }),
+ arrayOf(MotionEvent.PointerCoords().apply {
+ this.x = x
+ this.y = y
+ pressure = 1f
+ size = 1f
+ }),
+ 0, 0, 1.0f, 1.0f, 0, 0, 0,0
+ )
+
/**
* Utils methods
*/
+ private fun blockingCall(
+ context: CoroutineContext = Dispatchers.Main.immediate,
+ block: suspend () -> T
+ ) = runBlocking {
+ mainScope.async(context) { block() }.await()
+ }
+
companion object {
private fun actionCodeToString(actionCode: Int): String =
when (actionCode) {
diff --git a/client/android/src/org/amnezia/vpn/TvFilePicker.kt b/client/android/src/org/amnezia/vpn/TvFilePicker.kt
new file mode 100644
index 00000000..1ac275eb
--- /dev/null
+++ b/client/android/src/org/amnezia/vpn/TvFilePicker.kt
@@ -0,0 +1,45 @@
+package org.amnezia.vpn
+
+import android.content.ActivityNotFoundException
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.result.contract.ActivityResultContracts
+import org.amnezia.vpn.util.Log
+
+private const val TAG = "TvFilePicker"
+
+class TvFilePicker : ComponentActivity() {
+
+ private val fileChooseResultLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) {
+ setResult(RESULT_OK, Intent().apply { data = it })
+ finish()
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ Log.v(TAG, "onCreate")
+ getFile()
+ }
+
+ override fun onNewIntent(intent: Intent) {
+ super.onNewIntent(intent)
+ Log.v(TAG, "onNewIntent")
+ getFile()
+ }
+
+ private fun getFile() {
+ try {
+ Log.v(TAG, "getFile")
+ fileChooseResultLauncher.launch("*/*")
+ } catch (_: ActivityNotFoundException) {
+ Log.w(TAG, "Activity not found")
+ setResult(RESULT_CANCELED, Intent().apply { putExtra("activityNotFound", true) })
+ finish()
+ } catch (e: Exception) {
+ Log.e(TAG, "Failed to get file: $e")
+ setResult(RESULT_CANCELED)
+ finish()
+ }
+ }
+}
diff --git a/client/configurators/wireguard_configurator.cpp b/client/configurators/wireguard_configurator.cpp
index 3f96e74c..1bca973d 100644
--- a/client/configurators/wireguard_configurator.cpp
+++ b/client/configurators/wireguard_configurator.cpp
@@ -120,7 +120,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
}
}
- QString subnetIp = containerConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress);
+ QString subnetIp = containerConfig.value(m_protocolName).toObject().value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress);
{
QStringList l = subnetIp.split(".", Qt::SkipEmptyParts);
if (l.isEmpty()) {
diff --git a/client/configurators/xray_configurator.cpp b/client/configurators/xray_configurator.cpp
index 786da47c..514aa821 100644
--- a/client/configurators/xray_configurator.cpp
+++ b/client/configurators/xray_configurator.cpp
@@ -3,38 +3,169 @@
#include
#include
#include
+#include
+#include "logger.h"
#include "containers/containers_defs.h"
#include "core/controllers/serverController.h"
#include "core/scripts_registry.h"
+namespace {
+Logger logger("XrayConfigurator");
+}
+
XrayConfigurator::XrayConfigurator(std::shared_ptr settings, const QSharedPointer &serverController, QObject *parent)
: ConfiguratorBase(settings, serverController, parent)
{
}
-QString XrayConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
- ErrorCode &errorCode)
+QString XrayConfigurator::prepareServerConfig(const ServerCredentials &credentials, DockerContainer container,
+ const QJsonObject &containerConfig, ErrorCode &errorCode)
{
- QString config = m_serverController->replaceVars(amnezia::scriptData(ProtocolScriptType::xray_template, container),
- m_serverController->genVarsForScript(credentials, container, containerConfig));
-
- QString xrayPublicKey =
- m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::PublicKeyPath, errorCode);
- xrayPublicKey.replace("\n", "");
-
- QString xrayUuid = m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::uuidPath, errorCode);
- xrayUuid.replace("\n", "");
-
- QString xrayShortId =
- m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::shortidPath, errorCode);
- xrayShortId.replace("\n", "");
-
+ // Generate new UUID for client
+ QString clientId = QUuid::createUuid().toString(QUuid::WithoutBraces);
+
+ // Get current server config
+ QString currentConfig = m_serverController->getTextFileFromContainer(
+ container, credentials, amnezia::protocols::xray::serverConfigPath, errorCode);
+
if (errorCode != ErrorCode::NoError) {
+ logger.error() << "Failed to get server config file";
return "";
}
- config.replace("$XRAY_CLIENT_ID", xrayUuid);
+ // Parse current config as JSON
+ QJsonDocument doc = QJsonDocument::fromJson(currentConfig.toUtf8());
+ if (doc.isNull() || !doc.isObject()) {
+ logger.error() << "Failed to parse server config JSON";
+ errorCode = ErrorCode::InternalError;
+ return "";
+ }
+
+ QJsonObject serverConfig = doc.object();
+
+ // Validate server config structure
+ if (!serverConfig.contains("inbounds")) {
+ logger.error() << "Server config missing 'inbounds' field";
+ errorCode = ErrorCode::InternalError;
+ return "";
+ }
+
+ QJsonArray inbounds = serverConfig["inbounds"].toArray();
+ if (inbounds.isEmpty()) {
+ logger.error() << "Server config has empty 'inbounds' array";
+ errorCode = ErrorCode::InternalError;
+ return "";
+ }
+
+ QJsonObject inbound = inbounds[0].toObject();
+ if (!inbound.contains("settings")) {
+ logger.error() << "Inbound missing 'settings' field";
+ errorCode = ErrorCode::InternalError;
+ return "";
+ }
+
+ QJsonObject settings = inbound["settings"].toObject();
+ if (!settings.contains("clients")) {
+ logger.error() << "Settings missing 'clients' field";
+ errorCode = ErrorCode::InternalError;
+ return "";
+ }
+
+ QJsonArray clients = settings["clients"].toArray();
+
+ // Create configuration for new client
+ QJsonObject clientConfig {
+ {"id", clientId},
+ {"flow", "xtls-rprx-vision"}
+ };
+
+ clients.append(clientConfig);
+
+ // Update config
+ settings["clients"] = clients;
+ inbound["settings"] = settings;
+ inbounds[0] = inbound;
+ serverConfig["inbounds"] = inbounds;
+
+ // Save updated config to server
+ QString updatedConfig = QJsonDocument(serverConfig).toJson();
+ errorCode = m_serverController->uploadTextFileToContainer(
+ container,
+ credentials,
+ updatedConfig,
+ amnezia::protocols::xray::serverConfigPath,
+ libssh::ScpOverwriteMode::ScpOverwriteExisting
+ );
+ if (errorCode != ErrorCode::NoError) {
+ logger.error() << "Failed to upload updated config";
+ return "";
+ }
+
+ // Restart container
+ QString restartScript = QString("sudo docker restart $CONTAINER_NAME");
+ errorCode = m_serverController->runScript(
+ credentials,
+ m_serverController->replaceVars(restartScript, m_serverController->genVarsForScript(credentials, container))
+ );
+
+ if (errorCode != ErrorCode::NoError) {
+ logger.error() << "Failed to restart container";
+ return "";
+ }
+
+ return clientId;
+}
+
+QString XrayConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
+ const QJsonObject &containerConfig, ErrorCode &errorCode)
+{
+ // Get client ID from prepareServerConfig
+ QString xrayClientId = prepareServerConfig(credentials, container, containerConfig, errorCode);
+ if (errorCode != ErrorCode::NoError || xrayClientId.isEmpty()) {
+ logger.error() << "Failed to prepare server config";
+ errorCode = ErrorCode::InternalError;
+ return "";
+ }
+
+ QString config = m_serverController->replaceVars(amnezia::scriptData(ProtocolScriptType::xray_template, container),
+ m_serverController->genVarsForScript(credentials, container, containerConfig));
+
+ if (config.isEmpty()) {
+ logger.error() << "Failed to get config template";
+ errorCode = ErrorCode::InternalError;
+ return "";
+ }
+
+ QString xrayPublicKey =
+ m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::PublicKeyPath, errorCode);
+ if (errorCode != ErrorCode::NoError || xrayPublicKey.isEmpty()) {
+ logger.error() << "Failed to get public key";
+ errorCode = ErrorCode::InternalError;
+ return "";
+ }
+ xrayPublicKey.replace("\n", "");
+
+ QString xrayShortId =
+ m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::shortidPath, errorCode);
+ if (errorCode != ErrorCode::NoError || xrayShortId.isEmpty()) {
+ logger.error() << "Failed to get short ID";
+ errorCode = ErrorCode::InternalError;
+ return "";
+ }
+ xrayShortId.replace("\n", "");
+
+ // Validate all required variables are present
+ if (!config.contains("$XRAY_CLIENT_ID") || !config.contains("$XRAY_PUBLIC_KEY") || !config.contains("$XRAY_SHORT_ID")) {
+ logger.error() << "Config template missing required variables:"
+ << "XRAY_CLIENT_ID:" << !config.contains("$XRAY_CLIENT_ID")
+ << "XRAY_PUBLIC_KEY:" << !config.contains("$XRAY_PUBLIC_KEY")
+ << "XRAY_SHORT_ID:" << !config.contains("$XRAY_SHORT_ID");
+ errorCode = ErrorCode::InternalError;
+ return "";
+ }
+
+ config.replace("$XRAY_CLIENT_ID", xrayClientId);
config.replace("$XRAY_PUBLIC_KEY", xrayPublicKey);
config.replace("$XRAY_SHORT_ID", xrayShortId);
diff --git a/client/configurators/xray_configurator.h b/client/configurators/xray_configurator.h
index 2acfdf71..8ed4e775 100644
--- a/client/configurators/xray_configurator.h
+++ b/client/configurators/xray_configurator.h
@@ -14,6 +14,10 @@ public:
QString createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
ErrorCode &errorCode);
+
+private:
+ QString prepareServerConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
+ ErrorCode &errorCode);
};
#endif // XRAY_CONFIGURATOR_H
diff --git a/client/core/controllers/apiController.cpp b/client/core/controllers/apiController.cpp
index 75a3f93c..6562632a 100644
--- a/client/core/controllers/apiController.cpp
+++ b/client/core/controllers/apiController.cpp
@@ -50,6 +50,8 @@ namespace
constexpr char authData[] = "auth_data";
}
+ const int requestTimeoutMsecs = 12 * 1000; // 12 secs
+
ErrorCode checkErrors(const QList &sslErrors, QNetworkReply *reply)
{
if (!sslErrors.empty()) {
@@ -177,7 +179,7 @@ void ApiController::fillServerConfig(const QString &protocol, const ApiControlle
QStringList ApiController::getProxyUrls()
{
QNetworkRequest request;
- request.setTransferTimeout(7000);
+ request.setTransferTimeout(requestTimeoutMsecs);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QEventLoop wait;
@@ -280,7 +282,7 @@ void ApiController::updateServerConfigFromApi(const QString &installationUuid, c
if (serverConfig.value(config_key::configVersion).toInt()) {
QNetworkRequest request;
- request.setTransferTimeout(7000);
+ request.setTransferTimeout(requestTimeoutMsecs);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setRawHeader("Authorization", "Api-Key " + serverConfig.value(configKey::accessToken).toString().toUtf8());
QString endpoint = serverConfig.value(configKey::apiEdnpoint).toString();
@@ -336,7 +338,7 @@ ErrorCode ApiController::getServicesList(QByteArray &responseBody)
#endif
QNetworkRequest request;
- request.setTransferTimeout(7000);
+ request.setTransferTimeout(requestTimeoutMsecs);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setUrl(QString("%1v1/services").arg(m_gatewayEndpoint));
@@ -377,6 +379,13 @@ ErrorCode ApiController::getServicesList(QByteArray &responseBody)
auto errorCode = checkErrors(sslErrors, reply);
reply->deleteLater();
+
+ if (errorCode == ErrorCode::NoError) {
+ if (!responseBody.contains("services")) {
+ return ErrorCode::ApiServicesMissingError;
+ }
+ }
+
return errorCode;
}
@@ -390,7 +399,7 @@ ErrorCode ApiController::getConfigForService(const QString &installationUuid, co
#endif
QNetworkRequest request;
- request.setTransferTimeout(7000);
+ request.setTransferTimeout(requestTimeoutMsecs);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setUrl(QString("%1v1/config").arg(m_gatewayEndpoint));
diff --git a/client/core/controllers/serverController.cpp b/client/core/controllers/serverController.cpp
index b6795a01..7219ff7d 100644
--- a/client/core/controllers/serverController.cpp
+++ b/client/core/controllers/serverController.cpp
@@ -346,7 +346,9 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
}
if (container == DockerContainer::Awg) {
- if ((oldProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort)
+ if ((oldProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress)
+ != newProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress))
+ || (oldProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort)
!= newProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort))
|| (oldProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount)
!= newProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount))
@@ -370,8 +372,10 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
}
if (container == DockerContainer::WireGuard) {
- if (oldProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort)
- != newProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort))
+ if ((oldProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress)
+ != newProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress))
+ || (oldProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort)
+ != newProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort)))
return true;
}
@@ -607,6 +611,8 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
vars.append({ { "$SFTP_PASSWORD", sftpConfig.value(config_key::password).toString() } });
// Amnezia wireguard vars
+ vars.append({ { "$AWG_SUBNET_IP",
+ amneziaWireguarConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress) } });
vars.append({ { "$AWG_SERVER_PORT", amneziaWireguarConfig.value(config_key::port).toString(protocols::awg::defaultPort) } });
vars.append({ { "$JUNK_PACKET_COUNT", amneziaWireguarConfig.value(config_key::junkPacketCount).toString() } });
diff --git a/client/core/defs.h b/client/core/defs.h
index d00d347b..c0db2e12 100644
--- a/client/core/defs.h
+++ b/client/core/defs.h
@@ -109,6 +109,7 @@ namespace amnezia
ApiConfigSslError = 1104,
ApiMissingAgwPublicKey = 1105,
ApiConfigDecryptionError = 1106,
+ ApiServicesMissingError = 1107,
// QFile errors
OpenError = 1200,
diff --git a/client/core/errorstrings.cpp b/client/core/errorstrings.cpp
index 49534606..70f433c6 100644
--- a/client/core/errorstrings.cpp
+++ b/client/core/errorstrings.cpp
@@ -63,7 +63,8 @@ QString errorString(ErrorCode code) {
case (ErrorCode::ApiConfigTimeoutError): errorMessage = QObject::tr("Server response timeout on api request"); break;
case (ErrorCode::ApiMissingAgwPublicKey): errorMessage = QObject::tr("Missing AGW public key"); break;
case (ErrorCode::ApiConfigDecryptionError): errorMessage = QObject::tr("Failed to decrypt response payload"); break;
-
+ case (ErrorCode::ApiServicesMissingError): errorMessage = QObject::tr("Missing list of available services"); break;
+
// QFile errors
case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;
case(ErrorCode::ReadError): errorMessage = QObject::tr("QFile error: An error occurred when reading from the file"); break;
diff --git a/client/core/serialization/vmess_new.cpp b/client/core/serialization/vmess_new.cpp
index 6f3ec3e1..68d32203 100644
--- a/client/core/serialization/vmess_new.cpp
+++ b/client/core/serialization/vmess_new.cpp
@@ -104,7 +104,7 @@ QJsonObject Deserialize(const QString &vmessStr, QString *alias, QString *errMes
server.users.first().security = "auto";
}
- const static auto getQueryValue = [&query](const QString &key, const QString &defaultValue) {
+ const auto getQueryValue = [&query](const QString &key, const QString &defaultValue) {
if (query.hasQueryItem(key))
return query.queryItemValue(key, QUrl::FullyDecoded);
else
diff --git a/client/daemon/daemon.cpp b/client/daemon/daemon.cpp
index a234860b..081a7a90 100644
--- a/client/daemon/daemon.cpp
+++ b/client/daemon/daemon.cpp
@@ -114,12 +114,23 @@ bool Daemon::activate(const InterfaceConfig& 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));
@@ -135,15 +146,6 @@ bool Daemon::activate(const InterfaceConfig& config) {
return false;
}
- if (supportIPUtils()) {
- if (!iputils()->addInterfaceIPs(config)) {
- return false;
- }
- if (!iputils()->setMTUAndUp(config)) {
- return false;
- }
- }
-
// set routing
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
if (!wgutils()->updateRoutePrefix(ip)) {
diff --git a/client/daemon/daemon.h b/client/daemon/daemon.h
index 3d418d70..757c9ff0 100644
--- a/client/daemon/daemon.h
+++ b/client/daemon/daemon.h
@@ -8,6 +8,8 @@
#include
#include
+#include "daemon/daemonerrors.h"
+#include "daemonerrors.h"
#include "dnsutils.h"
#include "interfaceconfig.h"
#include "iputils.h"
@@ -51,7 +53,7 @@ class Daemon : public QObject {
*/
void activationFailure();
void disconnected();
- void backendFailure();
+ void backendFailure(DaemonError reason = DaemonError::ERROR_FATAL);
private:
bool maybeUpdateResolvers(const InterfaceConfig& config);
diff --git a/client/daemon/daemonerrors.h b/client/daemon/daemonerrors.h
new file mode 100644
index 00000000..3d271244
--- /dev/null
+++ b/client/daemon/daemonerrors.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
+
+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,
+};
diff --git a/client/daemon/daemonlocalserverconnection.cpp b/client/daemon/daemonlocalserverconnection.cpp
index edbc4c9b..bc57c71f 100644
--- a/client/daemon/daemonlocalserverconnection.cpp
+++ b/client/daemon/daemonlocalserverconnection.cpp
@@ -159,9 +159,10 @@ void DaemonLocalServerConnection::disconnected() {
write(obj);
}
-void DaemonLocalServerConnection::backendFailure() {
+void DaemonLocalServerConnection::backendFailure(DaemonError err) {
QJsonObject obj;
obj.insert("type", "backendFailure");
+ obj.insert("errorCode", static_cast(err));
write(obj);
}
diff --git a/client/daemon/daemonlocalserverconnection.h b/client/daemon/daemonlocalserverconnection.h
index ec32df75..34170cb3 100644
--- a/client/daemon/daemonlocalserverconnection.h
+++ b/client/daemon/daemonlocalserverconnection.h
@@ -7,6 +7,8 @@
#include
+#include "daemonerrors.h"
+
class QLocalSocket;
class DaemonLocalServerConnection final : public QObject {
@@ -23,7 +25,7 @@ class DaemonLocalServerConnection final : public QObject {
void connected(const QString& pubkey);
void disconnected();
- void backendFailure();
+ void backendFailure(DaemonError err);
void write(const QJsonObject& obj);
diff --git a/client/daemon/wireguardutils.h b/client/daemon/wireguardutils.h
index cdee40ef..b600a923 100644
--- a/client/daemon/wireguardutils.h
+++ b/client/daemon/wireguardutils.h
@@ -45,9 +45,11 @@ class WireguardUtils : public QObject {
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& addresses) = 0;
};
#endif // WIREGUARDUTILS_H
diff --git a/client/images/controls/external-link.svg b/client/images/controls/external-link.svg
new file mode 100644
index 00000000..6a51c902
--- /dev/null
+++ b/client/images/controls/external-link.svg
@@ -0,0 +1,5 @@
+
diff --git a/client/mozilla/localsocketcontroller.cpp b/client/mozilla/localsocketcontroller.cpp
index 5e9f0f97..1081bcae 100644
--- a/client/mozilla/localsocketcontroller.cpp
+++ b/client/mozilla/localsocketcontroller.cpp
@@ -1,9 +1,10 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-#include "protocols/protocols_defs.h"
#include "localsocketcontroller.h"
+#include
+
#include
#include
#include
@@ -17,6 +18,9 @@
#include "leakdetector.h"
#include "logger.h"
#include "models/server.h"
+#include "daemon/daemonerrors.h"
+
+#include "protocols/protocols_defs.h"
// How many times do we try to reconnect.
constexpr int MAX_CONNECTION_RETRY = 10;
@@ -451,8 +455,39 @@ void LocalSocketController::parseCommand(const QByteArray& command) {
}
if (type == "backendFailure") {
- qCritical() << "backendFailure";
- return;
+ if (!obj.contains("errorCode")) {
+ // report a generic error if we dont know what it is.
+ logger.error() << "generic backend failure error";
+ // REPORTERROR(ErrorHandler::ControllerError, "controller");
+ return;
+ }
+ auto errorCode = static_cast(obj["errorCode"].toInt());
+ if (errorCode >= (uint8_t)DaemonError::DAEMON_ERROR_MAX) {
+ // Also report a generic error if the code is invalid.
+ logger.error() << "invalid backend failure error code";
+ // REPORTERROR(ErrorHandler::ControllerError, "controller");
+ return;
+ }
+ switch (static_cast(errorCode)) {
+ case DaemonError::ERROR_NONE:
+ [[fallthrough]];
+ case DaemonError::ERROR_FATAL:
+ logger.error() << "generic backend failure error (fatal or error none)";
+ // REPORTERROR(ErrorHandler::ControllerError, "controller");
+ break;
+ case DaemonError::ERROR_SPLIT_TUNNEL_INIT_FAILURE:
+ [[fallthrough]];
+ case DaemonError::ERROR_SPLIT_TUNNEL_START_FAILURE:
+ [[fallthrough]];
+ case DaemonError::ERROR_SPLIT_TUNNEL_EXCLUDE_FAILURE:
+ logger.error() << "split tunnel backend failure error";
+ //REPORTERROR(ErrorHandler::SplitTunnelError, "controller");
+ break;
+ case DaemonError::DAEMON_ERROR_MAX:
+ // We should not get here.
+ Q_ASSERT(false);
+ break;
+ }
}
if (type == "logs") {
diff --git a/client/platforms/android/android_controller.cpp b/client/platforms/android/android_controller.cpp
index 2790eb1b..d9195f87 100644
--- a/client/platforms/android/android_controller.cpp
+++ b/client/platforms/android/android_controller.cpp
@@ -163,9 +163,7 @@ QString AndroidController::openFile(const QString &filter)
QString fileName;
connect(this, &AndroidController::fileOpened, this,
[&fileName, &wait](const QString &uri) {
- qDebug() << "Android event: file opened; uri:" << uri;
- fileName = QQmlFile::urlToLocalFileOrQrc(uri);
- qDebug() << "Android opened filename:" << fileName;
+ fileName = uri;
wait.quit();
},
static_cast(Qt::QueuedConnection | Qt::SingleShotConnection));
@@ -175,6 +173,25 @@ QString AndroidController::openFile(const QString &filter)
return fileName;
}
+int AndroidController::getFd(const QString &fileName)
+{
+ return callActivityMethod("getFd", "(Ljava/lang/String;)I",
+ QJniObject::fromString(fileName).object());
+}
+
+void AndroidController::closeFd()
+{
+ callActivityMethod("closeFd", "()V");
+}
+
+QString AndroidController::getFileName(const QString &uri)
+{
+ auto fileName = callActivityMethod("getFileName", "(Ljava/lang/String;)Ljava/lang/String;",
+ QJniObject::fromString(uri).object());
+ QJniEnvironment env;
+ return AndroidUtils::convertJString(env.jniEnv(), fileName.object());
+}
+
bool AndroidController::isCameraPresent()
{
return callActivityMethod("isCameraPresent", "()Z");
@@ -287,6 +304,11 @@ bool AndroidController::requestAuthentication()
return result;
}
+void AndroidController::sendTouch(float x, float y)
+{
+ callActivityMethod("sendTouch", "(FF)V", x, y);
+}
+
// Moving log processing to the Android side
jclass AndroidController::log;
jmethodID AndroidController::logDebug;
diff --git a/client/platforms/android/android_controller.h b/client/platforms/android/android_controller.h
index 759c9c3f..5707771e 100644
--- a/client/platforms/android/android_controller.h
+++ b/client/platforms/android/android_controller.h
@@ -34,6 +34,9 @@ public:
void resetLastServer(int serverIndex);
void saveFile(const QString &fileName, const QString &data);
QString openFile(const QString &filter);
+ int getFd(const QString &fileName);
+ void closeFd();
+ QString getFileName(const QString &uri);
bool isCameraPresent();
bool isOnTv();
void startQrReaderActivity();
@@ -48,6 +51,7 @@ public:
bool isNotificationPermissionGranted();
void requestNotificationPermission();
bool requestAuthentication();
+ void sendTouch(float x, float y);
static bool initLogging();
static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message);
diff --git a/client/platforms/linux/daemon/linuxfirewall.cpp b/client/platforms/linux/daemon/linuxfirewall.cpp
index 393c24f2..96194bc7 100644
--- a/client/platforms/linux/daemon/linuxfirewall.cpp
+++ b/client/platforms/linux/daemon/linuxfirewall.cpp
@@ -196,6 +196,8 @@ QStringList LinuxFirewall::getDNSRules(const QStringList& servers)
result << QStringLiteral("-o amn0+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server);
result << QStringLiteral("-o tun0+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server);
result << QStringLiteral("-o tun0+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server);
+ result << QStringLiteral("-o tun2+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server);
+ result << QStringLiteral("-o tun2+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server);
}
return result;
}
@@ -277,6 +279,7 @@ void LinuxFirewall::install()
installAnchor(Both, QStringLiteral("200.allowVPN"), {
QStringLiteral("-o amn0+ -j ACCEPT"),
QStringLiteral("-o tun0+ -j ACCEPT"),
+ QStringLiteral("-o tun2+ -j ACCEPT"),
});
installAnchor(IPv4, QStringLiteral("120.blockNets"), {});
diff --git a/client/platforms/linux/daemon/wireguardutilslinux.cpp b/client/platforms/linux/daemon/wireguardutilslinux.cpp
index 460a7fe1..1528d901 100644
--- a/client/platforms/linux/daemon/wireguardutilslinux.cpp
+++ b/client/platforms/linux/daemon/wireguardutilslinux.cpp
@@ -297,31 +297,6 @@ QList WireguardUtilsLinux::getPeerStatus() {
return peerList;
}
-
-void WireguardUtilsLinux::applyFirewallRules(FirewallParams& params)
-{
- // double-check + ensure our firewall is installed and enabled
- if (!LinuxFirewall::isInstalled()) LinuxFirewall::install();
-
- // Note: rule precedence is handled inside IpTablesFirewall
- LinuxFirewall::ensureRootAnchorPriority();
-
- LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("000.allowLoopback"), true);
- LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("100.blockAll"), params.blockAll);
- LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("110.allowNets"), params.allowNets);
- LinuxFirewall::updateAllowNets(params.allowAddrs);
- LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("120.blockNets"), params.blockNets);
- LinuxFirewall::updateBlockNets(params.blockAddrs);
- LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("200.allowVPN"), true);
- LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv6, QStringLiteral("250.blockIPv6"), true);
- LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("290.allowDHCP"), true);
- LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("300.allowLAN"), true);
- LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("310.blockDNS"), true);
- LinuxFirewall::updateDNSServers(params.dnsServers);
- LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("320.allowDNS"), true);
- LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("400.allowPIA"), true);
-}
-
bool WireguardUtilsLinux::updateRoutePrefix(const IPAddress& prefix) {
if (!m_rtmonitor) {
return false;
@@ -377,6 +352,26 @@ bool WireguardUtilsLinux::deleteExclusionRoute(const IPAddress& prefix) {
return m_rtmonitor->deleteExclusionRoute(prefix);
}
+bool WireguardUtilsLinux::excludeLocalNetworks(const QList& routes) {
+ if (!m_rtmonitor) {
+ return false;
+ }
+
+ // Explicitly discard LAN traffic that makes its way into the tunnel. This
+ // doesn't really exclude the LAN traffic, we just don't take any action to
+ // overrule the routes of other interfaces.
+ bool result = true;
+ for (const auto& prefix : routes) {
+ logger.error() << "Attempting to exclude:" << prefix.toString();
+ if (!m_rtmonitor->insertRoute(prefix)) {
+ result = false;
+ }
+ }
+
+ // TODO: A kill switch would be nice though :)
+ return result;
+}
+
QString WireguardUtilsLinux::uapiCommand(const QString& command) {
QLocalSocket socket;
QTimer uapiTimeout;
@@ -450,3 +445,27 @@ QString WireguardUtilsLinux::waitForTunnelName(const QString& filename) {
return QString();
}
+
+void WireguardUtilsLinux::applyFirewallRules(FirewallParams& params)
+{
+ // double-check + ensure our firewall is installed and enabled
+ if (!LinuxFirewall::isInstalled()) LinuxFirewall::install();
+
+ // Note: rule precedence is handled inside IpTablesFirewall
+ LinuxFirewall::ensureRootAnchorPriority();
+
+ LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("000.allowLoopback"), true);
+ LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("100.blockAll"), params.blockAll);
+ LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("110.allowNets"), params.allowNets);
+ LinuxFirewall::updateAllowNets(params.allowAddrs);
+ LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("120.blockNets"), params.blockNets);
+ LinuxFirewall::updateBlockNets(params.blockAddrs);
+ LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("200.allowVPN"), true);
+ LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv6, QStringLiteral("250.blockIPv6"), true);
+ LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("290.allowDHCP"), true);
+ LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("300.allowLAN"), true);
+ LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("310.blockDNS"), true);
+ LinuxFirewall::updateDNSServers(params.dnsServers);
+ LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("320.allowDNS"), true);
+ LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("400.allowPIA"), true);
+}
diff --git a/client/platforms/linux/daemon/wireguardutilslinux.h b/client/platforms/linux/daemon/wireguardutilslinux.h
index 9746ea4b..c324111d 100644
--- a/client/platforms/linux/daemon/wireguardutilslinux.h
+++ b/client/platforms/linux/daemon/wireguardutilslinux.h
@@ -37,6 +37,9 @@ public:
bool addExclusionRoute(const IPAddress& prefix) override;
bool deleteExclusionRoute(const IPAddress& prefix) override;
+
+ bool excludeLocalNetworks(const QList& lanAddressRanges) override;
+
void applyFirewallRules(FirewallParams& params);
signals:
void backendFailure();
diff --git a/client/platforms/macos/daemon/macosroutemonitor.cpp b/client/platforms/macos/daemon/macosroutemonitor.cpp
index 395f008a..bd991c01 100644
--- a/client/platforms/macos/daemon/macosroutemonitor.cpp
+++ b/client/platforms/macos/daemon/macosroutemonitor.cpp
@@ -358,8 +358,8 @@ void MacosRouteMonitor::rtmAppendAddr(struct rt_msghdr* rtm, size_t maxlen,
}
bool MacosRouteMonitor::rtmSendRoute(int action, const IPAddress& prefix,
- unsigned int ifindex,
- const void* gateway) {
+ unsigned int ifindex, const void* gateway,
+ int flags) {
constexpr size_t rtm_max_size = sizeof(struct rt_msghdr) +
sizeof(struct sockaddr_in6) * 2 +
sizeof(struct sockaddr_storage);
@@ -370,7 +370,7 @@ bool MacosRouteMonitor::rtmSendRoute(int action, const IPAddress& prefix,
rtm->rtm_version = RTM_VERSION;
rtm->rtm_type = action;
rtm->rtm_index = ifindex;
- rtm->rtm_flags = RTF_STATIC | RTF_UP;
+ rtm->rtm_flags = flags | RTF_STATIC | RTF_UP;
rtm->rtm_addrs = 0;
rtm->rtm_pid = 0;
rtm->rtm_seq = m_rtseq++;
@@ -490,7 +490,7 @@ bool MacosRouteMonitor::rtmFetchRoutes(int family) {
return false;
}
-bool MacosRouteMonitor::insertRoute(const IPAddress& prefix) {
+bool MacosRouteMonitor::insertRoute(const IPAddress& prefix, int flags) {
struct sockaddr_dl datalink;
memset(&datalink, 0, sizeof(datalink));
datalink.sdl_family = AF_LINK;
@@ -502,11 +502,11 @@ bool MacosRouteMonitor::insertRoute(const IPAddress& prefix) {
datalink.sdl_slen = 0;
memcpy(&datalink.sdl_data, qPrintable(m_ifname), datalink.sdl_nlen);
- return rtmSendRoute(RTM_ADD, prefix, m_ifindex, &datalink);
+ return rtmSendRoute(RTM_ADD, prefix, m_ifindex, &datalink, flags);
}
-bool MacosRouteMonitor::deleteRoute(const IPAddress& prefix) {
- return rtmSendRoute(RTM_DELETE, prefix, m_ifindex, nullptr);
+bool MacosRouteMonitor::deleteRoute(const IPAddress& prefix, int flags) {
+ return rtmSendRoute(RTM_DELETE, prefix, m_ifindex, nullptr, flags);
}
bool MacosRouteMonitor::addExclusionRoute(const IPAddress& prefix) {
diff --git a/client/platforms/macos/daemon/macosroutemonitor.h b/client/platforms/macos/daemon/macosroutemonitor.h
index b2483d76..78396690 100644
--- a/client/platforms/macos/daemon/macosroutemonitor.h
+++ b/client/platforms/macos/daemon/macosroutemonitor.h
@@ -24,8 +24,8 @@ class MacosRouteMonitor final : public QObject {
MacosRouteMonitor(const QString& ifname, QObject* parent = nullptr);
~MacosRouteMonitor();
- bool insertRoute(const IPAddress& prefix);
- bool deleteRoute(const IPAddress& prefix);
+ bool insertRoute(const IPAddress& prefix, int flags = 0);
+ bool deleteRoute(const IPAddress& prefix, int flags = 0);
int interfaceFlags() { return m_ifflags; }
bool addExclusionRoute(const IPAddress& prefix);
@@ -37,7 +37,7 @@ class MacosRouteMonitor final : public QObject {
void handleRtmUpdate(const struct rt_msghdr* msg, const QByteArray& payload);
void handleIfaceInfo(const struct if_msghdr* msg, const QByteArray& payload);
bool rtmSendRoute(int action, const IPAddress& prefix, unsigned int ifindex,
- const void* gateway);
+ const void* gateway, int flags = 0);
bool rtmFetchRoutes(int family);
static void rtmAppendAddr(struct rt_msghdr* rtm, size_t maxlen, int rtaddr,
const void* sa);
diff --git a/client/platforms/macos/daemon/wireguardutilsmacos.cpp b/client/platforms/macos/daemon/wireguardutilsmacos.cpp
index e2802ebc..eae22837 100644
--- a/client/platforms/macos/daemon/wireguardutilsmacos.cpp
+++ b/client/platforms/macos/daemon/wireguardutilsmacos.cpp
@@ -5,6 +5,7 @@
#include "wireguardutilsmacos.h"
#include
+#include
#include
#include
@@ -130,7 +131,6 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) {
}
int err = uapiErrno(uapiCommand(message));
-
if (err != 0) {
logger.error() << "Interface configuration failed:" << strerror(err);
} else {
@@ -211,7 +211,6 @@ bool WireguardUtilsMacos::updatePeer(const InterfaceConfig& config) {
logger.warning() << "Failed to create peer with no endpoints";
return false;
}
-
out << config.m_serverPort << "\n";
out << "replace_allowed_ips=true\n";
@@ -323,10 +322,10 @@ bool WireguardUtilsMacos::deleteRoutePrefix(const IPAddress& prefix) {
if (!m_rtmonitor) {
return false;
}
- if (prefix.prefixLength() > 0) {
- return m_rtmonitor->insertRoute(prefix);
- }
+ if (prefix.prefixLength() > 0) {
+ return m_rtmonitor->deleteRoute(prefix);
+ }
// Ensure that we do not replace the default route.
if (prefix.type() == QAbstractSocket::IPv4Protocol) {
return m_rtmonitor->deleteRoute(IPAddress("0.0.0.0/1")) &&
@@ -346,31 +345,6 @@ bool WireguardUtilsMacos::addExclusionRoute(const IPAddress& prefix) {
return m_rtmonitor->addExclusionRoute(prefix);
}
-void WireguardUtilsMacos::applyFirewallRules(FirewallParams& params)
-{
- // double-check + ensure our firewall is installed and enabled. This is necessary as
- // other software may disable pfctl before re-enabling with their own rules (e.g other VPNs)
- if (!MacOSFirewall::isInstalled()) MacOSFirewall::install();
-
- MacOSFirewall::ensureRootAnchorPriority();
- MacOSFirewall::setAnchorEnabled(QStringLiteral("000.allowLoopback"), true);
- MacOSFirewall::setAnchorEnabled(QStringLiteral("100.blockAll"), params.blockAll);
- MacOSFirewall::setAnchorEnabled(QStringLiteral("110.allowNets"), params.allowNets);
- MacOSFirewall::setAnchorTable(QStringLiteral("110.allowNets"), params.allowNets,
- QStringLiteral("allownets"), params.allowAddrs);
-
- MacOSFirewall::setAnchorEnabled(QStringLiteral("120.blockNets"), params.blockNets);
- MacOSFirewall::setAnchorTable(QStringLiteral("120.blockNets"), params.blockNets,
- QStringLiteral("blocknets"), params.blockAddrs);
-
- MacOSFirewall::setAnchorEnabled(QStringLiteral("200.allowVPN"), true);
- MacOSFirewall::setAnchorEnabled(QStringLiteral("250.blockIPv6"), true);
- MacOSFirewall::setAnchorEnabled(QStringLiteral("290.allowDHCP"), true);
- MacOSFirewall::setAnchorEnabled(QStringLiteral("300.allowLAN"), true);
- MacOSFirewall::setAnchorEnabled(QStringLiteral("310.blockDNS"), true);
- MacOSFirewall::setAnchorTable(QStringLiteral("310.blockDNS"), true, QStringLiteral("dnsaddr"), params.dnsServers);
-}
-
bool WireguardUtilsMacos::deleteExclusionRoute(const IPAddress& prefix) {
if (!m_rtmonitor) {
return false;
@@ -378,6 +352,26 @@ bool WireguardUtilsMacos::deleteExclusionRoute(const IPAddress& prefix) {
return m_rtmonitor->deleteExclusionRoute(prefix);
}
+bool WireguardUtilsMacos::excludeLocalNetworks(const QList& routes) {
+ if (!m_rtmonitor) {
+ return false;
+ }
+
+ // Explicitly discard LAN traffic that makes its way into the tunnel. This
+ // doesn't really exclude the LAN traffic, we just don't take any action to
+ // overrule the routes of other interfaces.
+ bool result = true;
+ for (const auto& prefix : routes) {
+ logger.error() << "Attempting to exclude:" << prefix.toString();
+ if (!m_rtmonitor->insertRoute(prefix, RTF_IFSCOPE | RTF_REJECT)) {
+ result = false;
+ }
+ }
+
+ // TODO: A kill switch would be nice though :)
+ return result;
+}
+
QString WireguardUtilsMacos::uapiCommand(const QString& command) {
QLocalSocket socket;
QTimer uapiTimeout;
@@ -454,3 +448,28 @@ QString WireguardUtilsMacos::waitForTunnelName(const QString& filename) {
return QString();
}
+
+void WireguardUtilsMacos::applyFirewallRules(FirewallParams& params)
+{
+ // double-check + ensure our firewall is installed and enabled. This is necessary as
+ // other software may disable pfctl before re-enabling with their own rules (e.g other VPNs)
+ if (!MacOSFirewall::isInstalled()) MacOSFirewall::install();
+
+ MacOSFirewall::ensureRootAnchorPriority();
+ MacOSFirewall::setAnchorEnabled(QStringLiteral("000.allowLoopback"), true);
+ MacOSFirewall::setAnchorEnabled(QStringLiteral("100.blockAll"), params.blockAll);
+ MacOSFirewall::setAnchorEnabled(QStringLiteral("110.allowNets"), params.allowNets);
+ MacOSFirewall::setAnchorTable(QStringLiteral("110.allowNets"), params.allowNets,
+ QStringLiteral("allownets"), params.allowAddrs);
+
+ MacOSFirewall::setAnchorEnabled(QStringLiteral("120.blockNets"), params.blockNets);
+ MacOSFirewall::setAnchorTable(QStringLiteral("120.blockNets"), params.blockNets,
+ QStringLiteral("blocknets"), params.blockAddrs);
+
+ MacOSFirewall::setAnchorEnabled(QStringLiteral("200.allowVPN"), true);
+ MacOSFirewall::setAnchorEnabled(QStringLiteral("250.blockIPv6"), true);
+ MacOSFirewall::setAnchorEnabled(QStringLiteral("290.allowDHCP"), true);
+ MacOSFirewall::setAnchorEnabled(QStringLiteral("300.allowLAN"), true);
+ MacOSFirewall::setAnchorEnabled(QStringLiteral("310.blockDNS"), true);
+ MacOSFirewall::setAnchorTable(QStringLiteral("310.blockDNS"), true, QStringLiteral("dnsaddr"), params.dnsServers);
+}
diff --git a/client/platforms/macos/daemon/wireguardutilsmacos.h b/client/platforms/macos/daemon/wireguardutilsmacos.h
index 243f4b64..3f0d3391 100644
--- a/client/platforms/macos/daemon/wireguardutilsmacos.h
+++ b/client/platforms/macos/daemon/wireguardutilsmacos.h
@@ -35,6 +35,9 @@ class WireguardUtilsMacos final : public WireguardUtils {
bool addExclusionRoute(const IPAddress& prefix) override;
bool deleteExclusionRoute(const IPAddress& prefix) override;
+
+ bool excludeLocalNetworks(const QList& lanAddressRanges) override;
+
void applyFirewallRules(FirewallParams& params);
signals:
diff --git a/client/platforms/windows/daemon/windowsdaemon.cpp b/client/platforms/windows/daemon/windowsdaemon.cpp
index 00435f0b..620e0a1c 100644
--- a/client/platforms/windows/daemon/windowsdaemon.cpp
+++ b/client/platforms/windows/daemon/windowsdaemon.cpp
@@ -5,6 +5,7 @@
#include "windowsdaemon.h"
#include
+#include
#include
#include
@@ -15,28 +16,34 @@
#include
#include
+#include "daemon/daemonerrors.h"
#include "dnsutilswindows.h"
#include "leakdetector.h"
#include "logger.h"
-#include "core/networkUtilities.h"
+#include "platforms/windows/daemon/windowsfirewall.h"
+#include "platforms/windows/daemon/windowssplittunnel.h"
#include "platforms/windows/windowscommons.h"
-#include "platforms/windows/windowsservicemanager.h"
#include "windowsfirewall.h"
+#include "core/networkUtilities.h"
+
namespace {
Logger logger("WindowsDaemon");
}
-WindowsDaemon::WindowsDaemon() : Daemon(nullptr), m_splitTunnelManager(this) {
+WindowsDaemon::WindowsDaemon() : Daemon(nullptr) {
MZ_COUNT_CTOR(WindowsDaemon);
+ m_firewallManager = WindowsFirewall::create(this);
+ Q_ASSERT(m_firewallManager != nullptr);
- m_wgutils = new WireguardUtilsWindows(this);
+ m_wgutils = WireguardUtilsWindows::create(m_firewallManager, this);
m_dnsutils = new DnsUtilsWindows(this);
+ m_splitTunnelManager = WindowsSplitTunnel::create(m_firewallManager);
- connect(m_wgutils, &WireguardUtilsWindows::backendFailure, this,
+ connect(m_wgutils.get(), &WireguardUtilsWindows::backendFailure, this,
&WindowsDaemon::monitorBackendFailure);
connect(this, &WindowsDaemon::activationFailure,
- []() { WindowsFirewall::instance()->disableKillSwitch(); });
+ [this]() { m_firewallManager->disableKillSwitch(); });
}
WindowsDaemon::~WindowsDaemon() {
@@ -57,28 +64,42 @@ void WindowsDaemon::prepareActivation(const InterfaceConfig& config, int inetAda
void WindowsDaemon::activateSplitTunnel(const InterfaceConfig& config, int vpnAdapterIndex) {
if (config.m_vpnDisabledApps.length() > 0) {
- m_splitTunnelManager.start(m_inetAdapterIndex, vpnAdapterIndex);
- m_splitTunnelManager.setRules(config.m_vpnDisabledApps);
+ m_splitTunnelManager->start(m_inetAdapterIndex, vpnAdapterIndex);
+ m_splitTunnelManager->excludeApps(config.m_vpnDisabledApps);
} else {
- m_splitTunnelManager.stop();
+ m_splitTunnelManager->stop();
}
}
bool WindowsDaemon::run(Op op, const InterfaceConfig& config) {
- if (op == Down) {
- m_splitTunnelManager.stop();
+ if (!m_splitTunnelManager) {
+ if (config.m_vpnDisabledApps.length() > 0) {
+ // The Client has sent us a list of disabled apps, but we failed
+ // to init the the split tunnel driver.
+ // So let the client know this was not possible
+ emit backendFailure(DaemonError::ERROR_SPLIT_TUNNEL_INIT_FAILURE);
+ }
return true;
}
- if (op == Up) {
- logger.debug() << "Tunnel UP, Starting SplitTunneling";
- if (!WindowsSplitTunnel::isInstalled()) {
- logger.warning() << "Split Tunnel Driver not Installed yet, fixing this.";
- WindowsSplitTunnel::installDriver();
- }
+ if (op == Down) {
+ m_splitTunnelManager->stop();
+ return true;
}
-
- activateSplitTunnel(config);
+ if (config.m_vpnDisabledApps.length() > 0) {
+ if (!m_splitTunnelManager->start(m_inetAdapterIndex)) {
+ emit backendFailure(DaemonError::ERROR_SPLIT_TUNNEL_START_FAILURE);
+ };
+ if (!m_splitTunnelManager->excludeApps(config.m_vpnDisabledApps)) {
+ emit backendFailure(DaemonError::ERROR_SPLIT_TUNNEL_EXCLUDE_FAILURE);
+ };
+ // Now the driver should be running (State == 4)
+ if (!m_splitTunnelManager->isRunning()) {
+ emit backendFailure(DaemonError::ERROR_SPLIT_TUNNEL_START_FAILURE);
+ }
+ return true;
+ }
+ m_splitTunnelManager->stop();
return true;
}
diff --git a/client/platforms/windows/daemon/windowsdaemon.h b/client/platforms/windows/daemon/windowsdaemon.h
index 7e38c41e..b17dc811 100644
--- a/client/platforms/windows/daemon/windowsdaemon.h
+++ b/client/platforms/windows/daemon/windowsdaemon.h
@@ -5,8 +5,11 @@
#ifndef WINDOWSDAEMON_H
#define WINDOWSDAEMON_H
+#include
+
#include "daemon/daemon.h"
#include "dnsutilswindows.h"
+#include "windowsfirewall.h"
#include "windowssplittunnel.h"
#include "windowstunnelservice.h"
#include "wireguardutilswindows.h"
@@ -25,7 +28,7 @@ class WindowsDaemon final : public Daemon {
protected:
bool run(Op op, const InterfaceConfig& config) override;
- WireguardUtils* wgutils() const override { return m_wgutils; }
+ WireguardUtils* wgutils() const override { return m_wgutils.get(); }
DnsUtils* dnsutils() override { return m_dnsutils; }
private:
@@ -39,9 +42,10 @@ class WindowsDaemon final : public Daemon {
int m_inetAdapterIndex = -1;
- WireguardUtilsWindows* m_wgutils = nullptr;
+ std::unique_ptr m_wgutils;
DnsUtilsWindows* m_dnsutils = nullptr;
- WindowsSplitTunnel m_splitTunnelManager;
+ std::unique_ptr m_splitTunnelManager;
+ QPointer m_firewallManager;
};
#endif // WINDOWSDAEMON_H
diff --git a/client/platforms/windows/daemon/windowsfirewall.cpp b/client/platforms/windows/daemon/windowsfirewall.cpp
index 3d45f228..03525387 100644
--- a/client/platforms/windows/daemon/windowsfirewall.cpp
+++ b/client/platforms/windows/daemon/windowsfirewall.cpp
@@ -9,11 +9,12 @@
#include
#include
#include
-//#include
-#include
-
+#include
+#include
#include
#include
+#include
+#include "winsock.h"
#include
#include
@@ -27,7 +28,6 @@
#include "leakdetector.h"
#include "logger.h"
#include "platforms/windows/windowsutils.h"
-#include "winsock.h"
#define IPV6_ADDRESS_SIZE 16
@@ -49,18 +49,13 @@ constexpr uint8_t HIGH_WEIGHT = 13;
constexpr uint8_t MAX_WEIGHT = 15;
} // namespace
-WindowsFirewall* WindowsFirewall::instance() {
- if (s_instance == nullptr) {
- s_instance = new WindowsFirewall(qApp);
+WindowsFirewall* WindowsFirewall::create(QObject* parent) {
+ if (s_instance != nullptr) {
+ // Only one instance of the firewall is allowed
+// Q_ASSERT(false);
+ return s_instance;
}
- return s_instance;
-}
-
-WindowsFirewall::WindowsFirewall(QObject* parent) : QObject(parent) {
- MZ_COUNT_CTOR(WindowsFirewall);
- Q_ASSERT(s_instance == nullptr);
-
- HANDLE engineHandle = NULL;
+ HANDLE engineHandle = nullptr;
DWORD result = ERROR_SUCCESS;
// Use dynamic sessions for efficiency and safety:
// -> Filtering policy objects are deleted even when the application crashes/
@@ -71,15 +66,24 @@ WindowsFirewall::WindowsFirewall(QObject* parent) : QObject(parent) {
logger.debug() << "Opening the filter engine.";
- result =
- FwpmEngineOpen0(NULL, RPC_C_AUTHN_WINNT, NULL, &session, &engineHandle);
+ result = FwpmEngineOpen0(nullptr, RPC_C_AUTHN_WINNT, nullptr, &session,
+ &engineHandle);
if (result != ERROR_SUCCESS) {
WindowsUtils::windowsLog("FwpmEngineOpen0 failed");
- return;
+ return nullptr;
}
logger.debug() << "Filter engine opened successfully.";
- m_sessionHandle = engineHandle;
+ if (!initSublayer()) {
+ return nullptr;
+ }
+ s_instance = new WindowsFirewall(engineHandle, parent);
+ return s_instance;
+}
+
+WindowsFirewall::WindowsFirewall(HANDLE session, QObject* parent)
+ : QObject(parent), m_sessionHandle(session) {
+ MZ_COUNT_CTOR(WindowsFirewall);
}
WindowsFirewall::~WindowsFirewall() {
@@ -89,15 +93,8 @@ WindowsFirewall::~WindowsFirewall() {
}
}
-bool WindowsFirewall::init() {
- if (m_init) {
- logger.warning() << "Alread initialised FW_WFP layer";
- return true;
- }
- if (m_sessionHandle == INVALID_HANDLE_VALUE) {
- logger.error() << "Cant Init Sublayer with invalid wfp handle";
- return false;
- }
+// static
+bool WindowsFirewall::initSublayer() {
// If we were not able to aquire a handle, this will fail anyway.
// We need to open up another handle because of wfp rules:
// If a wfp resource was created with SESSION_DYNAMIC,
@@ -157,11 +154,10 @@ bool WindowsFirewall::init() {
return false;
}
logger.debug() << "Initialised Sublayer";
- m_init = true;
return true;
}
-bool WindowsFirewall::enableKillSwitch(int vpnAdapterIndex) {
+bool WindowsFirewall::enableInterface(int vpnAdapterIndex) {
// Checks if the FW_Rule was enabled succesfully,
// disables the whole killswitch and returns false if not.
#define FW_OK(rule) \
@@ -184,7 +180,7 @@ bool WindowsFirewall::enableKillSwitch(int vpnAdapterIndex) {
} \
}
- logger.info() << "Enabling Killswitch Using Adapter:" << vpnAdapterIndex;
+ logger.info() << "Enabling firewall Using Adapter:" << vpnAdapterIndex;
FW_OK(allowTrafficOfAdapter(vpnAdapterIndex, MED_WEIGHT,
"Allow usage of VPN Adapter"));
FW_OK(allowDHCPTraffic(MED_WEIGHT, "Allow DHCP Traffic"));
@@ -200,6 +196,36 @@ bool WindowsFirewall::enableKillSwitch(int vpnAdapterIndex) {
#undef FW_OK
}
+// Allow unprotected traffic sent to the following local address ranges.
+bool WindowsFirewall::enableLanBypass(const QList& ranges) {
+ // Start the firewall transaction
+ auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
+ if (result != ERROR_SUCCESS) {
+ disableKillSwitch();
+ return false;
+ }
+ auto cleanup = qScopeGuard([&] {
+ FwpmTransactionAbort0(m_sessionHandle);
+ disableKillSwitch();
+ });
+
+ // Blocking unprotected traffic
+ for (const IPAddress& prefix : ranges) {
+ if (!allowTrafficTo(prefix, LOW_WEIGHT + 1, "Allow LAN bypass traffic")) {
+ return false;
+ }
+ }
+
+ result = FwpmTransactionCommit0(m_sessionHandle);
+ if (result != ERROR_SUCCESS) {
+ logger.error() << "FwpmTransactionCommit0 failed with error:" << result;
+ return false;
+ }
+
+ cleanup.dismiss();
+ return true;
+}
+
bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
// Start the firewall transaction
auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
@@ -238,10 +264,10 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
if (!config.m_excludedAddresses.empty()) {
for (const QString& i : config.m_excludedAddresses) {
- logger.debug() << "range: " << i;
+ logger.debug() << "excludedAddresses range: " << i;
- if (!allowTrafficToRange(i, HIGH_WEIGHT,
- "Allow Ecxlude route", config.m_serverPublicKey)) {
+ if (!allowTrafficTo(i, HIGH_WEIGHT,
+ "Allow Ecxlude route", config.m_serverPublicKey)) {
return false;
}
}
@@ -421,9 +447,59 @@ bool WindowsFirewall::allowTrafficOfAdapter(int networkAdapter, uint8_t weight,
return true;
}
+bool WindowsFirewall::allowTrafficTo(const IPAddress& addr, int weight,
+ const QString& title,
+ const QString& peer) {
+ GUID layerKeyOut;
+ GUID layerKeyIn;
+ if (addr.type() == QAbstractSocket::IPv4Protocol) {
+ layerKeyOut = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
+ layerKeyIn = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4;
+ } else {
+ layerKeyOut = FWPM_LAYER_ALE_AUTH_CONNECT_V6;
+ layerKeyIn = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6;
+ }
+
+ // Match the IP address range.
+ FWPM_FILTER_CONDITION0 cond[1] = {};
+ FWP_RANGE0 ipRange;
+ QByteArray lowIpV6Buffer;
+ QByteArray highIpV6Buffer;
+
+ importAddress(addr.address(), ipRange.valueLow, &lowIpV6Buffer);
+ importAddress(addr.broadcastAddress(), ipRange.valueHigh, &highIpV6Buffer);
+
+ cond[0].fieldKey = FWPM_CONDITION_IP_REMOTE_ADDRESS;
+ cond[0].matchType = FWP_MATCH_RANGE;
+ cond[0].conditionValue.type = FWP_RANGE_TYPE;
+ cond[0].conditionValue.rangeValue = &ipRange;
+
+ // Assemble the Filter base
+ FWPM_FILTER0 filter;
+ memset(&filter, 0, sizeof(filter));
+ filter.action.type = FWP_ACTION_PERMIT;
+ filter.weight.type = FWP_UINT8;
+ filter.weight.uint8 = weight;
+ filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
+ filter.numFilterConditions = 1;
+ filter.filterCondition = cond;
+
+ // Send the filters down to the firewall.
+ QString description = "Permit traffic %1 " + addr.toString();
+ filter.layerKey = layerKeyOut;
+ if (!enableFilter(&filter, title, description.arg("to"), peer)) {
+ return false;
+ }
+ filter.layerKey = layerKeyIn;
+ if (!enableFilter(&filter, title, description.arg("from"), peer)) {
+ return false;
+ }
+ return true;
+}
+
bool WindowsFirewall::allowTrafficTo(const QHostAddress& targetIP, uint port,
- int weight, const QString& title,
- const QString& peer) {
+ int weight, const QString& title,
+ const QString& peer) {
bool isIPv4 = targetIP.protocol() == QAbstractSocket::IPv4Protocol;
GUID layerOut =
isIPv4 ? FWPM_LAYER_ALE_AUTH_CONNECT_V4 : FWPM_LAYER_ALE_AUTH_CONNECT_V6;
@@ -484,57 +560,6 @@ bool WindowsFirewall::allowTrafficTo(const QHostAddress& targetIP, uint port,
return true;
}
-bool WindowsFirewall::allowTrafficToRange(const IPAddress& addr, uint8_t weight,
- const QString& title,
- const QString& peer) {
- QString description("Allow traffic %1 %2 ");
-
- auto lower = addr.address();
- auto upper = addr.broadcastAddress();
-
- const bool isV4 = addr.type() == QAbstractSocket::IPv4Protocol;
- const GUID layerKeyOut =
- isV4 ? FWPM_LAYER_ALE_AUTH_CONNECT_V4 : FWPM_LAYER_ALE_AUTH_CONNECT_V6;
- const GUID layerKeyIn = isV4 ? FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4
- : FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6;
-
- // Assemble the Filter base
- FWPM_FILTER0 filter;
- memset(&filter, 0, sizeof(filter));
- filter.action.type = FWP_ACTION_PERMIT;
- filter.weight.type = FWP_UINT8;
- filter.weight.uint8 = weight;
- filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
-
- FWPM_FILTER_CONDITION0 cond[1] = {0};
- FWP_RANGE0 ipRange;
- QByteArray lowIpV6Buffer;
- QByteArray highIpV6Buffer;
-
- importAddress(lower, ipRange.valueLow, &lowIpV6Buffer);
- importAddress(upper, ipRange.valueHigh, &highIpV6Buffer);
-
- cond[0].fieldKey = FWPM_CONDITION_IP_REMOTE_ADDRESS;
- cond[0].matchType = FWP_MATCH_RANGE;
- cond[0].conditionValue.type = FWP_RANGE_TYPE;
- cond[0].conditionValue.rangeValue = &ipRange;
-
- filter.numFilterConditions = 1;
- filter.filterCondition = cond;
-
- filter.layerKey = layerKeyOut;
- if (!enableFilter(&filter, title, description.arg("to").arg(addr.toString()),
- peer)) {
- return false;
- }
- filter.layerKey = layerKeyIn;
- if (!enableFilter(&filter, title,
- description.arg("from").arg(addr.toString()), peer)) {
- return false;
- }
- return true;
-}
-
bool WindowsFirewall::allowDHCPTraffic(uint8_t weight, const QString& title) {
// Allow outbound DHCPv4
{
@@ -734,7 +759,7 @@ bool WindowsFirewall::blockTrafficTo(const IPAddress& addr, uint8_t weight,
filter.weight.uint8 = weight;
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
- FWPM_FILTER_CONDITION0 cond[1] = {0};
+ FWPM_FILTER_CONDITION0 cond[1] = {};
FWP_RANGE0 ipRange;
QByteArray lowIpV6Buffer;
QByteArray highIpV6Buffer;
diff --git a/client/platforms/windows/daemon/windowsfirewall.h b/client/platforms/windows/daemon/windowsfirewall.h
index e0d5ebe8..55ee9417 100644
--- a/client/platforms/windows/daemon/windowsfirewall.h
+++ b/client/platforms/windows/daemon/windowsfirewall.h
@@ -26,18 +26,27 @@ struct FWP_CONDITION_VALUE0_;
class WindowsFirewall final : public QObject {
public:
- ~WindowsFirewall();
+ /**
+ * @brief Opens the Windows Filtering Platform, initializes the session,
+ * sublayer. Returns a WindowsFirewall object if successful, otherwise
+ * nullptr. If there is already a WindowsFirewall object, it will be returned.
+ *
+ * @param parent - parent QObject
+ * @return WindowsFirewall* - nullptr if failed to open the Windows Filtering
+ * Platform.
+ */
+ static WindowsFirewall* create(QObject* parent);
+ ~WindowsFirewall() override;
- static WindowsFirewall* instance();
- bool init();
-
- bool enableKillSwitch(int vpnAdapterIndex);
+ bool enableInterface(int vpnAdapterIndex);
+ bool enableLanBypass(const QList& ranges);
bool enablePeerTraffic(const InterfaceConfig& config);
bool disablePeerTraffic(const QString& pubkey);
bool disableKillSwitch();
private:
- WindowsFirewall(QObject* parent);
+ static bool initSublayer();
+ WindowsFirewall(HANDLE session, QObject* parent);
HANDLE m_sessionHandle;
bool m_init = false;
QList m_activeRules;
@@ -50,11 +59,10 @@ class WindowsFirewall final : public QObject {
bool blockTrafficTo(const IPAddress& addr, uint8_t weight,
const QString& title, const QString& peer = QString());
bool blockTrafficOnPort(uint port, uint8_t weight, const QString& title);
+ bool allowTrafficTo(const IPAddress& addr, int weight, const QString& title,
+ const QString& peer = QString());
bool allowTrafficTo(const QHostAddress& targetIP, uint port, int weight,
const QString& title, const QString& peer = QString());
- bool allowTrafficToRange(const IPAddress& addr, uint8_t weight,
- const QString& title,
- const QString& peer);
bool allowTrafficOfAdapter(int networkAdapter, uint8_t weight,
const QString& title);
bool allowDHCPTraffic(uint8_t weight, const QString& title);
diff --git a/client/platforms/windows/daemon/windowsroutemonitor.cpp b/client/platforms/windows/daemon/windowsroutemonitor.cpp
index 69967526..fb0fbf7e 100644
--- a/client/platforms/windows/daemon/windowsroutemonitor.cpp
+++ b/client/platforms/windows/daemon/windowsroutemonitor.cpp
@@ -13,6 +13,12 @@ namespace {
Logger logger("WindowsRouteMonitor");
}; // namespace
+// Attempt to mark routing entries that we create with a relatively
+// high metric. This ensures that we can skip over routes of our own
+// creation when processing route changes, and ensures that we give
+// way to other routing entries.
+constexpr const ULONG EXCLUSION_ROUTE_METRIC = 0x5e72;
+
// Called by the kernel on route changes - perform some basic filtering and
// invoke the routeChanged slot to do the real work.
static void routeChangeCallback(PVOID context, PMIB_IPFORWARD_ROW2 row,
@@ -20,22 +26,17 @@ static void routeChangeCallback(PVOID context, PMIB_IPFORWARD_ROW2 row,
WindowsRouteMonitor* monitor = (WindowsRouteMonitor*)context;
Q_UNUSED(type);
- // Ignore host route changes, and unsupported protocols.
- if (row->DestinationPrefix.Prefix.si_family == AF_INET6) {
- if (row->DestinationPrefix.PrefixLength >= 128) {
- return;
- }
- } else if (row->DestinationPrefix.Prefix.si_family == AF_INET) {
- if (row->DestinationPrefix.PrefixLength >= 32) {
- return;
- }
- } else {
+ // Ignore route changes that we created.
+ if ((row->Protocol == MIB_IPPROTO_NETMGMT) &&
+ (row->Metric == EXCLUSION_ROUTE_METRIC)) {
+ return;
+ }
+ if (monitor->getLuid() == row->InterfaceLuid.Value) {
return;
}
- if (monitor->getLuid() != row->InterfaceLuid.Value) {
- QMetaObject::invokeMethod(monitor, "routeChanged", Qt::QueuedConnection);
- }
+ // Invoke the route changed signal to do the real work in Qt.
+ QMetaObject::invokeMethod(monitor, "routeChanged", Qt::QueuedConnection);
}
// Perform prefix matching comparison on IP addresses in host order.
@@ -57,7 +58,8 @@ static int prefixcmp(const void* a, const void* b, size_t bits) {
return 0;
}
-WindowsRouteMonitor::WindowsRouteMonitor(QObject* parent) : QObject(parent) {
+WindowsRouteMonitor::WindowsRouteMonitor(quint64 luid, QObject* parent)
+ : QObject(parent), m_luid(luid) {
MZ_COUNT_CTOR(WindowsRouteMonitor);
logger.debug() << "WindowsRouteMonitor created.";
@@ -67,11 +69,13 @@ WindowsRouteMonitor::WindowsRouteMonitor(QObject* parent) : QObject(parent) {
WindowsRouteMonitor::~WindowsRouteMonitor() {
MZ_COUNT_DTOR(WindowsRouteMonitor);
CancelMibChangeNotify2(m_routeHandle);
- flushExclusionRoutes();
+
+ flushRouteTable(m_exclusionRoutes);
+ flushRouteTable(m_clonedRoutes);
logger.debug() << "WindowsRouteMonitor destroyed.";
}
-void WindowsRouteMonitor::updateValidInterfaces(int family) {
+void WindowsRouteMonitor::updateInterfaceMetrics(int family) {
PMIB_IPINTERFACE_TABLE table;
DWORD result = GetIpInterfaceTable(family, &table);
if (result != NO_ERROR) {
@@ -82,10 +86,10 @@ void WindowsRouteMonitor::updateValidInterfaces(int family) {
// Flush the list of interfaces that are valid for routing.
if ((family == AF_INET) || (family == AF_UNSPEC)) {
- m_validInterfacesIpv4.clear();
+ m_interfaceMetricsIpv4.clear();
}
if ((family == AF_INET6) || (family == AF_UNSPEC)) {
- m_validInterfacesIpv6.clear();
+ m_interfaceMetricsIpv6.clear();
}
// Rebuild the list of interfaces that are valid for routing.
@@ -101,12 +105,12 @@ void WindowsRouteMonitor::updateValidInterfaces(int family) {
if (row->Family == AF_INET) {
logger.debug() << "Interface" << row->InterfaceIndex
<< "is valid for IPv4 routing";
- m_validInterfacesIpv4.append(row->InterfaceLuid.Value);
+ m_interfaceMetricsIpv4[row->InterfaceLuid.Value] = row->Metric;
}
if (row->Family == AF_INET6) {
logger.debug() << "Interface" << row->InterfaceIndex
<< "is valid for IPv6 routing";
- m_validInterfacesIpv6.append(row->InterfaceLuid.Value);
+ m_interfaceMetricsIpv6[row->InterfaceLuid.Value] = row->Metric;
}
}
}
@@ -126,72 +130,72 @@ void WindowsRouteMonitor::updateExclusionRoute(MIB_IPFORWARD_ROW2* data,
if (row->InterfaceLuid.Value == m_luid) {
continue;
}
- // Ignore host routes, and shorter potential matches.
- if (row->DestinationPrefix.PrefixLength >=
- data->DestinationPrefix.PrefixLength) {
+ if (row->DestinationPrefix.PrefixLength < bestMatch) {
continue;
}
- if (row->DestinationPrefix.PrefixLength < bestMatch) {
+ // Ignore routes of our own creation.
+ if ((row->Protocol == data->Protocol) && (row->Metric == data->Metric)) {
continue;
}
// Check if the routing table entry matches the destination.
+ if (!routeContainsDest(&row->DestinationPrefix, &data->DestinationPrefix)) {
+ continue;
+ }
+
+ // Compute the combined interface and routing metric.
+ ULONG routeMetric = row->Metric;
if (data->DestinationPrefix.Prefix.si_family == AF_INET6) {
- if (row->DestinationPrefix.Prefix.Ipv6.sin6_family != AF_INET6) {
- continue;
- }
- if (!m_validInterfacesIpv6.contains(row->InterfaceLuid.Value)) {
- continue;
- }
- if (prefixcmp(&data->DestinationPrefix.Prefix.Ipv6.sin6_addr,
- &row->DestinationPrefix.Prefix.Ipv6.sin6_addr,
- row->DestinationPrefix.PrefixLength) != 0) {
+ if (!m_interfaceMetricsIpv6.contains(row->InterfaceLuid.Value)) {
continue;
}
+ routeMetric += m_interfaceMetricsIpv6[row->InterfaceLuid.Value];
} else if (data->DestinationPrefix.Prefix.si_family == AF_INET) {
- if (row->DestinationPrefix.Prefix.Ipv4.sin_family != AF_INET) {
- continue;
- }
- if (!m_validInterfacesIpv4.contains(row->InterfaceLuid.Value)) {
- continue;
- }
- if (prefixcmp(&data->DestinationPrefix.Prefix.Ipv4.sin_addr,
- &row->DestinationPrefix.Prefix.Ipv4.sin_addr,
- row->DestinationPrefix.PrefixLength) != 0) {
+ if (!m_interfaceMetricsIpv4.contains(row->InterfaceLuid.Value)) {
continue;
}
+ routeMetric += m_interfaceMetricsIpv4[row->InterfaceLuid.Value];
} else {
// Unsupported destination address family.
continue;
}
+ if (routeMetric < row->Metric) {
+ routeMetric = ULONG_MAX;
+ }
// Prefer routes with lower metric if we find multiple matches
// with the same prefix length.
if ((row->DestinationPrefix.PrefixLength == bestMatch) &&
- (row->Metric >= bestMetric)) {
+ (routeMetric >= bestMetric)) {
continue;
}
// If we got here, then this is the longest prefix match so far.
memcpy(&nexthop, &row->NextHop, sizeof(SOCKADDR_INET));
- bestLuid = row->InterfaceLuid.Value;
bestMatch = row->DestinationPrefix.PrefixLength;
- bestMetric = row->Metric;
+ bestMetric = routeMetric;
+ if (bestMatch == data->DestinationPrefix.PrefixLength) {
+ bestLuid = 0; // Don't write to the table if we find an exact match.
+ } else {
+ bestLuid = row->InterfaceLuid.Value;
+ }
}
// If neither the interface nor next-hop have changed, then do nothing.
- if ((data->InterfaceLuid.Value) == bestLuid &&
+ if (data->InterfaceLuid.Value == bestLuid &&
memcmp(&nexthop, &data->NextHop, sizeof(SOCKADDR_INET)) == 0) {
return;
}
- // Update the routing table entry.
+ // Delete the previous routing table entry, if any.
if (data->InterfaceLuid.Value != 0) {
DWORD result = DeleteIpForwardEntry2(data);
if ((result != NO_ERROR) && (result != ERROR_NOT_FOUND)) {
logger.error() << "Failed to delete route:" << result;
}
}
+
+ // Update the routing table entry.
data->InterfaceLuid.Value = bestLuid;
memcpy(&data->NextHop, &nexthop, sizeof(SOCKADDR_INET));
if (data->InterfaceLuid.Value != 0) {
@@ -202,10 +206,178 @@ void WindowsRouteMonitor::updateExclusionRoute(MIB_IPFORWARD_ROW2* data,
}
}
+// static
+bool WindowsRouteMonitor::routeContainsDest(const IP_ADDRESS_PREFIX* route,
+ const IP_ADDRESS_PREFIX* dest) {
+ if (route->Prefix.si_family != dest->Prefix.si_family) {
+ return false;
+ }
+ if (route->PrefixLength > dest->PrefixLength) {
+ return false;
+ }
+ if (route->Prefix.si_family == AF_INET) {
+ return prefixcmp(&route->Prefix.Ipv4.sin_addr, &dest->Prefix.Ipv4.sin_addr,
+ route->PrefixLength) == 0;
+ } else if (route->Prefix.si_family == AF_INET6) {
+ return prefixcmp(&route->Prefix.Ipv6.sin6_addr,
+ &dest->Prefix.Ipv6.sin6_addr, route->PrefixLength) == 0;
+ } else {
+ return false;
+ }
+}
+
+// static
+QHostAddress WindowsRouteMonitor::prefixToAddress(
+ const IP_ADDRESS_PREFIX* dest) {
+ if (dest->Prefix.si_family == AF_INET6) {
+ return QHostAddress(dest->Prefix.Ipv6.sin6_addr.s6_addr);
+ } else if (dest->Prefix.si_family == AF_INET) {
+ quint32 addr = htonl(dest->Prefix.Ipv4.sin_addr.s_addr);
+ return QHostAddress(addr);
+ } else {
+ return QHostAddress();
+ }
+}
+
+bool WindowsRouteMonitor::isRouteExcluded(const IP_ADDRESS_PREFIX* dest) const {
+ auto i = m_exclusionRoutes.constBegin();
+ while (i != m_exclusionRoutes.constEnd()) {
+ const MIB_IPFORWARD_ROW2* row = i.value();
+ if (routeContainsDest(&row->DestinationPrefix, dest)) {
+ return true;
+ }
+ i++;
+ }
+ return false;
+}
+
+void WindowsRouteMonitor::updateCapturedRoutes(int family) {
+ if (!m_defaultRouteCapture) {
+ return;
+ }
+
+ PMIB_IPFORWARD_TABLE2 table;
+ DWORD error = GetIpForwardTable2(family, &table);
+ if (error != NO_ERROR) {
+ updateCapturedRoutes(family, table);
+ FreeMibTable(table);
+ }
+}
+
+void WindowsRouteMonitor::updateCapturedRoutes(int family, void* ptable) {
+ PMIB_IPFORWARD_TABLE2 table = reinterpret_cast(ptable);
+ if (!m_defaultRouteCapture) {
+ return;
+ }
+
+ for (ULONG i = 0; i < table->NumEntries; i++) {
+ MIB_IPFORWARD_ROW2* row = &table->Table[i];
+ // Ignore routes into the VPN interface.
+ if (row->InterfaceLuid.Value == m_luid) {
+ continue;
+ }
+ // Ignore the default route
+ if (row->DestinationPrefix.PrefixLength == 0) {
+ continue;
+ }
+ // Ignore routes of our own creation.
+ if ((row->Protocol == MIB_IPPROTO_NETMGMT) &&
+ (row->Metric == EXCLUSION_ROUTE_METRIC)) {
+ continue;
+ }
+ // Ignore routes which should be excluded.
+ if (isRouteExcluded(&row->DestinationPrefix)) {
+ continue;
+ }
+ QHostAddress destination = prefixToAddress(&row->DestinationPrefix);
+ if (destination.isLoopback() || destination.isBroadcast() ||
+ destination.isLinkLocal() || destination.isMulticast()) {
+ continue;
+ }
+
+ // If we get here, this route should be cloned.
+ IPAddress prefix(destination, row->DestinationPrefix.PrefixLength);
+ MIB_IPFORWARD_ROW2* data = m_clonedRoutes.value(prefix, nullptr);
+ if (data != nullptr) {
+ // Count the number of matching entries in the main table.
+ data->Age++;
+ continue;
+ }
+ logger.debug() << "Capturing route to"
+ << logger.sensitive(prefix.toString());
+
+ // Clone the route and direct it into the VPN tunnel.
+ data = new MIB_IPFORWARD_ROW2;
+ InitializeIpForwardEntry(data);
+ data->InterfaceLuid.Value = m_luid;
+ data->DestinationPrefix = row->DestinationPrefix;
+ data->NextHop.si_family = data->DestinationPrefix.Prefix.si_family;
+
+ // Set the rest of the flags for a static route.
+ data->ValidLifetime = 0xffffffff;
+ data->PreferredLifetime = 0xffffffff;
+ data->Metric = 0;
+ data->Protocol = MIB_IPPROTO_NETMGMT;
+ data->Loopback = false;
+ data->AutoconfigureAddress = false;
+ data->Publish = false;
+ data->Immortal = false;
+ data->Age = 0;
+
+ // Route this traffic into the VPN tunnel.
+ DWORD result = CreateIpForwardEntry2(data);
+ if (result != NO_ERROR) {
+ logger.error() << "Failed to update route:" << result;
+ delete data;
+ } else {
+ m_clonedRoutes.insert(prefix, data);
+ data->Age++;
+ }
+ }
+
+ // Finally scan for any routes which were removed from the table. We do this
+ // by reusing the age field to count the number of matching entries in the
+ // main table.
+ auto i = m_clonedRoutes.begin();
+ while (i != m_clonedRoutes.end()) {
+ MIB_IPFORWARD_ROW2* data = i.value();
+ if (data->Age > 0) {
+ // Entry is in use, don't delete it.
+ data->Age = 0;
+ i++;
+ continue;
+ }
+ if ((family != AF_UNSPEC) &&
+ (data->DestinationPrefix.Prefix.si_family != family)) {
+ // We are not processing updates to this address family.
+ i++;
+ continue;
+ }
+
+ logger.debug() << "Removing route capture for"
+ << logger.sensitive(i.key().toString());
+
+ // Otherwise, this route is no longer in use.
+ DWORD result = DeleteIpForwardEntry2(data);
+ if ((result != NO_ERROR) && (result != ERROR_NOT_FOUND)) {
+ logger.error() << "Failed to delete route:" << result;
+ }
+ delete data;
+ i = m_clonedRoutes.erase(i);
+ }
+}
+
bool WindowsRouteMonitor::addExclusionRoute(const IPAddress& prefix) {
logger.debug() << "Adding exclusion route for"
<< logger.sensitive(prefix.toString());
+ // Silently ignore non-routeable addresses.
+ QHostAddress addr = prefix.address();
+ if (addr.isLoopback() || addr.isBroadcast() || addr.isLinkLocal() ||
+ addr.isMulticast()) {
+ return true;
+ }
+
if (m_exclusionRoutes.contains(prefix)) {
logger.warning() << "Exclusion route already exists";
return false;
@@ -232,7 +404,7 @@ bool WindowsRouteMonitor::addExclusionRoute(const IPAddress& prefix) {
// Set the rest of the flags for a static route.
data->ValidLifetime = 0xffffffff;
data->PreferredLifetime = 0xffffffff;
- data->Metric = 0;
+ data->Metric = EXCLUSION_ROUTE_METRIC;
data->Protocol = MIB_IPPROTO_NETMGMT;
data->Loopback = false;
data->AutoconfigureAddress = false;
@@ -254,7 +426,8 @@ bool WindowsRouteMonitor::addExclusionRoute(const IPAddress& prefix) {
delete data;
return false;
}
- updateValidInterfaces(family);
+ updateInterfaceMetrics(family);
+ updateCapturedRoutes(family, table);
updateExclusionRoute(data, table);
FreeMibTable(table);
@@ -266,26 +439,28 @@ bool WindowsRouteMonitor::deleteExclusionRoute(const IPAddress& prefix) {
logger.debug() << "Deleting exclusion route for"
<< logger.sensitive(prefix.address().toString());
- for (;;) {
- MIB_IPFORWARD_ROW2* data = m_exclusionRoutes.take(prefix);
- if (data == nullptr) {
- break;
- }
-
- DWORD result = DeleteIpForwardEntry2(data);
- if ((result != ERROR_NOT_FOUND) && (result != NO_ERROR)) {
- logger.error() << "Failed to delete route to"
- << logger.sensitive(prefix.toString())
- << "result:" << result;
- }
- delete data;
+ MIB_IPFORWARD_ROW2* data = m_exclusionRoutes.take(prefix);
+ if (data == nullptr) {
+ return true;
}
+ DWORD result = DeleteIpForwardEntry2(data);
+ if ((result != ERROR_NOT_FOUND) && (result != NO_ERROR)) {
+ logger.error() << "Failed to delete route to"
+ << logger.sensitive(prefix.toString())
+ << "result:" << result;
+ }
+
+ // Captured routes might have changed.
+ updateCapturedRoutes(data->DestinationPrefix.Prefix.si_family);
+
+ delete data;
return true;
}
-void WindowsRouteMonitor::flushExclusionRoutes() {
- for (auto i = m_exclusionRoutes.begin(); i != m_exclusionRoutes.end(); i++) {
+void WindowsRouteMonitor::flushRouteTable(
+ QHash& table) {
+ for (auto i = table.begin(); i != table.end(); i++) {
MIB_IPFORWARD_ROW2* data = i.value();
DWORD result = DeleteIpForwardEntry2(data);
if ((result != ERROR_NOT_FOUND) && (result != NO_ERROR)) {
@@ -295,7 +470,17 @@ void WindowsRouteMonitor::flushExclusionRoutes() {
}
delete data;
}
- m_exclusionRoutes.clear();
+ table.clear();
+}
+
+void WindowsRouteMonitor::setDetaultRouteCapture(bool enable) {
+ m_defaultRouteCapture = enable;
+
+ // Flush any captured routes when disabling the feature.
+ if (!m_defaultRouteCapture) {
+ flushRouteTable(m_clonedRoutes);
+ return;
+ }
}
void WindowsRouteMonitor::routeChanged() {
@@ -308,7 +493,8 @@ void WindowsRouteMonitor::routeChanged() {
return;
}
- updateValidInterfaces(AF_UNSPEC);
+ updateInterfaceMetrics(AF_UNSPEC);
+ updateCapturedRoutes(AF_UNSPEC, table);
for (MIB_IPFORWARD_ROW2* data : m_exclusionRoutes) {
updateExclusionRoute(data, table);
}
diff --git a/client/platforms/windows/daemon/windowsroutemonitor.h b/client/platforms/windows/daemon/windowsroutemonitor.h
index 0ae9a8a2..fa04f646 100644
--- a/client/platforms/windows/daemon/windowsroutemonitor.h
+++ b/client/platforms/windows/daemon/windowsroutemonitor.h
@@ -11,6 +11,8 @@
#include
#include
+#include
+#include
#include
#include "ipaddress.h"
@@ -19,28 +21,41 @@ class WindowsRouteMonitor final : public QObject {
Q_OBJECT
public:
- WindowsRouteMonitor(QObject* parent);
+ WindowsRouteMonitor(quint64 luid, QObject* parent);
~WindowsRouteMonitor();
+ void setDetaultRouteCapture(bool enable);
+
bool addExclusionRoute(const IPAddress& prefix);
bool deleteExclusionRoute(const IPAddress& prefix);
- void flushExclusionRoutes();
+ void flushExclusionRoutes() { return flushRouteTable(m_exclusionRoutes); };
- void setLuid(quint64 luid) { m_luid = luid; }
- quint64 getLuid() { return m_luid; }
+ quint64 getLuid() const { return m_luid; }
public slots:
void routeChanged();
private:
+ bool isRouteExcluded(const IP_ADDRESS_PREFIX* dest) const;
+ static bool routeContainsDest(const IP_ADDRESS_PREFIX* route,
+ const IP_ADDRESS_PREFIX* dest);
+ static QHostAddress prefixToAddress(const IP_ADDRESS_PREFIX* dest);
+
+ void flushRouteTable(QHash& table);
void updateExclusionRoute(MIB_IPFORWARD_ROW2* data, void* table);
- void updateValidInterfaces(int family);
+ void updateInterfaceMetrics(int family);
+ void updateCapturedRoutes(int family);
+ void updateCapturedRoutes(int family, void* table);
QHash m_exclusionRoutes;
- QList m_validInterfacesIpv4;
- QList m_validInterfacesIpv6;
+ QMap m_interfaceMetricsIpv4;
+ QMap m_interfaceMetricsIpv6;
- quint64 m_luid = 0;
+ // Default route cloning
+ bool m_defaultRouteCapture = false;
+ QHash m_clonedRoutes;
+
+ const quint64 m_luid = 0;
HANDLE m_routeHandle = INVALID_HANDLE_VALUE;
};
diff --git a/client/platforms/windows/daemon/windowssplittunnel.cpp b/client/platforms/windows/daemon/windowssplittunnel.cpp
index c4e893b2..63de153d 100644
--- a/client/platforms/windows/daemon/windowssplittunnel.cpp
+++ b/client/platforms/windows/daemon/windowssplittunnel.cpp
@@ -4,9 +4,15 @@
#include "windowssplittunnel.h"
+#include
+
+#include
+
#include "../windowscommons.h"
#include "../windowsservicemanager.h"
#include "logger.h"
+#include "platforms/windows/daemon/windowsfirewall.h"
+#include "platforms/windows/daemon/windowssplittunnel.h"
#include "platforms/windows/windowsutils.h"
#include "windowsfirewall.h"
@@ -18,34 +24,252 @@
#include
#include
#include
-#include
+
+#pragma region
+
+// Driver Configuration structures
+using CONFIGURATION_ENTRY = struct {
+ // Offset into buffer region that follows all entries.
+ // The image name uses the device path.
+ SIZE_T ImageNameOffset;
+ // Length of the String
+ USHORT ImageNameLength;
+};
+
+using CONFIGURATION_HEADER = struct {
+ // Number of entries immediately following the header.
+ SIZE_T NumEntries;
+
+ // Total byte length: header + entries + string buffer.
+ SIZE_T TotalLength;
+};
+
+// Used to Configure Which IP is network/vpn
+using IP_ADDRESSES_CONFIG = struct {
+ IN_ADDR TunnelIpv4;
+ IN_ADDR InternetIpv4;
+
+ IN6_ADDR TunnelIpv6;
+ IN6_ADDR InternetIpv6;
+};
+
+// Used to Define Which Processes are alive on activation
+using PROCESS_DISCOVERY_HEADER = struct {
+ SIZE_T NumEntries;
+ SIZE_T TotalLength;
+};
+
+using PROCESS_DISCOVERY_ENTRY = struct {
+ HANDLE ProcessId;
+ HANDLE ParentProcessId;
+
+ SIZE_T ImageNameOffset;
+ USHORT ImageNameLength;
+};
+
+using ProcessInfo = struct {
+ DWORD ProcessId;
+ DWORD ParentProcessId;
+ FILETIME CreationTime;
+ std::wstring DevicePath;
+};
+
+#ifndef CTL_CODE
+
+# define FILE_ANY_ACCESS 0x0000
+
+# define METHOD_BUFFERED 0
+# define METHOD_IN_DIRECT 1
+# define METHOD_NEITHER 3
+
+# define CTL_CODE(DeviceType, Function, Method, Access) \
+ (((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method))
+#endif
+
+// Known ControlCodes
+#define IOCTL_INITIALIZE CTL_CODE(0x8000, 1, METHOD_NEITHER, FILE_ANY_ACCESS)
+
+#define IOCTL_DEQUEUE_EVENT \
+ CTL_CODE(0x8000, 2, METHOD_BUFFERED, FILE_ANY_ACCESS)
+
+#define IOCTL_REGISTER_PROCESSES \
+ CTL_CODE(0x8000, 3, METHOD_BUFFERED, FILE_ANY_ACCESS)
+
+#define IOCTL_REGISTER_IP_ADDRESSES \
+ CTL_CODE(0x8000, 4, METHOD_BUFFERED, FILE_ANY_ACCESS)
+
+#define IOCTL_GET_IP_ADDRESSES \
+ CTL_CODE(0x8000, 5, METHOD_BUFFERED, FILE_ANY_ACCESS)
+
+#define IOCTL_SET_CONFIGURATION \
+ CTL_CODE(0x8000, 6, METHOD_BUFFERED, FILE_ANY_ACCESS)
+
+#define IOCTL_GET_CONFIGURATION \
+ CTL_CODE(0x8000, 7, METHOD_BUFFERED, FILE_ANY_ACCESS)
+
+#define IOCTL_CLEAR_CONFIGURATION \
+ CTL_CODE(0x8000, 8, METHOD_NEITHER, FILE_ANY_ACCESS)
+
+#define IOCTL_GET_STATE CTL_CODE(0x8000, 9, METHOD_BUFFERED, FILE_ANY_ACCESS)
+
+#define IOCTL_QUERY_PROCESS \
+ CTL_CODE(0x8000, 10, METHOD_BUFFERED, FILE_ANY_ACCESS)
+
+#define IOCTL_ST_RESET CTL_CODE(0x8000, 11, METHOD_NEITHER, FILE_ANY_ACCESS)
+
+constexpr static const auto DRIVER_SYMLINK = L"\\\\.\\MULLVADSPLITTUNNEL";
+constexpr static const auto DRIVER_FILENAME = "mullvad-split-tunnel.sys";
+constexpr static const auto DRIVER_SERVICE_NAME = L"AmneziaVPNSplitTunnel";
+constexpr static const auto MV_SERVICE_NAME = L"MullvadVPN";
+
+#pragma endregion
namespace {
Logger logger("WindowsSplitTunnel");
+
+ProcessInfo getProcessInfo(HANDLE process, const PROCESSENTRY32W& processMeta) {
+ ProcessInfo pi;
+ pi.ParentProcessId = processMeta.th32ParentProcessID;
+ pi.ProcessId = processMeta.th32ProcessID;
+ pi.CreationTime = {0, 0};
+ pi.DevicePath = L"";
+
+ FILETIME creationTime, null_time;
+ auto ok = GetProcessTimes(process, &creationTime, &null_time, &null_time,
+ &null_time);
+ if (ok) {
+ pi.CreationTime = creationTime;
+ }
+ wchar_t imagepath[MAX_PATH + 1];
+ if (K32GetProcessImageFileNameW(
+ process, imagepath, sizeof(imagepath) / sizeof(*imagepath)) != 0) {
+ pi.DevicePath = imagepath;
+ }
+ return pi;
}
-WindowsSplitTunnel::WindowsSplitTunnel(QObject* parent) : QObject(parent) {
+} // namespace
+
+std::unique_ptr WindowsSplitTunnel::create(
+ WindowsFirewall* fw) {
+ if (fw == nullptr) {
+ // Pre-Condition:
+ // Make sure the Windows Firewall has created the sublayer
+ // otherwise the driver will fail to initialize
+ logger.error() << "Failed to did not pass a WindowsFirewall obj"
+ << "The Driver cannot work with the sublayer not created";
+ return nullptr;
+ }
+ // 00: Check if we conflict with mullvad, if so.
if (detectConflict()) {
logger.error() << "Conflict detected, abort Split-Tunnel init.";
- uninstallDriver();
- return;
+ return nullptr;
}
-
- m_tries = 0;
-
+ // 01: Check if the driver is installed, if not do so.
if (!isInstalled()) {
logger.debug() << "Driver is not Installed, doing so";
auto handle = installDriver();
if (handle == INVALID_HANDLE_VALUE) {
WindowsUtils::windowsLog("Failed to install Driver");
- return;
+ return nullptr;
}
logger.debug() << "Driver installed";
CloseServiceHandle(handle);
} else {
- logger.debug() << "Driver is installed";
+ logger.debug() << "Driver was installed";
}
- initDriver();
+ // 02: Now check if the service is running
+ auto driver_manager =
+ WindowsServiceManager::open(QString::fromWCharArray(DRIVER_SERVICE_NAME));
+ if (Q_UNLIKELY(driver_manager == nullptr)) {
+ // Let's be fair if we end up here,
+ // after checking it exists and installing it,
+ // this is super unlikeley
+ Q_ASSERT(false);
+ logger.error()
+ << "WindowsServiceManager was unable fo find Split Tunnel service?";
+ return nullptr;
+ }
+ if (!driver_manager->isRunning()) {
+ logger.debug() << "Driver is not running, starting it";
+ // Start the service
+ if (!driver_manager->startService()) {
+ logger.error() << "Failed to start Split Tunnel Service";
+ return nullptr;
+ };
+ }
+ // 03: Open the Driver Symlink
+ auto driverFile = CreateFileW(DRIVER_SYMLINK, GENERIC_READ | GENERIC_WRITE, 0,
+ nullptr, OPEN_EXISTING, 0, nullptr);
+ ;
+ if (driverFile == INVALID_HANDLE_VALUE) {
+ WindowsUtils::windowsLog("Failed to open Driver: ");
+ // Only once, if the opening did not work. Try to reboot it. #
+ logger.info()
+ << "Failed to open driver, attempting only once to reboot driver";
+ if (!driver_manager->stopService()) {
+ logger.error() << "Unable stop driver";
+ return nullptr;
+ };
+ logger.info() << "Stopped driver, starting it again.";
+ if (!driver_manager->startService()) {
+ logger.error() << "Unable start driver";
+ return nullptr;
+ };
+ logger.info() << "Opening again.";
+ driverFile = CreateFileW(DRIVER_SYMLINK, GENERIC_READ | GENERIC_WRITE, 0,
+ nullptr, OPEN_EXISTING, 0, nullptr);
+ if (driverFile == INVALID_HANDLE_VALUE) {
+ logger.error() << "Opening Failed again, sorry!";
+ return nullptr;
+ }
+ }
+ if (!initDriver(driverFile)) {
+ logger.error() << "Failed to init driver";
+ return nullptr;
+ }
+ // We're ready to talk to the driver, it's alive and setup.
+ return std::make_unique(driverFile);
+}
+
+bool WindowsSplitTunnel::initDriver(HANDLE driverIO) {
+ // We need to now check the state and init it, if required
+ auto state = getState(driverIO);
+ if (state == STATE_UNKNOWN) {
+ logger.debug() << "Cannot check if driver is initialized";
+ return false;
+ }
+ if (state >= STATE_INITIALIZED) {
+ logger.debug() << "Driver already initialized: " << state;
+ // Reset Driver as it has wfp handles probably >:(
+ resetDriver(driverIO);
+
+ auto newState = getState(driverIO);
+ logger.debug() << "New state after reset:" << newState;
+ if (newState >= STATE_INITIALIZED) {
+ logger.debug() << "Reset unsuccesfull";
+ return false;
+ }
+ }
+
+ DWORD bytesReturned;
+ auto ok = DeviceIoControl(driverIO, IOCTL_INITIALIZE, nullptr, 0, nullptr, 0,
+ &bytesReturned, nullptr);
+ if (!ok) {
+ auto err = GetLastError();
+ logger.error() << "Driver init failed err -" << err;
+ logger.error() << "State:" << getState(driverIO);
+
+ return false;
+ }
+ logger.debug() << "Driver initialized" << getState(driverIO);
+ return true;
+}
+
+WindowsSplitTunnel::WindowsSplitTunnel(HANDLE driverIO) : m_driver(driverIO) {
+ logger.debug() << "Connected to the Driver";
+
+ Q_ASSERT(getState() == STATE_INITIALIZED);
}
WindowsSplitTunnel::~WindowsSplitTunnel() {
@@ -53,73 +277,12 @@ WindowsSplitTunnel::~WindowsSplitTunnel() {
uninstallDriver();
}
-void WindowsSplitTunnel::initDriver() {
- if (detectConflict()) {
- logger.error() << "Conflict detected, abort Split-Tunnel init.";
- return;
- }
- logger.debug() << "Try to open Split Tunnel Driver";
- // Open the Driver Symlink
- m_driver = CreateFileW(DRIVER_SYMLINK, GENERIC_READ | GENERIC_WRITE, 0,
- nullptr, OPEN_EXISTING, 0, nullptr);
- ;
- if (m_driver == INVALID_HANDLE_VALUE && m_tries < 500) {
- WindowsUtils::windowsLog("Failed to open Driver: ");
- m_tries++;
- Sleep(100);
- // If the handle is not present, try again after the serivce has started;
- auto driver_manager = WindowsServiceManager(DRIVER_SERVICE_NAME);
- QObject::connect(&driver_manager, &WindowsServiceManager::serviceStarted,
- this, &WindowsSplitTunnel::initDriver);
- driver_manager.startService();
- return;
- }
-
- logger.debug() << "Connected to the Driver";
- // Reset Driver as it has wfp handles probably >:(
-
- if (!WindowsFirewall::instance()->init()) {
- logger.error() << "Init WFP-Sublayer failed, driver won't be functional";
- return;
- }
-
- // We need to now check the state and init it, if required
-
- auto state = getState();
- if (state == STATE_UNKNOWN) {
- logger.debug() << "Cannot check if driver is initialized";
- }
- if (state >= STATE_INITIALIZED) {
- logger.debug() << "Driver already initialized: " << state;
- reset();
-
- auto newState = getState();
- logger.debug() << "New state after reset:" << newState;
- if (newState >= STATE_INITIALIZED) {
- logger.debug() << "Reset unsuccesfull";
- return;
- }
- }
-
- DWORD bytesReturned;
- auto ok = DeviceIoControl(m_driver, IOCTL_INITIALIZE, nullptr, 0, nullptr, 0,
- &bytesReturned, nullptr);
- if (!ok) {
- auto err = GetLastError();
- logger.error() << "Driver init failed err -" << err;
- logger.error() << "State:" << getState();
-
- return;
- }
- logger.debug() << "Driver initialized" << getState();
-}
-
-void WindowsSplitTunnel::setRules(const QStringList& appPaths) {
+bool WindowsSplitTunnel::excludeApps(const QStringList& appPaths) {
auto state = getState();
if (state != STATE_READY && state != STATE_RUNNING) {
logger.warning() << "Driver is not in the right State to set Rules"
<< state;
- return;
+ return false;
}
logger.debug() << "Pushing new Ruleset for Split-Tunnel " << state;
@@ -133,12 +296,13 @@ void WindowsSplitTunnel::setRules(const QStringList& appPaths) {
auto err = GetLastError();
WindowsUtils::windowsLog("Set Config Failed:");
logger.error() << "Failed to set Config err code " << err;
- return;
+ return false;
}
- logger.debug() << "New Configuration applied: " << getState();
+ logger.debug() << "New Configuration applied: " << stateString();
+ return true;
}
-void WindowsSplitTunnel::start(int inetAdapterIndex, int vpnAdapterIndex) {
+bool WindowsSplitTunnel::start(int inetAdapterIndex, int vpnAdapterIndex) {
// To Start we need to send 2 things:
// Network info (what is vpn what is network)
logger.debug() << "Starting SplitTunnel";
@@ -151,7 +315,7 @@ void WindowsSplitTunnel::start(int inetAdapterIndex, int vpnAdapterIndex) {
0, &bytesReturned, nullptr);
if (!ok) {
logger.error() << "Driver init failed";
- return;
+ return false;
}
}
@@ -164,16 +328,16 @@ void WindowsSplitTunnel::start(int inetAdapterIndex, int vpnAdapterIndex) {
nullptr);
if (!ok) {
logger.error() << "Failed to set Process Config";
- return;
+ return false;
}
- logger.debug() << "Set Process Config ok || new State:" << getState();
+ logger.debug() << "Set Process Config ok || new State:" << stateString();
}
if (getState() == STATE_INITIALIZED) {
logger.warning() << "Driver is still not ready after process list send";
- return;
+ return false;
}
- logger.debug() << "Driver is ready || new State:" << getState();
+ logger.debug() << "Driver is ready || new State:" << stateString();
auto config = generateIPConfiguration(inetAdapterIndex, vpnAdapterIndex);
auto ok = DeviceIoControl(m_driver, IOCTL_REGISTER_IP_ADDRESSES, &config[0],
@@ -181,9 +345,10 @@ void WindowsSplitTunnel::start(int inetAdapterIndex, int vpnAdapterIndex) {
nullptr);
if (!ok) {
logger.error() << "Failed to set Network Config";
- return;
+ return false;
}
- logger.debug() << "New Network Config Applied || new State:" << getState();
+ logger.debug() << "New Network Config Applied || new State:" << stateString();
+ return true;
}
void WindowsSplitTunnel::stop() {
@@ -197,25 +362,27 @@ void WindowsSplitTunnel::stop() {
logger.debug() << "Stopping Split tunnel successfull";
}
-void WindowsSplitTunnel::reset() {
+bool WindowsSplitTunnel::resetDriver(HANDLE driverIO) {
DWORD bytesReturned;
- auto ok = DeviceIoControl(m_driver, IOCTL_ST_RESET, nullptr, 0, nullptr, 0,
+ auto ok = DeviceIoControl(driverIO, IOCTL_ST_RESET, nullptr, 0, nullptr, 0,
&bytesReturned, nullptr);
if (!ok) {
logger.error() << "Reset Split tunnel not successfull";
- return;
+ return false;
}
logger.debug() << "Reset Split tunnel successfull";
+ return true;
}
-DRIVER_STATE WindowsSplitTunnel::getState() {
- if (m_driver == INVALID_HANDLE_VALUE) {
+// static
+WindowsSplitTunnel::DRIVER_STATE WindowsSplitTunnel::getState(HANDLE driverIO) {
+ if (driverIO == INVALID_HANDLE_VALUE) {
logger.debug() << "Can't query State from non Opened Driver";
return STATE_UNKNOWN;
}
DWORD bytesReturned;
SIZE_T outBuffer;
- bool ok = DeviceIoControl(m_driver, IOCTL_GET_STATE, nullptr, 0, &outBuffer,
+ bool ok = DeviceIoControl(driverIO, IOCTL_GET_STATE, nullptr, 0, &outBuffer,
sizeof(outBuffer), &bytesReturned, nullptr);
if (!ok) {
WindowsUtils::windowsLog("getState response failure");
@@ -225,7 +392,10 @@ DRIVER_STATE WindowsSplitTunnel::getState() {
WindowsUtils::windowsLog("getState response is empty");
return STATE_UNKNOWN;
}
- return static_cast(outBuffer);
+ return static_cast(outBuffer);
+}
+WindowsSplitTunnel::DRIVER_STATE WindowsSplitTunnel::getState() {
+ return getState(m_driver);
}
std::vector WindowsSplitTunnel::generateAppConfiguration(
@@ -273,58 +443,59 @@ std::vector WindowsSplitTunnel::generateAppConfiguration(
return outBuffer;
}
-std::vector WindowsSplitTunnel::generateIPConfiguration(
+std::vector WindowsSplitTunnel::generateIPConfiguration(
int inetAdapterIndex, int vpnAdapterIndex) {
- std::vector out(sizeof(IP_ADDRESSES_CONFIG));
+ std::vector out(sizeof(IP_ADDRESSES_CONFIG));
auto config = reinterpret_cast(&out[0]);
auto ifaces = QNetworkInterface::allInterfaces();
- if (vpnAdapterIndex == 0) {
+ if (vpnAdapterIndex == 0) {
vpnAdapterIndex = WindowsCommons::VPNAdapterIndex();
}
-
// Always the VPN
- getAddress(vpnAdapterIndex, &config->TunnelIpv4,
- &config->TunnelIpv6);
- // 2nd best route
- getAddress(inetAdapterIndex, &config->InternetIpv4, &config->InternetIpv6);
+ if (!getAddress(vpnAdapterIndex, &config->TunnelIpv4,
+ &config->TunnelIpv6)) {
+ return {};
+ }
+ // 2nd best route is usually the internet adapter
+ if (!getAddress(inetAdapterIndex, &config->InternetIpv4,
+ &config->InternetIpv6)) {
+ return {};
+ };
return out;
}
-void WindowsSplitTunnel::getAddress(int adapterIndex, IN_ADDR* out_ipv4,
+bool WindowsSplitTunnel::getAddress(int adapterIndex, IN_ADDR* out_ipv4,
IN6_ADDR* out_ipv6) {
QNetworkInterface target =
QNetworkInterface::interfaceFromIndex(adapterIndex);
logger.debug() << "Getting adapter info for:" << target.humanReadableName();
- // take the first v4/v6 Adress and convert to in_addr
- for (auto address : target.addressEntries()) {
- if (address.ip().protocol() == QAbstractSocket::IPv4Protocol) {
- auto adrr = address.ip().toString();
- std::wstring wstr = adrr.toStdWString();
- logger.debug() << "IpV4" << logger.sensitive(adrr);
- PCWSTR w_str_ip = wstr.c_str();
- auto ok = InetPtonW(AF_INET, w_str_ip, out_ipv4);
- if (ok != 1) {
- logger.debug() << "Ipv4 Conversation error" << WSAGetLastError();
+ auto get = [&target](QAbstractSocket::NetworkLayerProtocol protocol) {
+ for (auto address : target.addressEntries()) {
+ if (address.ip().protocol() != protocol) {
+ continue;
}
- break;
+ return address.ip().toString().toStdWString();
}
+ return std::wstring{};
+ };
+ auto ipv4 = get(QAbstractSocket::IPv4Protocol);
+ auto ipv6 = get(QAbstractSocket::IPv6Protocol);
+
+ if (InetPtonW(AF_INET, ipv4.c_str(), out_ipv4) != 1) {
+ logger.debug() << "Ipv4 Conversation error" << WSAGetLastError();
+ return false;
}
- for (auto address : target.addressEntries()) {
- if (address.ip().protocol() == QAbstractSocket::IPv6Protocol) {
- auto adrr = address.ip().toString();
- std::wstring wstr = adrr.toStdWString();
- logger.debug() << "IpV6" << logger.sensitive(adrr);
- PCWSTR w_str_ip = wstr.c_str();
- auto ok = InetPtonW(AF_INET6, w_str_ip, out_ipv6);
- if (ok != 1) {
- logger.error() << "Ipv6 Conversation error" << WSAGetLastError();
- }
- break;
- }
+ if (ipv6.empty()) {
+ std::memset(out_ipv6, 0x00, sizeof(IN6_ADDR));
+ return true;
}
+ if (InetPtonW(AF_INET6, ipv6.c_str(), out_ipv6) != 1) {
+ logger.debug() << "Ipv6 Conversation error" << WSAGetLastError();
+ }
+ return true;
}
std::vector WindowsSplitTunnel::generateProcessBlob() {
@@ -411,33 +582,6 @@ std::vector WindowsSplitTunnel::generateProcessBlob() {
return out;
}
-void WindowsSplitTunnel::close() {
- CloseHandle(m_driver);
- m_driver = INVALID_HANDLE_VALUE;
-}
-
-ProcessInfo WindowsSplitTunnel::getProcessInfo(
- HANDLE process, const PROCESSENTRY32W& processMeta) {
- ProcessInfo pi;
- pi.ParentProcessId = processMeta.th32ParentProcessID;
- pi.ProcessId = processMeta.th32ProcessID;
- pi.CreationTime = {0, 0};
- pi.DevicePath = L"";
-
- FILETIME creationTime, null_time;
- auto ok = GetProcessTimes(process, &creationTime, &null_time, &null_time,
- &null_time);
- if (ok) {
- pi.CreationTime = creationTime;
- }
- wchar_t imagepath[MAX_PATH + 1];
- if (K32GetProcessImageFileNameW(
- process, imagepath, sizeof(imagepath) / sizeof(*imagepath)) != 0) {
- pi.DevicePath = imagepath;
- }
- return pi;
-}
-
// static
SC_HANDLE WindowsSplitTunnel::installDriver() {
LPCWSTR displayName = L"Amnezia Split Tunnel Service";
@@ -448,15 +592,15 @@ SC_HANDLE WindowsSplitTunnel::installDriver() {
return (SC_HANDLE)INVALID_HANDLE_VALUE;
}
auto path = driver.absolutePath() + "/" + DRIVER_FILENAME;
- LPCWSTR binPath = (const wchar_t*)path.utf16();
+ auto binPath = (const wchar_t*)path.utf16();
auto scm_rights = SC_MANAGER_ALL_ACCESS;
- auto serviceManager = OpenSCManager(NULL, // local computer
- NULL, // servicesActive database
+ auto serviceManager = OpenSCManager(nullptr, // local computer
+ nullptr, // servicesActive database
scm_rights);
- auto service = CreateService(serviceManager, DRIVER_SERVICE_NAME, displayName,
- SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER,
- SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,
- binPath, nullptr, 0, nullptr, nullptr, nullptr);
+ auto service = CreateService(
+ serviceManager, DRIVER_SERVICE_NAME, displayName, SERVICE_ALL_ACCESS,
+ SERVICE_KERNEL_DRIVER, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, binPath,
+ nullptr, nullptr, nullptr, nullptr, nullptr);
CloseServiceHandle(serviceManager);
return service;
}
@@ -554,3 +698,25 @@ bool WindowsSplitTunnel::detectConflict() {
CloseServiceHandle(servicehandle);
return err == ERROR_SERVICE_DOES_NOT_EXIST;
}
+
+bool WindowsSplitTunnel::isRunning() { return getState() == STATE_RUNNING; }
+QString WindowsSplitTunnel::stateString() {
+ switch (getState()) {
+ case STATE_UNKNOWN:
+ return "STATE_UNKNOWN";
+ case STATE_NONE:
+ return "STATE_NONE";
+ case STATE_STARTED:
+ return "STATE_STARTED";
+ case STATE_INITIALIZED:
+ return "STATE_INITIALIZED";
+ case STATE_READY:
+ return "STATE_READY";
+ case STATE_RUNNING:
+ return "STATE_RUNNING";
+ case STATE_ZOMBIE:
+ return "STATE_ZOMBIE";
+ break;
+ }
+ return {};
+}
diff --git a/client/platforms/windows/daemon/windowssplittunnel.h b/client/platforms/windows/daemon/windowssplittunnel.h
index 466036d6..85c827f6 100644
--- a/client/platforms/windows/daemon/windowssplittunnel.h
+++ b/client/platforms/windows/daemon/windowssplittunnel.h
@@ -8,6 +8,7 @@
#include
#include
#include
+#include
// Note: the ws2tcpip.h import must come before the others.
// clang-format off
@@ -18,160 +19,78 @@
#include
#include
-// States for GetState
-enum DRIVER_STATE {
- STATE_UNKNOWN = -1,
- STATE_NONE = 0,
- STATE_STARTED = 1,
- STATE_INITIALIZED = 2,
- STATE_READY = 3,
- STATE_RUNNING = 4,
- STATE_ZOMBIE = 5,
-};
+class WindowsFirewall;
-#ifndef CTL_CODE
-
-# define FILE_ANY_ACCESS 0x0000
-
-# define METHOD_BUFFERED 0
-# define METHOD_IN_DIRECT 1
-# define METHOD_NEITHER 3
-
-# define CTL_CODE(DeviceType, Function, Method, Access) \
- (((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method))
-#endif
-
-// Known ControlCodes
-#define IOCTL_INITIALIZE CTL_CODE(0x8000, 1, METHOD_NEITHER, FILE_ANY_ACCESS)
-
-#define IOCTL_DEQUEUE_EVENT \
- CTL_CODE(0x8000, 2, METHOD_BUFFERED, FILE_ANY_ACCESS)
-
-#define IOCTL_REGISTER_PROCESSES \
- CTL_CODE(0x8000, 3, METHOD_BUFFERED, FILE_ANY_ACCESS)
-
-#define IOCTL_REGISTER_IP_ADDRESSES \
- CTL_CODE(0x8000, 4, METHOD_BUFFERED, FILE_ANY_ACCESS)
-
-#define IOCTL_GET_IP_ADDRESSES \
- CTL_CODE(0x8000, 5, METHOD_BUFFERED, FILE_ANY_ACCESS)
-
-#define IOCTL_SET_CONFIGURATION \
- CTL_CODE(0x8000, 6, METHOD_BUFFERED, FILE_ANY_ACCESS)
-
-#define IOCTL_GET_CONFIGURATION \
- CTL_CODE(0x8000, 7, METHOD_BUFFERED, FILE_ANY_ACCESS)
-
-#define IOCTL_CLEAR_CONFIGURATION \
- CTL_CODE(0x8000, 8, METHOD_NEITHER, FILE_ANY_ACCESS)
-
-#define IOCTL_GET_STATE CTL_CODE(0x8000, 9, METHOD_BUFFERED, FILE_ANY_ACCESS)
-
-#define IOCTL_QUERY_PROCESS \
- CTL_CODE(0x8000, 10, METHOD_BUFFERED, FILE_ANY_ACCESS)
-
-#define IOCTL_ST_RESET CTL_CODE(0x8000, 11, METHOD_NEITHER, FILE_ANY_ACCESS)
-
-// Driver Configuration structures
-
-typedef struct {
- // Offset into buffer region that follows all entries.
- // The image name uses the device path.
- SIZE_T ImageNameOffset;
- // Length of the String
- USHORT ImageNameLength;
-} CONFIGURATION_ENTRY;
-
-typedef struct {
- // Number of entries immediately following the header.
- SIZE_T NumEntries;
-
- // Total byte length: header + entries + string buffer.
- SIZE_T TotalLength;
-} CONFIGURATION_HEADER;
-
-// Used to Configure Which IP is network/vpn
-typedef struct {
- IN_ADDR TunnelIpv4;
- IN_ADDR InternetIpv4;
-
- IN6_ADDR TunnelIpv6;
- IN6_ADDR InternetIpv6;
-} IP_ADDRESSES_CONFIG;
-
-// Used to Define Which Processes are alive on activation
-typedef struct {
- SIZE_T NumEntries;
- SIZE_T TotalLength;
-} PROCESS_DISCOVERY_HEADER;
-
-typedef struct {
- HANDLE ProcessId;
- HANDLE ParentProcessId;
-
- SIZE_T ImageNameOffset;
- USHORT ImageNameLength;
-} PROCESS_DISCOVERY_ENTRY;
-
-typedef struct {
- DWORD ProcessId;
- DWORD ParentProcessId;
- FILETIME CreationTime;
- std::wstring DevicePath;
-} ProcessInfo;
-
-class WindowsSplitTunnel final : public QObject {
- Q_OBJECT
- Q_DISABLE_COPY_MOVE(WindowsSplitTunnel)
+class WindowsSplitTunnel final {
public:
- explicit WindowsSplitTunnel(QObject* parent);
+ /**
+ * @brief Installs and Initializes the Split Tunnel Driver.
+ *
+ * @param fw -
+ * @return std::unique_ptr - Is null on failure.
+ */
+ static std::unique_ptr create(WindowsFirewall* fw);
+
+ /**
+ * @brief Construct a new Windows Split Tunnel object
+ *
+ * @param driverIO - The Handle to the Driver's IO file, it assumes the driver
+ * is in STATE_INITIALIZED and the Firewall has been setup.
+ * Prefer using create() to get to this state.
+ */
+ WindowsSplitTunnel(HANDLE driverIO);
+ /**
+ * @brief Destroy the Windows Split Tunnel object and uninstalls the Driver.
+ */
~WindowsSplitTunnel();
// void excludeApps(const QStringList& paths);
// Excludes an Application from the VPN
- void setRules(const QStringList& appPaths);
+ bool excludeApps(const QStringList& appPaths);
// Fetches and Pushed needed info to move to engaged mode
- void start(int inetAdapterIndex, int vpnAdapterIndex = 0);
+ bool start(int inetAdapterIndex, int vpnAdapterIndex = 0);
// Deletes Rules and puts the driver into passive mode
void stop();
- // Resets the Whole Driver
- void reset();
- // Just close connection, leave state as is
- void close();
+ // Returns true if the split-tunnel driver is now up and running.
+ bool isRunning();
+ static bool detectConflict();
+
+ // States for GetState
+ enum DRIVER_STATE {
+ STATE_UNKNOWN = -1,
+ STATE_NONE = 0,
+ STATE_STARTED = 1,
+ STATE_INITIALIZED = 2,
+ STATE_READY = 3,
+ STATE_RUNNING = 4,
+ STATE_ZOMBIE = 5,
+ };
+
+ private:
// Installes the Kernel Driver as Driver Service
static SC_HANDLE installDriver();
static bool uninstallDriver();
static bool isInstalled();
- static bool detectConflict();
+ static bool initDriver(HANDLE driverIO);
+ static DRIVER_STATE getState(HANDLE driverIO);
+ static bool resetDriver(HANDLE driverIO);
- private slots:
- void initDriver();
-
- private:
HANDLE m_driver = INVALID_HANDLE_VALUE;
- constexpr static const auto DRIVER_SYMLINK = L"\\\\.\\MULLVADSPLITTUNNEL";
- constexpr static const auto DRIVER_FILENAME = "mullvad-split-tunnel.sys";
- constexpr static const auto DRIVER_SERVICE_NAME = L"AmneziaVPNSplitTunnel";
- constexpr static const auto MV_SERVICE_NAME = L"MullvadVPN";
DRIVER_STATE getState();
-
- int m_tries;
- // Initializes the WFP Sublayer
- bool initSublayer();
+ QString stateString();
// Generates a Configuration for Each APP
std::vector generateAppConfiguration(const QStringList& appPaths);
// Generates a Configuration which IP's are VPN and which network
- std::vector generateIPConfiguration(int inetAdapterIndex, int vpnAdapterIndex = 0);
+ std::vector generateIPConfiguration(int inetAdapterIndex, int vpnAdapterIndex = 0);
std::vector generateProcessBlob();
- void getAddress(int adapterIndex, IN_ADDR* out_ipv4, IN6_ADDR* out_ipv6);
+ [[nodiscard]] bool getAddress(int adapterIndex, IN_ADDR* out_ipv4,
+ IN6_ADDR* out_ipv6);
// Collects info about an Opened Process
- ProcessInfo getProcessInfo(HANDLE process,
- const PROCESSENTRY32W& processMeta);
// Converts a path to a Dos Path:
// e.g C:/a.exe -> /harddisk0/a.exe
diff --git a/client/platforms/windows/daemon/wireguardutilswindows.cpp b/client/platforms/windows/daemon/wireguardutilswindows.cpp
index 1a220235..0823b9d7 100644
--- a/client/platforms/windows/daemon/wireguardutilswindows.cpp
+++ b/client/platforms/windows/daemon/wireguardutilswindows.cpp
@@ -24,8 +24,20 @@ namespace {
Logger logger("WireguardUtilsWindows");
}; // namespace
-WireguardUtilsWindows::WireguardUtilsWindows(QObject* parent)
- : WireguardUtils(parent), m_tunnel(this), m_routeMonitor(this) {
+std::unique_ptr WireguardUtilsWindows::create(
+ WindowsFirewall* fw, QObject* parent) {
+ if (!fw) {
+ logger.error() << "WireguardUtilsWindows::create: no wfp handle";
+ return {};
+ }
+
+ // Can't use make_unique here as the Constructor is private :(
+ auto utils = new WireguardUtilsWindows(parent, fw);
+ return std::unique_ptr(utils);
+}
+
+WireguardUtilsWindows::WireguardUtilsWindows(QObject* parent, WindowsFirewall* fw)
+ : WireguardUtils(parent), m_tunnel(this), m_firewall(fw) {
MZ_COUNT_CTOR(WireguardUtilsWindows);
logger.debug() << "WireguardUtilsWindows created.";
@@ -114,13 +126,13 @@ bool WireguardUtilsWindows::addInterface(const InterfaceConfig& config) {
return false;
}
m_luid = luid.Value;
- m_routeMonitor.setLuid(luid.Value);
+ m_routeMonitor = new WindowsRouteMonitor(luid.Value, this);
if (config.m_killSwitchEnabled) {
// Enable the windows firewall
NET_IFINDEX ifindex;
ConvertInterfaceLuidToIndex(&luid, &ifindex);
- WindowsFirewall::instance()->enableKillSwitch(ifindex);
+ m_firewall->enableInterface(ifindex);
}
logger.debug() << "Registration completed";
@@ -128,7 +140,11 @@ bool WireguardUtilsWindows::addInterface(const InterfaceConfig& config) {
}
bool WireguardUtilsWindows::deleteInterface() {
- WindowsFirewall::instance()->disableKillSwitch();
+ if (m_routeMonitor) {
+ m_routeMonitor->deleteLater();
+ }
+
+ m_firewall->disableKillSwitch();
m_tunnel.stop();
return true;
}
@@ -141,7 +157,7 @@ bool WireguardUtilsWindows::updatePeer(const InterfaceConfig& config) {
if (config.m_killSwitchEnabled) {
// Enable the windows firewall for this peer.
- WindowsFirewall::instance()->enablePeerTraffic(config);
+ m_firewall->enablePeerTraffic(config);
}
logger.debug() << "Configuring peer" << publicKey.toHex()
<< "via" << config.m_serverIpv4AddrIn;
@@ -171,9 +187,9 @@ bool WireguardUtilsWindows::updatePeer(const InterfaceConfig& config) {
}
// Exclude the server address, except for multihop exit servers.
- if (config.m_hopType != InterfaceConfig::MultiHopExit) {
- m_routeMonitor.addExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
- m_routeMonitor.addExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
+ if (m_routeMonitor && config.m_hopType != InterfaceConfig::MultiHopExit) {
+ m_routeMonitor->addExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
+ m_routeMonitor->addExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
}
QString reply = m_tunnel.uapiCommand(message);
@@ -186,13 +202,13 @@ bool WireguardUtilsWindows::deletePeer(const InterfaceConfig& config) {
QByteArray::fromBase64(qPrintable(config.m_serverPublicKey));
// Clear exclustion routes for this peer.
- if (config.m_hopType != InterfaceConfig::MultiHopExit) {
- m_routeMonitor.deleteExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
- m_routeMonitor.deleteExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
+ if (m_routeMonitor && config.m_hopType != InterfaceConfig::MultiHopExit) {
+ m_routeMonitor->deleteExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
+ m_routeMonitor->deleteExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
}
// Disable the windows firewall for this peer.
- WindowsFirewall::instance()->disablePeerTraffic(config.m_serverPublicKey);
+ m_firewall->disablePeerTraffic(config.m_serverPublicKey);
QString message;
QTextStream out(&message);
@@ -238,6 +254,13 @@ void WireguardUtilsWindows::buildMibForwardRow(const IPAddress& prefix,
}
bool WireguardUtilsWindows::updateRoutePrefix(const IPAddress& prefix) {
+ if (m_routeMonitor && (prefix.prefixLength() == 0)) {
+ // If we are setting up a default route, instruct the route monitor to
+ // capture traffic to all non-excluded destinations
+ m_routeMonitor->setDetaultRouteCapture(true);
+ }
+ // Build the route
+
MIB_IPFORWARD_ROW2 entry;
buildMibForwardRow(prefix, &entry);
@@ -255,6 +278,12 @@ bool WireguardUtilsWindows::updateRoutePrefix(const IPAddress& prefix) {
}
bool WireguardUtilsWindows::deleteRoutePrefix(const IPAddress& prefix) {
+ if (m_routeMonitor && (prefix.prefixLength() == 0)) {
+ // Deactivate the route capture feature.
+ m_routeMonitor->setDetaultRouteCapture(false);
+ }
+ // Build the route
+
MIB_IPFORWARD_ROW2 entry;
buildMibForwardRow(prefix, &entry);
@@ -272,9 +301,28 @@ bool WireguardUtilsWindows::deleteRoutePrefix(const IPAddress& prefix) {
}
bool WireguardUtilsWindows::addExclusionRoute(const IPAddress& prefix) {
- return m_routeMonitor.addExclusionRoute(prefix);
+ return m_routeMonitor->addExclusionRoute(prefix);
}
bool WireguardUtilsWindows::deleteExclusionRoute(const IPAddress& prefix) {
- return m_routeMonitor.deleteExclusionRoute(prefix);
+ return m_routeMonitor->deleteExclusionRoute(prefix);
+}
+
+bool WireguardUtilsWindows::excludeLocalNetworks(
+ const QList& addresses) {
+ // If the interface isn't up then something went horribly wrong.
+ Q_ASSERT(m_routeMonitor);
+ // For each destination - attempt to exclude it from the VPN tunnel.
+ bool result = true;
+ for (const IPAddress& prefix : addresses) {
+ if (!m_routeMonitor->addExclusionRoute(prefix)) {
+ result = false;
+ }
+ }
+ // Permit LAN traffic through the firewall.
+ if (!m_firewall->enableLanBypass(addresses)) {
+ result = false;
+ }
+
+ return result;
}
diff --git a/client/platforms/windows/daemon/wireguardutilswindows.h b/client/platforms/windows/daemon/wireguardutilswindows.h
index 4fd67fad..276966b4 100644
--- a/client/platforms/windows/daemon/wireguardutilswindows.h
+++ b/client/platforms/windows/daemon/wireguardutilswindows.h
@@ -9,16 +9,21 @@
#include
#include
+#include
#include "daemon/wireguardutils.h"
#include "windowsroutemonitor.h"
#include "windowstunnelservice.h"
+class WindowsFirewall;
+class WindowsRouteMonitor;
+
class WireguardUtilsWindows final : public WireguardUtils {
Q_OBJECT
public:
- WireguardUtilsWindows(QObject* parent);
+ static std::unique_ptr create(WindowsFirewall* fw,
+ QObject* parent);
~WireguardUtilsWindows();
bool interfaceExists() override { return m_tunnel.isRunning(); }
@@ -39,15 +44,19 @@ class WireguardUtilsWindows final : public WireguardUtils {
bool addExclusionRoute(const IPAddress& prefix) override;
bool deleteExclusionRoute(const IPAddress& prefix) override;
+ bool WireguardUtilsWindows::excludeLocalNetworks(const QList& addresses) override;
+
signals:
void backendFailure();
private:
+ WireguardUtilsWindows(QObject* parent, WindowsFirewall* fw);
void buildMibForwardRow(const IPAddress& prefix, void* row);
quint64 m_luid = 0;
WindowsTunnelService m_tunnel;
- WindowsRouteMonitor m_routeMonitor;
+ QPointer m_routeMonitor;
+ QPointer m_firewall;
};
#endif // WIREGUARDUTILSWINDOWS_H
diff --git a/client/platforms/windows/windowsservicemanager.cpp b/client/platforms/windows/windowsservicemanager.cpp
index 3a334224..d5a21170 100644
--- a/client/platforms/windows/windowsservicemanager.cpp
+++ b/client/platforms/windows/windowsservicemanager.cpp
@@ -4,6 +4,7 @@
#include "windowsservicemanager.h"
+#include
#include
#include "Windows.h"
@@ -16,35 +17,44 @@ namespace {
Logger logger("WindowsServiceManager");
}
-WindowsServiceManager::WindowsServiceManager(LPCWSTR serviceName) {
+WindowsServiceManager::WindowsServiceManager(SC_HANDLE serviceManager,
+ SC_HANDLE service)
+ : QObject(qApp), m_serviceManager(serviceManager), m_service(service) {
+ m_timer.setSingleShot(false);
+}
+
+std::unique_ptr WindowsServiceManager::open(
+ const QString serviceName) {
+ LPCWSTR service = (const wchar_t*)serviceName.utf16();
+
DWORD err = NULL;
auto scm_rights = SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE |
SC_MANAGER_QUERY_LOCK_STATUS | STANDARD_RIGHTS_READ;
- m_serviceManager = OpenSCManager(NULL, // local computer
- NULL, // servicesActive database
- scm_rights);
+ auto manager = OpenSCManager(NULL, // local computer
+ NULL, // servicesActive database
+ scm_rights);
err = GetLastError();
if (err != NULL) {
logger.error() << " OpenSCManager failed code: " << err;
- return;
+ return {};
}
logger.debug() << "OpenSCManager access given - " << err;
- logger.debug() << "Opening Service - "
- << QString::fromWCharArray(serviceName);
+ logger.debug() << "Opening Service - " << serviceName;
// Try to get an elevated handle
- m_service = OpenService(m_serviceManager, // SCM database
- serviceName, // name of service
- (GENERIC_READ | SERVICE_START | SERVICE_STOP));
+ auto serviceHandle =
+ OpenService(manager, // SCM database
+ service, // name of service
+ (GENERIC_READ | SERVICE_START | SERVICE_STOP));
err = GetLastError();
if (err != NULL) {
+ CloseServiceHandle(manager);
WindowsUtils::windowsLog("OpenService failed");
- return;
+ return {};
}
- m_has_access = true;
- m_timer.setSingleShot(false);
logger.debug() << "Service manager execute access granted";
+ return std::make_unique(manager, serviceHandle);
}
WindowsServiceManager::~WindowsServiceManager() {
@@ -85,10 +95,6 @@ bool WindowsServiceManager::startPolling(DWORD goal_state, int max_wait_sec) {
SERVICE_STATUS_PROCESS WindowsServiceManager::getStatus() {
SERVICE_STATUS_PROCESS serviceStatus;
- if (!m_has_access) {
- logger.debug() << "Need read access to get service state";
- return serviceStatus;
- }
DWORD dwBytesNeeded; // Contains missing bytes if struct is too small?
QueryServiceStatusEx(m_service, // handle to service
SC_STATUS_PROCESS_INFO, // information level
@@ -119,10 +125,6 @@ bool WindowsServiceManager::startService() {
}
bool WindowsServiceManager::stopService() {
- if (!m_has_access) {
- logger.error() << "Need execute access to stop services";
- return false;
- }
auto state = getStatus().dwCurrentState;
if (state != SERVICE_RUNNING && state != SERVICE_START_PENDING) {
logger.warning() << ("Service stop not possible, as its not running");
diff --git a/client/platforms/windows/windowsservicemanager.h b/client/platforms/windows/windowsservicemanager.h
index 7638588f..31521245 100644
--- a/client/platforms/windows/windowsservicemanager.h
+++ b/client/platforms/windows/windowsservicemanager.h
@@ -12,7 +12,7 @@
#include "Winsvc.h"
/**
- * @brief The WindowsServiceManager provides control over the MozillaVPNBroker
+ * @brief The WindowsServiceManager provides control over the a
* service via SCM
*/
class WindowsServiceManager : public QObject {
@@ -20,7 +20,10 @@ class WindowsServiceManager : public QObject {
Q_DISABLE_COPY_MOVE(WindowsServiceManager)
public:
- WindowsServiceManager(LPCWSTR serviceName);
+ // Creates a WindowsServiceManager for the Named service.
+ // returns nullptr if
+ static std::unique_ptr open(const QString serviceName);
+ WindowsServiceManager(SC_HANDLE serviceManager, SC_HANDLE service);
~WindowsServiceManager();
// true if the Service is running
@@ -45,8 +48,6 @@ class WindowsServiceManager : public QObject {
// See
// SERVICE_STOPPED,SERVICE_STOP_PENDING,SERVICE_START_PENDING,SERVICE_RUNNING
SERVICE_STATUS_PROCESS getStatus();
- bool m_has_access = false;
- LPWSTR m_serviceName;
SC_HANDLE m_serviceManager;
SC_HANDLE m_service; // Service handle with r/w priv.
DWORD m_state_target;
diff --git a/client/protocols/xrayprotocol.cpp b/client/protocols/xrayprotocol.cpp
index 2dfbcc21..7c69ccde 100755
--- a/client/protocols/xrayprotocol.cpp
+++ b/client/protocols/xrayprotocol.cpp
@@ -1,7 +1,6 @@
#include "xrayprotocol.h"
#include "utilities.h"
-#include "containers/containers_defs.h"
#include "core/networkUtilities.h"
#include
@@ -22,9 +21,8 @@ XrayProtocol::XrayProtocol(const QJsonObject &configuration, QObject *parent):
XrayProtocol::~XrayProtocol()
{
+ qDebug() << "XrayProtocol::~XrayProtocol()";
XrayProtocol::stop();
- QThread::msleep(200);
- m_xrayProcess.close();
}
ErrorCode XrayProtocol::start()
@@ -36,10 +34,6 @@ ErrorCode XrayProtocol::start()
return lastError();
}
- if (Utils::processIsRunning(Utils::executable(xrayExecPath(), true))) {
- Utils::killProcessByName(Utils::executable(xrayExecPath(), true));
- }
-
#ifdef QT_DEBUG
m_xrayCfgFile.setAutoRemove(false);
#endif
@@ -54,9 +48,16 @@ ErrorCode XrayProtocol::start()
qDebug().noquote() << "XrayProtocol::start()"
<< xrayExecPath() << args.join(" ");
- m_xrayProcess.setProcessChannelMode(QProcess::MergedChannels);
+
+ m_xrayProcess.setProcessChannelMode(QProcess::MergedChannels);
m_xrayProcess.setProgram(xrayExecPath());
+
+ if (Utils::processIsRunning(Utils::executable("xray", false))) {
+ qDebug().noquote() << "kill previos xray";
+ Utils::killProcessByName(Utils::executable("xray", false));
+ }
+
m_xrayProcess.setArguments(args);
connect(&m_xrayProcess, &QProcess::readyReadStandardOutput, this, [this]() {
@@ -68,13 +69,9 @@ ErrorCode XrayProtocol::start()
connect(&m_xrayProcess, QOverload::of(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus) {
qDebug().noquote() << "XrayProtocol finished, exitCode, exitStatus" << exitCode << exitStatus;
setConnectionState(Vpn::ConnectionState::Disconnected);
- if (exitStatus != QProcess::NormalExit) {
- emit protocolError(amnezia::ErrorCode::XrayExecutableCrashed);
- stop();
- }
- if (exitCode != 0) {
- emit protocolError(amnezia::ErrorCode::InternalError);
- stop();
+ if ((exitStatus != QProcess::NormalExit) || (exitCode != 0)) {
+ emit protocolError(amnezia::ErrorCode::XrayExecutableCrashed);
+ emit setConnectionState(Vpn::ConnectionState::Error);
}
});
@@ -177,14 +174,14 @@ void XrayProtocol::stop()
IpcClient::Interface()->StartRoutingIpv6();
#endif
qDebug() << "XrayProtocol::stop()";
- m_xrayProcess.terminate();
+ m_xrayProcess.disconnect();
+ m_xrayProcess.kill();
+ m_xrayProcess.waitForFinished(3000);
if (m_t2sProcess) {
m_t2sProcess->stop();
}
-#ifdef Q_OS_WIN
- Utils::signalCtrl(m_xrayProcess.processId(), CTRL_C_EVENT);
-#endif
+ setConnectionState(Vpn::ConnectionState::Disconnected);
}
QString XrayProtocol::xrayExecPath()
diff --git a/client/resources.qrc b/client/resources.qrc
index a10a784d..ff03a6e7 100644
--- a/client/resources.qrc
+++ b/client/resources.qrc
@@ -1,225 +1,229 @@
+ fonts/pt-root-ui_vf.ttf
+ images/amneziaBigLogo.png
+ images/AmneziaVPN.png
+ images/controls/alert-circle.svg
+ images/controls/amnezia.svg
+ images/controls/app.svg
+ images/controls/archive-restore.svg
+ images/controls/arrow-left.svg
+ images/controls/arrow-right.svg
+ images/controls/bug.svg
+ images/controls/check.svg
+ images/controls/chevron-down.svg
+ images/controls/chevron-right.svg
+ images/controls/chevron-up.svg
+ images/controls/close.svg
+ images/controls/copy.svg
+ images/controls/delete.svg
+ images/controls/download.svg
+ images/controls/edit-3.svg
+ images/controls/eye-off.svg
+ images/controls/eye.svg
+ images/controls/external-link.svg
+ images/controls/file-check-2.svg
+ images/controls/file-cog-2.svg
+ images/controls/folder-open.svg
+ images/controls/folder-search-2.svg
+ images/controls/gauge.svg
+ images/controls/github.svg
+ images/controls/help-circle.svg
+ images/controls/history.svg
+ images/controls/home.svg
+ images/controls/info.svg
+ images/controls/mail.svg
+ images/controls/map-pin.svg
+ images/controls/more-vertical.svg
+ images/controls/plus.svg
+ images/controls/qr-code.svg
+ images/controls/radio-button-inner-circle-pressed.png
+ images/controls/radio-button-inner-circle.png
+ images/controls/radio-button-pressed.svg
+ images/controls/radio-button.svg
+ images/controls/radio.svg
+ images/controls/refresh-cw.svg
+ images/controls/save.svg
+ images/controls/scan-line.svg
+ images/controls/search.svg
+ images/controls/server.svg
+ images/controls/settings-2.svg
+ images/controls/settings.svg
+ images/controls/share-2.svg
+ images/controls/split-tunneling.svg
+ images/controls/tag.svg
+ images/controls/telegram.svg
+ images/controls/text-cursor.svg
+ images/controls/trash.svg
+ images/controls/x-circle.svgimages/tray/active.pngimages/tray/default.pngimages/tray/error.png
- images/AmneziaVPN.png
- server_scripts/remove_container.sh
- server_scripts/setup_host_firewall.sh
- server_scripts/openvpn_cloak/Dockerfile
+ server_scripts/awg/configure_container.sh
+ server_scripts/awg/Dockerfile
+ server_scripts/awg/run_container.sh
+ server_scripts/awg/start.sh
+ server_scripts/awg/template.conf
+ server_scripts/build_container.sh
+ server_scripts/check_connection.sh
+ server_scripts/check_server_is_busy.sh
+ server_scripts/check_user_in_sudo.sh
+ server_scripts/dns/configure_container.sh
+ server_scripts/dns/Dockerfile
+ server_scripts/dns/run_container.sh
+ server_scripts/install_docker.sh
+ server_scripts/ipsec/configure_container.sh
+ server_scripts/ipsec/Dockerfile
+ server_scripts/ipsec/mobileconfig.plist
+ server_scripts/ipsec/run_container.sh
+ server_scripts/ipsec/start.sh
+ server_scripts/ipsec/strongswan.profileserver_scripts/openvpn_cloak/configure_container.sh
+ server_scripts/openvpn_cloak/Dockerfile
+ server_scripts/openvpn_cloak/run_container.shserver_scripts/openvpn_cloak/start.shserver_scripts/openvpn_cloak/template.ovpn
- server_scripts/install_docker.sh
- server_scripts/build_container.sh
- server_scripts/prepare_host.sh
- server_scripts/check_connection.sh
- server_scripts/remove_all_containers.sh
- server_scripts/openvpn_cloak/run_container.sh
- server_scripts/openvpn/configure_container.sh
- server_scripts/openvpn/run_container.sh
- server_scripts/openvpn/template.ovpn
- server_scripts/openvpn/Dockerfile
- server_scripts/openvpn/start.shserver_scripts/openvpn_shadowsocks/configure_container.shserver_scripts/openvpn_shadowsocks/Dockerfileserver_scripts/openvpn_shadowsocks/run_container.shserver_scripts/openvpn_shadowsocks/start.shserver_scripts/openvpn_shadowsocks/template.ovpn
+ server_scripts/openvpn/configure_container.sh
+ server_scripts/openvpn/Dockerfile
+ server_scripts/openvpn/run_container.sh
+ server_scripts/openvpn/start.sh
+ server_scripts/openvpn/template.ovpn
+ server_scripts/prepare_host.sh
+ server_scripts/remove_all_containers.sh
+ server_scripts/remove_container.sh
+ server_scripts/setup_host_firewall.sh
+ server_scripts/sftp/configure_container.sh
+ server_scripts/sftp/Dockerfile
+ server_scripts/sftp/run_container.sh
+ server_scripts/socks5_proxy/configure_container.sh
+ server_scripts/socks5_proxy/Dockerfile
+ server_scripts/socks5_proxy/run_container.sh
+ server_scripts/socks5_proxy/start.sh
+ server_scripts/website_tor/configure_container.sh
+ server_scripts/website_tor/Dockerfile
+ server_scripts/website_tor/run_container.shserver_scripts/wireguard/configure_container.shserver_scripts/wireguard/Dockerfileserver_scripts/wireguard/run_container.shserver_scripts/wireguard/start.shserver_scripts/wireguard/template.conf
- server_scripts/website_tor/configure_container.sh
- server_scripts/website_tor/run_container.sh
- ui/qml/Config/GlobalConfig.qml
- ui/qml/Config/qmldir
- server_scripts/check_server_is_busy.sh
- server_scripts/dns/configure_container.sh
- server_scripts/dns/Dockerfile
- server_scripts/dns/run_container.sh
- server_scripts/sftp/configure_container.sh
- server_scripts/sftp/Dockerfile
- server_scripts/sftp/run_container.sh
- server_scripts/ipsec/configure_container.sh
- server_scripts/ipsec/Dockerfile
- server_scripts/ipsec/run_container.sh
- server_scripts/ipsec/start.sh
- server_scripts/ipsec/mobileconfig.plist
- server_scripts/ipsec/strongswan.profile
- server_scripts/website_tor/Dockerfile
- server_scripts/check_user_in_sudo.sh
- ui/qml/Controls2/BasicButtonType.qml
- ui/qml/Controls2/TextFieldWithHeaderType.qml
- ui/qml/Controls2/LabelWithButtonType.qml
- images/controls/arrow-right.svg
- images/controls/chevron-right.svg
- ui/qml/Controls2/ImageButtonType.qml
- ui/qml/Controls2/CardType.qml
- ui/qml/Controls2/CheckBoxType.qml
- images/controls/check.svg
- ui/qml/Controls2/DropDownType.qml
- ui/qml/Pages2/PageSetupWizardStart.qml
- ui/qml/main2.qml
- images/amneziaBigLogo.png
- ui/qml/Controls2/FlickableType.qml
- ui/qml/Pages2/PageSetupWizardCredentials.qml
- ui/qml/Controls2/HeaderType.qml
- images/controls/arrow-left.svg
- ui/qml/Pages2/PageSetupWizardProtocols.qml
- ui/qml/Pages2/PageSetupWizardEasy.qml
- images/controls/chevron-down.svg
- images/controls/chevron-up.svg
- ui/qml/Controls2/TextTypes/ParagraphTextType.qml
- ui/qml/Controls2/TextTypes/Header2TextType.qml
- ui/qml/Controls2/HorizontalRadioButton.qml
- ui/qml/Controls2/VerticalRadioButton.qml
- ui/qml/Controls2/SwitcherType.qml
- ui/qml/Controls2/TabButtonType.qml
- ui/qml/Pages2/PageSetupWizardProtocolSettings.qml
- ui/qml/Pages2/PageSetupWizardInstalling.qml
- ui/qml/Pages2/PageSetupWizardConfigSource.qml
- images/controls/folder-open.svg
- images/controls/qr-code.svg
- images/controls/text-cursor.svg
- ui/qml/Pages2/PageSetupWizardTextKey.qml
- ui/qml/Pages2/PageStart.qml
- ui/qml/Controls2/TabImageButtonType.qml
- images/controls/home.svg
- images/controls/settings-2.svg
- images/controls/share-2.svg
- ui/qml/Pages2/PageHome.qml
- ui/qml/Pages2/PageSettingsServersList.qml
- ui/qml/Pages2/PageShare.qml
- ui/qml/Controls2/TextTypes/Header1TextType.qml
- ui/qml/Controls2/TextTypes/LabelTextType.qml
- ui/qml/Controls2/TextTypes/ButtonTextType.qml
- ui/qml/Controls2/Header2Type.qml
- images/controls/plus.svg
- ui/qml/Components/ConnectButton.qml
- images/controls/download.svg
- ui/qml/Controls2/ProgressBarType.qml
- ui/qml/Components/ConnectionTypeSelectionDrawer.qml
- ui/qml/Components/HomeContainersListView.qml
- ui/qml/Controls2/TextTypes/CaptionTextType.qml
- images/controls/settings.svg
- ui/qml/Pages2/PageSettingsServerInfo.qml
- ui/qml/Controls2/PageType.qml
- ui/qml/Controls2/PopupType.qml
- images/controls/edit-3.svg
- ui/qml/Pages2/PageSettingsServerData.qml
- ui/qml/Components/SettingsContainersListView.qml
- ui/qml/Controls2/TextTypes/ListItemTitleType.qml
- ui/qml/Controls2/DividerType.qml
- ui/qml/Controls2/StackViewType.qml
- ui/qml/Pages2/PageSettings.qml
- images/controls/amnezia.svg
- images/controls/app.svg
- images/controls/radio.svg
- images/controls/save.svg
- images/controls/server.svg
- ui/qml/Pages2/PageSettingsServerProtocols.qml
- ui/qml/Pages2/PageSettingsServerServices.qml
- ui/qml/Pages2/PageSetupWizardViewConfig.qml
- images/controls/file-cog-2.svg
- ui/qml/Components/QuestionDrawer.qml
- ui/qml/Pages2/PageDeinstalling.qml
- ui/qml/Controls2/BackButtonType.qml
- ui/qml/Pages2/PageSettingsServerProtocol.qml
- ui/qml/Components/TransportProtoSelector.qml
- ui/qml/Controls2/ListViewWithRadioButtonType.qml
- images/controls/radio-button.svg
- images/controls/radio-button-inner-circle.png
- images/controls/radio-button-pressed.svg
- images/controls/radio-button-inner-circle-pressed.png
- ui/qml/Components/ShareConnectionDrawer.qml
- ui/qml/Pages2/PageSettingsConnection.qml
- ui/qml/Pages2/PageSettingsDns.qml
- ui/qml/Pages2/PageSettingsApplication.qml
- ui/qml/Pages2/PageSettingsBackup.qml
- images/controls/delete.svg
- ui/qml/Pages2/PageSettingsAbout.qml
- images/controls/github.svg
- images/controls/mail.svg
- images/controls/telegram.svg
- ui/qml/Controls2/TextTypes/SmallTextType.qml
- ui/qml/Filters/ContainersModelFilters.qml
- ui/qml/Components/SelectLanguageDrawer.qml
- ui/qml/Controls2/BusyIndicatorType.qml
- ui/qml/Pages2/PageProtocolOpenVpnSettings.qml
- ui/qml/Pages2/PageProtocolShadowSocksSettings.qml
- ui/qml/Pages2/PageProtocolCloakSettings.qml
- ui/qml/Pages2/PageProtocolXraySettings.qml
- ui/qml/Pages2/PageProtocolRaw.qml
- ui/qml/Pages2/PageSettingsLogging.qml
- ui/qml/Pages2/PageServiceSftpSettings.qml
- images/controls/copy.svg
- ui/qml/Pages2/PageServiceTorWebsiteSettings.qml
- ui/qml/Pages2/PageSetupWizardQrReader.qml
- images/controls/eye.svg
- images/controls/eye-off.svg
- ui/qml/Pages2/PageSettingsSplitTunneling.qml
- ui/qml/Controls2/ContextMenuType.qml
- ui/qml/Controls2/TextAreaType.qml
- images/controls/trash.svg
- images/controls/more-vertical.svg
- ui/qml/Controls2/ListViewWithLabelsType.qml
- ui/qml/Pages2/PageServiceDnsSettings.qml
- ui/qml/Controls2/TopCloseButtonType.qml
- images/controls/x-circle.svg
- ui/qml/Pages2/PageProtocolAwgSettings.qml
- server_scripts/awg/template.conf
- server_scripts/awg/start.sh
- server_scripts/awg/configure_container.sh
- server_scripts/awg/run_container.sh
- server_scripts/awg/Dockerfile
- ui/qml/Pages2/PageShareFullAccess.qml
- images/controls/close.svg
- images/controls/search.svgserver_scripts/xray/configure_container.shserver_scripts/xray/Dockerfileserver_scripts/xray/run_container.shserver_scripts/xray/start.shserver_scripts/xray/template.json
- ui/qml/Pages2/PageProtocolWireGuardSettings.qml
+ ui/qml/Components/AdLabel.qml
+ ui/qml/Components/ConnectButton.qml
+ ui/qml/Components/ConnectionTypeSelectionDrawer.qml
+ ui/qml/Components/HomeContainersListView.qmlui/qml/Components/HomeSplitTunnelingDrawer.qml
- images/controls/split-tunneling.svg
- ui/qml/Controls2/DrawerType2.qml
- ui/qml/Pages2/PageSettingsAppSplitTunneling.qmlui/qml/Components/InstalledAppsDrawer.qml
- images/controls/alert-circle.svg
- images/controls/file-check-2.svg
+ ui/qml/Components/QuestionDrawer.qml
+ ui/qml/Components/SelectLanguageDrawer.qml
+ ui/qml/Components/ServersListView.qml
+ ui/qml/Components/SettingsContainersListView.qml
+ ui/qml/Components/ShareConnectionDrawer.qml
+ ui/qml/Components/TransportProtoSelector.qml
+ ui/qml/Config/GlobalConfig.qml
+ ui/qml/Config/qmldir
+ ui/qml/Controls2/BackButtonType.qml
+ ui/qml/Controls2/BasicButtonType.qml
+ ui/qml/Controls2/BusyIndicatorType.qml
+ ui/qml/Controls2/CardType.qml
+ ui/qml/Controls2/CardWithIconsType.qml
+ ui/qml/Controls2/CheckBoxType.qml
+ ui/qml/Controls2/ContextMenuType.qml
+ ui/qml/Controls2/DividerType.qml
+ ui/qml/Controls2/DrawerType2.qml
+ ui/qml/Controls2/DropDownType.qml
+ ui/qml/Controls2/FlickableType.qml
+ ui/qml/Controls2/Header2Type.qml
+ ui/qml/Controls2/HeaderType.qml
+ ui/qml/Controls2/HorizontalRadioButton.qml
+ ui/qml/Controls2/ImageButtonType.qml
+ ui/qml/Controls2/LabelWithButtonType.qml
+ ui/qml/Controls2/LabelWithImageType.qml
+ ui/qml/Controls2/ListViewWithLabelsType.qml
+ ui/qml/Controls2/ListViewWithRadioButtonType.qml
+ ui/qml/Controls2/PageType.qml
+ ui/qml/Controls2/PopupType.qml
+ ui/qml/Controls2/ProgressBarType.qml
+ ui/qml/Controls2/ScrollBarType.qml
+ ui/qml/Controls2/StackViewType.qml
+ ui/qml/Controls2/SwitcherType.qml
+ ui/qml/Controls2/TabButtonType.qml
+ ui/qml/Controls2/TabImageButtonType.qml
+ ui/qml/Controls2/TextAreaType.qml
+ ui/qml/Controls2/TextAreaWithFooterType.qml
+ ui/qml/Controls2/TextFieldWithHeaderType.qml
+ ui/qml/Controls2/TextTypes/ButtonTextType.qml
+ ui/qml/Controls2/TextTypes/CaptionTextType.qml
+ ui/qml/Controls2/TextTypes/Header1TextType.qml
+ ui/qml/Controls2/TextTypes/Header2TextType.qml
+ ui/qml/Controls2/TextTypes/LabelTextType.qml
+ ui/qml/Controls2/TextTypes/ListItemTitleType.qml
+ ui/qml/Controls2/TextTypes/ParagraphTextType.qml
+ ui/qml/Controls2/TextTypes/SmallTextType.qml
+ ui/qml/Controls2/TopCloseButtonType.qml
+ ui/qml/Controls2/VerticalRadioButton.qmlui/qml/Controls2/WarningType.qml
- fonts/pt-root-ui_vf.ttf
- ui/qml/Modules/Style/qmldir
+ ui/qml/Filters/ContainersModelFilters.qml
+ ui/qml/main2.qmlui/qml/Modules/Style/AmneziaStyle.qml
+ ui/qml/Modules/Style/qmldir
+ ui/qml/Pages2/PageDeinstalling.qml
+ ui/qml/Pages2/PageDevMenu.qml
+ ui/qml/Pages2/PageHome.qml
+ ui/qml/Pages2/PageProtocolAwgSettings.qml
+ ui/qml/Pages2/PageProtocolCloakSettings.qml
+ ui/qml/Pages2/PageProtocolOpenVpnSettings.qml
+ ui/qml/Pages2/PageProtocolRaw.qml
+ ui/qml/Pages2/PageProtocolShadowSocksSettings.qml
+ ui/qml/Pages2/PageProtocolWireGuardSettings.qml
+ ui/qml/Pages2/PageProtocolXraySettings.qml
+ ui/qml/Pages2/PageServiceDnsSettings.qml
+ ui/qml/Pages2/PageServiceSftpSettings.qmlui/qml/Pages2/PageServiceSocksProxySettings.qml
- server_scripts/socks5_proxy/run_container.sh
- server_scripts/socks5_proxy/Dockerfile
- server_scripts/socks5_proxy/configure_container.sh
- server_scripts/socks5_proxy/start.sh
+ ui/qml/Pages2/PageServiceTorWebsiteSettings.qml
+ ui/qml/Pages2/PageSettings.qml
+ ui/qml/Pages2/PageSettingsAbout.qml
+ ui/qml/Pages2/PageSettingsApiLanguageList.qml
+ ui/qml/Pages2/PageSettingsApiServerInfo.qml
+ ui/qml/Pages2/PageSettingsApplication.qml
+ ui/qml/Pages2/PageSettingsAppSplitTunneling.qml
+ ui/qml/Pages2/PageSettingsBackup.qml
+ ui/qml/Pages2/PageSettingsConnection.qml
+ ui/qml/Pages2/PageSettingsDns.qml
+ ui/qml/Pages2/PageSettingsLogging.qml
+ ui/qml/Pages2/PageSettingsServerData.qml
+ ui/qml/Pages2/PageSettingsServerInfo.qml
+ ui/qml/Pages2/PageSettingsServerProtocol.qml
+ ui/qml/Pages2/PageSettingsServerProtocols.qml
+ ui/qml/Pages2/PageSettingsServerServices.qml
+ ui/qml/Pages2/PageSettingsServersList.qml
+ ui/qml/Pages2/PageSettingsSplitTunneling.qmlui/qml/Pages2/PageProtocolAwgClientSettings.qmlui/qml/Pages2/PageProtocolWireGuardClientSettings.qml
- ui/qml/Pages2/PageSetupWizardApiServicesList.qmlui/qml/Pages2/PageSetupWizardApiServiceInfo.qml
- ui/qml/Controls2/CardWithIconsType.qml
- images/controls/tag.svg
- images/controls/history.svg
- images/controls/gauge.svg
- images/controls/map-pin.svg
- ui/qml/Controls2/LabelWithImageType.qml
- images/controls/info.svg
- ui/qml/Controls2/TextAreaWithFooterType.qml
- images/controls/scan-line.svg
- images/controls/folder-search-2.svg
- ui/qml/Pages2/PageSettingsApiServerInfo.qml
- images/controls/bug.svg
- ui/qml/Pages2/PageDevMenu.qml
- images/controls/refresh-cw.svg
- ui/qml/Pages2/PageSettingsApiLanguageList.qml
- images/controls/archive-restore.svg
- images/controls/help-circle.svg
+ ui/qml/Pages2/PageSetupWizardApiServicesList.qml
+ ui/qml/Pages2/PageSetupWizardConfigSource.qml
+ ui/qml/Pages2/PageSetupWizardCredentials.qml
+ ui/qml/Pages2/PageSetupWizardEasy.qml
+ ui/qml/Pages2/PageSetupWizardInstalling.qml
+ ui/qml/Pages2/PageSetupWizardProtocols.qml
+ ui/qml/Pages2/PageSetupWizardProtocolSettings.qml
+ ui/qml/Pages2/PageSetupWizardQrReader.qml
+ ui/qml/Pages2/PageSetupWizardStart.qml
+ ui/qml/Pages2/PageSetupWizardTextKey.qml
+ ui/qml/Pages2/PageSetupWizardViewConfig.qml
+ ui/qml/Pages2/PageShare.qml
+ ui/qml/Pages2/PageShareFullAccess.qml
+ ui/qml/Pages2/PageStart.qmlimages/flagKit/ZW.svg
diff --git a/client/server_scripts/awg/configure_container.sh b/client/server_scripts/awg/configure_container.sh
index 322cc38f..2000c965 100644
--- a/client/server_scripts/awg/configure_container.sh
+++ b/client/server_scripts/awg/configure_container.sh
@@ -12,7 +12,7 @@ echo $WIREGUARD_PSK > /opt/amnezia/awg/wireguard_psk.key
cat > /opt/amnezia/awg/wg0.conf <
+
+ AdLabel
+
+
+ Amnezia Premium - for access to any website
+
+
+ApiServicesModel
-
+ Classic VPN for comfortable work, downloading large files and watching videos. Works for any sites. Speed up to %1 MBit/sشبكة VPN كلاسيكية للعمل المريح وتنزيل الملفات الكبيرة ومشاهدة مقاطع الفيديو. تعمل مع أي موقع. تصل السرعة إلى %1 ميجابت/ثانية
-
+ VPN to access blocked sites in regions with high levels of Internet censorship. شبكة VPN للولوج للمواقع المحظورة في بلاد ذو مستوي عالي من الرقابة علي الانترنت.
-
+ <p><a style="color: #EB5757;">Not available in your region. If you have VPN enabled, disable it, return to the previous screen, and try again.</a>
-
+ Amnezia Premium - A classic VPN for comfortable work, downloading large files, and watching videos in high resolution. It works for all websites, even in countries with the highest level of internet censorship.Amenzia Premium - شبكة VPN للعمل المريح, تحميل ملفات كبيرة الحجم, ومشاهدة مقاطع الفيديو ب جودة عالية. تعمل لجميع المواقع, حتي في البلاد ذو مستوي عالي من الرقابة علي الانترنت
-
+ Amnezia Free is a free VPN to bypass blocking in countries with high levels of internet censorshipAmnezia Free هو VPN مجاني لتخطي الحظر في البلاد ذو مستوي عالي من الرقابة علي الانترنت
-
+ %1 MBit/s%1 ميجابت/ثانية
-
+ %1 days%1 ايام
-
+ VPN will open only popular sites blocked in your region, such as Instagram, Facebook, Twitter and others. Other sites will be opened from your real IP address, <a href="%1/free" style="color: #FBB26A;">more details on the website.</a>سيقوم VPN فقط بفتح المواقع المشهورة المحظورة في بلدك, مثل Instagram, Facebook, Twitter و مواقع اخري. المواقع الاخري ستٌفتح من عنوان ال IP الحقيقي الخاص بك, <a href="%1/free" style="color: #FBB26A;">معلومات اخري علي الموقع.</a>
-
+ Freeمجاني
-
+ %1 $/month%1 دولار/الشهر
@@ -80,7 +88,7 @@
ConnectButton
-
+ Unable to disconnect during configuration preparationغير قادر علي قطع الاتصال اثناء إعداد التكوين
@@ -89,60 +97,60 @@
ConnectionController
-
-
-
+
+
+ Connectاتصل
-
+ VPN Protocols is not installed.
Please install VPN container at firstلم يتم تثبيت بروتوكولات VPN, من فضلك قم بتنزيل حاوية VPN اولاً
-
+ Connecting...اتصال...
-
+ Connectedتم الاتصال
-
+ Reconnecting...إعادة الاتصال...
-
+ Disconnecting...إنهاء الاتصال...
-
+ Preparing...جاري التحضير...
-
+ Settings updated successfully, reconnnection...تم تحديث الاعدادات بنجاح, جاري إعادة الاتصال...
-
+ Settings updated successfullyتم تحديث الاعدادات بنجاح
-
+ The selected protocol is not supported on the current platformالبروتوكول المحدد غير مدعوم علي المنصة الحالية
-
+ unable to create configurationغير قادر علي إنشاء تكوين
@@ -150,17 +158,17 @@
ConnectionTypeSelectionDrawer
-
+ Add new connectionإضافة اتصال جديد
-
+ Configure your serverقم بتهيئة الخادم الخاص بك
-
+ Open config file, key or QR codeافتح ملف تعريف, مفتاح تعريف او رمز QR
@@ -198,7 +206,7 @@
HomeContainersListView
-
+ Unable change protocol while there is an active connectionغير قادر علي تغيير البروتوكول اثناء تواجد اتصال
@@ -206,46 +214,46 @@
HomeSplitTunnelingDrawer
-
+ Split tunnelingتقسيم الانفاق
-
+ Allows you to connect to some sites or applications through a VPN connection and bypass othersيسمح لك بألاتصال ببعض المواقع او البرامج خلال اتصال VPN و تجاوز الاخرين
-
+ Split tunneling on the serverتقسيم الانفاق علي الخادم
-
+ Enabled
Can't be disabled for current serverمٌفعل
لا يمكن إقافة للخادم الحالي
-
+ Site-based split tunnelingانقسام الانفاق القائم علي الموقع
-
-
+
+ Enabledمٌفعل
-
-
+
+ Disabledمٌعطل
-
+ App-based split tunnelingانقسام الانفاق القائم علي التطبيق
@@ -253,23 +261,20 @@ Can't be disabled for current server
ImportController
- Unable to open file
- غير قادر علي فتح الملف
+ غير قادر علي فتح الملف
-
- Invalid configuration file
- ملف تكوين غير صحيح
+ ملف تكوين غير صحيح
-
+ Scanned %1 of %2.تم فحص%1 من %2.
-
+ In the imported configuration, potentially dangerous lines were found:في التكوين المستورد، تم العثور على سطور يحتمل أن تكون خطرة:
@@ -277,24 +282,24 @@ Can't be disabled for current server
InstallController
-
+ %1 installed successfully. %1 تم التثبيت بنجاح.
-
+ %1 is already installed on the server. %1 بالفعل مٌثبت علي الخادم.
-
+
Added containers that were already installed on the server
تمت إضافة الحاويات التي كانت مٌثبتة بالفعل علي الخادم
-
+
Already installed containers were found on the server. All installed containers have been added to the application
@@ -302,62 +307,62 @@ Already installed containers were found on the server. All installed containers
تمت إضافة جميع الحاويات المٌثبتة إلي التطبيق
-
+ Settings updated successfullyتم تحديث الاعدادات بنجاح
-
+ Server '%1' was rebootedتمت إعادة تشغيل الخادم%1
-
+ Server '%1' was removedتمت إزالة الخادم '%1'
-
+ All containers from server '%1' have been removedقد تم حذفها '%1' جميع الحاويات من الخادم
-
+ %1 has been removed from the server '%2'%1 تم حدف '%2' اسم الخادم
-
+ Api config removedتم حذف تكوين Api
-
+ %1 cached profile clearedتم مسح ملف تعريف %1 المخزن مؤقتًا
-
+ Please login as the userمن فضلك قم بتسجيل الدخول كمستخدم
-
+ Server added successfullyتمت إضافة الخادم بنجاح
-
+ %1 installed successfully.تم تحميل %1 بنجاح
-
+ API config reloadedتمت إعادة تحميل تكوين API
-
+ Successfully changed the country of connection to %1تم تغيير بلد الاتصال بنجاح إلى %1
@@ -370,12 +375,12 @@ Already installed containers were found on the server. All installed containers
اختر تطبيق
-
+ application nameاسم التطبيق
-
+ Add selectedاضف اختيارك
@@ -443,12 +448,12 @@ Already installed containers were found on the server. All installed containers
PageDevMenu
-
+ Gateway endpointنقطة نهاية البوابة
-
+ Dev gateway environment
@@ -456,85 +461,84 @@ Already installed containers were found on the server. All installed containers
PageHome
-
+ Logging enabledتم تمكين التسجيل
-
+ Split tunneling enabledتقسيم الانفاق مٌفعل
-
+ Split tunneling disabledتقسيم الانفاق مٌعطل
-
+ VPN protocolبروتوكول VPN
-
+ Serversالخوادم
- Unable change server while there is an active connection
- لا يمكن تغير الخادم بينما هناك اتصال مفعل
+ لا يمكن تغير الخادم بينما هناك اتصال مفعلPageProtocolAwgClientSettings
-
+ AmneziaWG settingsاعدادات AmneziaWG
-
+ MTU
-
+ Server settings
-
+ Portمنفذ
-
+ Saveاحفظ
-
+ Save settings?احفظ الإعدادات؟
-
+ Only the settings for this device will be changed
-
+ Continueواصل
-
+ Cancelإلغاء
-
+ Unable change settings while there is an active connectionلا يمكن تغيير الإعدادات أثناء وجود اتصال نشط
@@ -542,97 +546,102 @@ Already installed containers were found on the server. All installed containers
PageProtocolAwgSettings
-
+ AmneziaWG settingsاعدادات AmneziaWG
-
+ Portمنفذ
-
+ All users with whom you shared a connection with will no longer be able to connect to it.جميع المستخدمين الذين شاركت معهم اتصال لن يكونو قادرين علي الاتصال مرة اخري.
-
+ Saveاحفظ
-
+
+ VPN address subnet
+ الشبكة الفرعية لعنوان VPN
+
+
+ Jc - Junk packet countJc - عدد الحزم غير المرغوب فيها
-
+ Jmin - Junk packet minimum sizeJmin - الحجم الادني للحزم الغير مرغوب فيها
-
+ Jmax - Junk packet maximum sizeJmax - الحجم الاقصي للحزم الغير مرغوب فيها
-
+ S1 - Init packet junk sizeS1 - حجم حزمة البيانات العشوائية الأولية
-
+ S2 - Response packet junk sizeS2 - حجم حزمة الاستجابة غير المرغوب فيها
-
+ H1 - Init packet magic headerH1 - حزمة رأس سحرية مبدئية
-
+ H2 - Response packet magic headerH2 - رأس حزمة الاستجابة السحرية
-
+ H4 - Transport packet magic headerH4 - رأس حزمة النقل السحرية
-
+ H3 - Underload packet magic headerH3 - رأس حزمة السحر غير المحمل
-
+ The values of the H1-H4 fields must be uniqueيجب أن تكون قيم الحقول H1-H4 فريدة
-
+ The value of the field S1 + message initiation size (148) must not equal S2 + message response size (92)يجب ألا تساوي قيمة الحقل S1 + حجم بدء الرسالة (148) S2 + حجم استجابة الرسالة (92)
-
+ Save settings?احفظ الإعدادات؟
-
+ Continueواصل
-
+ Cancelإلغاء
-
+ Unable change settings while there is an active connectionلا يمكن تغيير الإعدادات أثناء وجود اتصال نشط
@@ -640,33 +649,33 @@ Already installed containers were found on the server. All installed containers
PageProtocolCloakSettings
-
+ Cloak settingsCloak إعدادات
-
+ Disguised as traffic fromمتنكراً في حركة مرور من
-
+ Portمنفذ
-
-
+
+ Cipherالشفرة
-
+ Saveاحفظ
-
+ Unable change settings while there is an active connectionلا يمكن تغيير الإعدادات أثناء وجود اتصال نشط
@@ -674,175 +683,175 @@ Already installed containers were found on the server. All installed containers
PageProtocolOpenVpnSettings
-
+ OpenVPN settingsOpenVPN اعدادات
-
+ VPN address subnetالشبكة الفرعية لعنوان VPN
-
+ Network protocolبروتوكول الشبكة
-
+ Portمنفذ
-
+ Auto-negotiate encryptionالتفاوض التلقائي علي الشبكة
-
-
+
+ Hash
-
+ SHA512
-
+ SHA384
-
+ SHA256
-
+ SHA3-512
-
+ SHA3-384
-
+ SHA3-256
-
+ whirlpool
-
+ BLAKE2b512
-
+ BLAKE2s256
-
+ SHA1
-
-
+
+ Cipherشفرة
-
+ AES-256-GCM
-
+ AES-192-GCM
-
+ AES-128-GCM
-
+ AES-256-CBC
-
+ AES-192-CBC
-
+ AES-128-CBC
-
+ ChaCha20-Poly1305
-
+ ARIA-256-CBC
-
+ CAMELLIA-256-CBC
-
+ noneلا شئ
-
+ TLS authTLS مصادقة
-
+ Block DNS requests outside of VPNاحظر طلبات DNS خارج ال VPN
-
+ Additional client configuration commandsاوامر تكوين العميل الاضافية
-
-
+
+ Commands:الاوامر:
-
+ Additional server configuration commandsاوامر تكوين الخادم الاضافية
-
+ Unable change settings while there is an active connectionلا يمكن تغيير الإعدادات أثناء وجود اتصال نشط
-
+ Saveاحفظ
@@ -850,42 +859,42 @@ Already installed containers were found on the server. All installed containers
PageProtocolRaw
-
+ settings إعدادات
-
+ Show connection optionsاظهر اختيارات الاتصال
-
+ Connection options %1%1 اختيارات الاتصال
-
+ Remove احذف
-
+ Remove %1 from server?احذف %1 من الخادم ?
-
+ All users with whom you shared a connection with will no longer be able to connect to it.جميع المستخدمين الذين شاركت معهم اتصال لن يكونو قادرين علي الاتصال مرة اخري.
-
+ Continueواصل
-
+ Cancelإلغاء
@@ -893,28 +902,28 @@ Already installed containers were found on the server. All installed containers
PageProtocolShadowSocksSettings
-
+ Shadowsocks settingsShadowsocks إعدادات
-
+ Portمنفذ
-
-
+
+ Cipherتشفير
-
+ Saveاحفظ
-
+ Unable change settings while there is an active connectionلا يمكن تغيير الإعدادات أثناء وجود اتصال نشط
@@ -922,52 +931,52 @@ Already installed containers were found on the server. All installed containers
PageProtocolWireGuardClientSettings
-
+ WG settingsإعدادات WG
-
+ MTU
-
+ Server settings
-
+ Portمنفذ
-
+ Saveاحفظ
-
+ Save settings?احفظ الإعدادات؟
-
+ Only the settings for this device will be changed
-
+ Continueواصل
-
+ Cancelإلغاء
-
+ Unable change settings while there is an active connectionلا يمكن تغيير الإعدادات أثناء وجود اتصال نشط
@@ -975,42 +984,47 @@ Already installed containers were found on the server. All installed containers
PageProtocolWireGuardSettings
-
+ WG settingsإعدادات WG
-
+
+ VPN address subnet
+ الشبكة الفرعية لعنوان VPN
+
+
+ Portمنفذ
-
+ Save settings?احفظ الإعدادات؟
-
+ All users with whom you shared a connection with will no longer be able to connect to it.جميع المستخدمين الذين شاركت معهم اتصال لن يكونو قادرين علي الاتصال مرة اخري.
-
+ Continueواصل
-
+ Cancelإلغاء
-
+ Unable change settings while there is an active connectionلا يمكن تغيير الإعدادات أثناء وجود اتصال نشط
-
+ Saveاحفظ
@@ -1018,22 +1032,22 @@ Already installed containers were found on the server. All installed containers
PageProtocolXraySettings
-
+ XRay settingsإعدادات XRay
-
+ Disguised as traffic fromمتنكراً في حركة مرور من
-
+ Saveاحفظ
-
+ Unable change settings while there is an active connectionلا يمكن تغيير الإعدادات أثناء وجود اتصال نشط
@@ -1041,39 +1055,39 @@ Already installed containers were found on the server. All installed containers
PageServiceDnsSettings
-
+ A DNS service is installed on your server, and it is only accessible via VPN.
تم تثبيت خدمة DNS علي الخادم الخاص بك, و فقط متاح من خلال VPN.
-
+ The DNS address is the same as the address of your server. You can configure DNS in the settings, under the connections tab.عنوان ال DNS متطابق لنفس عنوان الخادم بك, يمكنك تهيئة DNS في الاعدادات, تحت علامة تبويب الاتصال.
-
+ Remove احذف
-
+ Remove %1 from server?احذف %1 ?
-
+ Cannot remove AmneziaDNS from running serverلا يمكن إزالة AmneziaDNS من الخادم قيد التشغيل
-
+ Continueواصل
-
+ Cancelإلغاء
@@ -1081,67 +1095,67 @@ Already installed containers were found on the server. All installed containers
PageServiceSftpSettings
-
+ Settings updated successfullyتم تحديث الإعدادات بنجاح
-
+ SFTP settingsSFTP إعدادات
-
+ Hostاستضافة
-
-
-
-
+
+
+
+ Copiedتم الاستنساخ
-
+ Portمنفذ
-
+ User nameاسم المستخدم
-
+ Passwordكلمة المرور
-
+ Mount folder on deviceقم بتثبيت المجلد علي الجهاز
-
+ In order to mount remote SFTP folder as local drive, perform following steps: <br>لتثبيت مجلد SFTP كمحرك اقراص محلي, اتبع هذه الخطوات : <br>
-
-
+
+ <br>1. Install the latest version of <br>1. تحميل اخر اصدار من
-
-
+
+ <br>2. Install the latest version of <br>2. تحمير اخر اصدار من
-
+ Detailed instructionsتعليمات مفصلة
@@ -1149,69 +1163,69 @@ Already installed containers were found on the server. All installed containers
PageServiceSocksProxySettings
-
+ Settings updated successfullyتم تحديث الإعدادات بنجاح
-
-
+
+ SOCKS5 settingsإعدادات SOCKS5
-
+ Hostاستضافة
-
-
-
-
+
+
+
+ Copiedتم النسخ
-
-
+
+ Portمنفذ
-
+ User nameاسم المستخدم
-
-
+
+ Passwordكلمة المرور
-
+ Usernameاسم المستخدم
-
-
+
+ Change connection settingsتغيير إعدادات الاتصال
-
+ The port must be in the range of 1 to 65535يجب أن يكون المنفذ في النطاق من 1 إلى 65535
-
+ Password cannot be emptyلا يمكن ان تكون كلمة المرور فارغة
-
+ Username cannot be emptyاسم المستخدم لا يمكن ان يكون فارغ
@@ -1219,37 +1233,37 @@ Already installed containers were found on the server. All installed containers
PageServiceTorWebsiteSettings
-
+ Settings updated successfullyتم تحديث الإعدادات بنجاح
-
+ Tor website settingsTor إعدادات متصفح
-
+ Website addressعنوان المتصفح
-
+ Copiedتم الاستنساخ
-
+ Use <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor Browser</a> to open this URL.
-
+ After creating your onion site, it takes a few minutes for the Tor network to make it available for use.
-
+ When configuring WordPress set the this onion address as domain.عند تكوين WordPress قم بتعيين عنوان ال onion هذا ك domain.
@@ -1257,42 +1271,42 @@ Already installed containers were found on the server. All installed containers
PageSettings
-
+ Settingsإعدادات
-
+ Serversالخوادم
-
+ Connectionالاتصال
-
+ Applicationتطبيق
-
+ Backupنسخة احتياطية
-
+ About AmneziaVPNعن AmneziaVPN
-
+ Dev consoleوحدة تحكم التطوير
-
+ Close applicationإغلاق التطبيق
@@ -1300,32 +1314,32 @@ Already installed containers were found on the server. All installed containers
PageSettingsAbout
-
+ Support Amneziaدعم Amenzia
-
+ Amnezia is a free and open-source application. You can support the developers if you like it.هو تطبيق مجاني ومفتوح المصدر يمكنك دعم مطورين Amnezia إذا اعجبك.
-
+ Contactsالتواصل
-
+ Telegram groupمجموعة ال Telegram
-
+ To discuss featuresلمناقشة الميزات
-
+ https://t.me/amnezia_vpn_en
@@ -1334,122 +1348,145 @@ Already installed containers were found on the server. All installed containers
البريد
-
+ support@amnezia.org
-
+ For reviews and bug reportsلل مراجعات والابلاغات عن المشاكل
-
+ Copied
-
+ GitHubGitHub
-
+
+ Discover the source code
+
+
+
+ https://github.com/amnezia-vpn/amnezia-client
-
+ Websiteموقع
-
+
+ Visit official website
+
+
+
+ Software version: %1%1 :إصدار البرنامج
-
+ Check for updatesتحقق من وجود تحديثات
-
+ Privacy Policyسياسات الخصوصية
+
+ PageSettingsApiLanguageList
+
+
+ Unable change server location while there is an active connection
+
+
+PageSettingsApiServerInfo
-
+ For the regionللمنطقة
-
+ Priceالسعر
-
+ Work periodمدة العمل
-
+
+ Valid until
+
+
+
+ Speedالسرعة
-
+ Support tagعلامة الدعم
-
+ Copiedتم النسخ
-
+ Reload API configإعادة تحميل تكوين API
-
+ Reload API config?إعادة تحميل تكوين API
-
-
+
+ Continueواصل
-
-
+
+ Cancelإلغاء
-
+ Cannot reload API config during active connectionلا يمكن إعادة تحميل تكوين API اثناء تواجد اتصال نشط
-
+ Remove from applicationاحذف من التطبيق
-
+ Remove from application?احذف من التطبيق؟
-
+ Cannot remove server during active connectionلا يمكن إزالة الخادم أثناء الاتصال النشط
@@ -1457,12 +1494,12 @@ Already installed containers were found on the server. All installed containers
PageSettingsAppSplitTunneling
-
+ Cannot change split tunneling settings during active connectionلا يمكن تغير إعدادات تقسيم الانفاق بينما هناك اتصال مٌفعل
-
+ Only the apps from the list should have access via VPNيجب أن تتمتع التطبيقات الموجودة في القائمة فقط بإمكانية الوصول عبر VPN
@@ -1472,42 +1509,42 @@ Already installed containers were found on the server. All installed containers
لا يجب ان تتمتع التطبيقات في القائمة بولوج ل VPN
-
+ App split tunnelingتقسيم نفق التطبيق
-
+ Modeوضع
-
+ Remove احذف
-
+ Continueواصل
-
+ Cancelإلغاء
-
+ application nameاسم التطبيق
-
+ Open executable fileافتح ملف قابل للتنفيذ
-
+ Executable files (*.*)ملفات قابلة للتنفيذ (*.*)
@@ -1515,102 +1552,102 @@ Already installed containers were found on the server. All installed containers
PageSettingsApplication
-
+ Applicationتطبيق
-
+ Allow application screenshotsاسمح بلقطات شاشة التطبيق
-
+ Enable notificationsتفعيل الإشعارات
-
+ Enable notifications to show the VPN state in the status barتفعيل الإشعارات لإظهار حالة ال VPN في شريط الحالة
-
+ Auto startتشغيل تلقائي
-
+ Launch the application every time the device is startsقم بتشغيل التطبيق فكل مرة يتم فيها تشغيل الجهاز
-
+ Auto connectاتصال تلقائي
-
+ Connect to VPN on app startاتصل ب ال VPN عند تشغيل التطبيق
-
+ Start minimizedابدأ ب الحجم الادني
-
+ Launch application minimizedتشغيل التطبيق في الحد الادني
-
+ Languageاللغة
-
+ Loggingتسجيل
-
+ Enabledمٌفعل
-
+ Disabledمٌعطل
-
+ Reset settings and remove all data from the applicationإعادة ضبط الاعدادات ومسح جميع البيانات من التطبيق
-
+ Reset settings and remove all data from the application?إعادة ضبط الاعدادات ومسح جميع البيانات من التطبيق؟
-
+ All settings will be reset to default. All installed AmneziaVPN services will still remain on the server.سيتم ضبط الاعدادات الافتراضية. جميع خدمات AmneziaVPN المٌثبتة ستبقي علي الخادم.
-
+ Continueواصل
-
+ Cancelإلغاء
-
+ Cannot reset settings during active connectionلا يمكن إعادة ضبط الإعدادات اثناء تواجد اتصال فعال
@@ -1618,78 +1655,78 @@ Already installed containers were found on the server. All installed containers
PageSettingsBackup
-
+ Settings restored from backup fileتم إعادة الاعدادات من ملف نسخة احتياطية
-
+ Back up your configurationقم بعمل نسخة احتياطية
-
+ You can save your settings to a backup file to restore them the next time you install the application.يمكنك حفظ الإعدادات في ملف نسخة احتياطية لأعادتهم في المرة القادمة التي تثبت فيها التطبيق.
-
+ The backup will contain your passwords and private keys for all servers added to AmneziaVPN. Keep this information in a secure place.ستحتوي النسخة الاحتياطية علي كلمات مرورك و المفاتيح الخاصة للخوادم المٌضافة إلي AmneziaVPN. احفظ هذه المعلومات في مكان امن.
-
+ Make a backupإضافة نسخة احتياطية
-
+ Save backup fileاحفظ ملف النسخه الاحتياطيه
-
-
+
+ Backup files (*.backup)ملفات نٌسخ احتياطية (*.backup)
-
+ Backup file savedتم حفظ ملف النسخ الاحتياطي
-
+ Restore from backupاسترجاع من ملف يحتوي علي نسخة احتياطية
-
+ Open backup fileافتح ملف نسخ احتياطي
-
+ Import settings from a backup file?استرد الإعدادات من ملف نسخ احتياطي؟
-
+ All current settings will be resetستتم إعادة ضبط جميع الإعدادات الحالية
-
+ Continueواصل
-
+ Cancelإلغاء
-
+ Cannot restore backup settings during active connectionلا يمكن استعادة إعدادات النسخ الاحتياطي أثناء الاتصال النشط
@@ -1697,62 +1734,62 @@ Already installed containers were found on the server. All installed containers
PageSettingsConnection
-
+ Connectionالاتصال
-
+ When AmneziaDNS is not used or installedعندما يكون AmneziaDNS غير مٌثبت او غير مستخدم
-
+ Allows you to use the VPN only for certain Appsيسمح لك بأستخدام ال VPN علي تطبيقات معينة
-
+ Use AmneziaDNSاستخدم AmneziaDNS
-
+ If AmneziaDNS is installed on the serverفي حالة كان AmneziaDNS مٌثبت علي الخادم
-
+ DNS serversخوادم DNS
-
+ Site-based split tunnelingانقسام الانفاق القائم علي الموقع
-
+ Allows you to select which sites you want to access through the VPNيسمح لك بتحديد اي موقع تريد الوصول له عن طريق ال VPN
-
+ App-based split tunnelingانقسام الانفاق القائم علي التطبيق
-
+ KillSwitch
-
+ Disables your internet if your encrypted VPN connection drops out for any reason.يعطل اتصال الإنترنت الخاص بك إذا انقطع اتصال VPN المشفر لأي سبب من الأسباب.
-
+ Cannot change killSwitch settings during active connectionلا يمكن تغيير إعدادات KillSwitch اثناء تواجد اتصال فعال
@@ -1760,62 +1797,62 @@ Already installed containers were found on the server. All installed containers
PageSettingsDns
-
+ Default server does not support custom DNSالخادم الافتراضي لا يدعم DNS مخصص
-
+ DNS serversخوادم ال DNS
-
+ If AmneziaDNS is not used or installedAmneziaVPN ليس مٌستخدم او مٌثبت
-
+ Primary DNSالرئيسي DNS
-
+ Secondary DNSالثانوي DNS
-
+ Restore defaultاستعادة الافتراضي
-
+ Restore default DNS settings?قم بأعادة ضبط إعدادات ال DNS الافتراضية؟
-
+ Continueواصل
-
+ Cancelإلغاء
-
+ Settings have been resetلم يتم إعادة ضبط الإعدادات
-
+ Saveاحفظ
-
+ Settings savedتم حفظ الإعدادات
@@ -1827,12 +1864,12 @@ Already installed containers were found on the server. All installed containers
تم تمكين التسجيل. لاحظ أنه سيتم تعطيل السجلات تلقائيًا بعد 14 يومًا، وسيتم حذف جميع ملفات السجل.
-
+ Loggingالتسجيل
-
+ Enabling this function will save application's logs automatically. By default, logging functionality is disabled. Enable log saving in case of application malfunction.سيتم حفظ سجلات البرنامج بشكل تلقائي عند تفعيل هذه الميزة, بشكل افتراضي, هذه الميزة مٌعطلة. قم بتفعيل هذه الميزة في حالة هناك خلل في التطبيق.
@@ -1845,20 +1882,20 @@ Already installed containers were found on the server. All installed containers
افتح مجلد يحتوي علي سجلات
-
-
+
+ Saveاحفظ
-
-
+
+ Logs files (*.log)ملفات الولوج (*.log)
-
-
+
+ Logs file savedتم حفظ ملف السجل
@@ -1867,64 +1904,62 @@ Already installed containers were found on the server. All installed containers
احفظ السجلات في ملف
-
+ Enable logs
-
+ Clear logs?مسح السجلات؟
-
+ Continueواصل
-
+ Cancelإلغاء
-
+ Logs have been cleaned upتم مسح السجلات
-
+ Client logs
-
+ AmneziaVPN logs
-
-
+ Open logs folder
-
-
+ Export logs
-
+ Service logs
-
+ AmneziaVPN-service logs
-
+ Clear logsاحذف السجلات
@@ -1942,12 +1977,12 @@ Already installed containers were found on the server. All installed containers
لم يتم العثور علي اي خدمات مٌثبتة سابقاً
-
+ Do you want to reboot the server?هل تريد إعادة تشغيل الخادم؟
-
+ Do you want to clear server from Amnezia software?هل تريد حذف الخادم من Amnezia?
@@ -1957,18 +1992,18 @@ Already installed containers were found on the server. All installed containers
-
-
-
-
+
+
+
+ Continueواصل
-
-
-
-
+
+
+
+ Cancelإلغاء
@@ -1983,67 +2018,67 @@ Already installed containers were found on the server. All installed containers
اضفهم إلي التطبيق إذا لم يكونو ظاهرين
-
+ Reboot serverإعادة تشغيل الخادم
-
+ The reboot process may take approximately 30 seconds. Are you sure you wish to proceed?عملية إعادة التشغيل قد تستغرق 30 ثانية, هل تريد الاستكمال؟
-
+ Cannot reboot server during active connectionلا يمكن إعادة تشغيل الخادم أثناء الاتصال النشط
-
+ Remove server from applicationاحذف خادم من التطبيق
-
+ Do you want to remove the server from application?هل تريد حذف الخادم من التطبيق؟
-
+ Cannot remove server during active connectionلا يمكن إزالة الخادم أثناء الاتصال النشط
-
+ All users whom you shared a connection with will no longer be able to connect to it.جميع المستخدمين الذين شاركت معهم اتصال لن يستطيعو الاتصال مرة اخري.
-
+ Cannot clear server from Amnezia software during active connectionلا يمكن مسح الخادم من برنامج Amnezia أثناء الاتصال النشط
-
+ Reset API configإعادة تكوين API
-
+ Do you want to reset API config?هل تريد إعادة تكوين API?
-
+ Cannot reset API config during active connectionلا يمكن إعادة تعيين تكوين API أثناء الاتصال النشط
-
+ All installed AmneziaVPN services will still remain on the server.جميع خدمات AmneziaVPN المٌثبتة ستظل علي الخادم.
-
+ Clear server from Amnezia softwareاحذف خادم من Amnezia
@@ -2051,27 +2086,32 @@ Already installed containers were found on the server. All installed containers
PageSettingsServerInfo
-
+
+ Subscription is valid until
+
+
+
+ Server nameاسم الخادم
-
+ Saveاحفظ
-
+ Protocolsالبروتوكولات
-
+ Servicesالخدمات
-
+ Managementالإدارة
@@ -2079,7 +2119,7 @@ Already installed containers were found on the server. All installed containers
PageSettingsServerProtocol
-
+ settings الإعدادات
@@ -2088,7 +2128,7 @@ Already installed containers were found on the server. All installed containers
مسح ملف تعريف %1
-
+ Clear %1 profile?مسح ملف تعريف %1؟
@@ -2098,64 +2138,64 @@ Already installed containers were found on the server. All installed containers
-
+ Unable to clear %1 profile while there is an active connectionغير قادر على مسح ملف تعريف %1 أثناء وجود اتصال نشط
-
+ Remove احذف
-
+ Remove %1 from server?احذف %1 من الخادم؟
-
+ All users with whom you shared a connection will no longer be able to connect to it.جميع المستخدمين الذين شاركت معاهم اتصال لن يستطيعو الاتصال بعد الان.
-
+ Cannot remove active containerلا يمكن إزالة الحاوية النشطة
-
-
+
+ Continueواصل
-
+ connection settings
-
+ Click the "connect" button to create a connection configuration
-
+ server settings
-
+ Clear profile
-
+ The connection configuration will be deleted for this device only
-
-
+
+ Cancelإلغاء
@@ -2163,7 +2203,7 @@ Already installed containers were found on the server. All installed containers
PageSettingsServersList
-
+ Serversالخوادم
@@ -2171,100 +2211,100 @@ Already installed containers were found on the server. All installed containers
PageSettingsSplitTunneling
-
+ Default server does not support split tunneling functionالسرفر الافتراضي لا يدعم ميزة تقسيم الانفاق
-
+ Addresses from the list should not be accessed via VPNلا يجب الولوج للعنواين المذكورة هنا من خلال ال VPN
-
+ Split tunnelingتقسيم الانفاق
-
+ Modeوضع
-
+ Remove احذف
-
+ Continueواصل
-
+ Cancelإلغاء
-
+ Only the sites listed here will be accessed through the VPNسيتم الولوج للمواقع المذكورة هنا فقط عن طريق ال VPN
-
+ Cannot change split tunneling settings during active connectionلا يمكن تغير إعدادات تقسيم الانفاق بينما هناك اتصال مٌفعل
-
+ website or IPموقع او IP
-
+ Import / Export Sites
-
+ Importاسترد
-
+ Save site listاحفظ قائمة المواقع
-
+ Save sitesاحفظ المواقع
-
-
-
+
+
+ Sites files (*.json)
-
+ Import a list of sitesاسترد قائمة من المواقع
-
+ Replace site listتبديل قائمة المواقع
-
-
+
+ Open sites fileافتح ملف المواقع
-
+ Add imported sites to existing onesإضافة المواقع المستردة للمواقع الموجودة
@@ -2272,32 +2312,32 @@ Already installed containers were found on the server. All installed containers
PageSetupWizardApiServiceInfo
-
+ For the regionللمنطقة
-
+ Priceالسعر
-
+ Work periodمدة العمل
-
+ Speedالسرعة
-
+ Featuresالمميزات
-
+ Connectاتصل
@@ -2305,12 +2345,12 @@ Already installed containers were found on the server. All installed containers
PageSetupWizardApiServicesList
-
+ VPN by AmneziaVPN بواسطة Amnezia
-
+ Choose a VPN service that suits your needs.اختر خدمة VPN تلبي احتياجاتك
@@ -2318,7 +2358,7 @@ Already installed containers were found on the server. All installed containers
PageSetupWizardConfigSource
-
+ Connectionالاتصال
@@ -2328,155 +2368,190 @@ Already installed containers were found on the server. All installed containers
إعدادات
-
+ Enable logs
-
+
+ Support tag
+ علامة الدعم
+
+
+
+ Copied
+
+
+
+ Insert the key, add a configuration file or scan the QR-codeأدخل المفتاح، أضف ملف تكوين أو امسح رمز الاستجابة السريعة
-
+ Insert keyأدخل مفتاح
-
+ Insertأدخل
-
+ Continueواصل
-
+ Other connection optionsاختيارات اتصال اخري
-
+
+ Site Amnezia
+
+
+
+ VPN by AmneziaVPN بواسطة Amnezia
-
+ Connect to classic paid and free VPN services from Amneziaاتصل بخدمات VPN الكلاسيكية المدفوعة والمجانية من Amnezia
-
+ Self-hosted VPNVPN ذاتية الاستضافة
-
+ Configure Amnezia VPN on your own serverقم بتكوين Amnezia VPN على الخادم الخاص بك
-
+ Restore from backupاسترجاع من ملف يحتوي علي نسخة احتياطية
-
+
+
+
+
+
+ Open backup fileافتح ملف نسخ احتياطي
-
+ Backup files (*.backup)ملفات نٌسخ احتياطية (*.backup)
-
+ File with connection settingsملف إعدادات اتصال
-
+
+
+
+
+
+ Open config fileافتح ملف تكوين
-
+ QR codeرمز QR
-
+
+
+
+
+
+ I have nothingليس لدي اي شئ
+
+
+
+
+ PageSetupWizardCredentials
-
+ Configure your serverتكوين الخادم الخاص بك
-
+ Server IP address [:port]عنوان خادم IP [:منفذ]
-
+ Continueواصل
-
+ All data you enter will remain strictly confidential and will not be shared or disclosed to the Amnezia or any third partiesستظل جميع البيانات التي تدخلها سرية للغاية ولن تتم مشاركتها أو الكشف عنها ل Amnezia أو أي طرف ثالث
-
+ 255.255.255.255:22
-
+ SSH Username
-
+ Password or SSH private keyكلمة مرور او مفتاح SSH خاص
-
+ How to run your VPN serverكيف تقوم بتشغيل خادم ال VPN الخاص بك
-
+ Where to get connection data, step-by-step instructions for buying a VPSاين تحصل علي بيانات الاتصال, تعليمات خطوة ب خطوة لشراء VPS
-
+ Ip address cannot be emptyلا يمكن لعنوان IP ان يكون فارغ
-
+ Enter the address in the format 255.255.255.255:88ادخل العنوان في شكل 255.255.255.255:88
-
+ Login cannot be emptyتسجيل دخول لا يمكن ان يكون فارغ
-
+ Password/private key cannot be emptyكلمة مرور/مفتاح خاص لأ يمكن ان يكونو فارغين
@@ -2484,22 +2559,22 @@ Already installed containers were found on the server. All installed containers
PageSetupWizardEasy
-
+ What is the level of internet control in your region?ما هو مستوي التحكم في الانترنت في منطقتك؟
-
+ Choose a VPN protocolاختر بروتوكول VPN
-
+ Skip setupتخطي الإعداد
-
+ Continueواصل
@@ -2546,37 +2621,37 @@ Already installed containers were found on the server. All installed containers
PageSetupWizardProtocolSettings
-
+ Installing %1تثبيت %1
-
+ More detailedاكثر تفصيلاً
-
+ Closeاغلق
-
+ Network protocolبروتوكول شبكة
-
+ Portمنفذ
-
+ Installتثبيت
-
+ The port must be in the range of 1 to 65535يجب أن يكون المنفذ في النطاق من 1 إلى 65535
@@ -2584,12 +2659,12 @@ Already installed containers were found on the server. All installed containers
PageSetupWizardProtocols
-
+ VPN protocolVPN بروتوكول
-
+ Choose the one with the highest priority for you. Later, you can install other protocols and additional services, such as DNS proxy and SFTP.اختر بالنسبة للأولوية القصوى بالنسبة لك. ويمكنك لاحقًا تثبيت بروتوكولات وخدمات إضافية أخرى، مثل وكيل DNS وSFTP.
@@ -2605,7 +2680,7 @@ Already installed containers were found on the server. All installed containers
PageSetupWizardStart
-
+ Let's get startedهيا نبدأ
@@ -2613,28 +2688,28 @@ Already installed containers were found on the server. All installed containers
PageSetupWizardTextKey
-
+ Connection keyمفتاح اتصال
-
+ A line that starts with vpn://...يجب ان تٌكتب بهذه الطريقة حتي بوجود التحذير كي تظهر بشكل صحيح داخل التطبيقسطر يبدأ ب ...//:vpn
-
+ Keyمفتاح
-
+ Insertادخل
-
+ Continueواصل
@@ -2642,32 +2717,32 @@ Already installed containers were found on the server. All installed containers
PageSetupWizardViewConfig
-
+ New connectionاتصال جديد
-
+ Collapse contentطي المحتوي
-
+ Show contentاظهر المحتوي
-
+ Enable WireGuard obfuscation. It may be useful if WireGuard is blocked on your provider.تمكين تشويش WireGuard. قد يكون من المفيد إذا تم حظر WireGuard على مزود الخدمة الخاص بك.
-
+ Use connection codes only from sources you trust. Codes from public sources may have been created to intercept your data.استخدم رموز اتصال فقط من المصادر التي تثق بها, ربما تم إنشاء رموز من مصادر عامة لاعتراض بياناتك.
-
+ Connectاتصل
@@ -2675,207 +2750,212 @@ Already installed containers were found on the server. All installed containers
PageShare
-
+ Save OpenVPN configاحفظ تكوين OpenVPN
-
+ Save WireGuard configاحفظ تكوين WireGuard
-
+ Save AmneziaWG configاحفظ تكوين AmneziaWG
-
+ Save Shadowsocks configاحفظ تكوين Shadowsocks
-
+ Save Cloak configاحفظ تكوين Cloak
-
+ Save XRay configحفظ تكوين XRay
-
+ For the AmneziaVPN appAmneziaVPN من اجل تطبيق
-
+ OpenVPN native formatتنسيق OpenVPN الاصلي
-
+ WireGuard native formatتنسيق WireGuard الاصلي
-
+ AmneziaWG native formatتنسيق AmneziaWG اصلي
-
+ Shadowsocks native formatتنسيق Shadowsocks الاصلي
-
+ Cloak native formatتنسيق Cloak الاصلي
-
+ XRay native formatالشكل الاصلي ل XRay
-
+ Share VPN Accessشارك اتصال VPN
-
+ Share full access to the server and VPNشارك ولوج كامل للخادم و ال VPN
-
+ Use for your own devices, or share with those you trust to manage the server.استخدمه للأجهزة الخاصة بك، أو شاركه مع من تثق بهم لإدارة الخادم.
-
-
+
+ Usersالمستخدمين
-
+ Share VPN access without the ability to manage the serverشارك اتصال VPN بدون القدرة علي إدارة الخادم
-
+ Searchابحث
-
+ Creation date: %1تاريخ الإنشاء: %1
-
+ Latest handshake: %1اخر تصافح: %1
-
+ Data received: %1البيانات المستلمة: %1
-
+ Data sent: %1البيانات المٌرسلة: %1
-
+
+ Allowed IPs: %1
+
+
+
+ Renameإعادة التسمية
-
+ Client nameاسم العميل
-
+ Saveاحفظ
-
+ Revokeسحب وإبطال
-
+ Revoke the config for a user - %1?سحب وإبطال للمستخدم - %1?
-
+ The user will no longer be able to connect to your server.المستخدم لن يكون قادر علي الاتصال بعد الان.
-
+ Continueواصل
-
+ Cancelإلغاء
-
+ Connectionالاتصال
-
-
+
+ Serverخادم
-
+ File with connection settings to ملف بإعدادات إلي
-
-
+
+ Protocolبروتوكول
-
+ Connection to اتصال إلي
-
+ Config revokedتم سحب وإبطال التكوين
-
+ User nameاسم المستخدم
-
-
+
+ Connection formatتنسيق الاتصال
-
-
+
+ Shareشارك
@@ -2883,55 +2963,55 @@ Already installed containers were found on the server. All installed containers
PageShareFullAccess
-
+ Full access to the server and VPNولوج كامل للخادم و ال VPN
-
+ We recommend that you use full access to the server only for your own additional devices.
نحن ننصحك بأستخدام ولوج كامل للخادم فقط لأجهزتك الاضافية.
-
+ If you share full access with other people, they can remove and add protocols and services to the server, which will cause the VPN to work incorrectly for all users. إذا شاركت ولوج كامل مع الاشخاص, سيكونو قادرين علي حذف وإضافة بروتوكولات و خدمات إلي الخادم, والذي سيجعل VPN يعمل بشكل غير صحيح لجميع المستخدمين.
-
-
+
+ Serverخادم
-
+ Accessing التواصل
-
+ File with accessing settings to ملف مع إعدادات الوصول إلي
-
+ Shareمشاركة
-
+ Access error!خطأ في الوصول!
-
+ Connection to اتصال إلي
-
+ File with connection settings to معلف مع إعدادات الاتصال إلي
@@ -2939,17 +3019,17 @@ Already installed containers were found on the server. All installed containers
PageStart
-
+ Logging was disabled after 14 days, log files were deletedتم تعطيل التسجيل بعد 14 يومًا، وتم حذف ملفات السجل
-
+ Settings restored from backup fileتم تحميل الإعدادات من ملف نسخة احتياطية
-
+ Logging is enabled. Note that logs will be automaticallydisabled after 14 days, and all log files will be deleted.
@@ -2957,7 +3037,7 @@ Already installed containers were found on the server. All installed containers
PopupType
-
+ Closeاغلاق
@@ -3288,22 +3368,22 @@ Already installed containers were found on the server. All installed containers
انتهت مدة الاتصال بالخادم
-
+ VPN connection error
-
+ Error when retrieving configuration from APIخطأ عند استرداد التكوين من API
-
+ This config has already been added to the applicationهذا التكوين بالفعل تمت إضافتة للبرنامج
-
+ ErrorCode: %1.
@@ -3378,57 +3458,72 @@ Already installed containers were found on the server. All installed containers
التكوين لا يحتوي علي اي حاويات و اعتماد للأتصال بالخادم
-
+
+ Unable to open config file
+
+
+
+ In the response from the server, an empty config was receivedفي الاستجابة من الخادم، تم تلقي تكوين فارغ
-
+ SSL error occurredحدث خطأ SSL
-
+ Server response timeout on api requestانتهت مهلة استجابة الخادم عند طلب واجهة برمجة التطبيقات
-
+ Missing AGW public keyمفتاح AGW عام مفقود
+
+
+ Failed to decrypt response payload
+
+
+ Missing list of available services
+
+
+
+ QFile error: The file could not be openedخطأ QFile: لا يمكن فتح الملف
-
+ QFile error: An error occurred when reading from the fileخطأ QFile: ظهر خطأ اثناء القراءه من الملف
-
+ QFile error: The file could not be accessedخطأ QFile: لا يمكن الوصول للملف
-
+ QFile error: An unspecified error occurredخطأ QFile: ظهر خطأ غير محدد
-
+ QFile error: A fatal error occurredخطأ QFile: حدث خطأ فادح
-
+ QFile error: The operation was abortedخطأ QFile: تم إحباط العملية
-
+ Internal errorخطأ داخلي
@@ -3867,11 +3962,19 @@ While it offers a blend of security, stability, and speed, it's essential t
SelectLanguageDrawer
-
+ Choose languageاختر لغة
+
+ ServersListView
+
+
+ Unable change server while there is an active connection
+ لا يمكن تغير الخادم بينما هناك اتصال مفعل
+
+Settings
@@ -3889,12 +3992,12 @@ While it offers a blend of security, stability, and speed, it's essential t
SettingsController
-
+ Backup file is corruptedملف النسخه الاحتياطيه تالف
-
+ All settings have been reset to default valuesتم استرجاع جميع الإعدادات للإعدادات الافتراضية
@@ -3908,33 +4011,33 @@ While it offers a blend of security, stability, and speed, it's essential t
احفظ تكوين AmneziaVPN
-
+ Shareشارك
-
+ Copyانسخ
-
-
+
+ Copiedتم النسخ
-
+ Copy config stringانسخ نص التكوين
-
+ Show connection settingsاظهر إعدادات الاتصال
-
+ To read the QR code in the Amnezia app, select "Add server" → "I have data to connect" → "QR code, key or settings file"حتي تقرأ رمز ال QR في تطبيق Amnezia, اختار "إضافة خادم" - "لدي بيانات الاتصال" - "رمز Qr, او مفتاح تعريف او ملف إعدادات"
@@ -3957,27 +4060,27 @@ While it offers a blend of security, stability, and speed, it's essential t
تم حذف الموقع: %1
-
+ Can't open file: %1لا يمكن فتح ملف: %1
-
+ Failed to parse JSON data from file: %1فشل قراءه بيانات JSON من الملف: %1
-
+ The JSON data is not an array in file: %1بيانات ال JSON ليست مصفوفة في الملف: %1
-
+ Import completedاكتمل الاستيراد
-
+ Export completedاكتمل التصدير
@@ -4018,7 +4121,7 @@ While it offers a blend of security, stability, and speed, it's essential t
TextFieldWithHeaderType
-
+ The field can't be emptyالحقل لا يمكن ان يكون فارغ
@@ -4026,7 +4129,7 @@ While it offers a blend of security, stability, and speed, it's essential t
VpnConnection
-
+ Mbps
@@ -4100,12 +4203,12 @@ While it offers a blend of security, stability, and speed, it's essential t
main2
-
+ Private key passphraseعبارة المرور الخاصة بالمفتاح
-
+ Saveاحفظ
diff --git a/client/translations/amneziavpn_fa_IR.ts b/client/translations/amneziavpn_fa_IR.ts
index 6cd78e77..c48606be 100644
--- a/client/translations/amneziavpn_fa_IR.ts
+++ b/client/translations/amneziavpn_fa_IR.ts
@@ -1,55 +1,63 @@
+
+ AdLabel
+
+
+ Amnezia Premium - for access to any website
+
+
+ApiServicesModel
-
+ Classic VPN for comfortable work, downloading large files and watching videos. Works for any sites. Speed up to %1 MBit/sبرای کار راحت، دانلود فایلهای بزرگ و تماشای ویدیوها، از VPN کلاسیک استفاده کنید. این VPN برای هر سایتی کار میکند و سرعت آن تا %1 مگابیت بر ثانیه است.
-
+ VPN to access blocked sites in regions with high levels of Internet censorship. وی پی ان برای دسترسی به سایتهای مسدود شده در مناطق با سانسور شدید اینترنت.
-
+ <p><a style="color: #EB5757;">Not available in your region. If you have VPN enabled, disable it, return to the previous screen, and try again.</a>
-
+ Amnezia Premium - A classic VPN for comfortable work, downloading large files, and watching videos in high resolution. It works for all websites, even in countries with the highest level of internet censorship.امنزیا پریمیوم - یک وی پی ان کلاسیک برای کار راحت، دانلود فایلهای بزرگ و تماشای ویدیو با کیفیت بالا. قابل استفاده برای تمامی سایتها، حتی در کشورهایی با بالاترین سطح سانسور اینترنت.
-
+ Amnezia Free is a free VPN to bypass blocking in countries with high levels of internet censorshipامنزیا رایگان یک وی پی ان رایگان برای دور زدن مسدودیتها در کشورهایی با سطح بالای سانسور اینترنت است.
-
+ %1 MBit/s%1 MBit/s
-
+ %1 days%1 روز
-
+ VPN will open only popular sites blocked in your region, such as Instagram, Facebook, Twitter and others. Other sites will be opened from your real IP address, <a href="%1/free" style="color: #FBB26A;">more details on the website.</a>وی پی ان فقط سایتهای محبوبی را که در منطقه شما مسدود شدهاند، مانند اینستاگرام، فیسبوک، توییتر و غیره باز میکند. سایر سایتها با آدرس آیپی واقعی شما باز خواهند شد. <a href="%1/free" style="color: #FBB26A;">more details on the website.</a>
-
+ Freeرایگان
-
+ %1 $/month%1 $/ماه
@@ -80,7 +88,7 @@
ConnectButton
-
+ Unable to disconnect during configuration preparationدر هنگام آمادهسازی پیکربندی، نمیتوان از اتصال خارج شد.
@@ -88,63 +96,63 @@
ConnectionController
-
+ VPN Protocols is not installed.
Please install VPN container at firstپروتکل ویپیان نصب نشده است
لطفا کانتینر ویپیان را نصب کنید
-
+ Connecting...در حال ارتباط...
-
+ Connectedمتصل
-
+ Preparing...در حال آمادهسازی...
-
+ Settings updated successfully, reconnnection...تنظیمات به روز رسانی شد
در حال اتصال دوباره...
-
+ Settings updated successfullyتنظیمات با موفقیت بهروزرسانی شدند
-
+ The selected protocol is not supported on the current platformپروتکل انتخابشده در پلتفرم فعلی پشتیبانی نمیشود.
-
+ unable to create configurationنمیتوان پیکربندی را ایجاد کرد.
-
+ Reconnecting...اتصال دوباره...
-
-
-
+
+
+ Connectاتصال
-
+ Disconnecting...قطع ارتباط...
@@ -152,17 +160,17 @@
ConnectionTypeSelectionDrawer
-
+ Add new connectionایجاد ارتباط جدید
-
+ Configure your serverتنظیم سرور
-
+ Open config file, key or QR codeبارگذاری فایل تنظیمات، کلید یا QR Code
@@ -200,7 +208,7 @@
HomeContainersListView
-
+ Unable change protocol while there is an active connectionامکان تغییر پروتکل در هنگام متصل بودن وجود ندارد
@@ -212,45 +220,45 @@
HomeSplitTunnelingDrawer
-
+ Split tunnelingجداسازی ترافیک
-
+ Allows you to connect to some sites or applications through a VPN connection and bypass othersاجازه میدهد به شما که از طریق اتصال VPN به برخی از وبسایتها یا برنامهها وصل شوید و از دیگران عبور کنید
-
+ Split tunneling on the serverتقسیم تونلها در سرور
-
+ Enabled
Can't be disabled for current serverفعال
-
+ Site-based split tunnelingجداسازی ترافیک بر اساس سایت
-
-
+
+ Enabledفعال
-
-
+
+ Disabledغیر فعال
-
+ App-based split tunnelingجداسازی ترافیک بر اساس نرمافزار
@@ -258,23 +266,20 @@ Can't be disabled for current server
ImportController
- Unable to open file
- نمیتوان فایل را باز کرد.
+ نمیتوان فایل را باز کرد.
-
- Invalid configuration file
- فایل پیکربندی نامعتبر است.
+ فایل پیکربندی نامعتبر است.
-
+ Scanned %1 of %2.ارزیابی %1 از %2.
-
+ In the imported configuration, potentially dangerous lines were found:در پیکربندی وارد شده، خطوطی که ممکن است خطرناک باشند، یافت شدند:
@@ -282,86 +287,86 @@ Can't be disabled for current server
InstallController
-
+ %1 installed successfully. %1 با موفقیت نصب شد.
-
+ %1 is already installed on the server. %1 در حال حاضر بر روی سرور نصب شده است.
-
+
Added containers that were already installed on the server
کانتینرهایی که بر روی سرور موجود بودند اضافه شدند
-
+
Already installed containers were found on the server. All installed containers have been added to the application
کانتینرهای نصب شده بر روی سرور شناسایی شدند. تمام کانتینترهای نصب شده به نرم افزار اضافه شدند
-
+ Settings updated successfullyتنظیمات با موفقیت بهروزرسانی شدند
-
+ Server '%1' was rebootedسرور %1 راه اندازی مجدد شد
-
+ Server '%1' was removedسرور %1 حذف شد
-
+ All containers from server '%1' have been removedتمام کانتینترها از سرور %1 حذف شدند
-
+ %1 has been removed from the server '%2'%1 از سرور %2 حذف شد
-
+ Api config removedپیکربندی API حذف شد.
-
+ %1 cached profile cleared%1 پروفایل ذخیره شده پاک شد.
-
+ Please login as the userلطفا به عنوان کاربر وارد شوید
-
+ Server added successfullyسرور با موفقیت اضافه شد
-
+ %1 installed successfully.%1 با موفقیت نصب شد.
-
+ API config reloadedپیکربندی API دوباره بارگذاری شد.
-
+ Successfully changed the country of connection to %1کشور اتصال با موفقیت به %1 تغییر یافت.
@@ -374,12 +379,12 @@ Already installed containers were found on the server. All installed containers
انتخاب برنامه
-
+ application nameنام برنامه
-
+ Add selectedاضافه کردن انتخاب شده
@@ -447,12 +452,12 @@ Already installed containers were found on the server. All installed containers
PageDevMenu
-
+ Gateway endpoint
-
+ Dev gateway environment
@@ -460,85 +465,84 @@ Already installed containers were found on the server. All installed containers
PageHome
-
+ Logging enabledلاگبرداری فعال شد
-
+ Split tunneling enabledفعال شدن تونل تقسیمشده
-
+ Split tunneling disabledتونل تقسیمشده غیرفعال شده
-
+ VPN protocolپروتکل ویپیان
-
+ Serversسرورها
- Unable change server while there is an active connection
- امکان تغییر سرور در هنگام متصل بودن وجود ندارد
+ امکان تغییر سرور در هنگام متصل بودن وجود نداردPageProtocolAwgClientSettings
-
+ AmneziaWG settingsتنظیمات AmneziaWG
-
+ MTU
-
+ Server settings
-
+ Portپورت
-
+ Saveذخیره
-
+ Save settings?تنظیمات را ذخیره کن?
-
+ Only the settings for this device will be changed
-
+ Continue
-
+ Cancel
-
+ Unable change settings while there is an active connectionنمیتوان تنظیمات را تغییر داد در حالی که اتصال فعال است.
@@ -546,12 +550,12 @@ Already installed containers were found on the server. All installed containers
PageProtocolAwgSettings
-
+ AmneziaWG settingsتنظیمات AmneziaWG
-
+ Portپورت
@@ -564,87 +568,92 @@ Already installed containers were found on the server. All installed containers
آیا میخواهید AmneziaWG از سرور حذف شود؟
-
+ All users with whom you shared a connection with will no longer be able to connect to it.همه کاربرانی که با آنها ارتباطی به اشتراک گذاشتهاید دیگر قادر به اتصال به آن نخواهند بود.
-
+ Saveذخیره
-
+
+ VPN address subnet
+ زیرشبکه آدرس VPN
+
+
+ Jc - Junk packet count
-
+ Jmin - Junk packet minimum size
-
+ Jmax - Junk packet maximum size
-
+ S1 - Init packet junk size
-
+ S2 - Response packet junk size
-
+ H1 - Init packet magic header
-
+ H2 - Response packet magic header
-
+ H4 - Transport packet magic header
-
+ H3 - Underload packet magic header
-
+ The values of the H1-H4 fields must be unique
-
+ The value of the field S1 + message initiation size (148) must not equal S2 + message response size (92)
-
+ Save settings?تنظیمات را ذخیره کن?
-
+ Continueادامه
-
+ Cancelکنسل
-
+ Unable change settings while there is an active connectionنمیتوان تنظیمات را تغییر داد در حالی که اتصال فعال است.
@@ -652,33 +661,33 @@ Already installed containers were found on the server. All installed containers
PageProtocolCloakSettings
-
+ Cloak settingsتنظیمات Cloak
-
+ Disguised as traffic fromپنهان کردن به عنوان ترافیک از
-
+ Portپورت
-
-
+
+ Cipherرمزگذاری
-
+ Saveذخیره
-
+ Unable change settings while there is an active connectionنمیتوان تنظیمات را تغییر داد در حالی که اتصال فعال است.
@@ -686,170 +695,170 @@ Already installed containers were found on the server. All installed containers
PageProtocolOpenVpnSettings
-
+ OpenVPN settingsتنظیمات OpenVPN
-
+ VPN address subnetزیرشبکه آدرس VPN
-
+ Network protocolپروتکل شبکه
-
+ Portپورت
-
+ Auto-negotiate encryptionرمزگذاری خودکار
-
-
+
+ Hashهش
-
+ SHA512SHA512
-
+ SHA384SHA384
-
+ SHA256SHA256
-
+ SHA3-512SHA3-512
-
+ SHA3-384SHA3-384
-
+ SHA3-256SHA3-256
-
+ whirlpoolwhirlpool
-
+ BLAKE2b512BLAKE2b512
-
+ BLAKE2s256BLAKE2s256
-
+ SHA1SHA1
-
-
+
+ Cipherرمزگذاری
-
+ AES-256-GCMAES-256-GCM
-
+ AES-192-GCMAES-192-GCM
-
+ AES-128-GCMAES-128-GCM
-
+ AES-256-CBCAES-256-CBC
-
+ AES-192-CBCAES-192-CBC
-
+ AES-128-CBCAES-128-CBC
-
+ ChaCha20-Poly1305ChaCha20-Poly1305
-
+ ARIA-256-CBCARIA-256-CBC
-
+ CAMELLIA-256-CBCCAMELLIA-256-CBC
-
+ nonenone
-
+ TLS authاعتبار TLS
-
+ Block DNS requests outside of VPNمسدود کردن درخواستهای DNS خارج از ویپیان
-
+ Additional client configuration commandsتنظیمات و دستورات اضافه برنامه متصل شونده
-
-
+
+ Commands:دستورات:
-
+ Additional server configuration commandsتنظیمات و دستورات اضافه سرور
-
+ Unable change settings while there is an active connectionنمیتوان تنظیمات را تغییر داد در حالی که اتصال فعال است.
@@ -874,7 +883,7 @@ Already installed containers were found on the server. All installed containers
کنسل
-
+ Saveذخیره
@@ -882,32 +891,32 @@ Already installed containers were found on the server. All installed containers
PageProtocolRaw
-
+ settings تنظیمات
-
+ Show connection optionsنمایش تنظیمات اتصال
-
+ Connection options %1تنظیمات اتصال %1
-
+ Remove حذف
-
+ Remove %1 from server?%1 از سرور حذف شود؟
-
+ All users with whom you shared a connection with will no longer be able to connect to it.همه کاربرانی که با آنها ارتباطی به اشتراک گذاشتهاید دیگر قادر به اتصال به آن نخواهند بود.
@@ -916,12 +925,12 @@ Already installed containers were found on the server. All installed containers
همه کاربرانی که با آن این پروتکل VPN را به اشتراک گذاشتهاید دیگر نمیتوانند به آن متصل شوند.
-
+ Continueادامه
-
+ Cancelکنسل
@@ -929,28 +938,28 @@ Already installed containers were found on the server. All installed containers
PageProtocolShadowSocksSettings
-
+ Shadowsocks settingsتنظیمات Shadowsocks
-
+ Portپورت
-
-
+
+ Cipherرمزگذاری
-
+ Saveذخیره
-
+ Unable change settings while there is an active connectionنمیتوان تنظیمات را تغییر داد در حالی که اتصال فعال است.
@@ -958,52 +967,52 @@ Already installed containers were found on the server. All installed containers
PageProtocolWireGuardClientSettings
-
+ WG settingsتنظیمات WG
-
+ MTU
-
+ Server settings
-
+ Portپورت
-
+ Saveذخیره
-
+ Save settings?تنظیمات را ذخیره کن?
-
+ Only the settings for this device will be changed
-
+ Continue
-
+ Cancel
-
+ Unable change settings while there is an active connectionنمیتوان تنظیمات را تغییر داد در حالی که اتصال فعال است.
@@ -1011,32 +1020,37 @@ Already installed containers were found on the server. All installed containers
PageProtocolWireGuardSettings
-
+ WG settingsتنظیمات WG
-
+
+ VPN address subnet
+ زیرشبکه آدرس VPN
+
+
+ Portپورت
-
+ Save settings?تنظیمات را ذخیره کن?
-
+ All users with whom you shared a connection with will no longer be able to connect to it.همه کاربرانی که با آنها ارتباطی به اشتراک گذاشتهاید دیگر قادر به اتصال به آن نخواهند بود.
-
+ Continue
-
+ Unable change settings while there is an active connectionنمیتوان تنظیمات را تغییر داد در حالی که اتصال فعال است.
@@ -1045,12 +1059,12 @@ Already installed containers were found on the server. All installed containers
تمام کاربرانی که این ارتباط را با آنها به اشتراک گذاشتهاید دیگر نمیتوانند به آن متصل شوند.
-
+ Cancelکنسل
-
+ Saveذخیره
@@ -1058,22 +1072,22 @@ Already installed containers were found on the server. All installed containers
PageProtocolXraySettings
-
+ XRay settingsتنظیمات XRay
-
+ Disguised as traffic fromبهعنوان ترافیک از طرف زیر نمایش داده میشود
-
+ Saveذخیره
-
+ Unable change settings while there is an active connectionنمیتوان تنظیمات را تغییر داد در حالی که اتصال فعال است.
@@ -1088,39 +1102,39 @@ Already installed containers were found on the server. All installed containers
PageServiceDnsSettings
-
+ A DNS service is installed on your server, and it is only accessible via VPN.
یک سرویس DSN بر روی سرور شما نصب شده و فقط از طریق ویپیان قابل دسترسی میباشد.
-
+ The DNS address is the same as the address of your server. You can configure DNS in the settings, under the connections tab.آدرس DSN همان آدرس سرور شماست. میتوانید از قسمت تنظیمات و تب اتصالات DSN خود را تنظیم کنید.
-
+ Remove جذف
-
+ Remove %1 from server?%1 از سرور حذف شود؟
-
+ Continueادامه
-
+ Cancelکنسل
-
+ Cannot remove AmneziaDNS from running serverنمیتوان AmneziaDNS را از سرور در حال اجرا حذف کرد.
@@ -1128,67 +1142,67 @@ Already installed containers were found on the server. All installed containers
PageServiceSftpSettings
-
+ Settings updated successfullyتنظیمات با موفقیت بهروزرسانی شد
-
+ SFTP settingsتنظیمات SFTP
-
+ Hostهاست
-
-
-
-
+
+
+
+ Copiedکپی شد
-
+ Portپورت
-
+ User nameنام کاربری
-
+ Passwordرمز عبور
-
+ Mount folder on deviceبارگذاری پوشه بر روی دستگاه
-
+ In order to mount remote SFTP folder as local drive, perform following steps: <br>برای بارگذاری پوشه SFTP بر روی درایو محلی قدمهای زیر را انجام دهید: <br>
-
-
+
+ <br>1. Install the latest version of <br> 1. آخرین نسخه را نصب کنید
-
-
+
+ <br>2. Install the latest version of <br> 2. آخرین نسخه را نصب کنید
-
+ Detailed instructionsجزییات دستورالعملها
@@ -1212,69 +1226,69 @@ Already installed containers were found on the server. All installed containers
PageServiceSocksProxySettings
-
+ Settings updated successfullyتنظیمات با موفقیت بهروزرسانی شد.
-
-
+
+ SOCKS5 settingsتنظیمات SOCKS5
-
+ Hostهاستمیزبان
-
-
-
-
+
+
+
+ Copiedکپی شد
-
-
+
+ Portپورت
-
+ User nameنام کاربری
-
-
+
+ Passwordرمز عبور
-
+ Usernameنام کاربری
-
-
+
+ Change connection settingsتغییر تنظیمات اتصال
-
+ The port must be in the range of 1 to 65535پورت باید در محدوده ۱ تا ۶۵۵۳۵ باشد
-
+ Password cannot be emptyرمز عبور نمیتواند خالی باشد
-
+ Username cannot be emptyنام کاربری نمیتواند خالی باشد
@@ -1282,37 +1296,37 @@ Already installed containers were found on the server. All installed containers
PageServiceTorWebsiteSettings
-
+ Settings updated successfullyتنظیمات با موفقیت بهروزرسانی شد
-
+ Tor website settingsتنظیمات وبسایت Tor
-
+ Website addressآدرس وبسایت
-
+ Copiedکپی شد
-
+ Use <a href="https://www.torproject.org/download/" style="color: #FBB26A;">Tor Browser</a> to open this URL.
-
+ After creating your onion site, it takes a few minutes for the Tor network to make it available for use.پس از ایجاد سایت پیاز خود، چند دقیقه طول میکشد تا شبکه تور آن را برای استفاده فراهم کند.
-
+ When configuring WordPress set the this onion address as domain.زمانی که سایت وردپرس را تنظیم میکنید این آدرس پیازی را به عنوان دامنه قرار دهید.
@@ -1336,42 +1350,42 @@ Already installed containers were found on the server. All installed containers
PageSettings
-
+ Settingsتنظیمات
-
+ Serversسرورها
-
+ Connectionارتباط
-
+ Applicationنرمافزار
-
+ Backupبکآپ
-
+ About AmneziaVPNدرباره Amnezia
-
+ Dev console
-
+ Close applicationبستن نرمافزار
@@ -1379,37 +1393,37 @@ Already installed containers were found on the server. All installed containers
PageSettingsAbout
-
+ Support Amneziaپشتیبانی از Amnezia
-
+ Amnezia is a free and open-source application. You can support the developers if you like it.Amnezia یک برنامه رایگان و متن باز است. اگر دوست دارید می توانید از توسعه دهندگان حمایت کنید.
-
+ Contactsمخاطب
-
+ Telegram groupگروه تلگرام
-
+ To discuss featuresبرای گفتگو در مورد ویژگیها
-
+ https://t.me/amnezia_vpn_enhttps://t.me/amnezia_vpn_ir
-
+ support@amnezia.org
@@ -1418,121 +1432,144 @@ Already installed containers were found on the server. All installed containers
ایمیل
-
+ For reviews and bug reportsبرای ارائه نظرات و گزارشات باگ
-
+ Copiedکپی شد
-
+ GitHubGitHub
-
+
+ Discover the source code
+
+
+
+ https://github.com/amnezia-vpn/amnezia-clienthttps://github.com/amnezia-vpn/amnezia-client
-
+ Websiteوب سایت
+
+
+ Visit official website
+
+ https://amnezia.orghttps://amnezia.org
-
+ Software version: %1%1 :نسخه نرمافزار
-
+ Check for updatesبررسی بروزرسانی
-
+ Privacy Policy
+
+ PageSettingsApiLanguageList
+
+
+ Unable change server location while there is an active connection
+
+
+PageSettingsApiServerInfo
-
+ For the regionبرای منطقه
-
+ Priceقیمت
-
+ Work periodمدت زمان کار
-
+
+ Valid until
+
+
+
+ Speedسرعت
-
+ Support tag
-
+ Copiedکپی شد
-
+ Reload API configبارگذاری مجدد پیکربندی API
-
+ Reload API config?آیا میخواهید پیکربندی API را دوباره بارگذاری کنید؟
-
-
+
+ Continueادامه دهید
-
-
+
+ Cancelلغو
-
+ Cannot reload API config during active connectionنمیتوان پیکربندی API را در حین اتصال فعال دوباره بارگذاری کرد.
-
+ Remove from applicationحذف از برنامه
-
+ Remove from application?آیا میخواهید از برنامه حذف کنید؟
-
+ Cannot remove server during active connectionنمیتوان سرور را در حین اتصال فعال حذف کرد.
@@ -1540,12 +1577,12 @@ Already installed containers were found on the server. All installed containers
PageSettingsAppSplitTunneling
-
+ Cannot change split tunneling settings during active connectionنمی توان تنظیمات تونل تقسیم را در طول اتصال فعال تغییر دادنمیتوان تنظیمات تقسیم تونلینگ را در حین اتصال فعال تغییر داد.
-
+ Only the apps from the list should have access via VPNفقط برنامههای موجود در لیست باید از طریق VPN دسترسی داشته باشند.
@@ -1555,42 +1592,42 @@ Already installed containers were found on the server. All installed containers
برنامههای موجود در لیست نباید از طریق VPN دسترسی داشته باشند.
-
+ App split tunnelingتقسیم تونلینگ برنامهها
-
+ Modeحالت
-
+ Remove حذف
-
+ Continueادامه دهید
-
+ Cancelکنسل
-
+ application nameنام برنامه
-
+ Open executable fileفایل اجرایی را باز کنید
-
+ Executable files (*.*)فایلهای اجرایی (*.*)
@@ -1598,102 +1635,102 @@ Already installed containers were found on the server. All installed containers
PageSettingsApplication
-
+ Applicationنرم افزار
-
+ Allow application screenshotsمجوز اسکرینشات در برنامه
-
+ Enable notificationsفعال کردن اعلانها
-
+ Enable notifications to show the VPN state in the status barاعلان ها را فعال کنید تا وضعیت VPN را در نوار وضعیت ببینید
-
+ Auto startشروع خودکار
-
+ Launch the application every time the device is startsراهاندازی نرمافزار با هر بار روشن شدن دستگاه
-
+ Auto connectاتصال خودکار
-
+ Connect to VPN on app startاتصال به ویپیان با شروع نرمافزار
-
+ Start minimizedشروع به صورت کوچک
-
+ Launch application minimizedراهاندازی برنامه به صورت کوچک
-
+ Languageزبان
-
+ Loggingگزارشات
-
+ Enabledفعال
-
+ Disabledغیر فعال
-
+ Reset settings and remove all data from the applicationریست کردن تنظیمات و حذف تمام دادهها از نرمافزار
-
+ Reset settings and remove all data from the application?ریست کردن تنظیمات و حذف تمام دادهها از نرمافزار؟
-
+ All settings will be reset to default. All installed AmneziaVPN services will still remain on the server.تمام تنظیمات به حالت پیشفرض ریست میشوند. تمام سرویسهای Amnezia بر روی سرور باقی میمانند.
-
+ Continueادامه
-
+ Cancelکنسل
-
+ Cannot reset settings during active connectionنمیتوان تنظیمات را در حین اتصال فعال بازنشانی کرد.
@@ -1701,78 +1738,78 @@ Already installed containers were found on the server. All installed containers
PageSettingsBackup
-
+ Settings restored from backup fileتنظیمات از فایل پشتیبان بازیابی شد
-
+ Back up your configurationیک نسخه پشتیبان از تنظیمات خود تهیه
-
+ You can save your settings to a backup file to restore them the next time you install the application.میتوانید تنظیمات را در یک فایل پشتیبان ذخیره کرده و دفعه بعد که نرمافزار را نصب کردید آنها را بازیابی کنید.
-
+ The backup will contain your passwords and private keys for all servers added to AmneziaVPN. Keep this information in a secure place.پشتیبان حاوی رمزهای عبور و کلیدهای خصوصی شما برای تمام سرورهای اضافه شده به AmneziaVPN خواهد بود. این اطلاعات را در یک مکان امن نگه دارید
-
+ Make a backupایجاد یک پشتیبان
-
+ Save backup fileذخیره فایل پشتیبان
-
-
+
+ Backup files (*.backup)Backup files (*.backup)
-
+ Backup file savedفایل پشتیبان ذخیره شد
-
+ Restore from backupبازیابی از پشتیبان
-
+ Open backup fileباز کردن فایل پشتیبان
-
+ Import settings from a backup file?ورود تنظیمات از فایل پشتیبان؟
-
+ All current settings will be resetتمام تنظیمات جاری ریست خواهد شد
-
+ Continueادامه
-
+ Cancelکنسل
-
+ Cannot restore backup settings during active connectionنمیتوان تنظیمات پشتیبان را در حین اتصال فعال بازیابی کرد.
@@ -1780,62 +1817,62 @@ Already installed containers were found on the server. All installed containers
PageSettingsConnection
-
+ Connectionارتباط
-
+ Use AmneziaDNSاستفاده از AmneziaDNS
-
+ If AmneziaDNS is installed on the serverاگر AmneziaDNS بر روی سرور نصب شده باشد
-
+ DNS serversسرورهای DNS
-
+ When AmneziaDNS is not used or installedوقتی AmneziaDNS استفاده نشده یا نصب نشده است
-
+ Allows you to use the VPN only for certain Appsبه شما امکان می دهد از VPN فقط برای برخی برنامه ها استفاده کنید
-
+ KillSwitchKillSwitch
-
+ Disables your internet if your encrypted VPN connection drops out for any reason.اگر به هر دلیلی اتصال VPN رمزگذاری شده شما قطع شود، اینترنت شما را غیرفعال میکند.
-
+ Cannot change killSwitch settings during active connectionنمیتوان تنظیمات Kill Switch را در حین اتصال فعال تغییر داد.
-
+ Site-based split tunnelingجداسازی ترافیک بر اساس سایت
-
+ Allows you to select which sites you want to access through the VPNمیتوانید مشخص کنید که چه سایتهایی از ویپیان استفاده کنند
-
+ App-based split tunnelingجداسازی ترافیک بر اساس نرمافزار
@@ -1843,62 +1880,62 @@ Already installed containers were found on the server. All installed containers
PageSettingsDns
-
+ Default server does not support custom DNSسرور پیشفرض از دیاناس سفارشی پشتیبانی نمیکند
-
+ DNS serversسرورهای DNS
-
+ If AmneziaDNS is not used or installedاگر AmneziaDNS نصب نباشد یا استفاده نشود
-
+ Primary DNSDNS اصلی
-
+ Secondary DNSDNS ثانویه
-
+ Restore defaultبازگشت به پیشفرض
-
+ Restore default DNS settings?بازگشت به تنظیمات پیشفرض DNS؟
-
+ Continueادامه
-
+ Cancelکنسل
-
+ Settings have been resetتنظیمات ریست شد
-
+ Saveذخیره
-
+ Settings savedذخیره تنظیمات
@@ -1910,12 +1947,12 @@ Already installed containers were found on the server. All installed containers
ثبت وقایع فعال است. توجه داشته باشید که ثبت وقایع بهطور خودکار پس از ۱۴ روز غیرفعال شده و تمام فایلهای ثبت وقایع حذف خواهند شد.
-
+ Loggingگزارشات
-
+ Enabling this function will save application's logs automatically. By default, logging functionality is disabled. Enable log saving in case of application malfunction.فعال کردن این عملکرد باعث ذخیره خودکار لاگهای برنامه میشود. به طور پیشفرض، قابلیت ثبت لاگ غیرفعال است. در صورت بروز خطا در برنامه، ذخیره لاگ را فعال کنید.
@@ -1928,20 +1965,20 @@ Already installed containers were found on the server. All installed containers
باز کردن پوشه گزارشات
-
-
+
+ Saveذخیره
-
-
+
+ Logs files (*.log)Logs files (*.log)
-
-
+
+ Logs file savedفایل گزارشات ذخیره شد
@@ -1950,64 +1987,62 @@ Already installed containers were found on the server. All installed containers
ذخیره گزارشات در فایل
-
+ Enable logs
-
+ Clear logs?پاک کردن گزارشات؟
-
+ Continueادامه
-
+ Cancelکنسل
-
+ Logs have been cleaned upگزارشات پاک شدند
-
+ Client logs
-
+ AmneziaVPN logs
-
-
+ Open logs folder
-
-
+ Export logs
-
+ Service logs
-
+ AmneziaVPN-service logs
-
+ Clear logsپاک کردن گزارشات
@@ -2042,18 +2077,18 @@ Already installed containers were found on the server. All installed containers
-
-
-
-
+
+
+
+ Continueادامه
-
-
-
-
+
+
+
+ Cancelکنسل
@@ -2068,77 +2103,77 @@ Already installed containers were found on the server. All installed containers
اضافه کردن آنها به نرمافزار اگر نمایش داده نشدهاند
-
+ Reboot serverسرور را دوباره راهاندازی کنید
-
+ Do you want to reboot the server?آیا میخواهید سرور را دوباره راهاندازی کنید؟
-
+ The reboot process may take approximately 30 seconds. Are you sure you wish to proceed?فرآیند راهاندازی ممکن است حدود ۳۰ ثانیه طول بکشد. آیا مطمئن هستید که میخواهید ادامه دهید؟
-
+ Cannot reboot server during active connectionنمیتوان سرور را در حین اتصال فعال راهاندازی مجدد کرد.
-
+ Do you want to remove the server from application?آیا میخواهید سرور را از برنامه حذف کنید؟
-
+ Cannot remove server during active connectionنمیتوان سرور را در حین اتصال فعال حذف کرد.
-
+ Do you want to clear server from Amnezia software?آیا میخواهید سرور را از نرمافزار Amnezia پاک کنید؟
-
+ All users whom you shared a connection with will no longer be able to connect to it.همه کاربرانی که با آنها ارتباطی به اشتراک گذاشتهاید دیگر قادر به اتصال به آن نخواهند بود.
-
+ Cannot clear server from Amnezia software during active connectionنمیتوان سرور را در حین اتصال فعال از نرمافزار Amnezia پاک کرد.
-
+ Reset API configتنظیمات API را بازنشانی کنید
-
+ Do you want to reset API config?آیا می خواهید پیکربندی API را بازنشانی کنید؟
-
+ Cannot reset API config during active connectionنمیتوان پیکربندی API را در حین اتصال فعال بازنشانی کرد.
-
+ Remove server from applicationحذف کردن سرور از نرمافزار
-
+ All installed AmneziaVPN services will still remain on the server.تمام سرویسهای نصبشده Amnezia همچنان بر روی سرور باقی خواهند ماند.
-
+ Clear server from Amnezia softwareپاک کردن سرور از نرمافزار Amnezia
@@ -2146,27 +2181,32 @@ Already installed containers were found on the server. All installed containers
PageSettingsServerInfo
-
+
+ Subscription is valid until
+
+
+
+ Server nameنام سرور
-
+ Saveذخیره
-
+ Protocolsپروتکلها
-
+ Servicesسرویسها
-
+ Managementمدیریت
@@ -2174,7 +2214,7 @@ Already installed containers were found on the server. All installed containers
PageSettingsServerProtocol
-
+ settings تنظیمات
@@ -2183,7 +2223,7 @@ Already installed containers were found on the server. All installed containers
پاک کردن پروفایل %1
-
+ Clear %1 profile?آیا میخواهید پروفایل %1 را پاک کنید؟
@@ -2193,64 +2233,64 @@ Already installed containers were found on the server. All installed containers
-
+ Unable to clear %1 profile while there is an active connectionنمیتوان پروفایل %1 را در حین اتصال فعال پاک کرد.
-
+ Remove حذف
-
+ Remove %1 from server?حذف %1 از سرور؟
-
+ All users with whom you shared a connection will no longer be able to connect to it.تمام کاربرانی که این ارتباط را با آنها به اشتراک گذاشتهاید دیگر نمیتوانند به آن متصل شوند.
-
+ Cannot remove active containerنمیتوان کانتینر فعال را حذف کرد.
-
-
+
+ Continueادامه
-
+ connection settings
-
+ Click the "connect" button to create a connection configuration
-
+ server settings
-
+ Clear profile
-
+ The connection configuration will be deleted for this device only
-
-
+
+ Cancelکنسل
@@ -2258,7 +2298,7 @@ Already installed containers were found on the server. All installed containers
PageSettingsServersList
-
+ Serversسرورها
@@ -2266,100 +2306,100 @@ Already installed containers were found on the server. All installed containers
PageSettingsSplitTunneling
-
+ Default server does not support split tunneling functionسرور پیشفرض از عملکرد تونلسازی تقسیم شده پشتیبانی نمیکند
-
+ Addresses from the list should not be accessed via VPNدسترسی به آدرسهای لیست بدون ویپیان
-
+ Split tunnelingجداسازی ترافیک
-
+ Modeحالت
-
+ Remove حذف
-
+ Continueادامه
-
+ Cancelکنسل
-
+ Cannot change split tunneling settings during active connectionنمی توان تنظیمات تونل تقسیم را در طول اتصال فعال تغییر داد
-
+ Only the sites listed here will be accessed through the VPNتنها سایتهای موجود در اینجا از طریق VPN دسترسی داده خواهند شد
-
+ website or IPوبسایت یا آدرس IP
-
+ Import / Export Sitesوارد کردن / صادر کردن وبسایتها
-
+ Importبارگذاری
-
+ Save site listذخیره لیست سایتها
-
+ Save sitesذخیره سایتها
-
-
-
+
+
+ Sites files (*.json)Sites files (*.json)
-
+ Import a list of sitesبارگذاری لیست سایتها
-
+ Replace site listجایگزین کردن لیست سایت
-
-
+
+ Open sites fileباز کردن فایل سایتها
-
+ Add imported sites to existing onesاضافه کردن سایتهای بارگذاری شده به سایتهای موجود
@@ -2367,32 +2407,32 @@ Already installed containers were found on the server. All installed containers
PageSetupWizardApiServiceInfo
-
+ For the regionبرای منطقه
-
+ Priceقیمت
-
+ Work periodمدت زمان کار
-
+ Speedسرعت
-
+ Featuresویژگیها
-
+ Connectاتصال
@@ -2400,12 +2440,12 @@ Already installed containers were found on the server. All installed containers
PageSetupWizardApiServicesList
-
+ VPN by AmneziaVPN توسط Amnezia
-
+ Choose a VPN service that suits your needs.یک سرویس VPN که مناسب نیازهای شما باشد را انتخاب کنید.
@@ -2433,7 +2473,7 @@ It's okay as long as it's from someone you trust.
چی داری؟
-
+ File with connection settingsفایل شامل تنظیمات اتصال
@@ -2442,7 +2482,7 @@ It's okay as long as it's from someone you trust.
فایل شامل تنظیمات اتصال یا بکآپ
-
+ Connectionارتباط
@@ -2452,85 +2492,120 @@ It's okay as long as it's from someone you trust.
تنظیمات
-
+ Enable logs
-
+
+ Support tag
+
+
+
+
+ Copied
+ کپی شد
+
+
+ Insert the key, add a configuration file or scan the QR-codeکلید را وارد کنید، فایل پیکربندی را اضافه کنید یا کد QR را اسکن کنید
-
+ Insert keyکلید را وارد کنید
-
+ Insertوارد کردن
-
+ Continueادامه دهید
-
+ Other connection optionsگزینههای اتصال دیگر
-
+
+ Site Amnezia
+
+
+
+ VPN by AmneziaVPN توسط Amnezia
-
+ Connect to classic paid and free VPN services from Amneziaاتصال به سرویسهای VPN کلاسیک پولی و رایگان از Amnezia
-
+ Self-hosted VPNSelf-hosted VPN
-
+ Configure Amnezia VPN on your own serverپیکربندی VPN Amnezia بر روی سرور خودتان
-
+ Restore from backupبازیابی از پشتیبان
-
+
+
+
+
+
+ Open backup fileباز کردن فایل پشتیبان
-
+ Backup files (*.backup)Backup files (*.backup)
-
+
+
+
+
+
+ Open config fileباز کردن فایل تنظیمات
-
+ QR codeQR-Code
-
+
+
+
+
+
+ I have nothingمن هیچی ندارم
+
+
+
+
+ Key as textمتن شامل کلید
@@ -2539,67 +2614,67 @@ It's okay as long as it's from someone you trust.
PageSetupWizardCredentials
-
+ Server IP address [:port]آدرس آیپی سرور (:پورت)
-
+ Continueادامه
-
+ Enter the address in the format 255.255.255.255:88آدرس را با فرمت 255.255.255.255:88 وارد کنید
-
+ Configure your serverسرور خود را پیکربندی کنید
-
+ 255.255.255.255:22
-
+ SSH Username
-
+ Password or SSH private keyرمز عبور یا کلید خصوصی SSH
-
+ All data you enter will remain strictly confidential and will not be shared or disclosed to the Amnezia or any third partiesتمام دادههایی که شما وارد میکنید به شدت محرمانه است و با Amnezia یا هر شخص ثالث دیگری به اشتراک گذاشته نمیشود
-
+ How to run your VPN serverچگونه سرور VPN خود را اجرا کنید
-
+ Where to get connection data, step-by-step instructions for buying a VPSدادههای اتصال را از کجا دریافت کنید و دستورالعملهای مرحله به مرحله برای خرید یک VPS
-
+ Ip address cannot be emptyآدرس آیپی نمیتواند خالی باشد
-
+ Login cannot be emptyنامکاربری نمیتواند خالی باشد
-
+ Password/private key cannot be emptyپسورد یا کلید خصوصی نمیتواند خالی باشد
@@ -2607,22 +2682,22 @@ It's okay as long as it's from someone you trust.
PageSetupWizardEasy
-
+ What is the level of internet control in your region?سطح کنترل اینترنت در منطقه شما چگونه است؟
-
+ Choose a VPN protocolیک پروتکل VPN را انتخاب کنید
-
+ Skip setupرد شدن از تنظیم
-
+ Continueادامه
@@ -2669,37 +2744,37 @@ It's okay as long as it's from someone you trust.
PageSetupWizardProtocolSettings
-
+ Installing %1در حال نصب %1
-
+ More detailedجزییات بیشتر
-
+ Closeبستن
-
+ Network protocolپروتکل شبکه
-
+ Portپورت
-
+ Installنصب
-
+ The port must be in the range of 1 to 65535پورت باید در محدوده ۱ تا ۶۵۵۳۵ باشد
@@ -2707,12 +2782,12 @@ It's okay as long as it's from someone you trust.
PageSetupWizardProtocols
-
+ VPN protocolپروتکل ویپیان
-
+ Choose the one with the highest priority for you. Later, you can install other protocols and additional services, such as DNS proxy and SFTP.پروتکلی که بیشترین اولویت را برای شما دارد انتخاب کنید. بعدا، میتوانید پروتکلها و سرویسهای اضافه مانند پروکسی DNS و SFTP را هم نصب کنید.
@@ -2748,7 +2823,7 @@ It's okay as long as it's from someone you trust.
من هیچی ندارم
-
+ Let's get startedبیایید شروع کنیم
@@ -2756,27 +2831,27 @@ It's okay as long as it's from someone you trust.
PageSetupWizardTextKey
-
+ Connection keyکلید ارتباط
-
+ A line that starts with vpn://...خطی که با آن شروع می شود vpn://...
-
+ Keyکلید
-
+ Insertوارد کردن
-
+ Continueادامه
@@ -2784,32 +2859,32 @@ It's okay as long as it's from someone you trust.
PageSetupWizardViewConfig
-
+ New connectionارتباط جدید
-
+ Collapse contentجمع کردن محتوا
-
+ Show contentنمایش محتوا
-
+ Enable WireGuard obfuscation. It may be useful if WireGuard is blocked on your provider.فعالسازی استتار WireGuard. این ممکن است مفید باشد اگر WireGuard توسط ارائهدهنده شما مسدود شده باشد.
-
+ Use connection codes only from sources you trust. Codes from public sources may have been created to intercept your data.از کدهای اتصال فقط از منابع مورد اعتماد خود استفاده کنید. ممکن است کدهایی از منابع عمومی برای رهگیری داده های شما ایجاد شده باشند
-
+ Connectاتصال
@@ -2817,211 +2892,216 @@ It's okay as long as it's from someone you trust.
PageShare
-
+ OpenVPN native formatفرمت OpenVPN
-
+ WireGuard native formatفرمت WireGuard
-
+ Connectionارتباط
-
-
+
+ Serverسرور
-
+ Config revokedتنظیمات ابطالشد
-
+ Connection to ارتباط با
-
+ File with connection settings to فایل شامل تنظیمات ارتباط با
-
+ Save OpenVPN configذخیره تنظیمات OpenVPN
-
+ Save WireGuard configذخیره تنظیمات WireGuard
-
+ Save AmneziaWG configتنظیمات AmneziaWG را ذخیره کنید
-
+ Save Shadowsocks configذخیره تنظیمات Shadowsocks
-
+ Save Cloak configذخیره تنظیمات Cloak
-
+ Save XRay configذخیره پیکربندی XRay
-
+ For the AmneziaVPN appبرای نرمافزار AmneziaVPN
-
+ AmneziaWG native formatفرمت بومی AmneziaWG
-
+ Shadowsocks native formatفرمت Shadowsocks
-
+ Cloak native formatفرمت Cloak
-
+ XRay native formatفرمت بومی XRay
-
+ Share VPN Accessاتصال vpn را به اشتراک بگذارید
-
+ Share full access to the server and VPNبه اشتراک گذاشتن دسترسی کامل به سرور و ویپیان
-
+ Use for your own devices, or share with those you trust to manage the server.برای دستگاههای خودتان استفاده کنید یا با آنهایی که برای مدیریت سرور به آنها اعتماد دارید به اشتراک بگذارید.
-
-
+
+ Usersکاربران
-
+ User nameنام کاربری
-
+ Searchجستجو
-
+ Creation date: %1تاریخ ایجاد: %1
-
+ Latest handshake: %1آخرین ارتباط: %1
-
+ Data received: %1دادههای دریافت شده: %1
-
+ Data sent: %1دادههای ارسال شده: %1
+
+
+ Allowed IPs: %1
+
+ Creation date: تاریخ ایجاد:
-
+ Renameتغییر نام
-
+ Client nameنام کلاینت
-
+ Saveذخیره
-
+ Revokeابطال
-
+ Revoke the config for a user - %1?لغو پیکربندی برای یک کاربر - %1?
-
+ The user will no longer be able to connect to your server.کاربر دیگر نمیتواند به سرور وصل شود.
-
+ Continueادامه
-
+ Cancelکنسل
-
+ Share VPN access without the ability to manage the serverبه اشتراک گذاشتن دسترسی ویپیان بدون امکان مدیریت سرور
-
-
+
+ Protocolپروتکل
-
-
+
+ Connection formatفرمت ارتباط
-
-
+
+ Shareاشتراکگذاری
@@ -3029,55 +3109,55 @@ It's okay as long as it's from someone you trust.
PageShareFullAccess
-
+ Full access to the server and VPNدسترسی کامل به سرور و ویپیان
-
+ We recommend that you use full access to the server only for your own additional devices.
ما پیشنهاد میکنیم که ازحالت دسترسی کامل به سرور فقط برای دستگاههای دیگر خودتان استفاده کنید.
-
+ If you share full access with other people, they can remove and add protocols and services to the server, which will cause the VPN to work incorrectly for all users. اگر دسترسی کامل را با دیگران به اشتراک بگذارید، آنها میتوانند پروتکلها و سرویسها را حذف یا اضافه کنند که باعث میشود که ویپیان دیگر برای سایر کاربران کار نکند.
-
-
+
+ Serverسرور
-
+ Accessing در حال دسترسی به
-
+ File with accessing settings to فایل شامل تنظیمات دسترسی به
-
+ Shareاشتراکگذاری
-
+ Access error!خطای دسترسی!
-
+ Connection to ارتباط با
-
+ File with connection settings to فایل شامل تنظیمات ارتباط با
@@ -3085,17 +3165,17 @@ It's okay as long as it's from someone you trust.
PageStart
-
+ Logging was disabled after 14 days, log files were deletedثبت وقایع پس از ۱۴ روز غیرفعال شد و فایلهای ثبت وقایع حذف شدند
-
+ Settings restored from backup fileتنظیمات از فایل پشتیبان بازیابی شد
-
+ Logging is enabled. Note that logs will be automaticallydisabled after 14 days, and all log files will be deleted.
@@ -3103,7 +3183,7 @@ It's okay as long as it's from someone you trust.
PopupType
-
+ Closeبستن
@@ -3479,22 +3559,22 @@ It's okay as long as it's from someone you trust.
تنظیمات شامل هیچ کانتینر یا اعتبارنامهای برای اتصال به سرور نیست
-
+ VPN connection errorخطای اتصال VPN
-
+ Error when retrieving configuration from APIخطا هنگام بازیابی پیکربندی از API
-
+ This config has already been added to the applicationاین پیکربندی قبلاً به برنامه اضافه شده است
-
+ ErrorCode: %1. کد خطا: %1.
@@ -3564,57 +3644,72 @@ It's okay as long as it's from someone you trust.
VPN pool error: no available addresses
-
+
+ Unable to open config file
+
+
+
+ In the response from the server, an empty config was receivedدر پاسخ از سرور، پیکربندی خالی دریافت شد
-
+ SSL error occurredSSL error occurred
-
+ Server response timeout on api requestServer response timeout on api request
-
+ Missing AGW public key
+
+
+ Failed to decrypt response payload
+
+
- QFile error: The file could not be opened
-
-
-
-
- QFile error: An error occurred when reading from the file
-
-
-
-
- QFile error: The file could not be accessed
+ Missing list of available services
- QFile error: An unspecified error occurred
+ QFile error: The file could not be opened
- QFile error: A fatal error occurred
+ QFile error: An error occurred when reading from the file
+ QFile error: The file could not be accessed
+
+
+
+
+ QFile error: An unspecified error occurred
+
+
+
+
+ QFile error: A fatal error occurred
+
+
+
+ QFile error: The operation was aborted
-
+ Internal errorInternal error
@@ -4071,11 +4166,19 @@ For more detailed information, you can
SelectLanguageDrawer
-
+ Choose languageانتخاب زبان
+
+ ServersListView
+
+
+ Unable change server while there is an active connection
+ امکان تغییر سرور در هنگام متصل بودن وجود ندارد
+
+Settings
@@ -4093,7 +4196,7 @@ For more detailed information, you can
SettingsController
-
+ All settings have been reset to default valuesتمام تنظیمات به مقادیر پیش فرض ریست شد
@@ -4102,7 +4205,7 @@ For more detailed information, you can
پروفایل ذخیره شده پاک شد
-
+ Backup file is corruptedفایل بکآپ خراب شده است
@@ -4116,33 +4219,33 @@ For more detailed information, you can
ذخیره تنظیمات AmneziaVPN
-
+ Shareاشتراکگذاری
-
+ Copyکپی
-
-
+
+ Copiedکپی شد
-
+ Copy config stringکپیکردن متن تنظیمات
-
+ Show connection settingsنمایش تنظیمات ارتباط
-
+ To read the QR code in the Amnezia app, select "Add server" → "I have data to connect" → "QR code, key or settings file"برای خواندن QR Code در نرمافزار AmneziaVPN "اضافه کردن سرور" -> "من داده برای اتصال دارم" -> "QR Code، کلید یا فایل تنظیمات"
@@ -4165,27 +4268,27 @@ For more detailed information, you can
سایت حذف شد: %1
-
+ Can't open file: %1فایل باز نشد: %1
-
+ Failed to parse JSON data from file: %1مشکل در تحلیل دادههای JSON در فایل: %1
-
+ The JSON data is not an array in file: %1دادههای JSON در فایل به صورت آرایه نیستند: %1
-
+ Import completedبارگذاری کامل شد
-
+ Export completedخروجی گرفتن کامل شد
@@ -4226,7 +4329,7 @@ For more detailed information, you can
TextFieldWithHeaderType
-
+ The field can't be emptyاین فیلد نمیتواند خالی باشد.
@@ -4234,7 +4337,7 @@ For more detailed information, you can
VpnConnection
-
+ MbpsMbps
@@ -4316,12 +4419,12 @@ For more detailed information, you can
main2
-
+ Private key passphraseعبارت کلید خصوصی
-
+ Saveذخیره
diff --git a/client/translations/amneziavpn_hi_IN.ts b/client/translations/amneziavpn_hi_IN.ts
index ab459b7c..db095d5c 100644
--- a/client/translations/amneziavpn_hi_IN.ts
+++ b/client/translations/amneziavpn_hi_IN.ts
@@ -1,55 +1,63 @@
+
+ AdLabel
+
+
+ Amnezia Premium - for access to any website
+
+
+ApiServicesModel
-
+ Classic VPN for comfortable work, downloading large files and watching videos. Works for any sites. Speed up to %1 MBit/s
-
+ VPN to access blocked sites in regions with high levels of Internet censorship.
-
+ <p><a style="color: #EB5757;">Not available in your region. If you have VPN enabled, disable it, return to the previous screen, and try again.</a>
-
+ Amnezia Premium - A classic VPN for comfortable work, downloading large files, and watching videos in high resolution. It works for all websites, even in countries with the highest level of internet censorship.
-
+ Amnezia Free is a free VPN to bypass blocking in countries with high levels of internet censorship
-
+ %1 MBit/s
-
+ %1 days
-
+ VPN will open only popular sites blocked in your region, such as Instagram, Facebook, Twitter and others. Other sites will be opened from your real IP address, <a href="%1/free" style="color: #FBB26A;">more details on the website.</a>
-
+ Free
-
+ %1 $/month
@@ -80,7 +88,7 @@
ConnectButton
-
+ Unable to disconnect during configuration preparationकॉन्फ़िगरेशन तैयारी के दौरान डिस्कनेक्ट करने में असमर्थ
@@ -89,61 +97,61 @@
ConnectionController
-
-
-
+
+
+ Connectकनेक्ट
-
+ VPN Protocols is not installed.
Please install VPN container at firstपीएन प्रोटोकॉल स्थापित नहीं है.
कृपया पहले वीपीएन कंटेनर स्थापित करें
-
+ Connectedजुड़ा हुआ
-
+ The selected protocol is not supported on the current platformचयनित प्रोटोकॉल वर्तमान प्लेटफ़ॉर्म पर समर्थित नहीं है
-
+ unable to create configurationकॉन्फ़िगरेशन बनाने में असमर्थ
-
+ Connecting...कनेक्ट...
-
+ Reconnecting...पुनः कनेक्ट हो रहा है...
-
+ Disconnecting...डिस्कनेक्ट हो रहा है...
-
+ Preparing...तैयार कर रहे हैं...
-
+ Settings updated successfully, reconnnection...सेटिंग्स सफलतापूर्वक अपडेट हो गईं...
-
+ Settings updated successfullyसेटिंग्स सफलतापूर्वक अपडेट हो गईं
@@ -151,17 +159,17 @@
ConnectionTypeSelectionDrawer
-
+ Add new connectionनया कनेक्शन जोड़ें
-
+ Configure your serverअपना सर्वर कॉन्फ़िगर करें
-
+ Open config file, key or QR codeकॉन्फ़िग फ़ाइल, कुंजी या QR कोड खोलें
@@ -199,7 +207,7 @@
HomeContainersListView
-
+ Unable change protocol while there is an active connectionसक्रिय कनेक्शन होने पर प्रोटोकॉल बदलने में असमर्थ
@@ -207,46 +215,46 @@
HomeSplitTunnelingDrawer
-
+ Split tunnelingविभाजित सुरंग
-
+ Allows you to connect to some sites or applications through a VPN connection and bypass othersआपको वीपीएन कनेक्शन के माध्यम से कुछ साइटों या एप्लिकेशन से जुड़ने और अन्य को बायपास करने की अनुमति देता है
-
+ Split tunneling on the serverसर्वर पर स्प्लिट टनलिंग
-
+ Enabled
Can't be disabled for current serverसक्रिय
वर्तमान सर्वर के लिए अक्षम नहीं किया जा सकता
-
+ Site-based split tunnelingसाइट-आधारित विभाजित टनलिंग
-
-
+
+ Enabledसक्षम किया
-
-
+
+ Disabledअक्षम
-
+ App-based split tunnelingऐप-आधारित स्प्लिट टनलिंग
@@ -254,23 +262,20 @@ Can't be disabled for current server
ImportController
- Unable to open file
- फाइल खोलने में असमर्थ
+ फाइल खोलने में असमर्थ
-
- Invalid configuration file
- अमान्य कॉन्फ़िगरेशन फ़ाइल
+ अमान्य कॉन्फ़िगरेशन फ़ाइल
-
+ Scanned %1 of %2.%2 में से %1 स्कैन किया गया.
-
+ In the imported configuration, potentially dangerous lines were found:
@@ -278,86 +283,86 @@ Can't be disabled for current server
InstallController
-
+ %1 installed successfully. %1 सफलतापूर्वक स्थापित हुआ.
-
+ %1 is already installed on the server. %1 पहले से ही सर्वर पर स्थापित है.
-
+
Added containers that were already installed on the server
सर्वर पर पहले से स्थापित कंटेनर जोड़े गए
-
+
Already installed containers were found on the server. All installed containers have been added to the application
सर्वर पर पहले से स्थापित कंटेनर पाए गए। सभी स्थापित कंटेनरों को एप्लिकेशन में जोड़ दिया गया है
-
+ Settings updated successfullyसेटिंग्स सफलतापूर्वक अपडेट हो गईं
-
+ Server '%1' was rebootedसर्वर '%1' रीबूट किया गया था
-
+ Server '%1' was removedसर्वर '%1' रीबूट किया गया था
-
+ All containers from server '%1' have been removedसर्वर '%1' से सभी कंटेनर हटा दिए गए हैं
-
+ %1 has been removed from the server '%2'%1 को सर्वर '%2' से हटा दिया गया है
-
+ Api config removed
-
+ %1 cached profile cleared%1 कैश्ड प्रोफ़ाइल साफ़ की गई
-
+ Please login as the userकृपया उपयोगकर्ता के रूप में लॉगिन करें
-
+ Server added successfullyसर्वर सफलतापूर्वक जोड़ा गया
-
+ %1 installed successfully.
-
+ API config reloaded
-
+ Successfully changed the country of connection to %1
@@ -370,12 +375,12 @@ Already installed containers were found on the server. All installed containers
एप्लिकेशन चुनें
-
+ application nameआवेदन का नाम
-
+ Add selectedचुने हुए को जोड़ो
@@ -443,12 +448,12 @@ Already installed containers were found on the server. All installed containers
PageDevMenu
-
+ Gateway endpoint
-
+ Dev gateway environment
@@ -456,85 +461,84 @@ Already installed containers were found on the server. All installed containers
PageHome
-