Compare commits

...
Author SHA1 Message Date
NickVs2015 0d91176512 refactor: move reconnect logic to VpnConnection 2026-08-21 18:07:22 +03:00
NickVs2015 6ae5a874eb fix: removed unused func 2026-08-21 16:41:35 +03:00
NickVs2015 16651e9bf4 fix: removed unused func 2026-08-21 16:37:03 +03:00
NickVs2015 403d6a49bb fix: remove comment 2026-08-21 15:20:28 +03:00
NickVs2015 0777e7f231 revert: remove onSystemWoke UI-recovery mechanism 2026-08-21 15:18:09 +03:00
NickVs2015 8c79857d88 fix: fully serialize Logger file access with a recursive mutex 2026-08-21 12:11:47 +03:00
NickVs2015 b933ceda66 fix: share double-stop guard via VpnProtocol base class 2026-08-21 12:11:43 +03:00
NickVs2015 ca7056868a fix: retry WinTun adapter creation race after Windows resume 2026-08-21 12:11:37 +03:00
NickVs2015 2395c2dbc5 fix: resolve deadlock filewrite and remove qml ApplicationActive event 2026-08-17 16:45:29 +03:00
NickVs2015 ad97f6f209 fix: add log systemWoke received 2026-08-17 13:37:35 +03:00
NickVs2015 fb17015d21 fix: recover from stuck busy-indicator/disabled-controls on real OS wakeup (Windows/Linux) 2026-08-17 10:38:24 +03:00
NickVs2015 15de902111 fix: QRingBuffer corruption under concurrent logging 2026-08-15 13:51:20 +03:00
NickVs2015 f581ab8e6e fix: detect real sleep/resume via PBT_APMSUSPEND+PBT_APMRESUMEAUTOMATIC on Windows 2026-08-15 13:18:48 +03:00
NickVs2015 df16abf77e fix: correct leak-detector dtor macro and honor service poll timeout on Windows 2026-08-15 13:18:43 +03:00
NickVs2015 354d0b7210 fix: make WireguardProtocol::stop() idempotent to avoid double deactivate() 2026-08-15 13:18:13 +03:00
NickVs2015 a8f3f046d1 fix: correct ControlService call and ANSI error message decoding on Windows 2026-08-15 13:18:13 +03:00
16 changed files with 266 additions and 31 deletions

No files matched your search

@@ -61,6 +61,10 @@ void OpenVpnProtocol::cleanupResources()
void OpenVpnProtocol::stop()
{
if (guardStop()) {
return;
}
qDebug() << "OpenVpnProtocol::stop()";
const bool wasActive = m_connectionState == Vpn::ConnectionState::Preparing
@@ -179,6 +183,8 @@ void OpenVpnProtocol::updateRouteGateway(QString line)
ErrorCode OpenVpnProtocol::start()
{
resetStopGuard();
cleanupResources();
if (!QFileInfo::exists(configPath())) {
+14
View File
@@ -26,6 +26,20 @@ VpnProtocol::VpnProtocol(const QJsonObject &configuration, QObject *parent)
connect(m_timeoutTimer, &QTimer::timeout, this, &VpnProtocol::onTimeout);
}
bool VpnProtocol::guardStop()
{
if (m_stopped) {
return true;
}
m_stopped = true;
return false;
}
void VpnProtocol::resetStopGuard()
{
m_stopped = false;
}
void VpnProtocol::setLastError(ErrorCode lastError)
{
m_lastError = lastError;
+5
View File
@@ -89,6 +89,11 @@ protected:
void startTimeoutTimer();
void stopTimeoutTimer();
bool guardStop();
void resetStopGuard();
bool m_stopped = false;
Vpn::ConnectionState m_connectionState;
QString m_routeGateway;
+4 -1
View File
@@ -50,8 +50,10 @@ WireguardProtocol::~WireguardProtocol()
void WireguardProtocol::stop()
{
if (guardStop()) {
return;
}
stopMzImpl();
return;
}
ErrorCode WireguardProtocol::startMzImpl()
@@ -75,5 +77,6 @@ ErrorCode WireguardProtocol::stopMzImpl()
ErrorCode WireguardProtocol::start()
{
resetStopGuard();
return startMzImpl();
}
+6
View File
@@ -66,6 +66,8 @@ XrayProtocol::~XrayProtocol()
ErrorCode XrayProtocol::start()
{
resetStopGuard();
qDebug() << "XrayProtocol::start()";
// Inject SOCKS5 auth into the inbound before starting xray.
@@ -118,6 +120,10 @@ ErrorCode XrayProtocol::start()
void XrayProtocol::stop()
{
if (guardStop()) {
return;
}
qDebug() << "XrayProtocol::stop()";
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
@@ -50,7 +50,7 @@ WindowsTunnelService::WindowsTunnelService(QObject* parent) : QObject(parent) {
}
WindowsTunnelService::~WindowsTunnelService() {
MZ_COUNT_CTOR(WindowsTunnelService);
MZ_COUNT_DTOR(WindowsTunnelService);
stop();
CloseServiceHandle((SC_HANDLE)m_scm);
}
@@ -297,6 +297,11 @@ static bool waitForServiceStatus(SC_HANDLE service, DWORD expectedStatus) {
return true;
}
if (expectedStatus == SERVICE_RUNNING &&
status.dwCurrentState == SERVICE_STOPPED) {
return false;
}
logger.warning() << "The service is not in the right status yet.";
Sleep(1000);
@@ -40,8 +40,13 @@ LRESULT WindowsNetworkWatcher::PowerWndProcCallback(HWND hwnd, UINT uMsg, WPARAM
}
switch (uMsg) {
case WM_POWERBROADCAST:
if (wParam == PBT_APMRESUMESUSPEND) {
if (wParam == PBT_APMSUSPEND) {
obj->m_suspended = true;
} else if (wParam == PBT_APMRESUMEAUTOMATIC) {
if (obj->m_suspended) {
obj->m_suspended = false;
emit obj->wakeup();
}
}
break;
default:
@@ -28,6 +28,7 @@ class WindowsNetworkWatcher final : public NetworkWatcherImpl {
// to inform about network changes.
HANDLE m_wlanHandle = nullptr;
QString m_lastBSSID;
bool m_suspended = false;
};
#endif // WINDOWSNETWORKWATCHER_H
@@ -160,7 +160,7 @@ void WindowsPingSender::sendPing(const QHostAddress& dest, quint16 sequence) {
DWORD status = GetLastError();
if (status != ERROR_IO_PENDING) {
QString errmsg = WindowsUtils::getErrorMessage();
QString errmsg = WindowsUtils::getErrorMessage(status);
logger.error() << "failed to start Code: " << status
<< " Message: " << errmsg
<< " dest:" << logger.sensitive(dest.toString());
@@ -178,7 +178,7 @@ void WindowsPingSender::pingEventReady() {
if (error == IP_REQ_TIMED_OUT) {
return;
}
QString errmsg = WindowsUtils::getErrorMessage();
QString errmsg = WindowsUtils::getErrorMessage(error);
emit criticalPingError();
logger.error() << "No ping reply. Code: " << error
<< " Message: " << errmsg;
@@ -85,7 +85,7 @@ bool WindowsServiceManager::startPolling(DWORD goal_state, int max_wait_sec) {
return true;
}
logger.debug() << "Polling Status" << m_state_target
logger.debug() << "Polling Status" << goal_state
<< "wanted, has: " << status.dwCurrentState;
Sleep(1000);
++tries;
@@ -117,7 +117,10 @@ bool WindowsServiceManager::startService() {
NULL); // no arguments
if (ok) {
logger.debug() << ("Service start requested");
startPolling(SERVICE_RUNNING, 30);
ok = startPolling(SERVICE_RUNNING, 30);
if (!ok) {
logger.error() << "Timed out waiting for the service to start";
}
} else {
WindowsUtils::windowsLog("StartService failed");
}
@@ -130,10 +133,14 @@ bool WindowsServiceManager::stopService() {
logger.warning() << ("Service stop not possible, as its not running");
}
bool ok = ControlService(m_service, SERVICE_CONTROL_STOP, NULL);
SERVICE_STATUS status;
bool ok = ControlService(m_service, SERVICE_CONTROL_STOP, &status);
if (ok) {
logger.debug() << ("Service stop requested");
startPolling(SERVICE_STOPPED, 10);
ok = startPolling(SERVICE_STOPPED, 10);
if (!ok) {
logger.error() << "Timed out waiting for the service to stop";
}
} else {
WindowsUtils::windowsLog("StopService failed");
}
+4 -8
View File
@@ -27,20 +27,16 @@ QString WindowsUtils::getErrorMessage(quint32 code) {
nullptr, code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPSTR)&messageBuffer, 0, nullptr);
std::string message(messageBuffer, size);
QString result(message.c_str());
QString result = QString::fromLocal8Bit(messageBuffer, static_cast<int>(size)).trimmed();
LocalFree(messageBuffer);
return result;
}
QString WindowsUtils::getErrorMessage() {
return getErrorMessage(GetLastError());
}
// A simple function to log windows error messages.
void WindowsUtils::windowsLog(const QString& msg) {
QString errmsg = getErrorMessage();
logger.error() << msg << "-" << errmsg;
quint32 code = GetLastError();
QString errmsg = getErrorMessage(code);
logger.error() << msg << "-" << errmsg << QString("(code %1)").arg(code);
}
// Static
-1
View File
@@ -9,7 +9,6 @@
class WindowsUtils final {
public:
static QString getErrorMessage();
static QString getErrorMessage(quint32 code);
static void windowsLog(const QString& msg);
+160 -9
View File
@@ -36,6 +36,15 @@
using namespace ProtocolUtils;
#ifdef AMNEZIA_DESKTOP
namespace {
constexpr int RECONNECT_ATTEMPT_TIMEOUT_MSEC = 30 * 1000;
constexpr int RECONNECT_RETRY_BASE_MSEC = 1000;
constexpr int RECONNECT_RETRY_MAX_MSEC = 60 * 1000;
constexpr int RECONNECT_ATTEMPT_MIN_AGE_MSEC = 1000;
}
#endif
VpnConnection::VpnConnection(SecureServersRepository* serversRepository, SecureAppSettingsRepository* appSettingsRepository, QObject *parent)
: QObject(parent), m_serversRepository(serversRepository), m_appSettingsRepository(appSettingsRepository), m_checkTimer(this)
{
@@ -44,6 +53,13 @@ VpnConnection::VpnConnection(SecureServersRepository* serversRepository, SecureA
connect(IosController::Instance(), &IosController::connectionStateChanged, this, &VpnConnection::setConnectionState);
connect(IosController::Instance(), &IosController::bytesChanged, this, &VpnConnection::onBytesChanged);
#endif
#ifdef AMNEZIA_DESKTOP
m_reconnectRetryTimer.setSingleShot(true);
connect(&m_reconnectRetryTimer, &QTimer::timeout, this, &VpnConnection::startReconnectAttempt);
m_reconnectWatchdogTimer.setSingleShot(true);
connect(&m_reconnectWatchdogTimer, &QTimer::timeout, this, &VpnConnection::onReconnectWatchdogTimeout);
#endif
}
VpnConnection::~VpnConnection()
@@ -336,8 +352,9 @@ void VpnConnection::connectToVpn(const QString &serverId, DockerContainer contai
m_vpnConfiguration = vpnConfiguration;
#ifdef AMNEZIA_DESKTOP
cancelReconnect();
if (m_vpnProtocol) {
disconnect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError);
m_vpnProtocol->disconnect(this);
m_vpnProtocol->stop();
m_vpnProtocol.reset();
}
@@ -376,13 +393,14 @@ void VpnConnection::connectToVpn(const QString &serverId, DockerContainer contai
void VpnConnection::createProtocolConnections()
{
connect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError);
connect(m_vpnProtocol.data(), &VpnProtocol::connectionStateChanged, this, &VpnConnection::setConnectionState);
connect(m_vpnProtocol.data(), &VpnProtocol::connectionStateChanged, this, &VpnConnection::onProtocolConnectionStateChanged);
connect(m_vpnProtocol.data(), SIGNAL(bytesChanged(quint64, quint64)), this, SLOT(onBytesChanged(quint64, quint64)));
#ifdef AMNEZIA_DESKTOP
IpcClient::withInterface([this](QSharedPointer<IpcInterfaceReplica> rep) {
connect(rep.data(), &IpcInterfaceReplica::networkChanged, this, &VpnConnection::reconnectToVpn, Qt::QueuedConnection);
connect(rep.data(), &IpcInterfaceReplica::wakeup, this, &VpnConnection::reconnectToVpn, Qt::QueuedConnection);
const auto queuedUnique = static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::UniqueConnection);
connect(rep.data(), &IpcInterfaceReplica::networkChanged, this, &VpnConnection::onIpcNetworkChanged, queuedUnique);
connect(rep.data(), &IpcInterfaceReplica::wakeup, this, &VpnConnection::onIpcWakeup, queuedUnique);
});
#endif
}
@@ -549,29 +567,152 @@ QString VpnConnection::bytesPerSecToText(quint64 bytes)
return QString("%1 %2").arg(QString::number(mbps, 'f', 2)).arg(tr("Mbps")); // Mbit/s
}
void VpnConnection::reconnectToVpn() {
#ifdef AMNEZIA_DESKTOP
void VpnConnection::onIpcWakeup()
{
requestReconnect(QStringLiteral("wakeup"));
}
void VpnConnection::onIpcNetworkChanged()
{
requestReconnect(QStringLiteral("network change"));
}
void VpnConnection::requestReconnect(const QString &trigger)
{
if (m_vpnProtocol.isNull())
return;
if (m_reconnectActive) {
if (m_reconnectAttemptInFlight && m_reconnectAttemptAge.isValid()
&& m_reconnectAttemptAge.elapsed() < RECONNECT_ATTEMPT_MIN_AGE_MSEC) {
qDebug() << "Reconnect: new trigger" << trigger << "ignored, current attempt has just started";
m_reconnectAttempt = 0;
return;
}
qDebug() << "Reconnect: new trigger" << trigger << "while already reconnecting, retrying immediately";
m_reconnectAttempt = 0;
m_reconnectAttemptInFlight = false;
m_reconnectRetryTimer.stop();
m_reconnectWatchdogTimer.stop();
startReconnectAttempt();
return;
}
if (m_connectionState != Vpn::ConnectionState::Connected) {
qWarning() << QString("Reconnect triggered on %1 during inappropriate state: %2; ignoring slot")
qWarning() << QString("Reconnect triggered by %1 during inappropriate state: %2; ignoring")
.arg(trigger)
.arg(QMetaEnum::fromType<Vpn::ConnectionState>().valueToKey(m_connectionState));
return;
}
qDebug() << "Reconnect triggered. Reconnecting to the server";
qDebug() << "Reconnect triggered by" << trigger << ". Reconnecting to the server";
m_reconnectActive = true;
m_reconnectAttempt = 0;
setConnectionState(Vpn::ConnectionState::Reconnecting);
startReconnectAttempt();
}
void VpnConnection::startReconnectAttempt()
{
if (!m_reconnectActive)
return;
if (m_vpnProtocol.isNull()) {
cancelReconnect();
return;
}
++m_reconnectAttempt;
qDebug() << "Reconnect: attempt" << m_reconnectAttempt;
m_reconnectAttemptAge.start();
m_vpnProtocol->stop();
m_reconnectAttemptInFlight = true;
if (ErrorCode err = m_vpnProtocol->start(); err != ErrorCode::NoError) {
setConnectionState(Vpn::ConnectionState::Error);
emit vpnProtocolError(err);
qWarning() << "Reconnect: attempt" << m_reconnectAttempt << "failed to start, error" << err;
scheduleReconnectRetry();
return;
}
if (m_reconnectAttemptInFlight) {
m_reconnectWatchdogTimer.start(RECONNECT_ATTEMPT_TIMEOUT_MSEC);
}
}
void VpnConnection::onReconnectWatchdogTimeout()
{
if (!m_reconnectActive || !m_reconnectAttemptInFlight)
return;
qWarning() << "Reconnect: attempt" << m_reconnectAttempt << "did not reach the Connected state in time";
scheduleReconnectRetry();
}
void VpnConnection::scheduleReconnectRetry()
{
m_reconnectWatchdogTimer.stop();
m_reconnectAttemptInFlight = false;
if (!m_reconnectActive)
return;
const int delay = reconnectRetryDelayMsec();
qDebug() << "Reconnect: next attempt in" << delay << "ms";
m_reconnectRetryTimer.start(delay);
}
void VpnConnection::cancelReconnect()
{
m_reconnectActive = false;
m_reconnectAttemptInFlight = false;
m_reconnectRetryTimer.stop();
m_reconnectWatchdogTimer.stop();
}
int VpnConnection::reconnectRetryDelayMsec() const
{
const int exponent = qMin(m_reconnectAttempt > 0 ? m_reconnectAttempt - 1 : 0, 6);
return qMin(RECONNECT_RETRY_BASE_MSEC << exponent, RECONNECT_RETRY_MAX_MSEC);
}
#endif
void VpnConnection::onProtocolConnectionStateChanged(Vpn::ConnectionState state)
{
#ifdef AMNEZIA_DESKTOP
if (m_reconnectActive) {
switch (state) {
case Vpn::ConnectionState::Connected:
qDebug() << "Reconnect: succeeded on attempt" << m_reconnectAttempt;
cancelReconnect();
break; // propagate below
case Vpn::ConnectionState::Disconnected:
case Vpn::ConnectionState::Error:
onConnectionStateChanged(state);
if (m_reconnectAttemptInFlight) {
qWarning() << "Reconnect: attempt" << m_reconnectAttempt << "failed, protocol reported"
<< QMetaEnum::fromType<Vpn::ConnectionState>().valueToKey(state);
scheduleReconnectRetry();
}
return;
default:
return;
}
}
#endif
setConnectionState(state);
}
void VpnConnection::disconnectFromVpn()
{
#ifdef AMNEZIA_DESKTOP
cancelReconnect();
#endif
#if defined(Q_OS_IOS) || defined(MACOS_NE)
// iOS/macOS NE use IosController directly; m_vpnProtocol is not set there.
IosController::Instance()->disconnectVpn();
@@ -597,6 +738,10 @@ void VpnConnection::disconnectFromVpn()
});
#endif
#ifdef AMNEZIA_DESKTOP
m_vpnProtocol->disconnect(this);
#endif
m_vpnProtocol->stop();
#if !defined(Q_OS_ANDROID) && !defined(AMNEZIA_DESKTOP)
@@ -604,13 +749,19 @@ void VpnConnection::disconnectFromVpn()
#endif
m_vpnProtocol = nullptr;
#ifdef AMNEZIA_DESKTOP
setConnectionState(Vpn::ConnectionState::Disconnected);
#endif
}
void VpnConnection::setConnectionState(Vpn::ConnectionState state) {
onConnectionStateChanged(state);
#ifndef AMNEZIA_DESKTOP
if (state == Vpn::Disconnected && m_connectionState == Vpn::Reconnecting)
return;
#endif
m_connectionState = state;
emit connectionStateChanged(state);
+24 -1
View File
@@ -6,6 +6,7 @@
#include <QString>
#include <QScopedPointer>
#include <QRemoteObjectNode>
#include <QElapsedTimer>
#include <QTimer>
#include "core/protocols/vpnProtocol.h"
@@ -50,7 +51,6 @@ public:
public slots:
void setRepositories(SecureServersRepository* serversRepository, SecureAppSettingsRepository* appSettingsRepository);
void connectToVpn(const QString &serverId, DockerContainer container, const QJsonObject &vpnConfiguration);
void reconnectToVpn();
void disconnectFromVpn();
void onKillSwitchModeChanged(bool enabled);
@@ -69,6 +69,15 @@ protected slots:
void onBytesChanged(quint64 receivedBytes, quint64 sentBytes);
void onConnectionStateChanged(Vpn::ConnectionState state);
private slots:
void onProtocolConnectionStateChanged(Vpn::ConnectionState state);
#ifdef AMNEZIA_DESKTOP
void onIpcWakeup();
void onIpcNetworkChanged();
void startReconnectAttempt();
void onReconnectWatchdogTimeout();
#endif
protected:
QSharedPointer<VpnProtocol> m_vpnProtocol;
@@ -96,6 +105,20 @@ private:
void appendSplitTunnelingConfig();
void appendKillSwitchConfig();
#ifdef AMNEZIA_DESKTOP
void requestReconnect(const QString &trigger);
void scheduleReconnectRetry();
void cancelReconnect();
int reconnectRetryDelayMsec() const;
bool m_reconnectActive = false;
bool m_reconnectAttemptInFlight = false;
int m_reconnectAttempt = 0;
QTimer m_reconnectRetryTimer{this};
QTimer m_reconnectWatchdogTimer{this};
QElapsedTimer m_reconnectAttemptAge;
#endif
};
#endif // VPNCONNECTION_H
+15 -3
View File
@@ -22,6 +22,7 @@
QFile Logger::m_file;
QTextStream Logger::m_textStream;
QRecursiveMutex Logger::m_fileMutex;
QString Logger::m_logFileName = QString("%1.log").arg(APPLICATION_NAME);
QString Logger::m_serviceLogFileName = QString("%1.log").arg(SERVICE_NAME);
@@ -68,6 +69,7 @@ bool Logger::init(bool isServiceLogger)
return false;
}
QMutexLocker locker(&m_fileMutex);
m_file.setFileName(appDir.filePath(logFileName));
if (!m_file.open(QIODevice::Append)) {
qWarning() << "Cannot open log file:" << logFileName;
@@ -84,6 +86,7 @@ bool Logger::init(bool isServiceLogger)
void Logger::deInit()
{
QMutexLocker locker(&m_fileMutex);
m_textStream.setDevice(nullptr);
m_file.close();
}
@@ -137,9 +140,11 @@ QString Logger::serviceLogsFilePath()
QString Logger::getLogFile()
{
QMutexLocker locker(&m_fileMutex);
if (m_file.isOpen()) {
m_file.flush();
}
QFile file(userLogsFilePath());
file.open(QIODevice::ReadOnly);
@@ -154,9 +159,11 @@ QString Logger::getLogFile()
QString Logger::getServiceLogFile()
{
QMutexLocker locker(&m_fileMutex);
if (m_file.isOpen()) {
m_file.flush();
}
QFile file(serviceLogsFilePath());
file.open(QIODevice::ReadOnly);
@@ -184,6 +191,8 @@ bool Logger::openLogsFolder(bool isServiceLogger)
void Logger::clearLogs(bool isServiceLogger)
{
QMutexLocker locker(&m_fileMutex);
bool isLogActive = m_file.isOpen();
m_file.close();
@@ -243,9 +252,12 @@ Logger::LogStreamer::~LogStreamer()
.arg(QDateTime::currentDateTimeUtc().toString("[yyyy-MM-dd hh:mm:ss.zzzZ]"),
logLevelString, m_logger->className(), m_data->m_buffer.trimmed());
if (m_file.isOpen()) {
QTextStream logToFile(&m_file);
logToFile << message << Qt::endl << Qt::flush;
{
QMutexLocker locker(&m_fileMutex);
if (m_file.isOpen()) {
QTextStream logToFile(&m_file);
logToFile << message << Qt::endl << Qt::flush;
}
}
QTextStream logToOutput((m_logLevel == LogLevel::Error) ? stderr : stdout);
+2
View File
@@ -4,6 +4,7 @@
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QRecursiveMutex>
#include <QString>
#include <QTextStream>
@@ -104,6 +105,7 @@ private:
static QFile m_file;
static QTextStream m_textStream;
static QRecursiveMutex m_fileMutex;
static QString m_logFileName;
static QString m_serviceLogFileName;