Compare commits

...
Author SHA1 Message Date
NickVs2015 dc4fa5735c fix: remove comments 2026-08-23 16:52:31 +03:00
NickVs2015 3ebf48920e fix: keep kill-switch firewall up during automatic reconnect attempts 2026-08-23 16:33:56 +03:00
NickVs2015 a633d2f80b fix: stop reconnect backoff from being bypassed by repeated triggers 2026-08-23 16:33:56 +03:00
NickVs2015andPokamest Nikak bee62898fa fix: linux reconnect-after-wakeup state machine
Co-Authored-By: Pokamest Nikak <pokamest@gmail.com>
2026-08-23 16:33:49 +03:00
NickVs2015 b66e3def09 fix: resolve deadlock filewrite and remove qml ApplicationActive event 2026-08-18 09:49:33 +03:00
NickVs2015 39a652df5b fix: QRingBuffer corruption under concurrent logging 2026-08-18 09:49:27 +03:00
NickVs2015 542ae60d27 fix: add log systemWoke received 2026-08-18 09:44:09 +03:00
NickVs2015 87d90ee92f fix: recover from stuck busy-indicator/disabled-controls on real OS wakeup (Windows/Linux) 2026-08-18 09:44:04 +03:00
NickVs2015 d906df6927 fix: prevent double WireGuard stop and fix NM sleep/wake 2026-08-18 09:43:53 +03:00
NickVs2015 9a472c6793 fix: bad rule xargs, cgroup v2 mkdir, amnvpnrt rt_tables, restart flush 2026-08-06 19:33:37 +03:00
NickVs2015 9bf974a2f4 fix: amnvpnrt flush error 2026-08-06 16:42:19 +03:00
NickVs2015 46100fd311 fix: make Linux DNS retry setup 2026-08-06 15:16:29 +03:00
NickVs2015 34b62f8fd6 fix: replace m_linkDefaultRoutes map with single m_gatewayIfindex
fix: solve infinite reconnect NM down
2026-08-06 14:46:01 +03:00
NickVs2015 b8e9ec8570 fix: killswitch compatible 2026-08-06 14:01:24 +03:00
NickVs2015 c35988d79f fix: NM down/up reconnection problem 2026-08-06 14:01:24 +03:00
NickVs2015 410538c6b2 fix: dns rewrites and flush 2026-08-06 14:01:24 +03:00
NickVs2015 cc71330c27 fix: make dbus async 2026-08-06 14:01:24 +03:00
29 changed files with 533 additions and 87 deletions

No files matched your search

+5
View File
@@ -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;
+14 -1
View File
@@ -72,6 +72,8 @@ public:
static VpnProtocol* factory(amnezia::DockerContainer container, const QJsonObject &configuration);
void setKeepFirewallOnNextStop(bool keep) { m_keepFirewallOnStop = keep; }
signals:
void bytesChanged(quint64 receivedBytes, quint64 sentBytes);
void connectionStateChanged(Vpn::ConnectionState state);
@@ -89,6 +91,16 @@ protected:
void startTimeoutTimer();
void stopTimeoutTimer();
// Remembers the error for lastError() without forcing the Error connection
// state, unlike setLastError().
void recordLastError(ErrorCode lastError);
bool takeKeepFirewallOnNextStop() {
bool keep = m_keepFirewallOnStop;
m_keepFirewallOnStop = false;
return keep;
}
Vpn::ConnectionState m_connectionState;
QString m_routeGateway;
@@ -99,7 +111,8 @@ protected:
private:
QTimer* m_timeoutTimer;
ErrorCode m_lastError;
ErrorCode m_lastError = ErrorCode::NoError;
bool m_keepFirewallOnStop = false;
quint64 m_receivedBytes;
quint64 m_sentBytes;
};
+31 -9
View File
@@ -1,4 +1,5 @@
#include <QCoreApplication>
#include <QDebug>
#include <QFileInfo>
#include <QProcess>
#include <QTcpSocket>
@@ -50,30 +51,51 @@ WireguardProtocol::~WireguardProtocol()
void WireguardProtocol::stop()
{
stopMzImpl();
if (m_stopped) {
return;
}
m_stopped = true;
stopMzImpl(takeKeepFirewallOnNextStop());
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;
}
ErrorCode WireguardProtocol::stopMzImpl()
ErrorCode WireguardProtocol::stopMzImpl(bool keepFirewall)
{
m_impl->deactivate();
m_impl->deactivate(keepFirewall);
return ErrorCode::NoError;
}
ErrorCode WireguardProtocol::start()
{
m_stopped = false;
return startMzImpl();
}
+2 -1
View File
@@ -23,11 +23,12 @@ public:
void stop() override;
ErrorCode startMzImpl();
ErrorCode stopMzImpl();
ErrorCode stopMzImpl(bool keepFirewall);
private:
QScopedPointer<ControllerImpl> m_impl;
bool m_stopped = false;
};
#endif // WIREGUARDPROTOCOL_H
+1
View File
@@ -68,6 +68,7 @@ namespace amnezia
OpenVpnUnknownError = 701,
OpenVpnTapAdapterError = 702,
AddressPoolError = 703,
EndpointResolutionError = 704,
// 3rd party utils errors
OpenSslFailed = 800,
+1
View File
@@ -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;
+3 -3
View File
@@ -468,7 +468,7 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
return true;
}
bool Daemon::deactivate(bool emitSignals) {
bool Daemon::deactivate(bool emitSignals, bool keepFirewall) {
Q_ASSERT(wgutils() != nullptr);
// Deactivate the main interface.
@@ -507,7 +507,7 @@ bool Daemon::deactivate(bool emitSignals) {
m_connections.clear();
// Delete the interface
return wgutils()->deleteInterface();
return wgutils()->deleteInterface(keepFirewall);
}
QString Daemon::logs() {
@@ -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);
+1 -1
View File
@@ -33,7 +33,7 @@ class Daemon : public QObject {
static bool parseConfig(const QJsonObject& obj, InterfaceConfig& config);
virtual bool activate(const InterfaceConfig& config);
virtual bool deactivate(bool emitSignals = true);
virtual bool deactivate(bool emitSignals = true, bool keepFirewall = false);
virtual QJsonObject getStatus();
// Callback before any Activating measure is done
@@ -119,7 +119,7 @@ void DaemonLocalServerConnection::parseCommand(const QByteArray& data) {
}
if (type == "deactivate") {
Daemon::instance()->deactivate(true);
Daemon::instance()->deactivate(true, obj.value("keepFirewall").toBool(false));
return;
}
+1 -1
View File
@@ -35,7 +35,7 @@ class WireguardUtils : public QObject {
virtual bool interfaceExists() = 0;
virtual QString interfaceName() { return WG_INTERFACE; }
virtual bool addInterface(const InterfaceConfig& config) = 0;
virtual bool deleteInterface() = 0;
virtual bool deleteInterface(bool keepFirewall = false) = 0;
virtual bool updatePeer(const InterfaceConfig& config) = 0;
virtual bool deletePeer(const InterfaceConfig& config) = 0;
+1 -1
View File
@@ -42,7 +42,7 @@ class ControllerImpl : public QObject {
// This method terminates the VPN tunnel. The VPN client is in
// "disconnecting" state until the "disconnected" signal is received.
virtual void deactivate() = 0;
virtual void deactivate(bool keepFirewall = false) = 0;
// This method is used to retrieve the VPN tunnel status (mainly the number
// of bytes sent and received). It's called always when the VPN tunnel is
+42 -4
View File
@@ -76,7 +76,11 @@ void LocalSocketController::errorOccurred(
}
qCritical() << "ControllerError";
disconnectInternal();
m_daemonState = eDisconnected;
m_initializingRetry = 0;
m_initializingTimer.stop();
emit disconnected();
}
void LocalSocketController::disconnectInternal() {
@@ -122,7 +126,34 @@ void LocalSocketController::daemonConnected() {
checkStatus();
}
bool LocalSocketController::isSocketAlive() const {
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();
@@ -264,17 +295,19 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
write(json);
}
void LocalSocketController::deactivate() {
void LocalSocketController::deactivate(bool keepFirewall) {
logger.debug() << "Deactivating";
if (m_daemonState != eReady) {
if (m_daemonState != eReady || !isSocketAlive()) {
logger.debug() << "No disconnect, controller is not ready";
reconnectToDaemon();
emit disconnected();
return;
}
QJsonObject json;
json.insert("type", "deactivate");
json.insert("keepFirewall", keepFirewall);
write(json);
emit disconnected();
}
@@ -334,7 +367,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);
+3 -1
View File
@@ -26,7 +26,7 @@ class LocalSocketController final : public ControllerImpl {
void activate(const QJsonObject& rawConfig) override;
void deactivate() override;
void deactivate(bool keepFirewall = false) override;
void checkStatus() override;
@@ -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);
+125 -19
View File
@@ -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;
+15 -1
View File
@@ -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));
}
@@ -199,7 +199,7 @@ bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) {
return (err == 0);
}
bool WireguardUtilsLinux::deleteInterface() {
bool WireguardUtilsLinux::deleteInterface(bool keepFirewall) {
if (m_rtmonitor) {
delete m_rtmonitor;
m_rtmonitor = nullptr;
@@ -221,7 +221,9 @@ bool WireguardUtilsLinux::deleteInterface() {
QFile::remove(wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".name"));
// double-check + ensure our firewall is installed and enabled
KillSwitch::instance()->disableKillSwitch();
if (!keepFirewall) {
KillSwitch::instance()->disableKillSwitch();
}
return true;
}
@@ -263,7 +265,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));
}
@@ -26,7 +26,7 @@ public:
}
QString interfaceName() override { return m_ifname; }
bool addInterface(const InterfaceConfig& config) override;
bool deleteInterface() override;
bool deleteInterface(bool keepFirewall = false) override;
bool updatePeer(const InterfaceConfig& config) override;
bool deletePeer(const InterfaceConfig& config) override;
@@ -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
@@ -197,7 +197,7 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) {
return (err == 0);
}
bool WireguardUtilsMacos::deleteInterface() {
bool WireguardUtilsMacos::deleteInterface(bool keepFirewall) {
if (m_rtmonitor) {
delete m_rtmonitor;
m_rtmonitor = nullptr;
@@ -219,7 +219,9 @@ bool WireguardUtilsMacos::deleteInterface() {
QFile::remove(wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".name"));
// double-check + ensure our firewall is installed and enabled
KillSwitch::instance()->disableKillSwitch();
if (!keepFirewall) {
KillSwitch::instance()->disableKillSwitch();
}
return true;
}
@@ -24,7 +24,7 @@ class WireguardUtilsMacos final : public WireguardUtils {
}
QString interfaceName() override { return m_ifname; }
bool addInterface(const InterfaceConfig& config) override;
bool deleteInterface() override;
bool deleteInterface(bool keepFirewall = false) override;
bool updatePeer(const InterfaceConfig& config) override;
bool deletePeer(const InterfaceConfig& config) override;
@@ -138,12 +138,14 @@ bool WireguardUtilsWindows::addInterface(const InterfaceConfig& config) {
return true;
}
bool WireguardUtilsWindows::deleteInterface() {
bool WireguardUtilsWindows::deleteInterface(bool keepFirewall) {
if (m_routeMonitor) {
m_routeMonitor->deleteLater();
}
m_firewall->disableKillSwitch();
if (!keepFirewall) {
m_firewall->disableKillSwitch();
}
m_tunnel.stop();
return true;
}
@@ -32,7 +32,7 @@ class WireguardUtilsWindows final : public WireguardUtils {
}
static const QString s_interfaceName() { return "AmneziaVPN"; }
bool addInterface(const InterfaceConfig& config) override;
bool deleteInterface() override;
bool deleteInterface(bool keepFirewall = false) override;
bool updatePeer(const InterfaceConfig& config) override;
bool deletePeer(const InterfaceConfig& config) override;
+169 -11
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,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 +395,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 +569,157 @@ 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_reconnectAttemptAge.isValid()
&& m_reconnectAttemptAge.elapsed() < RECONNECT_ATTEMPT_MIN_AGE_MSEC) {
qDebug() << "Reconnect: new trigger" << trigger << "ignored, last attempt started too recently";
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();
m_vpnProtocol->setKeepFirewallOnNextStop(true);
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;
}
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:
// 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 +745,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 +756,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; // 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
+36 -14
View File
@@ -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);
+2
View File
@@ -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;
+7 -2
View File
@@ -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;
}