From c8d52231bf306e265d5b8d08a03df57cf62ee1ba Mon Sep 17 00:00:00 2001 From: cd-amn Date: Tue, 9 Jun 2026 18:58:30 +0400 Subject: [PATCH] fix: close traffic leak during seamless tunnel switch --- client/core/vpnTrafficGuard.cpp | 31 +++++++++++++++++++++++++++++++ client/vpnConnection.cpp | 11 ++++++++--- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/client/core/vpnTrafficGuard.cpp b/client/core/vpnTrafficGuard.cpp index 97cc6af1a..0e4398cd1 100644 --- a/client/core/vpnTrafficGuard.cpp +++ b/client/core/vpnTrafficGuard.cpp @@ -1,6 +1,7 @@ #include "vpnTrafficGuard.h" #include +#include #include #include #include @@ -484,7 +485,37 @@ void VpnTrafficGuard::swap(Tunnel* from, Tunnel* to) if (from) { 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); + + 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 iface) { + auto reply = iface->flushDns(); + if (!reply.waitForFinished(5000) || !reply.returnValue()) { + qWarning() << "VpnTrafficGuard::swap: trailing sync IPC timed out or failed"; + } + }); + if (from) { m_allowedEndpoints.removeAll(from->remoteAddress()); #ifndef Q_OS_WIN diff --git a/client/vpnConnection.cpp b/client/vpnConnection.cpp index f8395250a..d119d2f36 100644 --- a/client/vpnConnection.cpp +++ b/client/vpnConnection.cpp @@ -577,9 +577,14 @@ void VpnConnection::onTunnelPrepared() m_remoteAddress = m_active->remoteAddress(); m_trafficGuard->setConfig(m_vpnConfiguration); - m_trafficGuard->swap(oldTunnel, m_active); - delete oldTunnel; - releaseIfname(oldIfname); + // Run the swap from a clean event-loop tick so the nested QEventLoop inside + // VpnTrafficGuard::swap does not deadlock the LSC.readData stack frame that + // delivered Tunnel::prepared. + QMetaObject::invokeMethod(this, [this, oldTunnel, oldIfname]() { + m_trafficGuard->swap(oldTunnel, m_active); + delete oldTunnel; + releaseIfname(oldIfname); + }, Qt::QueuedConnection); return; }