Compare commits

..

5 Commits

Author SHA1 Message Date
dranik 7b96cfec8c remove comment & reset file 2026-06-10 10:44:55 +03:00
dranik 657b0fd60c add close proc & fix check inet 2026-06-10 10:42:36 +03:00
dranik 1be22a3051 Fix: Add check inet and liveness monitor Xray 2026-06-09 16:06:56 +03:00
yp 594635e5cf fix: script remove docker volume (#2686)
* move sudo docker volume rm -f

* fix: remove unnecessary function

---------

Co-authored-by: vkamn <vk@amnezia.org>
2026-06-04 22:58:39 +08:00
vkamn f9b106cf5b fix: various fixes (#2693)
* fix: fixed country model update

* fix: fixed context menu crush on ios

* fix: fixed passphrase dialog freeze

* fix: fixed country switch

* fix: fixed start minimized

* fix: fixed black screen after remove container

* refactor: return cloak and ss only for view

* fix: fixed default server change after improt while connected

* fix: divider visibility

* fix: fixed revoke admin user

* fix: fixed language restore after backup

* fix: link hover for tor settings page

* fix: fixed openvpn connecntion status

* fix: fixed free color status

* fix: fixed client config update

* chore: bump version
2026-06-04 22:45:53 +08:00
76 changed files with 816 additions and 994 deletions
+2 -2
View File
@@ -4,7 +4,7 @@ set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(PROJECT AmneziaVPN)
set(AMNEZIAVPN_VERSION 4.9.0.1)
set(AMNEZIAVPN_VERSION 4.9.0.2)
set(QT_CREATOR_SKIP_PACKAGE_MANAGER_SETUP ON CACHE BOOL "" FORCE)
set(CMAKE_PROJECT_TOP_LEVEL_INCLUDES
@@ -28,7 +28,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 2122)
set(APP_ANDROID_VERSION_CODE 2123)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux")
-10
View File
@@ -260,16 +260,6 @@ if(WIN32)
)
endif()
if(APPLE AND NOT IOS AND NOT MACOS_NE)
set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/core/protocols/ikev2VpnProtocolMacos.h
)
set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/core/protocols/ikev2VpnProtocolMacos.mm
)
endif()
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
message("Client desktop build")
add_compile_definitions(AMNEZIA_DESKTOP)
@@ -49,14 +49,92 @@ void ConnectionController::setConnectionState(Vpn::ConnectionState state)
}
}
ErrorCode ConnectionController::prepareConnection(const QString &serverId,
QJsonObject& vpnConfiguration,
DockerContainer& container)
ErrorCode ConnectionController::defaultContainerForServer(const QString &serverId, DockerContainer &container) const
{
const auto kind = m_serversRepository->serverKind(serverId);
switch (kind) {
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
const auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
if (!cfg.has_value()) {
return ErrorCode::InternalError;
}
container = cfg->defaultContainer;
return ErrorCode::NoError;
}
case serverConfigUtils::ConfigType::SelfHostedUser: {
const auto cfg = m_serversRepository->selfHostedUserConfig(serverId);
if (!cfg.has_value()) {
return ErrorCode::InternalError;
}
container = cfg->defaultContainer;
return ErrorCode::NoError;
}
case serverConfigUtils::ConfigType::Native: {
const auto cfg = m_serversRepository->nativeConfig(serverId);
if (!cfg.has_value()) {
return ErrorCode::InternalError;
}
container = cfg->defaultContainer;
return ErrorCode::NoError;
}
case serverConfigUtils::ConfigType::AmneziaPremiumV2:
case serverConfigUtils::ConfigType::AmneziaFreeV3:
case serverConfigUtils::ConfigType::ExternalPremium: {
const auto cfg = m_serversRepository->apiV2Config(serverId);
if (!cfg.has_value()) {
return ErrorCode::InternalError;
}
container = cfg->defaultContainer;
return ErrorCode::NoError;
}
case serverConfigUtils::ConfigType::AmneziaPremiumV1:
case serverConfigUtils::ConfigType::AmneziaFreeV2:
return ErrorCode::LegacyApiV1NotSupportedError;
case serverConfigUtils::ConfigType::Invalid:
default:
return ErrorCode::InternalError;
}
}
ErrorCode ConnectionController::isConnectionSupported(const QString &serverId) const
{
if (serverId.isEmpty()) {
return ErrorCode::InternalError;
}
if (!isServiceReady()) {
return ErrorCode::AmneziaServiceNotRunning;
}
if (serverConfigUtils::isLegacyApiSubscription(m_serversRepository->serverKind(serverId))) {
return ErrorCode::LegacyApiV1NotSupportedError;
}
DockerContainer container = DockerContainer::None;
const ErrorCode errorCode = defaultContainerForServer(serverId, container);
if (errorCode != ErrorCode::NoError) {
return errorCode;
}
if (container == DockerContainer::None) {
return ErrorCode::NoInstalledContainersError;
}
if (ContainerUtils::isUnsupportedContainer(container)) {
return ErrorCode::LegacyContainerNotSupportedError;
}
if (!isContainerSupported(container)) {
return ErrorCode::NotSupportedOnThisPlatform;
}
return ErrorCode::NoError;
}
ErrorCode ConnectionController::prepareConnection(const QString &serverId,
QJsonObject& vpnConfiguration,
DockerContainer& container)
{
ContainerConfig containerConfigModel;
QPair<QString, QString> dns;
QString hostName;
@@ -120,10 +198,6 @@ ErrorCode ConnectionController::prepareConnection(const QString &serverId,
return ErrorCode::InternalError;
}
if (!isContainerSupported(container)) {
return ErrorCode::NotSupportedOnThisPlatform;
}
vpnConfiguration = createConnectionConfiguration(dns, isApiConfig, hostName, description, configVersion,
containerConfigModel, container);
@@ -34,6 +34,8 @@ public:
QJsonObject& vpnConfiguration,
DockerContainer& container);
ErrorCode isConnectionSupported(const QString &serverId) const;
ErrorCode openConnection(const QString &serverId);
void closeConnection();
@@ -73,6 +75,8 @@ signals:
#endif
private:
ErrorCode defaultContainerForServer(const QString &serverId, DockerContainer &container) const;
SecureServersRepository* m_serversRepository;
SecureAppSettingsRepository* m_appSettingsRepository;
VpnConnection* m_vpnConnection;
+1 -1
View File
@@ -191,7 +191,7 @@ void CoreController::initControllers()
m_languageUiController = new LanguageUiController(m_settingsController, m_languageModel, this);
setQmlContextProperty("LanguageUiController", m_languageUiController);
m_settingsUiController = new SettingsUiController(m_settingsController, m_serversController, m_languageUiController, this);
m_settingsUiController = new SettingsUiController(m_settingsController, m_serversController, this);
setQmlContextProperty("SettingsController", m_settingsUiController);
m_pageController = new PageController(m_serversController, m_settingsController, this);
+23 -19
View File
@@ -33,7 +33,6 @@
#include "core/controllers/connectionController.h"
#include "ui/models/clientManagementModel.h"
#include "ui/controllers/api/apiNewsUiController.h"
#include "ui/models/api/apiCountryModel.h"
#include "ui/models/containersModel.h"
#include "core/utils/containerEnum.h"
@@ -156,15 +155,17 @@ void CoreSignalHandlers::initExportControllerHandler()
void CoreSignalHandlers::initImportControllerHandler()
{
connect(m_coreController->m_importCoreController, &ImportController::importFinished, this, [this]() {
if (!m_coreController->m_connectionController->isConnected()) {
int newServerIndex = m_coreController->m_serversController->getServersCount() - 1;
const QString serverId = m_coreController->m_serversController->getServerId(newServerIndex);
if (!serverId.isEmpty()) {
m_coreController->m_serversController->setDefaultServer(serverId);
}
if (m_coreController->m_serversUiController) {
m_coreController->m_serversUiController->setProcessedServerId(serverId);
}
if (m_coreController->m_connectionUiController->isConnected()) {
return;
}
const int newServerIndex = m_coreController->m_serversController->getServersCount() - 1;
const QString serverId = m_coreController->m_serversController->getServerId(newServerIndex);
if (!serverId.isEmpty()) {
m_coreController->m_serversController->setDefaultServer(serverId);
}
if (m_coreController->m_serversUiController) {
m_coreController->m_serversUiController->setProcessedServerId(serverId);
}
});
}
@@ -176,17 +177,14 @@ void CoreSignalHandlers::initApiCountryModelUpdateHandler()
if (processedServerId.isEmpty()) {
return;
}
QJsonArray availableCountries;
QString serverCountryCode;
const auto apiV2 = m_coreController->m_serversRepository->apiV2Config(processedServerId);
if (apiV2.has_value()) {
availableCountries = apiV2->apiConfig.availableCountries;
serverCountryCode = apiV2->apiConfig.serverCountryCode;
if (!apiV2.has_value()) {
return;
}
m_coreController->m_apiCountryModel->updateModel(availableCountries, serverCountryCode);
m_coreController->m_apiCountryModel->updateModel(apiV2->apiConfig.availableCountries,
apiV2->apiConfig.serverCountryCode);
});
}
@@ -237,13 +235,16 @@ void CoreSignalHandlers::initLanguageHandler()
connect(m_coreController->m_settingsUiController, &SettingsUiController::resetLanguageToSystem, m_coreController->m_languageUiController, [this]() {
m_coreController->m_languageUiController->changeLanguage(m_coreController->m_languageUiController->getSystemLanguageEnum());
});
connect(m_coreController->m_settingsUiController, &SettingsUiController::appLanguageChanged, m_coreController->m_languageUiController, [this]() {
m_coreController->m_languageUiController->onAppLanguageChanged(m_coreController->m_settingsController->getAppLanguage());
});
}
void CoreSignalHandlers::initAutoConnectHandler()
{
if (m_coreController->m_settingsUiController->isAutoConnectEnabled()
&& !m_coreController->m_serversController->getDefaultServerId().isEmpty()) {
QTimer::singleShot(1000, this, [this]() { m_coreController->m_connectionUiController->openConnection(); });
QTimer::singleShot(1000, this, [this]() { m_coreController->m_connectionUiController->toggleConnection(); });
}
}
@@ -348,6 +349,9 @@ void CoreSignalHandlers::initUnsupportedConnectDrawerHandler()
{
connect(m_coreController->m_subscriptionUiController, &SubscriptionUiController::unsupportedConnectDrawerRequested,
m_coreController->m_pageController, &PageController::unsupportedConnectDrawerRequested);
connect(m_coreController->m_connectionUiController, &ConnectionUiController::unsupportedConnectDrawerRequested,
m_coreController->m_pageController, &PageController::unsupportedConnectDrawerRequested);
}
void CoreSignalHandlers::initStrictKillSwitchHandler()
@@ -72,6 +72,16 @@ namespace
}
return false;
}
QString buildRemoveContainerScript(const amnezia::ScriptVars &vars, bool removeDataVolume)
{
QString script = SshSession::replaceVars(amnezia::scriptData(SharedScriptType::remove_container), vars);
if (removeDataVolume) {
script += QLatin1String("\nsudo docker volume rm -f $CONTAINER_NAME-data 2>/dev/null || true");
script = SshSession::replaceVars(script, vars);
}
return script;
}
}
InstallController::InstallController(SecureServersRepository *serversRepository,
@@ -120,14 +130,10 @@ ErrorCode InstallController::setupContainer(const ServerCredentials &credentials
return e;
qDebug().noquote() << "InstallController::setupContainer prepareHostWorker finished";
amnezia::ScriptVars removeContainerVars =
const amnezia::ScriptVars removeContainerVars =
amnezia::genBaseVars(credentials, container, QString(), QString());
if (!isUpdate) {
removeContainerVars.append({ { "$REMOVE_CONTAINER_DATA", QStringLiteral("1") } });
}
sshSession.runScript(credentials,
sshSession.replaceVars(amnezia::scriptData(SharedScriptType::remove_container),
removeContainerVars));
const bool removeDataVolume = !isUpdate && (container == DockerContainer::MtProxy || container == DockerContainer::Telemt);
sshSession.runScript(credentials, buildRemoveContainerScript(removeContainerVars, removeDataVolume));
qDebug().noquote() << "InstallController::setupContainer removeContainer finished";
qDebug().noquote() << "buildContainerWorker start";
@@ -152,8 +158,8 @@ ErrorCode InstallController::setupContainer(const ServerCredentials &credentials
return startupContainerWorker(credentials, container, config, sshSession);
}
ErrorCode InstallController::updateContainer(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig,
ContainerConfig &newConfig)
ErrorCode InstallController::updateServerConfig(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig,
ContainerConfig &newConfig)
{
if (!isUpdateDockerContainerRequired(container, oldConfig, newConfig)) {
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
@@ -185,7 +191,7 @@ ErrorCode InstallController::updateContainer(const QString &serverId, DockerCont
SshSession sshSession(this);
bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig);
qDebug() << "InstallController::updateContainer for container" << container << "reinstall required is" << reinstallRequired;
qDebug() << "InstallController::updateServerConfig for container" << container << "reinstall required is" << reinstallRequired;
bool xrayServerSettingsChanged = false;
if (container == DockerContainer::Xray || container == DockerContainer::SSXray) {
@@ -213,11 +219,11 @@ ErrorCode InstallController::updateContainer(const QString &serverId, DockerCont
if (errorCode == ErrorCode::NoError && xrayServerSettingsChanged && !skipXrayInboundSync) {
DnsSettings dnsSettings = { m_appSettingsRepository->primaryDns(), m_appSettingsRepository->secondaryDns() };
XrayConfigurator xrayConfigurator(&sshSession);
qDebug() << "InstallController::updateContainer applying Xray server inbound sync, reinstall="
qDebug() << "InstallController::updateServerConfig applying Xray server inbound sync, reinstall="
<< reinstallRequired;
errorCode = xrayConfigurator.applyServerSettingsToRemote(credentials, container, newConfig, dnsSettings, false);
if (errorCode != ErrorCode::NoError) {
qDebug() << "InstallController::updateContainer Xray inbound sync failed, error="
qDebug() << "InstallController::updateServerConfig Xray inbound sync failed, error="
<< static_cast<int>(errorCode);
}
}
@@ -236,6 +242,41 @@ ErrorCode InstallController::updateContainer(const QString &serverId, DockerCont
return errorCode;
}
ErrorCode InstallController::updateClientConfig(const QString &serverId, DockerContainer container, ContainerConfig &newConfig)
{
switch (m_serversRepository->serverKind(serverId)) {
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
auto config = m_serversRepository->selfHostedAdminConfig(serverId);
if (!config.has_value()) {
return ErrorCode::InternalError;
}
config->updateContainerConfig(container, newConfig);
m_serversRepository->editServer(serverId, config->toJson(), serverConfigUtils::ConfigType::SelfHostedAdmin);
return ErrorCode::NoError;
}
case serverConfigUtils::ConfigType::SelfHostedUser: {
auto config = m_serversRepository->selfHostedUserConfig(serverId);
if (!config.has_value()) {
return ErrorCode::InternalError;
}
config->updateContainerConfig(container, newConfig);
m_serversRepository->editServer(serverId, config->toJson(), serverConfigUtils::ConfigType::SelfHostedUser);
return ErrorCode::NoError;
}
case serverConfigUtils::ConfigType::Native: {
auto config = m_serversRepository->nativeConfig(serverId);
if (!config.has_value()) {
return ErrorCode::InternalError;
}
config->updateContainerConfig(container, newConfig);
m_serversRepository->editServer(serverId, config->toJson(), serverConfigUtils::ConfigType::Native);
return ErrorCode::NoError;
}
default:
return ErrorCode::InternalError;
}
}
void InstallController::clearCachedProfile(const QString &serverId, DockerContainer container)
{
if (ContainerUtils::containerService(container) == ServiceType::Other) {
@@ -980,12 +1021,11 @@ ErrorCode InstallController::removeContainer(const QString &serverId, DockerCont
return ErrorCode::InternalError;
}
SshSession sshSession(this);
amnezia::ScriptVars removeContainerVars =
const amnezia::ScriptVars removeContainerVars =
amnezia::genBaseVars(credentials, container, QString(), QString());
removeContainerVars.append({ { "$REMOVE_CONTAINER_DATA", QStringLiteral("1") } });
ErrorCode errorCode = sshSession.runScript(
credentials,
sshSession.replaceVars(amnezia::scriptData(SharedScriptType::remove_container), removeContainerVars));
const bool removeDataVolume = (container == DockerContainer::MtProxy || container == DockerContainer::Telemt);
ErrorCode errorCode =
sshSession.runScript(credentials, buildRemoveContainerScript(removeContainerVars, removeDataVolume));
if (errorCode == ErrorCode::NoError) {
QMap<DockerContainer, ContainerConfig> containers = adminConfig->containers;
@@ -1463,7 +1503,7 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
QString transportProtoStr = containerAndPortMatch.captured(3);
DockerContainer container = ContainerUtils::containerFromString(name);
if (container == DockerContainer::None) {
if (container == DockerContainer::None || ContainerUtils::isUnsupportedContainer(container)) {
continue;
}
@@ -1488,7 +1528,7 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
QString transportProtoStr = torOrDnsRegMatch.captured(3);
DockerContainer container = ContainerUtils::containerFromString(name);
if (container == DockerContainer::None) {
if (container == DockerContainer::None || ContainerUtils::isUnsupportedContainer(container)) {
continue;
}
@@ -34,7 +34,12 @@ public:
~InstallController();
ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, ContainerConfig &config, bool isUpdate = false);
ErrorCode updateContainer(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig, ContainerConfig &newConfig);
// Updates server-side container settings (admin self-hosted only): reconfigures the container over SSH.
ErrorCode updateServerConfig(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig, ContainerConfig &newConfig);
// Updates client-local settings only: rewrites the stored container config for any self-hosted/native server. No SSH.
ErrorCode updateClientConfig(const QString &serverId, DockerContainer container, ContainerConfig &newConfig);
ErrorCode rebootServer(const QString &serverId);
ErrorCode removeAllContainers(const QString &serverId);
@@ -29,6 +29,11 @@ ContainerConfig NativeServerConfig::containerConfig(DockerContainer container) c
return containers.value(container);
}
void NativeServerConfig::updateContainerConfig(DockerContainer container, const ContainerConfig &config)
{
containers[container] = config;
}
QPair<QString, QString> NativeServerConfig::getDnsPair(const QString &primaryDns, const QString &secondaryDns) const
{
QString d1 = dns1;
@@ -27,6 +27,8 @@ struct NativeServerConfig {
bool hasContainers() const;
ContainerConfig containerConfig(DockerContainer container) const;
void updateContainerConfig(DockerContainer container, const ContainerConfig &config);
QPair<QString, QString> getDnsPair(const QString &primaryDns, const QString &secondaryDns) const;
QJsonObject toJson() const;
@@ -43,6 +43,11 @@ ContainerConfig SelfHostedUserServerConfig::containerConfig(DockerContainer cont
return containers.value(container);
}
void SelfHostedUserServerConfig::updateContainerConfig(DockerContainer container, const ContainerConfig &config)
{
containers[container] = config;
}
QPair<QString, QString> SelfHostedUserServerConfig::getDnsPair(const QString &primaryDns,
const QString &secondaryDns) const
{
@@ -32,6 +32,8 @@ struct SelfHostedUserServerConfig {
bool hasContainers() const;
ContainerConfig containerConfig(DockerContainer container) const;
void updateContainerConfig(DockerContainer container, const ContainerConfig &config);
QPair<QString, QString> getDnsPair(const QString &primaryDns, const QString &secondaryDns) const;
QJsonObject toJson() const;
@@ -1,64 +0,0 @@
#ifndef IKEV2_VPN_PROTOCOL_MACOS_H
#define IKEV2_VPN_PROTOCOL_MACOS_H
#include <QJsonObject>
#include <QObject>
#include <QString>
#include <QTimer>
#include "vpnProtocol.h"
#if defined(__OBJC__)
#include <NetworkExtension/NetworkExtension.h>
#endif
class Ikev2ProtocolMacos : public VpnProtocol
{
Q_OBJECT
public:
explicit Ikev2ProtocolMacos(const QJsonObject &configuration, QObject *parent = nullptr);
~Ikev2ProtocolMacos() override;
ErrorCode start() override;
void stop() override;
static QString tunnelName() { return "AmneziaVPN IKEv2"; }
private:
void readIkev2Configuration(const QJsonObject &configuration);
bool storeClientIdentity();
void handleStatusChange(int rawStatus);
void startTunnelNow();
void reportError(ErrorCode code);
void startHandshakeTimeoutTimer();
void stopHandshakeTimeoutTimer();
void removeStatusObserver();
private:
QJsonObject m_config;
QString m_hostName;
QString m_clientId;
QString m_clientCertBase64;
QString m_clientCertPassword;
QTimer *m_handshakeTimeoutTimer { nullptr };
bool m_handshakeTimedOut { false };
bool m_startWhenDisconnected { false };
bool m_tunnelStarted { false };
int m_startRetries { 0 };
void *m_statusObserver { nullptr };
int m_lastVpnStatus { 0 };
static constexpr int HANDSHAKE_TIMEOUT_SEC = 20;
static constexpr int MAX_START_RETRIES = 5;
};
#endif // IKEV2_VPN_PROTOCOL_MACOS_H
@@ -1,666 +0,0 @@
#include "ikev2VpnProtocolMacos.h"
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QPointer>
#include <QTemporaryFile>
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/ipcClient.h"
#include "ipc.h"
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/pkcs12.h>
#include <openssl/x509.h>
#import <Foundation/Foundation.h>
#import <NetworkExtension/NetworkExtension.h>
#import <Security/Security.h>
namespace {
const char *kRepackedP12Password = "amnezia";
const char *kVpnSystemKeychainPath = "/Library/Keychains/System.keychain";
const char *vpnStatusName(int status)
{
switch (status) {
case NEVPNStatusInvalid: return "Invalid";
case NEVPNStatusDisconnected: return "Disconnected";
case NEVPNStatusConnecting: return "Connecting";
case NEVPNStatusConnected: return "Connected";
case NEVPNStatusReasserting: return "Reasserting";
case NEVPNStatusDisconnecting: return "Disconnecting";
default: return "Unknown";
}
}
bool prepareIdentity(const QByteArray &source, const QString &friendlyName, QByteArray &repackedP12, QByteArray &caCertDer)
{
const unsigned char *cursor = reinterpret_cast<const unsigned char *>(source.constData());
PKCS12 *sourceP12 = d2i_PKCS12(nullptr, &cursor, source.size());
if (!sourceP12) {
qCritical() << "[IKEv2-mac] failed to parse client p12 container";
return false;
}
EVP_PKEY *privateKey = nullptr;
X509 *certificate = nullptr;
STACK_OF(X509) *caChain = nullptr;
int parsed = PKCS12_parse(sourceP12, "", &privateKey, &certificate, &caChain);
PKCS12_free(sourceP12);
if (!parsed || !privateKey || !certificate) {
qCritical() << "[IKEv2-mac] failed to extract key/certificate from client p12";
if (privateKey) EVP_PKEY_free(privateKey);
if (certificate) X509_free(certificate);
if (caChain) sk_X509_pop_free(caChain, X509_free);
return false;
}
const int caCount = caChain ? sk_X509_num(caChain) : 0;
qInfo() << "[IKEv2-mac] CA certificates bundled in client p12:" << caCount;
if (caCount > 0) {
X509 *caCert = sk_X509_value(caChain, caCount - 1);
unsigned char *caEncoded = nullptr;
int caLength = i2d_X509(caCert, &caEncoded);
if (caLength > 0 && caEncoded) {
caCertDer = QByteArray(reinterpret_cast<const char *>(caEncoded), caLength);
OPENSSL_free(caEncoded);
}
}
PKCS12 *repacked = PKCS12_create(kRepackedP12Password,
friendlyName.toUtf8().constData(),
privateKey,
certificate,
caChain,
NID_pbe_WithSHA1And3_Key_TripleDES_CBC,
NID_pbe_WithSHA1And3_Key_TripleDES_CBC,
PKCS12_DEFAULT_ITER,
PKCS12_DEFAULT_ITER,
0);
EVP_PKEY_free(privateKey);
X509_free(certificate);
if (caChain) sk_X509_pop_free(caChain, X509_free);
if (!repacked) {
qCritical() << "[IKEv2-mac] failed to repackage client p12";
return false;
}
unsigned char *encoded = nullptr;
int encodedLength = i2d_PKCS12(repacked, &encoded);
PKCS12_free(repacked);
if (encodedLength <= 0 || !encoded) {
qCritical() << "[IKEv2-mac] failed to serialize repackaged client p12";
return false;
}
repackedP12 = QByteArray(reinterpret_cast<const char *>(encoded), encodedLength);
OPENSSL_free(encoded);
return true;
}
void removeIdentityFromLoginKeychain(const QString &label)
{
NSDictionary *query = @{
(__bridge id)kSecClass : (__bridge id)kSecClassIdentity,
(__bridge id)kSecAttrLabel : label.toNSString(),
(__bridge id)kSecMatchLimit : (__bridge id)kSecMatchLimitAll,
};
SecItemDelete((__bridge CFDictionaryRef)query);
}
bool importIdentityToLoginKeychain(const QByteArray &p12, const QString &label)
{
SecKeychainRef loginKeychain = NULL;
if (SecKeychainCopyDefault(&loginKeychain) != errSecSuccess || loginKeychain == NULL) {
qCritical() << "[IKEv2-mac] cannot open the login keychain";
return false;
}
CFMutableArrayRef trustedApps = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
SecTrustedApplicationRef selfApp = NULL;
if (SecTrustedApplicationCreateFromPath(NULL, &selfApp) == errSecSuccess) {
CFArrayAppendValue(trustedApps, selfApp);
CFRelease(selfApp);
}
SecTrustedApplicationRef neAgent = NULL;
if (SecTrustedApplicationCreateFromPath("/usr/libexec/neagent", &neAgent) == errSecSuccess) {
CFArrayAppendValue(trustedApps, neAgent);
CFRelease(neAgent);
}
SecAccessRef access = NULL;
OSStatus accessStatus = SecAccessCreate((__bridge CFStringRef)label.toNSString(), trustedApps, &access);
CFRelease(trustedApps);
if (accessStatus != errSecSuccess || access == NULL) {
qCritical() << "[IKEv2-mac] SecAccessCreate failed, status" << (int)accessStatus;
CFRelease(loginKeychain);
return false;
}
NSData *p12Data = [NSData dataWithBytes:p12.constData() length:p12.size()];
NSDictionary *options = @{
(__bridge id)kSecImportExportPassphrase : [NSString stringWithUTF8String:kRepackedP12Password],
(__bridge id)kSecImportExportKeychain : (__bridge id)loginKeychain,
(__bridge id)kSecImportExportAccess : (__bridge id)access,
};
CFArrayRef items = NULL;
OSStatus importStatus = SecPKCS12Import((__bridge CFDataRef)p12Data, (__bridge CFDictionaryRef)options, &items);
if (items) {
CFRelease(items);
}
CFRelease(access);
CFRelease(loginKeychain);
if (importStatus != errSecSuccess) {
qCritical() << "[IKEv2-mac] SecPKCS12Import into login keychain failed, status" << (int)importStatus;
return false;
}
return true;
}
NSData *copyIdentityPersistentRef(const QString &label)
{
SecKeychainRef loginKeychain = NULL;
SecKeychainCopyDefault(&loginKeychain);
NSMutableDictionary *query = [@{
(__bridge id)kSecClass : (__bridge id)kSecClassIdentity,
(__bridge id)kSecAttrLabel : label.toNSString(),
(__bridge id)kSecMatchLimit : (__bridge id)kSecMatchLimitOne,
(__bridge id)kSecReturnPersistentRef : @YES,
} mutableCopy];
if (loginKeychain) {
query[(__bridge id)kSecMatchSearchList] = @[ (__bridge id)loginKeychain ];
}
CFTypeRef persistentRef = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &persistentRef);
if (loginKeychain) {
CFRelease(loginKeychain);
}
if (status != errSecSuccess || persistentRef == NULL) {
qDebug() << "[IKEv2-mac] client identity not present in login keychain, status" << (int)status;
return nil;
}
return (NSData *)CFAutorelease(persistentRef);
}
void runPrivilegedSecurity(const QString &label, const QStringList &arguments)
{
auto process = IpcClient::CreatePrivilegedProcess();
if (!process) {
qCritical() << "[IKEv2-mac] privileged service is unavailable for" << label;
return;
}
process->setProgram(PermittedProcess::Security);
process->setArguments(arguments);
process->start();
bool started = false;
{
auto reply = process->waitForStarted(5000);
reply.waitForFinished();
started = reply.returnValue();
}
bool finished = false;
{
auto reply = process->waitForFinished(15000);
reply.waitForFinished();
finished = reply.returnValue();
}
QByteArray out;
QByteArray err;
{
auto reply = process->readAllStandardOutput();
reply.waitForFinished();
out = reply.returnValue();
}
{
auto reply = process->readAllStandardError();
reply.waitForFinished();
err = reply.returnValue();
}
qInfo() << "[IKEv2-mac]" << label << "started" << started << "finished" << finished
<< "| out:" << out.trimmed() << "| err:" << err.trimmed();
}
void disableVpnConfiguration()
{
NEVPNManager *manager = [NEVPNManager sharedManager];
if (!manager.enabled) {
return;
}
manager.enabled = NO;
[manager saveToPreferencesWithCompletionHandler:^(NSError *error) {
if (error) {
qWarning() << "[IKEv2-mac] failed to disable VPN configuration:"
<< QString::fromNSString(error.localizedDescription);
} else {
qInfo() << "[IKEv2-mac] VPN configuration disabled";
}
}];
}
} // namespace
Ikev2ProtocolMacos::Ikev2ProtocolMacos(const QJsonObject &configuration, QObject *parent)
: VpnProtocol(configuration, parent)
{
readIkev2Configuration(configuration);
}
Ikev2ProtocolMacos::~Ikev2ProtocolMacos()
{
qInfo() << "[IKEv2-mac] ~Ikev2ProtocolMacos()";
if (m_handshakeTimeoutTimer) {
m_handshakeTimeoutTimer->stop();
m_handshakeTimeoutTimer = nullptr;
}
removeStatusObserver();
}
void Ikev2ProtocolMacos::readIkev2Configuration(const QJsonObject &configuration)
{
m_config = configuration.value(ProtocolUtils::key_proto_config_data(Proto::Ikev2)).toObject();
m_hostName = m_config.value(configKey::hostName).toString();
m_clientId = m_config.value(configKey::userName).toString();
m_clientCertBase64 = m_config.value(configKey::cert).toString();
m_clientCertPassword = m_config.value(configKey::password).toString();
}
bool Ikev2ProtocolMacos::storeClientIdentity()
{
if (copyIdentityPersistentRef(m_clientId) != nil) {
qInfo() << "[IKEv2-mac] client identity already in login keychain, reusing it";
return true;
}
qInfo() << "[IKEv2-mac] installing client identity into login keychain";
QByteArray sourceP12 = QByteArray::fromBase64(m_clientCertBase64.toUtf8());
QByteArray repackedP12;
QByteArray caCertDer;
if (!prepareIdentity(sourceP12, m_clientId, repackedP12, caCertDer)) {
return false;
}
removeIdentityFromLoginKeychain(m_clientId);
if (!importIdentityToLoginKeychain(repackedP12, m_clientId)) {
return false;
}
if (!caCertDer.isEmpty()) {
QTemporaryFile caFile(QDir::tempPath() + "/amnezia-ikev2-ca-XXXXXX.cer");
caFile.setAutoRemove(false);
if (caFile.open()) {
const QString caPath = caFile.fileName();
caFile.write(caCertDer);
caFile.close();
runPrivilegedSecurity("CA trust", { "add-trusted-cert", "-d", "-r", "trustRoot",
"-k", QString::fromLatin1(kVpnSystemKeychainPath), caPath });
QFile::remove(caPath);
}
} else {
qWarning() << "[IKEv2-mac] no CA certificate in client p12; server validation may fail";
}
return true;
}
void Ikev2ProtocolMacos::reportError(ErrorCode code)
{
QMetaObject::invokeMethod(this, [this, code]() { setLastError(code); }, Qt::QueuedConnection);
}
void Ikev2ProtocolMacos::removeStatusObserver()
{
if (m_statusObserver) {
NEVPNManager *manager = [NEVPNManager sharedManager];
[[NSNotificationCenter defaultCenter] removeObserver:(id)m_statusObserver
name:NEVPNStatusDidChangeNotification
object:manager.connection];
m_statusObserver = nullptr;
}
}
ErrorCode Ikev2ProtocolMacos::start()
{
qInfo() << "[IKEv2-mac] start() requested, host =" << m_hostName << ", clientId =" << m_clientId;
if (m_hostName.isEmpty() || m_clientCertBase64.isEmpty()) {
qCritical() << "[IKEv2-mac] missing server address or client certificate";
setLastError(ErrorCode::IKEv2ConfigError);
return ErrorCode::IKEv2ConfigError;
}
if (!storeClientIdentity()) {
setLastError(ErrorCode::IKEv2ConfigError);
return ErrorCode::IKEv2ConfigError;
}
m_handshakeTimedOut = false;
m_lastVpnStatus = NEVPNStatusInvalid;
m_startRetries = 0;
m_tunnelStarted = false;
setConnectionState(Vpn::ConnectionState::Connecting);
NSString *nsServerAddress = m_hostName.toNSString();
NSString *nsLocalIdentifier = m_clientId.toNSString();
QString clientId = m_clientId;
QPointer<Ikev2ProtocolMacos> self = this;
dispatch_async(dispatch_get_main_queue(), ^{
if (!self) return;
NEVPNManager *manager = [NEVPNManager sharedManager];
[manager loadFromPreferencesWithCompletionHandler:^(NSError *loadError) {
if (!self) return;
if (loadError) {
qCritical() << "[IKEv2-mac] loading VPN preferences failed:"
<< QString::fromNSString(loadError.localizedDescription);
self->reportError(ErrorCode::IKEv2LoadError);
return;
}
NSData *identityReference = copyIdentityPersistentRef(clientId);
if (identityReference == nil) {
self->reportError(ErrorCode::IKEv2ConfigError);
return;
}
NEVPNProtocolIKEv2 *protocol = [[NEVPNProtocolIKEv2 alloc] init];
protocol.serverAddress = nsServerAddress;
protocol.remoteIdentifier = nsServerAddress;
protocol.localIdentifier = nsLocalIdentifier;
protocol.authenticationMethod = NEVPNIKEAuthenticationMethodCertificate;
protocol.certificateType = NEVPNIKEv2CertificateTypeRSA;
protocol.identityReference = identityReference;
protocol.useExtendedAuthentication = NO;
protocol.enablePFS = NO;
protocol.disconnectOnSleep = NO;
protocol.deadPeerDetectionRate = NEVPNIKEv2DeadPeerDetectionRateMedium;
protocol.IKESecurityAssociationParameters.encryptionAlgorithm = NEVPNIKEv2EncryptionAlgorithmAES256;
protocol.IKESecurityAssociationParameters.integrityAlgorithm = NEVPNIKEv2IntegrityAlgorithmSHA256;
protocol.IKESecurityAssociationParameters.diffieHellmanGroup = NEVPNIKEv2DiffieHellmanGroup14;
protocol.IKESecurityAssociationParameters.lifetimeMinutes = 1410;
protocol.childSecurityAssociationParameters.encryptionAlgorithm = NEVPNIKEv2EncryptionAlgorithmAES128GCM;
protocol.childSecurityAssociationParameters.diffieHellmanGroup = NEVPNIKEv2DiffieHellmanGroup14;
protocol.childSecurityAssociationParameters.lifetimeMinutes = 1410;
[manager setProtocolConfiguration:protocol];
[manager setLocalizedDescription:Ikev2ProtocolMacos::tunnelName().toNSString()];
[manager setEnabled:YES];
[manager setOnDemandEnabled:NO];
[manager saveToPreferencesWithCompletionHandler:^(NSError *firstSaveError) {
if (!self) return;
if (firstSaveError) {
qCritical() << "[IKEv2-mac] saving VPN preferences failed:"
<< QString::fromNSString(firstSaveError.localizedDescription);
self->reportError(ErrorCode::IKEv2SaveError);
return;
}
[manager loadFromPreferencesWithCompletionHandler:^(NSError *reloadError) {
if (!self) return;
if (reloadError) {
qCritical() << "[IKEv2-mac] reloading VPN preferences failed:"
<< QString::fromNSString(reloadError.localizedDescription);
self->reportError(ErrorCode::IKEv2LoadError);
return;
}
[manager saveToPreferencesWithCompletionHandler:^(NSError *resaveError) {
if (!self) return;
if (resaveError) {
qCritical() << "[IKEv2-mac] re-saving VPN preferences failed:"
<< QString::fromNSString(resaveError.localizedDescription);
self->reportError(ErrorCode::IKEv2SaveError);
return;
}
self->removeStatusObserver();
self->m_statusObserver = (void *)[[NSNotificationCenter defaultCenter]
addObserverForName:NEVPNStatusDidChangeNotification
object:manager.connection
queue:nil
usingBlock:^(NSNotification *notification) {
if (!self) return;
NEVPNConnection *connection = notification.object;
int rawStatus = (int)connection.status;
QMetaObject::invokeMethod(
self, [self, rawStatus]() { if (self) self->handleStatusChange(rawStatus); },
Qt::QueuedConnection);
}];
NEVPNStatus current = manager.connection.status;
if (current == NEVPNStatusDisconnected || current == NEVPNStatusInvalid) {
qInfo() << "[IKEv2-mac] preferences saved, connection is"
<< vpnStatusName(current) << "- starting tunnel now";
self->m_startWhenDisconnected = false;
self->startTunnelNow();
} else {
qInfo() << "[IKEv2-mac] preferences saved, connection is"
<< vpnStatusName(current) << "- waiting for it to disconnect first";
self->m_startWhenDisconnected = true;
[manager.connection stopVPNTunnel];
}
}];
}];
}];
}];
});
return ErrorCode::NoError;
}
void Ikev2ProtocolMacos::stop()
{
qInfo() << "[IKEv2-mac] stop() requested";
stopHandshakeTimeoutTimer();
m_startWhenDisconnected = false;
NEVPNManager *manager = [NEVPNManager sharedManager];
NEVPNStatus status = manager.connection.status;
qInfo() << "[IKEv2-mac] stop(): current NEVPNStatus =" << (int)status;
removeStatusObserver();
if (status != NEVPNStatusDisconnected && status != NEVPNStatusInvalid) {
[manager.connection stopVPNTunnel];
qInfo() << "[IKEv2-mac] stop(): stopVPNTunnel issued";
}
disableVpnConfiguration();
setConnectionState(Vpn::ConnectionState::Disconnected);
}
void Ikev2ProtocolMacos::startTunnelNow()
{
qInfo() << "[IKEv2-mac] startVPNTunnel (attempt" << (m_startRetries + 1) << ")";
NEVPNManager *manager = [NEVPNManager sharedManager];
NSError *startError = nil;
[manager.connection startVPNTunnelAndReturnError:&startError];
if (!startError) {
m_startRetries = 0;
m_tunnelStarted = true;
QMetaObject::invokeMethod(this, [this]() { startHandshakeTimeoutTimer(); }, Qt::QueuedConnection);
return;
}
if (m_startRetries < MAX_START_RETRIES) {
m_startRetries++;
qWarning() << "[IKEv2-mac] startVPNTunnel failed, will retry (" << m_startRetries << "):"
<< QString::fromNSString(startError.localizedDescription);
QPointer<Ikev2ProtocolMacos> self = this;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.7 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (!self) return;
[[NEVPNManager sharedManager] loadFromPreferencesWithCompletionHandler:^(NSError *reloadError) {
if (!self) return;
self->startTunnelNow();
}];
});
return;
}
qCritical() << "[IKEv2-mac] starting the tunnel failed:"
<< QString::fromNSString(startError.localizedDescription);
disableVpnConfiguration();
reportError(ErrorCode::IKEv2ConnectError);
}
void Ikev2ProtocolMacos::handleStatusChange(int rawStatus)
{
NEVPNStatus vpnStatus = static_cast<NEVPNStatus>(rawStatus);
const Vpn::ConnectionState currentState = connectionState();
qInfo() << "[IKEv2-mac] status ->" << vpnStatusName(rawStatus)
<< "| uiState:" << textConnectionState()
<< "| lastStatus:" << vpnStatusName(m_lastVpnStatus)
<< "| waitingToStart:" << m_startWhenDisconnected;
if (m_startWhenDisconnected) {
if (vpnStatus == NEVPNStatusDisconnecting) {
return;
}
if (vpnStatus == NEVPNStatusDisconnected) {
m_startWhenDisconnected = false;
m_lastVpnStatus = NEVPNStatusDisconnected;
QPointer<Ikev2ProtocolMacos> self = this;
dispatch_async(dispatch_get_main_queue(), ^{
if (self) self->startTunnelNow();
});
return;
}
}
switch (vpnStatus) {
case NEVPNStatusConnecting:
setConnectionState(Vpn::ConnectionState::Connecting);
break;
case NEVPNStatusConnected:
stopHandshakeTimeoutTimer();
m_lastVpnStatus = vpnStatus;
m_tunnelStarted = true;
qInfo() << "[IKEv2-mac] tunnel established";
setConnectionState(Vpn::ConnectionState::Connected);
break;
case NEVPNStatusReasserting:
m_lastVpnStatus = vpnStatus;
setConnectionState(Vpn::ConnectionState::Reconnecting);
break;
case NEVPNStatusDisconnecting:
if (!m_tunnelStarted) {
return;
}
stopHandshakeTimeoutTimer();
setConnectionState(Vpn::ConnectionState::Disconnecting);
break;
case NEVPNStatusDisconnected: {
if (!m_tunnelStarted) {
m_lastVpnStatus = vpnStatus;
return;
}
stopHandshakeTimeoutTimer();
removeStatusObserver();
if (m_handshakeTimedOut) {
qCritical() << "[IKEv2-mac] connection failed: handshake timed out";
setLastError(ErrorCode::IKEv2TimeoutError);
} else if (m_lastVpnStatus == NEVPNStatusInvalid && currentState == Vpn::ConnectionState::Connecting) {
qCritical() << "[IKEv2-mac] connection failed: server rejected the configuration";
setLastError(ErrorCode::IKEv2ConfigError);
} else if (m_lastVpnStatus == NEVPNStatusReasserting
&& (currentState == Vpn::ConnectionState::Connecting
|| currentState == Vpn::ConnectionState::Connected)) {
qWarning() << "[IKEv2-mac] connection lost (network unavailable)";
setConnectionState(Vpn::ConnectionState::Disconnected);
} else if (m_lastVpnStatus == NEVPNStatusConnected
&& (currentState == Vpn::ConnectionState::Connecting
|| currentState == Vpn::ConnectionState::Connected)) {
qWarning() << "[IKEv2-mac] tunnel turned off outside the app (system settings)";
setConnectionState(Vpn::ConnectionState::Disconnected);
} else {
setConnectionState(Vpn::ConnectionState::Disconnected);
}
m_lastVpnStatus = vpnStatus;
disableVpnConfiguration();
break;
}
case NEVPNStatusInvalid:
if (!m_tunnelStarted) {
m_lastVpnStatus = vpnStatus;
return;
}
stopHandshakeTimeoutTimer();
removeStatusObserver();
qCritical() << "[IKEv2-mac] VPN profile became invalid";
m_lastVpnStatus = vpnStatus;
setLastError(ErrorCode::IKEv2ConfigError);
disableVpnConfiguration();
break;
default:
break;
}
}
void Ikev2ProtocolMacos::startHandshakeTimeoutTimer()
{
stopHandshakeTimeoutTimer();
m_handshakeTimeoutTimer = new QTimer(this);
m_handshakeTimeoutTimer->setSingleShot(true);
m_handshakeTimeoutTimer->setInterval(HANDSHAKE_TIMEOUT_SEC * 1000);
connect(m_handshakeTimeoutTimer, &QTimer::timeout, this, [this]() {
if (connectionState() == Vpn::ConnectionState::Connecting) {
m_handshakeTimedOut = true;
dispatch_async(dispatch_get_main_queue(), ^{
[[NEVPNManager sharedManager].connection stopVPNTunnel];
});
}
});
m_handshakeTimeoutTimer->start();
}
void Ikev2ProtocolMacos::stopHandshakeTimeoutTimer()
{
if (m_handshakeTimeoutTimer) {
m_handshakeTimeoutTimer->stop();
m_handshakeTimeoutTimer->deleteLater();
m_handshakeTimeoutTimer = nullptr;
}
}
+24 -13
View File
@@ -39,33 +39,44 @@ QString OpenVpnProtocol::defaultConfigPath()
return p;
}
void OpenVpnProtocol::stop()
void OpenVpnProtocol::cleanupResources()
{
qDebug() << "OpenVpnProtocol::stop()";
setConnectionState(Vpn::ConnectionState::Disconnecting);
// TODO: need refactoring
// sendTermSignal() will even return true while server connected ???
if ((m_connectionState == Vpn::ConnectionState::Preparing) || (m_connectionState == Vpn::ConnectionState::Connecting)
|| (m_connectionState == Vpn::ConnectionState::Connected)
|| (m_connectionState == Vpn::ConnectionState::Reconnecting)) {
if (m_openVpnProcess || openVpnProcessIsRunning()) {
if (!sendTermSignal()) {
killOpenVpnProcess();
}
QThread::msleep(10);
m_managementServer.stop();
}
m_managementServer.stop();
#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
QRemoteObjectPendingReply<bool> reply = iface->disableKillSwitch();
if (!reply.waitForFinished(1000) && !reply.returnValue()) {
qWarning() << "OpenVpnProtocol::stop(): Failed to disable killswitch";
qWarning() << "OpenVpnProtocol::cleanupResources(): Failed to disable killswitch";
}
});
#endif
}
setConnectionState(Vpn::ConnectionState::Disconnected);
void OpenVpnProtocol::stop()
{
qDebug() << "OpenVpnProtocol::stop()";
const bool wasActive = m_connectionState == Vpn::ConnectionState::Preparing
|| m_connectionState == Vpn::ConnectionState::Connecting
|| m_connectionState == Vpn::ConnectionState::Connected
|| m_connectionState == Vpn::ConnectionState::Reconnecting;
if (wasActive) {
setConnectionState(Vpn::ConnectionState::Disconnecting);
}
cleanupResources();
if (wasActive || m_connectionState == Vpn::ConnectionState::Disconnecting) {
setConnectionState(Vpn::ConnectionState::Disconnected);
}
}
ErrorCode OpenVpnProtocol::prepare()
@@ -168,7 +179,7 @@ void OpenVpnProtocol::updateRouteGateway(QString line)
ErrorCode OpenVpnProtocol::start()
{
OpenVpnProtocol::stop();
cleanupResources();
if (!QFileInfo::exists(configPath())) {
setLastError(ErrorCode::OpenVpnConfigMissing);
+1
View File
@@ -29,6 +29,7 @@ protected slots:
void onReadyReadDataFromManagementServer();
private:
void cleanupResources();
QString configPath() const;
bool openVpnProcessIsRunning() const;
bool sendTermSignal();
-6
View File
@@ -14,10 +14,6 @@
#include "ikev2VpnProtocolWindows.h"
#endif
#if defined(Q_OS_MACX) && !defined(MACOS_NE)
#include "ikev2VpnProtocolMacos.h"
#endif
VpnProtocol::VpnProtocol(const QJsonObject &configuration, QObject *parent)
: QObject(parent),
m_connectionState(Vpn::ConnectionState::Unknown),
@@ -115,8 +111,6 @@ VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject &
switch (container) {
#if defined(Q_OS_WINDOWS)
case DockerContainer::Ipsec: return new Ikev2Protocol(configuration);
#elif defined(Q_OS_MACX) && !defined(MACOS_NE)
case DockerContainer::Ipsec: return new Ikev2ProtocolMacos(configuration);
#endif
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) and !defined MACOS_NE || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
case DockerContainer::OpenVpn: return new OpenVpnProtocol(configuration);
+183 -38
View File
@@ -9,10 +9,13 @@
#include "ipc.h"
#include <QCryptographicHash>
#include <QJsonArray>
#include <QJsonDocument>
#include <QTimer>
#include <QJsonObject>
#include <QNetworkInterface>
#include <QNetworkProxy>
#include <QTcpSocket>
#include <QtCore/qlogging.h>
#include <QtCore/qobjectdefs.h>
#include <QtCore/qprocess.h>
@@ -56,6 +59,28 @@ XrayProtocol::XrayProtocol(const QJsonObject &configuration, QObject *parent) :
qWarning() << "Xray config string is not a valid JSON object";
m_xrayConfig = {};
}
m_serverPort = extractServerPort();
}
int XrayProtocol::extractServerPort() const
{
const QJsonArray outbounds = m_xrayConfig.value(amnezia::protocols::xray::outbounds).toArray();
if (outbounds.isEmpty())
return 0;
const QJsonObject settings = outbounds.first().toObject().value(amnezia::protocols::xray::settings).toObject();
QJsonArray servers;
if (settings.contains(amnezia::protocols::xray::vnext))
servers = settings.value(amnezia::protocols::xray::vnext).toArray();
else if (settings.contains(amnezia::protocols::xray::servers))
servers = settings.value(amnezia::protocols::xray::servers).toArray();
if (servers.isEmpty())
return 0;
return servers.first().toObject().value(amnezia::protocols::xray::port).toInt();
}
XrayProtocol::~XrayProtocol()
@@ -68,6 +93,13 @@ ErrorCode XrayProtocol::start()
{
qDebug() << "XrayProtocol::start()";
m_connectivityProbeStarted = false;
if (!probeServerReachable()) {
qCritical() << "XrayProtocol: VPN server" << m_remoteAddress << "is unreachable";
return ErrorCode::XrayServerUnreachable;
}
// Inject SOCKS5 auth into the inbound before starting xray.
// Re-uses existing credentials if the config already has them (e.g. imported config).
amnezia::serialization::inbounds::InboundCredentials creds;
@@ -104,22 +136,50 @@ ErrorCode XrayProtocol::start()
qDebug() << "XrayProtocol: patched legacy inbound listen address to 127.0.0.1";
}
startTimeoutTimer();
return IpcClient::withInterface(
[&](QSharedPointer<IpcInterfaceReplica> iface) {
auto xrayStart = iface->xrayStart(xrayConfigStr);
if (!xrayStart.waitForFinished() || !xrayStart.returnValue()) {
qCritical() << "Failed to start xray";
stopTimeoutTimer();
return ErrorCode::XrayExecutableCrashed;
}
return startTun2Socks();
},
[]() { return ErrorCode::AmneziaServiceConnectionFailed; });
[this]() {
stopTimeoutTimer();
return ErrorCode::AmneziaServiceConnectionFailed;
});
}
void XrayProtocol::stop()
{
qDebug() << "XrayProtocol::stop()";
stopTimeoutTimer();
stopLivenessMonitor();
if (m_tun2socksProcess) {
m_tun2socksProcess->blockSignals(true);
#ifndef Q_OS_WIN
m_tun2socksProcess->terminate();
auto waitForFinished = m_tun2socksProcess->waitForFinished(1000);
if (!waitForFinished.waitForFinished() || !waitForFinished.returnValue()) {
qWarning() << "Failed to terminate tun2socks. Killing the process...";
m_tun2socksProcess->kill();
m_tun2socksProcess->waitForFinished(1000);
}
#else
m_tun2socksProcess->kill();
#endif
m_tun2socksProcess->close();
m_tun2socksProcess.reset();
}
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
auto disableKillSwitch = iface->disableKillSwitch();
if (!disableKillSwitch.waitForFinished() || !disableKillSwitch.returnValue())
@@ -142,31 +202,13 @@ void XrayProtocol::stop()
qWarning() << "Failed to stop xray";
});
if (m_tun2socksProcess) {
m_tun2socksProcess->blockSignals(true);
#ifndef Q_OS_WIN
m_tun2socksProcess->terminate();
auto waitForFinished = m_tun2socksProcess->waitForFinished(1000);
if (!waitForFinished.waitForFinished() || !waitForFinished.returnValue()) {
qWarning() << "Failed to terminate tun2socks. Killing the process...";
m_tun2socksProcess->kill();
}
#else
// terminate does not do anything useful on Windows
// so just kill the process
m_tun2socksProcess->kill();
#endif
m_tun2socksProcess->close();
m_tun2socksProcess.reset();
}
setConnectionState(Vpn::ConnectionState::Disconnected);
}
ErrorCode XrayProtocol::startTun2Socks()
{
m_tunResourceBusy = false;
m_tun2socksProcess = IpcClient::CreatePrivilegedProcess();
if (!m_tun2socksProcess->waitForSource()) {
return ErrorCode::AmneziaServiceConnectionFailed;
@@ -191,15 +233,31 @@ ErrorCode XrayProtocol::startTun2Socks()
if (!line.contains("[TCP]") && !line.contains("[UDP]"))
qDebug() << "[tun2socks]:" << line;
if (line.contains("[STACK] tun://") && line.contains("<-> socks5://")) {
disconnect(m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::readyReadStandardOutput, this, nullptr);
if (line.contains("resource busy"))
m_tunResourceBusy = true;
if (ErrorCode res = setupRouting(); res != ErrorCode::NoError) {
stop();
setLastError(res);
} else {
setConnectionState(Vpn::ConnectionState::Connected);
}
if (line.contains("[STACK] tun://") && line.contains("<-> socks5://") && !m_connectivityProbeStarted) {
m_connectivityProbeStarted = true;
disconnect(m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::readyReadStandardOutput, this, nullptr);
disconnect(m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::readyReadStandardError, this, nullptr);
runConnectivityProbe([this](bool ok) {
if (!ok) {
qCritical() << "Xray connectivity probe failed: no traffic flows through the tunnel";
stop();
setLastError(ErrorCode::XrayConnectivityCheckFailed);
return;
}
if (ErrorCode res = setupRouting(); res != ErrorCode::NoError) {
stop();
setLastError(res);
} else {
stopTimeoutTimer();
setConnectionState(Vpn::ConnectionState::Connected);
startLivenessMonitor();
}
});
}
},
Qt::QueuedConnection);
@@ -207,15 +265,7 @@ ErrorCode XrayProtocol::startTun2Socks()
connect(
m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::finished, this,
[this](int exitCode, QProcess::ExitStatus exitStatus) {
// Check stdout for "resource busy" — the TUN device was not yet released
// by the previous tun2socks instance. Retry after a short delay.
bool resourceBusy = false;
if (m_tun2socksProcess) {
auto readOut = m_tun2socksProcess->readAllStandardOutput();
if (readOut.waitForFinished()) {
resourceBusy = readOut.returnValue().contains("resource busy");
}
}
const bool resourceBusy = m_tunResourceBusy;
if (resourceBusy && m_tun2socksRetryCount < maxTun2SocksRetries) {
m_tun2socksRetryCount++;
@@ -331,3 +381,98 @@ ErrorCode XrayProtocol::setupRouting()
},
[]() { return ErrorCode::AmneziaServiceConnectionFailed; });
}
bool XrayProtocol::probeServerReachable()
{
if (m_remoteAddress.isEmpty() || m_serverPort <= 0) {
qWarning() << "XrayProtocol: skipping server reachability probe (address/port unknown)";
return true;
}
QTcpSocket sock;
sock.connectToHost(m_remoteAddress, static_cast<quint16>(m_serverPort));
const bool ok = sock.waitForConnected(m_serverProbeTimeoutMs);
if (!ok) {
qWarning() << "XrayProtocol: server" << m_remoteAddress << ":" << m_serverPort
<< "unreachable:" << sock.errorString();
}
sock.abort();
return ok;
}
void XrayProtocol::runConnectivityProbe(std::function<void(bool)> onResult)
{
if (m_remoteAddress.isEmpty() || m_serverPort <= 0) {
qWarning() << "XrayProtocol: connectivity probe skipped (server address/port unknown)";
onResult(true);
return;
}
auto *sock = new QTcpSocket(this);
QNetworkProxy proxy(QNetworkProxy::Socks5Proxy, QStringLiteral("127.0.0.1"),
static_cast<quint16>(m_socksPort), m_socksUser, m_socksPassword);
proxy.setCapabilities(QNetworkProxy::TunnelingCapability | QNetworkProxy::HostNameLookupCapability);
sock->setProxy(proxy);
auto *timeout = new QTimer(this);
timeout->setSingleShot(true);
auto done = QSharedPointer<bool>::create(false);
auto finish = [=](bool ok) {
if (*done)
return;
*done = true;
timeout->stop();
timeout->deleteLater();
sock->abort();
sock->deleteLater();
onResult(ok);
};
connect(sock, &QTcpSocket::connected, this, [=]() { finish(true); });
connect(sock, &QAbstractSocket::errorOccurred, this, [=](QAbstractSocket::SocketError) { finish(false); });
connect(timeout, &QTimer::timeout, this, [=]() { finish(false); });
timeout->start(m_connectivityProbeTimeoutMs);
sock->connectToHost(m_remoteAddress, static_cast<quint16>(m_serverPort));
}
void XrayProtocol::startLivenessMonitor()
{
if (!m_livenessTimer) {
m_livenessTimer = new QTimer(this);
connect(m_livenessTimer, &QTimer::timeout, this, [this]() {
if (connectionState() != Vpn::ConnectionState::Connected)
return;
runConnectivityProbe([this](bool ok) {
if (connectionState() != Vpn::ConnectionState::Connected)
return;
if (ok) {
m_livenessFailures = 0;
} else if (++m_livenessFailures >= m_maxLivenessFailures) {
qCritical() << "XrayProtocol: liveness check failed" << m_livenessFailures
<< "times in a row, the tunnel is dead";
stop();
setLastError(ErrorCode::XrayConnectionLost);
} else {
qWarning() << "XrayProtocol: liveness check failed (" << m_livenessFailures << "/"
<< m_maxLivenessFailures << ")";
}
});
});
}
m_livenessFailures = 0;
m_livenessTimer->start(m_livenessIntervalMs);
}
void XrayProtocol::stopLivenessMonitor()
{
if (m_livenessTimer)
m_livenessTimer->stop();
m_livenessFailures = 0;
}
+27
View File
@@ -6,12 +6,16 @@
#include <QHostAddress>
#include <QList>
#include <functional>
#include "core/utils/errorCodes.h"
#include "core/utils/routeModes.h"
#include "core/utils/commonStructs.h"
#include "core/utils/ipcClient.h"
#include "vpnProtocol.h"
class QTimer;
class XrayProtocol : public VpnProtocol
{
public:
@@ -25,10 +29,17 @@ private:
ErrorCode setupRouting();
ErrorCode startTun2Socks();
bool probeServerReachable();
void runConnectivityProbe(std::function<void(bool)> onResult);
void startLivenessMonitor();
void stopLivenessMonitor();
int extractServerPort() const;
QJsonObject m_xrayConfig;
amnezia::RouteMode m_routeMode;
QList<QHostAddress> m_dnsServers;
QString m_remoteAddress;
int m_serverPort = 0;
QString m_socksUser;
QString m_socksPassword;
@@ -38,6 +49,22 @@ private:
int m_tun2socksRetryCount = 0;
static constexpr int maxTun2SocksRetries = 5;
static constexpr int tun2socksRetryDelayMs = 400;
bool m_connectivityProbeStarted = false;
bool m_tunResourceBusy = false;
QTimer *m_livenessTimer = nullptr;
int m_livenessFailures = 0;
static constexpr int defaultServerProbeTimeoutMs = 5000;
static constexpr int defaultConnectivityProbeTimeoutMs = 7000;
static constexpr int defaultLivenessIntervalMs = 15000;
static constexpr int defaultMaxLivenessFailures = 3;
int m_serverProbeTimeoutMs = defaultServerProbeTimeoutMs;
int m_connectivityProbeTimeoutMs = defaultConnectivityProbeTimeoutMs;
int m_livenessIntervalMs = defaultLivenessIntervalMs;
int m_maxLivenessFailures = defaultMaxLivenessFailures;
};
#endif // XRAYPROTOCOL_H
+2
View File
@@ -15,6 +15,8 @@ namespace amnezia
Awg2,
WireGuard,
OpenVpn,
Cloak,
ShadowSocks,
Ipsec,
Xray,
SSXray,
@@ -21,6 +21,8 @@ QString ContainerUtils::containerToString(DockerContainer c)
{
if (c == DockerContainer::None)
return "none";
if (c == DockerContainer::Cloak)
return "amnezia-openvpn-cloak";
if (c == DockerContainer::Awg)
return "amnezia-awg";
if (c == DockerContainer::Awg2)
@@ -62,6 +64,8 @@ QMap<DockerContainer, QString> ContainerUtils::containerHumanNames()
{
return { { DockerContainer::None, "Not installed" },
{ DockerContainer::OpenVpn, "OpenVPN" },
{ DockerContainer::ShadowSocks, "OpenVPN over SS" },
{ DockerContainer::Cloak, "OpenVPN over Cloak" },
{ DockerContainer::WireGuard, "WireGuard" },
{ DockerContainer::Awg, "AmneziaWG" },
{ DockerContainer::Awg2, "AmneziaWG" },
@@ -83,6 +87,10 @@ QMap<DockerContainer, QString> ContainerUtils::containerDescriptions()
return { { DockerContainer::OpenVpn,
QObject::tr("OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its "
"own security protocol with SSL/TLS for key exchange.") },
{ DockerContainer::ShadowSocks,
QObject::tr("This protocol is no longer supported.") },
{ DockerContainer::Cloak,
QObject::tr("This protocol is no longer supported.") },
{ DockerContainer::WireGuard,
QObject::tr("WireGuard - popular VPN protocol with high performance, high speed and low power "
"consumption.") },
@@ -194,6 +202,9 @@ QMap<DockerContainer, QString> ContainerUtils::containerDetailedDescriptions()
ServiceType ContainerUtils::containerService(DockerContainer c)
{
if (isUnsupportedContainer(c)) {
return ServiceType::Vpn;
}
return ProtocolUtils::protocolService(defaultProtocol(c));
}
@@ -202,6 +213,8 @@ Proto ContainerUtils::defaultProtocol(DockerContainer c)
switch (c) {
case DockerContainer::None: return Proto::Unknown;
case DockerContainer::OpenVpn: return Proto::OpenVpn;
case DockerContainer::Cloak:
case DockerContainer::ShadowSocks: return Proto::Unknown;
case DockerContainer::WireGuard: return Proto::WireGuard;
case DockerContainer::Awg2: return Proto::Awg;
case DockerContainer::Awg: return Proto::Awg;
@@ -252,6 +265,8 @@ bool ContainerUtils::isSupportedByCurrentPlatform(DockerContainer c)
// macOS build using Network Extension allow OpenVPN for parity with iOS.
switch (c) {
case DockerContainer::OpenVpn: return true;
case DockerContainer::Cloak: return false;
case DockerContainer::ShadowSocks: return false;
case DockerContainer::WireGuard: return true;
case DockerContainer::Awg2: return true;
case DockerContainer::Awg: return true;
@@ -265,7 +280,7 @@ bool ContainerUtils::isSupportedByCurrentPlatform(DockerContainer c)
#elif defined(Q_OS_MAC)
switch (c) {
case DockerContainer::WireGuard: return true;
case DockerContainer::Ipsec: return true;
case DockerContainer::Ipsec: return false;
default: return true;
}
@@ -336,6 +351,10 @@ int ContainerUtils::easySetupOrder(DockerContainer container)
bool ContainerUtils::isShareable(DockerContainer container)
{
if (isUnsupportedContainer(container)) {
return false;
}
switch (container) {
case DockerContainer::TorWebSite: return false;
case DockerContainer::Dns: return false;
@@ -352,6 +371,11 @@ bool ContainerUtils::isAwgContainer(DockerContainer container)
return container == DockerContainer::Awg || container == DockerContainer::Awg2;
}
bool ContainerUtils::isUnsupportedContainer(DockerContainer container)
{
return container == DockerContainer::Cloak || container == DockerContainer::ShadowSocks;
}
QJsonObject ContainerUtils::getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig)
{
QString protocolConfigString = containerConfig.value(ProtocolUtils::protoToString(protocol))
@@ -45,6 +45,8 @@ namespace amnezia
bool isAwgContainer(DockerContainer container);
bool isUnsupportedContainer(DockerContainer container);
QJsonObject getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig);
int installPageOrder(DockerContainer container);
+4 -5
View File
@@ -66,16 +66,14 @@ namespace amnezia
OpenVpnUnknownError = 701,
OpenVpnTapAdapterError = 702,
AddressPoolError = 703,
IKEv2ConfigError = 710,
IKEv2LoadError = 711,
IKEv2SaveError = 712,
IKEv2ConnectError = 713,
IKEv2TimeoutError = 714,
// 3rd party utils errors
OpenSslFailed = 800,
XrayExecutableCrashed = 803,
Tun2SockExecutableCrashed = 804,
XrayServerUnreachable = 805,
XrayConnectivityCheckFailed = 806,
XrayConnectionLost = 807,
// import and install errors
ImportInvalidConfigError = 900,
@@ -84,6 +82,7 @@ namespace amnezia
ImportBackupFileUseRestoreInstead = 903,
RestoreBackupInvalidError = 904,
LegacyApiV1NotSupportedError = 905,
LegacyContainerNotSupportedError = 906,
// Android errors
AndroidError = 1000,
+4 -5
View File
@@ -59,21 +59,20 @@ QString errorString(ErrorCode code) {
case (ErrorCode::OpenVpnExecutableMissing): errorMessage = QObject::tr("OpenVPN executable missing"); break;
case (ErrorCode::AmneziaServiceConnectionFailed): errorMessage = QObject::tr("Amnezia helper service error"); break;
case (ErrorCode::OpenSslFailed): errorMessage = QObject::tr("OpenSSL failed"); break;
case (ErrorCode::XrayServerUnreachable): errorMessage = QObject::tr("Can't connect: the VPN server is unreachable"); break;
case (ErrorCode::XrayConnectivityCheckFailed): errorMessage = QObject::tr("Can't connect: no internet traffic flows through the tunnel"); break;
case (ErrorCode::XrayConnectionLost): errorMessage = QObject::tr("Connection lost: traffic stopped flowing through the tunnel"); break;
// VPN errors
case (ErrorCode::OpenVpnAdaptersInUseError): errorMessage = QObject::tr("Can't connect: another VPN connection is active"); break;
case (ErrorCode::OpenVpnTapAdapterError): errorMessage = QObject::tr("Can't setup OpenVPN TAP network adapter"); break;
case (ErrorCode::AddressPoolError): errorMessage = QObject::tr("VPN pool error: no available addresses"); break;
case (ErrorCode::IKEv2ConfigError): errorMessage = QObject::tr("Can't connect: invalid IKEv2 configuration"); break;
case (ErrorCode::IKEv2LoadError): errorMessage = QObject::tr("Can't connect: failed to load IKEv2 VPN preferences"); break;
case (ErrorCode::IKEv2SaveError): errorMessage = QObject::tr("Can't connect: failed to save IKEv2 VPN preferences"); break;
case (ErrorCode::IKEv2ConnectError): errorMessage = QObject::tr("Can't connect: failed to start IKEv2 connection"); break;
case (ErrorCode::IKEv2TimeoutError): errorMessage = QObject::tr("Can't connect: IKEv2 connection timeout"); break;
case (ErrorCode::ImportInvalidConfigError): errorMessage = QObject::tr("The config does not contain any containers and credentials for connecting to the server"); break;
case (ErrorCode::ImportBackupFileUseRestoreInstead): errorMessage = QObject::tr("Backup files cannot be imported here. Use 'Restore from backup' instead."); break;
case (ErrorCode::RestoreBackupInvalidError): errorMessage = QObject::tr("Backup file is corrupted or has invalid format"); break;
case (ErrorCode::LegacyApiV1NotSupportedError): errorMessage = QObject::tr("This legacy Amnezia subscription format is no longer supported"); break;
case (ErrorCode::LegacyContainerNotSupportedError): errorMessage = QObject::tr("This protocol is no longer supported. Please select another protocol or remove this container from the server settings."); break;
case (ErrorCode::ImportOpenConfigError): errorMessage = QObject::tr("Unable to open config file"); break;
case (ErrorCode::NoInstalledContainersError): errorMessage = QObject::tr("VPN Protocols is not installed.\n Please install VPN container at first"); break;
-5
View File
@@ -290,11 +290,6 @@ QString Utils::tun2socksPath()
return Utils::executable("tun2socks", true);
}
QString Utils::securityPath()
{
return "/usr/bin/security";
}
#ifdef Q_OS_WIN
// Inspired from http://stackoverflow.com/a/15281070/1529139
// and http://stackoverflow.com/q/40059902/1529139
-1
View File
@@ -34,7 +34,6 @@ public:
static QString wireguardExecPath();
static QString certUtilPath();
static QString tun2socksPath();
static QString securityPath();
static void logException(const std::exception &e);
static void logException(const std::exception_ptr &eptr = std::current_exception());
+6 -2
View File
@@ -8,6 +8,7 @@
#include <QFileInfo>
#include <QLocalSocket>
#include "daemon.h"
#include "daemonlocalserverconnection.h"
#include "leakdetector.h"
#include "logger.h"
@@ -58,8 +59,11 @@ bool DaemonLocalServer::initialize() {
DaemonLocalServerConnection* connection =
new DaemonLocalServerConnection(&m_server, socket);
connect(socket, &QLocalSocket::disconnected, connection,
&DaemonLocalServerConnection::deleteLater);
connect(socket, &QLocalSocket::disconnected, connection, [connection]() {
logger.debug() << "Client connection dropped, deactivating daemon";
Daemon::instance()->deactivate(true);
connection->deleteLater();
});
});
return true;
+1 -2
View File
@@ -1,4 +1,3 @@
sudo docker stop $CONTAINER_NAME;\
sudo docker rm -fv $CONTAINER_NAME;\
sudo docker rmi $CONTAINER_NAME;\
test "$REMOVE_CONTAINER_DATA" = "1" && sudo docker volume rm -f ${CONTAINER_NAME}-data 2>/dev/null || true
sudo docker rmi $CONTAINER_NAME;
@@ -475,8 +475,7 @@ bool SubscriptionUiController::deactivateExternalDevice(const QString &serverId,
void SubscriptionUiController::validateConfig()
{
const QString serverId = m_serversController->getDefaultServerId();
if (!serverId.isEmpty() && m_serversController->isLegacyApiV1Server(serverId)) {
emit unsupportedConnectDrawerRequested();
if (serverId.isEmpty()) {
emit configValidated(false);
return;
}
@@ -8,6 +8,8 @@
#include "amneziaApplication.h"
#include "core/controllers/serversController.h"
#include "core/models/containerConfig.h"
#include "core/utils/containerEnum.h"
ConnectionUiController::ConnectionUiController(ConnectionController* connectionController,
ServersController* serversController,
@@ -33,7 +35,7 @@ void ConnectionUiController::openConnection()
ErrorCode errorCode = m_connectionController->openConnection(serverId);
if (errorCode != ErrorCode::NoError) {
emit connectionErrorOccurred(errorCode);
notifyConnectionBlocked(errorCode);
return;
}
}
@@ -130,10 +132,36 @@ void ConnectionUiController::toggleConnection()
} else if (isConnected()) {
closeConnection();
} else {
const QString serverId = m_serversController->getDefaultServerId();
if (serverId.isEmpty()) {
return;
}
const ErrorCode errorCode = m_connectionController->isConnectionSupported(serverId);
if (errorCode != ErrorCode::NoError) {
notifyConnectionBlocked(errorCode);
return;
}
emit prepareConfig();
}
}
void ConnectionUiController::notifyConnectionBlocked(ErrorCode errorCode)
{
if (errorCode == ErrorCode::LegacyApiV1NotSupportedError) {
emit unsupportedConnectDrawerRequested();
return;
}
if (errorCode == ErrorCode::NoInstalledContainersError) {
emit noInstalledContainers();
return;
}
emit connectionErrorOccurred(errorCode);
}
bool ConnectionUiController::isConnectionInProgress() const
{
return m_isConnectionInProgress;
@@ -143,3 +171,32 @@ bool ConnectionUiController::isConnected() const
{
return m_isConnected;
}
bool ConnectionUiController::isRevokeBlockedDuringActiveConnection(const QString &serverId, int containerIndex,
const QString &clientId) const
{
if (clientId.isEmpty() || (!isConnected() && !isConnectionInProgress())) {
return false;
}
if (m_serversController->getDefaultServerId() != serverId) {
return false;
}
if (static_cast<int>(m_serversController->getDefaultContainer(serverId)) != containerIndex) {
return false;
}
const auto adminConfig = m_serversController->selfHostedAdminConfig(serverId);
if (!adminConfig.has_value()) {
return false;
}
const QString connectionClientId =
adminConfig->containerConfig(static_cast<DockerContainer>(containerIndex)).protocolConfig.clientId();
if (connectionClientId.isEmpty()) {
return false;
}
return connectionClientId == clientId || connectionClientId.contains(clientId);
}
@@ -35,6 +35,8 @@ public slots:
void openConnection();
void closeConnection();
bool isRevokeBlockedDuringActiveConnection(const QString &serverId, int containerIndex, const QString &clientId) const;
ErrorCode getLastConnectionError();
void onConnectionStateChanged(Vpn::ConnectionState state);
@@ -48,9 +50,12 @@ signals:
void connectButtonClicked();
void preparingConfig();
void prepareConfig();
void unsupportedConnectDrawerRequested();
void noInstalledContainers();
private:
Vpn::ConnectionState getCurrentConnectionState();
void notifyConnectionBlocked(ErrorCode errorCode);
ConnectionController* m_connectionController;
ServersController* m_serversController;
@@ -75,13 +75,7 @@ InstallUiController::InstallUiController(InstallController *installController,
m_connectionController(connectionController)
{
connect(m_installController, &InstallController::configValidated, this, &InstallUiController::configValidated);
connect(m_installController, &InstallController::validationErrorOccurred, this, [this](ErrorCode errorCode) {
if (errorCode == ErrorCode::NoInstalledContainersError) {
emit noInstalledContainers();
} else {
emit installationErrorOccurred(errorCode);
}
});
connect(m_installController, &InstallController::validationErrorOccurred, this, &InstallUiController::installationErrorOccurred);
}
InstallUiController::~InstallUiController()
@@ -217,15 +211,13 @@ void InstallUiController::scanServerForInstalledContainers(const QString &server
emit installationErrorOccurred(errorCode);
}
void InstallUiController::updateContainer(const QString &serverId, int containerIndex, int protocolIndex, bool closePage)
bool InstallUiController::buildContainerConfigFromModel(int containerIndex, int protocolIndex, ContainerConfig &containerConfig)
{
DockerContainer container = static_cast<DockerContainer>(containerIndex);
Proto protocolType = static_cast<Proto>(protocolIndex);
ContainerConfig containerConfig;
containerConfig.container = container;
switch (protocolType) {
case Proto::Awg: {
containerConfig.protocolConfig = m_awgConfigModel->getProtocolConfig();
@@ -271,6 +263,41 @@ void InstallUiController::updateContainer(const QString &serverId, int container
}
#endif
default:
return false;
}
return true;
}
void InstallUiController::updateClientConfig(const QString &serverId, int containerIndex, int protocolIndex, bool closePage)
{
DockerContainer container = static_cast<DockerContainer>(containerIndex);
Proto protocolType = static_cast<Proto>(protocolIndex);
ContainerConfig containerConfig;
if (!buildContainerConfigFromModel(containerIndex, protocolIndex, containerConfig)) {
return;
}
ErrorCode errorCode = m_installController->updateClientConfig(serverId, container, containerConfig);
if (errorCode == ErrorCode::NoError) {
ContainerConfig updatedConfig = m_serversController->getContainerConfig(serverId, container);
m_protocolModel->updateModel(updatedConfig);
updateProtocolConfigModel(serverId, static_cast<int>(container), static_cast<int>(protocolType));
emit updateContainerFinished(tr("Settings updated successfully"), closePage);
return;
}
emit installationErrorOccurred(errorCode);
}
void InstallUiController::updateServerConfig(const QString &serverId, int containerIndex, int protocolIndex, bool closePage)
{
DockerContainer container = static_cast<DockerContainer>(containerIndex);
Proto protocolType = static_cast<Proto>(protocolIndex);
ContainerConfig containerConfig;
if (!buildContainerConfigFromModel(containerIndex, protocolIndex, containerConfig)) {
return;
}
ContainerConfig oldContainerConfig = m_serversController->getContainerConfig(serverId, container);
@@ -305,13 +332,13 @@ void InstallUiController::updateContainer(const QString &serverId, int container
QFuture<ErrorCode> future =
QtConcurrent::run([installController, serverId, container, oldConfigCopy,
newConfigCopy]() mutable -> ErrorCode {
return installController->updateContainer(serverId, container, oldConfigCopy, newConfigCopy);
return installController->updateServerConfig(serverId, container, oldConfigCopy, newConfigCopy);
});
watcher->setFuture(future);
return;
}
ErrorCode errorCode = m_installController->updateContainer(serverId, container, oldContainerConfig, containerConfig);
ErrorCode errorCode = m_installController->updateServerConfig(serverId, container, oldContainerConfig, containerConfig);
if (errorCode == ErrorCode::NoError) {
ContainerConfig updatedConfig = m_serversController->getContainerConfig(serverId, container);
@@ -64,7 +64,8 @@ public slots:
void scanServerForInstalledContainers(const QString &serverId);
void updateContainer(const QString &serverId, int containerIndex, int protocolIndex, bool closePage = true);
void updateServerConfig(const QString &serverId, int containerIndex, int protocolIndex, bool closePage = true);
void updateClientConfig(const QString &serverId, int containerIndex, int protocolIndex, bool closePage = true);
void removeServer(const QString &serverId);
void rebootServer(const QString &serverId);
@@ -132,7 +133,6 @@ signals:
void cachedProfileCleared(const QString &message);
void apiConfigRemoved(const QString &message);
void noInstalledContainers();
void configValidated(bool isValid);
private:
@@ -162,6 +162,8 @@ private:
QString m_privateKeyPassphrase;
void updateProtocolConfigModel(const QString &serverId, int containerIndex, int protocolIndex);
bool buildContainerConfigFromModel(int containerIndex, int protocolIndex, ContainerConfig &containerConfig);
};
#endif // INSTALLUICONTROLLER_H
+18 -13
View File
@@ -156,7 +156,17 @@ void ServersUiController::updateModel()
m_serversModel->updateModel(m_orderedServerDescriptions, defaultServerId);
updateContainersModel();
if (!m_processedServerId.isEmpty()) {
if (isServerFromApi(m_processedServerId)) {
const auto &description = serverDescriptionById(m_processedServerId);
if (description.isApiV2 && description.isCountrySelectionAvailable
&& !description.apiAvailableCountries.isEmpty()) {
emit updateApiCountryModel();
}
} else {
updateContainersModel();
}
}
updateDefaultServerContainersModel();
if (hadServersFromGatewayBefore != hasServersFromGatewayNow) {
@@ -350,19 +360,14 @@ void ServersUiController::setProcessedServerId(const QString &serverId)
m_processedServerId = normalizedServerId;
if (newIndex >= 0) {
updateContainersModel();
for (const auto &description : m_orderedServerDescriptions) {
if (description.serverId != normalizedServerId) {
continue;
if (isServerFromApi(m_processedServerId)) {
const auto &description = serverDescriptionById(m_processedServerId);
if (description.isApiV2 && description.isCountrySelectionAvailable
&& !description.apiAvailableCountries.isEmpty()) {
emit updateApiCountryModel();
}
if (description.isApiV2) {
if (description.isCountrySelectionAvailable && !description.apiAvailableCountries.isEmpty()) {
emit updateApiCountryModel();
}
emit updateApiServicesModel();
}
break;
} else {
updateContainersModel();
}
}
@@ -113,7 +113,6 @@ signals:
void processedContainerIndexChanged(int index);
void hasServersFromGatewayApiChanged();
void updateApiCountryModel();
void updateApiServicesModel();
public:
void updateModel();
@@ -22,12 +22,10 @@
SettingsUiController::SettingsUiController(SettingsController* settingsController,
ServersController* serversController,
LanguageUiController* languageUiController,
QObject *parent)
: QObject(parent),
m_settingsController(settingsController),
m_serversController(serversController),
m_languageUiController(languageUiController)
m_serversController(serversController)
{
#ifdef Q_OS_ANDROID
connect(AndroidController::instance(), &AndroidController::notificationStateChanged, this, &SettingsUiController::onNotificationStateChanged);
@@ -157,13 +155,13 @@ void SettingsUiController::restoreAppConfigFromData(const QByteArray &data)
{
ErrorCode errorCode = m_settingsController->restoreAppConfigFromData(data);
if (errorCode == ErrorCode::NoError) {
emit appLanguageChanged(
static_cast<LanguageSettings::AvailableLanguageEnum>(m_languageUiController->getCurrentLanguageIndex()));
emit appLanguageChanged();
bool amneziaDnsEnabled = m_settingsController->isAmneziaDnsEnabled();
emit amneziaDnsToggled(amneziaDnsEnabled);
emit restoreBackupFinished();
emit autoStartChanged();
emit startMinimizedChanged();
} else {
emit errorOccurred(errorCode);
@@ -178,6 +176,7 @@ QString SettingsUiController::getAppVersion()
void SettingsUiController::clearSettings()
{
m_settingsController->clearSettings();
emit autoStartChanged();
emit startMinimizedChanged();
emit resetLanguageToSystem();
@@ -206,9 +205,8 @@ bool SettingsUiController::isAutoStartEnabled()
void SettingsUiController::toggleAutoStart(bool enable)
{
m_settingsController->toggleAutoStart(enable);
if (!enable) {
emit startMinimizedChanged();
}
emit autoStartChanged();
emit startMinimizedChanged();
}
bool SettingsUiController::isStartMinimizedEnabled()
+3 -5
View File
@@ -5,8 +5,6 @@
#include "core/controllers/settingsController.h"
#include "core/controllers/serversController.h"
#include "ui/controllers/languageUiController.h"
#include "ui/models/languageModel.h"
#include "core/utils/errorCodes.h"
#include "core/utils/routeModes.h"
#include "core/utils/commonStructs.h"
@@ -17,7 +15,6 @@ class SettingsUiController : public QObject
public:
explicit SettingsUiController(SettingsController* settingsController,
ServersController* serversController,
LanguageUiController* languageUiController,
QObject *parent = nullptr);
Q_PROPERTY(QString primaryDns READ getPrimaryDns WRITE setPrimaryDns NOTIFY primaryDnsChanged)
@@ -32,6 +29,7 @@ public:
Q_PROPERTY(bool isDevGatewayEnv READ isDevGatewayEnv WRITE toggleDevGatewayEnv NOTIFY devGatewayEnvChanged)
Q_PROPERTY(bool isHomeAdLabelVisible READ isHomeAdLabelVisible NOTIFY isHomeAdLabelVisibleChanged)
Q_PROPERTY(bool autoStartEnabled READ isAutoStartEnabled NOTIFY autoStartChanged)
Q_PROPERTY(bool startMinimized READ isStartMinimizedEnabled NOTIFY startMinimizedChanged)
public slots:
@@ -122,7 +120,7 @@ signals:
void loggingDisableByWatcher();
void appLanguageChanged(const LanguageSettings::AvailableLanguageEnum language);
void appLanguageChanged();
void resetLanguageToSystem();
void onNotificationStateChanged();
@@ -135,12 +133,12 @@ signals:
void activityResumed();
void isHomeAdLabelVisibleChanged(bool visible);
void autoStartChanged();
void startMinimizedChanged();
private:
SettingsController* m_settingsController;
ServersController* m_serversController;
LanguageUiController* m_languageUiController;
};
#endif
+1 -1
View File
@@ -30,7 +30,7 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
switch (role) {
case SubscriptionStatusRole: {
if (m_accountInfoData.configType == serverConfigUtils::ConfigType::AmneziaFreeV3) {
return tr("Active");
return QStringLiteral("<p><a style=\"color: #28c840;\">%1</a>").arg(tr("Active"));
}
return apiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate)
@@ -27,6 +27,7 @@ QVariant ClientManagementModel::data(const QModelIndex &index, int role) const
auto userData = client.value(configKey::userData).toObject();
switch (role) {
case ClientIdRole: return client.value(configKey::clientId).toString();
case ClientNameRole: return userData.value(configKey::clientName).toString();
case CreationDateRole: return userData.value(configKey::creationDate).toString();
case LatestHandshakeRole: return userData.value(configKey::latestHandshake).toString();
@@ -62,6 +63,7 @@ void ClientManagementModel::updateClientName(int row, const QString &newName)
QHash<int, QByteArray> ClientManagementModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[ClientIdRole] = "clientId";
roles[ClientNameRole] = "clientName";
roles[CreationDateRole] = "creationDate";
roles[LatestHandshakeRole] = "latestHandshake";
+2 -1
View File
@@ -10,7 +10,8 @@ class ClientManagementModel : public QAbstractListModel
public:
enum Roles {
ClientNameRole = Qt::UserRole + 1,
ClientIdRole = Qt::UserRole + 1,
ClientNameRole,
CreationDateRole,
LatestHandshakeRole,
DataReceivedRole,
+4
View File
@@ -23,6 +23,10 @@ public:
Q_INVOKABLE int containerFromString(const QString &container) const {
return static_cast<int>(amnezia::ContainerUtils::containerFromString(container));
}
Q_INVOKABLE bool isUnsupportedContainer(int containerIndex) const {
return amnezia::ContainerUtils::isUnsupportedContainer(static_cast<amnezia::DockerContainer>(containerIndex));
}
};
#endif // CONTAINERPROPS_H
+4 -1
View File
@@ -67,6 +67,7 @@ QVariant ContainersModel::data(const QModelIndex &index, int role) const
case IsCurrentlyProcessedRole: return container == static_cast<DockerContainer>(m_processedContainerIndex);
case IsSupportedRole: return ContainerUtils::isSupportedByCurrentPlatform(container);
case IsShareableRole: return ContainerUtils::isShareable(container);
case IsUnsupportedContainerRole: return ContainerUtils::isUnsupportedContainer(container);
case IsVpnContainerRole: return ContainerUtils::containerService(container) == ServiceType::Vpn;
case IsServiceContainerRole: return ContainerUtils::containerService(container) == ServiceType::Other;
case IsIpsecRole: return container == DockerContainer::Ipsec;
@@ -142,7 +143,8 @@ bool ContainersModel::hasInstalledProtocols()
bool ContainersModel::isInstallationAllowed(DockerContainer container)
{
return container != DockerContainer::Awg;
return container != DockerContainer::Awg
&& !ContainerUtils::isUnsupportedContainer(container);
}
void ContainersModel::openContainerSettings(int containerIndex)
@@ -176,6 +178,7 @@ QHash<int, QByteArray> ContainersModel::roleNames() const
roles[IsCurrentlyProcessedRole] = "isCurrentlyProcessed";
roles[IsSupportedRole] = "isSupported";
roles[IsShareableRole] = "isShareable";
roles[IsUnsupportedContainerRole] = "isUnsupportedContainer";
roles[IsInstallationAllowedRole] = "isInstallationAllowed";
roles[InstallPageOrderRole] = "installPageOrder";
+2
View File
@@ -39,6 +39,8 @@ public:
IsSupportedRole,
IsShareableRole,
IsUnsupportedContainerRole,
InstallPageOrderRole,
// Container type check roles
@@ -56,14 +56,17 @@ ListViewType {
return
}
if (checked) {
containersDropDown.closeTriggered()
ServersUiController.setDefaultContainer(ServersUiController.defaultServerId, proxyDefaultServerContainersModel.mapToSource(index))
} else {
ServersUiController.processedContainerIndex = proxyDefaultServerContainersModel.mapToSource(index)
var containerIndex = proxyDefaultServerContainersModel.mapToSource(index)
if (!isInstalled) {
ServersUiController.processedContainerIndex = containerIndex
PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings)
containersDropDown.closeTriggered()
return
}
containersDropDown.closeTriggered()
ServersUiController.setDefaultContainer(ServersUiController.defaultServerId, containerIndex)
}
MouseArea {
@@ -5,7 +5,6 @@ import QtQuick.Layouts
import SortFilterProxyModel 0.2
import PageEnum 1.0
import ContainerProps 1.0
import "../Controls2"
import "../Controls2/TextTypes"
+30 -9
View File
@@ -6,8 +6,36 @@ Menu {
popupType: Popup.Native
onAboutToShow: blocker.enabled = true
onClosed: blocker.enabled = false
property Item inputBlocker: null
Component {
id: inputBlockerComponent
MouseArea {
anchors.fill: parent
preventStealing: true
}
}
onAboutToShow: {
if (!textObj || !textObj.window) {
return
}
const contentItem = textObj.window.contentItem
if (!inputBlocker) {
inputBlocker = inputBlockerComponent.createObject(contentItem)
} else {
inputBlocker.parent = contentItem
}
}
onClosed: {
if (inputBlocker) {
inputBlocker.destroy()
inputBlocker = null
}
}
MenuItem {
text: qsTr("C&ut")
@@ -31,11 +59,4 @@ Menu {
enabled: textObj.length > 0
onTriggered: textObj.selectAll()
}
MouseArea {
id: blocker
z: 2
enabled: false
preventStealing: true
}
}
+2 -2
View File
@@ -25,8 +25,8 @@ PageType {
filters: [
ValueFilter {
roleName: "isCurrentlyProcessed"
value: true
roleName: "serverId"
value: ServersUiController.processedServerId
}
]
}
@@ -440,8 +440,7 @@ PageType {
return
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Awg)
InstallController.updateClientConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Awg)
}
var noButtonFunction = function() {}
@@ -561,7 +561,7 @@ PageType {
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Awg)
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Awg)
}
var noButtonFunction = function() {}
@@ -434,7 +434,7 @@ PageType {
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.OpenVpn)
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.OpenVpn)
}
var noButtonFunction = function() {
if (!GC.isMobile()) {
@@ -128,8 +128,7 @@ PageType {
return
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.WireGuard)
InstallController.updateClientConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.WireGuard)
}
var noButtonFunction = function() {}
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
@@ -129,7 +129,7 @@ PageType {
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.WireGuard)
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.WireGuard)
}
var noButtonFunction = function() {
if (!GC.isMobile()) {
@@ -112,7 +112,7 @@ PageType {
return
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
}
var noButtonFunction = function () {
if (typeof GC !== "undefined" && !GC.isMobile()) {
@@ -279,7 +279,7 @@ PageType {
return
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
}
var noButtonFunction = function () {
if (typeof GC !== "undefined" && !GC.isMobile()) {
@@ -213,7 +213,7 @@ PageType {
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
}
var noButtonFunction = function() {
if (!GC.isMobile()) saveButton.forceActiveFocus()
@@ -742,7 +742,7 @@ PageType {
return
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
}
var noButtonFunction = function () {
if (typeof GC !== "undefined" && !GC.isMobile()) {
@@ -95,7 +95,7 @@ PageType {
return
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
}
var noButtonFunction = function () {
if (typeof GC !== "undefined" && !GC.isMobile()) {
@@ -211,7 +211,7 @@ PageType {
return
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
}
var noButtonFunction = function () {
if (typeof GC !== "undefined" && !GC.isMobile()) {
@@ -208,7 +208,7 @@ PageType {
return
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
}
var noButtonFunction = function () {
if (typeof GC !== "undefined" && !GC.isMobile()) {
@@ -179,7 +179,7 @@ PageType {
function mtProxyScheduleUpdate(closePage) {
var cp = closePage === undefined ? false : closePage
Qt.callLater(function () {
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.MtProxy, cp)
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.MtProxy, cp)
})
}
@@ -285,7 +285,7 @@ PageType {
}
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Socks5Proxy)
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Socks5Proxy)
tempPort = portTextField.textField.text
tempUsername = usernameTextField.textField.text
tempPassword = passwordTextField.textField.text
@@ -154,7 +154,7 @@ PageType {
function telemtScheduleUpdate(closePage) {
var cp = closePage === undefined ? false : closePage
Qt.callLater(function () {
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Telemt, cp)
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Telemt, cp)
})
}
@@ -100,6 +100,12 @@ PageType {
onLinkActivated: Qt.openUrlExternally(link)
textFormat: Text.RichText
text: qsTr("Use <a href=\"https://www.torproject.org/download/\" style=\"color: #FBB26A;\">Tor Browser</a> to open this URL.")
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
}
}
ParagraphTextType {
@@ -30,6 +30,16 @@ PageType {
root.isInAppPurchase = ApiAccountInfoModel.data("isInAppPurchase")
}
function selectConnectionCountry(countryIndex, countryCode, countryName) {
if (countryIndex === ApiCountryModel.currentIndex) {
return
}
PageController.showBusyIndicator(true)
SubscriptionUiController.updateServiceFromGateway(ServersUiController.processedServerId, countryCode, countryName)
PageController.showBusyIndicator(false)
}
Component.onCompleted: {
root.updateSubscriptionState()
}
@@ -83,7 +93,7 @@ PageType {
model: ApiCountryModel
currentIndex: 0
currentIndex: ApiCountryModel.currentIndex
ButtonGroup {
id: containersRadioButtonGroup
@@ -204,15 +214,7 @@ PageType {
return
}
if (index !== ApiCountryModel.currentIndex) {
PageController.showBusyIndicator(true)
var prevIndex = ApiCountryModel.currentIndex
ApiCountryModel.currentIndex = index
if (!SubscriptionUiController.updateServiceFromGateway(ServersUiController.processedServerId, countryCode, countryName)) {
ApiCountryModel.currentIndex = prevIndex
}
PageController.showBusyIndicator(false)
}
root.selectConnectionCountry(index, countryCode, countryName)
}
Keys.onEnterPressed: {
@@ -108,9 +108,9 @@ PageType {
text: qsTr("Auto start")
descriptionText: qsTr("Launch the application every time the device is starts")
checked: SettingsController.isAutoStartEnabled()
checked: SettingsController.autoStartEnabled
onToggled: function() {
if (checked !== SettingsController.isAutoStartEnabled()) {
if (checked !== SettingsController.autoStartEnabled) {
SettingsController.toggleAutoStart(checked)
}
}
@@ -154,10 +154,10 @@ PageType {
text: qsTr("Start minimized")
descriptionText: qsTr("Launch application minimized (works with autostart option turned on)")
enabled: SettingsController.isAutoStartEnabled()
enabled: SettingsController.autoStartEnabled
opacity: enabled ? 1.0 : 0.5
checked: SettingsController.isAutoStartEnabled() && SettingsController.startMinimized
checked: SettingsController.autoStartEnabled && SettingsController.startMinimized
onToggled: function() {
if (checked !== SettingsController.startMinimized) {
SettingsController.toggleStartMinimized(checked)
@@ -166,7 +166,7 @@ PageType {
}
DividerType {
visible: !GC.isMobile()
visible: !GC.isMobile() && ServersUiController.hasServersFromGatewayApi
}
SwitcherType {
@@ -36,17 +36,6 @@ PageType {
function onRebootServerFinished(finishedMessage) {
PageController.showNotificationMessage(finishedMessage)
}
function onRemoveAllContainersFinished(finishedMessage) {
PageController.closePage() // close deInstalling page
PageController.showNotificationMessage(finishedMessage)
}
function onRemoveContainerFinished(finishedMessage) {
PageController.closePage() // close deInstalling page
PageController.closePage() // close page with remove button
PageController.showNotificationMessage(finishedMessage)
}
}
Connections {
@@ -17,7 +17,8 @@ import "../Components"
PageType {
id: root
property bool isClearCacheVisible: ServersUiController.isProcessedServerHasWriteAccess() && !ContainersModel.isServiceContainer(ServersUiController.processedContainerIndex)
property bool isUnsupportedContainer: ContainerProps.isUnsupportedContainer(ServersUiController.processedContainerIndex)
property bool isClearCacheVisible: !isUnsupportedContainer && ServersUiController.isProcessedServerHasWriteAccess() && !ContainersModel.isServiceContainer(ServersUiController.processedContainerIndex)
BackButtonType {
id: backButton
@@ -52,10 +53,11 @@ PageType {
Layout.bottomMargin: 32
headerText: ContainersModel.getProcessedContainerName() + qsTr(" settings")
descriptionText: root.isUnsupportedContainer ? qsTr("This protocol is no longer supported.") : ""
}
}
model: ProtocolsModel
model: root.isUnsupportedContainer ? null : ProtocolsModel
delegate: ColumnLayout {
id: delegateContent
@@ -29,6 +29,10 @@ PageType {
ValueFilter {
roleName: "isInstallationAllowed"
value: true
},
ValueFilter {
roleName: "isUnsupportedContainer"
value: false
}
]
sorters: RoleSorter {
+20 -7
View File
@@ -382,6 +382,10 @@ PageType {
ValueFilter {
roleName: "isShareable"
value: true
},
ValueFilter {
roleName: "isUnsupportedContainer"
value: false
}
]
}
@@ -396,9 +400,19 @@ PageType {
target: serverSelector
function onServerSelectorIndexChanged() {
var defaultContainer = proxyContainersModel.mapFromSource(ServersUiController.serverDefaultContainer(ServersUiController.processedServerId))
if (!proxyContainersModel.count) {
root.shareButtonEnabled = false
return
}
var defaultContainer = proxyContainersModel.mapFromSource(
ServersUiController.serverDefaultContainer(ServersUiController.processedServerId))
if (defaultContainer < 0) {
defaultContainer = 0
}
containerSelectorListView.selectedIndex = defaultContainer
containerSelectorListView.positionViewAtIndex(selectedIndex, ListView.Beginning)
containerSelectorListView.positionViewAtIndex(defaultContainer, ListView.Beginning)
containerSelectorListView.triggerCurrentItem()
}
}
@@ -837,11 +851,10 @@ PageType {
var noButtonFunction = function() {
}
var isActiveConfigForCurrentClient = ServersUiController.isDefaultServerCurrentlyProcessed()
&& ServersUiController.serverDefaultContainer(ServersUiController.defaultServerId) === ServersUiController.processedContainerIndex
if ((ConnectionController.isConnectionInProgress || ConnectionController.isConnected)
&& isActiveConfigForCurrentClient) {
if (ConnectionController.isRevokeBlockedDuringActiveConnection(
ServersUiController.processedServerId,
ServersUiController.processedContainerIndex,
clientId)) {
PageController.showNotificationMessage("Unable to revoke current config during active connection")
} else {
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
+25 -4
View File
@@ -105,6 +105,19 @@ PageType {
}
}
Connections {
objectName: "connectionControllerConnections"
target: ConnectionController
function onNoInstalledContainers() {
PageController.setTriggeredByConnectButton(true)
ServersUiController.setProcessedServerId(ServersUiController.defaultServerId)
PageController.goToPage(PageEnum.PageSetupWizardEasy)
}
}
Connections {
objectName: "installControllerConnections"
@@ -153,11 +166,19 @@ PageType {
PageController.showNotificationMessage(finishedMessage)
}
function onNoInstalledContainers() {
PageController.setTriggeredByConnectButton(true)
function onRemoveAllContainersFinished(finishedMessage) {
if (tabBarStackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageDeinstalling)) {
PageController.closePage()
}
PageController.showNotificationMessage(finishedMessage)
}
ServersUiController.setProcessedServerId(ServersUiController.defaultServerId)
PageController.goToPage(PageEnum.PageSetupWizardEasy)
function onRemoveContainerFinished(finishedMessage) {
if (tabBarStackView.currentItem.objectName === PageController.getPagePath(PageEnum.PageDeinstalling)) {
PageController.closePage()
}
PageController.closePage()
PageController.showNotificationMessage(finishedMessage)
}
}
+8
View File
@@ -234,6 +234,8 @@ Window {
DrawerType2 {
id: privateKeyPassphraseDrawer
property bool isCloseByUser: false
anchors.fill: parent
expandedHeight: root.height * 0.35 + PageController.safeAreaBottomMargin + PageController.imeHeight
@@ -253,6 +255,11 @@ Window {
}
function onAboutToHide() {
if (privateKeyPassphraseDrawer.isCloseByUser === false) {
privateKeyPassphraseDrawer.isCloseByUser = true
PageController.passphraseRequestDrawerClosed("")
}
if (passphrase.textField.text !== "") {
PageController.showBusyIndicator(true)
}
@@ -293,6 +300,7 @@ Window {
text: qsTr("Save")
clickedFunc: function() {
privateKeyPassphraseDrawer.isCloseByUser = true
privateKeyPassphraseDrawer.closeTriggered()
PageController.passphraseRequestDrawerClosed(passphrase.textField.text)
}
+1 -4
View File
@@ -15,8 +15,7 @@ enum PermittedProcess {
OpenVPN,
Wireguard,
Tun2Socks,
CertUtil,
Security
CertUtil
};
inline QString permittedProcessPath(PermittedProcess pid)
@@ -30,8 +29,6 @@ inline QString permittedProcessPath(PermittedProcess pid)
return Utils::certUtilPath();
case PermittedProcess::Tun2Socks:
return Utils::tun2socksPath();
case PermittedProcess::Security:
return Utils::securityPath();
default:
return "";
}
+36
View File
@@ -28,6 +28,42 @@ IpcServer::IpcServer(QObject *parent) : IpcInterfaceSource(parent)
connect(&m_pingHelper, &PingHelper::connectionLose, this, &IpcServer::connectionLose);
}
IpcServer::~IpcServer()
{
}
void IpcServer::resetServiceState()
{
qDebug() << "IpcServer::resetServiceState — tearing down active VPN state";
Xray::getInstance().stopXray();
for (auto it = m_processes.cbegin(); it != m_processes.cend(); ++it) {
const ProcessDescriptor &pd = it.value();
if (!pd.ipcProcess)
continue;
pd.ipcProcess->terminate();
if (!pd.ipcProcess->waitForFinished(1000)) {
pd.ipcProcess->kill();
pd.ipcProcess->waitForFinished(1000);
}
pd.ipcProcess->close();
}
m_processes.clear();
Utils::killProcessByName(Utils::tun2socksPath());
Utils::killProcessByName(Utils::openVpnExecPath());
KillSwitch::instance()->disableKillSwitch();
Router::restoreResolvers();
Router::clearSavedRoutes();
Router::StartRoutingIpv6();
Router::flushDns();
m_pingHelper.stop();
}
int IpcServer::createPrivilegedProcess()
{
#ifdef MZ_DEBUG
+4
View File
@@ -17,6 +17,10 @@ class IpcServer : public IpcInterfaceSource
{
public:
explicit IpcServer(QObject *parent = nullptr);
virtual ~IpcServer();
void resetServiceState();
virtual int createPrivilegedProcess() override;
virtual int routeAddList(const QString &gw, const QStringList &ips) override;
+14 -1
View File
@@ -35,7 +35,20 @@ LocalServer::LocalServer(QObject *parent) : QObject(parent),
QObject::connect(m_server.data(), &QLocalServer::newConnection, this, [this]() {
qDebug() << "LocalServer new connection";
m_serverNode.addHostSideConnection(m_server->nextPendingConnection());
QLocalSocket *socket = m_server->nextPendingConnection();
if (!socket)
return;
m_activeClientSocket = socket;
QObject::connect(socket, &QLocalSocket::disconnected, this, [this, socket]() {
qDebug() << "LocalServer: client disconnected";
if (m_activeClientSocket == socket)
m_ipcServer.resetServiceState();
});
m_serverNode.addHostSideConnection(socket);
if (!m_isRemotingEnabled) {
m_isRemotingEnabled = true;
+2
View File
@@ -41,6 +41,8 @@ public:
QRemoteObjectHost m_serverNode;
bool m_isRemotingEnabled = false;
QPointer<QLocalSocket> m_activeClientSocket;
NetworkWatcher m_networkWatcher;
#ifdef Q_OS_LINUX
DaemonLocalServer server{qApp};