mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-08-23 11:40:06 +03:00
Compare commits
14 Commits
dev
...
fix/linux-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aab275cacd | ||
|
|
b66e3def09 | ||
|
|
39a652df5b | ||
|
|
542ae60d27 | ||
|
|
87d90ee92f | ||
|
|
d906df6927 | ||
|
|
9a472c6793 | ||
|
|
9bf974a2f4 | ||
|
|
46100fd311 | ||
|
|
34b62f8fd6 | ||
|
|
b8e9ec8570 | ||
|
|
c35988d79f | ||
|
|
410538c6b2 | ||
|
|
cc71330c27 |
2
client/3rd/amneziawg-apple
vendored
2
client/3rd/amneziawg-apple
vendored
Submodule client/3rd/amneziawg-apple updated: 4bafa5958a...fe2e2d7ebb
@@ -29,6 +29,7 @@ ConnectionController::ConnectionController(SecureServersRepository* serversRepos
|
||||
m_vpnConnection(vpnConnection)
|
||||
{
|
||||
connect(m_vpnConnection, &VpnConnection::connectionStateChanged, this, &ConnectionController::connectionStateChanged);
|
||||
connect(m_vpnConnection, &VpnConnection::systemWoke, this, &ConnectionController::systemWoke);
|
||||
connect(this, &ConnectionController::openConnectionRequested, m_vpnConnection, &VpnConnection::connectToVpn, Qt::QueuedConnection);
|
||||
connect(this, &ConnectionController::closeConnectionRequested, m_vpnConnection, &VpnConnection::disconnectFromVpn, Qt::QueuedConnection);
|
||||
connect(this, &ConnectionController::killSwitchModeChangedRequested, m_vpnConnection, &VpnConnection::onKillSwitchModeChanged, Qt::QueuedConnection);
|
||||
|
||||
@@ -65,6 +65,7 @@ public:
|
||||
|
||||
signals:
|
||||
void connectionStateChanged(Vpn::ConnectionState state);
|
||||
void systemWoke();
|
||||
void openConnectionRequested(const QString &serverId, DockerContainer container, const QJsonObject &vpnConfiguration);
|
||||
void closeConnectionRequested();
|
||||
void killSwitchModeChangedRequested(bool enabled);
|
||||
|
||||
@@ -35,6 +35,11 @@ void VpnProtocol::setLastError(ErrorCode lastError)
|
||||
qCritical().noquote() << "VpnProtocol error, code" << m_lastError << errorString(m_lastError);
|
||||
}
|
||||
|
||||
void VpnProtocol::recordLastError(ErrorCode lastError)
|
||||
{
|
||||
m_lastError = lastError;
|
||||
}
|
||||
|
||||
ErrorCode VpnProtocol::lastError() const
|
||||
{
|
||||
return m_lastError;
|
||||
|
||||
@@ -89,6 +89,10 @@ protected:
|
||||
void startTimeoutTimer();
|
||||
void stopTimeoutTimer();
|
||||
|
||||
// Remembers the error for lastError() without forcing the Error connection
|
||||
// state, unlike setLastError().
|
||||
void recordLastError(ErrorCode lastError);
|
||||
|
||||
Vpn::ConnectionState m_connectionState;
|
||||
|
||||
QString m_routeGateway;
|
||||
@@ -99,7 +103,7 @@ protected:
|
||||
|
||||
private:
|
||||
QTimer* m_timeoutTimer;
|
||||
ErrorCode m_lastError;
|
||||
ErrorCode m_lastError = ErrorCode::NoError;
|
||||
quint64 m_receivedBytes;
|
||||
quint64 m_sentBytes;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QFileInfo>
|
||||
#include <QProcess>
|
||||
#include <QTcpSocket>
|
||||
@@ -50,19 +51,39 @@ WireguardProtocol::~WireguardProtocol()
|
||||
|
||||
void WireguardProtocol::stop()
|
||||
{
|
||||
if (m_stopped) {
|
||||
return;
|
||||
}
|
||||
m_stopped = true;
|
||||
stopMzImpl();
|
||||
return;
|
||||
}
|
||||
|
||||
ErrorCode WireguardProtocol::startMzImpl()
|
||||
{
|
||||
QString protocolName = m_rawConfig.value("protocol").toString();
|
||||
QJsonObject vpnConfigData = m_rawConfig.value(protocolName + "_config_data").toObject();
|
||||
vpnConfigData[configKey::hostName] = NetworkUtilities::getIPAddress(vpnConfigData.value(configKey::hostName).toString());
|
||||
m_rawConfig.insert(protocolName + "_config_data", vpnConfigData);
|
||||
m_rawConfig[configKey::hostName] = NetworkUtilities::getIPAddress(m_rawConfig[configKey::hostName].toString());
|
||||
const QString protocolName = m_rawConfig.value("protocol").toString();
|
||||
const QString configDataKey = protocolName + "_config_data";
|
||||
QJsonObject vpnConfigData = m_rawConfig.value(configDataKey).toObject();
|
||||
|
||||
m_impl->activate(m_rawConfig);
|
||||
const QString endpointHost = vpnConfigData.value(configKey::hostName).toString();
|
||||
const QString endpointIp = NetworkUtilities::getIPAddress(endpointHost);
|
||||
if (endpointIp.isEmpty()) {
|
||||
qWarning() << "WireguardProtocol: unable to resolve the endpoint host, aborting this attempt";
|
||||
recordLastError(ErrorCode::EndpointResolutionError);
|
||||
return ErrorCode::EndpointResolutionError;
|
||||
}
|
||||
|
||||
// Activate a resolved copy: m_rawConfig must keep the original hostname so
|
||||
// a later attempt (e.g. reconnect after wakeup) re-resolves it from scratch
|
||||
// instead of reusing a stale — or empty — address.
|
||||
QJsonObject rawConfig = m_rawConfig;
|
||||
vpnConfigData[configKey::hostName] = endpointIp;
|
||||
rawConfig.insert(configDataKey, vpnConfigData);
|
||||
if (rawConfig.value(configKey::hostName).toString() == endpointHost) {
|
||||
rawConfig[configKey::hostName] = endpointIp;
|
||||
}
|
||||
|
||||
m_impl->activate(rawConfig);
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
@@ -75,5 +96,6 @@ ErrorCode WireguardProtocol::stopMzImpl()
|
||||
|
||||
ErrorCode WireguardProtocol::start()
|
||||
{
|
||||
m_stopped = false;
|
||||
return startMzImpl();
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ public:
|
||||
private:
|
||||
|
||||
QScopedPointer<ControllerImpl> m_impl;
|
||||
bool m_stopped = false;
|
||||
};
|
||||
|
||||
#endif // WIREGUARDPROTOCOL_H
|
||||
|
||||
@@ -68,6 +68,7 @@ namespace amnezia
|
||||
OpenVpnUnknownError = 701,
|
||||
OpenVpnTapAdapterError = 702,
|
||||
AddressPoolError = 703,
|
||||
EndpointResolutionError = 704,
|
||||
|
||||
// 3rd party utils errors
|
||||
OpenSslFailed = 800,
|
||||
|
||||
@@ -66,6 +66,7 @@ QString errorString(ErrorCode code) {
|
||||
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::EndpointResolutionError): errorMessage = QObject::tr("Failed to resolve the VPN server address"); 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;
|
||||
|
||||
@@ -637,7 +637,7 @@ void Daemon::checkHandshake() {
|
||||
pendingHandshakes++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Check again if there were connections that haven't completed a handshake.
|
||||
if (pendingHandshakes > 0) {
|
||||
m_handshakeTimer.start(HANDSHAKE_POLL_MSEC);
|
||||
|
||||
@@ -76,7 +76,16 @@ void LocalSocketController::errorOccurred(
|
||||
}
|
||||
|
||||
qCritical() << "ControllerError";
|
||||
disconnectInternal();
|
||||
|
||||
// The socket to the daemon is gone (daemon restart/crash or a failed
|
||||
// connection attempt). Unlike a plain tunnel-down message we cannot talk to
|
||||
// the daemon anymore, so remember that and let the next activate()/
|
||||
// deactivate() re-establish the connection instead of writing into a dead
|
||||
// socket forever.
|
||||
m_daemonState = eDisconnected;
|
||||
m_initializingRetry = 0;
|
||||
m_initializingTimer.stop();
|
||||
emit disconnected();
|
||||
}
|
||||
|
||||
void LocalSocketController::disconnectInternal() {
|
||||
@@ -122,7 +131,38 @@ void LocalSocketController::daemonConnected() {
|
||||
checkStatus();
|
||||
}
|
||||
|
||||
bool LocalSocketController::isSocketAlive() const {
|
||||
// ConnectingState counts as alive: on a fresh protocol activate() runs
|
||||
// right after initialize(), before the connection (and the status
|
||||
// handshake) completes — writes are buffered by the socket, and the daemon
|
||||
// processes them once the connection is established.
|
||||
const QLocalSocket::LocalSocketState state = m_socket->state();
|
||||
return state == QLocalSocket::ConnectedState ||
|
||||
state == QLocalSocket::ConnectingState;
|
||||
}
|
||||
|
||||
void LocalSocketController::reconnectToDaemon() {
|
||||
if (m_daemonState == eInitializing) {
|
||||
// A connection attempt is already in progress.
|
||||
return;
|
||||
}
|
||||
if (isSocketAlive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.warning() << "Daemon socket is not connected; reconnecting";
|
||||
m_initializingRetry = 0;
|
||||
initializeInternal();
|
||||
}
|
||||
|
||||
void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
||||
if (!isSocketAlive()) {
|
||||
logger.error() << "Cannot activate, daemon connection is not ready";
|
||||
reconnectToDaemon();
|
||||
emit disconnected();
|
||||
return;
|
||||
}
|
||||
|
||||
QString protocolName = rawConfig.value("protocol").toString();
|
||||
|
||||
int splitTunnelType = rawConfig.value("splitTunnelType").toInt();
|
||||
@@ -267,8 +307,9 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
||||
void LocalSocketController::deactivate() {
|
||||
logger.debug() << "Deactivating";
|
||||
|
||||
if (m_daemonState != eReady) {
|
||||
if (m_daemonState != eReady || !isSocketAlive()) {
|
||||
logger.debug() << "No disconnect, controller is not ready";
|
||||
reconnectToDaemon();
|
||||
emit disconnected();
|
||||
return;
|
||||
}
|
||||
@@ -334,7 +375,12 @@ void LocalSocketController::readData() {
|
||||
logger.debug() << "Reading";
|
||||
|
||||
Q_ASSERT(m_socket);
|
||||
Q_ASSERT(m_daemonState == eInitializing || m_daemonState == eReady);
|
||||
if (m_daemonState != eInitializing && m_daemonState != eReady) {
|
||||
// Stray data delivered around a socket teardown — nothing to do with it.
|
||||
m_socket->readAll();
|
||||
m_buffer.clear();
|
||||
return;
|
||||
}
|
||||
QByteArray input = m_socket->readAll();
|
||||
m_buffer.append(input);
|
||||
|
||||
|
||||
@@ -39,6 +39,8 @@ class LocalSocketController final : public ControllerImpl {
|
||||
private:
|
||||
void initializeInternal();
|
||||
void disconnectInternal();
|
||||
void reconnectToDaemon();
|
||||
bool isSocketAlive() const;
|
||||
|
||||
void daemonConnected();
|
||||
void errorOccurred(QLocalSocket::LocalSocketError socketError);
|
||||
|
||||
@@ -7,10 +7,14 @@
|
||||
#include <net/if.h>
|
||||
|
||||
#include <QDBusVariant>
|
||||
#include <QNetworkInterface>
|
||||
#include <QTimer>
|
||||
#include <QtDBus/QtDBus>
|
||||
|
||||
#include "core/utils/networkUtilities.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
#include "router_linux.h"
|
||||
|
||||
constexpr const char* DBUS_RESOLVE_SERVICE = "org.freedesktop.resolve1";
|
||||
constexpr const char* DBUS_RESOLVE_PATH = "/org/freedesktop/resolve1";
|
||||
@@ -27,24 +31,56 @@ DnsUtilsLinux::DnsUtilsLinux(QObject* parent) : DnsUtils(parent) {
|
||||
logger.debug() << "DnsUtilsLinux created.";
|
||||
|
||||
QDBusConnection conn = QDBusConnection::systemBus();
|
||||
m_resolver = new QDBusInterface(DBUS_RESOLVE_SERVICE, DBUS_RESOLVE_PATH,
|
||||
DBUS_RESOLVE_MANAGER, conn, this);
|
||||
auto* watcher = new QDBusServiceWatcher(
|
||||
DBUS_RESOLVE_SERVICE, conn,
|
||||
QDBusServiceWatcher::WatchForRegistration |
|
||||
QDBusServiceWatcher::WatchForUnregistration, this);
|
||||
|
||||
connect(watcher, &QDBusServiceWatcher::serviceRegistered,
|
||||
this, &DnsUtilsLinux::onResolverRegistered);
|
||||
connect(watcher, &QDBusServiceWatcher::serviceUnregistered,
|
||||
this, &DnsUtilsLinux::onResolverUnregistered);
|
||||
|
||||
if (conn.interface()->isServiceRegistered(DBUS_RESOLVE_SERVICE)) {
|
||||
onResolverRegistered();
|
||||
}
|
||||
}
|
||||
|
||||
void DnsUtilsLinux::onResolverRegistered() {
|
||||
m_resolver.reset(new QDBusInterface(DBUS_RESOLVE_SERVICE, DBUS_RESOLVE_PATH,
|
||||
DBUS_RESOLVE_MANAGER,
|
||||
QDBusConnection::systemBus()));
|
||||
logger.debug() << "systemd-resolved available, DNS resolver initialized";
|
||||
|
||||
if (!m_pendingIfname.isEmpty()) {
|
||||
logger.debug() << "Re-applying DNS configuration for" << m_pendingIfname;
|
||||
updateResolvers(m_pendingIfname, m_pendingResolvers);
|
||||
}
|
||||
}
|
||||
|
||||
void DnsUtilsLinux::onResolverUnregistered() {
|
||||
logger.debug() << "systemd-resolved disappeared, dropping DNS resolver";
|
||||
m_resolver.reset();
|
||||
}
|
||||
|
||||
DnsUtilsLinux::~DnsUtilsLinux() {
|
||||
MZ_COUNT_DTOR(DnsUtilsLinux);
|
||||
|
||||
for (auto iterator = m_linkDomains.constBegin();
|
||||
iterator != m_linkDomains.constEnd(); ++iterator) {
|
||||
QList<QVariant> argumentList;
|
||||
argumentList << QVariant::fromValue(iterator.key());
|
||||
argumentList << QVariant::fromValue(iterator.value());
|
||||
m_resolver->asyncCallWithArgumentList(QStringLiteral("SetLinkDomains"),
|
||||
argumentList);
|
||||
}
|
||||
if (m_resolver) {
|
||||
if (m_gatewayIfindex > 0)
|
||||
setLinkDefaultRoute(m_gatewayIfindex, true);
|
||||
|
||||
if (m_ifindex > 0) {
|
||||
m_resolver->asyncCall(QStringLiteral("RevertLink"), m_ifindex);
|
||||
for (auto iterator = m_linkDomains.constBegin();
|
||||
iterator != m_linkDomains.constEnd(); ++iterator) {
|
||||
QList<QVariant> argumentList;
|
||||
argumentList << QVariant::fromValue(iterator.key());
|
||||
argumentList << QVariant::fromValue(iterator.value());
|
||||
m_resolver->asyncCallWithArgumentList(QStringLiteral("SetLinkDomains"),
|
||||
argumentList);
|
||||
}
|
||||
if (m_ifindex > 0) {
|
||||
m_resolver->asyncCall(QStringLiteral("RevertLink"), m_ifindex);
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug() << "DnsUtilsLinux destroyed.";
|
||||
@@ -52,19 +88,52 @@ DnsUtilsLinux::~DnsUtilsLinux() {
|
||||
|
||||
bool DnsUtilsLinux::updateResolvers(const QString& ifname,
|
||||
const QList<QHostAddress>& resolvers) {
|
||||
if (m_gatewayIfindex > 0) {
|
||||
setLinkDefaultRoute(m_gatewayIfindex, true);
|
||||
m_gatewayIfindex = 0;
|
||||
}
|
||||
|
||||
const int previousIfindex = m_ifindex;
|
||||
m_ifindex = if_nametoindex(qPrintable(ifname));
|
||||
if (m_ifindex <= 0) {
|
||||
logger.error() << "Unable to resolve ifindex for" << ifname;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_pendingIfname = ifname;
|
||||
m_pendingResolvers = resolvers;
|
||||
|
||||
if (!m_resolver) {
|
||||
logger.debug() << "systemd-resolved not ready, queuing DNS configuration";
|
||||
return true;
|
||||
}
|
||||
|
||||
const int gwIdx = NetworkUtilities::getGatewayAndIface().second.index();
|
||||
if (gwIdx > 0 && gwIdx != m_ifindex && gwIdx != m_gatewayIfindex) {
|
||||
m_gatewayIfindex = gwIdx;
|
||||
setLinkDefaultRoute(gwIdx, false);
|
||||
}
|
||||
|
||||
setLinkDNS(m_ifindex, resolvers);
|
||||
setLinkDefaultRoute(m_ifindex, true);
|
||||
updateLinkDomains();
|
||||
|
||||
if (previousIfindex > 0 && previousIfindex != m_ifindex) {
|
||||
m_resolver->callWithArgumentList(QDBus::Block, QStringLiteral("RevertLink"),
|
||||
{QVariant::fromValue(previousIfindex)});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DnsUtilsLinux::restoreResolvers() {
|
||||
m_pendingIfname.clear();
|
||||
m_pendingResolvers.clear();
|
||||
|
||||
if (m_gatewayIfindex > 0) {
|
||||
setLinkDefaultRoute(m_gatewayIfindex, true);
|
||||
m_gatewayIfindex = 0;
|
||||
}
|
||||
|
||||
for (auto iterator = m_linkDomains.constBegin();
|
||||
iterator != m_linkDomains.constEnd(); ++iterator) {
|
||||
setLinkDomains(iterator.key(), iterator.value());
|
||||
@@ -72,7 +141,7 @@ bool DnsUtilsLinux::restoreResolvers() {
|
||||
m_linkDomains.clear();
|
||||
|
||||
/* Revert the VPN interface's DNS configuration */
|
||||
if (m_ifindex > 0) {
|
||||
if (m_ifindex > 0 && m_resolver) {
|
||||
QList<QVariant> argumentList = {QVariant::fromValue(m_ifindex)};
|
||||
QDBusPendingReply<> reply = m_resolver->asyncCallWithArgumentList(
|
||||
QStringLiteral("RevertLink"), argumentList);
|
||||
@@ -90,13 +159,17 @@ bool DnsUtilsLinux::restoreResolvers() {
|
||||
void DnsUtilsLinux::dnsCallCompleted(QDBusPendingCallWatcher* call) {
|
||||
QDBusPendingReply<> reply = *call;
|
||||
if (reply.isError()) {
|
||||
logger.error() << "Error received from the DBus service";
|
||||
logger.debug() << "DBus call failed (may be transient after systemd-resolved restart)";
|
||||
logger.debug() << "Restarting resolved to clear its query backlog";
|
||||
RouterLinux::Instance().flushDns();
|
||||
scheduleRetry();
|
||||
}
|
||||
delete call;
|
||||
}
|
||||
|
||||
void DnsUtilsLinux::setLinkDNS(int ifindex,
|
||||
const QList<QHostAddress>& resolvers) {
|
||||
if (!m_resolver) return;
|
||||
QList<DnsResolver> resolverList;
|
||||
char ifnamebuf[IF_NAMESIZE];
|
||||
const char* ifname = if_indextoname(ifindex, ifnamebuf);
|
||||
@@ -121,6 +194,7 @@ void DnsUtilsLinux::setLinkDNS(int ifindex,
|
||||
|
||||
void DnsUtilsLinux::setLinkDomains(int ifindex,
|
||||
const QList<DnsLinkDomain>& domains) {
|
||||
if (!m_resolver) return;
|
||||
char ifnamebuf[IF_NAMESIZE];
|
||||
const char* ifname = if_indextoname(ifindex, ifnamebuf);
|
||||
if (ifname) {
|
||||
@@ -144,6 +218,7 @@ void DnsUtilsLinux::setLinkDomains(int ifindex,
|
||||
}
|
||||
|
||||
void DnsUtilsLinux::setLinkDefaultRoute(int ifindex, bool enable) {
|
||||
if (!m_resolver) return;
|
||||
QList<QVariant> argumentList;
|
||||
argumentList << QVariant::fromValue(ifindex);
|
||||
argumentList << QVariant::fromValue(enable);
|
||||
@@ -156,6 +231,7 @@ void DnsUtilsLinux::setLinkDefaultRoute(int ifindex, bool enable) {
|
||||
}
|
||||
|
||||
void DnsUtilsLinux::updateLinkDomains() {
|
||||
if (!m_resolver) return;
|
||||
/* Get the list of search domains, and remove any others that might conspire
|
||||
* to satisfy DNS resolution. Unfortunately, this is a pain because Qt doesn't
|
||||
* seem to be able to demarshall complex property types.
|
||||
@@ -174,11 +250,20 @@ void DnsUtilsLinux::updateLinkDomains() {
|
||||
|
||||
void DnsUtilsLinux::dnsDomainsReceived(QDBusPendingCallWatcher* call) {
|
||||
QDBusPendingReply<QVariant> reply = *call;
|
||||
call->deleteLater();
|
||||
if (reply.isError()) {
|
||||
logger.error() << "Error retrieving the DNS domains from the DBus service";
|
||||
delete call;
|
||||
// systemd-resolved may still be starting up after a restart — retry a few times
|
||||
if (m_ifindex > 0 && m_domainRetries++ < 5) {
|
||||
logger.debug() << "systemd-resolved not ready yet, retrying DNS setup ("
|
||||
<< m_domainRetries << "/5)";
|
||||
QTimer::singleShot(500, this, &DnsUtilsLinux::updateLinkDomains);
|
||||
} else {
|
||||
logger.warning() << "Failed to configure DNS after 5 retries";
|
||||
m_domainRetries = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
m_domainRetries = 0;
|
||||
|
||||
/* Update the state of the DNS domains */
|
||||
m_linkDomains.clear();
|
||||
@@ -204,9 +289,30 @@ void DnsUtilsLinux::dnsDomainsReceived(QDBusPendingCallWatcher* call) {
|
||||
}
|
||||
|
||||
/* Add a root search domain for the new interface. */
|
||||
QList<DnsLinkDomain> newlist = {root};
|
||||
setLinkDomains(m_ifindex, newlist);
|
||||
delete call;
|
||||
if (m_ifindex > 0) {
|
||||
setLinkDomains(m_ifindex, {root});
|
||||
|
||||
/* Disable DefaultRoute on the physical gateway so systemd-resolved
|
||||
* routes all DNS through the VPN interface. */
|
||||
const int gwIdx = NetworkUtilities::getGatewayAndIface().second.index();
|
||||
if (gwIdx > 0 && gwIdx != m_ifindex && gwIdx != m_gatewayIfindex) {
|
||||
m_gatewayIfindex = gwIdx;
|
||||
setLinkDefaultRoute(gwIdx, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DnsUtilsLinux::scheduleRetry() {
|
||||
if (m_pendingIfname.isEmpty() || m_retryPending || m_domainRetries >= 5)
|
||||
return;
|
||||
m_retryPending = true;
|
||||
++m_domainRetries;
|
||||
logger.debug() << "Retrying full DNS setup (" << m_domainRetries << "/5)";
|
||||
QTimer::singleShot(1000, this, [this]() {
|
||||
m_retryPending = false;
|
||||
if (!m_pendingIfname.isEmpty())
|
||||
updateResolvers(m_pendingIfname, m_pendingResolvers);
|
||||
});
|
||||
}
|
||||
|
||||
static DnsMetatypeRegistrationProxy s_dnsMetatypeProxy;
|
||||
|
||||
@@ -6,7 +6,12 @@
|
||||
#define DNSUTILSLINUX_H
|
||||
|
||||
#include <QDBusInterface>
|
||||
#include <QScopedPointer>
|
||||
#include <QDBusPendingCallWatcher>
|
||||
#include <QDBusServiceWatcher>
|
||||
#include <QHostAddress>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
|
||||
#include "daemon/dnsutils.h"
|
||||
#include "dbustypeslinux.h"
|
||||
@@ -29,13 +34,22 @@ class DnsUtilsLinux final : public DnsUtils {
|
||||
void updateLinkDomains();
|
||||
|
||||
private slots:
|
||||
void onResolverRegistered();
|
||||
void onResolverUnregistered();
|
||||
void dnsCallCompleted(QDBusPendingCallWatcher*);
|
||||
void dnsDomainsReceived(QDBusPendingCallWatcher*);
|
||||
|
||||
private:
|
||||
void scheduleRetry();
|
||||
|
||||
int m_ifindex = 0;
|
||||
int m_gatewayIfindex = 0;
|
||||
int m_domainRetries = 0;
|
||||
bool m_retryPending = false;
|
||||
QMap<int, DnsLinkDomainList> m_linkDomains;
|
||||
QDBusInterface* m_resolver = nullptr;
|
||||
QScopedPointer<QDBusInterface> m_resolver;
|
||||
QString m_pendingIfname;
|
||||
QList<QHostAddress> m_pendingResolvers;
|
||||
};
|
||||
|
||||
#endif // DNSUTILSLINUX_H
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "linuxfirewall.h"
|
||||
#include "logger.h"
|
||||
#include "xray_defs.h"
|
||||
#include <QFileInfo>
|
||||
#include <QProcess>
|
||||
|
||||
#define BRAND_CODE "amn"
|
||||
@@ -109,7 +110,7 @@ int LinuxFirewall::linkChain(LinuxFirewall::IPVersion ip, const QString& chain,
|
||||
// (we can't safely delete all rules at once since rule numbers change)
|
||||
// TODO: occasionally this script results in warnings in logs "Bad rule (does a matching rule exist in the chain?)" - this happens when
|
||||
// the e.g OUTPUT chain is empty but this script attempts to delete things from it anyway. It doesn't cause any problems, but we should still fix at some point..
|
||||
return execute(QStringLiteral("if ! %1 -L %2 -n --line-numbers -t %4 2> /dev/null | awk 'int($1) == 1 && $2 == \"%3\" { found=1 } END { if(found==1) { exit 0 } else { exit 1 } }' ; then %1 -I %2 -j %3 -t %4 && %1 -L %2 -n --line-numbers -t %4 2> /dev/null | awk 'int($1) > 1 && $2 == \"%3\" { print $1; exit }' | xargs %1 -t %4 -D %2 ; fi").arg(cmd, parent, chain, tableName));
|
||||
return execute(QStringLiteral("if ! %1 -L %2 -n --line-numbers -t %4 2> /dev/null | awk 'int($1) == 1 && $2 == \"%3\" { found=1 } END { if(found==1) { exit 0 } else { exit 1 } }' ; then %1 -I %2 -j %3 -t %4 && %1 -L %2 -n --line-numbers -t %4 2> /dev/null | awk 'int($1) > 1 && $2 == \"%3\" { print $1; exit }' | xargs -r %1 -t %4 -D %2 ; fi").arg(cmd, parent, chain, tableName));
|
||||
}
|
||||
else
|
||||
return execute(QStringLiteral("if ! %1 -C %2 -j %3 -t %4 2> /dev/null ; then %1 -A %2 -j %3 -t %4; fi").arg(cmd, parent, chain, tableName));
|
||||
@@ -501,13 +502,22 @@ int LinuxFirewall::execute(const QString &command, bool ignoreErrors)
|
||||
logger.debug() << "(" << exitCode << ") $ " << command;
|
||||
if (!out.isEmpty())
|
||||
logger.info() << out;
|
||||
if (!err.isEmpty())
|
||||
if (!err.isEmpty() && !ignoreErrors)
|
||||
logger.warning() << err;
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
void LinuxFirewall::setupTrafficSplitting()
|
||||
{
|
||||
const QString cgroupBase = QStringLiteral("/sys/fs/cgroup/net_cls");
|
||||
if (!QFileInfo::exists(cgroupBase)) {
|
||||
logger.warning() << "net_cls cgroup v1 not available, traffic splitting disabled";
|
||||
return;
|
||||
}
|
||||
execute(QStringLiteral(
|
||||
"if ! grep -qE '^[0-9]+[[:space:]]+%1$' /etc/iproute2/rt_tables 2>/dev/null ; then "
|
||||
"echo '200 %1' >> /etc/iproute2/rt_tables ; fi"
|
||||
).arg(kRtableName));
|
||||
auto cGroupDir = "/sys/fs/cgroup/net_cls/" BRAND_CODE "vpnexclusions/";
|
||||
logger.info() << "Should be setting up cgroup in" << cGroupDir << "for traffic splitting";
|
||||
execute(QStringLiteral("if [ ! -d %1 ] ; then mkdir %1 ; sleep 0.1 ; echo %2 > %1/net_cls.classid ; fi").arg(cGroupDir).arg(kCGroupId));
|
||||
@@ -519,6 +529,7 @@ void LinuxFirewall::teardownTrafficSplitting()
|
||||
{
|
||||
logger.info() << "Tearing down cgroup and routing rules";
|
||||
execute(QStringLiteral("if ip rule list | grep -q %1; then ip rule del from all fwmark %1 lookup %2 2> /dev/null ; fi").arg(kPacketTag, kRtableName));
|
||||
execute(QStringLiteral("ip route flush table %1").arg(kRtableName));
|
||||
execute(QStringLiteral("ip route flush table %1").arg(kRtableName), true);
|
||||
execute(QStringLiteral("ip route flush cache"));
|
||||
execute(QStringLiteral("sed -i '/%1/d' /etc/iproute2/rt_tables").arg(kRtableName));
|
||||
}
|
||||
|
||||
@@ -263,7 +263,11 @@ bool WireguardUtilsLinux::updatePeer(const InterfaceConfig& config) {
|
||||
// Exclude the server address, except for multihop exit servers.
|
||||
if ((config.m_hopType != InterfaceConfig::MultiHopExit) &&
|
||||
(m_rtmonitor != nullptr)) {
|
||||
m_rtmonitor->addExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
|
||||
if (!config.m_serverIpv4AddrIn.isEmpty() &&
|
||||
!m_rtmonitor->addExclusionRoute(IPAddress(config.m_serverIpv4AddrIn))) {
|
||||
logger.error() << "No gateway — cannot add server exclusion route";
|
||||
return false;
|
||||
}
|
||||
m_rtmonitor->addExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
enum NMState {
|
||||
NM_STATE_UNKNOWN = 0,
|
||||
NM_STATE_ASLEEP = 10,
|
||||
NM_STATE_DISABLED = 10,
|
||||
NM_STATE_DISCONNECTED = 20,
|
||||
NM_STATE_DISCONNECTING = 30,
|
||||
NM_STATE_CONNECTING = 40,
|
||||
@@ -202,9 +201,18 @@ void LinuxNetworkWatcherWorker::NMStateChanged(quint32 state)
|
||||
{
|
||||
logger.debug() << "NMStateChanged " << state;
|
||||
|
||||
if (state == NM_STATE_ASLEEP || state == NM_STATE_DISABLED) {
|
||||
// NM_STATE_ASLEEP means networking just went *down* (e.g. NM tearing
|
||||
// interfaces down for suspend), not that the system woke up. Fire
|
||||
// wakeup() on the transition back out of it, once NM starts doing
|
||||
// anything again, rather than on entering it.
|
||||
if (state == NM_STATE_ASLEEP) {
|
||||
m_wasAsleep = true;
|
||||
} else if (m_wasAsleep) {
|
||||
m_wasAsleep = false;
|
||||
emit wakeup();
|
||||
} else if (state == NM_STATE_CONNECTED_GLOBAL) {
|
||||
}
|
||||
|
||||
if (state == NM_STATE_CONNECTED_GLOBAL) {
|
||||
emit networkChanged();
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,10 @@ class LinuxNetworkWatcherWorker final : public QObject {
|
||||
// initialization. When a property of them changes, we check if the access
|
||||
// point is active and unsecure.
|
||||
QStringList m_devicePaths;
|
||||
|
||||
// Set on NM_STATE_ASLEEP, cleared when NetworkManager comes back. Guards
|
||||
// wakeup() so it fires on the resume transition, not on going to sleep.
|
||||
bool m_wasAsleep = false;
|
||||
};
|
||||
|
||||
#endif // LINUXNETWORKWATCHERWORKER_H
|
||||
|
||||
@@ -19,6 +19,7 @@ ConnectionUiController::ConnectionUiController(ConnectionController* connectionC
|
||||
m_serversController(serversController)
|
||||
{
|
||||
connect(m_connectionController, &ConnectionController::connectionStateChanged, this, &ConnectionUiController::onConnectionStateChanged);
|
||||
connect(m_connectionController, &ConnectionController::systemWoke, this, &ConnectionUiController::systemWoke);
|
||||
|
||||
connect(this, &ConnectionUiController::connectButtonClicked, this, &ConnectionUiController::toggleConnection, Qt::QueuedConnection);
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ public slots:
|
||||
|
||||
signals:
|
||||
void connectionStateChanged();
|
||||
void systemWoke();
|
||||
|
||||
void connectionErrorOccurred(ErrorCode errorCode);
|
||||
|
||||
|
||||
@@ -16,6 +16,31 @@ Window {
|
||||
id: root
|
||||
objectName: "mainWindow"
|
||||
|
||||
property bool controlsDisabled: false
|
||||
|
||||
Connections {
|
||||
target: PageController
|
||||
function onDisableControls(disabled) {
|
||||
root.controlsDisabled = disabled
|
||||
}
|
||||
}
|
||||
|
||||
function clearStuckUiBlockIfNeeded(reason) {
|
||||
if (busyIndicator.visible || root.controlsDisabled) {
|
||||
console.warn("UI was still blocked (busyIndicator/disableControls) on", reason, "; clearing it")
|
||||
busyIndicator.visible = false
|
||||
PageController.disableControls(false)
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: ConnectionController
|
||||
function onSystemWoke() {
|
||||
console.log("ConnectionController.systemWoke received")
|
||||
root.clearStuckUiBlockIfNeeded("system wakeup")
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Qt.application
|
||||
function onStateChanged() {
|
||||
|
||||
@@ -36,6 +36,20 @@
|
||||
|
||||
using namespace ProtocolUtils;
|
||||
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
namespace {
|
||||
// A reconnect attempt that has not reached Connected within this time is
|
||||
// considered failed and is retried (some failures — e.g. a daemon-side
|
||||
// activation error — produce no client-visible event at all).
|
||||
constexpr int RECONNECT_ATTEMPT_TIMEOUT_MSEC = 30 * 1000;
|
||||
constexpr int RECONNECT_RETRY_BASE_MSEC = 1000;
|
||||
constexpr int RECONNECT_RETRY_MAX_MSEC = 60 * 1000;
|
||||
// A fresh trigger does not restart an attempt younger than this: such an
|
||||
// attempt was started under (almost) the same network conditions anyway.
|
||||
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 +58,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 +357,11 @@ 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);
|
||||
// Detach every slot of ours (state, bytes, errors) so tearing the old
|
||||
// protocol down doesn't inject events into the new connection flow.
|
||||
m_vpnProtocol->disconnect(this);
|
||||
m_vpnProtocol->stop();
|
||||
m_vpnProtocol.reset();
|
||||
}
|
||||
@@ -376,13 +400,19 @@ 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);
|
||||
// The replica is thread-local and long-lived while this method runs on
|
||||
// every connect — UniqueConnection keeps these from piling up.
|
||||
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);
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_LINUX)
|
||||
connect(rep.data(), &IpcInterfaceReplica::wakeup, this, &VpnConnection::systemWoke, queuedUnique);
|
||||
#endif
|
||||
});
|
||||
#endif
|
||||
}
|
||||
@@ -549,29 +579,168 @@ 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) {
|
||||
// Conditions changed (e.g. the network actually came back after
|
||||
// wakeup) — restart the backoff sequence and try again right away.
|
||||
// An in-flight attempt that was started before this trigger is likely
|
||||
// doomed (it raced the network coming up), so restart it too instead
|
||||
// of waiting out its watchdog; a just-started attempt is left alone.
|
||||
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();
|
||||
}
|
||||
|
||||
m_vpnProtocol->stop();
|
||||
if (ErrorCode err = m_vpnProtocol->start(); err != ErrorCode::NoError) {
|
||||
setConnectionState(Vpn::ConnectionState::Error);
|
||||
emit vpnProtocolError(err);
|
||||
void VpnConnection::startReconnectAttempt()
|
||||
{
|
||||
if (!m_reconnectActive)
|
||||
return;
|
||||
|
||||
if (m_vpnProtocol.isNull()) {
|
||||
cancelReconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
++m_reconnectAttempt;
|
||||
qDebug() << "Reconnect: attempt" << m_reconnectAttempt;
|
||||
m_reconnectAttemptAge.start();
|
||||
|
||||
// stop() may synchronously emit Disconnected; while the machine is active
|
||||
// (and no attempt is in flight yet) onProtocolConnectionStateChanged
|
||||
// suppresses it so the UI stays in Reconnecting.
|
||||
m_vpnProtocol->stop();
|
||||
|
||||
m_reconnectAttemptInFlight = true;
|
||||
if (ErrorCode err = m_vpnProtocol->start(); err != ErrorCode::NoError) {
|
||||
qWarning() << "Reconnect: attempt" << m_reconnectAttempt << "failed to start, error" << err;
|
||||
scheduleReconnectRetry();
|
||||
return;
|
||||
}
|
||||
|
||||
// start() may have failed synchronously through a protocol event, in which
|
||||
// case the retry is already scheduled and the watchdog must stay off.
|
||||
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
|
||||
{
|
||||
// 1s, 2s, 4s, ... capped at RECONNECT_RETRY_MAX_MSEC; a fresh trigger
|
||||
// resets m_reconnectAttempt and thus the sequence.
|
||||
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:
|
||||
// Keep the between-attempts cleanup the old code used to run for
|
||||
// a swallowed Disconnected (DNS flush, saved-routes cleanup), but
|
||||
// hold the UI in Reconnecting and keep retrying.
|
||||
onConnectionStateChanged(state);
|
||||
if (m_reconnectAttemptInFlight) {
|
||||
qWarning() << "Reconnect: attempt" << m_reconnectAttempt << "failed, protocol reported"
|
||||
<< QMetaEnum::fromType<Vpn::ConnectionState>().valueToKey(state);
|
||||
scheduleReconnectRetry();
|
||||
}
|
||||
return;
|
||||
default:
|
||||
// Transient states while retrying — keep showing Reconnecting.
|
||||
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 +766,13 @@ void VpnConnection::disconnectFromVpn()
|
||||
});
|
||||
#endif
|
||||
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
// Drive the final state ourselves: a protocol that is already internally
|
||||
// Disconnected (e.g. after failed reconnect attempts) will not emit
|
||||
// another Disconnected, which used to leave the UI stuck in Disconnecting.
|
||||
m_vpnProtocol->disconnect(this);
|
||||
#endif
|
||||
|
||||
m_vpnProtocol->stop();
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(AMNEZIA_DESKTOP)
|
||||
@@ -604,13 +780,22 @@ 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
|
||||
// On desktop the reconnect machine decides which protocol events are
|
||||
// propagated (see onProtocolConnectionStateChanged); on mobile keep the
|
||||
// historical behavior of hiding the stop() blip during a reconnect.
|
||||
if (state == Vpn::Disconnected && m_connectionState == Vpn::Reconnecting)
|
||||
return;
|
||||
#endif
|
||||
|
||||
m_connectionState = state;
|
||||
emit connectionStateChanged(state);
|
||||
|
||||
@@ -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);
|
||||
@@ -65,10 +65,21 @@ signals:
|
||||
|
||||
void serviceIsNotReady();
|
||||
|
||||
void systemWoke();
|
||||
|
||||
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 +107,24 @@ private:
|
||||
|
||||
void appendSplitTunnelingConfig();
|
||||
void appendKillSwitchConfig();
|
||||
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
// Auto-reconnect state machine (wakeup / network change). While it is
|
||||
// active the UI is held in the Reconnecting state and stop()/start()
|
||||
// attempts are retried with backoff until the protocol reports Connected
|
||||
// or the user cancels via connect/disconnect.
|
||||
void requestReconnect(const QString &trigger);
|
||||
void scheduleReconnectRetry();
|
||||
void cancelReconnect();
|
||||
int reconnectRetryDelayMsec() const;
|
||||
|
||||
bool m_reconnectActive = false; // machine engaged, UI shows Reconnecting
|
||||
bool m_reconnectAttemptInFlight = false; // start() issued, waiting for the outcome
|
||||
int m_reconnectAttempt = 0; // attempts since the last trigger, drives backoff
|
||||
QTimer m_reconnectRetryTimer{this}; // single-shot, schedules the next attempt
|
||||
QTimer m_reconnectWatchdogTimer{this}; // single-shot, bounds a single attempt
|
||||
QElapsedTimer m_reconnectAttemptAge; // how long the in-flight attempt has been running
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif // VPNCONNECTION_H
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
QFile Logger::m_file;
|
||||
QTextStream Logger::m_textStream;
|
||||
QMutex Logger::m_fileMutex;
|
||||
QString Logger::m_logFileName = QString("%1.log").arg(APPLICATION_NAME);
|
||||
QString Logger::m_serviceLogFileName = QString("%1.log").arg(SERVICE_NAME);
|
||||
|
||||
@@ -68,15 +69,22 @@ bool Logger::init(bool isServiceLogger)
|
||||
return false;
|
||||
}
|
||||
|
||||
m_file.setFileName(appDir.filePath(logFileName));
|
||||
if (!m_file.open(QIODevice::Append)) {
|
||||
bool opened = false;
|
||||
{
|
||||
QMutexLocker locker(&m_fileMutex);
|
||||
m_file.setFileName(appDir.filePath(logFileName));
|
||||
opened = m_file.open(QIODevice::Append);
|
||||
if (opened) {
|
||||
m_file.setTextModeEnabled(true);
|
||||
m_textStream.setDevice(&m_file);
|
||||
}
|
||||
}
|
||||
|
||||
if (!opened) {
|
||||
qWarning() << "Cannot open log file:" << logFileName;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_file.setTextModeEnabled(true);
|
||||
m_textStream.setDevice(&m_file);
|
||||
|
||||
qInstallMessageHandler(messageHandler);
|
||||
|
||||
return true;
|
||||
@@ -84,6 +92,7 @@ bool Logger::init(bool isServiceLogger)
|
||||
|
||||
void Logger::deInit()
|
||||
{
|
||||
QMutexLocker locker(&m_fileMutex);
|
||||
m_textStream.setDevice(nullptr);
|
||||
m_file.close();
|
||||
}
|
||||
@@ -137,8 +146,11 @@ QString Logger::serviceLogsFilePath()
|
||||
|
||||
QString Logger::getLogFile()
|
||||
{
|
||||
if (m_file.isOpen()) {
|
||||
m_file.flush();
|
||||
{
|
||||
QMutexLocker locker(&m_fileMutex);
|
||||
if (m_file.isOpen()) {
|
||||
m_file.flush();
|
||||
}
|
||||
}
|
||||
QFile file(userLogsFilePath());
|
||||
|
||||
@@ -154,8 +166,11 @@ QString Logger::getLogFile()
|
||||
|
||||
QString Logger::getServiceLogFile()
|
||||
{
|
||||
if (m_file.isOpen()) {
|
||||
m_file.flush();
|
||||
{
|
||||
QMutexLocker locker(&m_fileMutex);
|
||||
if (m_file.isOpen()) {
|
||||
m_file.flush();
|
||||
}
|
||||
}
|
||||
QFile file(serviceLogsFilePath());
|
||||
|
||||
@@ -184,8 +199,12 @@ bool Logger::openLogsFolder(bool isServiceLogger)
|
||||
|
||||
void Logger::clearLogs(bool isServiceLogger)
|
||||
{
|
||||
bool isLogActive = m_file.isOpen();
|
||||
m_file.close();
|
||||
bool isLogActive;
|
||||
{
|
||||
QMutexLocker locker(&m_fileMutex);
|
||||
isLogActive = m_file.isOpen();
|
||||
m_file.close();
|
||||
}
|
||||
|
||||
QFile file(isServiceLogger ? serviceLogsFilePath() : userLogsFilePath());
|
||||
|
||||
@@ -243,9 +262,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);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QMutex>
|
||||
#include <QString>
|
||||
#include <QTextStream>
|
||||
|
||||
@@ -104,6 +105,7 @@ private:
|
||||
|
||||
static QFile m_file;
|
||||
static QTextStream m_textStream;
|
||||
static QMutex m_fileMutex;
|
||||
static QString m_logFileName;
|
||||
static QString m_serviceLogFileName;
|
||||
|
||||
|
||||
@@ -178,11 +178,16 @@ bool RouterLinux::flushDns()
|
||||
}
|
||||
|
||||
p.waitForFinished();
|
||||
QByteArray output(p.readAll());
|
||||
QByteArray output = p.readAll();
|
||||
if ((p.exitStatus() != QProcess::NormalExit) || (p.exitCode() != 0)) {
|
||||
qDebug().noquote() << "Failed to flush DNS: " + output;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (output.isEmpty())
|
||||
qDebug().noquote() << "Flush dns completed";
|
||||
else
|
||||
qDebug().noquote() << "OUTPUT systemctl restart nscd/systemd-resolved: " + output;
|
||||
qDebug().noquote() << "OUTPUT systemctl restart: " + output;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user