diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index d84430068..acfbaafa6 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -236,8 +236,14 @@ file(GLOB UI_MODELS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/models/services/*.cpp ) -file(GLOB UI_CONTROLLERS_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/controllers/*.h) -file(GLOB UI_CONTROLLERS_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ui/controllers/*.cpp) +file(GLOB UI_CONTROLLERS_H CONFIGURE_DEPENDS + ${CMAKE_CURRENT_LIST_DIR}/ui/controllers/*.h + ${CMAKE_CURRENT_LIST_DIR}/ui/models/localServices/*.h +) +file(GLOB UI_CONTROLLERS_CPP CONFIGURE_DEPENDS + ${CMAKE_CURRENT_LIST_DIR}/ui/controllers/*.cpp + ${CMAKE_CURRENT_LIST_DIR}/ui/models/localServices/*.cpp +) set(HEADERS ${HEADERS} ${COMMON_FILES_H} @@ -261,13 +267,13 @@ if(WIN32) ) set(HEADERS ${HEADERS} - ${CMAKE_CURRENT_LIST_DIR}/protocols/goodbyedpi.h ${CMAKE_CURRENT_LIST_DIR}/protocols/ikev2_vpn_protocol_windows.h + ${CMAKE_CURRENT_LIST_DIR}/localServices/goodByeDpi.h ) set(SOURCES ${SOURCES} - ${CMAKE_CURRENT_LIST_DIR}/protocols/goodbyedpi.cpp ${CMAKE_CURRENT_LIST_DIR}/protocols/ikev2_vpn_protocol_windows.cpp + ${CMAKE_CURRENT_LIST_DIR}/localServices/goodByeDpi.cpp ) set(RESOURCES ${RESOURCES} diff --git a/client/amnezia_application.cpp b/client/amnezia_application.cpp index b8ce5b004..dbe1acfc7 100644 --- a/client/amnezia_application.cpp +++ b/client/amnezia_application.cpp @@ -235,6 +235,7 @@ void AmneziaApplication::registerTypes() Vpn::declareQmlVpnConnectionStateEnum(); PageLoader::declareQmlPageEnum(); + PageLoader::declareQmlFolderEnum(); } void AmneziaApplication::loadFonts() @@ -455,4 +456,13 @@ void AmneziaApplication::initControllers() m_systemController.reset(new SystemController(m_settings)); m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get()); + + m_localServicesController.reset(new LocalServicesController(m_serversModel, m_settings)); + m_engine->rootContext()->setContextProperty("LocalServicesController", m_localServicesController.get()); + connect(m_connectionController.get(), &ConnectionController::startLocalService, m_localServicesController.get(), + &LocalServicesController::start); + connect(m_connectionController.get(), &ConnectionController::stopLocalService, m_localServicesController.get(), + &LocalServicesController::stop); + connect(m_localServicesController.get(), &LocalServicesController::serviceStateChanged, m_connectionController.get(), + &ConnectionController::connectionStateChanged); } diff --git a/client/amnezia_application.h b/client/amnezia_application.h index 6fb61f44a..1675ad4ea 100644 --- a/client/amnezia_application.h +++ b/client/amnezia_application.h @@ -24,6 +24,7 @@ #include "ui/controllers/sitesController.h" #include "ui/controllers/systemController.h" #include "ui/controllers/appSplitTunnelingController.h" +#include "ui/controllers/localServicesController.h" #include "ui/models/containers_model.h" #include "ui/models/languageModel.h" #include "ui/models/protocols/cloakConfigModel.h" @@ -136,6 +137,7 @@ private: QScopedPointer m_sitesController; QScopedPointer m_systemController; QScopedPointer m_appSplitTunnelingController; + QScopedPointer m_localServicesController; QNetworkAccessManager *m_nam; diff --git a/client/containers/containers_defs.cpp b/client/containers/containers_defs.cpp index 5720b6d78..06f8fd0c6 100644 --- a/client/containers/containers_defs.cpp +++ b/client/containers/containers_defs.cpp @@ -53,8 +53,6 @@ QVector ContainerProps::protocolsForContainer(amnezia::DockerCon switch (container) { case DockerContainer::None: return {}; - case DockerContainer::GoodbyeDPI: return { Proto::GoodyeDPI }; - case DockerContainer::OpenVpn: return { Proto::OpenVpn }; case DockerContainer::ShadowSocks: return { Proto::OpenVpn, Proto::ShadowSocks }; @@ -99,7 +97,6 @@ QMap ContainerProps::containerHumanNames() { DockerContainer::Xray, "XRay" }, { DockerContainer::Ipsec, QObject::tr("IPsec") }, { DockerContainer::SSXray, "Shadowsocks"}, - { DockerContainer::GoodbyeDPI, "GoodbyeDPI"}, { DockerContainer::TorWebSite, QObject::tr("Website in Tor network") }, { DockerContainer::Dns, QObject::tr("AmneziaDNS") }, @@ -139,9 +136,7 @@ QMap ContainerProps::containerDescriptions() { DockerContainer::Sftp, QObject::tr("Create a file vault on your server to securely store and transfer files.") }, { DockerContainer::Socks5Proxy, - QObject::tr("") } , - { DockerContainer::GoodbyeDPI, - QObject::tr("GoodbyeDPI — Deep Packet Inspection circumvention utility") }}; + QObject::tr("") } }; } QMap ContainerProps::containerDetailedDescriptions() @@ -250,10 +245,7 @@ QMap ContainerProps::containerDetailedDescriptions() "You will be able to access it using\n FileZilla or other SFTP clients, " "as well as mount the disk on your device to access\n it directly from your device.\n\n" "For more detailed information, you can\n find it in the support section under \"Create SFTP file storage.\" ") }, - { DockerContainer::Socks5Proxy, QObject::tr("SOCKS5 proxy server") }, - { DockerContainer::GoodbyeDPI, QObject::tr("This software designed to bypass Deep Packet Inspection systems found in many Internet Service Providers which block access to certain websites. \n" - "It handles DPI connected using optical splitter or port mirroring (Passive DPI) which do not block any data but just replying faster than requested destination," - "and Active DPI connected in sequence.") } + { DockerContainer::Socks5Proxy, QObject::tr("SOCKS5 proxy server") } } }; } @@ -280,7 +272,6 @@ Proto ContainerProps::defaultProtocol(DockerContainer c) case DockerContainer::Dns: return Proto::Dns; case DockerContainer::Sftp: return Proto::Sftp; case DockerContainer::Socks5Proxy: return Proto::Socks5Proxy; - case DockerContainer::GoodbyeDPI: return Proto::GoodyeDPI; default: return Proto::Any; } } @@ -387,7 +378,6 @@ bool ContainerProps::isShareable(DockerContainer container) case DockerContainer::Dns: return false; case DockerContainer::Sftp: return false; case DockerContainer::Socks5Proxy: return false; - case DockerContainer::GoodbyeDPI: return false; default: return true; } } @@ -413,7 +403,6 @@ int ContainerProps::installPageOrder(DockerContainer container) case DockerContainer::Xray: return 3; case DockerContainer::Ipsec: return 7; case DockerContainer::SSXray: return 8; - case DockerContainer::GoodbyeDPI: return 9; default: return 0; } } diff --git a/client/containers/containers_defs.h b/client/containers/containers_defs.h index 6a6a5719f..0d7f9aa15 100644 --- a/client/containers/containers_defs.h +++ b/client/containers/containers_defs.h @@ -24,7 +24,6 @@ namespace amnezia Ipsec, Xray, SSXray, - GoodbyeDPI, // non-vpn TorWebSite, diff --git a/client/core/controllers/vpnConfigurationController.cpp b/client/core/controllers/vpnConfigurationController.cpp index 9cebf8738..818cf57e6 100644 --- a/client/core/controllers/vpnConfigurationController.cpp +++ b/client/core/controllers/vpnConfigurationController.cpp @@ -34,8 +34,7 @@ ErrorCode VpnConfigurationsController::createProtocolConfigForContainer(const Se { ErrorCode errorCode = ErrorCode::NoError; - if (ContainerProps::containerService(container) == ServiceType::Other || - container == DockerContainer::GoodbyeDPI) { + if (ContainerProps::containerService(container) == ServiceType::Other) { return errorCode; } @@ -62,8 +61,7 @@ ErrorCode VpnConfigurationsController::createProtocolConfigString(const bool isA { ErrorCode errorCode = ErrorCode::NoError; - if (ContainerProps::containerService(container) == ServiceType::Other || - container == DockerContainer::GoodbyeDPI) { + if (ContainerProps::containerService(container) == ServiceType::Other) { return errorCode; } @@ -84,8 +82,7 @@ QJsonObject VpnConfigurationsController::createVpnConfiguration(const QPairwaitForSource(1000); + if (!m_goodbyeDPIProcess->isInitialized()) { + qWarning() << "IpcProcess replica is not connected!"; + return amnezia::ErrorCode::AmneziaServiceConnectionFailed; + } + + m_goodbyeDPIProcess->setProgram(amnezia::PermittedProcess::GoodbyeDPI); + + QStringList arguments; + arguments << QString("-%1").arg(modset); + arguments << QString("--blacklist %1").arg(blackListFile); + + m_goodbyeDPIProcess->setArguments(arguments); + qDebug() << arguments.join(" "); + + connect(m_goodbyeDPIProcess.data(), &PrivilegedProcess::errorOccurred, + [&](QProcess::ProcessError error) { qDebug() << "PrivilegedProcess errorOccurred" << error; }); + + connect(m_goodbyeDPIProcess.data(), &PrivilegedProcess::stateChanged, [&](QProcess::ProcessState newState) { + qDebug() << "PrivilegedProcess stateChanged" << newState; + if (newState == QProcess::Running) { + qDebug() << "PrivilegedProcess running"; + emit serviceStateChanged(Vpn::ConnectionState::Connected); + } + }); + + connect(m_goodbyeDPIProcess.data(), &PrivilegedProcess::finished, this, [&]() { + qDebug() << "PrivilegedProcess finished"; + emit serviceStateChanged(Vpn::ConnectionState::Disconnected); + }); + + m_goodbyeDPIProcess->start(); + return amnezia::ErrorCode::NoError; +} + +void GoodByeDpi::stop() +{ + if (m_goodbyeDPIProcess) { + m_goodbyeDPIProcess->close(); + } +} diff --git a/client/localServices/goodByeDpi.h b/client/localServices/goodByeDpi.h new file mode 100644 index 000000000..c2419e0e6 --- /dev/null +++ b/client/localServices/goodByeDpi.h @@ -0,0 +1,25 @@ +#ifndef GOODBYEDPI_H +#define GOODBYEDPI_H + +#include + +#include "core/defs.h" +#include "core/privileged_process.h" +#include "protocols/vpnprotocol.h" + +class GoodByeDpi : public QObject +{ + Q_OBJECT +public: + explicit GoodByeDpi(QObject *parent = nullptr); + + amnezia::ErrorCode start(const QString &blackListFile, const int modset); + void stop(); + +private: + QSharedPointer m_goodbyeDPIProcess; +signals: + void serviceStateChanged(Vpn::ConnectionState state); +}; + +#endif // GOODBYEDPI_H diff --git a/client/protocols/goodbyedpi.cpp b/client/protocols/goodbyedpi.cpp deleted file mode 100644 index fff45ca41..000000000 --- a/client/protocols/goodbyedpi.cpp +++ /dev/null @@ -1,85 +0,0 @@ -#include -#include -#include - -#include - -#include - -#include "logger.h" -#include "goodbyedpi.h" -#include "utilities.h" - - -GoodbyeDPIProtocol::GoodbyeDPIProtocol(const QJsonObject &configuration, QObject* parent) : - VpnProtocol(configuration, parent) -{ - qDebug() << "GoodbyeDPIProtocol::GoodbyeDPIProtocol()"; -} - -GoodbyeDPIProtocol::~GoodbyeDPIProtocol() -{ - qDebug() << "GoodbyeDPIProtocol::~GoodbyeDPIProtocol()"; - GoodbyeDPIProtocol::stop(); -} - -void GoodbyeDPIProtocol::stop() -{ - if (m_goodbyeDPIProcess) { - m_goodbyeDPIProcess->close(); - } - setConnectionState(Vpn::ConnectionState::Disconnected); -} - -ErrorCode GoodbyeDPIProtocol::start() -{ - qDebug() << "GoodbyeDPIProtocol::start()"; - - if (!QFileInfo::exists(Utils::goodbyedpiPath())) { - setLastError(ErrorCode::GoodByeDPIExecutableMissing); - return lastError(); - } - - m_goodbyeDPIProcess = IpcClient::CreatePrivilegedProcess(); - - if (!m_goodbyeDPIProcess) { - setLastError(ErrorCode::AmneziaServiceConnectionFailed); - return ErrorCode::AmneziaServiceConnectionFailed; - } - - m_goodbyeDPIProcess->waitForSource(1000); - if (!m_goodbyeDPIProcess->isInitialized()) { - qWarning() << "IpcProcess replica is not connected!"; - setLastError(ErrorCode::AmneziaServiceConnectionFailed); - return ErrorCode::AmneziaServiceConnectionFailed; - } - - m_goodbyeDPIProcess->setProgram(PermittedProcess::GoodbyeDPI); - - QStringList arguments({"-9", "--blacklist", QCoreApplication::applicationDirPath() + "/goodbyedpi/russia-blacklist.txt", - "--blacklist", QCoreApplication::applicationDirPath() + "/goodbyedpi/russia-youtube.txt"}); - - m_goodbyeDPIProcess->setArguments(arguments); - qDebug() << arguments.join(" "); - - connect(m_goodbyeDPIProcess.data(), &PrivilegedProcess::errorOccurred, - [&](QProcess::ProcessError error) { qDebug() << "PrivilegedProcess errorOccurred" << error; }); - - connect(m_goodbyeDPIProcess.data(), &PrivilegedProcess::stateChanged, - [&](QProcess::ProcessState newState) { - qDebug() << "PrivilegedProcess stateChanged" << newState; - if (newState == QProcess::Running) - { - setConnectionState(Vpn::ConnectionState::Connected); - } - }); - - connect(m_goodbyeDPIProcess.data(), &PrivilegedProcess::finished, this, - [&]() { - setConnectionState(Vpn::ConnectionState::Disconnected); - }); - - - m_goodbyeDPIProcess->start(); - return ErrorCode::NoError; -} diff --git a/client/protocols/goodbyedpi.h b/client/protocols/goodbyedpi.h deleted file mode 100644 index beb16d1ad..000000000 --- a/client/protocols/goodbyedpi.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef GOODBYEDPI_H -#define GOODBYEDPI_H - -#include -#include -#include -#include -#include - -#include "vpnprotocol.h" -#include "core/ipcclient.h" - -class GoodbyeDPIProtocol : public VpnProtocol -{ - Q_OBJECT - -public: - explicit GoodbyeDPIProtocol(const QJsonObject& configuration, QObject* parent = nullptr); - virtual ~GoodbyeDPIProtocol() override; - - ErrorCode start() override; - void stop() override; - -private: - QSharedPointer m_goodbyeDPIProcess; -}; - -#endif // GOODBYEDPI_H diff --git a/client/protocols/protocols_defs.cpp b/client/protocols/protocols_defs.cpp index a440f24a2..ac5bb1ad8 100644 --- a/client/protocols/protocols_defs.cpp +++ b/client/protocols/protocols_defs.cpp @@ -75,7 +75,6 @@ QMap ProtocolProps::protocolHumanNames() { Proto::SSXray, "Shadowsocks"}, - { Proto::GoodyeDPI, "GoodbyeDPI"}, { Proto::TorWebSite, "Website in Tor network" }, { Proto::Dns, "DNS Service" }, { Proto::Sftp, QObject::tr("SFTP service") }, @@ -100,7 +99,6 @@ amnezia::ServiceType ProtocolProps::protocolService(Proto p) case Proto::Awg: return ServiceType::Vpn; case Proto::Ikev2: return ServiceType::Vpn; case Proto::Xray: return ServiceType::Vpn; - case Proto::GoodyeDPI: return ServiceType::Vpn; case Proto::TorWebSite: return ServiceType::Other; case Proto::Dns: return ServiceType::Other; diff --git a/client/protocols/protocols_defs.h b/client/protocols/protocols_defs.h index 46ec28e24..0ea264d74 100644 --- a/client/protocols/protocols_defs.h +++ b/client/protocols/protocols_defs.h @@ -100,6 +100,8 @@ namespace amnezia constexpr char clientId[] = "clientId"; + constexpr char isGoodbyeDpi[] = "is_goodbye_dpi"; + } namespace protocols diff --git a/client/protocols/vpnprotocol.cpp b/client/protocols/vpnprotocol.cpp index e0cdd5bb9..056089b8f 100644 --- a/client/protocols/vpnprotocol.cpp +++ b/client/protocols/vpnprotocol.cpp @@ -14,7 +14,6 @@ #ifdef Q_OS_WINDOWS #include "ikev2_vpn_protocol_windows.h" - #include "goodbyedpi.h" #endif VpnProtocol::VpnProtocol(const QJsonObject &configuration, QObject *parent) @@ -109,7 +108,6 @@ VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject & switch (container) { #if defined(Q_OS_WINDOWS) case DockerContainer::Ipsec: return new Ikev2Protocol(configuration); - case DockerContainer::GoodbyeDPI: return new GoodbyeDPIProtocol(configuration); #endif #if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) case DockerContainer::OpenVpn: return new OpenVpnProtocol(configuration); diff --git a/client/resources.qrc b/client/resources.qrc index fed42a004..6c8ebcfeb 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -218,6 +218,7 @@ ui/qml/Pages2/PageSettingsApiLanguageList.qml images/controls/archive-restore.svg images/controls/help-circle.svg + ui/qml/Pages2/LocalServices/PageGoodByeDpiSettings.qml images/flagKit/ZW.svg diff --git a/client/settings.cpp b/client/settings.cpp index 490ede526..470e75791 100644 --- a/client/settings.cpp +++ b/client/settings.cpp @@ -523,3 +523,33 @@ QString Settings::getGatewayEndpoint() { return m_gatewayEndpoint; } + +void Settings::setGoodbyeDpiBlackListFile(const QString &file) +{ + setValue("Conf/goodbyeDpiBlackListFile", file); +} + +QString Settings::getGoodbyeDpiBlackListFile() const +{ + return value("Conf/goodbyeDpiBlackListFile").toString(); +} + +void Settings::toggleGoodbyeDpi(bool enable) +{ + setValue("Conf/isGoodbyeDpiEnabled", enable); +} + +bool Settings::isGoodbyeDpiEnabled() const +{ + return value("Conf/isGoodbyeDpiEnabled", false).toBool(); +} + +void Settings::setGoodbyeDpiModset(const int modset) +{ + setValue("Conf/goodbyeDpiModset", modset); +} + +int Settings::getGoodbyeDpiModset() const +{ + return value("Conf/goodbyeDpiModset", 9).toInt(); +} diff --git a/client/settings.h b/client/settings.h index 55a3d0575..6fe9dc7eb 100644 --- a/client/settings.h +++ b/client/settings.h @@ -113,7 +113,10 @@ public: QString routeModeString(RouteMode mode) const; RouteMode routeMode() const; - void setRouteMode(RouteMode mode) { setValue("Conf/routeMode", mode); } + void setRouteMode(RouteMode mode) + { + setValue("Conf/routeMode", mode); + } bool isSitesSplitTunnelingEnabled() const; void setSitesSplitTunnelingEnabled(bool enabled); @@ -219,6 +222,15 @@ public: void setGatewayEndpoint(const QString &endpoint); QString getGatewayEndpoint(); + void setGoodbyeDpiBlackListFile(const QString &file); + QString getGoodbyeDpiBlackListFile() const; + + void toggleGoodbyeDpi(bool enable); + bool isGoodbyeDpiEnabled() const; + + void setGoodbyeDpiModset(const int modset); + int getGoodbyeDpiModset() const; + signals: void saveLogsChanged(bool enabled); void screenshotsEnabledChanged(bool enabled); diff --git a/client/ui/controllers/connectionController.cpp b/client/ui/controllers/connectionController.cpp index c7f950007..d1b2d586a 100644 --- a/client/ui/controllers/connectionController.cpp +++ b/client/ui/controllers/connectionController.cpp @@ -44,6 +44,13 @@ void ConnectionController::openConnection() int serverIndex = m_serversModel->getDefaultServerIndex(); QJsonObject serverConfig = m_serversModel->getServerConfig(serverIndex); + + const auto isGoodbyeDpi = serverConfig.value(config_key::isGoodbyeDpi).toBool(false); + if (isGoodbyeDpi) { + emit startLocalService(); + return; + } + auto configVersion = serverConfig.value(config_key::configVersion).toInt(); emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Preparing); @@ -65,6 +72,15 @@ void ConnectionController::openConnection() void ConnectionController::closeConnection() { + int serverIndex = m_serversModel->getDefaultServerIndex(); + QJsonObject serverConfig = m_serversModel->getServerConfig(serverIndex); + + const auto isGoodbyeDpi = serverConfig.value(config_key::isGoodbyeDpi).toBool(false); + if (isGoodbyeDpi) { + emit stopLocalService(); + return; + } + emit disconnectFromVpn(); } diff --git a/client/ui/controllers/connectionController.h b/client/ui/controllers/connectionController.h index 25d4d74a1..5104ca6e9 100644 --- a/client/ui/controllers/connectionController.h +++ b/client/ui/controllers/connectionController.h @@ -61,6 +61,9 @@ signals: void updateApiConfigFromTelegram(); void configFromApiUpdated(); + void startLocalService(); + void stopLocalService(); + private: Vpn::ConnectionState getCurrentConnectionState(); bool isProtocolConfigExists(const QJsonObject &containerConfig, const DockerContainer container); diff --git a/client/ui/controllers/localServicesController.cpp b/client/ui/controllers/localServicesController.cpp new file mode 100644 index 000000000..36e23de3e --- /dev/null +++ b/client/ui/controllers/localServicesController.cpp @@ -0,0 +1,101 @@ +#include "localServicesController.h" + +namespace +{ + // Logger logger("ServerController"); +} + +LocalServicesController::LocalServicesController(const QSharedPointer &serversModel, + const std::shared_ptr &settings, QObject *parent) + : QObject(parent), m_serversModel(serversModel), m_settings(settings) +{ + connect(&m_goodbyeDpiService, &GoodByeDpi::serviceStateChanged, this, &LocalServicesController::serviceStateChanged); +} + +LocalServicesController::~LocalServicesController() +{ + m_goodbyeDpiService.stop(); +} + +void LocalServicesController::toggleGoodbyeDpi(bool enable) +{ + if (enable) { + // auto file = getGoodbyeDpiBlackListFile(); + // auto modset = getGoodbyeDpiModset(); + // auto errorCode = ErrorCode::NoError;//m_goodbyeDpiService.start(file, modset); + // if (errorCode != ErrorCode::NoError) { + // emit errorOccurred(errorCode); + // } else { + + QJsonObject server; + server.insert(config_key::isGoodbyeDpi, true); + server.insert(config_key::description, "GoodbyeDPI service"); + server.insert(config_key::name, "GoodbyeDPI"); + m_serversModel->addServer(server); + m_serversModel->setDefaultServerIndex(m_serversModel->getServersCount() - 1); + + m_settings->toggleGoodbyeDpi(true); + emit toggleGoodbyeDpiFinished(tr("GoodbyeDPI added to home page")); + // } + } else { + // m_goodbyeDpiService.stop(); + + for (int i = 0; i < m_serversModel->getServersCount(); i++) { + if (m_serversModel->getServerConfig(i).value(config_key::isGoodbyeDpi).toBool(false)) { + m_serversModel->setProcessedServerIndex(i); + m_serversModel->removeServer(); + break; + } + } + + m_settings->toggleGoodbyeDpi(false); + emit toggleGoodbyeDpiFinished("GoodbyeDPI removed from home page"); + } +} + +bool LocalServicesController::isGoodbyeDpiEnabled() +{ + return m_settings->isGoodbyeDpiEnabled(); +} + +void LocalServicesController::setGoodbyeDpiBlackListFile(const QString &file) +{ + m_settings->setGoodbyeDpiBlackListFile(file); +} + +QString LocalServicesController::getGoodbyeDpiBlackListFile() +{ + auto file = m_settings->getGoodbyeDpiBlackListFile(); + if (file.isEmpty()) { + return m_defaultBlackListFile; + } + return file; +} + +void LocalServicesController::resetGoodbyeDpiBlackListFile() +{ + m_settings->setGoodbyeDpiBlackListFile(m_defaultBlackListFile); +} + +void LocalServicesController::setGoodbyeDpiModset(const int modset) +{ + m_settings->setGoodbyeDpiModset(modset); +} + +int LocalServicesController::getGoodbyeDpiModset() +{ + return m_settings->getGoodbyeDpiModset(); +} + +void LocalServicesController::start() +{ + auto errorCode = m_goodbyeDpiService.start(getGoodbyeDpiBlackListFile(), getGoodbyeDpiModset()); + if (errorCode != ErrorCode::NoError) { + emit errorOccurred(errorCode); + } +} + +void LocalServicesController::stop() +{ + m_goodbyeDpiService.stop(); +} diff --git a/client/ui/controllers/localServicesController.h b/client/ui/controllers/localServicesController.h new file mode 100644 index 000000000..4daa517f6 --- /dev/null +++ b/client/ui/controllers/localServicesController.h @@ -0,0 +1,50 @@ +#ifndef LOCALSERVICESCONTROLLER_H +#define LOCALSERVICESCONTROLLER_H + +#include + +#include "localServices/goodByeDpi.h" +#include "protocols/vpnprotocol.h" +#include "settings.h" +#include "ui/models/servers_model.h" + +class LocalServicesController : public QObject +{ + Q_OBJECT + +public: + LocalServicesController(const QSharedPointer &serversModel, const std::shared_ptr &settings, + QObject *parent = nullptr); + ~LocalServicesController(); + + Q_PROPERTY(bool isGoodbyeDpiEnabled READ isGoodbyeDpiEnabled NOTIFY toggleGoodbyeDpiFinished) + +public slots: + void toggleGoodbyeDpi(bool enable); + bool isGoodbyeDpiEnabled(); + + void setGoodbyeDpiBlackListFile(const QString &file); + QString getGoodbyeDpiBlackListFile(); + void resetGoodbyeDpiBlackListFile(); + + void setGoodbyeDpiModset(const int modset); + int getGoodbyeDpiModset(); + + void start(); + void stop(); + +signals: + void errorOccurred(ErrorCode errorCode); + void toggleGoodbyeDpiFinished(const QString &message); + void serviceStateChanged(Vpn::ConnectionState state); + +private: + std::shared_ptr m_settings; + QSharedPointer m_serversModel; + + GoodByeDpi m_goodbyeDpiService; + bool m_isGoodbyeDpiServiceEnabled = false; + QString m_defaultBlackListFile = QCoreApplication::applicationDirPath() + "/goodbyedpi/blacklist.txt"; +}; + +#endif // LOCALSERVICESCONTROLLER_H diff --git a/client/ui/controllers/pageController.cpp b/client/ui/controllers/pageController.cpp index 3e5e5cc3d..91c8ddc43 100644 --- a/client/ui/controllers/pageController.cpp +++ b/client/ui/controllers/pageController.cpp @@ -59,11 +59,19 @@ bool PageController::isStartPageVisible() } } -QString PageController::getPagePath(PageLoader::PageEnum page) +QString PageController::getPagePath(PageLoader::PageEnum page, PageLoader::FolderEnum folder) { QMetaEnum metaEnum = QMetaEnum::fromType(); QString pageName = metaEnum.valueToKey(static_cast(page)); - return "qrc:/ui/qml/Pages2/" + pageName + ".qml"; + + metaEnum = QMetaEnum::fromType(); + QString folderName = ""; + if (metaEnum.value(static_cast(folder)) != static_cast(PageLoader::FolderEnum::Root)) { + folderName = metaEnum.valueToKey(static_cast(folder)); + folderName += "/"; + } + + return "qrc:/ui/qml/Pages2/" + folderName + pageName + ".qml"; } void PageController::closeWindow() diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 2cc2d983d..771569681 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -61,7 +61,9 @@ namespace PageLoader PageShareFullAccess, - PageDevMenu + PageDevMenu, + + PageGoodByeDpiSettings }; Q_ENUM_NS(PageEnum) @@ -69,6 +71,19 @@ namespace PageLoader { qmlRegisterUncreatableMetaObject(PageLoader::staticMetaObject, "PageEnum", 1, 0, "PageEnum", "Error: only enums"); } + + Q_NAMESPACE + enum class FolderEnum { + Root = 0, + LocalServices + + }; + Q_ENUM_NS(FolderEnum) + + static void declareQmlFolderEnum() + { + qmlRegisterUncreatableMetaObject(PageLoader::staticMetaObject, "FolderEnum", 1, 0, "FolderEnum", "Error: only enums"); + } } class PageController : public QObject @@ -80,7 +95,7 @@ public: public slots: bool isStartPageVisible(); - QString getPagePath(PageLoader::PageEnum page); + QString getPagePath(PageLoader::PageEnum page, PageLoader::FolderEnum folder = PageLoader::FolderEnum::Root); void closeWindow(); void hideWindow(); @@ -103,7 +118,7 @@ public slots: void onShowErrorMessage(amnezia::ErrorCode errorCode); signals: - void goToPage(PageLoader::PageEnum page, bool slide = true); + void goToPage(PageLoader::PageEnum page, PageLoader::FolderEnum folder = PageLoader::FolderEnum::Root, bool slide = true); void goToStartPage(); void goToPageHome(); void goToPageSettings(); diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index 93fd89714..5ca5a8791 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -16,8 +16,7 @@ SettingsController::SettingsController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, - const QSharedPointer &languageModel, - const QSharedPointer &sitesModel, + const QSharedPointer &languageModel, const QSharedPointer &sitesModel, const QSharedPointer &appSplitTunnelingModel, const std::shared_ptr &settings, QObject *parent) : QObject(parent), @@ -31,7 +30,8 @@ SettingsController::SettingsController(const QSharedPointer &serve m_appVersion = QString("%1 (%2, %3)").arg(QString(APP_VERSION), __DATE__, GIT_COMMIT_HASH); checkIfNeedDisableLogs(); #ifdef Q_OS_ANDROID - connect(AndroidController::instance(), &AndroidController::notificationStateChanged, this, &SettingsController::onNotificationStateChanged); + connect(AndroidController::instance(), &AndroidController::notificationStateChanged, this, + &SettingsController::onNotificationStateChanged); #endif } @@ -131,8 +131,7 @@ void SettingsController::restoreAppConfigFromData(const QByteArray &data) bool ok = m_settings->restoreAppConfig(data); if (ok) { m_serversModel->resetModel(); - m_languageModel->changeLanguage( - static_cast(m_languageModel->getCurrentLanguageIndex())); + m_languageModel->changeLanguage(static_cast(m_languageModel->getCurrentLanguageIndex())); emit restoreBackupFinished(); } else { emit changeSettingsErrorOccurred(tr("Backup file is corrupted")); @@ -148,8 +147,7 @@ void SettingsController::clearSettings() { m_settings->clearSettings(); m_serversModel->resetModel(); - m_languageModel->changeLanguage( - static_cast(m_languageModel->getCurrentLanguageIndex())); + m_languageModel->changeLanguage(static_cast(m_languageModel->getCurrentLanguageIndex())); m_sitesModel->setRouteMode(Settings::RouteMode::VpnOnlyForwardSites); m_sitesModel->toggleSplitTunneling(false); diff --git a/client/ui/controllers/settingsController.h b/client/ui/controllers/settingsController.h index a18888a9e..a2cb1fbf5 100644 --- a/client/ui/controllers/settingsController.h +++ b/client/ui/controllers/settingsController.h @@ -8,6 +8,7 @@ #include "ui/models/servers_model.h" #include "ui/models/sites_model.h" #include "ui/models/appSplitTunnelingModel.h" +#include "localServices/goodByeDpi.h" class SettingsController : public QObject { @@ -112,6 +113,8 @@ private: QSharedPointer m_appSplitTunnelingModel; std::shared_ptr m_settings; + GoodByeDpi m_goodbyeDpiService; + QString m_appVersion; QDateTime m_loggingDisableDate; diff --git a/client/ui/models/containers_model.cpp b/client/ui/models/containers_model.cpp index 9f722d773..41d26bc72 100644 --- a/client/ui/models/containers_model.cpp +++ b/client/ui/models/containers_model.cpp @@ -37,13 +37,7 @@ QVariant ContainersModel::data(const QModelIndex &index, int role) const case EasySetupHeaderRole: return ContainerProps::easySetupHeader(container); case EasySetupDescriptionRole: return ContainerProps::easySetupDescription(container); case EasySetupOrderRole: return ContainerProps::easySetupOrder(container); - case IsInstalledRole: { -#ifdef Q_OS_WIN - if (container == DockerContainer::GoodbyeDPI) - return true; -#endif - return m_containers.contains(container); - } + case IsInstalledRole: return m_containers.contains(container); case IsCurrentlyProcessedRole: return container == static_cast(m_processedContainerIndex); case IsSupportedRole: return ContainerProps::isSupportedByCurrentPlatform(container); case IsShareableRole: return ContainerProps::isShareable(container); diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index 85e5dae2d..3805dd59e 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -88,9 +88,10 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const const QJsonObject server = m_servers.at(index.row()).toObject(); const auto apiConfig = server.value(configKey::apiConfig).toObject(); const auto configVersion = server.value(config_key::configVersion).toInt(); + const auto isGoodbyeDpi = server.value(config_key::isGoodbyeDpi).toBool(false); switch (role) { case NameRole: { - if (configVersion) { + if (configVersion || isGoodbyeDpi) { return server.value(config_key::name).toString(); } auto name = server.value(config_key::description).toString(); @@ -100,6 +101,10 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const return name; } case ServerDescriptionRole: { + if (isGoodbyeDpi) { + return server.value(config_key::description).toString(); + } + auto description = getServerDescription(server, index.row()); return configVersion ? description : description + server.value(config_key::hostName).toString(); } @@ -144,6 +149,9 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const QString primaryDns = server.value(config_key::dns1).toString(); return primaryDns == protocols::dns::amneziaDnsIp; } + case IsGoodByeDpiRole: { + return isGoodbyeDpi; + } } return QVariant(); @@ -208,6 +216,12 @@ QString ServersModel::getServerDescription(const QJsonObject &server, const int const QString ServersModel::getDefaultServerDescriptionCollapsed() { const QJsonObject server = m_servers.at(m_defaultServerIndex).toObject(); + + const auto isGoodbyeDpi = server.value(config_key::isGoodbyeDpi).toBool(false); + if (isGoodbyeDpi) { + return server.value(config_key::description).toString(); + } + const auto configVersion = server.value(config_key::configVersion).toInt(); auto description = getServerDescription(server, m_defaultServerIndex); if (configVersion) { @@ -222,6 +236,12 @@ const QString ServersModel::getDefaultServerDescriptionCollapsed() const QString ServersModel::getDefaultServerDescriptionExpanded() { const QJsonObject server = m_servers.at(m_defaultServerIndex).toObject(); + + const auto isGoodbyeDpi = server.value(config_key::isGoodbyeDpi).toBool(false); + if (isGoodbyeDpi) { + return server.value(config_key::description).toString(); + } + const auto configVersion = server.value(config_key::configVersion).toInt(); auto description = getServerDescription(server, m_defaultServerIndex); if (configVersion) { @@ -370,6 +390,8 @@ QHash ServersModel::roleNames() const roles[IsCountrySelectionAvailableRole] = "isCountrySelectionAvailable"; roles[ApiAvailableCountriesRole] = "apiAvailableCountries"; roles[ApiServerCountryCodeRole] = "apiServerCountryCode"; + + roles[IsGoodByeDpiRole] = "isGoodbyeDpi"; return roles; } diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index 0f18ea301..7e4271758 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -38,7 +38,9 @@ public: ApiAvailableCountriesRole, ApiServerCountryCodeRole, - HasAmneziaDns + HasAmneziaDns, + + IsGoodByeDpiRole }; ServersModel(std::shared_ptr settings, QObject *parent = nullptr); diff --git a/client/ui/qml/Pages2/LocalServices/PageGoodByeDpiSettings.qml b/client/ui/qml/Pages2/LocalServices/PageGoodByeDpiSettings.qml new file mode 100644 index 000000000..58c47ade1 --- /dev/null +++ b/client/ui/qml/Pages2/LocalServices/PageGoodByeDpiSettings.qml @@ -0,0 +1,159 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 +import Style 1.0 + +import "./" +import "../../Controls2" +import "../../Config" +import "../../Controls2/TextTypes" +import "../../Components" + +PageType { + id: root + + BackButtonType { + id: backButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 20 + } + + FlickableType { + id: fl + anchors.top: backButton.bottom + anchors.bottom: parent.bottom + contentHeight: content.height + + ColumnLayout { + id: content + + property bool isGoodbyeDpiEnabled: LocalServicesController.isGoodbyeDpiEnabled + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + spacing: 16 + + HeaderType { + Layout.fillWidth: true + + headerText: qsTr("GoodbyeDPI settings") + descriptionText: qsTr("Deep Packet Inspection circumvention utility") + } + + SwitcherType { + Layout.fillWidth: true + + text: qsTr("Enable GoodbyeDPI") + + checked: LocalServicesController.isGoodbyeDpiEnabled + onCheckedChanged: { + if (checked !== LocalServicesController.isGoodbyeDpiEnabled) { + LocalServicesController.toggleGoodbyeDpi(checked) + } + } + } + + RowLayout { + Layout.fillWidth: true + Layout.topMargin: 16 + + enabled: !content.isGoodbyeDpiEnabled + + ListItemTitleType { + Layout.fillWidth: true + + text: LocalServicesController.getGoodbyeDpiBlackListFile() + } + + ImageButtonType { + image: "qrc:/images/controls/folder-search-2.svg" + imageColor: AmneziaStyle.color.paleGray + + onClicked: function() { + var fileName = SystemController.getFileName(qsTr("Open black list file"), + qsTr("Text files (*.txt)")) + + LocalServicesController.setGoodbyeDpiBlackListFile(fileName) + } + } + + ImageButtonType { + image: "qrc:/images/controls/trash.svg" + imageColor: AmneziaStyle.color.paleGray + + onClicked: function() { + LocalServicesController.resetGoodbyeDpiBlackListFile() + } + } + } + + DropDownType { + id: modsetDropDown + Layout.fillWidth: true + + descriptionText: qsTr("Setup templates") + headerText: qsTr("Modset") + + drawerParent: root + + enabled: !content.isGoodbyeDpiEnabled + + listView: ListViewWithRadioButtonType { + id: modsetListView + + rootWidth: root.width + + model: ListModel { + ListElement { name : "-p -r -s -f 2 -k 2 -n -e 2" } + ListElement { name : "-p -r -s -f 2 -k 2 -n -e 40" } + ListElement { name : "-p -r -s -e 40" } + ListElement { name : "-p -r -s" } + ListElement { name : "-f 2 -e 2 --auto-ttl --reverse-frag --max-payload" } + ListElement { name : "-f 2 -e 2 --wrong-seq --reverse-frag --max-payload" } + ListElement { name : "-f 2 -e 2 --wrong-chksum --reverse-frag --max-payload" } + ListElement { name : "-f 2 -e 2 --wrong-seq --wrong-chksum --reverse-frag --max-payload" } + ListElement { name : "-f 2 -e 2 --wrong-seq --wrong-chksum --reverse-frag --max-payload -q" } + } + + clickedFunction: function() { + modsetDropDown.text = selectedText + LocalServicesController.setGoodbyeDpiModset(currentIndex + 1) + modsetDropDown.close() + } + + Component.onCompleted: { + modsetListView.currentIndex = (LocalServicesController.getGoodbyeDpiModset() - 1) + modsetListView.triggerCurrentItem() + } + } + } + + BasicButtonType { + id: detailedInstructionsButton + implicitHeight: 32 + + defaultColor: AmneziaStyle.color.transparent + hoveredColor: AmneziaStyle.color.translucentWhite + pressedColor: AmneziaStyle.color.sheerWhite + disabledColor: AmneziaStyle.color.mutedGray + textColor: AmneziaStyle.color.goldenApricot + + text: qsTr("Description of options") + + clickedFunc: function() { + Qt.openUrlExternally("https://github.com/ValdikSS/GoodbyeDPI?tab=readme-ov-file#how-to-use") + } + } + } + } + +} diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index 8074337a9..c8296dc4e 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -105,6 +105,8 @@ PageType { buttonTextLabel.font.pixelSize: 14 buttonTextLabel.font.weight: 500 + visible: !ServersModel.getDefaultServerData("isGoodbyeDpi") + property bool isSplitTunnelingEnabled: SitesModel.isTunnelingEnabled || AppSplitTunnelingModel.isTunnelingEnabled || ServersModel.isDefaultServerDefaultContainerHasSplitTunneling @@ -304,7 +306,7 @@ PageType { Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter spacing: 8 - visible: !ServersModel.isDefaultServerFromApi + visible: !ServersModel.isDefaultServerFromApi && !ServersModel.getDefaultServerData("isGoodbyeDpi") Item { id: focusItem1 @@ -535,8 +537,12 @@ PageType { Keys.onReturnPressed: serverInfoButton.clicked() onClicked: function() { - ServersModel.processedIndex = index - PageController.goToPage(PageEnum.PageSettingsServerInfo) + if (ServersModel.getDefaultServerData("isGoodbyeDpi")) { + PageController.goToPage(PageEnum.PageGoodByeDpiSettings, PageEnum.LocalServices) + } else { + ServersModel.processedIndex = index + PageController.goToPage(PageEnum.PageSettingsServerInfo) + } drawer.close() } } diff --git a/client/ui/qml/Pages2/PageSettings.qml b/client/ui/qml/Pages2/PageSettings.qml index bb5ca7667..76cae3b58 100644 --- a/client/ui/qml/Pages2/PageSettings.qml +++ b/client/ui/qml/Pages2/PageSettings.qml @@ -95,6 +95,20 @@ PageType { DividerType {} + LabelWithButtonType { + Layout.fillWidth: true + + text: qsTr("Local bypass services") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + leftImageSource: "qrc:/images/controls/app.svg" + + clickedFunction: function() { + PageController.goToPage(PageEnum.PageGoodByeDpiSettings, PageEnum.LocalServices) + } + } + + DividerType {} + LabelWithButtonType { id: backup Layout.fillWidth: true diff --git a/client/ui/qml/Pages2/PageSettingsServersList.qml b/client/ui/qml/Pages2/PageSettingsServersList.qml index 102dd46ff..58fe9e04b 100644 --- a/client/ui/qml/Pages2/PageSettingsServersList.qml +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -111,6 +111,8 @@ PageType { id: server Layout.fillWidth: true + visible: !isGoodbyeDpi + text: name parentFlickable: fl descriptionText: { diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index 1128761d3..9a12d172f 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -54,7 +54,7 @@ PageType { function onServerAlreadyExists(serverIndex) { PageController.goToStartPage() ServersModel.processedIndex = serverIndex - PageController.goToPage(PageEnum.PageSettingsServerInfo, false) + PageController.goToPage(PageEnum.PageSettingsServerInfo, PageEnum.LocalServices, false) PageController.showErrorMessage(qsTr("The server has already been added to the application")) } diff --git a/client/ui/qml/Pages2/PageStart.qml b/client/ui/qml/Pages2/PageStart.qml index 770347ca2..4b6bb651b 100644 --- a/client/ui/qml/Pages2/PageStart.qml +++ b/client/ui/qml/Pages2/PageStart.qml @@ -60,8 +60,8 @@ PageType { tabBarStackView.pop() } - function onGoToPage(page, slide) { - var pagePath = PageController.getPagePath(page) + function onGoToPage(page, folder, slide) { + var pagePath = PageController.getPagePath(page, folder) if (slide) { tabBarStackView.push(pagePath, { "objectName" : pagePath }, StackView.PushTransition) @@ -204,6 +204,14 @@ PageType { } } + Connections { + target: LocalServicesController + + function onErrorOccurred(error) { + PageController.showErrorMessage(error) + } + } + StackViewType { id: tabBarStackView