diff --git a/client/3rd-prebuilt b/client/3rd-prebuilt index efad1a5b..0f3748ef 160000 --- a/client/3rd-prebuilt +++ b/client/3rd-prebuilt @@ -1 +1 @@ -Subproject commit efad1a5b5cb8e8ab61e49ccdca18c9090a0da8d3 +Subproject commit 0f3748efd7cc04e0c914304b68931f925bed1259 diff --git a/client/configurators/openvpn_configurator.cpp b/client/configurators/openvpn_configurator.cpp index fafb7c2b..6d6603da 100644 --- a/client/configurators/openvpn_configurator.cpp +++ b/client/configurators/openvpn_configurator.cpp @@ -13,10 +13,10 @@ #include #endif +#include "core/networkUtilities.h" #include "containers/containers_defs.h" #include "core/controllers/serverController.h" #include "core/scripts_registry.h" -#include "core/server_defs.h" #include "settings.h" #include "utilities.h" @@ -24,6 +24,7 @@ #include #include + OpenVpnConfigurator::OpenVpnConfigurator(std::shared_ptr settings, const QSharedPointer &serverController, QObject *parent) : ConfiguratorBase(settings, serverController, parent) @@ -119,20 +120,14 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPairisSitesSplitTunnelingEnabled()) { config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n"); - -#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) - // Prevent ipv6 leak - config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n"); -#endif config.append("block-ipv6\n"); } else if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) { - // no redirect-gateway + // no redirect-gateway } else if (m_settings->routeMode() == Settings::VpnAllExceptSites) { #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n"); // Prevent ipv6 leak - config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n"); #endif config.append("block-ipv6\n"); } @@ -169,7 +164,6 @@ QString OpenVpnConfigurator::processConfigWithExportSettings(const QPair ContainerProps::containerDetailedDescriptions() { return { { DockerContainer::OpenVpn, - QObject::tr( - "OpenVPN stands as one of the most popular and time-tested VPN protocols available.\n" - "It employs its unique security protocol, " - "leveraging the strength of SSL/TLS for encryption and key exchange. " - "Furthermore, OpenVPN's support for a multitude of authentication methods makes it versatile and adaptable, " - "catering to a wide range of devices and operating systems. " - "Due to its open-source nature, OpenVPN benefits from extensive scrutiny by the global community, " - "which continually reinforces its security. " - "With a strong balance of performance, security, and compatibility, " - "OpenVPN remains a top choice for privacy-conscious individuals and businesses alike.\n\n" - "* Available in the DefaultVPN across all platforms\n" - "* Normal power consumption on mobile devices\n" - "* Flexible customisation to suit user needs to work with different operating systems and devices\n" - "* Recognised by DPI systems and therefore susceptible to blocking\n" - "* Can operate over both TCP and UDP network protocols.") }, + QObject::tr("OpenVPN is one of the most popular and reliable VPN protocols. " + "It uses SSL/TLS encryption, supports a wide variety of devices and operating systems, " + "and is continuously improved by the community due to its open-source nature. " + "It provides a good balance between speed and security but is easily recognized by DPI systems, " + "making it susceptible to blocking.\n" + "\nFeatures:\n" + "* Available on all AmneziaVPN platforms\n" + "* Normal battery consumption on mobile devices\n" + "* Flexible customization for various devices and OS\n" + "* Operates over both TCP and UDP protocols") }, { DockerContainer::ShadowSocks, - QObject::tr("Shadowsocks, inspired by the SOCKS5 protocol, safeguards the connection using the AEAD cipher. " - "Although Shadowsocks is designed to be discreet and challenging to identify, it isn't identical to a standard HTTPS connection." - "However, certain traffic analysis systems might still detect a Shadowsocks connection. " - "Due to limited support in Amnezia, it's recommended to use AmneziaWG protocol.\n\n" - "* Available in the DefaultVPN only on desktop platforms\n" - "* Configurable encryption protocol\n" + QObject::tr("Shadowsocks is based on the SOCKS5 protocol and encrypts connections using AEAD cipher. " + "Although designed to be discreet, it doesn't mimic a standard HTTPS connection and can be detected by some DPI systems. " + "Due to limited support in Amnezia, we recommend using the AmneziaWG protocol.\n" + "\nFeatures:\n" + "* Available in AmneziaVPN only on desktop platforms\n" + "* Customizable encryption protocol\n" "* Detectable by some DPI systems\n" - "* Works over TCP network protocol.") }, + "* Operates over TCP protocol\n") }, { DockerContainer::Cloak, - QObject::tr("This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for " - "protecting against detection.\n\n" - "OpenVPN provides a secure VPN connection by encrypting all internet traffic between the client " - "and the server.\n\n" - "Cloak protects OpenVPN from detection. \n\n" - "Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, " - "and also protects the VPN from detection by Active Probing. This makes it very resistant to " - "being detected\n\n" - "Immediately after receiving the first data packet, Cloak authenticates the incoming connection. " - "If authentication fails, the plugin masks the server as a fake website and your VPN becomes " - "invisible to analysis systems.\n\n" - "* Available in the DefaultVPN across all platforms\n" + QObject::tr("This combination includes the OpenVPN protocol and the Cloak plugin, specifically designed to protect against blocking.\n" + "\nOpenVPN securely encrypts all internet traffic between your device and the server.\n" + "\nThe Cloak plugin further protects the connection from DPI detection. " + "It modifies traffic metadata to disguise VPN traffic as regular web traffic and prevents detection through active probing. " + "If an incoming connection fails authentication, Cloak serves a fake website, making your VPN invisible to traffic analysis systems.\n" + "\nIn regions with heavy internet censorship, we strongly recommend using OpenVPN with Cloak from your first connection.\n" + "\nFeatures:\n" + "* Available on all AmneziaVPN platforms\n" "* High power consumption on mobile devices\n" - "* Flexible settings\n" - "* Not recognised by detection systems\n" - "* Works over TCP network protocol, 443 port.\n") }, + "* Flexible configuration options\n" + "* Undetectable by DPI systems\n" + "* Operates over TCP protocol on port 443") }, { DockerContainer::WireGuard, - QObject::tr("A relatively new popular VPN protocol with a simplified architecture.\n" - "WireGuard provides stable VPN connection and high performance on all devices. It uses hard-coded encryption " - "settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput.\n" - "WireGuard is very susceptible to detection and blocking due to its distinct packet signatures. " - "Unlike some other VPN protocols that employ obfuscation techniques, " - "the consistent signature patterns of WireGuard packets can be more easily identified and " - "thus blocked by advanced Deep Packet Inspection (DPI) systems and other network monitoring tools.\n\n" - "* Available in the DefaultVPN across all platforms\n" - "* Low power consumption\n" - "* Minimum number of settings\n" - "* Easily recognised by DPI analysis systems, susceptible to blocking\n" - "* Works over UDP network protocol.") }, + QObject::tr("WireGuard is a modern, streamlined VPN protocol offering stable connectivity and excellent performance across all devices. " + "It uses fixed encryption settings, delivering lower latency and higher data transfer speeds compared to OpenVPN. " + "However, WireGuard is easily identifiable by DPI systems due to its distinctive packet signatures, making it susceptible to blocking.\n" + "\nFeatures:\n" + "* Available on all AmneziaVPN platforms\n" + "* Low power consumption on mobile devices\n" + "* Minimal configuration required\n" + "* Easily detected by DPI systems (susceptible to blocking)\n" + "* Operates over UDP protocol") }, { DockerContainer::Awg, - QObject::tr("A modern iteration of the popular VPN protocol, " - "AmneziaWG builds upon the foundation set by WireGuard, " - "retaining its simplified architecture and high-performance capabilities across devices.\n" - "While WireGuard is known for its efficiency, " - "it had issues with being easily detected due to its distinct packet signatures. " - "AmneziaWG solves this problem by using better obfuscation methods, " - "making its traffic blend in with regular internet traffic.\n" - "This means that AmneziaWG keeps the fast performance of the original " - "while adding an extra layer of stealth, " - "making it a great choice for those wanting a fast and discreet VPN connection.\n\n" - "* Available in the DefaultVPN across all platforms\n" - "* Low power consumption\n" - "* Minimum number of settings\n" - "* Not recognised by traffic analysis systems\n" - "* Works over UDP network protocol.") }, + QObject::tr("AmneziaWG is a modern VPN protocol based on WireGuard, " + "combining simplified architecture with high performance across all devices. " + "It addresses WireGuard's main vulnerability (easy detection by DPI systems) through advanced obfuscation techniques, " + "making VPN traffic indistinguishable from regular internet traffic.\n" + "\nAmneziaWG is an excellent choice for those seeking a fast, stealthy VPN connection.\n" + "\nFeatures:\n" + "* Available on all AmneziaVPN platforms\n" + "* Low battery consumption on mobile devices\n" + "* Minimal settings required\n" + "* Undetectable by traffic analysis systems (DPI)\n" + "* Operates over UDP protocol") }, { DockerContainer::Xray, - QObject::tr("The REALITY protocol, a pioneering development by the creators of XRay, " - "is designed to provide the highest level of protection against detection through its innovative approach to security and privacy.\n" - "It uniquely identifies attackers during the TLS handshake phase, seamlessly operating as a proxy for legitimate clients while diverting attackers to genuine websites, " - "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. " - "This makes REALITY a robust solution for maintaining internet freedom.") - }, + QObject::tr("REALITY is an innovative protocol developed by the creators of XRay, designed specifically to combat high levels of internet censorship. " + "REALITY identifies censorship systems during the TLS handshake, " + "redirecting suspicious traffic seamlessly to legitimate websites like google.com while providing genuine TLS certificates. " + "This allows VPN traffic to blend indistinguishably with regular web traffic without special configuration." + "\nUnlike older protocols such as VMess, VLESS, and XTLS-Vision, REALITY incorporates an advanced built-in \"friend-or-foe\" detection mechanism, " + "effectively protecting against DPI and other traffic analysis methods.\n" + "\nFeatures:\n" + "* Resistant to active probing and DPI detection\n" + "* No special configuration required to disguise traffic\n" + "* Highly effective in heavily censored regions\n" + "* Minimal battery consumption on devices\n" + "* Operates over TCP protocol") }, { DockerContainer::Ipsec, - 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, " - "making it particularly adaptive in dynamic network environments. \n" - "While it offers a blend of security, stability, and speed, " - "it's essential to note that IKEv2 can be easily detected and is susceptible to blocking.\n\n" - "* Available in the DefaultVPN only on Windows\n" - "* Low power consumption, on mobile devices\n" - "* Minimal configuration\n" - "* Recognised by DPI analysis systems\n" - "* Works over UDP network protocol, ports 500 and 4500.") }, + QObject::tr("IKEv2, combined with IPSec encryption, is a modern and reliable VPN protocol. " + "It reconnects quickly when switching networks or devices, making it ideal for dynamic network environments. " + "While it provides good security and speed, it's easily recognized by DPI systems and susceptible to blocking.\n" + "\nFeatures:\n" + "* Available in AmneziaVPN only on Windows\n" + "* Low battery consumption on mobile devices\n" + "* Minimal configuration required\n" + "* Detectable by DPI analysis systems(easily blocked)\n" + "* Operates over UDP protocol(ports 500 and 4500)") }, { DockerContainer::TorWebSite, QObject::tr("Website in Tor network") }, { DockerContainer::Dns, QObject::tr("DNS Service") }, diff --git a/client/core/controllers/coreController.cpp b/client/core/controllers/coreController.cpp index 1eabc77e..f47555bf 100644 --- a/client/core/controllers/coreController.cpp +++ b/client/core/controllers/coreController.cpp @@ -48,6 +48,9 @@ void CoreController::initModels() m_sitesModel.reset(new SitesModel(m_settings, this)); m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get()); + m_allowedDnsModel.reset(new AllowedDnsModel(m_settings, this)); + m_engine->rootContext()->setContextProperty("AllowedDnsModel", m_allowedDnsModel.get()); + m_appSplitTunnelingModel.reset(new AppSplitTunnelingModel(m_settings, this)); m_engine->rootContext()->setContextProperty("AppSplitTunnelingModel", m_appSplitTunnelingModel.get()); @@ -130,6 +133,9 @@ void CoreController::initControllers() m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel)); m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get()); + m_allowedDnsController.reset(new AllowedDnsController(m_settings, m_allowedDnsModel)); + m_engine->rootContext()->setContextProperty("AllowedDnsController", m_allowedDnsController.get()); + m_appSplitTunnelingController.reset(new AppSplitTunnelingController(m_settings, m_appSplitTunnelingModel)); m_engine->rootContext()->setContextProperty("AppSplitTunnelingController", m_appSplitTunnelingController.get()); @@ -214,6 +220,7 @@ void CoreController::initSignalHandlers() initAutoConnectHandler(); initAmneziaDnsToggledHandler(); initPrepareConfigHandler(); + initStrictKillSwitchHandler(); } void CoreController::initNotificationHandler() @@ -356,6 +363,12 @@ void CoreController::initPrepareConfigHandler() }); } +void CoreController::initStrictKillSwitchHandler() +{ + connect(m_settingsController.get(), &SettingsController::strictKillSwitchEnabledChanged, + m_vpnConnection.get(), &VpnConnection::onKillSwitchModeChanged); +} + QSharedPointer CoreController::pageController() const { return m_pageController; diff --git a/client/core/controllers/coreController.h b/client/core/controllers/coreController.h index 700504af..6342d738 100644 --- a/client/core/controllers/coreController.h +++ b/client/core/controllers/coreController.h @@ -8,6 +8,7 @@ #include "ui/controllers/api/apiConfigsController.h" #include "ui/controllers/api/apiSettingsController.h" #include "ui/controllers/appSplitTunnelingController.h" +#include "ui/controllers/allowedDnsController.h" #include "ui/controllers/connectionController.h" #include "ui/controllers/exportController.h" #include "ui/controllers/focusController.h" @@ -18,6 +19,7 @@ #include "ui/controllers/sitesController.h" #include "ui/controllers/systemController.h" +#include "ui/models/allowed_dns_model.h" #include "ui/models/containers_model.h" #include "ui/models/languageModel.h" #include "ui/models/protocols/cloakConfigModel.h" @@ -80,6 +82,7 @@ private: void initAutoConnectHandler(); void initAmneziaDnsToggledHandler(); void initPrepareConfigHandler(); + void initStrictKillSwitchHandler(); QQmlApplicationEngine *m_engine {}; // TODO use parent child system here? std::shared_ptr m_settings; @@ -102,6 +105,7 @@ private: QScopedPointer m_sitesController; QScopedPointer m_systemController; QScopedPointer m_appSplitTunnelingController; + QScopedPointer m_allowedDnsController; QScopedPointer m_apiSettingsController; QScopedPointer m_apiConfigsController; @@ -112,6 +116,7 @@ private: QSharedPointer m_languageModel; QSharedPointer m_protocolsModel; QSharedPointer m_sitesModel; + QSharedPointer m_allowedDnsModel; QSharedPointer m_appSplitTunnelingModel; QSharedPointer m_clientManagementModel; diff --git a/client/core/controllers/gatewayController.cpp b/client/core/controllers/gatewayController.cpp index f8c23c1a..0d86b9d5 100644 --- a/client/core/controllers/gatewayController.cpp +++ b/client/core/controllers/gatewayController.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include "QBlockCipher.h" #include "QRsa.h" @@ -14,6 +15,11 @@ #include "amnezia_application.h" #include "core/api/apiUtils.h" #include "utilities.h" +#include "core/networkUtilities.h" + +#ifdef AMNEZIA_DESKTOP + #include "core/ipcclient.h" +#endif namespace { @@ -50,6 +56,17 @@ ErrorCode GatewayController::get(const QString &endpoint, QByteArray &responseBo request.setUrl(QString(endpoint).arg(m_gatewayEndpoint)); + // bypass killSwitch exceptions for API-gateway +#ifdef AMNEZIA_DESKTOP + { + QString host = QUrl(request.url()).host(); + QString ip = NetworkUtilities::getIPAddress(host); + if (!ip.isEmpty()) { + IpcClient::Interface()->addKillSwitchAllowedRange(QStringList{ip}); + } + } +#endif + QNetworkReply *reply; reply = amnApp->networkManager()->get(request); @@ -101,6 +118,17 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api request.setUrl(endpoint.arg(m_gatewayEndpoint)); + // bypass killSwitch exceptions for API-gateway +#ifdef AMNEZIA_DESKTOP + { + QString host = QUrl(request.url()).host(); + QString ip = NetworkUtilities::getIPAddress(host); + if (!ip.isEmpty()) { + IpcClient::Interface()->addKillSwitchAllowedRange(QStringList{ip}); + } + } +#endif + QSimpleCrypto::QBlockCipher blockCipher; QByteArray key = blockCipher.generatePrivateSalt(32); QByteArray iv = blockCipher.generatePrivateSalt(32); diff --git a/client/core/controllers/serverController.cpp b/client/core/controllers/serverController.cpp index d8c94f4d..8ff6b6c8 100644 --- a/client/core/controllers/serverController.cpp +++ b/client/core/controllers/serverController.cpp @@ -138,7 +138,7 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container, if (overwriteMode == libssh::ScpOverwriteMode::ScpOverwriteExisting) { e = runScript(credentials, - replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(path), + replaceVars(QStringLiteral("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName, path), genVarsForScript(credentials, container)), cbReadStd, cbReadStd); @@ -146,7 +146,7 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container, return e; } else if (overwriteMode == libssh::ScpOverwriteMode::ScpAppendToExisting) { e = runScript(credentials, - replaceVars(QString("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName).arg(tmpFileName), + replaceVars(QStringLiteral("sudo docker cp %1 $CONTAINER_NAME:/%2").arg(tmpFileName, tmpFileName), genVarsForScript(credentials, container)), cbReadStd, cbReadStd); @@ -154,7 +154,7 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container, return e; e = runScript(credentials, - replaceVars(QString("sudo docker exec -i $CONTAINER_NAME sh -c \"cat %1 >> %2\"").arg(tmpFileName).arg(path), + replaceVars(QStringLiteral("sudo docker exec -i $CONTAINER_NAME sh -c \"cat %1 >> %2\"").arg(tmpFileName, path), genVarsForScript(credentials, container)), cbReadStd, cbReadStd); @@ -177,7 +177,7 @@ QByteArray ServerController::getTextFileFromContainer(DockerContainer container, errorCode = ErrorCode::NoError; - QString script = QString("sudo docker exec -i %1 sh -c \"xxd -p \'%2\'\"").arg(ContainerProps::containerToString(container)).arg(path); + QString script = QStringLiteral("sudo docker exec -i %1 sh -c \"xxd -p '%2'\"").arg(ContainerProps::containerToString(container), path); QString stdOut; auto cbReadStdOut = [&](const QString &data, libssh::Client &) { @@ -383,6 +383,13 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c return true; } + if (container == DockerContainer::Xray) { + if (oldProtoConfig.value(config_key::port).toString(protocols::xray::defaultPort) + != newProtoConfig.value(config_key::port).toString(protocols::xray::defaultPort)) { + return true; + } + } + return false; } @@ -439,15 +446,22 @@ ErrorCode ServerController::buildContainerWorker(const ServerCredentials &creden stdOut += data + "\n"; return ErrorCode::NoError; }; + auto cbReadStdErr = [&](const QString &data, libssh::Client &) { + stdOut += data + "\n"; + return ErrorCode::NoError; + }; - errorCode = + ErrorCode error = runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::build_container), genVarsForScript(credentials, container, config)), - cbReadStdOut); - if (errorCode) - return errorCode; + cbReadStdOut, cbReadStdErr); + + if (stdOut.contains("doesn't work on cgroups v2")) + return ErrorCode::ServerDockerOnCgroupsV2; + if (stdOut.contains("cgroup mountpoint does not exist")) + return ErrorCode::ServerCgroupMountpoint; - return errorCode; + return error; } ErrorCode ServerController::runContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config) diff --git a/client/core/defs.h b/client/core/defs.h index 2e683314..eff3df3b 100644 --- a/client/core/defs.h +++ b/client/core/defs.h @@ -58,6 +58,8 @@ namespace amnezia ServerUserDirectoryNotAccessible = 208, ServerUserNotAllowedInSudoers = 209, ServerUserPasswordRequired = 210, + ServerDockerOnCgroupsV2 = 211, + ServerCgroupMountpoint = 212, // Ssh connection errors SshRequestDeniedError = 300, diff --git a/client/core/errorstrings.cpp b/client/core/errorstrings.cpp index 9dcd8065..6abab0e0 100644 --- a/client/core/errorstrings.cpp +++ b/client/core/errorstrings.cpp @@ -26,6 +26,8 @@ QString errorString(ErrorCode code) { case(ErrorCode::ServerUserDirectoryNotAccessible): errorMessage = QObject::tr("The server user's home directory is not accessible"); break; case(ErrorCode::ServerUserNotAllowedInSudoers): errorMessage = QObject::tr("Action not allowed in sudoers"); break; case(ErrorCode::ServerUserPasswordRequired): errorMessage = QObject::tr("The user's password is required"); break; + case(ErrorCode::ServerDockerOnCgroupsV2): errorMessage = QObject::tr("Docker error: runc doesn't work on cgroups v2"); break; + case(ErrorCode::ServerCgroupMountpoint): errorMessage = QObject::tr("Server error: cgroup mountpoint does not exist"); break; // Libssh errors case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break; diff --git a/client/core/networkUtilities.cpp b/client/core/networkUtilities.cpp index a5825f0d..cf33fa55 100644 --- a/client/core/networkUtilities.cpp +++ b/client/core/networkUtilities.cpp @@ -12,6 +12,7 @@ #include #include #include "qendian.h" + #include #endif #ifdef Q_OS_LINUX #include @@ -185,6 +186,17 @@ int NetworkUtilities::AdapterIndexTo(const QHostAddress& dst) { return 0; } +bool NetworkUtilities::checkIpv6Enabled() { +#ifdef Q_OS_WIN + QSettings RegHLM("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Tcpip6\\Parameters", + QSettings::NativeFormat); + int ret = RegHLM.value("DisabledComponents", 0).toInt(); + qDebug() << "Check for Windows disabled IPv6 return " << ret; + return (ret != 255); +#endif + return true; +} + #ifdef Q_OS_WIN DWORD GetAdaptersAddressesWrapper(const ULONG Family, const ULONG Flags, diff --git a/client/core/networkUtilities.h b/client/core/networkUtilities.h index 3b64b547..1bd1114c 100644 --- a/client/core/networkUtilities.h +++ b/client/core/networkUtilities.h @@ -16,6 +16,7 @@ public: 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 bool checkIpv6Enabled(); static QString getGatewayAndIface(); // Returns the Interface Index that could Route to dst static int AdapterIndexTo(const QHostAddress& dst); @@ -29,7 +30,6 @@ public: static QString netMaskFromIpWithSubnet(const QString ip); static QString ipAddressFromIpWithSubnet(const QString ip); - static QStringList summarizeRoutes(const QStringList &ips, const QString cidr); }; diff --git a/client/daemon/daemon.cpp b/client/daemon/daemon.cpp index 081a7a90..e4b0ab3d 100644 --- a/client/daemon/daemon.cpp +++ b/client/daemon/daemon.cpp @@ -371,6 +371,9 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) { if (!parseStringList(obj, "vpnDisabledApps", config.m_vpnDisabledApps)) { return false; } + if (!parseStringList(obj, "allowedDnsServers", config.m_allowedDnsServers)) { + return false; + } config.m_killSwitchEnabled = QVariant(obj.value("killSwitchOption").toString()).toBool(); diff --git a/client/daemon/interfaceconfig.cpp b/client/daemon/interfaceconfig.cpp index b2ad31c6..f0adcc92 100644 --- a/client/daemon/interfaceconfig.cpp +++ b/client/daemon/interfaceconfig.cpp @@ -48,6 +48,13 @@ QJsonObject InterfaceConfig::toJson() const { } json.insert("excludedAddresses", jsExcludedAddresses); + + QJsonArray jsAllowedDnsServers; + for (const QString& i : m_allowedDnsServers) { + jsAllowedDnsServers.append(QJsonValue(i)); + } + json.insert("allowedDnsServers", jsAllowedDnsServers); + QJsonArray disabledApps; for (const QString& i : m_vpnDisabledApps) { disabledApps.append(QJsonValue(i)); diff --git a/client/daemon/interfaceconfig.h b/client/daemon/interfaceconfig.h index 6a816f87..ee43a253 100644 --- a/client/daemon/interfaceconfig.h +++ b/client/daemon/interfaceconfig.h @@ -6,6 +6,7 @@ #define INTERFACECONFIG_H #include +#include #include #include "ipaddress.h" @@ -37,6 +38,7 @@ class InterfaceConfig { QList m_allowedIPAddressRanges; QStringList m_excludedAddresses; QStringList m_vpnDisabledApps; + QStringList m_allowedDnsServers; bool m_killSwitchEnabled; #if defined(MZ_ANDROID) || defined(MZ_IOS) QString m_installationId; diff --git a/client/mozilla/localsocketcontroller.cpp b/client/mozilla/localsocketcontroller.cpp index 714e023e..24d926ee 100644 --- a/client/mozilla/localsocketcontroller.cpp +++ b/client/mozilla/localsocketcontroller.cpp @@ -123,6 +123,7 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) { int appSplitTunnelType = rawConfig.value(amnezia::config_key::appSplitTunnelType).toInt(); QJsonArray splitTunnelApps = rawConfig.value(amnezia::config_key::splitTunnelApps).toArray(); + QJsonArray allowedDns = rawConfig.value(amnezia::config_key::allowedDnsServers).toArray(); QJsonObject wgConfig = rawConfig.value(protocolName + "_config_data").toObject(); @@ -226,6 +227,8 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) { json.insert("vpnDisabledApps", splitTunnelApps); + json.insert("allowedDnsServers", allowedDns); + json.insert(amnezia::config_key::killSwitchOption, rawConfig.value(amnezia::config_key::killSwitchOption)); if (protocolName == amnezia::config_key::awg) { diff --git a/client/platforms/linux/daemon/iputilslinux.cpp b/client/platforms/linux/daemon/iputilslinux.cpp index f0f2fbab..63bd92f9 100644 --- a/client/platforms/linux/daemon/iputilslinux.cpp +++ b/client/platforms/linux/daemon/iputilslinux.cpp @@ -31,7 +31,9 @@ IPUtilsLinux::~IPUtilsLinux() { } bool IPUtilsLinux::addInterfaceIPs(const InterfaceConfig& config) { - return addIP4AddressToDevice(config) && addIP6AddressToDevice(config); + bool ret = addIP4AddressToDevice(config); + addIP6AddressToDevice(config); + return ret; } bool IPUtilsLinux::setMTUAndUp(const InterfaceConfig& config) { diff --git a/client/platforms/linux/daemon/linuxfirewall.cpp b/client/platforms/linux/daemon/linuxfirewall.cpp index 1057b8fa..157004dd 100644 --- a/client/platforms/linux/daemon/linuxfirewall.cpp +++ b/client/platforms/linux/daemon/linuxfirewall.cpp @@ -455,9 +455,6 @@ void LinuxFirewall::updateDNSServers(const QStringList& servers) void LinuxFirewall::updateAllowNets(const QStringList& servers) { - static QStringList existingServers {}; - - existingServers = servers; execute(QStringLiteral("iptables -F %1.110.allowNets").arg(kAnchorName)); for (const QString& rule : getAllowRule(servers)) execute(QStringLiteral("iptables -A %1.110.allowNets %2").arg(kAnchorName, rule)); diff --git a/client/platforms/linux/daemon/wireguardutilslinux.cpp b/client/platforms/linux/daemon/wireguardutilslinux.cpp index 1528d901..0fbb65a8 100644 --- a/client/platforms/linux/daemon/wireguardutilslinux.cpp +++ b/client/platforms/linux/daemon/wireguardutilslinux.cpp @@ -17,6 +17,8 @@ #include "leakdetector.h" #include "logger.h" +#include "killswitch.h" + constexpr const int WG_TUN_PROC_TIMEOUT = 5000; constexpr const char* WG_RUNTIME_DIR = "/var/run/amneziawg"; @@ -182,7 +184,7 @@ bool WireguardUtilsLinux::deleteInterface() { QFile::remove(wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".name")); // double-check + ensure our firewall is installed and enabled - LinuxFirewall::uninstall(); + KillSwitch::instance()->disableKillSwitch(); return true; } diff --git a/client/platforms/macos/daemon/wireguardutilsmacos.cpp b/client/platforms/macos/daemon/wireguardutilsmacos.cpp index eae22837..1d8aa6e0 100644 --- a/client/platforms/macos/daemon/wireguardutilsmacos.cpp +++ b/client/platforms/macos/daemon/wireguardutilsmacos.cpp @@ -16,6 +16,8 @@ #include "leakdetector.h" #include "logger.h" +#include "killswitch.h" + constexpr const int WG_TUN_PROC_TIMEOUT = 5000; constexpr const char* WG_RUNTIME_DIR = "/var/run/amneziawg"; @@ -180,7 +182,7 @@ bool WireguardUtilsMacos::deleteInterface() { QFile::remove(wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".name")); // double-check + ensure our firewall is installed and enabled - MacOSFirewall::uninstall(); + KillSwitch::instance()->disableKillSwitch(); return true; } diff --git a/client/platforms/windows/daemon/windowsfirewall.cpp b/client/platforms/windows/daemon/windowsfirewall.cpp index 6eae9149..b10e6caf 100644 --- a/client/platforms/windows/daemon/windowsfirewall.cpp +++ b/client/platforms/windows/daemon/windowsfirewall.cpp @@ -29,6 +29,8 @@ #include "logger.h" #include "platforms/windows/windowsutils.h" +#include "killswitch.h" + #define IPV6_ADDRESS_SIZE 16 // ID for the Firewall Sublayer @@ -180,16 +182,29 @@ bool WindowsFirewall::enableInterface(int vpnAdapterIndex) { } \ } - logger.info() << "Enabling firewall Using Adapter:" << vpnAdapterIndex; + logger.info() << "Enabling Killswitch Using Adapter:" << vpnAdapterIndex; + if (vpnAdapterIndex < 0) + { + IPAddress allv4("0.0.0.0/0"); + if (!blockTrafficTo(allv4, MED_WEIGHT, + "Block Internet", "killswitch")) { + return false; + } + IPAddress allv6("::/0"); + if (!blockTrafficTo(allv6, MED_WEIGHT, + "Block Internet", "killswitch")) { + return false; + } + } else FW_OK(allowTrafficOfAdapter(vpnAdapterIndex, MED_WEIGHT, - "Allow usage of VPN Adapter")); + "Allow usage of VPN Adapter")); FW_OK(allowDHCPTraffic(MED_WEIGHT, "Allow DHCP Traffic")); - FW_OK(allowHyperVTraffic(MED_WEIGHT, "Allow Hyper-V Traffic")); + FW_OK(allowHyperVTraffic(MAX_WEIGHT, "Allow Hyper-V Traffic")); FW_OK(allowTrafficForAppOnAll(getCurrentPath(), MAX_WEIGHT, "Allow all for DefaultVPN.exe")); FW_OK(blockTrafficOnPort(53, MED_WEIGHT, "Block all DNS")); - FW_OK( - allowLoopbackTraffic(MED_WEIGHT, "Allow Loopback traffic on device %1")); + FW_OK(allowLoopbackTraffic(MED_WEIGHT, + "Allow Loopback traffic on device %1")); logger.debug() << "Killswitch on! Rules:" << m_activeRules.length(); return true; @@ -226,6 +241,37 @@ bool WindowsFirewall::enableLanBypass(const QList& ranges) { return true; } +// Allow unprotected traffic sent to the following address ranges. +bool WindowsFirewall::allowTrafficRange(const QStringList& ranges) { + // Start the firewall transaction + auto result = FwpmTransactionBegin(m_sessionHandle, NULL); + if (result != ERROR_SUCCESS) { + disableKillSwitch(); + return false; + } + auto cleanup = qScopeGuard([&] { + FwpmTransactionAbort0(m_sessionHandle); + disableKillSwitch(); + }); + + for (const QString& addr : ranges) { + logger.debug() << "Allow killswitch exclude: " << addr; + if (!allowTrafficTo(QHostAddress(addr), HIGH_WEIGHT, "Allow killswitch bypass traffic")) { + return false; + } + } + + result = FwpmTransactionCommit0(m_sessionHandle); + if (result != ERROR_SUCCESS) { + logger.error() << "FwpmTransactionCommit0 failed with error:" << result; + return false; + } + + cleanup.dismiss(); + return true; +} + + bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) { // Start the firewall transaction auto result = FwpmTransactionBegin(m_sessionHandle, NULL); @@ -262,12 +308,20 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) { } } + for (const QString& dns : config.m_allowedDnsServers) { + logger.debug() << "Allow DNS: " << dns; + if (!allowTrafficTo(QHostAddress(dns), 53, HIGH_WEIGHT, + "Allow DNS-Server", config.m_serverPublicKey)) { + return false; + } + } + if (!config.m_excludedAddresses.empty()) { for (const QString& i : config.m_excludedAddresses) { logger.debug() << "excludedAddresses range: " << i; if (!allowTrafficTo(i, HIGH_WEIGHT, - "Allow Ecxlude route", config.m_serverPublicKey)) { + "Allow Ecxlude route", config.m_serverPublicKey)) { return false; } } @@ -313,37 +367,41 @@ bool WindowsFirewall::disablePeerTraffic(const QString& pubkey) { } bool WindowsFirewall::disableKillSwitch() { - auto result = FwpmTransactionBegin(m_sessionHandle, NULL); - auto cleanup = qScopeGuard([&] { + return KillSwitch::instance()->disableKillSwitch(); +} + +bool WindowsFirewall::allowAllTraffic() { + auto result = FwpmTransactionBegin(m_sessionHandle, NULL); + auto cleanup = qScopeGuard([&] { + if (result != ERROR_SUCCESS) { + FwpmTransactionAbort0(m_sessionHandle); + } + }); if (result != ERROR_SUCCESS) { - FwpmTransactionAbort0(m_sessionHandle); + logger.error() << "FwpmTransactionBegin0 failed. Return value:.\n" + << result; + return false; } - }); - if (result != ERROR_SUCCESS) { - logger.error() << "FwpmTransactionBegin0 failed. Return value:.\n" - << result; - return false; - } - for (const auto& filterID : m_peerRules.values()) { - FwpmFilterDeleteById0(m_sessionHandle, filterID); - } + for (const auto& filterID : m_peerRules.values()) { + FwpmFilterDeleteById0(m_sessionHandle, filterID); + } - for (const auto& filterID : qAsConst(m_activeRules)) { - FwpmFilterDeleteById0(m_sessionHandle, filterID); - } + for (const auto& filterID : qAsConst(m_activeRules)) { + FwpmFilterDeleteById0(m_sessionHandle, filterID); + } - // Commit! - result = FwpmTransactionCommit0(m_sessionHandle); - if (result != ERROR_SUCCESS) { - logger.error() << "FwpmTransactionCommit0 failed. Return value:.\n" - << result; - return false; - } - m_peerRules.clear(); - m_activeRules.clear(); - logger.debug() << "Firewall Disabled!"; - return true; + // Commit! + result = FwpmTransactionCommit0(m_sessionHandle); + if (result != ERROR_SUCCESS) { + logger.error() << "FwpmTransactionCommit0 failed. Return value:.\n" + << result; + return false; + } + m_peerRules.clear(); + m_activeRules.clear(); + logger.debug() << "Firewall Disabled!"; + return true; } bool WindowsFirewall::allowTrafficForAppOnAll(const QString& exePath, diff --git a/client/platforms/windows/daemon/windowsfirewall.h b/client/platforms/windows/daemon/windowsfirewall.h index 55ee9417..9a0062da 100644 --- a/client/platforms/windows/daemon/windowsfirewall.h +++ b/client/platforms/windows/daemon/windowsfirewall.h @@ -43,6 +43,8 @@ class WindowsFirewall final : public QObject { bool enablePeerTraffic(const InterfaceConfig& config); bool disablePeerTraffic(const QString& pubkey); bool disableKillSwitch(); + bool allowAllTraffic(); + bool allowTrafficRange(const QStringList& ranges); private: static bool initSublayer(); diff --git a/client/platforms/windows/daemon/wireguardutilswindows.cpp b/client/platforms/windows/daemon/wireguardutilswindows.cpp index 0823b9d7..d01ef54a 100644 --- a/client/platforms/windows/daemon/wireguardutilswindows.cpp +++ b/client/platforms/windows/daemon/wireguardutilswindows.cpp @@ -14,8 +14,6 @@ #include "leakdetector.h" #include "logger.h" -#include "platforms/windows/windowscommons.h" -#include "windowsdaemon.h" #include "windowsfirewall.h" #pragma comment(lib, "iphlpapi.lib") @@ -269,6 +267,13 @@ bool WireguardUtilsWindows::updateRoutePrefix(const IPAddress& prefix) { if (result == ERROR_OBJECT_ALREADY_EXISTS) { return true; } + + // Case for ipv6 route with disabled ipv6 + if (prefix.address().protocol() == QAbstractSocket::IPv6Protocol + && result == ERROR_NOT_FOUND) { + return true; + } + if (result != NO_ERROR) { logger.error() << "Failed to create route to" << prefix.toString() diff --git a/client/protocols/openvpnprotocol.cpp b/client/protocols/openvpnprotocol.cpp index 4c2feb52..429b85a6 100644 --- a/client/protocols/openvpnprotocol.cpp +++ b/client/protocols/openvpnprotocol.cpp @@ -171,6 +171,11 @@ ErrorCode OpenVpnProtocol::start() return lastError(); } +#ifdef AMNEZIA_DESKTOP + IpcClient::Interface()->addKillSwitchAllowedRange(QStringList(NetworkUtilities::getIPAddress( + m_configData.value(amnezia::config_key::hostName).toString()))); +#endif + // Detect default gateway #ifdef Q_OS_MAC QProcess p; diff --git a/client/protocols/protocols_defs.h b/client/protocols/protocols_defs.h index 865edae4..feeabb2f 100644 --- a/client/protocols/protocols_defs.h +++ b/client/protocols/protocols_defs.h @@ -95,6 +95,8 @@ namespace amnezia constexpr char splitTunnelApps[] = "splitTunnelApps"; constexpr char appSplitTunnelType[] = "appSplitTunnelType"; + constexpr char allowedDnsServers[] = "allowedDnsServers"; + constexpr char killSwitchOption[] = "killSwitchOption"; constexpr char crc[] = "crc"; diff --git a/client/resources.qrc b/client/resources.qrc index 1da08178..258be90c 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -131,6 +131,7 @@ ui/qml/Components/SettingsContainersListView.qml ui/qml/Components/ShareConnectionDrawer.qml ui/qml/Components/TransportProtoSelector.qml + ui/qml/Components/AddSitePanel.qml ui/qml/Config/GlobalConfig.qml ui/qml/Config/qmldir ui/qml/Controls2/BackButtonType.qml @@ -145,7 +146,9 @@ ui/qml/Controls2/DropDownType.qml ui/qml/Controls2/FlickableType.qml ui/qml/Controls2/Header2Type.qml - ui/qml/Controls2/HeaderType.qml + ui/qml/Controls2/BaseHeaderType.qml + ui/qml/Controls2/HeaderTypeWithButton.qml + ui/qml/Controls2/HeaderTypeWithSwitcher.qml ui/qml/Controls2/HorizontalRadioButton.qml ui/qml/Controls2/ImageButtonType.qml ui/qml/Controls2/LabelWithButtonType.qml @@ -201,6 +204,8 @@ ui/qml/Pages2/PageSettingsBackup.qml ui/qml/Pages2/PageSettingsConnection.qml ui/qml/Pages2/PageSettingsDns.qml + ui/qml/Pages2/PageSettingsKillSwitch.qml + ui/qml/Pages2/PageSettingsKillSwitchExceptions.qml ui/qml/Pages2/PageSettingsLogging.qml ui/qml/Pages2/PageSettingsServerData.qml ui/qml/Pages2/PageSettingsServerInfo.qml diff --git a/client/secure_qsettings.cpp b/client/secure_qsettings.cpp index 068f896a..1dffc9b9 100644 --- a/client/secure_qsettings.cpp +++ b/client/secure_qsettings.cpp @@ -1,7 +1,7 @@ #include "secure_qsettings.h" -#include "QAead.h" -#include "QBlockCipher.h" +#include "../client/3rd/QSimpleCrypto/src/include/QAead.h" +#include "../client/3rd/QSimpleCrypto/src/include/QBlockCipher.h" #include "utilities.h" #include #include diff --git a/client/secure_qsettings.h b/client/secure_qsettings.h index 3f04096e..8878e1d5 100644 --- a/client/secure_qsettings.h +++ b/client/secure_qsettings.h @@ -6,7 +6,7 @@ #include #include -#include "keychain.h" +#include "../client/3rd/qtkeychain/qtkeychain/keychain.h" class SecureQSettings : public QObject { diff --git a/client/settings.cpp b/client/settings.cpp index 94b11d00..9a0a32e5 100644 --- a/client/settings.cpp +++ b/client/settings.cpp @@ -443,6 +443,16 @@ void Settings::setKillSwitchEnabled(bool enabled) setValue("Conf/killSwitchEnabled", enabled); } +bool Settings::isStrictKillSwitchEnabled() const +{ + return value("Conf/strictKillSwitchEnabled", false).toBool(); +} + +void Settings::setStrictKillSwitchEnabled(bool enabled) +{ + setValue("Conf/strictKillSwitchEnabled", enabled); +} + QString Settings::getInstallationUuid(const bool needCreate) { auto uuid = value("Conf/installationUuid", "").toString(); @@ -548,3 +558,13 @@ void Settings::disableHomeAdLabel() { setValue("Conf/homeAdLabelVisible", false); } + +QStringList Settings::allowedDnsServers() const +{ + return value("Conf/allowedDnsServers").toStringList(); +} + +void Settings::setAllowedDnsServers(const QStringList &servers) +{ + setValue("Conf/allowedDnsServers", servers); +} diff --git a/client/settings.h b/client/settings.h index b383d3da..01155c0c 100644 --- a/client/settings.h +++ b/client/settings.h @@ -213,6 +213,10 @@ public: bool isKillSwitchEnabled() const; void setKillSwitchEnabled(bool enabled); + + bool isStrictKillSwitchEnabled() const; + void setStrictKillSwitchEnabled(bool enabled); + QString getInstallationUuid(const bool needCreate); void resetGatewayEndpoint(); @@ -225,6 +229,9 @@ public: bool isHomeAdLabelVisible(); void disableHomeAdLabel(); + QStringList allowedDnsServers() const; + void setAllowedDnsServers(const QStringList &servers); + signals: void saveLogsChanged(bool enabled); void screenshotsEnabledChanged(bool enabled); diff --git a/client/translations/defaultvpn_ru_RU.ts b/client/translations/defaultvpn_ru_RU.ts index 84255853..5a6c174f 100644 --- a/client/translations/defaultvpn_ru_RU.ts +++ b/client/translations/defaultvpn_ru_RU.ts @@ -100,8 +100,8 @@ - AmneziaFree provides free unlimited access to a basic set of web sites, such as Facebook, Instagram, Twitter (X), Discord, Telegram, and others. YouTube is not included in the free plan. - AmneziaFree предоставляет бесплатный неограниченный доступ к базовому набору сайтов и приложений, таким как Facebook, Instagram, Twitter (X), Discord, Telegram и другим. YouTube не включен в бесплатный тариф. + Amnezia Free provides unlimited, free access to a basic set of websites and apps, including Facebook, Instagram, Twitter (X), Discord, Telegram, and more. YouTube is not included in the free plan. + Amnezia Free позволяет бесплатно и без ограничений пользоваться базовым набором сайтов и приложений, включая Facebook, Instagram, Twitter (X), Discord, Telegram и другие. YouTube не входит в бесплатный тариф. Amnezia Premium is VPN for comfortable work, downloading large files and watching videos in 8K resolution. Works for any sites with no restrictions. @@ -127,6 +127,11 @@ %1 days %1 дней + + + + + VPN will open only popular sites blocked in your region, such as Instagram, Facebook, Twitter and others. Other sites will be opened from your real IP address, <a href="%1/free" style="color: #FBB26A;">more details on the website.</a> @@ -1752,8 +1757,12 @@ Already installed containers were found on the server. All installed containers + This will unlink the device from your subscription. You can reconnect it anytime by pressing "Reload API config" in subscription settings on device. + Это отключит устройство от вашей подписки. Вы можете повторно подключить его в любое время, нажав "Перезагрузить конфигурацию API" в настройках подписки на устройстве. + + This will unlink the device from your subscription. You can reconnect it anytime by pressing Connect. - Это устройство будет отвязано от вашей подписки. Вы можете подключить его снова в любой момент, нажав кнопку "Подключиться". + Это устройство будет отвязано от вашей подписки. Вы можете подключить его снова в любой момент, нажав кнопку "Подключиться". @@ -2105,8 +2114,12 @@ Already installed containers were found on the server. All installed containers + This will unlink the device from your subscription. You can reconnect it anytime by pressing "Reload API config" in subscription settings on device. + Это отключит устройство от вашей подписки. Вы можете повторно подключить его в любое время, нажав "Перезагрузить конфигурацию API" в настройках подписки на устройстве. + + This will unlink the device from your subscription. You can reconnect it anytime by pressing Connect. - Это устройство будет отвязано от вашей подписки. Вы можете подключить его снова в любой момент, нажав кнопку Подключиться. + Это устройство будет отвязано от вашей подписки. Вы можете подключить его снова в любой момент, нажав кнопку Подключиться. @@ -2815,11 +2828,6 @@ Already installed containers were found on the server. All installed containers No new installed containers found Новые установленные протоколы и сервисы не обнаружены - - - - - @@ -3023,11 +3031,6 @@ Already installed containers were found on the server. All installed containers Clear %1 profile? Очистить профиль %1? - - - - - connection settings @@ -3286,7 +3289,7 @@ It's okay as long as it's from someone you trust. Что у вас есть? - + File with connection settings Файл с настройками подключения @@ -3295,112 +3298,135 @@ It's okay as long as it's from someone you trust. Файл с настройками подключения или резервной копией - + Connection Соединение - + Settings Настройки - + Enable logs Включить запись логов + Export client logs + + + + + Save + Сохранить + + + + Logs files (*.log) + Файлы логов (*.log) + + + + Logs file saved + Файл с логами сохранен + + + Support tag Support tag - + Copied Скопировано - + Insert the key, add a configuration file or scan the QR-code Вставьте ключ, добавьте файл конфигурации или отсканируйте QR-код - + Insert key Вставьте ключ - + Insert Вставить - + Continue Продолжить - + Other connection options Другие варианты подключения - + Site Amnezia Сайт Amnezia - + VPN by Amnezia VPN от Amnezia - + Connect to classic paid and free VPN services from Amnezia Подключайтесь к классическим платным и бесплатным VPN-сервисам от Amnezia - + Self-hosted VPN Self-hosted VPN - + Configure Amnezia VPN on your own server Настроить VPN на собственном сервере - + Restore from backup Восстановить из резервной копии - + + + + - + Open backup file Открыть резервную копию - + Backup files (*.backup) Файлы резервных копий (*.backup) - + Open config file Открыть файл с конфигурацией - + QR code QR-код - + I have nothing У меня ничего нет @@ -4674,94 +4700,56 @@ and will not be shared or disclosed to the Amnezia or any third parties Создайте на сервере файловое хранилище для безопасного хранения и передачи файлов. - This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for protecting against blocking. + + AmneziaWG is a modern VPN protocol based on WireGuard, combining simplified architecture with high performance across all devices. It addresses WireGuard's main vulnerability (easy detection by DPI systems) through advanced obfuscation techniques, making VPN traffic indistinguishable from regular internet traffic. -OpenVPN provides a secure VPN connection by encrypting all internet traffic between the client and the server. +AmneziaWG is an excellent choice for those seeking a fast, stealthy VPN connection. -Cloak protects OpenVPN from detection and blocking. +Features: +* Available on all AmneziaVPN platforms +* Low battery consumption on mobile devices +* Minimal settings required +* Undetectable by traffic analysis systems (DPI) +* Operates over UDP protocol + AmneziaWG — современный VPN-протокол на основе WireGuard, сочетающий простую архитектуру и высокую производительность на всех устройствах. Он устраняет основной недостаток WireGuard (лёгкое обнаружение трафика системами DPI) за счёт эффективного маскирования VPN-трафика под обычный интернет-трафик. -Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, and also protects the VPN from detection by Active Probing. This makes it very resistant to being detected +Таким образом, AmneziaWG идеально подойдёт тем, кто ищет быстрое и незаметное VPN-соединение. -Immediately after receiving the first data packet, Cloak authenticates the incoming connection. If authentication fails, the plugin masks the server as a fake website and your VPN becomes invisible to analysis systems. - -If there is a extreme level of Internet censorship in your region, we advise you to use only OpenVPN over Cloak from the first connection - -* Available in the AmneziaVPN across all platforms -* High power consumption on mobile devices -* Flexible settings -* Not recognised by DPI analysis systems -* Works over TCP network protocol, 443 port. - - Это связка протокола OpenVPN и плагина Cloak, разработанная специально для защиты от блокировки. - -OpenVPN обеспечивает безопасное VPN-соединение, шифруя весь интернет-трафик между клиентом и сервером. - -Cloak защищает OpenVPN от обнаружения и блокировки. - -Cloak изменяет метаданные пакетов таким образом, что полностью маскирует VPN-трафик под обычный веб-трафик, а также защищает VPN от обнаружения с помощью активного зондирования. Это делает его очень защищенным от обнаружения. - -Сразу после получения первого пакета данных Cloak устанавливает подлинность входящего соединения. Если аутентификация не проходит, плагин маскирует сервер под фальшивый веб-сайт, и ваш VPN становится невидимым для систем анализа трафика. - -Если в вашем регионе наблюдается жесткая интернет-цензура, мы советуем вам уже при первом подключении использовать только OpenVPN over Cloak. - -* Доступен в AmneziaVPN на всех платформах -* Высокое энергопотребление на мобильных устройствах -* Гибкие настройки -* Не распознается системами DPI-анализа -* Работает по сетевому протоколу TCP, использует порт 443 - - - A relatively new popular VPN protocol with a simplified architecture. -WireGuard provides stable VPN connection and high performance on all devices. It uses hard-coded encryption settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput. -WireGuard is very susceptible to blocking due to its distinct packet signatures. Unlike some other VPN protocols that employ obfuscation techniques, the consistent signature patterns of WireGuard packets can be more easily identified and thus blocked by advanced Deep Packet Inspection (DPI) systems and other network monitoring tools. - -* Available in the AmneziaVPN across all platforms -* Low power consumption -* Minimum number of settings -* Easily recognised by DPI analysis systems, susceptible to blocking -* Works over UDP network protocol. - Относительно новый и популярный VPN-протокол с простой архитектурой. -WireGuard обеспечивает стабильное VPN-соединение и высокую производительность на всех устройствах. Он использует строго заданные настройки шифрования. WireGuard по сравнению с OpenVPN имеет меньшую задержку и лучшую пропускную способность при передаче данных. -WireGuard очень уязвим для блокировки из-за характерных сигнатур пакетов. В отличие от некоторых других VPN-протоколов, использующих методы обфускации, последовательные сигнатуры пакетов WireGuard легче идентифицируются и, следовательно, могут блокироваться современными Deep Packet Inspection (DPI) системами и другими инструментами для сетевого мониторинга. - -* Доступен в AmneziaVPN на всех платформах +Особенности: +* Доступен во всех версиях AmneziaVPN * Низкое энергопотребление на мобильных устройствах -* Минимальная конфигурация -* Легко распознается системами DPI-анализа, поддается блокировке -* Работает по сетевому протоколу UDP +* Минимум настроек +* Незаметен для систем анализа трафика (DPI) +* Работает по протоколу UDP + - 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. -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. -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. -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. - Протокол REALITY, новаторская разработка создателей XRay, специально спроектирован для противодействия самой строгой цензуре с помощью нового способа обхода блокировок. -Он уникальным образом идентифицирует цензоров на этапе TLS-рукопожатия, беспрепятственно работая в качестве прокси для реальных клиентов и перенаправляя цензоров на реальные сайты, такие как google.com, тем самым предъявляя подлинный TLS-сертификат и данные. -REALITY отличается от аналогичных технологий благодаря способности без специальной настройки маскировать веб-трафик так, как будто он поступает со случайных легитимных сайтов. -В отличие от более старых протоколов, таких как VMess, VLESS и транспорт XTLS-Vision, технология распознавания "друг или враг" на этапе TLS-рукопожатия повышает безопасность и обходит обнаружение сложными системами DPI-анализа, которые используют методы активного зондирования. Это делает REALITY эффективным решением для поддержания свободы интернета в регионах с жесткой цензурой. - - - IKEv2, paired with the IPSec encryption layer, stands as a modern and stable VPN protocol. -One of its distinguishing features is its ability to swiftly switch between networks and devices, making it particularly adaptive in dynamic network environments. -While it offers a blend of security, stability, and speed, it's essential to note that IKEv2 can be easily detected and is susceptible to blocking. + + REALITY is an innovative protocol developed by the creators of XRay, designed specifically to combat high levels of internet censorship. REALITY identifies censorship systems during the TLS handshake, redirecting suspicious traffic seamlessly to legitimate websites like google.com while providing genuine TLS certificates. This allows VPN traffic to blend indistinguishably with regular web traffic without special configuration. +Unlike older protocols such as VMess, VLESS, and XTLS-Vision, REALITY incorporates an advanced built-in "friend-or-foe" detection mechanism, effectively protecting against DPI and other traffic analysis methods. -* Available in the AmneziaVPN only on Windows -* Low power consumption, on mobile devices -* Minimal configuration -* Recognised by DPI analysis systems -* Works over UDP network protocol, ports 500 and 4500. - IKEv2 в сочетании с уровнем шифрования IPSec представляет собой современный и стабильный VPN-протокол. -Он может быстро переключаться между сетями и устройствами, что делает его особенно адаптивным в динамичных сетевых средах. -Несмотря на сочетание безопасности, стабильности и скорости, необходимо отметить, что IKEv2 легко обнаруживается и подвержен блокировке. +Features: +* Resistant to active probing and DPI detection +* No special configuration required to disguise traffic +* Highly effective in heavily censored regions +* Minimal battery consumption on devices +* Operates over TCP protocol + REALITY — это инновационный протокол от разработчиков XRay, специально созданный для эффективного противодействия жесткой интернет-цензуре. -* Доступен в AmneziaVPN только для Windows -* Низкое энергопотребление на мобильных устройствах -* Минимальная конфигурация -* Распознается системами DPI-анализа -* Работает по сетевому протоколу UDP, использует порты 500 и 4500 +REALITY распознаёт системы блокировки во время TLS-рукопожатия и незаметно перенаправляет подозрительные запросы на реальные сайты, такие как google.com, предъявляя подлинные TLS-сертификаты. Это позволяет маскировать VPN-трафик под обычный веб-трафик без дополнительных настроек. + +В отличие от протоколов старого поколения (VMess, VLESS и XTLS-Vision), REALITY использует встроенную технологию распознавания «свой-чужой», надёжно защищая от DPI и других методов сетевого анализа. + +Особенности: +* Устойчив к активному зондированию и DPI-системам +* Не требует специальной настройки для маскировки трафика +* Эффективен в регионах с жесткой цензурой +* Минимальное энергопотребление на устройствах +* Работает по протоколу TCP + - + DNS Service Сервис DNS @@ -4772,7 +4760,7 @@ While it offers a blend of security, stability, and speed, it's essential t - + Website in Tor network Веб-сайт в сети Tor @@ -4811,187 +4799,141 @@ While it offers a blend of security, stability, and speed, it's essential t XRay with REALITY masks VPN traffic as web traffic and protects against active probing. It is highly resistant to detection and offers high speed. XRay с REALITY маскирует VPN-трафик под веб-трафик. Обладает высокой устойчивостью к обнаружению и обеспечивает высокую скорость соединения. + + + + + - OpenVPN stands as one of the most popular and time-tested VPN protocols available. -It employs its unique security protocol, leveraging the strength of SSL/TLS for encryption and key exchange. Furthermore, OpenVPN's support for a multitude of authentication methods makes it versatile and adaptable, catering to a wide range of devices and operating systems. Due to its open-source nature, OpenVPN benefits from extensive scrutiny by the global community, which continually reinforces its security. With a strong balance of performance, security, and compatibility, OpenVPN remains a top choice for privacy-conscious individuals and businesses alike. + OpenVPN is one of the most popular and reliable VPN protocols. It uses SSL/TLS encryption, supports a wide variety of devices and operating systems, and is continuously improved by the community due to its open-source nature. It provides a good balance between speed and security but is easily recognized by DPI systems, making it susceptible to blocking. -* Available in the DefaultVPN across all platforms -* Normal power consumption on mobile devices -* Flexible customisation to suit user needs to work with different operating systems and devices -* Recognised by DPI systems and therefore susceptible to blocking -* Can operate over both TCP and UDP network protocols. - +Features: +* Available on all AmneziaVPN platforms +* Normal battery consumption on mobile devices +* Flexible customization for various devices and OS +* Operates over both TCP and UDP protocols + OpenVPN — один из самых популярных и надежных VPN-протоколов. Он использует шифрование SSL/TLS, совместим со множеством устройств и ОС, а благодаря открытому коду постоянно совершенствуется сообществом. Имеет хороший баланс скорости и безопасности, но легко распознаётся системами DPI, что делает его уязвимым к блокировкам. + +Особенности: +* Доступен во всех приложениях AmneziaVPN +* Нормальное энергопотребление на мобильных устройствах +* Гибкие настройки под разные устройства и ОС +* Работает по TCP и UDP - - Shadowsocks, inspired by the SOCKS5 protocol, safeguards the connection using the AEAD cipher. Although Shadowsocks is designed to be discreet and challenging to identify, it isn't identical to a standard HTTPS connection.However, certain traffic analysis systems might still detect a Shadowsocks connection. Due to limited support in Amnezia, it's recommended to use AmneziaWG protocol. + + Shadowsocks is based on the SOCKS5 protocol and encrypts connections using AEAD cipher. Although designed to be discreet, it doesn't mimic a standard HTTPS connection and can be detected by some DPI systems. Due to limited support in Amnezia, we recommend using the AmneziaWG protocol. -* Available in the DefaultVPN only on desktop platforms -* Configurable encryption protocol +Features: +* Available in AmneziaVPN only on desktop platforms +* Customizable encryption protocol * Detectable by some DPI systems -* Works over TCP network protocol. - - - - - This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for protecting against detection. - -OpenVPN provides a secure VPN connection by encrypting all internet traffic between the client and the server. - -Cloak protects OpenVPN from detection. - -Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, and also protects the VPN from detection by Active Probing. This makes it very resistant to being detected - -Immediately after receiving the first data packet, Cloak authenticates the incoming connection. If authentication fails, the plugin masks the server as a fake website and your VPN becomes invisible to analysis systems. - -* Available in the DefaultVPN across all platforms -* High power consumption on mobile devices -* Flexible settings -* Not recognised by detection systems -* Works over TCP network protocol, 443 port. +* Operates over TCP protocol - + Shadowsocks основан на протоколе SOCKS5 и шифрует соединение алгоритмом AEAD. Он разработан так, чтобы быть малозаметным, однако не идентичен HTTPS, поэтому может распознаваться некоторыми системами DPI. В связи с ограниченной поддержкой в Amnezia, рекомендуем использовать протокол AmneziaWG. + +Особенности: +* Доступен только на ПК в AmneziaVPN +* Настраиваемое шифрование +* Может обнаруживаться некоторыми DPI-системами +* Работает по протоколу TCP - - A relatively new popular VPN protocol with a simplified architecture. -WireGuard provides stable VPN connection and high performance on all devices. It uses hard-coded encryption settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput. -WireGuard is very susceptible to detection and blocking due to its distinct packet signatures. Unlike some other VPN protocols that employ obfuscation techniques, the consistent signature patterns of WireGuard packets can be more easily identified and thus blocked by advanced Deep Packet Inspection (DPI) systems and other network monitoring tools. + + This combination includes the OpenVPN protocol and the Cloak plugin, specifically designed to protect against blocking. -* Available in the DefaultVPN across all platforms -* Low power consumption -* Minimum number of settings -* Easily recognised by DPI analysis systems, susceptible to blocking -* Works over UDP network protocol. - - - - - A modern iteration of the popular VPN protocol, AmneziaWG builds upon the foundation set by WireGuard, retaining its simplified architecture and high-performance capabilities across devices. -While WireGuard is known for its efficiency, it had issues with being easily detected due to its distinct packet signatures. AmneziaWG solves this problem by using better obfuscation methods, making its traffic blend in with regular internet traffic. -This means that AmneziaWG keeps the fast performance of the original while adding an extra layer of stealth, making it a great choice for those wanting a fast and discreet VPN connection. +OpenVPN securely encrypts all internet traffic between your device and the server. -* Available in the DefaultVPN across all platforms -* Low power consumption -* Minimum number of settings -* Not recognised by traffic analysis systems -* Works over UDP network protocol. - - - - - IKEv2, paired with the IPSec encryption layer, stands as a modern and stable VPN protocol. -One of its distinguishing features is its ability to swiftly switch between networks and devices, making it particularly adaptive in dynamic network environments. -While it offers a blend of security, stability, and speed, it's essential to note that IKEv2 can be easily detected and is susceptible to blocking. +The Cloak plugin further protects the connection from DPI detection. It modifies traffic metadata to disguise VPN traffic as regular web traffic and prevents detection through active probing. If an incoming connection fails authentication, Cloak serves a fake website, making your VPN invisible to traffic analysis systems. -* Available in the DefaultVPN only on Windows -* Low power consumption, on mobile devices -* Minimal configuration -* Recognised by DPI analysis systems -* Works over UDP network protocol, ports 500 and 4500. - - - - OpenVPN stands as one of the most popular and time-tested VPN protocols available. -It employs its unique security protocol, leveraging the strength of SSL/TLS for encryption and key exchange. Furthermore, OpenVPN's support for a multitude of authentication methods makes it versatile and adaptable, catering to a wide range of devices and operating systems. Due to its open-source nature, OpenVPN benefits from extensive scrutiny by the global community, which continually reinforces its security. With a strong balance of performance, security, and compatibility, OpenVPN remains a top choice for privacy-conscious individuals and businesses alike. +In regions with heavy internet censorship, we strongly recommend using OpenVPN with Cloak from your first connection. -* Available in the AmneziaVPN across all platforms -* Normal power consumption on mobile devices -* Flexible customisation to suit user needs to work with different operating systems and devices -* Recognised by DPI systems and therefore susceptible to blocking -* Can operate over both TCP and UDP network protocols. - OpenVPN является одним из самых популярных и проверенных временем VPN-протоколов. Он использует собственный протокол безопасности, и криптографические протоколы SSL/TLS для шифрования и обмена ключами. Более того, поддержка множества методов аутентификации делает OpenVPN универсальным, адаптируемым и подходящим для широкого спектра устройств и операционных систем. Благодаря своему открытому коду, OpenVPN подвергается тщательной проверке со стороны мирового сообщества, что постоянно укрепляет его безопасность. Имея отличный баланс между производительностью, безопасностью и совместимостью OpenVPN остается лучшим выбором для людей и компаний, заботящихся о конфиденциальности, однако OpenVPN легко распознается современными системами анализа трафика. -Доступен в AmneziaVPN на всех платформах -Нормальное энергопотребление на мобильных устройствах -Гибкая настройка полезная при работе с различными операционными системами и устройствами -Распознается системами DPI и, следовательно, уязвим к блокировкам -Может работать как по TCP, так и по UDP протоколу. - - - This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for protecting against detection. - -OpenVPN provides a secure VPN connection by encrypting all internet traffic between the client and the server. - -Cloak protects OpenVPN from detection. - -Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, and also protects the VPN from detection by Active Probing. This makes it very resistant to being detected - -Immediately after receiving the first data packet, Cloak authenticates the incoming connection. If authentication fails, the plugin masks the server as a fake website and your VPN becomes invisible to analysis systems. - -* Available in the AmneziaVPN across all platforms +Features: +* Available on all AmneziaVPN platforms * High power consumption on mobile devices -* Flexible settings -* Not recognised by detection systems -* Works over TCP network protocol, 443 port. - - Это связка протокола OpenVPN и плагина Cloak, созданная специально для защиты от обнаружения. +* Flexible configuration options +* Undetectable by DPI systems +* Operates over TCP protocol on port 443 + Эта комбинация состоит из протокола OpenVPN и плагина Cloak, специально разработанных для защиты от блокировок. -OpenVPN обеспечивает безопасное VPN-соединение, шифруя весь интернет-трафик между клиентом и сервером. +OpenVPN надёжно шифрует весь интернет-трафик между вами и сервером. -Плагин Cloak защищает OpenVPN от обнаружения. +Плагин Cloak дополнительно защищает соединение от распознавания системами DPI. Он изменяет метаданные трафика, маскируя VPN-подключение под обычный веб-трафик, и предотвращает обнаружение с помощью активного зондирования. Если попытка подключения не прошла аутентификацию, Cloak выдаёт поддельный веб-сайт, делая VPN невидимым для анализирующих систем. -Cloak может изменять метаданные пакета, чтобы полностью замаскировать VPN-трафик под обычный веб-трафик, а также защищает VPN от обнаружения с помощью метода Active Probing. Это делает его очень устойчивым к обнаружению. +Если в вашем регионе сильная интернет-цензура, мы рекомендуем сразу использовать OpenVPN с плагином Cloak. -Сразу после получения первого пакета данных Cloak аутентифицирует входящее соединение, если аутентификация не удалась, плагин маскирует сервер под настоящий веб-сайт, и ваш VPN становится невидимым для систем анализа. Имеет низкую скорость работы в сравнении с другими похожими протоколами. - -* Доступно в AmneziaVPN на всех платформах. +Особенности: +* Доступен на всех платформах AmneziaVPN * Высокое энергопотребление на мобильных устройствах * Гибкие настройки -* Не распознается системами обнаружения. -* Работает по сетевому протоколу TCP, порт 443. - +* Незаметен для систем DPI-анализа +* Использует протокол TCP на порту 443 - A relatively new popular VPN protocol with a simplified architecture. -WireGuard provides stable VPN connection and high performance on all devices. It uses hard-coded encryption settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput. -WireGuard is very susceptible to detection and blocking due to its distinct packet signatures. Unlike some other VPN protocols that employ obfuscation techniques, the consistent signature patterns of WireGuard packets can be more easily identified and thus blocked by advanced Deep Packet Inspection (DPI) systems and other network monitoring tools. + + WireGuard is a modern, streamlined VPN protocol offering stable connectivity and excellent performance across all devices. It uses fixed encryption settings, delivering lower latency and higher data transfer speeds compared to OpenVPN. However, WireGuard is easily identifiable by DPI systems due to its distinctive packet signatures, making it susceptible to blocking. -* Available in the AmneziaVPN across all platforms -* Low power consumption -* Minimum number of settings -* Easily recognised by DPI analysis systems, susceptible to blocking -* Works over UDP network protocol. - Популярный VPN-протокол с упрощенной архитектурой. -WireGuard обеспечивает стабильное VPN-соединение и высокую производительность на всех устройствах. Он использует закодированные настройки шифрования. WireGuard по сравнению с OpenVPN имеет меньшую задержку и лучшую пропускную способность передачи данных. -WireGuard очень чувствителен к обнаружению и блокировке из-за различных сигнатур пакетов. В отличие от некоторых других VPN протоколов, использующих методы запутывания, последовательные шаблоны сигнатур пакетов WireGuard легко идентифицируются системами анализа трафика. +Features: +* Available on all AmneziaVPN platforms +* Low power consumption on mobile devices +* Minimal configuration required +* Easily detected by DPI systems (susceptible to blocking) +* Operates over UDP protocol + WireGuard — современный и простой VPN-протокол, который обеспечивает стабильное соединение и высокую скорость передачи данных на любых устройствах. Он использует фиксированные настройки шифрования, имеет меньшую задержку и выше пропускную способность по сравнению с OpenVPN. -* Доступно в AmneziaVPN на всех платформах. -* Низкое энергопотребление -* Минимальное количество настроек -* Легко распознается системами анализа DPI, подвержен блокировке. -* Работает по сетевому протоколу UDP. +Однако WireGuard легко распознаётся системами DPI из-за характерных сигнатур трафика, что делает его уязвимым к блокировкам. + +Особенности: +* Доступен на всех платформах AmneziaVPN +* Низкое энергопотребление на мобильных устройствах +* Минимум настроек +* Легко определяется DPI-системами (подвержен блокировкам) +* Работает по протоколу UDP - A modern iteration of the popular VPN protocol, AmneziaWG builds upon the foundation set by WireGuard, retaining its simplified architecture and high-performance capabilities across devices. -While WireGuard is known for its efficiency, it had issues with being easily detected due to its distinct packet signatures. AmneziaWG solves this problem by using better obfuscation methods, making its traffic blend in with regular internet traffic. -This means that AmneziaWG keeps the fast performance of the original while adding an extra layer of stealth, making it a great choice for those wanting a fast and discreet VPN connection. + + IKEv2, combined with IPSec encryption, is a modern and reliable VPN protocol. It reconnects quickly when switching networks or devices, making it ideal for dynamic network environments. While it provides good security and speed, it's easily recognized by DPI systems and susceptible to blocking. -* Available in the AmneziaVPN across all platforms -* Low power consumption -* Minimum number of settings -* Not recognised by traffic analysis systems -* Works over UDP network protocol. - AmneziaWG — это современная версия популярного VPN протокола, основанная на базе WireGuard, сохранившая упрощенную архитектуру и высокопроизводительные возможности на всех устройствах. -Хотя WireGuard известен своей эффективностью, обнаружить его довольно легко из-за различных сигнатур пакетов. AmneziaWG решает эту проблему, используя более совершенные методы работы, смешивая свой трафик с обычным интернет-трафиком. -Это означает, что AmneziaWG сохраняет высокую производительность оригинала, добавляя при этом дополнительный уровень скрытности, что делает его отличным выбором для тех, кому нужно быстрое и незаметное VPN-соединение. +Features: +* Available in AmneziaVPN only on Windows +* Low battery consumption on mobile devices +* Minimal configuration required +* Detectable by DPI analysis systems(easily blocked) +* Operates over UDP protocol(ports 500 and 4500) + IKEv2 — современный и стабильный VPN-протокол, работающий совместно с шифрованием IPSec. Он обеспечивает быстрое переподключение при смене сети или устройства, отлично подходит для динамичных сетевых условий. Несмотря на хорошую скорость и безопасность, легко распознаётся системами DPI и подвержен блокировкам. -* Доступно в AmneziaVPN на всех платформах. -* Низкое энергопотребление -* Минимальное количество настроек -* Не распознается системами анализа трафика. -* Работает по сетевому протоколу UDP. +Особенности: +* Доступен в AmneziaVPN только на Windows +* Низкое энергопотребление на мобильных устройствах +* Минимум настроек +* Распознаётся DPI-системами (легко блокируется) +* Работает по UDP (порты 500 и 4500) - - The REALITY protocol, a pioneering development by the creators of XRay, is designed to provide the highest level of protection against detection through its innovative approach to security and privacy. -It uniquely identifies attackers during the TLS handshake phase, seamlessly operating as a proxy for legitimate clients while diverting attackers to genuine websites, thus presenting an authentic TLS certificate and data. -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. -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. This makes REALITY a robust solution for maintaining internet freedom. - Протокол REALITY, современная разработка от создателей XRay. Призван обеспечить высочайший уровень защиты от обнаружения благодаря инновационному подходу к безопасности и конфиденциальности. -Он безошибочно идентифицирует злоумышленников на этапе установления связи TLS, беспрепятственно работая в качестве прокси-сервера для оригинального клиента и перенаправляя злоумышленников на подлинные веб-сайты, предоставляя тем самым подлинный сертификат TLS и данные. -Эта расширенная возможность отличает REALITY от аналогичных технологий тем, что способна маскироваться под случайный веб-трафик без использования специальных настроек. -В отличие от старых протоколов, таких как VMess, VLESS и транспорт XTLS-Vision, REALITY имеет инновационную технологию распознавания «свой-чужой».Это делает REALITY надежным решением для обеспечения доступа к свободному интернету. + + AmneziaWG is a modern VPN protocol based on WireGuard, combining simplified architecture with high performance across all devices. It addresses WireGuard’s main vulnerability (easy detection by DPI systems) through advanced obfuscation techniques, making VPN traffic indistinguishable from regular internet traffic. + + AmneziaWG is an excellent choice for those seeking a fast, stealthy VPN connection. + + Features: + + * Available on all AmneziaVPN platforms + * Low battery consumption on mobile devices + * Minimal settings required + * Undetectable by traffic analysis systems (DPI) + * Operates over UDP protocol + + AmneziaWG — современный VPN-протокол на основе WireGuard, сочетающий простую архитектуру и высокую производительность на всех устройствах. Он устраняет основной недостаток WireGuard (лёгкое обнаружение трафика системами DPI) за счёт эффективного маскирования VPN-трафика под обычный интернет-трафик. + +Таким образом, AmneziaWG идеально подойдёт тем, кто ищет быстрое и незаметное VPN-соединение. + +Особенности: +* Доступен во всех версиях AmneziaVPN +* Низкое энергопотребление на мобильных устройствах +* Минимум настроек +* Незаметен для систем анализа трафика (DPI) +* Работает по протоколу UDP WireGuard - New popular VPN protocol with high performance, high speed and low power consumption. Recommended for regions with low levels of censorship. @@ -5020,49 +4962,7 @@ Unlike older protocols such as VMess, VLESS, and the XTLS-Vision transport, REAL Замените текущий DNS-сервер на свой собственный. Это повысит уровень вашей конфиденциальности. - OpenVPN stands as one of the most popular and time-tested VPN protocols available. -It employs its unique security protocol, leveraging the strength of SSL/TLS for encryption and key exchange. Furthermore, OpenVPN's support for a multitude of authentication methods makes it versatile and adaptable, catering to a wide range of devices and operating systems. Due to its open-source nature, OpenVPN benefits from extensive scrutiny by the global community, which continually reinforces its security. With a strong balance of performance, security, and compatibility, OpenVPN remains a top choice for privacy-conscious individuals and businesses alike. - -* Available in the AmneziaVPN across all platforms -* Normal power consumption on mobile devices -* Flexible customisation to suit user needs to work with different operating systems and devices -* Recognised by DPI analysis systems and therefore susceptible to blocking -* Can operate over both TCP and UDP network protocols. - OpenVPN — один из самых популярных и проверенных временем VPN-протоколов. -В нем используется уникальный протокол безопасности, опирающийся на SSL/TLS для шифрования и обмена ключами. Кроме того, OpenVPN поддерживает множество методов аутентификации, что делает его универсальным и адаптируемым к широкому спектру устройств и операционных систем. Благодаря открытому исходному коду OpenVPN подвергается тщательному анализу со стороны мирового сообщества, что постоянно повышает его безопасность. Оптимальное соотношение производительности, безопасности и совместимости делает OpenVPN лучшим выбором как для частных лиц, так и для компаний, заботящихся о конфиденциальности. - -* Доступен в AmneziaVPN на всех платформах -* Нормальное энергопотребление на мобильных устройствах -* Гибкая настройка под нужды пользователя для работы с различными операционными системами и устройствами -* Распознается системами DPI-анализа и поэтому подвержен блокировке -* Может работать по сетевым протоколам TCP и UDP - - - Shadowsocks, inspired by the SOCKS5 protocol, safeguards the connection using the AEAD cipher. Although Shadowsocks is designed to be discreet and challenging to identify, it isn't identical to a standard HTTPS connection.However, certain traffic analysis systems might still detect a Shadowsocks connection. Due to limited support in Amnezia, it's recommended to use AmneziaWG protocol. - -* Available in the AmneziaVPN only on desktop platforms -* Configurable encryption protocol -* Detectable by some DPI systems -* Works over TCP network protocol. - Shadowsocks создан на основе протокола SOCKS5, защищает соединение с помощью шифра AEAD. Несмотря на то, что протокол Shadowsocks разработан таким образом, чтобы быть незаметным и сложным для идентификации, он не идентичен стандартному HTTPS-соединению, поэтому некоторые системы анализа трафика всё же могут обнаружить соединение Shadowsocks. В связи с ограниченной поддержкой в Amnezia рекомендуется использовать протокол AmneziaWG. - -* Доступен в AmneziaVPN только для ПК и ноутбуков -* Настраиваемый протокол шифрования -* Распознается некоторыми системами DPI-анализа -* Работает по сетевому протоколу TCP. - - - 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. - 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. - 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. - 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. - Протокол REALITY, новаторская разработка создателей XRay, специально спроектирован для противодействия самой строгой цензуре с помощью нового способа обхода блокировок. - Он уникальным образом идентифицирует цензоров на этапе TLS-рукопожатия, беспрепятственно работая в качестве прокси для реальных клиентов и перенаправляя цензоров на реальные сайты, такие как google.com, тем самым предъявляя подлинный TLS-сертификат и данные. - REALITY отличается от аналогичных технологий благодаря способности без специальной настройки маскировать веб-трафик так, как будто он поступает со случайных легитимных сайтов. - В отличие от более старых протоколов, таких как VMess, VLESS и XTLS-Vision, технология распознавания "друг или враг" на этапе TLS-рукопожатия повышает безопасность и обходит обнаружение сложными системами DPI-анализа, которые используют методы активного зондирования. Это делает REALITY эффективным решением для поддержания свободы интернета в регионах с жесткой цензурой. - - - + After installation, Amnezia will create a file storage on your server. You will be able to access it using @@ -5079,84 +4979,6 @@ For more detailed information, you can Более подробную информацию вы можете найти в разделе поддержки "Создание файлового хранилища SFTP." - - - This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for blocking protection. - -OpenVPN provides a secure VPN connection by encrypting all Internet traffic between the client and the server. - -Cloak protects OpenVPN from detection and blocking. - -Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, and also protects the VPN from detection by Active Probing. This makes it very resistant to being detected - -Immediately after receiving the first data packet, Cloak authenticates the incoming connection. If authentication fails, the plugin masks the server as a fake website and your VPN becomes invisible to analysis systems. - -If there is a extreme level of Internet censorship in your region, we advise you to use only OpenVPN over Cloak from the first connection - -* Available in the AmneziaVPN across all platforms -* High power consumption on mobile devices -* Flexible settings -* Not recognised by DPI analysis systems -* Works over TCP network protocol, 443 port. - - OpenVPN over Cloak - это комбинация протокола OpenVPN и плагина Cloak, разработанного специально для защиты от обнаружения и блокировок. - -Протокол OpenVPN обеспечивает безопасное VPN-соединение за счет шифрования всего интернет-трафика между клиентом и сервером. - -Плагин Cloak защищает OpenVPN от обнаружения и блокировок. - -Cloak может изменять метаданные пакетов. Он полностью маскирует VPN-трафик под обычный веб-трафик, а также защищает VPN от обнаружения с помощью Active Probing. Это делает его очень устойчивым к обнаружению - -Сразу же после получения первого пакета данных Cloak проверяет подлинность входящего соединения. Если аутентификация не проходит, плагин маскирует сервер под поддельный сайт, и ваш VPN становится невидимым для аналитических систем. - -Если в вашем регионе экстремальный уровень цензуры в Интернете, мы советуем вам с первого подключения использовать только OpenVPN over Cloak - -* Доступен в AmneziaVPN для всех платформ -* Высокое энергопотребление на мобильных устройствах -* Гибкие настройки -* Не распознается системами DPI-анализа -* Работает по сетевому протоколу TCP, 443 порт. - - - - A relatively new popular VPN protocol with a simplified architecture. -Provides stable VPN connection, high performance on all devices. Uses hard-coded encryption settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput. -WireGuard is very susceptible to blocking due to its distinct packet signatures. Unlike some other VPN protocols that employ obfuscation techniques, the consistent signature patterns of WireGuard packets can be more easily identified and thus blocked by advanced Deep Packet Inspection (DPI) systems and other network monitoring tools. - -* Available in the AmneziaVPN across all platforms -* Low power consumption -* Minimum number of settings -* Easily recognised by DPI analysis systems, susceptible to blocking -* Works over UDP network protocol. - WireGuard - относительно новый популярный VPN-протокол с упрощенной архитектурой. -Обеспечивает стабильное VPN-соединение, высокую производительность на всех устройствах. Использует жестко заданные настройки шифрования. WireGuard по сравнению с OpenVPN имеет меньшую задержку и лучшую пропускную способность при передаче данных. -WireGuard очень восприимчив к блокированию из-за особенностей сигнатур пакетов. В отличие от некоторых других VPN-протоколов, использующих методы обфускации, последовательные сигнатуры пакетов WireGuard легче выявляются и, соответственно, блокируются современными системами глубокой проверки пакетов (DPI) и другими средствами сетевого мониторинга. - -* Доступен в AmneziaVPN для всех платформ -* Низкое энергопотребление -* Минимальное количество настроек -* Легко распознается системами DPI-анализа, подвержен блокировке -* Работает по сетевому протоколу UDP. - - - A modern iteration of the popular VPN protocol, AmneziaWG builds upon the foundation set by WireGuard, retaining its simplified architecture and high-performance capabilities across devices. -While WireGuard is known for its efficiency, it had issues with being easily detected due to its distinct packet signatures. AmneziaWG solves this problem by using better obfuscation methods, making its traffic blend in with regular internet traffic. -This means that AmneziaWG keeps the fast performance of the original while adding an extra layer of stealth, making it a great choice for those wanting a fast and discreet VPN connection. - -* Available in the AmneziaVPN across all platforms -* Low power consumption -* Minimum number of settings -* Not recognised by DPI analysis systems, resistant to blocking -* Works over UDP network protocol. - AmneziaWG — усовершенствованная версия популярного VPN-протокола WireGuard. AmneziaWG опирается на фундамент, заложенный WireGuard, сохраняя упрощенную архитектуру и высокую производительность на различных устройствах. -Хотя WireGuard известен своей эффективностью, у него были проблемы с обнаружением из-за характерных сигнатур пакетов. AmneziaWG решает эту проблему за счет использования более совершенных методов обфускации, благодаря чему его трафик сливается с обычным интернет-трафиком. -Таким образом, AmneziaWG сохраняет высокую производительность оригинального протокола, добавляя при этом дополнительный уровень скрытности, что делает его отличным выбором для тех, кому нужно быстрое и незаметное VPN-соединение. - -* Доступен в AmneziaVPN на всех платформах -* Низкое энергопотребление на мобильных устройствах -* Минимальное количество настроек -* Не распознается системами DPI-анализа, устойчив к блокировке -* Работает по сетевому протоколу UDP AmneziaWG container @@ -5239,7 +5061,7 @@ This means that AmneziaWG keeps the fast performance of the original while addin - + SOCKS5 proxy server Прокси-сервер SOCKS5 @@ -5657,12 +5479,12 @@ This means that AmneziaWG keeps the fast performance of the original while addin Хочу просто повысить уровень приватности - + Automatic Автоматическая - + AmneziaWG protocol will be installed. It provides high connection speed and ensures stable operation even in the most challenging network conditions. Будет установлен протокол AmneziaWG. Он обеспечивает высокую скорость соединения и гарантирует стабильную работу даже в самых сложных условиях. diff --git a/client/ui/controllers/allowedDnsController.cpp b/client/ui/controllers/allowedDnsController.cpp new file mode 100644 index 00000000..12e8b599 --- /dev/null +++ b/client/ui/controllers/allowedDnsController.cpp @@ -0,0 +1,101 @@ +#include "allowedDnsController.h" + +#include +#include +#include +#include +#include + +#include "systemController.h" +#include "core/networkUtilities.h" +#include "core/defs.h" + +AllowedDnsController::AllowedDnsController(const std::shared_ptr &settings, + const QSharedPointer &allowedDnsModel, + QObject *parent) + : QObject(parent), m_settings(settings), m_allowedDnsModel(allowedDnsModel) +{ +} + +void AllowedDnsController::addDns(QString ip) +{ + if (ip.isEmpty()) { + return; + } + + if (!NetworkUtilities::ipAddressRegExp().match(ip).hasMatch()) { + emit errorOccurred(tr("The address does not look like a valid IP address")); + return; + } + + if (m_allowedDnsModel->addDns(ip)) { + emit finished(tr("New DNS server added: %1").arg(ip)); + } else { + emit errorOccurred(tr("DNS server already exists: %1").arg(ip)); + } +} + +void AllowedDnsController::removeDns(int index) +{ + auto modelIndex = m_allowedDnsModel->index(index); + auto ip = m_allowedDnsModel->data(modelIndex, AllowedDnsModel::Roles::IpRole).toString(); + m_allowedDnsModel->removeDns(modelIndex); + + emit finished(tr("DNS server removed: %1").arg(ip)); +} + +void AllowedDnsController::importDns(const QString &fileName, bool replaceExisting) +{ + QByteArray jsonData; + if (!SystemController::readFile(fileName, jsonData)) { + emit errorOccurred(tr("Can't open file: %1").arg(fileName)); + return; + } + + QJsonDocument jsonDocument = QJsonDocument::fromJson(jsonData); + if (jsonDocument.isNull()) { + emit errorOccurred(tr("Failed to parse JSON data from file: %1").arg(fileName)); + return; + } + + if (!jsonDocument.isArray()) { + emit errorOccurred(tr("The JSON data is not an array in file: %1").arg(fileName)); + return; + } + + auto jsonArray = jsonDocument.array(); + QStringList dnsServers; + + for (auto jsonValue : jsonArray) { + auto ip = jsonValue.toString(); + + if (!NetworkUtilities::ipAddressRegExp().match(ip).hasMatch()) { + qDebug() << ip << " is not a valid IP address"; + continue; + } + + dnsServers.append(ip); + } + + m_allowedDnsModel->addDnsList(dnsServers, replaceExisting); + + emit finished(tr("Import completed")); +} + +void AllowedDnsController::exportDns(const QString &fileName) +{ + auto dnsServers = m_allowedDnsModel->getCurrentDnsServers(); + + QJsonArray jsonArray; + + for (const auto &ip : dnsServers) { + jsonArray.append(ip); + } + + QJsonDocument jsonDocument(jsonArray); + QByteArray jsonData = jsonDocument.toJson(); + + SystemController::saveFile(fileName, jsonData); + + emit finished(tr("Export completed")); +} diff --git a/client/ui/controllers/allowedDnsController.h b/client/ui/controllers/allowedDnsController.h new file mode 100644 index 00000000..5509a036 --- /dev/null +++ b/client/ui/controllers/allowedDnsController.h @@ -0,0 +1,35 @@ +#ifndef ALLOWEDDNSCONTROLLER_H +#define ALLOWEDDNSCONTROLLER_H + +#include + +#include "settings.h" +#include "ui/models/allowed_dns_model.h" + +class AllowedDnsController : public QObject +{ + Q_OBJECT +public: + explicit AllowedDnsController(const std::shared_ptr &settings, + const QSharedPointer &allowedDnsModel, + QObject *parent = nullptr); + +public slots: + void addDns(QString ip); + void removeDns(int index); + + void importDns(const QString &fileName, bool replaceExisting); + void exportDns(const QString &fileName); + +signals: + void errorOccurred(const QString &errorMessage); + void finished(const QString &message); + + void saveFile(const QString &fileName, const QString &data); + +private: + std::shared_ptr m_settings; + QSharedPointer m_allowedDnsModel; +}; + +#endif // ALLOWEDDNSCONTROLLER_H diff --git a/client/ui/controllers/exportController.cpp b/client/ui/controllers/exportController.cpp index b47111ae..c487e3b2 100644 --- a/client/ui/controllers/exportController.cpp +++ b/client/ui/controllers/exportController.cpp @@ -145,7 +145,7 @@ void ExportController::generateOpenVpnConfig(const QString &clientName) } QStringList lines = nativeConfig.value(config_key::config).toString().replace("\r", "").split("\n"); - for (const QString &line : lines) { + for (const QString &line : std::as_const(lines)) { m_config.append(line + "\n"); } @@ -163,7 +163,7 @@ void ExportController::generateWireGuardConfig(const QString &clientName) } QStringList lines = nativeConfig.value(config_key::config).toString().replace("\r", "").split("\n"); - for (const QString &line : lines) { + for (const QString &line : std::as_const(lines)) { m_config.append(line + "\n"); } @@ -183,7 +183,7 @@ void ExportController::generateAwgConfig(const QString &clientName) } QStringList lines = nativeConfig.value(config_key::config).toString().replace("\r", "").split("\n"); - for (const QString &line : lines) { + for (const QString &line : std::as_const(lines)) { m_config.append(line + "\n"); } @@ -211,7 +211,7 @@ void ExportController::generateShadowSocksConfig() } QStringList lines = QString(QJsonDocument(nativeConfig).toJson()).replace("\r", "").split("\n"); - for (const QString &line : lines) { + for (const QString &line : std::as_const(lines)) { m_config.append(line + "\n"); } @@ -240,7 +240,7 @@ void ExportController::generateCloakConfig() nativeConfig.insert("ProxyMethod", "shadowsocks"); QStringList lines = QString(QJsonDocument(nativeConfig).toJson()).replace("\r", "").split("\n"); - for (const QString &line : lines) { + for (const QString &line : std::as_const(lines)) { m_config.append(line + "\n"); } @@ -257,7 +257,7 @@ void ExportController::generateXrayConfig(const QString &clientName) } QStringList lines = QString(QJsonDocument(nativeConfig).toJson()).replace("\r", "").split("\n"); - for (const QString &line : lines) { + for (const QString &line : std::as_const(lines)) { m_config.append(line + "\n"); } diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index be66d8f3..fdc06120 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -665,27 +665,27 @@ void ImportController::checkForMaliciousStrings(const QJsonObject &serverConfig) containerConfig[ProtocolProps::protoToString(Proto::OpenVpn)].toObject()[config_key::last_config].toString(); QString protocolConfigJson = QJsonDocument::fromJson(protocolConfig.toUtf8()).object()[config_key::config].toString(); - const QRegularExpression regExp { "(\\w+-\\w+|\\w+)" }; - const size_t dangerousTagsMaxCount = 3; - // https://github.com/OpenVPN/openvpn/blob/master/doc/man-sections/script-options.rst QStringList dangerousTags { "up", "tls-verify", "ipchange", "client-connect", "route-up", "route-pre-down", "client-disconnect", "down", "learn-address", "auth-user-pass-verify" }; QStringList maliciousStrings; - QStringList lines = protocolConfigJson.replace("\r", "").split("\n"); - for (const QString &l : lines) { - QRegularExpressionMatch match = regExp.match(l); - if (dangerousTags.contains(match.captured(0))) { - maliciousStrings << l; + QStringList lines = protocolConfigJson.split('\n', Qt::SkipEmptyParts); + + for (const QString &rawLine : lines) { + QString line = rawLine.trimmed(); + + QString command = line.section(' ', 0, 0, QString::SectionSkipEmpty); + if (dangerousTags.contains(command, Qt::CaseInsensitive)) { + maliciousStrings << rawLine; } } m_maliciousWarningText = tr("This configuration contains an OpenVPN setup. OpenVPN configurations can include malicious " "scripts, so only add it if you fully trust the provider of this config. "); - if (maliciousStrings.size() >= dangerousTagsMaxCount) { + if (!maliciousStrings.isEmpty()) { m_maliciousWarningText.push_back(tr("
In the imported configuration, potentially dangerous lines were found:")); for (const auto &string : maliciousStrings) { m_maliciousWarningText.push_back(QString("
%1").arg(string)); diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index 7a6d8d40..eab8979a 100755 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -363,7 +363,8 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia QJsonObject config; Proto mainProto = ContainerProps::defaultProtocol(container); - for (auto protocol : ContainerProps::protocolsForContainer(container)) { + const auto &protocols = ContainerProps::protocolsForContainer(container); + for (const auto &protocol : protocols) { QJsonObject containerConfig; if (protocol == mainProto) { containerConfig.insert(config_key::port, port); @@ -387,6 +388,7 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia } } + containerConfig[config_key::subnet_address] = serverConfigMap.value("Address").remove("/24"); 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); @@ -398,6 +400,25 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia serverConfigMap.value(config_key::underloadPacketMagicHeader); containerConfig[config_key::transportPacketMagicHeader] = serverConfigMap.value(config_key::transportPacketMagicHeader); + + } else if (protocol == Proto::WireGuard) { + QString serverConfig = serverController->getTextFileFromContainer(container, credentials, + protocols::wireguard::serverConfigPath, errorCode); + + QMap 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::subnet_address] = serverConfigMap.value("Address").remove("/24"); } else if (protocol == Proto::Sftp) { stdOut.clear(); script = QString("sudo docker inspect --format '{{.Config.Cmd}}' %1").arg(name); @@ -432,6 +453,51 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia containerConfig.insert(config_key::userName, userName); containerConfig.insert(config_key::password, password); } + } else if (protocol == Proto::Xray) { + QString currentConfig = serverController->getTextFileFromContainer( + container, credentials, amnezia::protocols::xray::serverConfigPath, errorCode); + + QJsonDocument doc = QJsonDocument::fromJson(currentConfig.toUtf8()); + qDebug() << doc; + if (doc.isNull() || !doc.isObject()) { + logger.error() << "Failed to parse server config JSON"; + errorCode = ErrorCode::InternalError; + return errorCode; + } + QJsonObject serverConfig = doc.object(); + + if (!serverConfig.contains("inbounds")) { + logger.error() << "Server config missing 'inbounds' field"; + errorCode = ErrorCode::InternalError; + return errorCode; + } + + QJsonArray inbounds = serverConfig["inbounds"].toArray(); + if (inbounds.isEmpty()) { + logger.error() << "Server config has empty 'inbounds' array"; + errorCode = ErrorCode::InternalError; + return errorCode; + } + + QJsonObject inbound = inbounds[0].toObject(); + if (!inbound.contains("streamSettings")) { + logger.error() << "Inbound missing 'streamSettings' field"; + errorCode = ErrorCode::InternalError; + return errorCode; + } + + QJsonObject streamSettings = inbound["streamSettings"].toObject(); + QJsonObject realitySettings = streamSettings["realitySettings"].toObject(); + if (!realitySettings.contains("serverNames")) { + logger.error() << "Settings missing 'clients' field"; + errorCode = ErrorCode::InternalError; + return errorCode; + } + + QString siteName = realitySettings["serverNames"][0].toString(); + qDebug() << siteName; + + containerConfig.insert(config_key::site, siteName); } config.insert(config_key::container, ContainerProps::containerToString(container)); diff --git a/client/ui/controllers/pageController.h b/client/ui/controllers/pageController.h index 7fc2ad44..2aad835e 100644 --- a/client/ui/controllers/pageController.h +++ b/client/ui/controllers/pageController.h @@ -32,13 +32,15 @@ namespace PageLoader PageSettingsLogging, PageSettingsSplitTunneling, PageSettingsAppSplitTunneling, + PageSettingsKillSwitch, PageSettingsApiServerInfo, PageSettingsApiAvailableCountries, PageSettingsApiSupport, PageSettingsApiInstructions, PageSettingsApiNativeConfigs, PageSettingsApiDevices, - + PageSettingsKillSwitchExceptions, + PageServiceSftpSettings, PageServiceTorWebsiteSettings, PageServiceDnsSettings, diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index 090a55dd..878baa1a 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -245,6 +245,23 @@ bool SettingsController::isKillSwitchEnabled() void SettingsController::toggleKillSwitch(bool enable) { m_settings->setKillSwitchEnabled(enable); + emit killSwitchEnabledChanged(); + if (enable == false) { + emit strictKillSwitchEnabledChanged(false); + } else { + emit strictKillSwitchEnabledChanged(isStrictKillSwitchEnabled()); + } +} + +bool SettingsController::isStrictKillSwitchEnabled() +{ + return m_settings->isStrictKillSwitchEnabled(); +} + +void SettingsController::toggleStrictKillSwitch(bool enable) +{ + m_settings->setStrictKillSwitchEnabled(enable); + emit strictKillSwitchEnabledChanged(enable); } bool SettingsController::isNotificationPermissionGranted() diff --git a/client/ui/controllers/settingsController.h b/client/ui/controllers/settingsController.h index 7781f6c7..1485e1a0 100644 --- a/client/ui/controllers/settingsController.h +++ b/client/ui/controllers/settingsController.h @@ -24,6 +24,8 @@ public: Q_PROPERTY(QString secondaryDns READ getSecondaryDns WRITE setSecondaryDns NOTIFY secondaryDnsChanged) Q_PROPERTY(bool isLoggingEnabled READ isLoggingEnabled WRITE toggleLogging NOTIFY loggingStateChanged) Q_PROPERTY(bool isNotificationPermissionGranted READ isNotificationPermissionGranted NOTIFY onNotificationStateChanged) + Q_PROPERTY(bool isKillSwitchEnabled READ isKillSwitchEnabled WRITE toggleKillSwitch NOTIFY killSwitchEnabledChanged) + Q_PROPERTY(bool strictKillSwitchEnabled READ isStrictKillSwitchEnabled WRITE toggleStrictKillSwitch NOTIFY strictKillSwitchEnabledChanged) Q_PROPERTY(bool isDevModeEnabled READ isDevModeEnabled NOTIFY devModeEnabled) Q_PROPERTY(QString gatewayEndpoint READ getGatewayEndpoint WRITE setGatewayEndpoint NOTIFY gatewayEndpointChanged) @@ -75,6 +77,9 @@ public slots: bool isKillSwitchEnabled(); void toggleKillSwitch(bool enable); + bool isStrictKillSwitchEnabled(); + void toggleStrictKillSwitch(bool enable); + bool isNotificationPermissionGranted(); void requestNotificationPermission(); @@ -98,6 +103,8 @@ signals: void primaryDnsChanged(); void secondaryDnsChanged(); void loggingStateChanged(); + void killSwitchEnabledChanged(); + void strictKillSwitchEnabledChanged(bool enabled); void restoreBackupFinished(); void changeSettingsFinished(const QString &finishedMessage); diff --git a/client/ui/models/allowed_dns_model.cpp b/client/ui/models/allowed_dns_model.cpp new file mode 100644 index 00000000..e3c59945 --- /dev/null +++ b/client/ui/models/allowed_dns_model.cpp @@ -0,0 +1,86 @@ +#include "allowed_dns_model.h" + +AllowedDnsModel::AllowedDnsModel(std::shared_ptr settings, QObject *parent) + : QAbstractListModel(parent), m_settings(settings) +{ + fillDnsServers(); +} + +int AllowedDnsModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return m_dnsServers.size(); +} + +QVariant AllowedDnsModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= static_cast(rowCount())) + return QVariant(); + + switch (role) { + case IpRole: + return m_dnsServers.at(index.row()); + default: + return QVariant(); + } +} + +bool AllowedDnsModel::addDns(const QString &ip) +{ + if (m_dnsServers.contains(ip)) { + return false; + } + + beginInsertRows(QModelIndex(), rowCount(), rowCount()); + m_dnsServers.append(ip); + m_settings->setAllowedDnsServers(m_dnsServers); + endInsertRows(); + return true; +} + +void AllowedDnsModel::addDnsList(const QStringList &dnsServers, bool replaceExisting) +{ + beginResetModel(); + + if (replaceExisting) { + m_dnsServers.clear(); + } + + for (const QString &ip : dnsServers) { + if (!m_dnsServers.contains(ip)) { + m_dnsServers.append(ip); + } + } + + m_settings->setAllowedDnsServers(m_dnsServers); + endResetModel(); +} + +void AllowedDnsModel::removeDns(QModelIndex index) +{ + if (!index.isValid() || index.row() >= m_dnsServers.size()) { + return; + } + + beginRemoveRows(QModelIndex(), index.row(), index.row()); + m_dnsServers.removeAt(index.row()); + m_settings->setAllowedDnsServers(m_dnsServers); + endRemoveRows(); +} + +QStringList AllowedDnsModel::getCurrentDnsServers() +{ + return m_dnsServers; +} + +QHash AllowedDnsModel::roleNames() const +{ + QHash roles; + roles[IpRole] = "ip"; + return roles; +} + +void AllowedDnsModel::fillDnsServers() +{ + m_dnsServers = m_settings->allowedDnsServers(); +} diff --git a/client/ui/models/allowed_dns_model.h b/client/ui/models/allowed_dns_model.h new file mode 100644 index 00000000..fdefcc0e --- /dev/null +++ b/client/ui/models/allowed_dns_model.h @@ -0,0 +1,37 @@ +#ifndef ALLOWEDDNSMODEL_H +#define ALLOWEDDNSMODEL_H + +#include +#include "settings.h" + +class AllowedDnsModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum Roles { + IpRole = Qt::UserRole + 1 + }; + + explicit AllowedDnsModel(std::shared_ptr settings, QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +public slots: + bool addDns(const QString &ip); + void addDnsList(const QStringList &dnsServers, bool replaceExisting); + void removeDns(QModelIndex index); + QStringList getCurrentDnsServers(); + +protected: + QHash roleNames() const override; + +private: + void fillDnsServers(); + + std::shared_ptr m_settings; + QStringList m_dnsServers; +}; + +#endif // ALLOWEDDNSMODEL_H diff --git a/client/ui/models/api/apiServicesModel.cpp b/client/ui/models/api/apiServicesModel.cpp index 65f17758..b893096a 100644 --- a/client/ui/models/api/apiServicesModel.cpp +++ b/client/ui/models/api/apiServicesModel.cpp @@ -69,7 +69,7 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const "Access all websites and online resources. Speeds up to %1 Mbps.") .arg(speed); } else if (serviceType == serviceType::amneziaFree) { - QString description = tr("AmneziaFree provides free unlimited access to a basic set of web sites, such as Facebook, Instagram, Twitter (X), Discord, Telegram, and others. YouTube is not included in the free plan."); + QString description = tr("Amnezia Free provides unlimited, free access to a basic set of websites and apps, including Facebook, Instagram, Twitter (X), Discord, Telegram, and more. YouTube is not included in the free plan."); if (!isServiceAvailable) { description += tr("

Not available in your region. If you have VPN enabled, disable it, " "return to the previous screen, and try again."); @@ -82,7 +82,7 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const return tr("Amnezia Premium is classic VPN for for seamless work, downloading large files, and watching videos. " "Access all websites and online resources."); } else { - return tr("AmneziaFree provides free unlimited access to a basic set of web sites, such as Facebook, Instagram, Twitter (X), Discord, Telegram, and others. YouTube is not included in the free plan."); + return tr("Amnezia Free provides unlimited, free access to a basic set of websites and apps, including Facebook, Instagram, Twitter (X), Discord, Telegram, and more. YouTube is not included in the free plan."); } } case IsServiceAvailableRole: { diff --git a/client/ui/models/protocols/xrayConfigModel.cpp b/client/ui/models/protocols/xrayConfigModel.cpp index 84bbb2f7..3917b544 100644 --- a/client/ui/models/protocols/xrayConfigModel.cpp +++ b/client/ui/models/protocols/xrayConfigModel.cpp @@ -20,6 +20,7 @@ bool XrayConfigModel::setData(const QModelIndex &index, const QVariant &value, i switch (role) { case Roles::SiteRole: m_protocolConfig.insert(config_key::site, value.toString()); break; + case Roles::PortRole: m_protocolConfig.insert(config_key::port, value.toString()); break; } emit dataChanged(index, index, QList { role }); @@ -34,6 +35,7 @@ QVariant XrayConfigModel::data(const QModelIndex &index, int role) const switch (role) { case Roles::SiteRole: return m_protocolConfig.value(config_key::site).toString(protocols::xray::defaultSite); + case Roles::PortRole: return m_protocolConfig.value(config_key::port).toString(protocols::xray::defaultPort); } return QVariant(); @@ -67,6 +69,7 @@ QHash XrayConfigModel::roleNames() const QHash roles; roles[SiteRole] = "site"; + roles[PortRole] = "port"; return roles; } diff --git a/client/ui/models/protocols/xrayConfigModel.h b/client/ui/models/protocols/xrayConfigModel.h index cb57f858..41aac589 100644 --- a/client/ui/models/protocols/xrayConfigModel.h +++ b/client/ui/models/protocols/xrayConfigModel.h @@ -12,7 +12,8 @@ class XrayConfigModel : public QAbstractListModel public: enum Roles { - SiteRole + SiteRole, + PortRole }; explicit XrayConfigModel(QObject *parent = nullptr); diff --git a/client/ui/qml/Components/AddSitePanel.qml b/client/ui/qml/Components/AddSitePanel.qml new file mode 100644 index 00000000..18fdfa57 --- /dev/null +++ b/client/ui/qml/Components/AddSitePanel.qml @@ -0,0 +1,73 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import Style 1.0 +import "../Controls2" +import "../Controls2/TextTypes" + +Item { + id: root + + property bool enabled: true + property string placeholderText: "" + property alias textField: searchField.textField + + signal addClicked(string text) + signal moreClicked() + + implicitWidth: 360 + implicitHeight: 96 + + Rectangle { + id: background + anchors.fill: parent + color: "#0E0F12" + opacity: 0.85 + z: -1 + } + + RowLayout { + id: addSiteButton + + enabled: root.enabled + spacing: 2 + + anchors { + fill: parent + topMargin: 16 + leftMargin: 16 + rightMargin: 16 + bottomMargin: 24 + } + + TextFieldWithHeaderType { + id: searchField + + Layout.fillWidth: true + rightButtonClickedOnEnter: true + + textField.placeholderText: root.placeholderText + buttonImageSource: "qrc:/images/controls/plus.svg" + + clickedFunc: function() { + root.addClicked(textField.text) + textField.text = "" + } + } + + ImageButtonType { + id: addSiteButtonImage + implicitWidth: 56 + implicitHeight: 56 + + image: "qrc:/images/controls/more-vertical.svg" + imageColor: AmneziaStyle.color.paleGray + + onClicked: root.moreClicked() + + Keys.onReturnPressed: addSiteButtonImage.clicked() + Keys.onEnterPressed: addSiteButtonImage.clicked() + } + } +} diff --git a/client/ui/qml/Components/HomeContainersListView.qml b/client/ui/qml/Components/HomeContainersListView.qml index 337918f1..1cbdd82d 100644 --- a/client/ui/qml/Components/HomeContainersListView.qml +++ b/client/ui/qml/Components/HomeContainersListView.qml @@ -18,7 +18,8 @@ ListView { property var selectedText width: rootWidth - height: contentItem.height + anchors.top: parent.top + anchors.bottom: parent.bottom clip: true snapMode: ListView.SnapToItem diff --git a/client/ui/qml/Controls2/BaseHeaderType.qml b/client/ui/qml/Controls2/BaseHeaderType.qml new file mode 100644 index 00000000..eb7fe36f --- /dev/null +++ b/client/ui/qml/Controls2/BaseHeaderType.qml @@ -0,0 +1,45 @@ +import QtQuick +import QtQuick.Layouts + +import Style 1.0 + +import "TextTypes" + +Item { + id: root + + property string headerText + property int headerTextMaximumLineCount: 2 + property int headerTextElide: Qt.ElideRight + property string descriptionText + property alias headerRow: headerRow + + implicitWidth: content.implicitWidth + implicitHeight: content.implicitHeight + + ColumnLayout { + id: content + anchors.fill: parent + + RowLayout { + id: headerRow + + Header1TextType { + id: header + Layout.fillWidth: true + text: root.headerText + maximumLineCount: root.headerTextMaximumLineCount + elide: root.headerTextElide + } + } + + ParagraphTextType { + id: description + Layout.topMargin: 16 + Layout.fillWidth: true + text: root.descriptionText + color: AmneziaStyle.color.mutedGray + visible: root.descriptionText !== "" + } + } +} diff --git a/client/ui/qml/Controls2/DropDownType.qml b/client/ui/qml/Controls2/DropDownType.qml index ae6dac85..633b11cf 100644 --- a/client/ui/qml/Controls2/DropDownType.qml +++ b/client/ui/qml/Controls2/DropDownType.qml @@ -239,6 +239,7 @@ Item { sourceComponent: root.listView Layout.fillHeight: true + Layout.fillWidth: true } } } diff --git a/client/ui/qml/Controls2/HeaderType.qml b/client/ui/qml/Controls2/HeaderType.qml deleted file mode 100644 index 1366148d..00000000 --- a/client/ui/qml/Controls2/HeaderType.qml +++ /dev/null @@ -1,86 +0,0 @@ -import QtQuick -import QtQuick.Layouts - -import Style 1.0 - -import "TextTypes" - -Item { - id: root - - property string actionButtonImage - property var actionButtonFunction - - property alias actionButton: headerActionButton - - property string headerText - property int headerTextMaximumLineCount: 2 - property int headerTextElide: Qt.ElideRight - - property string descriptionText - - implicitWidth: content.implicitWidth - implicitHeight: content.implicitHeight - - ColumnLayout { - id: content - anchors.fill: parent - - RowLayout { - Header1TextType { - id: header - - Layout.fillWidth: true - - text: root.headerText - maximumLineCount: root.headerTextMaximumLineCount - elide: root.headerTextElide - } - - ImageButtonType { - id: headerActionButton - - implicitWidth: 40 - implicitHeight: 40 - - Layout.alignment: Qt.AlignRight - - image: root.actionButtonImage - imageColor: AmneziaStyle.color.paleGray - - visible: image ? true : false - - onClicked: { - if (actionButtonFunction && typeof actionButtonFunction === "function") { - actionButtonFunction() - } - } - } - } - - ParagraphTextType { - id: description - - Layout.topMargin: 16 - Layout.fillWidth: true - - text: root.descriptionText - - color: AmneziaStyle.color.mutedGray - - visible: root.descriptionText !== "" - } - } - - Keys.onEnterPressed: { - if (actionButtonFunction && typeof actionButtonFunction === "function") { - actionButtonFunction() - } - } - - Keys.onReturnPressed: { - if (actionButtonFunction && typeof actionButtonFunction === "function") { - actionButtonFunction() - } - } -} diff --git a/client/ui/qml/Controls2/HeaderTypeWithButton.qml b/client/ui/qml/Controls2/HeaderTypeWithButton.qml new file mode 100644 index 00000000..7feff3ce --- /dev/null +++ b/client/ui/qml/Controls2/HeaderTypeWithButton.qml @@ -0,0 +1,44 @@ +import QtQuick +import QtQuick.Layouts + +import Style 1.0 + +BaseHeaderType { + id: root + + property string actionButtonImage + property var actionButtonFunction + property alias actionButton: headerActionButton + + Component.onCompleted: { + headerRow.children.push(headerActionButton) + } + + ImageButtonType { + id: headerActionButton + implicitWidth: 40 + implicitHeight: 40 + Layout.alignment: Qt.AlignRight + image: root.actionButtonImage + imageColor: AmneziaStyle.color.paleGray + visible: image ? true : false + + onClicked: { + if (actionButtonFunction && typeof actionButtonFunction === "function") { + actionButtonFunction() + } + } + } + + Keys.onEnterPressed: { + if (actionButtonFunction && typeof actionButtonFunction === "function") { + actionButtonFunction() + } + } + + Keys.onReturnPressed: { + if (actionButtonFunction && typeof actionButtonFunction === "function") { + actionButtonFunction() + } + } +} diff --git a/client/ui/qml/Controls2/HeaderTypeWithSwitcher.qml b/client/ui/qml/Controls2/HeaderTypeWithSwitcher.qml new file mode 100644 index 00000000..2fa4e735 --- /dev/null +++ b/client/ui/qml/Controls2/HeaderTypeWithSwitcher.qml @@ -0,0 +1,28 @@ +import QtQuick +import QtQuick.Layouts + +import Style 1.0 + +BaseHeaderType { + id: root + + property var switcherFunction + property bool showSwitcher: false + property alias switcher: headerSwitcher + + Component.onCompleted: { + headerRow.children.push(headerSwitcher) + } + + SwitcherType { + id: headerSwitcher + Layout.alignment: Qt.AlignRight + visible: root.showSwitcher + + onToggled: { + if (switcherFunction && typeof switcherFunction === "function") { + switcherFunction(checked) + } + } + } +} diff --git a/client/ui/qml/Controls2/VerticalRadioButton.qml b/client/ui/qml/Controls2/VerticalRadioButton.qml index bee8ef7b..7ad6afc8 100644 --- a/client/ui/qml/Controls2/VerticalRadioButton.qml +++ b/client/ui/qml/Controls2/VerticalRadioButton.qml @@ -20,7 +20,11 @@ RadioButton { property string selectedColor: AmneziaStyle.color.transparent property string textColor: AmneziaStyle.color.paleGray + property string textDisabledColor: AmneziaStyle.color.mutedGray property string selectedTextColor: AmneziaStyle.color.goldenApricot + property string selectedTextDisabledColor: AmneziaStyle.color.burntOrange + property string descriptionColor: AmneziaStyle.color.mutedGray + property string descriptionDisabledColor: AmneziaStyle.color.charcoalGray property string borderFocusedColor: AmneziaStyle.color.paleGray property int borderFocusedWidth: 1 @@ -30,6 +34,12 @@ RadioButton { property bool isFocusable: true + + property string radioButtonInnerCirclePressedSource: "qrc:/images/controls/radio-button-inner-circle-pressed.png" + property string radioButtonInnerCircleSource: "qrc:/images/controls/radio-button-inner-circle.png" + property string radioButtonPressedSource: "qrc:/images/controls/radio-button-pressed.svg" + property string radioButtonDefaultSource: "qrc:/images/controls/radio-button.svg" + Keys.onTabPressed: { FocusController.nextKeyTabItem() } @@ -94,14 +104,15 @@ RadioButton { if (showImage) { return imageSource } else if (root.pressed) { - return "qrc:/images/controls/radio-button-inner-circle-pressed.png" + return root.radioButtonInnerCirclePressedSource } else if (root.checked) { - return "qrc:/images/controls/radio-button-inner-circle.png" + return root.radioButtonInnerCircleSource } return "" } + opacity: root.enabled ? 1.0 : 0.3 anchors.centerIn: parent width: 24 @@ -113,12 +124,13 @@ RadioButton { if (showImage) { return "" } else if (root.pressed || root.checked) { - return "qrc:/images/controls/radio-button-pressed.svg" + return root.radioButtonPressedSource } else { - return "qrc:/images/controls/radio-button.svg" + return root.radioButtonDefaultSource } } + opacity: root.enabled ? 1.0 : 0.3 anchors.centerIn: parent width: 24 @@ -148,10 +160,11 @@ RadioButton { elide: root.textElide color: { - if (root.checked) { - return selectedTextColor + if (root.enabled) { + return root.checked ? selectedTextColor : textColor + } else { + return root.checked ? selectedTextDisabledColor : textDisabledColor } - return textColor } Layout.fillWidth: true @@ -164,7 +177,7 @@ RadioButton { CaptionTextType { id: description - color: AmneziaStyle.color.mutedGray + color: root.enabled ? root.descriptionColor : root.descriptionDisabledColor text: root.descriptionText visible: root.descriptionText !== "" @@ -177,6 +190,7 @@ RadioButton { MouseArea { anchors.fill: root cursorShape: Qt.PointingHandCursor + preventStealing: false enabled: false } } diff --git a/client/ui/qml/Pages2/PageDeinstalling.qml b/client/ui/qml/Pages2/PageDeinstalling.qml index f5fdb29a..69b1f319 100644 --- a/client/ui/qml/Pages2/PageDeinstalling.qml +++ b/client/ui/qml/Pages2/PageDeinstalling.qml @@ -56,7 +56,7 @@ PageType { anchors.rightMargin: 16 anchors.leftMargin: 16 - HeaderType { + BaseHeaderType { Layout.fillWidth: true Layout.topMargin: 20 diff --git a/client/ui/qml/Pages2/PageDevMenu.qml b/client/ui/qml/Pages2/PageDevMenu.qml index d7afde1d..5fccb43a 100644 --- a/client/ui/qml/Pages2/PageDevMenu.qml +++ b/client/ui/qml/Pages2/PageDevMenu.qml @@ -39,7 +39,7 @@ PageType { header: ColumnLayout { width: listView.width - HeaderType { + BaseHeaderType { id: header Layout.fillWidth: true diff --git a/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml b/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml index c22fdf0c..b8cf5f93 100644 --- a/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml @@ -91,7 +91,7 @@ PageType { spacing: 0 - HeaderType { + BaseHeaderType { Layout.fillWidth: true headerText: qsTr("AmneziaWG settings") diff --git a/client/ui/qml/Pages2/PageProtocolAwgSettings.qml b/client/ui/qml/Pages2/PageProtocolAwgSettings.qml index 8c629b68..e8fd2b94 100644 --- a/client/ui/qml/Pages2/PageProtocolAwgSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolAwgSettings.qml @@ -91,7 +91,7 @@ PageType { spacing: 0 - HeaderType { + BaseHeaderType { Layout.fillWidth: true headerText: qsTr("AmneziaWG settings") diff --git a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml index 686ccd7b..7a0fafbd 100644 --- a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml @@ -76,7 +76,7 @@ PageType { spacing: 0 - HeaderType { + BaseHeaderType { Layout.fillWidth: true headerText: qsTr("Cloak settings") diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index 9cc628b7..2e00d54a 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -75,7 +75,7 @@ PageType { spacing: 0 - HeaderType { + BaseHeaderType { Layout.fillWidth: true headerText: qsTr("OpenVPN settings") diff --git a/client/ui/qml/Pages2/PageProtocolRaw.qml b/client/ui/qml/Pages2/PageProtocolRaw.qml index 03b4e297..bba3eafe 100644 --- a/client/ui/qml/Pages2/PageProtocolRaw.qml +++ b/client/ui/qml/Pages2/PageProtocolRaw.qml @@ -32,7 +32,7 @@ PageType { id: backButton } - HeaderType { + BaseHeaderType { Layout.fillWidth: true Layout.leftMargin: 16 Layout.rightMargin: 16 diff --git a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml index 5786012b..63e60dcb 100644 --- a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml @@ -78,7 +78,7 @@ PageType { spacing: 0 - HeaderType { + BaseHeaderType { Layout.fillWidth: true headerText: qsTr("Shadowsocks settings") diff --git a/client/ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml b/client/ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml index a30c17e7..96ec1dc6 100644 --- a/client/ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolWireGuardClientSettings.qml @@ -85,7 +85,7 @@ PageType { spacing: 0 - HeaderType { + BaseHeaderType { Layout.fillWidth: true headerText: qsTr("WG settings") diff --git a/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml b/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml index 10523b74..7b5180f3 100644 --- a/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml @@ -77,7 +77,7 @@ PageType { spacing: 0 - HeaderType { + BaseHeaderType { Layout.fillWidth: true headerText: qsTr("WG settings") } diff --git a/client/ui/qml/Pages2/PageProtocolXraySettings.qml b/client/ui/qml/Pages2/PageProtocolXraySettings.qml index 90705d3e..d22e31a2 100644 --- a/client/ui/qml/Pages2/PageProtocolXraySettings.qml +++ b/client/ui/qml/Pages2/PageProtocolXraySettings.qml @@ -75,7 +75,7 @@ PageType { spacing: 0 - HeaderType { + BaseHeaderType { Layout.fillWidth: true headerText: qsTr("XRay settings") } @@ -93,9 +93,9 @@ PageType { var tmpText = textField.text tmpText = tmpText.toLocaleLowerCase() - var indexHttps = tmpText.indexOf("https://") - if (indexHttps === 0) { + if (tmpText.startsWith("https://")) { tmpText = textField.text.substring(8) + site = tmpText } else { site = textField.text } @@ -103,8 +103,29 @@ PageType { } } + TextFieldWithHeaderType { + id: portTextField + Layout.fillWidth: true + Layout.topMargin: 16 + + enabled: delegateItem.isEnabled + + headerText: qsTr("Port") + textField.text: port + textField.maximumLength: 5 + textField.validator: IntValidator { bottom: 1; top: 65535 } + + textField.onEditingFinished: { + if (textField.text !== port) { + port = textField.text + } + } + + checkEmptyText: true + } + BasicButtonType { - id: basicButton + id: saveButton Layout.fillWidth: true Layout.topMargin: 24 Layout.bottomMargin: 24 diff --git a/client/ui/qml/Pages2/PageServiceDnsSettings.qml b/client/ui/qml/Pages2/PageServiceDnsSettings.qml index cef29813..d534f991 100644 --- a/client/ui/qml/Pages2/PageServiceDnsSettings.qml +++ b/client/ui/qml/Pages2/PageServiceDnsSettings.qml @@ -43,7 +43,7 @@ PageType { anchors.left: parent.left anchors.right: parent.right - HeaderType { + BaseHeaderType { id: header Layout.fillWidth: true diff --git a/client/ui/qml/Pages2/PageServiceSftpSettings.qml b/client/ui/qml/Pages2/PageServiceSftpSettings.qml index 2deb315c..b58cb2e0 100644 --- a/client/ui/qml/Pages2/PageServiceSftpSettings.qml +++ b/client/ui/qml/Pages2/PageServiceSftpSettings.qml @@ -85,7 +85,7 @@ PageType { spacing: 0 - HeaderType { + BaseHeaderType { Layout.fillWidth: true Layout.leftMargin: 16 Layout.rightMargin: 16 diff --git a/client/ui/qml/Pages2/PageServiceSocksProxySettings.qml b/client/ui/qml/Pages2/PageServiceSocksProxySettings.qml index 1b77267a..b1daa0fb 100644 --- a/client/ui/qml/Pages2/PageServiceSocksProxySettings.qml +++ b/client/ui/qml/Pages2/PageServiceSocksProxySettings.qml @@ -77,7 +77,7 @@ PageType { spacing: 0 - HeaderType { + BaseHeaderType { Layout.fillWidth: true Layout.leftMargin: 16 Layout.rightMargin: 16 @@ -217,7 +217,7 @@ PageType { } } - HeaderType { + BaseHeaderType { Layout.fillWidth: true headerText: qsTr("SOCKS5 settings") diff --git a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml index 249c70c7..200beeb8 100644 --- a/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml +++ b/client/ui/qml/Pages2/PageServiceTorWebsiteSettings.qml @@ -54,7 +54,7 @@ PageType { spacing: 0 - HeaderType { + BaseHeaderType { Layout.fillWidth: true Layout.leftMargin: 16 Layout.rightMargin: 16 diff --git a/client/ui/qml/Pages2/PageSettings.qml b/client/ui/qml/Pages2/PageSettings.qml index a134fbac..59645d6a 100644 --- a/client/ui/qml/Pages2/PageSettings.qml +++ b/client/ui/qml/Pages2/PageSettings.qml @@ -29,7 +29,7 @@ PageType { spacing: 0 - HeaderType { + BaseHeaderType { id: header Layout.fillWidth: true Layout.topMargin: 24 diff --git a/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml b/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml index 6e67ef1f..ce57e97e 100644 --- a/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml +++ b/client/ui/qml/Pages2/PageSettingsApiAvailableCountries.qml @@ -69,7 +69,7 @@ PageType { Layout.topMargin: 20 } - HeaderType { + HeaderTypeWithButton { id: headerContent objectName: "headerContent" @@ -135,12 +135,6 @@ PageType { } } - MouseArea { - anchors.fill: containerRadioButton - cursorShape: Qt.PointingHandCursor - enabled: false - } - Keys.onEnterPressed: { if (checkable) { checked = true diff --git a/client/ui/qml/Pages2/PageSettingsApiDevices.qml b/client/ui/qml/Pages2/PageSettingsApiDevices.qml index c6a2f98c..231b58a0 100644 --- a/client/ui/qml/Pages2/PageSettingsApiDevices.qml +++ b/client/ui/qml/Pages2/PageSettingsApiDevices.qml @@ -35,7 +35,7 @@ PageType { id: backButton } - HeaderType { + BaseHeaderType { id: header Layout.fillWidth: true @@ -77,7 +77,7 @@ PageType { } var headerText = qsTr("Are you sure you want to unlink this device?") - var descriptionText = qsTr("This will unlink the device from your subscription. You can reconnect it anytime by pressing Connect.") + var descriptionText = qsTr("This will unlink the device from your subscription. You can reconnect it anytime by pressing \"Reload API config\" in subscription settings on device.") var yesButtonText = qsTr("Continue") var noButtonText = qsTr("Cancel") diff --git a/client/ui/qml/Pages2/PageSettingsApiInstructions.qml b/client/ui/qml/Pages2/PageSettingsApiInstructions.qml index 7961594b..9b325b82 100644 --- a/client/ui/qml/Pages2/PageSettingsApiInstructions.qml +++ b/client/ui/qml/Pages2/PageSettingsApiInstructions.qml @@ -91,7 +91,7 @@ PageType { id: backButton } - HeaderType { + BaseHeaderType { id: header Layout.fillWidth: true diff --git a/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml b/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml index 50fd3ada..1efe598e 100644 --- a/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml +++ b/client/ui/qml/Pages2/PageSettingsApiNativeConfigs.qml @@ -38,7 +38,7 @@ PageType { id: backButton } - HeaderType { + BaseHeaderType { id: header Layout.fillWidth: true diff --git a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml index 689502c1..93118755 100644 --- a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml @@ -93,7 +93,7 @@ PageType { Layout.topMargin: 20 } - HeaderType { + HeaderTypeWithButton { id: headerContent objectName: "headerContent" @@ -333,7 +333,7 @@ PageType { clickedFunc: function() { var headerText = qsTr("Are you sure you want to unlink this device?") - var descriptionText = qsTr("This will unlink the device from your subscription. You can reconnect it anytime by pressing Connect.") + var descriptionText = qsTr("This will unlink the device from your subscription. You can reconnect it anytime by pressing \"Reload API config\" in subscription settings on device.") var yesButtonText = qsTr("Continue") var noButtonText = qsTr("Cancel") diff --git a/client/ui/qml/Pages2/PageSettingsApiSupport.qml b/client/ui/qml/Pages2/PageSettingsApiSupport.qml index 0ea8ec84..af629ebe 100644 --- a/client/ui/qml/Pages2/PageSettingsApiSupport.qml +++ b/client/ui/qml/Pages2/PageSettingsApiSupport.qml @@ -71,7 +71,7 @@ PageType { id: backButton } - HeaderType { + BaseHeaderType { id: header Layout.fillWidth: true diff --git a/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml index b6920a8f..e31c92db 100644 --- a/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsAppSplitTunneling.qml @@ -79,29 +79,22 @@ PageType { id: backButton } - RowLayout { - HeaderType { - Layout.fillWidth: true - Layout.leftMargin: 16 + HeaderTypeWithSwitcher { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 - headerText: qsTr("App split tunneling") + headerText: qsTr("App split tunneling") + enabled: root.pageEnabled + showSwitcher: true + switcher { + checked: AppSplitTunnelingModel.isTunnelingEnabled enabled: root.pageEnabled } - - SwitcherType { - id: switcher - - Layout.fillWidth: true - Layout.rightMargin: 16 - - enabled: root.pageEnabled - - checked: AppSplitTunnelingModel.isTunnelingEnabled - onToggled: { - AppSplitTunnelingModel.toggleSplitTunneling(checked) - selector.text = root.routeModesModel[getRouteModesModelIndex()].name - } + switcherFunction: function(checked) { + AppSplitTunnelingModel.toggleSplitTunneling(checked) + selector.text = root.routeModesModel[getRouteModesModelIndex()].name } } diff --git a/client/ui/qml/Pages2/PageSettingsApplication.qml b/client/ui/qml/Pages2/PageSettingsApplication.qml index 443f6371..7be7ee07 100644 --- a/client/ui/qml/Pages2/PageSettingsApplication.qml +++ b/client/ui/qml/Pages2/PageSettingsApplication.qml @@ -38,7 +38,7 @@ PageType { spacing: 0 - HeaderType { + BaseHeaderType { Layout.fillWidth: true Layout.leftMargin: 16 Layout.rightMargin: 16 diff --git a/client/ui/qml/Pages2/PageSettingsBackup.qml b/client/ui/qml/Pages2/PageSettingsBackup.qml index 9a0a86f9..999609b6 100644 --- a/client/ui/qml/Pages2/PageSettingsBackup.qml +++ b/client/ui/qml/Pages2/PageSettingsBackup.qml @@ -60,7 +60,7 @@ PageType { spacing: 16 - HeaderType { + BaseHeaderType { Layout.fillWidth: true headerText: qsTr("Back up your configuration") diff --git a/client/ui/qml/Pages2/PageSettingsConnection.qml b/client/ui/qml/Pages2/PageSettingsConnection.qml index 69671f27..84b98230 100644 --- a/client/ui/qml/Pages2/PageSettingsConnection.qml +++ b/client/ui/qml/Pages2/PageSettingsConnection.qml @@ -36,7 +36,7 @@ PageType { anchors.left: parent.left anchors.right: parent.right - HeaderType { + BaseHeaderType { Layout.fillWidth: true Layout.leftMargin: 16 Layout.rightMargin: 16 @@ -94,9 +94,7 @@ PageType { } } - DividerType { - visible: root.isAppSplitTinnelingEnabled - } + DividerType {} LabelWithButtonType { id: splitTunnelingButton2 @@ -119,29 +117,20 @@ PageType { visible: root.isAppSplitTinnelingEnabled } - SwitcherType { - id: killSwitchSwitcher + LabelWithButtonType { + id: killSwitchButton visible: !GC.isMobile() Layout.fillWidth: true - Layout.margins: 16 text: qsTr("KillSwitch") - descriptionText: qsTr("Disables your internet if your encrypted VPN connection drops out for any reason.") + descriptionText: qsTr("Blocks network connections without VPN") + rightImageSource: "qrc:/images/controls/chevron-right.svg" parentFlickable: fl - checked: SettingsController.isKillSwitchEnabled() - checkable: !ConnectionController.isConnected - onCheckedChanged: { - if (checked !== SettingsController.isKillSwitchEnabled()) { - SettingsController.toggleKillSwitch(checked) - } - } - onClicked: { - if (!checkable) { - PageController.showNotificationMessage(qsTr("Cannot change KillSwitch settings during active connection")) - } + clickedFunction: function() { + PageController.goToPage(PageEnum.PageSettingsKillSwitch) } } diff --git a/client/ui/qml/Pages2/PageSettingsDns.qml b/client/ui/qml/Pages2/PageSettingsDns.qml index d78c5aa8..d5e2c52b 100644 --- a/client/ui/qml/Pages2/PageSettingsDns.qml +++ b/client/ui/qml/Pages2/PageSettingsDns.qml @@ -50,7 +50,7 @@ PageType { spacing: 16 - HeaderType { + BaseHeaderType { Layout.fillWidth: true headerText: qsTr("DNS servers") diff --git a/client/ui/qml/Pages2/PageSettingsKillSwitch.qml b/client/ui/qml/Pages2/PageSettingsKillSwitch.qml new file mode 100644 index 00000000..f6e0f5d7 --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsKillSwitch.qml @@ -0,0 +1,123 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import PageEnum 1.0 +import Style 1.0 + +import "./" +import "../Controls2" +import "../Config" + +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 + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + HeaderTypeWithSwitcher { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: qsTr("KillSwitch") + descriptionText: qsTr("Enable to ensure network traffic goes through a secure VPN tunnel, preventing accidental exposure of your IP and DNS queries if the connection drops") + + showSwitcher: true + switcher { + checked: SettingsController.isKillSwitchEnabled + enabled: !ConnectionController.isConnected + } + switcherFunction: function(checked) { + if (!ConnectionController.isConnected) { + SettingsController.isKillSwitchEnabled = checked + } else { + PageController.showNotificationMessage(qsTr("Cannot change killSwitch settings during active connection")) + switcher.checked = SettingsController.isKillSwitchEnabled + } + } + } + + VerticalRadioButton { + id: softKillSwitch + Layout.fillWidth: true + Layout.topMargin: 32 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + enabled: SettingsController.isKillSwitchEnabled && !ConnectionController.isConnected + checked: !SettingsController.strictKillSwitchEnabled + + text: qsTr("Soft KillSwitch") + descriptionText: qsTr("Internet connection is blocked if VPN connection drops accidentally") + + onClicked: function() { + SettingsController.strictKillSwitchEnabled = false + } + } + + DividerType {} + + VerticalRadioButton { + id: strictKillSwitch + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + enabled: SettingsController.isKillSwitchEnabled && !ConnectionController.isConnected + checked: SettingsController.strictKillSwitchEnabled + + text: qsTr("Strict KillSwitch") + descriptionText: qsTr("Internet connection is blocked even if VPN was turned off manually or not started") + + onClicked: function() { + var headerText = qsTr("Just a little heads-up") + var descriptionText = qsTr("If you disconnect from VPN or the VPN connection drops while the Strict Kill Switch is turned on, your internet access will be disabled. To restore it, connect to VPN, change the Kill Switch mode or turn the Kill Switch off.") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") + + var yesButtonFunction = function() { + SettingsController.strictKillSwitchEnabled = true + } + var noButtonFunction = function() { + } + + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) + } + } + + DividerType {} + + LabelWithButtonType { + Layout.topMargin: 32 + Layout.fillWidth: true + + enabled: true + text: qsTr("DNS Exceptions") + descriptionText: qsTr("DNS servers from the list will remain accessible when Kill Switch is triggered") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + PageController.goToPage(PageEnum.PageSettingsKillSwitchExceptions) + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsKillSwitchExceptions.qml b/client/ui/qml/Pages2/PageSettingsKillSwitchExceptions.qml new file mode 100644 index 00000000..d442b60c --- /dev/null +++ b/client/ui/qml/Pages2/PageSettingsKillSwitchExceptions.qml @@ -0,0 +1,302 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Dialogs + +import QtCore + +import SortFilterProxyModel 0.2 + +import PageEnum 1.0 +import ProtocolEnum 1.0 +import ContainerProps 1.0 +import Style 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +PageType { + id: root + + property bool pageEnabled: true + + ColumnLayout { + id: header + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + anchors.topMargin: 20 + + BackButtonType { + id: backButton + } + + BaseHeaderType { + enabled: root.pageEnabled + + Layout.fillWidth: true + Layout.leftMargin: 16 + + headerText: qsTr("DNS Exceptions") + descriptionText: qsTr("DNS servers from the list will remain accessible when Kill Switch is triggered") + } + } + + ListView { + id: listView + + anchors.top: header.bottom + anchors.topMargin: 16 + anchors.bottom: parent.bottom + + width: parent.width + + enabled: root.pageEnabled + + property bool isFocusable: true + + cacheBuffer: 200 + displayMarginBeginning: 40 + displayMarginEnd: 40 + + ScrollBar.vertical: ScrollBarType { } + + footer: Item { + width: listView.width + height: addSitePanel.height + } + + footerPositioning: ListView.InlineFooter + + model: SortFilterProxyModel { + id: dnsFilterModel + sourceModel: AllowedDnsModel + filters: [ + RegExpFilter { + roleName: "ip" + pattern: ".*" + addSitePanel.textField.text + ".*" + caseSensitivity: Qt.CaseInsensitive + } + ] + } + + clip: true + + reuseItems: true + + delegate: ColumnLayout { + id: delegateContent + + width: listView.width + + LabelWithButtonType { + id: site + Layout.fillWidth: true + + text: ip + rightImageSource: "qrc:/images/controls/trash.svg" + rightImageColor: AmneziaStyle.color.paleGray + + clickedFunction: function() { + var headerText = qsTr("Delete ") + ip + "?" + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") + + var yesButtonFunction = function() { + AllowedDnsController.removeDns(dnsFilterModel.mapToSource(index)) + if (!GC.isMobile()) { + site.rightButton.forceActiveFocus() + } + } + var noButtonFunction = function() { + if (!GC.isMobile()) { + site.rightButton.forceActiveFocus() + } + } + + showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) + } + } + + DividerType {} + } + } + + AddSitePanel { + id: addSitePanel + + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + + enabled: root.pageEnabled + placeholderText: qsTr("IPv4 address") + + onAddClicked: function(text) { + PageController.showBusyIndicator(true) + AllowedDnsController.addDns(text) + PageController.showBusyIndicator(false) + } + + onMoreClicked: { + moreActionsDrawer.openTriggered() + } + } + + DrawerType2 { + id: moreActionsDrawer + + anchors.fill: parent + expandedHeight: parent.height * 0.4375 + + expandedStateContent: ColumnLayout { + id: moreActionsDrawerContent + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + Header2Type { + Layout.fillWidth: true + Layout.margins: 16 + + headerText: qsTr("Import / Export addresses") + } + + LabelWithButtonType { + id: importSitesButton + Layout.fillWidth: true + + text: qsTr("Import") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + importSitesDrawer.openTriggered() + } + } + + DividerType {} + + LabelWithButtonType { + id: exportSitesButton + Layout.fillWidth: true + text: qsTr("Save address list") + + clickedFunction: function() { + var fileName = "" + if (GC.isMobile()) { + fileName = "amnezia_killswitch_exceptions.json" + } else { + fileName = SystemController.getFileName(qsTr("Save addresses"), + qsTr("Address files (*.json)"), + StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/amnezia_killswitch_exceptions", + true, + ".json") + } + if (fileName !== "") { + PageController.showBusyIndicator(true) + AllowedDnsController.exportDns(fileName) + moreActionsDrawer.closeTriggered() + PageController.showBusyIndicator(false) + } + } + } + + DividerType {} + } + } + + DrawerType2 { + id: importSitesDrawer + + anchors.fill: parent + expandedHeight: parent.height * 0.4375 + + expandedStateContent: Item { + implicitHeight: importSitesDrawer.expandedHeight + + BackButtonType { + id: importSitesDrawerBackButton + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.topMargin: 16 + + backButtonFunction: function() { + importSitesDrawer.closeTriggered() + } + } + + FlickableType { + anchors.top: importSitesDrawerBackButton.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + + contentHeight: importSitesDrawerContent.height + + ColumnLayout { + id: importSitesDrawerContent + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + Header2Type { + Layout.fillWidth: true + Layout.margins: 16 + + headerText: qsTr("Import address list") + } + + LabelWithButtonType { + id: importSitesButton2 + Layout.fillWidth: true + + text: qsTr("Replace address list") + + clickedFunction: function() { + var fileName = SystemController.getFileName(qsTr("Open address file"), + qsTr("Address files (*.json)")) + if (fileName !== "") { + importSitesDrawerContent.importSites(fileName, true) + } + } + } + + DividerType {} + + LabelWithButtonType { + id: importSitesButton3 + Layout.fillWidth: true + text: qsTr("Add imported addresses to existing ones") + + clickedFunction: function() { + var fileName = SystemController.getFileName(qsTr("Open address file"), + qsTr("Address files (*.json)")) + if (fileName !== "") { + importSitesDrawerContent.importSites(fileName, false) + } + } + } + + function importSites(fileName, replaceExistingSites) { + PageController.showBusyIndicator(true) + AllowedDnsController.importDns(fileName, replaceExistingSites) + PageController.showBusyIndicator(false) + importSitesDrawer.closeTriggered() + moreActionsDrawer.closeTriggered() + } + + DividerType {} + } + } + } + } +} diff --git a/client/ui/qml/Pages2/PageSettingsLogging.qml b/client/ui/qml/Pages2/PageSettingsLogging.qml index bf436bee..dd0fac6a 100644 --- a/client/ui/qml/Pages2/PageSettingsLogging.qml +++ b/client/ui/qml/Pages2/PageSettingsLogging.qml @@ -40,7 +40,7 @@ PageType { header: ColumnLayout { width: listView.width - HeaderType { + BaseHeaderType { Layout.fillWidth: true Layout.leftMargin: 16 Layout.rightMargin: 16 diff --git a/client/ui/qml/Pages2/PageSettingsServerInfo.qml b/client/ui/qml/Pages2/PageSettingsServerInfo.qml index d350ebef..6ac81764 100644 --- a/client/ui/qml/Pages2/PageSettingsServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsServerInfo.qml @@ -71,7 +71,7 @@ PageType { objectName: "backButton" } - HeaderType { + HeaderTypeWithButton { id: headerContent objectName: "headerContent" diff --git a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml index ade94ebb..fce9b2a3 100644 --- a/client/ui/qml/Pages2/PageSettingsServerProtocol.qml +++ b/client/ui/qml/Pages2/PageSettingsServerProtocol.qml @@ -34,7 +34,7 @@ PageType { id: backButton } - HeaderType { + BaseHeaderType { Layout.fillWidth: true Layout.leftMargin: 16 Layout.rightMargin: 16 diff --git a/client/ui/qml/Pages2/PageSettingsServersList.qml b/client/ui/qml/Pages2/PageSettingsServersList.qml index 554b6cbb..57e39ae8 100644 --- a/client/ui/qml/Pages2/PageSettingsServersList.qml +++ b/client/ui/qml/Pages2/PageSettingsServersList.qml @@ -31,7 +31,7 @@ PageType { id: backButton } - HeaderType { + BaseHeaderType { Layout.fillWidth: true Layout.leftMargin: 16 Layout.rightMargin: 16 diff --git a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml index f5978687..292f903a 100644 --- a/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml +++ b/client/ui/qml/Pages2/PageSettingsSplitTunneling.qml @@ -94,33 +94,22 @@ PageType { id: backButton } - RowLayout { - HeaderType { - enabled: root.pageEnabled + HeaderTypeWithSwitcher { + Layout.fillWidth: true + Layout.leftMargin: 16 + Layout.rightMargin: 16 - Layout.fillWidth: true - Layout.leftMargin: 16 - - headerText: qsTr("Split tunneling") - } - - SwitcherType { - id: switcher - - enabled: root.pageEnabled - - Layout.fillWidth: true - Layout.rightMargin: 16 - - function onToggledFunc() { - SitesModel.toggleSplitTunneling(this.checked) - selector.text = root.routeModesModel[getRouteModesModelIndex()].name - } + headerText: qsTr("Split tunneling") + enabled: root.pageEnabled + showSwitcher: true + switcher { checked: SitesModel.isTunnelingEnabled - onToggled: { onToggledFunc() } - Keys.onEnterPressed: { onToggledFunc() } - Keys.onReturnPressed: { onToggledFunc() } + enabled: root.pageEnabled + } + switcherFunction: function(checked) { + SitesModel.toggleSplitTunneling(checked) + selector.text = root.routeModesModel[getRouteModesModelIndex()].name } } diff --git a/client/ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml b/client/ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml index 134e73b6..30128de6 100644 --- a/client/ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml +++ b/client/ui/qml/Pages2/PageSetupWizardApiServiceInfo.qml @@ -35,7 +35,7 @@ PageType { Layout.topMargin: 20 } - HeaderType { + BaseHeaderType { Layout.fillWidth: true Layout.topMargin: 8 Layout.rightMargin: 16 diff --git a/client/ui/qml/Pages2/PageSetupWizardApiServicesList.qml b/client/ui/qml/Pages2/PageSetupWizardApiServicesList.qml index c3e3edbc..549eb381 100644 --- a/client/ui/qml/Pages2/PageSetupWizardApiServicesList.qml +++ b/client/ui/qml/Pages2/PageSetupWizardApiServicesList.qml @@ -28,7 +28,7 @@ PageType { Layout.topMargin: 20 } - HeaderType { + BaseHeaderType { Layout.fillWidth: true Layout.topMargin: 8 Layout.rightMargin: 16 diff --git a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml index 09210d22..92a40f06 100644 --- a/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml +++ b/client/ui/qml/Pages2/PageSetupWizardConfigSource.qml @@ -45,7 +45,7 @@ PageType { header: ColumnLayout { width: listView.width - HeaderType { + HeaderTypeWithButton { id: moreButton property bool isVisible: SettingsController.getInstallationUuid() !== "" || PageController.isStartPageVisible() @@ -76,7 +76,7 @@ PageType { anchors.right: parent.right spacing: 0 - HeaderType { + BaseHeaderType { Layout.fillWidth: true Layout.topMargin: 32 Layout.leftMargin: 16 @@ -114,11 +114,11 @@ PageType { clickedFunction: function() { var fileName = "" if (GC.isMobile()) { - fileName = "AmneziaVPN.log" + fileName = "DefaultVPN.log" } else { fileName = SystemController.getFileName(qsTr("Save"), qsTr("Logs files (*.log)"), - StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/AmneziaVPN", + StandardPaths.standardLocations(StandardPaths.DocumentsLocation) + "/DefaultVPN", true, ".log") } diff --git a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml index cdafa47f..63d4d5f6 100644 --- a/client/ui/qml/Pages2/PageSetupWizardCredentials.qml +++ b/client/ui/qml/Pages2/PageSetupWizardCredentials.qml @@ -66,7 +66,7 @@ PageType { header: ColumnLayout { width: listView.width - HeaderType { + BaseHeaderType { Layout.fillWidth: true Layout.leftMargin: 16 Layout.rightMargin: 16 diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index ae04f635..096406e9 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -59,7 +59,7 @@ PageType { spacing: 16 - HeaderType { + BaseHeaderType { id: header implicitWidth: parent.width diff --git a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml index 1128761d..822931b8 100644 --- a/client/ui/qml/Pages2/PageSetupWizardInstalling.qml +++ b/client/ui/qml/Pages2/PageSetupWizardInstalling.qml @@ -118,7 +118,7 @@ PageType { anchors.rightMargin: 16 anchors.leftMargin: 16 - HeaderType { + BaseHeaderType { Layout.fillWidth: true Layout.topMargin: 20 diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index 50d1ea81..ac7fc4b2 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -96,7 +96,7 @@ PageType { Layout.leftMargin: -16 } - HeaderType { + BaseHeaderType { id: header Layout.fillWidth: true diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml index 6b6b6038..7afab630 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocols.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocols.qml @@ -58,7 +58,7 @@ PageType { header: ColumnLayout { width: listView.width - HeaderType { + BaseHeaderType { id: header Layout.fillWidth: true diff --git a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml index 3cf154e4..930efb57 100644 --- a/client/ui/qml/Pages2/PageSetupWizardTextKey.qml +++ b/client/ui/qml/Pages2/PageSetupWizardTextKey.qml @@ -33,7 +33,7 @@ PageType { Layout.topMargin: 20 } - HeaderType { + BaseHeaderType { Layout.fillWidth: true Layout.rightMargin: 16 Layout.leftMargin: 16 diff --git a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml index 14096742..cfa9c90f 100644 --- a/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml +++ b/client/ui/qml/Pages2/PageSetupWizardViewConfig.qml @@ -61,7 +61,7 @@ PageType { anchors.rightMargin: 16 anchors.leftMargin: 16 - HeaderType { + BaseHeaderType { headerText: qsTr("New connection") } diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index b32917c2..ad25e2f5 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -169,7 +169,7 @@ PageType { spacing: 0 - HeaderType { + HeaderTypeWithButton { id: header Layout.fillWidth: true Layout.topMargin: 24 diff --git a/client/ui/qml/Pages2/PageShareFullAccess.qml b/client/ui/qml/Pages2/PageShareFullAccess.qml index 70fd6292..82effb57 100644 --- a/client/ui/qml/Pages2/PageShareFullAccess.qml +++ b/client/ui/qml/Pages2/PageShareFullAccess.qml @@ -44,7 +44,7 @@ PageType { spacing: 0 - HeaderType { + BaseHeaderType { Layout.fillWidth: true Layout.topMargin: 24 diff --git a/client/vpnconnection.cpp b/client/vpnconnection.cpp index ff875b39..3de0f035 100644 --- a/client/vpnconnection.cpp +++ b/client/vpnconnection.cpp @@ -52,6 +52,28 @@ void VpnConnection::onBytesChanged(quint64 receivedBytes, quint64 sentBytes) emit bytesChanged(receivedBytes, sentBytes); } +void VpnConnection::onKillSwitchModeChanged(bool enabled) +{ +#ifdef AMNEZIA_DESKTOP + if (!m_IpcClient) { + m_IpcClient = new IpcClient(this); + } + + if (!m_IpcClient->isSocketConnected()) { + if (!IpcClient::init(m_IpcClient)) { + qWarning() << "Error occurred when init IPC client"; + emit serviceIsNotReady(); + return; + } + } + + if (IpcClient::Interface()) { + qDebug() << "Set KillSwitch Strict mode enabled " << enabled; + IpcClient::Interface()->refreshKillSwitch(enabled); + } +#endif +} + void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state) { @@ -286,6 +308,7 @@ void VpnConnection::createProtocolConnections() void VpnConnection::appendKillSwitchConfig() { m_vpnConfiguration.insert(config_key::killSwitchOption, QVariant(m_settings->isKillSwitchEnabled()).toString()); + m_vpnConfiguration.insert(config_key::allowedDnsServers, QVariant(m_settings->allowedDnsServers()).toJsonValue()); } void VpnConnection::appendSplitTunnelingConfig() diff --git a/client/vpnconnection.h b/client/vpnconnection.h index 0160edce..cb5aaaf9 100644 --- a/client/vpnconnection.h +++ b/client/vpnconnection.h @@ -48,14 +48,14 @@ public: public slots: void connectToVpn(int serverIndex, - const ServerCredentials &credentials, DockerContainer container, const QJsonObject &vpnConfiguration); + const ServerCredentials &credentials, DockerContainer container, const QJsonObject &vpnConfiguration); void disconnectFromVpn(); - void addRoutes(const QStringList &ips); void deleteRoutes(const QStringList &ips); void flushDns(); + void onKillSwitchModeChanged(bool enabled); signals: void bytesChanged(quint64 receivedBytes, quint64 sentBytes); diff --git a/deploy/build_linux.sh b/deploy/build_linux.sh index fb474699..7d63c1ad 100755 --- a/deploy/build_linux.sh +++ b/deploy/build_linux.sh @@ -41,6 +41,10 @@ if [ -z "${QT_VERSION+x}" ]; then QT_BIN_DIR=/opt/Qt/$QT_VERSION/gcc_64/bin elif [ -f $HOME/Qt/$QT_VERSION/gcc_64/bin/qmake ]; then QT_BIN_DIR=$HOME/Qt/$QT_VERSION/gcc_64/bin + elif [ -f /usr/lib/qt6/bin/qmake ]; then + QT_BIN_DIR=/usr/lib/qt6/bin + elif [ -f /usr/lib/x86_64-linux-gnu/qt6/bin/qmake ]; then + QT_BIN_DIR=/usr/lib/x86_64-linux-gnu/qt6/bin fi fi @@ -56,7 +60,7 @@ echo "Building App..." cd $BUILD_DIR $QT_BIN_DIR/qt-cmake -S $PROJECT_DIR -cmake --build . --config release +cmake --build . -j --config release # Build and run tests here diff --git a/ipc/ipc_interface.rep b/ipc/ipc_interface.rep index c0f031fe..4ecae9bc 100644 --- a/ipc/ipc_interface.rep +++ b/ipc/ipc_interface.rep @@ -29,6 +29,10 @@ class IpcInterface SLOT( void StopRoutingIpv6() ); SLOT( bool disableKillSwitch() ); + SLOT( bool disableAllTraffic() ); + SLOT( bool refreshKillSwitch( bool enabled ) ); + SLOT( bool addKillSwitchAllowedRange( const QStringList ranges ) ); + SLOT( bool resetKillSwitchAllowedRange( const QStringList ranges ) ); SLOT( bool enablePeerTraffic( const QJsonObject &configStr) ); SLOT( bool enableKillSwitch( const QJsonObject &excludeAddr, int vpnAdapterIndex) ); SLOT( bool updateResolvers(const QString& ifname, const QList& resolvers) ); diff --git a/ipc/ipcserver.cpp b/ipc/ipcserver.cpp index 17f34499..0c7f5295 100644 --- a/ipc/ipcserver.cpp +++ b/ipc/ipcserver.cpp @@ -8,21 +8,12 @@ #include "logger.h" #include "router.h" -#include "../core/networkUtilities.h" -#include "../client/protocols/protocols_defs.h" +#include "killswitch.h" + #ifdef Q_OS_WIN - #include "../client/platforms/windows/daemon/windowsdaemon.h" - #include "../client/platforms/windows/daemon/windowsfirewall.h" #include "tapcontroller_win.h" #endif -#ifdef Q_OS_LINUX - #include "../client/platforms/linux/daemon/linuxfirewall.h" -#endif - -#ifdef Q_OS_MACOS - #include "../client/platforms/macos/daemon/macosfirewall.h" -#endif IpcServer::IpcServer(QObject *parent) : IpcInterfaceSource(parent) @@ -188,174 +179,37 @@ void IpcServer::setLogsEnabled(bool enabled) } } +bool IpcServer::resetKillSwitchAllowedRange(QStringList ranges) +{ + return KillSwitch::instance()->resetAllowedRange(ranges); +} + +bool IpcServer::addKillSwitchAllowedRange(QStringList ranges) +{ + return KillSwitch::instance()->addAllowedRange(ranges); +} + +bool IpcServer::disableAllTraffic() +{ + return KillSwitch::instance()->disableAllTraffic(); +} + bool IpcServer::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterIndex) { -#ifdef Q_OS_WIN - auto firewallManager = WindowsFirewall::create(this); - Q_ASSERT(firewallManager != nullptr); - return firewallManager->enableInterface(vpnAdapterIndex); -#endif - -#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS) - int splitTunnelType = configStr.value("splitTunnelType").toInt(); - QJsonArray splitTunnelSites = configStr.value("splitTunnelSites").toArray(); - bool blockAll = 0; - bool allowNets = 0; - bool blockNets = 0; - QStringList allownets; - QStringList blocknets; - - if (splitTunnelType == 0) { - blockAll = true; - allowNets = true; - allownets.append(configStr.value("vpnServer").toString()); - } else if (splitTunnelType == 1) { - blockNets = true; - for (auto v : splitTunnelSites) { - blocknets.append(v.toString()); - } - } else if (splitTunnelType == 2) { - blockAll = true; - allowNets = true; - allownets.append(configStr.value("vpnServer").toString()); - for (auto v : splitTunnelSites) { - allownets.append(v.toString()); - } - } -#endif - -#ifdef Q_OS_LINUX - // double-check + ensure our firewall is installed and enabled - if (!LinuxFirewall::isInstalled()) - LinuxFirewall::install(); - LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("000.allowLoopback"), true); - LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("100.blockAll"), blockAll); - LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("110.allowNets"), allowNets); - LinuxFirewall::updateAllowNets(allownets); - LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("120.blockNets"), blockAll); - LinuxFirewall::updateBlockNets(blocknets); - LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("200.allowVPN"), true); - LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv6, QStringLiteral("250.blockIPv6"), true); - LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("290.allowDHCP"), true); - LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("300.allowLAN"), true); - LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("310.blockDNS"), true); - QStringList dnsServers; - dnsServers.append(configStr.value(amnezia::config_key::dns1).toString()); - dnsServers.append(configStr.value(amnezia::config_key::dns2).toString()); - dnsServers.append("127.0.0.1"); - dnsServers.append("127.0.0.53"); - LinuxFirewall::updateDNSServers(dnsServers); - LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("320.allowDNS"), true); - LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("400.allowPIA"), true); -#endif - -#ifdef Q_OS_MACOS - - // double-check + ensure our firewall is installed and enabled. This is necessary as - // other software may disable pfctl before re-enabling with their own rules (e.g other VPNs) - if (!MacOSFirewall::isInstalled()) - MacOSFirewall::install(); - - MacOSFirewall::ensureRootAnchorPriority(); - MacOSFirewall::setAnchorEnabled(QStringLiteral("000.allowLoopback"), true); - MacOSFirewall::setAnchorEnabled(QStringLiteral("100.blockAll"), blockAll); - MacOSFirewall::setAnchorEnabled(QStringLiteral("110.allowNets"), allowNets); - MacOSFirewall::setAnchorTable(QStringLiteral("110.allowNets"), allowNets, QStringLiteral("allownets"), allownets); - - MacOSFirewall::setAnchorEnabled(QStringLiteral("120.blockNets"), blockNets); - MacOSFirewall::setAnchorTable(QStringLiteral("120.blockNets"), blockNets, QStringLiteral("blocknets"), blocknets); - MacOSFirewall::setAnchorEnabled(QStringLiteral("200.allowVPN"), true); - MacOSFirewall::setAnchorEnabled(QStringLiteral("250.blockIPv6"), true); - MacOSFirewall::setAnchorEnabled(QStringLiteral("290.allowDHCP"), true); - MacOSFirewall::setAnchorEnabled(QStringLiteral("300.allowLAN"), true); - - QStringList dnsServers; - dnsServers.append(configStr.value(amnezia::config_key::dns1).toString()); - dnsServers.append(configStr.value(amnezia::config_key::dns2).toString()); - MacOSFirewall::setAnchorEnabled(QStringLiteral("310.blockDNS"), true); - MacOSFirewall::setAnchorTable(QStringLiteral("310.blockDNS"), true, QStringLiteral("dnsaddr"), dnsServers); -#endif - - return true; + return KillSwitch::instance()->enableKillSwitch(configStr, vpnAdapterIndex); } bool IpcServer::disableKillSwitch() { -#ifdef Q_OS_WIN - auto firewallManager = WindowsFirewall::create(this); - Q_ASSERT(firewallManager != nullptr); - return firewallManager->disableKillSwitch(); -#endif - -#ifdef Q_OS_LINUX - LinuxFirewall::uninstall(); -#endif - -#ifdef Q_OS_MACOS - MacOSFirewall::uninstall(); -#endif - - return true; + return KillSwitch::instance()->disableKillSwitch(); } bool IpcServer::enablePeerTraffic(const QJsonObject &configStr) { -#ifdef Q_OS_WIN - InterfaceConfig config; - config.m_dnsServer = configStr.value(amnezia::config_key::dns1).toString(); - config.m_serverPublicKey = "openvpn"; - config.m_serverIpv4Gateway = configStr.value("vpnGateway").toString(); - config.m_serverIpv4AddrIn = configStr.value("vpnServer").toString(); - int vpnAdapterIndex = configStr.value("vpnAdapterIndex").toInt(); - int inetAdapterIndex = configStr.value("inetAdapterIndex").toInt(); - - int splitTunnelType = configStr.value("splitTunnelType").toInt(); - QJsonArray splitTunnelSites = configStr.value("splitTunnelSites").toArray(); - - QStringList AllowedIPAddesses; - - // Use APP split tunnel - if (splitTunnelType == 0 || splitTunnelType == 2) { - config.m_allowedIPAddressRanges.append(IPAddress(QHostAddress("0.0.0.0"), 0)); - config.m_allowedIPAddressRanges.append(IPAddress(QHostAddress("::"), 0)); - } - - if (splitTunnelType == 1) { - for (auto v : splitTunnelSites) { - QString ipRange = v.toString(); - if (ipRange.split('/').size() > 1) { - config.m_allowedIPAddressRanges.append( - IPAddress(QHostAddress(ipRange.split('/')[0]), atoi(ipRange.split('/')[1].toLocal8Bit()))); - } else { - config.m_allowedIPAddressRanges.append(IPAddress(QHostAddress(ipRange), 32)); - } - } - } - - config.m_excludedAddresses.append(configStr.value("vpnServer").toString()); - if (splitTunnelType == 2) { - for (auto v : splitTunnelSites) { - QString ipRange = v.toString(); - config.m_excludedAddresses.append(ipRange); - } - } - - for (const QJsonValue &i : configStr.value(amnezia::config_key::splitTunnelApps).toArray()) { - if (!i.isString()) { - break; - } - config.m_vpnDisabledApps.append(i.toString()); - } - - // killSwitch toggle - if (QVariant(configStr.value(amnezia::config_key::killSwitchOption).toString()).toBool()) { - auto firewallManager = WindowsFirewall::create(this); - Q_ASSERT(firewallManager != nullptr); - firewallManager->enablePeerTraffic(config); - } - - WindowsDaemon::instance()->prepareActivation(config, inetAdapterIndex); - WindowsDaemon::instance()->activateSplitTunnel(config, vpnAdapterIndex); -#endif - return true; + return KillSwitch::instance()->enablePeerTraffic(configStr); +} + +bool IpcServer::refreshKillSwitch(bool enabled) +{ + return KillSwitch::instance()->refresh(enabled); } diff --git a/ipc/ipcserver.h b/ipc/ipcserver.h index 9810046b..00d36354 100644 --- a/ipc/ipcserver.h +++ b/ipc/ipcserver.h @@ -34,9 +34,13 @@ public: virtual bool deleteTun(const QString &dev) override; virtual void StartRoutingIpv6() override; virtual void StopRoutingIpv6() override; + virtual bool disableAllTraffic() override; + virtual bool addKillSwitchAllowedRange(QStringList ranges) override; + virtual bool resetKillSwitchAllowedRange(QStringList ranges) override; virtual bool enablePeerTraffic(const QJsonObject &configStr) override; virtual bool enableKillSwitch(const QJsonObject &excludeAddr, int vpnAdapterIndex) override; virtual bool disableKillSwitch() override; + virtual bool refreshKillSwitch( bool enabled ) override; virtual bool updateResolvers(const QString& ifname, const QList& resolvers) override; private: diff --git a/service/server/CMakeLists.txt b/service/server/CMakeLists.txt index 1436e370..20b05acd 100644 --- a/service/server/CMakeLists.txt +++ b/service/server/CMakeLists.txt @@ -12,8 +12,47 @@ qt_standard_project_setup() configure_file(${CMAKE_SOURCE_DIR}/version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h) +set(QSIMPLECRYPTO_DIR ${CMAKE_CURRENT_LIST_DIR}/../../client/3rd/QSimpleCrypto/src) + + +set(OPENSSL_ROOT_DIR "${CMAKE_CURRENT_LIST_DIR}/../../client/3rd-prebuilt/3rd-prebuilt/openssl/") +set(OPENSSL_LIBRARIES_DIR "${OPENSSL_ROOT_DIR}/lib") + +set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/windows/include") +if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8") + set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/windows/win64/libssl.lib") + set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/windows/win64/libcrypto.lib") +else() + set(OPENSSL_LIB_SSL_PATH "${OPENSSL_ROOT_DIR}/windows/win32/libssl.lib") + set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/windows/win32/libcrypto.lib") +endif() + + +if(WIN32) + set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/windows/include") + if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8") + set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/windows/win64/libcrypto.lib") + else() + set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/windows/win32/libcrypto.lib") + endif() +elseif(APPLE AND NOT IOS) + set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/macos/include") + set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/macos/lib/libcrypto.a") +elseif(LINUX) + set(OPENSSL_INCLUDE_DIR "${OPENSSL_ROOT_DIR}/linux/include") + set(OPENSSL_LIB_CRYPTO_PATH "${OPENSSL_ROOT_DIR}/linux/x86_64/libcrypto.a") +endif() + +set(OPENSSL_USE_STATIC_LIBS TRUE) + +include_directories( + ${OPENSSL_INCLUDE_DIR} + ${QSIMPLECRYPTO_DIR} +) + set(HEADERS ${CMAKE_CURRENT_LIST_DIR}/../../client/utilities.h + ${CMAKE_CURRENT_LIST_DIR}/../../client/secure_qsettings.h ${CMAKE_CURRENT_LIST_DIR}/../../client/core/networkUtilities.h ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipc.h ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipcserver.h @@ -22,12 +61,20 @@ set(HEADERS ${CMAKE_CURRENT_LIST_DIR}/localserver.h ${CMAKE_CURRENT_LIST_DIR}/../../common/logger/logger.h ${CMAKE_CURRENT_LIST_DIR}/router.h + ${CMAKE_CURRENT_LIST_DIR}/killswitch.h ${CMAKE_CURRENT_LIST_DIR}/systemservice.h ${CMAKE_CURRENT_BINARY_DIR}/version.h + ${QSIMPLECRYPTO_DIR}/include/QAead.h + ${QSIMPLECRYPTO_DIR}/include/QBlockCipher.h + ${QSIMPLECRYPTO_DIR}/include/QRsa.h + ${QSIMPLECRYPTO_DIR}/include/QSimpleCrypto_global.h + ${QSIMPLECRYPTO_DIR}/include/QX509.h + ${QSIMPLECRYPTO_DIR}/include/QX509Store.h ) set(SOURCES ${CMAKE_CURRENT_LIST_DIR}/../../client/utilities.cpp + ${CMAKE_CURRENT_LIST_DIR}/../../client/secure_qsettings.cpp ${CMAKE_CURRENT_LIST_DIR}/../../client/core/networkUtilities.cpp ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipcserver.cpp ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipcserverprocess.cpp @@ -36,7 +83,13 @@ set(SOURCES ${CMAKE_CURRENT_LIST_DIR}/../../common/logger/logger.cpp ${CMAKE_CURRENT_LIST_DIR}/main.cpp ${CMAKE_CURRENT_LIST_DIR}/router.cpp + ${CMAKE_CURRENT_LIST_DIR}/killswitch.cpp ${CMAKE_CURRENT_LIST_DIR}/systemservice.cpp + ${QSIMPLECRYPTO_DIR}/sources/QAead.cpp + ${QSIMPLECRYPTO_DIR}/sources/QBlockCipher.cpp + ${QSIMPLECRYPTO_DIR}/sources/QRsa.cpp + ${QSIMPLECRYPTO_DIR}/sources/QX509.cpp + ${QSIMPLECRYPTO_DIR}/sources/QX509Store.cpp ) # Mozilla headres @@ -133,6 +186,7 @@ if(WIN32) set(SOURCES ${SOURCES} ${CMAKE_CURRENT_LIST_DIR}/tapcontroller_win.cpp ${CMAKE_CURRENT_LIST_DIR}/router_win.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/windows/daemon/windowsdaemon.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/windows/daemon/windowsdaemontunnel.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/windows/daemon/windowsfirewall.cpp @@ -159,6 +213,8 @@ if(WIN32) gdi32 Advapi32 Kernel32 + ${OPENSSL_LIB_CRYPTO_PATH} + qt6keychain ) add_compile_definitions(_WINSOCKAPI_) @@ -203,6 +259,9 @@ if(APPLE) ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/macos/daemon/wireguardutilsmacos.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/macos/daemon/macosfirewall.cpp ) + + set(LIBS ${OPENSSL_LIB_CRYPTO_PATH} qt6keychain) + endif() if(LINUX) @@ -233,6 +292,9 @@ if(LINUX) ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/linuxroutemonitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/linuxfirewall.cpp ) + + set(LIBS ${OPENSSL_LIB_CRYPTO_PATH} qt6keychain -static-libstdc++ -static-libgcc -ldl) + endif() include(${CMAKE_CURRENT_LIST_DIR}/../src/qtservice.cmake) @@ -245,6 +307,7 @@ include_directories( ${CMAKE_CURRENT_BINARY_DIR} ) + add_executable(${PROJECT} ${SOURCES} ${HEADERS}) target_link_libraries(${PROJECT} PRIVATE Qt6::Core Qt6::Widgets Qt6::Network Qt6::RemoteObjects Qt6::Core5Compat Qt6::DBus ${LIBS}) target_compile_definitions(${PROJECT} PRIVATE "MZ_$") diff --git a/service/server/killswitch.cpp b/service/server/killswitch.cpp new file mode 100644 index 00000000..c44bd6a2 --- /dev/null +++ b/service/server/killswitch.cpp @@ -0,0 +1,358 @@ +#include "killswitch.h" + + +#include +#include + +#include "../client/protocols/protocols_defs.h" +#include "qjsonarray.h" +#include "version.h" + +#ifdef Q_OS_WIN + #include "../client/platforms/windows/daemon/windowsfirewall.h" + #include "../client/platforms/windows/daemon/windowsdaemon.h" +#endif + +#ifdef Q_OS_LINUX + #include "../client/platforms/linux/daemon/linuxfirewall.h" +#endif + +#ifdef Q_OS_MACOS + #include "../client/platforms/macos/daemon/macosfirewall.h" +#endif + +KillSwitch* s_instance = nullptr; + +KillSwitch* KillSwitch::instance() +{ + if (s_instance == nullptr) { + s_instance = new KillSwitch(qApp); + } + return s_instance; +} + +bool KillSwitch::init() +{ +#ifdef Q_OS_LINUX + if (!LinuxFirewall::isInstalled()) { + LinuxFirewall::install(); + } + m_appSettigns = QSharedPointer(new SecureQSettings(ORGANIZATION_NAME, APPLICATION_NAME, nullptr)); +#endif +#ifdef Q_OS_MACOS + if (!MacOSFirewall::isInstalled()) { + MacOSFirewall::install(); + } + m_appSettigns = QSharedPointer(new SecureQSettings(ORGANIZATION_NAME, APPLICATION_NAME, nullptr)); +#endif + if (isStrictKillSwitchEnabled()) { + return disableAllTraffic(); + } + + return true; +} + +bool KillSwitch::refresh(bool enabled) +{ +#ifdef Q_OS_WIN + QSettings RegHLM("HKEY_LOCAL_MACHINE\\Software\\" + QString(ORGANIZATION_NAME) + + "\\" + QString(APPLICATION_NAME), QSettings::NativeFormat); + RegHLM.setValue("strictKillSwitchEnabled", enabled); +#endif + +#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS) + m_appSettigns->setValue("Conf/strictKillSwitchEnabled", enabled); +#endif + + if (isStrictKillSwitchEnabled()) { + return disableAllTraffic(); + } else { + return disableKillSwitch(); + } + +} + +bool KillSwitch::isStrictKillSwitchEnabled() +{ +#ifdef Q_OS_WIN + QSettings RegHLM("HKEY_LOCAL_MACHINE\\Software\\" + QString(ORGANIZATION_NAME) + + "\\" + QString(APPLICATION_NAME), QSettings::NativeFormat); + return RegHLM.value("strictKillSwitchEnabled", false).toBool(); +#endif + m_appSettigns->sync(); + return m_appSettigns->value("Conf/strictKillSwitchEnabled", false).toBool(); +} + +bool KillSwitch::disableKillSwitch() { +#ifdef Q_OS_LINUX + if (isStrictKillSwitchEnabled()) { + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("000.allowLoopback"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("100.blockAll"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("110.allowNets"), false); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("120.blockNets"), false); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("200.allowVPN"), false); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv6, QStringLiteral("250.blockIPv6"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("290.allowDHCP"), false); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("300.allowLAN"), false); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("310.blockDNS"), false); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("320.allowDNS"), false); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("400.allowPIA"), false); + } else { + LinuxFirewall::uninstall(); + } +#endif + +#ifdef Q_OS_MACOS + if (isStrictKillSwitchEnabled()) { + MacOSFirewall::setAnchorEnabled(QStringLiteral("000.allowLoopback"), true); + MacOSFirewall::setAnchorEnabled(QStringLiteral("100.blockAll"), true); + MacOSFirewall::setAnchorEnabled(QStringLiteral("110.allowNets"), false); + MacOSFirewall::setAnchorEnabled(QStringLiteral("120.blockNets"), false); + MacOSFirewall::setAnchorEnabled(QStringLiteral("200.allowVPN"), false); + MacOSFirewall::setAnchorEnabled(QStringLiteral("250.blockIPv6"), true); + MacOSFirewall::setAnchorEnabled(QStringLiteral("290.allowDHCP"), false); + MacOSFirewall::setAnchorEnabled(QStringLiteral("300.allowLAN"), false); + MacOSFirewall::setAnchorEnabled(QStringLiteral("310.blockDNS"), false); + } else { + MacOSFirewall::uninstall(); + } +#endif + +#ifdef Q_OS_WIN + if (isStrictKillSwitchEnabled()) { + return disableAllTraffic(); + } + return WindowsFirewall::create(this)->allowAllTraffic(); +#endif + + m_allowedRanges.clear(); + return true; +} + +bool KillSwitch::disableAllTraffic() { +#ifdef Q_OS_WIN + WindowsFirewall::create(this)->enableInterface(-1); +#endif +#ifdef Q_OS_LINUX + if (!LinuxFirewall::isInstalled()) { + LinuxFirewall::install(); + } + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("100.blockAll"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("000.allowLoopback"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv6, QStringLiteral("250.blockIPv6"), true); +#endif +#ifdef Q_OS_MACOS + // double-check + ensure our firewall is installed and enabled. This is necessary as + // other software may disable pfctl before re-enabling with their own rules (e.g other VPNs) + if (!MacOSFirewall::isInstalled()) + MacOSFirewall::install(); + MacOSFirewall::ensureRootAnchorPriority(); + MacOSFirewall::setAnchorEnabled(QStringLiteral("100.blockAll"), true); + MacOSFirewall::setAnchorEnabled(QStringLiteral("000.allowLoopback"), true); + MacOSFirewall::setAnchorEnabled(QStringLiteral("250.blockIPv6"), true); +#endif + m_allowedRanges.clear(); + return true; +} + +bool KillSwitch::resetAllowedRange(const QStringList &ranges) { + + m_allowedRanges = ranges; + +#ifdef Q_OS_LINUX + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("110.allowNets"), true); + LinuxFirewall::updateAllowNets(m_allowedRanges); +#endif + +#ifdef Q_OS_MACOS + MacOSFirewall::setAnchorEnabled(QStringLiteral("110.allowNets"), true); + MacOSFirewall::setAnchorTable(QStringLiteral("110.allowNets"), true, QStringLiteral("allownets"), m_allowedRanges); +#endif + +#ifdef Q_OS_WIN + if (isStrictKillSwitchEnabled()) { + WindowsFirewall::create(this)->enableInterface(-1); + } + WindowsFirewall::create(this)->allowTrafficRange(m_allowedRanges); +#endif + + return true; +} + +bool KillSwitch::addAllowedRange(const QStringList &ranges) { + for (const QString &range : ranges) { + if (!range.isEmpty() && !m_allowedRanges.contains(range)) { + m_allowedRanges.append(range); + } + } + + return resetAllowedRange(m_allowedRanges); +} + +bool KillSwitch::enablePeerTraffic(const QJsonObject &configStr) { +#ifdef Q_OS_WIN + InterfaceConfig config; + config.m_dnsServer = configStr.value(amnezia::config_key::dns1).toString(); + config.m_serverPublicKey = "openvpn"; + config.m_serverIpv4Gateway = configStr.value("vpnGateway").toString(); + config.m_serverIpv4AddrIn = configStr.value("vpnServer").toString(); + int vpnAdapterIndex = configStr.value("vpnAdapterIndex").toInt(); + int inetAdapterIndex = configStr.value("inetAdapterIndex").toInt(); + + int splitTunnelType = configStr.value("splitTunnelType").toInt(); + QJsonArray splitTunnelSites = configStr.value("splitTunnelSites").toArray(); + + // Use APP split tunnel + if (splitTunnelType == 0 || splitTunnelType == 2) { + config.m_allowedIPAddressRanges.append(IPAddress(QHostAddress("0.0.0.0"), 0)); + config.m_allowedIPAddressRanges.append(IPAddress(QHostAddress("::"), 0)); + } + + if (splitTunnelType == 1) { + for (auto v : splitTunnelSites) { + QString ipRange = v.toString(); + if (ipRange.split('/').size() > 1) { + config.m_allowedIPAddressRanges.append( + IPAddress(QHostAddress(ipRange.split('/')[0]), atoi(ipRange.split('/')[1].toLocal8Bit()))); + } else { + config.m_allowedIPAddressRanges.append(IPAddress(QHostAddress(ipRange), 32)); + } + } + } + + config.m_excludedAddresses.append(configStr.value("vpnServer").toString()); + if (splitTunnelType == 2) { + for (auto v : splitTunnelSites) { + QString ipRange = v.toString(); + config.m_excludedAddresses.append(ipRange); + } + } + + for (const QJsonValue &i : configStr.value(amnezia::config_key::splitTunnelApps).toArray()) { + if (!i.isString()) { + break; + } + config.m_vpnDisabledApps.append(i.toString()); + } + + for (auto dns : configStr.value(amnezia::config_key::allowedDnsServers).toArray()) { + if (!dns.isString()) { + break; + } + config.m_allowedDnsServers.append(dns.toString()); + } + + // killSwitch toggle + if (QVariant(configStr.value(amnezia::config_key::killSwitchOption).toString()).toBool()) { + WindowsFirewall::create(this)->enablePeerTraffic(config); + } + + WindowsDaemon::instance()->prepareActivation(config, inetAdapterIndex); + WindowsDaemon::instance()->activateSplitTunnel(config, vpnAdapterIndex); +#endif + return true; +} + +bool KillSwitch::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterIndex) { +#ifdef Q_OS_WIN + return WindowsFirewall::create(this)->enableInterface(vpnAdapterIndex); +#endif + +#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS) + int splitTunnelType = configStr.value("splitTunnelType").toInt(); + QJsonArray splitTunnelSites = configStr.value("splitTunnelSites").toArray(); + bool blockAll = 0; + bool allowNets = 0; + bool blockNets = 0; + QStringList allownets; + QStringList blocknets; + + if (splitTunnelType == 0) { + blockAll = true; + allowNets = true; + allownets.append(configStr.value("vpnServer").toString()); + } else if (splitTunnelType == 1) { + blockNets = true; + for (auto v : splitTunnelSites) { + blocknets.append(v.toString()); + } + } else if (splitTunnelType == 2) { + blockAll = true; + allowNets = true; + allownets.append(configStr.value("vpnServer").toString()); + for (auto v : splitTunnelSites) { + allownets.append(v.toString()); + } + } +#endif + +#ifdef Q_OS_LINUX + if (!LinuxFirewall::isInstalled()) { + LinuxFirewall::install(); + } + + // double-check + ensure our firewall is installed and enabled + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("000.allowLoopback"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("100.blockAll"), blockAll); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("110.allowNets"), allowNets); + LinuxFirewall::updateAllowNets(allownets); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("120.blockNets"), blockAll); + LinuxFirewall::updateBlockNets(blocknets); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("200.allowVPN"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv6, QStringLiteral("250.blockIPv6"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("290.allowDHCP"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("300.allowLAN"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("310.blockDNS"), true); + QStringList dnsServers; + dnsServers.append(configStr.value(amnezia::config_key::dns1).toString()); + dnsServers.append(configStr.value(amnezia::config_key::dns2).toString()); + dnsServers.append("127.0.0.1"); + dnsServers.append("127.0.0.53"); + + for (auto dns : configStr.value(amnezia::config_key::allowedDnsServers).toArray()) { + if (!dns.isString()) { + break; + } + dnsServers.append(dns.toString()); + } + + LinuxFirewall::updateDNSServers(dnsServers); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("320.allowDNS"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("400.allowPIA"), true); +#endif + +#ifdef Q_OS_MACOS + // double-check + ensure our firewall is installed and enabled. This is necessary as + // other software may disable pfctl before re-enabling with their own rules (e.g other VPNs) + if (!MacOSFirewall::isInstalled()) + MacOSFirewall::install(); + + MacOSFirewall::ensureRootAnchorPriority(); + MacOSFirewall::setAnchorEnabled(QStringLiteral("000.allowLoopback"), true); + MacOSFirewall::setAnchorEnabled(QStringLiteral("100.blockAll"), blockAll); + MacOSFirewall::setAnchorEnabled(QStringLiteral("110.allowNets"), allowNets); + MacOSFirewall::setAnchorTable(QStringLiteral("110.allowNets"), allowNets, QStringLiteral("allownets"), allownets); + + MacOSFirewall::setAnchorEnabled(QStringLiteral("120.blockNets"), blockNets); + MacOSFirewall::setAnchorTable(QStringLiteral("120.blockNets"), blockNets, QStringLiteral("blocknets"), blocknets); + MacOSFirewall::setAnchorEnabled(QStringLiteral("200.allowVPN"), true); + MacOSFirewall::setAnchorEnabled(QStringLiteral("250.blockIPv6"), true); + MacOSFirewall::setAnchorEnabled(QStringLiteral("290.allowDHCP"), true); + MacOSFirewall::setAnchorEnabled(QStringLiteral("300.allowLAN"), true); + + QStringList dnsServers; + dnsServers.append(configStr.value(amnezia::config_key::dns1).toString()); + dnsServers.append(configStr.value(amnezia::config_key::dns2).toString()); + + for (auto dns : configStr.value(amnezia::config_key::allowedDnsServers).toArray()) { + if (!dns.isString()) { + break; + } + dnsServers.append(dns.toString()); + } + + MacOSFirewall::setAnchorEnabled(QStringLiteral("310.blockDNS"), true); + MacOSFirewall::setAnchorTable(QStringLiteral("310.blockDNS"), true, QStringLiteral("dnsaddr"), dnsServers); +#endif + return true; +} diff --git a/service/server/killswitch.h b/service/server/killswitch.h new file mode 100644 index 00000000..519e2ed2 --- /dev/null +++ b/service/server/killswitch.h @@ -0,0 +1,31 @@ +#ifndef KILLSWITCH_H +#define KILLSWITCH_H + +#include +#include + +#include "secure_qsettings.h" + +class KillSwitch : public QObject +{ + Q_OBJECT +public: + static KillSwitch *instance(); + bool init(); + bool refresh(bool enabled); + bool disableKillSwitch(); + bool disableAllTraffic(); + bool enablePeerTraffic(const QJsonObject &configStr); + bool enableKillSwitch(const QJsonObject &configStr, int vpnAdapterIndex); + bool resetAllowedRange(const QStringList &ranges); + bool addAllowedRange(const QStringList &ranges); + bool isStrictKillSwitchEnabled(); + +private: + KillSwitch(QObject* parent) {}; + QStringList m_allowedRanges; + QSharedPointer m_appSettigns; + +}; + +#endif // KILLSWITCH_H diff --git a/service/server/localserver.cpp b/service/server/localserver.cpp index 8a5079cb..4f005a59 100644 --- a/service/server/localserver.cpp +++ b/service/server/localserver.cpp @@ -5,13 +5,12 @@ #include "ipc.h" #include "localserver.h" -#include "utilities.h" -#include "router.h" +#include "killswitch.h" #include "logger.h" #ifdef Q_OS_WIN -#include "tapcontroller_win.h" + #include "tapcontroller_win.h" #endif namespace { @@ -47,6 +46,8 @@ LocalServer::LocalServer(QObject *parent) : QObject(parent), return; } + KillSwitch::instance()->init(); + #ifdef Q_OS_LINUX // Signal handling for a proper shutdown. QObject::connect(qApp, &QCoreApplication::aboutToQuit,