Merge branch 'dev' of github.com:amnezia-vpn/amnezia-client into HEAD

This commit is contained in:
vladimir.kuznetsov 2024-05-20 10:49:42 +02:00
commit 3241782098
308 changed files with 23129 additions and 5889 deletions

View file

@ -233,7 +233,7 @@ jobs:
- name: 'Setup xcode' - name: 'Setup xcode'
uses: maxim-lobanov/setup-xcode@v1 uses: maxim-lobanov/setup-xcode@v1
with: with:
xcode-version: '13.4' xcode-version: '14.3.1'
- name: 'Install Qt' - name: 'Install Qt'
uses: jurplel/install-qt-action@v3 uses: jurplel/install-qt-action@v3

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.4.1.4 project(${PROJECT} VERSION 4.5.3.0
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 49) set(APP_ANDROID_VERSION_CODE 52)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux") set(MZ_PLATFORM_NAME "linux")

@ -1 +1 @@
Subproject commit ab4e6b680dc3d3d81809886c2c25b77750659c1a Subproject commit c969f28b84f8343cdae0b5f39cfd876f8a1e01cb

@ -1 +1 @@
Subproject commit 6f71d0743d96b022863e2e4d6ebf7984842669ee Subproject commit 7c821a8d5c1ad5ad94e0763b4f25a875b5a6fe1b

View file

@ -72,7 +72,7 @@ namespace QSimpleCrypto
/// \param notAfter - X509 end date. /// \param notAfter - X509 end date.
/// \return Returns OpenSSL X509 structure or nullptr, if error happened. Returned value must be cleaned up with 'X509_free' to avoid memory leak. /// \return Returns OpenSSL X509 structure or nullptr, if error happened. Returned value must be cleaned up with 'X509_free' to avoid memory leak.
/// ///
X509* generateSelfSignedCertificate(const RSA* rsa, const QMap<QByteArray, QByteArray>& additionalData, X509* generateSelfSignedCertificate(RSA* rsa, const QMap<QByteArray, QByteArray>& additionalData,
const QByteArray& certificateFileName = "", const EVP_MD* md = EVP_sha512(), const QByteArray& certificateFileName = "", const EVP_MD* md = EVP_sha512(),
const long& serialNumber = 1, const long& version = x509LastVersion, const long& serialNumber = 1, const long& version = x509LastVersion,
const long& notBefore = 0, const long& notAfter = oneYear); const long& notBefore = 0, const long& notAfter = oneYear);

View file

@ -139,7 +139,7 @@ X509* QSimpleCrypto::QX509::verifyCertificate(X509* x509, X509_STORE* store)
/// \param notAfter - X509 end date. /// \param notAfter - X509 end date.
/// \return Returns OpenSSL X509 structure or nullptr, if error happened. Returned value must be cleaned up with 'X509_free' to avoid memory leak. /// \return Returns OpenSSL X509 structure or nullptr, if error happened. Returned value must be cleaned up with 'X509_free' to avoid memory leak.
/// ///
X509* QSimpleCrypto::QX509::generateSelfSignedCertificate(const RSA* rsa, const QMap<QByteArray, QByteArray>& additionalData, X509* QSimpleCrypto::QX509::generateSelfSignedCertificate(RSA* rsa, const QMap<QByteArray, QByteArray>& additionalData,
const QByteArray& certificateFileName, const EVP_MD* md, const QByteArray& certificateFileName, const EVP_MD* md,
const long& serialNumber, const long& version, const long& serialNumber, const long& version,
const long& notBefore, const long& notAfter) const long& notBefore, const long& notAfter)

View file

@ -63,11 +63,14 @@ qt6_add_resources(QRC ${QRC} ${CMAKE_CURRENT_LIST_DIR}/resources.qrc)
set(CMAKE_AUTORCC ON) set(CMAKE_AUTORCC ON)
set(AMNEZIAVPN_TS_FILES set(AMNEZIAVPN_TS_FILES
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ru.ts ${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ru_RU.ts
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_zh_CN.ts ${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_zh_CN.ts
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_fa_IR.ts ${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_fa_IR.ts
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ar.ts ${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ar_EG.ts
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_my_MM.ts ${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_my_MM.ts
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_uk_UA.ts
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ur_PK.ts
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_hi_IN.ts
) )
file(GLOB_RECURSE AMNEZIAVPN_TS_SOURCES *.qrc *.cpp *.h *.ui) file(GLOB_RECURSE AMNEZIAVPN_TS_SOURCES *.qrc *.cpp *.h *.ui)
@ -119,16 +122,18 @@ set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/core/errorstrings.h ${CMAKE_CURRENT_LIST_DIR}/core/errorstrings.h
${CMAKE_CURRENT_LIST_DIR}/core/scripts_registry.h ${CMAKE_CURRENT_LIST_DIR}/core/scripts_registry.h
${CMAKE_CURRENT_LIST_DIR}/core/server_defs.h ${CMAKE_CURRENT_LIST_DIR}/core/server_defs.h
${CMAKE_CURRENT_LIST_DIR}/core/controllers/apiController.h
${CMAKE_CURRENT_LIST_DIR}/core/controllers/serverController.h ${CMAKE_CURRENT_LIST_DIR}/core/controllers/serverController.h
${CMAKE_CURRENT_LIST_DIR}/core/controllers/vpnConfigurationController.h
${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.h ${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.h
${CMAKE_CURRENT_LIST_DIR}/protocols/qml_register_protocols.h ${CMAKE_CURRENT_LIST_DIR}/protocols/qml_register_protocols.h
${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.h
${CMAKE_CURRENT_LIST_DIR}/ui/pages.h ${CMAKE_CURRENT_LIST_DIR}/ui/pages.h
${CMAKE_CURRENT_LIST_DIR}/ui/property_helper.h ${CMAKE_CURRENT_LIST_DIR}/ui/property_helper.h
${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.h ${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.h
${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.h ${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.h
${CMAKE_CURRENT_BINARY_DIR}/version.h ${CMAKE_CURRENT_BINARY_DIR}/version.h
${CMAKE_CURRENT_LIST_DIR}/core/sshclient.h ${CMAKE_CURRENT_LIST_DIR}/core/sshclient.h
${CMAKE_CURRENT_LIST_DIR}/core/networkUtilities.h
) )
# Mozilla headres # Mozilla headres
@ -146,11 +151,16 @@ include_directories(mozilla/models)
if(NOT IOS) if(NOT IOS)
set(HEADERS ${HEADERS} set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/MobileUtils.h
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.h ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.h
) )
endif() endif()
if(NOT ANDROID)
set(HEADERS ${HEADERS}
${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.h
)
endif()
set(SOURCES ${SOURCES} set(SOURCES ${SOURCES}
${CMAKE_CURRENT_LIST_DIR}/migrations.cpp ${CMAKE_CURRENT_LIST_DIR}/migrations.cpp
${CMAKE_CURRENT_LIST_DIR}/amnezia_application.cpp ${CMAKE_CURRENT_LIST_DIR}/amnezia_application.cpp
@ -158,12 +168,14 @@ set(SOURCES ${SOURCES}
${CMAKE_CURRENT_LIST_DIR}/core/errorstrings.cpp ${CMAKE_CURRENT_LIST_DIR}/core/errorstrings.cpp
${CMAKE_CURRENT_LIST_DIR}/core/scripts_registry.cpp ${CMAKE_CURRENT_LIST_DIR}/core/scripts_registry.cpp
${CMAKE_CURRENT_LIST_DIR}/core/server_defs.cpp ${CMAKE_CURRENT_LIST_DIR}/core/server_defs.cpp
${CMAKE_CURRENT_LIST_DIR}/core/controllers/apiController.cpp
${CMAKE_CURRENT_LIST_DIR}/core/controllers/serverController.cpp ${CMAKE_CURRENT_LIST_DIR}/core/controllers/serverController.cpp
${CMAKE_CURRENT_LIST_DIR}/core/controllers/vpnConfigurationController.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.cpp ${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.cpp
${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.cpp
${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.cpp ${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.cpp ${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.cpp
${CMAKE_CURRENT_LIST_DIR}/core/sshclient.cpp ${CMAKE_CURRENT_LIST_DIR}/core/sshclient.cpp
${CMAKE_CURRENT_LIST_DIR}/core/networkUtilities.cpp
) )
# Mozilla sources # Mozilla sources
@ -180,11 +192,16 @@ endif()
if(NOT IOS) if(NOT IOS)
set(SOURCES ${SOURCES} set(SOURCES ${SOURCES}
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/MobileUtils.cpp
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.cpp ${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.cpp
) )
endif() endif()
if(NOT ANDROID)
set(SOURCES ${SOURCES}
${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.cpp
)
endif()
file(GLOB COMMON_FILES_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/*.h) file(GLOB COMMON_FILES_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/*.h)
file(GLOB COMMON_FILES_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/*.cpp) file(GLOB COMMON_FILES_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/*.cpp)
@ -293,6 +310,7 @@ if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.h ${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.h
${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.h ${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.h
${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.h ${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.h
${CMAKE_CURRENT_LIST_DIR}/protocols/xrayprotocol.h
${CMAKE_CURRENT_LIST_DIR}/protocols/awgprotocol.h ${CMAKE_CURRENT_LIST_DIR}/protocols/awgprotocol.h
) )
@ -304,6 +322,7 @@ if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.cpp ${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.cpp ${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.cpp ${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/xrayprotocol.cpp
${CMAKE_CURRENT_LIST_DIR}/protocols/awgprotocol.cpp ${CMAKE_CURRENT_LIST_DIR}/protocols/awgprotocol.cpp
) )
endif() endif()

View file

@ -9,14 +9,15 @@
#include <QTextDocument> #include <QTextDocument>
#include <QTimer> #include <QTimer>
#include <QTranslator> #include <QTranslator>
#include <QQuickItem> #include <QQuickItem>
#include "logger.h" #include "logger.h"
#include "ui/models/installedAppsModel.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 "core/installedAppsImageProvider.h"
#include "platforms/android/android_controller.h" #include "platforms/android/android_controller.h"
#endif #endif
@ -30,8 +31,8 @@
#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,
int timeout, const QString &userData) const QString &userData)
: SingleApplication(argc, argv, allowSecondary, options, timeout, userData) : SingleApplication(argc, argv, allowSecondary, options, timeout, userData)
#endif #endif
{ {
@ -44,16 +45,17 @@ AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecond
s.setValue("permFixed", true); s.setValue("permFixed", true);
} }
QString configLoc1 = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/" QString configLoc1 = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/" + ORGANIZATION_NAME + "/"
+ ORGANIZATION_NAME + "/" + APPLICATION_NAME + ".conf"; + 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 + "/"
+ ORGANIZATION_NAME + "/" + APPLICATION_NAME + "/" + APPLICATION_NAME + ".conf"; + 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);
m_nam = new QNetworkAccessManager(this);
} }
AmneziaApplication::~AmneziaApplication() AmneziaApplication::~AmneziaApplication()
@ -82,8 +84,7 @@ void AmneziaApplication::init()
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_vpnConnection.reset(new VpnConnection(m_settings));
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();
@ -96,21 +97,16 @@ void AmneziaApplication::init()
qFatal("Android logging initialization failed"); qFatal("Android logging initialization failed");
} }
AndroidController::instance()->setSaveLogs(m_settings->isSaveLogs()); AndroidController::instance()->setSaveLogs(m_settings->isSaveLogs());
connect(m_settings.get(), &Settings::saveLogsChanged, connect(m_settings.get(), &Settings::saveLogsChanged, AndroidController::instance(), &AndroidController::setSaveLogs);
AndroidController::instance(), &AndroidController::setSaveLogs);
AndroidController::instance()->setScreenshotsEnabled(m_settings->isScreenshotsEnabled()); AndroidController::instance()->setScreenshotsEnabled(m_settings->isScreenshotsEnabled());
connect(m_settings.get(), &Settings::screenshotsEnabledChanged, connect(m_settings.get(), &Settings::screenshotsEnabledChanged, AndroidController::instance(), &AndroidController::setScreenshotsEnabled);
AndroidController::instance(), &AndroidController::setScreenshotsEnabled);
connect(m_settings.get(), &Settings::serverRemoved, connect(m_settings.get(), &Settings::serverRemoved, AndroidController::instance(), &AndroidController::resetLastServer);
AndroidController::instance(), &AndroidController::resetLastServer);
connect(m_settings.get(), &Settings::settingsCleared, connect(m_settings.get(), &Settings::settingsCleared, []() { AndroidController::instance()->resetLastServer(-1); });
[](){ AndroidController::instance()->resetLastServer(-1); });
connect(AndroidController::instance(), &AndroidController::initConnectionState, this, connect(AndroidController::instance(), &AndroidController::initConnectionState, this, [this](Vpn::ConnectionState state) {
[this](Vpn::ConnectionState state) {
m_connectionController->onConnectionStateChanged(state); m_connectionController->onConnectionStateChanged(state);
if (m_vpnConnection) if (m_vpnConnection)
m_vpnConnection->restoreConnection(); m_vpnConnection->restoreConnection();
@ -124,6 +120,8 @@ void AmneziaApplication::init()
m_importController->extractConfigFromData(data); m_importController->extractConfigFromData(data);
m_pageController->goToPageViewConfig(); m_pageController->goToPageViewConfig();
}); });
m_engine->addImageProvider(QLatin1String("installedAppImage"), new InstalledAppsImageProvider);
#endif #endif
#ifdef Q_OS_IOS #ifdef Q_OS_IOS
@ -140,28 +138,24 @@ void AmneziaApplication::init()
m_settingsController->importBackupFromOutside(filePath); m_settingsController->importBackupFromOutside(filePath);
}); });
QTimer::singleShot(0, this, [this](){ QTimer::singleShot(0, this, [this]() { AmneziaVPN::toggleScreenshots(m_settings->isScreenshotsEnabled()); });
AmneziaVPN::toggleScreenshots(m_settings->isScreenshotsEnabled());
});
connect(m_settings.get(), &Settings::screenshotsEnabledChanged, [](bool enabled) { connect(m_settings.get(), &Settings::screenshotsEnabledChanged, [](bool enabled) { AmneziaVPN::toggleScreenshots(enabled); });
AmneziaVPN::toggleScreenshots(enabled);
});
#endif #endif
#ifndef Q_OS_ANDROID
m_notificationHandler.reset(NotificationHandler::create(nullptr)); m_notificationHandler.reset(NotificationHandler::create(nullptr));
connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(), connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(),
&NotificationHandler::setConnectionState); &NotificationHandler::setConnectionState);
connect(m_notificationHandler.get(), &NotificationHandler::raiseRequested, m_pageController.get(), connect(m_notificationHandler.get(), &NotificationHandler::raiseRequested, m_pageController.get(), &PageController::raiseMainWindow);
&PageController::raiseMainWindow);
connect(m_notificationHandler.get(), &NotificationHandler::connectRequested, m_connectionController.get(), connect(m_notificationHandler.get(), &NotificationHandler::connectRequested, m_connectionController.get(),
&ConnectionController::openConnection); static_cast<void (ConnectionController::*)()>(&ConnectionController::openConnection));
connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(), connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(),
&ConnectionController::closeConnection); &ConnectionController::closeConnection);
connect(this, &AmneziaApplication::translationsUpdated, m_notificationHandler.get(), connect(this, &AmneziaApplication::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated);
&NotificationHandler::onTranslationsUpdated); #endif
m_engine->load(url); m_engine->load(url);
m_systemController->setQmlRoot(m_engine->rootObjects().value(0)); m_systemController->setQmlRoot(m_engine->rootObjects().value(0));
@ -234,7 +228,8 @@ void AmneziaApplication::registerTypes()
qmlRegisterSingletonType(QUrl("qrc:/ui/qml/Filters/ContainersModelFilters.qml"), "ContainersModelFilters", 1, 0, qmlRegisterSingletonType(QUrl("qrc:/ui/qml/Filters/ContainersModelFilters.qml"), "ContainersModelFilters", 1, 0,
"ContainersModelFilters"); "ContainersModelFilters");
// qmlRegisterType<InstalledAppsModel>("InstalledAppsModel", 1, 0, "InstalledAppsModel");
Vpn::declareQmlVpnConnectionStateEnum(); Vpn::declareQmlVpnConnectionStateEnum();
PageLoader::declareQmlPageEnum(); PageLoader::declareQmlPageEnum();
} }
@ -311,8 +306,7 @@ void AmneziaApplication::initModels()
m_serversModel.reset(new ServersModel(m_settings, this)); m_serversModel.reset(new ServersModel(m_settings, this));
m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get()); m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get());
connect(m_serversModel.get(), &ServersModel::containersUpdated, m_containersModel.get(), connect(m_serversModel.get(), &ServersModel::containersUpdated, m_containersModel.get(), &ContainersModel::updateModel);
&ContainersModel::updateModel);
connect(m_serversModel.get(), &ServersModel::defaultServerContainersUpdated, m_defaultServerContainersModel.get(), connect(m_serversModel.get(), &ServersModel::defaultServerContainersUpdated, m_defaultServerContainersModel.get(),
&ContainersModel::updateModel); &ContainersModel::updateModel);
m_serversModel->resetModel(); m_serversModel->resetModel();
@ -325,6 +319,9 @@ void AmneziaApplication::initModels()
m_sitesModel.reset(new SitesModel(m_settings, this)); m_sitesModel.reset(new SitesModel(m_settings, this));
m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get()); m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get());
m_appSplitTunnelingModel.reset(new AppSplitTunnelingModel(m_settings, this));
m_engine->rootContext()->setContextProperty("AppSplitTunnelingModel", m_appSplitTunnelingModel.get());
m_protocolsModel.reset(new ProtocolsModel(m_settings, this)); m_protocolsModel.reset(new ProtocolsModel(m_settings, this));
m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get()); m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get());
@ -343,6 +340,9 @@ void AmneziaApplication::initModels()
m_awgConfigModel.reset(new AwgConfigModel(this)); m_awgConfigModel.reset(new AwgConfigModel(this));
m_engine->rootContext()->setContextProperty("AwgConfigModel", m_awgConfigModel.get()); m_engine->rootContext()->setContextProperty("AwgConfigModel", m_awgConfigModel.get());
m_xrayConfigModel.reset(new XrayConfigModel(this));
m_engine->rootContext()->setContextProperty("XrayConfigModel", m_xrayConfigModel.get());
#ifdef Q_OS_WINDOWS #ifdef Q_OS_WINDOWS
m_ikev2ConfigModel.reset(new Ikev2ConfigModel(this)); m_ikev2ConfigModel.reset(new Ikev2ConfigModel(this));
m_engine->rootContext()->setContextProperty("Ikev2ConfigModel", m_ikev2ConfigModel.get()); m_engine->rootContext()->setContextProperty("Ikev2ConfigModel", m_ikev2ConfigModel.get());
@ -355,28 +355,27 @@ void AmneziaApplication::initModels()
m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get()); m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get());
connect(m_clientManagementModel.get(), &ClientManagementModel::adminConfigRevoked, m_serversModel.get(), connect(m_clientManagementModel.get(), &ClientManagementModel::adminConfigRevoked, m_serversModel.get(),
&ServersModel::clearCachedProfile); &ServersModel::clearCachedProfile);
connect(m_configurator.get(), &VpnConfigurator::newVpnConfigCreated, this,
[this](const QString &clientId, const QString &clientName, const DockerContainer container,
ServerCredentials credentials) {
m_serversModel->reloadDefaultServerContainerConfig();
m_clientManagementModel->appendClient(clientId, clientName, container, credentials);
emit m_configurator->clientModelUpdated();
});
} }
void AmneziaApplication::initControllers() void AmneziaApplication::initControllers()
{ {
m_connectionController.reset(new ConnectionController(m_serversModel, m_containersModel, m_vpnConnection)); m_connectionController.reset(
new ConnectionController(m_serversModel, m_containersModel, m_clientManagementModel, m_vpnConnection, m_settings));
m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get()); m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get());
connect(this, &AmneziaApplication::translationsUpdated, m_connectionController.get(), connect(m_connectionController.get(), &ConnectionController::connectionErrorOccurred, this, [this](const QString &errorMessage) {
&ConnectionController::onTranslationsUpdated); emit m_pageController->showErrorMessage(errorMessage);
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
});
connect(m_connectionController.get(), &ConnectionController::connectButtonClicked, m_connectionController.get(),
&ConnectionController::toggleConnection, Qt::QueuedConnection);
connect(this, &AmneziaApplication::translationsUpdated, m_connectionController.get(), &ConnectionController::onTranslationsUpdated);
m_pageController.reset(new PageController(m_serversModel, m_settings)); m_pageController.reset(new PageController(m_serversModel, m_settings));
m_engine->rootContext()->setContextProperty("PageController", m_pageController.get()); m_engine->rootContext()->setContextProperty("PageController", m_pageController.get());
m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_settings)); m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_clientManagementModel, m_settings));
m_engine->rootContext()->setContextProperty("InstallController", m_installController.get()); m_engine->rootContext()->setContextProperty("InstallController", m_installController.get());
connect(m_installController.get(), &InstallController::passphraseRequestStarted, m_pageController.get(), connect(m_installController.get(), &InstallController::passphraseRequestStarted, m_pageController.get(),
&PageController::showPassphraseRequestDrawer); &PageController::showPassphraseRequestDrawer);
@ -388,36 +387,23 @@ void AmneziaApplication::initControllers()
m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings)); m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings));
m_engine->rootContext()->setContextProperty("ImportController", m_importController.get()); m_engine->rootContext()->setContextProperty("ImportController", m_importController.get());
m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_clientManagementModel, m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_clientManagementModel, m_settings));
m_settings, m_configurator));
m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get()); m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get());
m_settingsController.reset( m_settingsController.reset(
new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, m_settings)); new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, m_appSplitTunnelingModel, m_settings));
m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get()); m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get());
if (m_settingsController->isAutoConnectEnabled() && m_serversModel->getDefaultServerIndex() >= 0) { if (m_settingsController->isAutoConnectEnabled() && m_serversModel->getDefaultServerIndex() >= 0) {
QTimer::singleShot(1000, this, [this]() { m_connectionController->openConnection(); }); QTimer::singleShot(1000, this, [this]() { m_connectionController->openConnection(); });
} }
connect(m_settingsController.get(), &SettingsController::amneziaDnsToggled, m_serversModel.get(), connect(m_settingsController.get(), &SettingsController::amneziaDnsToggled, m_serversModel.get(), &ServersModel::toggleAmneziaDns);
&ServersModel::toggleAmneziaDns);
m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel)); m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel));
m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get()); m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get());
m_appSplitTunnelingController.reset(new AppSplitTunnelingController(m_settings, m_appSplitTunnelingModel));
m_engine->rootContext()->setContextProperty("AppSplitTunnelingController", m_appSplitTunnelingController.get());
m_systemController.reset(new SystemController(m_settings)); m_systemController.reset(new SystemController(m_settings));
m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get()); m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get());
m_apiController.reset(new ApiController(m_serversModel, m_containersModel));
m_engine->rootContext()->setContextProperty("ApiController", m_apiController.get());
connect(m_apiController.get(), &ApiController::updateStarted, this,
[this]() { emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Connecting); });
connect(m_apiController.get(), &ApiController::errorOccurred, this, [this](const QString &errorMessage) {
if (m_connectionController->isConnectionInProgress()) {
emit m_pageController->showErrorMessage(errorMessage);
}
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
});
connect(m_apiController.get(), &ApiController::updateFinished, m_connectionController.get(),
&ConnectionController::toggleConnection);
} }

View file

@ -2,6 +2,7 @@
#define AMNEZIA_APPLICATION_H #define AMNEZIA_APPLICATION_H
#include <QCommandLineParser> #include <QCommandLineParser>
#include <QNetworkAccessManager>
#include <QQmlApplicationEngine> #include <QQmlApplicationEngine>
#include <QQmlContext> #include <QQmlContext>
#include <QThread> #include <QThread>
@ -14,8 +15,6 @@
#include "settings.h" #include "settings.h"
#include "vpnconnection.h" #include "vpnconnection.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"
@ -24,11 +23,13 @@
#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/appSplitTunnelingController.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"
#ifndef Q_OS_ANDROID
#include "ui/notificationhandler.h" #include "ui/notificationhandler.h"
#endif
#ifdef Q_OS_WINDOWS #ifdef Q_OS_WINDOWS
#include "ui/models/protocols/ikev2ConfigModel.h" #include "ui/models/protocols/ikev2ConfigModel.h"
#endif #endif
@ -36,11 +37,13 @@
#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/xrayConfigModel.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"
#include "ui/models/appSplitTunnelingModel.h"
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance())) #define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
@ -73,6 +76,7 @@ public:
bool parseCommands(); bool parseCommands();
QQmlApplicationEngine *qmlEngine() const; QQmlApplicationEngine *qmlEngine() const;
QNetworkAccessManager *manager() { return m_nam; }
signals: signals:
void translationsUpdated(); void translationsUpdated();
@ -83,7 +87,6 @@ private:
QQmlApplicationEngine *m_engine {}; QQmlApplicationEngine *m_engine {};
std::shared_ptr<Settings> m_settings; std::shared_ptr<Settings> m_settings;
std::shared_ptr<VpnConfigurator> m_configurator;
QSharedPointer<ContainerProps> m_containerProps; QSharedPointer<ContainerProps> m_containerProps;
QSharedPointer<ProtocolProps> m_protocolProps; QSharedPointer<ProtocolProps> m_protocolProps;
@ -97,11 +100,13 @@ private:
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<AppSplitTunnelingModel> m_appSplitTunnelingModel;
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<XrayConfigModel> m_xrayConfigModel;
QScopedPointer<WireGuardConfigModel> m_wireGuardConfigModel; QScopedPointer<WireGuardConfigModel> m_wireGuardConfigModel;
QScopedPointer<AwgConfigModel> m_awgConfigModel; QScopedPointer<AwgConfigModel> m_awgConfigModel;
#ifdef Q_OS_WINDOWS #ifdef Q_OS_WINDOWS
@ -112,7 +117,9 @@ private:
QSharedPointer<VpnConnection> m_vpnConnection; QSharedPointer<VpnConnection> m_vpnConnection;
QThread m_vpnConnectionThread; QThread m_vpnConnectionThread;
#ifndef Q_OS_ANDROID
QScopedPointer<NotificationHandler> m_notificationHandler; QScopedPointer<NotificationHandler> m_notificationHandler;
#endif
QScopedPointer<ConnectionController> m_connectionController; QScopedPointer<ConnectionController> m_connectionController;
QScopedPointer<PageController> m_pageController; QScopedPointer<PageController> m_pageController;
@ -122,7 +129,9 @@ private:
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_apiController; QScopedPointer<AppSplitTunnelingController> m_appSplitTunnelingController;
QNetworkAccessManager *m_nam;
}; };
#endif // AMNEZIA_APPLICATION_H #endif // AMNEZIA_APPLICATION_H

View file

@ -24,9 +24,7 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" tools:ignore="QueryAllPackagesPermission" />
<!-- Enable when VPN-per-app mode will be implemented -->
<!-- <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/> -->
<application <application
android:name=".AmneziaApplication" android:name=".AmneziaApplication"

View file

@ -66,6 +66,7 @@ class Awg : Wireguard() {
return AwgConfig.build { return AwgConfig.build {
configWireguard(configData, configDataJson) configWireguard(configData, configDataJson)
configSplitTunneling(config) configSplitTunneling(config)
configAppSplitTunneling(config)
configData["Jc"]?.let { setJc(it.toInt()) } configData["Jc"]?.let { setJc(it.toInt()) }
configData["Jmin"]?.let { setJmin(it.toInt()) } configData["Jmin"]?.let { setJmin(it.toInt()) }
configData["Jmax"]?.let { setJmax(it.toInt()) } configData["Jmax"]?.let { setJmax(it.toInt()) }

View file

@ -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")

View file

@ -13,7 +13,9 @@ 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.amnezia.vpn.util.net.parseInetAddress
import org.json.JSONObject import org.json.JSONObject
/** /**
@ -77,8 +79,15 @@ 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}")
} }
// exclude remote server ip from vpn routes
val remoteServer = config.getString("hostName")
val remoteServerAddress = InetNetwork(parseInetAddress(remoteServer))
configBuilder.excludeRoute(remoteServerAddress)
configPluggableTransport(configBuilder, config) configPluggableTransport(configBuilder, config)
configBuilder.configSplitTunneling(config) configBuilder.configSplitTunneling(config)
configBuilder.configAppSplitTunneling(config)
scope.launch { scope.launch {
val status = client.connect() val status = client.connect()

View file

@ -64,6 +64,22 @@ abstract class Protocol {
} }
} }
protected fun ProtocolConfig.Builder.configAppSplitTunneling(config: JSONObject) {
val splitTunnelType = config.optInt("appSplitTunnelType")
if (splitTunnelType == SPLIT_TUNNEL_DISABLE) return
val splitTunnelApps = config.getJSONArray("splitTunnelApps")
val appHandlerFunc = when (splitTunnelType) {
SPLIT_TUNNEL_INCLUDE -> ::includeApplication
SPLIT_TUNNEL_EXCLUDE -> ::excludeApplication
else -> throw BadConfigException("Unexpected value of the 'appSplitTunnelType' parameter: $splitTunnelType")
}
for (i in 0 until splitTunnelApps.length()) {
appHandlerFunc(splitTunnelApps.getString(i))
}
}
protected open fun buildVpnInterface(config: ProtocolConfig, vpnBuilder: Builder) { protected open fun buildVpnInterface(config: ProtocolConfig, vpnBuilder: Builder) {
vpnBuilder.setSession(VPN_SESSION_NAME) vpnBuilder.setSession(VPN_SESSION_NAME)
@ -89,20 +105,27 @@ abstract class Protocol {
vpnBuilder.addSearchDomain(it) vpnBuilder.addSearchDomain(it)
} }
for (addr in config.routes) { for ((inetNetwork, include) in config.routes) {
Log.d(TAG, "addRoute: $addr") if (include) {
vpnBuilder.addRoute(addr) Log.d(TAG, "addRoute: $inetNetwork")
vpnBuilder.addRoute(inetNetwork)
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
Log.d(TAG, "excludeRoute: $inetNetwork")
vpnBuilder.excludeRoute(inetNetwork)
} else {
Log.e(TAG, "Trying to exclude route $inetNetwork on old Android")
}
}
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { for (app in config.includedApplications) {
for (addr in config.excludedRoutes) { Log.d(TAG, "addAllowedApplication")
Log.d(TAG, "excludeRoute: $addr") vpnBuilder.addAllowedApplication(app)
vpnBuilder.excludeRoute(addr)
}
} }
for (app in config.excludedApplications) { for (app in config.excludedApplications) {
Log.d(TAG, "addDisallowedApplication: $app") Log.d(TAG, "addDisallowedApplication")
vpnBuilder.addDisallowedApplication(app) vpnBuilder.addDisallowedApplication(app)
} }

View file

@ -12,10 +12,10 @@ open class ProtocolConfig protected constructor(
val addresses: Set<InetNetwork>, val addresses: Set<InetNetwork>,
val dnsServers: Set<InetAddress>, val dnsServers: Set<InetAddress>,
val searchDomain: String?, val searchDomain: String?,
val routes: Set<InetNetwork>, val routes: Set<Route>,
val excludedRoutes: Set<InetNetwork>,
val includedAddresses: Set<InetNetwork>, val includedAddresses: Set<InetNetwork>,
val excludedAddresses: Set<InetNetwork>, val excludedAddresses: Set<InetNetwork>,
val includedApplications: Set<String>,
val excludedApplications: Set<String>, val excludedApplications: Set<String>,
val httpProxy: ProxyInfo?, val httpProxy: ProxyInfo?,
val allowAllAF: Boolean, val allowAllAF: Boolean,
@ -28,9 +28,9 @@ open class ProtocolConfig protected constructor(
builder.dnsServers, builder.dnsServers,
builder.searchDomain, builder.searchDomain,
builder.routes, builder.routes,
builder.excludedRoutes,
builder.includedAddresses, builder.includedAddresses,
builder.excludedAddresses, builder.excludedAddresses,
builder.includedApplications,
builder.excludedApplications, builder.excludedApplications,
builder.httpProxy, builder.httpProxy,
builder.allowAllAF, builder.allowAllAF,
@ -41,10 +41,10 @@ open class ProtocolConfig protected constructor(
open class Builder(blockingMode: Boolean) { open class Builder(blockingMode: Boolean) {
internal val addresses: MutableSet<InetNetwork> = hashSetOf() internal val addresses: MutableSet<InetNetwork> = hashSetOf()
internal val dnsServers: MutableSet<InetAddress> = hashSetOf() internal val dnsServers: MutableSet<InetAddress> = hashSetOf()
internal val routes: MutableSet<InetNetwork> = hashSetOf() internal val routes: MutableSet<Route> = mutableSetOf()
internal val excludedRoutes: MutableSet<InetNetwork> = hashSetOf()
internal val includedAddresses: MutableSet<InetNetwork> = hashSetOf() internal val includedAddresses: MutableSet<InetNetwork> = hashSetOf()
internal val excludedAddresses: MutableSet<InetNetwork> = hashSetOf() internal val excludedAddresses: MutableSet<InetNetwork> = hashSetOf()
internal val includedApplications: MutableSet<String> = hashSetOf()
internal val excludedApplications: MutableSet<String> = hashSetOf() internal val excludedApplications: MutableSet<String> = hashSetOf()
internal var searchDomain: String? = null internal var searchDomain: String? = null
@ -74,13 +74,21 @@ open class ProtocolConfig protected constructor(
fun setSearchDomain(domain: String) = apply { this.searchDomain = domain } fun setSearchDomain(domain: String) = apply { this.searchDomain = domain }
fun addRoute(route: InetNetwork) = apply { this.routes += route } fun addRoute(route: InetNetwork) = apply { this.routes += Route(route, true) }
fun addRoutes(routes: Collection<InetNetwork>) = apply { this.routes += routes } fun addRoutes(routes: Collection<InetNetwork>) = apply { this.routes += routes.map { Route(it, true) } }
fun removeRoute(route: InetNetwork) = apply { this.routes.remove(route) }
fun excludeRoute(route: InetNetwork) = apply { this.routes += Route(route, false) }
fun excludeRoutes(routes: Collection<InetNetwork>) = apply { this.routes += routes.map { Route(it, false) } }
fun removeRoute(route: InetNetwork) = apply { this.routes.removeIf { it.inetNetwork == route } }
fun clearRoutes() = apply { this.routes.clear() } fun clearRoutes() = apply { this.routes.clear() }
fun excludeRoute(route: InetNetwork) = apply { this.excludedRoutes += route } fun prependRoutes(block: Builder.() -> Unit) = apply {
fun excludeRoutes(routes: Collection<InetNetwork>) = apply { this.excludedRoutes += routes } val savedRoutes = mutableListOf<Route>().apply { addAll(routes) }
routes.clear()
block()
routes.addAll(savedRoutes)
}
fun includeAddress(addr: InetNetwork) = apply { this.includedAddresses += addr } fun includeAddress(addr: InetNetwork) = apply { this.includedAddresses += addr }
fun includeAddresses(addresses: Collection<InetNetwork>) = apply { this.includedAddresses += addresses } fun includeAddresses(addresses: Collection<InetNetwork>) = apply { this.includedAddresses += addresses }
@ -88,6 +96,9 @@ open class ProtocolConfig protected constructor(
fun excludeAddress(addr: InetNetwork) = apply { this.excludedAddresses += addr } fun excludeAddress(addr: InetNetwork) = apply { this.excludedAddresses += addr }
fun excludeAddresses(addresses: Collection<InetNetwork>) = apply { this.excludedAddresses += addresses } fun excludeAddresses(addresses: Collection<InetNetwork>) = apply { this.excludedAddresses += addresses }
fun includeApplication(application: String) = apply { this.includedApplications += application }
fun includeApplications(applications: Collection<String>) = apply { this.includedApplications += applications }
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 }
@ -111,37 +122,46 @@ open class ProtocolConfig protected constructor(
// remove default routes, if any // remove default routes, if any
removeRoute(InetNetwork("0.0.0.0", 0)) removeRoute(InetNetwork("0.0.0.0", 0))
removeRoute(InetNetwork("::", 0)) removeRoute(InetNetwork("::", 0))
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { removeRoute(InetNetwork("2000::", 3))
// for older versions of Android, add the default route to the excluded routes prependRoutes {
// to correctly build the excluded subnets list later
excludeRoute(InetNetwork("0.0.0.0", 0))
}
addRoutes(includedAddresses) 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))
} }
} else if (excludedAddresses.isNotEmpty()) {
prependRoutes {
addRoute(InetNetwork("0.0.0.0", 0))
addRoute(InetNetwork("2000::", 3))
excludeRoutes(excludedAddresses) excludeRoutes(excludedAddresses)
} }
} }
}
private fun processExcludedRoutes() { private fun processRoutes() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU && excludedRoutes.isNotEmpty()) { // replace ::/0 as it may cause LAN connection issues
// todo: rewrite, taking into account the current routes val ipv6DefaultRoute = InetNetwork("::", 0)
if (routes.removeIf { it.include && it.inetNetwork == ipv6DefaultRoute }) {
prependRoutes {
addRoute(InetNetwork("2000::", 3))
}
}
// for older versions of Android, build a list of subnets without excluded routes // for older versions of Android, build a list of subnets without excluded routes
// and add them to routes // and add them to routes
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU && routes.any { !it.include }) {
val ipRangeSet = IpRangeSet() val ipRangeSet = IpRangeSet()
ipRangeSet.remove(IpRange("127.0.0.0", 8)) routes.forEach {
excludedRoutes.forEach { if (it.include) ipRangeSet.add(IpRange(it.inetNetwork))
ipRangeSet.remove(IpRange(it)) else ipRangeSet.remove(IpRange(it.inetNetwork))
} }
// remove default routes, if any ipRangeSet.remove(IpRange("127.0.0.0", 8))
removeRoute(InetNetwork("0.0.0.0", 0)) ipRangeSet.remove(IpRange("::1", 128))
removeRoute(InetNetwork("::", 0)) routes.clear()
ipRangeSet.subnets().forEach(::addRoute) ipRangeSet.subnets().forEach(::addRoute)
addRoute(InetNetwork("2000::", 3)) }
// filter ipv4 and ipv6 loopback addresses
val ipv6Loopback = InetNetwork("::1", 128)
routes.removeIf {
it.include &&
if (it.inetNetwork.isIpv4) it.inetNetwork.address.address[0] == 127.toByte()
else it.inetNetwork == ipv6Loopback
} }
} }
@ -159,7 +179,7 @@ open class ProtocolConfig protected constructor(
protected fun configBuild() { protected fun configBuild() {
processSplitTunneling() processSplitTunneling()
processExcludedRoutes() processRoutes()
validate() validate()
} }
@ -171,3 +191,5 @@ open class ProtocolConfig protected constructor(
Builder(blockingMode).apply(block).build() Builder(blockingMode).apply(block).build()
} }
} }
data class Route(val inetNetwork: InetNetwork, val include: Boolean)

View file

@ -1,5 +1,26 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<resources> <resources>
<string name="connecting">Подключение</string> <string name="disconnected">Не подключено</string>
<string name="disconnecting">Отключение</string> <string name="connected">Подключено</string>
<string name="connecting">Подключение…</string>
<string name="disconnecting">Отключение…</string>
<string name="reconnecting">Переподключение…</string>
<string name="connect">Подключиться</string>
<string name="disconnect">Отключиться</string>
<string name="ok">ОК</string>
<string name="cancel">Отмена</string>
<string name="yes">Да</string>
<string name="no">Нет</string>
<string name="vpnGranted">VPN-подключение разрешено</string>
<string name="vpnSetupFailed">Ошибка настройки VPN</string>
<string name="vpnSetupFailedMessage">Чтобы подключиться к AmneziaVPN необходимо:\n\n- Разрешить приложению подключаться к сети VPN\n- Отключить функцию \"Постоянная VPN\" для всех остальных VPN-приложений в системных настройках VPN</string>
<string name="openVpnSettings">Открыть настройки VPN</string>
<string name="notificationChannelDescription">Уведомления сервиса AmneziaVPN</string>
<string name="notificationDialogTitle">Сервис AmneziaVPN</string>
<string name="notificationDialogMessage">Показывать статус VPN в строке состояния?</string>
<string name="notificationSettingsDialogTitle">Настройки уведомлений</string>
<string name="notificationSettingsDialogMessage">Для показа уведомлений необходимо включить уведомления в системных настройках</string>
<string name="openNotificationSettings">Открыть настройки уведомлений</string>
</resources> </resources>

View file

@ -1,5 +1,26 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<resources> <resources>
<string name="connecting">Connecting</string> <string name="disconnected">Not connected</string>
<string name="disconnecting">Disconnecting</string> <string name="connected">Connected</string>
<string name="connecting">Connecting…</string>
<string name="disconnecting">Disconnecting…</string>
<string name="reconnecting">Reconnecting…</string>
<string name="connect">Connect</string>
<string name="disconnect">Disconnect</string>
<string name="ok">OK</string>
<string name="cancel">Cancel</string>
<string name="yes">Yes</string>
<string name="no">No</string>
<string name="vpnGranted">VPN permission granted</string>
<string name="vpnSetupFailed">VPN setup error</string>
<string name="vpnSetupFailedMessage">To connect to AmneziaVPN, please do the following:\n\n- Allow the app to set up a VPN connection\n- Disable Always-on VPN for any other VPN app in the VPN system settings</string>
<string name="openVpnSettings">Open VPN settings</string>
<string name="notificationChannelDescription">AmneziaVPN service notification</string>
<string name="notificationDialogTitle">AmneziaVPN service</string>
<string name="notificationDialogMessage">Show the VPN state in the status bar?</string>
<string name="notificationSettingsDialogTitle">Notification settings</string>
<string name="notificationSettingsDialogMessage">To show notifications, you must enable notifications in the system settings</string>
<string name="openNotificationSettings">Open notification settings</string>
</resources> </resources>

View file

@ -1,37 +1,48 @@
package org.amnezia.vpn package org.amnezia.vpn
import android.Manifest
import android.app.AlertDialog
import android.app.NotificationManager
import android.content.BroadcastReceiver
import android.content.ComponentName import android.content.ComponentName
import android.content.Intent 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.content.pm.PackageManager
import android.net.Uri import android.graphics.Bitmap
import android.net.VpnService import android.net.VpnService
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.os.IBinder import android.os.IBinder
import android.os.Looper import android.os.Looper
import android.os.Message import android.os.Message
import android.os.Messenger import android.os.Messenger
import android.provider.Settings
import android.view.WindowManager.LayoutParams import android.view.WindowManager.LayoutParams
import android.webkit.MimeTypeMap import android.webkit.MimeTypeMap
import android.widget.Toast import android.widget.Toast
import androidx.annotation.MainThread import androidx.annotation.MainThread
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import java.io.IOException import java.io.IOException
import kotlin.LazyThreadSafetyMode.NONE import kotlin.LazyThreadSafetyMode.NONE
import kotlin.text.RegexOption.IGNORE_CASE import kotlin.text.RegexOption.IGNORE_CASE
import AppListProvider
import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.amnezia.vpn.protocol.getStatistics import org.amnezia.vpn.protocol.getStatistics
import org.amnezia.vpn.protocol.getStatus import org.amnezia.vpn.protocol.getStatus
import org.amnezia.vpn.qt.QtAndroidController import org.amnezia.vpn.qt.QtAndroidController
import org.amnezia.vpn.util.Log import org.amnezia.vpn.util.Log
import org.amnezia.vpn.util.Prefs
import org.qtproject.qt.android.bindings.QtActivity import org.qtproject.qt.android.bindings.QtActivity
private const val TAG = "AmneziaActivity" private const val TAG = "AmneziaActivity"
@ -40,6 +51,9 @@ const val ACTIVITY_MESSENGER_NAME = "Activity"
private const val CHECK_VPN_PERMISSION_ACTION_CODE = 1 private const val CHECK_VPN_PERMISSION_ACTION_CODE = 1
private const val CREATE_FILE_ACTION_CODE = 2 private const val CREATE_FILE_ACTION_CODE = 2
private const val OPEN_FILE_ACTION_CODE = 3 private const val OPEN_FILE_ACTION_CODE = 3
private const val CHECK_NOTIFICATION_PERMISSION_ACTION_CODE = 4
private const val PREFS_NOTIFICATION_PERMISSION_ASKED = "NOTIFICATION_PERMISSION_ASKED"
class AmneziaActivity : QtActivity() { class AmneziaActivity : QtActivity() {
@ -48,8 +62,11 @@ class AmneziaActivity : QtActivity() {
private var isWaitingStatus = true private var isWaitingStatus = true
private var isServiceConnected = false private var isServiceConnected = false
private var isInBoundState = false private var isInBoundState = false
private var notificationStateReceiver: BroadcastReceiver? = null
private lateinit var vpnServiceMessenger: IpcMessenger private lateinit var vpnServiceMessenger: IpcMessenger
private var tmpFileContentToSave: String = ""
private val actionResultHandlers = mutableMapOf<Int, ActivityResultHandler>()
private val permissionRequestHandlers = mutableMapOf<Int, PermissionRequestHandler>()
private val vpnServiceEventHandler: Handler by lazy(NONE) { private val vpnServiceEventHandler: Handler by lazy(NONE) {
object : Handler(Looper.getMainLooper()) { object : Handler(Looper.getMainLooper()) {
@ -129,10 +146,6 @@ class AmneziaActivity : QtActivity() {
} }
} }
private data class CheckVpnPermissionCallbacks(val onSuccess: () -> Unit, val onFail: () -> Unit)
private var checkVpnPermissionCallbacks: CheckVpnPermissionCallbacks? = null
/** /**
* Activity overloaded methods * Activity overloaded methods
*/ */
@ -147,9 +160,30 @@ class AmneziaActivity : QtActivity() {
doBindService() doBindService()
} }
) )
registerBroadcastReceivers()
intent?.let(::processIntent) intent?.let(::processIntent)
} }
private fun registerBroadcastReceivers() {
notificationStateReceiver = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
registerBroadcastReceiver(
arrayOf(
NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED,
NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED
)
) {
Log.d(
TAG, "Notification state changed: ${it?.action}, blocked = " +
"${it?.getBooleanExtra(NotificationManager.EXTRA_BLOCKED_STATE, false)}"
)
mainScope.launch {
qtInitialized.await()
QtAndroidController.onNotificationStateChanged()
}
}
} else null
}
override fun onNewIntent(intent: Intent?) { override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent) super.onNewIntent(intent)
Log.d(TAG, "onNewIntent: $intent") Log.d(TAG, "onNewIntent: $intent")
@ -187,50 +221,46 @@ class AmneziaActivity : QtActivity() {
override fun onDestroy() { override fun onDestroy() {
Log.d(TAG, "Destroy Amnezia activity") Log.d(TAG, "Destroy Amnezia activity")
unregisterBroadcastReceiver(notificationStateReceiver)
notificationStateReceiver = null
mainScope.cancel() mainScope.cancel()
super.onDestroy() super.onDestroy()
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) { Log.d(TAG, "Process activity result, code: ${actionCodeToString(requestCode)}, " +
CREATE_FILE_ACTION_CODE -> { "resultCode: $resultCode, data: $data")
actionResultHandlers[requestCode]?.let { handler ->
when (resultCode) { when (resultCode) {
RESULT_OK -> { RESULT_OK -> handler.onSuccess(data)
data?.data?.let { uri -> else -> handler.onFail(data)
alterDocument(uri)
}
}
} }
handler.onAny(data)
actionResultHandlers.remove(requestCode)
} ?: super.onActivityResult(requestCode, resultCode, data)
} }
OPEN_FILE_ACTION_CODE -> { private fun startActivityForResult(intent: Intent, requestCode: Int, handler: ActivityResultHandler) {
when (resultCode) { actionResultHandlers[requestCode] = handler
RESULT_OK -> data?.data?.toString() ?: "" startActivityForResult(intent, requestCode)
else -> ""
}.let { uri ->
QtAndroidController.onFileOpened(uri)
}
} }
CHECK_VPN_PERMISSION_ACTION_CODE -> { override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
when (resultCode) { Log.d(TAG, "Process permission result, code: ${actionCodeToString(requestCode)}, " +
RESULT_OK -> { "permissions: ${permissions.contentToString()}, results: ${grantResults.contentToString()}")
Log.d(TAG, "Vpn permission granted") permissionRequestHandlers[requestCode]?.let { handler ->
Toast.makeText(this, "Vpn permission granted", Toast.LENGTH_LONG).show() if (grantResults.isNotEmpty()) {
checkVpnPermissionCallbacks?.run { onSuccess() } if (grantResults[0] == PackageManager.PERMISSION_GRANTED) handler.onSuccess()
else handler.onFail()
}
handler.onAny()
permissionRequestHandlers.remove(requestCode)
} ?: super.onRequestPermissionsResult(requestCode, permissions, grantResults)
} }
else -> { private fun requestPermission(permission: String, requestCode: Int, handler: PermissionRequestHandler) {
Log.w(TAG, "Vpn permission denied, resultCode: $resultCode") permissionRequestHandlers[requestCode] = handler
Toast.makeText(this, "Vpn permission denied", Toast.LENGTH_LONG).show() requestPermissions(arrayOf(permission), requestCode)
checkVpnPermissionCallbacks?.run { onFail() }
}
}
checkVpnPermissionCallbacks = null
}
else -> super.onActivityResult(requestCode, resultCode, data)
}
} }
/** /**
@ -262,22 +292,75 @@ class AmneziaActivity : QtActivity() {
/** /**
* Methods of starting and stopping VpnService * Methods of starting and stopping VpnService
*/ */
private fun checkVpnPermissionAndStart(vpnConfig: String) { @MainThread
checkVpnPermission( private fun checkVpnPermission(onPermissionGranted: () -> Unit) {
onSuccess = { startVpn(vpnConfig) }, Log.d(TAG, "Check VPN permission")
onFail = QtAndroidController::onVpnPermissionRejected VpnService.prepare(applicationContext)?.let { intent ->
) startActivityForResult(intent, CHECK_VPN_PERMISSION_ACTION_CODE, ActivityResultHandler(
onSuccess = {
Log.d(TAG, "Vpn permission granted")
Toast.makeText(this@AmneziaActivity, resources.getText(R.string.vpnGranted), Toast.LENGTH_LONG).show()
onPermissionGranted()
},
onFail = {
Log.w(TAG, "Vpn permission denied")
showOnVpnPermissionRejectDialog()
mainScope.launch {
qtInitialized.await()
QtAndroidController.onVpnPermissionRejected()
}
}
))
} ?: onPermissionGranted()
} }
@MainThread private fun showOnVpnPermissionRejectDialog() {
private fun checkVpnPermission(onSuccess: () -> Unit, onFail: () -> Unit) { AlertDialog.Builder(this)
Log.d(TAG, "Check VPN permission") .setTitle(R.string.vpnSetupFailed)
VpnService.prepare(applicationContext)?.let { .setMessage(R.string.vpnSetupFailedMessage)
checkVpnPermissionCallbacks = CheckVpnPermissionCallbacks(onSuccess, onFail) .setNegativeButton(R.string.ok) { _, _ -> }
startActivityForResult(it, CHECK_VPN_PERMISSION_ACTION_CODE) .setPositiveButton(R.string.openVpnSettings) { _, _ ->
return startActivity(Intent(Settings.ACTION_VPN_SETTINGS))
} }
onSuccess() .show()
}
private fun checkNotificationPermission(onChecked: () -> Unit) {
Log.d(TAG, "Check notification permission")
if (
!isNotificationPermissionGranted() &&
!Prefs.load<Boolean>(PREFS_NOTIFICATION_PERMISSION_ASKED)
) {
showNotificationPermissionDialog(onChecked)
} else {
onChecked()
}
}
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private fun showNotificationPermissionDialog(onChecked: () -> Unit) {
AlertDialog.Builder(this)
.setTitle(R.string.notificationDialogTitle)
.setMessage(R.string.notificationDialogMessage)
.setNegativeButton(R.string.no) { _, _ ->
Prefs.save(PREFS_NOTIFICATION_PERMISSION_ASKED, true)
onChecked()
}
.setPositiveButton(R.string.yes) { _, _ ->
val saveAsked: () -> Unit = {
Prefs.save(PREFS_NOTIFICATION_PERMISSION_ASKED, true)
}
requestPermission(
Manifest.permission.POST_NOTIFICATIONS,
CHECK_NOTIFICATION_PERMISSION_ACTION_CODE,
PermissionRequestHandler(
onSuccess = saveAsked,
onFail = saveAsked,
onAny = onChecked
)
)
}
.show()
} }
@MainThread @MainThread
@ -305,28 +388,21 @@ class AmneziaActivity : QtActivity() {
Intent(this, AmneziaVpnService::class.java).apply { Intent(this, AmneziaVpnService::class.java).apply {
putExtra(MSG_VPN_CONFIG, vpnConfig) putExtra(MSG_VPN_CONFIG, vpnConfig)
}.also { }.also {
try {
ContextCompat.startForegroundService(this, it) ContextCompat.startForegroundService(this, it)
} catch (e: SecurityException) {
Log.e(TAG, "Failed to start AmneziaVpnService: $e")
QtAndroidController.onServiceError()
}
} }
} }
@MainThread
private fun disconnectFromVpn() { private fun disconnectFromVpn() {
Log.d(TAG, "Disconnect from VPN") Log.d(TAG, "Disconnect from VPN")
vpnServiceMessenger.send(Action.DISCONNECT) vpnServiceMessenger.send(Action.DISCONNECT)
} }
// saving file
private fun alterDocument(uri: Uri) {
try {
contentResolver.openOutputStream(uri)?.use { os ->
os.bufferedWriter().use { it.write(tmpFileContentToSave) }
}
} catch (e: IOException) {
e.printStackTrace()
}
tmpFileContentToSave = ""
}
/** /**
* Methods called by Qt * Methods called by Qt
*/ */
@ -340,7 +416,11 @@ class AmneziaActivity : QtActivity() {
fun start(vpnConfig: String) { fun start(vpnConfig: String) {
Log.v(TAG, "Start VPN") Log.v(TAG, "Start VPN")
mainScope.launch { mainScope.launch {
checkVpnPermissionAndStart(vpnConfig) checkVpnPermission {
checkNotificationPermission {
startVpn(vpnConfig)
}
}
} }
} }
@ -372,14 +452,26 @@ class AmneziaActivity : QtActivity() {
fun saveFile(fileName: String, data: String) { fun saveFile(fileName: String, data: String) {
Log.d(TAG, "Save file $fileName") Log.d(TAG, "Save file $fileName")
mainScope.launch { mainScope.launch {
tmpFileContentToSave = data
Intent(Intent.ACTION_CREATE_DOCUMENT).apply { Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE) addCategory(Intent.CATEGORY_OPENABLE)
type = "text/*" type = "text/*"
putExtra(Intent.EXTRA_TITLE, fileName) putExtra(Intent.EXTRA_TITLE, fileName)
}.also { }.also {
startActivityForResult(it, CREATE_FILE_ACTION_CODE) startActivityForResult(it, CREATE_FILE_ACTION_CODE, ActivityResultHandler(
onSuccess = {
it?.data?.let { uri ->
Log.d(TAG, "Save file to $uri")
try {
contentResolver.openOutputStream(uri)?.use { os ->
os.bufferedWriter().use { it.write(data) }
}
} catch (e: IOException) {
Log.e(TAG, "Failed to save file $uri: $e")
// todo: send error to Qt
}
}
}
))
} }
} }
} }
@ -387,18 +479,21 @@ class AmneziaActivity : QtActivity() {
@Suppress("unused") @Suppress("unused")
fun openFile(filter: String?) { fun openFile(filter: String?) {
Log.v(TAG, "Open file with filter: $filter") Log.v(TAG, "Open file with filter: $filter")
mainScope.launch {
val mimeTypes = if (!filter.isNullOrEmpty()) { val mimeTypes = if (!filter.isNullOrEmpty()) {
val extensionRegex = "\\*\\.[a-z .]+".toRegex(IGNORE_CASE) val extensionRegex = "\\*\\.([a-z0-9]+)".toRegex(IGNORE_CASE)
val mime = MimeTypeMap.getSingleton() val mime = MimeTypeMap.getSingleton()
extensionRegex.findAll(filter).map { extensionRegex.findAll(filter).map {
mime.getMimeTypeFromExtension(it.value.drop(2)) it.groups[1]?.value?.let { mime.getMimeTypeFromExtension(it) } ?: "*/*"
}.filterNotNull().toSet() }.toSet()
} else emptySet() } else emptySet()
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.v(TAG, "File mimyType filter: $mimeTypes")
if ("*/*" in mimeTypes) {
type = "*/*"
} else {
when (mimeTypes.size) { when (mimeTypes.size) {
1 -> type = mimeTypes.first() 1 -> type = mimeTypes.first()
@ -409,14 +504,20 @@ class AmneziaActivity : QtActivity() {
else -> type = "*/*" else -> type = "*/*"
} }
}
}.also { }.also {
startActivityForResult(it, OPEN_FILE_ACTION_CODE) startActivityForResult(it, OPEN_FILE_ACTION_CODE, ActivityResultHandler(
onSuccess = {
val uri = it?.data?.toString() ?: ""
Log.d(TAG, "Open file: $uri")
mainScope.launch {
qtInitialized.await()
QtAndroidController.onFileOpened(uri)
}
}
))
} }
} }
@Suppress("unused")
fun setNotificationText(title: String, message: String, timerSec: Int) {
Log.v(TAG, "Set notification text")
} }
@Suppress("unused") @Suppress("unused")
@ -432,7 +533,7 @@ class AmneziaActivity : QtActivity() {
@Suppress("unused") @Suppress("unused")
fun setSaveLogs(enabled: Boolean) { fun setSaveLogs(enabled: Boolean) {
Log.d(TAG, "Set save logs: $enabled") Log.v(TAG, "Set save logs: $enabled")
mainScope.launch { mainScope.launch {
Log.saveLogs = enabled Log.saveLogs = enabled
vpnServiceMessenger.send { vpnServiceMessenger.send {
@ -452,8 +553,10 @@ class AmneziaActivity : QtActivity() {
@Suppress("unused") @Suppress("unused")
fun clearLogs() { fun clearLogs() {
Log.v(TAG, "Clear logs") Log.v(TAG, "Clear logs")
mainScope.launch {
Log.clearLogs() Log.clearLogs()
} }
}
@Suppress("unused") @Suppress("unused")
fun setScreenshotsEnabled(enabled: Boolean) { fun setScreenshotsEnabled(enabled: Boolean) {
@ -463,4 +566,104 @@ class AmneziaActivity : QtActivity() {
window.setFlags(flag, LayoutParams.FLAG_SECURE) window.setFlags(flag, LayoutParams.FLAG_SECURE)
} }
} }
@Suppress("unused")
fun minimizeApp() {
Log.v(TAG, "Minimize application")
mainScope.launch {
moveTaskToBack(false)
} }
}
@Suppress("unused")
fun getAppList(): String {
Log.v(TAG, "Get app list")
var appList = ""
runBlocking {
mainScope.launch {
withContext(Dispatchers.IO) {
appList = AppListProvider.getAppList(packageManager, packageName)
}
}.join()
}
return appList
}
@Suppress("unused")
fun getAppIcon(packageName: String, width: Int, height: Int): Bitmap {
Log.v(TAG, "Get app icon")
return AppListProvider.getAppIcon(packageManager, packageName, width, height)
}
@Suppress("unused")
fun isNotificationPermissionGranted(): Boolean = applicationContext.isNotificationPermissionGranted()
@Suppress("unused")
fun requestNotificationPermission() {
val shouldShowPreRequest = shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)
requestPermission(
Manifest.permission.POST_NOTIFICATIONS,
CHECK_NOTIFICATION_PERMISSION_ACTION_CODE,
PermissionRequestHandler(
onSuccess = {
mainScope.launch {
Prefs.save(PREFS_NOTIFICATION_PERMISSION_ASKED, true)
vpnServiceMessenger.send(Action.NOTIFICATION_PERMISSION_GRANTED)
qtInitialized.await()
QtAndroidController.onNotificationStateChanged()
}
},
onFail = {
if (!Prefs.load<Boolean>(PREFS_NOTIFICATION_PERMISSION_ASKED)) {
Prefs.save(PREFS_NOTIFICATION_PERMISSION_ASKED, true)
} else {
val shouldShowPostRequest =
shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)
if (!shouldShowPreRequest && !shouldShowPostRequest) {
showNotificationSettingsDialog()
}
}
}
)
)
}
private fun showNotificationSettingsDialog() {
AlertDialog.Builder(this)
.setTitle(R.string.notificationSettingsDialogTitle)
.setMessage(R.string.notificationSettingsDialogMessage)
.setNegativeButton(R.string.cancel) { _, _ -> }
.setPositiveButton(R.string.openNotificationSettings) { _, _ ->
startActivity(Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply {
putExtra(Settings.EXTRA_APP_PACKAGE, packageName)
})
}
.show()
}
/**
* Utils methods
*/
companion object {
private fun actionCodeToString(actionCode: Int): String =
when (actionCode) {
CHECK_VPN_PERMISSION_ACTION_CODE -> "CHECK_VPN_PERMISSION"
CREATE_FILE_ACTION_CODE -> "CREATE_FILE"
OPEN_FILE_ACTION_CODE -> "OPEN_FILE"
CHECK_NOTIFICATION_PERMISSION_ACTION_CODE -> "CHECK_NOTIFICATION_PERMISSION"
else -> actionCode.toString()
}
}
}
private class ActivityResultHandler(
val onSuccess: (data: Intent?) -> Unit = {},
val onFail: (data: Intent?) -> Unit = {},
val onAny: (data: Intent?) -> Unit = {}
)
private class PermissionRequestHandler(
val onSuccess: () -> Unit = {},
val onFail: () -> Unit = {},
val onAny: () -> Unit = {}
)

View file

@ -3,14 +3,11 @@ package org.amnezia.vpn
import androidx.camera.camera2.Camera2Config import androidx.camera.camera2.Camera2Config
import androidx.camera.core.CameraSelector 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.NotificationManagerCompat
import org.amnezia.vpn.util.Log import org.amnezia.vpn.util.Log
import org.amnezia.vpn.util.Prefs 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" private const val TAG = "AmneziaApplication"
const val NOTIFICATION_CHANNEL_ID: String = "org.amnezia.vpn.notification"
class AmneziaApplication : QtApplication(), CameraXConfig.Provider { class AmneziaApplication : QtApplication(), CameraXConfig.Provider {
@ -20,7 +17,7 @@ class AmneziaApplication : QtApplication(), CameraXConfig.Provider {
Log.init(this) Log.init(this)
VpnStateStore.init(this) VpnStateStore.init(this)
Log.d(TAG, "Create Amnezia application") Log.d(TAG, "Create Amnezia application")
createNotificationChannel() ServiceNotification.createNotificationChannel(this)
} }
override fun getCameraXConfig(): CameraXConfig = CameraXConfig.Builder override fun getCameraXConfig(): CameraXConfig = CameraXConfig.Builder
@ -28,14 +25,4 @@ class AmneziaApplication : QtApplication(), CameraXConfig.Provider {
.setMinimumLoggingLevel(android.util.Log.ERROR) .setMinimumLoggingLevel(android.util.Log.ERROR)
.setAvailableCamerasLimiter(CameraSelector.DEFAULT_BACK_CAMERA) .setAvailableCamerasLimiter(CameraSelector.DEFAULT_BACK_CAMERA)
.build() .build()
private fun createNotificationChannel() {
NotificationManagerCompat.from(this).createNotificationChannel(
Builder(NOTIFICATION_CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_LOW)
.setName("AmneziaVPN")
.setDescription("AmneziaVPN service notification")
.setShowBadge(false)
.build()
)
}
} }

View file

@ -0,0 +1,56 @@
package org.amnezia.vpn
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.RegisterReceiverFlags
import org.amnezia.vpn.protocol.ProtocolState
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
import org.amnezia.vpn.protocol.ProtocolState.CONNECTING
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTING
import org.amnezia.vpn.protocol.ProtocolState.RECONNECTING
import org.amnezia.vpn.protocol.ProtocolState.UNKNOWN
fun Context.getString(state: ProtocolState): String =
getString(
when (state) {
DISCONNECTED, UNKNOWN -> R.string.disconnected
CONNECTED -> R.string.connected
CONNECTING -> R.string.connecting
DISCONNECTING -> R.string.disconnecting
RECONNECTING -> R.string.reconnecting
}
)
fun Context.registerBroadcastReceiver(
action: String,
@RegisterReceiverFlags flags: Int = ContextCompat.RECEIVER_EXPORTED,
onReceive: (Intent?) -> Unit
): BroadcastReceiver = registerBroadcastReceiver(arrayOf(action), flags, onReceive)
fun Context.registerBroadcastReceiver(
actions: Array<String>,
@RegisterReceiverFlags flags: Int = ContextCompat.RECEIVER_EXPORTED,
onReceive: (Intent?) -> Unit
): BroadcastReceiver =
object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
onReceive(intent)
}
}.also {
ContextCompat.registerReceiver(
this,
it,
IntentFilter().apply {
actions.forEach(::addAction)
},
flags
)
}
fun Context.unregisterBroadcastReceiver(receiver: BroadcastReceiver?) {
receiver?.let { this.unregisterReceiver(it) }
}

View file

@ -188,11 +188,16 @@ class AmneziaTileService : TileService() {
true true
} }
private fun startVpnService() = private fun startVpnService() {
try {
ContextCompat.startForegroundService( ContextCompat.startForegroundService(
applicationContext, applicationContext,
Intent(this, AmneziaVpnService::class.java) Intent(this, AmneziaVpnService::class.java)
) )
} catch (e: SecurityException) {
Log.e(TAG, "Failed to start AmneziaVpnService: $e")
}
}
private fun connectToVpn() = vpnServiceMessenger.send(Action.CONNECT) private fun connectToVpn() = vpnServiceMessenger.send(Action.CONNECT)
@ -230,7 +235,7 @@ class AmneziaTileService : TileService() {
val tile = qsTile ?: return val tile = qsTile ?: return
tile.apply { tile.apply {
label = vpnState.serverName ?: DEFAULT_TILE_LABEL label = vpnState.serverName ?: DEFAULT_TILE_LABEL
when (vpnState.protocolState) { when (val protocolState = vpnState.protocolState) {
CONNECTED -> { CONNECTED -> {
state = Tile.STATE_ACTIVE state = Tile.STATE_ACTIVE
subtitleCompat = null subtitleCompat = null
@ -241,14 +246,9 @@ class AmneziaTileService : TileService() {
subtitleCompat = null subtitleCompat = null
} }
CONNECTING, RECONNECTING -> { CONNECTING, DISCONNECTING, RECONNECTING -> {
state = Tile.STATE_UNAVAILABLE state = Tile.STATE_UNAVAILABLE
subtitleCompat = resources.getString(R.string.connecting) subtitleCompat = getString(protocolState)
}
DISCONNECTING -> {
state = Tile.STATE_UNAVAILABLE
subtitleCompat = resources.getString(R.string.disconnecting)
} }
} }
updateTile() updateTile()

View file

@ -2,8 +2,8 @@ package org.amnezia.vpn
import android.app.ActivityManager import android.app.ActivityManager
import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE
import android.app.Notification import android.app.NotificationManager
import android.app.PendingIntent import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST
@ -15,10 +15,12 @@ import android.os.IBinder
import android.os.Looper import android.os.Looper
import android.os.Message import android.os.Message
import android.os.Messenger import android.os.Messenger
import android.os.PowerManager
import android.os.Process import android.os.Process
import androidx.annotation.MainThread import androidx.annotation.MainThread
import androidx.core.app.NotificationCompat
import androidx.core.app.ServiceCompat import androidx.core.app.ServiceCompat
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import kotlin.LazyThreadSafetyMode.NONE import kotlin.LazyThreadSafetyMode.NONE
import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineExceptionHandler
@ -54,11 +56,14 @@ 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.Prefs
import org.amnezia.vpn.util.net.NetworkState import org.amnezia.vpn.util.net.NetworkState
import org.amnezia.vpn.util.net.TrafficStats
import org.json.JSONException import org.json.JSONException
import org.json.JSONObject import org.json.JSONObject
private const val TAG = "AmneziaVpnService" private const val TAG = "AmneziaVpnService"
const val ACTION_DISCONNECT = "org.amnezia.vpn.action.disconnect"
const val MSG_VPN_CONFIG = "VPN_CONFIG" const val MSG_VPN_CONFIG = "VPN_CONFIG"
const val MSG_ERROR = "ERROR" const val MSG_ERROR = "ERROR"
const val MSG_SAVE_LOGS = "SAVE_LOGS" const val MSG_SAVE_LOGS = "SAVE_LOGS"
@ -69,8 +74,8 @@ private const val PREFS_CONFIG_KEY = "LAST_CONF"
private const val PREFS_SERVER_NAME = "LAST_SERVER_NAME" private const val PREFS_SERVER_NAME = "LAST_SERVER_NAME"
private const val PREFS_SERVER_INDEX = "LAST_SERVER_INDEX" private const val PREFS_SERVER_INDEX = "LAST_SERVER_INDEX"
private const val PROCESS_NAME = "org.amnezia.vpn:amneziaVpnService" private const val PROCESS_NAME = "org.amnezia.vpn:amneziaVpnService"
private const val NOTIFICATION_ID = 1337 // private const val STATISTICS_SENDING_TIMEOUT = 1000L
private const val STATISTICS_SENDING_TIMEOUT = 1000L private const val TRAFFIC_STATS_UPDATE_TIMEOUT = 1000L
private const val DISCONNECT_TIMEOUT = 5000L private const val DISCONNECT_TIMEOUT = 5000L
private const val STOP_SERVICE_TIMEOUT = 5000L private const val STOP_SERVICE_TIMEOUT = 5000L
@ -96,8 +101,14 @@ class AmneziaVpnService : VpnService() {
private var connectionJob: Job? = null private var connectionJob: Job? = null
private var disconnectionJob: Job? = null private var disconnectionJob: Job? = null
private var statisticsSendingJob: Job? = null private var trafficStatsUpdateJob: Job? = null
// private var statisticsSendingJob: Job? = null
private lateinit var networkState: NetworkState private lateinit var networkState: NetworkState
private lateinit var trafficStats: TrafficStats
private var disconnectReceiver: BroadcastReceiver? = null
private var notificationStateReceiver: BroadcastReceiver? = null
private var screenOnReceiver: BroadcastReceiver? = null
private var screenOffReceiver: BroadcastReceiver? = null
private val clientMessengers = ConcurrentHashMap<Messenger, IpcMessenger>() private val clientMessengers = ConcurrentHashMap<Messenger, IpcMessenger>()
private val isActivityConnected private val isActivityConnected
@ -131,13 +142,13 @@ class AmneziaVpnService : VpnService() {
val messenger = IpcMessenger(msg.replyTo, clientName) val messenger = IpcMessenger(msg.replyTo, clientName)
clientMessengers[msg.replyTo] = messenger clientMessengers[msg.replyTo] = messenger
Log.d(TAG, "Messenger client '$clientName' was registered") Log.d(TAG, "Messenger client '$clientName' was registered")
if (clientName == ACTIVITY_MESSENGER_NAME && isConnected) launchSendingStatistics() // if (clientName == ACTIVITY_MESSENGER_NAME && isConnected) launchSendingStatistics()
} }
Action.UNREGISTER_CLIENT -> { Action.UNREGISTER_CLIENT -> {
clientMessengers.remove(msg.replyTo)?.let { clientMessengers.remove(msg.replyTo)?.let {
Log.d(TAG, "Messenger client '${it.name}' was unregistered") Log.d(TAG, "Messenger client '${it.name}' was unregistered")
if (it.name == ACTIVITY_MESSENGER_NAME) stopSendingStatistics() // if (it.name == ACTIVITY_MESSENGER_NAME) stopSendingStatistics()
} }
} }
@ -159,6 +170,10 @@ class AmneziaVpnService : VpnService() {
} }
} }
Action.NOTIFICATION_PERMISSION_GRANTED -> {
enableNotification()
}
Action.SET_SAVE_LOGS -> { Action.SET_SAVE_LOGS -> {
Log.saveLogs = msg.data.getBoolean(MSG_SAVE_LOGS) Log.saveLogs = msg.data.getBoolean(MSG_SAVE_LOGS)
} }
@ -181,25 +196,7 @@ class AmneziaVpnService : VpnService() {
else -> 0 else -> 0
} }
private val notification: Notification by lazy(NONE) { private val serviceNotification: ServiceNotification by lazy(NONE) { ServiceNotification(this) }
NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_amnezia_round)
.setShowWhen(false)
.setContentIntent(
PendingIntent.getActivity(
this,
0,
Intent(this, AmneziaActivity::class.java),
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
)
.setOngoing(true)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setCategory(NotificationCompat.CATEGORY_SERVICE)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
.build()
}
/** /**
* Service overloaded methods * Service overloaded methods
@ -212,14 +209,14 @@ class AmneziaVpnService : VpnService() {
loadServerData() loadServerData()
launchProtocolStateHandler() launchProtocolStateHandler()
networkState = NetworkState(this, ::reconnect) networkState = NetworkState(this, ::reconnect)
trafficStats = TrafficStats()
registerBroadcastReceivers()
} }
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val isAlwaysOnCompat = val isAlwaysOn = intent != null && intent.action == SERVICE_INTERFACE
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) isAlwaysOn
else intent?.component?.packageName != packageName
if (isAlwaysOnCompat) { if (isAlwaysOn) {
Log.d(TAG, "Start service via Always-on") Log.d(TAG, "Start service via Always-on")
connect() connect()
} else if (intent?.getBooleanExtra(AFTER_PERMISSION_CHECK, false) == true) { } else if (intent?.getBooleanExtra(AFTER_PERMISSION_CHECK, false) == true) {
@ -229,7 +226,10 @@ class AmneziaVpnService : VpnService() {
Log.d(TAG, "Start service") Log.d(TAG, "Start service")
connect(intent?.getStringExtra(MSG_VPN_CONFIG)) connect(intent?.getStringExtra(MSG_VPN_CONFIG))
} }
ServiceCompat.startForeground(this, NOTIFICATION_ID, notification, foregroundServiceTypeCompat) ServiceCompat.startForeground(
this, NOTIFICATION_ID, serviceNotification.buildNotification(serverName, protocolState.value),
foregroundServiceTypeCompat
)
return START_REDELIVER_INTENT return START_REDELIVER_INTENT
} }
@ -269,6 +269,7 @@ class AmneziaVpnService : VpnService() {
override fun onDestroy() { override fun onDestroy() {
Log.d(TAG, "Destroy service") Log.d(TAG, "Destroy service")
unregisterBroadcastReceivers()
runBlocking { runBlocking {
disconnect() disconnect()
disconnectionJob?.join() disconnectionJob?.join()
@ -289,6 +290,63 @@ class AmneziaVpnService : VpnService() {
stopSelf() stopSelf()
} }
private fun registerBroadcastReceivers() {
Log.d(TAG, "Register broadcast receivers")
disconnectReceiver = registerBroadcastReceiver(ACTION_DISCONNECT, ContextCompat.RECEIVER_NOT_EXPORTED) {
Log.d(TAG, "Broadcast request received: $ACTION_DISCONNECT")
disconnect()
}
notificationStateReceiver = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
registerBroadcastReceiver(
arrayOf(
NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED,
NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED
)
) {
val state = it?.getBooleanExtra(NotificationManager.EXTRA_BLOCKED_STATE, false)
Log.d(TAG, "Notification state changed: ${it?.action}, blocked = $state")
if (state == false) {
enableNotification()
} else {
disableNotification()
}
}
} else null
registerScreenStateBroadcastReceivers()
}
private fun registerScreenStateBroadcastReceivers() {
if (serviceNotification.isNotificationEnabled()) {
Log.d(TAG, "Register screen state broadcast receivers")
screenOnReceiver = registerBroadcastReceiver(Intent.ACTION_SCREEN_ON) {
if (isConnected && serviceNotification.isNotificationEnabled()) startTrafficStatsUpdateJob()
}
screenOffReceiver = registerBroadcastReceiver(Intent.ACTION_SCREEN_OFF) {
stopTrafficStatsUpdateJob()
}
}
}
private fun unregisterScreenStateBroadcastReceivers() {
Log.d(TAG, "Unregister screen state broadcast receivers")
unregisterBroadcastReceiver(screenOnReceiver)
unregisterBroadcastReceiver(screenOffReceiver)
screenOnReceiver = null
screenOffReceiver = null
}
private fun unregisterBroadcastReceivers() {
Log.d(TAG, "Unregister broadcast receivers")
unregisterBroadcastReceiver(disconnectReceiver)
unregisterBroadcastReceiver(notificationStateReceiver)
unregisterScreenStateBroadcastReceivers()
disconnectReceiver = null
notificationStateReceiver = null
}
/** /**
* Methods responsible for processing VPN connection * Methods responsible for processing VPN connection
*/ */
@ -297,29 +355,8 @@ class AmneziaVpnService : VpnService() {
// drop first default UNKNOWN state // drop first default UNKNOWN state
protocolState.drop(1).collect { protocolState -> protocolState.drop(1).collect { protocolState ->
Log.d(TAG, "Protocol state changed: $protocolState") Log.d(TAG, "Protocol state changed: $protocolState")
when (protocolState) {
CONNECTED -> {
networkState.bindNetworkListener()
if (isActivityConnected) launchSendingStatistics()
}
DISCONNECTED -> { serviceNotification.updateNotification(serverName, protocolState)
networkState.unbindNetworkListener()
stopSendingStatistics()
if (!isServiceBound) stopService()
}
DISCONNECTING -> {
networkState.unbindNetworkListener()
stopSendingStatistics()
}
RECONNECTING -> {
stopSendingStatistics()
}
CONNECTING, UNKNOWN -> {}
}
clientMessengers.send { clientMessengers.send {
ServiceEvent.STATUS_CHANGED.packToMessage { ServiceEvent.STATUS_CHANGED.packToMessage {
@ -328,13 +365,41 @@ class AmneziaVpnService : VpnService() {
} }
VpnStateStore.store { VpnState(protocolState, serverName, serverIndex) } VpnStateStore.store { VpnState(protocolState, serverName, serverIndex) }
when (protocolState) {
CONNECTED -> {
networkState.bindNetworkListener()
// if (isActivityConnected) launchSendingStatistics()
launchTrafficStatsUpdate()
}
DISCONNECTED -> {
networkState.unbindNetworkListener()
stopTrafficStatsUpdateJob()
// stopSendingStatistics()
if (!isServiceBound) stopService()
}
DISCONNECTING -> {
networkState.unbindNetworkListener()
stopTrafficStatsUpdateJob()
// stopSendingStatistics()
}
RECONNECTING -> {
stopTrafficStatsUpdateJob()
// stopSendingStatistics()
}
CONNECTING, UNKNOWN -> {}
}
} }
} }
} }
@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 {
@ -345,12 +410,62 @@ class AmneziaVpnService : VpnService() {
delay(STATISTICS_SENDING_TIMEOUT) delay(STATISTICS_SENDING_TIMEOUT)
} }
} }
} */ }
} }
@MainThread @MainThread
private fun stopSendingStatistics() { private fun stopSendingStatistics() {
statisticsSendingJob?.cancel() statisticsSendingJob?.cancel()
} */
@MainThread
private fun enableNotification() {
registerScreenStateBroadcastReceivers()
serviceNotification.updateNotification(serverName, protocolState.value)
launchTrafficStatsUpdate()
}
@MainThread
private fun disableNotification() {
unregisterScreenStateBroadcastReceivers()
stopTrafficStatsUpdateJob()
}
@MainThread
private fun launchTrafficStatsUpdate() {
stopTrafficStatsUpdateJob()
if (isConnected &&
serviceNotification.isNotificationEnabled() &&
getSystemService<PowerManager>()?.isInteractive != false
) {
Log.d(TAG, "Launch traffic stats update")
trafficStats.reset()
startTrafficStatsUpdateJob()
}
}
@MainThread
private fun startTrafficStatsUpdateJob() {
if (trafficStatsUpdateJob == null && trafficStats.isSupported()) {
Log.d(TAG, "Start traffic stats update")
trafficStatsUpdateJob = mainScope.launch {
while (true) {
trafficStats.getSpeed().let { speed ->
if (isConnected) {
serviceNotification.updateSpeed(speed)
}
}
delay(TRAFFIC_STATS_UPDATE_TIMEOUT)
}
}
}
}
@MainThread
private fun stopTrafficStatsUpdateJob() {
Log.d(TAG, "Stop traffic stats update")
trafficStatsUpdateJob?.cancel()
trafficStatsUpdateJob = null
} }
@MainThread @MainThread
@ -473,6 +588,7 @@ class AmneziaVpnService : VpnService() {
private fun saveServerData(config: JSONObject?) { private fun saveServerData(config: JSONObject?) {
serverName = config?.opt("description") as String? serverName = config?.opt("description") as String?
serverIndex = config?.opt("serverIndex") as Int? ?: -1 serverIndex = config?.opt("serverIndex") as Int? ?: -1
Log.d(TAG, "Save server data: ($serverIndex, $serverName)")
Prefs.save(PREFS_SERVER_NAME, serverName) Prefs.save(PREFS_SERVER_NAME, serverName)
Prefs.save(PREFS_SERVER_INDEX, serverIndex) Prefs.save(PREFS_SERVER_INDEX, serverIndex)
} }
@ -480,6 +596,7 @@ class AmneziaVpnService : VpnService() {
private fun loadServerData() { private fun loadServerData() {
serverName = Prefs.load<String>(PREFS_SERVER_NAME).ifBlank { null } serverName = Prefs.load<String>(PREFS_SERVER_NAME).ifBlank { null }
if (serverName != null) serverIndex = Prefs.load(PREFS_SERVER_INDEX) if (serverName != null) serverIndex = Prefs.load(PREFS_SERVER_INDEX)
Log.d(TAG, "Load server data: ($serverIndex, $serverName)")
} }
private fun checkPermission(): Boolean = private fun checkPermission(): Boolean =
@ -496,8 +613,7 @@ class AmneziaVpnService : VpnService() {
companion object { companion object {
fun isRunning(context: Context): Boolean = fun isRunning(context: Context): Boolean =
(context.getSystemService(ACTIVITY_SERVICE) as ActivityManager) context.getSystemService<ActivityManager>()!!.runningAppProcesses.any {
.runningAppProcesses.any {
it.processName == PROCESS_NAME && it.importance <= IMPORTANCE_FOREGROUND_SERVICE it.processName == PROCESS_NAME && it.importance <= IMPORTANCE_FOREGROUND_SERVICE
} }
} }

View file

@ -0,0 +1,73 @@
import android.Manifest.permission.INTERNET
import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.pm.PackageManager.NameNotFoundException
import android.graphics.Bitmap
import android.graphics.Bitmap.Config.ARGB_8888
import androidx.core.graphics.drawable.toBitmapOrNull
import org.amnezia.vpn.util.Log
import org.json.JSONArray
import org.json.JSONObject
private const val TAG = "AppListProvider"
object AppListProvider {
fun getAppList(pm: PackageManager, selfPackageName: String): String {
val jsonArray = JSONArray()
pm.getPackagesHoldingPermissions(arrayOf(INTERNET), 0)
.filter { it.packageName != selfPackageName }
.map { App(it, pm) }
.sortedWith(App::compareTo)
.map(App::toJson)
.forEach(jsonArray::put)
return jsonArray.toString()
}
fun getAppIcon(pm: PackageManager, packageName: String, width: Int, height: Int): Bitmap {
val icon = try {
pm.getApplicationIcon(packageName)
} catch (e: NameNotFoundException) {
Log.e(TAG, "Package $packageName was not found: $e")
pm.defaultActivityIcon
}
val w: Int = if (width > 0) width else icon.intrinsicWidth
val h: Int = if (height > 0) height else icon.intrinsicHeight
return icon.toBitmapOrNull(w, h, ARGB_8888)
?: Bitmap.createBitmap(w, h, ARGB_8888)
}
}
private class App(pi: PackageInfo, pm: PackageManager, ai: ApplicationInfo = pi.applicationInfo) : Comparable<App> {
val name: String?
val packageName: String = pi.packageName
val icon: Boolean = ai.icon != 0
val isLaunchable: Boolean = pm.getLaunchIntentForPackage(packageName) != null
init {
val name = ai.loadLabel(pm).toString()
this.name = if (name != packageName) name else null
}
override fun compareTo(other: App): Int {
val r = other.isLaunchable.compareTo(isLaunchable)
if (r != 0) return r
if (name != other.name) {
return when {
name == null -> 1
other.name == null -> -1
else -> String.CASE_INSENSITIVE_ORDER.compare(name, other.name)
}
}
return String.CASE_INSENSITIVE_ORDER.compare(packageName, other.packageName)
}
fun toJson(): JSONObject {
val jsonObject = JSONObject()
jsonObject.put("package", packageName)
jsonObject.put("name", name)
jsonObject.put("icon", icon)
jsonObject.put("launchable", isLaunchable)
return jsonObject
}
}

View file

@ -32,6 +32,7 @@ enum class Action : IpcMessage {
CONNECT, CONNECT,
DISCONNECT, DISCONNECT,
REQUEST_STATUS, REQUEST_STATUS,
NOTIFICATION_PERMISSION_GRANTED,
SET_SAVE_LOGS SET_SAVE_LOGS
} }

View file

@ -1,189 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.amnezia.vpn;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.Manifest.permission;
import android.net.Uri;
import android.os.Build;
import android.util.Log;
import android.webkit.WebView;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
// Gets used by /platforms/android/androidAppListProvider.cpp
public class PackageManagerHelper {
final static String TAG = "PackageManagerHelper";
final static int MIN_CHROME_VERSION = 65;
final static List<String> CHROME_BROWSERS = Arrays.asList(
new String[] {"com.google.android.webview", "com.android.webview", "com.google.chrome"});
private static String getAllAppNames(Context ctx) {
JSONObject output = new JSONObject();
PackageManager pm = ctx.getPackageManager();
List<String> browsers = getBrowserIDs(pm);
List<PackageInfo> packs = pm.getInstalledPackages(PackageManager.GET_PERMISSIONS);
for (int i = 0; i < packs.size(); i++) {
PackageInfo p = packs.get(i);
// Do not add ourselves and System Apps to the list, unless it might be a browser
if ((!isSystemPackage(p,pm) || browsers.contains(p.packageName))
&& !isSelf(p)) {
String appid = p.packageName;
String appName = p.applicationInfo.loadLabel(pm).toString();
try {
output.put(appid, appName);
} catch (JSONException e) {
e.printStackTrace();
}
}
}
return output.toString();
}
private static Drawable getAppIcon(Context ctx, String id) {
try {
return ctx.getPackageManager().getApplicationIcon(id);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return new ColorDrawable(Color.TRANSPARENT);
}
private static boolean isSystemPackage(PackageInfo pkgInfo, PackageManager pm) {
if( (pkgInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0){
// no system app
return false;
}
// For Systems Packages there are Cases where we want to add it anyway:
// Has the use Internet permission (otherwise makes no sense)
// Had at least 1 update (this means it's probably on any AppStore)
// Has a a launch activity (has a ui and is not just a system service)
if(!usesInternet(pkgInfo)){
return true;
}
if(!hadUpdate(pkgInfo)){
return true;
}
if(pm.getLaunchIntentForPackage(pkgInfo.packageName) == null){
// If there is no way to launch this from a homescreen, def a sys package
return true;
}
return false;
}
private static boolean isSelf(PackageInfo pkgInfo) {
return pkgInfo.packageName.equals("org.amnezia.vpn")
|| pkgInfo.packageName.equals("org.amnezia.vpn.debug");
}
private static boolean usesInternet(PackageInfo pkgInfo){
if(pkgInfo.requestedPermissions == null){
return false;
}
for(int i=0; i < pkgInfo.requestedPermissions.length; i++) {
String permission = pkgInfo.requestedPermissions[i];
if(Manifest.permission.INTERNET.equals(permission)){
return true;
}
}
return false;
}
private static boolean hadUpdate(PackageInfo pkgInfo){
return pkgInfo.lastUpdateTime > pkgInfo.firstInstallTime;
}
// Returns List of all Packages that can classify themselves as browsers
private static List<String> getBrowserIDs(PackageManager pm) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://www.amnezia.org/"));
intent.addCategory(Intent.CATEGORY_BROWSABLE);
// We've tried using PackageManager.MATCH_DEFAULT_ONLY flag and found that browsers that
// are not set as the default browser won't be matched even if they had CATEGORY_DEFAULT set
// in the intent filter
List<ResolveInfo> resolveInfos = pm.queryIntentActivities(intent, PackageManager.MATCH_ALL);
List<String> browsers = new ArrayList<String>();
for (int i = 0; i < resolveInfos.size(); i++) {
ResolveInfo info = resolveInfos.get(i);
String browserID = info.activityInfo.packageName;
browsers.add(browserID);
}
return browsers;
}
// Gets called in AndroidAuthenticationListener;
public static boolean isWebViewSupported(Context ctx) {
Log.v(TAG, "Checking if installed Webview is compatible with FxA");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
// The default Webview is able do to FXA
return true;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
PackageInfo pi = WebView.getCurrentWebViewPackage();
if (CHROME_BROWSERS.contains(pi.packageName)) {
return isSupportedChromeBrowser(pi);
}
return isNotAncientBrowser(pi);
}
// Before O the webview is hardcoded, but we dont know which package it is.
// Check if com.google.android.webview is installed
PackageManager pm = ctx.getPackageManager();
try {
PackageInfo pi = pm.getPackageInfo("com.google.android.webview", 0);
return isSupportedChromeBrowser(pi);
} catch (PackageManager.NameNotFoundException e) {
}
// Otherwise check com.android.webview
try {
PackageInfo pi = pm.getPackageInfo("com.android.webview", 0);
return isSupportedChromeBrowser(pi);
} catch (PackageManager.NameNotFoundException e) {
}
Log.e(TAG, "Android System WebView is not found");
// Giving up :(
return false;
}
private static boolean isSupportedChromeBrowser(PackageInfo pi) {
Log.d(TAG, "Checking Chrome Based Browser: " + pi.packageName);
Log.d(TAG, "version name: " + pi.versionName);
Log.d(TAG, "version code: " + pi.versionCode);
try {
String versionCode = pi.versionName.split(Pattern.quote(" "))[0];
String majorVersion = versionCode.split(Pattern.quote("."))[0];
int version = Integer.parseInt(majorVersion);
return version >= MIN_CHROME_VERSION;
} catch (Exception e) {
Log.e(TAG, "Failed to check Chrome Version Code " + pi.versionName);
return false;
}
}
private static boolean isNotAncientBrowser(PackageInfo pi) {
// Not a google chrome - So the version name is worthless
// Lets just make sure the WebView
// used is not ancient ==> Was updated in at least the last 365 days
Log.d(TAG, "Checking Chrome Based Browser: " + pi.packageName);
Log.d(TAG, "version name: " + pi.versionName);
Log.d(TAG, "version code: " + pi.versionCode);
double oneYearInMillis = 31536000000L;
return pi.lastUpdateTime > (System.currentTimeMillis() - oneYearInMillis);
}
}

View file

@ -0,0 +1,180 @@
package org.amnezia.vpn
import android.Manifest.permission
import android.annotation.SuppressLint
import android.app.Notification
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import androidx.core.app.NotificationChannelCompat.Builder
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationCompat.Action
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import org.amnezia.vpn.protocol.ProtocolState
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
import org.amnezia.vpn.util.Log
import org.amnezia.vpn.util.net.TrafficStats.TrafficData
private const val TAG = "ServiceNotification"
private const val OLD_NOTIFICATION_CHANNEL_ID: String = "org.amnezia.vpn.notification"
private const val NOTIFICATION_CHANNEL_ID: String = "org.amnezia.vpn.notifications"
const val NOTIFICATION_ID = 1337
private const val GET_ACTIVITY_REQUEST_CODE = 0
private const val CONNECT_REQUEST_CODE = 1
private const val DISCONNECT_REQUEST_CODE = 2
class ServiceNotification(private val context: Context) {
private val upDownSymbols = when (Build.BRAND) {
"Infinix" -> '˅' to '˄'
else -> '↓' to '↑'
}
private val notificationManager = NotificationManagerCompat.from(context)
private val notificationBuilder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
.setShowWhen(false)
.setOngoing(true)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
.setCategory(NotificationCompat.CATEGORY_SERVICE)
.setContentIntent(
PendingIntent.getActivity(
context,
GET_ACTIVITY_REQUEST_CODE,
Intent(context, AmneziaActivity::class.java),
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
)
private val zeroSpeed: String = with(TrafficData.ZERO) {
formatSpeedString(rxString, txString)
}
fun buildNotification(serverName: String?, state: ProtocolState): Notification {
val speedString = if (state == CONNECTED) zeroSpeed else null
Log.d(TAG, "Build notification: $serverName, $state")
return notificationBuilder
.setSmallIcon(R.drawable.ic_amnezia_round)
.setContentTitle(serverName ?: "AmneziaVPN")
.setContentText(context.getString(state))
.setSubText(speedString)
.setWhen(System.currentTimeMillis())
.clearActions()
.apply {
getAction(state)?.let {
addAction(it)
}
}
.build()
}
private fun buildNotification(speed: TrafficData): Notification =
notificationBuilder
.setWhen(System.currentTimeMillis())
.setSubText(getSpeedString(speed))
.build()
fun isNotificationEnabled(): Boolean {
if (!context.isNotificationPermissionGranted()) return false
if (!notificationManager.areNotificationsEnabled()) return false
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
return notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID)
?.let { it.importance != NotificationManager.IMPORTANCE_NONE } ?: true
}
return true
}
@SuppressLint("MissingPermission")
fun updateNotification(serverName: String?, state: ProtocolState) {
if (context.isNotificationPermissionGranted()) {
Log.d(TAG, "Update notification: $serverName, $state")
notificationManager.notify(NOTIFICATION_ID, buildNotification(serverName, state))
}
}
@SuppressLint("MissingPermission")
fun updateSpeed(speed: TrafficData) {
if (context.isNotificationPermissionGranted()) {
notificationManager.notify(NOTIFICATION_ID, buildNotification(speed))
}
}
private fun getSpeedString(traffic: TrafficData) =
if (traffic == TrafficData.ZERO) zeroSpeed
else formatSpeedString(traffic.rxString, traffic.txString)
private fun formatSpeedString(rx: String, tx: String) = with(upDownSymbols) { "$first $rx $second $tx" }
private fun getAction(state: ProtocolState): Action? {
return when (state) {
CONNECTED -> {
Action(
0, context.getString(R.string.disconnect),
PendingIntent.getBroadcast(
context,
DISCONNECT_REQUEST_CODE,
Intent(ACTION_DISCONNECT).apply {
setPackage("org.amnezia.vpn")
},
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
)
}
DISCONNECTED -> {
Action(
0, context.getString(R.string.connect),
createServicePendingIntent(
context,
CONNECT_REQUEST_CODE,
Intent(context, AmneziaVpnService::class.java),
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
)
}
else -> null
}
}
private val createServicePendingIntent: (Context, Int, Intent, Int) -> PendingIntent =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
PendingIntent::getForegroundService
} else {
PendingIntent::getService
}
companion object {
fun createNotificationChannel(context: Context) {
with(NotificationManagerCompat.from(context)) {
deleteNotificationChannel(OLD_NOTIFICATION_CHANNEL_ID)
createNotificationChannel(
Builder(NOTIFICATION_CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_DEFAULT)
.setShowBadge(false)
.setSound(null, null)
.setVibrationEnabled(false)
.setLightsEnabled(false)
.setName("AmneziaVPN")
.setDescription(context.resources.getString(R.string.notificationChannelDescription))
.build()
)
}
}
}
}
fun Context.isNotificationPermissionGranted(): Boolean =
Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU ||
ContextCompat.checkSelfPermission(this, permission.POST_NOTIFICATIONS) ==
PackageManager.PERMISSION_GRANTED

View file

@ -1,12 +1,14 @@
package org.amnezia.vpn package org.amnezia.vpn
import android.app.AlertDialog
import android.app.KeyguardManager import android.app.KeyguardManager
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.res.Configuration.UI_MODE_NIGHT_MASK
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import android.net.VpnService import android.net.VpnService
import android.os.Bundle import android.os.Bundle
import android.provider.Settings
import android.widget.Toast import android.widget.Toast
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResult import androidx.activity.result.ActivityResult
@ -29,11 +31,9 @@ class VpnRequestActivity : ComponentActivity() {
val requestIntent = VpnService.prepare(applicationContext) val requestIntent = VpnService.prepare(applicationContext)
if (requestIntent != null) { if (requestIntent != null) {
if (getSystemService<KeyguardManager>()!!.isKeyguardLocked) { if (getSystemService<KeyguardManager>()!!.isKeyguardLocked) {
userPresentReceiver = object : BroadcastReceiver() { userPresentReceiver = registerBroadcastReceiver(Intent.ACTION_USER_PRESENT) {
override fun onReceive(context: Context?, intent: Intent?) =
requestLauncher.launch(requestIntent) requestLauncher.launch(requestIntent)
} }
registerReceiver(userPresentReceiver, IntentFilter(Intent.ACTION_USER_PRESENT))
} else { } else {
requestLauncher.launch(requestIntent) requestLauncher.launch(requestIntent)
} }
@ -45,26 +45,49 @@ class VpnRequestActivity : ComponentActivity() {
} }
override fun onDestroy() { override fun onDestroy() {
userPresentReceiver?.let { unregisterBroadcastReceiver(userPresentReceiver)
unregisterReceiver(it) userPresentReceiver = null
}
super.onDestroy() super.onDestroy()
} }
private fun checkRequestResult(result: ActivityResult) { private fun checkRequestResult(result: ActivityResult) {
when (result.resultCode) { when (val resultCode = result.resultCode) {
RESULT_OK -> onPermissionGranted() RESULT_OK -> {
else -> Toast.makeText(this, "Vpn permission denied", Toast.LENGTH_LONG).show() onPermissionGranted()
}
finish() finish()
} }
else -> {
Log.w(TAG, "Vpn permission denied, resultCode: $resultCode")
showOnVpnPermissionRejectDialog()
}
}
}
private fun onPermissionGranted() { private fun onPermissionGranted() {
Toast.makeText(this, "Vpn permission granted", Toast.LENGTH_LONG).show() Toast.makeText(this, resources.getString(R.string.vpnGranted), Toast.LENGTH_LONG).show()
Intent(applicationContext, AmneziaVpnService::class.java).apply { Intent(applicationContext, AmneziaVpnService::class.java).apply {
putExtra(AFTER_PERMISSION_CHECK, true) putExtra(AFTER_PERMISSION_CHECK, true)
}.also { }.also {
ContextCompat.startForegroundService(this, it) ContextCompat.startForegroundService(this, it)
} }
} }
private fun showOnVpnPermissionRejectDialog() {
AlertDialog.Builder(this, getDialogTheme())
.setTitle(R.string.vpnSetupFailed)
.setMessage(R.string.vpnSetupFailedMessage)
.setNegativeButton(R.string.ok) { _, _ -> }
.setPositiveButton(R.string.openVpnSettings) { _, _ ->
startActivity(Intent(Settings.ACTION_VPN_SETTINGS))
}
.setOnDismissListener { finish() }
.show()
}
private fun getDialogTheme(): Int =
if (resources.configuration.uiMode and UI_MODE_NIGHT_MASK == UI_MODE_NIGHT_YES)
android.R.style.Theme_DeviceDefault_Dialog_Alert
else
android.R.style.Theme_DeviceDefault_Light_Dialog_Alert
} }

View file

@ -4,6 +4,8 @@ import android.app.Application
import androidx.datastore.core.MultiProcessDataStoreFactory import androidx.datastore.core.MultiProcessDataStoreFactory
import androidx.datastore.core.Serializer import androidx.datastore.core.Serializer
import androidx.datastore.dataStoreFile import androidx.datastore.dataStoreFile
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.InputStream import java.io.InputStream
import java.io.ObjectInputStream import java.io.ObjectInputStream
import java.io.ObjectOutputStream import java.io.ObjectOutputStream
@ -59,7 +61,8 @@ private class VpnStateSerializer : Serializer<VpnState> {
override suspend fun readFrom(input: InputStream): VpnState { override suspend fun readFrom(input: InputStream): VpnState {
return withContext(Dispatchers.IO) { return withContext(Dispatchers.IO) {
ObjectInputStream(input).use { val bios = ByteArrayInputStream(input.readBytes())
ObjectInputStream(bios).use {
it.readObject() as VpnState it.readObject() as VpnState
} }
} }
@ -67,9 +70,11 @@ private class VpnStateSerializer : Serializer<VpnState> {
override suspend fun writeTo(t: VpnState, output: OutputStream) { override suspend fun writeTo(t: VpnState, output: OutputStream) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
ObjectOutputStream(output).use { val baos = ByteArrayOutputStream()
ObjectOutputStream(baos).use {
it.writeObject(t) it.writeObject(t)
} }
output.write(baos.toByteArray())
} }
} }
} }

View file

@ -17,6 +17,7 @@ object QtAndroidController {
external fun onServiceError() external fun onServiceError()
external fun onVpnPermissionRejected() external fun onVpnPermissionRejected()
external fun onNotificationStateChanged()
external fun onVpnStateChanged(stateCode: Int) external fun onVpnStateChanged(stateCode: Int)
external fun onStatisticsUpdate(rxBytes: Long, txBytes: Long) external fun onStatisticsUpdate(rxBytes: Long, txBytes: Long)

View file

@ -17,5 +17,7 @@ android {
} }
dependencies { dependencies {
implementation(libs.androidx.core)
implementation(libs.kotlinx.coroutines)
implementation(libs.androidx.security.crypto) implementation(libs.androidx.security.crypto)
} }

View file

@ -109,11 +109,13 @@ object Log {
"${deviceInfo()}\n${readLogs()}\nLOGCAT:\n${getLogcat()}" "${deviceInfo()}\n${readLogs()}\nLOGCAT:\n${getLogcat()}"
fun clearLogs() { fun clearLogs() {
if (logDir.exists()) {
withLock { withLock {
logFile.delete() logFile.delete()
rotateLogFile.delete() rotateLogFile.delete()
} }
} }
}
private fun log(tag: String, msg: String, priority: Priority) { private fun log(tag: String, msg: String, priority: Priority) {
if (saveLogs && priority != V) saveLogMsg(formatLogMsg(tag, msg, priority)) if (saveLogs && priority != V) saveLogMsg(formatLogMsg(tag, msg, priority))

View file

@ -1,16 +1,21 @@
package org.amnezia.vpn.util.net package org.amnezia.vpn.util.net
import java.net.Inet4Address
import java.net.InetAddress import java.net.InetAddress
data class InetEndpoint(val address: InetAddress, val port: Int) { data class InetEndpoint(val address: InetAddress, val port: Int) {
override fun toString(): String = "${address.hostAddress}:$port" override fun toString(): String = if (address is Inet4Address) {
"${address.ip}:$port"
} else {
"[${address.ip}]:$port"
}
companion object { companion object {
fun parse(data: String): InetEndpoint { fun parse(data: String): InetEndpoint {
val split = data.split(":") val i = data.lastIndexOf(':')
val address = parseInetAddress(split.first()) val address = parseInetAddress(data.substring(0, i))
val port = split.last().toInt() val port = data.substring(i + 1).toInt()
return InetEndpoint(address, port) return InetEndpoint(address, port)
} }
} }

View file

@ -9,7 +9,11 @@ data class InetNetwork(val address: InetAddress, val mask: Int) {
constructor(address: InetAddress) : this(address, address.maxPrefixLength) constructor(address: InetAddress) : this(address, address.maxPrefixLength)
override fun toString(): String = "${address.hostAddress}/$mask" val isIpv4: Boolean = address is Inet4Address
val isIpv6: Boolean
get() = !isIpv4
override fun toString(): String = "${address.ip}/$mask"
companion object { companion object {
fun parse(data: String): InetNetwork { fun parse(data: String): InetNetwork {

View file

@ -3,12 +3,17 @@ package org.amnezia.vpn.util.net
import java.net.InetAddress import java.net.InetAddress
@OptIn(ExperimentalUnsignedTypes::class) @OptIn(ExperimentalUnsignedTypes::class)
class IpAddress private constructor(private val address: UByteArray) : Comparable<IpAddress> { internal class IpAddress private constructor(private val address: UByteArray) : Comparable<IpAddress> {
val size: Int = address.size val size: Int = address.size
val lastIndex: Int = address.lastIndex val lastIndex: Int = address.lastIndex
val maxMask: Int = size * 8 val maxMask: Int = size * 8
@OptIn(ExperimentalStdlibApi::class)
val hexFormat: HexFormat by lazy {
HexFormat { number.removeLeadingZeros = true }
}
constructor(inetAddress: InetAddress) : this(inetAddress.address.asUByteArray()) constructor(inetAddress: InetAddress) : this(inetAddress.address.asUByteArray())
constructor(ipAddress: String) : this(parseInetAddress(ipAddress)) constructor(ipAddress: String) : this(parseInetAddress(ipAddress))
@ -43,6 +48,8 @@ class IpAddress private constructor(private val address: UByteArray) : Comparabl
return copy return copy
} }
fun isMinIp(): Boolean = address.all { it == 0x00u.toUByte() }
fun isMaxIp(): Boolean = address.all { it == 0xffu.toUByte() } fun isMaxIp(): Boolean = address.all { it == 0xffu.toUByte() }
override fun compareTo(other: IpAddress): Int { override fun compareTo(other: IpAddress): Int {
@ -74,12 +81,14 @@ class IpAddress private constructor(private val address: UByteArray) : Comparabl
private fun toIpv6String(): String { private fun toIpv6String(): String {
val sb = StringBuilder() val sb = StringBuilder()
var i = 0 var i = 0
var block: Int
while (i < size) { while (i < size) {
sb.append(address[i++].toHexString()) block = address[i++].toInt() shl 8
sb.append(address[i++].toHexString()) block += address[i++].toInt()
sb.append(block.toHexString(hexFormat))
sb.append(':') sb.append(':')
} }
sb.deleteAt(sb.lastIndex) sb.deleteAt(sb.lastIndex)
return sb.toString() return convertIpv6ToCanonicalForm(sb.toString())
} }
} }

View file

@ -2,14 +2,24 @@ package org.amnezia.vpn.util.net
import java.net.InetAddress import java.net.InetAddress
class IpRange(private val start: IpAddress, private val end: IpAddress) : Comparable<IpRange> { class IpRange internal constructor(
internal val start: IpAddress,
internal val end: IpAddress
) : Comparable<IpRange> {
init { init {
if (start > end) throw IllegalArgumentException("Start IP: $start is greater then end IP: $end") if (start.size != end.size) {
throw IllegalArgumentException(
"Unable to create a range between IPv4 and IPv6 addresses (start IP: [$start], end IP: [$end])"
)
}
if (start > end) throw IllegalArgumentException("Start IP: [$start] is greater then end IP: [$end]")
} }
private constructor(addresses: Pair<IpAddress, IpAddress>) : this(addresses.first, addresses.second) private constructor(addresses: Pair<IpAddress, IpAddress>) : this(addresses.first, addresses.second)
internal constructor(ipAddress: IpAddress) : this(ipAddress, ipAddress)
constructor(inetAddress: InetAddress, mask: Int) : this(from(inetAddress, mask)) constructor(inetAddress: InetAddress, mask: Int) : this(from(inetAddress, mask))
constructor(address: String, mask: Int) : this(parseInetAddress(address), mask) constructor(address: String, mask: Int) : this(parseInetAddress(address), mask)
@ -22,6 +32,13 @@ class IpRange(private val start: IpAddress, private val end: IpAddress) : Compar
private fun isIntersect(other: IpRange): Boolean = private fun isIntersect(other: IpRange): Boolean =
(start <= other.end) && (end >= other.start) (start <= other.end) && (end >= other.start)
operator fun plus(other: IpRange): IpRange? {
if (start > other.end && !start.isMinIp() && start.dec() == other.end) return IpRange(other.start, end)
if (end < other.start && !end.isMaxIp() && end.inc() == other.start) return IpRange(start, other.end)
if (!isIntersect(other)) return null
return IpRange(minOf(start, other.start), maxOf(end, other.end))
}
operator fun minus(other: IpRange): List<IpRange>? { operator fun minus(other: IpRange): List<IpRange>? {
if (this in other) return emptyList() if (this in other) return emptyList()
if (!isIntersect(other)) return null if (!isIntersect(other)) return null
@ -94,9 +111,7 @@ class IpRange(private val start: IpAddress, private val end: IpAddress) : Compar
return result return result
} }
override fun toString(): String { override fun toString(): String = if (start == end) "<$start>" else "<$start - $end>"
return "$start - $end"
}
companion object { companion object {
private fun from(inetAddress: InetAddress, mask: Int): Pair<IpAddress, IpAddress> { private fun from(inetAddress: InetAddress, mask: Int): Pair<IpAddress, IpAddress> {

View file

@ -1,15 +1,35 @@
package org.amnezia.vpn.util.net package org.amnezia.vpn.util.net
class IpRangeSet(ipRange: IpRange = IpRange("0.0.0.0", 0)) { class IpRangeSet {
private val ranges = sortedSetOf(ipRange) private val ranges = sortedSetOf<IpRange>()
fun add(ipRange: IpRange) {
val iterator = ranges.iterator()
var rangeToAdd = ipRange
run {
while (iterator.hasNext()) {
val curRange = iterator.next()
if (rangeToAdd.end < curRange.start &&
!rangeToAdd.end.isMaxIp() &&
rangeToAdd.end.inc() != curRange.start) break
(curRange + rangeToAdd)?.let { resultRange ->
if (resultRange == curRange) return@run
iterator.remove()
rangeToAdd = resultRange
}
}
ranges += rangeToAdd
}
}
fun remove(ipRange: IpRange) { fun remove(ipRange: IpRange) {
val iterator = ranges.iterator() val iterator = ranges.iterator()
val splitRanges = mutableListOf<IpRange>() val splitRanges = mutableListOf<IpRange>()
while (iterator.hasNext()) { while (iterator.hasNext()) {
val range = iterator.next() val curRange = iterator.next()
(range - ipRange)?.let { resultRanges -> if (ipRange.end < curRange.start) break
(curRange - ipRange)?.let { resultRanges ->
iterator.remove() iterator.remove()
splitRanges += resultRanges splitRanges += resultRanges
} }
@ -17,10 +37,7 @@ class IpRangeSet(ipRange: IpRange = IpRange("0.0.0.0", 0)) {
ranges += splitRanges ranges += splitRanges
} }
fun subnets(): List<InetNetwork> = fun subnets(): List<InetNetwork> = ranges.map(IpRange::subnets).flatten()
ranges.map(IpRange::subnets).flatten()
override fun toString(): String { override fun toString(): String = ranges.toString()
return ranges.toString()
}
} }

View file

@ -10,7 +10,9 @@ import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
import android.net.NetworkRequest import android.net.NetworkRequest
import android.os.Build import android.os.Build
import android.os.Handler import android.os.Handler
import androidx.core.content.getSystemService
import kotlin.LazyThreadSafetyMode.NONE import kotlin.LazyThreadSafetyMode.NONE
import kotlinx.coroutines.delay
import org.amnezia.vpn.util.Log import org.amnezia.vpn.util.Log
private const val TAG = "NetworkState" private const val TAG = "NetworkState"
@ -28,7 +30,7 @@ class NetworkState(
} }
private val connectivityManager: ConnectivityManager by lazy(NONE) { private val connectivityManager: ConnectivityManager by lazy(NONE) {
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager context.getSystemService<ConnectivityManager>()!!
} }
private val networkRequest: NetworkRequest by lazy(NONE) { private val networkRequest: NetworkRequest by lazy(NONE) {
@ -80,13 +82,24 @@ class NetworkState(
} }
} }
fun bindNetworkListener() { suspend fun bindNetworkListener() {
if (isListenerBound) return if (isListenerBound) return
Log.d(TAG, "Bind network listener") Log.d(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) {
try {
connectivityManager.requestNetwork(networkRequest, networkCallback, handler) connectivityManager.requestNetwork(networkRequest, networkCallback, handler)
} catch (e: SecurityException) {
Log.e(TAG, "Failed to bind network listener: $e")
// Android 11 bug: https://issuetracker.google.com/issues/175055271
if (e.message?.startsWith("Package android does not belong to") == true) {
delay(1000)
connectivityManager.requestNetwork(networkRequest, networkCallback, handler)
} else {
throw e
}
}
} else { } else {
connectivityManager.requestNetwork(networkRequest, networkCallback) connectivityManager.requestNetwork(networkRequest, networkCallback)
} }

View file

@ -5,12 +5,14 @@ import android.net.ConnectivityManager
import android.net.InetAddresses import android.net.InetAddresses
import android.net.NetworkCapabilities import android.net.NetworkCapabilities
import android.os.Build import android.os.Build
import androidx.core.content.getSystemService
import java.lang.reflect.InvocationTargetException
import java.net.Inet4Address import java.net.Inet4Address
import java.net.Inet6Address import java.net.Inet6Address
import java.net.InetAddress import java.net.InetAddress
fun getLocalNetworks(context: Context, ipv6: Boolean): List<InetNetwork> { fun getLocalNetworks(context: Context, ipv6: Boolean): List<InetNetwork> {
val connectivityManager = context.getSystemService(ConnectivityManager::class.java) val connectivityManager = context.getSystemService<ConnectivityManager>()!!
connectivityManager.activeNetwork?.let { network -> connectivityManager.activeNetwork?.let { network ->
val netCapabilities = connectivityManager.getNetworkCapabilities(network) val netCapabilities = connectivityManager.getNetworkCapabilities(network)
val linkProperties = connectivityManager.getLinkProperties(network) val linkProperties = connectivityManager.getLinkProperties(network)
@ -39,8 +41,28 @@ private val parseNumericAddressCompat: (String) -> InetAddress =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
InetAddresses::parseNumericAddress InetAddresses::parseNumericAddress
} else { } else {
try {
val m = InetAddress::class.java.getMethod("parseNumericAddress", String::class.java) val m = InetAddress::class.java.getMethod("parseNumericAddress", String::class.java)
fun(address: String): InetAddress { fun(address: String): InetAddress {
try {
return m.invoke(null, address) as InetAddress return m.invoke(null, address) as InetAddress
} catch (e: InvocationTargetException) {
throw e.cause ?: e
} }
} }
} catch (_: NoSuchMethodException) {
fun(address: String): InetAddress {
return InetAddress.getByName(address)
}
}
}
internal fun convertIpv6ToCanonicalForm(ipv6: String): String = ipv6
.replace("((?:(?:^|:)0+\\b){2,}):?(?!\\S*\\b\\1:0+\\b)(\\S*)".toRegex(), "::$2")
internal val InetAddress.ip: String
get() = if (this is Inet4Address) {
hostAddress!!
} else {
convertIpv6ToCanonicalForm(hostAddress!!)
}

View file

@ -0,0 +1,93 @@
package org.amnezia.vpn.util.net
import android.net.TrafficStats
import android.os.Build
import android.os.Process
import android.os.SystemClock
import kotlin.math.roundToLong
private const val BYTE = 1L
private const val KiB = BYTE shl 10
private const val MiB = KiB shl 10
private const val GiB = MiB shl 10
private const val TiB = GiB shl 10
class TrafficStats {
private var lastTrafficData = TrafficData.ZERO
private var lastTimestamp = 0L
private val getTrafficDataCompat: () -> TrafficData =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val iface = "tun0"
fun(): TrafficData {
return TrafficData(TrafficStats.getRxBytes(iface), TrafficStats.getTxBytes(iface))
}
} else {
val uid = Process.myUid()
fun(): TrafficData {
return TrafficData(TrafficStats.getUidRxBytes(uid), TrafficStats.getUidTxBytes(uid))
}
}
fun reset() {
lastTrafficData = getTrafficDataCompat()
lastTimestamp = SystemClock.elapsedRealtime()
}
fun isSupported(): Boolean =
lastTrafficData.rx != TrafficStats.UNSUPPORTED.toLong() && lastTrafficData.tx != TrafficStats.UNSUPPORTED.toLong()
fun getSpeed(): TrafficData {
val timestamp = SystemClock.elapsedRealtime()
val elapsedSeconds = (timestamp - lastTimestamp) / 1000.0
val trafficData = getTrafficDataCompat()
val speed = trafficData.diff(lastTrafficData, elapsedSeconds)
lastTrafficData = trafficData
lastTimestamp = timestamp
return speed
}
class TrafficData(val rx: Long, val tx: Long) {
private var _rxString: String? = null
val rxString: String
get() {
if (_rxString == null) _rxString = rx.speedToString()
return _rxString ?: throw AssertionError("Set to null by another thread")
}
private var _txString: String? = null
val txString: String
get() {
if (_txString == null) _txString = tx.speedToString()
return _txString ?: throw AssertionError("Set to null by another thread")
}
fun diff(other: TrafficData, elapsedSeconds: Double): TrafficData {
val rx = ((this.rx - other.rx) / elapsedSeconds).round()
val tx = ((this.tx - other.tx) / elapsedSeconds).round()
return if (rx == 0L && tx == 0L) ZERO else TrafficData(rx, tx)
}
private fun Double.round() = if (isNaN()) 0L else roundToLong()
private fun Long.speedToString() =
when {
this < KiB -> formatSize(this, BYTE, "B/s")
this < MiB -> formatSize(this, KiB, "KiB/s")
this < GiB -> formatSize(this, MiB, "MiB/s")
this < TiB -> formatSize(this, GiB, "GiB/s")
else -> formatSize(this, TiB, "TiB/s")
}
private fun formatSize(bytes: Long, divider: Long, unit: String): String {
val s = (bytes.toDouble() / divider * 100).roundToLong() / 100.0
return "${s.toString().removeSuffix(".0")} $unit"
}
companion object {
val ZERO: TrafficData = TrafficData(0L, 0L)
}
}
}

View file

@ -95,6 +95,7 @@ open class Wireguard : Protocol() {
return WireguardConfig.build { return WireguardConfig.build {
configWireguard(configData, configDataJson) configWireguard(configData, configDataJson)
configSplitTunneling(config) configSplitTunneling(config)
configAppSplitTunneling(config)
} }
} }
@ -157,7 +158,7 @@ open class Wireguard : Protocol() {
if (tunFd == null) { if (tunFd == null) {
throw VpnStartException("Create VPN interface: permission not granted or revoked") throw VpnStartException("Create VPN interface: permission not granted or revoked")
} }
Log.v(TAG, "Wg-go backend ${GoBackend.awgVersion()}") Log.i(TAG, "awg-go backend ${GoBackend.awgVersion()}")
tunnelHandle = GoBackend.awgTurnOn(ifName, tunFd.detachFd(), config.toWgUserspaceString()) tunnelHandle = GoBackend.awgTurnOn(ifName, tunFd.detachFd(), config.toWgUserspaceString())
} }

View file

@ -37,8 +37,8 @@ open class WireguardConfig protected constructor(
open fun appendPeerLine(sb: StringBuilder) = with(sb) { open fun appendPeerLine(sb: StringBuilder) = with(sb) {
appendLine("public_key=$publicKeyHex") appendLine("public_key=$publicKeyHex")
routes.forEach { route -> routes.filter { it.include }.forEach { route ->
appendLine("allowed_ip=$route") appendLine("allowed_ip=${route.inetNetwork}")
} }
appendLine("endpoint=$endpoint") appendLine("endpoint=$endpoint")
if (persistentKeepalive != 0) if (persistentKeepalive != 0)

View file

@ -20,37 +20,35 @@ set(QT_ANDROID_MULTI_ABI_FORWARD_VARS "QT_NO_GLOBAL_APK_TARGET_PART_OF_ALL;CMAKE
# We need to include qtprivate api's # We need to include qtprivate api's
# As QAndroidBinder is not yet implemented with a public api # As QAndroidBinder is not yet implemented with a public api
set(LIBS ${LIBS} Qt6::CorePrivate) set(LIBS ${LIBS} Qt6::CorePrivate -ljnigraphics)
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/platforms/android) link_directories(${CMAKE_CURRENT_SOURCE_DIR}/platforms/android)
set(HEADERS ${HEADERS} set(HEADERS ${HEADERS}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_notificationhandler.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.h
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.h ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.h
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.h ${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.h
${CMAKE_CURRENT_SOURCE_DIR}/core/installedAppsImageProvider.h
) )
set(SOURCES ${SOURCES} set(SOURCES ${SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.cpp ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.cpp
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_notificationhandler.cpp
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.cpp ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.cpp
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.cpp ${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.cpp
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.cpp ${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.cpp
${CMAKE_CURRENT_SOURCE_DIR}/core/installedAppsImageProvider.cpp
) )
foreach(abi IN ITEMS ${QT_ANDROID_ABIS}) foreach(abi IN ITEMS ${QT_ANDROID_ABIS})
set_property(TARGET ${PROJECT} PROPERTY QT_ANDROID_EXTRA_LIBS set_property(TARGET ${PROJECT} PROPERTY QT_ANDROID_EXTRA_LIBS
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/amneziawg/android/${abi}/libwg.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/amneziawg/android/${abi}/libwg-go.so ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/amneziawg/android/${abi}/libwg-go.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/amneziawg/android/${abi}/libwg-quick.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/libck-ovpn-plugin.so ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/libck-ovpn-plugin.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/libovpn3.so ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/libovpn3.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/libovpnutil.so ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/libovpnutil.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/librsapss.so ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/librsapss.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openssl/android/${abi}/libcrypto_3.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openssl/android/${abi}/libssl_3.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/libssh/android/${abi}/libssh.so ${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/libssh/android/${abi}/libssh.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openssl3/android/${abi}/libcrypto_3.so
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openssl3/android/${abi}/libssl_3.so
) )
endforeach() endforeach()

View file

@ -46,7 +46,6 @@ set(SOURCES ${SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/MobileUtils.mm
) )
@ -108,16 +107,19 @@ target_sources(${PROJECT} PRIVATE
${CLIENT_ROOT_DIR}/platforms/ios/Log.swift ${CLIENT_ROOT_DIR}/platforms/ios/Log.swift
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift ${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
${CLIENT_ROOT_DIR}/platforms/ios/ScreenProtection.swift ${CLIENT_ROOT_DIR}/platforms/ios/ScreenProtection.swift
${CLIENT_ROOT_DIR}/platforms/ios/VPNCController.swift
) )
target_sources(${PROJECT} PRIVATE target_sources(${PROJECT} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/AmneziaVPNLaunchScreen.storyboard ${CMAKE_CURRENT_SOURCE_DIR}/ios/app/AmneziaVPNLaunchScreen.storyboard
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Media.xcassets ${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Media.xcassets
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
) )
set_property(TARGET ${PROJECT} APPEND PROPERTY RESOURCE set_property(TARGET ${PROJECT} APPEND PROPERTY RESOURCE
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/AmneziaVPNLaunchScreen.storyboard ${CMAKE_CURRENT_SOURCE_DIR}/ios/app/AmneziaVPNLaunchScreen.storyboard
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Media.xcassets ${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Media.xcassets
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
) )
add_subdirectory(ios/networkextension) add_subdirectory(ios/networkextension)

View file

@ -3,17 +3,15 @@
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonObject> #include <QJsonObject>
#include "core/controllers/serverController.h" AwgConfigurator::AwgConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent)
: WireguardConfigurator(settings, serverController, true, parent)
AwgConfigurator::AwgConfigurator(std::shared_ptr<Settings> settings, QObject *parent)
: WireguardConfigurator(settings, true, parent)
{ {
} }
QString AwgConfigurator::genAwgConfig(const ServerCredentials &credentials, DockerContainer container, QString AwgConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode) ErrorCode &errorCode)
{ {
QString config = WireguardConfigurator::genWireguardConfig(credentials, container, containerConfig, clientId, errorCode); QString config = WireguardConfigurator::createConfig(credentials, container, containerConfig, errorCode);
QJsonObject jsonConfig = QJsonDocument::fromJson(config.toUtf8()).object(); QJsonObject jsonConfig = QJsonDocument::fromJson(config.toUtf8()).object();
QString awgConfig = jsonConfig.value(config_key::config).toString(); QString awgConfig = jsonConfig.value(config_key::config).toString();
@ -41,8 +39,8 @@ QString AwgConfigurator::genAwgConfig(const ServerCredentials &credentials, Dock
jsonConfig[config_key::responsePacketMagicHeader] = configMap.value(config_key::responsePacketMagicHeader); jsonConfig[config_key::responsePacketMagicHeader] = configMap.value(config_key::responsePacketMagicHeader);
jsonConfig[config_key::underloadPacketMagicHeader] = configMap.value(config_key::underloadPacketMagicHeader); jsonConfig[config_key::underloadPacketMagicHeader] = configMap.value(config_key::underloadPacketMagicHeader);
jsonConfig[config_key::transportPacketMagicHeader] = configMap.value(config_key::transportPacketMagicHeader); jsonConfig[config_key::transportPacketMagicHeader] = configMap.value(config_key::transportPacketMagicHeader);
jsonConfig[config_key::mtu] = containerConfig.value(ProtocolProps::protoToString(Proto::Awg)).toObject(). jsonConfig[config_key::mtu] =
value(config_key::mtu).toString(protocols::awg::defaultMtu); containerConfig.value(ProtocolProps::protoToString(Proto::Awg)).toObject().value(config_key::mtu).toString(protocols::awg::defaultMtu);
return QJsonDocument(jsonConfig).toJson(); return QJsonDocument(jsonConfig).toJson();
} }

View file

@ -9,10 +9,10 @@ class AwgConfigurator : public WireguardConfigurator
{ {
Q_OBJECT Q_OBJECT
public: public:
AwgConfigurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr); AwgConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
QString genAwgConfig(const ServerCredentials &credentials, DockerContainer container, QString createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode = nullptr); const QJsonObject &containerConfig, ErrorCode &errorCode);
}; };
#endif // AWGCONFIGURATOR_H #endif // AWGCONFIGURATOR_H

View file

@ -1,34 +1,33 @@
#include "cloak_configurator.h" #include "cloak_configurator.h"
#include <QFile> #include <QFile>
#include <QJsonObject>
#include <QJsonDocument> #include <QJsonDocument>
#include <QJsonObject>
#include "core/controllers/serverController.h"
#include "containers/containers_defs.h" #include "containers/containers_defs.h"
#include "core/controllers/serverController.h"
CloakConfigurator::CloakConfigurator(std::shared_ptr<Settings> settings, QObject *parent): CloakConfigurator::CloakConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent)
ConfiguratorBase(settings, parent) : ConfiguratorBase(settings, serverController, parent)
{ {
} }
QString CloakConfigurator::genCloakConfig(const ServerCredentials &credentials, QString CloakConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode) ErrorCode &errorCode)
{ {
ErrorCode e = ErrorCode::NoError; QString cloakPublicKey =
ServerController serverController(m_settings); m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::cloak::ckPublicKeyPath, errorCode);
QString cloakPublicKey = serverController.getTextFileFromContainer(container, credentials,
amnezia::protocols::cloak::ckPublicKeyPath, &e);
cloakPublicKey.replace("\n", ""); cloakPublicKey.replace("\n", "");
QString cloakBypassUid = serverController.getTextFileFromContainer(container, credentials, if (errorCode != ErrorCode::NoError) {
amnezia::protocols::cloak::ckBypassUidKeyPath, &e); return "";
}
QString cloakBypassUid =
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::cloak::ckBypassUidKeyPath, errorCode);
cloakBypassUid.replace("\n", ""); cloakBypassUid.replace("\n", "");
if (e) { if (errorCode != ErrorCode::NoError) {
if (errorCode) *errorCode = e;
return ""; return "";
} }
@ -45,9 +44,8 @@ QString CloakConfigurator::genCloakConfig(const ServerCredentials &credentials,
config.insert("RemoteHost", credentials.hostName); config.insert("RemoteHost", credentials.hostName);
config.insert("RemotePort", "$CLOAK_SERVER_PORT"); config.insert("RemotePort", "$CLOAK_SERVER_PORT");
QString textCfg = serverController.replaceVars(QJsonDocument(config).toJson(), QString textCfg = m_serverController->replaceVars(QJsonDocument(config).toJson(),
serverController.genVarsForScript(credentials, container, containerConfig)); m_serverController->genVarsForScript(credentials, container, containerConfig));
// qDebug().noquote() << textCfg;
return textCfg; return textCfg;
} }

View file

@ -7,14 +7,14 @@
using namespace amnezia; using namespace amnezia;
class CloakConfigurator : ConfiguratorBase class CloakConfigurator : public ConfiguratorBase
{ {
Q_OBJECT Q_OBJECT
public: public:
CloakConfigurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr); CloakConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
QString genCloakConfig(const ServerCredentials &credentials, DockerContainer container, QString createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr); const QJsonObject &containerConfig, ErrorCode &errorCode);
}; };
#endif // CLOAK_CONFIGURATOR_H #endif // CLOAK_CONFIGURATOR_H

View file

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

View file

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

View file

@ -9,18 +9,18 @@
#include <QUuid> #include <QUuid>
#include "containers/containers_defs.h" #include "containers/containers_defs.h"
#include "core/controllers/serverController.h"
#include "core/scripts_registry.h" #include "core/scripts_registry.h"
#include "core/server_defs.h" #include "core/server_defs.h"
#include "core/controllers/serverController.h"
#include "utilities.h" #include "utilities.h"
Ikev2Configurator::Ikev2Configurator(std::shared_ptr<Settings> settings, QObject *parent) Ikev2Configurator::Ikev2Configurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent)
: ConfiguratorBase(settings, parent) : ConfiguratorBase(settings, serverController, parent)
{ {
} }
Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const ServerCredentials &credentials, Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const ServerCredentials &credentials, DockerContainer container,
DockerContainer container, ErrorCode *errorCode) ErrorCode &errorCode)
{ {
Ikev2Configurator::ConnectionData connData; Ikev2Configurator::ConnectionData connData;
connData.host = credentials.hostName; connData.host = credentials.hostName;
@ -39,18 +39,14 @@ Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const Se
"--extKeyUsage serverAuth,clientAuth -8 \"%1\"") "--extKeyUsage serverAuth,clientAuth -8 \"%1\"")
.arg(connData.clientId); .arg(connData.clientId);
ServerController serverController(m_settings); errorCode = m_serverController->runContainerScript(credentials, container, scriptCreateCert);
ErrorCode e = serverController.runContainerScript(credentials, container, scriptCreateCert);
QString scriptExportCert = QString("pk12util -W \"%1\" -d sql:/etc/ipsec.d -n \"%2\" -o \"%3\"") QString scriptExportCert =
.arg(connData.password) QString("pk12util -W \"%1\" -d sql:/etc/ipsec.d -n \"%2\" -o \"%3\"").arg(connData.password).arg(connData.clientId).arg(certFileName);
.arg(connData.clientId) errorCode = m_serverController->runContainerScript(credentials, container, scriptExportCert);
.arg(certFileName);
e = serverController.runContainerScript(credentials, container, scriptExportCert);
connData.clientCert = serverController.getTextFileFromContainer(container, credentials, certFileName, &e); connData.clientCert = m_serverController->getTextFileFromContainer(container, credentials, certFileName, errorCode);
connData.caCert = connData.caCert = m_serverController->getTextFileFromContainer(container, credentials, "/etc/ipsec.d/ca_cert_base64.p12", errorCode);
serverController.getTextFileFromContainer(container, credentials, "/etc/ipsec.d/ca_cert_base64.p12", &e);
qDebug() << "Ikev2Configurator::ConnectionData client cert size:" << connData.clientCert.size(); qDebug() << "Ikev2Configurator::ConnectionData client cert size:" << connData.clientCert.size();
qDebug() << "Ikev2Configurator::ConnectionData ca cert size:" << connData.caCert.size(); qDebug() << "Ikev2Configurator::ConnectionData ca cert size:" << connData.caCert.size();
@ -58,13 +54,13 @@ Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const Se
return connData; return connData;
} }
QString Ikev2Configurator::genIkev2Config(const ServerCredentials &credentials, DockerContainer container, QString Ikev2Configurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
const QJsonObject &containerConfig, ErrorCode *errorCode) ErrorCode &errorCode)
{ {
Q_UNUSED(containerConfig) Q_UNUSED(containerConfig)
ConnectionData connData = prepareIkev2Config(credentials, container, errorCode); ConnectionData connData = prepareIkev2Config(credentials, container, errorCode);
if (errorCode && *errorCode) { if (errorCode != ErrorCode::NoError) {
return ""; return "";
} }

View file

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

View file

@ -14,9 +14,9 @@
#endif #endif
#include "containers/containers_defs.h" #include "containers/containers_defs.h"
#include "core/controllers/serverController.h"
#include "core/scripts_registry.h" #include "core/scripts_registry.h"
#include "core/server_defs.h" #include "core/server_defs.h"
#include "core/controllers/serverController.h"
#include "settings.h" #include "settings.h"
#include "utilities.h" #include "utilities.h"
@ -24,74 +24,61 @@
#include <openssl/rsa.h> #include <openssl/rsa.h>
#include <openssl/x509.h> #include <openssl/x509.h>
OpenVpnConfigurator::OpenVpnConfigurator(std::shared_ptr<Settings> settings, QObject *parent) OpenVpnConfigurator::OpenVpnConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController,
: ConfiguratorBase(settings, parent) QObject *parent)
: ConfiguratorBase(settings, serverController, parent)
{ {
} }
OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(const ServerCredentials &credentials, OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(const ServerCredentials &credentials,
DockerContainer container, DockerContainer container, ErrorCode &errorCode)
ErrorCode *errorCode)
{ {
OpenVpnConfigurator::ConnectionData connData = OpenVpnConfigurator::createCertRequest(); OpenVpnConfigurator::ConnectionData connData = OpenVpnConfigurator::createCertRequest();
connData.host = credentials.hostName; connData.host = credentials.hostName;
if (connData.privKey.isEmpty() || connData.request.isEmpty()) { if (connData.privKey.isEmpty() || connData.request.isEmpty()) {
if (errorCode) errorCode = ErrorCode::OpenSslFailed;
*errorCode = ErrorCode::OpenSslFailed;
return connData; return connData;
} }
QString reqFileName = QString("%1/%2.req").arg(amnezia::protocols::openvpn::clientsDirPath).arg(connData.clientId); QString reqFileName = QString("%1/%2.req").arg(amnezia::protocols::openvpn::clientsDirPath).arg(connData.clientId);
ServerController serverController(m_settings); errorCode = m_serverController->uploadTextFileToContainer(container, credentials, connData.request, reqFileName);
ErrorCode e = serverController.uploadTextFileToContainer(container, credentials, connData.request, reqFileName); if (errorCode != ErrorCode::NoError) {
if (e) {
if (errorCode)
*errorCode = e;
return connData; return connData;
} }
e = signCert(container, credentials, connData.clientId); errorCode = signCert(container, credentials, connData.clientId);
if (e) { if (errorCode != ErrorCode::NoError) {
if (errorCode)
*errorCode = e;
return connData; return connData;
} }
connData.caCert = serverController.getTextFileFromContainer(container, credentials, connData.caCert =
amnezia::protocols::openvpn::caCertPath, &e); m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::caCertPath, errorCode);
connData.clientCert = serverController.getTextFileFromContainer( connData.clientCert = m_serverController->getTextFileFromContainer(
container, credentials, container, credentials, QString("%1/%2.crt").arg(amnezia::protocols::openvpn::clientCertPath).arg(connData.clientId), errorCode);
QString("%1/%2.crt").arg(amnezia::protocols::openvpn::clientCertPath).arg(connData.clientId), &e);
if (e) { if (errorCode != ErrorCode::NoError) {
if (errorCode)
*errorCode = e;
return connData; return connData;
} }
connData.taKey = serverController.getTextFileFromContainer(container, credentials, connData.taKey = m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::taKeyPath, errorCode);
amnezia::protocols::openvpn::taKeyPath, &e);
if (connData.caCert.isEmpty() || connData.clientCert.isEmpty() || connData.taKey.isEmpty()) { if (connData.caCert.isEmpty() || connData.clientCert.isEmpty() || connData.taKey.isEmpty()) {
if (errorCode) errorCode = ErrorCode::SshScpFailureError;
*errorCode = ErrorCode::SshScpFailureError;
} }
return connData; return connData;
} }
QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container, QString OpenVpnConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode) const QJsonObject &containerConfig, ErrorCode &errorCode)
{ {
ServerController serverController(m_settings); QString config = m_serverController->replaceVars(amnezia::scriptData(ProtocolScriptType::openvpn_template, container),
QString config = m_serverController->genVarsForScript(credentials, container, containerConfig));
serverController.replaceVars(amnezia::scriptData(ProtocolScriptType::openvpn_template, container),
serverController.genVarsForScript(credentials, container, containerConfig));
ConnectionData connData = prepareOpenVpnConfig(credentials, container, errorCode); ConnectionData connData = prepareOpenVpnConfig(credentials, container, errorCode);
if (errorCode && *errorCode) { if (errorCode != ErrorCode::NoError) {
return ""; return "";
} }
@ -113,31 +100,32 @@ QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentia
QJsonObject jConfig; QJsonObject jConfig;
jConfig[config_key::config] = config; jConfig[config_key::config] = config;
clientId = connData.clientId; jConfig[config_key::clientId] = connData.clientId;
return QJsonDocument(jConfig).toJson(); return QJsonDocument(jConfig).toJson();
} }
QString OpenVpnConfigurator::processConfigWithLocalSettings(QString jsonConfig, const int serverIndex) QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString)
{ {
QJsonObject json = QJsonDocument::fromJson(jsonConfig.toUtf8()).object(); processConfigWithDnsSettings(dns, protocolConfigString);
QJsonObject json = QJsonDocument::fromJson(protocolConfigString.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()) { if (!isApiConfig) {
QRegularExpression regex("redirect-gateway.*"); QRegularExpression regex("redirect-gateway.*");
config.replace(regex, ""); config.replace(regex, "");
if (m_settings->routeMode() == Settings::VpnAllSites) { if (!m_settings->isSitesSplitTunnelingEnabled()) {
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");
} } else if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
// no redirect-gateway // no redirect-gateway
} } else 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
@ -164,9 +152,12 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(QString jsonConfig,
return QJsonDocument(json).toJson(); return QJsonDocument(json).toJson();
} }
QString OpenVpnConfigurator::processConfigWithExportSettings(QString jsonConfig) QString OpenVpnConfigurator::processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString)
{ {
QJsonObject json = QJsonDocument::fromJson(jsonConfig.toUtf8()).object(); processConfigWithDnsSettings(dns, protocolConfigString);
QJsonObject json = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
QString config = json[config_key::config].toString(); QString config = json[config_key::config].toString();
QRegularExpression regex("redirect-gateway.*"); QRegularExpression regex("redirect-gateway.*");
@ -198,12 +189,10 @@ ErrorCode OpenVpnConfigurator::signCert(DockerContainer container, const ServerC
.arg(ContainerProps::containerToString(container)) .arg(ContainerProps::containerToString(container))
.arg(clientId); .arg(clientId);
ServerController serverController(m_settings);
QStringList scriptList { script_import, script_sign }; QStringList scriptList { script_import, script_sign };
QString script = serverController.replaceVars(scriptList.join("\n"), QString script = m_serverController->replaceVars(scriptList.join("\n"), m_serverController->genVarsForScript(credentials, container));
serverController.genVarsForScript(credentials, container));
return serverController.runScript(credentials, script); return m_serverController->runScript(credentials, script);
} }
OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest() OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest()
@ -237,8 +226,8 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest()
X509_NAME_add_entry_by_txt(x509_name, "C", MBSTRING_ASC, (unsigned char *)"ORG", -1, -1, 0); X509_NAME_add_entry_by_txt(x509_name, "C", MBSTRING_ASC, (unsigned char *)"ORG", -1, -1, 0);
X509_NAME_add_entry_by_txt(x509_name, "O", MBSTRING_ASC, (unsigned char *)"", -1, -1, 0); X509_NAME_add_entry_by_txt(x509_name, "O", MBSTRING_ASC, (unsigned char *)"", -1, -1, 0);
X509_NAME_add_entry_by_txt(x509_name, "CN", MBSTRING_ASC, X509_NAME_add_entry_by_txt(x509_name, "CN", MBSTRING_ASC, reinterpret_cast<unsigned char const *>(clientIdUtf8.data()),
reinterpret_cast<unsigned char const *>(clientIdUtf8.data()), clientIdUtf8.size(), -1, 0); clientIdUtf8.size(), -1, 0);
// 4. set public key of x509 req // 4. set public key of x509 req
ret = X509_REQ_set_pubkey(x509_req, pKey); ret = X509_REQ_set_pubkey(x509_req, pKey);

View file

@ -7,13 +7,14 @@
#include "configurator_base.h" #include "configurator_base.h"
#include "core/defs.h" #include "core/defs.h"
class OpenVpnConfigurator : ConfiguratorBase class OpenVpnConfigurator : public ConfiguratorBase
{ {
Q_OBJECT Q_OBJECT
public: public:
OpenVpnConfigurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr); OpenVpnConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
struct ConnectionData { struct ConnectionData
{
QString clientId; QString clientId;
QString request; // certificate request QString request; // certificate request
QString privKey; // client private key QString privKey; // client private key
@ -23,21 +24,20 @@ public:
QString host; // host ip QString host; // host ip
}; };
QString genOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container, QString createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode = nullptr); const QJsonObject &containerConfig, ErrorCode &errorCode);
QString processConfigWithLocalSettings(QString jsonConfig, const int serverIndex); QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString processConfigWithExportSettings(QString jsonConfig); QString &protocolConfigString);
QString processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
ErrorCode signCert(DockerContainer container, QString &protocolConfigString);
const ServerCredentials &credentials, QString clientId);
static ConnectionData createCertRequest(); static ConnectionData createCertRequest();
private: private:
ConnectionData prepareOpenVpnConfig(const ServerCredentials &credentials, ConnectionData prepareOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container,
DockerContainer container, ErrorCode *errorCode = nullptr); ErrorCode &errorCode);
ErrorCode signCert(DockerContainer container, const ServerCredentials &credentials, QString clientId);
}; };
#endif // OPENVPN_CONFIGURATOR_H #endif // OPENVPN_CONFIGURATOR_H

View file

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

View file

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

View file

@ -17,8 +17,8 @@
#include "core/server_defs.h" #include "core/server_defs.h"
#include "utilities.h" #include "utilities.h"
SshConfigurator::SshConfigurator(std::shared_ptr<Settings> settings, QObject *parent) SshConfigurator::SshConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent)
: ConfiguratorBase(settings, parent) : ConfiguratorBase(settings, serverController, parent)
{ {
} }
@ -82,8 +82,7 @@ void SshConfigurator::openSshTerminal(const ServerCredentials &credentials)
// p->setNativeArguments(QString("%1@%2") // p->setNativeArguments(QString("%1@%2")
// .arg(credentials.userName).arg(credentials.hostName).arg(credentials.secretData)); // .arg(credentials.userName).arg(credentials.hostName).arg(credentials.secretData));
} else { } else {
p->setNativeArguments( p->setNativeArguments(QString("%1@%2 -pw %3").arg(credentials.userName).arg(credentials.hostName).arg(credentials.secretData));
QString("%1@%2 -pw %3").arg(credentials.userName).arg(credentials.hostName).arg(credentials.secretData));
} }
#else #else
p->setProgram("/bin/bash"); p->setProgram("/bin/bash");

View file

@ -11,7 +11,7 @@ class SshConfigurator : ConfiguratorBase
{ {
Q_OBJECT Q_OBJECT
public: public:
SshConfigurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr); SshConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
QProcessEnvironment prepareEnv(); QProcessEnvironment prepareEnv();
QString convertOpenSShKey(const QString &key); QString convertOpenSShKey(const QString &key);

View file

@ -1,127 +0,0 @@
#include "vpn_configurator.h"
#include "cloak_configurator.h"
#include "ikev2_configurator.h"
#include "openvpn_configurator.h"
#include "shadowsocks_configurator.h"
#include "ssh_configurator.h"
#include "wireguard_configurator.h"
#include "awg_configurator.h"
#include <QFile>
#include <QJsonDocument>
#include <QJsonObject>
#include "containers/containers_defs.h"
#include "settings.h"
#include "utilities.h"
VpnConfigurator::VpnConfigurator(std::shared_ptr<Settings> settings, QObject *parent)
: ConfiguratorBase(settings, parent)
{
openVpnConfigurator = std::shared_ptr<OpenVpnConfigurator>(new OpenVpnConfigurator(settings, this));
shadowSocksConfigurator = std::shared_ptr<ShadowSocksConfigurator>(new ShadowSocksConfigurator(settings, this));
cloakConfigurator = std::shared_ptr<CloakConfigurator>(new CloakConfigurator(settings, this));
wireguardConfigurator = std::shared_ptr<WireguardConfigurator>(new WireguardConfigurator(settings, false, this));
ikev2Configurator = std::shared_ptr<Ikev2Configurator>(new Ikev2Configurator(settings, this));
sshConfigurator = std::shared_ptr<SshConfigurator>(new SshConfigurator(settings, this));
awgConfigurator = std::shared_ptr<AwgConfigurator>(new AwgConfigurator(settings, this));
}
QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, Proto proto, QString &clientId, ErrorCode *errorCode)
{
switch (proto) {
case Proto::OpenVpn:
return openVpnConfigurator->genOpenVpnConfig(credentials, container, containerConfig, clientId, errorCode);
case Proto::ShadowSocks:
return shadowSocksConfigurator->genShadowSocksConfig(credentials, container, containerConfig, errorCode);
case Proto::Cloak: return cloakConfigurator->genCloakConfig(credentials, container, containerConfig, errorCode);
case Proto::WireGuard:
return wireguardConfigurator->genWireguardConfig(credentials, container, containerConfig, clientId, errorCode);
case Proto::Awg:
return awgConfigurator->genAwgConfig(credentials, container, containerConfig, clientId, errorCode);
case Proto::Ikev2: return ikev2Configurator->genIkev2Config(credentials, container, containerConfig, errorCode);
default: return "";
}
}
QPair<QString, QString> VpnConfigurator::getDnsForConfig(int serverIndex)
{
QPair<QString, QString> dns;
bool useAmneziaDns = m_settings->useAmneziaDns();
const QJsonObject &server = m_settings->server(serverIndex);
dns.first = server.value(config_key::dns1).toString();
dns.second = server.value(config_key::dns2).toString();
if (dns.first.isEmpty() || !Utils::checkIPv4Format(dns.first)) {
if (useAmneziaDns && m_settings->containers(serverIndex).contains(DockerContainer::Dns)) {
dns.first = protocols::dns::amneziaDnsIp;
} else
dns.first = m_settings->primaryDns();
}
if (dns.second.isEmpty() || !Utils::checkIPv4Format(dns.second)) {
dns.second = m_settings->secondaryDns();
}
qDebug() << "VpnConfigurator::getDnsForConfig" << dns.first << dns.second;
return dns;
}
QString &VpnConfigurator::processConfigWithDnsSettings(int serverIndex, DockerContainer container, Proto proto,
QString &config)
{
auto dns = getDnsForConfig(serverIndex);
config.replace("$PRIMARY_DNS", dns.first);
config.replace("$SECONDARY_DNS", dns.second);
return config;
}
QString &VpnConfigurator::processConfigWithLocalSettings(int serverIndex, DockerContainer container, Proto proto,
QString &config)
{
processConfigWithDnsSettings(serverIndex, container, proto, config);
if (proto == Proto::OpenVpn) {
config = openVpnConfigurator->processConfigWithLocalSettings(config, serverIndex);
}
return config;
}
QString &VpnConfigurator::processConfigWithExportSettings(int serverIndex, DockerContainer container, Proto proto,
QString &config)
{
processConfigWithDnsSettings(serverIndex, container, proto, config);
if (proto == Proto::OpenVpn) {
config = openVpnConfigurator->processConfigWithExportSettings(config);
}
return config;
}
void VpnConfigurator::updateContainerConfigAfterInstallation(DockerContainer container, QJsonObject &containerConfig,
const QString &stdOut)
{
Proto mainProto = ContainerProps::defaultProtocol(container);
if (container == DockerContainer::TorWebSite) {
QJsonObject protocol = containerConfig.value(ProtocolProps::protoToString(mainProto)).toObject();
qDebug() << "amnezia-tor onions" << stdOut;
QString onion = stdOut;
onion.replace("\n", "");
protocol.insert(config_key::site, onion);
containerConfig.insert(ProtocolProps::protoToString(mainProto), protocol);
}
}

View file

@ -1,52 +0,0 @@
#ifndef VPN_CONFIGURATOR_H
#define VPN_CONFIGURATOR_H
#include <QObject>
#include "configurator_base.h"
#include "core/defs.h"
class OpenVpnConfigurator;
class ShadowSocksConfigurator;
class CloakConfigurator;
class WireguardConfigurator;
class Ikev2Configurator;
class SshConfigurator;
class AwgConfigurator;
// Retrieve connection settings from server
class VpnConfigurator : public ConfiguratorBase
{
Q_OBJECT
public:
explicit VpnConfigurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
QString genVpnProtocolConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, Proto proto, QString &clientId,
ErrorCode *errorCode = nullptr);
QPair<QString, QString> getDnsForConfig(int serverIndex);
QString &processConfigWithDnsSettings(int serverIndex, DockerContainer container, Proto proto, QString &config);
QString &processConfigWithLocalSettings(int serverIndex, DockerContainer container, Proto proto, QString &config);
QString &processConfigWithExportSettings(int serverIndex, DockerContainer container, Proto proto, QString &config);
// workaround for containers which is not support normal configuration
void updateContainerConfigAfterInstallation(DockerContainer container, QJsonObject &containerConfig,
const QString &stdOut);
std::shared_ptr<OpenVpnConfigurator> openVpnConfigurator;
std::shared_ptr<ShadowSocksConfigurator> shadowSocksConfigurator;
std::shared_ptr<CloakConfigurator> cloakConfigurator;
std::shared_ptr<WireguardConfigurator> wireguardConfigurator;
std::shared_ptr<Ikev2Configurator> ikev2Configurator;
std::shared_ptr<SshConfigurator> sshConfigurator;
std::shared_ptr<AwgConfigurator> awgConfigurator;
signals:
void newVpnConfigCreated(const QString &clientId, const QString &clientName, const DockerContainer container,
ServerCredentials credentials);
void clientModelUpdated();
};
#endif // VPN_CONFIGURATOR_H

View file

@ -19,15 +19,13 @@
#include "settings.h" #include "settings.h"
#include "utilities.h" #include "utilities.h"
WireguardConfigurator::WireguardConfigurator(std::shared_ptr<Settings> settings, bool isAwg, QObject *parent) WireguardConfigurator::WireguardConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController,
: ConfiguratorBase(settings, parent), m_isAwg(isAwg) bool isAwg, QObject *parent)
: ConfiguratorBase(settings, serverController, parent), m_isAwg(isAwg)
{ {
m_serverConfigPath = m_serverConfigPath = m_isAwg ? amnezia::protocols::awg::serverConfigPath : amnezia::protocols::wireguard::serverConfigPath;
m_isAwg ? amnezia::protocols::awg::serverConfigPath : amnezia::protocols::wireguard::serverConfigPath; m_serverPublicKeyPath = m_isAwg ? amnezia::protocols::awg::serverPublicKeyPath : amnezia::protocols::wireguard::serverPublicKeyPath;
m_serverPublicKeyPath = m_serverPskKeyPath = m_isAwg ? amnezia::protocols::awg::serverPskKeyPath : amnezia::protocols::wireguard::serverPskKeyPath;
m_isAwg ? amnezia::protocols::awg::serverPublicKeyPath : amnezia::protocols::wireguard::serverPublicKeyPath;
m_serverPskKeyPath =
m_isAwg ? amnezia::protocols::awg::serverPskKeyPath : amnezia::protocols::wireguard::serverPskKeyPath;
m_configTemplate = m_isAwg ? ProtocolScriptType::awg_template : ProtocolScriptType::wireguard_template; m_configTemplate = m_isAwg ? ProtocolScriptType::awg_template : ProtocolScriptType::wireguard_template;
m_protocolName = m_isAwg ? config_key::awg : config_key::wireguard; m_protocolName = m_isAwg ? config_key::awg : config_key::wireguard;
@ -71,16 +69,14 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::genClientKeys()
WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardConfig(const ServerCredentials &credentials, WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardConfig(const ServerCredentials &credentials,
DockerContainer container, DockerContainer container,
const QJsonObject &containerConfig, const QJsonObject &containerConfig, ErrorCode &errorCode)
ErrorCode *errorCode)
{ {
WireguardConfigurator::ConnectionData connData = WireguardConfigurator::genClientKeys(); WireguardConfigurator::ConnectionData connData = WireguardConfigurator::genClientKeys();
connData.host = credentials.hostName; connData.host = credentials.hostName;
connData.port = containerConfig.value(m_protocolName).toObject().value(config_key::port).toString(m_defaultPort); connData.port = containerConfig.value(m_protocolName).toObject().value(config_key::port).toString(m_defaultPort);
if (connData.clientPrivKey.isEmpty() || connData.clientPubKey.isEmpty()) { if (connData.clientPrivKey.isEmpty() || connData.clientPubKey.isEmpty()) {
if (errorCode) errorCode = ErrorCode::InternalError;
*errorCode = ErrorCode::InternalError;
return connData; return connData;
} }
@ -111,9 +107,8 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
return ErrorCode::NoError; return ErrorCode::NoError;
}; };
e = serverController.runContainerScript(credentials, container, script, cbReadStdOut); errorCode = m_serverController->runContainerScript(credentials, container, script, cbReadStdOut);
if (errorCode && e) { if (errorCode != ErrorCode::NoError) {
*errorCode = e;
return connData; return connData;
} }
@ -127,21 +122,18 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
} else { } else {
int next = ips.last().split(".").last().toInt() + 1; int next = ips.last().split(".").last().toInt() + 1;
if (next > 254) { if (next > 254) {
if (errorCode) errorCode = ErrorCode::AddressPoolError;
*errorCode = ErrorCode::AddressPoolError;
return connData; return connData;
} }
nextIpNumber = QString::number(next); nextIpNumber = QString::number(next);
} }
} }
QString subnetIp = QString subnetIp = containerConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress);
containerConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress);
{ {
QStringList l = subnetIp.split(".", Qt::SkipEmptyParts); QStringList l = subnetIp.split(".", Qt::SkipEmptyParts);
if (l.isEmpty()) { if (l.isEmpty()) {
if (errorCode) errorCode = ErrorCode::AddressPoolError;
*errorCode = ErrorCode::AddressPoolError;
return connData; return connData;
} }
l.removeLast(); l.removeLast();
@ -151,20 +143,16 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
} }
// Get keys // Get keys
connData.serverPubKey = serverController.getTextFileFromContainer(container, credentials, m_serverPublicKeyPath, &e); connData.serverPubKey = m_serverController->getTextFileFromContainer(container, credentials, m_serverPublicKeyPath, errorCode);
connData.serverPubKey.replace("\n", ""); connData.serverPubKey.replace("\n", "");
if (e) { if (errorCode != ErrorCode::NoError) {
if (errorCode)
*errorCode = e;
return connData; return connData;
} }
connData.pskKey = serverController.getTextFileFromContainer(container, credentials, m_serverPskKeyPath, &e); connData.pskKey = m_serverController->getTextFileFromContainer(container, credentials, m_serverPskKeyPath, errorCode);
connData.pskKey.replace("\n", ""); connData.pskKey.replace("\n", "");
if (e) { if (errorCode != ErrorCode::NoError) {
if (errorCode)
*errorCode = e;
return connData; return connData;
} }
@ -175,35 +163,31 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
"AllowedIPs = %3/32\n\n") "AllowedIPs = %3/32\n\n")
.arg(connData.clientPubKey, connData.pskKey, connData.clientIP); .arg(connData.clientPubKey, connData.pskKey, connData.clientIP);
e = serverController.uploadTextFileToContainer(container, credentials, configPart, m_serverConfigPath, errorCode = m_serverController->uploadTextFileToContainer(container, credentials, configPart, m_serverConfigPath,
libssh::ScpOverwriteMode::ScpAppendToExisting); libssh::ScpOverwriteMode::ScpAppendToExisting);
if (e) { if (errorCode != ErrorCode::NoError) {
if (errorCode)
*errorCode = e;
return connData; return connData;
} }
QString script = QString("sudo docker exec -i $CONTAINER_NAME bash -c '%4 syncconf %2 <(%3 strip %1)'") QString script = QString("sudo docker exec -i $CONTAINER_NAME bash -c '%4 syncconf %2 <(%3 strip %1)'")
.arg(m_serverConfigPath, m_interfaceName, m_wgQuickBinaryName, m_wgBinaryName); .arg(m_serverConfigPath, m_interfaceName, m_wgQuickBinaryName, m_wgBinaryName);
e = serverController.runScript( errorCode = m_serverController->runScript(
credentials, serverController.replaceVars(script, serverController.genVarsForScript(credentials, container))); credentials, m_serverController->replaceVars(script, m_serverController->genVarsForScript(credentials, container)));
return connData; return connData;
} }
QString WireguardConfigurator::genWireguardConfig(const ServerCredentials &credentials, DockerContainer container, QString WireguardConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &containerConfig, QString &clientId, const QJsonObject &containerConfig, ErrorCode &errorCode)
ErrorCode *errorCode)
{ {
ServerController serverController(m_settings);
QString scriptData = amnezia::scriptData(m_configTemplate, container); QString scriptData = amnezia::scriptData(m_configTemplate, container);
QString config = serverController.replaceVars( QString config =
scriptData, serverController.genVarsForScript(credentials, container, containerConfig)); m_serverController->replaceVars(scriptData, m_serverController->genVarsForScript(credentials, container, containerConfig));
ConnectionData connData = prepareWireguardConfig(credentials, container, containerConfig, errorCode); ConnectionData connData = prepareWireguardConfig(credentials, container, containerConfig, errorCode);
if (errorCode && *errorCode) { if (errorCode != ErrorCode::NoError) {
return ""; return "";
} }
@ -223,30 +207,25 @@ QString WireguardConfigurator::genWireguardConfig(const ServerCredentials &crede
jConfig[config_key::client_pub_key] = connData.clientPubKey; jConfig[config_key::client_pub_key] = connData.clientPubKey;
jConfig[config_key::psk_key] = connData.pskKey; jConfig[config_key::psk_key] = connData.pskKey;
jConfig[config_key::server_pub_key] = connData.serverPubKey; jConfig[config_key::server_pub_key] = connData.serverPubKey;
jConfig[config_key::mtu] = wireguarConfig.value(config_key::mtu).toString(protocols::wireguard::defaultMtu); jConfig[config_key::mtu] = wireguarConfig.value(config_key::mtu).toString(protocols::wireguard::defaultMtu);
clientId = connData.clientPubKey; jConfig[config_key::clientId] = connData.clientPubKey;
return QJsonDocument(jConfig).toJson(); return QJsonDocument(jConfig).toJson();
} }
QString WireguardConfigurator::processConfigWithLocalSettings(QString config) QString WireguardConfigurator::processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString)
{ {
// TODO replace DNS if it already set processConfigWithDnsSettings(dns, protocolConfigString);
config.replace("$PRIMARY_DNS", m_settings->primaryDns());
config.replace("$SECONDARY_DNS", m_settings->secondaryDns());
QJsonObject jConfig; return protocolConfigString;
jConfig[config_key::config] = config;
return QJsonDocument(jConfig).toJson();
} }
QString WireguardConfigurator::processConfigWithExportSettings(QString config) QString WireguardConfigurator::processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString)
{ {
config.replace("$PRIMARY_DNS", m_settings->primaryDns()); processConfigWithDnsSettings(dns, protocolConfigString);
config.replace("$SECONDARY_DNS", m_settings->secondaryDns());
return config; return protocolConfigString;
} }

View file

@ -12,7 +12,8 @@ class WireguardConfigurator : public ConfiguratorBase
{ {
Q_OBJECT Q_OBJECT
public: public:
WireguardConfigurator(std::shared_ptr<Settings> settings, bool isAwg, QObject *parent = nullptr); WireguardConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, bool isAwg,
QObject *parent = nullptr);
struct ConnectionData struct ConnectionData
{ {
@ -25,17 +26,17 @@ public:
QString port; QString port;
}; };
QString genWireguardConfig(const ServerCredentials &credentials, DockerContainer container, QString createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode = nullptr); ErrorCode &errorCode);
QString processConfigWithLocalSettings(QString config); QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig, QString &protocolConfigString);
QString processConfigWithExportSettings(QString config); QString processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig, QString &protocolConfigString);
static ConnectionData genClientKeys(); 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);
bool m_isAwg; bool m_isAwg;
QString m_serverConfigPath; QString m_serverConfigPath;

View file

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

View file

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

View file

@ -1,5 +1,8 @@
#include "containers_defs.h" #include "containers_defs.h"
#include "QJsonObject"
#include "QJsonDocument"
QDebug operator<<(QDebug debug, const amnezia::DockerContainer &c) QDebug operator<<(QDebug debug, const amnezia::DockerContainer &c)
{ {
QDebugStateSaver saver(debug); QDebugStateSaver saver(debug);
@ -58,6 +61,8 @@ QVector<amnezia::Proto> ContainerProps::protocolsForContainer(amnezia::DockerCon
case DockerContainer::Ipsec: return { Proto::Ikev2 /*, Protocol::L2tp */ }; case DockerContainer::Ipsec: return { Proto::Ikev2 /*, Protocol::L2tp */ };
case DockerContainer::Xray: return { Proto::Xray };
case DockerContainer::Dns: return { Proto::Dns }; case DockerContainer::Dns: return { Proto::Dns };
case DockerContainer::Sftp: return { Proto::Sftp }; case DockerContainer::Sftp: return { Proto::Sftp };
@ -85,6 +90,7 @@ QMap<DockerContainer, QString> ContainerProps::containerHumanNames()
{ DockerContainer::Cloak, "OpenVPN over Cloak" }, { DockerContainer::Cloak, "OpenVPN over Cloak" },
{ DockerContainer::WireGuard, "WireGuard" }, { DockerContainer::WireGuard, "WireGuard" },
{ DockerContainer::Awg, "AmneziaWG" }, { DockerContainer::Awg, "AmneziaWG" },
{ DockerContainer::Xray, "XRay" },
{ DockerContainer::Ipsec, QObject::tr("IPsec") }, { DockerContainer::Ipsec, QObject::tr("IPsec") },
{ DockerContainer::TorWebSite, QObject::tr("Website in Tor network") }, { DockerContainer::TorWebSite, QObject::tr("Website in Tor network") },
@ -111,6 +117,9 @@ QMap<DockerContainer, QString> ContainerProps::containerDescriptions()
QObject::tr("AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, " QObject::tr("AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, "
"but very resistant to blockages. " "but very resistant to blockages. "
"Recommended for regions with high levels of censorship.") }, "Recommended for regions with high levels of censorship.") },
{ DockerContainer::Xray,
QObject::tr("XRay with REALITY - Suitable for countries with the highest level of internet censorship. "
"Traffic masking as web traffic at the TLS level, and protection against detection by active probing methods.") },
{ DockerContainer::Ipsec, { DockerContainer::Ipsec,
QObject::tr("IKEv2 - Modern stable protocol, a bit faster than others, restores connection after " QObject::tr("IKEv2 - Modern stable protocol, a bit faster than others, restores connection after "
"signal loss. It has native support on the latest versions of Android and iOS.") }, "signal loss. It has native support on the latest versions of Android and iOS.") },
@ -199,6 +208,17 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
"* Minimum number of settings\n" "* Minimum number of settings\n"
"* Not recognised by DPI analysis systems, resistant to blocking\n" "* Not recognised by DPI analysis systems, resistant to blocking\n"
"* Works over UDP network protocol.") }, "* Works over UDP network protocol.") },
{ DockerContainer::Xray,
QObject::tr("The REALITY protocol, a pioneering development by the creators of XRay, "
"is specifically designed to counteract the highest levels of internet censorship through its novel approach to evasion.\n"
"It uniquely identifies censors during the TLS handshake phase, seamlessly operating as a proxy for legitimate clients while diverting censors to genuine websites like google.com, "
"thus presenting an authentic TLS certificate and data. \n"
"This advanced capability differentiates REALITY from similar technologies by its ability to disguise web traffic as coming from random, "
"legitimate sites without the need for specific configurations. \n"
"Unlike older protocols such as VMess, VLESS, and the XTLS-Vision transport, "
"REALITY's innovative \"friend or foe\" recognition at the TLS handshake enhances security and circumvents detection by sophisticated DPI systems employing active probing techniques. "
"This makes REALITY a robust solution for maintaining internet freedom in environments with stringent censorship.")
},
{ DockerContainer::Ipsec, { DockerContainer::Ipsec,
QObject::tr("IKEv2, paired with the IPSec encryption layer, stands as a modern and stable VPN protocol.\n" QObject::tr("IKEv2, paired with the IPSec encryption layer, stands as a modern and stable VPN protocol.\n"
"One of its distinguishing features is its ability to swiftly switch between networks and devices, " "One of its distinguishing features is its ability to swiftly switch between networks and devices, "
@ -213,7 +233,11 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
{ DockerContainer::TorWebSite, QObject::tr("Website in Tor network") }, { DockerContainer::TorWebSite, QObject::tr("Website in Tor network") },
{ DockerContainer::Dns, QObject::tr("DNS Service") }, { DockerContainer::Dns, QObject::tr("DNS Service") },
{ DockerContainer::Sftp, QObject::tr("Sftp file sharing service - is secure FTP service") } { DockerContainer::Sftp,
QObject::tr("After installation, Amnezia will create a\n\n file storage on your server. "
"You will be able to access it using\n FileZilla or other SFTP clients, "
"as well as mount the disk on your device to access\n it directly from your device.\n\n"
"For more detailed information, you can\n find it in the support section under \"Create SFTP file storage.\" ") }
}; };
} }
@ -231,6 +255,7 @@ Proto ContainerProps::defaultProtocol(DockerContainer c)
case DockerContainer::ShadowSocks: return Proto::ShadowSocks; case DockerContainer::ShadowSocks: return Proto::ShadowSocks;
case DockerContainer::WireGuard: return Proto::WireGuard; case DockerContainer::WireGuard: return Proto::WireGuard;
case DockerContainer::Awg: return Proto::Awg; case DockerContainer::Awg: return Proto::Awg;
case DockerContainer::Xray: return Proto::Xray;
case DockerContainer::Ipsec: return Proto::Ikev2; case DockerContainer::Ipsec: return Proto::Ikev2;
case DockerContainer::TorWebSite: return Proto::TorWebSite; case DockerContainer::TorWebSite: return Proto::TorWebSite;
@ -274,7 +299,6 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
#elif defined(Q_OS_LINUX) #elif defined(Q_OS_LINUX)
switch (c) { switch (c) {
case DockerContainer::WireGuard: return true;
case DockerContainer::Ipsec: return false; case DockerContainer::Ipsec: return false;
default: return true; default: return true;
} }
@ -297,7 +321,7 @@ bool ContainerProps::isEasySetupContainer(DockerContainer container)
switch (container) { switch (container) {
case DockerContainer::WireGuard: return true; case DockerContainer::WireGuard: return true;
case DockerContainer::Awg: return true; case DockerContainer::Awg: return true;
case DockerContainer::Cloak: return true; // case DockerContainer::Cloak: return true;
default: return false; default: return false;
} }
} }
@ -306,8 +330,8 @@ QString ContainerProps::easySetupHeader(DockerContainer container)
{ {
switch (container) { switch (container) {
case DockerContainer::WireGuard: return tr("Low"); case DockerContainer::WireGuard: return tr("Low");
case DockerContainer::Awg: return tr("Medium or High"); case DockerContainer::Awg: return tr("High");
case DockerContainer::Cloak: return tr("Extreme"); // case DockerContainer::Cloak: return tr("Extreme");
default: return ""; default: return "";
} }
} }
@ -317,8 +341,8 @@ QString ContainerProps::easySetupDescription(DockerContainer container)
switch (container) { switch (container) {
case DockerContainer::WireGuard: return tr("I just want to increase the level of my privacy."); case DockerContainer::WireGuard: return tr("I just want to increase the level of my privacy.");
case DockerContainer::Awg: return tr("I want to bypass censorship. This option recommended in most cases."); case DockerContainer::Awg: return tr("I want to bypass censorship. This option recommended in most cases.");
case DockerContainer::Cloak: // case DockerContainer::Cloak:
return tr("Most VPN protocols are blocked. Recommended if other options are not working."); // return tr("Most VPN protocols are blocked. Recommended if other options are not working.");
default: return ""; default: return "";
} }
} }
@ -328,7 +352,7 @@ int ContainerProps::easySetupOrder(DockerContainer container)
switch (container) { switch (container) {
case DockerContainer::WireGuard: return 3; case DockerContainer::WireGuard: return 3;
case DockerContainer::Awg: return 2; case DockerContainer::Awg: return 2;
case DockerContainer::Cloak: return 1; // case DockerContainer::Cloak: return 1;
default: return 0; default: return 0;
} }
} }
@ -342,3 +366,13 @@ bool ContainerProps::isShareable(DockerContainer container)
default: return true; default: return true;
} }
} }
QJsonObject ContainerProps::getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig)
{
QString protocolConfigString = containerConfig.value(ProtocolProps::protoToString(protocol))
.toObject()
.value(config_key::last_config)
.toString();
return QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
}

View file

@ -22,6 +22,7 @@ namespace amnezia
Cloak, Cloak,
ShadowSocks, ShadowSocks,
Ipsec, Ipsec,
Xray,
// non-vpn // non-vpn
TorWebSite, TorWebSite,
@ -67,6 +68,8 @@ namespace amnezia
static int easySetupOrder(amnezia::DockerContainer container); static int easySetupOrder(amnezia::DockerContainer container);
static bool isShareable(amnezia::DockerContainer container); static bool isShareable(amnezia::DockerContainer container);
static QJsonObject getProtocolConfigFromContainer(const amnezia::Proto protocol, const QJsonObject &containerConfig);
}; };
static void declareQmlContainerEnum() static void declareQmlContainerEnum()

View file

@ -0,0 +1,159 @@
#include "apiController.h"
#include <QEventLoop>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QtConcurrent>
#include "amnezia_application.h"
#include "configurators/wireguard_configurator.h"
#include "version.h"
#include "core/errorstrings.h"
namespace
{
namespace configKey
{
constexpr char cloak[] = "cloak";
constexpr char awg[] = "awg";
constexpr char apiEdnpoint[] = "api_endpoint";
constexpr char accessToken[] = "api_key";
constexpr char certificate[] = "certificate";
constexpr char publicKey[] = "public_key";
constexpr char protocol[] = "protocol";
constexpr char uuid[] = "installation_uuid";
constexpr char osVersion[] = "os_version";
constexpr char appVersion[] = "app_version";
}
}
ApiController::ApiController(QObject *parent) : QObject(parent)
{
}
void ApiController::processApiConfig(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData, QString &config)
{
if (protocol == configKey::cloak) {
config.replace("<key>", "<key>\n");
config.replace("$OPENVPN_PRIV_KEY", apiPayloadData.certRequest.privKey);
return;
} else if (protocol == configKey::awg) {
config.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", apiPayloadData.wireGuardClientPrivKey);
}
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;
}
obj[configKey::osVersion] = QSysInfo::productType();
obj[configKey::appVersion] = QString(APP_VERSION);
return obj;
}
void ApiController::updateServerConfigFromApi(const QString &installationUuid, const int serverIndex, QJsonObject serverConfig)
{
#ifdef Q_OS_IOS
IosController::Instance()->requestInetAccess();
QThread::msleep(10);
#endif
auto containerConfig = serverConfig.value(config_key::containers).toArray();
if (serverConfig.value(config_key::configVersion).toInt()) {
QNetworkRequest request;
request.setTransferTimeout(7000);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
request.setRawHeader("Authorization", "Api-Key " + serverConfig.value(configKey::accessToken).toString().toUtf8());
QString endpoint = serverConfig.value(configKey::apiEdnpoint).toString();
request.setUrl(endpoint);
QString protocol = serverConfig.value(configKey::protocol).toString();
ApiPayloadData apiPayloadData = generateApiPayloadData(protocol);
QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData);
apiPayload[configKey::uuid] = installationUuid;
QByteArray requestBody = QJsonDocument(apiPayload).toJson();
QNetworkReply *reply = amnApp->manager()->post(request, requestBody); // ??
QObject::connect(reply, &QNetworkReply::finished, [this, reply, protocol, apiPayloadData, serverIndex, serverConfig]() mutable {
if (reply->error() == QNetworkReply::NoError) {
QString contents = QString::fromUtf8(reply->readAll());
QString data = QJsonDocument::fromJson(contents.toUtf8()).object().value(config_key::config).toString();
data.replace("vpn://", "");
QByteArray ba = QByteArray::fromBase64(data.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
if (ba.isEmpty()) {
emit errorOccurred(errorString(ErrorCode::ApiConfigEmptyError));
return;
}
QByteArray ba_uncompressed = qUncompress(ba);
if (!ba_uncompressed.isEmpty()) {
ba = ba_uncompressed;
}
QString configStr = ba;
processApiConfig(protocol, apiPayloadData, configStr);
QJsonObject apiConfig = QJsonDocument::fromJson(configStr.toUtf8()).object();
serverConfig[config_key::dns1] = apiConfig.value(config_key::dns1);
serverConfig[config_key::dns2] = apiConfig.value(config_key::dns2);
serverConfig[config_key::containers] = apiConfig.value(config_key::containers);
serverConfig[config_key::hostName] = apiConfig.value(config_key::hostName);
auto defaultContainer = apiConfig.value(config_key::defaultContainer).toString();
serverConfig[config_key::defaultContainer] = defaultContainer;
emit configUpdated(true, serverConfig, serverIndex);
} else {
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
emit errorOccurred(errorString(ErrorCode::ApiConfigTimeoutError));
} else {
QString err = reply->errorString();
qDebug() << QString::fromUtf8(reply->readAll());
qDebug() << reply->error();
qDebug() << err;
qDebug() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
emit errorOccurred(errorString(ErrorCode::ApiConfigDownloadError));
}
}
reply->deleteLater();
});
QObject::connect(reply, &QNetworkReply::errorOccurred,
[this, reply](QNetworkReply::NetworkError error) { qDebug() << reply->errorString() << error; });
connect(reply, &QNetworkReply::sslErrors, [this, reply](const QList<QSslError> &errors) {
qDebug().noquote() << errors;
emit errorOccurred(errorString(ErrorCode::ApiConfigSslError));
});
}
}

View file

@ -4,29 +4,28 @@
#include <QObject> #include <QObject>
#include "configurators/openvpn_configurator.h" #include "configurators/openvpn_configurator.h"
#include "ui/models/containers_model.h"
#include "ui/models/servers_model.h" #ifdef Q_OS_IOS
#include "platforms/ios/ios_controller.h"
#endif
class ApiController : public QObject class ApiController : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit ApiController(const QSharedPointer<ServersModel> &serversModel, explicit ApiController(QObject *parent = nullptr);
const QSharedPointer<ContainersModel> &containersModel, QObject *parent = nullptr);
public slots: public slots:
void updateServerConfigFromApi(); void updateServerConfigFromApi(const QString &installationUuid, const int serverIndex, QJsonObject serverConfig);
void clearApiConfig();
signals: signals:
void updateStarted();
void updateFinished(bool isConfigUpdateStarted);
void errorOccurred(const QString &errorMessage); void errorOccurred(const QString &errorMessage);
void configUpdated(const bool updateConfig, const QJsonObject &config, const int serverIndex);
private: private:
struct ApiPayloadData { struct ApiPayloadData
{
OpenVpnConfigurator::ConnectionData certRequest; OpenVpnConfigurator::ConnectionData certRequest;
QString wireGuardClientPrivKey; QString wireGuardClientPrivKey;
@ -36,11 +35,6 @@ private:
ApiPayloadData generateApiPayloadData(const QString &protocol); ApiPayloadData generateApiPayloadData(const QString &protocol);
QJsonObject fillApiPayload(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData); QJsonObject fillApiPayload(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData);
void processApiConfig(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData, QString &config); void processApiConfig(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData, QString &config);
QSharedPointer<ServersModel> m_serversModel;
QSharedPointer<ContainersModel> m_containersModel;
bool m_isConfigUpdateStarted = false;
}; };
#endif // APICONTROLLER_H #endif // APICONTROLLER_H

View file

@ -23,13 +23,13 @@
#include <thread> #include <thread>
#include "containers/containers_defs.h" #include "containers/containers_defs.h"
#include "logger.h" #include "core/networkUtilities.h"
#include "core/scripts_registry.h" #include "core/scripts_registry.h"
#include "core/server_defs.h" #include "core/server_defs.h"
#include "logger.h"
#include "settings.h" #include "settings.h"
#include "utilities.h" #include "utilities.h"
#include "vpnConfigurationController.h"
#include <configurators/vpn_configurator.h>
namespace namespace
{ {
@ -95,8 +95,7 @@ ErrorCode ServerController::runScript(const ServerCredentials &credentials, QStr
return ErrorCode::NoError; return ErrorCode::NoError;
} }
ErrorCode ErrorCode ServerController::runContainerScript(const ServerCredentials &credentials, DockerContainer container, QString script,
ServerController::runContainerScript(const ServerCredentials &credentials, DockerContainer container, QString script,
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdOut, const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdOut,
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdErr) const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdErr)
{ {
@ -116,9 +115,8 @@ ServerController::runContainerScript(const ServerCredentials &credentials, Docke
return e; return e;
} }
ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container, const ServerCredentials &credentials, ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container, const ServerCredentials &credentials, const QString &file,
const QString &file, const QString &path, const QString &path, libssh::ScpOverwriteMode overwriteMode)
libssh::ScpOverwriteMode overwriteMode)
{ {
ErrorCode e = ErrorCode::NoError; ErrorCode e = ErrorCode::NoError;
QString tmpFileName = QString("/tmp/%1.tmp").arg(Utils::getRandomString(16)); QString tmpFileName = QString("/tmp/%1.tmp").arg(Utils::getRandomString(16));
@ -156,10 +154,8 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
if (e) if (e)
return e; return e;
e = runScript( e = runScript(credentials,
credentials, replaceVars(QString("sudo docker exec -i $CONTAINER_NAME sh -c \"cat %1 >> %2\"").arg(tmpFileName).arg(path),
replaceVars(
QString("sudo docker exec -i $CONTAINER_NAME sh -c \"cat %1 >> %2\"").arg(tmpFileName).arg(path),
genVarsForScript(credentials, container)), genVarsForScript(credentials, container)),
cbReadStd, cbReadStd); cbReadStd, cbReadStd);
@ -172,21 +168,17 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
return ErrorCode::ServerContainerMissingError; return ErrorCode::ServerContainerMissingError;
} }
runScript(credentials, runScript(credentials, replaceVars(QString("sudo shred -u %1").arg(tmpFileName), genVarsForScript(credentials, container)));
replaceVars(QString("sudo shred -u %1").arg(tmpFileName), genVarsForScript(credentials, container)));
return e; return e;
} }
QByteArray ServerController::getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials, QByteArray ServerController::getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials, const QString &path,
const QString &path, ErrorCode *errorCode) ErrorCode &errorCode)
{ {
if (errorCode) errorCode = ErrorCode::NoError;
*errorCode = ErrorCode::NoError;
QString script = QString("sudo docker exec -i %1 sh -c \"xxd -p \'%2\'\"") QString script = QString("sudo docker exec -i %1 sh -c \"xxd -p \'%2\'\"").arg(ContainerProps::containerToString(container)).arg(path);
.arg(ContainerProps::containerToString(container))
.arg(path);
QString stdOut; QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) { auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
@ -194,12 +186,12 @@ QByteArray ServerController::getTextFileFromContainer(DockerContainer container,
return ErrorCode::NoError; return ErrorCode::NoError;
}; };
*errorCode = runScript(credentials, script, cbReadStdOut); errorCode = runScript(credentials, script, cbReadStdOut);
return QByteArray::fromHex(stdOut.toUtf8()); return QByteArray::fromHex(stdOut.toUtf8());
} }
ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath,
const QString &remotePath, libssh::ScpOverwriteMode overwriteMode) libssh::ScpOverwriteMode overwriteMode)
{ {
auto error = m_sshClient.connectToHost(credentials); auto error = m_sshClient.connectToHost(credentials);
if (error != ErrorCode::NoError) { if (error != ErrorCode::NoError) {
@ -245,12 +237,10 @@ ErrorCode ServerController::removeAllContainers(const ServerCredentials &credent
ErrorCode ServerController::removeContainer(const ServerCredentials &credentials, DockerContainer container) ErrorCode ServerController::removeContainer(const ServerCredentials &credentials, DockerContainer container)
{ {
return runScript(credentials, return runScript(credentials,
replaceVars(amnezia::scriptData(SharedScriptType::remove_container), replaceVars(amnezia::scriptData(SharedScriptType::remove_container), genVarsForScript(credentials, container)));
genVarsForScript(credentials, container)));
} }
ErrorCode ServerController::setupContainer(const ServerCredentials &credentials, DockerContainer container, ErrorCode ServerController::setupContainer(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config, bool isUpdate)
QJsonObject &config, bool isUpdate)
{ {
qDebug().noquote() << "ServerController::setupContainer" << ContainerProps::containerToString(container); qDebug().noquote() << "ServerController::setupContainer" << ContainerProps::containerToString(container);
ErrorCode e = ErrorCode::NoError; ErrorCode e = ErrorCode::NoError;
@ -310,12 +300,11 @@ ErrorCode ServerController::setupContainer(const ServerCredentials &credentials,
return startupContainerWorker(credentials, container, config); return startupContainerWorker(credentials, container, config);
} }
ErrorCode ServerController::updateContainer(const ServerCredentials &credentials, DockerContainer container, ErrorCode ServerController::updateContainer(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &oldConfig,
const QJsonObject &oldConfig, QJsonObject &newConfig) QJsonObject &newConfig)
{ {
bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig); bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig);
qDebug() << "ServerController::updateContainer for container" << container << "reinstall required is" qDebug() << "ServerController::updateContainer for container" << container << "reinstall required is" << reinstallRequired;
<< reinstallRequired;
if (reinstallRequired) { if (reinstallRequired) {
return setupContainer(credentials, container, newConfig, true); return setupContainer(credentials, container, newConfig, true);
@ -328,8 +317,7 @@ ErrorCode ServerController::updateContainer(const ServerCredentials &credentials
} }
} }
bool ServerController::isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, bool ServerController::isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig)
const QJsonObject &newConfig)
{ {
Proto mainProto = ContainerProps::defaultProtocol(container); Proto mainProto = ContainerProps::defaultProtocol(container);
@ -408,8 +396,7 @@ ErrorCode ServerController::installDockerWorker(const ServerCredentials &credent
}; };
ErrorCode error = ErrorCode error =
runScript(credentials, runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::install_docker), genVarsForScript(credentials)),
replaceVars(amnezia::scriptData(SharedScriptType::install_docker), genVarsForScript(credentials)),
cbReadStdOut, cbReadStdErr); cbReadStdOut, cbReadStdErr);
qDebug().noquote() << "ServerController::installDockerWorker" << stdOut; qDebug().noquote() << "ServerController::installDockerWorker" << stdOut;
@ -421,17 +408,13 @@ ErrorCode ServerController::installDockerWorker(const ServerCredentials &credent
return error; return error;
} }
ErrorCode ServerController::prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, ErrorCode ServerController::prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
const QJsonObject &config)
{ {
// create folder on host // create folder on host
return runScript( return runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::prepare_host), genVarsForScript(credentials, container)));
credentials,
replaceVars(amnezia::scriptData(SharedScriptType::prepare_host), genVarsForScript(credentials, container)));
} }
ErrorCode ServerController::buildContainerWorker(const ServerCredentials &credentials, DockerContainer container, ErrorCode ServerController::buildContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
const QJsonObject &config)
{ {
ErrorCode e = uploadFileToHost(credentials, amnezia::scriptData(ProtocolScriptType::dockerfile, container).toUtf8(), ErrorCode e = uploadFileToHost(credentials, amnezia::scriptData(ProtocolScriptType::dockerfile, container).toUtf8(),
amnezia::server::getDockerfileFolder(container) + "/Dockerfile"); amnezia::server::getDockerfileFolder(container) + "/Dockerfile");
@ -444,13 +427,9 @@ ErrorCode ServerController::buildContainerWorker(const ServerCredentials &creden
stdOut += data + "\n"; stdOut += data + "\n";
return ErrorCode::NoError; return ErrorCode::NoError;
}; };
// auto cbReadStdErr = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
// stdOut += data + "\n";
// };
e = runScript(credentials, e = runScript(credentials,
replaceVars(amnezia::scriptData(SharedScriptType::build_container), replaceVars(amnezia::scriptData(SharedScriptType::build_container), genVarsForScript(credentials, container, config)),
genVarsForScript(credentials, container, config)),
cbReadStdOut); cbReadStdOut);
if (e) if (e)
return e; return e;
@ -458,17 +437,13 @@ ErrorCode ServerController::buildContainerWorker(const ServerCredentials &creden
return e; return e;
} }
ErrorCode ServerController::runContainerWorker(const ServerCredentials &credentials, DockerContainer container, ErrorCode ServerController::runContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config)
QJsonObject &config)
{ {
QString stdOut; QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) { auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n"; stdOut += data + "\n";
return ErrorCode::NoError; return ErrorCode::NoError;
}; };
// auto cbReadStdErr = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
// stdOut += data + "\n";
// };
ErrorCode e = runScript(credentials, ErrorCode e = runScript(credentials,
replaceVars(amnezia::scriptData(ProtocolScriptType::run_container, container), replaceVars(amnezia::scriptData(ProtocolScriptType::run_container, container),
@ -485,8 +460,7 @@ ErrorCode ServerController::runContainerWorker(const ServerCredentials &credenti
return e; return e;
} }
ErrorCode ServerController::configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, ErrorCode ServerController::configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config)
QJsonObject &config)
{ {
QString stdOut; QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) { auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
@ -503,13 +477,12 @@ ErrorCode ServerController::configureContainerWorker(const ServerCredentials &cr
genVarsForScript(credentials, container, config)), genVarsForScript(credentials, container, config)),
cbReadStdOut, cbReadStdErr); cbReadStdOut, cbReadStdErr);
m_configurator->updateContainerConfigAfterInstallation(container, config, stdOut); VpnConfigurationsController::updateContainerConfigAfterInstallation(container, config, stdOut);
return e; return e;
} }
ErrorCode ServerController::startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, ErrorCode ServerController::startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
const QJsonObject &config)
{ {
QString script = amnezia::scriptData(ProtocolScriptType::container_startup, container); QString script = amnezia::scriptData(ProtocolScriptType::container_startup, container);
@ -517,8 +490,7 @@ ErrorCode ServerController::startupContainerWorker(const ServerCredentials &cred
return ErrorCode::NoError; return ErrorCode::NoError;
} }
ErrorCode e = uploadTextFileToContainer(container, credentials, ErrorCode e = uploadTextFileToContainer(container, credentials, replaceVars(script, genVarsForScript(credentials, container, config)),
replaceVars(script, genVarsForScript(credentials, container, config)),
"/opt/amnezia/start.sh"); "/opt/amnezia/start.sh");
if (e) if (e)
return e; return e;
@ -529,14 +501,15 @@ ErrorCode ServerController::startupContainerWorker(const ServerCredentials &cred
genVarsForScript(credentials, container, config))); genVarsForScript(credentials, container, config)));
} }
ServerController::Vars ServerController::genVarsForScript(const ServerCredentials &credentials, ServerController::Vars ServerController::genVarsForScript(const ServerCredentials &credentials, DockerContainer container,
DockerContainer container, const QJsonObject &config) const QJsonObject &config)
{ {
const QJsonObject &openvpnConfig = config.value(ProtocolProps::protoToString(Proto::OpenVpn)).toObject(); const QJsonObject &openvpnConfig = config.value(ProtocolProps::protoToString(Proto::OpenVpn)).toObject();
const QJsonObject &cloakConfig = config.value(ProtocolProps::protoToString(Proto::Cloak)).toObject(); const QJsonObject &cloakConfig = config.value(ProtocolProps::protoToString(Proto::Cloak)).toObject();
const QJsonObject &ssConfig = config.value(ProtocolProps::protoToString(Proto::ShadowSocks)).toObject(); const QJsonObject &ssConfig = config.value(ProtocolProps::protoToString(Proto::ShadowSocks)).toObject();
const QJsonObject &wireguarConfig = config.value(ProtocolProps::protoToString(Proto::WireGuard)).toObject(); const QJsonObject &wireguarConfig = config.value(ProtocolProps::protoToString(Proto::WireGuard)).toObject();
const QJsonObject &amneziaWireguarConfig = config.value(ProtocolProps::protoToString(Proto::Awg)).toObject(); const QJsonObject &amneziaWireguarConfig = config.value(ProtocolProps::protoToString(Proto::Awg)).toObject();
const QJsonObject &xrayConfig = config.value(ProtocolProps::protoToString(Proto::Xray)).toObject();
const QJsonObject &sftpConfig = config.value(ProtocolProps::protoToString(Proto::Sftp)).toObject(); const QJsonObject &sftpConfig = config.value(ProtocolProps::protoToString(Proto::Sftp)).toObject();
Vars vars; Vars vars;
@ -544,24 +517,19 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
vars.append({ { "$REMOTE_HOST", credentials.hostName } }); vars.append({ { "$REMOTE_HOST", credentials.hostName } });
// OpenVPN vars // OpenVPN vars
vars.append( vars.append({ { "$OPENVPN_SUBNET_IP",
{ { "$OPENVPN_SUBNET_IP",
openvpnConfig.value(config_key::subnet_address).toString(protocols::openvpn::defaultSubnetAddress) } }); openvpnConfig.value(config_key::subnet_address).toString(protocols::openvpn::defaultSubnetAddress) } });
vars.append({ { "$OPENVPN_SUBNET_CIDR", vars.append({ { "$OPENVPN_SUBNET_CIDR", openvpnConfig.value(config_key::subnet_cidr).toString(protocols::openvpn::defaultSubnetCidr) } });
openvpnConfig.value(config_key::subnet_cidr).toString(protocols::openvpn::defaultSubnetCidr) } }); vars.append({ { "$OPENVPN_SUBNET_MASK", openvpnConfig.value(config_key::subnet_mask).toString(protocols::openvpn::defaultSubnetMask) } });
vars.append({ { "$OPENVPN_SUBNET_MASK",
openvpnConfig.value(config_key::subnet_mask).toString(protocols::openvpn::defaultSubnetMask) } });
vars.append({ { "$OPENVPN_PORT", openvpnConfig.value(config_key::port).toString(protocols::openvpn::defaultPort) } }); vars.append({ { "$OPENVPN_PORT", openvpnConfig.value(config_key::port).toString(protocols::openvpn::defaultPort) } });
vars.append( vars.append({ { "$OPENVPN_TRANSPORT_PROTO",
{ { "$OPENVPN_TRANSPORT_PROTO",
openvpnConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto) } }); openvpnConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto) } });
bool isNcpDisabled = openvpnConfig.value(config_key::ncp_disable).toBool(protocols::openvpn::defaultNcpDisable); bool isNcpDisabled = openvpnConfig.value(config_key::ncp_disable).toBool(protocols::openvpn::defaultNcpDisable);
vars.append({ { "$OPENVPN_NCP_DISABLE", isNcpDisabled ? protocols::openvpn::ncpDisableString : "" } }); vars.append({ { "$OPENVPN_NCP_DISABLE", isNcpDisabled ? protocols::openvpn::ncpDisableString : "" } });
vars.append({ { "$OPENVPN_CIPHER", vars.append({ { "$OPENVPN_CIPHER", openvpnConfig.value(config_key::cipher).toString(protocols::openvpn::defaultCipher) } });
openvpnConfig.value(config_key::cipher).toString(protocols::openvpn::defaultCipher) } });
vars.append({ { "$OPENVPN_HASH", openvpnConfig.value(config_key::hash).toString(protocols::openvpn::defaultHash) } }); vars.append({ { "$OPENVPN_HASH", openvpnConfig.value(config_key::hash).toString(protocols::openvpn::defaultHash) } });
bool isTlsAuth = openvpnConfig.value(config_key::tls_auth).toBool(protocols::openvpn::defaultTlsAuth); bool isTlsAuth = openvpnConfig.value(config_key::tls_auth).toBool(protocols::openvpn::defaultTlsAuth);
@ -572,39 +540,35 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
} }
vars.append({ { "$OPENVPN_ADDITIONAL_CLIENT_CONFIG", vars.append({ { "$OPENVPN_ADDITIONAL_CLIENT_CONFIG",
openvpnConfig.value(config_key::additional_client_config) openvpnConfig.value(config_key::additional_client_config).toString(protocols::openvpn::defaultAdditionalClientConfig) } });
.toString(protocols::openvpn::defaultAdditionalClientConfig) } });
vars.append({ { "$OPENVPN_ADDITIONAL_SERVER_CONFIG", vars.append({ { "$OPENVPN_ADDITIONAL_SERVER_CONFIG",
openvpnConfig.value(config_key::additional_server_config) openvpnConfig.value(config_key::additional_server_config).toString(protocols::openvpn::defaultAdditionalServerConfig) } });
.toString(protocols::openvpn::defaultAdditionalServerConfig) } });
// ShadowSocks vars // ShadowSocks vars
vars.append({ { "$SHADOWSOCKS_SERVER_PORT", vars.append({ { "$SHADOWSOCKS_SERVER_PORT", ssConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort) } });
ssConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort) } });
vars.append({ { "$SHADOWSOCKS_LOCAL_PORT", vars.append({ { "$SHADOWSOCKS_LOCAL_PORT",
ssConfig.value(config_key::local_port).toString(protocols::shadowsocks::defaultLocalProxyPort) } }); ssConfig.value(config_key::local_port).toString(protocols::shadowsocks::defaultLocalProxyPort) } });
vars.append({ { "$SHADOWSOCKS_CIPHER", vars.append({ { "$SHADOWSOCKS_CIPHER", ssConfig.value(config_key::cipher).toString(protocols::shadowsocks::defaultCipher) } });
ssConfig.value(config_key::cipher).toString(protocols::shadowsocks::defaultCipher) } });
vars.append({ { "$CONTAINER_NAME", ContainerProps::containerToString(container) } }); vars.append({ { "$CONTAINER_NAME", ContainerProps::containerToString(container) } });
vars.append({ { "$DOCKERFILE_FOLDER", "/opt/amnezia/" + ContainerProps::containerToString(container) } }); vars.append({ { "$DOCKERFILE_FOLDER", "/opt/amnezia/" + ContainerProps::containerToString(container) } });
// Cloak vars // Cloak vars
vars.append({ { "$CLOAK_SERVER_PORT", cloakConfig.value(config_key::port).toString(protocols::cloak::defaultPort) } }); vars.append({ { "$CLOAK_SERVER_PORT", cloakConfig.value(config_key::port).toString(protocols::cloak::defaultPort) } });
vars.append({ { "$FAKE_WEB_SITE_ADDRESS", vars.append({ { "$FAKE_WEB_SITE_ADDRESS", cloakConfig.value(config_key::site).toString(protocols::cloak::defaultRedirSite) } });
cloakConfig.value(config_key::site).toString(protocols::cloak::defaultRedirSite) } });
// Xray vars
vars.append({ { "$XRAY_SITE_NAME", xrayConfig.value(config_key::site).toString(protocols::xray::defaultSite) } });
// Wireguard vars // Wireguard vars
vars.append( vars.append({ { "$WIREGUARD_SUBNET_IP",
{ { "$WIREGUARD_SUBNET_IP",
wireguarConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress) } }); wireguarConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress) } });
vars.append({ { "$WIREGUARD_SUBNET_CIDR", vars.append({ { "$WIREGUARD_SUBNET_CIDR",
wireguarConfig.value(config_key::subnet_cidr).toString(protocols::wireguard::defaultSubnetCidr) } }); wireguarConfig.value(config_key::subnet_cidr).toString(protocols::wireguard::defaultSubnetCidr) } });
vars.append({ { "$WIREGUARD_SUBNET_MASK", vars.append({ { "$WIREGUARD_SUBNET_MASK",
wireguarConfig.value(config_key::subnet_mask).toString(protocols::wireguard::defaultSubnetMask) } }); wireguarConfig.value(config_key::subnet_mask).toString(protocols::wireguard::defaultSubnetMask) } });
vars.append({ { "$WIREGUARD_SERVER_PORT", vars.append({ { "$WIREGUARD_SERVER_PORT", wireguarConfig.value(config_key::port).toString(protocols::wireguard::defaultPort) } });
wireguarConfig.value(config_key::port).toString(protocols::wireguard::defaultPort) } });
// IPsec vars // IPsec vars
vars.append({ { "$IPSEC_VPN_L2TP_NET", "192.168.42.0/24" } }); vars.append({ { "$IPSEC_VPN_L2TP_NET", "192.168.42.0/24" } });
@ -627,32 +591,24 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
vars.append({ { "$SECONDARY_SERVER_DNS", m_settings->secondaryDns() } }); vars.append({ { "$SECONDARY_SERVER_DNS", m_settings->secondaryDns() } });
// Sftp vars // Sftp vars
vars.append( vars.append({ { "$SFTP_PORT", sftpConfig.value(config_key::port).toString(QString::number(ProtocolProps::defaultPort(Proto::Sftp))) } });
{ { "$SFTP_PORT",
sftpConfig.value(config_key::port).toString(QString::number(ProtocolProps::defaultPort(Proto::Sftp))) } });
vars.append({ { "$SFTP_USER", sftpConfig.value(config_key::userName).toString() } }); vars.append({ { "$SFTP_USER", sftpConfig.value(config_key::userName).toString() } });
vars.append({ { "$SFTP_PASSWORD", sftpConfig.value(config_key::password).toString() } }); vars.append({ { "$SFTP_PASSWORD", sftpConfig.value(config_key::password).toString() } });
// Amnezia wireguard vars // Amnezia wireguard vars
vars.append({ { "$AWG_SERVER_PORT", vars.append({ { "$AWG_SERVER_PORT", amneziaWireguarConfig.value(config_key::port).toString(protocols::awg::defaultPort) } });
amneziaWireguarConfig.value(config_key::port).toString(protocols::awg::defaultPort) } });
vars.append({ { "$JUNK_PACKET_COUNT", amneziaWireguarConfig.value(config_key::junkPacketCount).toString() } }); vars.append({ { "$JUNK_PACKET_COUNT", amneziaWireguarConfig.value(config_key::junkPacketCount).toString() } });
vars.append({ { "$JUNK_PACKET_MIN_SIZE", amneziaWireguarConfig.value(config_key::junkPacketMinSize).toString() } }); vars.append({ { "$JUNK_PACKET_MIN_SIZE", amneziaWireguarConfig.value(config_key::junkPacketMinSize).toString() } });
vars.append({ { "$JUNK_PACKET_MAX_SIZE", amneziaWireguarConfig.value(config_key::junkPacketMaxSize).toString() } }); vars.append({ { "$JUNK_PACKET_MAX_SIZE", amneziaWireguarConfig.value(config_key::junkPacketMaxSize).toString() } });
vars.append({ { "$INIT_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::initPacketJunkSize).toString() } }); vars.append({ { "$INIT_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::initPacketJunkSize).toString() } });
vars.append({ { "$RESPONSE_PACKET_JUNK_SIZE", vars.append({ { "$RESPONSE_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::responsePacketJunkSize).toString() } });
amneziaWireguarConfig.value(config_key::responsePacketJunkSize).toString() } }); vars.append({ { "$INIT_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::initPacketMagicHeader).toString() } });
vars.append({ { "$INIT_PACKET_MAGIC_HEADER", vars.append({ { "$RESPONSE_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::responsePacketMagicHeader).toString() } });
amneziaWireguarConfig.value(config_key::initPacketMagicHeader).toString() } }); vars.append({ { "$UNDERLOAD_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::underloadPacketMagicHeader).toString() } });
vars.append({ { "$RESPONSE_PACKET_MAGIC_HEADER", vars.append({ { "$TRANSPORT_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::transportPacketMagicHeader).toString() } });
amneziaWireguarConfig.value(config_key::responsePacketMagicHeader).toString() } });
vars.append({ { "$UNDERLOAD_PACKET_MAGIC_HEADER",
amneziaWireguarConfig.value(config_key::underloadPacketMagicHeader).toString() } });
vars.append({ { "$TRANSPORT_PACKET_MAGIC_HEADER",
amneziaWireguarConfig.value(config_key::transportPacketMagicHeader).toString() } });
QString serverIp = Utils::getIPAddress(credentials.hostName); QString serverIp = NetworkUtilities::getIPAddress(credentials.hostName);
if (!serverIp.isEmpty()) { if (!serverIp.isEmpty()) {
vars.append({ { "$SERVER_IP_ADDRESS", serverIp } }); vars.append({ { "$SERVER_IP_ADDRESS", serverIp } });
} else { } else {
@ -662,7 +618,7 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
return vars; return vars;
} }
QString ServerController::checkSshConnection(const ServerCredentials &credentials, ErrorCode *errorCode) QString ServerController::checkSshConnection(const ServerCredentials &credentials, ErrorCode &errorCode)
{ {
QString stdOut; QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) { auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
@ -674,11 +630,7 @@ QString ServerController::checkSshConnection(const ServerCredentials &credential
return ErrorCode::NoError; return ErrorCode::NoError;
}; };
ErrorCode e = errorCode = runScript(credentials, amnezia::scriptData(SharedScriptType::check_connection), cbReadStdOut, cbReadStdErr);
runScript(credentials, amnezia::scriptData(SharedScriptType::check_connection), cbReadStdOut, cbReadStdErr);
if (errorCode)
*errorCode = e;
return stdOut; return stdOut;
} }
@ -690,9 +642,7 @@ void ServerController::cancelInstallation()
ErrorCode ServerController::setupServerFirewall(const ServerCredentials &credentials) ErrorCode ServerController::setupServerFirewall(const ServerCredentials &credentials)
{ {
return runScript( return runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::setup_host_firewall), genVarsForScript(credentials)));
credentials,
replaceVars(amnezia::scriptData(SharedScriptType::setup_host_firewall), genVarsForScript(credentials)));
} }
QString ServerController::replaceVars(const QString &script, const Vars &vars) QString ServerController::replaceVars(const QString &script, const Vars &vars)
@ -704,8 +654,7 @@ QString ServerController::replaceVars(const QString &script, const Vars &vars)
return s; return s;
} }
ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
const QJsonObject &config)
{ {
if (container == DockerContainer::Dns) { if (container == DockerContainer::Dns) {
return ErrorCode::NoError; return ErrorCode::NoError;
@ -728,15 +677,12 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential
QStringList fixedPorts = ContainerProps::fixedPortsForContainer(container); QStringList fixedPorts = ContainerProps::fixedPortsForContainer(container);
QString defaultPort("%1"); QString defaultPort("%1");
QString port = QString port = containerConfig.value(config_key::port).toString(defaultPort.arg(ProtocolProps::defaultPort(protocol)));
containerConfig.value(config_key::port).toString(defaultPort.arg(ProtocolProps::defaultPort(protocol))); QString defaultTransportProto = ProtocolProps::transportProtoToString(ProtocolProps::defaultTransportProto(protocol), protocol);
QString defaultTransportProto =
ProtocolProps::transportProtoToString(ProtocolProps::defaultTransportProto(protocol), protocol);
QString transportProto = containerConfig.value(config_key::transport_proto).toString(defaultTransportProto); QString transportProto = containerConfig.value(config_key::transport_proto).toString(defaultTransportProto);
// TODO reimplement with netstat // TODO reimplement with netstat
QString script = QString script = QString("which lsof &>/dev/null || true && sudo lsof -i -P -n 2>/dev/null | grep -E ':%1 ").arg(port);
QString("which lsof &>/dev/null || true && sudo lsof -i -P -n 2>/dev/null | grep -E ':%1 ").arg(port);
for (auto &port : fixedPorts) { for (auto &port : fixedPorts) {
script = script.append("|:%1").arg(port); script = script.append("|:%1").arg(port);
} }
@ -746,8 +692,7 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential
script = script.append(" | grep LISTEN"); script = script.append(" | grep LISTEN");
} }
ErrorCode errorCode = runScript(credentials, replaceVars(script, genVarsForScript(credentials, container)), ErrorCode errorCode = runScript(credentials, replaceVars(script, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr);
cbReadStdOut, cbReadStdErr);
if (errorCode != ErrorCode::NoError) { if (errorCode != ErrorCode::NoError) {
return errorCode; return errorCode;
} }
@ -775,8 +720,7 @@ ErrorCode ServerController::isUserInSudo(const ServerCredentials &credentials, D
}; };
const QString scriptData = amnezia::scriptData(SharedScriptType::check_user_in_sudo); const QString scriptData = amnezia::scriptData(SharedScriptType::check_user_in_sudo);
ErrorCode error = ErrorCode error = runScript(credentials, replaceVars(scriptData, genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr);
runScript(credentials, replaceVars(scriptData, genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr);
if (!stdOut.contains("sudo")) if (!stdOut.contains("sudo"))
return ErrorCode::ServerUserNotInSudo; return ErrorCode::ServerUserNotInSudo;
@ -806,9 +750,7 @@ ErrorCode ServerController::isServerDpkgBusy(const ServerCredentials &credential
return ErrorCode::ServerCancelInstallation; return ErrorCode::ServerCancelInstallation;
} }
stdOut.clear(); stdOut.clear();
runScript(credentials, runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::check_server_is_busy), genVarsForScript(credentials)),
replaceVars(amnezia::scriptData(SharedScriptType::check_server_is_busy),
genVarsForScript(credentials)),
cbReadStdOut, cbReadStdErr); cbReadStdOut, cbReadStdErr);
if (stdOut.contains("Packet manager not found")) if (stdOut.contains("Packet manager not found"))
@ -839,156 +781,6 @@ ErrorCode ServerController::isServerDpkgBusy(const ServerCredentials &credential
return future.result(); return future.result();
} }
ErrorCode ServerController::getAlreadyInstalledContainers(const ServerCredentials &credentials,
QMap<DockerContainer, QJsonObject> &installedContainers)
{
QString stdOut;
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};
auto cbReadStdErr = [&](const QString &data, libssh::Client &) {
stdOut += data + "\n";
return ErrorCode::NoError;
};
QString script = QString("sudo docker ps --format '{{.Names}} {{.Ports}}'");
ErrorCode errorCode = runScript(credentials, script, cbReadStdOut, cbReadStdErr);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
auto containersInfo = stdOut.split("\n");
for (auto &containerInfo : containersInfo) {
if (containerInfo.isEmpty()) {
continue;
}
const static QRegularExpression containerAndPortRegExp("(amnezia[-a-z]*).*?:([0-9]*)->[0-9]*/(udp|tcp).*");
QRegularExpressionMatch containerAndPortMatch = containerAndPortRegExp.match(containerInfo);
if (containerAndPortMatch.hasMatch()) {
QString name = containerAndPortMatch.captured(1);
QString port = containerAndPortMatch.captured(2);
QString transportProto = containerAndPortMatch.captured(3);
DockerContainer container = ContainerProps::containerFromString(name);
QJsonObject config;
Proto mainProto = ContainerProps::defaultProtocol(container);
for (auto protocol : ContainerProps::protocolsForContainer(container)) {
QJsonObject containerConfig;
if (protocol == mainProto) {
containerConfig.insert(config_key::port, port);
containerConfig.insert(config_key::transport_proto, transportProto);
if (protocol == Proto::Awg) {
QString serverConfigPath;
if (container == DockerContainer::Awg) {
if (isNewAwgContainer(credentials)) {
serverConfigPath = amnezia::protocols::awg::serverConfigPath;
} else {
serverConfigPath = "/opt/amnezia/awg/wg0.conf";
}
}
QString serverConfig = getTextFileFromContainer(container, credentials, serverConfigPath, &errorCode);
QMap<QString, QString> serverConfigMap;
auto serverConfigLines = serverConfig.split("\n");
for (auto &line : serverConfigLines) {
auto trimmedLine = line.trimmed();
if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) {
continue;
} else {
QStringList parts = trimmedLine.split(" = ");
if (parts.count() == 2) {
serverConfigMap.insert(parts[0].trimmed(), parts[1].trimmed());
}
}
}
containerConfig[config_key::junkPacketCount] = serverConfigMap.value(config_key::junkPacketCount);
containerConfig[config_key::junkPacketMinSize] = serverConfigMap.value(config_key::junkPacketMinSize);
containerConfig[config_key::junkPacketMaxSize] = serverConfigMap.value(config_key::junkPacketMaxSize);
containerConfig[config_key::initPacketJunkSize] = serverConfigMap.value(config_key::initPacketJunkSize);
containerConfig[config_key::responsePacketJunkSize] = serverConfigMap.value(config_key::responsePacketJunkSize);
containerConfig[config_key::initPacketMagicHeader] = serverConfigMap.value(config_key::initPacketMagicHeader);
containerConfig[config_key::responsePacketMagicHeader] = serverConfigMap.value(config_key::responsePacketMagicHeader);
containerConfig[config_key::underloadPacketMagicHeader] = serverConfigMap.value(config_key::underloadPacketMagicHeader);
containerConfig[config_key::transportPacketMagicHeader] = serverConfigMap.value(config_key::transportPacketMagicHeader);
} else if (protocol == Proto::Sftp) {
stdOut.clear();
script = QString("sudo docker inspect --format '{{.Config.Cmd}}' %1").arg(name);
ErrorCode errorCode = runScript(credentials, script, cbReadStdOut, cbReadStdErr);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
auto sftpInfo = stdOut.split(":");
if (sftpInfo.size() < 2) {
logger.error() << "Key parameters for the sftp container are missing";
continue;
}
auto userName = sftpInfo.at(0);
userName = userName.remove(0, 1);
auto password = sftpInfo.at(1);
containerConfig.insert(config_key::userName, userName);
containerConfig.insert(config_key::password, password);
}
config.insert(config_key::container, ContainerProps::containerToString(container));
}
config.insert(ProtocolProps::protoToString(protocol), containerConfig);
}
installedContainers.insert(container, config);
}
const static QRegularExpression torOrDnsRegExp("(amnezia-(?:torwebsite|dns)).*?([0-9]*)/(udp|tcp).*");
QRegularExpressionMatch torOrDnsRegMatch = torOrDnsRegExp.match(containerInfo);
if (torOrDnsRegMatch.hasMatch()) {
QString name = torOrDnsRegMatch.captured(1);
QString port = torOrDnsRegMatch.captured(2);
QString transportProto = torOrDnsRegMatch.captured(3);
DockerContainer container = ContainerProps::containerFromString(name);
QJsonObject config;
Proto mainProto = ContainerProps::defaultProtocol(container);
for (auto protocol : ContainerProps::protocolsForContainer(container)) {
QJsonObject containerConfig;
if (protocol == mainProto) {
containerConfig.insert(config_key::port, port);
containerConfig.insert(config_key::transport_proto, transportProto);
if (protocol == Proto::TorWebSite) {
stdOut.clear();
script = QString("sudo docker exec -i %1 sh -c 'cat /var/lib/tor/hidden_service/hostname'").arg(name);
ErrorCode errorCode = runScript(credentials, script, cbReadStdOut, cbReadStdErr);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
if (stdOut.isEmpty()) {
logger.error() << "Key parameters for the tor container are missing";
continue;
}
QString onion = stdOut;
onion.replace("\n", "");
containerConfig.insert(config_key::site, onion);
}
config.insert(config_key::container, ContainerProps::containerToString(container));
}
config.insert(ProtocolProps::protoToString(protocol), containerConfig);
}
installedContainers.insert(container, config);
}
}
return ErrorCode::NoError;
}
ErrorCode ServerController::getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey, ErrorCode ServerController::getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey,
const std::function<QString()> &callback) const std::function<QString()> &callback)
{ {

View file

@ -25,22 +25,18 @@ public:
ErrorCode rebootServer(const ServerCredentials &credentials); 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, bool isUpdate = false);
bool isUpdate = false); ErrorCode updateContainer(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &oldConfig,
ErrorCode updateContainer(const ServerCredentials &credentials, DockerContainer container, QJsonObject &newConfig);
const QJsonObject &oldConfig, QJsonObject &newConfig);
ErrorCode getAlreadyInstalledContainers(const ServerCredentials &credentials,
QMap<DockerContainer, QJsonObject> &installedContainers);
ErrorCode startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, ErrorCode startupContainerWorker(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &config = QJsonObject()); const QJsonObject &config = QJsonObject());
ErrorCode uploadTextFileToContainer( ErrorCode uploadTextFileToContainer(DockerContainer container, const ServerCredentials &credentials, const QString &file,
DockerContainer container, const ServerCredentials &credentials, const QString &file, const QString &path, const QString &path,
libssh::ScpOverwriteMode overwriteMode = libssh::ScpOverwriteMode::ScpOverwriteExisting); libssh::ScpOverwriteMode overwriteMode = libssh::ScpOverwriteMode::ScpOverwriteExisting);
QByteArray getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials, QByteArray getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials, const QString &path,
const QString &path, ErrorCode *errorCode = nullptr); ErrorCode &errorCode);
QString replaceVars(const QString &script, const Vars &vars); QString replaceVars(const QString &script, const Vars &vars);
Vars genVarsForScript(const ServerCredentials &credentials, DockerContainer container = DockerContainer::None, Vars genVarsForScript(const ServerCredentials &credentials, DockerContainer container = DockerContainer::None,
@ -50,12 +46,11 @@ public:
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdOut = nullptr, const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdOut = nullptr,
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdErr = nullptr); const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdErr = nullptr);
ErrorCode ErrorCode runContainerScript(const ServerCredentials &credentials, DockerContainer container, QString script,
runContainerScript(const ServerCredentials &credentials, DockerContainer container, QString script,
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdOut = nullptr, const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdOut = nullptr,
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdErr = nullptr); const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdErr = nullptr);
QString checkSshConnection(const ServerCredentials &credentials, ErrorCode *errorCode = nullptr); QString checkSshConnection(const ServerCredentials &credentials, ErrorCode &errorCode);
void cancelInstallation(); void cancelInstallation();
@ -66,18 +61,14 @@ public:
private: private:
ErrorCode installDockerWorker(const ServerCredentials &credentials, DockerContainer container); ErrorCode installDockerWorker(const ServerCredentials &credentials, DockerContainer container);
ErrorCode prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, ErrorCode prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject());
const QJsonObject &config = QJsonObject());
ErrorCode buildContainerWorker(const ServerCredentials &credentials, DockerContainer container, ErrorCode buildContainerWorker(const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &config = QJsonObject()); const QJsonObject &config = QJsonObject());
ErrorCode runContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config); ErrorCode runContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config);
ErrorCode configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, ErrorCode configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config);
QJsonObject &config);
ErrorCode isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, ErrorCode isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config);
const QJsonObject &config); bool isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig);
bool isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig,
const QJsonObject &newConfig);
ErrorCode isUserInSudo(const ServerCredentials &credentials, DockerContainer container); ErrorCode isUserInSudo(const ServerCredentials &credentials, DockerContainer container);
ErrorCode isServerDpkgBusy(const ServerCredentials &credentials, DockerContainer container); ErrorCode isServerDpkgBusy(const ServerCredentials &credentials, DockerContainer container);

View file

@ -0,0 +1,138 @@
#include "vpnConfigurationController.h"
#include "configurators/awg_configurator.h"
#include "configurators/cloak_configurator.h"
#include "configurators/ikev2_configurator.h"
#include "configurators/openvpn_configurator.h"
#include "configurators/shadowsocks_configurator.h"
#include "configurators/wireguard_configurator.h"
#include "configurators/xray_configurator.h"
VpnConfigurationsController::VpnConfigurationsController(const std::shared_ptr<Settings> &settings,
QSharedPointer<ServerController> serverController, QObject *parent)
: QObject { parent }, m_settings(settings), m_serverController(serverController)
{
}
QScopedPointer<ConfiguratorBase> VpnConfigurationsController::createConfigurator(const Proto protocol)
{
switch (protocol) {
case Proto::OpenVpn: return QScopedPointer<ConfiguratorBase>(new OpenVpnConfigurator(m_settings, m_serverController));
case Proto::ShadowSocks: return QScopedPointer<ConfiguratorBase>(new ShadowSocksConfigurator(m_settings, m_serverController));
case Proto::Cloak: return QScopedPointer<ConfiguratorBase>(new CloakConfigurator(m_settings, m_serverController));
case Proto::WireGuard: return QScopedPointer<ConfiguratorBase>(new WireguardConfigurator(m_settings, m_serverController, false));
case Proto::Awg: return QScopedPointer<ConfiguratorBase>(new AwgConfigurator(m_settings, m_serverController));
case Proto::Ikev2: return QScopedPointer<ConfiguratorBase>(new Ikev2Configurator(m_settings, m_serverController));
case Proto::Xray: return QScopedPointer<ConfiguratorBase>(new XrayConfigurator(m_settings, m_serverController));
default: return QScopedPointer<ConfiguratorBase>();
}
}
ErrorCode VpnConfigurationsController::createProtocolConfigForContainer(const ServerCredentials &credentials,
const DockerContainer container, QJsonObject &containerConfig)
{
ErrorCode errorCode = ErrorCode::NoError;
if (ContainerProps::containerService(container) == ServiceType::Other) {
return errorCode;
}
for (Proto protocol : ContainerProps::protocolsForContainer(container)) {
QJsonObject protocolConfig = containerConfig.value(ProtocolProps::protoToString(protocol)).toObject();
auto configurator = createConfigurator(protocol);
QString protocolConfigString = configurator->createConfig(credentials, container, containerConfig, errorCode);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
protocolConfig.insert(config_key::last_config, protocolConfigString);
containerConfig.insert(ProtocolProps::protoToString(protocol), protocolConfig);
}
return errorCode;
}
ErrorCode VpnConfigurationsController::createProtocolConfigString(const bool isApiConfig, const QPair<QString, QString> &dns,
const ServerCredentials &credentials, const DockerContainer container,
const QJsonObject &containerConfig, const Proto protocol,
QString &protocolConfigString)
{
ErrorCode errorCode = ErrorCode::NoError;
if (ContainerProps::containerService(container) == ServiceType::Other) {
return errorCode;
}
auto configurator = createConfigurator(protocol);
protocolConfigString = configurator->createConfig(credentials, container, containerConfig, errorCode);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
protocolConfigString = configurator->processConfigWithExportSettings(dns, isApiConfig, protocolConfigString);
return errorCode;
}
QJsonObject VpnConfigurationsController::createVpnConfiguration(const QPair<QString, QString> &dns, const QJsonObject &serverConfig,
const QJsonObject &containerConfig, const DockerContainer container,
ErrorCode &errorCode)
{
QJsonObject vpnConfiguration {};
if (ContainerProps::containerService(container) == ServiceType::Other) {
return vpnConfiguration;
}
bool isApiConfig = serverConfig.value(config_key::configVersion).toInt();
for (ProtocolEnumNS::Proto proto : ContainerProps::protocolsForContainer(container)) {
if (isApiConfig && container == DockerContainer::Cloak && proto == ProtocolEnumNS::Proto::ShadowSocks) {
continue;
}
QString protocolConfigString =
containerConfig.value(ProtocolProps::protoToString(proto)).toObject().value(config_key::last_config).toString();
auto configurator = createConfigurator(proto);
protocolConfigString = configurator->processConfigWithLocalSettings(dns, isApiConfig, protocolConfigString);
QJsonObject vpnConfigData = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
vpnConfigData = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
vpnConfiguration.insert(ProtocolProps::key_proto_config_data(proto), vpnConfigData);
}
Proto proto = ContainerProps::defaultProtocol(container);
vpnConfiguration[config_key::vpnproto] = ProtocolProps::protoToString(proto);
vpnConfiguration[config_key::dns1] = dns.first;
vpnConfiguration[config_key::dns2] = dns.second;
vpnConfiguration[config_key::hostName] = serverConfig.value(config_key::hostName).toString();
vpnConfiguration[config_key::description] = serverConfig.value(config_key::description).toString();
vpnConfiguration[config_key::configVersion] = serverConfig.value(config_key::configVersion).toInt();
// TODO: try to get hostName, port, description for 3rd party configs
// vpnConfiguration[config_key::port] = ...;
return vpnConfiguration;
}
void VpnConfigurationsController::updateContainerConfigAfterInstallation(const DockerContainer container, QJsonObject &containerConfig,
const QString &stdOut)
{
Proto mainProto = ContainerProps::defaultProtocol(container);
if (container == DockerContainer::TorWebSite) {
QJsonObject protocol = containerConfig.value(ProtocolProps::protoToString(mainProto)).toObject();
qDebug() << "amnezia-tor onions" << stdOut;
QString onion = stdOut;
onion.replace("\n", "");
protocol.insert(config_key::site, onion);
containerConfig.insert(ProtocolProps::protoToString(mainProto), protocol);
}
}

View file

@ -0,0 +1,36 @@
#ifndef VPNCONFIGIRATIONSCONTROLLER_H
#define VPNCONFIGIRATIONSCONTROLLER_H
#include <QObject>
#include "configurators/configurator_base.h"
#include "containers/containers_defs.h"
#include "core/defs.h"
#include "settings.h"
class VpnConfigurationsController : public QObject
{
Q_OBJECT
public:
explicit VpnConfigurationsController(const std::shared_ptr<Settings> &settings, QSharedPointer<ServerController> serverController, QObject *parent = nullptr);
public slots:
ErrorCode createProtocolConfigForContainer(const ServerCredentials &credentials, const DockerContainer container,
QJsonObject &containerConfig);
ErrorCode createProtocolConfigString(const bool isApiConfig, const QPair<QString, QString> &dns, const ServerCredentials &credentials,
const DockerContainer container, const QJsonObject &containerConfig, const Proto protocol,
QString &protocolConfigString);
QJsonObject createVpnConfiguration(const QPair<QString, QString> &dns, const QJsonObject &serverConfig,
const QJsonObject &containerConfig, const DockerContainer container, ErrorCode &errorCode);
static void updateContainerConfigAfterInstallation(const DockerContainer container, QJsonObject &containerConfig, const QString &stdOut);
signals:
private:
QScopedPointer<ConfiguratorBase> createConfigurator(const Proto protocol);
std::shared_ptr<Settings> m_settings;
QSharedPointer<ServerController> m_serverController;
};
#endif // VPNCONFIGIRATIONSCONTROLLER_H

View file

@ -22,12 +22,27 @@ namespace amnezia
} }
}; };
struct InstalledAppInfo {
QString appName;
QString packageName;
QString appPath;
bool operator==(const InstalledAppInfo& other) const {
if (!packageName.isEmpty()) {
return packageName == other.packageName;
} else {
return appPath == other.appPath;
}
}
};
enum ErrorCode { enum ErrorCode {
// General error codes // General error codes
NoError = 0, NoError = 0,
UnknownError = 100, UnknownError = 100,
InternalError = 101, InternalError = 101,
NotImplementedError = 102, NotImplementedError = 102,
AmneziaServiceNotRunning = 103,
// Server errors // Server errors
ServerCheckFailed = 200, ServerCheckFailed = 200,
@ -59,6 +74,8 @@ namespace amnezia
CloakExecutableMissing = 602, CloakExecutableMissing = 602,
AmneziaServiceConnectionFailed = 603, AmneziaServiceConnectionFailed = 603,
ExecutableMissing = 604, ExecutableMissing = 604,
XrayExecutableMissing = 605,
Tun2SockExecutableMissing = 606,
// VPN errors // VPN errors
OpenVpnAdaptersInUseError = 700, OpenVpnAdaptersInUseError = 700,
@ -70,6 +87,8 @@ namespace amnezia
OpenSslFailed = 800, OpenSslFailed = 800,
ShadowSocksExecutableCrashed = 801, ShadowSocksExecutableCrashed = 801,
CloakExecutableCrashed = 802, CloakExecutableCrashed = 802,
XrayExecutableCrashed = 803,
Tun2SockExecutableCrashed = 804,
// import and install errors // import and install errors
ImportInvalidConfigError = 900, ImportInvalidConfigError = 900,
@ -80,6 +99,9 @@ namespace amnezia
// Api errors // Api errors
ApiConfigDownloadError = 1100, ApiConfigDownloadError = 1100,
ApiConfigAlreadyAdded = 1101, ApiConfigAlreadyAdded = 1101,
ApiConfigEmptyError = 1102,
ApiConfigTimeoutError = 1103,
ApiConfigSslError = 1104,
// QFile errors // QFile errors
OpenError = 1200, OpenError = 1200,

View file

@ -11,6 +11,7 @@ QString errorString(ErrorCode code) {
case(NoError): errorMessage = QObject::tr("No error"); break; case(NoError): errorMessage = QObject::tr("No error"); break;
case(UnknownError): errorMessage = QObject::tr("Unknown Error"); break; case(UnknownError): errorMessage = QObject::tr("Unknown Error"); break;
case(NotImplementedError): errorMessage = QObject::tr("Function not implemented"); break; case(NotImplementedError): errorMessage = QObject::tr("Function not implemented"); break;
case(AmneziaServiceNotRunning): errorMessage = QObject::tr("Background service is not running"); break;
// Server errors // Server errors
case(ServerCheckFailed): errorMessage = QObject::tr("Server check failed"); break; case(ServerCheckFailed): errorMessage = QObject::tr("Server check failed"); break;
@ -19,6 +20,7 @@ QString errorString(ErrorCode code) {
case(ServerDockerFailedError): errorMessage = QObject::tr("Server error: Docker failed"); break; case(ServerDockerFailedError): errorMessage = QObject::tr("Server error: Docker failed"); break;
case(ServerCancelInstallation): errorMessage = QObject::tr("Installation canceled by user"); break; case(ServerCancelInstallation): errorMessage = QObject::tr("Installation canceled by user"); break;
case(ServerUserNotInSudo): errorMessage = QObject::tr("The user does not have permission to use sudo"); break; case(ServerUserNotInSudo): errorMessage = QObject::tr("The user does not have permission to use sudo"); break;
case(ServerPacketManagerError): errorMessage = QObject::tr("Server error: Packet manager error"); break;
// Libssh errors // Libssh errors
case(SshRequestDeniedError): errorMessage = QObject::tr("Ssh request was denied"); break; case(SshRequestDeniedError): errorMessage = QObject::tr("Ssh request was denied"); break;
@ -55,6 +57,9 @@ QString errorString(ErrorCode code) {
// Api errors // Api errors
case (ApiConfigDownloadError): errorMessage = QObject::tr("Error when retrieving configuration from API"); break; case (ApiConfigDownloadError): errorMessage = QObject::tr("Error when retrieving configuration from API"); break;
case (ApiConfigAlreadyAdded): errorMessage = QObject::tr("This config has already been added to the application"); break; case (ApiConfigAlreadyAdded): errorMessage = QObject::tr("This config has already been added to the application"); break;
case (ApiConfigEmptyError): errorMessage = QObject::tr("In the response from the server, an empty config was received"); break;
case (ApiConfigSslError): errorMessage = QObject::tr("SSL error occurred"); break;
case (ApiConfigTimeoutError): errorMessage = QObject::tr("Server response timeout on api request"); break;
// QFile errors // QFile errors
case(OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break; case(OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;

View file

@ -0,0 +1,12 @@
#include "installedAppsImageProvider.h"
#include "platforms/android/android_controller.h"
InstalledAppsImageProvider::InstalledAppsImageProvider() : QQuickImageProvider(QQuickImageProvider::Pixmap)
{
}
QPixmap InstalledAppsImageProvider::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize)
{
return AndroidController::instance()->getAppIcon(id, size, requestedSize);
}

View file

@ -0,0 +1,15 @@
#ifndef INSTALLEDAPPSIMAGEPROVIDER_H
#define INSTALLEDAPPSIMAGEPROVIDER_H
#include <QObject>
#include <QQuickImageProvider>
class InstalledAppsImageProvider : public QQuickImageProvider
{
public:
InstalledAppsImageProvider();
QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override;
};
#endif // INSTALLEDAPPSIMAGEPROVIDER_H

View file

@ -71,7 +71,7 @@ QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
} }
QRemoteObjectPendingReply<int> futureResult = Instance()->m_ipcClient->createPrivilegedProcess(); QRemoteObjectPendingReply<int> futureResult = Instance()->m_ipcClient->createPrivilegedProcess();
futureResult.waitForFinished(1000); futureResult.waitForFinished(5000);
int pid = futureResult.returnValue(); int pid = futureResult.returnValue();

View file

@ -0,0 +1,462 @@
#include "networkUtilities.h"
#ifdef Q_OS_WIN
#include <windows.h>
#include <Ipexport.h>
#include <Ws2tcpip.h>
#include <ws2ipdef.h>
#include <stdint.h>
#include <Iphlpapi.h>
#include <Iptypes.h>
#include <WinSock2.h>
#include <winsock.h>
#include <QNetworkInterface>
#include "qendian.h"
#endif
#ifdef Q_OS_LINUX
#include <arpa/inet.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>
#endif
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS)
#include <sys/param.h>
#include <sys/sysctl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/route.h>
#endif
#include <QHostAddress>
#include <QHostInfo>
QRegularExpression NetworkUtilities::ipAddressRegExp()
{
return QRegularExpression("^((25[0-5]|(2[0-4]|1[0-9]|[1-9]|)[0-9])(\\.(?!$)|$)){4}$");
}
QRegularExpression NetworkUtilities::ipAddressPortRegExp()
{
return QRegularExpression("^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}"
"(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(\\:[0-9]{1,5}){0,1}$");
}
QRegExp NetworkUtilities::ipAddressWithSubnetRegExp()
{
return QRegExp("(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}"
"(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(\\/[0-9]{1,2}){0,1}");
}
QRegExp NetworkUtilities::ipNetwork24RegExp()
{
return QRegExp("^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}"
"0$");
}
QRegExp NetworkUtilities::ipPortRegExp()
{
return QRegExp("^()([1-9]|[1-5]?[0-9]{2,4}|6[1-4][0-9]{3}|65[1-4][0-9]{2}|655[1-2][0-9]|6553[1-5])$");
}
QRegExp NetworkUtilities::domainRegExp()
{
return QRegExp("(((?!\\-))(xn\\-\\-)?[a-z0-9\\-_]{0,61}[a-z0-9]{1,1}\\.)*(xn\\-\\-)?([a-z0-9\\-]{1,61}|[a-z0-"
"9\\-]{1,30})\\.[a-z]{2,}");
}
QString NetworkUtilities::netMaskFromIpWithSubnet(const QString ip)
{
if (!ip.contains("/"))
return "255.255.255.255";
bool ok;
int prefix = ip.split("/").at(1).toInt(&ok);
if (!ok)
return "255.255.255.255";
unsigned long mask = (0xFFFFFFFF << (32 - prefix)) & 0xFFFFFFFF;
return QString("%1.%2.%3.%4").arg(mask >> 24).arg((mask >> 16) & 0xFF).arg((mask >> 8) & 0xFF).arg(mask & 0xFF);
}
QString NetworkUtilities::ipAddressFromIpWithSubnet(const QString ip)
{
if (ip.count(".") != 3)
return "";
return ip.split("/").first();
}
QStringList NetworkUtilities::summarizeRoutes(const QStringList &ips, const QString cidr)
{
// QMap<int, int>
// QHostAddress
// QMap<QString, QStringList> subnets; // <"a.b", <list subnets>>
// for (const QString &ip : ips) {
// if (ip.count(".") != 3) continue;
// const QStringList &parts = ip.split(".");
// subnets[parts.at(0) + "." + parts.at(1)].append(ip);
// }
return QStringList();
}
QString NetworkUtilities::getIPAddress(const QString &host)
{
if (ipAddressRegExp().match(host).hasMatch()) {
return host;
}
QList<QHostAddress> addresses = QHostInfo::fromName(host).addresses();
if (!addresses.isEmpty()) {
return addresses.first().toString();
}
qDebug() << "Unable to resolve address for " << host;
return "";
}
QString NetworkUtilities::getStringBetween(const QString &s, const QString &a, const QString &b)
{
int ap = s.indexOf(a), bp = s.indexOf(b, ap + a.length());
if (ap < 0 || bp < 0)
return QString();
ap += a.length();
if (bp - ap <= 0)
return QString();
return s.mid(ap, bp - ap).trimmed();
}
bool NetworkUtilities::checkIPv4Format(const QString &ip)
{
if (ip.isEmpty())
return false;
int count = ip.count(".");
if (count != 3)
return false;
QHostAddress addr(ip);
return (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol);
}
bool NetworkUtilities::checkIpSubnetFormat(const QString &ip)
{
if (!ip.contains("/"))
return checkIPv4Format(ip);
QStringList parts = ip.split("/");
if (parts.size() != 2)
return false;
bool ok;
int subnet = parts.at(1).toInt(&ok);
if (subnet >= 0 && subnet <= 32 && ok)
return checkIPv4Format(parts.at(0));
else
return false;
}
// static
int NetworkUtilities::AdapterIndexTo(const QHostAddress& dst) {
#ifdef Q_OS_WIN
qDebug() << "Getting Current Internet Adapter that routes to"
<< dst.toString();
quint32_be ipBigEndian;
quint32 ip = dst.toIPv4Address();
qToBigEndian(ip, &ipBigEndian);
_MIB_IPFORWARDROW routeInfo;
auto result = GetBestRoute(ipBigEndian, 0, &routeInfo);
if (result != NO_ERROR) {
return -1;
}
auto adapter =
QNetworkInterface::interfaceFromIndex(routeInfo.dwForwardIfIndex);
qDebug() << "Internet Adapter:" << adapter.name();
return routeInfo.dwForwardIfIndex;
#endif
return 0;
}
#ifdef Q_OS_WIN
DWORD GetAdaptersAddressesWrapper(const ULONG Family,
const ULONG Flags,
const PVOID Reserved,
_Out_ PIP_ADAPTER_ADDRESSES& pAdapterAddresses) {
DWORD dwRetVal = 0;
int iter = 0;
constexpr int max_iter = 3;
ULONG AdapterAddressesLen = 15000;
do {
// xassert2(pAdapterAddresses == nullptr);
pAdapterAddresses = (IP_ADAPTER_ADDRESSES*)malloc(AdapterAddressesLen);
if (pAdapterAddresses == nullptr) {
qDebug() << "can not malloc" << AdapterAddressesLen << "bytes";
return ERROR_OUTOFMEMORY;
}
dwRetVal = GetAdaptersAddresses(Family, Flags, NULL, pAdapterAddresses, &AdapterAddressesLen);
if (dwRetVal == ERROR_BUFFER_OVERFLOW) {
free(pAdapterAddresses);
pAdapterAddresses = nullptr;
} else {
break;
}
iter++;
} while ((dwRetVal == ERROR_BUFFER_OVERFLOW) && (iter < max_iter));
if (dwRetVal != NO_ERROR) {
qDebug() << "Family: " << Family << ", Flags: " << Flags << " AdapterAddressesLen: " << AdapterAddressesLen <<
", dwRetVal:" << dwRetVal << ", iter: " << iter;
if (pAdapterAddresses) {
free(pAdapterAddresses);
pAdapterAddresses = nullptr;
}
}
return dwRetVal;
}
#endif
QString NetworkUtilities::getGatewayAndIface()
{
#ifdef Q_OS_WIN
constexpr int BUFF_LEN = 100;
char buff[BUFF_LEN] = {'\0'};
QString result;
PIP_ADAPTER_ADDRESSES pAdapterAddresses = nullptr;
DWORD dwRetVal =
GetAdaptersAddressesWrapper(AF_INET, GAA_FLAG_INCLUDE_GATEWAYS, NULL, pAdapterAddresses);
if (dwRetVal != NO_ERROR) {
qDebug() << "ipv4 stack detect GetAdaptersAddresses failed.";
return "";
}
PIP_ADAPTER_ADDRESSES pCurAddress = pAdapterAddresses;
while (pCurAddress) {
PIP_ADAPTER_GATEWAY_ADDRESS_LH gateway = pCurAddress->FirstGatewayAddress;
if (gateway) {
SOCKET_ADDRESS gateway_address = gateway->Address;
if (gateway->Address.lpSockaddr->sa_family == AF_INET) {
sockaddr_in* sa_in = (sockaddr_in*)gateway->Address.lpSockaddr;
QString gw = inet_ntop(AF_INET, &(sa_in->sin_addr), buff, BUFF_LEN);
qDebug() << "gateway IPV4:" << gw;
struct sockaddr_in addr;
if (inet_pton(AF_INET, buff, &addr.sin_addr) == 1) {
qDebug() << "this is true v4 !";
result = gw;
}
}
}
pCurAddress = pCurAddress->Next;
}
free(pAdapterAddresses);
return result;
#endif
#ifdef Q_OS_LINUX
constexpr int BUFFER_SIZE = 100;
int received_bytes = 0, msg_len = 0, route_attribute_len = 0;
int sock = -1, msgseq = 0;
struct nlmsghdr *nlh, *nlmsg;
struct rtmsg *route_entry;
// This struct contain route attributes (route type)
struct rtattr *route_attribute;
char gateway_address[INET_ADDRSTRLEN], interface[IF_NAMESIZE];
char msgbuf[BUFFER_SIZE], buffer[BUFFER_SIZE];
char *ptr = buffer;
struct timeval tv;
if ((sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) {
perror("socket failed");
return "";
}
memset(msgbuf, 0, sizeof(msgbuf));
memset(gateway_address, 0, sizeof(gateway_address));
memset(interface, 0, sizeof(interface));
memset(buffer, 0, sizeof(buffer));
/* point the header and the msg structure pointers into the buffer */
nlmsg = (struct nlmsghdr *)msgbuf;
/* Fill in the nlmsg header*/
nlmsg->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
nlmsg->nlmsg_type = RTM_GETROUTE; // Get the routes from kernel routing table .
nlmsg->nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST; // The message is a request for dump.
nlmsg->nlmsg_seq = msgseq++; // Sequence of the message packet.
nlmsg->nlmsg_pid = getpid(); // PID of process sending the request.
/* 1 Sec Timeout to avoid stall */
tv.tv_sec = 1;
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (struct timeval *)&tv, sizeof(struct timeval));
/* send msg */
if (send(sock, nlmsg, nlmsg->nlmsg_len, 0) < 0) {
perror("send failed");
return "";
}
/* receive response */
do
{
received_bytes = recv(sock, ptr, sizeof(buffer) - msg_len, 0);
if (received_bytes < 0) {
perror("Error in recv");
return "";
}
nlh = (struct nlmsghdr *) ptr;
/* Check if the header is valid */
if((NLMSG_OK(nlmsg, received_bytes) == 0) ||
(nlmsg->nlmsg_type == NLMSG_ERROR))
{
perror("Error in received packet");
return "";
}
/* If we received all data break */
if (nlh->nlmsg_type == NLMSG_DONE)
break;
else {
ptr += received_bytes;
msg_len += received_bytes;
}
/* Break if its not a multi part message */
if ((nlmsg->nlmsg_flags & NLM_F_MULTI) == 0)
break;
}
while ((nlmsg->nlmsg_seq != msgseq) || (nlmsg->nlmsg_pid != getpid()));
/* parse response */
for ( ; NLMSG_OK(nlh, received_bytes); nlh = NLMSG_NEXT(nlh, received_bytes))
{
/* Get the route data */
route_entry = (struct rtmsg *) NLMSG_DATA(nlh);
/* We are just interested in main routing table */
if (route_entry->rtm_table != RT_TABLE_MAIN)
continue;
route_attribute = (struct rtattr *) RTM_RTA(route_entry);
route_attribute_len = RTM_PAYLOAD(nlh);
/* Loop through all attributes */
for ( ; RTA_OK(route_attribute, route_attribute_len);
route_attribute = RTA_NEXT(route_attribute, route_attribute_len))
{
switch(route_attribute->rta_type) {
case RTA_OIF:
if_indextoname(*(int *)RTA_DATA(route_attribute), interface);
break;
case RTA_GATEWAY:
inet_ntop(AF_INET, RTA_DATA(route_attribute),
gateway_address, sizeof(gateway_address));
break;
default:
break;
}
}
if ((*gateway_address) && (*interface)) {
qDebug() << "Gateway " << gateway_address << " for interface " << interface;
break;
}
}
close(sock);
return gateway_address;
#endif
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS)
QString gateway;
int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_FLAGS, RTF_GATEWAY};
int afinet_type[] = {AF_INET, AF_INET6};
for (int ip_type = 0; ip_type <= 1; ip_type++)
{
mib[3] = afinet_type[ip_type];
size_t needed = 0;
if (sysctl(mib, sizeof(mib) / sizeof(int), nullptr, &needed, nullptr, 0) < 0)
return "";
char* buf;
if ((buf = new char[needed]) == 0)
return "";
if (sysctl(mib, sizeof(mib) / sizeof(int), buf, &needed, nullptr, 0) < 0)
{
qDebug() << "sysctl: net.route.0.0.dump";
delete[] buf;
return gateway;
}
struct rt_msghdr* rt;
for (char* p = buf; p < buf + needed; p += rt->rtm_msglen)
{
rt = reinterpret_cast<struct rt_msghdr*>(p);
struct sockaddr* sa = reinterpret_cast<struct sockaddr*>(rt + 1);
struct sockaddr* sa_tab[RTAX_MAX];
for (int i = 0; i < RTAX_MAX; i++)
{
if (rt->rtm_addrs & (1 << i))
{
sa_tab[i] = sa;
sa = reinterpret_cast<struct sockaddr*>(
reinterpret_cast<char*>(sa) +
((sa->sa_len) > 0 ? (1 + (((sa->sa_len) - 1) | (sizeof(long) - 1))) : sizeof(long)));
}
else
{
sa_tab[i] = nullptr;
}
}
if (((rt->rtm_addrs & (RTA_DST | RTA_GATEWAY)) == (RTA_DST | RTA_GATEWAY)) &&
sa_tab[RTAX_DST]->sa_family == afinet_type[ip_type] &&
sa_tab[RTAX_GATEWAY]->sa_family == afinet_type[ip_type])
{
if (afinet_type[ip_type] == AF_INET)
{
if ((reinterpret_cast<struct sockaddr_in*>(sa_tab[RTAX_DST]))->sin_addr.s_addr == 0)
{
char dstStr4[INET_ADDRSTRLEN];
char srcStr4[INET_ADDRSTRLEN];
memcpy(srcStr4,
&(reinterpret_cast<struct sockaddr_in*>(sa_tab[RTAX_GATEWAY]))->sin_addr,
sizeof(struct in_addr));
if (inet_ntop(AF_INET, srcStr4, dstStr4, INET_ADDRSTRLEN) != nullptr)
gateway = dstStr4;
break;
}
}
else if (afinet_type[ip_type] == AF_INET6)
{
if ((reinterpret_cast<struct sockaddr_in*>(sa_tab[RTAX_DST]))->sin_addr.s_addr == 0)
{
char dstStr6[INET6_ADDRSTRLEN];
char srcStr6[INET6_ADDRSTRLEN];
memcpy(srcStr6,
&(reinterpret_cast<struct sockaddr_in6*>(sa_tab[RTAX_GATEWAY]))->sin6_addr,
sizeof(struct in6_addr));
if (inet_ntop(AF_INET6, srcStr6, dstStr6, INET6_ADDRSTRLEN) != nullptr)
gateway = dstStr6;
break;
}
}
}
}
free(buf);
}
return gateway;
#endif
}

View file

@ -0,0 +1,36 @@
#ifndef NETWORKUTILITIES_H
#define NETWORKUTILITIES_H
#include <QRegularExpression>
#include <QRegExp>
#include <QString>
#include <QHostAddress>
class NetworkUtilities : public QObject
{
Q_OBJECT
public:
static QString getIPAddress(const QString &host);
static QString getStringBetween(const QString &s, const QString &a, const QString &b);
static bool checkIPv4Format(const QString &ip);
static bool checkIpSubnetFormat(const QString &ip);
static QString getGatewayAndIface();
// Returns the Interface Index that could Route to dst
static int AdapterIndexTo(const QHostAddress& dst);
static QRegularExpression ipAddressRegExp();
static QRegularExpression ipAddressPortRegExp();
static QRegExp ipAddressWithSubnetRegExp();
static QRegExp ipNetwork24RegExp();
static QRegExp ipPortRegExp();
static QRegExp domainRegExp();
static QString netMaskFromIpWithSubnet(const QString ip);
static QString ipAddressFromIpWithSubnet(const QString ip);
static QStringList summarizeRoutes(const QStringList &ips, const QString cidr);
};
#endif // NETWORKUTILITIES_H

View file

@ -13,11 +13,12 @@ QString amnezia::scriptFolder(amnezia::DockerContainer container)
case DockerContainer::WireGuard: return QLatin1String("wireguard"); case DockerContainer::WireGuard: return QLatin1String("wireguard");
case DockerContainer::Awg: return QLatin1String("awg"); case DockerContainer::Awg: return QLatin1String("awg");
case DockerContainer::Ipsec: return QLatin1String("ipsec"); case DockerContainer::Ipsec: return QLatin1String("ipsec");
case DockerContainer::Xray: return QLatin1String("xray");
case DockerContainer::TorWebSite: return QLatin1String("website_tor"); case DockerContainer::TorWebSite: return QLatin1String("website_tor");
case DockerContainer::Dns: return QLatin1String("dns"); case DockerContainer::Dns: return QLatin1String("dns");
case DockerContainer::Sftp: return QLatin1String("sftp"); case DockerContainer::Sftp: return QLatin1String("sftp");
default: return ""; default: return QString();
} }
} }
@ -47,6 +48,7 @@ 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");
case ProtocolScriptType::xray_template: return QLatin1String("template.json");
default: return QString(); default: return QString();
} }
} }

View file

@ -27,7 +27,8 @@ enum ProtocolScriptType {
container_startup, container_startup,
openvpn_template, openvpn_template,
wireguard_template, wireguard_template,
awg_template awg_template,
xray_template
}; };

View file

@ -23,6 +23,13 @@ namespace libssh {
ErrorCode Client::connectToHost(const ServerCredentials &credentials) ErrorCode Client::connectToHost(const ServerCredentials &credentials)
{ {
if (m_session != nullptr) {
if (!ssh_is_connected(m_session)) {
ssh_free(m_session);
m_session = nullptr;
}
}
if (m_session == nullptr) { if (m_session == nullptr) {
m_session = ssh_new(); m_session = ssh_new();

View file

@ -248,9 +248,10 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
GETVALUE("privateKey", config.m_privateKey, String); GETVALUE("privateKey", config.m_privateKey, String);
GETVALUE("serverPublicKey", config.m_serverPublicKey, String); GETVALUE("serverPublicKey", config.m_serverPublicKey, String);
GETVALUE("serverPskKey", config.m_serverPskKey, String);
GETVALUE("serverPort", config.m_serverPort, Double); GETVALUE("serverPort", config.m_serverPort, Double);
config.m_serverPskKey = obj.value("serverPskKey").toString();
if (!obj.contains("deviceMTU") || obj.value("deviceMTU").toString().toInt() == 0) if (!obj.contains("deviceMTU") || obj.value("deviceMTU").toString().toInt() == 0)
{ {
config.m_deviceMTU = 1420; config.m_deviceMTU = 1420;
@ -373,19 +374,33 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
return false; return false;
} }
if (!obj.value("Jc").isNull() && !obj.value("Jmin").isNull() config.m_killSwitchEnabled = QVariant(obj.value("killSwitchOption").toString()).toBool();
&& !obj.value("Jmax").isNull() && !obj.value("S1").isNull()
&& !obj.value("S2").isNull() && !obj.value("H1").isNull() if (!obj.value("Jc").isNull()) {
&& !obj.value("H2").isNull() && !obj.value("H3").isNull()
&& !obj.value("H4").isNull()) {
config.m_junkPacketCount = obj.value("Jc").toString(); config.m_junkPacketCount = obj.value("Jc").toString();
}
if (!obj.value("Jmin").isNull()) {
config.m_junkPacketMinSize = obj.value("Jmin").toString(); config.m_junkPacketMinSize = obj.value("Jmin").toString();
}
if (!obj.value("Jmax").isNull()) {
config.m_junkPacketMaxSize = obj.value("Jmax").toString(); config.m_junkPacketMaxSize = obj.value("Jmax").toString();
}
if (!obj.value("S1").isNull()) {
config.m_initPacketJunkSize = obj.value("S1").toString(); config.m_initPacketJunkSize = obj.value("S1").toString();
}
if (!obj.value("S2").isNull()) {
config.m_responsePacketJunkSize = obj.value("S2").toString(); config.m_responsePacketJunkSize = obj.value("S2").toString();
}
if (!obj.value("H1").isNull()) {
config.m_initPacketMagicHeader = obj.value("H1").toString(); config.m_initPacketMagicHeader = obj.value("H1").toString();
}
if (!obj.value("H2").isNull()) {
config.m_responsePacketMagicHeader = obj.value("H2").toString(); config.m_responsePacketMagicHeader = obj.value("H2").toString();
}
if (!obj.value("H3").isNull()) {
config.m_underloadPacketMagicHeader = obj.value("H3").toString(); config.m_underloadPacketMagicHeader = obj.value("H3").toString();
}
if (!obj.value("H4").isNull()) {
config.m_transportPacketMagicHeader = obj.value("H4").toString(); config.m_transportPacketMagicHeader = obj.value("H4").toString();
} }

View file

@ -35,7 +35,9 @@ class Daemon : public QObject {
virtual QJsonObject getStatus(); virtual QJsonObject getStatus();
// Callback before any Activating measure is done // Callback before any Activating measure is done
virtual void prepareActivation(const InterfaceConfig& config){ virtual void prepareActivation(const InterfaceConfig& config, int inetAdapterIndex = 0) {
Q_UNUSED(config) };
virtual void activateSplitTunnel(const InterfaceConfig& config, int vpnAdapterIndex = 0) {
Q_UNUSED(config) }; Q_UNUSED(config) };
QString logs(); QString logs();

View file

@ -37,6 +37,7 @@ class InterfaceConfig {
QList<IPAddress> m_allowedIPAddressRanges; QList<IPAddress> m_allowedIPAddressRanges;
QStringList m_excludedAddresses; QStringList m_excludedAddresses;
QStringList m_vpnDisabledApps; QStringList m_vpnDisabledApps;
bool m_killSwitchEnabled;
#if defined(MZ_ANDROID) || defined(MZ_IOS) #if defined(MZ_ANDROID) || defined(MZ_IOS)
QString m_installationId; QString m_installationId;
#endif #endif

View file

@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 8V12" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M12 16H12.01" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 518 B

View file

@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4 22H18C18.5304 22 19.0391 21.7893 19.4142 21.4142C19.7893 21.0391 20 20.5304 20 20V7.5L14.5 2H6C5.46957 2 4.96086 2.21071 4.58579 2.58579C4.21071 2.96086 4 3.46957 4 4V8" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M14 2V8H20" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 15L5 17L9 13" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 581 B

View file

@ -51,6 +51,13 @@
<true/> <true/>
<key>NSCameraUsageDescription</key> <key>NSCameraUsageDescription</key>
<string>Amnezia VPN needs access to the camera for reading QR-codes.</string> <string>Amnezia VPN needs access to the camera for reading QR-codes.</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<false/>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
<key>CFBundleIcons</key> <key>CFBundleIcons</key>
<dict/> <dict/>
<key>CFBundleIcons~ipad</key> <key>CFBundleIcons~ipad</key>

View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>1C8F.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>35F9.1</string>
</array>
</dict>
</array>
</dict>
</plist>

View file

@ -84,11 +84,20 @@ target_sources(networkextension PRIVATE
${CLIENT_ROOT_DIR}/platforms/ios/Log.swift ${CLIENT_ROOT_DIR}/platforms/ios/Log.swift
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift ${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider.swift ${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider.swift
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+OpenVPNAdapterDelegate.swift ${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+WireGuard.swift
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+OpenVPN.swift
${CLIENT_ROOT_DIR}/platforms/ios/WGConfig.swift ${CLIENT_ROOT_DIR}/platforms/ios/WGConfig.swift
${CLIENT_ROOT_DIR}/platforms/ios/iosglue.mm ${CLIENT_ROOT_DIR}/platforms/ios/iosglue.mm
) )
target_sources(networkextension PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/PrivacyInfo.xcprivacy
)
set_property(TARGET networkextension APPEND PROPERTY RESOURCE
${CMAKE_CURRENT_SOURCE_DIR}/PrivacyInfo.xcprivacy
)
## Build wireguard-go-version.h ## Build wireguard-go-version.h
execute_process( execute_process(
COMMAND go list -m golang.zx2c4.com/wireguard COMMAND go list -m golang.zx2c4.com/wireguard

View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>1C8F.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
</array>
</dict>
</plist>

View file

@ -64,6 +64,7 @@ int main(int argc, char *argv[])
qInfo().noquote() << QString("Started %1 version %2 %3").arg(APPLICATION_NAME, APP_VERSION, GIT_COMMIT_HASH); qInfo().noquote() << QString("Started %1 version %2 %3").arg(APPLICATION_NAME, APP_VERSION, GIT_COMMIT_HASH);
qInfo().noquote() << QString("%1 (%2)").arg(QSysInfo::prettyProductName(), QSysInfo::currentCpuArchitecture()); qInfo().noquote() << QString("%1 (%2)").arg(QSysInfo::prettyProductName(), QSysInfo::currentCpuArchitecture());
qInfo().noquote() << QString("SSL backend: %1").arg(QSslSocket::sslLibraryVersionString());
return app.exec(); return app.exec();
} }

View file

@ -117,6 +117,9 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
int splitTunnelType = rawConfig.value("splitTunnelType").toInt(); int splitTunnelType = rawConfig.value("splitTunnelType").toInt();
QJsonArray splitTunnelSites = rawConfig.value("splitTunnelSites").toArray(); QJsonArray splitTunnelSites = rawConfig.value("splitTunnelSites").toArray();
int appSplitTunnelType = rawConfig.value(amnezia::config_key::appSplitTunnelType).toInt();
QJsonArray splitTunnelApps = rawConfig.value(amnezia::config_key::splitTunnelApps).toArray();
QJsonObject wgConfig = rawConfig.value(protocolName + "_config_data").toObject(); QJsonObject wgConfig = rawConfig.value(protocolName + "_config_data").toObject();
QJsonObject json; QJsonObject json;
@ -124,10 +127,14 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
// json.insert("hopindex", QJsonValue((double)hop.m_hopindex)); // json.insert("hopindex", QJsonValue((double)hop.m_hopindex));
json.insert("privateKey", wgConfig.value(amnezia::config_key::client_priv_key)); json.insert("privateKey", wgConfig.value(amnezia::config_key::client_priv_key));
json.insert("deviceIpv4Address", wgConfig.value(amnezia::config_key::client_ip)); json.insert("deviceIpv4Address", wgConfig.value(amnezia::config_key::client_ip));
// todo review wg ipv6
#ifdef Q_OS_MACOS // set up IPv6 unique-local-address, ULA, with "fd00::/8" prefix, not globally routable.
json.insert("deviceIpv6Address", "dead::1"); // this will be default IPv6 gateway, OS recognizes that IPv6 link is local and switches to IPv4.
#endif // Otherwise some OSes (Linux) try IPv6 forever and hang.
// https://en.wikipedia.org/wiki/Unique_local_address (RFC 4193)
// https://man7.org/linux/man-pages/man5/gai.conf.5.html
json.insert("deviceIpv6Address", "fd58:baa6:dead::1"); // simply "dead::1" is globally-routable, don't use it
json.insert("serverPublicKey", wgConfig.value(amnezia::config_key::server_pub_key)); json.insert("serverPublicKey", wgConfig.value(amnezia::config_key::server_pub_key));
json.insert("serverPskKey", wgConfig.value(amnezia::config_key::psk_key)); json.insert("serverPskKey", wgConfig.value(amnezia::config_key::psk_key));
json.insert("serverIpv4AddrIn", wgConfig.value(amnezia::config_key::hostName)); json.insert("serverIpv4AddrIn", wgConfig.value(amnezia::config_key::hostName));
@ -213,12 +220,9 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
json.insert("excludedAddresses", jsExcludedAddresses); json.insert("excludedAddresses", jsExcludedAddresses);
json.insert("vpnDisabledApps", splitTunnelApps);
// QJsonArray splitTunnelApps; json.insert(amnezia::config_key::killSwitchOption, rawConfig.value(amnezia::config_key::killSwitchOption));
// for (const auto& uri : hop.m_vpnDisabledApps) {
// splitTunnelApps.append(QJsonValue(uri));
// }
// json.insert("vpnDisabledApps", splitTunnelApps);
if (protocolName == amnezia::config_key::awg) { if (protocolName == amnezia::config_key::awg) {
json.insert(amnezia::config_key::junkPacketCount, wgConfig.value(amnezia::config_key::junkPacketCount)); json.insert(amnezia::config_key::junkPacketCount, wgConfig.value(amnezia::config_key::junkPacketCount));
@ -230,6 +234,24 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
json.insert(amnezia::config_key::responsePacketMagicHeader, wgConfig.value(amnezia::config_key::responsePacketMagicHeader)); json.insert(amnezia::config_key::responsePacketMagicHeader, wgConfig.value(amnezia::config_key::responsePacketMagicHeader));
json.insert(amnezia::config_key::underloadPacketMagicHeader, wgConfig.value(amnezia::config_key::underloadPacketMagicHeader)); json.insert(amnezia::config_key::underloadPacketMagicHeader, wgConfig.value(amnezia::config_key::underloadPacketMagicHeader));
json.insert(amnezia::config_key::transportPacketMagicHeader, wgConfig.value(amnezia::config_key::transportPacketMagicHeader)); json.insert(amnezia::config_key::transportPacketMagicHeader, wgConfig.value(amnezia::config_key::transportPacketMagicHeader));
} else if (!wgConfig.value(amnezia::config_key::junkPacketCount).isUndefined()
&& !wgConfig.value(amnezia::config_key::junkPacketMinSize).isUndefined()
&& !wgConfig.value(amnezia::config_key::junkPacketMaxSize).isUndefined()
&& !wgConfig.value(amnezia::config_key::initPacketJunkSize).isUndefined()
&& !wgConfig.value(amnezia::config_key::responsePacketJunkSize).isUndefined()
&& !wgConfig.value(amnezia::config_key::initPacketMagicHeader).isUndefined()
&& !wgConfig.value(amnezia::config_key::responsePacketMagicHeader).isUndefined()
&& !wgConfig.value(amnezia::config_key::underloadPacketMagicHeader).isUndefined()
&& !wgConfig.value(amnezia::config_key::transportPacketMagicHeader).isUndefined()) {
json.insert(amnezia::config_key::junkPacketCount, wgConfig.value(amnezia::config_key::junkPacketCount));
json.insert(amnezia::config_key::junkPacketMinSize, wgConfig.value(amnezia::config_key::junkPacketMinSize));
json.insert(amnezia::config_key::junkPacketMaxSize, wgConfig.value(amnezia::config_key::junkPacketMaxSize));
json.insert(amnezia::config_key::initPacketJunkSize, wgConfig.value(amnezia::config_key::initPacketJunkSize));
json.insert(amnezia::config_key::responsePacketJunkSize, wgConfig.value(amnezia::config_key::responsePacketJunkSize));
json.insert(amnezia::config_key::initPacketMagicHeader, wgConfig.value(amnezia::config_key::initPacketMagicHeader));
json.insert(amnezia::config_key::responsePacketMagicHeader, wgConfig.value(amnezia::config_key::responsePacketMagicHeader));
json.insert(amnezia::config_key::underloadPacketMagicHeader, wgConfig.value(amnezia::config_key::underloadPacketMagicHeader));
json.insert(amnezia::config_key::transportPacketMagicHeader, wgConfig.value(amnezia::config_key::transportPacketMagicHeader));
} }
write(json); write(json);

View file

@ -171,9 +171,15 @@ void NetworkWatcher::unsecuredNetwork(const QString& networkName,
} }
QString NetworkWatcher::getCurrentTransport() { QNetworkInformation::Reachability NetworkWatcher::getReachability() {
auto type = m_impl->getTransportType(); if (m_simulatedDisconnection) {
QMetaEnum metaEnum = QMetaEnum::fromType<NetworkWatcherImpl::TransportType>(); return QNetworkInformation::Reachability::Disconnected;
return QString(metaEnum.valueToKey(type)) } else if (QNetworkInformation::instance()) {
.remove("TransportType_", Qt::CaseSensitive); return QNetworkInformation::instance()->reachability();
}
return QNetworkInformation::Reachability::Unknown;
}
void NetworkWatcher::simulateDisconnection(bool simulatedDisconnection) {
m_simulatedDisconnection = simulatedDisconnection;
} }

View file

@ -7,7 +7,8 @@
#include <QElapsedTimer> #include <QElapsedTimer>
#include <QMap> #include <QMap>
#include <QObject> #include <QNetworkInformation>
class NetworkWatcherImpl; class NetworkWatcherImpl;
@ -22,10 +23,13 @@ class NetworkWatcher final : public QObject {
void initialize(); void initialize();
// public for the inspector. // Public for the Inspector.
void unsecuredNetwork(const QString& networkName, const QString& networkId); void unsecuredNetwork(const QString& networkName, const QString& networkId);
// Used for the Inspector. simulateOffline = true to mock being disconnected,
// false to restore.
void simulateDisconnection(bool simulatedDisconnection);
QString getCurrentTransport(); QNetworkInformation::Reachability getReachability();
signals: signals:
void networkChange(); void networkChange();
@ -33,8 +37,6 @@ class NetworkWatcher final : public QObject {
private: private:
void settingsChanged(); void settingsChanged();
// void notificationClicked(NotificationHandler::Message message);
private: private:
bool m_active = false; bool m_active = false;
bool m_reportUnsecuredNetwork = false; bool m_reportUnsecuredNetwork = false;
@ -46,6 +48,9 @@ class NetworkWatcher final : public QObject {
// This is used to connect NotificationHandler lazily. // This is used to connect NotificationHandler lazily.
bool m_firstNotification = true; bool m_firstNotification = true;
// Used to simulate network disconnection in the Inspector
bool m_simulatedDisconnection = false;
}; };
#endif // NETWORKWATCHER_H #endif // NETWORKWATCHER_H

View file

@ -5,6 +5,7 @@
#ifndef NETWORKWATCHERIMPL_H #ifndef NETWORKWATCHERIMPL_H
#define NETWORKWATCHERIMPL_H #define NETWORKWATCHERIMPL_H
#include <QNetworkInformation>
#include <QObject> #include <QObject>
class NetworkWatcherImpl : public QObject { class NetworkWatcherImpl : public QObject {
@ -33,9 +34,6 @@ class NetworkWatcherImpl : public QObject {
}; };
Q_ENUM(TransportType); Q_ENUM(TransportType);
// Returns the current type of Network Connection
virtual TransportType getTransportType() = 0;
signals: signals:
// Fires when the Device Connects to an unsecured Network // Fires when the Device Connects to an unsecured Network
void unsecuredNetwork(const QString& networkName, const QString& networkId); void unsecuredNetwork(const QString& networkName, const QString& networkId);
@ -44,9 +42,6 @@ class NetworkWatcherImpl : public QObject {
// too. // too.
void networkChanged(QString newBSSID); void networkChanged(QString newBSSID);
// Fired when the Device changed the Type of Transport
void transportChanged(NetworkWatcherImpl::TransportType transportType);
private: private:
bool m_active = false; bool m_active = false;
}; };

View file

@ -2,6 +2,9 @@
#include <QJsonDocument> #include <QJsonDocument>
#include <QQmlFile> #include <QQmlFile>
#include <QEventLoop> #include <QEventLoop>
#include <QImage>
#include <android/bitmap.h>
#include "android_controller.h" #include "android_controller.h"
#include "android_utils.h" #include "android_utils.h"
@ -90,6 +93,7 @@ bool AndroidController::initialize()
{"onServiceDisconnected", "()V", reinterpret_cast<void *>(onServiceDisconnected)}, {"onServiceDisconnected", "()V", reinterpret_cast<void *>(onServiceDisconnected)},
{"onServiceError", "()V", reinterpret_cast<void *>(onServiceError)}, {"onServiceError", "()V", reinterpret_cast<void *>(onServiceError)},
{"onVpnPermissionRejected", "()V", reinterpret_cast<void *>(onVpnPermissionRejected)}, {"onVpnPermissionRejected", "()V", reinterpret_cast<void *>(onVpnPermissionRejected)},
{"onNotificationStateChanged", "()V", reinterpret_cast<void *>(onNotificationStateChanged)},
{"onVpnStateChanged", "(I)V", reinterpret_cast<void *>(onVpnStateChanged)}, {"onVpnStateChanged", "(I)V", reinterpret_cast<void *>(onVpnStateChanged)},
{"onStatisticsUpdate", "(JJ)V", reinterpret_cast<void *>(onStatisticsUpdate)}, {"onStatisticsUpdate", "(JJ)V", reinterpret_cast<void *>(onStatisticsUpdate)},
{"onFileOpened", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onFileOpened)}, {"onFileOpened", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onFileOpened)},
@ -170,14 +174,6 @@ QString AndroidController::openFile(const QString &filter)
return fileName; return fileName;
} }
void AndroidController::setNotificationText(const QString &title, const QString &message, int timerSec)
{
callActivityMethod("setNotificationText", "(Ljava/lang/String;Ljava/lang/String;I)V",
QJniObject::fromString(title).object<jstring>(),
QJniObject::fromString(message).object<jstring>(),
(jint) timerSec);
}
bool AndroidController::isCameraPresent() bool AndroidController::isCameraPresent()
{ {
return callActivityMethod<jboolean>("isCameraPresent", "()Z"); return callActivityMethod<jboolean>("isCameraPresent", "()Z");
@ -209,6 +205,61 @@ void AndroidController::setScreenshotsEnabled(bool enabled)
callActivityMethod("setScreenshotsEnabled", "(Z)V", enabled); callActivityMethod("setScreenshotsEnabled", "(Z)V", enabled);
} }
void AndroidController::minimizeApp()
{
callActivityMethod("minimizeApp", "()V");
}
QJsonArray AndroidController::getAppList()
{
QJniObject appList = callActivityMethod<jstring>("getAppList", "()Ljava/lang/String;");
QJsonArray jsonAppList = QJsonDocument::fromJson(appList.toString().toUtf8()).array();
return jsonAppList;
}
QPixmap AndroidController::getAppIcon(const QString &package, QSize *size, const QSize &requestedSize)
{
QJniObject bitmap = callActivityMethod<jobject>("getAppIcon", "(Ljava/lang/String;II)Landroid/graphics/Bitmap;",
QJniObject::fromString(package).object<jstring>(),
requestedSize.width(), requestedSize.height());
QJniEnvironment env;
AndroidBitmapInfo info;
if (AndroidBitmap_getInfo(env.jniEnv(), bitmap.object(), &info) != ANDROID_BITMAP_RESULT_SUCCESS) return {};
void *pixels;
if (AndroidBitmap_lockPixels(env.jniEnv(), bitmap.object(), &pixels) != ANDROID_BITMAP_RESULT_SUCCESS) return {};
int width = info.width;
int height = info.height;
size->setWidth(width);
size->setHeight(height);
QImage image(width, height, QImage::Format_RGBA8888);
if (info.stride == uint32_t(image.bytesPerLine())) {
memcpy((void *) image.constBits(), pixels, info.stride * height);
} else {
auto *bmpPtr = static_cast<uchar *>(pixels);
for (int i = 0; i < height; i++, bmpPtr += info.stride)
memcpy((void *) image.constScanLine(i), bmpPtr, width);
}
if (AndroidBitmap_unlockPixels(env.jniEnv(), bitmap.object()) != ANDROID_BITMAP_RESULT_SUCCESS) return {};
return QPixmap::fromImage(image);
}
bool AndroidController::isNotificationPermissionGranted()
{
return callActivityMethod<jboolean>("isNotificationPermissionGranted", "()Z");
}
void AndroidController::requestNotificationPermission()
{
callActivityMethod("requestNotificationPermission", "()V");
}
// Moving log processing to the Android side // Moving log processing to the Android side
jclass AndroidController::log; jclass AndroidController::log;
jmethodID AndroidController::logDebug; jmethodID AndroidController::logDebug;
@ -361,6 +412,15 @@ void AndroidController::onVpnPermissionRejected(JNIEnv *env, jobject thiz)
emit AndroidController::instance()->vpnPermissionRejected(); emit AndroidController::instance()->vpnPermissionRejected();
} }
// static
void AndroidController::onNotificationStateChanged(JNIEnv *env, jobject thiz)
{
Q_UNUSED(env);
Q_UNUSED(thiz);
emit AndroidController::instance()->notificationStateChanged();
}
// static // static
void AndroidController::onVpnStateChanged(JNIEnv *env, jobject thiz, jint stateCode) void AndroidController::onVpnStateChanged(JNIEnv *env, jobject thiz, jint stateCode)
{ {

View file

@ -2,6 +2,7 @@
#define ANDROID_CONTROLLER_H #define ANDROID_CONTROLLER_H
#include <QJniObject> #include <QJniObject>
#include <QPixmap>
#include "protocols/vpnprotocol.h" #include "protocols/vpnprotocol.h"
@ -31,7 +32,6 @@ public:
ErrorCode start(const QJsonObject &vpnConfig); ErrorCode start(const QJsonObject &vpnConfig);
void stop(); void stop();
void resetLastServer(int serverIndex); void resetLastServer(int serverIndex);
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(); bool isCameraPresent();
@ -40,6 +40,11 @@ public:
void exportLogsFile(const QString &fileName); void exportLogsFile(const QString &fileName);
void clearLogs(); void clearLogs();
void setScreenshotsEnabled(bool enabled); void setScreenshotsEnabled(bool enabled);
void minimizeApp();
QJsonArray getAppList();
QPixmap getAppIcon(const QString &package, QSize *size, const QSize &requestedSize);
bool isNotificationPermissionGranted();
void requestNotificationPermission();
static bool initLogging(); static bool initLogging();
static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message); static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message);
@ -50,6 +55,7 @@ signals:
void serviceDisconnected(); void serviceDisconnected();
void serviceError(); void serviceError();
void vpnPermissionRejected(); void vpnPermissionRejected();
void notificationStateChanged();
void vpnStateChanged(ConnectionState state); void vpnStateChanged(ConnectionState state);
void statisticsUpdated(quint64 rxBytes, quint64 txBytes); void statisticsUpdated(quint64 rxBytes, quint64 txBytes);
void fileOpened(QString uri); void fileOpened(QString uri);
@ -77,6 +83,7 @@ private:
static void onServiceDisconnected(JNIEnv *env, jobject thiz); static void onServiceDisconnected(JNIEnv *env, jobject thiz);
static void onServiceError(JNIEnv *env, jobject thiz); static void onServiceError(JNIEnv *env, jobject thiz);
static void onVpnPermissionRejected(JNIEnv *env, jobject thiz); static void onVpnPermissionRejected(JNIEnv *env, jobject thiz);
static void onNotificationStateChanged(JNIEnv *env, jobject thiz);
static void onVpnStateChanged(JNIEnv *env, jobject thiz, jint stateCode); static void onVpnStateChanged(JNIEnv *env, jobject thiz, jint stateCode);
static void onStatisticsUpdate(JNIEnv *env, jobject thiz, jlong rxBytes, jlong txBytes); static void onStatisticsUpdate(JNIEnv *env, jobject thiz, jlong rxBytes, jlong txBytes);
static void onConfigImported(JNIEnv *env, jobject thiz, jstring data); static void onConfigImported(JNIEnv *env, jobject thiz, jstring data);

View file

@ -1,21 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "android_notificationhandler.h"
#include "platforms/android/android_controller.h"
AndroidNotificationHandler::AndroidNotificationHandler(QObject* parent)
: NotificationHandler(parent) {
}
AndroidNotificationHandler::~AndroidNotificationHandler() {
}
void AndroidNotificationHandler::notify(NotificationHandler::Message type,
const QString& title,
const QString& message, int timerMsec) {
Q_UNUSED(type);
qDebug() << "Send notification - " << message;
AndroidController::instance()->setNotificationText(title, message,
timerMsec / 1000);
}

View file

@ -1,24 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef ANDROIDNOTIFICATIONHANDLER_H
#define ANDROIDNOTIFICATIONHANDLER_H
#include "ui/notificationhandler.h"
#include <QObject>
class AndroidNotificationHandler final : public NotificationHandler {
Q_DISABLE_COPY_MOVE(AndroidNotificationHandler)
public:
AndroidNotificationHandler(QObject* parent);
~AndroidNotificationHandler();
protected:
void notify(Message type, const QString& title, const QString& message,
int timerMsec) override;
};
#endif // ANDROIDNOTIFICATIONHANDLER_H

View file

@ -13,10 +13,6 @@ class DummyNetworkWatcher final : public NetworkWatcherImpl {
~DummyNetworkWatcher(); ~DummyNetworkWatcher();
void initialize() override; void initialize() override;
NetworkWatcherImpl::TransportType getTransportType() override {
return TransportType_Other;
};
}; };
#endif // DUMMYNETWORKWATCHER_H #endif // DUMMYNETWORKWATCHER_H

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