Compare commits

..

1 Commits

Author SHA1 Message Date
pokamest 9c30b0f034 Merge pull request #496 from amnezia-vpn/dev
Release 4.2.0.1
2024-01-15 05:35:29 -05:00
121 changed files with 1424 additions and 3581 deletions
+14 -32
View File
@@ -48,18 +48,14 @@ jobs:
export QIF_BIN_DIR=${{ runner.temp }}/Qt/Tools/QtInstallerFramework/${{ env.QIF_VERSION }}/bin export QIF_BIN_DIR=${{ runner.temp }}/Qt/Tools/QtInstallerFramework/${{ env.QIF_VERSION }}/bin
bash deploy/build_linux.sh bash deploy/build_linux.sh
- name: 'Pack installer'
run: cd deploy && tar -cf AmneziaVPN_Linux_Installer.tar AmneziaVPN_Linux_Installer.bin
- name: 'Upload installer artifact' - name: 'Upload installer artifact'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: AmneziaVPN_Linux_installer.tar name: AmneziaVPN_Linux_installer
path: deploy/AmneziaVPN_Linux_Installer.tar path: deploy/AmneziaVPN_Linux_Installer
retention-days: 7 retention-days: 7
- name: 'Upload unpacked artifact' - name: 'Upload unpacked artifact'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: AmneziaVPN_Linux_unpacked name: AmneziaVPN_Linux_unpacked
path: deploy/AppDir path: deploy/AppDir
@@ -114,14 +110,13 @@ jobs:
call deploy\\build_windows.bat call deploy\\build_windows.bat
- name: 'Upload installer artifact' - name: 'Upload installer artifact'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: AmneziaVPN_Windows_installer name: AmneziaVPN_Windows_installer
path: AmneziaVPN_x${{ env.BUILD_ARCH }}.exe path: AmneziaVPN_x${{ env.BUILD_ARCH }}.exe
retention-days: 7 retention-days: 7
- name: 'Upload unpacked artifact' - name: 'Upload unpacked artifact'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: AmneziaVPN_Windows_unpacked name: AmneziaVPN_Windows_unpacked
path: deploy\\build_${{ env.BUILD_ARCH }}\\client\\Release path: deploy\\build_${{ env.BUILD_ARCH }}\\client\\Release
@@ -205,7 +200,7 @@ jobs:
IOS_NE_PROVISIONING_PROFILE: ${{ secrets.IOS_NE_PROVISIONING_PROFILE }} IOS_NE_PROVISIONING_PROFILE: ${{ secrets.IOS_NE_PROVISIONING_PROFILE }}
# - name: 'Upload appstore .ipa and dSYMs to artifacts' # - name: 'Upload appstore .ipa and dSYMs to artifacts'
# uses: actions/upload-artifact@v4 # uses: actions/upload-artifact@v3
# with: # with:
# name: app-store ipa & dsyms # name: app-store ipa & dsyms
# path: | # path: |
@@ -260,14 +255,13 @@ jobs:
bash deploy/build_macos.sh bash deploy/build_macos.sh
- name: 'Upload installer artifact' - name: 'Upload installer artifact'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: AmneziaVPN_MacOS_installer name: AmneziaVPN_MacOS_installer
path: AmneziaVPN.dmg path: AmneziaVPN.dmg
retention-days: 7 retention-days: 7
- name: 'Upload unpacked artifact' - name: 'Upload unpacked artifact'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: AmneziaVPN_MacOS_unpacked name: AmneziaVPN_MacOS_unpacked
path: deploy/build/client/AmneziaVPN.app path: deploy/build/client/AmneziaVPN.app
@@ -381,44 +375,32 @@ jobs:
ANDROID_KEYSTORE_KEY_ALIAS: ${{ secrets.ANDROID_RELEASE_KEYSTORE_KEY_ALIAS }} ANDROID_KEYSTORE_KEY_ALIAS: ${{ secrets.ANDROID_RELEASE_KEYSTORE_KEY_ALIAS }}
ANDROID_KEYSTORE_KEY_PASS: ${{ secrets.ANDROID_RELEASE_KEYSTORE_KEY_PASS }} ANDROID_KEYSTORE_KEY_PASS: ${{ secrets.ANDROID_RELEASE_KEYSTORE_KEY_PASS }}
shell: bash shell: bash
run: ./deploy/build_android.sh --aab --apk all --build-platform ${{ env.ANDROID_BUILD_PLATFORM }} run: ./deploy/build_android.sh --apk all --build-platform ${{ env.ANDROID_BUILD_PLATFORM }}
- name: 'Upload x86_64 apk' - name: 'Upload x86_64 apk'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: AmneziaVPN-android-x86_64 name: AmneziaVPN-android-x86_64
path: deploy/build/AmneziaVPN-x86_64-release.apk path: deploy/build/AmneziaVPN-x86_64-release.apk
compression-level: 0
retention-days: 7 retention-days: 7
- name: 'Upload x86 apk' - name: 'Upload x86 apk'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: AmneziaVPN-android-x86 name: AmneziaVPN-android-x86
path: deploy/build/AmneziaVPN-x86-release.apk path: deploy/build/AmneziaVPN-x86-release.apk
compression-level: 0
retention-days: 7 retention-days: 7
- name: 'Upload arm64-v8a apk' - name: 'Upload arm64-v8a apk'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: AmneziaVPN-android-arm64-v8a name: AmneziaVPN-android-arm64-v8a
path: deploy/build/AmneziaVPN-arm64-v8a-release.apk path: deploy/build/AmneziaVPN-arm64-v8a-release.apk
compression-level: 0
retention-days: 7 retention-days: 7
- name: 'Upload armeabi-v7a apk' - name: 'Upload armeabi-v7a apk'
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: AmneziaVPN-android-armeabi-v7a name: AmneziaVPN-android-armeabi-v7a
path: deploy/build/AmneziaVPN-armeabi-v7a-release.apk path: deploy/build/AmneziaVPN-armeabi-v7a-release.apk
compression-level: 0
retention-days: 7
- name: 'Upload aab'
uses: actions/upload-artifact@v4
with:
name: AmneziaVPN-android
path: deploy/build/AmneziaVPN-release.aab
compression-level: 0
retention-days: 7 retention-days: 7
+3 -3
View File
@@ -22,6 +22,6 @@
[submodule "client/3rd-prebuilt"] [submodule "client/3rd-prebuilt"]
path = client/3rd-prebuilt path = client/3rd-prebuilt
url = https://github.com/amnezia-vpn/3rd-prebuilt url = https://github.com/amnezia-vpn/3rd-prebuilt
[submodule "client/3rd/amneziawg-apple"] [submodule "client/3rd/awg-apple"]
path = client/3rd/amneziawg-apple path = client/3rd/awg-apple
url = https://github.com/amnezia-vpn/amneziawg-apple url = https://github.com/amnezia-vpn/awg-apple
+2 -2
View File
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN) set(PROJECT AmneziaVPN)
project(${PROJECT} VERSION 4.3.0.0 project(${PROJECT} VERSION 4.2.0.1
DESCRIPTION "AmneziaVPN" DESCRIPTION "AmneziaVPN"
HOMEPAGE_URL "https://amnezia.org/" HOMEPAGE_URL "https://amnezia.org/"
) )
@@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
set(RELEASE_DATE "${CURRENT_DATE}") set(RELEASE_DATE "${CURRENT_DATE}")
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
set(APP_ANDROID_VERSION_CODE 44) set(APP_ANDROID_VERSION_CODE 39)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux") set(MZ_PLATFORM_NAME "linux")
+4 -12
View File
@@ -18,6 +18,7 @@ Amnezia is an open-source VPN client, with a key feature that enables you to dep
[https://www.reddit.com/r/AmneziaVPN](https://www.reddit.com/r/AmneziaVPN) - Reddit [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_en](https://t.me/amnezia_vpn_en) - Telegram support channel (English)
[https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - Telegram support channel (Russian) [https://t.me/amnezia_vpn](https://t.me/amnezia_vpn) - Telegram support channel (Russian)
[https://signal.group/...](https://signal.group/#CjQKIB2gUf8QH_IXnOJMGQWMDjYz9cNfmRQipGWLFiIgc4MwEhAKBONrSiWHvoUFbbD0xwdh) - Signal channel
## Tech ## Tech
@@ -35,7 +36,7 @@ AmneziaVPN uses a number of open source projects to work:
Make sure to pull all submodules after checking out the repo. Make sure to pull all submodules after checking out the repo.
```bash ```bash
git submodule update --init --recursive git submodule update --init
``` ```
## Development ## Development
@@ -49,15 +50,7 @@ Look deploy folder for build scripts.
1. First, make sure you have [XCode](https://developer.apple.com/xcode/) installed, at least version 14 or higher. 1. First, make sure you have [XCode](https://developer.apple.com/xcode/) installed, at least version 14 or higher.
2. We use QT to generate the XCode project. we need QT version 6.6.1. Install QT for macos in [here](https://doc.qt.io/qt-6/macos.html) or [QT Online Installer](https://www.qt.io/download-open-source). Required modules: 2. We use QT to generate the XCode project. we need QT version 6.4. Install QT for macos in [here](https://doc.qt.io/qt-6/macos.html)
- macOS
- iOS
- Qt 5 Compatibility Module
- Qt Shader Tools
- Additional Libraries:
- Qt Image Formats
- Qt Multimedia
- Qt Remote Objects
3. Install cmake is require. We recommend cmake version 3.25. You can install cmake in [here](https://cmake.org/download/) 3. Install cmake is require. We recommend cmake version 3.25. You can install cmake in [here](https://cmake.org/download/)
@@ -73,11 +66,10 @@ gomobile init
5. Build project 5. Build project
```bash ```bash
export QT_BIN_DIR="<PATH-TO-QT-FOLDER>/Qt/<QT-VERSION>/ios/bin" export QT_BIN_DIR="<PATH-TO-QT-FOLDER>/Qt/<QT-VERSION>/ios/bin"
export QT_MACOS_ROOT_DIR="<PATH-TO-QT-FOLDER>/Qt/<QT-VERSION>/macos"
export QT_IOS_BIN=$QT_BIN_DIR export QT_IOS_BIN=$QT_BIN_DIR
export PATH=$PATH:~/go/bin export PATH=$PATH:~/go/bin
mkdir build-ios mkdir build-ios
$QT_IOS_BIN/qt-cmake . -B build-ios -GXcode -DQT_HOST_PATH=$QT_MACOS_ROOT_DIR $QT_IOS_BIN/qt-cmake . -B build-ios -GXcode -DQT_HOST_PATH=$QT_BIN_DIR
``` ```
Replace PATH-TO-QT-FOLDER and QT-VERSION to your environment Replace PATH-TO-QT-FOLDER and QT-VERSION to your environment
Vendored Submodule
+1
Submodule client/3rd/awg-apple added at 233eda6760
+380 -389
View File
@@ -1,389 +1,380 @@
#include "amnezia_application.h" #include "amnezia_application.h"
#include <QClipboard> #include <QClipboard>
#include <QFontDatabase> #include <QFontDatabase>
#include <QMimeData> #include <QMimeData>
#include <QQuickStyle> #include <QQuickStyle>
#include <QResource> #include <QResource>
#include <QStandardPaths> #include <QStandardPaths>
#include <QTextDocument> #include <QTextDocument>
#include <QTimer> #include <QTimer>
#include <QTranslator> #include <QTranslator>
#include <QQuickItem> #include <QQuickItem>
#include "logger.h" #include "logger.h"
#include "version.h" #include "version.h"
#include "platforms/ios/QRCodeReaderBase.h" #include "platforms/ios/QRCodeReaderBase.h"
#if defined(Q_OS_ANDROID) #if defined(Q_OS_ANDROID)
#include "platforms/android/android_controller.h" #include "platforms/android/android_controller.h"
#endif #endif
#include "protocols/qml_register_protocols.h" #include "protocols/qml_register_protocols.h"
#if defined(Q_OS_IOS) #if defined(Q_OS_IOS)
#include "platforms/ios/ios_controller.h" #include "platforms/ios/ios_controller.h"
#endif #endif
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv) AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv)
#else #else
AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecondary, SingleApplication::Options options, AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecondary, SingleApplication::Options options,
int timeout, const QString &userData) int timeout, const QString &userData)
: SingleApplication(argc, argv, allowSecondary, options, timeout, userData) : SingleApplication(argc, argv, allowSecondary, options, timeout, userData)
#endif #endif
{ {
setQuitOnLastWindowClosed(false); setQuitOnLastWindowClosed(false);
// Fix config file permissions // Fix config file permissions
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) #if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
{ {
QSettings s(ORGANIZATION_NAME, APPLICATION_NAME); QSettings s(ORGANIZATION_NAME, APPLICATION_NAME);
s.setValue("permFixed", true); s.setValue("permFixed", true);
} }
QString configLoc1 = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/" QString configLoc1 = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/"
+ ORGANIZATION_NAME + "/" + APPLICATION_NAME + ".conf"; + ORGANIZATION_NAME + "/" + APPLICATION_NAME + ".conf";
QFile::setPermissions(configLoc1, QFileDevice::ReadOwner | QFileDevice::WriteOwner); QFile::setPermissions(configLoc1, QFileDevice::ReadOwner | QFileDevice::WriteOwner);
QString configLoc2 = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/" QString configLoc2 = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/"
+ ORGANIZATION_NAME + "/" + APPLICATION_NAME + "/" + APPLICATION_NAME + ".conf"; + ORGANIZATION_NAME + "/" + APPLICATION_NAME + "/" + APPLICATION_NAME + ".conf";
QFile::setPermissions(configLoc2, QFileDevice::ReadOwner | QFileDevice::WriteOwner); QFile::setPermissions(configLoc2, QFileDevice::ReadOwner | QFileDevice::WriteOwner);
#endif #endif
m_settings = std::shared_ptr<Settings>(new Settings); m_settings = std::shared_ptr<Settings>(new Settings);
} }
AmneziaApplication::~AmneziaApplication() AmneziaApplication::~AmneziaApplication()
{ {
m_vpnConnectionThread.quit(); m_vpnConnectionThread.quit();
m_vpnConnectionThread.wait(3000); m_vpnConnectionThread.wait(3000);
if (m_engine) { if (m_engine) {
QObject::disconnect(m_engine, 0, 0, 0); QObject::disconnect(m_engine, 0, 0, 0);
delete m_engine; delete m_engine;
} }
} }
void AmneziaApplication::init() void AmneziaApplication::init()
{ {
m_engine = new QQmlApplicationEngine; m_engine = new QQmlApplicationEngine;
const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml")); const QUrl url(QStringLiteral("qrc:/ui/qml/main2.qml"));
QObject::connect( QObject::connect(
m_engine, &QQmlApplicationEngine::objectCreated, this, m_engine, &QQmlApplicationEngine::objectCreated, this,
[url](QObject *obj, const QUrl &objUrl) { [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl) if (!obj && url == objUrl)
QCoreApplication::exit(-1); QCoreApplication::exit(-1);
}, },
Qt::QueuedConnection); Qt::QueuedConnection);
m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance()); m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance());
m_configurator = std::shared_ptr<VpnConfigurator>(new VpnConfigurator(m_settings, this)); m_configurator = std::shared_ptr<VpnConfigurator>(new VpnConfigurator(m_settings, this));
m_vpnConnection.reset(new VpnConnection(m_settings, m_configurator)); m_vpnConnection.reset(new VpnConnection(m_settings, m_configurator));
m_vpnConnection->moveToThread(&m_vpnConnectionThread); m_vpnConnection->moveToThread(&m_vpnConnectionThread);
m_vpnConnectionThread.start(); m_vpnConnectionThread.start();
initModels(); initModels();
loadTranslator(); loadTranslator();
initControllers(); initControllers();
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
if(!AndroidController::initLogging()) { connect(AndroidController::instance(), &AndroidController::initConnectionState, this,
qFatal("Android logging initialization failed"); [this](Vpn::ConnectionState state) {
} m_connectionController->onConnectionStateChanged(state);
AndroidController::instance()->setSaveLogs(m_settings->isSaveLogs()); if (m_vpnConnection)
connect(m_settings.get(), &Settings::saveLogsChanged, m_vpnConnection->restoreConnection();
AndroidController::instance(), &AndroidController::setSaveLogs); });
if (!AndroidController::instance()->initialize()) {
connect(AndroidController::instance(), &AndroidController::initConnectionState, this, qCritical() << QString("Init failed");
[this](Vpn::ConnectionState state) { if (m_vpnConnection)
m_connectionController->onConnectionStateChanged(state); emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Error);
if (m_vpnConnection) return;
m_vpnConnection->restoreConnection(); }
});
if (!AndroidController::instance()->initialize()) { connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, [this](QString data) {
qFatal("Android controller initialization failed"); m_pageController->replaceStartPage();
} m_importController->extractConfigFromData(data);
m_pageController->goToPageViewConfig();
connect(AndroidController::instance(), &AndroidController::importConfigFromOutside, [this](QString data) { });
m_pageController->replaceStartPage(); #endif
m_importController->extractConfigFromData(data);
m_pageController->goToPageViewConfig(); #ifdef Q_OS_IOS
}); IosController::Instance()->initialize();
#endif connect(IosController::Instance(), &IosController::importConfigFromOutside, [this](QString data) {
m_pageController->replaceStartPage();
#ifdef Q_OS_IOS m_importController->extractConfigFromData(data);
IosController::Instance()->initialize(); m_pageController->goToPageViewConfig();
connect(IosController::Instance(), &IosController::importConfigFromOutside, [this](QString data) { });
m_pageController->replaceStartPage();
m_importController->extractConfigFromData(data); connect(IosController::Instance(), &IosController::importBackupFromOutside, [this](QString filePath) {
m_pageController->goToPageViewConfig(); m_pageController->replaceStartPage();
}); m_pageController->goToPageSettingsBackup();
m_settingsController->importBackupFromOutside(filePath);
connect(IosController::Instance(), &IosController::importBackupFromOutside, [this](QString filePath) { });
m_pageController->replaceStartPage(); #endif
m_pageController->goToPageSettingsBackup();
m_settingsController->importBackupFromOutside(filePath); m_notificationHandler.reset(NotificationHandler::create(nullptr));
});
#endif connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(),
&NotificationHandler::setConnectionState);
m_notificationHandler.reset(NotificationHandler::create(nullptr));
connect(m_notificationHandler.get(), &NotificationHandler::raiseRequested, m_pageController.get(),
connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(), &PageController::raiseMainWindow);
&NotificationHandler::setConnectionState); connect(m_notificationHandler.get(), &NotificationHandler::connectRequested, m_connectionController.get(),
&ConnectionController::openConnection);
connect(m_notificationHandler.get(), &NotificationHandler::raiseRequested, m_pageController.get(), connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(),
&PageController::raiseMainWindow); &ConnectionController::closeConnection);
connect(m_notificationHandler.get(), &NotificationHandler::connectRequested, m_connectionController.get(), connect(this, &AmneziaApplication::translationsUpdated, m_notificationHandler.get(),
&ConnectionController::openConnection); &NotificationHandler::onTranslationsUpdated);
connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(),
&ConnectionController::closeConnection); m_engine->load(url);
connect(this, &AmneziaApplication::translationsUpdated, m_notificationHandler.get(), m_systemController->setQmlRoot(m_engine->rootObjects().value(0));
&NotificationHandler::onTranslationsUpdated);
if (m_settings->isSaveLogs()) {
m_engine->load(url); if (!Logger::init()) {
m_systemController->setQmlRoot(m_engine->rootObjects().value(0)); qWarning() << "Initialization of debug subsystem failed";
}
#ifndef Q_OS_ANDROID }
if (m_settings->isSaveLogs()) {
if (!Logger::init()) { #ifdef Q_OS_WIN
qWarning() << "Initialization of debug subsystem failed"; if (m_parser.isSet("a"))
} m_pageController->showOnStartup();
} else
#endif emit m_pageController->raiseMainWindow();
#else
#ifdef Q_OS_WIN m_pageController->showOnStartup();
if (m_parser.isSet("a")) #endif
m_pageController->showOnStartup();
else // TODO - fix
emit m_pageController->raiseMainWindow(); #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
#else if (isPrimary()) {
m_pageController->showOnStartup(); QObject::connect(this, &SingleApplication::instanceStarted, m_pageController.get(), [this]() {
#endif qDebug() << "Secondary instance started, showing this window instead";
emit m_pageController->raiseMainWindow();
// TODO - fix });
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) }
if (isPrimary()) { #endif
QObject::connect(this, &SingleApplication::instanceStarted, m_pageController.get(), [this]() {
qDebug() << "Secondary instance started, showing this window instead"; // Android TextField clipboard workaround
emit m_pageController->raiseMainWindow(); // https://bugreports.qt.io/browse/QTBUG-113461
}); #ifdef Q_OS_ANDROID
} QObject::connect(qApp, &QGuiApplication::applicationStateChanged, [](Qt::ApplicationState state) {
#endif if (state == Qt::ApplicationActive) {
if (qApp->clipboard()->mimeData()->formats().contains("text/html")) {
// Android TextArea clipboard workaround QTextDocument doc;
// Text from TextArea always has "text/html" mime-type: doc.setHtml(qApp->clipboard()->mimeData()->html());
// /qt/6.6.1/Src/qtdeclarative/src/quick/items/qquicktextcontrol.cpp:1865 qApp->clipboard()->setText(doc.toPlainText());
// Next, html is created for this mime-type: }
// /qt/6.6.1/Src/qtdeclarative/src/quick/items/qquicktextcontrol.cpp:1885 }
// And this html goes to the Androids clipboard, i.e. text from TextArea is always copied as richText: });
// /qt/6.6.1/Src/qtbase/src/plugins/platforms/android/androidjniclipboard.cpp:46 #endif
// So we catch all the copies to the clipboard and clear them from "text/html" }
#ifdef Q_OS_ANDROID
connect(QGuiApplication::clipboard(), &QClipboard::dataChanged, []() { void AmneziaApplication::registerTypes()
auto clipboard = QGuiApplication::clipboard(); {
if (clipboard->mimeData()->hasHtml()) { qRegisterMetaType<ServerCredentials>("ServerCredentials");
clipboard->setText(clipboard->text());
} qRegisterMetaType<DockerContainer>("DockerContainer");
}); qRegisterMetaType<TransportProto>("TransportProto");
#endif qRegisterMetaType<Proto>("Proto");
} qRegisterMetaType<ServiceType>("ServiceType");
void AmneziaApplication::registerTypes() declareQmlProtocolEnum();
{ declareQmlContainerEnum();
qRegisterMetaType<ServerCredentials>("ServerCredentials");
qmlRegisterType<QRCodeReader>("QRCodeReader", 1, 0, "QRCodeReader");
qRegisterMetaType<DockerContainer>("DockerContainer");
qRegisterMetaType<TransportProto>("TransportProto"); m_containerProps.reset(new ContainerProps());
qRegisterMetaType<Proto>("Proto"); qmlRegisterSingletonInstance("ContainerProps", 1, 0, "ContainerProps", m_containerProps.get());
qRegisterMetaType<ServiceType>("ServiceType");
m_protocolProps.reset(new ProtocolProps());
declareQmlProtocolEnum(); qmlRegisterSingletonInstance("ProtocolProps", 1, 0, "ProtocolProps", m_protocolProps.get());
declareQmlContainerEnum();
qmlRegisterSingletonType(QUrl("qrc:/ui/qml/Filters/ContainersModelFilters.qml"), "ContainersModelFilters", 1, 0,
qmlRegisterType<QRCodeReader>("QRCodeReader", 1, 0, "QRCodeReader"); "ContainersModelFilters");
m_containerProps.reset(new ContainerProps()); //
qmlRegisterSingletonInstance("ContainerProps", 1, 0, "ContainerProps", m_containerProps.get()); Vpn::declareQmlVpnConnectionStateEnum();
PageLoader::declareQmlPageEnum();
m_protocolProps.reset(new ProtocolProps()); }
qmlRegisterSingletonInstance("ProtocolProps", 1, 0, "ProtocolProps", m_protocolProps.get());
void AmneziaApplication::loadFonts()
qmlRegisterSingletonType(QUrl("qrc:/ui/qml/Filters/ContainersModelFilters.qml"), "ContainersModelFilters", 1, 0, {
"ContainersModelFilters"); QQuickStyle::setStyle("Basic");
// QFontDatabase::addApplicationFont(":/fonts/pt-root-ui_vf.ttf");
Vpn::declareQmlVpnConnectionStateEnum(); }
PageLoader::declareQmlPageEnum();
} void AmneziaApplication::loadTranslator()
{
void AmneziaApplication::loadFonts() auto locale = m_settings->getAppLanguage();
{ m_translator.reset(new QTranslator());
QQuickStyle::setStyle("Basic"); updateTranslator(locale);
}
QFontDatabase::addApplicationFont(":/fonts/pt-root-ui_vf.ttf");
} void AmneziaApplication::updateTranslator(const QLocale &locale)
{
void AmneziaApplication::loadTranslator() if (!m_translator->isEmpty()) {
{ QCoreApplication::removeTranslator(m_translator.get());
auto locale = m_settings->getAppLanguage(); }
m_translator.reset(new QTranslator());
updateTranslator(locale); QString strFileName = QString(":/translations/amneziavpn") + QLatin1String("_") + locale.name() + ".qm";
} if (m_translator->load(strFileName)) {
if (QCoreApplication::installTranslator(m_translator.get())) {
void AmneziaApplication::updateTranslator(const QLocale &locale) m_settings->setAppLanguage(locale);
{ }
if (!m_translator->isEmpty()) { } else {
QCoreApplication::removeTranslator(m_translator.get()); m_settings->setAppLanguage(QLocale::English);
} }
QString strFileName = QString(":/translations/amneziavpn") + QLatin1String("_") + locale.name() + ".qm"; m_engine->retranslate();
if (m_translator->load(strFileName)) {
if (QCoreApplication::installTranslator(m_translator.get())) { emit translationsUpdated();
m_settings->setAppLanguage(locale); }
}
} else { bool AmneziaApplication::parseCommands()
m_settings->setAppLanguage(QLocale::English); {
} m_parser.setApplicationDescription(APPLICATION_NAME);
m_parser.addHelpOption();
m_engine->retranslate(); m_parser.addVersionOption();
emit translationsUpdated(); QCommandLineOption c_autostart { { "a", "autostart" }, "System autostart" };
} m_parser.addOption(c_autostart);
bool AmneziaApplication::parseCommands() QCommandLineOption c_cleanup { { "c", "cleanup" }, "Cleanup logs" };
{ m_parser.addOption(c_cleanup);
m_parser.setApplicationDescription(APPLICATION_NAME);
m_parser.addHelpOption(); m_parser.process(*this);
m_parser.addVersionOption();
if (m_parser.isSet(c_cleanup)) {
QCommandLineOption c_autostart { { "a", "autostart" }, "System autostart" }; Logger::cleanUp();
m_parser.addOption(c_autostart); QTimer::singleShot(100, this, [this] { quit(); });
exec();
QCommandLineOption c_cleanup { { "c", "cleanup" }, "Cleanup logs" }; return false;
m_parser.addOption(c_cleanup); }
return true;
m_parser.process(*this); }
if (m_parser.isSet(c_cleanup)) { QQmlApplicationEngine *AmneziaApplication::qmlEngine() const
Logger::cleanUp(); {
QTimer::singleShot(100, this, [this] { quit(); }); return m_engine;
exec(); }
return false;
} void AmneziaApplication::initModels()
return true; {
} m_containersModel.reset(new ContainersModel(this));
m_engine->rootContext()->setContextProperty("ContainersModel", m_containersModel.get());
QQmlApplicationEngine *AmneziaApplication::qmlEngine() const
{ m_serversModel.reset(new ServersModel(m_settings, this));
return m_engine; m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get());
} connect(m_serversModel.get(), &ServersModel::containersUpdated, m_containersModel.get(),
&ContainersModel::updateModel);
void AmneziaApplication::initModels() connect(m_serversModel.get(), &ServersModel::defaultContainerChanged, m_containersModel.get(),
{ &ContainersModel::setDefaultContainer);
m_containersModel.reset(new ContainersModel(this)); m_containersModel->setDefaultContainer(m_serversModel->getDefaultContainer()); // make better?
m_engine->rootContext()->setContextProperty("ContainersModel", m_containersModel.get());
m_languageModel.reset(new LanguageModel(m_settings, this));
m_serversModel.reset(new ServersModel(m_settings, this)); m_engine->rootContext()->setContextProperty("LanguageModel", m_languageModel.get());
m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get()); connect(m_languageModel.get(), &LanguageModel::updateTranslations, this, &AmneziaApplication::updateTranslator);
connect(m_serversModel.get(), &ServersModel::containersUpdated, m_containersModel.get(), connect(this, &AmneziaApplication::translationsUpdated, m_languageModel.get(), &LanguageModel::translationsUpdated);
&ContainersModel::updateModel);
connect(m_serversModel.get(), &ServersModel::defaultContainerChanged, m_containersModel.get(), m_sitesModel.reset(new SitesModel(m_settings, this));
&ContainersModel::setDefaultContainer); m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get());
m_containersModel->setDefaultContainer(m_serversModel->getDefaultContainer()); // make better?
m_protocolsModel.reset(new ProtocolsModel(m_settings, this));
m_languageModel.reset(new LanguageModel(m_settings, this)); m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get());
m_engine->rootContext()->setContextProperty("LanguageModel", m_languageModel.get());
connect(m_languageModel.get(), &LanguageModel::updateTranslations, this, &AmneziaApplication::updateTranslator); m_openVpnConfigModel.reset(new OpenVpnConfigModel(this));
connect(this, &AmneziaApplication::translationsUpdated, m_languageModel.get(), &LanguageModel::translationsUpdated); m_engine->rootContext()->setContextProperty("OpenVpnConfigModel", m_openVpnConfigModel.get());
m_sitesModel.reset(new SitesModel(m_settings, this)); m_shadowSocksConfigModel.reset(new ShadowSocksConfigModel(this));
m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get()); m_engine->rootContext()->setContextProperty("ShadowSocksConfigModel", m_shadowSocksConfigModel.get());
m_protocolsModel.reset(new ProtocolsModel(m_settings, this)); m_cloakConfigModel.reset(new CloakConfigModel(this));
m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get()); m_engine->rootContext()->setContextProperty("CloakConfigModel", m_cloakConfigModel.get());
m_openVpnConfigModel.reset(new OpenVpnConfigModel(this)); m_wireGuardConfigModel.reset(new WireGuardConfigModel(this));
m_engine->rootContext()->setContextProperty("OpenVpnConfigModel", m_openVpnConfigModel.get()); m_engine->rootContext()->setContextProperty("WireGuardConfigModel", m_wireGuardConfigModel.get());
m_shadowSocksConfigModel.reset(new ShadowSocksConfigModel(this)); m_awgConfigModel.reset(new AwgConfigModel(this));
m_engine->rootContext()->setContextProperty("ShadowSocksConfigModel", m_shadowSocksConfigModel.get()); m_engine->rootContext()->setContextProperty("AwgConfigModel", m_awgConfigModel.get());
m_cloakConfigModel.reset(new CloakConfigModel(this)); #ifdef Q_OS_WINDOWS
m_engine->rootContext()->setContextProperty("CloakConfigModel", m_cloakConfigModel.get()); m_ikev2ConfigModel.reset(new Ikev2ConfigModel(this));
m_engine->rootContext()->setContextProperty("Ikev2ConfigModel", m_ikev2ConfigModel.get());
m_wireGuardConfigModel.reset(new WireGuardConfigModel(this)); #endif
m_engine->rootContext()->setContextProperty("WireGuardConfigModel", m_wireGuardConfigModel.get());
m_sftpConfigModel.reset(new SftpConfigModel(this));
m_awgConfigModel.reset(new AwgConfigModel(this)); m_engine->rootContext()->setContextProperty("SftpConfigModel", m_sftpConfigModel.get());
m_engine->rootContext()->setContextProperty("AwgConfigModel", m_awgConfigModel.get());
m_clientManagementModel.reset(new ClientManagementModel(m_settings, this));
#ifdef Q_OS_WINDOWS m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get());
m_ikev2ConfigModel.reset(new Ikev2ConfigModel(this)); connect(m_clientManagementModel.get(), &ClientManagementModel::adminConfigRevoked,
m_engine->rootContext()->setContextProperty("Ikev2ConfigModel", m_ikev2ConfigModel.get()); m_serversModel.get(), &ServersModel::clearCachedProfile);
#endif
connect(m_configurator.get(), &VpnConfigurator::newVpnConfigCreated, this,
m_sftpConfigModel.reset(new SftpConfigModel(this)); [this](const QString &clientId, const QString &clientName, const DockerContainer container,
m_engine->rootContext()->setContextProperty("SftpConfigModel", m_sftpConfigModel.get()); ServerCredentials credentials) {
m_serversModel->reloadContainerConfig();
m_clientManagementModel.reset(new ClientManagementModel(m_settings, this)); m_clientManagementModel->appendClient(clientId, clientName, container, credentials);
m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get()); emit m_configurator->clientModelUpdated();
connect(m_clientManagementModel.get(), &ClientManagementModel::adminConfigRevoked, });
m_serversModel.get(), &ServersModel::clearCachedProfile); }
connect(m_configurator.get(), &VpnConfigurator::newVpnConfigCreated, this, void AmneziaApplication::initControllers()
[this](const QString &clientId, const QString &clientName, const DockerContainer container, {
ServerCredentials credentials) { m_connectionController.reset(new ConnectionController(m_serversModel, m_containersModel, m_vpnConnection));
m_serversModel->reloadContainerConfig(); m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get());
m_clientManagementModel->appendClient(clientId, clientName, container, credentials);
emit m_configurator->clientModelUpdated(); connect(this, &AmneziaApplication::translationsUpdated, m_connectionController.get(),
}); &ConnectionController::onTranslationsUpdated);
}
m_pageController.reset(new PageController(m_serversModel, m_settings));
void AmneziaApplication::initControllers() m_engine->rootContext()->setContextProperty("PageController", m_pageController.get());
{
m_connectionController.reset(new ConnectionController(m_serversModel, m_containersModel, m_vpnConnection)); m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_settings));
m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get()); m_engine->rootContext()->setContextProperty("InstallController", m_installController.get());
connect(m_installController.get(), &InstallController::passphraseRequestStarted, m_pageController.get(),
connect(this, &AmneziaApplication::translationsUpdated, m_connectionController.get(), &PageController::showPassphraseRequestDrawer);
&ConnectionController::onTranslationsUpdated); connect(m_pageController.get(), &PageController::passphraseRequestDrawerClosed, m_installController.get(),
&InstallController::setEncryptedPassphrase);
m_pageController.reset(new PageController(m_serversModel, m_settings)); connect(m_installController.get(), &InstallController::currentContainerUpdated, m_connectionController.get(),
m_engine->rootContext()->setContextProperty("PageController", m_pageController.get()); &ConnectionController::onCurrentContainerUpdated);
m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_settings)); m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings));
m_engine->rootContext()->setContextProperty("InstallController", m_installController.get()); m_engine->rootContext()->setContextProperty("ImportController", m_importController.get());
connect(m_installController.get(), &InstallController::passphraseRequestStarted, m_pageController.get(),
&PageController::showPassphraseRequestDrawer); m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_clientManagementModel,
connect(m_pageController.get(), &PageController::passphraseRequestDrawerClosed, m_installController.get(), m_settings, m_configurator));
&InstallController::setEncryptedPassphrase); m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get());
connect(m_installController.get(), &InstallController::currentContainerUpdated, m_connectionController.get(),
&ConnectionController::onCurrentContainerUpdated); m_settingsController.reset(new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, m_settings));
m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get());
m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings)); if (m_settingsController->isAutoConnectEnabled() && m_serversModel->getDefaultServerIndex() >= 0) {
m_engine->rootContext()->setContextProperty("ImportController", m_importController.get()); QTimer::singleShot(1000, this, [this]() { m_connectionController->openConnection(); });
}
m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_clientManagementModel, connect(m_settingsController.get(), &SettingsController::amneziaDnsToggled , m_serversModel.get(),
m_settings, m_configurator)); &ServersModel::toggleAmneziaDns);
m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get());
m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel));
m_settingsController.reset(new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, m_settings)); m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get());
m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get());
if (m_settingsController->isAutoConnectEnabled() && m_serversModel->getDefaultServerIndex() >= 0) { m_systemController.reset(new SystemController(m_settings));
QTimer::singleShot(1000, this, [this]() { m_connectionController->openConnection(); }); m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get());
}
connect(m_settingsController.get(), &SettingsController::amneziaDnsToggled , m_serversModel.get(), m_cloudController.reset(new ApiController(m_serversModel, m_containersModel));
&ServersModel::toggleAmneziaDns); m_engine->rootContext()->setContextProperty("ApiController", m_cloudController.get());
}
m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel));
m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get());
m_systemController.reset(new SystemController(m_settings));
m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get());
m_cloudController.reset(new ApiController(m_serversModel, m_containersModel));
m_engine->rootContext()->setContextProperty("ApiController", m_cloudController.get());
}
+127 -127
View File
@@ -1,127 +1,127 @@
#ifndef AMNEZIA_APPLICATION_H #ifndef AMNEZIA_APPLICATION_H
#define AMNEZIA_APPLICATION_H #define AMNEZIA_APPLICATION_H
#include <QCommandLineParser> #include <QCommandLineParser>
#include <QQmlApplicationEngine> #include <QQmlApplicationEngine>
#include <QQmlContext> #include <QQmlContext>
#include <QThread> #include <QThread>
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#include <QGuiApplication> #include <QGuiApplication>
#else #else
#include <QApplication> #include <QApplication>
#endif #endif
#include "settings.h" #include "settings.h"
#include "vpnconnection.h" #include "vpnconnection.h"
#include "configurators/vpn_configurator.h" #include "configurators/vpn_configurator.h"
#include "ui/controllers/connectionController.h" #include "ui/controllers/connectionController.h"
#include "ui/controllers/exportController.h" #include "ui/controllers/exportController.h"
#include "ui/controllers/importController.h" #include "ui/controllers/importController.h"
#include "ui/controllers/installController.h" #include "ui/controllers/installController.h"
#include "ui/controllers/pageController.h" #include "ui/controllers/pageController.h"
#include "ui/controllers/settingsController.h" #include "ui/controllers/settingsController.h"
#include "ui/controllers/sitesController.h" #include "ui/controllers/sitesController.h"
#include "ui/controllers/systemController.h" #include "ui/controllers/systemController.h"
#include "ui/controllers/apiController.h" #include "ui/controllers/apiController.h"
#include "ui/models/containers_model.h" #include "ui/models/containers_model.h"
#include "ui/models/languageModel.h" #include "ui/models/languageModel.h"
#include "ui/models/protocols/cloakConfigModel.h" #include "ui/models/protocols/cloakConfigModel.h"
#include "ui/notificationhandler.h" #include "ui/notificationhandler.h"
#ifdef Q_OS_WINDOWS #ifdef Q_OS_WINDOWS
#include "ui/models/protocols/ikev2ConfigModel.h" #include "ui/models/protocols/ikev2ConfigModel.h"
#endif #endif
#include "ui/models/protocols/awgConfigModel.h" #include "ui/models/protocols/awgConfigModel.h"
#include "ui/models/protocols/openvpnConfigModel.h" #include "ui/models/protocols/openvpnConfigModel.h"
#include "ui/models/protocols/shadowsocksConfigModel.h" #include "ui/models/protocols/shadowsocksConfigModel.h"
#include "ui/models/protocols/wireguardConfigModel.h" #include "ui/models/protocols/wireguardConfigModel.h"
#include "ui/models/protocols_model.h" #include "ui/models/protocols_model.h"
#include "ui/models/servers_model.h" #include "ui/models/servers_model.h"
#include "ui/models/services/sftpConfigModel.h" #include "ui/models/services/sftpConfigModel.h"
#include "ui/models/sites_model.h" #include "ui/models/sites_model.h"
#include "ui/models/clientManagementModel.h" #include "ui/models/clientManagementModel.h"
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance())) #define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
#define AMNEZIA_BASE_CLASS QGuiApplication #define AMNEZIA_BASE_CLASS QGuiApplication
#else #else
#define AMNEZIA_BASE_CLASS SingleApplication #define AMNEZIA_BASE_CLASS SingleApplication
#define QAPPLICATION_CLASS QApplication #define QAPPLICATION_CLASS QApplication
#include "singleapplication.h" #include "singleapplication.h"
#endif #endif
class AmneziaApplication : public AMNEZIA_BASE_CLASS class AmneziaApplication : public AMNEZIA_BASE_CLASS
{ {
Q_OBJECT Q_OBJECT
public: public:
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) #if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
AmneziaApplication(int &argc, char *argv[]); AmneziaApplication(int &argc, char *argv[]);
#else #else
AmneziaApplication(int &argc, char *argv[], bool allowSecondary = false, AmneziaApplication(int &argc, char *argv[], bool allowSecondary = false,
SingleApplication::Options options = SingleApplication::User, int timeout = 1000, SingleApplication::Options options = SingleApplication::User, int timeout = 1000,
const QString &userData = {}); const QString &userData = {});
#endif #endif
virtual ~AmneziaApplication(); virtual ~AmneziaApplication();
void init(); void init();
void registerTypes(); void registerTypes();
void loadFonts(); void loadFonts();
void loadTranslator(); void loadTranslator();
void updateTranslator(const QLocale &locale); void updateTranslator(const QLocale &locale);
bool parseCommands(); bool parseCommands();
QQmlApplicationEngine *qmlEngine() const; QQmlApplicationEngine *qmlEngine() const;
signals: signals:
void translationsUpdated(); void translationsUpdated();
private: private:
void initModels(); void initModels();
void initControllers(); void initControllers();
QQmlApplicationEngine *m_engine {}; QQmlApplicationEngine *m_engine {};
std::shared_ptr<Settings> m_settings; std::shared_ptr<Settings> m_settings;
std::shared_ptr<VpnConfigurator> m_configurator; std::shared_ptr<VpnConfigurator> m_configurator;
QSharedPointer<ContainerProps> m_containerProps; QSharedPointer<ContainerProps> m_containerProps;
QSharedPointer<ProtocolProps> m_protocolProps; QSharedPointer<ProtocolProps> m_protocolProps;
QSharedPointer<QTranslator> m_translator; QSharedPointer<QTranslator> m_translator;
QCommandLineParser m_parser; QCommandLineParser m_parser;
QSharedPointer<ContainersModel> m_containersModel; QSharedPointer<ContainersModel> m_containersModel;
QSharedPointer<ServersModel> m_serversModel; QSharedPointer<ServersModel> m_serversModel;
QSharedPointer<LanguageModel> m_languageModel; QSharedPointer<LanguageModel> m_languageModel;
QSharedPointer<ProtocolsModel> m_protocolsModel; QSharedPointer<ProtocolsModel> m_protocolsModel;
QSharedPointer<SitesModel> m_sitesModel; QSharedPointer<SitesModel> m_sitesModel;
QSharedPointer<ClientManagementModel> m_clientManagementModel; QSharedPointer<ClientManagementModel> m_clientManagementModel;
QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel; QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel;
QScopedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel; QScopedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel;
QScopedPointer<CloakConfigModel> m_cloakConfigModel; QScopedPointer<CloakConfigModel> m_cloakConfigModel;
QScopedPointer<WireGuardConfigModel> m_wireGuardConfigModel; QScopedPointer<WireGuardConfigModel> m_wireGuardConfigModel;
QScopedPointer<AwgConfigModel> m_awgConfigModel; QScopedPointer<AwgConfigModel> m_awgConfigModel;
#ifdef Q_OS_WINDOWS #ifdef Q_OS_WINDOWS
QScopedPointer<Ikev2ConfigModel> m_ikev2ConfigModel; QScopedPointer<Ikev2ConfigModel> m_ikev2ConfigModel;
#endif #endif
QScopedPointer<SftpConfigModel> m_sftpConfigModel; QScopedPointer<SftpConfigModel> m_sftpConfigModel;
QSharedPointer<VpnConnection> m_vpnConnection; QSharedPointer<VpnConnection> m_vpnConnection;
QThread m_vpnConnectionThread; QThread m_vpnConnectionThread;
QScopedPointer<NotificationHandler> m_notificationHandler; QScopedPointer<NotificationHandler> m_notificationHandler;
QScopedPointer<ConnectionController> m_connectionController; QScopedPointer<ConnectionController> m_connectionController;
QScopedPointer<PageController> m_pageController; QScopedPointer<PageController> m_pageController;
QScopedPointer<InstallController> m_installController; QScopedPointer<InstallController> m_installController;
QScopedPointer<ImportController> m_importController; QScopedPointer<ImportController> m_importController;
QScopedPointer<ExportController> m_exportController; QScopedPointer<ExportController> m_exportController;
QScopedPointer<SettingsController> m_settingsController; QScopedPointer<SettingsController> m_settingsController;
QScopedPointer<SitesController> m_sitesController; QScopedPointer<SitesController> m_sitesController;
QScopedPointer<SystemController> m_systemController; QScopedPointer<SystemController> m_systemController;
QScopedPointer<ApiController> m_cloudController; QScopedPointer<ApiController> m_cloudController;
}; };
#endif // AMNEZIA_APPLICATION_H #endif // AMNEZIA_APPLICATION_H
+3 -9
View File
@@ -1,8 +1,6 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<!-- Leave package attribute for androiddeployqt --> <!-- Leave package attribute for androiddeployqt -->
<manifest <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.amnezia.vpn" package="org.amnezia.vpn"
android:versionName="-- %%INSERT_VERSION_NAME%% --" android:versionName="-- %%INSERT_VERSION_NAME%% --"
android:versionCode="-- %%INSERT_VERSION_CODE%% --" android:versionCode="-- %%INSERT_VERSION_CODE%% --"
@@ -33,17 +31,13 @@
android:label="-- %%INSERT_APP_NAME%% --" android:label="-- %%INSERT_APP_NAME%% --"
android:icon="@mipmap/icon" android:icon="@mipmap/icon"
android:roundIcon="@mipmap/icon_round" android:roundIcon="@mipmap/icon_round"
android:theme="@style/NoActionBar" android:theme="@style/NoActionBar">
android:fullBackupContent="@xml/backup_content"
android:dataExtractionRules="@xml/data_extraction_rules"
tools:targetApi="s">
<activity <activity
android:name=".AmneziaActivity" android:name=".AmneziaActivity"
android:configChanges="uiMode|screenSize|smallestScreenSize|screenLayout|orientation|density android:configChanges="uiMode|screenSize|smallestScreenSize|screenLayout|orientation|density
|fontScale|layoutDirection|locale|keyboard|keyboardHidden|navigation|mcc|mnc" |fontScale|layoutDirection|locale|keyboard|keyboardHidden|navigation|mcc|mnc"
android:launchMode="singleInstance" android:launchMode="singleInstance"
android:windowSoftInputMode="adjustResize"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
@@ -152,7 +146,7 @@
android:authorities="org.amnezia.vpn.qtprovider" android:authorities="org.amnezia.vpn.qtprovider"
android:exported="false" android:exported="false"
android:grantUriPermissions="true"> android:grantUriPermissions="true">
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/qtprovider_paths" /> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/qtprovider_paths" />
</provider> </provider>
</application> </application>
</manifest> </manifest>
@@ -99,7 +99,7 @@ class AwgConfig private constructor(
fun setH3(h3: Long) = apply { this.h3 = h3 } fun setH3(h3: Long) = apply { this.h3 = h3 }
fun setH4(h4: Long) = apply { this.h4 = h4 } fun setH4(h4: Long) = apply { this.h4 = h4 }
override fun build(): AwgConfig = configBuild().run { AwgConfig(this@Builder) } override fun build(): AwgConfig = AwgConfig(this)
} }
companion object { companion object {
+1
View File
@@ -108,6 +108,7 @@ dependencies {
implementation(project(":cloak")) implementation(project(":cloak"))
implementation(libs.androidx.core) implementation(libs.androidx.core)
implementation(libs.androidx.activity) implementation(libs.androidx.activity)
implementation(libs.androidx.security.crypto)
implementation(libs.kotlinx.coroutines) implementation(libs.kotlinx.coroutines)
implementation(libs.bundles.androidx.camera) implementation(libs.bundles.androidx.camera)
implementation(libs.google.mlkit) implementation(libs.google.mlkit)
@@ -3,9 +3,6 @@ package org.amnezia.vpn.protocol.cloak
import android.util.Base64 import android.util.Base64
import net.openvpn.ovpn3.ClientAPI_Config import net.openvpn.ovpn3.ClientAPI_Config
import org.amnezia.vpn.protocol.openvpn.OpenVpn import org.amnezia.vpn.protocol.openvpn.OpenVpn
import org.amnezia.vpn.protocol.openvpn.OpenVpnConfig
import org.amnezia.vpn.util.net.InetNetwork
import org.amnezia.vpn.util.net.parseInetAddress
import org.json.JSONObject import org.json.JSONObject
/** /**
@@ -54,13 +51,6 @@ class Cloak : OpenVpn() {
return openVpnConfig return openVpnConfig
} }
override fun configPluggableTransport(configBuilder: OpenVpnConfig.Builder, config: JSONObject) {
// exclude remote server ip from vpn routes
val remoteServer = config.getString("hostName")
val remoteServerAddress = InetNetwork(parseInetAddress(remoteServer))
configBuilder.excludeRoute(remoteServerAddress)
}
private fun checkCloakJson(cloakConfigJson: JSONObject): JSONObject { private fun checkCloakJson(cloakConfigJson: JSONObject): JSONObject {
cloakConfigJson.put("NumConn", 1) cloakConfigJson.put("NumConn", 1)
cloakConfigJson.put("ProxyMethod", "openvpn") cloakConfigJson.put("ProxyMethod", "openvpn")
@@ -2,6 +2,7 @@ package org.amnezia.vpn.protocol.openvpn
import android.content.Context import android.content.Context
import android.net.VpnService.Builder import android.net.VpnService.Builder
import android.os.Build
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@@ -13,6 +14,7 @@ import org.amnezia.vpn.protocol.ProtocolState
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
import org.amnezia.vpn.protocol.Statistics import org.amnezia.vpn.protocol.Statistics
import org.amnezia.vpn.protocol.VpnStartException import org.amnezia.vpn.protocol.VpnStartException
import org.amnezia.vpn.util.net.InetNetwork
import org.amnezia.vpn.util.net.getLocalNetworks import org.amnezia.vpn.util.net.getLocalNetworks
import org.json.JSONObject import org.json.JSONObject
@@ -77,8 +79,16 @@ open class OpenVpn : Protocol() {
if (evalConfig.error) { if (evalConfig.error) {
throw BadConfigException("OpenVPN config parse error: ${evalConfig.message}") throw BadConfigException("OpenVPN config parse error: ${evalConfig.message}")
} }
configPluggableTransport(configBuilder, config) configBuilder.apply {
configBuilder.configSplitTunneling(config) // fix for split tunneling
// The exclude split tunneling OpenVpn configuration does not contain a default route.
// It is required for split tunneling in newer versions of Android.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
addRoute(InetNetwork("0.0.0.0", 0))
addRoute(InetNetwork("::", 0))
}
configSplitTunneling(config)
}
scope.launch { scope.launch {
val status = client.connect() val status = client.connect()
@@ -112,8 +122,6 @@ open class OpenVpn : Protocol() {
return openVpnConfig return openVpnConfig
} }
protected open fun configPluggableTransport(configBuilder: OpenVpnConfig.Builder, config: JSONObject) {}
private fun makeEstablish(vpnBuilder: Builder): (OpenVpnConfig.Builder) -> Int = { configBuilder -> private fun makeEstablish(vpnBuilder: Builder): (OpenVpnConfig.Builder) -> Int = { configBuilder ->
val openVpnConfig = configBuilder.build() val openVpnConfig = configBuilder.build()
buildVpnInterface(openVpnConfig, vpnBuilder) buildVpnInterface(openVpnConfig, vpnBuilder)
@@ -52,7 +52,7 @@ class OpenVpnClient(
// Callback to construct a new tun builder // Callback to construct a new tun builder
// Should be called first. // Should be called first.
override fun tun_builder_new(): Boolean { override fun tun_builder_new(): Boolean {
Log.d(TAG, "tun_builder_new") Log.v(TAG, "tun_builder_new")
configBuilder.clearAddresses() configBuilder.clearAddresses()
return true return true
} }
@@ -60,7 +60,7 @@ class OpenVpnClient(
// Callback to set MTU of the VPN interface // Callback to set MTU of the VPN interface
// Never called more than once per tun_builder session. // Never called more than once per tun_builder session.
override fun tun_builder_set_mtu(mtu: Int): Boolean { override fun tun_builder_set_mtu(mtu: Int): Boolean {
Log.d(TAG, "tun_builder_set_mtu: $mtu") Log.v(TAG, "tun_builder_set_mtu: $mtu")
configBuilder.setMtu(mtu) configBuilder.setMtu(mtu)
return true return true
} }
@@ -71,7 +71,7 @@ class OpenVpnClient(
address: String, prefix_length: Int, address: String, prefix_length: Int,
gateway: String, ipv6: Boolean, net30: Boolean gateway: String, ipv6: Boolean, net30: Boolean
): Boolean { ): Boolean {
Log.d(TAG, "tun_builder_add_address: $address, $prefix_length, $gateway, $ipv6, $net30") Log.v(TAG, "tun_builder_add_address: $address, $prefix_length, $gateway, $ipv6, $net30")
configBuilder.addAddress(InetNetwork(address, prefix_length)) configBuilder.addAddress(InetNetwork(address, prefix_length))
return true return true
} }
@@ -80,7 +80,7 @@ class OpenVpnClient(
// May be called more than once per tun_builder session // May be called more than once per tun_builder session
// metric is optional and should be ignored if < 0 // metric is optional and should be ignored if < 0
override fun tun_builder_add_route(address: String, prefix_length: Int, metric: Int, ipv6: Boolean): Boolean { override fun tun_builder_add_route(address: String, prefix_length: Int, metric: Int, ipv6: Boolean): Boolean {
Log.d(TAG, "tun_builder_add_route: $address, $prefix_length, $metric, $ipv6") Log.v(TAG, "tun_builder_add_route: $address, $prefix_length, $metric, $ipv6")
if (address == "remote_host") return false if (address == "remote_host") return false
configBuilder.addRoute(InetNetwork(address, prefix_length)) configBuilder.addRoute(InetNetwork(address, prefix_length))
return true return true
@@ -90,8 +90,10 @@ class OpenVpnClient(
// May be called more than once per tun_builder session // May be called more than once per tun_builder session
// metric is optional and should be ignored if < 0 // metric is optional and should be ignored if < 0
override fun tun_builder_exclude_route(address: String, prefix_length: Int, metric: Int, ipv6: Boolean): Boolean { override fun tun_builder_exclude_route(address: String, prefix_length: Int, metric: Int, ipv6: Boolean): Boolean {
Log.d(TAG, "tun_builder_exclude_route: $address, $prefix_length, $metric, $ipv6") Log.v(TAG, "tun_builder_exclude_route: $address, $prefix_length, $metric, $ipv6")
configBuilder.excludeRoute(InetNetwork(address, prefix_length)) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
configBuilder.excludeRoute(InetNetwork(address, prefix_length))
}
return true return true
} }
@@ -102,7 +104,7 @@ class OpenVpnClient(
// domain should be routed. // domain should be routed.
// Guaranteed to be called after tun_builder_reroute_gw. // Guaranteed to be called after tun_builder_reroute_gw.
override fun tun_builder_add_dns_server(address: String, ipv6: Boolean): Boolean { override fun tun_builder_add_dns_server(address: String, ipv6: Boolean): Boolean {
Log.d(TAG, "tun_builder_add_dns_server: $address, $ipv6") Log.v(TAG, "tun_builder_add_dns_server: $address, $ipv6")
configBuilder.addDnsServer(parseInetAddress(address)) configBuilder.addDnsServer(parseInetAddress(address))
return true return true
} }
@@ -117,28 +119,28 @@ class OpenVpnClient(
// ignored for that family // ignored for that family
// See also Android's VPNService.Builder.allowFamily method // See also Android's VPNService.Builder.allowFamily method
/* override fun tun_builder_set_allow_family(af: Int, allow: Boolean): Boolean { /* override fun tun_builder_set_allow_family(af: Int, allow: Boolean): Boolean {
Log.d(TAG, "tun_builder_set_allow_family: $af, $allow") Log.v(TAG, "tun_builder_set_allow_family: $af, $allow")
return true return true
} */ } */
// Callback to set address of remote server // Callback to set address of remote server
// Never called more than once per tun_builder session. // Never called more than once per tun_builder session.
override fun tun_builder_set_remote_address(address: String, ipv6: Boolean): Boolean { override fun tun_builder_set_remote_address(address: String, ipv6: Boolean): Boolean {
Log.d(TAG, "tun_builder_set_remote_address: $address, $ipv6") Log.v(TAG, "tun_builder_set_remote_address: $address, $ipv6")
return true return true
} }
// Optional callback that indicates OSI layer, should be 2 or 3. // Optional callback that indicates OSI layer, should be 2 or 3.
// Defaults to 3. // Defaults to 3.
override fun tun_builder_set_layer(layer: Int): Boolean { override fun tun_builder_set_layer(layer: Int): Boolean {
Log.d(TAG, "tun_builder_set_layer: $layer") Log.v(TAG, "tun_builder_set_layer: $layer")
return layer == 3 return layer == 3
} }
// Callback to set the session name // Callback to set the session name
// Never called more than once per tun_builder session. // Never called more than once per tun_builder session.
override fun tun_builder_set_session_name(name: String): Boolean { override fun tun_builder_set_session_name(name: String): Boolean {
Log.d(TAG, "tun_builder_set_session_name: $name") Log.v(TAG, "tun_builder_set_session_name: $name")
return true return true
} }
@@ -147,7 +149,7 @@ class OpenVpnClient(
// if the tunnel could not be established. // if the tunnel could not be established.
// Always called last after tun_builder session has been configured. // Always called last after tun_builder session has been configured.
override fun tun_builder_establish(): Int { override fun tun_builder_establish(): Int {
Log.d(TAG, "tun_builder_establish") Log.v(TAG, "tun_builder_establish")
return establish(configBuilder) return establish(configBuilder)
} }
@@ -157,7 +159,7 @@ class OpenVpnClient(
// flags are defined in RGWFlags (rgwflags.hpp). // flags are defined in RGWFlags (rgwflags.hpp).
// Never called more than once per tun_builder session. // Never called more than once per tun_builder session.
override fun tun_builder_reroute_gw(ipv4: Boolean, ipv6: Boolean, flags: Long): Boolean { override fun tun_builder_reroute_gw(ipv4: Boolean, ipv6: Boolean, flags: Long): Boolean {
Log.d(TAG, "tun_builder_reroute_gw: $ipv4, $ipv6, $flags") Log.v(TAG, "tun_builder_reroute_gw: $ipv4, $ipv6, $flags")
if ((flags and EMULATED_EXCLUDE_ROUTES.toLong()) != 0L) return true if ((flags and EMULATED_EXCLUDE_ROUTES.toLong()) != 0L) return true
if (ipv4) { if (ipv4) {
configBuilder.addRoute(InetNetwork("0.0.0.0", 0)) configBuilder.addRoute(InetNetwork("0.0.0.0", 0))
@@ -174,7 +176,7 @@ class OpenVpnClient(
// reroute_dns parameter. // reroute_dns parameter.
// Guaranteed to be called after tun_builder_reroute_gw. // Guaranteed to be called after tun_builder_reroute_gw.
override fun tun_builder_add_search_domain(domain: String): Boolean { override fun tun_builder_add_search_domain(domain: String): Boolean {
Log.d(TAG, "tun_builder_add_search_domain: $domain") Log.v(TAG, "tun_builder_add_search_domain: $domain")
configBuilder.setSearchDomain(domain) configBuilder.setSearchDomain(domain)
return true return true
} }
@@ -182,7 +184,7 @@ class OpenVpnClient(
// Callback to set the HTTP proxy // Callback to set the HTTP proxy
// Never called more than once per tun_builder session. // Never called more than once per tun_builder session.
override fun tun_builder_set_proxy_http(host: String, port: Int): Boolean { override fun tun_builder_set_proxy_http(host: String, port: Int): Boolean {
Log.d(TAG, "tun_builder_set_proxy_http: $host, $port") Log.v(TAG, "tun_builder_set_proxy_http: $host, $port")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
try { try {
configBuilder.setHttpProxy(ProxyInfo.buildDirectProxy(host, port)) configBuilder.setHttpProxy(ProxyInfo.buildDirectProxy(host, port))
@@ -197,7 +199,7 @@ class OpenVpnClient(
// Callback to set the HTTPS proxy // Callback to set the HTTPS proxy
// Never called more than once per tun_builder session. // Never called more than once per tun_builder session.
override fun tun_builder_set_proxy_https(host: String, port: Int): Boolean { override fun tun_builder_set_proxy_https(host: String, port: Int): Boolean {
Log.d(TAG, "tun_builder_set_proxy_https: $host, $port") Log.v(TAG, "tun_builder_set_proxy_https: $host, $port")
return false return false
} }
@@ -206,7 +208,7 @@ class OpenVpnClient(
// to exclude them from the VPN network are generated // to exclude them from the VPN network are generated
// This should be a list of CIDR networks (e.g. 192.168.0.0/24) // This should be a list of CIDR networks (e.g. 192.168.0.0/24)
override fun tun_builder_get_local_networks(ipv6: Boolean): ClientAPI_StringVec { override fun tun_builder_get_local_networks(ipv6: Boolean): ClientAPI_StringVec {
Log.d(TAG, "tun_builder_get_local_networks: $ipv6") Log.v(TAG, "tun_builder_get_local_networks: $ipv6")
val networks = ClientAPI_StringVec() val networks = ClientAPI_StringVec()
for (address in getLocalNetworks(ipv6)) { for (address in getLocalNetworks(ipv6)) {
networks.add(address.toString()) networks.add(address.toString())
@@ -220,21 +222,21 @@ class OpenVpnClient(
// tun_builder_reroute_gw. Route metric is ignored // tun_builder_reroute_gw. Route metric is ignored
// if < 0. // if < 0.
/* override fun tun_builder_set_route_metric_default(metric: Int): Boolean { /* override fun tun_builder_set_route_metric_default(metric: Int): Boolean {
Log.d(TAG, "tun_builder_set_route_metric_default: $metric") Log.v(TAG, "tun_builder_set_route_metric_default: $metric")
return super.tun_builder_set_route_metric_default(metric) return super.tun_builder_set_route_metric_default(metric)
} */ } */
// Callback to add a host which should bypass the proxy // Callback to add a host which should bypass the proxy
// May be called more than once per tun_builder session // May be called more than once per tun_builder session
/* override fun tun_builder_add_proxy_bypass(bypass_host: String): Boolean { /* override fun tun_builder_add_proxy_bypass(bypass_host: String): Boolean {
Log.d(TAG, "tun_builder_add_proxy_bypass: $bypass_host") Log.v(TAG, "tun_builder_add_proxy_bypass: $bypass_host")
return super.tun_builder_add_proxy_bypass(bypass_host) return super.tun_builder_add_proxy_bypass(bypass_host)
} */ } */
// Callback to set the proxy "Auto Config URL" // Callback to set the proxy "Auto Config URL"
// Never called more than once per tun_builder session. // Never called more than once per tun_builder session.
/* override fun tun_builder_set_proxy_auto_config_url(url: String): Boolean { /* override fun tun_builder_set_proxy_auto_config_url(url: String): Boolean {
Log.d(TAG, "tun_builder_set_proxy_auto_config_url: $url") Log.v(TAG, "tun_builder_set_proxy_auto_config_url: $url")
return super.tun_builder_set_proxy_auto_config_url(url) return super.tun_builder_set_proxy_auto_config_url(url)
} */ } */
@@ -243,7 +245,7 @@ class OpenVpnClient(
// May be called more than once per tun_builder session. // May be called more than once per tun_builder session.
// Guaranteed to be called after tun_builder_reroute_gw. // Guaranteed to be called after tun_builder_reroute_gw.
/* override fun tun_builder_add_wins_server(address: String): Boolean { /* override fun tun_builder_add_wins_server(address: String): Boolean {
Log.d(TAG, "tun_builder_add_wins_server: $address") Log.v(TAG, "tun_builder_add_wins_server: $address")
return super.tun_builder_add_wins_server(address) return super.tun_builder_add_wins_server(address)
} */ } */
@@ -252,7 +254,7 @@ class OpenVpnClient(
// set the "Connection-specific DNS Suffix" property on // set the "Connection-specific DNS Suffix" property on
// the TAP driver. // the TAP driver.
/* override fun tun_builder_set_adapter_domain_suffix(name: String): Boolean { /* override fun tun_builder_set_adapter_domain_suffix(name: String): Boolean {
Log.d(TAG, "tun_builder_set_adapter_domain_suffix: $name") Log.v(TAG, "tun_builder_set_adapter_domain_suffix: $name")
return super.tun_builder_set_adapter_domain_suffix(name) return super.tun_builder_set_adapter_domain_suffix(name)
} */ } */
@@ -264,13 +266,13 @@ class OpenVpnClient(
// tun_builder_establish_lite() will be called. Otherwise, // tun_builder_establish_lite() will be called. Otherwise,
// tun_builder_establish() will be called. // tun_builder_establish() will be called.
/* override fun tun_builder_persist(): Boolean { /* override fun tun_builder_persist(): Boolean {
Log.d(TAG, "tun_builder_persist") Log.v(TAG, "tun_builder_persist")
return super.tun_builder_persist() return super.tun_builder_persist()
} */ } */
// Indicates a reconnection with persisted tun state. // Indicates a reconnection with persisted tun state.
/* override fun tun_builder_establish_lite() { /* override fun tun_builder_establish_lite() {
Log.d(TAG, "tun_builder_establish_lite") Log.v(TAG, "tun_builder_establish_lite")
super.tun_builder_establish_lite() super.tun_builder_establish_lite()
} */ } */
@@ -278,7 +280,7 @@ class OpenVpnClient(
// If disconnect == true, then the teardown is occurring // If disconnect == true, then the teardown is occurring
// prior to final disconnect. // prior to final disconnect.
/* override fun tun_builder_teardown(disconnect: Boolean) { /* override fun tun_builder_teardown(disconnect: Boolean) {
Log.d(TAG, "tun_builder_teardown: $disconnect") Log.v(TAG, "tun_builder_teardown: $disconnect")
super.tun_builder_teardown(disconnect) super.tun_builder_teardown(disconnect)
} */ } */
@@ -288,7 +290,7 @@ class OpenVpnClient(
// Parse OpenVPN configuration file. // Parse OpenVPN configuration file.
override fun eval_config(arg0: ClientAPI_Config): ClientAPI_EvalConfig { override fun eval_config(arg0: ClientAPI_Config): ClientAPI_EvalConfig {
Log.d(TAG, "eval_config") Log.v(TAG, "eval_config")
return super.eval_config(arg0) return super.eval_config(arg0)
} }
@@ -297,7 +299,7 @@ class OpenVpnClient(
// to event() and log() functions. Make sure to call eval_config() // to event() and log() functions. Make sure to call eval_config()
// and possibly provide_creds() as well before this function. // and possibly provide_creds() as well before this function.
override fun connect(): ClientAPI_Status { override fun connect(): ClientAPI_Status {
Log.d(TAG, "connect") Log.v(TAG, "connect")
return super.connect() return super.connect()
} }
@@ -305,7 +307,7 @@ class OpenVpnClient(
// Will be called from the thread executing connect(). // Will be called from the thread executing connect().
// The remote and ipv6 are the remote host this socket will connect to // The remote and ipv6 are the remote host this socket will connect to
override fun socket_protect(socket: Int, remote: String, ipv6: Boolean): Boolean { override fun socket_protect(socket: Int, remote: String, ipv6: Boolean): Boolean {
Log.d(TAG, "socket_protect: $socket, $remote, $ipv6") Log.v(TAG, "socket_protect: $socket, $remote, $ipv6")
return protect(socket) return protect(socket)
} }
@@ -313,7 +315,7 @@ class OpenVpnClient(
// May be called asynchronously from a different thread // May be called asynchronously from a different thread
// when connect() is running. // when connect() is running.
override fun stop() { override fun stop() {
Log.d(TAG, "stop") Log.v(TAG, "stop")
super.stop() super.stop()
} }
@@ -321,21 +323,21 @@ class OpenVpnClient(
// when network is down. May be called from a different thread // when network is down. May be called from a different thread
// when connect() is running. // when connect() is running.
override fun pause(reason: String) { override fun pause(reason: String) {
Log.d(TAG, "pause: $reason") Log.v(TAG, "pause: $reason")
super.pause(reason) super.pause(reason)
} }
// Resume the client after it has been paused. May be called from a // Resume the client after it has been paused. May be called from a
// different thread when connect() is running. // different thread when connect() is running.
override fun resume() { override fun resume() {
Log.d(TAG, "resume") Log.v(TAG, "resume")
super.resume() super.resume()
} }
// Do a disconnect/reconnect cycle n seconds from now. May be called // Do a disconnect/reconnect cycle n seconds from now. May be called
// from a different thread when connect() is running. // from a different thread when connect() is running.
override fun reconnect(seconds: Int) { override fun reconnect(seconds: Int) {
Log.d(TAG, "reconnect: $seconds") Log.v(TAG, "reconnect")
super.reconnect(seconds) super.reconnect(seconds)
} }
@@ -344,14 +346,14 @@ class OpenVpnClient(
// CONNECTION_TIMEOUT event. If true, the core will enter a PAUSE // CONNECTION_TIMEOUT event. If true, the core will enter a PAUSE
// state. // state.
override fun pause_on_connection_timeout(): Boolean { override fun pause_on_connection_timeout(): Boolean {
Log.d(TAG, "pause_on_connection_timeout") Log.v(TAG, "pause_on_connection_timeout")
return false return false
} }
// Return information about the most recent connection. Should be called // Return information about the most recent connection. Should be called
// after an event of type "CONNECTED". // after an event of type "CONNECTED".
/* override fun connection_info(): ClientAPI_ConnectionInfo { /* override fun connection_info(): ClientAPI_ConnectionInfo {
Log.d(TAG, "connection_info") Log.v(TAG, "connection_info")
return super.connection_info() return super.connection_info()
} */ } */
@@ -364,7 +366,7 @@ class OpenVpnClient(
override fun event(event: ClientAPI_Event) { override fun event(event: ClientAPI_Event) {
val name = event.name val name = event.name
val info = event.info val info = event.info
Log.d(TAG, "OpenVpn event: $name: $info") Log.v(TAG, "OpenVpn event: $name: $info")
when (name) { when (name) {
"COMPRESSION_ENABLED", "WARN" -> Log.w(TAG, "$name: $info") "COMPRESSION_ENABLED", "WARN" -> Log.w(TAG, "$name: $info")
"CONNECTED" -> state.value = CONNECTED "CONNECTED" -> state.value = CONNECTED
@@ -396,31 +398,31 @@ class OpenVpnClient(
// return transport stats only // return transport stats only
override fun transport_stats(): ClientAPI_TransportStats { override fun transport_stats(): ClientAPI_TransportStats {
Log.d(TAG, "transport_stats") Log.v(TAG, "transport_stats")
return super.transport_stats() return super.transport_stats()
} }
// return a stats value, index should be >= 0 and < stats_n() // return a stats value, index should be >= 0 and < stats_n()
/* override fun stats_value(index: Int): Long { /* override fun stats_value(index: Int): Long {
Log.d(TAG, "stats_value: $index") Log.v(TAG, "stats_value: $index")
return super.stats_value(index) return super.stats_value(index)
} */ } */
// return all stats in a bundle // return all stats in a bundle
/* override fun stats_bundle(): ClientAPI_LLVector { /* override fun stats_bundle(): ClientAPI_LLVector {
Log.d(TAG, "stats_bundle") Log.v(TAG, "stats_bundle")
return super.stats_bundle() return super.stats_bundle()
} */ } */
// return tun stats only // return tun stats only
/* override fun tun_stats(): ClientAPI_InterfaceStats { /* override fun tun_stats(): ClientAPI_InterfaceStats {
Log.d(TAG, "tun_stats") Log.v(TAG, "tun_stats")
return super.tun_stats() return super.tun_stats()
} */ } */
// post control channel message // post control channel message
/* override fun post_cc_msg(msg: String) { /* override fun post_cc_msg(msg: String) {
Log.d(TAG, "post_cc_msg: $msg") Log.v(TAG, "post_cc_msg: $msg")
super.post_cc_msg(msg) super.post_cc_msg(msg)
} */ } */
} }
@@ -11,7 +11,7 @@ class OpenVpnConfig private constructor(
class Builder : ProtocolConfig.Builder(false) { class Builder : ProtocolConfig.Builder(false) {
override var mtu: Int = OPENVPN_DEFAULT_MTU override var mtu: Int = OPENVPN_DEFAULT_MTU
override fun build(): OpenVpnConfig = configBuild().run { OpenVpnConfig(this@Builder) } override fun build(): OpenVpnConfig = OpenVpnConfig(this)
} }
companion object { companion object {
@@ -14,6 +14,8 @@ import java.util.zip.ZipFile
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import org.amnezia.vpn.util.Log import org.amnezia.vpn.util.Log
import org.amnezia.vpn.util.net.InetNetwork import org.amnezia.vpn.util.net.InetNetwork
import org.amnezia.vpn.util.net.IpRange
import org.amnezia.vpn.util.net.IpRangeSet
import org.json.JSONObject import org.json.JSONObject
private const val TAG = "Protocol" private const val TAG = "Protocol"
@@ -51,16 +53,40 @@ abstract class Protocol {
val splitTunnelType = config.optInt("splitTunnelType") val splitTunnelType = config.optInt("splitTunnelType")
if (splitTunnelType == SPLIT_TUNNEL_DISABLE) return if (splitTunnelType == SPLIT_TUNNEL_DISABLE) return
val splitTunnelSites = config.getJSONArray("splitTunnelSites") val splitTunnelSites = config.getJSONArray("splitTunnelSites")
val addressHandlerFunc = when (splitTunnelType) { when (splitTunnelType) {
SPLIT_TUNNEL_INCLUDE -> ::includeAddress SPLIT_TUNNEL_INCLUDE -> {
SPLIT_TUNNEL_EXCLUDE -> ::excludeAddress // remove default routes, if any
removeRoute(InetNetwork("0.0.0.0", 0))
removeRoute(InetNetwork("::", 0))
// add routes from config
for (i in 0 until splitTunnelSites.length()) {
val address = InetNetwork.parse(splitTunnelSites.getString(i))
addRoute(address)
}
}
else -> throw BadConfigException("Unexpected value of the 'splitTunnelType' parameter: $splitTunnelType") SPLIT_TUNNEL_EXCLUDE -> {
} if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// exclude routes from config
for (i in 0 until splitTunnelSites.length()) { for (i in 0 until splitTunnelSites.length()) {
val address = InetNetwork.parse(splitTunnelSites.getString(i)) val address = InetNetwork.parse(splitTunnelSites.getString(i))
addressHandlerFunc(address) excludeRoute(address)
}
} else {
// For older versions of Android, build a list of subnets without excluded addresses
val ipRangeSet = IpRangeSet()
ipRangeSet.remove(IpRange("127.0.0.0", 8))
for (i in 0 until splitTunnelSites.length()) {
val address = InetNetwork.parse(splitTunnelSites.getString(i))
ipRangeSet.remove(IpRange(address))
}
// remove default routes, if any
removeRoute(InetNetwork("0.0.0.0", 0))
removeRoute(InetNetwork("::", 0))
ipRangeSet.subnets().forEach(::addRoute)
addRoute(InetNetwork("2000::", 3))
}
}
} }
} }
@@ -5,8 +5,6 @@ import android.os.Build
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import java.net.InetAddress import java.net.InetAddress
import org.amnezia.vpn.util.net.InetNetwork import org.amnezia.vpn.util.net.InetNetwork
import org.amnezia.vpn.util.net.IpRange
import org.amnezia.vpn.util.net.IpRangeSet
open class ProtocolConfig protected constructor( open class ProtocolConfig protected constructor(
val addresses: Set<InetNetwork>, val addresses: Set<InetNetwork>,
@@ -14,8 +12,6 @@ open class ProtocolConfig protected constructor(
val searchDomain: String?, val searchDomain: String?,
val routes: Set<InetNetwork>, val routes: Set<InetNetwork>,
val excludedRoutes: Set<InetNetwork>, val excludedRoutes: Set<InetNetwork>,
val includedAddresses: Set<InetNetwork>,
val excludedAddresses: Set<InetNetwork>,
val excludedApplications: Set<String>, val excludedApplications: Set<String>,
val httpProxy: ProxyInfo?, val httpProxy: ProxyInfo?,
val allowAllAF: Boolean, val allowAllAF: Boolean,
@@ -29,8 +25,6 @@ open class ProtocolConfig protected constructor(
builder.searchDomain, builder.searchDomain,
builder.routes, builder.routes,
builder.excludedRoutes, builder.excludedRoutes,
builder.includedAddresses,
builder.excludedAddresses,
builder.excludedApplications, builder.excludedApplications,
builder.httpProxy, builder.httpProxy,
builder.allowAllAF, builder.allowAllAF,
@@ -43,8 +37,6 @@ open class ProtocolConfig protected constructor(
internal val dnsServers: MutableSet<InetAddress> = hashSetOf() internal val dnsServers: MutableSet<InetAddress> = hashSetOf()
internal val routes: MutableSet<InetNetwork> = hashSetOf() internal val routes: MutableSet<InetNetwork> = hashSetOf()
internal val excludedRoutes: MutableSet<InetNetwork> = hashSetOf() internal val excludedRoutes: MutableSet<InetNetwork> = hashSetOf()
internal val includedAddresses: MutableSet<InetNetwork> = hashSetOf()
internal val excludedAddresses: MutableSet<InetNetwork> = hashSetOf()
internal val excludedApplications: MutableSet<String> = hashSetOf() internal val excludedApplications: MutableSet<String> = hashSetOf()
internal var searchDomain: String? = null internal var searchDomain: String? = null
@@ -79,15 +71,12 @@ open class ProtocolConfig protected constructor(
fun removeRoute(route: InetNetwork) = apply { this.routes.remove(route) } fun removeRoute(route: InetNetwork) = apply { this.routes.remove(route) }
fun clearRoutes() = apply { this.routes.clear() } fun clearRoutes() = apply { this.routes.clear() }
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
fun excludeRoute(route: InetNetwork) = apply { this.excludedRoutes += route } fun excludeRoute(route: InetNetwork) = apply { this.excludedRoutes += route }
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
fun excludeRoutes(routes: Collection<InetNetwork>) = apply { this.excludedRoutes += routes } fun excludeRoutes(routes: Collection<InetNetwork>) = apply { this.excludedRoutes += routes }
fun includeAddress(addr: InetNetwork) = apply { this.includedAddresses += addr }
fun includeAddresses(addresses: Collection<InetNetwork>) = apply { this.includedAddresses += addresses }
fun excludeAddress(addr: InetNetwork) = apply { this.excludedAddresses += addr }
fun excludeAddresses(addresses: Collection<InetNetwork>) = apply { this.excludedAddresses += addresses }
fun excludeApplication(application: String) = apply { this.excludedApplications += application } fun excludeApplication(application: String) = apply { this.excludedApplications += application }
fun excludeApplications(applications: Collection<String>) = apply { this.excludedApplications += applications } fun excludeApplications(applications: Collection<String>) = apply { this.excludedApplications += applications }
@@ -102,49 +91,6 @@ open class ProtocolConfig protected constructor(
fun setMtu(mtu: Int) = apply { this.mtu = mtu } fun setMtu(mtu: Int) = apply { this.mtu = mtu }
private fun processSplitTunneling() {
if (includedAddresses.isNotEmpty() && excludedAddresses.isNotEmpty()) {
throw BadConfigException("Config contains addresses for inclusive and exclusive split tunneling at the same time")
}
if (includedAddresses.isNotEmpty()) {
// remove default routes, if any
removeRoute(InetNetwork("0.0.0.0", 0))
removeRoute(InetNetwork("::", 0))
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
// for older versions of Android, add the default route to the excluded routes
// to correctly build the excluded subnets list later
excludeRoute(InetNetwork("0.0.0.0", 0))
}
addRoutes(includedAddresses)
} else if (excludedAddresses.isNotEmpty()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// default routes are required for split tunneling in newer versions of Android
addRoute(InetNetwork("0.0.0.0", 0))
addRoute(InetNetwork("::", 0))
}
excludeRoutes(excludedAddresses)
}
}
private fun processExcludedRoutes() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU && excludedRoutes.isNotEmpty()) {
// todo: rewrite, taking into account the current routes
// for older versions of Android, build a list of subnets without excluded routes
// and add them to routes
val ipRangeSet = IpRangeSet()
ipRangeSet.remove(IpRange("127.0.0.0", 8))
excludedRoutes.forEach {
ipRangeSet.remove(IpRange(it))
}
// remove default routes, if any
removeRoute(InetNetwork("0.0.0.0", 0))
removeRoute(InetNetwork("::", 0))
ipRangeSet.subnets().forEach(::addRoute)
addRoute(InetNetwork("2000::", 3))
}
}
private fun validate() { private fun validate() {
val errorMessage = StringBuilder() val errorMessage = StringBuilder()
@@ -157,13 +103,7 @@ open class ProtocolConfig protected constructor(
if (errorMessage.isNotEmpty()) throw BadConfigException(errorMessage.toString()) if (errorMessage.isNotEmpty()) throw BadConfigException(errorMessage.toString())
} }
protected fun configBuild() { open fun build(): ProtocolConfig = validate().run { ProtocolConfig(this@Builder) }
processSplitTunneling()
processExcludedRoutes()
validate()
}
open fun build(): ProtocolConfig = configBuild().run { ProtocolConfig(this@Builder) }
} }
companion object { companion object {
@@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
<exclude domain="sharedpref" path="." />
</full-backup-content>
@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<data-extraction-rules>
<cloud-backup>
<exclude domain="sharedpref" path="." />
</cloud-backup>
<device-transfer>
<exclude domain="sharedpref" path="." />
</device-transfer>
</data-extraction-rules>
@@ -5,7 +5,6 @@ import android.content.Intent
import android.content.Intent.EXTRA_MIME_TYPES import android.content.Intent.EXTRA_MIME_TYPES
import android.content.Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY import android.content.Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
import android.content.ServiceConnection import android.content.ServiceConnection
import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
import android.net.VpnService import android.net.VpnService
import android.os.Bundle import android.os.Bundle
@@ -64,7 +63,6 @@ class AmneziaActivity : QtActivity() {
ServiceEvent.DISCONNECTED -> { ServiceEvent.DISCONNECTED -> {
QtAndroidController.onVpnDisconnected() QtAndroidController.onVpnDisconnected()
doUnbindService()
} }
ServiceEvent.RECONNECTING -> { ServiceEvent.RECONNECTING -> {
@@ -145,7 +143,7 @@ class AmneziaActivity : QtActivity() {
*/ */
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
Log.d(TAG, "Create Amnezia activity: $intent") Log.v(TAG, "Create Amnezia activity: $intent")
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
vpnServiceMessenger = IpcMessenger( vpnServiceMessenger = IpcMessenger(
onDeadObjectException = ::doUnbindService, onDeadObjectException = ::doUnbindService,
@@ -156,7 +154,7 @@ class AmneziaActivity : QtActivity() {
override fun onNewIntent(intent: Intent?) { override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent) super.onNewIntent(intent)
Log.d(TAG, "onNewIntent: $intent") Log.v(TAG, "onNewIntent: $intent")
intent?.let(::processIntent) intent?.let(::processIntent)
} }
@@ -176,7 +174,7 @@ class AmneziaActivity : QtActivity() {
override fun onStart() { override fun onStart() {
super.onStart() super.onStart()
Log.d(TAG, "Start Amnezia activity") Log.v(TAG, "Start Amnezia activity")
mainScope.launch { mainScope.launch {
qtInitialized.await() qtInitialized.await()
doBindService() doBindService()
@@ -184,13 +182,13 @@ class AmneziaActivity : QtActivity() {
} }
override fun onStop() { override fun onStop() {
Log.d(TAG, "Stop Amnezia activity") Log.v(TAG, "Stop Amnezia activity")
doUnbindService() doUnbindService()
super.onStop() super.onStop()
} }
override fun onDestroy() { override fun onDestroy() {
Log.d(TAG, "Destroy Amnezia activity") Log.v(TAG, "Destroy Amnezia activity")
mainScope.cancel() mainScope.cancel()
super.onDestroy() super.onDestroy()
} }
@@ -219,7 +217,7 @@ class AmneziaActivity : QtActivity() {
CHECK_VPN_PERMISSION_ACTION_CODE -> { CHECK_VPN_PERMISSION_ACTION_CODE -> {
when (resultCode) { when (resultCode) {
RESULT_OK -> { RESULT_OK -> {
Log.d(TAG, "Vpn permission granted") Log.v(TAG, "Vpn permission granted")
Toast.makeText(this, "Vpn permission granted", Toast.LENGTH_LONG).show() Toast.makeText(this, "Vpn permission granted", Toast.LENGTH_LONG).show()
checkVpnPermissionCallbacks?.run { onSuccess() } checkVpnPermissionCallbacks?.run { onSuccess() }
} }
@@ -242,7 +240,7 @@ class AmneziaActivity : QtActivity() {
*/ */
@MainThread @MainThread
private fun doBindService() { private fun doBindService() {
Log.d(TAG, "Bind service") Log.v(TAG, "Bind service")
Intent(this, AmneziaVpnService::class.java).also { Intent(this, AmneziaVpnService::class.java).also {
bindService(it, serviceConnection, BIND_ABOVE_CLIENT) bindService(it, serviceConnection, BIND_ABOVE_CLIENT)
} }
@@ -253,7 +251,7 @@ class AmneziaActivity : QtActivity() {
@MainThread @MainThread
private fun doUnbindService() { private fun doUnbindService() {
if (isInBoundState) { if (isInBoundState) {
Log.d(TAG, "Unbind service") Log.v(TAG, "Unbind service")
isWaitingStatus = true isWaitingStatus = true
QtAndroidController.onServiceDisconnected() QtAndroidController.onServiceDisconnected()
vpnServiceMessenger.reset() vpnServiceMessenger.reset()
@@ -288,7 +286,7 @@ class AmneziaActivity : QtActivity() {
@MainThread @MainThread
private fun checkVpnPermission(onSuccess: () -> Unit, onFail: () -> Unit) { private fun checkVpnPermission(onSuccess: () -> Unit, onFail: () -> Unit) {
Log.d(TAG, "Check VPN permission") Log.v(TAG, "Check VPN permission")
VpnService.prepare(applicationContext)?.let { VpnService.prepare(applicationContext)?.let {
checkVpnPermissionCallbacks = CheckVpnPermissionCallbacks(onSuccess, onFail) checkVpnPermissionCallbacks = CheckVpnPermissionCallbacks(onSuccess, onFail)
startActivityForResult(it, CHECK_VPN_PERMISSION_ACTION_CODE) startActivityForResult(it, CHECK_VPN_PERMISSION_ACTION_CODE)
@@ -309,7 +307,7 @@ class AmneziaActivity : QtActivity() {
} }
private fun connectToVpn(vpnConfig: String) { private fun connectToVpn(vpnConfig: String) {
Log.d(TAG, "Connect to VPN") Log.v(TAG, "Connect to VPN")
vpnServiceMessenger.send { vpnServiceMessenger.send {
Action.CONNECT.packToMessage { Action.CONNECT.packToMessage {
putString(VPN_CONFIG, vpnConfig) putString(VPN_CONFIG, vpnConfig)
@@ -318,7 +316,7 @@ class AmneziaActivity : QtActivity() {
} }
private fun startVpnService(vpnConfig: String) { private fun startVpnService(vpnConfig: String) {
Log.d(TAG, "Start VPN service") Log.v(TAG, "Start VPN service")
Intent(this, AmneziaVpnService::class.java).apply { Intent(this, AmneziaVpnService::class.java).apply {
putExtra(VPN_CONFIG, vpnConfig) putExtra(VPN_CONFIG, vpnConfig)
}.also { }.also {
@@ -327,7 +325,7 @@ class AmneziaActivity : QtActivity() {
} }
private fun disconnectFromVpn() { private fun disconnectFromVpn() {
Log.d(TAG, "Disconnect from VPN") Log.v(TAG, "Disconnect from VPN")
vpnServiceMessenger.send(Action.DISCONNECT) vpnServiceMessenger.send(Action.DISCONNECT)
} }
@@ -371,7 +369,7 @@ class AmneziaActivity : QtActivity() {
@Suppress("unused") @Suppress("unused")
fun saveFile(fileName: String, data: String) { fun saveFile(fileName: String, data: String) {
Log.d(TAG, "Save file $fileName") Log.v(TAG, "Save file $fileName")
mainScope.launch { mainScope.launch {
tmpFileContentToSave = data tmpFileContentToSave = data
@@ -399,7 +397,7 @@ class AmneziaActivity : QtActivity() {
Intent(Intent.ACTION_OPEN_DOCUMENT).apply { Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE) addCategory(Intent.CATEGORY_OPENABLE)
Log.v(TAG, "File mimyType filter: $mimeTypes") Log.d(TAG, "File mimyType filter: $mimeTypes")
when (mimeTypes.size) { when (mimeTypes.size) {
1 -> type = mimeTypes.first() 1 -> type = mimeTypes.first()
@@ -418,10 +416,14 @@ class AmneziaActivity : QtActivity() {
@Suppress("unused") @Suppress("unused")
fun setNotificationText(title: String, message: String, timerSec: Int) { fun setNotificationText(title: String, message: String, timerSec: Int) {
Log.v(TAG, "Set notification text") Log.v(TAG, "Set notification text")
Log.w(TAG, "Not yet implemented")
} }
@Suppress("unused") @Suppress("unused")
fun isCameraPresent(): Boolean = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA) fun cleanupLogs() {
Log.v(TAG, "Cleanup logs")
Log.w(TAG, "Not yet implemented")
}
@Suppress("unused") @Suppress("unused")
fun startQrCodeReader() { fun startQrCodeReader() {
@@ -430,29 +432,4 @@ class AmneziaActivity : QtActivity() {
startActivity(it) startActivity(it)
} }
} }
@Suppress("unused")
fun setSaveLogs(enabled: Boolean) {
Log.d(TAG, "Set save logs: $enabled")
mainScope.launch {
Log.saveLogs = enabled
vpnServiceMessenger.send {
Action.SET_SAVE_LOGS.packToMessage {
putBoolean(SAVE_LOGS, enabled)
}
}
}
}
@Suppress("unused")
fun exportLogsFile(fileName: String) {
Log.v(TAG, "Export logs file")
saveFile(fileName, Log.getLogs())
}
@Suppress("unused")
fun clearLogs() {
Log.v(TAG, "Clear logs")
Log.clearLogs()
}
} }
@@ -5,20 +5,14 @@ import androidx.camera.core.CameraSelector
import androidx.camera.core.CameraXConfig import androidx.camera.core.CameraXConfig
import androidx.core.app.NotificationChannelCompat.Builder import androidx.core.app.NotificationChannelCompat.Builder
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import org.amnezia.vpn.util.Log
import org.amnezia.vpn.util.Prefs
import org.qtproject.qt.android.bindings.QtApplication import org.qtproject.qt.android.bindings.QtApplication
private const val TAG = "AmneziaApplication"
const val NOTIFICATION_CHANNEL_ID: String = "org.amnezia.vpn.notification" const val NOTIFICATION_CHANNEL_ID: String = "org.amnezia.vpn.notification"
class AmneziaApplication : QtApplication(), CameraXConfig.Provider { class AmneziaApplication : QtApplication(), CameraXConfig.Provider {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
Prefs.init(this)
Log.init(this)
Log.d(TAG, "Create Amnezia application")
createNotificationChannel() createNotificationChannel()
} }
@@ -50,7 +50,6 @@ import org.amnezia.vpn.protocol.putStatistics
import org.amnezia.vpn.protocol.putStatus import org.amnezia.vpn.protocol.putStatus
import org.amnezia.vpn.protocol.wireguard.Wireguard import org.amnezia.vpn.protocol.wireguard.Wireguard
import org.amnezia.vpn.util.Log import org.amnezia.vpn.util.Log
import org.amnezia.vpn.util.Prefs
import org.amnezia.vpn.util.net.NetworkState import org.amnezia.vpn.util.net.NetworkState
import org.json.JSONException import org.json.JSONException
import org.json.JSONObject import org.json.JSONObject
@@ -59,8 +58,6 @@ private const val TAG = "AmneziaVpnService"
const val VPN_CONFIG = "VPN_CONFIG" const val VPN_CONFIG = "VPN_CONFIG"
const val ERROR_MSG = "ERROR_MSG" const val ERROR_MSG = "ERROR_MSG"
const val SAVE_LOGS = "SAVE_LOGS"
const val AFTER_PERMISSION_CHECK = "AFTER_PERMISSION_CHECK" const val AFTER_PERMISSION_CHECK = "AFTER_PERMISSION_CHECK"
private const val PREFS_CONFIG_KEY = "LAST_CONF" private const val PREFS_CONFIG_KEY = "LAST_CONF"
private const val NOTIFICATION_ID = 1337 private const val NOTIFICATION_ID = 1337
@@ -121,7 +118,7 @@ class AmneziaVpnService : VpnService() {
Action.CONNECT -> { Action.CONNECT -> {
val vpnConfig = msg.data.getString(VPN_CONFIG) val vpnConfig = msg.data.getString(VPN_CONFIG)
Prefs.save(PREFS_CONFIG_KEY, vpnConfig) saveConfigToPrefs(vpnConfig)
connect(vpnConfig) connect(vpnConfig)
} }
@@ -138,10 +135,6 @@ class AmneziaVpnService : VpnService() {
} }
} }
} }
Action.SET_SAVE_LOGS -> {
Log.saveLogs = msg.data.getBoolean(SAVE_LOGS)
}
} }
} }
} }
@@ -186,7 +179,7 @@ class AmneziaVpnService : VpnService() {
*/ */
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
Log.d(TAG, "Create Amnezia VPN service") Log.v(TAG, "Create Amnezia VPN service")
mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) mainScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
connectionScope = CoroutineScope(SupervisorJob() + Dispatchers.IO + connectionExceptionHandler) connectionScope = CoroutineScope(SupervisorJob() + Dispatchers.IO + connectionExceptionHandler)
clientMessenger = IpcMessenger(messengerName = "Client") clientMessenger = IpcMessenger(messengerName = "Client")
@@ -200,15 +193,15 @@ class AmneziaVpnService : VpnService() {
else intent?.component?.packageName != packageName else intent?.component?.packageName != packageName
if (isAlwaysOnCompat) { if (isAlwaysOnCompat) {
Log.d(TAG, "Start service via Always-on") Log.v(TAG, "Start service via Always-on")
connect(Prefs.load(PREFS_CONFIG_KEY)) connect(loadConfigFromPrefs())
} else if (intent?.getBooleanExtra(AFTER_PERMISSION_CHECK, false) == true) { } else if (intent?.getBooleanExtra(AFTER_PERMISSION_CHECK, false) == true) {
Log.d(TAG, "Start service after permission check") Log.v(TAG, "Start service after permission check")
connect(Prefs.load(PREFS_CONFIG_KEY)) connect(loadConfigFromPrefs())
} else { } else {
Log.d(TAG, "Start service") Log.v(TAG, "Start service")
val vpnConfig = intent?.getStringExtra(VPN_CONFIG) val vpnConfig = intent?.getStringExtra(VPN_CONFIG)
Prefs.save(PREFS_CONFIG_KEY, vpnConfig) saveConfigToPrefs(vpnConfig)
connect(vpnConfig) connect(vpnConfig)
} }
ServiceCompat.startForeground(this, NOTIFICATION_ID, notification, foregroundServiceTypeCompat) ServiceCompat.startForeground(this, NOTIFICATION_ID, notification, foregroundServiceTypeCompat)
@@ -244,7 +237,7 @@ class AmneziaVpnService : VpnService() {
} }
override fun onRevoke() { override fun onRevoke() {
Log.d(TAG, "onRevoke") Log.v(TAG, "onRevoke")
// Calls to onRevoke() method may not happen on the main thread of the process // Calls to onRevoke() method may not happen on the main thread of the process
mainScope.launch { mainScope.launch {
disconnect() disconnect()
@@ -252,7 +245,7 @@ class AmneziaVpnService : VpnService() {
} }
override fun onDestroy() { override fun onDestroy() {
Log.d(TAG, "Destroy service") Log.v(TAG, "Destroy service")
runBlocking { runBlocking {
disconnect() disconnect()
disconnectionJob?.join() disconnectionJob?.join()
@@ -263,7 +256,7 @@ class AmneziaVpnService : VpnService() {
} }
private fun stopService() { private fun stopService() {
Log.d(TAG, "Stop service") Log.v(TAG, "Stop service")
// the coroutine below will be canceled during the onDestroy call // the coroutine below will be canceled during the onDestroy call
mainScope.launch { mainScope.launch {
delay(STOP_SERVICE_TIMEOUT) delay(STOP_SERVICE_TIMEOUT)
@@ -279,7 +272,7 @@ class AmneziaVpnService : VpnService() {
private fun launchProtocolStateHandler() { private fun launchProtocolStateHandler() {
mainScope.launch { mainScope.launch {
protocolState.collect { protocolState -> protocolState.collect { protocolState ->
Log.d(TAG, "Protocol state changed: $protocolState") Log.d(TAG, "Protocol state: $protocolState")
when (protocolState) { when (protocolState) {
CONNECTED -> { CONNECTED -> {
clientMessenger.send(ServiceEvent.CONNECTED) clientMessenger.send(ServiceEvent.CONNECTED)
@@ -312,7 +305,7 @@ class AmneziaVpnService : VpnService() {
@MainThread @MainThread
private fun launchSendingStatistics() { private fun launchSendingStatistics() {
/* if (isServiceBound && isConnected) { if (isServiceBound && isConnected) {
statisticsSendingJob = mainScope.launch { statisticsSendingJob = mainScope.launch {
while (true) { while (true) {
clientMessenger.send { clientMessenger.send {
@@ -323,7 +316,7 @@ class AmneziaVpnService : VpnService() {
delay(STATISTICS_SENDING_TIMEOUT) delay(STATISTICS_SENDING_TIMEOUT)
} }
} }
} */ }
} }
@MainThread @MainThread
@@ -335,7 +328,7 @@ class AmneziaVpnService : VpnService() {
private fun connect(vpnConfig: String?) { private fun connect(vpnConfig: String?) {
if (isConnected || protocolState.value == CONNECTING) return if (isConnected || protocolState.value == CONNECTING) return
Log.d(TAG, "Start VPN connection") Log.v(TAG, "Start VPN connection")
protocolState.value = CONNECTING protocolState.value = CONNECTING
@@ -364,7 +357,7 @@ class AmneziaVpnService : VpnService() {
private fun disconnect() { private fun disconnect() {
if (isUnknown || isDisconnected || protocolState.value == DISCONNECTING) return if (isUnknown || isDisconnected || protocolState.value == DISCONNECTING) return
Log.d(TAG, "Stop VPN connection") Log.v(TAG, "Stop VPN connection")
protocolState.value = DISCONNECTING protocolState.value = DISCONNECTING
@@ -390,7 +383,7 @@ class AmneziaVpnService : VpnService() {
private fun reconnect() { private fun reconnect() {
if (!isConnected) return if (!isConnected) return
Log.d(TAG, "Reconnect VPN") Log.v(TAG, "Reconnect VPN")
protocolState.value = RECONNECTING protocolState.value = RECONNECTING
@@ -446,4 +439,10 @@ class AmneziaVpnService : VpnService() {
} else { } else {
true true
} }
private fun loadConfigFromPrefs(): String? =
Prefs.get(this).getString(PREFS_CONFIG_KEY, null)
private fun saveConfigToPrefs(config: String?) =
Prefs.get(this).edit().putString(PREFS_CONFIG_KEY, config).apply()
} }
@@ -29,20 +29,20 @@ class ImportConfigActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
Log.d(TAG, "Create Import Config Activity: $intent") Log.v(TAG, "Create Import Config Activity: $intent")
intent?.let(::readConfig) intent?.let(::readConfig)
} }
override fun onNewIntent(intent: Intent?) { override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent) super.onNewIntent(intent)
Log.d(TAG, "onNewIntent: $intent") Log.v(TAG, "onNewIntent: $intent")
intent?.let(::readConfig) intent?.let(::readConfig)
} }
private fun readConfig(intent: Intent) { private fun readConfig(intent: Intent) {
when (intent.action) { when (intent.action) {
ACTION_SEND -> { ACTION_SEND -> {
Log.d(TAG, "Process SEND action, type: ${intent.type}") Log.v(TAG, "Process SEND action, type: ${intent.type}")
when (intent.type) { when (intent.type) {
"application/octet-stream" -> { "application/octet-stream" -> {
intent.getUriCompat()?.let { uri -> intent.getUriCompat()?.let { uri ->
@@ -60,7 +60,7 @@ class ImportConfigActivity : ComponentActivity() {
} }
ACTION_VIEW -> { ACTION_VIEW -> {
Log.d(TAG, "Process VIEW action, scheme: ${intent.scheme}") Log.v(TAG, "Process VIEW action, scheme: ${intent.scheme}")
when (intent.scheme) { when (intent.scheme) {
"file", "content" -> { "file", "content" -> {
intent.data?.let { uri -> intent.data?.let { uri ->
@@ -128,7 +128,7 @@ class ImportConfigActivity : ComponentActivity() {
private fun startMainActivity(config: String) { private fun startMainActivity(config: String) {
if (config.isNotBlank()) { if (config.isNotBlank()) {
Log.d(TAG, "startMainActivity") Log.v(TAG, "startMainActivity")
Intent(applicationContext, AmneziaActivity::class.java).apply { Intent(applicationContext, AmneziaActivity::class.java).apply {
action = ACTION_IMPORT_CONFIG action = ACTION_IMPORT_CONFIG
addCategory(CATEGORY_DEFAULT) addCategory(CATEGORY_DEFAULT)
@@ -32,8 +32,7 @@ enum class Action : IpcMessage {
REGISTER_CLIENT, REGISTER_CLIENT,
CONNECT, CONNECT,
DISCONNECT, DISCONNECT,
REQUEST_STATUS, REQUEST_STATUS
SET_SAVE_LOGS
} }
fun <T> T.packToMessage(): Message fun <T> T.packToMessage(): Message
@@ -0,0 +1,25 @@
package org.amnezia.vpn
import android.content.Context
import android.content.SharedPreferences
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
import org.amnezia.vpn.util.Log
private const val TAG = "Prefs"
private const val PREFS_FILE = "org.amnezia.vpn.prefs"
private const val SECURE_PREFS_FILE = "$PREFS_FILE.secure"
object Prefs {
fun get(context: Context, appContext: Context = context.applicationContext): SharedPreferences =
try {
EncryptedSharedPreferences(
appContext,
SECURE_PREFS_FILE,
MasterKey(appContext)
)
} catch (e: Exception) {
Log.e(TAG, "Getting Encryption Storage failed: ${e.message}, plaintext fallback")
appContext.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE)
}
}
@@ -25,7 +25,7 @@ class VpnRequestActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
Log.d(TAG, "Start request activity") Log.v(TAG, "Start request activity")
val requestIntent = VpnService.prepare(applicationContext) val requestIntent = VpnService.prepare(applicationContext)
if (requestIntent != null) { if (requestIntent != null) {
if (getSystemService<KeyguardManager>()!!.isKeyguardLocked) { if (getSystemService<KeyguardManager>()!!.isKeyguardLocked) {
-4
View File
@@ -15,7 +15,3 @@ android {
buildConfig = true buildConfig = true
} }
} }
dependencies {
implementation(libs.androidx.security.crypto)
}
+25 -244
View File
@@ -1,252 +1,33 @@
package org.amnezia.vpn.util package org.amnezia.vpn.util
import android.content.Context
import android.icu.text.DateFormat
import android.icu.text.SimpleDateFormat
import android.os.Build
import android.os.Process
import java.io.File
import java.io.IOException
import java.io.RandomAccessFile
import java.nio.channels.FileChannel
import java.nio.channels.FileLock
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.Date
import java.util.Locale
import java.util.concurrent.locks.ReentrantLock
import org.amnezia.vpn.util.Log.Priority.D
import org.amnezia.vpn.util.Log.Priority.E
import org.amnezia.vpn.util.Log.Priority.F
import org.amnezia.vpn.util.Log.Priority.I
import org.amnezia.vpn.util.Log.Priority.V
import org.amnezia.vpn.util.Log.Priority.W
import android.util.Log as NativeLog import android.util.Log as NativeLog
private const val TAG = "Log" class Log {
private const val LOG_FILE_NAME = "amneziaVPN.log" companion object {
private const val ROTATE_LOG_FILE_NAME = "amneziaVPN.rotate.log" fun v(tag: String, msg: String) = debugLog(tag, msg, NativeLog::v)
private const val LOCK_FILE_NAME = ".lock"
private const val DATE_TIME_PATTERN = "MM-dd HH:mm:ss.SSS"
private const val PREFS_SAVE_LOGS_KEY = "SAVE_LOGS"
private const val LOG_MAX_FILE_SIZE = 1024 * 1024
/** fun d(tag: String, msg: String) = debugLog(tag, msg, NativeLog::d)
* | Priority | Save to file | Logcat logging |
* |-------------------|--------------|----------------------------------------------| fun i(tag: String, msg: String) = log(tag, msg, NativeLog::i)
* | Verbose | Don't save | Only in Debug build |
* | Debug | Save | In Debug build or if log saving is enabled | fun w(tag: String, msg: String) = log(tag, msg, NativeLog::w)
* | Info, Warn, Error | Save | Enabled |
* | Fatal (Assert) | Save | Enabled. Depending on system configuration, | fun e(tag: String, msg: String) = log(tag, msg, NativeLog::e)
* | | | create a report and/or terminate the process |
*/ fun v(tag: String, msg: Any?) = v(tag, msg.toString())
object Log {
private val dateTimeFormat: Any = fun d(tag: String, msg: Any?) = d(tag, msg.toString())
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) DateTimeFormatter.ofPattern(DATE_TIME_PATTERN)
else object : ThreadLocal<DateFormat>() { fun i(tag: String, msg: Any?) = i(tag, msg.toString())
override fun initialValue(): DateFormat = SimpleDateFormat(DATE_TIME_PATTERN, Locale.US)
fun w(tag: String, msg: Any?) = w(tag, msg.toString())
fun e(tag: String, msg: Any?) = e(tag, msg.toString())
private inline fun log(tag: String, msg: String, delegate: (String, String) -> Unit) = delegate(tag, msg)
private inline fun debugLog(tag: String, msg: String, delegate: (String, String) -> Unit) {
if (BuildConfig.DEBUG) delegate(tag, msg)
} }
private lateinit var logDir: File
private val logFile: File by lazy { File(logDir, LOG_FILE_NAME) }
private val rotateLogFile: File by lazy { File(logDir, ROTATE_LOG_FILE_NAME) }
private val fileLock: FileChannel by lazy { RandomAccessFile(File(logDir, LOCK_FILE_NAME).path, "rw").channel }
private val threadLock: ReentrantLock by lazy { ReentrantLock() }
@Volatile
private var _saveLogs: Boolean = false
var saveLogs: Boolean
get() = _saveLogs
set(value) {
if (_saveLogs != value) {
if (value && !logDir.exists() && !logDir.mkdir()) {
NativeLog.e(TAG, "Failed to create dir: $logDir")
return
}
_saveLogs = value
Prefs.save(PREFS_SAVE_LOGS_KEY, value)
}
}
@JvmStatic
fun v(tag: String, msg: String) = log(tag, msg, V)
@JvmStatic
fun d(tag: String, msg: String) = log(tag, msg, D)
@JvmStatic
fun i(tag: String, msg: String) = log(tag, msg, I)
@JvmStatic
fun w(tag: String, msg: String) = log(tag, msg, W)
@JvmStatic
fun e(tag: String, msg: String) = log(tag, msg, E)
@JvmStatic
fun f(tag: String, msg: String) = log(tag, msg, F)
fun v(tag: String, msg: Any?) = v(tag, msg.toString())
fun d(tag: String, msg: Any?) = d(tag, msg.toString())
fun i(tag: String, msg: Any?) = i(tag, msg.toString())
fun w(tag: String, msg: Any?) = w(tag, msg.toString())
fun e(tag: String, msg: Any?) = e(tag, msg.toString())
fun f(tag: String, msg: Any?) = f(tag, msg.toString())
fun init(context: Context) {
v(TAG, "Init Log")
logDir = File(context.cacheDir, "logs")
saveLogs = Prefs.load(PREFS_SAVE_LOGS_KEY)
}
fun getLogs(): String =
"${deviceInfo()}\n${readLogs()}\nLOGCAT:\n${getLogcat()}"
fun clearLogs() {
withLock {
logFile.delete()
rotateLogFile.delete()
}
}
private fun log(tag: String, msg: String, priority: Priority) {
if (saveLogs && priority != V) saveLogMsg(formatLogMsg(tag, msg, priority))
if (priority == F) {
NativeLog.wtf(tag, msg)
} else if (
(priority != V && priority != D) ||
(priority == V && BuildConfig.DEBUG) ||
(priority == D && (BuildConfig.DEBUG || saveLogs))
) {
NativeLog.println(priority.level, tag, msg)
}
}
private fun saveLogMsg(msg: String) {
withTryLock(condition = { logFile.length() > LOG_MAX_FILE_SIZE }) {
logFile.renameTo(rotateLogFile)
}
try {
logFile.appendText(msg)
} catch (e: IOException) {
NativeLog.e(TAG, "Failed to write log: $e")
}
}
private fun formatLogMsg(tag: String, msg: String, priority: Priority): String {
val date = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
LocalDateTime.now().format(dateTimeFormat as DateTimeFormatter)
} else {
@Suppress("UNCHECKED_CAST")
(dateTimeFormat as ThreadLocal<DateFormat>).get()?.format(Date())
}
return "$date ${Process.myPid()} ${Process.myTid()} $priority [${Thread.currentThread().name}] " +
"$tag: $msg\n"
}
private fun deviceInfo(): String {
val sb = StringBuilder()
sb.append("Model: ").appendLine(Build.MODEL)
sb.append("Brand: ").appendLine(Build.BRAND)
sb.append("Product: ").appendLine(Build.PRODUCT)
sb.append("Device: ").appendLine(Build.DEVICE)
sb.append("Codename: ").appendLine(Build.VERSION.CODENAME)
sb.append("Release: ").appendLine(Build.VERSION.RELEASE)
sb.append("SDK: ").appendLine(Build.VERSION.SDK_INT)
sb.append("ABI: ").appendLine(Build.SUPPORTED_ABIS.joinToString())
return sb.toString()
}
private fun readLogs(): String {
var logText = ""
withLock {
try {
if (rotateLogFile.exists()) logText = rotateLogFile.readText()
if (logFile.exists()) logText += logFile.readText()
} catch (e: IOException) {
val errorMsg = "Failed to read log: $e"
NativeLog.e(TAG, errorMsg)
logText += errorMsg
}
}
return logText
}
private fun getLogcat(): String {
try {
val process = ProcessBuilder("logcat", "-d").redirectErrorStream(true).start()
return process.inputStream.reader().readText()
} catch (e: IOException) {
val errorMsg = "Failed to get logcat log: $e"
NativeLog.e(TAG, errorMsg)
return errorMsg
}
}
private fun withLock(block: () -> Unit) {
threadLock.lock()
try {
var l: FileLock? = null
try {
l = fileLock.lock()
block()
} catch (e: IOException) {
NativeLog.e(TAG, "Failed to get file lock: $e")
} finally {
try {
l?.release()
} catch (e: IOException) {
NativeLog.e(TAG, "Failed to release file lock: $e")
}
}
} finally {
threadLock.unlock()
}
}
private fun withTryLock(condition: () -> Boolean, block: () -> Unit) {
if (condition()) {
if (threadLock.tryLock()) {
try {
if (condition()) {
var l: FileLock? = null
try {
l = fileLock.tryLock()
if (l != null) {
if (condition()) {
block()
}
}
} catch (e: IOException) {
NativeLog.e(TAG, "Failed to get file tryLock: $e")
} finally {
try {
l?.release()
} catch (e: IOException) {
NativeLog.e(TAG, "Failed to release file tryLock: $e")
}
}
}
} finally {
threadLock.unlock()
}
}
}
}
private enum class Priority(val level: Int) {
V(2),
D(3),
I(4),
W(5),
E(6),
F(7)
} }
} }
@@ -1,58 +0,0 @@
package org.amnezia.vpn.util
import android.app.Application
import android.content.Context
import android.content.SharedPreferences
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
import kotlin.reflect.typeOf
private const val TAG = "Prefs"
private const val PREFS_FILE = "org.amnezia.vpn.prefs"
private const val SECURE_PREFS_FILE = "$PREFS_FILE.secure"
object Prefs {
private lateinit var app: Application
val prefs: SharedPreferences
get() = try {
EncryptedSharedPreferences(
app,
SECURE_PREFS_FILE,
MasterKey(app)
)
} catch (e: Exception) {
Log.e(TAG, "Getting Encryption Storage failed: $e, plaintext fallback")
app.getSharedPreferences(PREFS_FILE, Context.MODE_PRIVATE)
}
fun init(app: Application) {
Log.v(TAG, "Init Prefs")
this.app = app
}
fun save(key: String, value: Boolean) =
prefs.edit().putBoolean(key, value).apply()
fun save(key: String, value: String?) =
prefs.edit().putString(key, value).apply()
fun save(key: String, value: Int) =
prefs.edit().putInt(key, value).apply()
fun save(key: String, value: Long) =
prefs.edit().putLong(key, value).apply()
fun save(key: String, value: Float) =
prefs.edit().putFloat(key, value).apply()
inline fun <reified T> load(key: String): T {
return when (typeOf<T>()) {
typeOf<Boolean>() -> prefs.getBoolean(key, false)
typeOf<String>() -> prefs.getString(key, "")
typeOf<Int>() -> prefs.getInt(key, 0)
typeOf<Long>() -> prefs.getLong(key, 0L)
typeOf<Float>() -> prefs.getFloat(key, 0f)
else -> throw IllegalArgumentException("SharedPreferences does not support type: ${typeOf<T>()}")
} as T
}
}
@@ -82,7 +82,7 @@ class NetworkState(
fun bindNetworkListener() { fun bindNetworkListener() {
if (isListenerBound) return if (isListenerBound) return
Log.d(TAG, "Bind network listener") Log.v(TAG, "Bind network listener")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
connectivityManager.registerBestMatchingNetworkCallback(networkRequest, networkCallback, handler) connectivityManager.registerBestMatchingNetworkCallback(networkRequest, networkCallback, handler)
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@@ -95,7 +95,7 @@ class NetworkState(
fun unbindNetworkListener() { fun unbindNetworkListener() {
if (!isListenerBound) return if (!isListenerBound) return
Log.d(TAG, "Unbind network listener") Log.v(TAG, "Unbind network listener")
connectivityManager.unregisterNetworkCallback(networkCallback) connectivityManager.unregisterNetworkCallback(networkCallback)
isListenerBound = false isListenerBound = false
currentNetwork = null currentNetwork = null
@@ -99,10 +99,7 @@ open class Wireguard : Protocol() {
} }
protected fun WireguardConfig.Builder.configWireguard(configData: Map<String, String>) { protected fun WireguardConfig.Builder.configWireguard(configData: Map<String, String>) {
configData["Address"]?.split(",")?.map { address -> configData["Address"]?.let { addAddress(InetNetwork.parse(it)) }
InetNetwork.parse(address.trim())
}?.forEach(::addAddress)
configData["DNS"]?.split(",")?.map { dns -> configData["DNS"]?.split(",")?.map { dns ->
parseInetAddress(dns.trim()) parseInetAddress(dns.trim())
}?.forEach(::addDnsServer) }?.forEach(::addDnsServer)
@@ -11,7 +11,7 @@ open class WireguardConfig protected constructor(
val endpoint: InetEndpoint, val endpoint: InetEndpoint,
val persistentKeepalive: Int, val persistentKeepalive: Int,
val publicKeyHex: String, val publicKeyHex: String,
val preSharedKeyHex: String?, val preSharedKeyHex: String,
val privateKeyHex: String val privateKeyHex: String
) : ProtocolConfig(protocolConfigBuilder) { ) : ProtocolConfig(protocolConfigBuilder) {
@@ -43,8 +43,7 @@ open class WireguardConfig protected constructor(
appendLine("endpoint=$endpoint") appendLine("endpoint=$endpoint")
if (persistentKeepalive != 0) if (persistentKeepalive != 0)
appendLine("persistent_keepalive_interval=$persistentKeepalive") appendLine("persistent_keepalive_interval=$persistentKeepalive")
if (preSharedKeyHex != null) appendLine("preshared_key=$preSharedKeyHex")
appendLine("preshared_key=$preSharedKeyHex")
} }
open class Builder : ProtocolConfig.Builder(true) { open class Builder : ProtocolConfig.Builder(true) {
@@ -57,7 +56,7 @@ open class WireguardConfig protected constructor(
internal lateinit var publicKeyHex: String internal lateinit var publicKeyHex: String
private set private set
internal var preSharedKeyHex: String? = null internal lateinit var preSharedKeyHex: String
private set private set
internal lateinit var privateKeyHex: String internal lateinit var privateKeyHex: String
@@ -75,7 +74,7 @@ open class WireguardConfig protected constructor(
fun setPrivateKeyHex(privateKeyHex: String) = apply { this.privateKeyHex = privateKeyHex } fun setPrivateKeyHex(privateKeyHex: String) = apply { this.privateKeyHex = privateKeyHex }
override fun build(): WireguardConfig = configBuild().run { WireguardConfig(this@Builder) } override fun build(): WireguardConfig = WireguardConfig(this)
} }
companion object { companion object {
+1 -1
View File
@@ -97,7 +97,7 @@ target_compile_options(${PROJECT} PRIVATE
-DVPN_NE_BUNDLEID=\"${BUILD_IOS_APP_IDENTIFIER}.network-extension\" -DVPN_NE_BUNDLEID=\"${BUILD_IOS_APP_IDENTIFIER}.network-extension\"
) )
set(WG_APPLE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rd/amneziawg-apple/Sources) set(WG_APPLE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rd/awg-apple/Sources)
target_sources(${PROJECT} PRIVATE target_sources(${PROJECT} PRIVATE
# ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosvpnprotocol.swift # ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosvpnprotocol.swift
+19 -21
View File
@@ -118,33 +118,31 @@ QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentia
return QJsonDocument(jConfig).toJson(); return QJsonDocument(jConfig).toJson();
} }
QString OpenVpnConfigurator::processConfigWithLocalSettings(QString jsonConfig, const int serverIndex) QString OpenVpnConfigurator::processConfigWithLocalSettings(QString jsonConfig)
{ {
QJsonObject json = QJsonDocument::fromJson(jsonConfig.toUtf8()).object(); QJsonObject json = QJsonDocument::fromJson(jsonConfig.toUtf8()).object();
QString config = json[config_key::config].toString(); QString config = json[config_key::config].toString();
if (!m_settings->server(serverIndex).value(config_key::configVersion).toInt()) { QRegularExpression regex("redirect-gateway.*");
QRegularExpression regex("redirect-gateway.*"); config.replace(regex, "");
config.replace(regex, "");
if (m_settings->routeMode() == Settings::VpnAllSites) { if (m_settings->routeMode() == Settings::VpnAllSites) {
config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n"); config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n");
// Prevent ipv6 leak // Prevent ipv6 leak
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n"); config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
config.append("block-ipv6\n"); config.append("block-ipv6\n");
} }
if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) { if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
// no redirect-gateway // no redirect-gateway
} }
if (m_settings->routeMode() == Settings::VpnAllExceptSites) { if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
#ifndef Q_OS_ANDROID #ifndef Q_OS_ANDROID
config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n"); config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n");
#endif #endif
// Prevent ipv6 leak // Prevent ipv6 leak
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n"); config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
config.append("block-ipv6\n"); config.append("block-ipv6\n");
}
} }
#ifndef MZ_WINDOWS #ifndef MZ_WINDOWS
+1 -1
View File
@@ -26,7 +26,7 @@ public:
QString genOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container, QString genOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode = nullptr); const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode = nullptr);
QString processConfigWithLocalSettings(QString jsonConfig, const int serverIndex); QString processConfigWithLocalSettings(QString jsonConfig);
QString processConfigWithExportSettings(QString jsonConfig); QString processConfigWithExportSettings(QString jsonConfig);
ErrorCode signCert(DockerContainer container, ErrorCode signCert(DockerContainer container,
+1 -1
View File
@@ -92,7 +92,7 @@ QString &VpnConfigurator::processConfigWithLocalSettings(int serverIndex, Docker
processConfigWithDnsSettings(serverIndex, container, proto, config); processConfigWithDnsSettings(serverIndex, container, proto, config);
if (proto == Proto::OpenVpn) { if (proto == Proto::OpenVpn) {
config = openVpnConfigurator->processConfigWithLocalSettings(config, serverIndex); config = openVpnConfigurator->processConfigWithLocalSettings(config);
} }
return config; return config;
} }
@@ -31,11 +31,11 @@ public:
QString processConfigWithLocalSettings(QString config); QString processConfigWithLocalSettings(QString config);
QString processConfigWithExportSettings(QString config); QString processConfigWithExportSettings(QString config);
static ConnectionData genClientKeys();
private: private:
ConnectionData prepareWireguardConfig(const ServerCredentials &credentials, DockerContainer container, ConnectionData prepareWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr); const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
ConnectionData genClientKeys();
bool m_isAwg; bool m_isAwg;
QString m_serverConfigPath; QString m_serverConfigPath;
+7 -7
View File
@@ -98,11 +98,11 @@ QMap<DockerContainer, QString> ContainerProps::containerDescriptions()
QObject::tr("OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its " QObject::tr("OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its "
"own security protocol with SSL/TLS for key exchange.") }, "own security protocol with SSL/TLS for key exchange.") },
{ DockerContainer::ShadowSocks, { DockerContainer::ShadowSocks,
QObject::tr("ShadowSocks - masks VPN traffic, making it similar to normal web traffic, but it " QObject::tr("ShadowSocks - masks VPN traffic, making it similar to normal web traffic, but is "
"may be recognized by analysis systems in some highly censored regions.") }, "recognised by analysis systems in some highly censored regions.") },
{ DockerContainer::Cloak, { DockerContainer::Cloak,
QObject::tr("OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against " QObject::tr("OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against "
"active-probing detection. Ideal for bypassing blocking in regions with the highest levels " "active-probbing detection. Ideal for bypassing blocking in regions with the highest levels "
"of censorship.") }, "of censorship.") },
{ DockerContainer::WireGuard, { DockerContainer::WireGuard,
QObject::tr("WireGuard - New popular VPN protocol with high performance, high speed and low power " QObject::tr("WireGuard - New popular VPN protocol with high performance, high speed and low power "
@@ -119,7 +119,7 @@ QMap<DockerContainer, QString> ContainerProps::containerDescriptions()
{ DockerContainer::Dns, { DockerContainer::Dns,
QObject::tr("Replace the current DNS server with your own. This will increase your privacy level.") }, QObject::tr("Replace the current DNS server with your own. This will increase your privacy level.") },
{ DockerContainer::Sftp, { DockerContainer::Sftp,
QObject::tr("Create a file vault on your server to securely store and transfer files.") } }; QObject::tr("Creates a file vault on your server to securely store and transfer files.") } };
} }
QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions() QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
@@ -153,8 +153,8 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
"* Works over TCP network protocol.") }, "* Works over TCP network protocol.") },
{ DockerContainer::Cloak, { DockerContainer::Cloak,
QObject::tr("This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for " QObject::tr("This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for "
"protecting against blocking.\n\n" "blocking protection.\n\n"
"OpenVPN provides a secure VPN connection by encrypting all internet traffic between the client " "OpenVPN provides a secure VPN connection by encrypting all Internet traffic between the client "
"and the server.\n\n" "and the server.\n\n"
"Cloak protects OpenVPN from detection and blocking. \n\n" "Cloak protects OpenVPN from detection and blocking. \n\n"
"Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, " "Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, "
@@ -172,7 +172,7 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
"* Works over TCP network protocol, 443 port.\n") }, "* Works over TCP network protocol, 443 port.\n") },
{ DockerContainer::WireGuard, { DockerContainer::WireGuard,
QObject::tr("A relatively new popular VPN protocol with a simplified architecture.\n" QObject::tr("A relatively new popular VPN protocol with a simplified architecture.\n"
"WireGuard provides stable VPN connection and high performance on all devices. It uses hard-coded encryption " "Provides stable VPN connection, high performance on all devices. Uses hard-coded encryption "
"settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput.\n" "settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput.\n"
"WireGuard is very susceptible to blocking due to its distinct packet signatures. " "WireGuard is very susceptible to blocking due to its distinct packet signatures. "
"Unlike some other VPN protocols that employ obfuscation techniques, " "Unlike some other VPN protocols that employ obfuscation techniques, "
@@ -225,24 +225,6 @@ ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credential
return ErrorCode::NoError; return ErrorCode::NoError;
} }
ErrorCode ServerController::rebootServer(const ServerCredentials &credentials)
{
QString script = QString("sudo reboot");
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data;
return ErrorCode::NoError;
};
auto cbReadStdErr = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};
return runScript(credentials, script, cbReadStdOut, cbReadStdErr);
}
ErrorCode ServerController::removeAllContainers(const ServerCredentials &credentials) ErrorCode ServerController::removeAllContainers(const ServerCredentials &credentials)
{ {
return runScript(credentials, amnezia::scriptData(SharedScriptType::remove_all_containers)); return runScript(credentials, amnezia::scriptData(SharedScriptType::remove_all_containers));
@@ -22,7 +22,6 @@ public:
typedef QList<QPair<QString, QString>> Vars; typedef QList<QPair<QString, QString>> Vars;
ErrorCode rebootServer(const ServerCredentials &credentials);
ErrorCode removeAllContainers(const ServerCredentials &credentials); ErrorCode removeAllContainers(const ServerCredentials &credentials);
ErrorCode removeContainer(const ServerCredentials &credentials, DockerContainer container); ErrorCode removeContainer(const ServerCredentials &credentials, DockerContainer container);
ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config, ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config,
+59 -74
View File
@@ -4,92 +4,77 @@
#include <QMetaEnum> #include <QMetaEnum>
#include <QObject> #include <QObject>
namespace amnezia namespace amnezia {
constexpr const qint16 qrMagicCode = 1984;
struct ServerCredentials
{ {
QString hostName;
QString userName;
QString secretData;
int port = 22;
constexpr const qint16 qrMagicCode = 1984; bool isValid() const { return !hostName.isEmpty() && !userName.isEmpty() && !secretData.isEmpty() && port > 0; }
};
struct ServerCredentials enum ErrorCode
{ {
QString hostName; // General error codes
QString userName; NoError = 0,
QString secretData; UnknownError,
int port = 22; InternalError,
NotImplementedError,
bool isValid() const // Server errors
{ ServerCheckFailed,
return !hostName.isEmpty() && !userName.isEmpty() && !secretData.isEmpty() && port > 0; ServerPortAlreadyAllocatedError,
} ServerContainerMissingError,
}; ServerDockerFailedError,
ServerCancelInstallation,
ServerUserNotInSudo,
ServerPacketManagerError,
enum ErrorCode { // Ssh connection errors
// General error codes SshRequestDeniedError, SshInterruptedError, SshInternalError,
NoError = 0, SshPrivateKeyError, SshPrivateKeyFormatError, SshTimeoutError,
UnknownError = 100,
InternalError = 101,
NotImplementedError = 102,
// Server errors // Ssh sftp errors
ServerCheckFailed = 200, SshSftpEofError, SshSftpNoSuchFileError, SshSftpPermissionDeniedError,
ServerPortAlreadyAllocatedError = 201, SshSftpFailureError, SshSftpBadMessageError, SshSftpNoConnectionError,
ServerContainerMissingError = 202, SshSftpConnectionLostError, SshSftpOpUnsupportedError, SshSftpInvalidHandleError,
ServerDockerFailedError = 203, SshSftpNoSuchPathError, SshSftpFileAlreadyExistsError, SshSftpWriteProtectError,
ServerCancelInstallation = 204, SshSftpNoMediaError,
ServerUserNotInSudo = 205,
ServerPacketManagerError = 206,
// Ssh connection errors // Local errors
SshRequestDeniedError = 300, OpenVpnConfigMissing,
SshInterruptedError = 301, OpenVpnManagementServerError,
SshInternalError = 302, ConfigMissing,
SshPrivateKeyError = 303,
SshPrivateKeyFormatError = 304,
SshTimeoutError = 305,
// Ssh sftp errors // Distro errors
SshSftpEofError = 400, OpenVpnExecutableMissing,
SshSftpNoSuchFileError = 401, ShadowSocksExecutableMissing,
SshSftpPermissionDeniedError = 402, CloakExecutableMissing,
SshSftpFailureError = 403, AmneziaServiceConnectionFailed,
SshSftpBadMessageError = 404, ExecutableMissing,
SshSftpNoConnectionError = 405,
SshSftpConnectionLostError = 406,
SshSftpOpUnsupportedError = 407,
SshSftpInvalidHandleError = 408,
SshSftpNoSuchPathError = 409,
SshSftpFileAlreadyExistsError = 410,
SshSftpWriteProtectError = 411,
SshSftpNoMediaError = 412,
// Local errors // VPN errors
OpenVpnConfigMissing = 500, OpenVpnAdaptersInUseError,
OpenVpnManagementServerError = 501, OpenVpnUnknownError,
ConfigMissing = 502, OpenVpnTapAdapterError,
AddressPoolError,
// Distro errors // 3rd party utils errors
OpenVpnExecutableMissing = 600, OpenSslFailed,
ShadowSocksExecutableMissing = 601, ShadowSocksExecutableCrashed,
CloakExecutableMissing = 602, CloakExecutableCrashed,
AmneziaServiceConnectionFailed = 603,
ExecutableMissing = 604,
// VPN errors // import and install errors
OpenVpnAdaptersInUseError = 700, ImportInvalidConfigError,
OpenVpnUnknownError = 701,
OpenVpnTapAdapterError = 702,
AddressPoolError = 703,
// 3rd party utils errors // Android errors
OpenSslFailed = 800, AndroidError
ShadowSocksExecutableCrashed = 801, };
CloakExecutableCrashed = 802,
// import and install errors
ImportInvalidConfigError = 900,
// Android errors
AndroidError = 1000
};
} // namespace amnezia } // namespace amnezia
+42 -46
View File
@@ -2,74 +2,70 @@
using namespace amnezia; using namespace amnezia;
QString errorString(ErrorCode code) { QString errorString(ErrorCode code){
QString errorMessage;
switch (code) { switch (code) {
// General error codes // General error codes
case(NoError): errorMessage = QObject::tr("No error"); break; case(NoError): return QObject::tr("No error");
case(UnknownError): errorMessage = QObject::tr("Unknown Error"); break; case(UnknownError): return QObject::tr("Unknown Error");
case(NotImplementedError): errorMessage = QObject::tr("Function not implemented"); break; case(NotImplementedError): return QObject::tr("Function not implemented");
// Server errors // Server errors
case(ServerCheckFailed): errorMessage = QObject::tr("Server check failed"); break; case(ServerCheckFailed): return QObject::tr("Server check failed");
case(ServerPortAlreadyAllocatedError): errorMessage = QObject::tr("Server port already used. Check for another software"); break; case(ServerPortAlreadyAllocatedError): return QObject::tr("Server port already used. Check for another software");
case(ServerContainerMissingError): errorMessage = QObject::tr("Server error: Docker container missing"); break; case(ServerContainerMissingError): return QObject::tr("Server error: Docker container missing");
case(ServerDockerFailedError): errorMessage = QObject::tr("Server error: Docker failed"); break; case(ServerDockerFailedError): return QObject::tr("Server error: Docker failed");
case(ServerCancelInstallation): errorMessage = QObject::tr("Installation canceled by user"); break; case(ServerCancelInstallation): return QObject::tr("Installation canceled by user");
case(ServerUserNotInSudo): errorMessage = QObject::tr("The user does not have permission to use sudo"); break; case(ServerUserNotInSudo): return QObject::tr("The user does not have permission to use sudo");
// Libssh errors // Libssh errors
case(SshRequestDeniedError): errorMessage = QObject::tr("Ssh request was denied"); break; case(SshRequestDeniedError): return QObject::tr("Ssh request was denied");
case(SshInterruptedError): errorMessage = QObject::tr("Ssh request was interrupted"); break; case(SshInterruptedError): return QObject::tr("Ssh request was interrupted");
case(SshInternalError): errorMessage = QObject::tr("Ssh internal error"); break; case(SshInternalError): return QObject::tr("Ssh internal error");
case(SshPrivateKeyError): errorMessage = QObject::tr("Invalid private key or invalid passphrase entered"); break; case(SshPrivateKeyError): return QObject::tr("Invalid private key or invalid passphrase entered");
case(SshPrivateKeyFormatError): errorMessage = QObject::tr("The selected private key format is not supported, use openssh ED25519 key types or PEM key types"); break; case(SshPrivateKeyFormatError): return QObject::tr("The selected private key format is not supported, use openssh ED25519 key types or PEM key types");
case(SshTimeoutError): errorMessage = QObject::tr("Timeout connecting to server"); break; case(SshTimeoutError): return QObject::tr("Timeout connecting to server");
// Libssh sftp errors // Libssh sftp errors
case(SshSftpEofError): errorMessage = QObject::tr("Sftp error: End-of-file encountered"); break; case(SshSftpEofError): return QObject::tr("Sftp error: End-of-file encountered");
case(SshSftpNoSuchFileError): errorMessage = QObject::tr("Sftp error: File does not exist"); break; case(SshSftpNoSuchFileError): return QObject::tr("Sftp error: File does not exist");
case(SshSftpPermissionDeniedError): errorMessage = QObject::tr("Sftp error: Permission denied"); break; case(SshSftpPermissionDeniedError): return QObject::tr("Sftp error: Permission denied");
case(SshSftpFailureError): errorMessage = QObject::tr("Sftp error: Generic failure"); break; case(SshSftpFailureError): return QObject::tr("Sftp error: Generic failure");
case(SshSftpBadMessageError): errorMessage = QObject::tr("Sftp error: Garbage received from server"); break; case(SshSftpBadMessageError): return QObject::tr("Sftp error: Garbage received from server");
case(SshSftpNoConnectionError): errorMessage = QObject::tr("Sftp error: No connection has been set up"); break; case(SshSftpNoConnectionError): return QObject::tr("Sftp error: No connection has been set up");
case(SshSftpConnectionLostError): errorMessage = QObject::tr("Sftp error: There was a connection, but we lost it"); break; case(SshSftpConnectionLostError): return QObject::tr("Sftp error: There was a connection, but we lost it");
case(SshSftpOpUnsupportedError): errorMessage = QObject::tr("Sftp error: Operation not supported by libssh yet"); break; case(SshSftpOpUnsupportedError): return QObject::tr("Sftp error: Operation not supported by libssh yet");
case(SshSftpInvalidHandleError): errorMessage = QObject::tr("Sftp error: Invalid file handle"); break; case(SshSftpInvalidHandleError): return QObject::tr("Sftp error: Invalid file handle");
case(SshSftpNoSuchPathError): errorMessage = QObject::tr("Sftp error: No such file or directory path exists"); break; case(SshSftpNoSuchPathError): return QObject::tr("Sftp error: No such file or directory path exists");
case(SshSftpFileAlreadyExistsError): errorMessage = QObject::tr("Sftp error: An attempt to create an already existing file or directory has been made"); break; case(SshSftpFileAlreadyExistsError): return QObject::tr("Sftp error: An attempt to create an already existing file or directory has been made");
case(SshSftpWriteProtectError): errorMessage = QObject::tr("Sftp error: Write-protected filesystem"); break; case(SshSftpWriteProtectError): return QObject::tr("Sftp error: Write-protected filesystem");
case(SshSftpNoMediaError): errorMessage = QObject::tr("Sftp error: No media was in remote drive"); break; case(SshSftpNoMediaError): return QObject::tr("Sftp error: No media was in remote drive");
// Local errors // Local errors
case (OpenVpnConfigMissing): errorMessage = QObject::tr("OpenVPN config missing"); break; case (OpenVpnConfigMissing): return QObject::tr("OpenVPN config missing");
case (OpenVpnManagementServerError): errorMessage = QObject::tr("OpenVPN management server error"); break; case (OpenVpnManagementServerError): return QObject::tr("OpenVPN management server error");
// Distro errors // Distro errors
case (OpenVpnExecutableMissing): errorMessage = QObject::tr("OpenVPN executable missing"); break; case (OpenVpnExecutableMissing): return QObject::tr("OpenVPN executable missing");
case (ShadowSocksExecutableMissing): errorMessage = QObject::tr("ShadowSocks (ss-local) executable missing"); break; case (ShadowSocksExecutableMissing): return QObject::tr("ShadowSocks (ss-local) executable missing");
case (CloakExecutableMissing): errorMessage = QObject::tr("Cloak (ck-client) executable missing"); break; case (CloakExecutableMissing): return QObject::tr("Cloak (ck-client) executable missing");
case (AmneziaServiceConnectionFailed): errorMessage = QObject::tr("Amnezia helper service error"); break; case (AmneziaServiceConnectionFailed): return QObject::tr("Amnezia helper service error");
case (OpenSslFailed): errorMessage = QObject::tr("OpenSSL failed"); break; case (OpenSslFailed): return QObject::tr("OpenSSL failed");
// VPN errors // VPN errors
case (OpenVpnAdaptersInUseError): errorMessage = QObject::tr("Can't connect: another VPN connection is active"); break; case (OpenVpnAdaptersInUseError): return QObject::tr("Can't connect: another VPN connection is active");
case (OpenVpnTapAdapterError): errorMessage = QObject::tr("Can't setup OpenVPN TAP network adapter"); break; case (OpenVpnTapAdapterError): return QObject::tr("Can't setup OpenVPN TAP network adapter");
case (AddressPoolError): errorMessage = QObject::tr("VPN pool error: no available addresses"); break; case (AddressPoolError): return QObject::tr("VPN pool error: no available addresses");
case (ImportInvalidConfigError): errorMessage = QObject::tr("The config does not contain any containers and credentials for connecting to the server"); break; case (ImportInvalidConfigError): return QObject::tr("The config does not contain any containers and credentials for connecting to the server");
// Android errors // Android errors
case (AndroidError): errorMessage = QObject::tr("VPN connection error"); break; case (AndroidError): return QObject::tr("VPN connection error");
case(InternalError): case(InternalError):
default: default:
errorMessage = QObject::tr("Internal error"); break; return QObject::tr("Internal error");
} }
return QObject::tr("ErrorCode: %1. ").arg(code) + errorMessage;
} }
QDebug operator<<(QDebug debug, const ErrorCode &e) QDebug operator<<(QDebug debug, const ErrorCode &e)
-2
View File
@@ -33,7 +33,6 @@ QString amnezia::scriptName(SharedScriptType type)
case SharedScriptType::check_connection: return QLatin1String("check_connection.sh"); case SharedScriptType::check_connection: return QLatin1String("check_connection.sh");
case SharedScriptType::check_server_is_busy: return QLatin1String("check_server_is_busy.sh"); case SharedScriptType::check_server_is_busy: return QLatin1String("check_server_is_busy.sh");
case SharedScriptType::check_user_in_sudo: return QLatin1String("check_user_in_sudo.sh"); case SharedScriptType::check_user_in_sudo: return QLatin1String("check_user_in_sudo.sh");
default: return QString();
} }
} }
@@ -47,7 +46,6 @@ QString amnezia::scriptName(ProtocolScriptType type)
case ProtocolScriptType::openvpn_template: return QLatin1String("template.ovpn"); case ProtocolScriptType::openvpn_template: return QLatin1String("template.ovpn");
case ProtocolScriptType::wireguard_template: return QLatin1String("template.conf"); case ProtocolScriptType::wireguard_template: return QLatin1String("template.conf");
case ProtocolScriptType::awg_template: return QLatin1String("template.conf"); case ProtocolScriptType::awg_template: return QLatin1String("template.conf");
default: return QString();
} }
} }
+1 -1
View File
@@ -58,7 +58,7 @@ target_link_libraries(networkextension PRIVATE ${FW_UI_KIT})
target_compile_options(networkextension PRIVATE -DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\") target_compile_options(networkextension PRIVATE -DGROUP_ID=\"${BUILD_IOS_GROUP_IDENTIFIER}\")
target_compile_options(networkextension PRIVATE -DNETWORK_EXTENSION=1) target_compile_options(networkextension PRIVATE -DNETWORK_EXTENSION=1)
set(WG_APPLE_SOURCE_DIR ${CLIENT_ROOT_DIR}/3rd/amneziawg-apple/Sources) set(WG_APPLE_SOURCE_DIR ${CLIENT_ROOT_DIR}/3rd/awg-apple/Sources)
target_sources(networkextension PRIVATE target_sources(networkextension PRIVATE
${WG_APPLE_SOURCE_DIR}/WireGuardKit/WireGuardAdapter.swift ${WG_APPLE_SOURCE_DIR}/WireGuardKit/WireGuardAdapter.swift
@@ -1,6 +1,6 @@
#include "wireguard-go-version.h" #include "wireguard-go-version.h"
#include "3rd/amneziawg-apple/Sources/WireGuardKitGo/wireguard.h" #include "3rd/awg-apple/Sources/WireGuardKitGo/wireguard.h"
#include "3rd/amneziawg-apple/Sources/WireGuardKitC/WireGuardKitC.h" #include "3rd/awg-apple/Sources/WireGuardKitC/WireGuardKitC.h"
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
+1 -1
View File
@@ -29,7 +29,7 @@ void debugMessageHandler(QtMsgType type, const QMessageLogContext& context, cons
} }
// Skip annoying messages from Qt // Skip annoying messages from Qt
if (msg.startsWith("Unknown property") || msg.startsWith("Could not create pixmap") || msg.startsWith("Populating font") || msg.startsWith("stale focus object")) { if (msg.startsWith("Unknown property") || msg.startsWith("Could not create pixmap") || msg.startsWith("Populating font")) {
return; return;
} }
+10 -110
View File
@@ -12,15 +12,13 @@ namespace
AndroidController *s_instance = nullptr; AndroidController *s_instance = nullptr;
constexpr auto QT_ANDROID_CONTROLLER_CLASS = "org/amnezia/vpn/qt/QtAndroidController"; constexpr auto QT_ANDROID_CONTROLLER_CLASS = "org/amnezia/vpn/qt/QtAndroidController";
constexpr auto ANDROID_LOG_CLASS = "org/amnezia/vpn/util/Log";
constexpr auto TAG = "AmneziaQt";
} // namespace } // namespace
AndroidController::AndroidController() : QObject() AndroidController::AndroidController() : QObject()
{ {
connect(this, &AndroidController::status, this, connect(this, &AndroidController::status, this,
[this](AndroidController::ConnectionState state) { [this](AndroidController::ConnectionState state) {
qDebug() << "Android event: status =" << textConnectionState(state); qDebug() << "Android event: status; state:" << textConnectionState(state);
if (isWaitingStatus) { if (isWaitingStatus) {
qDebug() << "Initialization by service status"; qDebug() << "Initialization by service status";
isWaitingStatus = false; isWaitingStatus = false;
@@ -128,19 +126,24 @@ bool AndroidController::initialize()
// static // static
template <typename Ret, typename ...Args> template <typename Ret, typename ...Args>
auto AndroidController::callActivityMethod(const char *methodName, const char *signature, Args &&...args) auto AndroidController::callActivityMethod(const char *methodName, const char *signature,
const std::function<Ret()> &defValue, Args &&...args)
{ {
qDebug() << "Call activity method:" << methodName; qDebug() << "Call activity method:" << methodName;
QJniObject activity = AndroidUtils::getActivity(); QJniObject activity = AndroidUtils::getActivity();
Q_ASSERT(activity.isValid()); if (activity.isValid()) {
return activity.callMethod<Ret>(methodName, signature, std::forward<Args>(args)...); return activity.callMethod<Ret>(methodName, signature, std::forward<Args>(args)...);
} else {
qCritical() << "Activity is not valid";
return defValue();
}
} }
// static // static
template <typename ...Args> template <typename ...Args>
void AndroidController::callActivityMethod(const char *methodName, const char *signature, Args &&...args) void AndroidController::callActivityMethod(const char *methodName, const char *signature, Args &&...args)
{ {
callActivityMethod<void>(methodName, signature, std::forward<Args>(args)...); callActivityMethod<void>(methodName, signature, [] {}, std::forward<Args>(args)...);
} }
ErrorCode AndroidController::start(const QJsonObject &vpnConfig) ErrorCode AndroidController::start(const QJsonObject &vpnConfig)
@@ -191,114 +194,11 @@ void AndroidController::setNotificationText(const QString &title, const QString
(jint) timerSec); (jint) timerSec);
} }
bool AndroidController::isCameraPresent()
{
return callActivityMethod<jboolean>("isCameraPresent", "()Z");
}
void AndroidController::startQrReaderActivity() void AndroidController::startQrReaderActivity()
{ {
callActivityMethod("startQrCodeReader", "()V"); callActivityMethod("startQrCodeReader", "()V");
} }
void AndroidController::setSaveLogs(bool enabled)
{
callActivityMethod("setSaveLogs", "(Z)V", enabled);
}
void AndroidController::exportLogsFile(const QString &fileName)
{
callActivityMethod("exportLogsFile", "(Ljava/lang/String;)V",
QJniObject::fromString(fileName).object<jstring>());
}
void AndroidController::clearLogs()
{
callActivityMethod("clearLogs", "()V");
}
// Moving log processing to the Android side
jclass AndroidController::log;
jmethodID AndroidController::logDebug;
jmethodID AndroidController::logInfo;
jmethodID AndroidController::logWarning;
jmethodID AndroidController::logError;
jmethodID AndroidController::logFatal;
// static
bool AndroidController::initLogging()
{
QJniEnvironment env;
log = env.findClass(ANDROID_LOG_CLASS);
if (log == nullptr) {
qCritical() << "Android log class" << ANDROID_LOG_CLASS << "not found";
return false;
}
auto logMethodSignature = "(Ljava/lang/String;Ljava/lang/String;)V";
logDebug = env.findStaticMethod(log, "d", logMethodSignature);
if (logDebug == nullptr) {
qCritical() << "Android debug log method not found";
return false;
}
logInfo = env.findStaticMethod(log, "i", logMethodSignature);
if (logInfo == nullptr) {
qCritical() << "Android info log method not found";
return false;
}
logWarning = env.findStaticMethod(log, "w", logMethodSignature);
if (logWarning == nullptr) {
qCritical() << "Android warning log method not found";
return false;
}
logError = env.findStaticMethod(log, "e", logMethodSignature);
if (logError == nullptr) {
qCritical() << "Android error log method not found";
return false;
}
logFatal = env.findStaticMethod(log, "f", logMethodSignature);
if (logFatal == nullptr) {
qCritical() << "Android fatal log method not found";
return false;
}
qInstallMessageHandler(messageHandler);
return true;
}
// static
void AndroidController::messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message)
{
jmethodID logMethod = logDebug;
switch (type) {
case QtDebugMsg:
logMethod = logDebug;
break;
case QtInfoMsg:
logMethod = logInfo;
break;
case QtWarningMsg:
logMethod = logWarning;
break;
case QtCriticalMsg:
logMethod = logError;
break;
case QtFatalMsg:
logMethod = logFatal;
break;
}
QString formattedMessage = qFormatLogMessage(type, context, message);
QJniObject::callStaticMethod<void>(log, logMethod,
QJniObject::fromString(TAG).object<jstring>(),
QJniObject::fromString(formattedMessage).object<jstring>());
}
void AndroidController::qtAndroidControllerInitialized() void AndroidController::qtAndroidControllerInitialized()
{ {
callActivityMethod("qtAndroidControllerInitialized", "()V"); callActivityMethod("qtAndroidControllerInitialized", "()V");
+2 -15
View File
@@ -33,14 +33,7 @@ public:
void setNotificationText(const QString &title, const QString &message, int timerSec); void setNotificationText(const QString &title, const QString &message, int timerSec);
void saveFile(const QString &fileName, const QString &data); void saveFile(const QString &fileName, const QString &data);
QString openFile(const QString &filter); QString openFile(const QString &filter);
bool isCameraPresent();
void startQrReaderActivity(); void startQrReaderActivity();
void setSaveLogs(bool enabled);
void exportLogsFile(const QString &fileName);
void clearLogs();
static bool initLogging();
static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message);
signals: signals:
void connectionStateChanged(Vpn::ConnectionState state); void connectionStateChanged(Vpn::ConnectionState state);
@@ -60,13 +53,6 @@ signals:
private: private:
bool isWaitingStatus = true; bool isWaitingStatus = true;
static jclass log;
static jmethodID logDebug;
static jmethodID logInfo;
static jmethodID logWarning;
static jmethodID logError;
static jmethodID logFatal;
void qtAndroidControllerInitialized(); void qtAndroidControllerInitialized();
static Vpn::ConnectionState convertState(ConnectionState state); static Vpn::ConnectionState convertState(ConnectionState state);
@@ -86,7 +72,8 @@ private:
static bool decodeQrCode(JNIEnv *env, jobject thiz, jstring data); static bool decodeQrCode(JNIEnv *env, jobject thiz, jstring data);
template <typename Ret, typename ...Args> template <typename Ret, typename ...Args>
static auto callActivityMethod(const char *methodName, const char *signature, Args &&...args); static auto callActivityMethod(const char *methodName, const char *signature,
const std::function<Ret()> &defValue, Args &&...args);
template <typename ...Args> template <typename ...Args>
static void callActivityMethod(const char *methodName, const char *signature, Args &&...args); static void callActivityMethod(const char *methodName, const char *signature, Args &&...args);
}; };
-1
View File
@@ -43,7 +43,6 @@ bool MobileUtils::shareText(const QStringList& filesToSend) {
UIPopoverPresentationController *popController = activityController.popoverPresentationController; UIPopoverPresentationController *popController = activityController.popoverPresentationController;
if (popController) { if (popController) {
popController.sourceView = qtController.view; popController.sourceView = qtController.view;
popController.sourceRect = CGRectMake(100, 100, 100, 100);
} }
QEventLoop wait; QEventLoop wait;
@@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "3rd/amneziawg-apple/Sources/WireGuardKitC/WireGuardKitC.h" #include "3rd/awg-apple/Sources/WireGuardKitC/WireGuardKitC.h"
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
+5 -113
View File
@@ -249,107 +249,8 @@ void IosController::vpnStatusDidChange(void *pNotification)
NETunnelProviderSession *session = (NETunnelProviderSession *)pNotification; NETunnelProviderSession *session = (NETunnelProviderSession *)pNotification;
if (session /* && session == TunnelManager.session */ ) { if (session /* && session == TunnelManager.session */ ) {
qDebug() << "IosController::vpnStatusDidChange" << iosStatusToState(session.status) << session; qDebug() << "IosController::vpnStatusDidChange" << iosStatusToState(session.status) << session;
emit connectionStateChanged(iosStatusToState(session.status));
if (session.status == NEVPNStatusDisconnected) {
if (@available(iOS 16.0, *)) {
[session fetchLastDisconnectErrorWithCompletionHandler:^(NSError * _Nullable error) {
if (error != nil) {
qDebug() << "Disconnect error" << error.domain << error.code << error.localizedDescription;
if ([error.domain isEqualToString:NEVPNConnectionErrorDomain]) {
switch (error.code) {
case NEVPNConnectionErrorOverslept:
qDebug() << "Disconnect error info" << "The VPN connection was terminated because the system slept for an extended period of time.";
break;
case NEVPNConnectionErrorNoNetworkAvailable:
qDebug() << "Disconnect error info" << "The VPN connection could not be established because the system is not connected to a network.";
break;
case NEVPNConnectionErrorUnrecoverableNetworkChange:
qDebug() << "Disconnect error info" << "The VPN connection was terminated because the network conditions changed in such a way that the VPN connection could not be maintained.";
break;
case NEVPNConnectionErrorConfigurationFailed:
qDebug() << "Disconnect error info" << "The VPN connection could not be established because the configuration is invalid. ";
break;
case NEVPNConnectionErrorServerAddressResolutionFailed:
qDebug() << "Disconnect error info" << "The address of the VPN server could not be determined.";
break;
case NEVPNConnectionErrorServerNotResponding:
qDebug() << "Disconnect error info" << "Network communication with the VPN server has failed.";
break;
case NEVPNConnectionErrorServerDead:
qDebug() << "Disconnect error info" << "The VPN server is no longer functioning.";
break;
case NEVPNConnectionErrorAuthenticationFailed:
qDebug() << "Disconnect error info" << "The user credentials were rejected by the VPN server.";
break;
case NEVPNConnectionErrorClientCertificateInvalid:
qDebug() << "Disconnect error info" << "The client certificate is invalid.";
break;
case NEVPNConnectionErrorClientCertificateNotYetValid:
qDebug() << "Disconnect error info" << "The client certificate will not be valid until some future point in time.";
break;
case NEVPNConnectionErrorClientCertificateExpired:
qDebug() << "Disconnect error info" << "The validity period of the client certificate has passed.";
break;
case NEVPNConnectionErrorPluginFailed:
qDebug() << "Disconnect error info" << "The VPN plugin died unexpectedly.";
break;
case NEVPNConnectionErrorConfigurationNotFound:
qDebug() << "Disconnect error info" << "The VPN configuration could not be found.";
break;
case NEVPNConnectionErrorPluginDisabled:
qDebug() << "Disconnect error info" << "The VPN plugin could not be found or needed to be updated.";
break;
case NEVPNConnectionErrorNegotiationFailed:
qDebug() << "Disconnect error info" << "The VPN protocol negotiation failed.";
break;
case NEVPNConnectionErrorServerDisconnected:
qDebug() << "Disconnect error info" << "The VPN server terminated the connection.";
break;
case NEVPNConnectionErrorServerCertificateInvalid:
qDebug() << "Disconnect error info" << "The server certificate is invalid.";
break;
case NEVPNConnectionErrorServerCertificateNotYetValid:
qDebug() << "Disconnect error info" << "The server certificate will not be valid until some future point in time.";
break;
case NEVPNConnectionErrorServerCertificateExpired:
qDebug() << "Disconnect error info" << "The validity period of the server certificate has passed.";
break;
default:
qDebug() << "Disconnect error info" << "Unknown code.";
break;
}
}
NSError *underlyingError = error.userInfo[@"NSUnderlyingError"];
if (underlyingError != nil) {
qDebug() << "Disconnect underlying error" << underlyingError.domain << underlyingError.code << underlyingError.localizedDescription;
if ([underlyingError.domain isEqualToString:@"NEAgentErrorDomain"]) {
switch (underlyingError.code) {
case 1:
qDebug() << "Disconnect underlying error" << "General. Use sysdiagnose.";
break;
case 2:
qDebug() << "Disconnect underlying error" << "Plug-in unavailable. Use sysdiagnose.";
break;
default:
qDebug() << "Disconnect underlying error" << "Unknown code. Use sysdiagnose.";
break;
}
}
}
} else {
qDebug() << "Disconnect error is absent";
}
}];
} else {
qDebug() << "Disconnect error is unavailable on iOS < 16.0";
}
}
emit connectionStateChanged(iosStatusToState(session.status));
} }
} }
@@ -451,15 +352,6 @@ bool IosController::startWireGuard(const QString &config)
void IosController::startTunnel() void IosController::startTunnel()
{ {
NSString *protocolName = @"Unknown";
NETunnelProviderProtocol *tunnelProtocol = (NETunnelProviderProtocol *)m_currentTunnel.protocolConfiguration;
if (tunnelProtocol.providerConfiguration[@"wireguard"] != nil) {
protocolName = @"WireGuard";
} else if (tunnelProtocol.providerConfiguration[@"ovpn"] != nil) {
protocolName = @"OpenVPN";
}
m_rxBytes = 0; m_rxBytes = 0;
m_txBytes = 0; m_txBytes = 0;
@@ -481,7 +373,7 @@ void IosController::startTunnel()
[m_currentTunnel loadFromPreferencesWithCompletionHandler:^(NSError *loadError) { [m_currentTunnel loadFromPreferencesWithCompletionHandler:^(NSError *loadError) {
if (loadError) { if (loadError) {
qDebug().nospace() << "IosController::start" << protocolName << ": Connect " << protocolName << " Tunnel Load Error" << loadError.localizedDescription.UTF8String; qDebug() << "IosController::startOpenVPN : Connect OpenVPN Tunnel Load Error" << loadError.localizedDescription.UTF8String;
emit connectionStateChanged(Vpn::ConnectionState::Error); emit connectionStateChanged(Vpn::ConnectionState::Error);
return; return;
} }
@@ -509,11 +401,11 @@ void IosController::startTunnel()
BOOL started = [m_currentTunnel.connection startVPNTunnelWithOptions:nil andReturnError:&startError]; BOOL started = [m_currentTunnel.connection startVPNTunnelWithOptions:nil andReturnError:&startError];
if (!started || startError) { if (!started || startError) {
qDebug().nospace() << "IosController::start" << protocolName << " : Connect " << protocolName << " Tunnel Start Error" qDebug() << "IosController::startOpenVPN : Connect OpenVPN Tunnel Start Error"
<< (startError ? startError.localizedDescription.UTF8String : ""); << (startError ? startError.localizedDescription.UTF8String : "");
emit connectionStateChanged(Vpn::ConnectionState::Error); emit connectionStateChanged(Vpn::ConnectionState::Error);
} else { } else {
qDebug().nospace() << "IosController::start" << protocolName << " : Starting the tunnel succeeded"; qDebug() << "IosController::startOpenVPN : Starting the tunnel succeeded";
} }
}]; }];
}); });
+24 -24
View File
@@ -17,41 +17,41 @@ public class Logger {
deinit {} deinit {}
func log(message: String) { func log(message: String) {
// let suiteName = "group.org.amnezia.AmneziaVPN" let suiteName = "group.org.amnezia.AmneziaVPN"
// let logKey = "logMessages" let logKey = "logMessages"
// let sharedDefaults = UserDefaults(suiteName: suiteName) let sharedDefaults = UserDefaults(suiteName: suiteName)
// var logs = sharedDefaults?.array(forKey: logKey) as? [String] ?? [] var logs = sharedDefaults?.array(forKey: logKey) as? [String] ?? []
// logs.append(message) logs.append(message)
// sharedDefaults?.set(logs, forKey: logKey) sharedDefaults?.set(logs, forKey: logKey)
} }
private func writeLog(to targetFile: String) -> Bool { func writeLog(to targetFile: String) -> Bool {
return true; return true;
} }
static func configureGlobal(tagged tag: String, withFilePath filePath: String?) { static func configureGlobal(tagged tag: String, withFilePath filePath: String?) {
// if Logger.global != nil { if Logger.global != nil {
// return return
// } }
//
// Logger.global = Logger(tagged: tag) Logger.global = Logger(tagged: tag)
//
// var appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown version" var appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown version"
//
// if let appBuild = Bundle.main.infoDictionary?["CFBundleVersion"] as? String { if let appBuild = Bundle.main.infoDictionary?["CFBundleVersion"] as? String {
// appVersion += " (\(appBuild))" appVersion += " (\(appBuild))"
// } }
//
// Logger.global?.log(message: "App version: \(appVersion)") Logger.global?.log(message: "App version: \(appVersion)")
} }
} }
func wg_log(_ type: OSLogType, staticMessage msg: StaticString) { func wg_log(_ type: OSLogType, staticMessage msg: StaticString) {
// os_log(msg, log: OSLog.default, type: type) os_log(msg, log: OSLog.default, type: type)
// Logger.global?.log(message: "\(msg)") Logger.global?.log(message: "\(msg)")
} }
func wg_log(_ type: OSLogType, message msg: String) { func wg_log(_ type: OSLogType, message msg: String) {
// os_log("%{AMNEZIA}s", log: OSLog.default, type: type, msg) os_log("%{AMNEZIA}s", log: OSLog.default, type: type, msg)
// Logger.global?.log(message: msg) Logger.global?.log(message: msg)
} }
@@ -1,518 +0,0 @@
// Copyright (c) 2023 Private Internet Access, Inc.
//
// This file is part of the Private Internet Access Desktop Client.
//
// The Private Internet Access Desktop Client is free software: you can
// redistribute it and/or modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
//
// The Private Internet Access Desktop Client is distributed in the hope that
// it will be useful, but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with the Private Internet Access Desktop Client. If not, see
// <https://www.gnu.org/licenses/>.
// Copyright (c) 2024 AmneziaVPN
// This file has been modified for AmneziaVPN
//
// This file is based on the work of the Private Internet Access Desktop Client.
// The original code of the Private Internet Access Desktop Client is copyrighted (c) 2023 Private Internet Access, Inc. and licensed under GPL3.
//
// The modified version of this file is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this file. If not, see <https://www.gnu.org/licenses/>.
#include "linuxfirewall.h"
#include "logger.h"
#include <QProcess>
#define BRAND_CODE "amn"
namespace {
Logger logger("LinuxFirewall");
} // namespace
namespace
{
const QString kAnchorName{BRAND_CODE "vpn"};
const QString kPacketTag{"0x3211"};
const QString kCGroupId{"0x567"};
const QString enabledKeyTemplate = "enabled:%1:%2";
const QString disabledKeyTemplate = "disabled:%1:%2";
const QString kVpnGroupName = BRAND_CODE "vpn";
QHash<QString, LinuxFirewall::FilterCallbackFunc> anchorCallbacks;
}
QString LinuxFirewall::kRtableName = QStringLiteral("%1rt").arg(kAnchorName);
QString LinuxFirewall::kOutputChain = QStringLiteral("OUTPUT");
QString LinuxFirewall::kPostRoutingChain = QStringLiteral("POSTROUTING");
QString LinuxFirewall::kPreRoutingChain = QStringLiteral("PREROUTING");
QString LinuxFirewall::kRootChain = QStringLiteral("%1.anchors").arg(kAnchorName);
QString LinuxFirewall::kFilterTable = QStringLiteral("filter");
QString LinuxFirewall::kNatTable = QStringLiteral("nat");
QString LinuxFirewall::kRawTable = QStringLiteral("raw");
QString LinuxFirewall::kMangleTable = QStringLiteral("mangle");
static QString getCommand(LinuxFirewall::IPVersion ip)
{
return ip == LinuxFirewall::IPv6 ? QStringLiteral("ip6tables") : QStringLiteral("iptables");
}
int LinuxFirewall::createChain(LinuxFirewall::IPVersion ip, const QString& chain, const QString& tableName)
{
if (ip == Both)
{
int result4 = createChain(IPv4, chain, tableName);
int result6 = createChain(IPv6, chain, tableName);
return result4 ? result4 : result6;
}
const QString cmd = getCommand(ip);
return execute(QStringLiteral("%1 -N %2 -t %3 || %1 -F %2 -t %3").arg(cmd, chain, tableName));
}
int LinuxFirewall::deleteChain(LinuxFirewall::IPVersion ip, const QString& chain, const QString& tableName)
{
if (ip == Both)
{
int result4 = deleteChain(IPv4, chain, tableName);
int result6 = deleteChain(IPv6, chain, tableName);
return result4 ? result4 : result6;
}
const QString cmd = getCommand(ip);
return execute(QStringLiteral("if %1 -L %2 -n -t %3 > /dev/null 2> /dev/null ; then %1 -F %2 -t %3 && %1 -X %2 -t %3; fi").arg(cmd, chain, tableName));
}
int LinuxFirewall::linkChain(LinuxFirewall::IPVersion ip, const QString& chain, const QString& parent, bool mustBeFirst, const QString& tableName)
{
if (ip == Both)
{
int result4 = linkChain(IPv4, chain, parent, mustBeFirst, tableName);
int result6 = linkChain(IPv6, chain, parent, mustBeFirst, tableName);
return result4 ? result4 : result6;
}
const QString cmd = getCommand(ip);
if (mustBeFirst)
{
// This monster shell script does the following:
// 1. Check if a rule with the appropriate target exists at the top of the parent chain
// 2. If not, insert a jump rule at the top of the parent chain
// 3. Look for and delete a single rule with the designated target at an index > 1
// (we can't safely delete all rules at once since rule numbers change)
// TODO: occasionally this script results in warnings in logs "Bad rule (does a matching rule exist in the chain?)" - this happens when
// the e.g OUTPUT chain is empty but this script attempts to delete things from it anyway. It doesn't cause any problems, but we should still fix at some point..
return execute(QStringLiteral("if ! %1 -L %2 -n --line-numbers -t %4 2> /dev/null | awk 'int($1) == 1 && $2 == \"%3\" { found=1 } END { if(found==1) { exit 0 } else { exit 1 } }' ; then %1 -I %2 -j %3 -t %4 && %1 -L %2 -n --line-numbers -t %4 2> /dev/null | awk 'int($1) > 1 && $2 == \"%3\" { print $1; exit }' | xargs %1 -t %4 -D %2 ; fi").arg(cmd, parent, chain, tableName));
}
else
return execute(QStringLiteral("if ! %1 -C %2 -j %3 -t %4 2> /dev/null ; then %1 -A %2 -j %3 -t %4; fi").arg(cmd, parent, chain, tableName));
}
int LinuxFirewall::unlinkChain(LinuxFirewall::IPVersion ip, const QString& chain, const QString& parent, const QString& tableName)
{
if (ip == Both)
{
int result4 = unlinkChain(IPv4, chain, parent, tableName);
int result6 = unlinkChain(IPv6, chain, parent, tableName);
return result4 ? result4 : result6;
}
const QString cmd = getCommand(ip);
return execute(QStringLiteral("if %1 -C %2 -j %3 -t %4 2> /dev/null ; then %1 -D %2 -j %3 -t %4; fi").arg(cmd, parent, chain, tableName));
}
void LinuxFirewall::ensureRootAnchorPriority(LinuxFirewall::IPVersion ip)
{
linkChain(ip, kRootChain, kOutputChain, true);
}
void LinuxFirewall::installAnchor(LinuxFirewall::IPVersion ip, const QString& anchor, const QStringList& rules, const QString& tableName,
const FilterCallbackFunc& enableFunc, const FilterCallbackFunc& disableFunc)
{
if (ip == Both)
{
installAnchor(IPv4, anchor, rules, tableName, enableFunc, disableFunc);
installAnchor(IPv6, anchor, rules, tableName, enableFunc, disableFunc);
return;
}
const QString cmd = getCommand(ip);
const QString anchorChain = QStringLiteral("%1.a.%2").arg(kAnchorName, anchor);
const QString actualChain = QStringLiteral("%1.%2").arg(kAnchorName, anchor);
// Start by defining a placeholder chain, which stays locked into place
// in the root chain without being removed or recreated, ensuring the
// intended precedence order.
createChain(ip, anchorChain, tableName);
linkChain(ip, anchorChain, kRootChain, false, tableName);
if(enableFunc)
{
const QString key = enabledKeyTemplate.arg(tableName, anchor);
if(!anchorCallbacks.contains(key)) anchorCallbacks[key] = enableFunc;
}
if(disableFunc)
{
const QString key = disabledKeyTemplate.arg(tableName, anchor);
if(!anchorCallbacks.contains(key)) anchorCallbacks[key] = disableFunc;
}
// Create the actual rule chain, which we'll insert or remove from the
// placeholder anchor when needed.
createChain(ip, actualChain, tableName);
for (const QString& rule : rules)
execute(QStringLiteral("%1 -A %2 %3 -t %4").arg(cmd, actualChain, rule, tableName));
}
void LinuxFirewall::uninstallAnchor(LinuxFirewall::IPVersion ip, const QString& anchor, const QString& tableName)
{
if (ip == Both)
{
uninstallAnchor(IPv4, anchor, tableName);
uninstallAnchor(IPv6, anchor, tableName);
return;
}
const QString cmd = getCommand(ip);
const QString anchorChain = QStringLiteral("%1.a.%2").arg(kAnchorName, anchor);
const QString actualChain = QStringLiteral("%1.%2").arg(kAnchorName, anchor);
unlinkChain(ip, anchorChain, kRootChain, tableName);
deleteChain(ip, anchorChain, tableName);
deleteChain(ip, actualChain, tableName);
}
QStringList LinuxFirewall::getDNSRules(const QStringList& servers)
{
QStringList result;
for (const QString& server : servers)
{
result << QStringLiteral("-o amn0+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server);
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);
}
return result;
}
QStringList LinuxFirewall::getAllowRule(const QStringList& servers)
{
QStringList result;
for (const QString& server : servers)
{
result << QStringLiteral("-d %1 -j ACCEPT").arg(server);
}
return result;
}
QStringList LinuxFirewall::getBlockRule(const QStringList& servers)
{
QStringList result;
for (const QString& server : servers)
{
result << QStringLiteral("-d %1 -j REJECT").arg(server);
}
return result;
}
void LinuxFirewall::install()
{
// Clean up any existing rules if they exist.
uninstall();
// Create a root filter chain to hold all our other anchors in order.
createChain(Both, kRootChain, kFilterTable);
// Create a root raw chain
createChain(Both, kRootChain, kRawTable);
// Create a root NAT chain
createChain(Both, kRootChain, kNatTable);
// Create a root Mangle chain
createChain(Both, kRootChain, kMangleTable);
// Install our filter rulesets in each corresponding anchor chain.
installAnchor(Both, QStringLiteral("000.allowLoopback"), {
QStringLiteral("-o lo+ -j ACCEPT"),
});
installAnchor(IPv4, QStringLiteral("320.allowDNS"), {});
installAnchor(Both, QStringLiteral("310.blockDNS"), {
QStringLiteral("-p udp --dport 53 -j REJECT"),
QStringLiteral("-p tcp --dport 53 -j REJECT"),
});
installAnchor(IPv4, QStringLiteral("300.allowLAN"), {
QStringLiteral("-d 10.0.0.0/8 -j ACCEPT"),
QStringLiteral("-d 169.254.0.0/16 -j ACCEPT"),
QStringLiteral("-d 172.16.0.0/12 -j ACCEPT"),
QStringLiteral("-d 192.168.0.0/16 -j ACCEPT"),
QStringLiteral("-d 224.0.0.0/4 -j ACCEPT"),
QStringLiteral("-d 255.255.255.255/32 -j ACCEPT"),
});
installAnchor(IPv6, QStringLiteral("300.allowLAN"), {
QStringLiteral("-d fc00::/7 -j ACCEPT"),
QStringLiteral("-d fe80::/10 -j ACCEPT"),
QStringLiteral("-d ff00::/8 -j ACCEPT"),
});
installAnchor(IPv4, QStringLiteral("290.allowDHCP"), {
QStringLiteral("-p udp -d 255.255.255.255 --sport 68 --dport 67 -j ACCEPT"),
});
installAnchor(IPv6, QStringLiteral("290.allowDHCP"), {
QStringLiteral("-p udp -d ff00::/8 --sport 546 --dport 547 -j ACCEPT"),
});
installAnchor(IPv6, QStringLiteral("250.blockIPv6"), {
QStringLiteral("! -o lo+ -j REJECT"),
});
installAnchor(Both, QStringLiteral("200.allowVPN"), {
QStringLiteral("-o amn0+ -j ACCEPT"),
QStringLiteral("-o tun0+ -j ACCEPT"),
});
installAnchor(IPv4, QStringLiteral("120.blockNets"), {});
installAnchor(IPv4, QStringLiteral("110.allowNets"), {});
installAnchor(Both, QStringLiteral("100.blockAll"), {
QStringLiteral("-j REJECT"),
});
// NAT rules
installAnchor(Both, QStringLiteral("100.transIp"), {
// Only need the original interface, not the IP.
// The interface should remain much more stable/unchangeable than the IP
// (IP can change when changing networks, but interface only changes if adding/removing NICs)
// this is just a stub rule - the real rule is set at run-time
// and updates dynamically (via replaceAnchor) when our interface changes
// it'll take this form: "-o <interface name> -j MASQUERADE"
QStringLiteral("-j MASQUERADE")
}, kNatTable);
// Mangle rules
installAnchor(Both, QStringLiteral("100.tagPkts"), {
QStringLiteral("-m cgroup --cgroup %1 -j MARK --set-mark %2").arg(kCGroupId, kPacketTag)
}, kMangleTable, setupTrafficSplitting, teardownTrafficSplitting);
// A rule to mitigate CVE-2019-14899 - drop packets addressed to the local
// VPN IP but that are not actually received on the VPN interface.
// See here: https://seclists.org/oss-sec/2019/q4/122
installAnchor(Both, QStringLiteral("100.vpnTunOnly"), {
// To be replaced at runtime
QStringLiteral("-j ACCEPT")
}, kRawTable);
// Insert our fitler root chain at the top of the OUTPUT chain.
linkChain(Both, kRootChain, kOutputChain, true, kFilterTable);
// Insert our NAT root chain at the top of the POSTROUTING chain.
linkChain(Both, kRootChain, kPostRoutingChain, true, kNatTable);
// Insert our Mangle root chain at the top of the OUTPUT chain.
linkChain(Both, kRootChain, kOutputChain, true, kMangleTable);
// Insert our Raw root chain at the top of the PREROUTING chain.
linkChain(Both, kRootChain, kPreRoutingChain, true, kRawTable);
setupTrafficSplitting();
}
void LinuxFirewall::uninstall()
{
// Filter chain
unlinkChain(Both, kRootChain, kOutputChain, kFilterTable);
deleteChain(Both, kRootChain, kFilterTable);
// Raw chain
unlinkChain(Both, kRootChain, kPreRoutingChain, kRawTable);
deleteChain(Both, kRootChain, kRawTable);
// NAT chain
unlinkChain(Both, kRootChain, kPostRoutingChain, kNatTable);
deleteChain(Both, kRootChain, kNatTable);
// Mangle chain
unlinkChain(Both, kRootChain, kOutputChain, kMangleTable);
deleteChain(Both, kRootChain, kMangleTable);
// Remove filter anchors
uninstallAnchor(Both, QStringLiteral("000.allowLoopback"));
uninstallAnchor(Both, QStringLiteral("400.allowPIA"));
uninstallAnchor(IPv4, QStringLiteral("320.allowDNS"));
uninstallAnchor(Both, QStringLiteral("310.blockDNS"));
uninstallAnchor(Both, QStringLiteral("300.allowLAN"));
uninstallAnchor(Both, QStringLiteral("290.allowDHCP"));
uninstallAnchor(IPv6, QStringLiteral("250.blockIPv6"));
uninstallAnchor(Both, QStringLiteral("200.allowVPN"));
uninstallAnchor(IPv4, QStringLiteral("120.blockNets"));
uninstallAnchor(IPv4, QStringLiteral("110.allowNets"));
uninstallAnchor(Both, QStringLiteral("100.blockAll"));
// Remove Nat anchors
uninstallAnchor(Both, QStringLiteral("100.transIp"), kNatTable);
// Remove Mangle anchors
uninstallAnchor(Both, QStringLiteral("100.tagPkts"), kMangleTable);
// Remove Raw anchors
uninstallAnchor(Both, QStringLiteral("100.vpnTunOnly"), kRawTable);
teardownTrafficSplitting();
logger.debug() << "LinuxFirewall::uninstall() complete";
}
bool LinuxFirewall::isInstalled()
{
return execute(QStringLiteral("iptables -C %1 -j %2 2> /dev/null").arg(kOutputChain, kRootChain)) == 0;
}
void LinuxFirewall::enableAnchor(LinuxFirewall::IPVersion ip, const QString &anchor, const QString& tableName)
{
if (ip == Both)
{
enableAnchor(IPv4, anchor, tableName);
enableAnchor(IPv6, anchor, tableName);
return;
}
const QString cmd = getCommand(ip);
const QString ipStr = ip == IPv6 ? QStringLiteral("(IPv6)") : QStringLiteral("(IPv4)");
execute(QStringLiteral("if %1 -C %5.a.%2 -j %5.%2 -t %4 2> /dev/null ; then echo '%2%3: ON' ; else echo '%2%3: OFF -> ON' ; %1 -A %5.a.%2 -j %5.%2 -t %4; fi").arg(cmd, anchor, ipStr, tableName, kAnchorName));
}
void LinuxFirewall::replaceAnchor(LinuxFirewall::IPVersion ip, const QString &anchor, const QString &newRule, const QString& tableName)
{
if (ip == Both)
{
replaceAnchor(IPv4, anchor, newRule, tableName);
replaceAnchor(IPv6, anchor, newRule, tableName);
return;
}
const QString cmd = getCommand(ip);
const QString ipStr = ip == IPv6 ? QStringLiteral("(IPv6)") : QStringLiteral("(IPv4)");
execute(QStringLiteral("%1 -R %7.%2 1 %3 -t %4 ; echo 'Replaced rule %7.%2 %5 with %6'").arg(cmd, anchor, newRule, tableName, ipStr, newRule, kAnchorName));
}
void LinuxFirewall::disableAnchor(LinuxFirewall::IPVersion ip, const QString &anchor, const QString& tableName)
{
if (ip == Both)
{
disableAnchor(IPv4, anchor, tableName);
disableAnchor(IPv6, anchor, tableName);
return;
}
const QString cmd = getCommand(ip);
const QString ipStr = ip == IPv6 ? QStringLiteral("(IPv6)") : QStringLiteral("(IPv4)");
execute(QStringLiteral("if ! %1 -C %5.a.%2 -j %5.%2 -t %4 2> /dev/null ; then echo '%2%3: OFF' ; else echo '%2%3: ON -> OFF' ; %1 -F %5.a.%2 -t %4; fi").arg(cmd, anchor, ipStr, tableName, kAnchorName));
}
bool LinuxFirewall::isAnchorEnabled(LinuxFirewall::IPVersion ip, const QString &anchor, const QString& tableName)
{
const QString cmd = getCommand(ip);
return execute(QStringLiteral("%1 -C %4.a.%2 -j %4.%2 -t %3 2> /dev/null").arg(cmd, anchor, tableName, kAnchorName)) == 0;
}
void LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPVersion ip, const QString &anchor, bool enabled, const QString &tableName)
{
if (enabled)
{
enableAnchor(ip, anchor, tableName);
const QString key = enabledKeyTemplate.arg(tableName, anchor);
if(anchorCallbacks.contains(key)) anchorCallbacks[key]();
}
else
{
disableAnchor(ip, anchor, tableName);
const QString key = disabledKeyTemplate.arg(tableName, anchor);
if(anchorCallbacks.contains(key)) anchorCallbacks[key]();
}
}
void LinuxFirewall::updateDNSServers(const QStringList& servers)
{
static QStringList existingServers {};
existingServers = servers;
execute(QStringLiteral("iptables -F %1.320.allowDNS").arg(kAnchorName));
for (const QString& rule : getDNSRules(servers))
execute(QStringLiteral("iptables -A %1.320.allowDNS %2").arg(kAnchorName, rule));
}
void LinuxFirewall::updateAllowNets(const QStringList& servers)
{
static QStringList existingServers {};
existingServers = servers;
execute(QStringLiteral("iptables -F %1.110.allowNets").arg(kAnchorName));
for (const QString& rule : getAllowRule(servers))
execute(QStringLiteral("iptables -A %1.110.allowNets %2").arg(kAnchorName, rule));
}
void LinuxFirewall::updateBlockNets(const QStringList& servers)
{
static QStringList existingServers {};
existingServers = servers;
execute(QStringLiteral("iptables -F %1.120.blockNets").arg(kAnchorName));
for (const QString& rule : getBlockRule(servers))
execute(QStringLiteral("iptables -A %1.120.blockNets %2").arg(kAnchorName, rule));
}
int waitForExitCode(QProcess& process)
{
if (!process.waitForFinished() || process.error() == QProcess::FailedToStart)
return -2;
else if (process.exitStatus() != QProcess::NormalExit)
return -1;
else
return process.exitCode();
}
int LinuxFirewall::execute(const QString &command, bool ignoreErrors)
{
QProcess p;
p.start(QStringLiteral("/bin/bash"), {QStringLiteral("-c"), command}, QProcess::ReadOnly);
p.closeWriteChannel();
int exitCode = waitForExitCode(p);
auto out = p.readAllStandardOutput().trimmed();
auto err = p.readAllStandardError().trimmed();
if ((exitCode != 0 || !err.isEmpty()) && !ignoreErrors)
logger.warning() << "(" << exitCode << ") $ " << command;
else if (false)
logger.debug() << "(" << exitCode << ") $ " << command;
if (!out.isEmpty())
logger.info() << out;
if (!err.isEmpty())
logger.warning() << err;
return exitCode;
}
void LinuxFirewall::setupTrafficSplitting()
{
auto cGroupDir = "/sys/fs/cgroup/net_cls/" BRAND_CODE "vpnexclusions/";
logger.info() << "Should be setting up cgroup in" << cGroupDir << "for traffic splitting";
execute(QStringLiteral("if [ ! -d %1 ] ; then mkdir %1 ; sleep 0.1 ; echo %2 > %1/net_cls.classid ; fi").arg(cGroupDir).arg(kCGroupId));
// Set a rule with priority 100 (lower priority than local but higher than main/default, 0 is highest priority)
execute(QStringLiteral("if ! ip rule list | grep -q %1 ; then ip rule add from all fwmark %1 lookup %2 pri 100 ; fi").arg(kPacketTag, kRtableName));
}
void LinuxFirewall::teardownTrafficSplitting()
{
logger.info() << "Tearing down cgroup and routing rules";
execute(QStringLiteral("if ip rule list | grep -q %1; then ip rule del from all fwmark %1 lookup %2 2> /dev/null ; fi").arg(kPacketTag, kRtableName));
execute(QStringLiteral("ip route flush table %1").arg(kRtableName));
execute(QStringLiteral("ip route flush cache"));
}
@@ -1,107 +0,0 @@
// Copyright (c) 2023 Private Internet Access, Inc.
//
// This file is part of the Private Internet Access Desktop Client.
//
// The Private Internet Access Desktop Client is free software: you can
// redistribute it and/or modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
//
// The Private Internet Access Desktop Client is distributed in the hope that
// it will be useful, but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with the Private Internet Access Desktop Client. If not, see
// <https://www.gnu.org/licenses/>.
// Copyright (c) 2024 AmneziaVPN
// This file has been modified for AmneziaVPN
//
// This file is based on the work of the Private Internet Access Desktop Client.
// The original code of the Private Internet Access Desktop Client is copyrighted (c) 2023 Private Internet Access, Inc. and licensed under GPL3.
//
// The modified version of this file is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this file. If not, see <https://www.gnu.org/licenses/>.
#ifndef LINUXFIREWALL_H
#define LINUXFIREWALL_H
#include <QString>
#include <QStringList>
// Descriptor for a set of firewall rules to be appled.
//
struct FirewallParams
{
QStringList dnsServers;
QVector<QString> excludeApps; // Apps to exclude if VPN exemptions are enabled
QStringList allowAddrs;
QStringList blockAddrs;
// The follow flags indicate which general rulesets are needed. Note that
// this is after some sanity filtering, i.e. an allow rule may be listed
// as not needed if there were no block rules preceding it. The rulesets
// should be thought of as in last-match order.
bool blockAll; // Block all traffic by default
bool allowVPN; // Exempt traffic through VPN tunnel
bool allowDHCP; // Exempt DHCP traffic
bool blockIPv6; // Block all IPv6 traffic
bool allowLAN; // Exempt LAN traffic, including IPv6 LAN traffic
bool blockDNS; // Block all DNS traffic except specified DNS servers
bool allowPIA; // Exempt PIA executables
bool allowLoopback; // Exempt loopback traffic
bool allowHnsd; // Exempt Handshake DNS traffic
bool allowVpnExemptions; // Exempt specified traffic from the tunnel (route it over the physical uplink instead)
bool allowNets;
bool blockNets;
};
class LinuxFirewall
{
public:
enum IPVersion { IPv4, IPv6, Both };
// Table names
static QString kFilterTable, kNatTable, kMangleTable, kRtableName, kRawTable;
public:
using FilterCallbackFunc = std::function<void()>;
private:
static int createChain(IPVersion ip, const QString& chain, const QString& tableName = kFilterTable);
static int deleteChain(IPVersion ip, const QString& chain, const QString& tableName = kFilterTable);
static int linkChain(IPVersion ip, const QString& chain, const QString& parent, bool mustBeFirst = false, const QString& tableName = kFilterTable);
static int unlinkChain(IPVersion ip, const QString& chain, const QString& parent, const QString& tableName = kFilterTable);
static void installAnchor(IPVersion ip, const QString& anchor, const QStringList& rules, const QString& tableName = kFilterTable, const FilterCallbackFunc& enableFunc = {}, const FilterCallbackFunc& disableFunc = {});
static void uninstallAnchor(IPVersion ip, const QString& anchor, const QString& tableName = kFilterTable);
static QStringList getDNSRules(const QStringList& servers);
static QStringList getAllowRule(const QStringList& servers);
static QStringList getBlockRule(const QStringList& servers);
static void setupTrafficSplitting();
static void teardownTrafficSplitting();
static int execute(const QString& command, bool ignoreErrors = false);
private:
// Chain names
static QString kOutputChain, kRootChain, kPostRoutingChain, kPreRoutingChain;
public:
static void install();
static void uninstall();
static bool isInstalled();
static void ensureRootAnchorPriority(IPVersion ip = Both);
static void enableAnchor(IPVersion ip, const QString& anchor, const QString& tableName = kFilterTable);
static void disableAnchor(IPVersion ip, const QString& anchor, const QString& tableName = kFilterTable);
static bool isAnchorEnabled(IPVersion ip, const QString& anchor, const QString& tableName = kFilterTable);
static void setAnchorEnabled(IPVersion ip, const QString& anchor, bool enabled, const QString& tableName = kFilterTable);
static void replaceAnchor(LinuxFirewall::IPVersion ip, const QString &anchor, const QString &newRule, const QString& tableName);
static void updateDNSServers(const QStringList& servers);
static void updateAllowNets(const QStringList& servers);
static void updateBlockNets(const QStringList& servers);
};
#endif // LINUXFIREWALL_H
@@ -11,9 +11,7 @@
#include <QFile> #include <QFile>
#include <QLocalSocket> #include <QLocalSocket>
#include <QTimer> #include <QTimer>
#include <QThread>
#include "linuxfirewall.h"
#include "leakdetector.h" #include "leakdetector.h"
#include "logger.h" #include "logger.h"
@@ -118,27 +116,7 @@ bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) {
int err = uapiErrno(uapiCommand(message)); int err = uapiErrno(uapiCommand(message));
if (err != 0) { if (err != 0) {
logger.error() << "Interface configuration failed:" << strerror(err); logger.error() << "Interface configuration failed:" << strerror(err);
} else {
FirewallParams params { };
params.dnsServers.append(config.m_dnsServer);
if (config.m_allowedIPAddressRanges.at(0).toString() == "0.0.0.0/0"){
params.blockAll = true;
if (config.m_excludedAddresses.size()) {
params.allowNets = true;
foreach (auto net, config.m_excludedAddresses) {
params.allowAddrs.append(net.toUtf8());
}
}
} else {
params.blockNets = true;
foreach (auto net, config.m_allowedIPAddressRanges) {
params.blockAddrs.append(net.toString());
}
}
applyFirewallRules(params);
} }
return (err == 0); return (err == 0);
} }
@@ -162,9 +140,6 @@ bool WireguardUtilsLinux::deleteInterface() {
// Garbage collect. // Garbage collect.
QDir wgRuntimeDir(WG_RUNTIME_DIR); QDir wgRuntimeDir(WG_RUNTIME_DIR);
QFile::remove(wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".name")); QFile::remove(wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".name"));
// double-check + ensure our firewall is installed and enabled
LinuxFirewall::uninstall();
return true; return true;
} }
@@ -277,31 +252,6 @@ QList<WireguardUtils::PeerStatus> WireguardUtilsLinux::getPeerStatus() {
return peerList; 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) { bool WireguardUtilsLinux::updateRoutePrefix(const IPAddress& prefix) {
if (!m_rtmonitor) { if (!m_rtmonitor) {
return false; return false;
@@ -8,11 +8,8 @@
#include <QObject> #include <QObject>
#include <QProcess> #include <QProcess>
#include "daemon/wireguardutils.h" #include "daemon/wireguardutils.h"
#include "linuxroutemonitor.h" #include "linuxroutemonitor.h"
#include "linuxfirewall.h"
class WireguardUtilsLinux final : public WireguardUtils { class WireguardUtilsLinux final : public WireguardUtils {
Q_OBJECT Q_OBJECT
@@ -37,7 +34,7 @@ public:
bool addExclusionRoute(const IPAddress& prefix) override; bool addExclusionRoute(const IPAddress& prefix) override;
bool deleteExclusionRoute(const IPAddress& prefix) override; bool deleteExclusionRoute(const IPAddress& prefix) override;
void applyFirewallRules(FirewallParams& params);
signals: signals:
void backendFailure(); void backendFailure();
@@ -1,199 +0,0 @@
// Copyright (c) 2023 Private Internet Access, Inc.
//
// This file is part of the Private Internet Access Desktop Client.
//
// The Private Internet Access Desktop Client is free software: you can
// redistribute it and/or modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
//
// The Private Internet Access Desktop Client is distributed in the hope that
// it will be useful, but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with the Private Internet Access Desktop Client. If not, see
// <https://www.gnu.org/licenses/>.
// Copyright (c) 2024 AmneziaVPN
// This file has been modified for AmneziaVPN
//
// This file is based on the work of the Private Internet Access Desktop Client.
// The original code of the Private Internet Access Desktop Client is copyrighted (c) 2023 Private Internet Access, Inc. and licensed under GPL3.
//
// The modified version of this file is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this file. If not, see <https://www.gnu.org/licenses/>.
#include "macosfirewall.h"
#include "logger.h"
#include <QProcess>
#include <QCoreApplication>
#define BRAND_IDENTIFIER "amn"
namespace {
Logger logger("MacOSFirewall");
} // namespace
#include "macosfirewall.h"
#define ResourceDir qApp->applicationDirPath() + "/pf"
#define DaemonDataDir qApp->applicationDirPath() + "/pf"
#include <QProcess>
static QString kRootAnchor = QStringLiteral(BRAND_IDENTIFIER);
static QByteArray kPfWarning = "pfctl: Use of -f option, could result in flushing of rules\npresent in the main ruleset added by the system at startup.\nSee /etc/pf.conf for further details.\n";
int waitForExitCode(QProcess& process)
{
if (!process.waitForFinished() || process.error() == QProcess::FailedToStart)
return -2;
else if (process.exitStatus() != QProcess::NormalExit)
return -1;
else
return process.exitCode();
}
int MacOSFirewall::execute(const QString& command, bool ignoreErrors)
{
QProcess p;
p.start(QStringLiteral("/bin/bash"), { QStringLiteral("-c"), command }, QProcess::ReadOnly);
p.closeWriteChannel();
int exitCode = waitForExitCode(p);
auto out = p.readAllStandardOutput().trimmed();
auto err = p.readAllStandardError().replace(kPfWarning, "").trimmed();
if ((exitCode != 0 || !err.isEmpty()) && !ignoreErrors)
logger.info() << "(" << exitCode << ") $ " << command;
else if (false)
logger.info() << "(" << exitCode << ") $ " << command;
if (!out.isEmpty()) logger.info() << out;
if (!err.isEmpty()) logger.info() << err;
return exitCode;
}
void MacOSFirewall::installRootAnchors()
{
logger.info() << "Installing PF root anchors";
// Append our NAT anchors by reading back and re-applying NAT rules only
auto insertNatAnchors = QStringLiteral(
"( "
R"(pfctl -sn | grep -v '%1/*'; )" // Translation rules (includes both nat and rdr, despite the modifier being 'nat')
R"(echo 'nat-anchor "%2/*"'; )" // PIA's translation anchors
R"(echo 'rdr-anchor "%3/*"'; )"
R"(echo 'load anchor "%4" from "%5/%6.conf"'; )" // Load the PIA anchors from file
") | pfctl -N -f -").arg(kRootAnchor, kRootAnchor, kRootAnchor, kRootAnchor, ResourceDir, kRootAnchor);
execute(insertNatAnchors);
// Append our filter anchor by reading back and re-applying filter rules
// only. pfctl -sr also includes scrub rules, but these will be ignored
// due to -R.
auto insertFilterAnchor = QStringLiteral(
"( "
R"(pfctl -sr | grep -v '%1/*'; )" // Filter rules (everything from pfctl -sr except 'scrub')
R"(echo 'anchor "%2/*"'; )" // PIA's filter anchors
R"(echo 'load anchor "%3" from "%4/%5.conf"'; )" // Load the PIA anchors from file
" ) | pfctl -R -f -").arg(kRootAnchor, kRootAnchor, kRootAnchor, ResourceDir, kRootAnchor);
execute(insertFilterAnchor);
}
void MacOSFirewall::install()
{
// remove hard-coded (legacy) pia anchor from /etc/pf.conf if it exists
execute(QStringLiteral("if grep -Fq '%1' /etc/pf.conf ; then echo \"`cat /etc/pf.conf | grep -vF '%1'`\" > /etc/pf.conf ; fi").arg(kRootAnchor));
// Clean up any existing rules if they exist.
uninstall();
timespec waitTime{0, 10'000'000};
::nanosleep(&waitTime, nullptr);
logger.info() << "Installing PF root anchor";
installRootAnchors();
execute(QStringLiteral("pfctl -E 2>&1 | grep -F 'Token : ' | cut -c9- > '%1/pf.token'").arg(DaemonDataDir));
}
void MacOSFirewall::uninstall()
{
logger.info() << "Uninstalling PF root anchor";
execute(QStringLiteral("pfctl -q -a '%1' -F all").arg(kRootAnchor));
execute(QStringLiteral("test -f '%1/pf.token' && pfctl -X `cat '%1/pf.token'` && rm '%1/pf.token'").arg(DaemonDataDir));
execute(QStringLiteral("test -f /etc/pf.conf && pfctl -F all -f /etc/pf.conf"));
}
bool MacOSFirewall::isInstalled()
{
return isPFEnabled() && isRootAnchorLoaded();
}
bool MacOSFirewall::isPFEnabled()
{
return 0 == execute(QStringLiteral("test -s '%1/pf.token' && pfctl -s References | grep -qFf '%1/pf.token'").arg(DaemonDataDir), true);
}
void MacOSFirewall::ensureRootAnchorPriority()
{
// We check whether our anchor appears last in the ruleset. If it does not, then remove it and re-add it last (this happens atomically).
// Appearing last ensures priority.
execute(QStringLiteral("if ! pfctl -sr | tail -1 | grep -qF '%1'; then echo -e \"$(pfctl -sr | grep -vF '%1')\\n\"'anchor \"%1\"' | pfctl -f - ; fi").arg(kRootAnchor));
}
bool MacOSFirewall::isRootAnchorLoaded()
{
// Our Root anchor is loaded if:
// 1. It is is included among the top-level anchors
// 2. It is not empty (i.e it contains sub-anchors)
return 0 == execute(QStringLiteral("pfctl -sr | grep -q '%1' && pfctl -q -a '%1' -s rules 2> /dev/null | grep -q .").arg(kRootAnchor), true);
}
void MacOSFirewall::enableAnchor(const QString& anchor)
{
execute(QStringLiteral("if pfctl -q -a '%1/%2' -s rules 2> /dev/null | grep -q . ; then echo '%2: ON' ; else echo '%2: OFF -> ON' ; pfctl -q -a '%1/%2' -F all -f '%3/%1.%2.conf' ; fi").arg(kRootAnchor, anchor, ResourceDir));
}
void MacOSFirewall::disableAnchor(const QString& anchor)
{
execute(QStringLiteral("if ! pfctl -q -a '%1/%2' -s rules 2> /dev/null | grep -q . ; then echo '%2: OFF' ; else echo '%2: ON -> OFF' ; pfctl -q -a '%1/%2' -F all ; fi").arg(kRootAnchor, anchor));
}
bool MacOSFirewall::isAnchorEnabled(const QString& anchor)
{
return 0 == execute(QStringLiteral("pfctl -q -a '%1/%2' -s rules 2> /dev/null | grep -q .").arg(kRootAnchor, anchor), true);
}
void MacOSFirewall::setAnchorEnabled(const QString& anchor, bool enabled)
{
if (enabled)
enableAnchor(anchor);
else
disableAnchor(anchor);
}
void MacOSFirewall::setAnchorTable(const QString& anchor, bool enabled, const QString& table, const QStringList& items)
{
if (enabled)
execute(QStringLiteral("pfctl -q -a '%1/%2' -t '%3' -T replace %4").arg(kRootAnchor, anchor, table, items.join(' ')));
else
execute(QStringLiteral("pfctl -q -a '%1/%2' -t '%3' -T kill").arg(kRootAnchor, anchor, table), true);
}
void MacOSFirewall::setAnchorWithRules(const QString& anchor, bool enabled, const QStringList &ruleList)
{
if (!enabled)
return (void)execute(QStringLiteral("pfctl -q -a '%1/%2' -F rules").arg(kRootAnchor, anchor), true);
else
return (void)execute(QStringLiteral("echo -e \"%1\" | pfctl -q -a '%2/%3' -f -").arg(ruleList.join('\n'), kRootAnchor, anchor), true);
}
@@ -1,90 +0,0 @@
// Copyright (c) 2023 Private Internet Access, Inc.
//
// This file is part of the Private Internet Access Desktop Client.
//
// The Private Internet Access Desktop Client is free software: you can
// redistribute it and/or modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
//
// The Private Internet Access Desktop Client is distributed in the hope that
// it will be useful, but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with the Private Internet Access Desktop Client. If not, see
// <https://www.gnu.org/licenses/>.
// Copyright (c) 2024 AmneziaVPN
// This file has been modified for AmneziaVPN
//
// This file is based on the work of the Private Internet Access Desktop Client.
// The original code of the Private Internet Access Desktop Client is copyrighted (c) 2023 Private Internet Access, Inc. and licensed under GPL3.
//
// The modified version of this file is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this file. If not, see <https://www.gnu.org/licenses/>.
#ifndef MACOSFIREWALL_H
#define MACOSFIREWALL_H
#include <QString>
#include <QStringList>
// Descriptor for a set of firewall rules to be appled.
//
struct FirewallParams
{
QStringList dnsServers;
QVector<QString> excludeApps; // Apps to exclude if VPN exemptions are enabled
QStringList allowAddrs;
QStringList blockAddrs;
// The follow flags indicate which general rulesets are needed. Note that
// this is after some sanity filtering, i.e. an allow rule may be listed
// as not needed if there were no block rules preceding it. The rulesets
// should be thought of as in last-match order.
bool blockAll; // Block all traffic by default
bool blockNets;
bool allowNets;
bool allowVPN; // Exempt traffic through VPN tunnel
bool allowDHCP; // Exempt DHCP traffic
bool blockIPv6; // Block all IPv6 traffic
bool allowLAN; // Exempt LAN traffic, including IPv6 LAN traffic
bool blockDNS; // Block all DNS traffic except specified DNS servers
bool allowPIA; // Exempt PIA executables
bool allowLoopback; // Exempt loopback traffic
bool allowHnsd; // Exempt Handshake DNS traffic
bool allowVpnExemptions; // Exempt specified traffic from the tunnel (route it over the physical uplink instead)
};
class MacOSFirewall
{
private:
static int execute(const QString &command, bool ignoreErrors = false);
static bool isPFEnabled();
static bool isRootAnchorLoaded();
public:
static void install();
static void uninstall();
static bool isInstalled();
static void enableAnchor(const QString &anchor);
static void disableAnchor(const QString &anchor);
static bool isAnchorEnabled(const QString &anchor);
static void setAnchorEnabled(const QString &anchor, bool enable);
static void setAnchorTable(const QString &anchor, bool enabled, const QString &table, const QStringList &items);
static void setAnchorWithRules(const QString &anchor, bool enabled, const QStringList &rules);
static void ensureRootAnchorPriority();
static void installRootAnchors();
};
#endif // MACOSFIREWALL_H
@@ -114,30 +114,9 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) {
} }
int err = uapiErrno(uapiCommand(message)); int err = uapiErrno(uapiCommand(message));
if (err != 0) { if (err != 0) {
logger.error() << "Interface configuration failed:" << strerror(err); logger.error() << "Interface configuration failed:" << strerror(err);
} else {
FirewallParams params { };
params.dnsServers.append(config.m_dnsServer);
if (config.m_allowedIPAddressRanges.at(0).toString() == "0.0.0.0/0"){
params.blockAll = true;
if (config.m_excludedAddresses.size()) {
params.allowNets = true;
foreach (auto net, config.m_excludedAddresses) {
params.allowAddrs.append(net.toUtf8());
}
}
} else {
params.blockNets = true;
foreach (auto net, config.m_allowedIPAddressRanges) {
params.blockAddrs.append(net.toString());
}
}
applyFirewallRules(params);
} }
return (err == 0); return (err == 0);
} }
@@ -161,10 +140,6 @@ bool WireguardUtilsMacos::deleteInterface() {
// Garbage collect. // Garbage collect.
QDir wgRuntimeDir(WG_RUNTIME_DIR); QDir wgRuntimeDir(WG_RUNTIME_DIR);
QFile::remove(wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".name")); QFile::remove(wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".name"));
// double-check + ensure our firewall is installed and enabled
MacOSFirewall::uninstall();
return true; return true;
} }
@@ -327,31 +302,6 @@ bool WireguardUtilsMacos::addExclusionRoute(const IPAddress& prefix) {
return m_rtmonitor->addExclusionRoute(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) { bool WireguardUtilsMacos::deleteExclusionRoute(const IPAddress& prefix) {
if (!m_rtmonitor) { if (!m_rtmonitor) {
return false; return false;
@@ -10,7 +10,6 @@
#include "daemon/wireguardutils.h" #include "daemon/wireguardutils.h"
#include "macosroutemonitor.h" #include "macosroutemonitor.h"
#include "macosfirewall.h"
class WireguardUtilsMacos final : public WireguardUtils { class WireguardUtilsMacos final : public WireguardUtils {
Q_OBJECT Q_OBJECT
@@ -35,7 +34,6 @@ class WireguardUtilsMacos final : public WireguardUtils {
bool addExclusionRoute(const IPAddress& prefix) override; bool addExclusionRoute(const IPAddress& prefix) override;
bool deleteExclusionRoute(const IPAddress& prefix) override; bool deleteExclusionRoute(const IPAddress& prefix) override;
void applyFirewallRules(FirewallParams& params);
signals: signals:
void backendFailure(); void backendFailure();
@@ -66,7 +66,7 @@ ErrorCode OpenVpnOverCloakProtocol::start()
emit protocolError(amnezia::ErrorCode::CloakExecutableCrashed); emit protocolError(amnezia::ErrorCode::CloakExecutableCrashed);
stop(); stop();
} }
if (exitCode !=0 ) { if (exitCode !=0 ){
emit protocolError(amnezia::ErrorCode::InternalError); emit protocolError(amnezia::ErrorCode::InternalError);
stop(); stop();
} }
+10 -36
View File
@@ -4,7 +4,6 @@
#include <QRandomGenerator> #include <QRandomGenerator>
#include <QTcpServer> #include <QTcpServer>
#include <QTcpSocket> #include <QTcpSocket>
#include <QNetworkInterface>
#include "logger.h" #include "logger.h"
#include "openvpnprotocol.h" #include "openvpnprotocol.h"
@@ -54,11 +53,6 @@ void OpenVpnProtocol::stop()
QThread::msleep(10); QThread::msleep(10);
m_managementServer.stop(); m_managementServer.stop();
} }
#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
IpcClient::Interface()->disableKillSwitch();
#endif
setConnectionState(Vpn::ConnectionState::Disconnected); setConnectionState(Vpn::ConnectionState::Disconnected);
} }
@@ -91,13 +85,13 @@ void OpenVpnProtocol::killOpenVpnProcess()
void OpenVpnProtocol::readOpenVpnConfiguration(const QJsonObject &configuration) void OpenVpnProtocol::readOpenVpnConfiguration(const QJsonObject &configuration)
{ {
if (configuration.contains(ProtocolProps::key_proto_config_data(Proto::OpenVpn))) { if (configuration.contains(ProtocolProps::key_proto_config_data(Proto::OpenVpn))) {
m_configData = configuration;
QJsonObject jConfig = configuration.value(ProtocolProps::key_proto_config_data(Proto::OpenVpn)).toObject(); QJsonObject jConfig = configuration.value(ProtocolProps::key_proto_config_data(Proto::OpenVpn)).toObject();
m_configFile.open(); m_configFile.open();
m_configFile.write(jConfig.value(config_key::config).toString().toUtf8()); m_configFile.write(jConfig.value(config_key::config).toString().toUtf8());
m_configFile.close(); m_configFile.close();
m_configFileName = m_configFile.fileName(); m_configFileName = m_configFile.fileName();
qDebug().noquote() << QString("Set config data") << m_configFileName; qDebug().noquote() << QString("Set config data") << m_configFileName;
} }
} }
@@ -144,18 +138,12 @@ uint OpenVpnProtocol::selectMgmtPort()
void OpenVpnProtocol::updateRouteGateway(QString line) void OpenVpnProtocol::updateRouteGateway(QString line)
{ {
if (line.contains("net_route_v4_best_gw")) { // TODO: fix for macos
QStringList params = line.split(" "); line = line.split("ROUTE_GATEWAY", Qt::SkipEmptyParts).at(1);
if (params.size() == 6) { if (!line.contains("/"))
m_routeGateway = params.at(3); return;
} m_routeGateway = line.split("/", Qt::SkipEmptyParts).first();
} else { m_routeGateway.replace(" ", "");
line = line.split("ROUTE_GATEWAY", Qt::SkipEmptyParts).at(1);
if (!line.contains("/"))
return;
m_routeGateway = line.split("/", Qt::SkipEmptyParts).first();
m_routeGateway.replace(" ", "");
}
qDebug() << "Set VPN route gateway" << m_routeGateway; qDebug() << "Set VPN route gateway" << m_routeGateway;
} }
@@ -294,7 +282,7 @@ void OpenVpnProtocol::onReadyReadDataFromManagementServer()
} }
} }
if (line.contains("ROUTE_GATEWAY") || line.contains("net_route_v4_best_gw")) { if (line.contains("ROUTE_GATEWAY")) {
updateRouteGateway(line); updateRouteGateway(line);
} }
@@ -332,28 +320,14 @@ void OpenVpnProtocol::updateVpnGateway(const QString &line)
// line looks like // line looks like
// PUSH: Received control message: 'PUSH_REPLY,route 10.8.0.1,topology net30,ping 10,ping-restart // PUSH: Received control message: 'PUSH_REPLY,route 10.8.0.1,topology net30,ping 10,ping-restart
// 120,ifconfig 10.8.0.6 10.8.0.5,peer-id 0,cipher AES-256-GCM' // 120,ifconfig 10.8.0.6 10.8.0.5,peer-id 0,cipher AES-256-GCM'
QStringList params = line.split(","); QStringList params = line.split(",");
for (const QString &l : params) { for (const QString &l : params) {
if (l.contains("ifconfig")) { if (l.contains("ifconfig")) {
if (l.split(" ").size() == 3) { if (l.split(" ").size() == 3) {
m_vpnLocalAddress = l.split(" ").at(1); m_vpnLocalAddress = l.split(" ").at(1);
m_vpnGateway = l.split(" ").at(2); m_vpnGateway = l.split(" ").at(2);
#ifdef Q_OS_WIN
QList<QNetworkInterface> netInterfaces = QNetworkInterface::allInterfaces();
for (int i = 0; i < netInterfaces.size(); i++) {
for (int j=0; j < netInterfaces.at(i).addressEntries().size(); j++)
{
if (m_vpnLocalAddress == netInterfaces.at(i).addressEntries().at(j).ip().toString()) {
IpcClient::Interface()->enableKillSwitch(QJsonObject(), netInterfaces.at(i).index());
m_configData.insert("vpnGateway", m_vpnGateway);
IpcClient::Interface()->enablePeerTraffic(m_configData);
}
}
}
#endif
#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
IpcClient::Interface()->enableKillSwitch(m_configData, 0);
#endif
qDebug() << QString("Set vpn local address %1, gw %2").arg(m_vpnLocalAddress).arg(vpnGateway()); qDebug() << QString("Set vpn local address %1, gw %2").arg(m_vpnLocalAddress).arg(vpnGateway());
} }
} }
-1
View File
@@ -44,7 +44,6 @@ private:
ManagementServer m_managementServer; ManagementServer m_managementServer;
QString m_configFileName; QString m_configFileName;
QJsonObject m_configData;
QTemporaryFile m_configFile; QTemporaryFile m_configFile;
uint selectMgmtPort(); uint selectMgmtPort();
+39
View File
@@ -13,6 +13,9 @@
WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *parent) WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *parent)
: VpnProtocol(configuration, parent) : VpnProtocol(configuration, parent)
{ {
m_configFile.setFileName(QDir::tempPath() + QDir::separator() + serviceName() + ".conf");
writeWireguardConfiguration(configuration);
m_impl.reset(new LocalSocketController()); m_impl.reset(new LocalSocketController());
connect(m_impl.get(), &ControllerImpl::connected, this, connect(m_impl.get(), &ControllerImpl::connected, this,
[this](const QString &pubkey, const QDateTime &connectionTimestamp) { [this](const QString &pubkey, const QDateTime &connectionTimestamp) {
@@ -47,9 +50,45 @@ ErrorCode WireguardProtocol::stopMzImpl()
return ErrorCode::NoError; return ErrorCode::NoError;
} }
void WireguardProtocol::writeWireguardConfiguration(const QJsonObject &configuration)
{
QJsonObject jConfig = configuration.value(ProtocolProps::key_proto_config_data(Proto::WireGuard)).toObject();
if (!m_configFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
qCritical() << "Failed to save wireguard config to" << m_configFile.fileName();
return;
}
m_configFile.write(jConfig.value(config_key::config).toString().toUtf8());
m_configFile.close();
m_configFileName = m_configFile.fileName();
m_isConfigLoaded = true;
qDebug().noquote() << QString("Set config data") << configPath();
qDebug().noquote() << QString("Set config data")
<< configuration.value(ProtocolProps::key_proto_config_data(Proto::WireGuard)).toString().toUtf8();
}
QString WireguardProtocol::configPath() const
{
return m_configFileName;
}
QString WireguardProtocol::serviceName() const
{
return "AmneziaVPN.WireGuard0";
}
ErrorCode WireguardProtocol::start() ErrorCode WireguardProtocol::start()
{ {
if (!m_isConfigLoaded) {
setLastError(ErrorCode::ConfigMissing);
return lastError();
}
return startMzImpl(); return startMzImpl();
} }
+9
View File
@@ -26,6 +26,15 @@ public:
ErrorCode stopMzImpl(); ErrorCode stopMzImpl();
private: private:
QString configPath() const;
void writeWireguardConfiguration(const QJsonObject &configuration);
QString serviceName() const;
private:
QString m_configFileName;
QFile m_configFile;
bool m_isConfigLoaded = false;
QScopedPointer<ControllerImpl> m_impl; QScopedPointer<ControllerImpl> m_impl;
}; };
+1 -3
View File
@@ -216,7 +216,6 @@ QString Settings::nextAvailableServerName() const
void Settings::setSaveLogs(bool enabled) void Settings::setSaveLogs(bool enabled)
{ {
setValue("Conf/saveLogs", enabled); setValue("Conf/saveLogs", enabled);
#ifndef Q_OS_ANDROID
if (!isSaveLogs()) { if (!isSaveLogs()) {
Logger::deInit(); Logger::deInit();
} else { } else {
@@ -224,8 +223,7 @@ void Settings::setSaveLogs(bool enabled)
qWarning() << "Initialization of debug subsystem failed"; qWarning() << "Initialization of debug subsystem failed";
} }
} }
#endif emit saveLogsChanged();
emit saveLogsChanged(enabled);
} }
QString Settings::routeModeString(RouteMode mode) const QString Settings::routeModeString(RouteMode mode) const
+1 -1
View File
@@ -190,7 +190,7 @@ public:
void clearSettings(); void clearSettings();
signals: signals:
void saveLogsChanged(bool enabled); void saveLogsChanged();
private: private:
QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const; QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const;
+91 -131
View File
@@ -133,12 +133,12 @@
<context> <context>
<name>HomeContainersListView</name> <name>HomeContainersListView</name>
<message> <message>
<location filename="../ui/qml/Components/HomeContainersListView.qml" line="76"/> <location filename="../ui/qml/Components/HomeContainersListView.qml" line="58"/>
<source>Unable change protocol while there is an active connection</source> <source>Unable change protocol while there is an active connection</source>
<translation>امکان تغییر پروتکل در هنگام متصل بودن وجود ندارد</translation> <translation>امکان تغییر پروتکل در هنگام متصل بودن وجود ندارد</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Components/HomeContainersListView.qml" line="85"/> <location filename="../ui/qml/Components/HomeContainersListView.qml" line="68"/>
<source>The selected protocol is not supported on the current platform</source> <source>The selected protocol is not supported on the current platform</source>
<translation>پروتکل انتخاب شده بر روی این پلتفرم پشتیبانی نمیشود</translation> <translation>پروتکل انتخاب شده بر روی این پلتفرم پشتیبانی نمیشود</translation>
</message> </message>
@@ -150,7 +150,7 @@
<context> <context>
<name>ImportController</name> <name>ImportController</name>
<message> <message>
<location filename="../ui/controllers/importController.cpp" line="416"/> <location filename="../ui/controllers/importController.cpp" line="411"/>
<source>Scanned %1 of %2.</source> <source>Scanned %1 of %2.</source>
<translation>ارزیابی %1 از %2.</translation> <translation>ارزیابی %1 از %2.</translation>
</message> </message>
@@ -190,31 +190,26 @@ Already installed containers were found on the server. All installed containers
</message> </message>
<message> <message>
<location filename="../ui/controllers/installController.cpp" line="305"/> <location filename="../ui/controllers/installController.cpp" line="305"/>
<source>Server &apos;%1&apos; was rebooted</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/controllers/installController.cpp" line="314"/>
<source>Server &apos;%1&apos; was removed</source> <source>Server &apos;%1&apos; was removed</source>
<translation>سرور %1 حذف شد</translation> <translation>سرور %1 حذف شد</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/installController.cpp" line="324"/> <location filename="../ui/controllers/installController.cpp" line="315"/>
<source>All containers from server &apos;%1&apos; have been removed</source> <source>All containers from server &apos;%1&apos; have been removed</source>
<translation>تمام کانتینترها از سرور %1 حذف شدند</translation> <translation>تمام کانتینترها از سرور %1 حذف شدند</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/installController.cpp" line="341"/> <location filename="../ui/controllers/installController.cpp" line="332"/>
<source>%1 has been removed from the server &apos;%2&apos;</source> <source>%1 has been removed from the server &apos;%2&apos;</source>
<translation>%1 از سرور %2 حذف شد</translation> <translation>%1 از سرور %2 حذف شد</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/installController.cpp" line="487"/> <location filename="../ui/controllers/installController.cpp" line="478"/>
<source>Please login as the user</source> <source>Please login as the user</source>
<translation>لطفا به عنوان کاربر وارد شوید</translation> <translation>لطفا به عنوان کاربر وارد شوید</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/installController.cpp" line="515"/> <location filename="../ui/controllers/installController.cpp" line="506"/>
<source>Server added successfully</source> <source>Server added successfully</source>
<translation>سرور با موفقیت اضافه شد</translation> <translation>سرور با موفقیت اضافه شد</translation>
</message> </message>
@@ -282,17 +277,17 @@ Already installed containers were found on the server. All installed containers
<context> <context>
<name>PageHome</name> <name>PageHome</name>
<message> <message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="318"/> <location filename="../ui/qml/Pages2/PageHome.qml" line="317"/>
<source>VPN protocol</source> <source>VPN protocol</source>
<translation>پروتکل ویپیان</translation> <translation>پروتکل ویپیان</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="362"/> <location filename="../ui/qml/Pages2/PageHome.qml" line="361"/>
<source>Servers</source> <source>Servers</source>
<translation>سرورها</translation> <translation>سرورها</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="454"/> <location filename="../ui/qml/Pages2/PageHome.qml" line="453"/>
<source>Unable change server while there is an active connection</source> <source>Unable change server while there is an active connection</source>
<translation>امکان تغییر سرور در هنگام متصل بودن وجود ندارد</translation> <translation>امکان تغییر سرور در هنگام متصل بودن وجود ندارد</translation>
</message> </message>
@@ -1225,62 +1220,57 @@ Already installed containers were found on the server. All installed containers
<context> <context>
<name>PageSettingsDns</name> <name>PageSettingsDns</name>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="35"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="45"/>
<source>Default server does not support custom dns</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="53"/>
<source>DNS servers</source> <source>DNS servers</source>
<translation>سرورهای DNS</translation> <translation>سرورهای DNS</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="58"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="50"/>
<source>If AmneziaDNS is not used or installed</source> <source>If AmneziaDNS is not used or installed</source>
<translation>اگر AmneziaDNS نصب نباشد یا استفاده نشود</translation> <translation>اگر AmneziaDNS نصب نباشد یا استفاده نشود</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="65"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="57"/>
<source>Primary DNS</source> <source>Primary DNS</source>
<translation>DNS اصلی</translation> <translation>DNS اصلی</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="77"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="69"/>
<source>Secondary DNS</source> <source>Secondary DNS</source>
<translation>DNS ثانویه</translation> <translation>DNS ثانویه</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="95"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="87"/>
<source>Restore default</source> <source>Restore default</source>
<translation>بازگشت به پیشفرض</translation> <translation>بازگشت به پیشفرض</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="98"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="90"/>
<source>Restore default DNS settings?</source> <source>Restore default DNS settings?</source>
<translation>بازگشت به تنظیمات پیشفرض DNS؟</translation> <translation>بازگشت به تنظیمات پیشفرض DNS؟</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="99"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="91"/>
<source>Continue</source> <source>Continue</source>
<translation>ادامه</translation> <translation>ادامه</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="100"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="92"/>
<source>Cancel</source> <source>Cancel</source>
<translation>کنسل</translation> <translation>کنسل</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="108"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="100"/>
<source>Settings have been reset</source> <source>Settings have been reset</source>
<translation>تنظیمات ریست شد</translation> <translation>تنظیمات ریست شد</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="120"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="112"/>
<source>Save</source> <source>Save</source>
<translation>ذخیره</translation> <translation>ذخیره</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="129"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="121"/>
<source>Settings saved</source> <source>Settings saved</source>
<translation>ذخیره تنظیمات</translation> <translation>ذخیره تنظیمات</translation>
</message> </message>
@@ -1298,52 +1288,52 @@ Already installed containers were found on the server. All installed containers
<translation>ذخیره گزارشات</translation> <translation>ذخیره گزارشات</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="87"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="86"/>
<source>Open folder with logs</source> <source>Open folder with logs</source>
<translation>باز کردن پوشه گزارشات</translation> <translation>باز کردن پوشه گزارشات</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="109"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="108"/>
<source>Save</source> <source>Save</source>
<translation>ذخیره</translation> <translation>ذخیره</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="110"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="109"/>
<source>Logs files (*.log)</source> <source>Logs files (*.log)</source>
<translation>Logs files (*.log)</translation> <translation>Logs files (*.log)</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="119"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="118"/>
<source>Logs file saved</source> <source>Logs file saved</source>
<translation>فایل گزارشات ذخیره شد</translation> <translation>فایل گزارشات ذخیره شد</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="128"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="127"/>
<source>Save logs to file</source> <source>Save logs to file</source>
<translation>ذخیره گزارشات در فایل</translation> <translation>ذخیره گزارشات در فایل</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="146"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="145"/>
<source>Clear logs?</source> <source>Clear logs?</source>
<translation>پاک کردن گزارشات؟</translation> <translation>پاک کردن گزارشات؟</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="147"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="146"/>
<source>Continue</source> <source>Continue</source>
<translation>ادامه</translation> <translation>ادامه</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="148"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="147"/>
<source>Cancel</source> <source>Cancel</source>
<translation>کنسل</translation> <translation>کنسل</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="155"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="154"/>
<source>Logs have been cleaned up</source> <source>Logs have been cleaned up</source>
<translation>گزارشات پاک شدند</translation> <translation>گزارشات پاک شدند</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="168"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="167"/>
<source>Clear logs</source> <source>Clear logs</source>
<translation>پاک کردن گزارشات</translation> <translation>پاک کردن گزارشات</translation>
</message> </message>
@@ -1356,17 +1346,17 @@ Already installed containers were found on the server. All installed containers
<translation>تمام کانتینرهای نصب شده به نرمافزار اضافه شدند</translation> <translation>تمام کانتینرهای نصب شده به نرمافزار اضافه شدند</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="91"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="87"/>
<source>Clear Amnezia cache</source> <source>Clear Amnezia cache</source>
<translation>پاک کردن حافظه داخلی Amnezia</translation> <translation>پاک کردن حافظه داخلی Amnezia</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="92"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="88"/>
<source>May be needed when changing other settings</source> <source>May be needed when changing other settings</source>
<translation>وقتی تنظیمات دیگر را تغییر دهید ممکن است نیاز باشد</translation> <translation>وقتی تنظیمات دیگر را تغییر دهید ممکن است نیاز باشد</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="95"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="91"/>
<source>Clear cached profiles?</source> <source>Clear cached profiles?</source>
<translation>پاک کردن پروفایل ذخیره شده؟</translation> <translation>پاک کردن پروفایل ذخیره شده؟</translation>
</message> </message>
@@ -1381,81 +1371,56 @@ Already installed containers were found on the server. All installed containers
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="97"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="93"/>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="145"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="140"/>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="177"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="171"/>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="208"/>
<source>Continue</source> <source>Continue</source>
<translation>ادامه</translation> <translation>ادامه</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="98"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="94"/>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="146"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="141"/>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="178"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="172"/>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="209"/>
<source>Cancel</source> <source>Cancel</source>
<translation>کنسل</translation> <translation>کنسل</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="121"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="117"/>
<source>Check the server for previously installed Amnezia services</source> <source>Check the server for previously installed Amnezia services</source>
<translation>چک کردن سرویسهای نصب شده Amnezia بر روی سرور</translation> <translation>چک کردن سرویسهای نصب شده Amnezia بر روی سرور</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="122"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="118"/>
<source>Add them to the application if they were not displayed</source> <source>Add them to the application if they were not displayed</source>
<translation>اضافه کردن آنها به نرمافزار اگر نمایش داده نشدهاند</translation> <translation>اضافه کردن آنها به نرمافزار اگر نمایش داده نشدهاند</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="139"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="134"/>
<source>Reboot server</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="143"/>
<source>Do you want to reboot the server?</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="144"/>
<source>??????????????????????????????</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="175"/>
<source>Do you want to remove the server?</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="206"/>
<source>Do you want to clear server from Amnezia software?</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="171"/>
<source>Remove server from application</source> <source>Remove server from application</source>
<translation>حذف کردن سرور از نرمافزار</translation> <translation>حذف کردن سرور از نرمافزار</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="138"/>
<source>Remove server?</source> <source>Remove server?</source>
<translation type="vanished">حذف سرور؟</translation> <translation>حذف سرور؟</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="176"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="139"/>
<source>All installed AmneziaVPN services will still remain on the server.</source> <source>All installed AmneziaVPN services will still remain on the server.</source>
<translation>تمام سرویسهای نصبشده Amnezia همچنان بر روی سرور باقی خواهند ماند.</translation> <translation>تمام سرویسهای نصبشده Amnezia همچنان بر روی سرور باقی خواهند ماند.</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="202"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="165"/>
<source>Clear server from Amnezia software</source> <source>Clear server from Amnezia software</source>
<translation>پاک کردن سرور از نرمافزار Amnezia</translation> <translation>پاک کردن سرور از نرمافزار Amnezia</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="169"/>
<source>Clear server from Amnezia software?</source> <source>Clear server from Amnezia software?</source>
<translation type="vanished">سرور از نرمافزار Amnezia پاک شود؟</translation> <translation>سرور از نرمافزار Amnezia پاک شود؟</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="207"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="170"/>
<source>All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted.</source> <source>All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted.</source>
<translation>تمام کانتینرها از سرور پاک شوند، به این معنی که تمام فایلهای پیکربندی، کلیدها و مجوزها حذف خواهند شد.</translation> <translation>تمام کانتینرها از سرور پاک شوند، به این معنی که تمام فایلهای پیکربندی، کلیدها و مجوزها حذف خواهند شد.</translation>
</message> </message>
@@ -1536,95 +1501,90 @@ Already installed containers were found on the server. All installed containers
<context> <context>
<name>PageSettingsSplitTunneling</name> <name>PageSettingsSplitTunneling</name>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="29"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="53"/>
<source>Default server does not support split tunneling function</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="59"/>
<source>Addresses from the list should be accessed via VPN</source> <source>Addresses from the list should be accessed via VPN</source>
<translation>دسترسی به آدرسهای لیست از طریق ویپیان</translation> <translation>دسترسی به آدرسهای لیست از طریق ویپیان</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="64"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="58"/>
<source>Addresses from the list should not be accessed via VPN</source> <source>Addresses from the list should not be accessed via VPN</source>
<translation>دسترسی به آدرسهای لیست بدون ویپیان</translation> <translation>دسترسی به آدرسهای لیست بدون ویپیان</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="96"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="90"/>
<source>Split tunneling</source> <source>Split tunneling</source>
<translation>جداسازی ترافیک</translation> <translation>جداسازی ترافیک</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="127"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="121"/>
<source>Mode</source> <source>Mode</source>
<translation>حالت</translation> <translation>حالت</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="205"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="199"/>
<source>Remove </source> <source>Remove </source>
<translation>حذف </translation> <translation>حذف </translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="206"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="200"/>
<source>Continue</source> <source>Continue</source>
<translation>ادامه</translation> <translation>ادامه</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="207"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="201"/>
<source>Cancel</source> <source>Cancel</source>
<translation>کنسل</translation> <translation>کنسل</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="254"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="248"/>
<source>Site or IP</source> <source>Site or IP</source>
<translation>سایت یا آیپی</translation> <translation>سایت یا آیپی</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="298"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="292"/>
<source>Import/Export Sites</source> <source>Import/Export Sites</source>
<translation>بارگذاری / خروجیگرفتن از سایتها</translation> <translation>بارگذاری / خروجیگرفتن از سایتها</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="304"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="298"/>
<source>Import</source> <source>Import</source>
<translation>بارگذاری</translation> <translation>بارگذاری</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="316"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="310"/>
<source>Save site list</source> <source>Save site list</source>
<translation>ذخیره لیست سایتها</translation> <translation>ذخیره لیست سایتها</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="323"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="317"/>
<source>Save sites</source> <source>Save sites</source>
<translation>ذخیره سایتها</translation> <translation>ذخیره سایتها</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="324"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="318"/>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="391"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="385"/>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="406"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="400"/>
<source>Sites files (*.json)</source> <source>Sites files (*.json)</source>
<translation>Sites files (*.json)</translation> <translation>Sites files (*.json)</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="381"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="375"/>
<source>Import a list of sites</source> <source>Import a list of sites</source>
<translation>بارگذاری لیست سایتها</translation> <translation>بارگذاری لیست سایتها</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="387"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="381"/>
<source>Replace site list</source> <source>Replace site list</source>
<translation>جایگزین کردن لیست سایت</translation> <translation>جایگزین کردن لیست سایت</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="390"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="384"/>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="405"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="399"/>
<source>Open sites file</source> <source>Open sites file</source>
<translation>باز کردن فایل سایتها</translation> <translation>باز کردن فایل سایتها</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="402"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="396"/>
<source>Add imported sites to existing ones</source> <source>Add imported sites to existing ones</source>
<translation>اضافه کردن سایتهای بارگذاری شده به سایتهای موجود</translation> <translation>اضافه کردن سایتهای بارگذاری شده به سایتهای موجود</translation>
</message> </message>
@@ -1751,22 +1711,22 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation>سطح کنترل اینترنت در منطقه شما چگونه است؟</translation> <translation>سطح کنترل اینترنت در منطقه شما چگونه است؟</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="139"/> <location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="137"/>
<source>Set up a VPN yourself</source> <source>Set up a VPN yourself</source>
<translation>یک ویپیان برای خودتان بسازید</translation> <translation>یک ویپیان برای خودتان بسازید</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="140"/> <location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="138"/>
<source>I want to choose a VPN protocol</source> <source>I want to choose a VPN protocol</source>
<translation>میخواهم پروتکل ویپیان را انتخاب کنم</translation> <translation>میخواهم پروتکل ویپیان را انتخاب کنم</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="159"/> <location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="157"/>
<source>Continue</source> <source>Continue</source>
<translation>ادامه</translation> <translation>ادامه</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="199"/> <location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="197"/>
<source>Set up later</source> <source>Set up later</source>
<translation>بعدا تنظیم شود</translation> <translation>بعدا تنظیم شود</translation>
</message> </message>
@@ -1774,7 +1734,7 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<context> <context>
<name>PageSetupWizardInstalling</name> <name>PageSetupWizardInstalling</name>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="61"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="60"/>
<source>The server has already been added to the application</source> <source>The server has already been added to the application</source>
<translation>سرور در حال حاضر به نرمافزار اضافه شده است</translation> <translation>سرور در حال حاضر به نرمافزار اضافه شده است</translation>
</message> </message>
@@ -1787,33 +1747,33 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation type="vanished">занят установкой других протоколов или сервисов. Установка Amnesia </translation> <translation type="vanished">занят установкой других протоколов или сервисов. Установка Amnesia </translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="67"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="66"/>
<source>Amnezia has detected that your server is currently </source> <source>Amnezia has detected that your server is currently </source>
<translation>برنامه Amnezia تشخیص داده است که سرور در حال حاضر </translation> <translation>برنامه Amnezia تشخیص داده است که سرور در حال حاضر </translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="68"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="67"/>
<source>busy installing other software. Amnezia installation </source> <source>busy installing other software. Amnezia installation </source>
<translation>مشغول نصب نرمافزار دیگری است. نصب Amnezia </translation> <translation>مشغول نصب نرمافزار دیگری است. نصب Amnezia </translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="69"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="68"/>
<source>will pause until the server finishes installing other software</source> <source>will pause until the server finishes installing other software</source>
<translation>متوقف شده تا زمانی که سرور نصب نرمافزار دیگر را تمام کند</translation> <translation>متوقف شده تا زمانی که سرور نصب نرمافزار دیگر را تمام کند</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="127"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="126"/>
<source>Installing</source> <source>Installing</source>
<translation>در حال نصب</translation> <translation>در حال نصب</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="166"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="165"/>
<source>Cancel installation</source> <source>Cancel installation</source>
<translation>لغو عملیات نصب</translation> <translation>لغو عملیات نصب</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="21"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="21"/>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="73"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="72"/>
<source>Usually it takes no more than 5 minutes</source> <source>Usually it takes no more than 5 minutes</source>
<translation>معمولا بیش از 5 دقیقه طول نمیکشد</translation> <translation>معمولا بیش از 5 دقیقه طول نمیکشد</translation>
</message> </message>
@@ -1936,27 +1896,27 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<context> <context>
<name>PageSetupWizardViewConfig</name> <name>PageSetupWizardViewConfig</name>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="64"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="63"/>
<source>New connection</source> <source>New connection</source>
<translation>ارتباط جدید</translation> <translation>ارتباط جدید</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="91"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="90"/>
<source>Do not use connection code from public sources. It could be created to intercept your data.</source> <source>Do not use connection code from public sources. It could be created to intercept your data.</source>
<translation>از کد اتصالی که در منابع عمومی هست استفاده نکنید. ممکن است برای شنود اطلاعات شما ایجاد شده باشد.</translation> <translation>از کد اتصالی که در منابع عمومی هست استفاده نکنید. ممکن است برای شنود اطلاعات شما ایجاد شده باشد.</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="106"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="105"/>
<source>Collapse content</source> <source>Collapse content</source>
<translation>جمع کردن محتوا</translation> <translation>جمع کردن محتوا</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="106"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="105"/>
<source>Show content</source> <source>Show content</source>
<translation>نمایش محتوا</translation> <translation>نمایش محتوا</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="149"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="148"/>
<source>Connect</source> <source>Connect</source>
<translation>اتصال</translation> <translation>اتصال</translation>
</message> </message>
@@ -2998,22 +2958,22 @@ This means that AmneziaWG keeps the fast performance of the original while addin
<context> <context>
<name>SettingsController</name> <name>SettingsController</name>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="27"/> <location filename="../ui/controllers/settingsController.cpp" line="26"/>
<source>Software version</source> <source>Software version</source>
<translation>نسخه نرمافزار</translation> <translation>نسخه نرمافزار</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="148"/> <location filename="../ui/controllers/settingsController.cpp" line="139"/>
<source>All settings have been reset to default values</source> <source>All settings have been reset to default values</source>
<translation>تمام تنظیمات به مقادیر پیش فرض ریست شد</translation> <translation>تمام تنظیمات به مقادیر پیش فرض ریست شد</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="154"/> <location filename="../ui/controllers/settingsController.cpp" line="145"/>
<source>Cached profiles cleared</source> <source>Cached profiles cleared</source>
<translation>پروفایل ذخیره شده پاک شد</translation> <translation>پروفایل ذخیره شده پاک شد</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="132"/> <location filename="../ui/controllers/settingsController.cpp" line="123"/>
<source>Backup file is corrupted</source> <source>Backup file is corrupted</source>
<translation>فایل بکآپ خراب شده است</translation> <translation>فایل بکآپ خراب شده است</translation>
</message> </message>
@@ -3145,7 +3105,7 @@ This means that AmneziaWG keeps the fast performance of the original while addin
<context> <context>
<name>VpnConnection</name> <name>VpnConnection</name>
<message> <message>
<location filename="../vpnconnection.cpp" line="438"/> <location filename="../vpnconnection.cpp" line="432"/>
<source>Mbps</source> <source>Mbps</source>
<translation>Mbps</translation> <translation>Mbps</translation>
</message> </message>
+97 -137
View File
@@ -132,12 +132,12 @@
<context> <context>
<name>HomeContainersListView</name> <name>HomeContainersListView</name>
<message> <message>
<location filename="../ui/qml/Components/HomeContainersListView.qml" line="76"/> <location filename="../ui/qml/Components/HomeContainersListView.qml" line="58"/>
<source>Unable change protocol while there is an active connection</source> <source>Unable change protocol while there is an active connection</source>
<translation>Невозможно изменить протокол при активном соединении</translation> <translation>Невозможно изменить протокол при активном соединении</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Components/HomeContainersListView.qml" line="85"/> <location filename="../ui/qml/Components/HomeContainersListView.qml" line="68"/>
<source>The selected protocol is not supported on the current platform</source> <source>The selected protocol is not supported on the current platform</source>
<translation>Выбранный протокол не поддерживается на данном устройстве</translation> <translation>Выбранный протокол не поддерживается на данном устройстве</translation>
</message> </message>
@@ -149,7 +149,7 @@
<context> <context>
<name>ImportController</name> <name>ImportController</name>
<message> <message>
<location filename="../ui/controllers/importController.cpp" line="416"/> <location filename="../ui/controllers/importController.cpp" line="411"/>
<source>Scanned %1 of %2.</source> <source>Scanned %1 of %2.</source>
<translation>Отсканировано %1 из%2.</translation> <translation>Отсканировано %1 из%2.</translation>
</message> </message>
@@ -188,31 +188,26 @@ Already installed containers were found on the server. All installed containers
</message> </message>
<message> <message>
<location filename="../ui/controllers/installController.cpp" line="305"/> <location filename="../ui/controllers/installController.cpp" line="305"/>
<source>Server &apos;%1&apos; was rebooted</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/controllers/installController.cpp" line="314"/>
<source>Server &apos;%1&apos; was removed</source> <source>Server &apos;%1&apos; was removed</source>
<translation>Сервер &apos;%1&apos; был удален</translation> <translation>Сервер &apos;%1&apos; был удален</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/installController.cpp" line="324"/> <location filename="../ui/controllers/installController.cpp" line="315"/>
<source>All containers from server &apos;%1&apos; have been removed</source> <source>All containers from server &apos;%1&apos; have been removed</source>
<translation>Все протоколы и сервисы были удалены с сервера &apos;%1&apos;</translation> <translation>Все протоколы и сервисы были удалены с сервера &apos;%1&apos;</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/installController.cpp" line="341"/> <location filename="../ui/controllers/installController.cpp" line="332"/>
<source>%1 has been removed from the server &apos;%2&apos;</source> <source>%1 has been removed from the server &apos;%2&apos;</source>
<translation>%1 был удален с сервера &apos;%2&apos;</translation> <translation>%1 был удален с сервера &apos;%2&apos;</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/installController.cpp" line="487"/> <location filename="../ui/controllers/installController.cpp" line="478"/>
<source>Please login as the user</source> <source>Please login as the user</source>
<translation>Пожалуйста, войдите в систему от имени пользователя</translation> <translation>Пожалуйста, войдите в систему от имени пользователя</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/installController.cpp" line="515"/> <location filename="../ui/controllers/installController.cpp" line="506"/>
<source>Server added successfully</source> <source>Server added successfully</source>
<translation>Сервер успешно добавлен</translation> <translation>Сервер успешно добавлен</translation>
</message> </message>
@@ -280,17 +275,17 @@ Already installed containers were found on the server. All installed containers
<context> <context>
<name>PageHome</name> <name>PageHome</name>
<message> <message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="318"/> <location filename="../ui/qml/Pages2/PageHome.qml" line="317"/>
<source>VPN protocol</source> <source>VPN protocol</source>
<translation>VPN протокол</translation> <translation>VPN протокол</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="362"/> <location filename="../ui/qml/Pages2/PageHome.qml" line="361"/>
<source>Servers</source> <source>Servers</source>
<translation>Серверы</translation> <translation>Серверы</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="454"/> <location filename="../ui/qml/Pages2/PageHome.qml" line="453"/>
<source>Unable change server while there is an active connection</source> <source>Unable change server while there is an active connection</source>
<translation>Невозможно изменить сервер при активном соединении</translation> <translation>Невозможно изменить сервер при активном соединении</translation>
</message> </message>
@@ -384,7 +379,7 @@ Already installed containers were found on the server. All installed containers
<message> <message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="311"/> <location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="311"/>
<source>Save and Restart Amnezia</source> <source>Save and Restart Amnezia</source>
<translation>Сохранить и перезагрузить Amnezia</translation> <translation>Сохранить и пререзагрузить Amnezia</translation>
</message> </message>
</context> </context>
<context> <context>
@@ -421,7 +416,7 @@ Already installed containers were found on the server. All installed containers
<message> <message>
<location filename="../ui/qml/Pages2/PageProtocolOpenVpnSettings.qml" line="77"/> <location filename="../ui/qml/Pages2/PageProtocolOpenVpnSettings.qml" line="77"/>
<source>OpenVPN settings</source> <source>OpenVPN settings</source>
<translation>OpenVPN настройки</translation> <translation>Настройки OpenVPN</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageProtocolOpenVpnSettings.qml" line="84"/> <location filename="../ui/qml/Pages2/PageProtocolOpenVpnSettings.qml" line="84"/>
@@ -1197,7 +1192,7 @@ Already installed containers were found on the server. All installed containers
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsConnection.qml" line="86"/> <location filename="../ui/qml/Pages2/PageSettingsConnection.qml" line="86"/>
<source>If AmneziaDNS is not used or installed</source> <source>If AmneziaDNS is not used or installed</source>
<translation>Эти адреса будут использоваться, если не включен AmneziaDNS</translation> <translation>Эти серверы будут использоваться, если не включен AmneziaDNS</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsConnection.qml" line="101"/> <location filename="../ui/qml/Pages2/PageSettingsConnection.qml" line="101"/>
@@ -1223,62 +1218,57 @@ Already installed containers were found on the server. All installed containers
<context> <context>
<name>PageSettingsDns</name> <name>PageSettingsDns</name>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="35"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="45"/>
<source>Default server does not support custom dns</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="53"/>
<source>DNS servers</source> <source>DNS servers</source>
<translation>DNS сервер</translation> <translation>DNS сервер</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="58"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="50"/>
<source>If AmneziaDNS is not used or installed</source> <source>If AmneziaDNS is not used or installed</source>
<translation>Эти адреса будут использоваться, если не включен или не установлен AmneziaDNS</translation> <translation>Эти адреса будут использоваться, если не включен или не установлен AmneziaDNS</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="65"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="57"/>
<source>Primary DNS</source> <source>Primary DNS</source>
<translation>Первичный DNS</translation> <translation>Первичный DNS</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="77"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="69"/>
<source>Secondary DNS</source> <source>Secondary DNS</source>
<translation>Вторичный DNS</translation> <translation>Вторичный DNS</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="95"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="87"/>
<source>Restore default</source> <source>Restore default</source>
<translation>Восстановить по умолчанию</translation> <translation>Восстановить по умолчанию</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="98"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="90"/>
<source>Restore default DNS settings?</source> <source>Restore default DNS settings?</source>
<translation>Восстановить настройки DNS по умолчанию?</translation> <translation>Восстановить настройки DNS по умолчанию?</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="99"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="91"/>
<source>Continue</source> <source>Continue</source>
<translation>Продолжить</translation> <translation>Продолжить</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="100"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="92"/>
<source>Cancel</source> <source>Cancel</source>
<translation>Отменить</translation> <translation>Отменить</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="108"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="100"/>
<source>Settings have been reset</source> <source>Settings have been reset</source>
<translation>Настройки сброшены</translation> <translation>Настройки сброшены</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="120"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="112"/>
<source>Save</source> <source>Save</source>
<translation>Сохранить</translation> <translation>Сохранить</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="129"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="121"/>
<source>Settings saved</source> <source>Settings saved</source>
<translation>Сохранить настройки</translation> <translation>Сохранить настройки</translation>
</message> </message>
@@ -1296,52 +1286,52 @@ Already installed containers were found on the server. All installed containers
<translation>Сохранять логи</translation> <translation>Сохранять логи</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="87"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="86"/>
<source>Open folder with logs</source> <source>Open folder with logs</source>
<translation>Открыть папку с логами</translation> <translation>Открыть папку с логами</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="109"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="108"/>
<source>Save</source> <source>Save</source>
<translation>Сохранить</translation> <translation>Сохранить</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="110"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="109"/>
<source>Logs files (*.log)</source> <source>Logs files (*.log)</source>
<translation>Logs files (*.log)</translation> <translation>Logs files (*.log)</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="119"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="118"/>
<source>Logs file saved</source> <source>Logs file saved</source>
<translation>Файл с логами сохранен</translation> <translation>Файл с логами сохранен</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="128"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="127"/>
<source>Save logs to file</source> <source>Save logs to file</source>
<translation>Сохранить логи в файл</translation> <translation>Сохранить логи в файл</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="146"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="145"/>
<source>Clear logs?</source> <source>Clear logs?</source>
<translation>Очистить логи?</translation> <translation>Очистить логи?</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="147"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="146"/>
<source>Continue</source> <source>Continue</source>
<translation>Продолжить</translation> <translation>Продолжить</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="148"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="147"/>
<source>Cancel</source> <source>Cancel</source>
<translation>Отменить</translation> <translation>Отменить</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="155"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="154"/>
<source>Logs have been cleaned up</source> <source>Logs have been cleaned up</source>
<translation>Логи удалены</translation> <translation>Логи удалены</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="168"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="167"/>
<source>Clear logs</source> <source>Clear logs</source>
<translation>Удалить логи</translation> <translation>Удалить логи</translation>
</message> </message>
@@ -1354,17 +1344,17 @@ Already installed containers were found on the server. All installed containers
<translation>Все установленные протоколы и сервисы были добавлены в приложение</translation> <translation>Все установленные протоколы и сервисы были добавлены в приложение</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="91"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="87"/>
<source>Clear Amnezia cache</source> <source>Clear Amnezia cache</source>
<translation>Очистить кэш Amnezia</translation> <translation>Очистить кэш Amnezia</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="92"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="88"/>
<source>May be needed when changing other settings</source> <source>May be needed when changing other settings</source>
<translation>Может понадобиться при изменении других настроек</translation> <translation>Может понадобиться при изменении других настроек</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="95"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="91"/>
<source>Clear cached profiles?</source> <source>Clear cached profiles?</source>
<translation>Удалить кэш Amnezia?</translation> <translation>Удалить кэш Amnezia?</translation>
</message> </message>
@@ -1379,81 +1369,56 @@ Already installed containers were found on the server. All installed containers
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="97"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="93"/>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="145"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="140"/>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="177"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="171"/>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="208"/>
<source>Continue</source> <source>Continue</source>
<translation>Продолжить</translation> <translation>Продолжить</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="98"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="94"/>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="146"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="141"/>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="178"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="172"/>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="209"/>
<source>Cancel</source> <source>Cancel</source>
<translation>Отменить</translation> <translation>Отменить</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="121"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="117"/>
<source>Check the server for previously installed Amnezia services</source> <source>Check the server for previously installed Amnezia services</source>
<translation>Проверить сервер на наличие ранее установленных сервисов Amnezia</translation> <translation>Проверить сервер на наличие ранее установленных сервисов Amnezia</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="122"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="118"/>
<source>Add them to the application if they were not displayed</source> <source>Add them to the application if they were not displayed</source>
<translation>Добавить их в приложение, если они не были отображены</translation> <translation>Добавить их в приложение, если они не были отображены</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="139"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="134"/>
<source>Reboot server</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="143"/>
<source>Do you want to reboot the server?</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="144"/>
<source>??????????????????????????????</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="175"/>
<source>Do you want to remove the server?</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="206"/>
<source>Do you want to clear server from Amnezia software?</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="171"/>
<source>Remove server from application</source> <source>Remove server from application</source>
<translation>Удалить сервер из приложения</translation> <translation>Удалить сервер из приложения</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="138"/>
<source>Remove server?</source> <source>Remove server?</source>
<translation type="vanished">Удалить сервер?</translation> <translation>Удалить сервер?</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="176"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="139"/>
<source>All installed AmneziaVPN services will still remain on the server.</source> <source>All installed AmneziaVPN services will still remain on the server.</source>
<translation>Все установленные сервисы и протоколы Amnezia всё ещё останутся на сервере.</translation> <translation>Все установленные сервисы и протоколы Amnezia всё ещё останутся на сервере.</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="202"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="165"/>
<source>Clear server from Amnezia software</source> <source>Clear server from Amnezia software</source>
<translation>Очистить сервер от протоколов и сервисов Amnezia</translation> <translation>Очистить сервер от протоколов и сервисов Amnezia</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="169"/>
<source>Clear server from Amnezia software?</source> <source>Clear server from Amnezia software?</source>
<translation type="vanished">Удалить все сервисы и протоколы Amnezia с сервера?</translation> <translation>Удалить все сервисы и протоколы Amnezia с сервера?</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="207"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="170"/>
<source>All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted.</source> <source>All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted.</source>
<translation>На сервере будут удалены все данные, связанные с Amnezia: протоколы, сервисы, конфигурационные файлы, ключи и сертификаты.</translation> <translation>На сервере будут удалены все данные, связанные с Amnezia: протоколы, сервисы, конфигурационные файлы, ключи и сертификаты.</translation>
</message> </message>
@@ -1534,95 +1499,90 @@ Already installed containers were found on the server. All installed containers
<context> <context>
<name>PageSettingsSplitTunneling</name> <name>PageSettingsSplitTunneling</name>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="29"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="53"/>
<source>Default server does not support split tunneling function</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="59"/>
<source>Addresses from the list should be accessed via VPN</source> <source>Addresses from the list should be accessed via VPN</source>
<translation>Только адреса из списка должны открываться через VPN</translation> <translation>Только адреса из списка должны открываться через VPN</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="64"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="58"/>
<source>Addresses from the list should not be accessed via VPN</source> <source>Addresses from the list should not be accessed via VPN</source>
<translation>Адреса из списка не должны открываться через VPN</translation> <translation>Адреса из списка не должны открываться через VPN</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="96"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="90"/>
<source>Split tunneling</source> <source>Split tunneling</source>
<translation>Раздельное VPN-туннелирование</translation> <translation>Раздельное VPN-туннелирование</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="127"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="121"/>
<source>Mode</source> <source>Mode</source>
<translation>Режим</translation> <translation>Режим</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="205"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="199"/>
<source>Remove </source> <source>Remove </source>
<translation>Удалить </translation> <translation>Удалить </translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="206"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="200"/>
<source>Continue</source> <source>Continue</source>
<translation>Продолжить</translation> <translation>Продолжить</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="207"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="201"/>
<source>Cancel</source> <source>Cancel</source>
<translation>Отменить</translation> <translation>Отменить</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="254"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="248"/>
<source>Site or IP</source> <source>Site or IP</source>
<translation>Сайт или IP</translation> <translation>Сайт или IP</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="298"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="292"/>
<source>Import/Export Sites</source> <source>Import/Export Sites</source>
<translation>Импорт/экспорт Сайтов</translation> <translation>Импорт/экспорт Сайтов</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="304"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="298"/>
<source>Import</source> <source>Import</source>
<translation>Импорт</translation> <translation>Импорт</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="316"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="310"/>
<source>Save site list</source> <source>Save site list</source>
<translation>Сохранить список сайтов</translation> <translation>Сохранить список сайтов</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="323"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="317"/>
<source>Save sites</source> <source>Save sites</source>
<translation>Сохранить</translation> <translation>Сохранить</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="324"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="318"/>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="391"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="385"/>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="406"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="400"/>
<source>Sites files (*.json)</source> <source>Sites files (*.json)</source>
<translation>Sites files (*.json)</translation> <translation>Sites files (*.json)</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="381"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="375"/>
<source>Import a list of sites</source> <source>Import a list of sites</source>
<translation>Импортировать список с сайтами</translation> <translation>Импортировать список с сайтами</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="387"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="381"/>
<source>Replace site list</source> <source>Replace site list</source>
<translation>Заменить список сайтов</translation> <translation>Заменить список сайтов</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="390"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="384"/>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="405"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="399"/>
<source>Open sites file</source> <source>Open sites file</source>
<translation>Открыть список с сайтами</translation> <translation>Открыть список с сайтами</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="402"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="396"/>
<source>Add imported sites to existing ones</source> <source>Add imported sites to existing ones</source>
<translation>Добавить импортированные сайты к существующим</translation> <translation>Добавить импортированные сайты к существующим</translation>
</message> </message>
@@ -1749,22 +1709,22 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation>Какой уровень контроля интернета в вашем регионе?</translation> <translation>Какой уровень контроля интернета в вашем регионе?</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="139"/> <location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="137"/>
<source>Set up a VPN yourself</source> <source>Set up a VPN yourself</source>
<translation>Настроить VPN самостоятельно</translation> <translation>Настроить VPN самостоятельно</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="140"/> <location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="138"/>
<source>I want to choose a VPN protocol</source> <source>I want to choose a VPN protocol</source>
<translation>Выбрать VPN-протокол</translation> <translation>Выбрать VPN-протокол</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="159"/> <location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="157"/>
<source>Continue</source> <source>Continue</source>
<translation>Продолжить</translation> <translation>Продолжить</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="199"/> <location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="197"/>
<source>Set up later</source> <source>Set up later</source>
<translation>Настроить позднее</translation> <translation>Настроить позднее</translation>
</message> </message>
@@ -1772,7 +1732,7 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<context> <context>
<name>PageSetupWizardInstalling</name> <name>PageSetupWizardInstalling</name>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="61"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="60"/>
<source>The server has already been added to the application</source> <source>The server has already been added to the application</source>
<translation>Сервер уже был добавлен в приложение</translation> <translation>Сервер уже был добавлен в приложение</translation>
</message> </message>
@@ -1785,33 +1745,33 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation type="vanished">занят установкой других протоколов или сервисов. Установка Amnesia </translation> <translation type="vanished">занят установкой других протоколов или сервисов. Установка Amnesia </translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="67"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="66"/>
<source>Amnezia has detected that your server is currently </source> <source>Amnezia has detected that your server is currently </source>
<translation>Amnezia обнаружила, что ваш сервер в настоящее время </translation> <translation>Amnezia обнаружила, что ваш сервер в настоящее время </translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="68"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="67"/>
<source>busy installing other software. Amnezia installation </source> <source>busy installing other software. Amnezia installation </source>
<translation>занят установкой другого программного обеспечения. Установка Amnezia </translation> <translation>занят установкой другого программного обеспечения. Установка Amnezia </translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="69"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="68"/>
<source>will pause until the server finishes installing other software</source> <source>will pause until the server finishes installing other software</source>
<translation>будет приостановлена до тех пор, пока сервер не завершит установку</translation> <translation>будет приостановлена до тех пор, пока сервер не завершит установку</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="127"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="126"/>
<source>Installing</source> <source>Installing</source>
<translation>Установка</translation> <translation>Установка</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="166"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="165"/>
<source>Cancel installation</source> <source>Cancel installation</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="21"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="21"/>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="73"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="72"/>
<source>Usually it takes no more than 5 minutes</source> <source>Usually it takes no more than 5 minutes</source>
<translation>Обычно это занимает не более 5 минут</translation> <translation>Обычно это занимает не более 5 минут</translation>
</message> </message>
@@ -1934,27 +1894,27 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<context> <context>
<name>PageSetupWizardViewConfig</name> <name>PageSetupWizardViewConfig</name>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="64"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="63"/>
<source>New connection</source> <source>New connection</source>
<translation>Новое соединение</translation> <translation>Новое соединение</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="91"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="90"/>
<source>Do not use connection code from public sources. It could be created to intercept your data.</source> <source>Do not use connection code from public sources. It could be created to intercept your data.</source>
<translation>Не используйте код подключения из публичных источников. Его могли создать, чтобы перехватывать ваши данные.</translation> <translation>Не используйте код подключения из публичных источников. Его могли создать, чтобы перехватывать ваши данные.</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="106"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="105"/>
<source>Collapse content</source> <source>Collapse content</source>
<translation>Свернуть</translation> <translation>Свернуть</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="106"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="105"/>
<source>Show content</source> <source>Show content</source>
<translation>Показать содержимое ключа</translation> <translation>Показать содержимое ключа</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="149"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="148"/>
<source>Connect</source> <source>Connect</source>
<translation>Подключиться</translation> <translation>Подключиться</translation>
</message> </message>
@@ -2087,7 +2047,7 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="598"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="598"/>
<source>Rename</source> <source>Rename</source>
<translation type="unfinished">Переименовать</translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="627"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="627"/>
@@ -2102,12 +2062,12 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="668"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="668"/>
<source>Revoke</source> <source>Revoke</source>
<translation type="unfinished">Отозвать</translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="671"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="671"/>
<source>Revoke the config for a user - %1?</source> <source>Revoke the config for a user - %1?</source>
<translation type="unfinished">Отозвать доступ для пользователя - %1?</translation> <translation type="unfinished"></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageShare.qml" line="672"/> <location filename="../ui/qml/Pages2/PageShare.qml" line="672"/>
@@ -2965,22 +2925,22 @@ This means that AmneziaWG keeps the fast performance of the original while addin
<context> <context>
<name>SettingsController</name> <name>SettingsController</name>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="27"/> <location filename="../ui/controllers/settingsController.cpp" line="26"/>
<source>Software version</source> <source>Software version</source>
<translation>Версия ПО</translation> <translation>Версия ПО</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="148"/> <location filename="../ui/controllers/settingsController.cpp" line="139"/>
<source>All settings have been reset to default values</source> <source>All settings have been reset to default values</source>
<translation>Все настройки были сброшены к значению &quot;По умолчанию&quot;</translation> <translation>Все настройки были сброшены к значению &quot;По умолчанию&quot;</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="154"/> <location filename="../ui/controllers/settingsController.cpp" line="145"/>
<source>Cached profiles cleared</source> <source>Cached profiles cleared</source>
<translation>Кэш профиля очищен</translation> <translation>Кэш профиля очищен</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="132"/> <location filename="../ui/controllers/settingsController.cpp" line="123"/>
<source>Backup file is corrupted</source> <source>Backup file is corrupted</source>
<translation>Backup файл поврежден</translation> <translation>Backup файл поврежден</translation>
</message> </message>
@@ -3112,7 +3072,7 @@ This means that AmneziaWG keeps the fast performance of the original while addin
<context> <context>
<name>VpnConnection</name> <name>VpnConnection</name>
<message> <message>
<location filename="../vpnconnection.cpp" line="438"/> <location filename="../vpnconnection.cpp" line="432"/>
<source>Mbps</source> <source>Mbps</source>
<translation>Mbps</translation> <translation>Mbps</translation>
</message> </message>
+91 -131
View File
@@ -135,12 +135,12 @@
<context> <context>
<name>HomeContainersListView</name> <name>HomeContainersListView</name>
<message> <message>
<location filename="../ui/qml/Components/HomeContainersListView.qml" line="76"/> <location filename="../ui/qml/Components/HomeContainersListView.qml" line="58"/>
<source>Unable change protocol while there is an active connection</source> <source>Unable change protocol while there is an active connection</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Components/HomeContainersListView.qml" line="85"/> <location filename="../ui/qml/Components/HomeContainersListView.qml" line="68"/>
<source>The selected protocol is not supported on the current platform</source> <source>The selected protocol is not supported on the current platform</source>
<translation></translation> <translation></translation>
</message> </message>
@@ -152,7 +152,7 @@
<context> <context>
<name>ImportController</name> <name>ImportController</name>
<message> <message>
<location filename="../ui/controllers/importController.cpp" line="416"/> <location filename="../ui/controllers/importController.cpp" line="411"/>
<source>Scanned %1 of %2.</source> <source>Scanned %1 of %2.</source>
<translation> %1 of %2.</translation> <translation> %1 of %2.</translation>
</message> </message>
@@ -199,21 +199,16 @@ Already installed containers were found on the server. All installed containers
</message> </message>
<message> <message>
<location filename="../ui/controllers/installController.cpp" line="305"/> <location filename="../ui/controllers/installController.cpp" line="305"/>
<source>Server &apos;%1&apos; was rebooted</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/controllers/installController.cpp" line="314"/>
<source>Server &apos;%1&apos; was removed</source> <source>Server &apos;%1&apos; was removed</source>
<translation> &apos;%1&apos;</translation> <translation> &apos;%1&apos;</translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/installController.cpp" line="324"/> <location filename="../ui/controllers/installController.cpp" line="315"/>
<source>All containers from server &apos;%1&apos; have been removed</source> <source>All containers from server &apos;%1&apos; have been removed</source>
<translation> &apos;%1&apos; </translation> <translation> &apos;%1&apos; </translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/installController.cpp" line="341"/> <location filename="../ui/controllers/installController.cpp" line="332"/>
<source>%1 has been removed from the server &apos;%2&apos;</source> <source>%1 has been removed from the server &apos;%2&apos;</source>
<translation>%1 &apos;%2&apos; </translation> <translation>%1 &apos;%2&apos; </translation>
</message> </message>
@@ -234,12 +229,12 @@ Already installed containers were found on the server. All installed containers
<translation type="obsolete"> </translation> <translation type="obsolete"> </translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/installController.cpp" line="487"/> <location filename="../ui/controllers/installController.cpp" line="478"/>
<source>Please login as the user</source> <source>Please login as the user</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/installController.cpp" line="515"/> <location filename="../ui/controllers/installController.cpp" line="506"/>
<source>Server added successfully</source> <source>Server added successfully</source>
<translation></translation> <translation></translation>
</message> </message>
@@ -307,17 +302,17 @@ Already installed containers were found on the server. All installed containers
<context> <context>
<name>PageHome</name> <name>PageHome</name>
<message> <message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="318"/> <location filename="../ui/qml/Pages2/PageHome.qml" line="317"/>
<source>VPN protocol</source> <source>VPN protocol</source>
<translation>VPN协议</translation> <translation>VPN协议</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="362"/> <location filename="../ui/qml/Pages2/PageHome.qml" line="361"/>
<source>Servers</source> <source>Servers</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageHome.qml" line="454"/> <location filename="../ui/qml/Pages2/PageHome.qml" line="453"/>
<source>Unable change server while there is an active connection</source> <source>Unable change server while there is an active connection</source>
<translation></translation> <translation></translation>
</message> </message>
@@ -1302,62 +1297,57 @@ And if you don&apos;t like the app, all the more support it - the donation will
<context> <context>
<name>PageSettingsDns</name> <name>PageSettingsDns</name>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="35"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="45"/>
<source>Default server does not support custom dns</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="53"/>
<source>DNS servers</source> <source>DNS servers</source>
<translation>DNS服务器</translation> <translation>DNS服务器</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="58"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="50"/>
<source>If AmneziaDNS is not used or installed</source> <source>If AmneziaDNS is not used or installed</source>
<translation>使AmneziaDNS</translation> <translation>使AmneziaDNS</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="65"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="57"/>
<source>Primary DNS</source> <source>Primary DNS</source>
<translation> DNS</translation> <translation> DNS</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="77"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="69"/>
<source>Secondary DNS</source> <source>Secondary DNS</source>
<translation> DNS</translation> <translation> DNS</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="95"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="87"/>
<source>Restore default</source> <source>Restore default</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="98"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="90"/>
<source>Restore default DNS settings?</source> <source>Restore default DNS settings?</source>
<translation>DNS配置</translation> <translation>DNS配置</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="99"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="91"/>
<source>Continue</source> <source>Continue</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="100"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="92"/>
<source>Cancel</source> <source>Cancel</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="108"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="100"/>
<source>Settings have been reset</source> <source>Settings have been reset</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="120"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="112"/>
<source>Save</source> <source>Save</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="129"/> <location filename="../ui/qml/Pages2/PageSettingsDns.qml" line="121"/>
<source>Settings saved</source> <source>Settings saved</source>
<translation></translation> <translation></translation>
</message> </message>
@@ -1375,52 +1365,52 @@ And if you don&apos;t like the app, all the more support it - the donation will
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="87"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="86"/>
<source>Open folder with logs</source> <source>Open folder with logs</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="109"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="108"/>
<source>Save</source> <source>Save</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="110"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="109"/>
<source>Logs files (*.log)</source> <source>Logs files (*.log)</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="119"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="118"/>
<source>Logs file saved</source> <source>Logs file saved</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="128"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="127"/>
<source>Save logs to file</source> <source>Save logs to file</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="146"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="145"/>
<source>Clear logs?</source> <source>Clear logs?</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="147"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="146"/>
<source>Continue</source> <source>Continue</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="148"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="147"/>
<source>Cancel</source> <source>Cancel</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="155"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="154"/>
<source>Logs have been cleaned up</source> <source>Logs have been cleaned up</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="168"/> <location filename="../ui/qml/Pages2/PageSettingsLogging.qml" line="167"/>
<source>Clear logs</source> <source>Clear logs</source>
<translation></translation> <translation></translation>
</message> </message>
@@ -1438,101 +1428,76 @@ And if you don&apos;t like the app, all the more support it - the donation will
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="91"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="87"/>
<source>Clear Amnezia cache</source> <source>Clear Amnezia cache</source>
<translation> Amnezia </translation> <translation> Amnezia </translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="92"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="88"/>
<source>May be needed when changing other settings</source> <source>May be needed when changing other settings</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="95"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="91"/>
<source>Clear cached profiles?</source> <source>Clear cached profiles?</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="143"/>
<source>Do you want to reboot the server?</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="144"/>
<source>??????????????????????????????</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="175"/>
<source>Do you want to remove the server?</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="206"/>
<source>Do you want to clear server from Amnezia software?</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="92"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="92"/>
<source></source> <source></source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="97"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="93"/>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="145"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="140"/>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="177"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="171"/>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="208"/>
<source>Continue</source> <source>Continue</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="98"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="94"/>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="146"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="141"/>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="178"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="172"/>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="209"/>
<source>Cancel</source> <source>Cancel</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="121"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="117"/>
<source>Check the server for previously installed Amnezia services</source> <source>Check the server for previously installed Amnezia services</source>
<translation> Amnezia </translation> <translation> Amnezia </translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="122"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="118"/>
<source>Add them to the application if they were not displayed</source> <source>Add them to the application if they were not displayed</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="139"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="134"/>
<source>Reboot server</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="171"/>
<source>Remove server from application</source> <source>Remove server from application</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="138"/>
<source>Remove server?</source> <source>Remove server?</source>
<translation type="vanished">?</translation> <translation>?</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="176"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="139"/>
<source>All installed AmneziaVPN services will still remain on the server.</source> <source>All installed AmneziaVPN services will still remain on the server.</source>
<translation> AmneziaVPN </translation> <translation> AmneziaVPN </translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="202"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="165"/>
<source>Clear server from Amnezia software</source> <source>Clear server from Amnezia software</source>
<translation>Amnezia中服务器信息</translation> <translation>Amnezia中服务器信息</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="169"/>
<source>Clear server from Amnezia software?</source> <source>Clear server from Amnezia software?</source>
<translation type="vanished">Amnezia中服务器信息</translation> <translation>Amnezia中服务器信息</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="207"/> <location filename="../ui/qml/Pages2/PageSettingsServerData.qml" line="170"/>
<source>All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted.</source> <source>All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted.</source>
<translation></translation> <translation></translation>
</message> </message>
@@ -1633,95 +1598,90 @@ And if you don&apos;t like the app, all the more support it - the donation will
<translation type="obsolete">VPN分流</translation> <translation type="obsolete">VPN分流</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="29"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="53"/>
<source>Default server does not support split tunneling function</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="59"/>
<source>Addresses from the list should be accessed via VPN</source> <source>Addresses from the list should be accessed via VPN</source>
<translation>使VPN访问</translation> <translation>使VPN访问</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="64"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="58"/>
<source>Addresses from the list should not be accessed via VPN</source> <source>Addresses from the list should not be accessed via VPN</source>
<translation>使VPN访问</translation> <translation>使VPN访问</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="96"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="90"/>
<source>Split tunneling</source> <source>Split tunneling</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="127"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="121"/>
<source>Mode</source> <source>Mode</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="205"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="199"/>
<source>Remove </source> <source>Remove </source>
<translation> </translation> <translation> </translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="206"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="200"/>
<source>Continue</source> <source>Continue</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="207"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="201"/>
<source>Cancel</source> <source>Cancel</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="254"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="248"/>
<source>Site or IP</source> <source>Site or IP</source>
<translation>IP地址</translation> <translation>IP地址</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="298"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="292"/>
<source>Import/Export Sites</source> <source>Import/Export Sites</source>
<translation>/</translation> <translation>/</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="304"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="298"/>
<source>Import</source> <source>Import</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="316"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="310"/>
<source>Save site list</source> <source>Save site list</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="323"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="317"/>
<source>Save sites</source> <source>Save sites</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="324"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="318"/>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="391"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="385"/>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="406"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="400"/>
<source>Sites files (*.json)</source> <source>Sites files (*.json)</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="381"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="375"/>
<source>Import a list of sites</source> <source>Import a list of sites</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="387"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="381"/>
<source>Replace site list</source> <source>Replace site list</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="390"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="384"/>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="405"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="399"/>
<source>Open sites file</source> <source>Open sites file</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="402"/> <location filename="../ui/qml/Pages2/PageSettingsSplitTunneling.qml" line="396"/>
<source>Add imported sites to existing ones</source> <source>Add imported sites to existing ones</source>
<translation></translation> <translation></translation>
</message> </message>
@@ -1848,22 +1808,22 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="139"/> <location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="137"/>
<source>Set up a VPN yourself</source> <source>Set up a VPN yourself</source>
<translation>VPN</translation> <translation>VPN</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="140"/> <location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="138"/>
<source>I want to choose a VPN protocol</source> <source>I want to choose a VPN protocol</source>
<translation>VPN协议</translation> <translation>VPN协议</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="159"/> <location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="157"/>
<source>Continue</source> <source>Continue</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="199"/> <location filename="../ui/qml/Pages2/PageSetupWizardEasy.qml" line="197"/>
<source>Set up later</source> <source>Set up later</source>
<translation></translation> <translation></translation>
</message> </message>
@@ -1872,27 +1832,27 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<name>PageSetupWizardInstalling</name> <name>PageSetupWizardInstalling</name>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="21"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="21"/>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="73"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="72"/>
<source>Usually it takes no more than 5 minutes</source> <source>Usually it takes no more than 5 minutes</source>
<translation>5</translation> <translation>5</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="61"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="60"/>
<source>The server has already been added to the application</source> <source>The server has already been added to the application</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="67"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="66"/>
<source>Amnezia has detected that your server is currently </source> <source>Amnezia has detected that your server is currently </source>
<translation>Amnezia </translation> <translation>Amnezia </translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="68"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="67"/>
<source>busy installing other software. Amnezia installation </source> <source>busy installing other software. Amnezia installation </source>
<translation>Amnezia安装</translation> <translation>Amnezia安装</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="166"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="165"/>
<source>Cancel installation</source> <source>Cancel installation</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
@@ -1905,12 +1865,12 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<translation type="vanished">Amnezia安装</translation> <translation type="vanished">Amnezia安装</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="69"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="68"/>
<source>will pause until the server finishes installing other software</source> <source>will pause until the server finishes installing other software</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="127"/> <location filename="../ui/qml/Pages2/PageSetupWizardInstalling.qml" line="126"/>
<source>Installing</source> <source>Installing</source>
<translation></translation> <translation></translation>
</message> </message>
@@ -2033,27 +1993,27 @@ and will not be shared or disclosed to the Amnezia or any third parties</source>
<context> <context>
<name>PageSetupWizardViewConfig</name> <name>PageSetupWizardViewConfig</name>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="64"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="63"/>
<source>New connection</source> <source>New connection</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="91"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="90"/>
<source>Do not use connection code from public sources. It could be created to intercept your data.</source> <source>Do not use connection code from public sources. It could be created to intercept your data.</source>
<translation>使</translation> <translation>使</translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="106"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="105"/>
<source>Collapse content</source> <source>Collapse content</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="106"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="105"/>
<source>Show content</source> <source>Show content</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="149"/> <location filename="../ui/qml/Pages2/PageSetupWizardViewConfig.qml" line="148"/>
<source>Connect</source> <source>Connect</source>
<translation></translation> <translation></translation>
</message> </message>
@@ -3109,22 +3069,22 @@ While it offers a blend of security, stability, and speed, it&apos;s essential t
<context> <context>
<name>SettingsController</name> <name>SettingsController</name>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="27"/> <location filename="../ui/controllers/settingsController.cpp" line="26"/>
<source>Software version</source> <source>Software version</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="132"/> <location filename="../ui/controllers/settingsController.cpp" line="123"/>
<source>Backup file is corrupted</source> <source>Backup file is corrupted</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="148"/> <location filename="../ui/controllers/settingsController.cpp" line="139"/>
<source>All settings have been reset to default values</source> <source>All settings have been reset to default values</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../ui/controllers/settingsController.cpp" line="154"/> <location filename="../ui/controllers/settingsController.cpp" line="145"/>
<source>Cached profiles cleared</source> <source>Cached profiles cleared</source>
<translation></translation> <translation></translation>
</message> </message>
@@ -3260,7 +3220,7 @@ While it offers a blend of security, stability, and speed, it&apos;s essential t
<context> <context>
<name>VpnConnection</name> <name>VpnConnection</name>
<message> <message>
<location filename="../vpnconnection.cpp" line="438"/> <location filename="../vpnconnection.cpp" line="432"/>
<source>Mbps</source> <source>Mbps</source>
<translation></translation> <translation></translation>
</message> </message>
+26 -33
View File
@@ -5,14 +5,12 @@
#include <QNetworkReply> #include <QNetworkReply>
#include "configurators/openvpn_configurator.h" #include "configurators/openvpn_configurator.h"
#include "configurators/wireguard_configurator.h"
namespace namespace
{ {
namespace configKey namespace configKey
{ {
constexpr char cloak[] = "cloak"; constexpr char cloak[] = "cloak";
constexpr char awg[] = "awg";
constexpr char apiEdnpoint[] = "api_endpoint"; constexpr char apiEdnpoint[] = "api_endpoint";
constexpr char accessToken[] = "api_key"; constexpr char accessToken[] = "api_key";
@@ -28,42 +26,33 @@ ApiController::ApiController(const QSharedPointer<ServersModel> &serversModel,
{ {
} }
void ApiController::processCloudConfig(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData, QString &config) QString ApiController::genPublicKey(const QString &protocol)
{
if (protocol == configKey::cloak) {
return ".";
}
return QString();
}
QString ApiController::genCertificateRequest(const QString &protocol)
{
if (protocol == configKey::cloak) {
m_certRequest = OpenVpnConfigurator::createCertRequest();
return m_certRequest.request;
}
return QString();
}
void ApiController::processCloudConfig(const QString &protocol, QString &config)
{ {
if (protocol == configKey::cloak) { if (protocol == configKey::cloak) {
config.replace("<key>", "<key>\n"); config.replace("<key>", "<key>\n");
config.replace("$OPENVPN_PRIV_KEY", apiPayloadData.certRequest.privKey); config.replace("$OPENVPN_PRIV_KEY", m_certRequest.privKey);
return; return;
} else if (protocol == configKey::awg) {
config.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", apiPayloadData.wireGuardClientPrivKey);
} }
return; return;
} }
ApiController::ApiPayloadData ApiController::generateApiPayloadData(const QString &protocol)
{
ApiController::ApiPayloadData apiPayload;
if (protocol == configKey::cloak) {
apiPayload.certRequest = OpenVpnConfigurator::createCertRequest();
} else if (protocol == configKey::awg) {
auto connData = WireguardConfigurator::genClientKeys();
apiPayload.wireGuardClientPubKey = connData.clientPubKey;
apiPayload.wireGuardClientPrivKey = connData.clientPrivKey;
}
return apiPayload;
}
QJsonObject ApiController::fillApiPayload(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData)
{
QJsonObject obj;
if (protocol == configKey::cloak) {
obj[configKey::certificate] = apiPayloadData.certRequest.request;
} else if (protocol == configKey::awg) {
obj[configKey::publicKey] = apiPayloadData.wireGuardClientPubKey;
}
return obj;
}
bool ApiController::updateServerConfigFromApi() bool ApiController::updateServerConfigFromApi()
{ {
auto serverConfig = m_serversModel->getDefaultServerConfig(); auto serverConfig = m_serversModel->getDefaultServerConfig();
@@ -82,9 +71,13 @@ bool ApiController::updateServerConfigFromApi()
QString protocol = serverConfig.value(configKey::protocol).toString(); QString protocol = serverConfig.value(configKey::protocol).toString();
auto apiPayloadData = generateApiPayloadData(protocol); QJsonObject obj;
QByteArray requestBody = QJsonDocument(fillApiPayload(protocol, apiPayloadData)).toJson(); obj[configKey::publicKey] = genPublicKey(protocol);
obj[configKey::certificate] = genCertificateRequest(protocol);
QByteArray requestBody = QJsonDocument(obj).toJson();
qDebug() << requestBody;
QScopedPointer<QNetworkReply> reply; QScopedPointer<QNetworkReply> reply;
reply.reset(manager.post(request, requestBody)); reply.reset(manager.post(request, requestBody));
@@ -107,7 +100,7 @@ bool ApiController::updateServerConfigFromApi()
} }
QString configStr = ba; QString configStr = ba;
processCloudConfig(protocol, apiPayloadData, configStr); processCloudConfig(protocol, configStr);
QJsonObject cloudConfig = QJsonDocument::fromJson(configStr.toUtf8()).object(); QJsonObject cloudConfig = QJsonDocument::fromJson(configStr.toUtf8()).object();
+5 -9
View File
@@ -22,19 +22,15 @@ signals:
void errorOccurred(const QString &errorMessage); void errorOccurred(const QString &errorMessage);
private: private:
struct ApiPayloadData { QString genPublicKey(const QString &protocol);
OpenVpnConfigurator::ConnectionData certRequest; QString genCertificateRequest(const QString &protocol);
QString wireGuardClientPrivKey; void processCloudConfig(const QString &protocol, QString &config);
QString wireGuardClientPubKey;
};
ApiPayloadData generateApiPayloadData(const QString &protocol);
QJsonObject fillApiPayload(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData);
void processCloudConfig(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData, QString &config);
QSharedPointer<ServersModel> m_serversModel; QSharedPointer<ServersModel> m_serversModel;
QSharedPointer<ContainersModel> m_containersModel; QSharedPointer<ContainersModel> m_containersModel;
OpenVpnConfigurator::ConnectionData m_certRequest;
}; };
#endif // APICONTROLLER_H #endif // APICONTROLLER_H
+1 -2
View File
@@ -327,8 +327,7 @@ void ExportController::updateClientManagementModel(const DockerContainer contain
void ExportController::revokeConfig(const int row, const DockerContainer container, ServerCredentials credentials) void ExportController::revokeConfig(const int row, const DockerContainer container, ServerCredentials credentials)
{ {
ErrorCode errorCode = m_clientManagementModel->revokeClient(row, container, credentials, ErrorCode errorCode = m_clientManagementModel->revokeClient(row, container, credentials);
m_serversModel->getCurrentlyProcessedServerIndex());
if (errorCode != ErrorCode::NoError) { if (errorCode != ErrorCode::NoError) {
emit exportErrorOccurred(errorString(errorCode)); emit exportErrorOccurred(errorString(errorCode));
} }
+1 -6
View File
@@ -239,12 +239,7 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data)
// && !configMap.value("PresharedKey").isEmpty() && !configMap.value("PublicKey").isEmpty()) { // && !configMap.value("PresharedKey").isEmpty() && !configMap.value("PublicKey").isEmpty()) {
lastConfig[config_key::client_priv_key] = configMap.value("PrivateKey"); lastConfig[config_key::client_priv_key] = configMap.value("PrivateKey");
lastConfig[config_key::client_ip] = configMap.value("Address"); lastConfig[config_key::client_ip] = configMap.value("Address");
if (!configMap.value("PresharedKey").isEmpty()) { lastConfig[config_key::psk_key] = configMap.value("PresharedKey");
lastConfig[config_key::psk_key] = configMap.value("PresharedKey");
} else if (!configMap.value("PreSharedKey").isEmpty()) {
lastConfig[config_key::psk_key] = configMap.value("PreSharedKey");
}
lastConfig[config_key::server_pub_key] = configMap.value("PublicKey"); lastConfig[config_key::server_pub_key] = configMap.value("PublicKey");
// } else { // } else {
// qDebug() << "Failed to import profile"; // qDebug() << "Failed to import profile";
@@ -296,15 +296,6 @@ void InstallController::updateContainer(QJsonObject config)
emit installationErrorOccurred(errorString(errorCode)); emit installationErrorOccurred(errorString(errorCode));
} }
void InstallController::rebootCurrentlyProcessedServer()
{
int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex();
QString serverName = m_serversModel->data(serverIndex, ServersModel::Roles::NameRole).toString();
m_serversModel->rebootServer();
emit rebootCurrentlyProcessedServerFinished(tr("Server '%1' was rebooted").arg(serverName));
}
void InstallController::removeCurrentlyProcessedServer() void InstallController::removeCurrentlyProcessedServer()
{ {
int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex(); int serverIndex = m_serversModel->getCurrentlyProcessedServerIndex();
@@ -31,7 +31,6 @@ public slots:
void updateContainer(QJsonObject config); void updateContainer(QJsonObject config);
void removeCurrentlyProcessedServer(); void removeCurrentlyProcessedServer();
void rebootCurrentlyProcessedServer();
void removeAllContainers(); void removeAllContainers();
void removeCurrentlyProcessedContainer(); void removeCurrentlyProcessedContainer();
@@ -54,7 +53,6 @@ signals:
void scanServerFinished(bool isInstalledContainerFound); void scanServerFinished(bool isInstalledContainerFound);
void rebootCurrentlyProcessedServerFinished(const QString &finishedMessage);
void removeCurrentlyProcessedServerFinished(const QString &finishedMessage); void removeCurrentlyProcessedServerFinished(const QString &finishedMessage);
void removeAllContainersFinished(const QString &finishedMessage); void removeAllContainersFinished(const QString &finishedMessage);
void removeCurrentlyProcessedContainerFinished(const QString &finishedMessage); void removeCurrentlyProcessedContainerFinished(const QString &finishedMessage);
@@ -8,7 +8,6 @@
#include "version.h" #include "version.h"
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
#include "platforms/android/android_utils.h" #include "platforms/android/android_utils.h"
#include "platforms/android/android_controller.h"
#include <QJniObject> #include <QJniObject>
#endif #endif
@@ -92,21 +91,13 @@ void SettingsController::openLogsFolder()
void SettingsController::exportLogsFile(const QString &fileName) void SettingsController::exportLogsFile(const QString &fileName)
{ {
#ifdef Q_OS_ANDROID
AndroidController::instance()->exportLogsFile(fileName);
#else
SystemController::saveFile(fileName, Logger::getLogFile()); SystemController::saveFile(fileName, Logger::getLogFile());
#endif
} }
void SettingsController::clearLogs() void SettingsController::clearLogs()
{ {
#ifdef Q_OS_ANDROID
AndroidController::instance()->clearLogs();
#else
Logger::clearLogs(); Logger::clearLogs();
Logger::clearServiceLogs(); Logger::clearServiceLogs();
#endif
} }
void SettingsController::backupAppConfig(const QString &fileName) void SettingsController::backupAppConfig(const QString &fileName)
@@ -206,14 +197,3 @@ void SettingsController::toggleScreenshotsEnabled(bool enable)
}); });
#endif #endif
} }
bool SettingsController::isCameraPresent()
{
#if defined Q_OS_IOS
return true;
#elif defined Q_OS_ANDROID
return AndroidController::instance()->isCameraPresent();
#else
return false;
#endif
}
@@ -59,8 +59,6 @@ public slots:
bool isScreenshotsEnabled(); bool isScreenshotsEnabled();
void toggleScreenshotsEnabled(bool enable); void toggleScreenshotsEnabled(bool enable);
bool isCameraPresent();
signals: signals:
void primaryDnsChanged(); void primaryDnsChanged();
void secondaryDnsChanged(); void secondaryDnsChanged();
+18 -40
View File
@@ -245,13 +245,8 @@ ErrorCode ClientManagementModel::appendClient(const QString &clientId, const QSt
const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson(); const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson();
ServerController serverController(m_settings); ServerController serverController(m_settings);
QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable"); const QString clientsTableFile =
if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks QString("/opt/amnezia/%1/clientsTable").arg(ContainerProps::containerTypeToString(container));
|| container == DockerContainer::Cloak) {
clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(DockerContainer::OpenVpn));
} else {
clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(container));
}
error = serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile); error = serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile);
if (error != ErrorCode::NoError) { if (error != ErrorCode::NoError) {
@@ -278,13 +273,8 @@ ErrorCode ClientManagementModel::renameClient(const int row, const QString &clie
const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson(); const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson();
ServerController serverController(m_settings); ServerController serverController(m_settings);
QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable"); const QString clientsTableFile =
if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks QString("/opt/amnezia/%1/clientsTable").arg(ContainerProps::containerTypeToString(container));
|| container == DockerContainer::Cloak) {
clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(DockerContainer::OpenVpn));
} else {
clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(container));
}
ErrorCode error = ErrorCode error =
serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile); serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile);
@@ -296,36 +286,30 @@ ErrorCode ClientManagementModel::renameClient(const int row, const QString &clie
} }
ErrorCode ClientManagementModel::revokeClient(const int row, const DockerContainer container, ErrorCode ClientManagementModel::revokeClient(const int row, const DockerContainer container,
ServerCredentials credentials, const int serverIndex) ServerCredentials credentials)
{ {
ErrorCode errorCode = ErrorCode::NoError; ErrorCode errorCode = ErrorCode::NoError;
auto client = m_clientsTable.at(row).toObject();
QString clientId = client.value(configKey::clientId).toString();
if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks
|| container == DockerContainer::Cloak) { || container == DockerContainer::Cloak) {
errorCode = revokeOpenVpn(row, container, credentials, serverIndex); errorCode = revokeOpenVpn(row, container, credentials);
} else if (container == DockerContainer::WireGuard || container == DockerContainer::Awg) { } else if (container == DockerContainer::WireGuard || container == DockerContainer::Awg) {
errorCode = revokeWireGuard(row, container, credentials); errorCode = revokeWireGuard(row, container, credentials);
} }
if (errorCode == ErrorCode::NoError) { if (errorCode == ErrorCode::NoError) {
const auto server = m_settings->server(serverIndex); auto client = m_clientsTable.at(row).toObject();
QString clientId = client.value(configKey::clientId).toString();
const auto server = m_settings->defaultServer();
QJsonArray containers = server.value(config_key::containers).toArray(); QJsonArray containers = server.value(config_key::containers).toArray();
for (auto i = 0; i < containers.size(); i++) { for (auto i = 0; i < containers.size(); i++) {
auto containerConfig = containers.at(i).toObject(); auto containerConfig = containers.at(i).toObject();
auto containerType = ContainerProps::containerFromString(containerConfig.value(config_key::container).toString()); auto containerType = ContainerProps::containerFromString(containerConfig.value(config_key::container).toString());
if (containerType == container) { auto protocolConfig = containerConfig.value(ContainerProps::containerTypeToString(containerType)).toObject();
QJsonObject protocolConfig;
if (container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) {
protocolConfig = containerConfig.value(ContainerProps::containerTypeToString(DockerContainer::OpenVpn)).toObject();
} else {
protocolConfig = containerConfig.value(ContainerProps::containerTypeToString(containerType)).toObject();
}
if (protocolConfig.value(config_key::last_config).toString().contains(clientId)) { if (protocolConfig.value(config_key::last_config).toString().contains(clientId)) {
emit adminConfigRevoked(container); emit adminConfigRevoked(container);
}
} }
} }
} }
@@ -334,7 +318,7 @@ ErrorCode ClientManagementModel::revokeClient(const int row, const DockerContain
} }
ErrorCode ClientManagementModel::revokeOpenVpn(const int row, const DockerContainer container, ErrorCode ClientManagementModel::revokeOpenVpn(const int row, const DockerContainer container,
ServerCredentials credentials, const int serverIndex) ServerCredentials credentials)
{ {
auto client = m_clientsTable.at(row).toObject(); auto client = m_clientsTable.at(row).toObject();
QString clientId = client.value(configKey::clientId).toString(); QString clientId = client.value(configKey::clientId).toString();
@@ -343,7 +327,6 @@ ErrorCode ClientManagementModel::revokeOpenVpn(const int row, const DockerContai
"cd /opt/amnezia/openvpn ;\\" "cd /opt/amnezia/openvpn ;\\"
"easyrsa revoke %1 ;\\" "easyrsa revoke %1 ;\\"
"easyrsa gen-crl ;\\" "easyrsa gen-crl ;\\"
"chmod 666 pki/crl.pem ;\\"
"cp pki/crl.pem .'") "cp pki/crl.pem .'")
.arg(clientId); .arg(clientId);
@@ -362,8 +345,8 @@ ErrorCode ClientManagementModel::revokeOpenVpn(const int row, const DockerContai
const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson(); const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson();
QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable"); const QString clientsTableFile =
clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(DockerContainer::OpenVpn)); QString("/opt/amnezia/%1/clientsTable").arg(ContainerProps::containerTypeToString(container));
error = serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile); error = serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile);
if (error != ErrorCode::NoError) { if (error != ErrorCode::NoError) {
logger.error() << "Failed to upload the clientsTable file to the server"; logger.error() << "Failed to upload the clientsTable file to the server";
@@ -412,13 +395,8 @@ ErrorCode ClientManagementModel::revokeWireGuard(const int row, const DockerCont
const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson(); const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson();
QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable"); const QString clientsTableFile =
if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks QString("/opt/amnezia/%1/clientsTable").arg(ContainerProps::containerTypeToString(container));
|| container == DockerContainer::Cloak) {
clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(DockerContainer::OpenVpn));
} else {
clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(container));
}
error = serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile); error = serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile);
if (error != ErrorCode::NoError) { if (error != ErrorCode::NoError) {
logger.error() << "Failed to upload the clientsTable file to the server"; logger.error() << "Failed to upload the clientsTable file to the server";
+2 -2
View File
@@ -28,7 +28,7 @@ public slots:
ServerCredentials credentials); ServerCredentials credentials);
ErrorCode renameClient(const int row, const QString &userName, const DockerContainer container, ErrorCode renameClient(const int row, const QString &userName, const DockerContainer container,
ServerCredentials credentials, bool addTimeStamp = false); ServerCredentials credentials, bool addTimeStamp = false);
ErrorCode revokeClient(const int index, const DockerContainer container, ServerCredentials credentials, const int serverIndex); ErrorCode revokeClient(const int index, const DockerContainer container, ServerCredentials credentials);
protected: protected:
QHash<int, QByteArray> roleNames() const override; QHash<int, QByteArray> roleNames() const override;
@@ -41,7 +41,7 @@ private:
void migration(const QByteArray &clientsTableString); void migration(const QByteArray &clientsTableString);
ErrorCode revokeOpenVpn(const int row, const DockerContainer container, ServerCredentials credentials, const int serverIndex); ErrorCode revokeOpenVpn(const int row, const DockerContainer container, ServerCredentials credentials);
ErrorCode revokeWireGuard(const int row, const DockerContainer container, ServerCredentials credentials); ErrorCode revokeWireGuard(const int row, const DockerContainer container, ServerCredentials credentials);
ErrorCode getOpenVpnClients(ServerController &serverController, DockerContainer container, ServerCredentials credentials, int &count); ErrorCode getOpenVpnClients(ServerController &serverController, DockerContainer container, ServerCredentials credentials, int &count);
@@ -1,4 +1,4 @@
#include "ikev2ConfigModel.h" #include "ikev2ConfigModel.h".h "
#include "protocols/protocols_defs.h" #include "protocols/protocols_defs.h"
+3 -21
View File
@@ -15,10 +15,6 @@ ServersModel::ServersModel(std::shared_ptr<Settings> settings, QObject *parent)
auto defaultContainer = ContainerProps::containerFromString(m_servers.at(serverIndex).toObject().value(config_key::defaultContainer).toString()); auto defaultContainer = ContainerProps::containerFromString(m_servers.at(serverIndex).toObject().value(config_key::defaultContainer).toString());
emit ServersModel::defaultContainerChanged(defaultContainer); emit ServersModel::defaultContainerChanged(defaultContainer);
}); });
connect(this, &ServersModel::currentlyProcessedServerIndexChanged, this, [this](const int serverIndex) {
auto defaultContainer = ContainerProps::containerFromString(m_servers.at(serverIndex).toObject().value(config_key::defaultContainer).toString());
emit ServersModel::defaultContainerChanged(defaultContainer);
});
} }
int ServersModel::rowCount(const QModelIndex &parent) const int ServersModel::rowCount(const QModelIndex &parent) const
@@ -251,9 +247,10 @@ void ServersModel::addServer(const QJsonObject &server)
void ServersModel::editServer(const QJsonObject &server) void ServersModel::editServer(const QJsonObject &server)
{ {
beginResetModel();
m_settings->editServer(m_currentlyProcessedServerIndex, server); m_settings->editServer(m_currentlyProcessedServerIndex, server);
m_servers.replace(m_currentlyProcessedServerIndex, m_settings->serversArray().at(m_currentlyProcessedServerIndex)); m_servers = m_settings->serversArray();
emit dataChanged(index(m_currentlyProcessedServerIndex, 0), index(m_currentlyProcessedServerIndex, 0)); endResetModel();
updateContainersModel(); updateContainersModel();
} }
@@ -272,7 +269,6 @@ void ServersModel::removeServer()
if (m_settings->serversCount() == 0) { if (m_settings->serversCount() == 0) {
setDefaultServerIndex(-1); setDefaultServerIndex(-1);
} }
setCurrentlyProcessedServerIndex(m_defaultServerIndex);
endResetModel(); endResetModel();
} }
@@ -435,15 +431,6 @@ ErrorCode ServersModel::removeAllContainers()
return errorCode; return errorCode;
} }
ErrorCode ServersModel::rebootServer()
{
ServerController serverController(m_settings);
auto credentials = m_settings->serverCredentials(m_currentlyProcessedServerIndex);
ErrorCode errorCode = serverController.rebootServer(credentials);
return errorCode;
}
ErrorCode ServersModel::removeContainer(const int containerIndex) ErrorCode ServersModel::removeContainer(const int containerIndex)
{ {
ServerController serverController(m_settings); ServerController serverController(m_settings);
@@ -539,8 +526,3 @@ void ServersModel::toggleAmneziaDns(bool enabled)
emit defaultServerDescriptionChanged(); emit defaultServerDescriptionChanged();
} }
bool ServersModel::isDefaultServerFromApi()
{
return m_settings->server(m_defaultServerIndex).value(config_key::configVersion).toInt();
}
-3
View File
@@ -88,7 +88,6 @@ public slots:
ErrorCode removeContainer(const int containerIndex); ErrorCode removeContainer(const int containerIndex);
ErrorCode removeAllContainers(); ErrorCode removeAllContainers();
ErrorCode rebootServer();
void setDefaultContainer(const int containerIndex); void setDefaultContainer(const int containerIndex);
DockerContainer getDefaultContainer(); DockerContainer getDefaultContainer();
@@ -98,8 +97,6 @@ public slots:
void toggleAmneziaDns(bool enabled); void toggleAmneziaDns(bool enabled);
bool isDefaultServerFromApi();
protected: protected:
QHash<int, QByteArray> roleNames() const override; QHash<int, QByteArray> roleNames() const override;
+1 -6
View File
@@ -124,13 +124,8 @@ void Autostart::setAutostart(bool autostart) {
if (file.open(QIODevice::ReadWrite)) { if (file.open(QIODevice::ReadWrite)) {
QTextStream stream(&file); QTextStream stream(&file);
stream << "[Desktop Entry]" << Qt::endl; stream << "[Desktop Entry]" << Qt::endl;
stream << "Exec=AmneziaVPN" << Qt::endl; stream << "Exec=" << appPath() << Qt::endl;
stream << "Type=Application" << Qt::endl; stream << "Type=Application" << Qt::endl;
stream << "Name=AmneziaVPN" << Qt::endl;
stream << "Comment=Client of your self-hosted VPN" << Qt::endl;
stream << "Icon=/usr/share/pixmaps/AmneziaVPN.png" << Qt::endl;
stream << "Categories=Network;Qt;Security;" << Qt::endl;
stream << "Terminal=false" << Qt::endl;
} }
} }
} }
@@ -26,24 +26,6 @@ ListView {
id: containersRadioButtonGroup id: containersRadioButtonGroup
} }
Connections {
target: ServersModel
function onCurrentlyProcessedServerIndexChanged() {
if (ContainersModel.getDefaultContainer()) {
menuContent.checkCurrentItem()
}
}
}
function checkCurrentItem() {
var item = menuContent.itemAtIndex(currentIndex)
if (item !== null) {
var radioButton = item.children[0].children[0]
radioButton.checked = true
}
}
delegate: Item { delegate: Item {
implicitWidth: rootWidth implicitWidth: rootWidth
implicitHeight: content.implicitHeight implicitHeight: content.implicitHeight
@@ -78,8 +60,9 @@ ListView {
} }
if (checked) { if (checked) {
containersDropDown.menuVisible = false
ServersModel.setDefaultContainer(proxyContainersModel.mapToSource(index)) ServersModel.setDefaultContainer(proxyContainersModel.mapToSource(index))
containersDropDown.menuVisible = false
} else { } else {
if (!isSupported && isInstalled) { if (!isSupported && isInstalled) {
PageController.showErrorMessage(qsTr("The selected protocol is not supported on the current platform")) PageController.showErrorMessage(qsTr("The selected protocol is not supported on the current platform"))
@@ -25,7 +25,7 @@ DrawerType {
property string configExtension: ".vpn" property string configExtension: ".vpn"
property string configCaption: qsTr("Save AmneziaVPN config") property string configCaption: qsTr("Save AmneziaVPN config")
property string configFileName: "amnezia_config" property string configFileName: "amnezia_config.vpn"
width: parent.width width: parent.width
height: parent.height * 0.9 height: parent.height * 0.9
-1
View File
@@ -31,7 +31,6 @@ PageType {
containersDropDown.rootButtonClickedFunction() containersDropDown.rootButtonClickedFunction()
} }
} }
function onForceCloseDrawer() { function onForceCloseDrawer() {
buttonContent.state = "collapsed" buttonContent.state = "collapsed"
} }
@@ -81,7 +81,7 @@ PageType {
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 32 Layout.topMargin: 32
headerText: qsTr("VPN address subnet") headerText: qsTr("VPN Addresses Subnet")
textFieldText: subnetAddress textFieldText: subnetAddress
textField.onEditingFinished: { textField.onEditingFinished: {
@@ -91,7 +91,7 @@ PageType {
onLinkActivated: Qt.openUrlExternally(link) onLinkActivated: Qt.openUrlExternally(link)
textFormat: Text.RichText textFormat: Text.RichText
text: qsTr("Use <a href=\"https://www.torproject.org/download/\" style=\"color: #FBB26A;\">Tor Browser</a> to open this URL.") text: qsTr("Use <a href=\"https://www.torproject.org/download/\" style=\"color: #FBB26A;\">Tor Browser</a> to open this url.")
} }
ParagraphTextType { ParagraphTextType {
@@ -100,7 +100,7 @@ PageType {
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.rightMargin: 16 Layout.rightMargin: 16
text: qsTr("After creating your onion site, it takes a few minutes for the Tor network to make it available for use.") text: qsTr("After installation it takes several minutes while your onion site will become available in the Tor Network.")
} }
ParagraphTextType { ParagraphTextType {
+1 -1
View File
@@ -53,7 +53,7 @@ PageType {
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.rightMargin: 16 Layout.rightMargin: 16
text: qsTr("Support Amnezia") text: qsTr("Support the project with a donation")
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
} }
@@ -83,7 +83,7 @@ PageType {
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("DNS servers") text: qsTr("DNS servers")
descriptionText: qsTr("When AmneziaDNS is not used or installed") descriptionText: qsTr("If AmneziaDNS is not used or installed")
rightImageSource: "qrc:/images/controls/chevron-right.svg" rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() { clickedFunction: function() {
@@ -117,7 +117,7 @@ PageType {
Layout.fillWidth: true Layout.fillWidth: true
text: qsTr("App-based split tunneling") text: qsTr("App-based split tunneling")
descriptionText: qsTr("Allows you to use the VPN only for certain Apps") descriptionText: qsTr("Allows you to use the VPN only for certain applications")
rightImageSource: "qrc:/images/controls/chevron-right.svg" rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() { clickedFunction: function() {
-8
View File
@@ -28,14 +28,6 @@ PageType {
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
contentHeight: content.height contentHeight: content.height
enabled: !ServersModel.isDefaultServerFromApi()
Component.onCompleted: {
if (ServersModel.isDefaultServerFromApi()) {
PageController.showNotificationMessage(qsTr("Default server does not support custom dns"))
}
}
ColumnLayout { ColumnLayout {
id: content id: content
+3 -4
View File
@@ -66,8 +66,7 @@ PageType {
ColumnLayout { ColumnLayout {
Layout.alignment: Qt.AlignBaseline Layout.alignment: Qt.AlignBaseline
Layout.preferredWidth: GC.isMobile() ? 0 : root.width / 3 Layout.preferredWidth: root.width / 3
visible: !GC.isMobile()
ImageButtonType { ImageButtonType {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
@@ -91,7 +90,7 @@ PageType {
ColumnLayout { ColumnLayout {
Layout.alignment: Qt.AlignBaseline Layout.alignment: Qt.AlignBaseline
Layout.preferredWidth: root.width / ( GC.isMobile() ? 2 : 3 ) Layout.preferredWidth: root.width / 3
ImageButtonType { ImageButtonType {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
@@ -132,7 +131,7 @@ PageType {
ColumnLayout { ColumnLayout {
Layout.alignment: Qt.AlignBaseline Layout.alignment: Qt.AlignBaseline
Layout.preferredWidth: root.width / ( GC.isMobile() ? 2 : 3 ) Layout.preferredWidth: root.width / 3
ImageButtonType { ImageButtonType {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
@@ -38,10 +38,6 @@ PageType {
PageController.showNotificationMessage(finishedMessage) PageController.showNotificationMessage(finishedMessage)
} }
function onRebootCurrentlyProcessedServerFinished(finishedMessage) {
PageController.showNotificationMessage(finishedMessage)
}
function onRemoveAllContainersFinished(finishedMessage) { function onRemoveAllContainersFinished(finishedMessage) {
PageController.closePage() // close deInstalling page PageController.closePage() // close deInstalling page
PageController.showNotificationMessage(finishedMessage) PageController.showNotificationMessage(finishedMessage)
@@ -132,39 +128,6 @@ PageType {
visible: content.isServerWithWriteAccess visible: content.isServerWithWriteAccess
} }
LabelWithButtonType {
visible: content.isServerWithWriteAccess
Layout.fillWidth: true
text: qsTr("Reboot server")
textColor: "#EB5757"
clickedFunction: function() {
questionDrawer.headerText = qsTr("Do you want to reboot the server?")
questionDrawer.descriptionText = qsTr("The reboot process may take approximately 30 seconds. Are you sure you wish to proceed?")
questionDrawer.yesButtonText = qsTr("Continue")
questionDrawer.noButtonText = qsTr("Cancel")
questionDrawer.yesButtonFunction = function() {
questionDrawer.visible = false
PageController.showBusyIndicator(true)
if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) {
ConnectionController.closeConnection()
}
InstallController.rebootCurrentlyProcessedServer()
PageController.showBusyIndicator(false)
}
questionDrawer.noButtonFunction = function() {
questionDrawer.visible = false
}
questionDrawer.visible = true
}
}
DividerType {
visible: content.isServerWithWriteAccess
}
LabelWithButtonType { LabelWithButtonType {
Layout.fillWidth: true Layout.fillWidth: true
@@ -172,7 +135,7 @@ PageType {
textColor: "#EB5757" textColor: "#EB5757"
clickedFunction: function() { clickedFunction: function() {
questionDrawer.headerText = qsTr("Do you want to remove the server?") questionDrawer.headerText = qsTr("Remove server?")
questionDrawer.descriptionText = qsTr("All installed AmneziaVPN services will still remain on the server.") questionDrawer.descriptionText = qsTr("All installed AmneziaVPN services will still remain on the server.")
questionDrawer.yesButtonText = qsTr("Continue") questionDrawer.yesButtonText = qsTr("Continue")
questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.noButtonText = qsTr("Cancel")
@@ -203,7 +166,7 @@ PageType {
textColor: "#EB5757" textColor: "#EB5757"
clickedFunction: function() { clickedFunction: function() {
questionDrawer.headerText = qsTr("Do you want to clear server from Amnezia software?") questionDrawer.headerText = qsTr("Clear server from Amnezia software?")
questionDrawer.descriptionText = qsTr("All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted.") questionDrawer.descriptionText = qsTr("All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted.")
questionDrawer.yesButtonText = qsTr("Continue") questionDrawer.yesButtonText = qsTr("Continue")
questionDrawer.noButtonText = qsTr("Cancel") questionDrawer.noButtonText = qsTr("Cancel")
@@ -225,7 +188,7 @@ PageType {
DividerType { DividerType {
visible: content.isServerWithWriteAccess visible: content.isServerWithWriteAccess
} }
QuestionDrawer { QuestionDrawer {
id: questionDrawer id: questionDrawer
@@ -55,9 +55,6 @@ PageType {
model: SortFilterProxyModel { model: SortFilterProxyModel {
id: proxyContainersModel id: proxyContainersModel
sourceModel: ContainersModel sourceModel: ContainersModel
sorters: [
RoleSorter { roleName: "isInstalled"; sortOrder: Qt.DescendingOrder }
]
} }
Component.onCompleted: updateContainersModelFilters() Component.onCompleted: updateContainersModelFilters()
@@ -21,13 +21,7 @@ PageType {
id: root id: root
property bool pageEnabled: { property bool pageEnabled: {
return !ConnectionController.isConnected && !ServersModel.isDefaultServerFromApi() return !ConnectionController.isConnected
}
Component.onCompleted: {
if (ServersModel.isDefaultServerFromApi()) {
PageController.showNotificationMessage(qsTr("Default server does not support split tunneling function"))
}
} }
Connections { Connections {
@@ -56,7 +50,7 @@ PageType {
QtObject { QtObject {
id: onlyForwardSites id: onlyForwardSites
property string name: qsTr("Only the sites listed here will be accessed through the VPN") property string name: qsTr("Addresses from the list should be accessed via VPN")
property int type: routeMode.onlyForwardSites property int type: routeMode.onlyForwardSites
} }
QtObject { QtObject {
@@ -251,7 +245,7 @@ PageType {
TextFieldWithHeaderType { TextFieldWithHeaderType {
Layout.fillWidth: true Layout.fillWidth: true
textFieldPlaceholderText: qsTr("website or IP") textFieldPlaceholderText: qsTr("Site or IP")
buttonImageSource: "qrc:/images/controls/plus.svg" buttonImageSource: "qrc:/images/controls/plus.svg"
clickedFunc: function() { clickedFunc: function() {
@@ -295,7 +289,7 @@ PageType {
Layout.fillWidth: true Layout.fillWidth: true
Layout.margins: 16 Layout.margins: 16
headerText: qsTr("Import / Export Sites") headerText: qsTr("Import/Export Sites")
} }
LabelWithButtonType { LabelWithButtonType {
@@ -90,7 +90,7 @@ It's okay as long as it's from someone you trust.")
LabelWithButtonType { LabelWithButtonType {
Layout.fillWidth: true Layout.fillWidth: true
visible: SettingsController.isCameraPresent() visible: GC.isMobile()
text: qsTr("QR-code") text: qsTr("QR-code")
rightImageSource: "qrc:/images/controls/chevron-right.svg" rightImageSource: "qrc:/images/controls/chevron-right.svg"
@@ -105,7 +105,7 @@ It's okay as long as it's from someone you trust.")
} }
DividerType { DividerType {
visible: SettingsController.isCameraPresent() visible: GC.isMobile()
} }
LabelWithButtonType { LabelWithButtonType {
@@ -73,7 +73,7 @@ PageType {
property bool hidePassword: true property bool hidePassword: true
Layout.fillWidth: true Layout.fillWidth: true
headerText: qsTr("Password or SSH private key") headerText: qsTr("Password / SSH private key")
textField.echoMode: hidePassword ? TextInput.Password : TextInput.Normal textField.echoMode: hidePassword ? TextInput.Password : TextInput.Normal
buttonImageSource: textFieldText !== "" ? (hidePassword ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg") buttonImageSource: textFieldText !== "" ? (hidePassword ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg")
: "" : ""
+6 -8
View File
@@ -112,19 +112,17 @@ PageType {
var defaultContainerProto = ContainerProps.defaultProtocol(dockerContainer) var defaultContainerProto = ContainerProps.defaultProtocol(dockerContainer)
containers.dockerContainer = dockerContainer containers.dockerContainer = dockerContainer
containers.containerDefaultPort = ProtocolProps.getPortForInstall(defaultContainerProto) containers.containerDefaultPort = ProtocolProps.defaultPort(defaultContainerProto)
containers.containerDefaultTransportProto = ProtocolProps.defaultTransportProto(defaultContainerProto) containers.containerDefaultTransportProto = ProtocolProps.defaultTransportProto(defaultContainerProto)
} }
} }
} }
}
Component.onCompleted: { Component.onCompleted: {
var item = containers.itemAtIndex(containers.currentIndex) if (index === containers.currentIndex) {
if (item !== null) { card.checked = true
var button = item.children[0].children[0] card.clicked()
button.checked = true }
button.clicked()
} }
} }
} }
@@ -42,7 +42,6 @@ PageType {
function onInstallServerFinished(finishedMessage) { function onInstallServerFinished(finishedMessage) {
if (!ConnectionController.isConnected) { if (!ConnectionController.isConnected) {
ServersModel.setDefaultServerIndex(ServersModel.getServersCount() - 1); ServersModel.setDefaultServerIndex(ServersModel.getServersCount() - 1);
ServersModel.currentlyProcessedIndex = ServersModel.defaultIndex
} }
PageController.goToStartPage() PageController.goToStartPage()

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