From e59a48f9f4f3abc84f630f21661718bf29a6db16 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Tue, 6 May 2025 08:11:58 -0700 Subject: [PATCH 01/40] Fixes for Windows killswitch (#1565) * fix: Win OpenVPN with strict mode killswitch * Fixes for Windows killswitch --- client/platforms/windows/daemon/windowsfirewall.cpp | 2 +- client/protocols/openvpnprotocol.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/platforms/windows/daemon/windowsfirewall.cpp b/client/platforms/windows/daemon/windowsfirewall.cpp index 85a2a155..1834452e 100644 --- a/client/platforms/windows/daemon/windowsfirewall.cpp +++ b/client/platforms/windows/daemon/windowsfirewall.cpp @@ -256,7 +256,7 @@ bool WindowsFirewall::allowTrafficRange(const QStringList& ranges) { for (const QString& addr : ranges) { logger.debug() << "Allow killswitch exclude: " << addr; - if (!allowTrafficTo(QHostAddress(addr), LOW_WEIGHT + 1, "Allow killswitch bypass traffic")) { + if (!allowTrafficTo(QHostAddress(addr), HIGH_WEIGHT, "Allow killswitch bypass traffic")) { return false; } } diff --git a/client/protocols/openvpnprotocol.cpp b/client/protocols/openvpnprotocol.cpp index 0c8f5907..429b85a6 100644 --- a/client/protocols/openvpnprotocol.cpp +++ b/client/protocols/openvpnprotocol.cpp @@ -171,7 +171,7 @@ ErrorCode OpenVpnProtocol::start() return lastError(); } -#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS) +#ifdef AMNEZIA_DESKTOP IpcClient::Interface()->addKillSwitchAllowedRange(QStringList(NetworkUtilities::getIPAddress( m_configData.value(amnezia::config_key::hostName).toString()))); #endif From 2c44999a31fc0ad5b3e10a03ec5e251111bf81a8 Mon Sep 17 00:00:00 2001 From: Mitternacht822 Date: Wed, 7 May 2025 17:17:42 +0400 Subject: [PATCH 02/40] Fixed bug with not applying changes to subnet address when reinstalling server (#1546) * fixed bug with not applying changes to subnet address when reinstalling server * fixed wireguard empty 'subnet address' field after reinstalling and removed showing mask for AWG and wireguard in UI --- client/ui/controllers/installController.cpp | 68 ++++++++++++++++++- .../qml/Pages2/PageProtocolXraySettings.qml | 4 +- 2 files changed, 69 insertions(+), 3 deletions(-) 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/qml/Pages2/PageProtocolXraySettings.qml b/client/ui/qml/Pages2/PageProtocolXraySettings.qml index ca30e0a9..bef84af7 100644 --- a/client/ui/qml/Pages2/PageProtocolXraySettings.qml +++ b/client/ui/qml/Pages2/PageProtocolXraySettings.qml @@ -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 } From acc4485e8190471c2e58bd7632aba62788ffba85 Mon Sep 17 00:00:00 2001 From: Nethius Date: Wed, 7 May 2025 21:18:11 +0800 Subject: [PATCH 03/40] bugfix: improve malicious string detection for openvpn configs (#1571) * bugfix: improve malicious string detection for openvpn configs --- client/ui/controllers/importController.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) 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)); From 63b5257986baa9dc30af0e73bd3a3bb88fe5d6b2 Mon Sep 17 00:00:00 2001 From: MrMirDan <58086007+MrMirDan@users.noreply.github.com> Date: Mon, 12 May 2025 10:31:41 +0300 Subject: [PATCH 04/40] chore: update text translations and text (#1573) --- client/containers/containers_defs.cpp | 153 +++-- client/translations/amneziavpn_ru_RU.ts | 537 +++++++----------- client/ui/models/api/apiServicesModel.cpp | 4 +- .../ui/qml/Pages2/PageSettingsApiDevices.qml | 2 +- .../qml/Pages2/PageSettingsApiServerInfo.qml | 2 +- 5 files changed, 290 insertions(+), 408 deletions(-) diff --git a/client/containers/containers_defs.cpp b/client/containers/containers_defs.cpp index 52b148c0..214e2a51 100644 --- a/client/containers/containers_defs.cpp +++ b/client/containers/containers_defs.cpp @@ -140,98 +140,83 @@ QMap 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 AmneziaVPN 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 AmneziaVPN 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 AmneziaVPN 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 AmneziaVPN 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 AmneziaVPN 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 AmneziaVPN 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/translations/amneziavpn_ru_RU.ts b/client/translations/amneziavpn_ru_RU.ts index c740a560..e653b32b 100644 --- a/client/translations/amneziavpn_ru_RU.ts +++ b/client/translations/amneziavpn_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> @@ -1655,8 +1660,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. - Это устройство будет отвязано от вашей подписки. Вы можете подключить его снова в любой момент, нажав кнопку "Подключиться". + Это устройство будет отвязано от вашей подписки. Вы можете подключить его снова в любой момент, нажав кнопку "Подключиться". @@ -2007,8 +2016,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. - Это устройство будет отвязано от вашей подписки. Вы можете подключить его снова в любой момент, нажав кнопку Подключиться. + Это устройство будет отвязано от вашей подписки. Вы можете подключить его снова в любой момент, нажав кнопку Подключиться. @@ -2618,11 +2631,6 @@ Already installed containers were found on the server. All installed containers No new installed containers found Новые установленные протоколы и сервисы не обнаружены - - - - - @@ -2787,11 +2795,6 @@ Already installed containers were found on the server. All installed containers Clear %1 profile? Очистить профиль %1? - - - - - connection settings @@ -3045,7 +3048,7 @@ It's okay as long as it's from someone you trust. Что у вас есть? - + File with connection settings Файл с настройками подключения @@ -3054,112 +3057,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 У меня ничего нет @@ -4404,95 +4430,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 @@ -4503,7 +4490,7 @@ While it offers a blend of security, stability, and speed, it's essential t - + Website in Tor network Веб-сайт в сети Tor @@ -4543,109 +4530,140 @@ While it offers a blend of security, stability, and speed, it's essential t 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. - -* 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 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. -OpenVPN provides a secure VPN connection by encrypting all internet traffic between the client and the server. +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, что делает его уязвимым к блокировкам. -Cloak protects OpenVPN from detection. +Особенности: +* Доступен во всех приложениях AmneziaVPN +* Нормальное энергопотребление на мобильных устройствах +* Гибкие настройки под разные устройства и ОС +* Работает по TCP и UDP + + + + 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. -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 -* High power consumption on mobile devices -* Flexible settings -* Not recognised by detection systems -* Works over TCP network protocol, 443 port. +Features: +* Available in AmneziaVPN only on desktop platforms +* Customizable encryption protocol +* Detectable by some DPI systems +* Operates over TCP protocol - Это связка протокола OpenVPN и плагина Cloak, созданная специально для защиты от обнаружения. + Shadowsocks основан на протоколе SOCKS5 и шифрует соединение алгоритмом AEAD. Он разработан так, чтобы быть малозаметным, однако не идентичен HTTPS, поэтому может распознаваться некоторыми системами DPI. В связи с ограниченной поддержкой в Amnezia, рекомендуем использовать протокол AmneziaWG. -OpenVPN обеспечивает безопасное VPN-соединение, шифруя весь интернет-трафик между клиентом и сервером. +Особенности: +* Доступен только на ПК в AmneziaVPN +* Настраиваемое шифрование +* Может обнаруживаться некоторыми DPI-системами +* Работает по протоколу TCP + + + + This combination includes the OpenVPN protocol and the Cloak plugin, specifically designed to protect against blocking. -Плагин Cloak защищает OpenVPN от обнаружения. +OpenVPN securely encrypts all internet traffic between your device and the server. -Cloak может изменять метаданные пакета, чтобы полностью замаскировать VPN-трафик под обычный веб-трафик, а также защищает VPN от обнаружения с помощью метода Active Probing. Это делает его очень устойчивым к обнаружению. +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. -Сразу после получения первого пакета данных Cloak аутентифицирует входящее соединение, если аутентификация не удалась, плагин маскирует сервер под настоящий веб-сайт, и ваш VPN становится невидимым для систем анализа. Имеет низкую скорость работы в сравнении с другими похожими протоколами. +In regions with heavy internet censorship, we strongly recommend using OpenVPN with Cloak from your first connection. -* Доступно в AmneziaVPN на всех платформах. +Features: +* Available on all AmneziaVPN platforms +* High power consumption on mobile devices +* Flexible configuration options +* Undetectable by DPI systems +* Operates over TCP protocol on port 443 + Эта комбинация состоит из протокола OpenVPN и плагина Cloak, специально разработанных для защиты от блокировок. + +OpenVPN надёжно шифрует весь интернет-трафик между вами и сервером. + +Плагин Cloak дополнительно защищает соединение от распознавания системами DPI. Он изменяет метаданные трафика, маскируя VPN-подключение под обычный веб-трафик, и предотвращает обнаружение с помощью активного зондирования. Если попытка подключения не прошла аутентификацию, Cloak выдаёт поддельный веб-сайт, делая VPN невидимым для анализирующих систем. + +Если в вашем регионе сильная интернет-цензура, мы рекомендуем сразу использовать OpenVPN с плагином Cloak. + +Особенности: +* Доступен на всех платформах 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. @@ -4674,50 +4692,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 @@ -4734,84 +4709,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 @@ -4894,7 +4791,7 @@ This means that AmneziaWG keeps the fast performance of the original while addin - + SOCKS5 proxy server Прокси-сервер SOCKS5 @@ -5308,12 +5205,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/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/qml/Pages2/PageSettingsApiDevices.qml b/client/ui/qml/Pages2/PageSettingsApiDevices.qml index d8e24d42..231b58a0 100644 --- a/client/ui/qml/Pages2/PageSettingsApiDevices.qml +++ b/client/ui/qml/Pages2/PageSettingsApiDevices.qml @@ -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/PageSettingsApiServerInfo.qml b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml index 48080c3a..93118755 100644 --- a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml @@ -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") From 7e380b6cfb46ae23ab3682e5a676f8585e6d22e3 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Mon, 12 May 2025 05:36:25 -0700 Subject: [PATCH 05/40] OpenVPN with system disabled IPv6 (#1563) * Fix for Win OpenVPN with disabled IPv6 and AllExceptSites Splittunnel mode * Remove unneeded stuff for ipv6 openvpn --- client/configurators/openvpn_configurator.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/client/configurators/openvpn_configurator.cpp b/client/configurators/openvpn_configurator.cpp index eca81afd..6d6603da 100644 --- a/client/configurators/openvpn_configurator.cpp +++ b/client/configurators/openvpn_configurator.cpp @@ -120,13 +120,6 @@ 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 - if (NetworkUtilities::checkIpv6Enabled()) { - 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) { @@ -135,7 +128,6 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPair Date: Mon, 12 May 2025 20:37:35 +0800 Subject: [PATCH 06/40] chore: update link to submodule (#1544) * chore: update link to submodule --- client/3rd-prebuilt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From a28ed6a977c6104004061665b9be9ad99dc6525c Mon Sep 17 00:00:00 2001 From: Mitternacht822 Date: Mon, 12 May 2025 18:14:59 +0400 Subject: [PATCH 07/40] feature: added the ability to change port after installing xray (#1556) * added the ability to change port after installing xray * fixed issue with not updating server config for xray on windows platform * fixed some warning in exportcontroller.cpp --- client/core/controllers/serverController.cpp | 15 ++++++++---- client/ui/controllers/exportController.cpp | 12 +++++----- .../ui/models/protocols/xrayConfigModel.cpp | 3 +++ client/ui/models/protocols/xrayConfigModel.h | 3 ++- .../qml/Pages2/PageProtocolXraySettings.qml | 23 ++++++++++++++++++- 5 files changed, 44 insertions(+), 12 deletions(-) diff --git a/client/core/controllers/serverController.cpp b/client/core/controllers/serverController.cpp index 9ac62759..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; } 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/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/Pages2/PageProtocolXraySettings.qml b/client/ui/qml/Pages2/PageProtocolXraySettings.qml index bef84af7..d22e31a2 100644 --- a/client/ui/qml/Pages2/PageProtocolXraySettings.qml +++ b/client/ui/qml/Pages2/PageProtocolXraySettings.qml @@ -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 From b457ef9a3f7f1c74f4898a3acd9e1955f80110b3 Mon Sep 17 00:00:00 2001 From: Nethius Date: Tue, 13 May 2025 12:29:33 +0800 Subject: [PATCH 08/40] feature/premium v1 migration (#1569) * feature: premium v1 migration * chore: added stage for macos with new qt version * chore: downgrade qif version * chore: minor ui fixes --- .github/workflows/deploy.yml | 82 +++++++- .github/workflows/tag-deploy.yml | 2 + client/CMakeLists.txt | 3 + client/core/api/apiDefs.h | 11 + client/core/api/apiUtils.cpp | 68 +++++- client/core/api/apiUtils.h | 2 + client/core/controllers/coreController.cpp | 28 ++- client/core/controllers/coreController.h | 4 + client/core/defs.h | 1 + client/core/errorstrings.cpp | 1 + client/resources.qrc | 3 + client/settings.cpp | 10 + client/settings.h | 3 + .../api/apiPremV1MigrationController.cpp | 127 ++++++++++++ .../api/apiPremV1MigrationController.h | 50 +++++ client/ui/models/servers_model.cpp | 19 ++ client/ui/models/servers_model.h | 1 + .../Components/ApiPremV1MigrationDrawer.qml | 194 ++++++++++++++++++ .../qml/Components/ApiPremV1SubListDrawer.qml | 89 ++++++++ client/ui/qml/Components/OtpCodeDrawer.qml | 77 +++++++ client/ui/qml/Components/QuestionDrawer.qml | 20 +- client/ui/qml/Pages2/PageHome.qml | 30 +++ .../ui/qml/Pages2/PageSettingsServerData.qml | 18 ++ 23 files changed, 829 insertions(+), 14 deletions(-) create mode 100644 client/ui/controllers/api/apiPremV1MigrationController.cpp create mode 100644 client/ui/controllers/api/apiPremV1MigrationController.h create mode 100644 client/ui/qml/Components/ApiPremV1MigrationDrawer.qml create mode 100644 client/ui/qml/Components/ApiPremV1SubListDrawer.qml create mode 100644 client/ui/qml/Components/OtpCodeDrawer.qml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index ae8768a6..86779f33 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -20,6 +20,8 @@ jobs: DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }} DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }} DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }} + FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }} + PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }} steps: - name: 'Install Qt' @@ -90,6 +92,8 @@ jobs: DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }} DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }} DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }} + FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }} + PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }} steps: - name: 'Get sources' @@ -156,6 +160,8 @@ jobs: DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }} DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }} DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }} + FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }} + PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }} steps: - name: 'Setup xcode' @@ -243,7 +249,7 @@ jobs: # ------------------------------------------------------ - Build-MacOS: + Build-MacOS-old: runs-on: macos-latest env: @@ -255,6 +261,78 @@ jobs: DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }} DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }} DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }} + FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }} + PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }} + + steps: + - name: 'Setup xcode' + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: '15.4.0' + + - name: 'Install Qt' + uses: jurplel/install-qt-action@v3 + with: + version: ${{ env.QT_VERSION }} + host: 'mac' + target: 'desktop' + arch: 'clang_64' + modules: 'qtremoteobjects qt5compat qtshadertools' + dir: ${{ runner.temp }} + setup-python: 'true' + set-env: 'true' + extra: '--external 7z --base ${{ env.QT_MIRROR }}' + + - name: 'Install Qt Installer Framework ${{ env.QIF_VERSION }}' + run: | + mkdir -pv ${{ runner.temp }}/Qt/Tools/QtInstallerFramework + wget https://qt.amzsvc.com/tools/ifw/${{ env.QIF_VERSION }}.zip + unzip ${{ env.QIF_VERSION }}.zip -d ${{ runner.temp }}/Qt/Tools/QtInstallerFramework/ + + - name: 'Get sources' + uses: actions/checkout@v4 + with: + submodules: 'true' + fetch-depth: 10 + + - name: 'Setup ccache' + uses: hendrikmuhs/ccache-action@v1.2 + + - name: 'Build project' + run: | + export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin" + export QIF_BIN_DIR="${{ runner.temp }}/Qt/Tools/QtInstallerFramework/${{ env.QIF_VERSION }}/bin" + bash deploy/build_macos.sh + + - name: 'Upload installer artifact' + uses: actions/upload-artifact@v4 + with: + name: AmneziaVPN_MacOS_old_installer + path: AmneziaVPN.dmg + retention-days: 7 + + - name: 'Upload unpacked artifact' + uses: actions/upload-artifact@v4 + with: + name: AmneziaVPN_MacOS_old_unpacked + path: deploy/build/client/AmneziaVPN.app + retention-days: 7 + +# ------------------------------------------------------ + + Build-MacOS: + runs-on: macos-latest + + env: + QT_VERSION: 6.8.0 + QIF_VERSION: 4.8.1 + PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }} + PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }} + DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }} + DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }} + DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }} + FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }} + PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }} steps: - name: 'Setup xcode' @@ -324,6 +402,8 @@ jobs: DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }} DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }} DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }} + FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }} + PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }} steps: - name: 'Install desktop Qt' diff --git a/.github/workflows/tag-deploy.yml b/.github/workflows/tag-deploy.yml index 2bcbd8c6..31c334bf 100644 --- a/.github/workflows/tag-deploy.yml +++ b/.github/workflows/tag-deploy.yml @@ -20,6 +20,8 @@ jobs: DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }} DEV_AGW_ENDPOINT: ${{ secrets.DEV_AGW_ENDPOINT }} DEV_S3_ENDPOINT: ${{ secrets.DEV_S3_ENDPOINT }} + FREE_V2_ENDPOINT: ${{ secrets.FREE_V2_ENDPOINT }} + PREM_V1_ENDPOINT: ${{ secrets.PREM_V1_ENDPOINT }} steps: - name: 'Install desktop Qt' diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index b3f775a0..a454142d 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -31,6 +31,9 @@ add_definitions(-DDEV_AGW_PUBLIC_KEY="$ENV{DEV_AGW_PUBLIC_KEY}") add_definitions(-DDEV_AGW_ENDPOINT="$ENV{DEV_AGW_ENDPOINT}") add_definitions(-DDEV_S3_ENDPOINT="$ENV{DEV_S3_ENDPOINT}") +add_definitions(-DFREE_V2_ENDPOINT="$ENV{FREE_V2_ENDPOINT}") +add_definitions(-DPREM_V1_ENDPOINT="$ENV{PREM_V1_ENDPOINT}") + if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) set(PACKAGES ${PACKAGES} Widgets) endif() diff --git a/client/core/api/apiDefs.h b/client/core/api/apiDefs.h index d1a92d9d..6d1a27fa 100644 --- a/client/core/api/apiDefs.h +++ b/client/core/api/apiDefs.h @@ -22,12 +22,19 @@ namespace apiDefs namespace key { constexpr QLatin1String configVersion("config_version"); + constexpr QLatin1String apiEndpoint("api_endpoint"); + constexpr QLatin1String apiKey("api_key"); + constexpr QLatin1String description("description"); + constexpr QLatin1String name("name"); + constexpr QLatin1String protocol("protocol"); constexpr QLatin1String apiConfig("api_config"); constexpr QLatin1String stackType("stack_type"); constexpr QLatin1String serviceType("service_type"); constexpr QLatin1String vpnKey("vpn_key"); + constexpr QLatin1String config("config"); + constexpr QLatin1String configs("configs"); constexpr QLatin1String installationUuid("installation_uuid"); constexpr QLatin1String workerLastUpdated("worker_last_updated"); @@ -51,6 +58,10 @@ namespace apiDefs constexpr QLatin1String website("website"); constexpr QLatin1String websiteName("website_name"); constexpr QLatin1String telegram("telegram"); + + constexpr QLatin1String id("id"); + constexpr QLatin1String orderId("order_id"); + constexpr QLatin1String migrationCode("migration_code"); } const int requestTimeoutMsecs = 12 * 1000; // 12 secs diff --git a/client/core/api/apiUtils.cpp b/client/core/api/apiUtils.cpp index f5f575c5..f85d2207 100644 --- a/client/core/api/apiUtils.cpp +++ b/client/core/api/apiUtils.cpp @@ -3,6 +3,24 @@ #include #include +namespace +{ + const QByteArray AMNEZIA_CONFIG_SIGNATURE = QByteArray::fromHex("000000ff"); + + QString escapeUnicode(const QString &input) + { + QString output; + for (QChar c : input) { + if (c.unicode() < 0x20 || c.unicode() > 0x7E) { + output += QString("\\u%1").arg(QString::number(c.unicode(), 16).rightJustified(4, '0')); + } else { + output += c; + } + } + return output; + } +} + bool apiUtils::isSubscriptionExpired(const QString &subscriptionEndDate) { QDateTime now = QDateTime::currentDateTime(); @@ -27,22 +45,28 @@ apiDefs::ConfigType apiUtils::getConfigType(const QJsonObject &serverConfigObjec case apiDefs::ConfigSource::Telegram: { }; case apiDefs::ConfigSource::AmneziaGateway: { - constexpr QLatin1String stackPremium("prem"); - constexpr QLatin1String stackFree("free"); - constexpr QLatin1String servicePremium("amnezia-premium"); constexpr QLatin1String serviceFree("amnezia-free"); constexpr QLatin1String serviceExternalPremium("external-premium"); + constexpr QLatin1String freeV2Endpoint(FREE_V2_ENDPOINT); + constexpr QLatin1String premiumV1Endpoint(PREM_V1_ENDPOINT); + auto apiConfigObject = serverConfigObject.value(apiDefs::key::apiConfig).toObject(); auto serviceType = apiConfigObject.value(apiDefs::key::serviceType).toString(); + auto apiEndpoint = serverConfigObject.value(apiDefs::key::apiEndpoint).toString(); + if (serviceType == servicePremium) { return apiDefs::ConfigType::AmneziaPremiumV2; } else if (serviceType == serviceFree) { return apiDefs::ConfigType::AmneziaFreeV3; } else if (serviceType == serviceExternalPremium) { return apiDefs::ConfigType::ExternalPremium; + } else if (apiEndpoint.contains(premiumV1Endpoint)) { + return apiDefs::ConfigType::AmneziaPremiumV1; + } else if (apiEndpoint.contains(freeV2Endpoint)) { + return apiDefs::ConfigType::AmneziaFreeV2; } } default: { @@ -95,3 +119,41 @@ bool apiUtils::isPremiumServer(const QJsonObject &serverConfigObject) apiDefs::ConfigType::ExternalPremium }; return premiumTypes.contains(getConfigType(serverConfigObject)); } + +QString apiUtils::getPremiumV1VpnKey(const QJsonObject &serverConfigObject) +{ + if (apiUtils::getConfigType(serverConfigObject) != apiDefs::ConfigType::AmneziaPremiumV1) { + return {}; + } + + QList> orderedFields; + orderedFields.append(qMakePair(apiDefs::key::name, serverConfigObject[apiDefs::key::name].toString())); + orderedFields.append(qMakePair(apiDefs::key::description, serverConfigObject[apiDefs::key::description].toString())); + orderedFields.append(qMakePair(apiDefs::key::configVersion, serverConfigObject[apiDefs::key::configVersion].toDouble())); + orderedFields.append(qMakePair(apiDefs::key::protocol, serverConfigObject[apiDefs::key::protocol].toString())); + orderedFields.append(qMakePair(apiDefs::key::apiEndpoint, serverConfigObject[apiDefs::key::apiEndpoint].toString())); + orderedFields.append(qMakePair(apiDefs::key::apiKey, serverConfigObject[apiDefs::key::apiKey].toString())); + + QString vpnKeyStr = "{"; + for (int i = 0; i < orderedFields.size(); ++i) { + const auto &pair = orderedFields[i]; + if (pair.second.typeId() == QMetaType::Type::QString) { + vpnKeyStr += "\"" + pair.first + "\": \"" + pair.second.toString() + "\""; + } else if (pair.second.typeId() == QMetaType::Type::Double || pair.second.typeId() == QMetaType::Type::Int) { + vpnKeyStr += "\"" + pair.first + "\": " + QString::number(pair.second.toDouble(), 'f', 1); + } + + if (i < orderedFields.size() - 1) { + vpnKeyStr += ", "; + } + } + vpnKeyStr += "}"; + + QByteArray vpnKeyCompressed = escapeUnicode(vpnKeyStr).toUtf8(); + vpnKeyCompressed = qCompress(vpnKeyCompressed, 6); + vpnKeyCompressed = vpnKeyCompressed.mid(4); + + QByteArray signedData = AMNEZIA_CONFIG_SIGNATURE + vpnKeyCompressed; + + return QString("vpn://%1").arg(QString(signedData.toBase64(QByteArray::Base64UrlEncoding))); +} diff --git a/client/core/api/apiUtils.h b/client/core/api/apiUtils.h index 47006e80..45eaf2de 100644 --- a/client/core/api/apiUtils.h +++ b/client/core/api/apiUtils.h @@ -19,6 +19,8 @@ namespace apiUtils apiDefs::ConfigSource getConfigSource(const QJsonObject &serverConfigObject); amnezia::ErrorCode checkNetworkReplyErrors(const QList &sslErrors, QNetworkReply *reply); + + QString getPremiumV1VpnKey(const QJsonObject &serverConfigObject); } #endif // APIUTILS_H diff --git a/client/core/controllers/coreController.cpp b/client/core/controllers/coreController.cpp index f2c09d45..0e72ef1a 100644 --- a/client/core/controllers/coreController.cpp +++ b/client/core/controllers/coreController.cpp @@ -148,6 +148,9 @@ void CoreController::initControllers() m_apiConfigsController.reset(new ApiConfigsController(m_serversModel, m_apiServicesModel, m_settings)); m_engine->rootContext()->setContextProperty("ApiConfigsController", m_apiConfigsController.get()); + + m_apiPremV1MigrationController.reset(new ApiPremV1MigrationController(m_serversModel, m_settings, this)); + m_engine->rootContext()->setContextProperty("ApiPremV1MigrationController", m_apiPremV1MigrationController.get()); } void CoreController::initAndroidController() @@ -220,6 +223,8 @@ void CoreController::initSignalHandlers() initAutoConnectHandler(); initAmneziaDnsToggledHandler(); initPrepareConfigHandler(); + initImportPremiumV2VpnKeyHandler(); + initShowMigrationDrawerHandler(); initStrictKillSwitchHandler(); } @@ -363,10 +368,29 @@ void CoreController::initPrepareConfigHandler() }); } +void CoreController::initImportPremiumV2VpnKeyHandler() +{ + connect(m_apiPremV1MigrationController.get(), &ApiPremV1MigrationController::importPremiumV2VpnKey, this, [this](const QString &vpnKey) { + m_importController->extractConfigFromData(vpnKey); + m_importController->importConfig(); + + emit m_apiPremV1MigrationController->migrationFinished(); + }); +} + +void CoreController::initShowMigrationDrawerHandler() +{ + QTimer::singleShot(1000, this, [this]() { + if (m_apiPremV1MigrationController->isPremV1MigrationReminderActive() && m_apiPremV1MigrationController->hasConfigsToMigration()) { + m_apiPremV1MigrationController->showMigrationDrawer(); + } + }); +} + void CoreController::initStrictKillSwitchHandler() { - connect(m_settingsController.get(), &SettingsController::strictKillSwitchEnabledChanged, - m_vpnConnection.get(), &VpnConnection::onKillSwitchModeChanged); + connect(m_settingsController.get(), &SettingsController::strictKillSwitchEnabledChanged, m_vpnConnection.get(), + &VpnConnection::onKillSwitchModeChanged); } QSharedPointer CoreController::pageController() const diff --git a/client/core/controllers/coreController.h b/client/core/controllers/coreController.h index 6342d738..9ae53562 100644 --- a/client/core/controllers/coreController.h +++ b/client/core/controllers/coreController.h @@ -7,6 +7,7 @@ #include "ui/controllers/api/apiConfigsController.h" #include "ui/controllers/api/apiSettingsController.h" +#include "ui/controllers/api/apiPremV1MigrationController.h" #include "ui/controllers/appSplitTunnelingController.h" #include "ui/controllers/allowedDnsController.h" #include "ui/controllers/connectionController.h" @@ -82,6 +83,8 @@ private: void initAutoConnectHandler(); void initAmneziaDnsToggledHandler(); void initPrepareConfigHandler(); + void initImportPremiumV2VpnKeyHandler(); + void initShowMigrationDrawerHandler(); void initStrictKillSwitchHandler(); QQmlApplicationEngine *m_engine {}; // TODO use parent child system here? @@ -109,6 +112,7 @@ private: QScopedPointer m_apiSettingsController; QScopedPointer m_apiConfigsController; + QScopedPointer m_apiPremV1MigrationController; QSharedPointer m_containersModel; QSharedPointer m_defaultServerContainersModel; diff --git a/client/core/defs.h b/client/core/defs.h index eff3df3b..674d1add 100644 --- a/client/core/defs.h +++ b/client/core/defs.h @@ -117,6 +117,7 @@ namespace amnezia ApiServicesMissingError = 1107, ApiConfigLimitError = 1108, ApiNotFoundError = 1109, + ApiMigrationError = 1110, // QFile errors OpenError = 1200, diff --git a/client/core/errorstrings.cpp b/client/core/errorstrings.cpp index 6abab0e0..f330bc34 100644 --- a/client/core/errorstrings.cpp +++ b/client/core/errorstrings.cpp @@ -74,6 +74,7 @@ QString errorString(ErrorCode code) { case (ErrorCode::ApiServicesMissingError): errorMessage = QObject::tr("Missing list of available services"); break; case (ErrorCode::ApiConfigLimitError): errorMessage = QObject::tr("The limit of allowed configurations per subscription has been exceeded"); break; case (ErrorCode::ApiNotFoundError): errorMessage = QObject::tr("Error when retrieving configuration from API"); break; + case (ErrorCode::ApiMigrationError): errorMessage = QObject::tr("A migration error occurred. Please contact our technical support"); break; // QFile errors case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break; diff --git a/client/resources.qrc b/client/resources.qrc index a36b60d1..72eb15c7 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -236,6 +236,9 @@ ui/qml/Pages2/PageSettingsApiNativeConfigs.qml ui/qml/Pages2/PageSettingsApiDevices.qml images/controls/monitor.svg + ui/qml/Components/ApiPremV1MigrationDrawer.qml + ui/qml/Components/ApiPremV1SubListDrawer.qml + ui/qml/Components/OtpCodeDrawer.qml images/flagKit/ZW.svg diff --git a/client/settings.cpp b/client/settings.cpp index 9a0a32e5..fb9c72c1 100644 --- a/client/settings.cpp +++ b/client/settings.cpp @@ -559,6 +559,16 @@ void Settings::disableHomeAdLabel() setValue("Conf/homeAdLabelVisible", false); } +bool Settings::isPremV1MigrationReminderActive() +{ + return value("Conf/premV1MigrationReminderActive", true).toBool(); +} + +void Settings::disablePremV1MigrationReminder() +{ + setValue("Conf/premV1MigrationReminderActive", false); +} + QStringList Settings::allowedDnsServers() const { return value("Conf/allowedDnsServers").toStringList(); diff --git a/client/settings.h b/client/settings.h index 01155c0c..eec6cc44 100644 --- a/client/settings.h +++ b/client/settings.h @@ -229,6 +229,9 @@ public: bool isHomeAdLabelVisible(); void disableHomeAdLabel(); + bool isPremV1MigrationReminderActive(); + void disablePremV1MigrationReminder(); + QStringList allowedDnsServers() const; void setAllowedDnsServers(const QStringList &servers); diff --git a/client/ui/controllers/api/apiPremV1MigrationController.cpp b/client/ui/controllers/api/apiPremV1MigrationController.cpp new file mode 100644 index 00000000..0a9b6139 --- /dev/null +++ b/client/ui/controllers/api/apiPremV1MigrationController.cpp @@ -0,0 +1,127 @@ +#include "apiPremV1MigrationController.h" + +#include +#include + +#include "core/api/apiDefs.h" +#include "core/api/apiUtils.h" +#include "core/controllers/gatewayController.h" + +ApiPremV1MigrationController::ApiPremV1MigrationController(const QSharedPointer &serversModel, + const std::shared_ptr &settings, QObject *parent) + : QObject(parent), m_serversModel(serversModel), m_settings(settings) +{ +} + +bool ApiPremV1MigrationController::hasConfigsToMigration() +{ + QJsonArray vpnKeys; + + auto serversCount = m_serversModel->getServersCount(); + for (size_t i = 0; i < serversCount; i++) { + auto serverConfigObject = m_serversModel->getServerConfig(i); + + if (apiUtils::getConfigType(serverConfigObject) != apiDefs::ConfigType::AmneziaPremiumV1) { + continue; + } + + QString vpnKey = apiUtils::getPremiumV1VpnKey(serverConfigObject); + vpnKeys.append(vpnKey); + } + + GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs); + QJsonObject apiPayload; + + apiPayload["configs"] = vpnKeys; + QByteArray responseBody; + ErrorCode errorCode = gatewayController.post(QString("%1v1/prem-v1/is-active-subscription"), apiPayload, responseBody); + + auto migrationsStatus = QJsonDocument::fromJson(responseBody).object(); + for (const auto &migrationStatus : migrationsStatus) { + if (migrationStatus == "not_found") { + return true; + } + } + + return false; +} + +void ApiPremV1MigrationController::getSubscriptionList(const QString &email) +{ + GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs); + QJsonObject apiPayload; + + apiPayload[apiDefs::key::email] = email; + QByteArray responseBody; + ErrorCode errorCode = gatewayController.post(QString("%1v1/prem-v1/subscription-list"), apiPayload, responseBody); + + if (errorCode == ErrorCode::NoError) { + m_email = email; + m_subscriptionsModel = QJsonDocument::fromJson(responseBody).array(); + if (m_subscriptionsModel.isEmpty()) { + emit noSubscriptionToMigrate(); + return; + } + + emit subscriptionsModelChanged(); + } else { + emit errorOccurred(ErrorCode::ApiMigrationError); + } +} + +QJsonArray ApiPremV1MigrationController::getSubscriptionModel() +{ + return m_subscriptionsModel; +} + +void ApiPremV1MigrationController::sendMigrationCode(const int subscriptionIndex) +{ + QEventLoop wait; + QTimer::singleShot(1000, &wait, &QEventLoop::quit); + wait.exec(); + + GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs); + QJsonObject apiPayload; + + apiPayload[apiDefs::key::email] = m_email; + QByteArray responseBody; + ErrorCode errorCode = gatewayController.post(QString("%1v1/prem-v1/migration-code"), apiPayload, responseBody); + + if (errorCode == ErrorCode::NoError) { + m_subscriptionIndex = subscriptionIndex; + emit otpSuccessfullySent(); + } else { + emit errorOccurred(ErrorCode::ApiMigrationError); + } +} + +void ApiPremV1MigrationController::migrate(const QString &migrationCode) +{ + GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs); + QJsonObject apiPayload; + + apiPayload[apiDefs::key::email] = m_email; + apiPayload[apiDefs::key::orderId] = m_subscriptionsModel.at(m_subscriptionIndex).toObject().value(apiDefs::key::id).toString(); + apiPayload[apiDefs::key::migrationCode] = migrationCode; + QByteArray responseBody; + ErrorCode errorCode = gatewayController.post(QString("%1v1/prem-v1/migrate"), apiPayload, responseBody); + + if (errorCode == ErrorCode::NoError) { + auto responseObject = QJsonDocument::fromJson(responseBody).object(); + QString premiumV2VpnKey = responseObject.value(apiDefs::key::config).toString(); + + emit importPremiumV2VpnKey(premiumV2VpnKey); + } else { + emit errorOccurred(ErrorCode::ApiMigrationError); + } +} + +bool ApiPremV1MigrationController::isPremV1MigrationReminderActive() +{ + return m_settings->isPremV1MigrationReminderActive(); +} + +void ApiPremV1MigrationController::disablePremV1MigrationReminder() +{ + m_settings->disablePremV1MigrationReminder(); +} diff --git a/client/ui/controllers/api/apiPremV1MigrationController.h b/client/ui/controllers/api/apiPremV1MigrationController.h new file mode 100644 index 00000000..d7c10460 --- /dev/null +++ b/client/ui/controllers/api/apiPremV1MigrationController.h @@ -0,0 +1,50 @@ +#ifndef APIPREMV1MIGRATIONCONTROLLER_H +#define APIPREMV1MIGRATIONCONTROLLER_H + +#include + +#include "ui/models/servers_model.h" + +class ApiPremV1MigrationController : public QObject +{ + Q_OBJECT +public: + ApiPremV1MigrationController(const QSharedPointer &serversModel, const std::shared_ptr &settings, + QObject *parent = nullptr); + + Q_PROPERTY(QJsonArray subscriptionsModel READ getSubscriptionModel NOTIFY subscriptionsModelChanged) + +public slots: + bool hasConfigsToMigration(); + void getSubscriptionList(const QString &email); + QJsonArray getSubscriptionModel(); + void sendMigrationCode(const int subscriptionIndex); + void migrate(const QString &migrationCode); + + bool isPremV1MigrationReminderActive(); + void disablePremV1MigrationReminder(); + +signals: + void subscriptionsModelChanged(); + + void otpSuccessfullySent(); + + void importPremiumV2VpnKey(const QString &vpnKey); + + void errorOccurred(ErrorCode errorCode); + + void showMigrationDrawer(); + void migrationFinished(); + + void noSubscriptionToMigrate(); + +private: + QSharedPointer m_serversModel; + std::shared_ptr m_settings; + + QJsonArray m_subscriptionsModel; + int m_subscriptionIndex; + QString m_email; +}; + +#endif // APIPREMV1MIGRATIONCONTROLLER_H diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index 7cde28b4..0a7b2526 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -348,6 +348,25 @@ void ServersModel::removeServer() endResetModel(); } +void ServersModel::removeServer(const int serverIndex) +{ + beginResetModel(); + m_settings->removeServer(serverIndex); + m_servers = m_settings->serversArray(); + + if (m_settings->defaultServerIndex() == serverIndex) { + setDefaultServerIndex(0); + } else if (m_settings->defaultServerIndex() > serverIndex) { + setDefaultServerIndex(m_settings->defaultServerIndex() - 1); + } + + if (m_settings->serversCount() == 0) { + setDefaultServerIndex(-1); + } + setProcessedServerIndex(m_defaultServerIndex); + endResetModel(); +} + QHash ServersModel::roleNames() const { QHash roles; diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index 4b790c7a..c4803708 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -90,6 +90,7 @@ public slots: void addServer(const QJsonObject &server); void editServer(const QJsonObject &server, const int serverIndex); void removeServer(); + void removeServer(const int serverIndex); QJsonObject getServerConfig(const int serverIndex); diff --git a/client/ui/qml/Components/ApiPremV1MigrationDrawer.qml b/client/ui/qml/Components/ApiPremV1MigrationDrawer.qml new file mode 100644 index 00000000..113ec1f6 --- /dev/null +++ b/client/ui/qml/Components/ApiPremV1MigrationDrawer.qml @@ -0,0 +1,194 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import QtCore + +import PageEnum 1.0 +import Style 1.0 + +import "./" +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" +import "../Components" + +DrawerType2 { + id: root + + expandedHeight: parent.height * 0.9 + + Connections { + target: ApiPremV1MigrationController + + function onErrorOccurred(error, goToPageHome) { + PageController.showErrorMessage(error) + root.closeTriggered() + } + } + + expandedStateContent: Item { + implicitHeight: root.expandedHeight + + ListViewType { + id: listView + + anchors.fill: parent + + model: 1 // fake model to force the ListView to be created without a model + snapMode: ListView.NoSnap + + header: ColumnLayout { + width: listView.width + + Header2Type { + id: header + Layout.fillWidth: true + Layout.topMargin: 20 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: qsTr("Switch to the new Amnezia Premium subscription") + } + } + + delegate: ColumnLayout { + width: listView.width + + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: 16 + anchors.rightMargin: 16 + + ParagraphTextType { + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.bottomMargin: 24 + + horizontalAlignment: Text.AlignLeft + textFormat: Text.RichText + text: { + var str = qsTr("We'll preserve all remaining days of your current subscription and give you an extra month as a thank you. ") + str += qsTr("This new subscription type will be actively developed with more locations and features added regularly. Currently available:") + str += "

    " + str += qsTr("
  • 9 locations (with more coming soon)
  • ") + str += qsTr("
  • Easier switching between countries in the app
  • ") + str += qsTr("
  • Personal dashboard to manage your subscription
  • ") + str += "
" + str += qsTr("Old keys will be deactivated after switching.") + } + } + + TextFieldWithHeaderType { + id: emailLabel + Layout.fillWidth: true + + borderColor: AmneziaStyle.color.mutedGray + headerTextColor: AmneziaStyle.color.paleGray + + headerText: qsTr("Email") + textField.placeholderText: qsTr("mail@example.com") + + + textField.onFocusChanged: { + textField.text = textField.text.replace(/^\s+|\s+$/g, '') + } + + Connections { + target: ApiPremV1MigrationController + + function onNoSubscriptionToMigrate() { + emailLabel.errorText = qsTr("No old format subscriptions for a given email") + } + } + } + + CaptionTextType { + Layout.fillWidth: true + Layout.topMargin: 16 + + color: AmneziaStyle.color.mutedGray + + text: qsTr("Enter the email you used for your current subscription") + } + + ApiPremV1SubListDrawer { + id: apiPremV1SubListDrawer + parent: root + + anchors.fill: parent + } + + OtpCodeDrawer { + id: otpCodeDrawer + parent: root + + anchors.fill: parent + } + + BasicButtonType { + id: yesButton + Layout.fillWidth: true + Layout.topMargin: 32 + + text: qsTr("Continue") + + clickedFunc: function() { + PageController.showBusyIndicator(true) + ApiPremV1MigrationController.getSubscriptionList(emailLabel.textField.text) + PageController.showBusyIndicator(false) + } + } + + BasicButtonType { + id: noButton + Layout.fillWidth: true + + defaultColor: AmneziaStyle.color.transparent + hoveredColor: AmneziaStyle.color.translucentWhite + pressedColor: AmneziaStyle.color.sheerWhite + disabledColor: AmneziaStyle.color.mutedGray + textColor: AmneziaStyle.color.paleGray + borderWidth: 1 + + text: qsTr("Remind me later") + + clickedFunc: function() { + root.closeTriggered() + } + } + + BasicButtonType { + Layout.alignment: Qt.AlignHCenter + Layout.topMargin: 32 + Layout.bottomMargin: 32 + implicitHeight: 32 + + defaultColor: "transparent" + hoveredColor: AmneziaStyle.color.translucentWhite + pressedColor: AmneziaStyle.color.sheerWhite + textColor: AmneziaStyle.color.vibrantRed + + text: qsTr("Don't remind me again") + + clickedFunc: function() { + var headerText = qsTr("No more reminders? You can always switch to the new format in the server settings") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") + + var yesButtonFunction = function() { + ApiPremV1MigrationController.disablePremV1MigrationReminder() + root.closeTriggered() + } + var noButtonFunction = function() { + } + + showQuestionDrawer(headerText, "", yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) + } + } + } + } + } +} diff --git a/client/ui/qml/Components/ApiPremV1SubListDrawer.qml b/client/ui/qml/Components/ApiPremV1SubListDrawer.qml new file mode 100644 index 00000000..221b7a89 --- /dev/null +++ b/client/ui/qml/Components/ApiPremV1SubListDrawer.qml @@ -0,0 +1,89 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import Style 1.0 + +import "../Controls2" +import "../Controls2/TextTypes" +import "../Config" + +DrawerType2 { + id: root + + Connections { + target: ApiPremV1MigrationController + + function onSubscriptionsModelChanged() { + if (ApiPremV1MigrationController.subscriptionsModel.length > 1) { + root.openTriggered() + } else { + sendMigrationCode(0) + } + } + } + + function sendMigrationCode(index) { + PageController.showBusyIndicator(true) + ApiPremV1MigrationController.sendMigrationCode(index) + root.closeTriggered() + PageController.showBusyIndicator(false) + } + + expandedHeight: parent.height * 0.9 + + expandedStateContent: Item { + implicitHeight: root.expandedHeight + + ListViewType { + id: listView + + anchors.fill: parent + + model: ApiPremV1MigrationController.subscriptionsModel + + header: ColumnLayout { + width: listView.width + + Header2Type { + id: header + Layout.fillWidth: true + Layout.topMargin: 20 + Layout.leftMargin: 16 + Layout.rightMargin: 16 + + headerText: qsTr("Choose Subscription") + } + } + + delegate: Item { + implicitWidth: listView.width + implicitHeight: delegateContent.implicitHeight + + ColumnLayout { + id: delegateContent + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + + LabelWithButtonType { + id: server + Layout.fillWidth: true + + text: qsTr("Order ID: ") + modelData.id + + descriptionText: qsTr("Purchase Date: ") + Qt.formatDateTime(new Date(modelData.created_at), "dd.MM.yyyy hh:mm") + rightImageSource: "qrc:/images/controls/chevron-right.svg" + + clickedFunction: function() { + sendMigrationCode(index) + } + } + + DividerType {} + } + } + } + } +} diff --git a/client/ui/qml/Components/OtpCodeDrawer.qml b/client/ui/qml/Components/OtpCodeDrawer.qml new file mode 100644 index 00000000..e26982b9 --- /dev/null +++ b/client/ui/qml/Components/OtpCodeDrawer.qml @@ -0,0 +1,77 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import Style 1.0 + +import "../Controls2" +import "../Controls2/TextTypes" + +import "../Config" + +DrawerType2 { + id: root + + Connections { + target: ApiPremV1MigrationController + + function onOtpSuccessfullySent() { + root.openTriggered() + } + } + + expandedHeight: parent.height * 0.6 + + expandedStateContent: Item { + implicitHeight: root.expandedHeight + + ColumnLayout { + + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: 16 + anchors.rightMargin: 16 + spacing: 0 + + Header2Type { + id: header + Layout.fillWidth: true + Layout.topMargin: 20 + + headerText: qsTr("OTP code was sent to your email") + } + + TextFieldWithHeaderType { + id: otpFiled + + borderColor: AmneziaStyle.color.mutedGray + headerTextColor: AmneziaStyle.color.paleGray + + Layout.fillWidth: true + Layout.topMargin: 16 + headerText: qsTr("OTP Code") + textField.maximumLength: 30 + checkEmptyText: true + } + + BasicButtonType { + id: saveButton + + Layout.fillWidth: true + Layout.topMargin: 16 + + text: qsTr("Continue") + + clickedFunc: function() { + PageController.showBusyIndicator(true) + ApiPremV1MigrationController.migrate(otpFiled.textField.text) + PageController.showBusyIndicator(false) + root.closeTriggered() + } + } + } + } +} diff --git a/client/ui/qml/Components/QuestionDrawer.qml b/client/ui/qml/Components/QuestionDrawer.qml index 0c14e52d..ddf5cda4 100644 --- a/client/ui/qml/Components/QuestionDrawer.qml +++ b/client/ui/qml/Components/QuestionDrawer.qml @@ -1,3 +1,5 @@ +pragma ComponentBehavior: Bound + import QtQuick import QtQuick.Controls import QtQuick.Layouts @@ -39,7 +41,7 @@ DrawerType2 { Layout.rightMargin: 16 Layout.leftMargin: 16 - text: headerText + text: root.headerText } ParagraphTextType { @@ -48,7 +50,7 @@ DrawerType2 { Layout.rightMargin: 16 Layout.leftMargin: 16 - text: descriptionText + text: root.descriptionText } BasicButtonType { @@ -58,11 +60,11 @@ DrawerType2 { Layout.rightMargin: 16 Layout.leftMargin: 16 - text: yesButtonText + text: root.yesButtonText clickedFunc: function() { - if (yesButtonFunction && typeof yesButtonFunction === "function") { - yesButtonFunction() + if (root.yesButtonFunction && typeof root.yesButtonFunction === "function") { + root.yesButtonFunction() } } } @@ -80,11 +82,13 @@ DrawerType2 { textColor: AmneziaStyle.color.paleGray borderWidth: 1 - text: noButtonText + visible: root.noButtonText !== "" + + text: root.noButtonText clickedFunc: function() { - if (noButtonFunction && typeof noButtonFunction === "function") { - noButtonFunction() + if (root.noButtonFunction && typeof root.noButtonFunction === "function") { + root.noButtonFunction() } } } diff --git a/client/ui/qml/Pages2/PageHome.qml b/client/ui/qml/Pages2/PageHome.qml index f7233a89..7934e5fb 100644 --- a/client/ui/qml/Pages2/PageHome.qml +++ b/client/ui/qml/Pages2/PageHome.qml @@ -33,6 +33,31 @@ PageType { } } + Connections { + + target: ApiPremV1MigrationController + + function onMigrationFinished() { + apiPremV1MigrationDrawer.closeTriggered() + + var headerText = qsTr("You've successfully switched to the new Amnezia Premium subscription!") + var descriptionText = qsTr("Old keys will no longer work. Please use your new subscription key to connect. \nThank you for staying with us!") + var yesButtonText = qsTr("Continue") + var noButtonText = "" + + var yesButtonFunction = function() { + } + var noButtonFunction = function() { + } + + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) + } + + function onShowMigrationDrawer() { + apiPremV1MigrationDrawer.openTriggered() + } + } + Item { objectName: "homeColumnItem" @@ -429,4 +454,9 @@ PageType { } } } + + ApiPremV1MigrationDrawer { + id: apiPremV1MigrationDrawer + anchors.fill: parent + } } diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index 977e669e..995ca74b 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -257,6 +257,24 @@ PageType { DividerType { visible: ServersModel.getProcessedServerData("isServerFromTelegramApi") } + + LabelWithButtonType { + id: labelWithButton6 + visible: ServersModel.getProcessedServerData("isServerFromTelegramApi") + Layout.fillWidth: true + + text: qsTr("Switch to the new Amnezia Premium subscription") + textColor: AmneziaStyle.color.vibrantRed + + clickedFunction: function() { + PageController.goToPageHome() + ApiPremV1MigrationController.showMigrationDrawer() + } + } + + DividerType { + visible: ServersModel.getProcessedServerData("isServerFromTelegramApi") + } } } } From 7702f2f74cc4a48362083693ba893938140bbfc6 Mon Sep 17 00:00:00 2001 From: Nethius Date: Thu, 15 May 2025 21:34:48 +0800 Subject: [PATCH 09/40] bugfix: adding gateway to exceptions only if strict killswitch is enabled (#1585) --- client/core/controllers/gatewayController.cpp | 19 +++++++++------ client/core/controllers/gatewayController.h | 4 +++- .../controllers/api/apiConfigsController.cpp | 24 ++++++++++++------- .../api/apiPremV1MigrationController.cpp | 12 ++++++---- .../controllers/api/apiSettingsController.cpp | 3 ++- 5 files changed, 41 insertions(+), 21 deletions(-) diff --git a/client/core/controllers/gatewayController.cpp b/client/core/controllers/gatewayController.cpp index 0d86b9d5..9a7ee6e5 100644 --- a/client/core/controllers/gatewayController.cpp +++ b/client/core/controllers/gatewayController.cpp @@ -14,8 +14,8 @@ #include "amnezia_application.h" #include "core/api/apiUtils.h" -#include "utilities.h" #include "core/networkUtilities.h" +#include "utilities.h" #ifdef AMNEZIA_DESKTOP #include "core/ipcclient.h" @@ -38,8 +38,13 @@ namespace constexpr QLatin1String errorResponsePattern3("Account not found."); } -GatewayController::GatewayController(const QString &gatewayEndpoint, bool isDevEnvironment, int requestTimeoutMsecs, QObject *parent) - : QObject(parent), m_gatewayEndpoint(gatewayEndpoint), m_isDevEnvironment(isDevEnvironment), m_requestTimeoutMsecs(requestTimeoutMsecs) +GatewayController::GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs, + const bool isStrictKillSwitchEnabled, QObject *parent) + : QObject(parent), + m_gatewayEndpoint(gatewayEndpoint), + m_isDevEnvironment(isDevEnvironment), + m_requestTimeoutMsecs(requestTimeoutMsecs), + m_isStrictKillSwitchEnabled(isStrictKillSwitchEnabled) { } @@ -58,11 +63,11 @@ ErrorCode GatewayController::get(const QString &endpoint, QByteArray &responseBo // bypass killSwitch exceptions for API-gateway #ifdef AMNEZIA_DESKTOP - { + if (m_isStrictKillSwitchEnabled) { QString host = QUrl(request.url()).host(); QString ip = NetworkUtilities::getIPAddress(host); if (!ip.isEmpty()) { - IpcClient::Interface()->addKillSwitchAllowedRange(QStringList{ip}); + IpcClient::Interface()->addKillSwitchAllowedRange(QStringList { ip }); } } #endif @@ -120,11 +125,11 @@ ErrorCode GatewayController::post(const QString &endpoint, const QJsonObject api // bypass killSwitch exceptions for API-gateway #ifdef AMNEZIA_DESKTOP - { + if (m_isStrictKillSwitchEnabled) { QString host = QUrl(request.url()).host(); QString ip = NetworkUtilities::getIPAddress(host); if (!ip.isEmpty()) { - IpcClient::Interface()->addKillSwitchAllowedRange(QStringList{ip}); + IpcClient::Interface()->addKillSwitchAllowedRange(QStringList { ip }); } } #endif diff --git a/client/core/controllers/gatewayController.h b/client/core/controllers/gatewayController.h index 45d989f0..9f91df53 100644 --- a/client/core/controllers/gatewayController.h +++ b/client/core/controllers/gatewayController.h @@ -15,7 +15,8 @@ class GatewayController : public QObject Q_OBJECT public: - explicit GatewayController(const QString &gatewayEndpoint, bool isDevEnvironment, int requestTimeoutMsecs, QObject *parent = nullptr); + explicit GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs, + const bool isStrictKillSwitchEnabled, QObject *parent = nullptr); amnezia::ErrorCode get(const QString &endpoint, QByteArray &responseBody); amnezia::ErrorCode post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody); @@ -30,6 +31,7 @@ private: int m_requestTimeoutMsecs; QString m_gatewayEndpoint; bool m_isDevEnvironment = false; + bool m_isStrictKillSwitchEnabled = false; }; #endif // GATEWAYCONTROLLER_H diff --git a/client/ui/controllers/api/apiConfigsController.cpp b/client/ui/controllers/api/apiConfigsController.cpp index 74e22a85..21d371bb 100644 --- a/client/ui/controllers/api/apiConfigsController.cpp +++ b/client/ui/controllers/api/apiConfigsController.cpp @@ -63,7 +63,8 @@ bool ApiConfigsController::exportNativeConfig(const QString &serverCountryCode, return false; } - GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs); + GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs, + m_settings->isStrictKillSwitchEnabled()); auto serverConfigObject = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex()); auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject(); @@ -94,7 +95,8 @@ bool ApiConfigsController::exportNativeConfig(const QString &serverCountryCode, bool ApiConfigsController::revokeNativeConfig(const QString &serverCountryCode) { - GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs); + GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs, + m_settings->isStrictKillSwitchEnabled()); auto serverConfigObject = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex()); auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject(); @@ -140,7 +142,8 @@ void ApiConfigsController::copyVpnKeyToClipboard() bool ApiConfigsController::fillAvailableServices() { - GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs); + GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs, + m_settings->isStrictKillSwitchEnabled()); QJsonObject apiPayload; apiPayload[configKey::osVersion] = QSysInfo::productType(); @@ -171,7 +174,8 @@ bool ApiConfigsController::importServiceFromGateway() return false; } - GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs); + GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs, + m_settings->isStrictKillSwitchEnabled()); auto installationUuid = m_settings->getInstallationUuid(true); auto userCountryCode = m_apiServicesModel->getCountryCode(); @@ -211,7 +215,8 @@ bool ApiConfigsController::importServiceFromGateway() bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const QString &newCountryCode, const QString &newCountryName, bool reloadServiceConfig) { - GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs); + GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs, + m_settings->isStrictKillSwitchEnabled()); auto serverConfig = m_serversModel->getServerConfig(serverIndex); auto apiConfig = serverConfig.value(configKey::apiConfig).toObject(); @@ -274,7 +279,8 @@ bool ApiConfigsController::updateServiceFromTelegram(const int serverIndex) QThread::msleep(10); #endif - GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs); + GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs, + m_settings->isStrictKillSwitchEnabled()); auto serverConfig = m_serversModel->getServerConfig(serverIndex); auto installationUuid = m_settings->getInstallationUuid(true); @@ -304,7 +310,8 @@ bool ApiConfigsController::updateServiceFromTelegram(const int serverIndex) bool ApiConfigsController::deactivateDevice() { - GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs); + GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs, + m_settings->isStrictKillSwitchEnabled()); auto serverIndex = m_serversModel->getProcessedServerIndex(); auto serverConfigObject = m_serversModel->getServerConfig(serverIndex); @@ -339,7 +346,8 @@ bool ApiConfigsController::deactivateDevice() bool ApiConfigsController::deactivateExternalDevice(const QString &uuid, const QString &serverCountryCode) { - GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs); + GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs, + m_settings->isStrictKillSwitchEnabled()); auto serverIndex = m_serversModel->getProcessedServerIndex(); auto serverConfigObject = m_serversModel->getServerConfig(serverIndex); diff --git a/client/ui/controllers/api/apiPremV1MigrationController.cpp b/client/ui/controllers/api/apiPremV1MigrationController.cpp index 0a9b6139..7b0ff100 100644 --- a/client/ui/controllers/api/apiPremV1MigrationController.cpp +++ b/client/ui/controllers/api/apiPremV1MigrationController.cpp @@ -29,7 +29,8 @@ bool ApiPremV1MigrationController::hasConfigsToMigration() vpnKeys.append(vpnKey); } - GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs); + GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs, + m_settings->isStrictKillSwitchEnabled()); QJsonObject apiPayload; apiPayload["configs"] = vpnKeys; @@ -48,7 +49,8 @@ bool ApiPremV1MigrationController::hasConfigsToMigration() void ApiPremV1MigrationController::getSubscriptionList(const QString &email) { - GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs); + GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs, + m_settings->isStrictKillSwitchEnabled()); QJsonObject apiPayload; apiPayload[apiDefs::key::email] = email; @@ -80,7 +82,8 @@ void ApiPremV1MigrationController::sendMigrationCode(const int subscriptionIndex QTimer::singleShot(1000, &wait, &QEventLoop::quit); wait.exec(); - GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs); + GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs, + m_settings->isStrictKillSwitchEnabled()); QJsonObject apiPayload; apiPayload[apiDefs::key::email] = m_email; @@ -97,7 +100,8 @@ void ApiPremV1MigrationController::sendMigrationCode(const int subscriptionIndex void ApiPremV1MigrationController::migrate(const QString &migrationCode) { - GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs); + GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs, + m_settings->isStrictKillSwitchEnabled()); QJsonObject apiPayload; apiPayload[apiDefs::key::email] = m_email; diff --git a/client/ui/controllers/api/apiSettingsController.cpp b/client/ui/controllers/api/apiSettingsController.cpp index 8927312d..b5da751d 100644 --- a/client/ui/controllers/api/apiSettingsController.cpp +++ b/client/ui/controllers/api/apiSettingsController.cpp @@ -48,7 +48,8 @@ bool ApiSettingsController::getAccountInfo(bool reload) wait.exec(); } - GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), requestTimeoutMsecs); + GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), requestTimeoutMsecs, + m_settings->isStrictKillSwitchEnabled()); auto processedIndex = m_serversModel->getProcessedServerIndex(); auto serverConfig = m_serversModel->getServerConfig(processedIndex); From e23cbe67ad361e0216520df3cc8a7ee8e69e1a2a Mon Sep 17 00:00:00 2001 From: Nethius Date: Fri, 16 May 2025 14:34:56 +0800 Subject: [PATCH 10/40] chore: added account_info request for amfree (#1586) --- client/ui/controllers/api/apiSettingsController.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/client/ui/controllers/api/apiSettingsController.cpp b/client/ui/controllers/api/apiSettingsController.cpp index b5da751d..f20f92bf 100644 --- a/client/ui/controllers/api/apiSettingsController.cpp +++ b/client/ui/controllers/api/apiSettingsController.cpp @@ -63,12 +63,10 @@ bool ApiSettingsController::getAccountInfo(bool reload) QByteArray responseBody; - if (apiUtils::isPremiumServer(serverConfig)) { - ErrorCode errorCode = gatewayController.post(QString("%1v1/account_info"), apiPayload, responseBody); - if (errorCode != ErrorCode::NoError) { - emit errorOccurred(errorCode); - return false; - } + ErrorCode errorCode = gatewayController.post(QString("%1v1/account_info"), apiPayload, responseBody); + if (errorCode != ErrorCode::NoError) { + emit errorOccurred(errorCode); + return false; } QJsonObject accountInfo = QJsonDocument::fromJson(responseBody).object(); From e16243ff551d175f2eca8adb4bc2751fb666f031 Mon Sep 17 00:00:00 2001 From: MrMirDan <58086007+MrMirDan@users.noreply.github.com> Date: Tue, 20 May 2025 05:55:24 +0300 Subject: [PATCH 11/40] chore: text translations etc (#1590) --- client/core/errorstrings.cpp | 2 +- client/translations/amneziavpn_ru_RU.ts | 1168 +++++++---------- .../Components/ApiPremV1MigrationDrawer.qml | 2 +- .../ui/qml/Pages2/PageSettingsKillSwitch.qml | 10 +- 4 files changed, 460 insertions(+), 722 deletions(-) diff --git a/client/core/errorstrings.cpp b/client/core/errorstrings.cpp index f330bc34..e141b3c7 100644 --- a/client/core/errorstrings.cpp +++ b/client/core/errorstrings.cpp @@ -74,7 +74,7 @@ QString errorString(ErrorCode code) { case (ErrorCode::ApiServicesMissingError): errorMessage = QObject::tr("Missing list of available services"); break; case (ErrorCode::ApiConfigLimitError): errorMessage = QObject::tr("The limit of allowed configurations per subscription has been exceeded"); break; case (ErrorCode::ApiNotFoundError): errorMessage = QObject::tr("Error when retrieving configuration from API"); break; - case (ErrorCode::ApiMigrationError): errorMessage = QObject::tr("A migration error occurred. Please contact our technical support"); break; + case (ErrorCode::ApiMigrationError): errorMessage = QObject::tr("A migration error has occurred. Please contact our technical support"); break; // QFile errors case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break; diff --git a/client/translations/amneziavpn_ru_RU.ts b/client/translations/amneziavpn_ru_RU.ts index e653b32b..af47ffb3 100644 --- a/client/translations/amneziavpn_ru_RU.ts +++ b/client/translations/amneziavpn_ru_RU.ts @@ -3,16 +3,60 @@ AdLabel - - Amnezia Premium - for access to any website - Amnezia Premium - для доступа к любым сайтам - Amnezia Premium - for access to all websites and online resources Amnezia Premium - доступ ко всем сайтам и онлайн ресурсам + + AllowedDnsController + + + The address does not look like a valid IP address + Адрес не похож на корректный IP-адрес + + + + New DNS server added: %1 + Добавлен новый DNS сервер: %1 + + + + DNS server already exists: %1 + DNS сервер уже существует: %1 + + + + DNS server removed: %1 + DNS сервер удален: %1 + + + + Can't open file: %1 + Невозможно открыть файл: %1 + + + + Failed to parse JSON data from file: %1 + Не удалось разобрать JSON-данные из файла: %1 + + + + The JSON data is not an array in file: %1 + JSON-данные не являются массивом в файле: %1 + + + + Import completed + Импорт завершён + + + + Export completed + Экспорт завершён + + ApiAccountInfoModel @@ -37,19 +81,9 @@ Классический VPN для комфортной работы, загрузки больших файлов и просмотра видео. Доступ ко всем сайтам и онлайн-ресурсам. Скорость — до 200 Мбит/с - + Free unlimited access to a basic set of websites such as Facebook, Instagram, Twitter (X), Discord, Telegram and more. YouTube is not included in the free plan. - Бесплатный неограниченный доступ к базовому набору сайтов и приложений, таким как Facebook, Instagram, Twitter (X), Discord, Telegram и другим. YouTube не включен в бесплатный тариф. - - - - amnezia_free_support_bot - - - - - amnezia_premium_support_bot - + Бесплатный неограниченный доступ к базовому набору сайтов и приложений, таким как Facebook, Instagram, Twitter (X), Discord, Telegram и другим. YouTube не включён в бесплатный тариф. @@ -70,43 +104,121 @@ Страна подключения изменена на %1 + + ApiPremV1MigrationDrawer + + + Switch to the new Amnezia Premium subscription + Перейдите на новый тип подписки Amnezia Premium + + + + We'll preserve all remaining days of your current subscription and give you an extra month as a thank you. + Мы сохраним все оставшиеся дни текущей подписки и подарим дополнительный месяц в благодарность за переход. + + + + This new subscription type will be actively developed with more locations and features added regularly. Currently available: + Именно новый тип подписки будет активно развиваться и пополняться новыми локациями и функциями. Уже доступны: + + + + <li>13 locations (with more coming soon)</li> + <li>13 локаций (их число будет расти)</li> + + + + <li>Easier switching between countries in the app</li> + <li>Удобное переключение между странами в приложении</li> + + + + <li>Personal dashboard to manage your subscription</li> + Личный кабинет для управления подпиской + + + + Old keys will be deactivated after switching. + После перехода старые ключи перестанут работать. + + + + Email + Email + + + + mail@example.com + + + + + No old format subscriptions for a given email + Для указанного адреса электронной почты нет подписок старого типа + + + + Enter the email you used for your current subscription + Укажите адрес почты, который использовали при заказе текущей подписки + + + + + Continue + Продолжить + + + + Remind me later + Напомнить позже + + + + Don't remind me again + Больше не напоминать + + + + No more reminders? You can always switch to the new format in the server settings + Отключить напоминания? Вы всегда сможете перейти на новый тип подписки в настройках сервера + + + + Cancel + Отменить + + + + ApiPremV1SubListDrawer + + + Choose Subscription + Выбрать подписку + + + + Order ID: + ID заказа: + + + + Purchase Date: + Дата покупки: + + ApiServicesModel - - Classic VPN for comfortable work, downloading large files and watching videos. Works for any sites. Speed up to %1 MBit/s - Классический VPN для комфортной работы, загрузки больших файлов и просмотра видео. Работает для любых сайтов. Скорость до %1 Мбит/с - - - VPN to access blocked sites in regions with high levels of Internet censorship. - VPN для доступа к заблокированным сайтам в регионах с высоким уровнем интернет-цензуры. - <p><a style="color: #EB5757;">Not available in your region. If you have VPN enabled, disable it, return to the previous screen, and try again.</a> <p><a style="color: #EB5757;">Недоступно в вашем регионе. Если у вас включен VPN, отключите его, вернитесь на предыдущий экран и попробуйте снова.</a> - - Amnezia Premium - A classic VPN for comfortable work, downloading large files, and watching videos in high resolution. It works for all websites, even in countries with the highest level of internet censorship. - Amnezia Premium — классический VPN для комфортной работы, загрузки больших файлов и просмотра видео в высоком разрешении. Работает на всех сайтах, даже в странах с самым высоким уровнем интернет-цензуры. - - - Amnezia Free is a free VPN to bypass blocking in countries with high levels of internet censorship - Amnezia Free - это бесплатный VPN для обхода блокировок в странах с высоким уровнем интернет-цензуры - - - Amnezia Premium is VPN for comfortable work, downloading large files and watching videos in 8K resolution. Works for any sites with no restrictions. Speed up to %1 MBit/s. Unlimited traffic. - Amnezia Premium — VPN для комфортной работы, скачивания больших файлов и просмотра видео в высоком разрешении. Скорость до %1 Мбит/с. Безлимитный трафик. - 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. - Amnezia Premium — VPN для комфортной работы, скачивания больших файлов и просмотра видео в высоком разрешении. Работает для любых сайтов без ограничений. - Amnezia Premium is classic VPN for seamless work, downloading large files, and watching videos. Access all websites and online resources. Speeds up to %1 Mbps. @@ -120,7 +232,7 @@ %1 MBit/s - + %1 Мбит/с @@ -181,12 +293,6 @@ ConnectionController - - VPN Protocols is not installed. - Please install VPN container at first - VPN-протоколы не установлены. - Пожалуйста, установите протокол - Connecting... @@ -212,14 +318,6 @@ Settings updated successfully Настройки успешно обновлены - - The selected protocol is not supported on the current platform - Выбранный протокол не поддерживается на данном устройстве - - - unable to create configuration - не удалось создать конфигурацию - Reconnecting... @@ -282,22 +380,14 @@ ExportController - - Access error! - Ошибка доступа! - HomeContainersListView - + Unable change protocol while there is an active connection Невозможно изменить протокол во время активного соединения - - The selected protocol is not supported on the current platform - Выбранный протокол не поддерживается на данном устройстве - HomeSplitTunnelingDrawer @@ -348,14 +438,6 @@ Can't be disabled for current server ImportController - - Unable to open file - Невозможно открыть файл - - - Invalid configuration file - Неверный файл конфигурации - Scanned %1 of %2. @@ -371,10 +453,6 @@ Can't be disabled for current server <br>In the imported configuration, potentially dangerous lines were found: <br>В импортированной конфигурации обнаружены потенциально опасные строки: - - In the imported configuration, potentially dangerous lines were found: - В импортированной конфигурации были обнаружены потенциально опасные строки: - InstallController @@ -403,62 +481,50 @@ Already installed containers were found on the server. All installed containers На сервере обнаружены установленные протоколы и сервисы. Все они были добавлены в приложение - + Settings updated successfully Настройки успешно обновлены - + Server '%1' was rebooted Сервер '%1' был перезагружен - + Server '%1' was removed Сервер '%1' был удален - + All containers from server '%1' have been removed Все протоколы и сервисы были удалены с сервера '%1' - + %1 has been removed from the server '%2' %1 был удален с сервера '%2' - + Api config removed Конфигурация API удалена - + %1 cached profile cleared %1 закэшированный профиль очищен - + Please login as the user Пожалуйста, войдите в систему от имени пользователя - + Server added successfully Сервер успешно добавлен - - %1 installed successfully. - %1 успешно установлен. - - - API config reloaded - Конфигурация API перезагружена - - - Successfully changed the country of connection to %1 - Изменение страны подключения на %1 - InstalledAppsDrawer @@ -525,6 +591,24 @@ Already installed containers were found on the server. All installed containers Обнаружена незащищенная сеть: + + OtpCodeDrawer + + + OTP code was sent to your email + Одноразовый код был отправлен на ваш email + + + + OTP Code + Одноразовый код + + + + Continue + Продолжить + + PageDeinstalling @@ -554,34 +638,47 @@ Already installed containers were found on the server. All installed containers PageHome - + + You've successfully switched to the new Amnezia Premium subscription! + Вы успешно перешли на новый тип подписки Amnezia Premium! + + + + Old keys will no longer work. Please use your new subscription key to connect. +Thank you for staying with us! + Старые ключи перестанут работать. Пожалуйста, используйте новый ключ для подключения. +Спасибо, что остаетесь с нами! + + + + Continue + Продолжить + + + Logging enabled Логирование включено - + Split tunneling enabled Раздельное туннелирование включено - + Split tunneling disabled Раздельное туннелирование выключено - + VPN protocol VPN-протокол - + Servers Серверы - - Unable change server while there is an active connection - Невозможно изменить сервер во время активного соединения - PageProtocolAwgClientSettings @@ -648,18 +745,6 @@ Already installed containers were found on the server. All installed containers Port Порт - - MTU - MTU - - - Remove AmneziaWG - Удалить AmneziaWG - - - Remove AmneziaWG from server? - Удалить AmneziaWG с сервера? - All users with whom you shared a connection with will no longer be able to connect to it. @@ -955,26 +1040,6 @@ Already installed containers were found on the server. All installed containers Unable change settings while there is an active connection Невозможно изменить настройки во время активного соединения - - Remove OpenVPN - Удалить OpenVPN - - - Remove OpenVPN from server? - Удалить OpenVPN с сервера? - - - All users with whom you shared a connection with will no longer be able to connect to it. - Все пользователи, с которыми вы поделились конфигурацией вашего VPN, больше не смогут к нему подключаться. - - - Continue - Продолжить - - - Cancel - Отменить - Save @@ -1137,27 +1202,11 @@ Already installed containers were found on the server. All installed containers All users with whom you shared a connection with will no longer be able to connect to it. Все пользователи, с которыми вы поделились конфигурацией вашего VPN, больше не смогут к нему подключаться. - - MTU - MTU - Unable change settings while there is an active connection Невозможно изменить настройки во время активного соединения - - Remove WG - Удалить WG - - - Remove WG from server? - Удалить WG с сервера? - - - All users with whom you shared a connection will no longer be able to connect to it. - Все пользователи, с которыми вы поделились конфигурацией вашего VPN, больше не смогут к нему подключаться. - Continue @@ -1187,12 +1236,17 @@ Already installed containers were found on the server. All installed containers Замаскировать трафик под - + + Port + Порт + + + Save Сохранить - + Unable change settings while there is an active connection Невозможно изменить настройки во время активного соединения @@ -1311,22 +1365,6 @@ Already installed containers were found on the server. All installed containers Detailed instructions Подробные инструкции - - Remove SFTP and all data stored there - Удалить SFTP-хранилище со всеми данными - - - Remove SFTP and all data stored there? - Удалить SFTP-хранилище и все хранящиеся там данные? - - - Continue - Продолжить - - - Cancel - Отменить - PageServiceSocksProxySettings @@ -1435,22 +1473,6 @@ Already installed containers were found on the server. All installed containers When configuring WordPress set the this onion address as domain. При настройке WordPress укажите этот onion-адрес в качестве домена. - - Remove website - Удалить сайт - - - The site with all data will be removed from the tor network. - Сайт со всеми данными будет удален из сети Tor. - - - Continue - Продолжить - - - Cancel - Отменить - PageSettings @@ -1536,19 +1558,11 @@ Already installed containers were found on the server. All installed containers support@amnezia.org support@amnezia.org - - Mail - Почта - For reviews and bug reports Для отзывов и сообщений об ошибках - - Copied - Скопировано - mailto:support@amnezia.org @@ -1579,10 +1593,6 @@ Already installed containers were found on the server. All installed containers Visit official website Посетить официальный сайт - - https://amnezia.org - https://amnezia.org - Software version: %1 @@ -1614,10 +1624,6 @@ Already installed containers were found on the server. All installed containers PageSettingsApiDevices - - Active devices - Активные устройства - Active Devices @@ -1663,10 +1669,6 @@ 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. - Это устройство будет отвязано от вашей подписки. Вы можете подключить его снова в любой момент, нажав кнопку "Подключиться". - Continue @@ -1763,10 +1765,6 @@ Already installed containers were found on the server. All installed containers PageSettingsApiLanguageList - - Unable change server location while there is an active connection - Невозможно изменить локацию во время активного соединения - PageSettingsApiNativeConfigs @@ -1775,10 +1773,6 @@ Already installed containers were found on the server. All installed containers Save AmneziaVPN config Сохранить конфигурацию AmneziaVPN - - Configuration files - Файл конфигурации - Configuration Files @@ -1857,74 +1851,26 @@ Already installed containers were found on the server. All installed containers PageSettingsApiServerInfo - - For the region - Для региона - - - Price - Цена - - - Work period - Период работы - - - Valid until - Действует до - - - Speed - Скорость - - - Copied - Скопировано - - - Subscription status - Статус подписки - - - Active connections - Активные соединения - Configurations have been updated for some countries. Download and install the updated configuration files Сетевые адреса одного или нескольких серверов были обновлены. Пожалуйста, удалите старые конфигурацию и загрузите новые файлы - - Subscription key - Ключ для подключения - Amnezia Premium subscription key Ключ подписки Amnezia Premium - - Save VPN key to file - Сохранить VPN-ключ в файле - Copy VPN key Скопировать VPN ключ - - Configuration files - Файл конфигурации - Manage configuration files Управление файлами конфигурации - - Active devices - Активные устройства - Subscription Status @@ -2019,10 +1965,6 @@ 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. - Это устройство будет отвязано от вашей подписки. Вы можете подключить его снова в любой момент, нажав кнопку Подключиться. - Cannot unlink device during active connection @@ -2051,40 +1993,21 @@ Already installed containers were found on the server. All installed containers Telegram - - Email Support - Email - Email Email - - - support@amnezia.org - - Email Billing & Orders По вопросам оплаты - - - help@vpnpay.io - - Website Сайт - - - amnezia.org - amnezia.org - Support @@ -2129,37 +2052,37 @@ Already installed containers were found on the server. All installed containers Раздельное туннелирование приложений - + Mode Режим - + Remove Удалить - + Continue Продолжить - + Cancel Отменить - + application name название приложения - + Open executable file Открыть исполняемый файл - + Executable files (*.*) Исполняемые файлы (*.*) @@ -2269,10 +2192,6 @@ Already installed containers were found on the server. All installed containers PageSettingsBackup - - Backup - Резервное копирование - Settings restored from backup file @@ -2283,10 +2202,6 @@ Already installed containers were found on the server. All installed containers Back up your configuration Создать резервную копию конфигурации - - Configuration backup - Резервная копия конфигурации - You can save your settings to a backup file to restore them the next time you install the application. @@ -2361,14 +2276,6 @@ Already installed containers were found on the server. All installed containers Connection Соединение - - Auto connect - Автоподключение - - - Connect to VPN on app start - Подключение к VPN при запуске приложения - Use AmneziaDNS @@ -2390,28 +2297,19 @@ Already installed containers were found on the server. All installed containers Когда AmneziaDNS не используется или не установлен - + Allows you to use the VPN only for certain Apps Позволяет использовать VPN только для определенных приложений - + KillSwitch KillSwitch - - Disables your internet if your encrypted VPN connection drops out for any reason. - Отключает ваше интернет-соединение, если ваше зашифрованное VPN-соединение по какой-либо причине прерывается. - - - - Cannot change KillSwitch settings during active connection - Невозможно изменить настройки KillSwitch во время активного подключения - - - Cannot change killSwitch settings during active connection - Невозможно изменить настройки аварийного выключателя во время активного соединения + + Blocks network connections without VPN + Блокирует интернет-соединение без VPN @@ -2424,14 +2322,10 @@ Already installed containers were found on the server. All installed containers Позволяет выбирать, к каким сайтам подключаться через VPN - + App-based split tunneling Раздельное туннелирование приложений - - Allows you to use the VPN only for certain applications - Позволяет использовать VPN только для определённых приложений - PageSettingsDns @@ -2445,10 +2339,6 @@ Already installed containers were found on the server. All installed containers DNS servers DNS-серверы - - When AmneziaDNS is not used or installed - Когда AmneziaDNS не используется или не установлен - If AmneziaDNS is not used or installed @@ -2487,7 +2377,7 @@ Already installed containers were found on the server. All installed containers Settings have been reset - Настройки сброшены + Настройки были сброшены @@ -2501,11 +2391,156 @@ Already installed containers were found on the server. All installed containers - PageSettingsLogging + PageSettingsKillSwitch - Logging is enabled. Note that logs will be automatically disabled after 14 days, and all log files will be deleted. - Логирование включено. Обратите внимание, что логирование будет автоматически отключено через 14 дней, и все логи будут удалены. + + KillSwitch + KillSwitch + + + Enable to ensure network traffic goes through a secure VPN tunnel, preventing accidental exposure of your IP and DNS queries if the connection drops + Включите, чтобы весь сетевой трафик проходил только через безопасный VPN-туннель. Это предотвратит случайное раскрытие вашего IP-адреса и DNS-запросов при разрыве соединения + + + + KillSwitch settings cannot be changed during an active connection + Настройки KillSwitch нельзя изменить во время активного подключения + + + + Soft KillSwitch + Soft KillSwitch + + + + Internet access is blocked if the VPN disconnects unexpectedly + Доступ в интернет блокируется при разрыве VPN-соединения + + + + Strict KillSwitch + Strict KillSwitch + + + + Internet connection is blocked even when VPN is turned off manually or hasn't started + Доступ в интернет блокируется, даже если VPN отключен вручную или не был запущен + + + + Just a little heads-up + Небольшое предупреждение + + + + If the VPN disconnects or drops while Strict KillSwitch is enabled, internet access will be blocked. To restore access, reconnect VPN or disable/change the KillSwitch. + Если VPN отключится или соединение прервётся при включённом Strict KillSwitch, доступ в интернет будет заблокирован. Чтобы восстановить доступ, снова подключитесь к VPN или отключите (измените) режим KillSwitch. + + + + Continue + Продолжить + + + + Cancel + Отменить + + + + DNS Exceptions + Исключения для DNS + + + + DNS servers listed here will remain accessible when KillSwitch is active. + DNS-серверы из этого списка останутся доступными при активном KillSwitch. + + + + PageSettingsKillSwitchExceptions + + + DNS Exceptions + Исключения для DNS + + + + DNS servers listed here will be excluded from KillSwitch restrictions and remain accessible when KillSwitch is active. + Перечисленные DNS-серверы будут исключены из ограничений KillSwitch и останутся доступными при активном KillSwitch. + + + + Delete + Удалить + + + + Continue + Продолжить + + + + Cancel + Отменить + + + + IPv4 address + IPv4 адрес + + + + Import / Export addresses + Импорт / Экспорт адресов + + + + Import + Импорт + + + + Save address list + Сохранить список адресов + + + + Save addresses + Сохранить адреса + + + + + + Address files (*.json) + Файлы адресов (*.json) + + + + Import address list + Импорт списка адресов + + + + Replace address list + Заменить список адресов + + + + + Open address file + Открыть файл адресов + + + + Add imported addresses to existing ones + Добавить импортированные адреса к существующим + + + + PageSettingsLogging Logging @@ -2516,14 +2551,6 @@ Already installed containers were found on the server. All installed containers Enabling this function will save application's logs automatically. By default, logging functionality is disabled. Enable log saving in case of application malfunction. Включение этой функции позволяет сохранять логи на вашем устройстве. По умолчанию она отключена. Включите сохранение логов в случае сбоев в работе приложения. - - Save logs - Сохранять логи - - - Open folder with logs - Открыть папку с логами - @@ -2542,10 +2569,6 @@ Already installed containers were found on the server. All installed containers Logs file saved Файл с логами сохранен - - Save logs to file - Сохранить логи в файл - Enable logs @@ -2614,18 +2637,6 @@ Already installed containers were found on the server. All installed containers All installed containers have been added to the application Все установленные протоколы и сервисы были добавлены в приложение - - Clear Amnezia cache - Очистить кэш Amnezia - - - May be needed when changing other settings - Может понадобиться при изменении других настроек - - - Clear cached profiles? - Удалить кэш Amnezia? - No new installed containers found @@ -2717,15 +2728,16 @@ Already installed containers were found on the server. All installed containers Cannot reset API config during active connection Невозможно сбросить конфигурацию API во время активного соединения + + + Switch to the new Amnezia Premium subscription + Перейти на новый тип подписки Amnezia Premium + Remove server from application Удалить сервер из приложения - - Remove server? - Удалить сервер? - All installed AmneziaVPN services will still remain on the server. @@ -2736,29 +2748,9 @@ Already installed containers were found on the server. All installed containers Clear server from Amnezia software Очистить сервер от протоколов и сервисов Amnezia - - Clear server from Amnezia software? - Удалить все сервисы и протоколы Amnezia с сервера? - - - All containers will be deleted on the server. This means that configuration files, keys and certificates will be deleted. - На сервере будут удалены все данные, связанные с Amnezia: протоколы, сервисы, конфигурационные файлы, ключи и сертификаты. - PageSettingsServerInfo - - Subscription is valid until - Подписка заканчивается через - - - Server name - Имя сервера - - - Save - Сохранить - Protocols @@ -2786,10 +2778,6 @@ Already installed containers were found on the server. All installed containers settings настройки - - Clear %1 profile - Очистить профиль %1 - Clear %1 profile? @@ -2845,10 +2833,6 @@ Already installed containers were found on the server. All installed containers Cannot remove active container Невозможно удалить активный контейнер - - All users who you shared a connection with will no longer be able to connect to it. - Все пользователи, с которыми вы поделились VPN, больше не смогут к нему подключаться. - @@ -2887,36 +2871,32 @@ Already installed containers were found on the server. All installed containers Адреса из списка не должны открываться через VPN - + Split tunneling Раздельное туннелирование сайтов - + Mode Режим - + Remove Удалить - + Continue Продолжить - + Cancel Отменить - Website or IP - Сайт или IP - - - + Import / Export Sites Импорт/экспорт сайтов @@ -2931,50 +2911,50 @@ Already installed containers were found on the server. All installed containers Невозможно изменить настройки раздельного туннелирования во время активного соединения - + website or IP веб-сайт или IP - + Import Импорт - + Save site list Сохранить список сайтов - + Save sites Сохранить сайты - - - + + + Sites files (*.json) Файлы сайтов (*.json) - + Import a list of sites Импортировать список с сайтами - + Replace site list Заменить список с сайтами - - + + Open sites file Открыть список с сайтами - + Add imported sites to existing ones Добавить импортированные сайты к существующим @@ -3027,35 +3007,11 @@ Already installed containers were found on the server. All installed containers PageSetupWizardConfigSource - - Server connection - Подключение к серверу - - - Do not use connection code from public sources. It may have been created to intercept your data. - -It's okay as long as it's from someone you trust. - Не используйте код подключения из публичных источников. Его могли создать, чтобы перехватить ваши данные. - -Всё в порядке, если кодом поделился пользователь, которому вы доверяете. - - - Do not use connection codes from untrusted sources, as they may be created to intercept your data. - Не используйте коды подключения из ненадежных источников, так как они могут быть созданы для перехвата ваших данных. - - - What do you have? - Что у вас есть? - File with connection settings Файл с настройками подключения - - File with connection settings or backup - Файл с настройками подключения или резервной копией - Connection @@ -3074,22 +3030,22 @@ It's okay as long as it's from someone you trust. Export client logs - + Экспорт логов клиента Save - Сохранить + Сохранить Logs files (*.log) - Файлы логов (*.log) + Файлы логов (*.log) Logs file saved - Файл с логами сохранен + Файл с логами сохранен @@ -3189,49 +3145,24 @@ It's okay as long as it's from someone you trust. I have nothing У меня ничего нет - - Key as text - Ключ в виде текста - PageSetupWizardCredentials - - Server connection - Подключение к серверу - Server IP address [:port] IP-адрес[:порт] сервера - - 255.255.255.255:88 - 255.255.255.255:88 - - - Password / SSH private key - Password / SSH private key - Continue Продолжить - - All data you enter will remain strictly confidential -and will not be shared or disclosed to the Amnezia or any third parties - Все данные, которые вы вводите, останутся строго конфиденциальными и не будут переданы или раскрыты Amnezia или каким-либо третьим лицам - Enter the address in the format 255.255.255.255:88 Введите адрес в формате 255.255.255.255:88 - - Login to connect via SSH - Login to connect via SSH - Configure your server @@ -3285,10 +3216,6 @@ and will not be shared or disclosed to the Amnezia or any third parties PageSetupWizardEasy - - What is the level of internet control in your region? - Какой уровень контроля над интернетом в вашем регионе? - Choose Installation Type @@ -3309,23 +3236,11 @@ and will not be shared or disclosed to the Amnezia or any third parties Skip setup Пропустить настройку - - Set up a VPN yourself - Настроить VPN самостоятельно - - - I want to choose a VPN protocol - Выбрать VPN-протокол - Continue Продолжить - - Set up later - Настроить позднее - PageSetupWizardInstalling @@ -3427,30 +3342,6 @@ and will not be shared or disclosed to the Amnezia or any third parties PageSetupWizardStart - - Settings restored from backup file - Настройки восстановлены из резервной копии - - - Free service for creating a personal VPN on your server. - Простое и бесплатное приложение для запуска собственного VPN на своем сервере. - - - Helps you access blocked content without revealing your privacy, even to VPN providers. - Помогает получить доступ к заблокированному контенту, не раскрывая вашу конфиденциальность даже провайдерам VPN. - - - I have the data to connect - У меня есть данные для подключения - - - I have nothing - У меня ничего нет - - - https://amnezia.org/instructions/0_starter-guide - https://amnezia.org/ru/starter-guide - Let's get started @@ -3492,10 +3383,6 @@ and will not be shared or disclosed to the Amnezia or any third parties New connection Новое соединение - - Do not use connection code from public sources. It could be created to intercept your data. - Не используйте код подключения из публичных источников. Его могли создать, чтобы перехватить ваши данные. - Collapse content @@ -3534,33 +3421,17 @@ and will not be shared or disclosed to the Amnezia or any third parties WireGuard native format Оригинальный формат WireGuard - - VPN Access - VPN-Доступ - Connection Соединение - - VPN access without the ability to manage the server - Доступ к VPN без возможности управления сервером - - - Access to server management. The user with whom you share full access to the connection will be able to add and remove your protocols and services to the server, as well as change settings. - Доступ к управлению сервером. Пользователь, с которым вы делитесь полным доступом к соединению, сможет добавлять и удалять ваши протоколы и службы на сервере, а также изменять настройки. - Server Сервер - - Accessing - Доступ - Config revoked @@ -3687,10 +3558,6 @@ and will not be shared or disclosed to the Amnezia or any third parties Allowed IPs: %1 Разрешенные подсети: %1 - - Creation date: - Дата создания: - Rename @@ -3731,10 +3598,6 @@ and will not be shared or disclosed to the Amnezia or any third parties Cancel Отменить - - Full access - Полный доступ - Share VPN access without the ability to manage the server @@ -4083,10 +3946,6 @@ and will not be shared or disclosed to the Amnezia or any third parties No error Нет ошибки - - Unknown Error - Неизвестная ошибка - Function not implemented @@ -4158,251 +4017,210 @@ and will not be shared or disclosed to the Amnezia or any third parties Требуется пароль пользователя - + + Docker error: runc doesn't work on cgroups v2 + Docker error: runc не работает на cgroups v2 + + + + Server error: cgroup mountpoint does not exist + Server error: cgroup mountpoint не существует + + + SSH request was denied SSH-запрос был отклонён - + SSH request was interrupted SSH-запрос был прерван - + SSH internal error Внутренняя ошибка SSH - + Invalid private key or invalid passphrase entered Введен неверный закрытый ключ или неверная парольная фраза - + The selected private key format is not supported, use openssh ED25519 key types or PEM key types Выбранный формат закрытого ключа не поддерживается, используйте типы ключей openssh ED25519 или PEM - + Timeout connecting to server Тайм-аут подключения к серверу - + SCP error: Generic failure Ошибка SCP: общий сбой - Sftp error: End-of-file encountered - Sftp error: End-of-file encountered - - - Sftp error: File does not exist - Sftp error: File does not exist - - - Sftp error: Permission denied - Sftp error: Permission denied - - - Sftp error: Generic failure - Sftp error: Generic failure - - - Sftp error: Garbage received from server - Sftp error: Garbage received from server - - - Sftp error: No connection has been set up - Sftp error: No connection has been set up - - - Sftp error: There was a connection, but we lost it - Sftp error: There was a connection, but we lost it - - - Sftp error: Operation not supported by libssh yet - Sftp error: Operation not supported by libssh yet - - - Sftp error: Invalid file handle - Sftp error: Invalid file handle - - - Sftp error: No such file or directory path exists - Sftp error: No such file or directory path exists - - - Sftp error: An attempt to create an already existing file or directory has been made - Sftp error: An attempt to create an already existing file or directory has been made - - - Sftp error: Write-protected filesystem - Sftp error: Write-protected filesystem - - - Sftp error: No media was in remote drive - Sftp error: No media was in remote drive - - - + The config does not contain any containers and credentials for connecting to the server Конфигурация не содержит каких-либо контейнеров и учетных данных для подключения к серверу - - + + Error when retrieving configuration from API Ошибка при получении конфигурации из API - + This config has already been added to the application Данная конфигурация уже была добавлена в приложение - + ErrorCode: %1. Код ошибки: %1. - Failed to save config to disk - Failed to save config to disk - - - + OpenVPN config missing Отсутствует конфигурация OpenVPN - + OpenVPN management server error Серверная ошибка управлением OpenVPN - + OpenVPN executable missing Отсутствует исполняемый файл OpenVPN - + Shadowsocks (ss-local) executable missing Отсутствует исполняемый файл Shadowsocks (ss-local) - + Cloak (ck-client) executable missing Отсутствует исполняемый файл Cloak (ck-client) - + Amnezia helper service error Ошибка вспомогательной службы Amnezia - + OpenSSL failed Ошибка OpenSSL - + Can't connect: another VPN connection is active Невозможно подключиться: активно другое VPN-соединение - + Can't setup OpenVPN TAP network adapter Невозможно настроить сетевой адаптер OpenVPN TAP - + VPN pool error: no available addresses Ошибка пула VPN: нет доступных адресов - + Unable to open config file Не удалось открыть файл конфигурации - + VPN Protocols is not installed. Please install VPN container at first VPN-протоколы не установлены. Пожалуйста, установите протокол - + VPN connection error Ошибка VPN-соединения - + In the response from the server, an empty config was received В ответе от сервера была получена пустая конфигурация - + SSL error occurred Произошла ошибка SSL - + Server response timeout on api request Тайм-аут ответа сервера на запрос API - + Missing AGW public key Отсутствует публичный ключ AGW - + Failed to decrypt response payload - + Missing list of available services Отсутствует список доступных сервисов - + The limit of allowed configurations per subscription has been exceeded Превышен лимит разрешенных конфигураций для одной подписки + A migration has error occurred. Please contact our technical support + Произошла ошибка миграции. Обратитесь в нашу техническую поддержку + + + QFile error: The file could not be opened Ошибка QFile: не удалось открыть файл - + QFile error: An error occurred when reading from the file Ошибка QFile: произошла ошибка при чтении из файла - + QFile error: The file could not be accessed Ошибка QFile: не удалось получить доступ к файлу - + QFile error: An unspecified error occurred Ошибка QFile: произошла неизвестная ошибка - + QFile error: A fatal error occurred Ошибка QFile: произошла фатальная ошибка - + QFile error: The operation was aborted Ошибка QFile: операция была прервана - + Internal error Внутренняя ошибка @@ -4411,14 +4229,6 @@ and will not be shared or disclosed to the Amnezia or any third parties IPsec IPsec - - Shadowsocks - masks VPN traffic, making it similar to normal web traffic, but it may be recognized by analysis systems in some highly censored regions. - Shadowsocks маскирует VPN-трафик под обычный веб-трафик, но распознается системами анализа в некоторых регионах с высоким уровнем цензуры. - - - OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against active-probing detection. Ideal for bypassing blocking in regions with the highest levels of censorship. - OpenVPN over Cloak — это OpenVPN с маскировкой VPN-трафика под обычный веб-трафик и защитой от обнаружения активным зондированием. Подходит для регионов с самым высоким уровнем цензуры. - IKEv2/IPsec - Modern stable protocol, a bit faster than others, restores connection after signal loss. It has native support on the latest versions of Android and iOS. @@ -4665,22 +4475,6 @@ Features: * Незаметен для систем анализа трафика (DPI) * Работает по протоколу UDP - - WireGuard - New popular VPN protocol with high performance, high speed and low power consumption. Recommended for regions with low levels of censorship. - WireGuard — новый популярный VPN-протокол с высокой производительностью, высокой скоростью и низким энергопотреблением. Рекомендуется для регионов с низким уровнем цензуры. - - - AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, but very resistant to blockages. Recommended for regions with high levels of censorship. - AmneziaWG — специальный протокол от Amnezia, основанный на протоколе WireGuard. Он такой же быстрый, как WireGuard, но очень устойчив к блокировкам. Рекомендуется для регионов с высоким уровнем цензуры. - - - XRay with REALITY - Suitable for countries with the highest level of internet censorship. Traffic masking as web traffic at the TLS level, and protection against detection by active probing methods. - XRay with REALITY подойдет для стран с самым высоким уровнем цензуры. Маскировка трафика под веб-трафик на уровне TLS и защита от обнаружения методами активного зондирования. - - - IKEv2/IPsec - Modern stable protocol, a bit faster than others, restores connection after signal loss. - IKEv2/IPsec — современный стабильный протокол, немного быстрее других, восстанавливает соединение после потери сигнала. - Deploy a WordPress site on the Tor network in two clicks. @@ -4710,18 +4504,6 @@ For more detailed information, you can Более подробную информацию вы можете найти в разделе поддержки "Создание файлового хранилища SFTP." - - AmneziaWG container - AmneziaWG протокол - - - Sftp file sharing service - is secure FTP service - Файловое хранилище для безопасного хранения данных - - - Sftp service - SFTP-сервис - Entry not found @@ -4977,10 +4759,6 @@ For more detailed information, you can All settings have been reset to default values Все настройки сброшены до значений по умолчанию - - Cached profiles cleared - Закэшированные профили очищены - Backup file is corrupted @@ -5114,7 +4892,7 @@ For more detailed information, you can VpnConnection - + Mbps Мбит/с @@ -5164,46 +4942,6 @@ For more detailed information, you can amnezia::ContainerProps - - Low - Низкий - - - High - Высокий - - - Extreme - Экстремальный - - - I just want to increase the level of my privacy. - Я просто хочу повысить уровень своей приватности. - - - I want to bypass censorship. This option recommended in most cases. - Я хочу обойти блокировки. Этот вариант рекомендуется в большинстве случаев. - - - Most VPN protocols are blocked. Recommended if other options are not working. - Большинство VPN-протоколов заблокированы. Рекомендуется, если другие варианты не работают. - - - Medium - Средний - - - Many foreign websites and VPN providers are blocked - Многие иностранные сайты и VPN-провайдеры заблокированы - - - Some foreign sites are blocked, but VPN providers are not blocked - Некоторые иностранные сайты заблокированы, но VPN-провайдеры не блокируются - - - I just want to increase the level of privacy - Хочу просто повысить уровень приватности - Automatic diff --git a/client/ui/qml/Components/ApiPremV1MigrationDrawer.qml b/client/ui/qml/Components/ApiPremV1MigrationDrawer.qml index 113ec1f6..21aa78a0 100644 --- a/client/ui/qml/Components/ApiPremV1MigrationDrawer.qml +++ b/client/ui/qml/Components/ApiPremV1MigrationDrawer.qml @@ -73,7 +73,7 @@ DrawerType2 { var str = qsTr("We'll preserve all remaining days of your current subscription and give you an extra month as a thank you. ") str += qsTr("This new subscription type will be actively developed with more locations and features added regularly. Currently available:") str += "
    " - str += qsTr("
  • 9 locations (with more coming soon)
  • ") + str += qsTr("
  • 13 locations (with more coming soon)
  • ") str += qsTr("
  • Easier switching between countries in the app
  • ") str += qsTr("
  • Personal dashboard to manage your subscription
  • ") str += "
" diff --git a/client/ui/qml/Pages2/PageSettingsKillSwitch.qml b/client/ui/qml/Pages2/PageSettingsKillSwitch.qml index f6e0f5d7..444eb415 100644 --- a/client/ui/qml/Pages2/PageSettingsKillSwitch.qml +++ b/client/ui/qml/Pages2/PageSettingsKillSwitch.qml @@ -49,7 +49,7 @@ PageType { if (!ConnectionController.isConnected) { SettingsController.isKillSwitchEnabled = checked } else { - PageController.showNotificationMessage(qsTr("Cannot change killSwitch settings during active connection")) + PageController.showNotificationMessage(qsTr("KillSwitch settings cannot be changed during an active connection")) switcher.checked = SettingsController.isKillSwitchEnabled } } @@ -66,7 +66,7 @@ PageType { checked: !SettingsController.strictKillSwitchEnabled text: qsTr("Soft KillSwitch") - descriptionText: qsTr("Internet connection is blocked if VPN connection drops accidentally") + descriptionText: qsTr("Internet access is blocked if the VPN disconnects unexpectedly") onClicked: function() { SettingsController.strictKillSwitchEnabled = false @@ -85,11 +85,11 @@ PageType { checked: SettingsController.strictKillSwitchEnabled text: qsTr("Strict KillSwitch") - descriptionText: qsTr("Internet connection is blocked even if VPN was turned off manually or not started") + descriptionText: qsTr("Internet connection is blocked even when VPN is turned off manually or hasn't 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 descriptionText = qsTr("If the VPN disconnects or drops while Strict KillSwitch is enabled, internet access will be blocked. To restore access, reconnect VPN or disable/change the KillSwitch.") var yesButtonText = qsTr("Continue") var noButtonText = qsTr("Cancel") @@ -111,7 +111,7 @@ PageType { enabled: true text: qsTr("DNS Exceptions") - descriptionText: qsTr("DNS servers from the list will remain accessible when Kill Switch is triggered") + descriptionText: qsTr("DNS servers listed here will remain accessible when KillSwitch is active.") rightImageSource: "qrc:/images/controls/chevron-right.svg" clickedFunction: function() { From df7bf204eac5576e03074b0f45dbfc9cb2c26534 Mon Sep 17 00:00:00 2001 From: Nethius Date: Tue, 20 May 2025 12:58:57 +0800 Subject: [PATCH 12/40] chore: minor ui changes (#1597) --- .gitignore | 6 ++- client/translations/amneziavpn_ru_RU.ts | 40 +++++++++++-------- .../ui/qml/Pages2/PageSettingsApiSupport.qml | 1 + .../PageSettingsKillSwitchExceptions.qml | 2 +- 4 files changed, 30 insertions(+), 19 deletions(-) diff --git a/.gitignore b/.gitignore index 5b90fd55..503adc2d 100644 --- a/.gitignore +++ b/.gitignore @@ -133,4 +133,8 @@ client/3rd/ShadowSocks/ss_ios.xcconfig out/ # CMake files -CMakeFiles/ \ No newline at end of file +CMakeFiles/ + +ios-ne-build.sh +macos-ne-build.sh +macos-signed-build.sh diff --git a/client/translations/amneziavpn_ru_RU.ts b/client/translations/amneziavpn_ru_RU.ts index af47ffb3..833db3d9 100644 --- a/client/translations/amneziavpn_ru_RU.ts +++ b/client/translations/amneziavpn_ru_RU.ts @@ -89,17 +89,17 @@ ApiConfigsController - + %1 installed successfully. %1 успешно установлен. - + API config reloaded Конфигурация API перезагружена - + Successfully changed the country of connection to %1 Страна подключения изменена на %1 @@ -149,7 +149,7 @@ mail@example.com - + mail@example.com @@ -242,7 +242,7 @@ - + @@ -378,9 +378,6 @@ Выбрать всё - - ExportController - HomeContainersListView @@ -1763,9 +1760,6 @@ Thank you for staying with us! Инструкции по настройке - - PageSettingsApiLanguageList - PageSettingsApiNativeConfigs @@ -2424,7 +2418,7 @@ Thank you for staying with us! - Internet connection is blocked even when VPN is turned off manually or hasn't started + Internet connection is blocked even when VPN is turned off manually or hasn't started Доступ в интернет блокируется, даже если VPN отключен вручную или не был запущен @@ -2466,9 +2460,17 @@ Thank you for staying with us! Исключения для DNS - DNS servers listed here will be excluded from KillSwitch restrictions and remain accessible when KillSwitch is active. - Перечисленные DNS-серверы будут исключены из ограничений KillSwitch и останутся доступными при активном KillSwitch. + Перечисленные DNS-серверы будут исключены из ограничений KillSwitch и останутся доступными при активном KillSwitch. + + + DNS servers from the list will remain accessible when KillSwitch is triggered + DNS-серверы из этого списка будут исключены из ограничений KillSwitch и останутся доступными при активном KillSwitch. + + + + DNS servers listed here will remain accessible when KillSwitch is active + DNS-серверы из этого списка останутся доступными при активном KillSwitch @@ -4077,6 +4079,11 @@ Thank you for staying with us! This config has already been added to the application Данная конфигурация уже была добавлена в приложение + + + A migration error has occurred. Please contact our technical support + Произошла ошибка миграции. Обратитесь в нашу техническую поддержку + ErrorCode: %1. @@ -4185,9 +4192,8 @@ Thank you for staying with us! Превышен лимит разрешенных конфигураций для одной подписки - A migration has error occurred. Please contact our technical support - Произошла ошибка миграции. Обратитесь в нашу техническую поддержку + Произошла ошибка миграции. Обратитесь в нашу техническую поддержку @@ -4342,7 +4348,7 @@ REALITY распознаёт системы блокировки во время - + diff --git a/client/ui/qml/Pages2/PageSettingsApiSupport.qml b/client/ui/qml/Pages2/PageSettingsApiSupport.qml index af629ebe..e10eb325 100644 --- a/client/ui/qml/Pages2/PageSettingsApiSupport.qml +++ b/client/ui/qml/Pages2/PageSettingsApiSupport.qml @@ -88,6 +88,7 @@ PageType { LabelWithButtonType { Layout.fillWidth: true + visible: link !== "" text: title descriptionText: description rightImageSource: "qrc:/images/controls/external-link.svg" diff --git a/client/ui/qml/Pages2/PageSettingsKillSwitchExceptions.qml b/client/ui/qml/Pages2/PageSettingsKillSwitchExceptions.qml index d442b60c..31d1721b 100644 --- a/client/ui/qml/Pages2/PageSettingsKillSwitchExceptions.qml +++ b/client/ui/qml/Pages2/PageSettingsKillSwitchExceptions.qml @@ -43,7 +43,7 @@ PageType { Layout.leftMargin: 16 headerText: qsTr("DNS Exceptions") - descriptionText: qsTr("DNS servers from the list will remain accessible when Kill Switch is triggered") + descriptionText: qsTr("DNS servers listed here will remain accessible when KillSwitch is active") } } From a3e73797c29d0901f387fe930bc4e1f96dc11ced Mon Sep 17 00:00:00 2001 From: Nethius Date: Tue, 20 May 2025 13:02:37 +0800 Subject: [PATCH 13/40] chore: bump version (#1598) --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0896973e..f0e1ca99 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 4.8.6.0 +project(${PROJECT} VERSION 4.8.7.0 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) @@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d") set(RELEASE_DATE "${CURRENT_DATE}") set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) -set(APP_ANDROID_VERSION_CODE 2083) +set(APP_ANDROID_VERSION_CODE 2084) if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") set(MZ_PLATFORM_NAME "linux") From a6508e642a3cc222589ec63688b936f76e4c447f Mon Sep 17 00:00:00 2001 From: Nethius Date: Tue, 20 May 2025 13:08:05 +0800 Subject: [PATCH 14/40] bugfix: fixed sending requests if there are no premium v1 keys in the application (#1599) --- .../api/apiPremV1MigrationController.cpp | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/client/ui/controllers/api/apiPremV1MigrationController.cpp b/client/ui/controllers/api/apiPremV1MigrationController.cpp index 7b0ff100..77352003 100644 --- a/client/ui/controllers/api/apiPremV1MigrationController.cpp +++ b/client/ui/controllers/api/apiPremV1MigrationController.cpp @@ -29,18 +29,20 @@ bool ApiPremV1MigrationController::hasConfigsToMigration() vpnKeys.append(vpnKey); } - GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs, - m_settings->isStrictKillSwitchEnabled()); - QJsonObject apiPayload; + if (!vpnKeys.isEmpty()) { + GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs, + m_settings->isStrictKillSwitchEnabled()); + QJsonObject apiPayload; - apiPayload["configs"] = vpnKeys; - QByteArray responseBody; - ErrorCode errorCode = gatewayController.post(QString("%1v1/prem-v1/is-active-subscription"), apiPayload, responseBody); + apiPayload["configs"] = vpnKeys; + QByteArray responseBody; + ErrorCode errorCode = gatewayController.post(QString("%1v1/prem-v1/is-active-subscription"), apiPayload, responseBody); - auto migrationsStatus = QJsonDocument::fromJson(responseBody).object(); - for (const auto &migrationStatus : migrationsStatus) { - if (migrationStatus == "not_found") { - return true; + auto migrationsStatus = QJsonDocument::fromJson(responseBody).object(); + for (const auto &migrationStatus : migrationsStatus) { + if (migrationStatus == "not_found") { + return true; + } } } From c2f9340db6623543cac2f567ea620ef9f303c18c Mon Sep 17 00:00:00 2001 From: Nethius Date: Wed, 21 May 2025 20:05:08 +0800 Subject: [PATCH 15/40] chore/ru translation (#1606) * chore: fix ru translation * chore: bump version --- CMakeLists.txt | 4 ++-- client/translations/amneziavpn_ru_RU.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f0e1ca99..dc76a6c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 4.8.7.0 +project(${PROJECT} VERSION 4.8.7.1 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) @@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d") set(RELEASE_DATE "${CURRENT_DATE}") set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) -set(APP_ANDROID_VERSION_CODE 2084) +set(APP_ANDROID_VERSION_CODE 2085) if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") set(MZ_PLATFORM_NAME "linux") diff --git a/client/translations/amneziavpn_ru_RU.ts b/client/translations/amneziavpn_ru_RU.ts index 833db3d9..14de59d1 100644 --- a/client/translations/amneziavpn_ru_RU.ts +++ b/client/translations/amneziavpn_ru_RU.ts @@ -134,7 +134,7 @@ <li>Personal dashboard to manage your subscription</li> - Личный кабинет для управления подпиской + <li>Личный кабинет для управления подпиской</li> @@ -2013,12 +2013,12 @@ Thank you for staying with us! Наши специалисты технической поддержки всегда готовы помочь вам. - + Support tag - + Copied Скопировано From 48a5452a6567a5cbd3bf7116c2cb17dd494c1e3e Mon Sep 17 00:00:00 2001 From: Nethius Date: Fri, 23 May 2025 14:53:55 +0800 Subject: [PATCH 16/40] chore/minor fixes (#1610) * bugfix: fixed the migration form appearing on app start * feature: added app version to api requests payload * chore: remove unused file * feature: extended logging in service part * chore: bump version * chore: update ru translation file --- CMakeLists.txt | 4 +- client/core/api/apiDefs.h | 1 + client/core/api/apiUtils.cpp | 23 +++++---- client/core/controllers/gatewayController.cpp | 9 ++++ client/core/defs.h | 1 + client/core/errorstrings.cpp | 1 + client/daemon/daemon.cpp | 3 +- .../platforms/linux/daemon/iputilslinux.cpp | 4 +- .../platforms/macos/daemon/iputilsmacos.cpp | 4 +- .../macos/daemon/macosroutemonitor.cpp | 10 ++-- .../windows/daemon/windowsroutemonitor.cpp | 15 +++--- client/translations/amneziavpn_ru_RU.ts | 49 ++++++++++--------- client/ui/Controls2 | 34 ------------- .../controllers/api/apiConfigsController.cpp | 6 +++ .../controllers/api/apiSettingsController.cpp | 2 + 15 files changed, 78 insertions(+), 88 deletions(-) delete mode 100644 client/ui/Controls2 diff --git a/CMakeLists.txt b/CMakeLists.txt index dc76a6c3..424dcf3a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 4.8.7.1 +project(${PROJECT} VERSION 4.8.7.2 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) @@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d") set(RELEASE_DATE "${CURRENT_DATE}") set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) -set(APP_ANDROID_VERSION_CODE 2085) +set(APP_ANDROID_VERSION_CODE 2086) if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") set(MZ_PLATFORM_NAME "linux") diff --git a/client/core/api/apiDefs.h b/client/core/api/apiDefs.h index 6d1a27fa..4588ef04 100644 --- a/client/core/api/apiDefs.h +++ b/client/core/api/apiDefs.h @@ -31,6 +31,7 @@ namespace apiDefs constexpr QLatin1String apiConfig("api_config"); constexpr QLatin1String stackType("stack_type"); constexpr QLatin1String serviceType("service_type"); + constexpr QLatin1String cliVersion("cli_version"); constexpr QLatin1String vpnKey("vpn_key"); constexpr QLatin1String config("config"); diff --git a/client/core/api/apiUtils.cpp b/client/core/api/apiUtils.cpp index f85d2207..7f3e6db3 100644 --- a/client/core/api/apiUtils.cpp +++ b/client/core/api/apiUtils.cpp @@ -41,32 +41,34 @@ bool apiUtils::isServerFromApi(const QJsonObject &serverConfigObject) apiDefs::ConfigType apiUtils::getConfigType(const QJsonObject &serverConfigObject) { auto configVersion = serverConfigObject.value(apiDefs::key::configVersion).toInt(); + switch (configVersion) { case apiDefs::ConfigSource::Telegram: { + constexpr QLatin1String freeV2Endpoint(FREE_V2_ENDPOINT); + constexpr QLatin1String premiumV1Endpoint(PREM_V1_ENDPOINT); + + auto apiEndpoint = serverConfigObject.value(apiDefs::key::apiEndpoint).toString(); + + if (apiEndpoint.contains(premiumV1Endpoint)) { + return apiDefs::ConfigType::AmneziaPremiumV1; + } else if (apiEndpoint.contains(freeV2Endpoint)) { + return apiDefs::ConfigType::AmneziaFreeV2; + } }; case apiDefs::ConfigSource::AmneziaGateway: { constexpr QLatin1String servicePremium("amnezia-premium"); constexpr QLatin1String serviceFree("amnezia-free"); constexpr QLatin1String serviceExternalPremium("external-premium"); - constexpr QLatin1String freeV2Endpoint(FREE_V2_ENDPOINT); - constexpr QLatin1String premiumV1Endpoint(PREM_V1_ENDPOINT); - auto apiConfigObject = serverConfigObject.value(apiDefs::key::apiConfig).toObject(); auto serviceType = apiConfigObject.value(apiDefs::key::serviceType).toString(); - auto apiEndpoint = serverConfigObject.value(apiDefs::key::apiEndpoint).toString(); - if (serviceType == servicePremium) { return apiDefs::ConfigType::AmneziaPremiumV2; } else if (serviceType == serviceFree) { return apiDefs::ConfigType::AmneziaFreeV3; } else if (serviceType == serviceExternalPremium) { return apiDefs::ConfigType::ExternalPremium; - } else if (apiEndpoint.contains(premiumV1Endpoint)) { - return apiDefs::ConfigType::AmneziaPremiumV1; - } else if (apiEndpoint.contains(freeV2Endpoint)) { - return apiDefs::ConfigType::AmneziaFreeV2; } } default: { @@ -94,6 +96,9 @@ amnezia::ErrorCode apiUtils::checkNetworkReplyErrors(const QList &ssl || reply->error() == QNetworkReply::NetworkError::TimeoutError) { qDebug() << reply->error(); return amnezia::ErrorCode::ApiConfigTimeoutError; + } else if (reply->error() == QNetworkReply::NetworkError::OperationNotImplementedError) { + qDebug() << reply->error(); + return amnezia::ErrorCode::ApiUpdateRequestError; } else { QString err = reply->errorString(); int httpStatusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); diff --git a/client/core/controllers/gatewayController.cpp b/client/core/controllers/gatewayController.cpp index 9a7ee6e5..26855ae6 100644 --- a/client/core/controllers/gatewayController.cpp +++ b/client/core/controllers/gatewayController.cpp @@ -36,6 +36,8 @@ namespace constexpr QLatin1String errorResponsePattern1("No active configuration found for"); constexpr QLatin1String errorResponsePattern2("No non-revoked public key found for"); constexpr QLatin1String errorResponsePattern3("Account not found."); + + constexpr QLatin1String updateRequestResponsePattern("client version update is required"); } GatewayController::GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs, @@ -311,6 +313,13 @@ bool GatewayController::shouldBypassProxy(QNetworkReply *reply, const QByteArray qDebug() << reply->error(); return true; } + } else if (reply->error() == QNetworkReply::NetworkError::OperationNotImplementedError) { + if (responseBody.contains(updateRequestResponsePattern)) { + return false; + } else { + qDebug() << reply->error(); + return true; + } } else if (reply->error() != QNetworkReply::NetworkError::NoError) { qDebug() << reply->error(); return true; diff --git a/client/core/defs.h b/client/core/defs.h index 674d1add..df6a1342 100644 --- a/client/core/defs.h +++ b/client/core/defs.h @@ -118,6 +118,7 @@ namespace amnezia ApiConfigLimitError = 1108, ApiNotFoundError = 1109, ApiMigrationError = 1110, + ApiUpdateRequestError = 1111, // QFile errors OpenError = 1200, diff --git a/client/core/errorstrings.cpp b/client/core/errorstrings.cpp index e141b3c7..7cc46220 100644 --- a/client/core/errorstrings.cpp +++ b/client/core/errorstrings.cpp @@ -75,6 +75,7 @@ QString errorString(ErrorCode code) { case (ErrorCode::ApiConfigLimitError): errorMessage = QObject::tr("The limit of allowed configurations per subscription has been exceeded"); break; case (ErrorCode::ApiNotFoundError): errorMessage = QObject::tr("Error when retrieving configuration from API"); break; case (ErrorCode::ApiMigrationError): errorMessage = QObject::tr("A migration error has occurred. Please contact our technical support"); break; + case (ErrorCode::ApiUpdateRequestError): errorMessage = QObject::tr("Please update the application to use this feature"); break; // QFile errors case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break; diff --git a/client/daemon/daemon.cpp b/client/daemon/daemon.cpp index e4b0ab3d..24e72629 100644 --- a/client/daemon/daemon.cpp +++ b/client/daemon/daemon.cpp @@ -149,8 +149,7 @@ bool Daemon::activate(const InterfaceConfig& config) { // set routing for (const IPAddress& ip : config.m_allowedIPAddressRanges) { if (!wgutils()->updateRoutePrefix(ip)) { - logger.debug() << "Routing configuration failed for" - << logger.sensitive(ip.toString()); + logger.debug() << "Routing configuration failed for" << ip.toString(); return false; } } diff --git a/client/platforms/linux/daemon/iputilslinux.cpp b/client/platforms/linux/daemon/iputilslinux.cpp index 63bd92f9..25d4f631 100644 --- a/client/platforms/linux/daemon/iputilslinux.cpp +++ b/client/platforms/linux/daemon/iputilslinux.cpp @@ -97,7 +97,7 @@ bool IPUtilsLinux::addIP4AddressToDevice(const InterfaceConfig& config) { // Set ifr to interface int ret = ioctl(sockfd, SIOCSIFADDR, &ifr); if (ret) { - logger.error() << "Failed to set IPv4: " << logger.sensitive(deviceAddr) + logger.error() << "Failed to set IPv4: " << deviceAddr << "error:" << strerror(errno); return false; } @@ -138,7 +138,7 @@ bool IPUtilsLinux::addIP6AddressToDevice(const InterfaceConfig& config) { // Set ifr6 to the interface ret = ioctl(sockfd, SIOCSIFADDR, &ifr6); if (ret && (errno != EEXIST)) { - logger.error() << "Failed to set IPv6: " << logger.sensitive(deviceAddr) + logger.error() << "Failed to set IPv6: " << deviceAddr << "error:" << strerror(errno); return false; } diff --git a/client/platforms/macos/daemon/iputilsmacos.cpp b/client/platforms/macos/daemon/iputilsmacos.cpp index 0599e2eb..901436ae 100644 --- a/client/platforms/macos/daemon/iputilsmacos.cpp +++ b/client/platforms/macos/daemon/iputilsmacos.cpp @@ -122,7 +122,7 @@ bool IPUtilsMacos::addIP4AddressToDevice(const InterfaceConfig& config) { // Set ifr to interface int ret = ioctl(sockfd, SIOCAIFADDR, &ifr); if (ret) { - logger.error() << "Failed to set IPv4: " << logger.sensitive(deviceAddr) + logger.error() << "Failed to set IPv4: " << deviceAddr << "error:" << strerror(errno); return false; } @@ -162,7 +162,7 @@ bool IPUtilsMacos::addIP6AddressToDevice(const InterfaceConfig& config) { // Set ifr to interface int ret = ioctl(sockfd, SIOCAIFADDR_IN6, &ifr6); if (ret) { - logger.error() << "Failed to set IPv6: " << logger.sensitive(deviceAddr) + logger.error() << "Failed to set IPv6: " << deviceAddr << "error:" << strerror(errno); return false; } diff --git a/client/platforms/macos/daemon/macosroutemonitor.cpp b/client/platforms/macos/daemon/macosroutemonitor.cpp index bd991c01..062f97f3 100644 --- a/client/platforms/macos/daemon/macosroutemonitor.cpp +++ b/client/platforms/macos/daemon/macosroutemonitor.cpp @@ -144,7 +144,7 @@ void MacosRouteMonitor::handleRtmDelete(const struct rt_msghdr* rtm, for (const IPAddress& prefix : m_exclusionRoutes) { if (prefix.address().protocol() == protocol) { logger.debug() << "Removing exclusion route to" - << logger.sensitive(prefix.toString()); + << prefix.toString(); rtmSendRoute(RTM_DELETE, prefix, rtm->rtm_index, nullptr); } } @@ -259,7 +259,7 @@ void MacosRouteMonitor::handleRtmUpdate(const struct rt_msghdr* rtm, for (const IPAddress& prefix : m_exclusionRoutes) { if (prefix.address().protocol() == protocol) { logger.debug() << "Updating exclusion route to" - << logger.sensitive(prefix.toString()); + << prefix.toString(); rtmSendRoute(rtm_type, prefix, ifindex, addrlist[1].constData()); } } @@ -510,8 +510,7 @@ bool MacosRouteMonitor::deleteRoute(const IPAddress& prefix, int flags) { } bool MacosRouteMonitor::addExclusionRoute(const IPAddress& prefix) { - logger.debug() << "Adding exclusion route for" - << logger.sensitive(prefix.toString()); + logger.debug() << "Adding exclusion route for" << prefix.toString(); if (m_exclusionRoutes.contains(prefix)) { logger.warning() << "Exclusion route already exists"; @@ -536,8 +535,7 @@ bool MacosRouteMonitor::addExclusionRoute(const IPAddress& prefix) { } bool MacosRouteMonitor::deleteExclusionRoute(const IPAddress& prefix) { - logger.debug() << "Deleting exclusion route for" - << logger.sensitive(prefix.toString()); + logger.debug() << "Deleting exclusion route for" << prefix.toString(); m_exclusionRoutes.removeAll(prefix); if (prefix.address().protocol() == QAbstractSocket::IPv4Protocol) { diff --git a/client/platforms/windows/daemon/windowsroutemonitor.cpp b/client/platforms/windows/daemon/windowsroutemonitor.cpp index fb0fbf7e..1d0ce4c2 100644 --- a/client/platforms/windows/daemon/windowsroutemonitor.cpp +++ b/client/platforms/windows/daemon/windowsroutemonitor.cpp @@ -303,8 +303,7 @@ void WindowsRouteMonitor::updateCapturedRoutes(int family, void* ptable) { data->Age++; continue; } - logger.debug() << "Capturing route to" - << logger.sensitive(prefix.toString()); + logger.debug() << "Capturing route to" << prefix.toString(); // Clone the route and direct it into the VPN tunnel. data = new MIB_IPFORWARD_ROW2; @@ -354,8 +353,7 @@ void WindowsRouteMonitor::updateCapturedRoutes(int family, void* ptable) { continue; } - logger.debug() << "Removing route capture for" - << logger.sensitive(i.key().toString()); + logger.debug() << "Removing route capture for" << i.key().toString(); // Otherwise, this route is no longer in use. DWORD result = DeleteIpForwardEntry2(data); @@ -368,8 +366,7 @@ void WindowsRouteMonitor::updateCapturedRoutes(int family, void* ptable) { } bool WindowsRouteMonitor::addExclusionRoute(const IPAddress& prefix) { - logger.debug() << "Adding exclusion route for" - << logger.sensitive(prefix.toString()); + logger.debug() << "Adding exclusion route for" << prefix.toString(); // Silently ignore non-routeable addresses. QHostAddress addr = prefix.address(); @@ -437,7 +434,7 @@ bool WindowsRouteMonitor::addExclusionRoute(const IPAddress& prefix) { bool WindowsRouteMonitor::deleteExclusionRoute(const IPAddress& prefix) { logger.debug() << "Deleting exclusion route for" - << logger.sensitive(prefix.address().toString()); + << prefix.address().toString(); MIB_IPFORWARD_ROW2* data = m_exclusionRoutes.take(prefix); if (data == nullptr) { @@ -447,7 +444,7 @@ bool WindowsRouteMonitor::deleteExclusionRoute(const IPAddress& prefix) { DWORD result = DeleteIpForwardEntry2(data); if ((result != ERROR_NOT_FOUND) && (result != NO_ERROR)) { logger.error() << "Failed to delete route to" - << logger.sensitive(prefix.toString()) + << prefix.toString() << "result:" << result; } @@ -465,7 +462,7 @@ void WindowsRouteMonitor::flushRouteTable( DWORD result = DeleteIpForwardEntry2(data); if ((result != ERROR_NOT_FOUND) && (result != NO_ERROR)) { logger.error() << "Failed to delete route to" - << logger.sensitive(i.key().toString()) + << i.key().toString() << "result:" << result; } delete data; diff --git a/client/translations/amneziavpn_ru_RU.ts b/client/translations/amneziavpn_ru_RU.ts index 14de59d1..1d8766ac 100644 --- a/client/translations/amneziavpn_ru_RU.ts +++ b/client/translations/amneziavpn_ru_RU.ts @@ -89,17 +89,17 @@ ApiConfigsController - + %1 installed successfully. %1 успешно установлен. - + API config reloaded Конфигурация API перезагружена - + Successfully changed the country of connection to %1 Страна подключения изменена на %1 @@ -358,7 +358,7 @@ ContextMenuType - + C&ut Вырезать @@ -368,12 +368,12 @@ Копировать - + &Paste Вставить - + &SelectAll Выбрать всё @@ -2412,42 +2412,42 @@ Thank you for staying with us! Доступ в интернет блокируется при разрыве VPN-соединения - + Strict KillSwitch Strict KillSwitch - + Internet connection is blocked even when VPN is turned off manually or hasn't started Доступ в интернет блокируется, даже если VPN отключен вручную или не был запущен - + Just a little heads-up Небольшое предупреждение - + If the VPN disconnects or drops while Strict KillSwitch is enabled, internet access will be blocked. To restore access, reconnect VPN or disable/change the KillSwitch. Если VPN отключится или соединение прервётся при включённом Strict KillSwitch, доступ в интернет будет заблокирован. Чтобы восстановить доступ, снова подключитесь к VPN или отключите (измените) режим KillSwitch. - + Continue Продолжить - + Cancel Отменить - + DNS Exceptions Исключения для DNS - + DNS servers listed here will remain accessible when KillSwitch is active. DNS-серверы из этого списка останутся доступными при активном KillSwitch. @@ -4085,7 +4085,12 @@ Thank you for staying with us! Произошла ошибка миграции. Обратитесь в нашу техническую поддержку
- + + Please update the application to use this feature + Пожалуйста, обновите приложение, чтобы использовать эту функцию + + + ErrorCode: %1. Код ошибки: %1. @@ -4196,37 +4201,37 @@ Thank you for staying with us! Произошла ошибка миграции. Обратитесь в нашу техническую поддержку
- + QFile error: The file could not be opened Ошибка QFile: не удалось открыть файл - + QFile error: An error occurred when reading from the file Ошибка QFile: произошла ошибка при чтении из файла - + QFile error: The file could not be accessed Ошибка QFile: не удалось получить доступ к файлу - + QFile error: An unspecified error occurred Ошибка QFile: произошла неизвестная ошибка - + QFile error: A fatal error occurred Ошибка QFile: произошла фатальная ошибка - + QFile error: The operation was aborted Ошибка QFile: операция была прервана - + Internal error Внутренняя ошибка diff --git a/client/ui/Controls2 b/client/ui/Controls2 deleted file mode 100644 index 13f01bb7..00000000 --- a/client/ui/Controls2 +++ /dev/null @@ -1,34 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts - -TextArea { - id: root - - width: parent.width - - topPadding: 16 - leftPadding: 16 - - color: "#D7D8DB" - selectionColor: "#412102" - selectedTextColor: "#D7D8DB" - placeholderTextColor: "#878B91" - - font.pixelSize: 16 - font.weight: Font.Medium - font.family: "PT Root UI VF" - - wrapMode: Text.Wrap - - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.RightButton - onClicked: contextMenu.open() - } - - ContextMenuType { - id: contextMenu - textObj: textField - } -} diff --git a/client/ui/controllers/api/apiConfigsController.cpp b/client/ui/controllers/api/apiConfigsController.cpp index 21d371bb..1b70315a 100644 --- a/client/ui/controllers/api/apiConfigsController.cpp +++ b/client/ui/controllers/api/apiConfigsController.cpp @@ -77,6 +77,7 @@ bool ApiConfigsController::exportNativeConfig(const QString &serverCountryCode, apiPayload[configKey::serverCountryCode] = serverCountryCode; apiPayload[configKey::serviceType] = apiConfigObject.value(configKey::serviceType); apiPayload[configKey::authData] = serverConfigObject.value(configKey::authData); + apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION); QByteArray responseBody; ErrorCode errorCode = gatewayController.post(QString("%1v1/native_config"), apiPayload, responseBody); @@ -109,6 +110,7 @@ bool ApiConfigsController::revokeNativeConfig(const QString &serverCountryCode) apiPayload[configKey::serverCountryCode] = serverCountryCode; apiPayload[configKey::serviceType] = apiConfigObject.value(configKey::serviceType); apiPayload[configKey::authData] = serverConfigObject.value(configKey::authData); + apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION); QByteArray responseBody; ErrorCode errorCode = gatewayController.post(QString("%1v1/revoke_native_config"), apiPayload, responseBody); @@ -188,6 +190,7 @@ bool ApiConfigsController::importServiceFromGateway() apiPayload[configKey::userCountryCode] = userCountryCode; apiPayload[configKey::serviceType] = serviceType; apiPayload[configKey::uuid] = installationUuid; + apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION); QByteArray responseBody; ErrorCode errorCode = gatewayController.post(QString("%1v1/config"), apiPayload, responseBody); @@ -233,6 +236,7 @@ bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const apiPayload[configKey::userCountryCode] = userCountryCode; apiPayload[configKey::serviceType] = serviceType; apiPayload[configKey::uuid] = installationUuid; + apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION); if (!newCountryCode.isEmpty()) { apiPayload[configKey::serverCountryCode] = newCountryCode; @@ -330,6 +334,7 @@ bool ApiConfigsController::deactivateDevice() apiPayload[configKey::serviceType] = apiConfigObject.value(configKey::serviceType); apiPayload[configKey::authData] = serverConfigObject.value(configKey::authData); apiPayload[configKey::uuid] = m_settings->getInstallationUuid(true); + apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION); QByteArray responseBody; ErrorCode errorCode = gatewayController.post(QString("%1v1/revoke_config"), apiPayload, responseBody); @@ -366,6 +371,7 @@ bool ApiConfigsController::deactivateExternalDevice(const QString &uuid, const Q apiPayload[configKey::serviceType] = apiConfigObject.value(configKey::serviceType); apiPayload[configKey::authData] = serverConfigObject.value(configKey::authData); apiPayload[configKey::uuid] = uuid; + apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION); QByteArray responseBody; ErrorCode errorCode = gatewayController.post(QString("%1v1/revoke_config"), apiPayload, responseBody); diff --git a/client/ui/controllers/api/apiSettingsController.cpp b/client/ui/controllers/api/apiSettingsController.cpp index f20f92bf..c4a75a5b 100644 --- a/client/ui/controllers/api/apiSettingsController.cpp +++ b/client/ui/controllers/api/apiSettingsController.cpp @@ -5,6 +5,7 @@ #include "core/api/apiUtils.h" #include "core/controllers/gatewayController.h" +#include "version.h" namespace { @@ -60,6 +61,7 @@ bool ApiSettingsController::getAccountInfo(bool reload) apiPayload[configKey::userCountryCode] = apiConfig.value(configKey::userCountryCode).toString(); apiPayload[configKey::serviceType] = apiConfig.value(configKey::serviceType).toString(); apiPayload[configKey::authData] = authData; + apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION); QByteArray responseBody; From 369e08844f0cb30a7d451cd5f19e269780852c38 Mon Sep 17 00:00:00 2001 From: Nethius Date: Fri, 23 May 2025 23:48:38 +0800 Subject: [PATCH 17/40] fix: temporarily hide the strict killswitch (#1612) --- client/ui/qml/Pages2/PageSettingsKillSwitch.qml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/client/ui/qml/Pages2/PageSettingsKillSwitch.qml b/client/ui/qml/Pages2/PageSettingsKillSwitch.qml index 444eb415..1ffcc8cf 100644 --- a/client/ui/qml/Pages2/PageSettingsKillSwitch.qml +++ b/client/ui/qml/Pages2/PageSettingsKillSwitch.qml @@ -81,7 +81,8 @@ PageType { Layout.leftMargin: 16 Layout.rightMargin: 16 - enabled: SettingsController.isKillSwitchEnabled && !ConnectionController.isConnected + visible: false + enabled: false //SettingsController.isKillSwitchEnabled && !ConnectionController.isConnected checked: SettingsController.strictKillSwitchEnabled text: qsTr("Strict KillSwitch") @@ -103,7 +104,9 @@ PageType { } } - DividerType {} + DividerType { + visible: false + } LabelWithButtonType { Layout.topMargin: 32 From 43c3ce9a6e4d083bc1990b42a7437069d7b09bcf Mon Sep 17 00:00:00 2001 From: Mitternacht822 Date: Thu, 5 Jun 2025 06:08:51 +0400 Subject: [PATCH 18/40] fix: fixed issue with not restoring autostart setting after backup (#1601) * fixed issue with not restoring autostart setting after backup * fixed bug when autostart setting was not saving innto backup file and not preserving after backup * deleted unused lines --- client/ui/controllers/settingsController.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index c5c569db..3e5cbe87 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -126,7 +126,13 @@ void SettingsController::clearLogs() void SettingsController::backupAppConfig(const QString &fileName) { - SystemController::saveFile(fileName, m_settings->backupAppConfig()); + QByteArray data = m_settings->backupAppConfig(); + QJsonDocument doc = QJsonDocument::fromJson(data); + QJsonObject config = doc.object(); + + config["Conf/autoStart"] = Autostart::isAutostart(); + + SystemController::saveFile(fileName, QJsonDocument(config).toJson()); } void SettingsController::restoreAppConfig(const QString &fileName) @@ -140,6 +146,15 @@ void SettingsController::restoreAppConfigFromData(const QByteArray &data) { bool ok = m_settings->restoreAppConfig(data); if (ok) { + QJsonObject newConfigData = QJsonDocument::fromJson(data).object(); + +#if defined(Q_OS_WINDOWS) || defined(Q_OS_LINUX) || defined(Q_OS_MACX) + bool autoStart = false; + if (newConfigData.contains("Conf/autoStart")) { + autoStart = newConfigData["Conf/autoStart"].toBool(); + } + toggleAutoStart(autoStart); +#endif m_serversModel->resetModel(); m_languageModel->changeLanguage( static_cast(m_languageModel->getCurrentLanguageIndex())); From 7a203868ec2494b28a86c99d1bf90df3d34feda2 Mon Sep 17 00:00:00 2001 From: Mitternacht822 Date: Thu, 5 Jun 2025 06:12:43 +0400 Subject: [PATCH 19/40] bugfix: fixed bug with not clearing autostart option (#1603) --- client/ui/controllers/settingsController.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index 3e5cbe87..3aa8f48b 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -182,6 +182,8 @@ void SettingsController::clearSettings() m_appSplitTunnelingModel->setRouteMode(Settings::AppsRouteMode::VpnAllExceptApps); m_appSplitTunnelingModel->toggleSplitTunneling(false); + toggleAutoStart(false); + emit changeSettingsFinished(tr("All settings have been reset to default values")); #ifdef Q_OS_IOS From a20516850c6e9495f55ca337c61cbfe9fd851e88 Mon Sep 17 00:00:00 2001 From: Mitternacht822 Date: Thu, 5 Jun 2025 06:13:37 +0400 Subject: [PATCH 20/40] fix: fixed bug when app language was not saved into backup file (#1588) --- client/settings.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/settings.h b/client/settings.h index eec6cc44..7c244cd4 100644 --- a/client/settings.h +++ b/client/settings.h @@ -174,11 +174,12 @@ public: QLocale getAppLanguage() { - return value("Conf/appLanguage", QLocale()).toLocale(); + QString localeStr = m_settings.value("Conf/appLanguage").toString(); + return QLocale(localeStr); }; void setAppLanguage(QLocale locale) { - setValue("Conf/appLanguage", locale); + setValue("Conf/appLanguage", locale.name()); }; bool isScreenshotsEnabled() const From 768ca1e73d5df195e5045507a6aa05cbc9b3402f Mon Sep 17 00:00:00 2001 From: Yaroslav Date: Thu, 5 Jun 2025 05:21:27 +0300 Subject: [PATCH 21/40] feat: add support for manual code signing and provisioning profiles for iOS builds (#1605) --- client/cmake/ios.cmake | 16 +++++++++++++++- client/ios/networkextension/CMakeLists.txt | 16 ++++++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/client/cmake/ios.cmake b/client/cmake/ios.cmake index 58192237..a498a5b1 100644 --- a/client/cmake/ios.cmake +++ b/client/cmake/ios.cmake @@ -76,8 +76,22 @@ set_target_properties(${PROJECT} PROPERTIES XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/Frameworks" XCODE_EMBED_APP_EXTENSIONS networkextension - XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic ) + +if(DEFINED DEPLOY) + set_target_properties(${PROJECT} PROPERTIES + XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution" + XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development" + XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual + XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "distr ios.org.amnezia.AmneziaVPN" + XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "dev ios.org.amnezia.AmneziaVPN" + ) +else() + set_target_properties(${PROJECT} PROPERTIES + XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic + ) +endif() + set_target_properties(${PROJECT} PROPERTIES XCODE_ATTRIBUTE_SWIFT_VERSION "5.0" XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES" diff --git a/client/ios/networkextension/CMakeLists.txt b/client/ios/networkextension/CMakeLists.txt index dde03b3b..64b1c3c4 100644 --- a/client/ios/networkextension/CMakeLists.txt +++ b/client/ios/networkextension/CMakeLists.txt @@ -26,10 +26,22 @@ set_target_properties(networkextension PROPERTIES XCODE_ATTRIBUTE_TARGETED_DEVICE_FAMILY "1,2" XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../../Frameworks" - - XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic ) +if(DEPLOY) + set_target_properties(networkextension PROPERTIES + XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Apple Distribution" + XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY[variant=Debug] "Apple Development" + XCODE_ATTRIBUTE_CODE_SIGN_STYLE Manual + XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER "distr ios.org.amnezia.AmneziaVPN" + XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER[variant=Debug] "dev ios.org.amnezia.AmneziaVPN" + ) +else() + set_target_properties(networkextension PROPERTIES + XCODE_ATTRIBUTE_CODE_SIGN_STYLE Automatic + ) +endif() + set_target_properties(networkextension PROPERTIES XCODE_ATTRIBUTE_SWIFT_VERSION "5.0" XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES" From c37662dbe2ab33413319a076f32b6e752e9761bc Mon Sep 17 00:00:00 2001 From: Mitternacht822 Date: Thu, 5 Jun 2025 17:48:23 +0400 Subject: [PATCH 22/40] fix: fixed the bug when split tunneling was not preserving after backup for Windows and Android platforms (#1584) * fixed the bug when split tunneling was not preserving after backup for Windows and Android platforms * fixed camelCase and setRouteMode() call * fixed site splitTunneling for all platforms * fixed issue with not preserving tunneling route mode --- client/ui/controllers/settingsController.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index 3aa8f48b..f8e97a1f 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -158,6 +158,18 @@ void SettingsController::restoreAppConfigFromData(const QByteArray &data) m_serversModel->resetModel(); m_languageModel->changeLanguage( static_cast(m_languageModel->getCurrentLanguageIndex())); + +#if defined(Q_OS_WINDOWS) || defined(Q_OS_ANDROID) + int appSplitTunnelingRouteMode = newConfigData.value("Conf/appsRouteMode").toInt(); + bool appSplittunnelingEnabled = newConfigData.value("Conf/appsSplitTunnelingEnabled").toBool(); + m_appSplitTunnelingModel->setRouteMode(appSplitTunnelingRouteMode); + m_appSplitTunnelingModel->toggleSplitTunneling(appSplittunnelingEnabled); +#endif + int siteSplitTunnelingRouteMode = newConfigData.value("Conf/routeMode").toInt(); + bool siteSplittunnelingEnabled = newConfigData.value("Conf/sitesSplitTunnelingEnabled").toBool(); + m_sitesModel->setRouteMode(siteSplitTunnelingRouteMode); + m_sitesModel->toggleSplitTunneling(siteSplittunnelingEnabled); + emit restoreBackupFinished(); } else { emit changeSettingsErrorOccurred(tr("Backup file is corrupted")); From d3715d00ae970aa91e28855472a9f525a5c5c763 Mon Sep 17 00:00:00 2001 From: Nethius Date: Mon, 9 Jun 2025 05:17:40 +0300 Subject: [PATCH 23/40] chore: fixed artifact names (#1635) --- deploy/deploy_s3.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/deploy_s3.sh b/deploy/deploy_s3.sh index c109a286..a139a5a5 100755 --- a/deploy/deploy_s3.sh +++ b/deploy/deploy_s3.sh @@ -28,10 +28,10 @@ wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSIO wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_android_7_armeabi-v7a.apk wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_android_7_x86.apk wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_android_7_x86_64.apk -wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_linux.tar.zip +wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_linux_x64.tar.zip wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_macos.dmg wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_macos_old.dmg -wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_x64.exe +wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/AmneziaVPN_${VERSION}_windows_x64.exe cd ../ From a2d30efaab530f8fbb75394e73a59dfffa1be5e6 Mon Sep 17 00:00:00 2001 From: aiamnezia Date: Mon, 16 Jun 2025 18:01:46 +0400 Subject: [PATCH 24/40] fix: add saving custom server name if it overridden by user (#1581) * Add saving custom server name if it overridden by user * clear duplicated code --- client/protocols/protocols_defs.h | 2 ++ client/ui/controllers/api/apiConfigsController.cpp | 4 ++++ client/ui/models/servers_model.cpp | 1 + 3 files changed, 7 insertions(+) diff --git a/client/protocols/protocols_defs.h b/client/protocols/protocols_defs.h index feeabb2f..c2d51454 100644 --- a/client/protocols/protocols_defs.h +++ b/client/protocols/protocols_defs.h @@ -103,6 +103,8 @@ namespace amnezia constexpr char clientId[] = "clientId"; + constexpr char nameOverriddenByUser[] = "nameOverriddenByUser"; + } namespace protocols diff --git a/client/ui/controllers/api/apiConfigsController.cpp b/client/ui/controllers/api/apiConfigsController.cpp index 1b70315a..4c58140c 100644 --- a/client/ui/controllers/api/apiConfigsController.cpp +++ b/client/ui/controllers/api/apiConfigsController.cpp @@ -261,6 +261,10 @@ bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const newServerConfig.insert(configKey::apiConfig, newApiConfig); newServerConfig.insert(configKey::authData, authData); + if (serverConfig.value(config_key::nameOverriddenByUser).toBool()) { + newServerConfig.insert(config_key::name, serverConfig.value(config_key::name)); + newServerConfig.insert(config_key::nameOverriddenByUser, true); + } m_serversModel->editServer(newServerConfig, serverIndex); if (reloadServiceConfig) { emit reloadServerFromApiFinished(tr("API config reloaded")); diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index 0a7b2526..5a70c16f 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -66,6 +66,7 @@ bool ServersModel::setData(const QModelIndex &index, const QVariant &value, int } else { server.insert(config_key::description, value.toString()); } + server.insert(config_key::nameOverriddenByUser, true); m_settings->editServer(index.row(), server); m_servers.replace(index.row(), server); if (index.row() == m_defaultServerIndex) { From 26059788890ed25ab10f6ae01a743b9a37926e69 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Tue, 17 Jun 2025 05:00:41 -0700 Subject: [PATCH 25/40] fix: allow internet traffic for strict mode with split tunnel (#1654) --- client/platforms/windows/daemon/wireguardutilswindows.cpp | 1 + client/protocols/openvpnprotocol.cpp | 2 +- client/protocols/xrayprotocol.cpp | 2 +- client/ui/qml/Pages2/PageSettingsKillSwitch.qml | 7 ++----- service/server/killswitch.cpp | 3 +++ 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/client/platforms/windows/daemon/wireguardutilswindows.cpp b/client/platforms/windows/daemon/wireguardutilswindows.cpp index d01ef54a..a5c9c84d 100644 --- a/client/platforms/windows/daemon/wireguardutilswindows.cpp +++ b/client/platforms/windows/daemon/wireguardutilswindows.cpp @@ -130,6 +130,7 @@ bool WireguardUtilsWindows::addInterface(const InterfaceConfig& config) { // Enable the windows firewall NET_IFINDEX ifindex; ConvertInterfaceLuidToIndex(&luid, &ifindex); + m_firewall->allowAllTraffic(); m_firewall->enableInterface(ifindex); } diff --git a/client/protocols/openvpnprotocol.cpp b/client/protocols/openvpnprotocol.cpp index 429b85a6..0bbdbd07 100644 --- a/client/protocols/openvpnprotocol.cpp +++ b/client/protocols/openvpnprotocol.cpp @@ -343,7 +343,7 @@ void OpenVpnProtocol::updateVpnGateway(const QString &line) // killSwitch toggle if (m_vpnLocalAddress == netInterfaces.at(i).addressEntries().at(j).ip().toString()) { if (QVariant(m_configData.value(config_key::killSwitchOption).toString()).toBool()) { - IpcClient::Interface()->enableKillSwitch(QJsonObject(), netInterfaces.at(i).index()); + IpcClient::Interface()->enableKillSwitch(m_configData, netInterfaces.at(i).index()); } m_configData.insert("vpnAdapterIndex", netInterfaces.at(i).index()); m_configData.insert("vpnGateway", m_vpnGateway); diff --git a/client/protocols/xrayprotocol.cpp b/client/protocols/xrayprotocol.cpp index faad8e94..9f26d1e6 100755 --- a/client/protocols/xrayprotocol.cpp +++ b/client/protocols/xrayprotocol.cpp @@ -134,7 +134,7 @@ ErrorCode XrayProtocol::startTun2Sock() // killSwitch toggle if (m_vpnLocalAddress == netInterfaces.at(i).addressEntries().at(j).ip().toString()) { if (QVariant(m_configData.value(config_key::killSwitchOption).toString()).toBool()) { - IpcClient::Interface()->enableKillSwitch(QJsonObject(), netInterfaces.at(i).index()); + IpcClient::Interface()->enableKillSwitch(m_configData, netInterfaces.at(i).index()); } m_configData.insert("vpnAdapterIndex", netInterfaces.at(i).index()); m_configData.insert("vpnGateway", m_vpnGateway); diff --git a/client/ui/qml/Pages2/PageSettingsKillSwitch.qml b/client/ui/qml/Pages2/PageSettingsKillSwitch.qml index 1ffcc8cf..444eb415 100644 --- a/client/ui/qml/Pages2/PageSettingsKillSwitch.qml +++ b/client/ui/qml/Pages2/PageSettingsKillSwitch.qml @@ -81,8 +81,7 @@ PageType { Layout.leftMargin: 16 Layout.rightMargin: 16 - visible: false - enabled: false //SettingsController.isKillSwitchEnabled && !ConnectionController.isConnected + enabled: SettingsController.isKillSwitchEnabled && !ConnectionController.isConnected checked: SettingsController.strictKillSwitchEnabled text: qsTr("Strict KillSwitch") @@ -104,9 +103,7 @@ PageType { } } - DividerType { - visible: false - } + DividerType {} LabelWithButtonType { Layout.topMargin: 32 diff --git a/service/server/killswitch.cpp b/service/server/killswitch.cpp index c44bd6a2..447be865 100644 --- a/service/server/killswitch.cpp +++ b/service/server/killswitch.cpp @@ -255,6 +255,9 @@ bool KillSwitch::enablePeerTraffic(const QJsonObject &configStr) { bool KillSwitch::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterIndex) { #ifdef Q_OS_WIN + if (configStr.value("splitTunnelType").toInt() != 0) { + WindowsFirewall::create(this)->allowAllTraffic(); + } return WindowsFirewall::create(this)->enableInterface(vpnAdapterIndex); #endif From e152e84ddc949c17754509bcf5b2ddd2a4ebdcf3 Mon Sep 17 00:00:00 2001 From: lunardunno <126363523+lunardunno@users.noreply.github.com> Date: Mon, 23 Jun 2025 06:32:56 +0400 Subject: [PATCH 26/40] feat: docker pull rate limit check (#1657) * Docker pull rate limit * Error code for DockerPullRateLimit * Extended description Error 213 Extended description for the error 213: Docker Pull Rate Limit * empty line removed --- client/core/controllers/serverController.cpp | 2 ++ client/core/defs.h | 1 + client/core/errorstrings.cpp | 1 + 3 files changed, 4 insertions(+) diff --git a/client/core/controllers/serverController.cpp b/client/core/controllers/serverController.cpp index 8ff6b6c8..f86e2865 100644 --- a/client/core/controllers/serverController.cpp +++ b/client/core/controllers/serverController.cpp @@ -460,6 +460,8 @@ ErrorCode ServerController::buildContainerWorker(const ServerCredentials &creden return ErrorCode::ServerDockerOnCgroupsV2; if (stdOut.contains("cgroup mountpoint does not exist")) return ErrorCode::ServerCgroupMountpoint; + if (stdOut.contains("have reached") && stdOut.contains("pull rate limit")) + return ErrorCode::DockerPullRateLimit; return error; } diff --git a/client/core/defs.h b/client/core/defs.h index df6a1342..64f52ce6 100644 --- a/client/core/defs.h +++ b/client/core/defs.h @@ -60,6 +60,7 @@ namespace amnezia ServerUserPasswordRequired = 210, ServerDockerOnCgroupsV2 = 211, ServerCgroupMountpoint = 212, + DockerPullRateLimit = 213, // Ssh connection errors SshRequestDeniedError = 300, diff --git a/client/core/errorstrings.cpp b/client/core/errorstrings.cpp index 7cc46220..bd5ccaba 100644 --- a/client/core/errorstrings.cpp +++ b/client/core/errorstrings.cpp @@ -28,6 +28,7 @@ QString errorString(ErrorCode code) { 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; + case(ErrorCode::DockerPullRateLimit): errorMessage = QObject::tr("Docker error: The pull rate limit has been reached"); break; // Libssh errors case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break; From 979ab42c5a424ccffb0fa8b843b9fcc517236f9d Mon Sep 17 00:00:00 2001 From: lunardunno <126363523+lunardunno@users.noreply.github.com> Date: Mon, 23 Jun 2025 06:34:40 +0400 Subject: [PATCH 27/40] feat: OpenSUSE support (#1557) * LOCK_FILE for zypper Checking LOCK_FILE for zypper to support OpenSUSE * Installation for OpenSUSE Docker installation support for OpenSUSE * quiet for zypper * LOCK_CMD variable Implementing the LOCK_CMD variable for different OS. * additional exception for "server is busy" * Replacing and with or Replacing && with || * undo changes to serverController * rpm.lock rpm.lock for dnf yum and zypper * LOCK_CMD check for dnf * Added zypper in check_user_in_sudo --- client/core/controllers/serverController.cpp | 2 +- client/server_scripts/check_server_is_busy.sh | 11 ++++++----- client/server_scripts/check_user_in_sudo.sh | 1 + client/server_scripts/install_docker.sh | 1 + 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/client/core/controllers/serverController.cpp b/client/core/controllers/serverController.cpp index f86e2865..a61a638b 100644 --- a/client/core/controllers/serverController.cpp +++ b/client/core/controllers/serverController.cpp @@ -827,7 +827,7 @@ ErrorCode ServerController::isServerDpkgBusy(const ServerCredentials &credential if (stdOut.contains("Packet manager not found")) return ErrorCode::ServerPacketManagerError; - if (stdOut.contains("fuser not installed")) + if (stdOut.contains("fuser not installed") || stdOut.contains("cat not installed")) return ErrorCode::NoError; if (stdOut.isEmpty()) { diff --git a/client/server_scripts/check_server_is_busy.sh b/client/server_scripts/check_server_is_busy.sh index 4e6a2c26..feddfed3 100644 --- a/client/server_scripts/check_server_is_busy.sh +++ b/client/server_scripts/check_server_is_busy.sh @@ -1,6 +1,7 @@ -if which apt-get > /dev/null 2>&1; then LOCK_FILE="/var/lib/dpkg/lock-frontend";\ -elif which dnf > /dev/null 2>&1; then LOCK_FILE="/var/run/dnf.pid";\ -elif which yum > /dev/null 2>&1; then LOCK_FILE="/var/run/yum.pid";\ -elif which pacman > /dev/null 2>&1; then LOCK_FILE="/var/lib/pacman/db.lck";\ +if which apt-get > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/lib/dpkg/lock-frontend";\ +elif which dnf > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/cache/dnf/* /var/run/dnf/* /var/lib/dnf/* /var/lib/rpm/*";\ +elif which yum > /dev/null 2>&1; then LOCK_CMD="cat"; LOCK_FILE="/var/run/yum.pid";\ +elif which zypper > /dev/null 2>&1; then LOCK_CMD="cat"; LOCK_FILE="/var/run/zypp.pid";\ +elif which pacman > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/lib/pacman/db.lck";\ else echo "Packet manager not found"; echo "Internal error"; exit 1; fi;\ -if command -v fuser > /dev/null 2>&1; then sudo fuser $LOCK_FILE 2>/dev/null; else echo "fuser not installed"; fi +if command -v $LOCK_CMD > /dev/null 2>&1; then sudo $LOCK_CMD $LOCK_FILE 2>/dev/null; else echo "$LOCK_CMD not installed"; fi diff --git a/client/server_scripts/check_user_in_sudo.sh b/client/server_scripts/check_user_in_sudo.sh index 685e6a18..f83f2fd7 100644 --- a/client/server_scripts/check_user_in_sudo.sh +++ b/client/server_scripts/check_user_in_sudo.sh @@ -1,6 +1,7 @@ if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); opt="--version";\ elif which dnf > /dev/null 2>&1; then pm=$(which dnf); opt="--version";\ elif which yum > /dev/null 2>&1; then pm=$(which yum); opt="--version";\ +elif which zypper > /dev/null 2>&1; then pm=$(which zypper); opt="--version";\ elif which pacman > /dev/null 2>&1; then pm=$(which pacman); opt="--version";\ else pm="uname"; opt="-a";\ fi;\ diff --git a/client/server_scripts/install_docker.sh b/client/server_scripts/install_docker.sh index 619b08d6..1e41bb5a 100644 --- a/client/server_scripts/install_docker.sh +++ b/client/server_scripts/install_docker.sh @@ -1,6 +1,7 @@ if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); silent_inst="-yq install"; check_pkgs="-yq update"; docker_pkg="docker.io"; dist="debian";\ elif which dnf > /dev/null 2>&1; then pm=$(which dnf); silent_inst="-yq install"; check_pkgs="-yq check-update"; docker_pkg="docker"; dist="fedora";\ elif which yum > /dev/null 2>&1; then pm=$(which yum); silent_inst="-y -q install"; check_pkgs="-y -q check-update"; docker_pkg="docker"; dist="centos";\ +elif which zypper > /dev/null 2>&1; then pm=$(which zypper); silent_inst="-nq install"; check_pkgs="-nq refresh"; docker_pkg="docker"; dist="opensuse";\ elif which pacman > /dev/null 2>&1; then pm=$(which pacman); silent_inst="-S --noconfirm --noprogressbar --quiet"; check_pkgs="-Sup"; docker_pkg="docker"; dist="archlinux";\ else echo "Packet manager not found"; exit 1; fi;\ echo "Dist: $dist, Packet manager: $pm, Install command: $silent_inst, Check pkgs command: $check_pkgs, Docker pkg: $docker_pkg";\ From f0626e2ecabf1115d264834beadc59de35a6559e Mon Sep 17 00:00:00 2001 From: aiamnezia Date: Wed, 2 Jul 2025 06:07:56 +0400 Subject: [PATCH 28/40] fix: delete premium V2 migration link from Free config Settings (#1671) * delete premium V2 update link from Free config Settings * Add debug logs * Add property for checking if server config is premium * remove debug logs --- client/ui/models/servers_model.cpp | 9 ++++++++- client/ui/models/servers_model.h | 5 ++++- client/ui/qml/Pages2/PageSettingsServerData.qml | 4 ++-- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index 5a70c16f..22813312 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -8,6 +8,8 @@ #include #endif +#include "core/api/apiUtils.h" + namespace { namespace configKey @@ -427,7 +429,7 @@ void ServersModel::updateDefaultServerContainersModel() emit defaultServerContainersUpdated(containers); } -QJsonObject ServersModel::getServerConfig(const int serverIndex) +QJsonObject ServersModel::getServerConfig(const int serverIndex) const { return m_servers.at(serverIndex).toObject(); } @@ -814,3 +816,8 @@ const QString ServersModel::getDefaultServerImagePathCollapsed() } return QString("qrc:/countriesFlags/images/flagKit/%1.svg").arg(countryCode.toUpper()); } + +bool ServersModel::processedServerIsPremium() const +{ + return apiUtils::isPremiumServer(getServerConfig(m_processedServerIndex)); +} diff --git a/client/ui/models/servers_model.h b/client/ui/models/servers_model.h index c4803708..c36b6534 100644 --- a/client/ui/models/servers_model.h +++ b/client/ui/models/servers_model.h @@ -63,6 +63,9 @@ public: Q_PROPERTY(bool isDefaultServerFromApi READ isDefaultServerFromApi NOTIFY defaultServerIndexChanged) Q_PROPERTY(int processedIndex READ getProcessedServerIndex WRITE setProcessedServerIndex NOTIFY processedServerIndexChanged) + Q_PROPERTY(bool processedServerIsPremium READ processedServerIsPremium NOTIFY processedServerChanged) + + bool processedServerIsPremium() const; public slots: void setDefaultServerIndex(const int index); @@ -92,7 +95,7 @@ public slots: void removeServer(); void removeServer(const int serverIndex); - QJsonObject getServerConfig(const int serverIndex); + QJsonObject getServerConfig(const int serverIndex) const; void reloadDefaultServerContainerConfig(); void updateContainerConfig(const int containerIndex, const QJsonObject config); diff --git a/client/ui/qml/Pages2/PageSettingsServerData.qml b/client/ui/qml/Pages2/PageSettingsServerData.qml index 995ca74b..82552958 100644 --- a/client/ui/qml/Pages2/PageSettingsServerData.qml +++ b/client/ui/qml/Pages2/PageSettingsServerData.qml @@ -260,7 +260,7 @@ PageType { LabelWithButtonType { id: labelWithButton6 - visible: ServersModel.getProcessedServerData("isServerFromTelegramApi") + visible: ServersModel.getProcessedServerData("isServerFromTelegramApi") && ServersModel.processedServerIsPremium Layout.fillWidth: true text: qsTr("Switch to the new Amnezia Premium subscription") @@ -273,7 +273,7 @@ PageType { } DividerType { - visible: ServersModel.getProcessedServerData("isServerFromTelegramApi") + visible: ServersModel.getProcessedServerData("isServerFromTelegramApi") && ServersModel.processedServerIsPremium } } } From b0a6bcc05536c9d615b835a790a90d6d42657d1a Mon Sep 17 00:00:00 2001 From: Mitternacht822 Date: Wed, 2 Jul 2025 06:11:22 +0400 Subject: [PATCH 29/40] =?UTF-8?q?fix:=20fixed=20issue=20when=20native=20co?= =?UTF-8?q?nnection=20format=20preserved=20after=20switching=20p=E2=80=A6?= =?UTF-8?q?=20(#1659)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fixed issue when native connection format preserved after switching protocol * moved newly added code into handler section --- client/ui/qml/Pages2/PageShare.qml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/ui/qml/Pages2/PageShare.qml b/client/ui/qml/Pages2/PageShare.qml index 48f74acf..0f0976bc 100644 --- a/client/ui/qml/Pages2/PageShare.qml +++ b/client/ui/qml/Pages2/PageShare.qml @@ -429,6 +429,11 @@ PageType { fillConnectionTypeModel() + if (exportTypeSelector.currentIndex >= root.connectionTypesModel.length) { + exportTypeSelector.currentIndex = 0 + exportTypeSelector.text = root.connectionTypesModel[0].name + } + if (accessTypeSelector.currentIndex === 1) { PageController.showBusyIndicator(true) ExportController.updateClientManagementModel(ContainersModel.getProcessedContainerIndex(), From 9dca80de18a9f2b3fd8339044eafc0b44fd91bd6 Mon Sep 17 00:00:00 2001 From: Mitternacht822 Date: Wed, 2 Jul 2025 06:11:52 +0400 Subject: [PATCH 30/40] fix: notification not showing when changed some protocols (#1666) * added notification about disconnecting users after applying changes for SS and Cloak servers pages * added notification about changing protocol data for server and some minor changes --- .../qml/Pages2/PageProtocolCloakSettings.qml | 51 +++++++++++++++---- .../Pages2/PageProtocolOpenVpnSettings.qml | 51 ++++++++++++++----- .../PageProtocolShadowSocksSettings.qml | 49 +++++++++++------- .../Pages2/PageProtocolWireGuardSettings.qml | 2 +- .../qml/Pages2/PageProtocolXraySettings.qml | 42 +++++++++++---- 5 files changed, 146 insertions(+), 49 deletions(-) diff --git a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml index 7a0fafbd..8e5129b0 100644 --- a/client/ui/qml/Pages2/PageProtocolCloakSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolCloakSettings.qml @@ -59,10 +59,13 @@ PageType { model: CloakConfigModel delegate: Item { - implicitWidth: listview.width - implicitHeight: col.implicitHeight + id: delegateItem property alias trafficFromField: trafficFromField + property bool isEnabled: ServersModel.isProcessedServerHasWriteAccess() + + implicitWidth: listview.width + implicitHeight: col.implicitHeight ColumnLayout { id: col @@ -78,7 +81,6 @@ PageType { BaseHeaderType { Layout.fillWidth: true - headerText: qsTr("Cloak settings") } @@ -88,6 +90,8 @@ PageType { Layout.fillWidth: true Layout.topMargin: 32 + enabled: delegateItem.isEnabled + headerText: qsTr("Disguised as traffic from") textField.text: site @@ -104,6 +108,8 @@ PageType { } } } + + checkEmptyText: true } TextFieldWithHeaderType { @@ -112,6 +118,8 @@ PageType { Layout.fillWidth: true Layout.topMargin: 16 + enabled: delegateItem.isEnabled + headerText: qsTr("Port") textField.text: port textField.maximumLength: 5 @@ -122,6 +130,8 @@ PageType { port = textField.text } } + + checkEmptyText: true } DropDownType { @@ -129,6 +139,8 @@ PageType { Layout.fillWidth: true Layout.topMargin: 16 + enabled: delegateItem.isEnabled + descriptionText: qsTr("Cipher") headerText: qsTr("Cipher") @@ -166,25 +178,46 @@ PageType { } BasicButtonType { - id: saveRestartButton + id: saveButton Layout.fillWidth: true Layout.topMargin: 24 Layout.bottomMargin: 24 + enabled: trafficFromField.errorText === "" && + portTextField.errorText === "" + text: qsTr("Save") clickedFunc: function() { forceActiveFocus() - if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { - PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection")) - return + var headerText = qsTr("Save settings?") + var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") + + var yesButtonFunction = function() { + if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { + PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection")) + return + } + + PageController.goToPage(PageEnum.PageSetupWizardInstalling) + InstallController.updateContainer(CloakConfigModel.getConfig()) } - PageController.goToPage(PageEnum.PageSetupWizardInstalling); - InstallController.updateContainer(CloakConfigModel.getConfig()) + var noButtonFunction = function() { + if (!GC.isMobile()) { + saveButton.forceActiveFocus() + } + } + + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } + + Keys.onEnterPressed: saveButton.clicked() + Keys.onReturnPressed: saveButton.clicked() } } } diff --git a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml index 2e00d54a..62cbd1f6 100644 --- a/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolOpenVpnSettings.qml @@ -58,10 +58,13 @@ PageType { model: OpenVpnConfigModel delegate: Item { - implicitWidth: listview.width - implicitHeight: col.implicitHeight + id: delegateItem property alias vpnAddressSubnetTextField: vpnAddressSubnetTextField + property bool isEnabled: ServersModel.isProcessedServerHasWriteAccess() + + implicitWidth: listview.width + implicitHeight: col.implicitHeight ColumnLayout { id: col @@ -77,7 +80,6 @@ PageType { BaseHeaderType { Layout.fillWidth: true - headerText: qsTr("OpenVPN settings") } @@ -87,6 +89,8 @@ PageType { Layout.fillWidth: true Layout.topMargin: 32 + enabled: delegateItem.isEnabled + headerText: qsTr("VPN address subnet") textField.text: subnetAddress @@ -97,6 +101,8 @@ PageType { subnetAddress = textField.text } } + + checkEmptyText: true } ParagraphTextType { @@ -134,7 +140,7 @@ PageType { Layout.topMargin: 40 parentFlickable: fl - enabled: isPortEditable + enabled: delegateItem.isEnabled headerText: qsTr("Port") textField.text: port @@ -146,6 +152,8 @@ PageType { port = textField.text } } + + checkEmptyText: true } SwitcherType { @@ -388,26 +396,45 @@ PageType { } BasicButtonType { - id: saveRestartButton + id: saveButton Layout.fillWidth: true Layout.topMargin: 24 Layout.bottomMargin: 24 + enabled: vpnAddressSubnetTextField.errorText === "" && + portTextField.errorText === "" + text: qsTr("Save") parentFlickable: fl - clickedFunc: function() { + onClicked: function() { forceActiveFocus() - if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { - PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection")) - return - } + var headerText = qsTr("Save settings?") + var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") - PageController.goToPage(PageEnum.PageSetupWizardInstalling); - InstallController.updateContainer(OpenVpnConfigModel.getConfig()) + var yesButtonFunction = function() { + if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { + PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection")) + return + } + + PageController.goToPage(PageEnum.PageSetupWizardInstalling); + InstallController.updateContainer(OpenVpnConfigModel.getConfig()) + } + var noButtonFunction = function() { + if (!GC.isMobile()) { + saveButton.forceActiveFocus() + } + } + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } + + Keys.onEnterPressed: saveButton.clicked() + Keys.onReturnPressed: saveButton.clicked() } } } diff --git a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml index 63e60dcb..92df3ec7 100644 --- a/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolShadowSocksSettings.qml @@ -57,15 +57,13 @@ PageType { model: ShadowSocksConfigModel delegate: Item { + id: delegateItem + + property bool isEnabled: ServersModel.isProcessedServerHasWriteAccess() + implicitWidth: listview.width implicitHeight: col.implicitHeight - property var focusItemId: portTextField.enabled ? - portTextField : - cipherDropDown.enabled ? - cipherDropDown : - saveRestartButton - ColumnLayout { id: col @@ -80,7 +78,6 @@ PageType { BaseHeaderType { Layout.fillWidth: true - headerText: qsTr("Shadowsocks settings") } @@ -90,7 +87,7 @@ PageType { Layout.fillWidth: true Layout.topMargin: 40 - enabled: isPortEditable + enabled: delegateItem.isEnabled headerText: qsTr("Port") textField.text: port @@ -102,6 +99,8 @@ PageType { port = textField.text } } + + checkEmptyText: true } DropDownType { @@ -109,7 +108,7 @@ PageType { Layout.fillWidth: true Layout.topMargin: 20 - enabled: isCipherEditable + enabled: delegateItem.isEnabled descriptionText: qsTr("Cipher") headerText: qsTr("Cipher") @@ -149,27 +148,43 @@ PageType { } BasicButtonType { - id: saveRestartButton + id: saveButton Layout.fillWidth: true Layout.topMargin: 24 Layout.bottomMargin: 24 - enabled: isPortEditable | isCipherEditable + enabled: portTextField.errorText === "" text: qsTr("Save") clickedFunc: function() { forceActiveFocus() - if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { - PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection")) - return - } + var headerText = qsTr("Save settings?") + var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") - PageController.goToPage(PageEnum.PageSetupWizardInstalling); - InstallController.updateContainer(ShadowSocksConfigModel.getConfig()) + var yesButtonFunction = function() { + if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { + PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection")) + return + } + + PageController.goToPage(PageEnum.PageSetupWizardInstalling); + InstallController.updateContainer(ShadowSocksConfigModel.getConfig()) + } + var noButtonFunction = function() { + if (!GC.isMobile()) { + saveButton.forceActiveFocus() + } + } + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } + + Keys.onEnterPressed: saveButton.clicked() + Keys.onReturnPressed: saveButton.clicked() } } } diff --git a/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml b/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml index 7b5180f3..21b35bc1 100644 --- a/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolWireGuardSettings.qml @@ -152,7 +152,7 @@ PageType { } var noButtonFunction = function() { if (!GC.isMobile()) { - saveRestartButton.forceActiveFocus() + saveButton.forceActiveFocus() } } showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) diff --git a/client/ui/qml/Pages2/PageProtocolXraySettings.qml b/client/ui/qml/Pages2/PageProtocolXraySettings.qml index d22e31a2..0bcd14de 100644 --- a/client/ui/qml/Pages2/PageProtocolXraySettings.qml +++ b/client/ui/qml/Pages2/PageProtocolXraySettings.qml @@ -58,7 +58,10 @@ PageType { model: XrayConfigModel delegate: Item { + id: delegateItem + property alias focusItemId: textFieldWithHeaderType.textField + property bool isEnabled: ServersModel.isProcessedServerHasWriteAccess() implicitWidth: listview.width implicitHeight: col.implicitHeight @@ -85,6 +88,8 @@ PageType { Layout.fillWidth: true Layout.topMargin: 32 + enabled: delegateItem.isEnabled + headerText: qsTr("Disguised as traffic from") textField.text: site @@ -101,6 +106,8 @@ PageType { } } } + + checkEmptyText: true } TextFieldWithHeaderType { @@ -130,23 +137,38 @@ PageType { Layout.topMargin: 24 Layout.bottomMargin: 24 + enabled: portTextField.errorText === "" + text: qsTr("Save") - onClicked: { + onClicked: function() { forceActiveFocus() - if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { - PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection")) - return - } + var headerText = qsTr("Save settings?") + var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.") + var yesButtonText = qsTr("Continue") + var noButtonText = qsTr("Cancel") - PageController.goToPage(PageEnum.PageSetupWizardInstalling); - InstallController.updateContainer(XrayConfigModel.getConfig()) - focusItem.forceActiveFocus() + var yesButtonFunction = function() { + if (ConnectionController.isConnected && ServersModel.getDefaultServerData("defaultContainer") === ContainersModel.getProcessedContainerIndex()) { + PageController.showNotificationMessage(qsTr("Unable change settings while there is an active connection")) + return + } + + PageController.goToPage(PageEnum.PageSetupWizardInstalling); + InstallController.updateContainer(XrayConfigModel.getConfig()) + //focusItem.forceActiveFocus() + } + var noButtonFunction = function() { + if (!GC.isMobile()) { + saveButton.forceActiveFocus() + } + } + showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction) } - Keys.onEnterPressed: basicButton.clicked() - Keys.onReturnPressed: basicButton.clicked() + Keys.onEnterPressed: saveButton.clicked() + Keys.onReturnPressed: saveButton.clicked() } } } From 127f8ed3bbccf31383504eb2a71396015b3d67fb Mon Sep 17 00:00:00 2001 From: Nethius Date: Wed, 2 Jul 2025 10:14:56 +0800 Subject: [PATCH 31/40] fix: fixed desktop entry version for linux (#1665) --- deploy/installer/config/AmneziaVPN.desktop.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/installer/config/AmneziaVPN.desktop.in b/deploy/installer/config/AmneziaVPN.desktop.in index 2a53074e..03ab570c 100755 --- a/deploy/installer/config/AmneziaVPN.desktop.in +++ b/deploy/installer/config/AmneziaVPN.desktop.in @@ -2,7 +2,7 @@ [Desktop Entry] Type=Application Name=AmneziaVPN -Version=@CMAKE_PROJECT_VERSION@ +Version=1.0 Comment=Client of your self-hosted VPN Exec=AmneziaVPN Icon=/usr/share/pixmaps/AmneziaVPN.png From b34193486300bc951219c655fd2734f6860feb9f Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Tue, 1 Jul 2025 19:16:58 -0700 Subject: [PATCH 32/40] fix: allow secondary DNS usage when AmneziaDNS is disabled (#1583) * Allow secondary DNS usage when AmneziaDNS is disabled * Don't setup secondary DNS for OpenVPN with AmneziaDNS --------- Co-authored-by: vladimir.kuznetsov --- client/configurators/openvpn_configurator.cpp | 12 +++++++++ client/daemon/daemon.cpp | 26 +++++++++++++----- client/daemon/interfaceconfig.cpp | 13 ++++++--- client/daemon/interfaceconfig.h | 3 ++- client/mozilla/localsocketcontroller.cpp | 9 ++++++- .../linux/daemon/wireguardutilslinux.cpp | 5 +++- .../macos/daemon/wireguardutilsmacos.cpp | 27 ++++++++++--------- .../windows/daemon/windowsfirewall.cpp | 23 +++++++++++++--- client/protocols/xrayprotocol.cpp | 7 ++++- service/server/killswitch.cpp | 23 +++++++++++++--- 10 files changed, 116 insertions(+), 32 deletions(-) diff --git a/client/configurators/openvpn_configurator.cpp b/client/configurators/openvpn_configurator.cpp index 6d6603da..f6996320 100644 --- a/client/configurators/openvpn_configurator.cpp +++ b/client/configurators/openvpn_configurator.cpp @@ -118,6 +118,12 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPairisSitesSplitTunnelingEnabled()) { config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n"); config.append("block-ipv6\n"); @@ -161,6 +167,12 @@ QString OpenVpnConfigurator::processConfigWithExportSettings(const QPair resolvers; - resolvers.append(QHostAddress(config.m_dnsServer)); + resolvers.append(QHostAddress(config.m_primaryDnsServer)); + if (!config.m_secondaryDnsServer.isEmpty()) { + resolvers.append(QHostAddress(config.m_secondaryDnsServer)); + } // If the DNS is not the Gateway, it's a user defined DNS // thus, not add any other :) - if (config.m_dnsServer == config.m_serverIpv4Gateway) { + if (config.m_primaryDnsServer == config.m_serverIpv4Gateway) { resolvers.append(QHostAddress(config.m_serverIpv6Gateway)); } @@ -279,15 +282,26 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) { config.m_serverIpv4Gateway = obj.value("serverIpv4Gateway").toString(); config.m_serverIpv6Gateway = obj.value("serverIpv6Gateway").toString(); - if (!obj.contains("dnsServer")) { - config.m_dnsServer = QString(); + if (!obj.contains("primaryDnsServer")) { + config.m_primaryDnsServer = QString(); } else { - QJsonValue value = obj.value("dnsServer"); + QJsonValue value = obj.value("primaryDnsServer"); if (!value.isString()) { logger.error() << "dnsServer is not a string"; return false; } - config.m_dnsServer = value.toString(); + config.m_primaryDnsServer = value.toString(); + } + + if (!obj.contains("secondaryDnsServer")) { + config.m_secondaryDnsServer = QString(); + } else { + QJsonValue value = obj.value("secondaryDnsServer"); + if (!value.isString()) { + logger.error() << "dnsServer is not a string"; + return false; + } + config.m_secondaryDnsServer = value.toString(); } if (!obj.contains("hopType")) { diff --git a/client/daemon/interfaceconfig.cpp b/client/daemon/interfaceconfig.cpp index f0adcc92..846cfebe 100644 --- a/client/daemon/interfaceconfig.cpp +++ b/client/daemon/interfaceconfig.cpp @@ -28,7 +28,8 @@ QJsonObject InterfaceConfig::toJson() const { (m_hopType == InterfaceConfig::SingleHop)) { json.insert("serverIpv4Gateway", QJsonValue(m_serverIpv4Gateway)); json.insert("serverIpv6Gateway", QJsonValue(m_serverIpv6Gateway)); - json.insert("dnsServer", QJsonValue(m_dnsServer)); + json.insert("primaryDnsServer", QJsonValue(m_primaryDnsServer)); + json.insert("secondaryDnsServer", QJsonValue(m_secondaryDnsServer)); } QJsonArray allowedIPAddesses; @@ -100,11 +101,15 @@ QString InterfaceConfig::toWgConf(const QMap& extra) const { out << "MTU = " << m_deviceMTU << "\n"; } - if (!m_dnsServer.isNull()) { - QStringList dnsServers(m_dnsServer); + if (!m_primaryDnsServer.isNull()) { + QStringList dnsServers; + dnsServers.append(m_primaryDnsServer); + if (!m_secondaryDnsServer.isNull()) { + dnsServers.append(m_secondaryDnsServer); + } // If the DNS is not the Gateway, it's a user defined DNS // thus, not add any other :) - if (m_dnsServer == m_serverIpv4Gateway) { + if (m_primaryDnsServer == m_serverIpv4Gateway) { dnsServers.append(m_serverIpv6Gateway); } out << "DNS = " << dnsServers.join(", ") << "\n"; diff --git a/client/daemon/interfaceconfig.h b/client/daemon/interfaceconfig.h index ee43a253..6ae400c2 100644 --- a/client/daemon/interfaceconfig.h +++ b/client/daemon/interfaceconfig.h @@ -32,7 +32,8 @@ class InterfaceConfig { QString m_serverIpv4AddrIn; QString m_serverPskKey; QString m_serverIpv6AddrIn; - QString m_dnsServer; + QString m_primaryDnsServer; + QString m_secondaryDnsServer; int m_serverPort = 0; int m_deviceMTU = 1420; QList m_allowedIPAddressRanges; diff --git a/client/mozilla/localsocketcontroller.cpp b/client/mozilla/localsocketcontroller.cpp index afa29c47..67924d47 100644 --- a/client/mozilla/localsocketcontroller.cpp +++ b/client/mozilla/localsocketcontroller.cpp @@ -149,7 +149,14 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) { json.insert("serverPort", wgConfig.value(amnezia::config_key::port).toInt()); json.insert("serverIpv4Gateway", wgConfig.value(amnezia::config_key::hostName)); // json.insert("serverIpv6Gateway", QJsonValue(hop.m_server.ipv6Gateway())); - json.insert("dnsServer", rawConfig.value(amnezia::config_key::dns1)); + + json.insert("primaryDnsServer", rawConfig.value(amnezia::config_key::dns1)); + + // We don't use secondary DNS if primary DNS is AmneziaDNS + if (!rawConfig.value(amnezia::config_key::dns1).toString(). + contains(amnezia::protocols::dns::amneziaDnsIp)) { + json.insert("secondaryDnsServer", rawConfig.value(amnezia::config_key::dns2)); + } QJsonArray jsAllowedIPAddesses; diff --git a/client/platforms/linux/daemon/wireguardutilslinux.cpp b/client/platforms/linux/daemon/wireguardutilslinux.cpp index 0fbb65a8..a12b8582 100644 --- a/client/platforms/linux/daemon/wireguardutilslinux.cpp +++ b/client/platforms/linux/daemon/wireguardutilslinux.cpp @@ -140,7 +140,10 @@ bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) { } else { if (config.m_killSwitchEnabled) { FirewallParams params { }; - params.dnsServers.append(config.m_dnsServer); + params.dnsServers.append(config.m_primaryDnsServer); + if (!config.m_secondaryDnsServer.isEmpty()) { + params.dnsServers.append(config.m_secondaryDnsServer); + } if (config.m_allowedIPAddressRanges.contains(IPAddress("0.0.0.0/0"))) { params.blockAll = true; if (config.m_excludedAddresses.size()) { diff --git a/client/platforms/macos/daemon/wireguardutilsmacos.cpp b/client/platforms/macos/daemon/wireguardutilsmacos.cpp index 1d8aa6e0..37170f20 100644 --- a/client/platforms/macos/daemon/wireguardutilsmacos.cpp +++ b/client/platforms/macos/daemon/wireguardutilsmacos.cpp @@ -136,26 +136,29 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) { if (err != 0) { logger.error() << "Interface configuration failed:" << strerror(err); } else { - if (config.m_killSwitchEnabled) { - FirewallParams params { }; - params.dnsServers.append(config.m_dnsServer); + if (config.m_killSwitchEnabled) { + FirewallParams params { }; + params.dnsServers.append(config.m_primaryDnsServer); + if (!config.m_secondaryDnsServer.isEmpty()) { + params.dnsServers.append(config.m_secondaryDnsServer); + } - if (config.m_allowedIPAddressRanges.contains(IPAddress("0.0.0.0/0"))) { + if (config.m_allowedIPAddressRanges.contains(IPAddress("0.0.0.0/0"))) { params.blockAll = true; if (config.m_excludedAddresses.size()) { - params.allowNets = true; - foreach (auto net, config.m_excludedAddresses) { - params.allowAddrs.append(net.toUtf8()); - } + params.allowNets = true; + foreach (auto net, config.m_excludedAddresses) { + params.allowAddrs.append(net.toUtf8()); + } } - } else { + } else { params.blockNets = true; foreach (auto net, config.m_allowedIPAddressRanges) { - params.blockAddrs.append(net.toString()); + params.blockAddrs.append(net.toString()); } - } - applyFirewallRules(params); } + applyFirewallRules(params); + } } return (err == 0); } diff --git a/client/platforms/windows/daemon/windowsfirewall.cpp b/client/platforms/windows/daemon/windowsfirewall.cpp index 1834452e..2556c417 100644 --- a/client/platforms/windows/daemon/windowsfirewall.cpp +++ b/client/platforms/windows/daemon/windowsfirewall.cpp @@ -291,15 +291,32 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) { "Block Internet", config.m_serverPublicKey)) { return false; } - if (!config.m_dnsServer.isEmpty()) { - if (!allowTrafficTo(QHostAddress(config.m_dnsServer), 53, HIGH_WEIGHT, + if (!config.m_primaryDnsServer.isEmpty()) { + if (!allowTrafficTo(QHostAddress(config.m_primaryDnsServer), 53, HIGH_WEIGHT, "Allow DNS-Server", config.m_serverPublicKey)) { return false; } // In some cases, we might configure a 2nd DNS server for IPv6, however // this should probably be cleaned up by converting m_dnsServer into // a QStringList instead. - if (config.m_dnsServer == config.m_serverIpv4Gateway) { + if (config.m_primaryDnsServer == config.m_serverIpv4Gateway) { + if (!allowTrafficTo(QHostAddress(config.m_serverIpv6Gateway), 53, + HIGH_WEIGHT, "Allow extra IPv6 DNS-Server", + config.m_serverPublicKey)) { + return false; + } + } + } + + if (!config.m_secondaryDnsServer.isEmpty()) { + if (!allowTrafficTo(QHostAddress(config.m_secondaryDnsServer), 53, HIGH_WEIGHT, + "Allow DNS-Server", config.m_serverPublicKey)) { + return false; + } + // In some cases, we might configure a 2nd DNS server for IPv6, however + // this should probably be cleaned up by converting m_dnsServer into + // a QStringList instead. + if (config.m_secondaryDnsServer == config.m_serverIpv4Gateway) { if (!allowTrafficTo(QHostAddress(config.m_serverIpv6Gateway), 53, HIGH_WEIGHT, "Allow extra IPv6 DNS-Server", config.m_serverPublicKey)) { diff --git a/client/protocols/xrayprotocol.cpp b/client/protocols/xrayprotocol.cpp index 9f26d1e6..84922634 100755 --- a/client/protocols/xrayprotocol.cpp +++ b/client/protocols/xrayprotocol.cpp @@ -98,8 +98,13 @@ ErrorCode XrayProtocol::startTun2Sock() if (vpnState == Vpn::ConnectionState::Connected) { setConnectionState(Vpn::ConnectionState::Connecting); QList dnsAddr; + dnsAddr.push_back(QHostAddress(m_configData.value(config_key::dns1).toString())); - dnsAddr.push_back(QHostAddress(m_configData.value(config_key::dns2).toString())); + // We don't use secondary DNS if primary DNS is AmneziaDNS + if (!m_configData.value(amnezia::config_key::dns1).toString(). + contains(amnezia::protocols::dns::amneziaDnsIp)) { + dnsAddr.push_back(QHostAddress(m_configData.value(config_key::dns2).toString())); + } #ifdef Q_OS_WIN QThread::msleep(8000); #endif diff --git a/service/server/killswitch.cpp b/service/server/killswitch.cpp index 447be865..d0cba03a 100644 --- a/service/server/killswitch.cpp +++ b/service/server/killswitch.cpp @@ -192,7 +192,14 @@ bool KillSwitch::addAllowedRange(const QStringList &ranges) { bool KillSwitch::enablePeerTraffic(const QJsonObject &configStr) { #ifdef Q_OS_WIN InterfaceConfig config; - config.m_dnsServer = configStr.value(amnezia::config_key::dns1).toString(); + + config.m_primaryDnsServer = configStr.value(amnezia::config_key::dns1).toString(); + + // We don't use secondary DNS if primary DNS is AmneziaDNS + if (!config.m_primaryDnsServer.contains(amnezia::protocols::dns::amneziaDnsIp)) { + config.m_secondaryDnsServer = configStr.value(amnezia::config_key::dns2).toString(); + } + config.m_serverPublicKey = "openvpn"; config.m_serverIpv4Gateway = configStr.value("vpnGateway").toString(); config.m_serverIpv4AddrIn = configStr.value("vpnServer").toString(); @@ -307,8 +314,14 @@ bool KillSwitch::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterIn 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()); + + // We don't use secondary DNS if primary DNS is AmneziaDNS + if (!configStr.value(amnezia::config_key::dns1).toString().contains(amnezia::protocols::dns::amneziaDnsIp)) { + dnsServers.append(configStr.value(amnezia::config_key::dns2).toString()); + } + dnsServers.append("127.0.0.1"); dnsServers.append("127.0.0.53"); @@ -345,7 +358,11 @@ bool KillSwitch::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterIn QStringList dnsServers; dnsServers.append(configStr.value(amnezia::config_key::dns1).toString()); - dnsServers.append(configStr.value(amnezia::config_key::dns2).toString()); + + // We don't use secondary DNS if primary DNS is AmneziaDNS + if (!configStr.value(amnezia::config_key::dns1).toString().contains(amnezia::protocols::dns::amneziaDnsIp)) { + dnsServers.append(configStr.value(amnezia::config_key::dns2).toString()); + } for (auto dns : configStr.value(amnezia::config_key::allowedDnsServers).toArray()) { if (!dns.isString()) { From 4d17e913b52a02d80381b61d94a4a767e9bc00cf Mon Sep 17 00:00:00 2001 From: Yaroslav Date: Thu, 3 Jul 2025 04:51:11 +0300 Subject: [PATCH 33/40] feat: native macos installer distribution (#1633) * Add uninstall option and output pkg Improve installer mode detection Fix macOS installer packaging Fix default selection for uninstall choice Remove obsolete tar handling and clean script copies * Improve macOS build script * fix: update macos firewall and package scripts for better compatibility and cleanup * Add DeveloperID certificate and improve macOS signing script Use keychain option for codesign and restore login keychain to list after signing * Update build_macos.sh * feat: add script to quit GUI application during uninstall on macos * fix: handle macos post-install when app is unpacked into localized folder * fix: improve post_install script to handle missing service plist and provide error logging --- .github/workflows/deploy.yml | 18 +- .../platforms/macos/daemon/macosfirewall.cpp | 14 +- deploy/DeveloperIDG2CA.cer | Bin 0 -> 1090 bytes deploy/build_macos.sh | 258 +++++++++++------- deploy/data/macos/check_install.sh | 5 + deploy/data/macos/check_uninstall.sh | 5 + deploy/data/macos/distribution.xml | 17 ++ deploy/data/macos/distribution_uninstall.xml | 13 + deploy/data/macos/post_install.sh | 41 ++- deploy/data/macos/post_uninstall.sh | 50 ++++ deploy/data/macos/uninstall_conclusion.html | 7 + deploy/data/macos/uninstall_welcome.html | 7 + deploy/installer/config.cmake | 5 - deploy/installer/config/macos.xml.in | 27 -- 14 files changed, 311 insertions(+), 156 deletions(-) create mode 100644 deploy/DeveloperIDG2CA.cer mode change 100755 => 100644 deploy/build_macos.sh create mode 100755 deploy/data/macos/check_install.sh create mode 100755 deploy/data/macos/check_uninstall.sh create mode 100644 deploy/data/macos/distribution.xml create mode 100644 deploy/data/macos/distribution_uninstall.xml create mode 100644 deploy/data/macos/uninstall_conclusion.html create mode 100644 deploy/data/macos/uninstall_welcome.html delete mode 100644 deploy/installer/config/macos.xml.in diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 86779f33..0c9dfb32 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -255,7 +255,6 @@ jobs: env: # Keep compat with MacOS 10.15 aka Catalina by Qt 6.4 QT_VERSION: 6.4.3 - QIF_VERSION: 4.6 PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }} PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }} DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }} @@ -283,11 +282,6 @@ jobs: set-env: 'true' extra: '--external 7z --base ${{ env.QT_MIRROR }}' - - name: 'Install Qt Installer Framework ${{ env.QIF_VERSION }}' - run: | - mkdir -pv ${{ runner.temp }}/Qt/Tools/QtInstallerFramework - wget https://qt.amzsvc.com/tools/ifw/${{ env.QIF_VERSION }}.zip - unzip ${{ env.QIF_VERSION }}.zip -d ${{ runner.temp }}/Qt/Tools/QtInstallerFramework/ - name: 'Get sources' uses: actions/checkout@v4 @@ -301,14 +295,13 @@ jobs: - name: 'Build project' run: | export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin" - export QIF_BIN_DIR="${{ runner.temp }}/Qt/Tools/QtInstallerFramework/${{ env.QIF_VERSION }}/bin" bash deploy/build_macos.sh - name: 'Upload installer artifact' uses: actions/upload-artifact@v4 with: name: AmneziaVPN_MacOS_old_installer - path: AmneziaVPN.dmg + path: deploy/build/pkg/AmneziaVPN.pkg retention-days: 7 - name: 'Upload unpacked artifact' @@ -325,7 +318,6 @@ jobs: env: QT_VERSION: 6.8.0 - QIF_VERSION: 4.8.1 PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }} PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }} DEV_AGW_PUBLIC_KEY: ${{ secrets.DEV_AGW_PUBLIC_KEY }} @@ -353,11 +345,6 @@ jobs: set-env: 'true' extra: '--external 7z --base ${{ env.QT_MIRROR }}' - - name: 'Install Qt Installer Framework ${{ env.QIF_VERSION }}' - run: | - mkdir -pv ${{ runner.temp }}/Qt/Tools/QtInstallerFramework - wget https://qt.amzsvc.com/tools/ifw/${{ env.QIF_VERSION }}.zip - unzip ${{ env.QIF_VERSION }}.zip -d ${{ runner.temp }}/Qt/Tools/QtInstallerFramework/ - name: 'Get sources' uses: actions/checkout@v4 @@ -371,14 +358,13 @@ jobs: - name: 'Build project' run: | export QT_BIN_DIR="${{ runner.temp }}/Qt/${{ env.QT_VERSION }}/macos/bin" - export QIF_BIN_DIR="${{ runner.temp }}/Qt/Tools/QtInstallerFramework/${{ env.QIF_VERSION }}/bin" bash deploy/build_macos.sh - name: 'Upload installer artifact' uses: actions/upload-artifact@v4 with: name: AmneziaVPN_MacOS_installer - path: AmneziaVPN.dmg + path: deploy/build/pkg/AmneziaVPN.pkg retention-days: 7 - name: 'Upload unpacked artifact' diff --git a/client/platforms/macos/daemon/macosfirewall.cpp b/client/platforms/macos/daemon/macosfirewall.cpp index 0fe51f23..5211c440 100644 --- a/client/platforms/macos/daemon/macosfirewall.cpp +++ b/client/platforms/macos/daemon/macosfirewall.cpp @@ -43,8 +43,16 @@ namespace { #include "macosfirewall.h" -#define ResourceDir qApp->applicationDirPath() + "/pf" -#define DaemonDataDir qApp->applicationDirPath() + "/pf" +#include +#include + +// Read-only rules bundled with the application. +#define ResourceDir (qApp->applicationDirPath() + "/pf") + +// Writable location that does NOT live inside the signed bundle. Using a +// constant path under /Library/Application Support keeps the signature intact +// and is accessible to the root helper. +#define DaemonDataDir QStringLiteral("/Library/Application Support/AmneziaVPN/pf") #include @@ -121,6 +129,8 @@ void MacOSFirewall::install() logger.info() << "Installing PF root anchor"; installRootAnchors(); + // Ensure writable directory exists, then store the token there. + QDir().mkpath(DaemonDataDir); execute(QStringLiteral("pfctl -E 2>&1 | grep -F 'Token : ' | cut -c9- > '%1/pf.token'").arg(DaemonDataDir)); } diff --git a/deploy/DeveloperIDG2CA.cer b/deploy/DeveloperIDG2CA.cer new file mode 100644 index 0000000000000000000000000000000000000000..8cbcf6f46ce8dcd0fb6e55441867a4608c032860 GIT binary patch literal 1090 zcmXqLVzD!5Vpdzg%*4pVBvQYH!T#)Y&#KeSzLS=8RTLj;iFG#MW#iOp^Jx3d%gD&h z%3zRW$Zf#M#vIDRCd?EXY$$9X2;y)Fb2%0iSS&nCU+yalX;MxjO;0c zCPpP>Z!@woFgG#sGXTZ8n3@lXWlhc{<6@_ zAeVa$Zs~tCyBS(Lx(wQVee%4v9DmC6_sxzm_*Wacv6#I#)Z>*hPs%}3Q09g3DcH!m}M zzME}-$);}wYkbz|o;?}o+I{{v$FroX4pX<9$DEPod-(p&(x<&A1GYQN=TcU6b?Bdx zUj1OoE6d9jOHD2wI{m=7UT>Su;iqa>RnM*J)hhopPh{Hk2Vdj$KD}68xZHa2OrI*H z#lG(g1g~CeYi#?H7fwq1S7-2zs_8d`#)~n?ko^>O~S}P zBuwq0phvQHX3=yH`M&5=acZK!OOzXoLyRRCDz!!3*x{YIYWcZo(++VRH_f_`f37RD zIVso6^6S55-|LI6bjZK$IKcnor?K1?f$fK41kGK{SvD+jncI`WTU{$#cV_G457+W- zMXMT?mRx?=Uwf(Jh2ioUN9FH7YKmOf(3s#R_GaGBE{9dpS`QQ3xOxisZ+hvx@ma(w zc&)N$X|k%KGE@HK=&108*Ije(pZQ)tKmPXdw<*i>UG(#PZ7V9c!nRmnw>qJnx@hvN w+Yzt&uIxN=z~GOOl<%%dJSvtmnyePgy!ZUclRbQUTGvOdj=AvX_8Ec00Cez?EdT%j literal 0 HcmV?d00001 diff --git a/deploy/build_macos.sh b/deploy/build_macos.sh old mode 100755 new mode 100644 index 5f6e9786..03f286fc --- a/deploy/build_macos.sh +++ b/deploy/build_macos.sh @@ -1,4 +1,15 @@ #!/bin/bash +# ----------------------------------------------------------------------------- +# Usage: +# Export the required signing credentials before running this script, e.g.: +# export MAC_APP_CERT_PW='pw-for-DeveloperID-Application' +# export MAC_INSTALL_CERT_PW='pw-for-DeveloperID-Installer' +# export MAC_SIGNER_ID='Developer ID Application: Some Company Name (XXXXXXXXXX)' +# export MAC_INSTALLER_SIGNER_ID='Developer ID Installer: Some Company Name (XXXXXXXXXX)' +# export APPLE_DEV_EMAIL='your@email.com' +# export APPLE_DEV_PASSWORD='' +# bash deploy/build_macos.sh [-n] +# ----------------------------------------------------------------------------- echo "Build script started ..." set -o errexit -o nounset @@ -14,10 +25,10 @@ done PROJECT_DIR=$(pwd) DEPLOY_DIR=$PROJECT_DIR/deploy -mkdir -p $DEPLOY_DIR/build -BUILD_DIR=$DEPLOY_DIR/build +mkdir -p "$DEPLOY_DIR/build" +BUILD_DIR="$DEPLOY_DIR/build" -echo "Project dir: ${PROJECT_DIR}" +echo "Project dir: ${PROJECT_DIR}" echo "Build dir: ${BUILD_DIR}" APP_NAME=AmneziaVPN @@ -28,39 +39,45 @@ PLIST_NAME=$APP_NAME.plist OUT_APP_DIR=$BUILD_DIR/client BUNDLE_DIR=$OUT_APP_DIR/$APP_FILENAME +# Prebuilt deployment assets are available via the symlink under deploy/data PREBUILT_DEPLOY_DATA_DIR=$PROJECT_DIR/deploy/data/deploy-prebuilt/macos DEPLOY_DATA_DIR=$PROJECT_DIR/deploy/data/macos -INSTALLER_DATA_DIR=$BUILD_DIR/installer/packages/$APP_DOMAIN/data -INSTALLER_BUNDLE_DIR=$BUILD_DIR/installer/$APP_FILENAME -DMG_FILENAME=$PROJECT_DIR/${APP_NAME}.dmg # Search Qt if [ -z "${QT_VERSION+x}" ]; then -QT_VERSION=6.4.3; -QIF_VERSION=4.6 +QT_VERSION=6.8.3; QT_BIN_DIR=$HOME/Qt/$QT_VERSION/macos/bin -QIF_BIN_DIR=$QT_BIN_DIR/../../../Tools/QtInstallerFramework/$QIF_VERSION/bin fi echo "Using Qt in $QT_BIN_DIR" -echo "Using QIF in $QIF_BIN_DIR" # Checking env -$QT_BIN_DIR/qt-cmake --version +"$QT_BIN_DIR/qt-cmake" --version cmake --version clang -v # Build App echo "Building App..." -cd $BUILD_DIR +cd "$BUILD_DIR" -$QT_BIN_DIR/qt-cmake -S $PROJECT_DIR -B $BUILD_DIR +"$QT_BIN_DIR/qt-cmake" -S "$PROJECT_DIR" -B "$BUILD_DIR" cmake --build . --config release --target all # Build and run tests here +# Create a temporary keychain and import certificates +KEYCHAIN_PATH="$PROJECT_DIR/mac_sign.keychain" +trap 'echo "Cleaning up mac_sign.keychain..."; security delete-keychain "$KEYCHAIN_PATH" 2>/dev/null || true; rm -f "$KEYCHAIN_PATH" 2>/dev/null || true' EXIT +KEYCHAIN=$(security default-keychain -d user | tr -d '"[:space:]"') +security list-keychains -d user -s "$KEYCHAIN_PATH" "$KEYCHAIN" "$(security list-keychains -d user | tr '\n' ' ')" +security create-keychain -p "" "$KEYCHAIN_PATH" +security import "$DEPLOY_DIR/DeveloperIdApplicationCertificate.p12" -k "$KEYCHAIN_PATH" -P "$MAC_APP_CERT_PW" -T /usr/bin/codesign +security import "$DEPLOY_DIR/DeveloperIdInstallerCertificate.p12" -k "$KEYCHAIN_PATH" -P "$MAC_INSTALL_CERT_PW" -T /usr/bin/codesign +security import "$DEPLOY_DIR/DeveloperIDG2CA.cer" -k "$KEYCHAIN_PATH" -T /usr/bin/codesign +security list-keychains -d user -s "$KEYCHAIN_PATH" + echo "____________________________________" echo "............Deploy.................." echo "____________________________________" @@ -69,102 +86,159 @@ echo "____________________________________" echo "Packaging ..." -cp -Rv $PREBUILT_DEPLOY_DATA_DIR/* $BUNDLE_DIR/Contents/macOS -$QT_BIN_DIR/macdeployqt $OUT_APP_DIR/$APP_FILENAME -always-overwrite -qmldir=$PROJECT_DIR -cp -av $BUILD_DIR/service/server/$APP_NAME-service $BUNDLE_DIR/Contents/macOS -cp -Rv $PROJECT_DIR/deploy/data/macos/* $BUNDLE_DIR/Contents/macOS -rm -f $BUNDLE_DIR/Contents/macOS/post_install.sh $BUNDLE_DIR/Contents/macOS/post_uninstall.sh +cp -Rv "$PREBUILT_DEPLOY_DATA_DIR"/* "$BUNDLE_DIR/Contents/macOS" +"$QT_BIN_DIR/macdeployqt" "$OUT_APP_DIR/$APP_FILENAME" -always-overwrite -qmldir="$PROJECT_DIR" +cp -av "$BUILD_DIR/service/server/$APP_NAME-service" "$BUNDLE_DIR/Contents/macOS" +rsync -av --exclude="$PLIST_NAME" --exclude=post_install.sh --exclude=post_uninstall.sh "$DEPLOY_DATA_DIR/" "$BUNDLE_DIR/Contents/macOS/" -if [ "${MAC_CERT_PW+x}" ]; then +if [ "${MAC_APP_CERT_PW+x}" ]; then - CERTIFICATE_P12=$DEPLOY_DIR/PrivacyTechAppleCertDeveloperId.p12 - WWDRCA=$DEPLOY_DIR/WWDRCA.cer - KEYCHAIN=amnezia.build.macos.keychain - TEMP_PASS=tmp_pass + # Path to the p12 that contains the Developer ID *Application* certificate + CERTIFICATE_P12=$DEPLOY_DIR/DeveloperIdApplicationCertificate.p12 - security create-keychain -p $TEMP_PASS $KEYCHAIN || true - security default-keychain -s $KEYCHAIN - security unlock-keychain -p $TEMP_PASS $KEYCHAIN + # Ensure launchd plist is bundled, but place it inside Resources so that + # the bundle keeps a valid structure (nothing but `Contents` at the root). + mkdir -p "$BUNDLE_DIR/Contents/Resources" + cp "$DEPLOY_DATA_DIR/$PLIST_NAME" "$BUNDLE_DIR/Contents/Resources/$PLIST_NAME" - security default-keychain - security list-keychains - - security import $WWDRCA -k $KEYCHAIN -T /usr/bin/codesign || true - security import $CERTIFICATE_P12 -k $KEYCHAIN -P $MAC_CERT_PW -T /usr/bin/codesign || true - - security set-key-partition-list -S apple-tool:,apple: -k $TEMP_PASS $KEYCHAIN - security find-identity -p codesigning + # Show available signing identities (useful for debugging) + security find-identity -p codesigning || true echo "Signing App bundle..." - /usr/bin/codesign --deep --force --verbose --timestamp -o runtime --sign "$MAC_SIGNER_ID" $BUNDLE_DIR - /usr/bin/codesign --verify -vvvv $BUNDLE_DIR || true - spctl -a -vvvv $BUNDLE_DIR || true + /usr/bin/codesign --deep --force --verbose --timestamp -o runtime --keychain "$KEYCHAIN_PATH" --sign "$MAC_SIGNER_ID" "$BUNDLE_DIR" + /usr/bin/codesign --verify -vvvv "$BUNDLE_DIR" || true + spctl -a -vvvv "$BUNDLE_DIR" || true - if [ "${NOTARIZE_APP+x}" ]; then - echo "Notarizing App bundle..." - /usr/bin/ditto -c -k --keepParent $BUNDLE_DIR $PROJECT_DIR/Bundle_to_notarize.zip - xcrun notarytool submit $PROJECT_DIR/Bundle_to_notarize.zip --apple-id $APPLE_DEV_EMAIL --team-id $MAC_TEAM_ID --password $APPLE_DEV_PASSWORD - rm $PROJECT_DIR/Bundle_to_notarize.zip - sleep 300 - xcrun stapler staple $BUNDLE_DIR - xcrun stapler validate $BUNDLE_DIR - spctl -a -vvvv $BUNDLE_DIR || true - fi fi echo "Packaging installer..." -mkdir -p $INSTALLER_DATA_DIR -cp -av $PROJECT_DIR/deploy/installer $BUILD_DIR -cp -av $DEPLOY_DATA_DIR/post_install.sh $INSTALLER_DATA_DIR/post_install.sh -cp -av $DEPLOY_DATA_DIR/post_uninstall.sh $INSTALLER_DATA_DIR/post_uninstall.sh -cp -av $DEPLOY_DATA_DIR/$PLIST_NAME $INSTALLER_DATA_DIR/$PLIST_NAME +PKG_DIR=$BUILD_DIR/pkg +# Remove any stale packaging data from previous runs +rm -rf "$PKG_DIR" +PKG_ROOT=$PKG_DIR/root +SCRIPTS_DIR=$PKG_DIR/scripts +RESOURCES_DIR=$PKG_DIR/resources +INSTALL_PKG=$PKG_DIR/${APP_NAME}_install.pkg +UNINSTALL_PKG=$PKG_DIR/${APP_NAME}_uninstall.pkg +FINAL_PKG=$PKG_DIR/${APP_NAME}.pkg +UNINSTALL_SCRIPTS_DIR=$PKG_DIR/uninstall_scripts -chmod a+x $INSTALLER_DATA_DIR/post_install.sh $INSTALLER_DATA_DIR/post_uninstall.sh +mkdir -p "$PKG_ROOT/Applications" "$SCRIPTS_DIR" "$RESOURCES_DIR" "$UNINSTALL_SCRIPTS_DIR" -cd $BUNDLE_DIR -tar czf $INSTALLER_DATA_DIR/$APP_NAME.tar.gz ./ +cp -R "$BUNDLE_DIR" "$PKG_ROOT/Applications" +# launchd plist is already inside the bundle; no need to add it again after signing +/usr/bin/codesign --deep --force --verbose --timestamp -o runtime --keychain "$KEYCHAIN_PATH" --sign "$MAC_SIGNER_ID" "$PKG_ROOT/Applications/$APP_FILENAME" +/usr/bin/codesign --verify --deep --strict --verbose=4 "$PKG_ROOT/Applications/$APP_FILENAME" || true +cp "$DEPLOY_DATA_DIR/post_install.sh" "$SCRIPTS_DIR/post_install.sh" +cp "$DEPLOY_DATA_DIR/post_uninstall.sh" "$UNINSTALL_SCRIPTS_DIR/postinstall" +mkdir -p "$RESOURCES_DIR/scripts" +cp "$DEPLOY_DATA_DIR/check_install.sh" "$RESOURCES_DIR/scripts/check_install.sh" +cp "$DEPLOY_DATA_DIR/check_uninstall.sh" "$RESOURCES_DIR/scripts/check_uninstall.sh" -echo "Building installer..." -$QIF_BIN_DIR/binarycreator --offline-only -v -c $BUILD_DIR/installer/config/macos.xml -p $BUILD_DIR/installer/packages -f $INSTALLER_BUNDLE_DIR +cat > "$SCRIPTS_DIR/postinstall" <<'EOS' +#!/bin/bash +SCRIPT_DIR="$(dirname "$0")" +bash "$SCRIPT_DIR/post_install.sh" +exit 0 +EOS -if [ "${MAC_CERT_PW+x}" ]; then - echo "Signing installer bundle..." - security unlock-keychain -p $TEMP_PASS $KEYCHAIN - /usr/bin/codesign --deep --force --verbose --timestamp -o runtime --sign "$MAC_SIGNER_ID" $INSTALLER_BUNDLE_DIR - /usr/bin/codesign --verify -vvvv $INSTALLER_BUNDLE_DIR || true +chmod +x "$SCRIPTS_DIR"/* +chmod +x "$UNINSTALL_SCRIPTS_DIR"/* +chmod +x "$RESOURCES_DIR/scripts"/* +cp "$PROJECT_DIR/LICENSE" "$RESOURCES_DIR/LICENSE" - if [ "${NOTARIZE_APP+x}" ]; then - echo "Notarizing installer bundle..." - /usr/bin/ditto -c -k --keepParent $INSTALLER_BUNDLE_DIR $PROJECT_DIR/Installer_bundle_to_notarize.zip - xcrun notarytool submit $PROJECT_DIR/Installer_bundle_to_notarize.zip --apple-id $APPLE_DEV_EMAIL --team-id $MAC_TEAM_ID --password $APPLE_DEV_PASSWORD - rm $PROJECT_DIR/Installer_bundle_to_notarize.zip - sleep 300 - xcrun stapler staple $INSTALLER_BUNDLE_DIR - xcrun stapler validate $INSTALLER_BUNDLE_DIR - spctl -a -vvvv $INSTALLER_BUNDLE_DIR || true - fi +APP_VERSION=$(grep -m1 -E 'project\(' "$PROJECT_DIR/CMakeLists.txt" | sed -E 's/.*VERSION ([0-9.]+).*/\1/') +echo "Building component package $INSTALL_PKG ..." + +# Disable bundle relocation so the app always ends up in /Applications even if +# another copy is lying around somewhere. We do this by letting pkgbuild +# analyse the contents, flipping the BundleIsRelocatable flag to false for every +# bundle it discovers and then feeding that plist back to pkgbuild. + +COMPONENT_PLIST="$PKG_DIR/component.plist" +# Create the component description plist first +pkgbuild --analyze --root "$PKG_ROOT" "$COMPONENT_PLIST" + +# Turn all `BundleIsRelocatable` keys to false (PlistBuddy is available on all +# macOS systems). We first convert to xml1 to ensure predictable formatting. + +# Turn relocation off for every bundle entry in the plist. PlistBuddy cannot +# address keys that contain slashes without quoting, so we iterate through the +# top-level keys it prints. +plutil -convert xml1 "$COMPONENT_PLIST" +for bundle_key in $(/usr/libexec/PlistBuddy -c "Print" "$COMPONENT_PLIST" | awk '/^[ \t]*[A-Za-z0-9].*\.app/ {print $1}'); do + /usr/libexec/PlistBuddy -c "Set :'${bundle_key}':BundleIsRelocatable false" "$COMPONENT_PLIST" || true +done + +# Now build the real payload package with the edited plist so that the final +# PackageInfo contains relocatable="false". +pkgbuild --root "$PKG_ROOT" \ + --identifier "$APP_DOMAIN" \ + --version "$APP_VERSION" \ + --install-location "/" \ + --scripts "$SCRIPTS_DIR" \ + --component-plist "$COMPONENT_PLIST" \ + --sign "$MAC_INSTALLER_SIGNER_ID" \ + "$INSTALL_PKG" + +# Build uninstaller component package +UNINSTALL_COMPONENT_PKG=$PKG_DIR/${APP_NAME}_uninstall_component.pkg +echo "Building uninstaller component package $UNINSTALL_COMPONENT_PKG ..." +pkgbuild --nopayload \ + --identifier "$APP_DOMAIN.uninstall" \ + --version "$APP_VERSION" \ + --scripts "$UNINSTALL_SCRIPTS_DIR" \ + --sign "$MAC_INSTALLER_SIGNER_ID" \ + "$UNINSTALL_COMPONENT_PKG" + +# Wrap uninstaller component in a distribution package for clearer UI +echo "Building uninstaller distribution package $UNINSTALL_PKG ..." +UNINSTALL_RESOURCES=$PKG_DIR/uninstall_resources +rm -rf "$UNINSTALL_RESOURCES" +mkdir -p "$UNINSTALL_RESOURCES" +cp "$DEPLOY_DATA_DIR/uninstall_welcome.html" "$UNINSTALL_RESOURCES" +cp "$DEPLOY_DATA_DIR/uninstall_conclusion.html" "$UNINSTALL_RESOURCES" +productbuild \ + --distribution "$DEPLOY_DATA_DIR/distribution_uninstall.xml" \ + --package-path "$PKG_DIR" \ + --resources "$UNINSTALL_RESOURCES" \ + --sign "$MAC_INSTALLER_SIGNER_ID" \ + "$UNINSTALL_PKG" + +cp "$PROJECT_DIR/deploy/data/macos/distribution.xml" "$PKG_DIR/distribution.xml" + +echo "Creating final installer $FINAL_PKG ..." +productbuild --distribution "$PKG_DIR/distribution.xml" \ + --package-path "$PKG_DIR" \ + --resources "$RESOURCES_DIR" \ + --sign "$MAC_INSTALLER_SIGNER_ID" \ + "$FINAL_PKG" + +if [ "${MAC_INSTALL_CERT_PW+x}" ] && [ "${NOTARIZE_APP+x}" ]; then + echo "Notarizing installer package..." + xcrun notarytool submit "$FINAL_PKG" \ + --apple-id "$APPLE_DEV_EMAIL" \ + --team-id "$MAC_TEAM_ID" \ + --password "$APPLE_DEV_PASSWORD" \ + --wait + + echo "Stapling ticket..." + xcrun stapler staple "$FINAL_PKG" + xcrun stapler validate "$FINAL_PKG" fi -echo "Building DMG installer..." -# Allow Terminal to make changes in Privacy & Security > App Management -hdiutil create -size 256mb -volname AmneziaVPN -srcfolder $BUILD_DIR/installer/$APP_NAME.app -ov -format UDZO $DMG_FILENAME - -if [ "${MAC_CERT_PW+x}" ]; then - echo "Signing DMG installer..." - security unlock-keychain -p $TEMP_PASS $KEYCHAIN - /usr/bin/codesign --deep --force --verbose --timestamp -o runtime --sign "$MAC_SIGNER_ID" $DMG_FILENAME - /usr/bin/codesign --verify -vvvv $DMG_FILENAME || true - - if [ "${NOTARIZE_APP+x}" ]; then - echo "Notarizing DMG installer..." - xcrun notarytool submit $DMG_FILENAME --apple-id $APPLE_DEV_EMAIL --team-id $MAC_TEAM_ID --password $APPLE_DEV_PASSWORD - sleep 300 - xcrun stapler staple $DMG_FILENAME - xcrun stapler validate $DMG_FILENAME - fi +if [ "${MAC_INSTALL_CERT_PW+x}" ]; then + /usr/bin/codesign --verify -vvvv "$FINAL_PKG" || true + spctl -a -vvvv "$FINAL_PKG" || true fi -echo "Finished, artifact is $DMG_FILENAME" +# Sign app bundle +/usr/bin/codesign --deep --force --verbose --timestamp -o runtime --keychain "$KEYCHAIN_PATH" --sign "$MAC_SIGNER_ID" "$BUNDLE_DIR" +spctl -a -vvvv "$BUNDLE_DIR" || true -# restore keychain -security default-keychain -s login.keychain +# Restore login keychain as the only user keychain and delete the temporary keychain +KEYCHAIN="$HOME/Library/Keychains/login.keychain-db" +security list-keychains -d user -s "$KEYCHAIN" +security delete-keychain "$KEYCHAIN_PATH" + +echo "Finished, artifact is $FINAL_PKG" diff --git a/deploy/data/macos/check_install.sh b/deploy/data/macos/check_install.sh new file mode 100755 index 00000000..adf63550 --- /dev/null +++ b/deploy/data/macos/check_install.sh @@ -0,0 +1,5 @@ +#!/bin/bash +if [ -d "/Applications/AmneziaVPN.app" ] || pgrep -x "AmneziaVPN-service" >/dev/null; then + exit 1 +fi +exit 0 diff --git a/deploy/data/macos/check_uninstall.sh b/deploy/data/macos/check_uninstall.sh new file mode 100755 index 00000000..e7a6f7e0 --- /dev/null +++ b/deploy/data/macos/check_uninstall.sh @@ -0,0 +1,5 @@ +#!/bin/bash +if [ -d "/Applications/AmneziaVPN.app" ] || pgrep -x "AmneziaVPN-service" >/dev/null; then + exit 0 +fi +exit 1 diff --git a/deploy/data/macos/distribution.xml b/deploy/data/macos/distribution.xml new file mode 100644 index 00000000..c0a1dc68 --- /dev/null +++ b/deploy/data/macos/distribution.xml @@ -0,0 +1,17 @@ + + + AmneziaVPN Installer + + + + + + + + + + + + AmneziaVPN_install.pkg + AmneziaVPN_uninstall_component.pkg + diff --git a/deploy/data/macos/distribution_uninstall.xml b/deploy/data/macos/distribution_uninstall.xml new file mode 100644 index 00000000..cf8932b9 --- /dev/null +++ b/deploy/data/macos/distribution_uninstall.xml @@ -0,0 +1,13 @@ + + Uninstall AmneziaVPN + + + + + + + + + + AmneziaVPN_uninstall_component.pkg + diff --git a/deploy/data/macos/post_install.sh b/deploy/data/macos/post_install.sh index acd3f93f..053c8e13 100755 --- a/deploy/data/macos/post_install.sh +++ b/deploy/data/macos/post_install.sh @@ -7,29 +7,42 @@ LOG_FOLDER=/var/log/$APP_NAME LOG_FILE="$LOG_FOLDER/post-install.log" APP_PATH=/Applications/$APP_NAME.app -if launchctl list "$APP_NAME-service" &> /dev/null; then - launchctl unload $LAUNCH_DAEMONS_PLIST_NAME - rm -f $LAUNCH_DAEMONS_PLIST_NAME +# Handle new installations unpacked into localized folder +if [ -d "/Applications/${APP_NAME}.localized" ]; then + echo "`date` Detected ${APP_NAME}.localized, migrating to standard path" >> $LOG_FILE + sudo rm -rf "$APP_PATH" + sudo mv "/Applications/${APP_NAME}.localized/${APP_NAME}.app" "$APP_PATH" + sudo rm -rf "/Applications/${APP_NAME}.localized" fi -tar xzf $APP_PATH/$APP_NAME.tar.gz -C $APP_PATH -rm -f $APP_PATH/$APP_NAME.tar.gz -sudo chmod -R a-w $APP_PATH/ -sudo chown -R root $APP_PATH/ -sudo chgrp -R wheel $APP_PATH/ +if launchctl list "$APP_NAME-service" &> /dev/null; then + launchctl unload "$LAUNCH_DAEMONS_PLIST_NAME" + rm -f "$LAUNCH_DAEMONS_PLIST_NAME" +fi + +sudo chmod -R a-w "$APP_PATH/" +sudo chown -R root "$APP_PATH/" +sudo chgrp -R wheel "$APP_PATH/" rm -rf $LOG_FOLDER mkdir -p $LOG_FOLDER echo "`date` Script started" > $LOG_FILE -killall -9 $APP_NAME-service 2>> $LOG_FILE +echo "Requesting ${APP_NAME} to quit gracefully" >> "$LOG_FILE" +osascript -e 'tell application "AmneziaVPN" to quit' -mv -f $APP_PATH/$PLIST_NAME $LAUNCH_DAEMONS_PLIST_NAME 2>> $LOG_FILE -chown root:wheel $LAUNCH_DAEMONS_PLIST_NAME -launchctl load $LAUNCH_DAEMONS_PLIST_NAME +PLIST_SOURCE="$APP_PATH/Contents/Resources/$PLIST_NAME" +if [ -f "$PLIST_SOURCE" ]; then + mv -f "$PLIST_SOURCE" "$LAUNCH_DAEMONS_PLIST_NAME" 2>> $LOG_FILE +else + echo "`date` ERROR: service plist not found at $PLIST_SOURCE" >> $LOG_FILE +fi + +chown root:wheel "$LAUNCH_DAEMONS_PLIST_NAME" +launchctl load "$LAUNCH_DAEMONS_PLIST_NAME" +echo "`date` Launching ${APP_NAME} application" >> $LOG_FILE +open -a "$APP_PATH" 2>> $LOG_FILE || true echo "`date` Service status: $?" >> $LOG_FILE echo "`date` Script finished" >> $LOG_FILE - -#rm -- "$0" diff --git a/deploy/data/macos/post_uninstall.sh b/deploy/data/macos/post_uninstall.sh index de7846db..d6c5cdbd 100755 --- a/deploy/data/macos/post_uninstall.sh +++ b/deploy/data/macos/post_uninstall.sh @@ -9,6 +9,19 @@ SYSTEM_APP_SUPPORT="/Library/Application Support/$APP_NAME" LOG_FOLDER="/var/log/$APP_NAME" CACHES_FOLDER="$HOME/Library/Caches/$APP_NAME" +# Attempt to quit the GUI application if it's currently running +if pgrep -x "$APP_NAME" > /dev/null; then + echo "Quitting $APP_NAME..." + osascript -e 'tell application "'"$APP_NAME"'" to quit' || true + # Wait up to 10 seconds for the app to terminate gracefully + for i in {1..10}; do + if ! pgrep -x "$APP_NAME" > /dev/null; then + break + fi + sleep 1 + done +fi + # Stop the running service if it exists if pgrep -x "${APP_NAME}-service" > /dev/null; then sudo killall -9 "${APP_NAME}-service" @@ -32,3 +45,40 @@ sudo rm -rf "$LOG_FOLDER" # Remove any caches left behind rm -rf "$CACHES_FOLDER" + +# Remove PF data directory created by firewall helper, if present +sudo rm -rf "/Library/Application Support/${APP_NAME}/pf" + +# ---------------- PF firewall cleanup ---------------------- +# Rules are loaded under the anchor "amn" (see macosfirewall.cpp) +# Flush only that anchor to avoid destroying user/system rules. + +PF_ANCHOR="amn" + +### Flush all PF rules, NATs, and tables under our anchor and sub-anchors ### +anchors=$(sudo pfctl -s Anchors 2>/dev/null | awk '/^'"${PF_ANCHOR}"'/ {sub(/\*$/, "", $1); print $1}') +for anc in $anchors; do + echo "Flushing PF anchor $anc" + sudo pfctl -a "$anc" -F all 2>/dev/null || true + # flush tables under this anchor + tables=$(sudo pfctl -s Tables 2>/dev/null | awk '/^'"$anc"'/ {print}') + for tbl in $tables; do + echo "Killing PF table $tbl" + sudo pfctl -t "$tbl" -T kill 2>/dev/null || true + done +done + +### Reload default PF config to restore system rules ### +if [ -f /etc/pf.conf ]; then + echo "Restoring system PF config" + sudo pfctl -f /etc/pf.conf 2>/dev/null || true +fi + +### Disable PF if no rules remain ### +if sudo pfctl -s info 2>/dev/null | grep -q '^Status: Enabled' && \ + ! sudo pfctl -sr 2>/dev/null | grep -q .; then + echo "Disabling PF" + sudo pfctl -d 2>/dev/null || true +fi + +# ----------------------------------------------------------- diff --git a/deploy/data/macos/uninstall_conclusion.html b/deploy/data/macos/uninstall_conclusion.html new file mode 100644 index 00000000..f5b8bb63 --- /dev/null +++ b/deploy/data/macos/uninstall_conclusion.html @@ -0,0 +1,7 @@ + +Uninstall Complete + +

AmneziaVPN has been uninstalled

+

Thank you for using AmneziaVPN. The application and its components have been removed.

+ + \ No newline at end of file diff --git a/deploy/data/macos/uninstall_welcome.html b/deploy/data/macos/uninstall_welcome.html new file mode 100644 index 00000000..9f3d97cb --- /dev/null +++ b/deploy/data/macos/uninstall_welcome.html @@ -0,0 +1,7 @@ + +Uninstall AmneziaVPN + +

Uninstall AmneziaVPN

+

This process will remove AmneziaVPN from your system. Click Continue to proceed.

+ + \ No newline at end of file diff --git a/deploy/installer/config.cmake b/deploy/installer/config.cmake index 13f09986..3c33a33c 100644 --- a/deploy/installer/config.cmake +++ b/deploy/installer/config.cmake @@ -4,11 +4,6 @@ if(WIN32) ${CMAKE_CURRENT_LIST_DIR}/config/windows.xml.in ${CMAKE_BINARY_DIR}/installer/config/windows.xml ) -elseif(APPLE AND NOT IOS) - configure_file( - ${CMAKE_CURRENT_LIST_DIR}/config/macos.xml.in - ${CMAKE_BINARY_DIR}/installer/config/macos.xml - ) elseif(LINUX) set(ApplicationsDir "@ApplicationsDir@") configure_file( diff --git a/deploy/installer/config/macos.xml.in b/deploy/installer/config/macos.xml.in deleted file mode 100644 index 3888d08d..00000000 --- a/deploy/installer/config/macos.xml.in +++ /dev/null @@ -1,27 +0,0 @@ - - - AmneziaVPN - @CMAKE_PROJECT_VERSION@ - AmneziaVPN - AmneziaVPN - AmneziaVPN - /Applications/AmneziaVPN.app - 600 - 380 - Mac - true - true - false - controlscript.js - false - true - false - true - - - https://amneziavpn.org/updates/macos - true - AmneziaVPN - repository for macOS - - - From efcc0b7efc9fd8f15081db44cf48e9ff205cb407 Mon Sep 17 00:00:00 2001 From: Nethius Date: Thu, 3 Jul 2025 09:58:23 +0800 Subject: [PATCH 34/40] feat: xray api support (#1679) * refactoring: moved shared code into reusable functions for ApiConfigsController * feat: add xray support in apiConfigsController * feat: added a temporary switch for the xray protocol on api settings page * feat: added supported protocols field processing * refactoring: moved IsProtocolSelectionSupported to apiAccountInfoModel --- client/core/api/apiDefs.h | 1 + .../controllers/api/apiConfigsController.cpp | 484 ++++++++++-------- .../ui/controllers/api/apiConfigsController.h | 18 +- client/ui/models/api/apiAccountInfoModel.cpp | 11 + client/ui/models/api/apiAccountInfoModel.h | 5 +- .../qml/Pages2/PageSettingsApiServerInfo.qml | 26 + 6 files changed, 330 insertions(+), 215 deletions(-) diff --git a/client/core/api/apiDefs.h b/client/core/api/apiDefs.h index 4588ef04..12c8051f 100644 --- a/client/core/api/apiDefs.h +++ b/client/core/api/apiDefs.h @@ -32,6 +32,7 @@ namespace apiDefs constexpr QLatin1String stackType("stack_type"); constexpr QLatin1String serviceType("service_type"); constexpr QLatin1String cliVersion("cli_version"); + constexpr QLatin1String supportedProtocols("supported_protocols"); constexpr QLatin1String vpnKey("vpn_key"); constexpr QLatin1String config("config"); diff --git a/client/ui/controllers/api/apiConfigsController.cpp b/client/ui/controllers/api/apiConfigsController.cpp index 4c58140c..eb693a9a 100644 --- a/client/ui/controllers/api/apiConfigsController.cpp +++ b/client/ui/controllers/api/apiConfigsController.cpp @@ -18,6 +18,7 @@ namespace { constexpr char cloak[] = "cloak"; constexpr char awg[] = "awg"; + constexpr char vless[] = "vless"; constexpr char apiEndpoint[] = "api_endpoint"; constexpr char accessToken[] = "api_key"; @@ -35,10 +36,6 @@ namespace constexpr char serviceInfo[] = "service_info"; constexpr char serviceProtocol[] = "service_protocol"; - constexpr char aesKey[] = "aes_key"; - constexpr char aesIv[] = "aes_iv"; - constexpr char aesSalt[] = "aes_salt"; - constexpr char apiPayload[] = "api_payload"; constexpr char keyPayload[] = "key_payload"; @@ -47,6 +44,169 @@ namespace constexpr char config[] = "config"; } + + struct ProtocolData + { + OpenVpnConfigurator::ConnectionData certRequest; + + QString wireGuardClientPrivKey; + QString wireGuardClientPubKey; + + QString xrayUuid; + }; + + struct GatewayRequestData + { + QString osVersion; + QString appVersion; + + QString installationUuid; + + QString userCountryCode; + QString serverCountryCode; + QString serviceType; + QString serviceProtocol; + + QJsonObject authData; + + QJsonObject toJsonObject() const + { + QJsonObject obj; + if (!osVersion.isEmpty()) { + obj[configKey::osVersion] = osVersion; + } + if (!appVersion.isEmpty()) { + obj[configKey::appVersion] = appVersion; + } + if (!installationUuid.isEmpty()) { + obj[configKey::uuid] = installationUuid; + } + if (!userCountryCode.isEmpty()) { + obj[configKey::userCountryCode] = userCountryCode; + } + if (!serverCountryCode.isEmpty()) { + obj[configKey::serverCountryCode] = serverCountryCode; + } + if (!serviceType.isEmpty()) { + obj[configKey::serviceType] = serviceType; + } + if (!serviceProtocol.isEmpty()) { + obj[configKey::serviceProtocol] = serviceProtocol; + } + if (!authData.isEmpty()) { + obj[configKey::authData] = authData; + } + return obj; + } + }; + + ProtocolData generateProtocolData(const QString &protocol) + { + ProtocolData protocolData; + if (protocol == configKey::cloak) { + protocolData.certRequest = OpenVpnConfigurator::createCertRequest(); + } else if (protocol == configKey::awg) { + auto connData = WireguardConfigurator::genClientKeys(); + protocolData.wireGuardClientPubKey = connData.clientPubKey; + protocolData.wireGuardClientPrivKey = connData.clientPrivKey; + } else if (protocol == configKey::vless) { + protocolData.xrayUuid = QUuid::createUuid().toString(QUuid::WithoutBraces); + } + + return protocolData; + } + + void appendProtocolDataToApiPayload(const QString &protocol, const ProtocolData &protocolData, QJsonObject &apiPayload) + { + if (protocol == configKey::cloak) { + apiPayload[configKey::certificate] = protocolData.certRequest.request; + } else if (protocol == configKey::awg) { + apiPayload[configKey::publicKey] = protocolData.wireGuardClientPubKey; + } else if (protocol == configKey::vless) { + apiPayload[configKey::publicKey] = protocolData.xrayUuid; + } + } + + ErrorCode fillServerConfig(const QString &protocol, const ProtocolData &apiPayloadData, const QByteArray &apiResponseBody, + QJsonObject &serverConfig) + { + QString data = QJsonDocument::fromJson(apiResponseBody).object().value(config_key::config).toString(); + + data.replace("vpn://", ""); + QByteArray ba = QByteArray::fromBase64(data.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + + if (ba.isEmpty()) { + qDebug() << "empty vpn key"; + return ErrorCode::ApiConfigEmptyError; + } + + QByteArray ba_uncompressed = qUncompress(ba); + if (!ba_uncompressed.isEmpty()) { + ba = ba_uncompressed; + } + + QString configStr = ba; + if (protocol == configKey::cloak) { + configStr.replace("", "\n"); + configStr.replace("$OPENVPN_PRIV_KEY", apiPayloadData.certRequest.privKey); + } else if (protocol == configKey::awg) { + configStr.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", apiPayloadData.wireGuardClientPrivKey); + auto newServerConfig = QJsonDocument::fromJson(configStr.toUtf8()).object(); + auto containers = newServerConfig.value(config_key::containers).toArray(); + if (containers.isEmpty()) { + qDebug() << "missing containers field"; + return ErrorCode::ApiConfigEmptyError; + } + auto container = containers.at(0).toObject(); + QString containerName = ContainerProps::containerTypeToString(DockerContainer::Awg); + auto serverProtocolConfig = container.value(containerName).toObject(); + auto clientProtocolConfig = + QJsonDocument::fromJson(serverProtocolConfig.value(config_key::last_config).toString().toUtf8()).object(); + serverProtocolConfig[config_key::junkPacketCount] = clientProtocolConfig.value(config_key::junkPacketCount); + serverProtocolConfig[config_key::junkPacketMinSize] = clientProtocolConfig.value(config_key::junkPacketMinSize); + serverProtocolConfig[config_key::junkPacketMaxSize] = clientProtocolConfig.value(config_key::junkPacketMaxSize); + serverProtocolConfig[config_key::initPacketJunkSize] = clientProtocolConfig.value(config_key::initPacketJunkSize); + serverProtocolConfig[config_key::responsePacketJunkSize] = clientProtocolConfig.value(config_key::responsePacketJunkSize); + serverProtocolConfig[config_key::initPacketMagicHeader] = clientProtocolConfig.value(config_key::initPacketMagicHeader); + serverProtocolConfig[config_key::responsePacketMagicHeader] = clientProtocolConfig.value(config_key::responsePacketMagicHeader); + serverProtocolConfig[config_key::underloadPacketMagicHeader] = clientProtocolConfig.value(config_key::underloadPacketMagicHeader); + serverProtocolConfig[config_key::transportPacketMagicHeader] = clientProtocolConfig.value(config_key::transportPacketMagicHeader); + container[containerName] = serverProtocolConfig; + containers.replace(0, container); + newServerConfig[config_key::containers] = containers; + configStr = QString(QJsonDocument(newServerConfig).toJson()); + } + + QJsonObject newServerConfig = QJsonDocument::fromJson(configStr.toUtf8()).object(); + serverConfig[config_key::dns1] = newServerConfig.value(config_key::dns1); + serverConfig[config_key::dns2] = newServerConfig.value(config_key::dns2); + serverConfig[config_key::containers] = newServerConfig.value(config_key::containers); + serverConfig[config_key::hostName] = newServerConfig.value(config_key::hostName); + + if (newServerConfig.value(config_key::configVersion).toInt() == apiDefs::ConfigSource::AmneziaGateway) { + serverConfig[config_key::configVersion] = newServerConfig.value(config_key::configVersion); + serverConfig[config_key::description] = newServerConfig.value(config_key::description); + serverConfig[config_key::name] = newServerConfig.value(config_key::name); + } + + auto defaultContainer = newServerConfig.value(config_key::defaultContainer).toString(); + serverConfig[config_key::defaultContainer] = defaultContainer; + + QVariantMap map = serverConfig.value(configKey::apiConfig).toObject().toVariantMap(); + map.insert(newServerConfig.value(configKey::apiConfig).toObject().toVariantMap()); + auto apiConfig = QJsonObject::fromVariantMap(map); + + if (newServerConfig.value(config_key::configVersion).toInt() == apiDefs::ConfigSource::AmneziaGateway) { + apiConfig.insert(apiDefs::key::supportedProtocols, + QJsonDocument::fromJson(apiResponseBody).object().value(apiDefs::key::supportedProtocols).toArray()); + } + + serverConfig[configKey::apiConfig] = apiConfig; + + qDebug() << serverConfig; + + return ErrorCode::NoError; + } } ApiConfigsController::ApiConfigsController(const QSharedPointer &serversModel, @@ -63,24 +223,26 @@ bool ApiConfigsController::exportNativeConfig(const QString &serverCountryCode, return false; } - GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs, - m_settings->isStrictKillSwitchEnabled()); - auto serverConfigObject = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex()); auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject(); - QString protocol = apiConfigObject.value(configKey::serviceProtocol).toString(); - ApiPayloadData apiPayloadData = generateApiPayloadData(protocol); + GatewayRequestData gatewayRequestData { QSysInfo::productType(), + QString(APP_VERSION), + m_settings->getInstallationUuid(true), + apiConfigObject.value(configKey::userCountryCode).toString(), + serverCountryCode, + apiConfigObject.value(configKey::serviceType).toString(), + m_apiServicesModel->getSelectedServiceProtocol(), + serverConfigObject.value(configKey::authData).toObject() }; - QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData); - apiPayload[configKey::userCountryCode] = apiConfigObject.value(configKey::userCountryCode); - apiPayload[configKey::serverCountryCode] = serverCountryCode; - apiPayload[configKey::serviceType] = apiConfigObject.value(configKey::serviceType); - apiPayload[configKey::authData] = serverConfigObject.value(configKey::authData); - apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION); + QString protocol = apiConfigObject.value(configKey::serviceProtocol).toString(); + ProtocolData protocolData = generateProtocolData(protocol); + + QJsonObject apiPayload = gatewayRequestData.toJsonObject(); + appendProtocolDataToApiPayload(gatewayRequestData.serviceProtocol, protocolData, apiPayload); QByteArray responseBody; - ErrorCode errorCode = gatewayController.post(QString("%1v1/native_config"), apiPayload, responseBody); + ErrorCode errorCode = executeRequest(QString("%1v1/native_config"), apiPayload, responseBody); if (errorCode != ErrorCode::NoError) { emit errorOccurred(errorCode); return false; @@ -88,7 +250,7 @@ bool ApiConfigsController::exportNativeConfig(const QString &serverCountryCode, QJsonObject jsonConfig = QJsonDocument::fromJson(responseBody).object(); QString nativeConfig = jsonConfig.value(configKey::config).toString(); - nativeConfig.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", apiPayloadData.wireGuardClientPrivKey); + nativeConfig.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", protocolData.wireGuardClientPrivKey); SystemController::saveFile(fileName, nativeConfig); return true; @@ -96,24 +258,22 @@ bool ApiConfigsController::exportNativeConfig(const QString &serverCountryCode, bool ApiConfigsController::revokeNativeConfig(const QString &serverCountryCode) { - GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs, - m_settings->isStrictKillSwitchEnabled()); - auto serverConfigObject = m_serversModel->getServerConfig(m_serversModel->getProcessedServerIndex()); auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject(); - QString protocol = apiConfigObject.value(configKey::serviceProtocol).toString(); - ApiPayloadData apiPayloadData = generateApiPayloadData(protocol); + GatewayRequestData gatewayRequestData { QSysInfo::productType(), + QString(APP_VERSION), + m_settings->getInstallationUuid(true), + apiConfigObject.value(configKey::userCountryCode).toString(), + serverCountryCode, + apiConfigObject.value(configKey::serviceType).toString(), + m_apiServicesModel->getSelectedServiceProtocol(), + serverConfigObject.value(configKey::authData).toObject() }; - QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData); - apiPayload[configKey::userCountryCode] = apiConfigObject.value(configKey::userCountryCode); - apiPayload[configKey::serverCountryCode] = serverCountryCode; - apiPayload[configKey::serviceType] = apiConfigObject.value(configKey::serviceType); - apiPayload[configKey::authData] = serverConfigObject.value(configKey::authData); - apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION); + QJsonObject apiPayload = gatewayRequestData.toJsonObject(); QByteArray responseBody; - ErrorCode errorCode = gatewayController.post(QString("%1v1/revoke_native_config"), apiPayload, responseBody); + ErrorCode errorCode = executeRequest(QString("%1v1/revoke_native_config"), apiPayload, responseBody); if (errorCode != ErrorCode::NoError && errorCode != ErrorCode::ApiNotFoundError) { emit errorOccurred(errorCode); return false; @@ -144,14 +304,11 @@ void ApiConfigsController::copyVpnKeyToClipboard() bool ApiConfigsController::fillAvailableServices() { - GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs, - m_settings->isStrictKillSwitchEnabled()); - QJsonObject apiPayload; apiPayload[configKey::osVersion] = QSysInfo::productType(); QByteArray responseBody; - ErrorCode errorCode = gatewayController.post(QString("%1v1/services"), apiPayload, responseBody); + ErrorCode errorCode = executeRequest(QString("%1v1/services"), apiPayload, responseBody); if (errorCode == ErrorCode::NoError) { if (!responseBody.contains("services")) { errorCode = ErrorCode::ApiServicesMissingError; @@ -170,34 +327,36 @@ bool ApiConfigsController::fillAvailableServices() bool ApiConfigsController::importServiceFromGateway() { - if (m_serversModel->isServerFromApiAlreadyExists(m_apiServicesModel->getCountryCode(), m_apiServicesModel->getSelectedServiceType(), - m_apiServicesModel->getSelectedServiceProtocol())) { + GatewayRequestData gatewayRequestData { QSysInfo::productType(), + QString(APP_VERSION), + m_settings->getInstallationUuid(true), + m_apiServicesModel->getCountryCode(), + "", + m_apiServicesModel->getSelectedServiceType(), + m_apiServicesModel->getSelectedServiceProtocol(), + QJsonObject() }; + + if (m_serversModel->isServerFromApiAlreadyExists(gatewayRequestData.userCountryCode, gatewayRequestData.serviceType, + gatewayRequestData.serviceProtocol)) { emit errorOccurred(ErrorCode::ApiConfigAlreadyAdded); return false; } - GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs, - m_settings->isStrictKillSwitchEnabled()); + ProtocolData protocolData = generateProtocolData(gatewayRequestData.serviceProtocol); - auto installationUuid = m_settings->getInstallationUuid(true); - auto userCountryCode = m_apiServicesModel->getCountryCode(); - auto serviceType = m_apiServicesModel->getSelectedServiceType(); - auto serviceProtocol = m_apiServicesModel->getSelectedServiceProtocol(); - - ApiPayloadData apiPayloadData = generateApiPayloadData(serviceProtocol); - - QJsonObject apiPayload = fillApiPayload(serviceProtocol, apiPayloadData); - apiPayload[configKey::userCountryCode] = userCountryCode; - apiPayload[configKey::serviceType] = serviceType; - apiPayload[configKey::uuid] = installationUuid; - apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION); + QJsonObject apiPayload = gatewayRequestData.toJsonObject(); + appendProtocolDataToApiPayload(gatewayRequestData.serviceProtocol, protocolData, apiPayload); QByteArray responseBody; - ErrorCode errorCode = gatewayController.post(QString("%1v1/config"), apiPayload, responseBody); + ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody); QJsonObject serverConfig; if (errorCode == ErrorCode::NoError) { - fillServerConfig(serviceProtocol, apiPayloadData, responseBody, serverConfig); + errorCode = fillServerConfig(gatewayRequestData.serviceProtocol, protocolData, responseBody, serverConfig); + if (errorCode != ErrorCode::NoError) { + emit errorOccurred(errorCode); + return false; + } QJsonObject apiConfig = serverConfig.value(configKey::apiConfig).toObject(); apiConfig.insert(configKey::userCountryCode, m_apiServicesModel->getCountryCode()); @@ -218,39 +377,33 @@ bool ApiConfigsController::importServiceFromGateway() bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const QString &newCountryCode, const QString &newCountryName, bool reloadServiceConfig) { - GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs, - m_settings->isStrictKillSwitchEnabled()); - auto serverConfig = m_serversModel->getServerConfig(serverIndex); auto apiConfig = serverConfig.value(configKey::apiConfig).toObject(); - auto authData = serverConfig.value(configKey::authData).toObject(); - auto installationUuid = m_settings->getInstallationUuid(true); - auto userCountryCode = apiConfig.value(configKey::userCountryCode).toString(); - auto serviceType = apiConfig.value(configKey::serviceType).toString(); - auto serviceProtocol = apiConfig.value(configKey::serviceProtocol).toString(); + GatewayRequestData gatewayRequestData { QSysInfo::productType(), + QString(APP_VERSION), + m_settings->getInstallationUuid(true), + apiConfig.value(configKey::userCountryCode).toString(), + newCountryCode, + apiConfig.value(configKey::serviceType).toString(), + apiConfig.value(configKey::serviceProtocol).toString(), + serverConfig.value(configKey::authData).toObject() }; - ApiPayloadData apiPayloadData = generateApiPayloadData(serviceProtocol); + ProtocolData protocolData = generateProtocolData(gatewayRequestData.serviceProtocol); - QJsonObject apiPayload = fillApiPayload(serviceProtocol, apiPayloadData); - apiPayload[configKey::userCountryCode] = userCountryCode; - apiPayload[configKey::serviceType] = serviceType; - apiPayload[configKey::uuid] = installationUuid; - apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION); - - if (!newCountryCode.isEmpty()) { - apiPayload[configKey::serverCountryCode] = newCountryCode; - } - if (!authData.isEmpty()) { - apiPayload[configKey::authData] = authData; - } + QJsonObject apiPayload = gatewayRequestData.toJsonObject(); + appendProtocolDataToApiPayload(gatewayRequestData.serviceProtocol, protocolData, apiPayload); QByteArray responseBody; - ErrorCode errorCode = gatewayController.post(QString("%1v1/config"), apiPayload, responseBody); + ErrorCode errorCode = executeRequest(QString("%1v1/config"), apiPayload, responseBody); QJsonObject newServerConfig; if (errorCode == ErrorCode::NoError) { - fillServerConfig(serviceProtocol, apiPayloadData, responseBody, newServerConfig); + errorCode = fillServerConfig(gatewayRequestData.serviceProtocol, protocolData, responseBody, newServerConfig); + if (errorCode != ErrorCode::NoError) { + emit errorOccurred(errorCode); + return false; + } QJsonObject newApiConfig = newServerConfig.value(configKey::apiConfig).toObject(); newApiConfig.insert(configKey::userCountryCode, apiConfig.value(configKey::userCountryCode)); @@ -259,7 +412,7 @@ bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const newApiConfig.insert(apiDefs::key::vpnKey, apiConfig.value(apiDefs::key::vpnKey)); newServerConfig.insert(configKey::apiConfig, newApiConfig); - newServerConfig.insert(configKey::authData, authData); + newServerConfig.insert(configKey::authData, gatewayRequestData.authData); if (serverConfig.value(config_key::nameOverriddenByUser).toBool()) { newServerConfig.insert(config_key::name, serverConfig.value(config_key::name)); @@ -294,10 +447,13 @@ bool ApiConfigsController::updateServiceFromTelegram(const int serverIndex) auto installationUuid = m_settings->getInstallationUuid(true); QString serviceProtocol = serverConfig.value(configKey::protocol).toString(); - ApiPayloadData apiPayloadData = generateApiPayloadData(serviceProtocol); + ProtocolData protocolData = generateProtocolData(serviceProtocol); - QJsonObject apiPayload = fillApiPayload(serviceProtocol, apiPayloadData); + QJsonObject apiPayload; + appendProtocolDataToApiPayload(serviceProtocol, protocolData, apiPayload); apiPayload[configKey::uuid] = installationUuid; + apiPayload[configKey::osVersion] = QSysInfo::productType(); + apiPayload[configKey::appVersion] = QString(APP_VERSION); apiPayload[configKey::accessToken] = serverConfig.value(configKey::accessToken).toString(); apiPayload[configKey::apiEndpoint] = serverConfig.value(configKey::apiEndpoint).toString(); @@ -305,7 +461,11 @@ bool ApiConfigsController::updateServiceFromTelegram(const int serverIndex) ErrorCode errorCode = gatewayController.post(QString("%1v1/proxy_config"), apiPayload, responseBody); if (errorCode == ErrorCode::NoError) { - fillServerConfig(serviceProtocol, apiPayloadData, responseBody, serverConfig); + errorCode = fillServerConfig(serviceProtocol, protocolData, responseBody, serverConfig); + if (errorCode != ErrorCode::NoError) { + emit errorOccurred(errorCode); + return false; + } m_serversModel->editServer(serverConfig, serverIndex); emit updateServerFromApiFinished(); @@ -318,9 +478,6 @@ bool ApiConfigsController::updateServiceFromTelegram(const int serverIndex) bool ApiConfigsController::deactivateDevice() { - GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs, - m_settings->isStrictKillSwitchEnabled()); - auto serverIndex = m_serversModel->getProcessedServerIndex(); auto serverConfigObject = m_serversModel->getServerConfig(serverIndex); auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject(); @@ -329,19 +486,19 @@ bool ApiConfigsController::deactivateDevice() return true; } - QString protocol = apiConfigObject.value(configKey::serviceProtocol).toString(); - ApiPayloadData apiPayloadData = generateApiPayloadData(protocol); + GatewayRequestData gatewayRequestData { QSysInfo::productType(), + QString(APP_VERSION), + m_settings->getInstallationUuid(true), + apiConfigObject.value(configKey::userCountryCode).toString(), + apiConfigObject.value(configKey::serverCountryCode).toString(), + apiConfigObject.value(configKey::serviceType).toString(), + "", + serverConfigObject.value(configKey::authData).toObject() }; - QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData); - apiPayload[configKey::userCountryCode] = apiConfigObject.value(configKey::userCountryCode); - apiPayload[configKey::serverCountryCode] = apiConfigObject.value(configKey::serverCountryCode); - apiPayload[configKey::serviceType] = apiConfigObject.value(configKey::serviceType); - apiPayload[configKey::authData] = serverConfigObject.value(configKey::authData); - apiPayload[configKey::uuid] = m_settings->getInstallationUuid(true); - apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION); + QJsonObject apiPayload = gatewayRequestData.toJsonObject(); QByteArray responseBody; - ErrorCode errorCode = gatewayController.post(QString("%1v1/revoke_config"), apiPayload, responseBody); + ErrorCode errorCode = executeRequest(QString("%1v1/revoke_config"), apiPayload, responseBody); if (errorCode != ErrorCode::NoError && errorCode != ErrorCode::ApiNotFoundError) { emit errorOccurred(errorCode); return false; @@ -355,9 +512,6 @@ bool ApiConfigsController::deactivateDevice() bool ApiConfigsController::deactivateExternalDevice(const QString &uuid, const QString &serverCountryCode) { - GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs, - m_settings->isStrictKillSwitchEnabled()); - auto serverIndex = m_serversModel->getProcessedServerIndex(); auto serverConfigObject = m_serversModel->getServerConfig(serverIndex); auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject(); @@ -366,19 +520,19 @@ bool ApiConfigsController::deactivateExternalDevice(const QString &uuid, const Q return true; } - QString protocol = apiConfigObject.value(configKey::serviceProtocol).toString(); - ApiPayloadData apiPayloadData = generateApiPayloadData(protocol); + GatewayRequestData gatewayRequestData { QSysInfo::productType(), + QString(APP_VERSION), + uuid, + apiConfigObject.value(configKey::userCountryCode).toString(), + serverCountryCode, + apiConfigObject.value(configKey::serviceType).toString(), + "", + serverConfigObject.value(configKey::authData).toObject() }; - QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData); - apiPayload[configKey::userCountryCode] = apiConfigObject.value(configKey::userCountryCode); - apiPayload[configKey::serverCountryCode] = serverCountryCode; - apiPayload[configKey::serviceType] = apiConfigObject.value(configKey::serviceType); - apiPayload[configKey::authData] = serverConfigObject.value(configKey::authData); - apiPayload[configKey::uuid] = uuid; - apiPayload[apiDefs::key::cliVersion] = QString(APP_VERSION); + QJsonObject apiPayload = gatewayRequestData.toJsonObject(); QByteArray responseBody; - ErrorCode errorCode = gatewayController.post(QString("%1v1/revoke_config"), apiPayload, responseBody); + ErrorCode errorCode = executeRequest(QString("%1v1/revoke_config"), apiPayload, responseBody); if (errorCode != ErrorCode::NoError && errorCode != ErrorCode::ApiNotFoundError) { emit errorOccurred(errorCode); return false; @@ -417,108 +571,29 @@ bool ApiConfigsController::isConfigValid() return true; } -ApiConfigsController::ApiPayloadData ApiConfigsController::generateApiPayloadData(const QString &protocol) +void ApiConfigsController::setCurrentProtocol(const QString &protocolName) { - ApiConfigsController::ApiPayloadData apiPayload; - if (protocol == configKey::cloak) { - apiPayload.certRequest = OpenVpnConfigurator::createCertRequest(); - } else if (protocol == configKey::awg) { - auto connData = WireguardConfigurator::genClientKeys(); - apiPayload.wireGuardClientPubKey = connData.clientPubKey; - apiPayload.wireGuardClientPrivKey = connData.clientPrivKey; - } - return apiPayload; + auto serverIndex = m_serversModel->getProcessedServerIndex(); + auto serverConfigObject = m_serversModel->getServerConfig(serverIndex); + auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject(); + + apiConfigObject[configKey::serviceProtocol] = protocolName; + + serverConfigObject.insert(configKey::apiConfig, apiConfigObject); + + m_serversModel->editServer(serverConfigObject, serverIndex); } -QJsonObject ApiConfigsController::fillApiPayload(const QString &protocol, const ApiPayloadData &apiPayloadData) +bool ApiConfigsController::isVlessProtocol() { - QJsonObject obj; - if (protocol == configKey::cloak) { - obj[configKey::certificate] = apiPayloadData.certRequest.request; - } else if (protocol == configKey::awg) { - obj[configKey::publicKey] = apiPayloadData.wireGuardClientPubKey; + auto serverIndex = m_serversModel->getProcessedServerIndex(); + auto serverConfigObject = m_serversModel->getServerConfig(serverIndex); + auto apiConfigObject = serverConfigObject.value(configKey::apiConfig).toObject(); + + if (apiConfigObject[configKey::serviceProtocol].toString() == "vless") { + return true; } - - obj[configKey::osVersion] = QSysInfo::productType(); - obj[configKey::appVersion] = QString(APP_VERSION); - - return obj; -} - -void ApiConfigsController::fillServerConfig(const QString &protocol, const ApiPayloadData &apiPayloadData, - const QByteArray &apiResponseBody, QJsonObject &serverConfig) -{ - QString data = QJsonDocument::fromJson(apiResponseBody).object().value(config_key::config).toString(); - - data.replace("vpn://", ""); - QByteArray ba = QByteArray::fromBase64(data.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); - - if (ba.isEmpty()) { - emit errorOccurred(ErrorCode::ApiConfigEmptyError); - return; - } - - QByteArray ba_uncompressed = qUncompress(ba); - if (!ba_uncompressed.isEmpty()) { - ba = ba_uncompressed; - } - - QString configStr = ba; - if (protocol == configKey::cloak) { - configStr.replace("", "\n"); - configStr.replace("$OPENVPN_PRIV_KEY", apiPayloadData.certRequest.privKey); - } else if (protocol == configKey::awg) { - configStr.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", apiPayloadData.wireGuardClientPrivKey); - auto newServerConfig = QJsonDocument::fromJson(configStr.toUtf8()).object(); - auto containers = newServerConfig.value(config_key::containers).toArray(); - if (containers.isEmpty()) { - return; // todo process error - } - auto container = containers.at(0).toObject(); - QString containerName = ContainerProps::containerTypeToString(DockerContainer::Awg); - auto containerConfig = container.value(containerName).toObject(); - auto protocolConfig = QJsonDocument::fromJson(containerConfig.value(config_key::last_config).toString().toUtf8()).object(); - containerConfig[config_key::junkPacketCount] = protocolConfig.value(config_key::junkPacketCount); - containerConfig[config_key::junkPacketMinSize] = protocolConfig.value(config_key::junkPacketMinSize); - containerConfig[config_key::junkPacketMaxSize] = protocolConfig.value(config_key::junkPacketMaxSize); - containerConfig[config_key::initPacketJunkSize] = protocolConfig.value(config_key::initPacketJunkSize); - containerConfig[config_key::responsePacketJunkSize] = protocolConfig.value(config_key::responsePacketJunkSize); - containerConfig[config_key::initPacketMagicHeader] = protocolConfig.value(config_key::initPacketMagicHeader); - containerConfig[config_key::responsePacketMagicHeader] = protocolConfig.value(config_key::responsePacketMagicHeader); - containerConfig[config_key::underloadPacketMagicHeader] = protocolConfig.value(config_key::underloadPacketMagicHeader); - containerConfig[config_key::transportPacketMagicHeader] = protocolConfig.value(config_key::transportPacketMagicHeader); - container[containerName] = containerConfig; - containers.replace(0, container); - newServerConfig[config_key::containers] = containers; - configStr = QString(QJsonDocument(newServerConfig).toJson()); - } - - QJsonObject newServerConfig = QJsonDocument::fromJson(configStr.toUtf8()).object(); - serverConfig[config_key::dns1] = newServerConfig.value(config_key::dns1); - serverConfig[config_key::dns2] = newServerConfig.value(config_key::dns2); - serverConfig[config_key::containers] = newServerConfig.value(config_key::containers); - serverConfig[config_key::hostName] = newServerConfig.value(config_key::hostName); - - if (newServerConfig.value(config_key::configVersion).toInt() == apiDefs::ConfigSource::AmneziaGateway) { - serverConfig[config_key::configVersion] = newServerConfig.value(config_key::configVersion); - serverConfig[config_key::description] = newServerConfig.value(config_key::description); - serverConfig[config_key::name] = newServerConfig.value(config_key::name); - } - - auto defaultContainer = newServerConfig.value(config_key::defaultContainer).toString(); - serverConfig[config_key::defaultContainer] = defaultContainer; - - QVariantMap map = serverConfig.value(configKey::apiConfig).toObject().toVariantMap(); - map.insert(newServerConfig.value(configKey::apiConfig).toObject().toVariantMap()); - auto apiConfig = QJsonObject::fromVariantMap(map); - - if (newServerConfig.value(config_key::configVersion).toInt() == apiDefs::ConfigSource::AmneziaGateway) { - apiConfig.insert(configKey::serviceInfo, QJsonDocument::fromJson(apiResponseBody).object().value(configKey::serviceInfo).toObject()); - } - - serverConfig[configKey::apiConfig] = apiConfig; - - return; + return false; } QList ApiConfigsController::getQrCodes() @@ -535,3 +610,10 @@ QString ApiConfigsController::getVpnKey() { return m_vpnKey; } + +ErrorCode ApiConfigsController::executeRequest(const QString &endpoint, const QJsonObject &apiPayload, QByteArray &responseBody) +{ + GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs, + m_settings->isStrictKillSwitchEnabled()); + return gatewayController.post(endpoint, apiPayload, responseBody); +} diff --git a/client/ui/controllers/api/apiConfigsController.h b/client/ui/controllers/api/apiConfigsController.h index 2fe981e4..a04a142c 100644 --- a/client/ui/controllers/api/apiConfigsController.h +++ b/client/ui/controllers/api/apiConfigsController.h @@ -35,6 +35,9 @@ public slots: bool isConfigValid(); + void setCurrentProtocol(const QString &protocolName); + bool isVlessProtocol(); + signals: void errorOccurred(ErrorCode errorCode); @@ -46,23 +49,12 @@ signals: void vpnKeyExportReady(); private: - struct ApiPayloadData - { - OpenVpnConfigurator::ConnectionData certRequest; - - QString wireGuardClientPrivKey; - QString wireGuardClientPubKey; - }; - - ApiPayloadData generateApiPayloadData(const QString &protocol); - QJsonObject fillApiPayload(const QString &protocol, const ApiPayloadData &apiPayloadData); - void fillServerConfig(const QString &protocol, const ApiPayloadData &apiPayloadData, const QByteArray &apiResponseBody, - QJsonObject &serverConfig); - QList getQrCodes(); int getQrCodesCount(); QString getVpnKey(); + ErrorCode executeRequest(const QString &endpoint, const QJsonObject &apiPayload, QByteArray &responseBody); + QList m_qrCodes; QString m_vpnKey; diff --git a/client/ui/models/api/apiAccountInfoModel.cpp b/client/ui/models/api/apiAccountInfoModel.cpp index fdd4e2ca..bd3027a4 100644 --- a/client/ui/models/api/apiAccountInfoModel.cpp +++ b/client/ui/models/api/apiAccountInfoModel.cpp @@ -75,6 +75,12 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const } return false; } + case IsProtocolSelectionSupportedRole: { + if (m_accountInfoData.supportedProtocols.size() > 1) { + return true; + } + return false; + } } return QVariant(); @@ -95,6 +101,10 @@ void ApiAccountInfoModel::updateModel(const QJsonObject &accountInfoObject, cons accountInfoData.configType = apiUtils::getConfigType(serverConfig); + for (const auto &protocol : accountInfoObject.value(apiDefs::key::supportedProtocols).toArray()) { + accountInfoData.supportedProtocols.push_back(protocol.toString()); + } + m_accountInfoData = accountInfoData; m_supportInfo = accountInfoObject.value(apiDefs::key::supportInfo).toObject(); @@ -159,6 +169,7 @@ QHash ApiAccountInfoModel::roleNames() const roles[ServiceDescriptionRole] = "serviceDescription"; roles[IsComponentVisibleRole] = "isComponentVisible"; roles[HasExpiredWorkerRole] = "hasExpiredWorker"; + roles[IsProtocolSelectionSupportedRole] = "isProtocolSelectionSupported"; return roles; } diff --git a/client/ui/models/api/apiAccountInfoModel.h b/client/ui/models/api/apiAccountInfoModel.h index ead92488..f0203967 100644 --- a/client/ui/models/api/apiAccountInfoModel.h +++ b/client/ui/models/api/apiAccountInfoModel.h @@ -18,7 +18,8 @@ public: ServiceDescriptionRole, EndDateRole, IsComponentVisibleRole, - HasExpiredWorkerRole + HasExpiredWorkerRole, + IsProtocolSelectionSupportedRole }; explicit ApiAccountInfoModel(QObject *parent = nullptr); @@ -51,6 +52,8 @@ private: int maxDeviceCount; apiDefs::ConfigType configType; + + QStringList supportedProtocols; }; AccountInfoData m_accountInfoData; diff --git a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml index 93118755..75832fa6 100644 --- a/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml +++ b/client/ui/qml/Pages2/PageSettingsApiServerInfo.qml @@ -158,6 +158,32 @@ PageType { readonly property bool isVisibleForAmneziaFree: ApiAccountInfoModel.data("isComponentVisible") + SwitcherType { + id: switcher + + readonly property bool isVlessProtocol: ApiConfigsController.isVlessProtocol() + + Layout.fillWidth: true + Layout.topMargin: 24 + Layout.rightMargin: 16 + Layout.leftMargin: 16 + + visible: ApiAccountInfoModel.data("isProtocolSelectionSupported") + + text: qsTr("Use VLESS protocol") + checked: switcher.isVlessProtocol + onToggled: function() { + if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) { + PageController.showNotificationMessage(qsTr("Cannot change protocol during active connection")) + } else { + PageController.showBusyIndicator(true) + ApiConfigsController.setCurrentProtocol(switcher.isVlessProtocol ? "awg" : "vless") + ApiConfigsController.updateServiceFromGateway(ServersModel.processedIndex, "", "", true) + PageController.showBusyIndicator(false) + } + } + } + WarningType { id: warning From f8bea71716826fe757be584b655f5d1f8133e6e2 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 7 Jul 2025 10:26:16 +0800 Subject: [PATCH 35/40] chore: temporarily hide the strict killswitch --- client/ui/qml/Pages2/PageSettingsKillSwitch.qml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/client/ui/qml/Pages2/PageSettingsKillSwitch.qml b/client/ui/qml/Pages2/PageSettingsKillSwitch.qml index 444eb415..ca1cd0d4 100644 --- a/client/ui/qml/Pages2/PageSettingsKillSwitch.qml +++ b/client/ui/qml/Pages2/PageSettingsKillSwitch.qml @@ -62,7 +62,8 @@ PageType { Layout.leftMargin: 16 Layout.rightMargin: 16 - enabled: SettingsController.isKillSwitchEnabled && !ConnectionController.isConnected + visible: false + // enabled: SettingsController.isKillSwitchEnabled && !ConnectionController.isConnected checked: !SettingsController.strictKillSwitchEnabled text: qsTr("Soft KillSwitch") @@ -73,7 +74,9 @@ PageType { } } - DividerType {} + DividerType { + visible: false + } VerticalRadioButton { id: strictKillSwitch From 42661618dc060d05794cc0dd503ed03cf579cf5d Mon Sep 17 00:00:00 2001 From: Nethius Date: Mon, 7 Jul 2025 10:44:35 +0800 Subject: [PATCH 36/40] chore: bump version (#1696) --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 424dcf3a..fec613de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 4.8.7.2 +project(${PROJECT} VERSION 4.8.8.1 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) @@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d") set(RELEASE_DATE "${CURRENT_DATE}") set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) -set(APP_ANDROID_VERSION_CODE 2086) +set(APP_ANDROID_VERSION_CODE 2087) if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") set(MZ_PLATFORM_NAME "linux") From 2380cd5cfbe75b610c2bba4c3748aef5982f50fa Mon Sep 17 00:00:00 2001 From: Nethius Date: Mon, 7 Jul 2025 12:03:25 +0800 Subject: [PATCH 37/40] feat: amneziawg 1.5 support (#1692) * Version bump 4.2.1.0 * feat: add special handshake params to ui * feat: finish adding params * feat: android/ios & fix qml * chore: fix android impl & update 3rd-prebuilt branch * chore: trigger build with windows build * fix: special handshake params to client * chore: update submodule * feat: s3, s4 * chore: update submodule * feat: s3 s4 cont * fix: kt set * chore: update submodule * feat: add default values for s3, s4 * fix: make new parameters optional * chore: update submodules * chore: restore translation files * fix: fixed awg native config import with new junk * chore: restore translation files * AWG v1.5 Build * refactoring: removed s3 s4 fileds from ui part * chore: update link to amneziawg-apple --------- Co-authored-by: pokamest Co-authored-by: Mark Puha Co-authored-by: albexk Co-authored-by: Mykola Baibuz --- .gitmodules | 1 + client/3rd-prebuilt | 2 +- client/3rd/amneziawg-apple | 2 +- .../vpn/protocol/wireguard/Wireguard.kt | 11 + .../vpn/protocol/wireguard/WireguardConfig.kt | 59 +++++- client/configurators/awg_configurator.cpp | 15 ++ client/core/controllers/serverController.cpp | 18 +- client/daemon/daemon.cpp | 37 +++- client/daemon/interfaceconfig.cpp | 16 ++ client/daemon/interfaceconfig.h | 5 + client/mozilla/localsocketcontroller.cpp | 39 +++- client/platforms/ios/WGConfig.swift | 22 +- client/platforms/ios/ios_controller.mm | 26 ++- .../linux/daemon/wireguardutilslinux.cpp | 16 ++ .../macos/daemon/wireguardutilsmacos.cpp | 16 ++ client/protocols/protocols_defs.h | 23 ++ client/resources.qrc | 1 + client/server_scripts/awg/Dockerfile | 5 +- .../server_scripts/awg/configure_container.sh | 1 + .../controllers/api/apiConfigsController.cpp | 18 ++ client/ui/controllers/importController.cpp | 56 +++-- client/ui/controllers/installController.cpp | 56 ++++- client/ui/models/protocols/awgConfigModel.cpp | 109 +++++++++- client/ui/models/protocols/awgConfigModel.h | 33 ++- client/ui/qml/Components/AwgTextField.qml | 15 ++ .../Pages2/PageProtocolAwgClientSettings.qml | 198 +++++++++++++----- .../ui/qml/Pages2/PageProtocolAwgSettings.qml | 144 +++++-------- 27 files changed, 758 insertions(+), 186 deletions(-) create mode 100644 client/ui/qml/Components/AwgTextField.qml diff --git a/.gitmodules b/.gitmodules index decab9b7..90edb582 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,6 +7,7 @@ [submodule "client/3rd-prebuilt"] path = client/3rd-prebuilt url = https://github.com/amnezia-vpn/3rd-prebuilt + branch = feature/special-handshake [submodule "client/3rd/amneziawg-apple"] path = client/3rd/amneziawg-apple url = https://github.com/amnezia-vpn/amneziawg-apple diff --git a/client/3rd-prebuilt b/client/3rd-prebuilt index 0f3748ef..840b7b07 160000 --- a/client/3rd-prebuilt +++ b/client/3rd-prebuilt @@ -1 +1 @@ -Subproject commit 0f3748efd7cc04e0c914304b68931f925bed1259 +Subproject commit 840b7b070e6ac8b90dda2fac6e98859b23727c0c diff --git a/client/3rd/amneziawg-apple b/client/3rd/amneziawg-apple index 76e7db55..811af0a8 160000 --- a/client/3rd/amneziawg-apple +++ b/client/3rd/amneziawg-apple @@ -1 +1 @@ -Subproject commit 76e7db556a6d7e2582f9481df91db188a46c009c +Subproject commit 811af0a83b3faeade89a9093a588595666d32066 diff --git a/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/Wireguard.kt b/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/Wireguard.kt index 80cab96d..42a27de4 100644 --- a/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/Wireguard.kt +++ b/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/Wireguard.kt @@ -120,10 +120,21 @@ open class Wireguard : Protocol() { configData.optStringOrNull("Jmax")?.let { setJmax(it.toInt()) } configData.optStringOrNull("S1")?.let { setS1(it.toInt()) } configData.optStringOrNull("S2")?.let { setS2(it.toInt()) } + configData.optStringOrNull("S3")?.let { setS3(it.toInt()) } + configData.optStringOrNull("S4")?.let { setS4(it.toInt()) } configData.optStringOrNull("H1")?.let { setH1(it.toLong()) } configData.optStringOrNull("H2")?.let { setH2(it.toLong()) } configData.optStringOrNull("H3")?.let { setH3(it.toLong()) } configData.optStringOrNull("H4")?.let { setH4(it.toLong()) } + configData.optStringOrNull("I1")?.let { setI1(it) } + configData.optStringOrNull("I2")?.let { setI2(it) } + configData.optStringOrNull("I3")?.let { setI3(it) } + configData.optStringOrNull("I4")?.let { setI4(it) } + configData.optStringOrNull("I5")?.let { setI5(it) } + configData.optStringOrNull("J1")?.let { setJ1(it) } + configData.optStringOrNull("J2")?.let { setJ2(it) } + configData.optStringOrNull("J3")?.let { setJ3(it) } + configData.optStringOrNull("Itime")?.let { setItime(it.toInt()) } } private fun start(config: WireguardConfig, vpnBuilder: Builder, protect: (Int) -> Boolean) { diff --git a/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/WireguardConfig.kt b/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/WireguardConfig.kt index 7ae3d43b..2dfbbae8 100644 --- a/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/WireguardConfig.kt +++ b/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/WireguardConfig.kt @@ -20,10 +20,21 @@ open class WireguardConfig protected constructor( val jmax: Int?, val s1: Int?, val s2: Int?, + val s3: Int?, + val s4: Int?, val h1: Long?, val h2: Long?, val h3: Long?, - val h4: Long? + val h4: Long?, + var i1: String?, + var i2: String?, + var i3: String?, + var i4: String?, + var i5: String?, + var j1: String?, + var j2: String?, + var j3: String?, + var itime: Int? ) : ProtocolConfig(protocolConfigBuilder) { protected constructor(builder: Builder) : this( @@ -39,10 +50,21 @@ open class WireguardConfig protected constructor( builder.jmax, builder.s1, builder.s2, + builder.s3, + builder.s4, builder.h1, builder.h2, builder.h3, - builder.h4 + builder.h4, + builder.i1, + builder.i2, + builder.i3, + builder.i4, + builder.i5, + builder.j1, + builder.j2, + builder.j3, + builder.itime ) fun toWgUserspaceString(): String = with(StringBuilder()) { @@ -61,10 +83,21 @@ open class WireguardConfig protected constructor( appendLine("jmax=$jmax") appendLine("s1=$s1") appendLine("s2=$s2") + s3?.let { appendLine("s3=$it") } + s4?.let { appendLine("s4=$it") } appendLine("h1=$h1") appendLine("h2=$h2") appendLine("h3=$h3") appendLine("h4=$h4") + i1?.let { appendLine("i1=$it") } + i2?.let { appendLine("i2=$it") } + i3?.let { appendLine("i3=$it") } + i4?.let { appendLine("i4=$it") } + i5?.let { appendLine("i5=$it") } + j1?.let { appendLine("j1=$it") } + j2?.let { appendLine("j2=$it") } + j3?.let { appendLine("j3=$it") } + itime?.let { appendLine("itime=$it") } } } @@ -117,10 +150,21 @@ open class WireguardConfig protected constructor( internal var jmax: Int? = null internal var s1: Int? = null internal var s2: Int? = null + internal var s3: Int? = null + internal var s4: Int? = null internal var h1: Long? = null internal var h2: Long? = null internal var h3: Long? = null internal var h4: Long? = null + internal var i1: String? = null + internal var i2: String? = null + internal var i3: String? = null + internal var i4: String? = null + internal var i5: String? = null + internal var j1: String? = null + internal var j2: String? = null + internal var j3: String? = null + internal var itime: Int? = null fun setEndpoint(endpoint: InetEndpoint) = apply { this.endpoint = endpoint } @@ -139,10 +183,21 @@ open class WireguardConfig protected constructor( fun setJmax(jmax: Int) = apply { this.jmax = jmax } fun setS1(s1: Int) = apply { this.s1 = s1 } fun setS2(s2: Int) = apply { this.s2 = s2 } + fun setS3(s3: Int) = apply { this.s3 = s3 } + fun setS4(s4: Int) = apply { this.s4 = s4 } fun setH1(h1: Long) = apply { this.h1 = h1 } fun setH2(h2: Long) = apply { this.h2 = h2 } fun setH3(h3: Long) = apply { this.h3 = h3 } fun setH4(h4: Long) = apply { this.h4 = h4 } + fun setI1(i1: String) = apply { this.i1 = i1 } + fun setI2(i2: String) = apply { this.i2 = i2 } + fun setI3(i3: String) = apply { this.i3 = i3 } + fun setI4(i4: String) = apply { this.i4 = i4 } + fun setI5(i5: String) = apply { this.i5 = i5 } + fun setJ1(j1: String) = apply { this.j1 = j1 } + fun setJ2(j2: String) = apply { this.j2 = j2 } + fun setJ3(j3: String) = apply { this.j3 = j3 } + fun setItime(itime: Int) = apply { this.itime = itime } override fun build(): WireguardConfig = configBuild().run { WireguardConfig(this@Builder) } } diff --git a/client/configurators/awg_configurator.cpp b/client/configurators/awg_configurator.cpp index 21b61ba4..f83acb19 100644 --- a/client/configurators/awg_configurator.cpp +++ b/client/configurators/awg_configurator.cpp @@ -1,4 +1,5 @@ #include "awg_configurator.h" +#include "protocols/protocols_defs.h" #include #include @@ -39,6 +40,20 @@ QString AwgConfigurator::createConfig(const ServerCredentials &credentials, Dock jsonConfig[config_key::responsePacketMagicHeader] = configMap.value(config_key::responsePacketMagicHeader); jsonConfig[config_key::underloadPacketMagicHeader] = configMap.value(config_key::underloadPacketMagicHeader); jsonConfig[config_key::transportPacketMagicHeader] = configMap.value(config_key::transportPacketMagicHeader); + + // jsonConfig[config_key::cookieReplyPacketJunkSize] = configMap.value(config_key::cookieReplyPacketJunkSize); + // jsonConfig[config_key::transportPacketJunkSize] = configMap.value(config_key::transportPacketJunkSize); + + // jsonConfig[config_key::specialJunk1] = configMap.value(amnezia::config_key::specialJunk1); + // jsonConfig[config_key::specialJunk2] = configMap.value(amnezia::config_key::specialJunk2); + // jsonConfig[config_key::specialJunk3] = configMap.value(amnezia::config_key::specialJunk3); + // jsonConfig[config_key::specialJunk4] = configMap.value(amnezia::config_key::specialJunk4); + // jsonConfig[config_key::specialJunk5] = configMap.value(amnezia::config_key::specialJunk5); + // jsonConfig[config_key::controlledJunk1] = configMap.value(amnezia::config_key::controlledJunk1); + // jsonConfig[config_key::controlledJunk2] = configMap.value(amnezia::config_key::controlledJunk2); + // jsonConfig[config_key::controlledJunk3] = configMap.value(amnezia::config_key::controlledJunk3); + // jsonConfig[config_key::specialHandshakeTimeout] = configMap.value(amnezia::config_key::specialHandshakeTimeout); + jsonConfig[config_key::mtu] = containerConfig.value(ProtocolProps::protoToString(Proto::Awg)).toObject().value(config_key::mtu).toString(protocols::awg::defaultMtu); diff --git a/client/core/controllers/serverController.cpp b/client/core/controllers/serverController.cpp index a61a638b..3c24edea 100644 --- a/client/core/controllers/serverController.cpp +++ b/client/core/controllers/serverController.cpp @@ -349,7 +349,7 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c if ((oldProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress) != newProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress)) || (oldProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort) - != newProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort)) + != newProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort)) || (oldProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount) != newProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount)) || (oldProtoConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize) @@ -366,8 +366,13 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c != newProtoConfig.value(config_key::responsePacketMagicHeader).toString(protocols::awg::defaultResponsePacketMagicHeader)) || (oldProtoConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader) != newProtoConfig.value(config_key::underloadPacketMagicHeader).toString(protocols::awg::defaultUnderloadPacketMagicHeader)) - || (oldProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader) - != newProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader))) + || (oldProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader)) + != newProtoConfig.value(config_key::transportPacketMagicHeader).toString(protocols::awg::defaultTransportPacketMagicHeader)) + // || (oldProtoConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize) + // != newProtoConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize)) + // || (oldProtoConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize) + // != newProtoConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize)) + return true; } @@ -375,7 +380,7 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c if ((oldProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress) != newProtoConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress)) || (oldProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort) - != newProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort))) + != newProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort))) return true; } @@ -455,7 +460,7 @@ ErrorCode ServerController::buildContainerWorker(const ServerCredentials &creden runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::build_container), genVarsForScript(credentials, container, config)), cbReadStdOut, cbReadStdErr); - + if (stdOut.contains("doesn't work on cgroups v2")) return ErrorCode::ServerDockerOnCgroupsV2; if (stdOut.contains("cgroup mountpoint does not exist")) @@ -641,6 +646,9 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential vars.append({ { "$UNDERLOAD_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::underloadPacketMagicHeader).toString() } }); vars.append({ { "$TRANSPORT_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::transportPacketMagicHeader).toString() } }); + vars.append({ { "$COOKIE_REPLY_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::cookieReplyPacketJunkSize).toString() } }); + vars.append({ { "$TRANSPORT_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::transportPacketJunkSize).toString() } }); + // Socks5 proxy vars vars.append({ { "$SOCKS5_PROXY_PORT", socks5ProxyConfig.value(config_key::port).toString(protocols::socks5Proxy::defaultPort) } }); auto username = socks5ProxyConfig.value(config_key::userName).toString(); diff --git a/client/daemon/daemon.cpp b/client/daemon/daemon.cpp index 33ec8cbc..2faff0ef 100644 --- a/client/daemon/daemon.cpp +++ b/client/daemon/daemon.cpp @@ -405,6 +405,13 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) { if (!obj.value("S2").isNull()) { config.m_responsePacketJunkSize = obj.value("S2").toString(); } + if (!obj.value("S3").isNull()) { + config.m_cookieReplyPacketJunkSize = obj.value("S3").toString(); + } + if (!obj.value("S4").isNull()) { + config.m_transportPacketJunkSize = obj.value("S4").toString(); + } + if (!obj.value("H1").isNull()) { config.m_initPacketMagicHeader = obj.value("H1").toString(); } @@ -418,6 +425,34 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) { config.m_transportPacketMagicHeader = obj.value("H4").toString(); } + if (!obj.value("I1").isNull()) { + config.m_specialJunk["I1"] = obj.value("I1").toString(); + } + if (!obj.value("I2").isNull()) { + config.m_specialJunk["I2"] = obj.value("I2").toString(); + } + if (!obj.value("I3").isNull()) { + config.m_specialJunk["I3"] = obj.value("I3").toString(); + } + if (!obj.value("I4").isNull()) { + config.m_specialJunk["I4"] = obj.value("I4").toString(); + } + if (!obj.value("I5").isNull()) { + config.m_specialJunk["I5"] = obj.value("I5").toString(); + } + if (!obj.value("J1").isNull()) { + config.m_controlledJunk["J1"] = obj.value("J1").toString(); + } + if (!obj.value("J2").isNull()) { + config.m_controlledJunk["J2"] = obj.value("J2").toString(); + } + if (!obj.value("J3").isNull()) { + config.m_controlledJunk["J3"] = obj.value("J3").toString(); + } + if (!obj.value("Itime").isNull()) { + config.m_specialHandshakeTimeout = obj.value("Itime").toString(); + } + return true; } @@ -460,7 +495,7 @@ bool Daemon::deactivate(bool emitSignals) { m_connections.clear(); // Delete the interface - return wgutils()->deleteInterface(); + return wgutils()->deleteInterface(); } QString Daemon::logs() { diff --git a/client/daemon/interfaceconfig.cpp b/client/daemon/interfaceconfig.cpp index 846cfebe..53da5d36 100644 --- a/client/daemon/interfaceconfig.cpp +++ b/client/daemon/interfaceconfig.cpp @@ -130,6 +130,12 @@ QString InterfaceConfig::toWgConf(const QMap& extra) const { if (!m_responsePacketJunkSize.isNull()) { out << "S2 = " << m_responsePacketJunkSize << "\n"; } + if (!m_cookieReplyPacketJunkSize.isNull()) { + out << "S3 = " << m_cookieReplyPacketJunkSize << "\n"; + } + if (!m_transportPacketJunkSize.isNull()) { + out << "S4 = " << m_transportPacketJunkSize << "\n"; + } if (!m_initPacketMagicHeader.isNull()) { out << "H1 = " << m_initPacketMagicHeader << "\n"; } @@ -143,6 +149,16 @@ QString InterfaceConfig::toWgConf(const QMap& extra) const { out << "H4 = " << m_transportPacketMagicHeader << "\n"; } + for (const QString& key : m_specialJunk.keys()) { + out << key << " = " << m_specialJunk[key] << "\n"; + } + for (const QString& key : m_controlledJunk.keys()) { + out << key << " = " << m_controlledJunk[key] << "\n"; + } + if (!m_specialHandshakeTimeout.isNull()) { + out << "Itime = " << m_specialHandshakeTimeout << "\n"; + } + // If any extra config was provided, append it now. for (const QString& key : extra.keys()) { out << key << " = " << extra[key] << "\n"; diff --git a/client/daemon/interfaceconfig.h b/client/daemon/interfaceconfig.h index 6ae400c2..06288e80 100644 --- a/client/daemon/interfaceconfig.h +++ b/client/daemon/interfaceconfig.h @@ -50,10 +50,15 @@ class InterfaceConfig { QString m_junkPacketMaxSize; QString m_initPacketJunkSize; QString m_responsePacketJunkSize; + QString m_cookieReplyPacketJunkSize; + QString m_transportPacketJunkSize; QString m_initPacketMagicHeader; QString m_responsePacketMagicHeader; QString m_underloadPacketMagicHeader; QString m_transportPacketMagicHeader; + QMap m_specialJunk; + QMap m_controlledJunk; + QString m_specialHandshakeTimeout; QJsonObject toJson() const; QString toWgConf( diff --git a/client/mozilla/localsocketcontroller.cpp b/client/mozilla/localsocketcontroller.cpp index 67924d47..9abab81c 100644 --- a/client/mozilla/localsocketcontroller.cpp +++ b/client/mozilla/localsocketcontroller.cpp @@ -38,7 +38,7 @@ LocalSocketController::LocalSocketController() { m_socket = new QLocalSocket(this); connect(m_socket, &QLocalSocket::connected, this, &LocalSocketController::daemonConnected); - connect(m_socket, &QLocalSocket::disconnected, this, + connect(m_socket, &QLocalSocket::disconnected, this, [&] { errorOccurred(QLocalSocket::PeerClosedError); }); connect(m_socket, &QLocalSocket::errorOccurred, this, &LocalSocketController::errorOccurred); @@ -135,7 +135,7 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) { // set up IPv6 unique-local-address, ULA, with "fd00::/8" prefix, not globally routable. // this will be default IPv6 gateway, OS recognizes that IPv6 link is local and switches to IPv4. - // Otherwise some OSes (Linux) try IPv6 forever and hang. + // Otherwise some OSes (Linux) try IPv6 forever and hang. // https://en.wikipedia.org/wiki/Unique_local_address (RFC 4193) // https://man7.org/linux/man-pages/man5/gai.conf.5.html json.insert("deviceIpv6Address", "fd58:baa6:dead::1"); // simply "dead::1" is globally-routable, don't use it @@ -244,28 +244,61 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) { json.insert(amnezia::config_key::junkPacketMaxSize, wgConfig.value(amnezia::config_key::junkPacketMaxSize)); json.insert(amnezia::config_key::initPacketJunkSize, wgConfig.value(amnezia::config_key::initPacketJunkSize)); json.insert(amnezia::config_key::responsePacketJunkSize, wgConfig.value(amnezia::config_key::responsePacketJunkSize)); + json.insert(amnezia::config_key::cookieReplyPacketJunkSize, wgConfig.value(amnezia::config_key::cookieReplyPacketJunkSize)); + json.insert(amnezia::config_key::transportPacketJunkSize, wgConfig.value(amnezia::config_key::transportPacketJunkSize)); json.insert(amnezia::config_key::initPacketMagicHeader, wgConfig.value(amnezia::config_key::initPacketMagicHeader)); json.insert(amnezia::config_key::responsePacketMagicHeader, wgConfig.value(amnezia::config_key::responsePacketMagicHeader)); json.insert(amnezia::config_key::underloadPacketMagicHeader, wgConfig.value(amnezia::config_key::underloadPacketMagicHeader)); json.insert(amnezia::config_key::transportPacketMagicHeader, wgConfig.value(amnezia::config_key::transportPacketMagicHeader)); + json.insert(amnezia::config_key::specialJunk1, wgConfig.value(amnezia::config_key::specialJunk1)); + json.insert(amnezia::config_key::specialJunk2, wgConfig.value(amnezia::config_key::specialJunk2)); + json.insert(amnezia::config_key::specialJunk3, wgConfig.value(amnezia::config_key::specialJunk3)); + json.insert(amnezia::config_key::specialJunk4, wgConfig.value(amnezia::config_key::specialJunk4)); + json.insert(amnezia::config_key::specialJunk5, wgConfig.value(amnezia::config_key::specialJunk5)); + json.insert(amnezia::config_key::controlledJunk1, wgConfig.value(amnezia::config_key::controlledJunk1)); + json.insert(amnezia::config_key::controlledJunk2, wgConfig.value(amnezia::config_key::controlledJunk2)); + json.insert(amnezia::config_key::controlledJunk3, wgConfig.value(amnezia::config_key::controlledJunk3)); + json.insert(amnezia::config_key::specialHandshakeTimeout, wgConfig.value(amnezia::config_key::specialHandshakeTimeout)); } else if (!wgConfig.value(amnezia::config_key::junkPacketCount).isUndefined() && !wgConfig.value(amnezia::config_key::junkPacketMinSize).isUndefined() && !wgConfig.value(amnezia::config_key::junkPacketMaxSize).isUndefined() && !wgConfig.value(amnezia::config_key::initPacketJunkSize).isUndefined() && !wgConfig.value(amnezia::config_key::responsePacketJunkSize).isUndefined() + && !wgConfig.value(amnezia::config_key::cookieReplyPacketJunkSize).isUndefined() + && !wgConfig.value(amnezia::config_key::transportPacketJunkSize).isUndefined() && !wgConfig.value(amnezia::config_key::initPacketMagicHeader).isUndefined() && !wgConfig.value(amnezia::config_key::responsePacketMagicHeader).isUndefined() && !wgConfig.value(amnezia::config_key::underloadPacketMagicHeader).isUndefined() - && !wgConfig.value(amnezia::config_key::transportPacketMagicHeader).isUndefined()) { + && !wgConfig.value(amnezia::config_key::transportPacketMagicHeader).isUndefined() + && !wgConfig.value(amnezia::config_key::specialJunk1).isUndefined() + && !wgConfig.value(amnezia::config_key::specialJunk2).isUndefined() + && !wgConfig.value(amnezia::config_key::specialJunk3).isUndefined() + && !wgConfig.value(amnezia::config_key::specialJunk4).isUndefined() + && !wgConfig.value(amnezia::config_key::specialJunk5).isUndefined() + && !wgConfig.value(amnezia::config_key::controlledJunk1).isUndefined() + && !wgConfig.value(amnezia::config_key::controlledJunk2).isUndefined() + && !wgConfig.value(amnezia::config_key::controlledJunk3).isUndefined() + && !wgConfig.value(amnezia::config_key::specialHandshakeTimeout).isUndefined()) { json.insert(amnezia::config_key::junkPacketCount, wgConfig.value(amnezia::config_key::junkPacketCount)); json.insert(amnezia::config_key::junkPacketMinSize, wgConfig.value(amnezia::config_key::junkPacketMinSize)); json.insert(amnezia::config_key::junkPacketMaxSize, wgConfig.value(amnezia::config_key::junkPacketMaxSize)); json.insert(amnezia::config_key::initPacketJunkSize, wgConfig.value(amnezia::config_key::initPacketJunkSize)); json.insert(amnezia::config_key::responsePacketJunkSize, wgConfig.value(amnezia::config_key::responsePacketJunkSize)); + json.insert(amnezia::config_key::cookieReplyPacketJunkSize, wgConfig.value(amnezia::config_key::cookieReplyPacketJunkSize)); + json.insert(amnezia::config_key::transportPacketJunkSize, wgConfig.value(amnezia::config_key::transportPacketJunkSize)); json.insert(amnezia::config_key::initPacketMagicHeader, wgConfig.value(amnezia::config_key::initPacketMagicHeader)); json.insert(amnezia::config_key::responsePacketMagicHeader, wgConfig.value(amnezia::config_key::responsePacketMagicHeader)); json.insert(amnezia::config_key::underloadPacketMagicHeader, wgConfig.value(amnezia::config_key::underloadPacketMagicHeader)); json.insert(amnezia::config_key::transportPacketMagicHeader, wgConfig.value(amnezia::config_key::transportPacketMagicHeader)); + json.insert(amnezia::config_key::specialJunk1, wgConfig.value(amnezia::config_key::specialJunk1)); + json.insert(amnezia::config_key::specialJunk2, wgConfig.value(amnezia::config_key::specialJunk2)); + json.insert(amnezia::config_key::specialJunk3, wgConfig.value(amnezia::config_key::specialJunk3)); + json.insert(amnezia::config_key::specialJunk4, wgConfig.value(amnezia::config_key::specialJunk4)); + json.insert(amnezia::config_key::specialJunk5, wgConfig.value(amnezia::config_key::specialJunk5)); + json.insert(amnezia::config_key::controlledJunk1, wgConfig.value(amnezia::config_key::controlledJunk1)); + json.insert(amnezia::config_key::controlledJunk2, wgConfig.value(amnezia::config_key::controlledJunk2)); + json.insert(amnezia::config_key::controlledJunk3, wgConfig.value(amnezia::config_key::controlledJunk3)); + json.insert(amnezia::config_key::specialHandshakeTimeout, wgConfig.value(amnezia::config_key::specialHandshakeTimeout)); } write(json); diff --git a/client/platforms/ios/WGConfig.swift b/client/platforms/ios/WGConfig.swift index e3b67efe..8f693387 100644 --- a/client/platforms/ios/WGConfig.swift +++ b/client/platforms/ios/WGConfig.swift @@ -4,7 +4,10 @@ struct WGConfig: Decodable { let initPacketMagicHeader, responsePacketMagicHeader: String? let underloadPacketMagicHeader, transportPacketMagicHeader: String? let junkPacketCount, junkPacketMinSize, junkPacketMaxSize: String? - let initPacketJunkSize, responsePacketJunkSize: String? + let initPacketJunkSize, responsePacketJunkSize, cookieReplyPacketJunkSize, transportPacketJunkSize: String? + let specialJunk1, specialJunk2, specialJunk3, specialJunk4, specialJunk5: String? + let controlledJunk1, controlledJunk2, controlledJunk3: String? + let specialHandshakeTimeout: String? let dns1: String let dns2: String let mtu: String @@ -23,7 +26,10 @@ struct WGConfig: Decodable { case initPacketMagicHeader = "H1", responsePacketMagicHeader = "H2" case underloadPacketMagicHeader = "H3", transportPacketMagicHeader = "H4" case junkPacketCount = "Jc", junkPacketMinSize = "Jmin", junkPacketMaxSize = "Jmax" - case initPacketJunkSize = "S1", responsePacketJunkSize = "S2" + case initPacketJunkSize = "S1", responsePacketJunkSize = "S2", cookieReplyPacketJunkSize = "S3", transportPacketJunkSize = "S4" + case specialJunk1 = "I1", specialJunk2 = "I2", specialJunk3 = "I3", specialJunk4 = "I4", specialJunk5 = "I5" + case controlledJunk1 = "J1", controlledJunk2 = "J2", controlledJunk3 = "J3" + case specialHandshakeTimeout = "Itime" case dns1 case dns2 case mtu @@ -47,11 +53,21 @@ struct WGConfig: Decodable { Jmax = \(junkPacketMaxSize!) S1 = \(initPacketJunkSize!) S2 = \(responsePacketJunkSize!) + S3 = \(cookieReplyPacketJunkSize!) + S4 = \(transportPacketJunkSize!) H1 = \(initPacketMagicHeader!) H2 = \(responsePacketMagicHeader!) H3 = \(underloadPacketMagicHeader!) H4 = \(transportPacketMagicHeader!) - + I1 = \(specialJunk1!) + I2 = \(specialJunk2!) + I3 = \(specialJunk3!) + I4 = \(specialJunk4!) + I5 = \(specialJunk5!) + J1 = \(controlledJunk1!) + J2 = \(controlledJunk2!) + J3 = \(controlledJunk3!) + Itime = \(specialHandshakeTimeout!) """ } diff --git a/client/platforms/ios/ios_controller.mm b/client/platforms/ios/ios_controller.mm index 85fb50b7..e64c6dce 100644 --- a/client/platforms/ios/ios_controller.mm +++ b/client/platforms/ios/ios_controller.mm @@ -507,6 +507,8 @@ bool IosController::setupWireGuard() wgConfig.insert(config_key::initPacketJunkSize, config[config_key::initPacketJunkSize]); wgConfig.insert(config_key::responsePacketJunkSize, config[config_key::responsePacketJunkSize]); + wgConfig.insert(config_key::cookieReplyPacketJunkSize, config[config_key::cookieReplyPacketJunkSize]); + wgConfig.insert(config_key::transportPacketJunkSize, config[config_key::transportPacketJunkSize]); wgConfig.insert(config_key::junkPacketCount, config[config_key::junkPacketCount]); wgConfig.insert(config_key::junkPacketMinSize, config[config_key::junkPacketMinSize]); @@ -605,11 +607,23 @@ bool IosController::setupAwg() wgConfig.insert(config_key::initPacketJunkSize, config[config_key::initPacketJunkSize]); wgConfig.insert(config_key::responsePacketJunkSize, config[config_key::responsePacketJunkSize]); + wgConfig.insert(config_key::cookieReplyPacketJunkSize, config[config_key::cookieReplyPacketJunkSize]); + wgConfig.insert(config_key::transportPacketJunkSize, config[config_key::transportPacketJunkSize]); wgConfig.insert(config_key::junkPacketCount, config[config_key::junkPacketCount]); wgConfig.insert(config_key::junkPacketMinSize, config[config_key::junkPacketMinSize]); wgConfig.insert(config_key::junkPacketMaxSize, config[config_key::junkPacketMaxSize]); + wgConfig.insert(config_key::specialJunk1, config[config_key::specialJunk1]); + wgConfig.insert(config_key::specialJunk2, config[config_key::specialJunk2]); + wgConfig.insert(config_key::specialJunk3, config[config_key::specialJunk3]); + wgConfig.insert(config_key::specialJunk4, config[config_key::specialJunk4]); + wgConfig.insert(config_key::specialJunk5, config[config_key::specialJunk5]); + wgConfig.insert(config_key::controlledJunk1, config[config_key::controlledJunk1]); + wgConfig.insert(config_key::controlledJunk2, config[config_key::controlledJunk2]); + wgConfig.insert(config_key::controlledJunk3, config[config_key::controlledJunk3]); + wgConfig.insert(config_key::specialHandshakeTimeout, config[config_key::specialHandshakeTimeout]); + QJsonDocument wgConfigDoc(wgConfig); QString wgConfigDocStr(wgConfigDoc.toJson(QJsonDocument::Compact)); @@ -794,9 +808,9 @@ bool IosController::shareText(const QStringList& filesToSend) { if (!qtController) return; UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:sharingItems applicationActivities:nil]; - + __block bool isAccepted = false; - + [activityController setCompletionWithItemsHandler:^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) { isAccepted = completed; emit finished(); @@ -808,11 +822,11 @@ bool IosController::shareText(const QStringList& filesToSend) { popController.sourceView = qtController.view; popController.sourceRect = CGRectMake(100, 100, 100, 100); } - + QEventLoop wait; QObject::connect(this, &IosController::finished, &wait, &QEventLoop::quit); wait.exec(); - + return isAccepted; } @@ -826,7 +840,7 @@ QString IosController::openFile() { if (!qtController) return; [qtController presentViewController:documentPicker animated:YES completion:nil]; - + __block QString filePath; documentPickerDelegate.documentPickerClosedCallback = ^(NSString *path) { @@ -841,7 +855,7 @@ QString IosController::openFile() { QEventLoop wait; QObject::connect(this, &IosController::finished, &wait, &QEventLoop::quit); wait.exec(); - + return filePath; } diff --git a/client/platforms/linux/daemon/wireguardutilslinux.cpp b/client/platforms/linux/daemon/wireguardutilslinux.cpp index a12b8582..cfde73e2 100644 --- a/client/platforms/linux/daemon/wireguardutilslinux.cpp +++ b/client/platforms/linux/daemon/wireguardutilslinux.cpp @@ -121,6 +121,12 @@ bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) { if (!config.m_responsePacketJunkSize.isEmpty()) { out << "s2=" << config.m_responsePacketJunkSize << "\n"; } + if (!config.m_cookieReplyPacketJunkSize.isEmpty()) { + out << "s3=" << config.m_cookieReplyPacketJunkSize << "\n"; + } + if (!config.m_transportPacketJunkSize.isEmpty()) { + out << "s4=" << config.m_transportPacketJunkSize << "\n"; + } if (!config.m_initPacketMagicHeader.isEmpty()) { out << "h1=" << config.m_initPacketMagicHeader << "\n"; } @@ -134,6 +140,16 @@ bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) { out << "h4=" << config.m_transportPacketMagicHeader << "\n"; } + for (const QString& key : config.m_specialJunk.keys()) { + out << key.toLower() << "=" << config.m_specialJunk.value(key) << "\n"; + } + for (const QString& key : config.m_controlledJunk.keys()) { + out << key.toLower() << "=" << config.m_controlledJunk.value(key) << "\n"; + } + if (!config.m_specialHandshakeTimeout.isEmpty()) { + out << "itime=" << config.m_specialHandshakeTimeout << "\n"; + } + int err = uapiErrno(uapiCommand(message)); if (err != 0) { logger.error() << "Interface configuration failed:" << strerror(err); diff --git a/client/platforms/macos/daemon/wireguardutilsmacos.cpp b/client/platforms/macos/daemon/wireguardutilsmacos.cpp index 37170f20..cce4afab 100644 --- a/client/platforms/macos/daemon/wireguardutilsmacos.cpp +++ b/client/platforms/macos/daemon/wireguardutilsmacos.cpp @@ -119,6 +119,12 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) { if (!config.m_responsePacketJunkSize.isEmpty()) { out << "s2=" << config.m_responsePacketJunkSize << "\n"; } + if (!config.m_cookieReplyPacketJunkSize.isEmpty()) { + out << "s3=" << config.m_cookieReplyPacketJunkSize << "\n"; + } + if (!config.m_transportPacketJunkSize.isEmpty()) { + out << "s4=" << config.m_transportPacketJunkSize << "\n"; + } if (!config.m_initPacketMagicHeader.isEmpty()) { out << "h1=" << config.m_initPacketMagicHeader << "\n"; } @@ -132,6 +138,16 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) { out << "h4=" << config.m_transportPacketMagicHeader << "\n"; } + for (const QString& key : config.m_specialJunk.keys()) { + out << key.toLower() << "=" << config.m_specialJunk.value(key) << "\n"; + } + for (const QString& key : config.m_controlledJunk.keys()) { + out << key.toLower() << "=" << config.m_controlledJunk.value(key) << "\n"; + } + if (!config.m_specialHandshakeTimeout.isEmpty()) { + out << "itime=" << config.m_specialHandshakeTimeout << "\n"; + } + int err = uapiErrno(uapiCommand(message)); if (err != 0) { logger.error() << "Interface configuration failed:" << strerror(err); diff --git a/client/protocols/protocols_defs.h b/client/protocols/protocols_defs.h index c2d51454..b4cbb6de 100644 --- a/client/protocols/protocols_defs.h +++ b/client/protocols/protocols_defs.h @@ -72,10 +72,21 @@ namespace amnezia constexpr char junkPacketMaxSize[] = "Jmax"; constexpr char initPacketJunkSize[] = "S1"; constexpr char responsePacketJunkSize[] = "S2"; + constexpr char cookieReplyPacketJunkSize[] = "S3"; + constexpr char transportPacketJunkSize[] = "S4"; constexpr char initPacketMagicHeader[] = "H1"; constexpr char responsePacketMagicHeader[] = "H2"; constexpr char underloadPacketMagicHeader[] = "H3"; constexpr char transportPacketMagicHeader[] = "H4"; + constexpr char specialJunk1[] = "I1"; + constexpr char specialJunk2[] = "I2"; + constexpr char specialJunk3[] = "I3"; + constexpr char specialJunk4[] = "I4"; + constexpr char specialJunk5[] = "I5"; + constexpr char controlledJunk1[] = "J1"; + constexpr char controlledJunk2[] = "J2"; + constexpr char controlledJunk3[] = "J3"; + constexpr char specialHandshakeTimeout[] = "Itime"; constexpr char openvpn[] = "openvpn"; constexpr char wireguard[] = "wireguard"; @@ -216,10 +227,22 @@ namespace amnezia constexpr char defaultJunkPacketMaxSize[] = "30"; constexpr char defaultInitPacketJunkSize[] = "15"; constexpr char defaultResponsePacketJunkSize[] = "18"; + constexpr char defaultCookieReplyPacketJunkSize[] = "20"; + constexpr char defaultTransportPacketJunkSize[] = "23"; + constexpr char defaultInitPacketMagicHeader[] = "1020325451"; constexpr char defaultResponsePacketMagicHeader[] = "3288052141"; constexpr char defaultTransportPacketMagicHeader[] = "2528465083"; constexpr char defaultUnderloadPacketMagicHeader[] = "1766607858"; + constexpr char defaultSpecialJunk1[] = ""; + constexpr char defaultSpecialJunk2[] = ""; + constexpr char defaultSpecialJunk3[] = ""; + constexpr char defaultSpecialJunk4[] = ""; + constexpr char defaultSpecialJunk5[] = ""; + constexpr char defaultControlledJunk1[] = ""; + constexpr char defaultControlledJunk2[] = ""; + constexpr char defaultControlledJunk3[] = ""; + constexpr char defaultSpecialHandshakeTimeout[] = ""; } namespace socks5Proxy diff --git a/client/resources.qrc b/client/resources.qrc index 72eb15c7..54b5846c 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -239,6 +239,7 @@ ui/qml/Components/ApiPremV1MigrationDrawer.qml ui/qml/Components/ApiPremV1SubListDrawer.qml ui/qml/Components/OtpCodeDrawer.qml + ui/qml/Components/AwgTextField.qml images/flagKit/ZW.svg diff --git a/client/server_scripts/awg/Dockerfile b/client/server_scripts/awg/Dockerfile index 8c536fc7..a6118a84 100644 --- a/client/server_scripts/awg/Dockerfile +++ b/client/server_scripts/awg/Dockerfile @@ -10,7 +10,7 @@ RUN mkdir -p /opt/amnezia RUN echo -e "#!/bin/bash\ntail -f /dev/null" > /opt/amnezia/start.sh RUN chmod a+x /opt/amnezia/start.sh -# Tune network +# Tune network RUN echo -e " \n\ fs.file-max = 51200 \n\ \n\ @@ -40,7 +40,8 @@ RUN echo -e " \n\ echo -e " \n\ * soft nofile 51200 \n\ * hard nofile 51200 \n\ - " | sed -e 's/^\s\+//g' | tee -a /etc/security/limits.conf + " | sed -e 's/^\s\+//g' | tee -a /etc/security/limits.conf ENTRYPOINT [ "dumb-init", "/opt/amnezia/start.sh" ] CMD [ "" ] + diff --git a/client/server_scripts/awg/configure_container.sh b/client/server_scripts/awg/configure_container.sh index 2000c965..e327f080 100644 --- a/client/server_scripts/awg/configure_container.sh +++ b/client/server_scripts/awg/configure_container.sh @@ -23,4 +23,5 @@ H1 = $INIT_PACKET_MAGIC_HEADER H2 = $RESPONSE_PACKET_MAGIC_HEADER H3 = $UNDERLOAD_PACKET_MAGIC_HEADER H4 = $TRANSPORT_PACKET_MAGIC_HEADER + EOF diff --git a/client/ui/controllers/api/apiConfigsController.cpp b/client/ui/controllers/api/apiConfigsController.cpp index eb693a9a..0b0a9b92 100644 --- a/client/ui/controllers/api/apiConfigsController.cpp +++ b/client/ui/controllers/api/apiConfigsController.cpp @@ -162,6 +162,9 @@ namespace auto serverProtocolConfig = container.value(containerName).toObject(); auto clientProtocolConfig = QJsonDocument::fromJson(serverProtocolConfig.value(config_key::last_config).toString().toUtf8()).object(); + + //TODO looks like this block can be removed after v1 configs EOL + serverProtocolConfig[config_key::junkPacketCount] = clientProtocolConfig.value(config_key::junkPacketCount); serverProtocolConfig[config_key::junkPacketMinSize] = clientProtocolConfig.value(config_key::junkPacketMinSize); serverProtocolConfig[config_key::junkPacketMaxSize] = clientProtocolConfig.value(config_key::junkPacketMaxSize); @@ -171,6 +174,21 @@ namespace serverProtocolConfig[config_key::responsePacketMagicHeader] = clientProtocolConfig.value(config_key::responsePacketMagicHeader); serverProtocolConfig[config_key::underloadPacketMagicHeader] = clientProtocolConfig.value(config_key::underloadPacketMagicHeader); serverProtocolConfig[config_key::transportPacketMagicHeader] = clientProtocolConfig.value(config_key::transportPacketMagicHeader); + + serverProtocolConfig[config_key::cookieReplyPacketJunkSize] = clientProtocolConfig.value(config_key::cookieReplyPacketJunkSize); + serverProtocolConfig[config_key::transportPacketJunkSize] = clientProtocolConfig.value(config_key::transportPacketJunkSize); + serverProtocolConfig[config_key::specialJunk1] = clientProtocolConfig.value(config_key::specialJunk1); + serverProtocolConfig[config_key::specialJunk2] = clientProtocolConfig.value(config_key::specialJunk2); + serverProtocolConfig[config_key::specialJunk3] = clientProtocolConfig.value(config_key::specialJunk3); + serverProtocolConfig[config_key::specialJunk4] = clientProtocolConfig.value(config_key::specialJunk4); + serverProtocolConfig[config_key::specialJunk5] = clientProtocolConfig.value(config_key::specialJunk5); + serverProtocolConfig[config_key::controlledJunk1] = clientProtocolConfig.value(config_key::controlledJunk1); + serverProtocolConfig[config_key::controlledJunk2] = clientProtocolConfig.value(config_key::controlledJunk2); + serverProtocolConfig[config_key::controlledJunk3] = clientProtocolConfig.value(config_key::controlledJunk3); + serverProtocolConfig[config_key::specialHandshakeTimeout] = clientProtocolConfig.value(config_key::specialHandshakeTimeout); + + // + container[containerName] = serverProtocolConfig; containers.replace(0, container); newServerConfig[config_key::containers] = containers; diff --git a/client/ui/controllers/importController.cpp b/client/ui/controllers/importController.cpp index fdc06120..ea1d5d8e 100644 --- a/client/ui/controllers/importController.cpp +++ b/client/ui/controllers/importController.cpp @@ -12,6 +12,7 @@ #include "core/errorstrings.h" #include "core/qrCodeUtils.h" #include "core/serialization/serialization.h" +#include "protocols/protocols_defs.h" #include "systemController.h" #include "utilities.h" @@ -286,6 +287,19 @@ void ImportController::processNativeWireGuardConfig() clientProtocolConfig[config_key::underloadPacketMagicHeader] = "3"; clientProtocolConfig[config_key::transportPacketMagicHeader] = "4"; + // clientProtocolConfig[config_key::cookieReplyPacketJunkSize] = "0"; + // clientProtocolConfig[config_key::transportPacketJunkSize] = "0"; + + // clientProtocolConfig[config_key::specialJunk1] = ""; + // clientProtocolConfig[config_key::specialJunk2] = ""; + // clientProtocolConfig[config_key::specialJunk3] = ""; + // clientProtocolConfig[config_key::specialJunk4] = ""; + // clientProtocolConfig[config_key::specialJunk5] = ""; + // clientProtocolConfig[config_key::controlledJunk1] = ""; + // clientProtocolConfig[config_key::controlledJunk2] = ""; + // clientProtocolConfig[config_key::controlledJunk3] = ""; + // clientProtocolConfig[config_key::specialHandshakeTimeout] = "0"; + clientProtocolConfig[config_key::isObfuscationEnabled] = true; serverProtocolConfig[config_key::last_config] = QString(QJsonDocument(clientProtocolConfig).toJson()); @@ -438,21 +452,33 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data) lastConfig[config_key::allowed_ips] = allowedIpsJsonArray; QString protocolName = "wireguard"; - if (!configMap.value(config_key::junkPacketCount).isEmpty() && !configMap.value(config_key::junkPacketMinSize).isEmpty() - && !configMap.value(config_key::junkPacketMaxSize).isEmpty() && !configMap.value(config_key::initPacketJunkSize).isEmpty() - && !configMap.value(config_key::responsePacketJunkSize).isEmpty() && !configMap.value(config_key::initPacketMagicHeader).isEmpty() - && !configMap.value(config_key::responsePacketMagicHeader).isEmpty() - && !configMap.value(config_key::underloadPacketMagicHeader).isEmpty() - && !configMap.value(config_key::transportPacketMagicHeader).isEmpty()) { - lastConfig[config_key::junkPacketCount] = configMap.value(config_key::junkPacketCount); - lastConfig[config_key::junkPacketMinSize] = configMap.value(config_key::junkPacketMinSize); - lastConfig[config_key::junkPacketMaxSize] = configMap.value(config_key::junkPacketMaxSize); - lastConfig[config_key::initPacketJunkSize] = configMap.value(config_key::initPacketJunkSize); - lastConfig[config_key::responsePacketJunkSize] = configMap.value(config_key::responsePacketJunkSize); - lastConfig[config_key::initPacketMagicHeader] = configMap.value(config_key::initPacketMagicHeader); - lastConfig[config_key::responsePacketMagicHeader] = configMap.value(config_key::responsePacketMagicHeader); - lastConfig[config_key::underloadPacketMagicHeader] = configMap.value(config_key::underloadPacketMagicHeader); - lastConfig[config_key::transportPacketMagicHeader] = configMap.value(config_key::transportPacketMagicHeader); + + const QStringList requiredJunkFields = { config_key::junkPacketCount, config_key::junkPacketMinSize, + config_key::junkPacketMaxSize, config_key::initPacketJunkSize, + config_key::responsePacketJunkSize, config_key::initPacketMagicHeader, + config_key::responsePacketMagicHeader, config_key::underloadPacketMagicHeader, + config_key::transportPacketMagicHeader }; + + const QStringList optionalJunkFields = { // config_key::cookieReplyPacketJunkSize, + // config_key::transportPacketJunkSize, + config_key::specialJunk1, config_key::specialJunk2, config_key::specialJunk3, + config_key::specialJunk4, config_key::specialJunk5, config_key::controlledJunk1, + config_key::controlledJunk2, config_key::controlledJunk3, config_key::specialHandshakeTimeout + }; + + bool hasAllRequiredFields = std::all_of(requiredJunkFields.begin(), requiredJunkFields.end(), + [&configMap](const QString &field) { return !configMap.value(field).isEmpty(); }); + if (hasAllRequiredFields) { + for (const QString &field : requiredJunkFields) { + lastConfig[field] = configMap.value(field); + } + + for (const QString &field : optionalJunkFields) { + if (!configMap.value(field).isEmpty()) { + lastConfig[field] = configMap.value(field); + } + } + protocolName = "awg"; m_configType = ConfigTypes::Awg; } diff --git a/client/ui/controllers/installController.cpp b/client/ui/controllers/installController.cpp index eab8979a..d7f9dfbc 100755 --- a/client/ui/controllers/installController.cpp +++ b/client/ui/controllers/installController.cpp @@ -8,6 +8,7 @@ #include #include +#include "core/api/apiUtils.h" #include "core/controllers/serverController.h" #include "core/controllers/vpnConfigurationController.h" #include "core/networkUtilities.h" @@ -15,7 +16,6 @@ #include "ui/models/protocols/awgConfigModel.h" #include "ui/models/protocols/wireguardConfigModel.h" #include "utilities.h" -#include "core/api/apiUtils.h" namespace { @@ -79,12 +79,36 @@ void InstallController::install(DockerContainer container, int port, TransportPr int s1 = QRandomGenerator::global()->bounded(15, 150); int s2 = QRandomGenerator::global()->bounded(15, 150); - while (s1 + AwgConstant::messageInitiationSize == s2 + AwgConstant::messageResponseSize) { + // int s3 = QRandomGenerator::global()->bounded(15, 150); + // int s4 = QRandomGenerator::global()->bounded(15, 150); + + // Ensure all values are unique and don't create equal packet sizes + QSet usedValues; + usedValues.insert(s1); + + while (usedValues.contains(s2) || s1 + AwgConstant::messageInitiationSize == s2 + AwgConstant::messageResponseSize) { s2 = QRandomGenerator::global()->bounded(15, 150); } + usedValues.insert(s2); + + // while (usedValues.contains(s3) + // || s1 + AwgConstant::messageInitiationSize == s3 + AwgConstant::messageCookieReplySize + // || s2 + AwgConstant::messageResponseSize == s3 + AwgConstant::messageCookieReplySize) { + // s3 = QRandomGenerator::global()->bounded(15, 150); + // } + // usedValues.insert(s3); + + // while (usedValues.contains(s4) + // || s1 + AwgConstant::messageInitiationSize == s4 + AwgConstant::messageTransportSize + // || s2 + AwgConstant::messageResponseSize == s4 + AwgConstant::messageTransportSize + // || s3 + AwgConstant::messageCookieReplySize == s4 + AwgConstant::messageTransportSize) { + // s4 = QRandomGenerator::global()->bounded(15, 150); + // } QString initPacketJunkSize = QString::number(s1); QString responsePacketJunkSize = QString::number(s2); + // QString cookieReplyPacketJunkSize = QString::number(s3); + // QString transportPacketJunkSize = QString::number(s4); QSet headersValue; while (headersValue.size() != 4) { @@ -108,6 +132,21 @@ void InstallController::install(DockerContainer container, int port, TransportPr containerConfig[config_key::responsePacketMagicHeader] = responsePacketMagicHeader; containerConfig[config_key::underloadPacketMagicHeader] = underloadPacketMagicHeader; containerConfig[config_key::transportPacketMagicHeader] = transportPacketMagicHeader; + + // TODO: + // containerConfig[config_key::cookieReplyPacketJunkSize] = cookieReplyPacketJunkSize; + // containerConfig[config_key::transportPacketJunkSize] = transportPacketJunkSize; + + // containerConfig[config_key::specialJunk1] = specialJunk1; + // containerConfig[config_key::specialJunk2] = specialJunk2; + // containerConfig[config_key::specialJunk3] = specialJunk3; + // containerConfig[config_key::specialJunk4] = specialJunk4; + // containerConfig[config_key::specialJunk5] = specialJunk5; + // containerConfig[config_key::controlledJunk1] = controlledJunk1; + // containerConfig[config_key::controlledJunk2] = controlledJunk2; + // containerConfig[config_key::controlledJunk3] = controlledJunk3; + // containerConfig[config_key::specialHandshakeTimeout] = specialHandshakeTimeout; + } else if (container == DockerContainer::Sftp) { containerConfig.insert(config_key::userName, protocols::sftp::defaultUserName); containerConfig.insert(config_key::password, Utils::getRandomString(16)); @@ -401,6 +440,19 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia containerConfig[config_key::transportPacketMagicHeader] = serverConfigMap.value(config_key::transportPacketMagicHeader); + // containerConfig[config_key::cookieReplyPacketJunkSize] = serverConfigMap.value(config_key::cookieReplyPacketJunkSize); + // containerConfig[config_key::transportPacketJunkSize] = serverConfigMap.value(config_key::transportPacketJunkSize); + + // containerConfig[config_key::specialJunk1] = serverConfigMap.value(config_key::specialJunk1); + // containerConfig[config_key::specialJunk2] = serverConfigMap.value(config_key::specialJunk2); + // containerConfig[config_key::specialJunk3] = serverConfigMap.value(config_key::specialJunk3); + // containerConfig[config_key::specialJunk4] = serverConfigMap.value(config_key::specialJunk4); + // containerConfig[config_key::specialJunk5] = serverConfigMap.value(config_key::specialJunk5); + // containerConfig[config_key::controlledJunk1] = serverConfigMap.value(config_key::controlledJunk1); + // containerConfig[config_key::controlledJunk2] = serverConfigMap.value(config_key::controlledJunk2); + // containerConfig[config_key::controlledJunk3] = serverConfigMap.value(config_key::controlledJunk3); + // containerConfig[config_key::specialHandshakeTimeout] = serverConfigMap.value(config_key::specialHandshakeTimeout); + } else if (protocol == Proto::WireGuard) { QString serverConfig = serverController->getTextFileFromContainer(container, credentials, protocols::wireguard::serverConfigPath, errorCode); diff --git a/client/ui/models/protocols/awgConfigModel.cpp b/client/ui/models/protocols/awgConfigModel.cpp index 860c8395..e14a3152 100644 --- a/client/ui/models/protocols/awgConfigModel.cpp +++ b/client/ui/models/protocols/awgConfigModel.cpp @@ -28,7 +28,17 @@ bool AwgConfigModel::setData(const QModelIndex &index, const QVariant &value, in case Roles::ClientJunkPacketCountRole: m_clientProtocolConfig.insert(config_key::junkPacketCount, value.toString()); break; case Roles::ClientJunkPacketMinSizeRole: m_clientProtocolConfig.insert(config_key::junkPacketMinSize, value.toString()); break; case Roles::ClientJunkPacketMaxSizeRole: m_clientProtocolConfig.insert(config_key::junkPacketMaxSize, value.toString()); break; - + case Roles::ClientSpecialJunk1Role: m_clientProtocolConfig.insert(config_key::specialJunk1, value.toString()); break; + case Roles::ClientSpecialJunk2Role: m_clientProtocolConfig.insert(config_key::specialJunk2, value.toString()); break; + case Roles::ClientSpecialJunk3Role: m_clientProtocolConfig.insert(config_key::specialJunk3, value.toString()); break; + case Roles::ClientSpecialJunk4Role: m_clientProtocolConfig.insert(config_key::specialJunk4, value.toString()); break; + case Roles::ClientSpecialJunk5Role: m_clientProtocolConfig.insert(config_key::specialJunk5, value.toString()); break; + case Roles::ClientControlledJunk1Role: m_clientProtocolConfig.insert(config_key::controlledJunk1, value.toString()); break; + case Roles::ClientControlledJunk2Role: m_clientProtocolConfig.insert(config_key::controlledJunk2, value.toString()); break; + case Roles::ClientControlledJunk3Role: m_clientProtocolConfig.insert(config_key::controlledJunk3, value.toString()); break; + case Roles::ClientSpecialHandshakeTimeoutRole: + m_clientProtocolConfig.insert(config_key::specialHandshakeTimeout, value.toString()); + break; case Roles::ServerJunkPacketCountRole: m_serverProtocolConfig.insert(config_key::junkPacketCount, value.toString()); break; case Roles::ServerJunkPacketMinSizeRole: m_serverProtocolConfig.insert(config_key::junkPacketMinSize, value.toString()); break; case Roles::ServerJunkPacketMaxSizeRole: m_serverProtocolConfig.insert(config_key::junkPacketMaxSize, value.toString()); break; @@ -36,6 +46,12 @@ bool AwgConfigModel::setData(const QModelIndex &index, const QVariant &value, in case Roles::ServerResponsePacketJunkSizeRole: m_serverProtocolConfig.insert(config_key::responsePacketJunkSize, value.toString()); break; + // case Roles::ServerCookieReplyPacketJunkSizeRole: + // m_serverProtocolConfig.insert(config_key::cookieReplyPacketJunkSize, value.toString()); + // break; + // case Roles::ServerTransportPacketJunkSizeRole: + // m_serverProtocolConfig.insert(config_key::transportPacketJunkSize, value.toString()); + // break; case Roles::ServerInitPacketMagicHeaderRole: m_serverProtocolConfig.insert(config_key::initPacketMagicHeader, value.toString()); break; case Roles::ServerResponsePacketMagicHeaderRole: m_serverProtocolConfig.insert(config_key::responsePacketMagicHeader, value.toString()); @@ -66,12 +82,23 @@ QVariant AwgConfigModel::data(const QModelIndex &index, int role) const case Roles::ClientJunkPacketCountRole: return m_clientProtocolConfig.value(config_key::junkPacketCount); case Roles::ClientJunkPacketMinSizeRole: return m_clientProtocolConfig.value(config_key::junkPacketMinSize); case Roles::ClientJunkPacketMaxSizeRole: return m_clientProtocolConfig.value(config_key::junkPacketMaxSize); + case Roles::ClientSpecialJunk1Role: return m_clientProtocolConfig.value(config_key::specialJunk1); + case Roles::ClientSpecialJunk2Role: return m_clientProtocolConfig.value(config_key::specialJunk2); + case Roles::ClientSpecialJunk3Role: return m_clientProtocolConfig.value(config_key::specialJunk3); + case Roles::ClientSpecialJunk4Role: return m_clientProtocolConfig.value(config_key::specialJunk4); + case Roles::ClientSpecialJunk5Role: return m_clientProtocolConfig.value(config_key::specialJunk5); + case Roles::ClientControlledJunk1Role: return m_clientProtocolConfig.value(config_key::controlledJunk1); + case Roles::ClientControlledJunk2Role: return m_clientProtocolConfig.value(config_key::controlledJunk2); + case Roles::ClientControlledJunk3Role: return m_clientProtocolConfig.value(config_key::controlledJunk3); + case Roles::ClientSpecialHandshakeTimeoutRole: return m_clientProtocolConfig.value(config_key::specialHandshakeTimeout); case Roles::ServerJunkPacketCountRole: return m_serverProtocolConfig.value(config_key::junkPacketCount); case Roles::ServerJunkPacketMinSizeRole: return m_serverProtocolConfig.value(config_key::junkPacketMinSize); case Roles::ServerJunkPacketMaxSizeRole: return m_serverProtocolConfig.value(config_key::junkPacketMaxSize); case Roles::ServerInitPacketJunkSizeRole: return m_serverProtocolConfig.value(config_key::initPacketJunkSize); case Roles::ServerResponsePacketJunkSizeRole: return m_serverProtocolConfig.value(config_key::responsePacketJunkSize); + // case Roles::ServerCookieReplyPacketJunkSizeRole: return m_serverProtocolConfig.value(config_key::cookieReplyPacketJunkSize); + // case Roles::ServerTransportPacketJunkSizeRole: return m_serverProtocolConfig.value(config_key::transportPacketJunkSize); case Roles::ServerInitPacketMagicHeaderRole: return m_serverProtocolConfig.value(config_key::initPacketMagicHeader); case Roles::ServerResponsePacketMagicHeaderRole: return m_serverProtocolConfig.value(config_key::responsePacketMagicHeader); case Roles::ServerUnderloadPacketMagicHeaderRole: return m_serverProtocolConfig.value(config_key::underloadPacketMagicHeader); @@ -94,7 +121,8 @@ void AwgConfigModel::updateModel(const QJsonObject &config) m_serverProtocolConfig.insert(config_key::transport_proto, serverProtocolConfig.value(config_key::transport_proto).toString(defaultTransportProto)); m_serverProtocolConfig[config_key::last_config] = serverProtocolConfig.value(config_key::last_config); - m_serverProtocolConfig[config_key::subnet_address] = serverProtocolConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress); + m_serverProtocolConfig[config_key::subnet_address] = + serverProtocolConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress); m_serverProtocolConfig[config_key::port] = serverProtocolConfig.value(config_key::port).toString(protocols::awg::defaultPort); m_serverProtocolConfig[config_key::junkPacketCount] = serverProtocolConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount); @@ -106,6 +134,10 @@ void AwgConfigModel::updateModel(const QJsonObject &config) serverProtocolConfig.value(config_key::initPacketJunkSize).toString(protocols::awg::defaultInitPacketJunkSize); m_serverProtocolConfig[config_key::responsePacketJunkSize] = serverProtocolConfig.value(config_key::responsePacketJunkSize).toString(protocols::awg::defaultResponsePacketJunkSize); + // m_serverProtocolConfig[config_key::cookieReplyPacketJunkSize] = + // serverProtocolConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize); + // m_serverProtocolConfig[config_key::transportPacketJunkSize] = + // serverProtocolConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize); m_serverProtocolConfig[config_key::initPacketMagicHeader] = serverProtocolConfig.value(config_key::initPacketMagicHeader).toString(protocols::awg::defaultInitPacketMagicHeader); m_serverProtocolConfig[config_key::responsePacketMagicHeader] = @@ -124,6 +156,24 @@ void AwgConfigModel::updateModel(const QJsonObject &config) clientProtocolConfig.value(config_key::junkPacketMinSize).toString(m_serverProtocolConfig[config_key::junkPacketMinSize].toString()); m_clientProtocolConfig[config_key::junkPacketMaxSize] = clientProtocolConfig.value(config_key::junkPacketMaxSize).toString(m_serverProtocolConfig[config_key::junkPacketMaxSize].toString()); + m_clientProtocolConfig[config_key::specialJunk1] = + clientProtocolConfig.value(config_key::specialJunk1).toString(protocols::awg::defaultSpecialJunk1); + m_clientProtocolConfig[config_key::specialJunk2] = + clientProtocolConfig.value(config_key::specialJunk2).toString(protocols::awg::defaultSpecialJunk2); + m_clientProtocolConfig[config_key::specialJunk3] = + clientProtocolConfig.value(config_key::specialJunk3).toString(protocols::awg::defaultSpecialJunk3); + m_clientProtocolConfig[config_key::specialJunk4] = + clientProtocolConfig.value(config_key::specialJunk4).toString(protocols::awg::defaultSpecialJunk4); + m_clientProtocolConfig[config_key::specialJunk5] = + clientProtocolConfig.value(config_key::specialJunk5).toString(protocols::awg::defaultSpecialJunk5); + m_clientProtocolConfig[config_key::controlledJunk1] = + clientProtocolConfig.value(config_key::controlledJunk1).toString(protocols::awg::defaultControlledJunk1); + m_clientProtocolConfig[config_key::controlledJunk2] = + clientProtocolConfig.value(config_key::controlledJunk2).toString(protocols::awg::defaultControlledJunk2); + m_clientProtocolConfig[config_key::controlledJunk3] = + clientProtocolConfig.value(config_key::controlledJunk3).toString(protocols::awg::defaultControlledJunk3); + m_clientProtocolConfig[config_key::specialHandshakeTimeout] = + clientProtocolConfig.value(config_key::specialHandshakeTimeout).toString(protocols::awg::defaultSpecialHandshakeTimeout); endResetModel(); } @@ -141,6 +191,15 @@ QJsonObject AwgConfigModel::getConfig() jsonConfig[config_key::junkPacketCount] = m_clientProtocolConfig[config_key::junkPacketCount]; jsonConfig[config_key::junkPacketMinSize] = m_clientProtocolConfig[config_key::junkPacketMinSize]; jsonConfig[config_key::junkPacketMaxSize] = m_clientProtocolConfig[config_key::junkPacketMaxSize]; + jsonConfig[config_key::specialJunk1] = m_clientProtocolConfig[config_key::specialJunk1]; + jsonConfig[config_key::specialJunk2] = m_clientProtocolConfig[config_key::specialJunk2]; + jsonConfig[config_key::specialJunk3] = m_clientProtocolConfig[config_key::specialJunk3]; + jsonConfig[config_key::specialJunk4] = m_clientProtocolConfig[config_key::specialJunk4]; + jsonConfig[config_key::specialJunk5] = m_clientProtocolConfig[config_key::specialJunk5]; + jsonConfig[config_key::controlledJunk1] = m_clientProtocolConfig[config_key::controlledJunk1]; + jsonConfig[config_key::controlledJunk2] = m_clientProtocolConfig[config_key::controlledJunk2]; + jsonConfig[config_key::controlledJunk3] = m_clientProtocolConfig[config_key::controlledJunk3]; + jsonConfig[config_key::specialHandshakeTimeout] = m_clientProtocolConfig[config_key::specialHandshakeTimeout]; m_serverProtocolConfig[config_key::last_config] = QString(QJsonDocument(jsonConfig).toJson()); } @@ -159,6 +218,17 @@ bool AwgConfigModel::isPacketSizeEqual(const int s1, const int s2) return (AwgConstant::messageInitiationSize + s1 == AwgConstant::messageResponseSize + s2); } +// bool AwgConfigModel::isPacketSizeEqual(const int s1, const int s2, const int s3, const int s4) +// { +// int initSize = AwgConstant::messageInitiationSize + s1; +// int responseSize = AwgConstant::messageResponseSize + s2; +// int cookieSize = AwgConstant::messageCookieReplySize + s3; +// int transportSize = AwgConstant::messageTransportSize + s4; + +// return (initSize == responseSize || initSize == cookieSize || initSize == transportSize || responseSize == cookieSize +// || responseSize == transportSize || cookieSize == transportSize); +// } + bool AwgConfigModel::isServerSettingsEqual() { const AwgConfig oldConfig(m_fullConfig.value(config_key::awg).toObject()); @@ -178,12 +248,24 @@ QHash AwgConfigModel::roleNames() const roles[ClientJunkPacketCountRole] = "clientJunkPacketCount"; roles[ClientJunkPacketMinSizeRole] = "clientJunkPacketMinSize"; roles[ClientJunkPacketMaxSizeRole] = "clientJunkPacketMaxSize"; + roles[ClientSpecialJunk1Role] = "clientSpecialJunk1"; + roles[ClientSpecialJunk2Role] = "clientSpecialJunk2"; + roles[ClientSpecialJunk3Role] = "clientSpecialJunk3"; + roles[ClientSpecialJunk4Role] = "clientSpecialJunk4"; + roles[ClientSpecialJunk5Role] = "clientSpecialJunk5"; + roles[ClientControlledJunk1Role] = "clientControlledJunk1"; + roles[ClientControlledJunk2Role] = "clientControlledJunk2"; + roles[ClientControlledJunk3Role] = "clientControlledJunk3"; + roles[ClientSpecialHandshakeTimeoutRole] = "clientSpecialHandshakeTimeout"; roles[ServerJunkPacketCountRole] = "serverJunkPacketCount"; roles[ServerJunkPacketMinSizeRole] = "serverJunkPacketMinSize"; roles[ServerJunkPacketMaxSizeRole] = "serverJunkPacketMaxSize"; roles[ServerInitPacketJunkSizeRole] = "serverInitPacketJunkSize"; roles[ServerResponsePacketJunkSizeRole] = "serverResponsePacketJunkSize"; + roles[ServerCookieReplyPacketJunkSizeRole] = "serverCookieReplyPacketJunkSize"; + roles[ServerTransportPacketJunkSizeRole] = "serverTransportPacketJunkSize"; + roles[ServerInitPacketMagicHeaderRole] = "serverInitPacketMagicHeader"; roles[ServerResponsePacketMagicHeaderRole] = "serverResponsePacketMagicHeader"; roles[ServerUnderloadPacketMagicHeaderRole] = "serverUnderloadPacketMagicHeader"; @@ -200,6 +282,16 @@ AwgConfig::AwgConfig(const QJsonObject &serverProtocolConfig) clientJunkPacketCount = clientProtocolConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount); clientJunkPacketMinSize = clientProtocolConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize); clientJunkPacketMaxSize = clientProtocolConfig.value(config_key::junkPacketMaxSize).toString(protocols::awg::defaultJunkPacketMaxSize); + clientSpecialJunk1 = clientProtocolConfig.value(config_key::specialJunk1).toString(protocols::awg::defaultSpecialJunk1); + clientSpecialJunk2 = clientProtocolConfig.value(config_key::specialJunk2).toString(protocols::awg::defaultSpecialJunk2); + clientSpecialJunk3 = clientProtocolConfig.value(config_key::specialJunk3).toString(protocols::awg::defaultSpecialJunk3); + clientSpecialJunk4 = clientProtocolConfig.value(config_key::specialJunk4).toString(protocols::awg::defaultSpecialJunk4); + clientSpecialJunk5 = clientProtocolConfig.value(config_key::specialJunk5).toString(protocols::awg::defaultSpecialJunk5); + clientControlledJunk1 = clientProtocolConfig.value(config_key::controlledJunk1).toString(protocols::awg::defaultControlledJunk1); + clientControlledJunk2 = clientProtocolConfig.value(config_key::controlledJunk2).toString(protocols::awg::defaultControlledJunk2); + clientControlledJunk3 = clientProtocolConfig.value(config_key::controlledJunk3).toString(protocols::awg::defaultControlledJunk3); + clientSpecialHandshakeTimeout = + clientProtocolConfig.value(config_key::specialHandshakeTimeout).toString(protocols::awg::defaultSpecialHandshakeTimeout); subnetAddress = serverProtocolConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress); port = serverProtocolConfig.value(config_key::port).toString(protocols::awg::defaultPort); @@ -209,6 +301,10 @@ AwgConfig::AwgConfig(const QJsonObject &serverProtocolConfig) serverInitPacketJunkSize = serverProtocolConfig.value(config_key::initPacketJunkSize).toString(protocols::awg::defaultInitPacketJunkSize); serverResponsePacketJunkSize = serverProtocolConfig.value(config_key::responsePacketJunkSize).toString(protocols::awg::defaultResponsePacketJunkSize); + // serverCookieReplyPacketJunkSize = + // serverProtocolConfig.value(config_key::cookieReplyPacketJunkSize).toString(protocols::awg::defaultCookieReplyPacketJunkSize); + // serverTransportPacketJunkSize = + // serverProtocolConfig.value(config_key::transportPacketJunkSize).toString(protocols::awg::defaultTransportPacketJunkSize); serverInitPacketMagicHeader = serverProtocolConfig.value(config_key::initPacketMagicHeader).toString(protocols::awg::defaultInitPacketMagicHeader); serverResponsePacketMagicHeader = @@ -224,6 +320,8 @@ bool AwgConfig::hasEqualServerSettings(const AwgConfig &other) const if (subnetAddress != other.subnetAddress || port != other.port || serverJunkPacketCount != other.serverJunkPacketCount || serverJunkPacketMinSize != other.serverJunkPacketMinSize || serverJunkPacketMaxSize != other.serverJunkPacketMaxSize || serverInitPacketJunkSize != other.serverInitPacketJunkSize || serverResponsePacketJunkSize != other.serverResponsePacketJunkSize + // || serverCookieReplyPacketJunkSize != other.serverCookieReplyPacketJunkSize + // || serverTransportPacketJunkSize != other.serverTransportPacketJunkSize || serverInitPacketMagicHeader != other.serverInitPacketMagicHeader || serverResponsePacketMagicHeader != other.serverResponsePacketMagicHeader || serverUnderloadPacketMagicHeader != other.serverUnderloadPacketMagicHeader @@ -236,7 +334,12 @@ bool AwgConfig::hasEqualServerSettings(const AwgConfig &other) const bool AwgConfig::hasEqualClientSettings(const AwgConfig &other) const { if (clientMtu != other.clientMtu || clientJunkPacketCount != other.clientJunkPacketCount - || clientJunkPacketMinSize != other.clientJunkPacketMinSize || clientJunkPacketMaxSize != other.clientJunkPacketMaxSize) { + || clientJunkPacketMinSize != other.clientJunkPacketMinSize || clientJunkPacketMaxSize != other.clientJunkPacketMaxSize + || clientSpecialJunk1 != other.clientSpecialJunk1 || clientSpecialJunk2 != other.clientSpecialJunk2 + || clientSpecialJunk3 != other.clientSpecialJunk3 || clientSpecialJunk4 != other.clientSpecialJunk4 + || clientSpecialJunk5 != other.clientSpecialJunk5 || clientControlledJunk1 != other.clientControlledJunk1 + || clientControlledJunk2 != other.clientControlledJunk2 || clientControlledJunk3 != other.clientControlledJunk3 + || clientSpecialHandshakeTimeout != other.clientSpecialHandshakeTimeout) { return false; } return true; diff --git a/client/ui/models/protocols/awgConfigModel.h b/client/ui/models/protocols/awgConfigModel.h index c1f8bb27..0c2374fc 100644 --- a/client/ui/models/protocols/awgConfigModel.h +++ b/client/ui/models/protocols/awgConfigModel.h @@ -6,9 +6,12 @@ #include "containers/containers_defs.h" -namespace AwgConstant { +namespace AwgConstant +{ const int messageInitiationSize = 148; const int messageResponseSize = 92; + const int messageCookieReplySize = 64; + const int messageTransportSize = 32; } struct AwgConfig @@ -22,12 +25,23 @@ struct AwgConfig QString clientJunkPacketCount; QString clientJunkPacketMinSize; QString clientJunkPacketMaxSize; + QString clientSpecialJunk1; + QString clientSpecialJunk2; + QString clientSpecialJunk3; + QString clientSpecialJunk4; + QString clientSpecialJunk5; + QString clientControlledJunk1; + QString clientControlledJunk2; + QString clientControlledJunk3; + QString clientSpecialHandshakeTimeout; QString serverJunkPacketCount; QString serverJunkPacketMinSize; QString serverJunkPacketMaxSize; QString serverInitPacketJunkSize; QString serverResponsePacketJunkSize; + QString serverCookieReplyPacketJunkSize; + QString serverTransportPacketJunkSize; QString serverInitPacketMagicHeader; QString serverResponsePacketMagicHeader; QString serverUnderloadPacketMagicHeader; @@ -35,7 +49,6 @@ struct AwgConfig bool hasEqualServerSettings(const AwgConfig &other) const; bool hasEqualClientSettings(const AwgConfig &other) const; - }; class AwgConfigModel : public QAbstractListModel @@ -51,16 +64,28 @@ public: ClientJunkPacketCountRole, ClientJunkPacketMinSizeRole, ClientJunkPacketMaxSizeRole, + ClientSpecialJunk1Role, + ClientSpecialJunk2Role, + ClientSpecialJunk3Role, + ClientSpecialJunk4Role, + ClientSpecialJunk5Role, + ClientControlledJunk1Role, + ClientControlledJunk2Role, + ClientControlledJunk3Role, + ClientSpecialHandshakeTimeoutRole, ServerJunkPacketCountRole, ServerJunkPacketMinSizeRole, ServerJunkPacketMaxSizeRole, ServerInitPacketJunkSizeRole, ServerResponsePacketJunkSizeRole, + ServerCookieReplyPacketJunkSizeRole, + ServerTransportPacketJunkSizeRole, + ServerInitPacketMagicHeaderRole, ServerResponsePacketMagicHeaderRole, ServerUnderloadPacketMagicHeaderRole, - ServerTransportPacketMagicHeaderRole + ServerTransportPacketMagicHeaderRole, }; explicit AwgConfigModel(QObject *parent = nullptr); @@ -75,7 +100,7 @@ public slots: QJsonObject getConfig(); bool isHeadersEqual(const QString &h1, const QString &h2, const QString &h3, const QString &h4); - bool isPacketSizeEqual(const int s1, const int s2); + bool isPacketSizeEqual(const int s1, const int s2/*, const int s3, const int s4*/); bool isServerSettingsEqual(); diff --git a/client/ui/qml/Components/AwgTextField.qml b/client/ui/qml/Components/AwgTextField.qml new file mode 100644 index 00000000..87b023d9 --- /dev/null +++ b/client/ui/qml/Components/AwgTextField.qml @@ -0,0 +1,15 @@ +pragma ComponentBehavior: Bound + +import QtQuick +import QtQuick.Layouts + +import "../Controls2" + +TextFieldWithHeaderType { + Layout.fillWidth: true + Layout.topMargin: 16 + + textField.validator: IntValidator { bottom: 0 } + + checkEmptyText: true +} diff --git a/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml b/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml index b8cf5f93..d97d09e8 100644 --- a/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolAwgClientSettings.qml @@ -115,14 +115,10 @@ PageType { KeyNavigation.tab: junkPacketCountTextField.textField } - TextFieldWithHeaderType { + AwgTextField { id: junkPacketCountTextField - Layout.fillWidth: true - Layout.topMargin: 16 - headerText: "Jc - Junk packet count" textField.text: clientJunkPacketCount - textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { if (textField.text !== clientJunkPacketCount) { @@ -130,19 +126,13 @@ PageType { } } - checkEmptyText: true - KeyNavigation.tab: junkPacketMinSizeTextField.textField } - TextFieldWithHeaderType { + AwgTextField { id: junkPacketMinSizeTextField - Layout.fillWidth: true - Layout.topMargin: 16 - headerText: "Jmin - Junk packet minimum size" textField.text: clientJunkPacketMinSize - textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { if (textField.text !== clientJunkPacketMinSize) { @@ -150,28 +140,144 @@ PageType { } } - checkEmptyText: true - KeyNavigation.tab: junkPacketMaxSizeTextField.textField } - TextFieldWithHeaderType { + AwgTextField { id: junkPacketMaxSizeTextField - Layout.fillWidth: true - Layout.topMargin: 16 - headerText: "Jmax - Junk packet maximum size" textField.text: clientJunkPacketMaxSize - textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { if (textField.text !== clientJunkPacketMaxSize) { clientJunkPacketMaxSize = textField.text } } + } - checkEmptyText: true + AwgTextField { + id: specialJunk1TextField + headerText: qsTr("I1 - First special junk packet") + textField.text: clientSpecialJunk1 + textField.validator: null + checkEmptyText: false + textField.onEditingFinished: { + if (textField.text !== clientSpecialJunk1) { + clientSpecialJunk1 = textField.text + } + } + } + + AwgTextField { + id: specialJunk2TextField + headerText: qsTr("I2 - Second special junk packet") + textField.text: clientSpecialJunk2 + textField.validator: null + checkEmptyText: false + + textField.onEditingFinished: { + if (textField.text !== clientSpecialJunk2) { + clientSpecialJunk2 = textField.text + } + } + } + + AwgTextField { + id: specialJunk3TextField + headerText: qsTr("I3 - Third special junk packet") + textField.text: clientSpecialJunk3 + textField.validator: null + checkEmptyText: false + + textField.onEditingFinished: { + if (textField.text !== clientSpecialJunk3) { + clientSpecialJunk3 = textField.text + } + } + } + + AwgTextField { + id: specialJunk4TextField + headerText: qsTr("I4 - Fourth special junk packet") + textField.text: clientSpecialJunk4 + textField.validator: null + checkEmptyText: false + + textField.onEditingFinished: { + if (textField.text !== clientSpecialJunk4) { + clientSpecialJunk4 = textField.text + } + } + } + + AwgTextField { + id: specialJunk5TextField + headerText: qsTr("I5 - Fifth special junk packet") + textField.text: clientSpecialJunk5 + textField.validator: null + checkEmptyText: false + + textField.onEditingFinished: { + if (textField.text !== clientSpecialJunk5 ) { + clientSpecialJunk5 = textField.text + } + } + } + + AwgTextField { + id: controlledJunk1TextField + headerText: qsTr("J1 - First controlled junk packet") + textField.text: clientControlledJunk1 + textField.validator: null + checkEmptyText: false + + textField.onEditingFinished: { + if (textField.text !== clientControlledJunk1) { + clientControlledJunk1 = textField.text + } + } + } + + AwgTextField { + id: controlledJunk2TextField + headerText: qsTr("J2 - Second controlled junk packet") + textField.text: clientControlledJunk2 + textField.validator: null + checkEmptyText: false + + textField.onEditingFinished: { + if (textField.text !== clientControlledJunk2) { + clientControlledJunk2 = textField.text + } + } + } + + AwgTextField { + id: controlledJunk3TextField + headerText: qsTr("J3 - Third controlled junk packet") + textField.text: clientControlledJunk3 + textField.validator: null + checkEmptyText: false + + textField.onEditingFinished: { + if (textField.text !== clientControlledJunk3) { + clientControlledJunk3 = textField.text + } + } + } + + AwgTextField { + id: iTimeTextField + headerText: qsTr("Itime - Special handshake timeout") + textField.text: clientSpecialHandshakeTimeout + checkEmptyText: false + + textField.onEditingFinished: { + if (textField.text !== clientSpecialHandshakeTimeout) { + clientSpecialHandshakeTimeout = textField.text + } + } } Header2TextType { @@ -181,82 +287,78 @@ PageType { text: qsTr("Server settings") } - TextFieldWithHeaderType { + AwgTextField { id: portTextField - Layout.fillWidth: true - Layout.topMargin: 8 - enabled: false headerText: qsTr("Port") textField.text: port } - TextFieldWithHeaderType { + AwgTextField { id: initPacketJunkSizeTextField - Layout.fillWidth: true - Layout.topMargin: 16 - enabled: false headerText: "S1 - Init packet junk size" textField.text: serverInitPacketJunkSize } - TextFieldWithHeaderType { + AwgTextField { id: responsePacketJunkSizeTextField - Layout.fillWidth: true - Layout.topMargin: 16 - enabled: false headerText: "S2 - Response packet junk size" textField.text: serverResponsePacketJunkSize } - TextFieldWithHeaderType { - id: initPacketMagicHeaderTextField - Layout.fillWidth: true - Layout.topMargin: 16 + // AwgTextField { + // id: cookieReplyPacketJunkSizeTextField + // enabled: false + // headerText: "S3 - Cookie Reply packet junk size" + // textField.text: serverCookieReplyPacketJunkSize + // } + + // AwgTextField { + // id: transportPacketJunkSizeTextField + // enabled: false + + // headerText: "S4 - Transport packet junk size" + // textField.text: serverTransportPacketJunkSize + // } + + AwgTextField { + id: initPacketMagicHeaderTextField enabled: false headerText: "H1 - Init packet magic header" textField.text: serverInitPacketMagicHeader } - TextFieldWithHeaderType { + AwgTextField { id: responsePacketMagicHeaderTextField - Layout.fillWidth: true - Layout.topMargin: 16 - enabled: false headerText: "H2 - Response packet magic header" textField.text: serverResponsePacketMagicHeader } - TextFieldWithHeaderType { + AwgTextField { id: underloadPacketMagicHeaderTextField - Layout.fillWidth: true - Layout.topMargin: 16 - enabled: false headerText: "H3 - Underload packet magic header" textField.text: serverUnderloadPacketMagicHeader } - TextFieldWithHeaderType { + AwgTextField { id: transportPacketMagicHeaderTextField - Layout.fillWidth: true - Layout.topMargin: 16 - enabled: false headerText: "H4 - Transport packet magic header" textField.text: serverTransportPacketMagicHeader } + } } } diff --git a/client/ui/qml/Pages2/PageProtocolAwgSettings.qml b/client/ui/qml/Pages2/PageProtocolAwgSettings.qml index e8fd2b94..699ae724 100644 --- a/client/ui/qml/Pages2/PageProtocolAwgSettings.qml +++ b/client/ui/qml/Pages2/PageProtocolAwgSettings.qml @@ -138,184 +138,139 @@ PageType { checkEmptyText: true } - TextFieldWithHeaderType { + AwgTextField { id: junkPacketCountTextField - Layout.fillWidth: true - Layout.topMargin: 16 - headerText: qsTr("Jc - Junk packet count") textField.text: serverJunkPacketCount - textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { - if (textField.text === "") { - textField.text = "0" - } - if (textField.text !== serverJunkPacketCount) { serverJunkPacketCount = textField.text } } - - checkEmptyText: true } - TextFieldWithHeaderType { + AwgTextField { id: junkPacketMinSizeTextField - Layout.fillWidth: true - Layout.topMargin: 16 - headerText: qsTr("Jmin - Junk packet minimum size") textField.text: serverJunkPacketMinSize - textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { if (textField.text !== serverJunkPacketMinSize) { serverJunkPacketMinSize = textField.text } } - - checkEmptyText: true } - TextFieldWithHeaderType { + AwgTextField { id: junkPacketMaxSizeTextField - Layout.fillWidth: true - Layout.topMargin: 16 - headerText: qsTr("Jmax - Junk packet maximum size") textField.text: serverJunkPacketMaxSize - textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { if (textField.text !== serverJunkPacketMaxSize) { serverJunkPacketMaxSize = textField.text } } - - checkEmptyText: true } - TextFieldWithHeaderType { + AwgTextField { id: initPacketJunkSizeTextField - Layout.fillWidth: true - Layout.topMargin: 16 - headerText: qsTr("S1 - Init packet junk size") textField.text: serverInitPacketJunkSize - textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { if (textField.text !== serverInitPacketJunkSize) { serverInitPacketJunkSize = textField.text } } - - checkEmptyText: true - - onActiveFocusChanged: { - if(activeFocus) { - listview.positionViewAtEnd() - } - } } - TextFieldWithHeaderType { + AwgTextField { id: responsePacketJunkSizeTextField - Layout.fillWidth: true - Layout.topMargin: 16 - headerText: qsTr("S2 - Response packet junk size") textField.text: serverResponsePacketJunkSize - textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { if (textField.text !== serverResponsePacketJunkSize) { serverResponsePacketJunkSize = textField.text } } - - checkEmptyText: true - - onActiveFocusChanged: { - if(activeFocus) { - listview.positionViewAtEnd() - } - } } - TextFieldWithHeaderType { - id: initPacketMagicHeaderTextField - Layout.fillWidth: true - Layout.topMargin: 16 + // AwgTextField { + // id: cookieReplyPacketJunkSizeTextField + // headerText: qsTr("S3 - Cookie reply packet junk size") + // textField.text: serverCookieReplyPacketJunkSize + // textField.onEditingFinished: { + // if (textField.text !== serverCookieReplyPacketJunkSize) { + // serverCookieReplyPacketJunkSize = textField.text + // } + // } + // } + + // AwgTextField { + // id: transportPacketJunkSizeTextField + // headerText: qsTr("S4 - Transport packet junk size") + // textField.text: serverTransportPacketJunkSize + + // textField.onEditingFinished: { + // if (textField.text !== serverTransportPacketJunkSize) { + // serverTransportPacketJunkSize = textField.text + // } + // } + // } + + AwgTextField { + id: initPacketMagicHeaderTextField headerText: qsTr("H1 - Init packet magic header") textField.text: serverInitPacketMagicHeader - textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { if (textField.text !== serverInitPacketMagicHeader) { serverInitPacketMagicHeader = textField.text } } - - checkEmptyText: true } - TextFieldWithHeaderType { + AwgTextField { id: responsePacketMagicHeaderTextField - Layout.fillWidth: true - Layout.topMargin: 16 - headerText: qsTr("H2 - Response packet magic header") textField.text: serverResponsePacketMagicHeader - textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { if (textField.text !== serverResponsePacketMagicHeader) { serverResponsePacketMagicHeader = textField.text } } - - checkEmptyText: true } - TextFieldWithHeaderType { - id: transportPacketMagicHeaderTextField - Layout.fillWidth: true - Layout.topMargin: 16 - - headerText: qsTr("H4 - Transport packet magic header") - textField.text: serverTransportPacketMagicHeader - textField.validator: IntValidator { bottom: 0 } - - textField.onEditingFinished: { - if (textField.text !== serverTransportPacketMagicHeader) { - serverTransportPacketMagicHeader = textField.text - } - } - - checkEmptyText: true - } - - TextFieldWithHeaderType { + AwgTextField { id: underloadPacketMagicHeaderTextField - Layout.fillWidth: true - Layout.topMargin: 16 - headerText: qsTr("H3 - Underload packet magic header") textField.text: serverUnderloadPacketMagicHeader - textField.validator: IntValidator { bottom: 0 } textField.onEditingFinished: { if (textField.text !== serverUnderloadPacketMagicHeader) { serverUnderloadPacketMagicHeader = textField.text } } - - checkEmptyText: true } + AwgTextField { + id: transportPacketMagicHeaderTextField + headerText: qsTr("H4 - Transport packet magic header") + textField.text: serverTransportPacketMagicHeader + + textField.onEditingFinished: { + if (textField.text !== serverTransportPacketMagicHeader) { + serverTransportPacketMagicHeader = textField.text + } + } + } + + BasicButtonType { id: saveRestartButton @@ -328,6 +283,8 @@ PageType { responsePacketMagicHeaderTextField.errorText === "" && initPacketMagicHeaderTextField.errorText === "" && responsePacketJunkSizeTextField.errorText === "" && + // cookieReplyHeaderJunkTextField.errorText === "" && + // transportHeaderJunkTextField.errorText === "" && initPacketJunkSizeTextField.errorText === "" && junkPacketMaxSizeTextField.errorText === "" && junkPacketMinSizeTextField.errorText === "" && @@ -360,6 +317,13 @@ PageType { PageController.showErrorMessage(qsTr("The value of the field S1 + message initiation size (148) must not equal S2 + message response size (92)")) return } + // if (AwgConfigModel.isPacketSizeEqual(parseInt(initPacketJunkSizeTextField.textField.text), + // parseInt(responsePacketJunkSizeTextField.textField.text), + // parseInt(cookieReplyPacketJunkSizeTextField.textField.text), + // parseInt(transportPacketJunkSizeTextField.textField.text))) { + // PageController.showErrorMessage(qsTr("The value of the field S1 + message initiation size (148) must not equal S2 + message response size (92) + S3 + cookie reply size (64) + S4 + transport packet size (32)")) + // return + // } } var headerText = qsTr("Save settings?") From 5445e6637b6f126db78c3666828e2f9ed0c5e964 Mon Sep 17 00:00:00 2001 From: Nethius Date: Tue, 8 Jul 2025 14:25:03 +0800 Subject: [PATCH 38/40] chore: minor fixes (#1616) * chore: removed unnecessary qdebug * fix: return soft and hide strict killswitch --- .../ui/controllers/api/apiConfigsController.cpp | 2 -- client/ui/qml/Pages2/PageSettingsKillSwitch.qml | 15 ++++++++------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/client/ui/controllers/api/apiConfigsController.cpp b/client/ui/controllers/api/apiConfigsController.cpp index 0b0a9b92..0f42beb7 100644 --- a/client/ui/controllers/api/apiConfigsController.cpp +++ b/client/ui/controllers/api/apiConfigsController.cpp @@ -221,8 +221,6 @@ namespace serverConfig[configKey::apiConfig] = apiConfig; - qDebug() << serverConfig; - return ErrorCode::NoError; } } diff --git a/client/ui/qml/Pages2/PageSettingsKillSwitch.qml b/client/ui/qml/Pages2/PageSettingsKillSwitch.qml index ca1cd0d4..d6d73b20 100644 --- a/client/ui/qml/Pages2/PageSettingsKillSwitch.qml +++ b/client/ui/qml/Pages2/PageSettingsKillSwitch.qml @@ -62,8 +62,7 @@ PageType { Layout.leftMargin: 16 Layout.rightMargin: 16 - visible: false - // enabled: SettingsController.isKillSwitchEnabled && !ConnectionController.isConnected + enabled: SettingsController.isKillSwitchEnabled && !ConnectionController.isConnected checked: !SettingsController.strictKillSwitchEnabled text: qsTr("Soft KillSwitch") @@ -74,9 +73,7 @@ PageType { } } - DividerType { - visible: false - } + DividerType {} VerticalRadioButton { id: strictKillSwitch @@ -84,7 +81,9 @@ PageType { Layout.leftMargin: 16 Layout.rightMargin: 16 - enabled: SettingsController.isKillSwitchEnabled && !ConnectionController.isConnected + visible: false + enabled: false + // enabled: SettingsController.isKillSwitchEnabled && !ConnectionController.isConnected checked: SettingsController.strictKillSwitchEnabled text: qsTr("Strict KillSwitch") @@ -106,7 +105,9 @@ PageType { } } - DividerType {} + DividerType { + visible: false + } LabelWithButtonType { Layout.topMargin: 32 From 10a107716cf3a566f77e99a56066140e61bdae0b Mon Sep 17 00:00:00 2001 From: Nethius Date: Tue, 8 Jul 2025 15:06:52 +0800 Subject: [PATCH 39/40] fix: fixed awg 1.5 fields processing for ios (#1700) --- client/platforms/ios/WGConfig.swift | 76 ++++++++++++++++++++--------- 1 file changed, 53 insertions(+), 23 deletions(-) diff --git a/client/platforms/ios/WGConfig.swift b/client/platforms/ios/WGConfig.swift index 8f693387..537687f1 100644 --- a/client/platforms/ios/WGConfig.swift +++ b/client/platforms/ios/WGConfig.swift @@ -46,29 +46,59 @@ struct WGConfig: Decodable { } var settings: String { - junkPacketCount == nil ? "" : - """ - Jc = \(junkPacketCount!) - Jmin = \(junkPacketMinSize!) - Jmax = \(junkPacketMaxSize!) - S1 = \(initPacketJunkSize!) - S2 = \(responsePacketJunkSize!) - S3 = \(cookieReplyPacketJunkSize!) - S4 = \(transportPacketJunkSize!) - H1 = \(initPacketMagicHeader!) - H2 = \(responsePacketMagicHeader!) - H3 = \(underloadPacketMagicHeader!) - H4 = \(transportPacketMagicHeader!) - I1 = \(specialJunk1!) - I2 = \(specialJunk2!) - I3 = \(specialJunk3!) - I4 = \(specialJunk4!) - I5 = \(specialJunk5!) - J1 = \(controlledJunk1!) - J2 = \(controlledJunk2!) - J3 = \(controlledJunk3!) - Itime = \(specialHandshakeTimeout!) - """ + guard junkPacketCount != nil else { return "" } + + var settingsLines: [String] = [] + + // Required parameters when junkPacketCount is present + settingsLines.append("Jc = \(junkPacketCount!)") + settingsLines.append("Jmin = \(junkPacketMinSize!)") + settingsLines.append("Jmax = \(junkPacketMaxSize!)") + settingsLines.append("S1 = \(initPacketJunkSize!)") + settingsLines.append("S2 = \(responsePacketJunkSize!)") + + settingsLines.append("H1 = \(initPacketMagicHeader!)") + settingsLines.append("H2 = \(responsePacketMagicHeader!)") + settingsLines.append("H3 = \(underloadPacketMagicHeader!)") + settingsLines.append("H4 = \(transportPacketMagicHeader!)") + + // Optional parameters - only add if not nil and not empty + if let s3 = cookieReplyPacketJunkSize, !s3.isEmpty { + settingsLines.append("S3 = \(s3)") + } + if let s4 = transportPacketJunkSize, !s4.isEmpty { + settingsLines.append("S4 = \(s4)") + } + + if let i1 = specialJunk1, !i1.isEmpty { + settingsLines.append("I1 = \(i1)") + } + if let i2 = specialJunk2, !i2.isEmpty { + settingsLines.append("I2 = \(i2)") + } + if let i3 = specialJunk3, !i3.isEmpty { + settingsLines.append("I3 = \(i3)") + } + if let i4 = specialJunk4, !i4.isEmpty { + settingsLines.append("I4 = \(i4)") + } + if let i5 = specialJunk5, !i5.isEmpty { + settingsLines.append("I5 = \(i5)") + } + if let j1 = controlledJunk1, !j1.isEmpty { + settingsLines.append("J1 = \(j1)") + } + if let j2 = controlledJunk2, !j2.isEmpty { + settingsLines.append("J2 = \(j2)") + } + if let j3 = controlledJunk3, !j3.isEmpty { + settingsLines.append("J3 = \(j3)") + } + if let itime = specialHandshakeTimeout, !itime.isEmpty { + settingsLines.append("Itime = \(itime)") + } + + return settingsLines.joined(separator: "\n") } var str: String {