fix: close traffic leak during seamless tunnel switch

This commit is contained in:
cd-amn
2026-06-09 18:58:30 +04:00
parent 54d28862f3
commit 7bb609866a
2 changed files with 39 additions and 3 deletions
+31
View File
@@ -1,6 +1,7 @@
#include "vpnTrafficGuard.h" #include "vpnTrafficGuard.h"
#include <QDebug> #include <QDebug>
#include <QEventLoop>
#include <QHostInfo> #include <QHostInfo>
#include <QNetworkInterface> #include <QNetworkInterface>
#include <QPointer> #include <QPointer>
@@ -484,7 +485,37 @@ void VpnTrafficGuard::swap(Tunnel* from, Tunnel* to)
if (from) { if (from) {
to->setHandoverIfname(from->ifname()); to->setHandoverIfname(from->ifname());
} }
QEventLoop loop;
QTimer guard;
guard.setSingleShot(true);
const auto activatedConn = connect(to, &Tunnel::activated, &loop, &QEventLoop::quit);
const auto failedConn = connect(to, &Tunnel::failed, &loop, [&loop](amnezia::ErrorCode) { loop.quit(); });
const auto timeoutConn = connect(&guard, &QTimer::timeout, &loop, [&loop]() {
qWarning() << "VpnTrafficGuard::swap: timed out waiting for new tunnel activation";
loop.quit();
});
commit(to); commit(to);
guard.start(5000);
loop.exec();
guard.stop();
disconnect(activatedConn);
disconnect(failedConn);
disconnect(timeoutConn);
// Service IPCs are processed in order on a single connection. Wait on a trailing
// sync request so the killswitch enable from the activation handlers is fully
// applied before we deactivate the previous tunnel.
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
auto reply = iface->flushDns();
if (!reply.waitForFinished(5000) || !reply.returnValue()) {
qWarning() << "VpnTrafficGuard::swap: trailing sync IPC timed out or failed";
}
});
if (from) { if (from) {
m_allowedEndpoints.removeAll(from->remoteAddress()); m_allowedEndpoints.removeAll(from->remoteAddress());
#ifndef Q_OS_WIN #ifndef Q_OS_WIN
+8 -3
View File
@@ -577,9 +577,14 @@ void VpnConnection::onTunnelPrepared()
m_remoteAddress = m_active->remoteAddress(); m_remoteAddress = m_active->remoteAddress();
m_trafficGuard->setConfig(m_vpnConfiguration); m_trafficGuard->setConfig(m_vpnConfiguration);
m_trafficGuard->swap(oldTunnel, m_active); // Run the swap from a clean event-loop tick so the nested QEventLoop inside
delete oldTunnel; // VpnTrafficGuard::swap does not deadlock the LSC.readData stack frame that
releaseIfname(oldIfname); // delivered Tunnel::prepared.
QMetaObject::invokeMethod(this, [this, oldTunnel, oldIfname]() {
m_trafficGuard->swap(oldTunnel, m_active);
delete oldTunnel;
releaseIfname(oldIfname);
}, Qt::QueuedConnection);
return; return;
} }