From 9b329ad5b1d6da715f28e879959a79bbf4d6040c Mon Sep 17 00:00:00 2001 From: cd-amn Date: Tue, 19 May 2026 12:25:22 +0000 Subject: [PATCH] refactor: move routing/KS/DNS lifecycle from Daemon to TrafficGuard --- client/core/vpnTrafficGuard.cpp | 158 +++++++++++++++++- client/core/vpnTrafficGuard.h | 15 +- client/daemon/daemon.cpp | 150 ++++++----------- client/daemon/daemon.h | 11 +- client/mozilla/localsocketcontroller.cpp | 15 +- client/mozilla/localsocketcontroller.h | 6 +- client/platforms/linux/daemon/linuxdaemon.cpp | 6 - client/platforms/linux/daemon/linuxdaemon.h | 2 - .../linux/daemon/wireguardutilslinux.cpp | 18 -- .../platforms/macos/daemon/dnsutilsmacos.cpp | 3 + client/platforms/macos/daemon/macosdaemon.cpp | 6 - client/platforms/macos/daemon/macosdaemon.h | 2 - .../macos/daemon/macosroutemonitor.cpp | 1 - .../macos/daemon/wireguardutilsmacos.cpp | 14 -- .../windows/daemon/wireguardutilswindows.cpp | 12 -- client/vpnConnection.cpp | 68 ++++---- ipc/ipc_interface.rep | 6 + ipc/ipcserver.cpp | 32 ++++ ipc/ipcserver.h | 7 + service/server/localserver.cpp | 14 +- 20 files changed, 319 insertions(+), 227 deletions(-) diff --git a/client/core/vpnTrafficGuard.cpp b/client/core/vpnTrafficGuard.cpp index 5000a73bb..6d8ae7494 100644 --- a/client/core/vpnTrafficGuard.cpp +++ b/client/core/vpnTrafficGuard.cpp @@ -13,6 +13,8 @@ #endif #include "core/utils/networkUtilities.h" +#include "core/tunnel.h" +#include "mozilla/localsocketcontroller.h" VpnTrafficGuard::VpnTrafficGuard(SecureAppSettingsRepository* appSettings, QObject *parent) : QObject(parent), m_appSettingsRepository(appSettings) @@ -230,29 +232,30 @@ void VpnTrafficGuard::applyFirewall(const QString &gateway, const QString &local #endif } -void VpnTrafficGuard::teardown() +void VpnTrafficGuard::flushAll() { #ifdef AMNEZIA_DESKTOP IpcClient::withInterface([&](QSharedPointer iface) { + iface->restoreTunnelResolvers(); QRemoteObjectPendingReply reply = iface->disableKillSwitch(); m_allowedEndpoints.clear(); //TODO: why it takes so long? if (!reply.waitForFinished(5000) || !reply.returnValue()) { - qWarning() << "VpnTrafficGuard::teardown: Failed to disable killswitch"; + qWarning() << "VpnTrafficGuard::flushAll: Failed to disable killswitch"; } else { - qDebug() << "VpnTrafficGuard::teardown: Successfully disabled killswitch"; + qDebug() << "VpnTrafficGuard::flushAll: Successfully disabled killswitch"; } auto flushDns = iface->flushDns(); if (flushDns.waitForFinished() && flushDns.returnValue()) - qDebug() << "VpnTrafficGuard::teardown: Successfully flushed DNS"; + qDebug() << "VpnTrafficGuard::flushAll: Successfully flushed DNS"; else - qWarning() << "VpnTrafficGuard::teardown: Failed to flush DNS"; + qWarning() << "VpnTrafficGuard::flushAll: Failed to flush DNS"; auto clearSavedRoutes = iface->clearSavedRoutes(); if (clearSavedRoutes.waitForFinished() && clearSavedRoutes.returnValue()) - qDebug() << "VpnTrafficGuard::teardown: Successfully cleared saved routes"; + qDebug() << "VpnTrafficGuard::flushAll: Successfully cleared saved routes"; else - qWarning() << "VpnTrafficGuard::teardown: Failed to clear saved routes"; + qWarning() << "VpnTrafficGuard::flushAll: Failed to clear saved routes"; if (m_ipv6RoutingStopped) { auto StartRoutingIpv6 = iface->StartRoutingIpv6(); if (!StartRoutingIpv6.waitForFinished() || !StartRoutingIpv6.returnValue()) { @@ -264,3 +267,144 @@ void VpnTrafficGuard::teardown() }); #endif } + +namespace { +QStringList allowedIpPrefixesFor(const QJsonObject& activateJson) +{ + QStringList prefixes; + const QJsonArray ranges = activateJson.value("allowedIPAddressRanges").toArray(); + for (const QJsonValue& v : ranges) { + const QJsonObject r = v.toObject(); + const QString addr = r.value("address").toString(); + if (addr.isEmpty()) continue; + prefixes.append(QStringLiteral("%1/%2").arg(addr).arg(r.value("range").toInt())); + } + return prefixes; +} + +QStringList excludedAddressesFor(const QJsonObject& activateJson) +{ + QStringList addrs; + const QJsonArray excluded = activateJson.value("excludedAddresses").toArray(); + for (const QJsonValue& v : excluded) { + const QString s = v.toString(); + if (!s.isEmpty()) addrs.append(s); + } + return addrs; +} + +QStringList resolversFor(const QJsonObject& activateJson) +{ + QStringList dns; + const QString primary = activateJson.value("primaryDnsServer").toString(); + if (!primary.isEmpty()) dns.append(primary); + const QString secondary = activateJson.value("secondaryDnsServer").toString(); + if (!secondary.isEmpty()) dns.append(secondary); + return dns; +} +} + +void VpnTrafficGuard::reserve(Tunnel* tunnel) +{ + if (!tunnel) return; +#ifdef AMNEZIA_DESKTOP + allowEndpoint(tunnel->remoteAddress()); +#else + Q_UNUSED(tunnel) +#endif +} + +void VpnTrafficGuard::release(Tunnel* tunnel) +{ + if (!tunnel) return; +#ifdef AMNEZIA_DESKTOP + revokeEndpoint(tunnel->remoteAddress()); +#else + Q_UNUSED(tunnel) +#endif +} + +void VpnTrafficGuard::applyPolicy(Tunnel* tunnel) +{ + if (!tunnel) return; +#ifdef AMNEZIA_DESKTOP + const QJsonObject activate = LocalSocketController::buildActivateJson(tunnel->config(), tunnel->ifname()); + const QStringList prefixes = allowedIpPrefixesFor(activate); + const QStringList excluded = excludedAddressesFor(activate); + const QStringList dns = resolversFor(activate); + const QString ifname = tunnel->ifname(); + const QString peer = tunnel->remoteAddress(); + + IpcClient::withInterface([&](QSharedPointer iface) { + if (!peer.isEmpty()) iface->addExclusionRoute(peer); + for (const QString& addr : excluded) { + iface->addExclusionRoute(addr); + } + for (const QString& prefix : prefixes) { + iface->addAllowedIp(ifname, prefix); + } + iface->setTunnelResolvers(ifname, dns); + iface->flushDns(); + }); +#else + Q_UNUSED(tunnel) +#endif +} + +void VpnTrafficGuard::revokePolicy(Tunnel* tunnel) +{ + if (!tunnel) return; +#ifdef AMNEZIA_DESKTOP + const QJsonObject activate = LocalSocketController::buildActivateJson(tunnel->config(), tunnel->ifname()); + const QStringList prefixes = allowedIpPrefixesFor(activate); + const QStringList excluded = excludedAddressesFor(activate); + const QString ifname = tunnel->ifname(); + const QString peer = tunnel->remoteAddress(); + + IpcClient::withInterface([&](QSharedPointer iface) { + for (const QString& prefix : prefixes) { + iface->delAllowedIp(ifname, prefix); + } + for (const QString& addr : excluded) { + iface->delExclusionRoute(addr); + } + if (!peer.isEmpty()) iface->delExclusionRoute(peer); + }); +#else + Q_UNUSED(tunnel) +#endif +} + +void VpnTrafficGuard::bringUp(Tunnel* tunnel) +{ + if (!tunnel) return; + reserve(tunnel); + tunnel->prepare(); +} + +void VpnTrafficGuard::commit(Tunnel* tunnel) +{ + if (!tunnel) return; + applyPolicy(tunnel); + tunnel->commit(); +} + +void VpnTrafficGuard::tearDown(Tunnel* tunnel) +{ + if (!tunnel) return; + revokePolicy(tunnel); + release(tunnel); + tunnel->deactivate(); +} + +void VpnTrafficGuard::swap(Tunnel* from, Tunnel* to) +{ + if (!to) return; + applyPolicy(to); + to->commit(); + if (from) { + revokePolicy(from); + release(from); + from->deactivate(); + } +} diff --git a/client/core/vpnTrafficGuard.h b/client/core/vpnTrafficGuard.h index c56047bce..141bbd111 100644 --- a/client/core/vpnTrafficGuard.h +++ b/client/core/vpnTrafficGuard.h @@ -5,6 +5,8 @@ #include "core/repositories/secureAppSettingsRepository.h" #include "protocols/vpnProtocol.h" +class Tunnel; + class VpnTrafficGuard : public QObject { Q_OBJECT @@ -17,10 +19,21 @@ public: const QSharedPointer &protocol, const QString &remoteAddress); - void teardown(); + void flushAll(); bool allowEndpoint(const QString &remoteAddress); void revokeEndpoint(const QString &remoteAddress); void applyFirewall(const QString &vpnGateway, const QString &vpnLocalAddress); + + void reserve(Tunnel* tunnel); + void release(Tunnel* tunnel); + void applyPolicy(Tunnel* tunnel); + void revokePolicy(Tunnel* tunnel); + + void bringUp(Tunnel* tunnel); + void commit(Tunnel* tunnel); + void tearDown(Tunnel* tunnel); + void swap(Tunnel* from, Tunnel* to); + private: void addSplitTunnelRoutes(const QString &gateway, amnezia::RouteMode mode); SecureAppSettingsRepository* m_appSettingsRepository; diff --git a/client/daemon/daemon.cpp b/client/daemon/daemon.cpp index e9d342960..e5dcd9d3c 100644 --- a/client/daemon/daemon.cpp +++ b/client/daemon/daemon.cpp @@ -101,13 +101,6 @@ bool Daemon::activate(const QString& ifname, const InterfaceConfig& config) { } } - if (!config.m_serverIpv4AddrIn.isEmpty()) { - addExclusionRoute(IPAddress(config.m_serverIpv4AddrIn)); - } - if (!config.m_serverIpv6AddrIn.isEmpty()) { - addExclusionRoute(IPAddress(config.m_serverIpv6AddrIn)); - } - // Add the peer to this interface. if (!wg->updatePeer(config)) { logger.error() << "Peer creation failed."; @@ -138,90 +131,32 @@ bool Daemon::setPrimary(const QString& ifname, const InterfaceConfig& config) { m_primaryIfname = priorPrimary; }); - for (const QString& i : config.m_excludedAddresses) { - addExclusionRoute(IPAddress(i)); - } - - for (const IPAddress& ip : config.m_allowedIPAddressRanges) { - if (!wg->updateRoutePrefix(ip)) { - logger.warning() << "setPrimary: route setup failed for" << ip.toString(); - } - } - - if (!maybeUpdateResolvers(config)) { - logger.warning() << "setPrimary: DNS resolver update failed"; - } - if (!run(Up, config)) { return false; } m_connections[ifname].m_config = config; - // Demote the prior primary AFTER the new primary is fully installed. - // Delete-after-install order preserves coverage during the make-before-break overlap. - if (!priorPrimary.isEmpty() && priorPrimary != ifname) { - demotePrimary(priorPrimary); - } - failure_guard.dismiss(); return true; } -void Daemon::demotePrimary(const QString& ifname) { - WireguardUtils* wg = wgutilsFor(ifname); - if (!wg) { - return; - } - const ConnectionState cs = m_connections.value(ifname); - const InterfaceConfig& config = cs.m_config; - - for (const IPAddress& ip : config.m_allowedIPAddressRanges) { - wg->deleteRoutePrefix(ip); - } - for (const QString& addr : config.m_excludedAddresses) { - if (addr.isEmpty()) { - continue; - } - IPAddress ip(addr); - if (m_excludedAddrSet.contains(ip)) { - delExclusionRoute(ip); - } - } -} - bool Daemon::deactivateTunnel(const QString& ifname) { WireguardUtils* wg = m_tunnels.value(ifname); const ConnectionState cs = m_connections.value(ifname); const InterfaceConfig& config = cs.m_config; const bool wasPrimary = (ifname == m_primaryIfname); + const bool isLastTunnel = wg && m_tunnels.size() == 1; if (wg) { logger.debug() << "deactivateTunnel" << wg->interfaceName(); - if (wasPrimary) { - for (const IPAddress& ip : config.m_allowedIPAddressRanges) { - wg->deleteRoutePrefix(ip); + if (isLastTunnel) { + for (const IPAddress& prefix : m_excludedAddrSet.keys()) { + wg->deleteExclusionRoute(prefix); } + m_excludedAddrSet.clear(); } wg->deletePeer(config); - - auto removeExclusion = [&](const QString& addr) { - if (addr.isEmpty()) { - return; - } - IPAddress ip(addr); - if (m_excludedAddrSet.contains(ip)) { - delExclusionRoute(ip); - } - }; - removeExclusion(config.m_serverIpv4AddrIn); - removeExclusion(config.m_serverIpv6AddrIn); - if (wasPrimary) { - for (const QString& i : config.m_excludedAddresses) { - removeExclusion(i); - } - } - wg->deleteInterface(); m_tunnels.remove(ifname); delete wg; @@ -234,30 +169,6 @@ bool Daemon::deactivateTunnel(const QString& ifname) { return true; } -bool Daemon::maybeUpdateResolvers(const InterfaceConfig& config) { - if ((config.m_hopType == InterfaceConfig::MultiHopExit) || - (config.m_hopType == InterfaceConfig::SingleHop)) { - QList resolvers; - resolvers.append(QHostAddress(config.m_primaryDnsServer)); - if (!config.m_secondaryDnsServer.isEmpty()) { - resolvers.append(QHostAddress(config.m_secondaryDnsServer)); - } - - // If the DNS is not the Gateway, it's a user defined DNS - // thus, not add any other :) - if (config.m_primaryDnsServer == config.m_serverIpv4Gateway) { - resolvers.append(QHostAddress(config.m_serverIpv6Gateway)); - } - - const QString ifname = wgutilsFor(config.m_ifname)->interfaceName(); - if (!dnsutils()->updateResolvers(ifname, resolvers)) { - return false; - } - } - - return true; -} - // static bool Daemon::parseStringList(const QJsonObject& obj, const QString& name, QStringList& list) { @@ -279,20 +190,25 @@ bool Daemon::parseStringList(const QJsonObject& obj, const QString& name, return true; } -bool Daemon::addExclusionRoute(const IPAddress& prefix) { +bool Daemon::addExclusionRoute(const QString &addr) { + IPAddress prefix(addr); if (m_excludedAddrSet.contains(prefix)) { m_excludedAddrSet[prefix]++; return true; } - if (!primaryWgutils()->addExclusionRoute(prefix)) { + WireguardUtils* wg = primaryWgutils(); + if (!wg || !wg->addExclusionRoute(prefix)) { return false; } m_excludedAddrSet[prefix] = 1; return true; } -bool Daemon::delExclusionRoute(const IPAddress& prefix) { - Q_ASSERT(m_excludedAddrSet.contains(prefix)); +bool Daemon::delExclusionRoute(const QString &addr) { + IPAddress prefix(addr); + if (!m_excludedAddrSet.contains(prefix)) { + return false; + } if (m_excludedAddrSet[prefix] > 1) { m_excludedAddrSet[prefix]--; return true; @@ -302,6 +218,32 @@ bool Daemon::delExclusionRoute(const IPAddress& prefix) { return wg && wg->deleteExclusionRoute(prefix); } +bool Daemon::addAllowedIp(const QString &ifname, const QString &prefix) { + WireguardUtils* wg = wgutilsFor(ifname); + return wg && wg->updateRoutePrefix(IPAddress(prefix)); +} + +bool Daemon::delAllowedIp(const QString &ifname, const QString &prefix) { + WireguardUtils* wg = wgutilsFor(ifname); + return wg && wg->deleteRoutePrefix(IPAddress(prefix)); +} + +bool Daemon::setTunnelResolvers(const QString &ifname, const QStringList &resolvers) { + WireguardUtils* wg = wgutilsFor(ifname); + if (!wg || !dnsutils()) { + return false; + } + QList hostAddrs; + for (const QString& r : resolvers) { + hostAddrs.append(QHostAddress(r)); + } + return dnsutils()->updateResolvers(wg->interfaceName(), hostAddrs); +} + +bool Daemon::restoreTunnelResolvers() { + return dnsutils() && dnsutils()->restoreResolvers(); +} + // static bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) { #define GETVALUE(name, where, jsontype) \ @@ -529,16 +471,20 @@ bool Daemon::deactivate(bool emitSignals) { emit disconnected(); } - if (!dnsutils()->restoreResolvers()) { - logger.warning() << "Failed to restore DNS resolvers."; - } - const QStringList ifnames = m_tunnels.keys(); for (const QString& ifname : ifnames) { if (ifname != primary) { deactivateTunnel(ifname); } } + + if (auto* wg = primaryWgutils()) { + for (const IPAddress& prefix : m_excludedAddrSet.keys()) { + wg->deleteExclusionRoute(prefix); + } + } + m_excludedAddrSet.clear(); + if (m_tunnels.contains(primary)) { deactivateTunnel(primary); } diff --git a/client/daemon/daemon.h b/client/daemon/daemon.h index b9d0cf811..fa1311067 100644 --- a/client/daemon/daemon.h +++ b/client/daemon/daemon.h @@ -37,6 +37,13 @@ class Daemon : public QObject { virtual bool deactivate(bool emitSignals = true); virtual QJsonObject getStatus(); + bool addExclusionRoute(const QString &addr); + bool delExclusionRoute(const QString &addr); + bool addAllowedIp(const QString &ifname, const QString &prefix); + bool delAllowedIp(const QString &ifname, const QString &prefix); + bool setTunnelResolvers(const QString &ifname, const QStringList &resolvers); + bool restoreTunnelResolvers(); + const QString& primaryIfname() const { return m_primaryIfname; } WireguardUtils* wgutilsFor(const QString& ifname) const { return m_tunnels.value(ifname); } @@ -56,10 +63,6 @@ class Daemon : public QObject { void backendFailure(DaemonError reason = DaemonError::ERROR_FATAL); private: - bool maybeUpdateResolvers(const InterfaceConfig& config); - bool addExclusionRoute(const IPAddress& address); - bool delExclusionRoute(const IPAddress& address); - void demotePrimary(const QString& ifname); void checkActivations(); WireguardUtils* primaryWgutils() const { return m_tunnels.value(m_primaryIfname); } QTimer m_activationTimer; diff --git a/client/mozilla/localsocketcontroller.cpp b/client/mozilla/localsocketcontroller.cpp index 514252247..cd2139861 100644 --- a/client/mozilla/localsocketcontroller.cpp +++ b/client/mozilla/localsocketcontroller.cpp @@ -122,7 +122,8 @@ void LocalSocketController::daemonConnected() { checkStatus(); } -QJsonObject LocalSocketController::buildActivateJson(const QJsonObject& rawConfig) { +QJsonObject LocalSocketController::buildActivateJson(const QJsonObject& rawConfig, + const QString& ifname) { QString protocolName = rawConfig.value("protocol").toString(); int splitTunnelType = rawConfig.value("splitTunnelType").toInt(); @@ -138,7 +139,6 @@ QJsonObject LocalSocketController::buildActivateJson(const QJsonObject& rawConfi // json.insert("hopindex", QJsonValue((double)hop.m_hopindex)); json.insert("privateKey", wgConfig.value(amnezia::configKey::clientPrivKey)); json.insert("deviceIpv4Address", wgConfig.value(amnezia::configKey::clientIp)); - m_deviceIpv4 = wgConfig.value(amnezia::configKey::clientIp).toString(); // set up IPv6 unique-local-address, ULA, with "fd00::/8" prefix, not globally routable. // this will be default IPv6 gateway, OS recognizes that IPv6 link is local and switches to IPv4. @@ -230,7 +230,6 @@ QJsonObject LocalSocketController::buildActivateJson(const QJsonObject& rawConfi json.insert("allowedIPAddressRanges", jsAllowedIPAddesses); QJsonArray jsExcludedAddresses; - jsExcludedAddresses.append(wgConfig.value(amnezia::configKey::hostName)); if (splitTunnelType == 2) { for (auto v : splitTunnelSites) { QString ipRange = v.toString(); @@ -292,18 +291,22 @@ QJsonObject LocalSocketController::buildActivateJson(const QJsonObject& rawConfi json.insert(amnezia::configKey::specialJunk5, wgConfig.value(amnezia::configKey::specialJunk5)); } - json.insert("ifname", m_ifname); + json.insert("ifname", ifname); return json; } void LocalSocketController::activate(const QJsonObject& rawConfig) { - QJsonObject json = buildActivateJson(rawConfig); + const QString protocolName = rawConfig.value("protocol").toString(); + const QJsonObject wgConfig = rawConfig.value(protocolName + "_config_data").toObject(); + m_deviceIpv4 = wgConfig.value(amnezia::configKey::clientIp).toString(); + + QJsonObject json = buildActivateJson(rawConfig, m_ifname); json.insert("type", "activate"); write(json); } void LocalSocketController::setPrimary(const QJsonObject& rawConfig) { - QJsonObject json = buildActivateJson(rawConfig); + QJsonObject json = buildActivateJson(rawConfig, m_ifname); json.insert("type", "setPrimary"); write(json); } diff --git a/client/mozilla/localsocketcontroller.h b/client/mozilla/localsocketcontroller.h index 672565dad..16bcb3a71 100644 --- a/client/mozilla/localsocketcontroller.h +++ b/client/mozilla/localsocketcontroller.h @@ -38,12 +38,14 @@ class LocalSocketController final : public ControllerImpl { bool multihopSupported() override { return true; } + public: + static QJsonObject buildActivateJson(const QJsonObject& rawConfig, + const QString& ifname); + private: void initializeInternal(); void disconnectInternal(); - QJsonObject buildActivateJson(const QJsonObject& rawConfig); - void daemonConnected(); void errorOccurred(QLocalSocket::LocalSocketError socketError); void readData(); diff --git a/client/platforms/linux/daemon/linuxdaemon.cpp b/client/platforms/linux/daemon/linuxdaemon.cpp index 2188cd281..776b5f3e2 100644 --- a/client/platforms/linux/daemon/linuxdaemon.cpp +++ b/client/platforms/linux/daemon/linuxdaemon.cpp @@ -17,7 +17,6 @@ #include "leakdetector.h" #include "logger.h" -#include "killswitch.h" namespace { Logger logger("LinuxDaemon"); @@ -51,8 +50,3 @@ LinuxDaemon* LinuxDaemon::instance() { return s_daemon; } -bool LinuxDaemon::deactivate(bool emitSignals) { - bool result = Daemon::deactivate(emitSignals); - KillSwitch::instance()->disableKillSwitch(); - return result; -} diff --git a/client/platforms/linux/daemon/linuxdaemon.h b/client/platforms/linux/daemon/linuxdaemon.h index bd5349673..8a2bda71b 100644 --- a/client/platforms/linux/daemon/linuxdaemon.h +++ b/client/platforms/linux/daemon/linuxdaemon.h @@ -18,8 +18,6 @@ class LinuxDaemon final : public Daemon { static LinuxDaemon* instance(); - bool deactivate(bool emitSignals = true) override; - protected: DnsUtils* dnsutils() override { return m_dnsutils; } bool supportIPUtils() const override { return true; } diff --git a/client/platforms/linux/daemon/wireguardutilslinux.cpp b/client/platforms/linux/daemon/wireguardutilslinux.cpp index d4af49f56..b6538a23d 100644 --- a/client/platforms/linux/daemon/wireguardutilslinux.cpp +++ b/client/platforms/linux/daemon/wireguardutilslinux.cpp @@ -208,15 +208,6 @@ bool WireguardUtilsLinux::updatePeer(const InterfaceConfig& config) { out << "allowed_ip=" << ip.toString() << "\n"; } - // Exclude the server address, except for multihop exit servers. - if ((config.m_hopType != InterfaceConfig::MultiHopExit) && - (m_rtmonitor != nullptr)) { - if (!config.m_serverIpv4AddrIn.isEmpty()) - m_rtmonitor->addExclusionRoute(IPAddress(config.m_serverIpv4AddrIn)); - if (!config.m_serverIpv6AddrIn.isEmpty()) - m_rtmonitor->addExclusionRoute(IPAddress(config.m_serverIpv6AddrIn)); - } - int err = uapiErrno(uapiCommand(message)); if (err != 0) { logger.error() << "Peer configuration failed:" << strerror(err); @@ -228,15 +219,6 @@ bool WireguardUtilsLinux::deletePeer(const InterfaceConfig& config) { QByteArray publicKey = QByteArray::fromBase64(qPrintable(config.m_serverPublicKey)); - // Clear exclustion routes for this peer. - if ((config.m_hopType != InterfaceConfig::MultiHopExit) && - (m_rtmonitor != nullptr)) { - if (!config.m_serverIpv4AddrIn.isEmpty()) - m_rtmonitor->deleteExclusionRoute(IPAddress(config.m_serverIpv4AddrIn)); - if (!config.m_serverIpv6AddrIn.isEmpty()) - m_rtmonitor->deleteExclusionRoute(IPAddress(config.m_serverIpv6AddrIn)); - } - QString message; QTextStream out(&message); out << "set=1\n"; diff --git a/client/platforms/macos/daemon/dnsutilsmacos.cpp b/client/platforms/macos/daemon/dnsutilsmacos.cpp index 62a7a3a3a..1fda77ad9 100644 --- a/client/platforms/macos/daemon/dnsutilsmacos.cpp +++ b/client/platforms/macos/daemon/dnsutilsmacos.cpp @@ -185,6 +185,9 @@ bool DnsUtilsMacos::restoreResolvers() { } void DnsUtilsMacos::backupService(const QString& uuid) { + if (m_prevServices.contains(uuid)) { + return; + } DnsBackup backup; CFStringRef path = CFStringCreateWithFormat( kCFAllocatorSystemDefault, nullptr, diff --git a/client/platforms/macos/daemon/macosdaemon.cpp b/client/platforms/macos/daemon/macosdaemon.cpp index 17bef9f8e..35ce970be 100644 --- a/client/platforms/macos/daemon/macosdaemon.cpp +++ b/client/platforms/macos/daemon/macosdaemon.cpp @@ -15,7 +15,6 @@ #include #include -#include "killswitch.h" #include "leakdetector.h" #include "logger.h" @@ -51,8 +50,3 @@ MacOSDaemon* MacOSDaemon::instance() { return s_daemon; } -bool MacOSDaemon::deactivate(bool emitSignals) { - bool result = Daemon::deactivate(emitSignals); - KillSwitch::instance()->disableKillSwitch(); - return result; -} diff --git a/client/platforms/macos/daemon/macosdaemon.h b/client/platforms/macos/daemon/macosdaemon.h index 3d7bd1c4a..573e9b67f 100644 --- a/client/platforms/macos/daemon/macosdaemon.h +++ b/client/platforms/macos/daemon/macosdaemon.h @@ -17,8 +17,6 @@ class MacOSDaemon final : public Daemon { static MacOSDaemon* instance(); - bool deactivate(bool emitSignals = true) override; - protected: DnsUtils* dnsutils() override { return m_dnsutils; } bool supportIPUtils() const override { return true; } diff --git a/client/platforms/macos/daemon/macosroutemonitor.cpp b/client/platforms/macos/daemon/macosroutemonitor.cpp index 062f97f37..edf97295f 100644 --- a/client/platforms/macos/daemon/macosroutemonitor.cpp +++ b/client/platforms/macos/daemon/macosroutemonitor.cpp @@ -51,7 +51,6 @@ MacosRouteMonitor::MacosRouteMonitor(const QString& ifname, QObject* parent) MacosRouteMonitor::~MacosRouteMonitor() { MZ_COUNT_DTOR(MacosRouteMonitor); - flushExclusionRoutes(); if (m_rtsock >= 0) { close(m_rtsock); } diff --git a/client/platforms/macos/daemon/wireguardutilsmacos.cpp b/client/platforms/macos/daemon/wireguardutilsmacos.cpp index 2aa6c6438..e13e03985 100644 --- a/client/platforms/macos/daemon/wireguardutilsmacos.cpp +++ b/client/platforms/macos/daemon/wireguardutilsmacos.cpp @@ -204,13 +204,6 @@ bool WireguardUtilsMacos::updatePeer(const InterfaceConfig& config) { out << "allowed_ip=" << ip.toString() << "\n"; } - // 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)); - m_rtmonitor->addExclusionRoute(IPAddress(config.m_serverIpv6AddrIn)); - } - int err = uapiErrno(uapiCommand(message)); if (err != 0) { logger.error() << "Peer configuration failed:" << strerror(err); @@ -222,13 +215,6 @@ bool WireguardUtilsMacos::deletePeer(const InterfaceConfig& config) { QByteArray publicKey = QByteArray::fromBase64(qPrintable(config.m_serverPublicKey)); - // Clear exclustion routes for this peer. - if ((config.m_hopType != InterfaceConfig::MultiHopExit) && - (m_rtmonitor != nullptr)) { - m_rtmonitor->deleteExclusionRoute(IPAddress(config.m_serverIpv4AddrIn)); - m_rtmonitor->deleteExclusionRoute(IPAddress(config.m_serverIpv6AddrIn)); - } - QString message; QTextStream out(&message); out << "set=1\n"; diff --git a/client/platforms/windows/daemon/wireguardutilswindows.cpp b/client/platforms/windows/daemon/wireguardutilswindows.cpp index c307fae41..36f5d2d15 100644 --- a/client/platforms/windows/daemon/wireguardutilswindows.cpp +++ b/client/platforms/windows/daemon/wireguardutilswindows.cpp @@ -185,12 +185,6 @@ bool WireguardUtilsWindows::updatePeer(const InterfaceConfig& config) { out << "allowed_ip=" << ip.toString() << "\n"; } - // Exclude the server address, except for multihop exit servers. - if (m_routeMonitor && config.m_hopType != InterfaceConfig::MultiHopExit) { - m_routeMonitor->addExclusionRoute(IPAddress(config.m_serverIpv4AddrIn)); - m_routeMonitor->addExclusionRoute(IPAddress(config.m_serverIpv6AddrIn)); - } - QString reply = m_tunnel.uapiCommand(message); logger.debug() << "DATA:" << reply; return true; @@ -200,12 +194,6 @@ bool WireguardUtilsWindows::deletePeer(const InterfaceConfig& config) { QByteArray publicKey = QByteArray::fromBase64(qPrintable(config.m_serverPublicKey)); - // Clear exclustion routes for this peer. - if (m_routeMonitor && config.m_hopType != InterfaceConfig::MultiHopExit) { - m_routeMonitor->deleteExclusionRoute(IPAddress(config.m_serverIpv4AddrIn)); - m_routeMonitor->deleteExclusionRoute(IPAddress(config.m_serverIpv6AddrIn)); - } - // Disable the windows firewall for this peer. m_firewall->disablePeerTraffic(config.m_serverPublicKey); diff --git a/client/vpnConnection.cpp b/client/vpnConnection.cpp index 4f6b93f8c..14433900d 100644 --- a/client/vpnConnection.cpp +++ b/client/vpnConnection.cpp @@ -146,9 +146,6 @@ void VpnConnection::wireTunnelSignals(Tunnel* tunnel, bool isActive) if (isActive) { connect(tunnel, &Tunnel::bytesChanged, this, &VpnConnection::onBytesChanged); - // Staging tunnel deliberately skips this wire: applying KS while the old - // primary is still serving would clobber its allow-rules. onTunnelActivated - // invokes applyFirewall manually after the make-before-break swap. connect(tunnel, &Tunnel::addressesUpdated, m_trafficGuard.data(), &VpnTrafficGuard::applyFirewall); } @@ -197,14 +194,14 @@ void VpnConnection::connectToVpn(const QString &serverId, DockerContainer contai #ifdef AMNEZIA_DESKTOP if (m_active) { const QString oldIfname = m_active->ifname(); - m_active->deactivate(); + m_trafficGuard->tearDown(m_active); delete m_active; m_active = nullptr; releaseIfname(oldIfname); } if (m_vpnProtocol) { disconnect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError); - m_trafficGuard->teardown(); + m_trafficGuard->flushAll(); m_vpnProtocol->stop(); m_vpnProtocol.reset(); } @@ -223,7 +220,7 @@ void VpnConnection::connectToVpn(const QString &serverId, DockerContainer contai wireTunnelSignals(m_active, /*isActive=*/true); wireDaemonReconnectSignals(); m_trafficGuard->setConfig(config); - m_active->prepare(); + m_trafficGuard->bringUp(m_active); return; } @@ -476,8 +473,7 @@ void VpnConnection::disconnectFromVpn() #ifdef AMNEZIA_DESKTOP if (m_staging) { - m_trafficGuard->revokeEndpoint(m_staging->remoteAddress()); - m_staging->deactivate(); + m_trafficGuard->tearDown(m_staging); releaseIfname(m_staging->ifname()); delete m_staging; m_staging = nullptr; @@ -485,9 +481,8 @@ void VpnConnection::disconnectFromVpn() if (m_active) { setConnectionState(Vpn::ConnectionState::Disconnecting); - m_trafficGuard->teardown(); - m_trafficGuard->revokeEndpoint(m_remoteAddress); - m_active->deactivate(); + m_trafficGuard->tearDown(m_active); + m_trafficGuard->flushAll(); releaseIfname(m_active->ifname()); delete m_active; m_active = nullptr; @@ -515,7 +510,7 @@ void VpnConnection::disconnectFromVpn() }); #endif #ifdef AMNEZIA_DESKTOP - m_trafficGuard->teardown(); + m_trafficGuard->flushAll(); #endif m_vpnProtocol->stop(); @@ -549,31 +544,19 @@ void VpnConnection::startTunnelSwitch(DockerContainer container, wireTunnelSignals(m_staging, /*isActive=*/false); setConnectionState(Vpn::ConnectionState::Switching); - m_staging->prepare(); + m_trafficGuard->bringUp(m_staging); } void VpnConnection::onTunnelPrepared() { Tunnel* tunnel = qobject_cast(sender()); if (!tunnel) return; - tunnel->commit(); -} -void VpnConnection::onTunnelActivated() -{ - Tunnel* tunnel = qobject_cast(sender()); - if (!tunnel) return; - - if (tunnel == m_staging) { - // Make-before-break gate passed: new tunnel is primary, old still allowed by KS. - if (m_active) { - const QString oldRemote = m_active->remoteAddress(); - const QString oldIfname = m_active->ifname(); - m_active->deactivate(); - delete m_active; - releaseIfname(oldIfname); - m_trafficGuard->revokeEndpoint(oldRemote); - } + if (tunnel == m_staging && m_active) { + const QString oldIfname = m_active->ifname(); + m_trafficGuard->swap(m_active, m_staging); + delete m_active; + releaseIfname(oldIfname); m_active = m_staging; m_staging = nullptr; @@ -583,17 +566,23 @@ void VpnConnection::onTunnelActivated() m_vpnConfiguration = m_active->config(); m_remoteAddress = m_active->remoteAddress(); m_trafficGuard->setConfig(m_vpnConfiguration); + return; + } + m_trafficGuard->commit(tunnel); +} + +void VpnConnection::onTunnelActivated() +{ + Tunnel* tunnel = qobject_cast(sender()); + if (!tunnel) return; + + if (tunnel == m_active) { + setConnectionState(Vpn::ConnectionState::Connected); if (auto proto = m_active->protocol()) { m_trafficGuard->applyFirewall(proto->vpnGateway(), proto->vpnLocalAddress()); } - setConnectionState(Vpn::ConnectionState::Connected); - return; - } - - if (tunnel == m_active) { - setConnectionState(Vpn::ConnectionState::Connected); } } @@ -603,7 +592,7 @@ void VpnConnection::onTunnelFailed(amnezia::ErrorCode error) if (!tunnel) return; if (tunnel == m_staging) { - m_trafficGuard->revokeEndpoint(m_staging->remoteAddress()); + m_trafficGuard->release(m_staging); m_staging->deactivate(); releaseIfname(m_staging->ifname()); m_staging->deleteLater(); @@ -614,9 +603,8 @@ void VpnConnection::onTunnelFailed(amnezia::ErrorCode error) } if (tunnel == m_active) { - m_trafficGuard->teardown(); - m_trafficGuard->revokeEndpoint(m_remoteAddress); - m_active->deactivate(); + m_trafficGuard->tearDown(m_active); + m_trafficGuard->flushAll(); releaseIfname(m_active->ifname()); m_active->deleteLater(); m_active = nullptr; diff --git a/ipc/ipc_interface.rep b/ipc/ipc_interface.rep index f26bd8b35..306bb5320 100644 --- a/ipc/ipc_interface.rep +++ b/ipc/ipc_interface.rep @@ -12,6 +12,12 @@ class IpcInterface SLOT( int routeAddList(const QString &gw, const QStringList &ips) ); SLOT( bool clearSavedRoutes() ); SLOT( bool routeDeleteList(const QString &gw, const QStringList &ip) ); + SLOT( bool addExclusionRoute(const QString &addr) ); + SLOT( bool delExclusionRoute(const QString &addr) ); + SLOT( bool addAllowedIp(const QString &ifname, const QString &prefix) ); + SLOT( bool delAllowedIp(const QString &ifname, const QString &prefix) ); + SLOT( bool setTunnelResolvers(const QString &ifname, const QStringList &resolvers) ); + SLOT( bool restoreTunnelResolvers() ); SLOT( bool flushDns() ); SLOT( void resetIpStack() ); diff --git a/ipc/ipcserver.cpp b/ipc/ipcserver.cpp index 4d02c1dd1..e28261cc7 100644 --- a/ipc/ipcserver.cpp +++ b/ipc/ipcserver.cpp @@ -18,6 +18,8 @@ #include "killswitch.h" #include "xray.h" +#include "../client/daemon/daemon.h" + #ifdef Q_OS_WIN #include "tapcontroller_win.h" #endif @@ -91,6 +93,36 @@ bool IpcServer::routeDeleteList(const QString &gw, const QStringList &ips) return Router::routeDeleteList(gw, ips); } +bool IpcServer::addExclusionRoute(const QString &addr) +{ + return Daemon::instance() && Daemon::instance()->addExclusionRoute(addr); +} + +bool IpcServer::delExclusionRoute(const QString &addr) +{ + return Daemon::instance() && Daemon::instance()->delExclusionRoute(addr); +} + +bool IpcServer::addAllowedIp(const QString &ifname, const QString &prefix) +{ + return Daemon::instance() && Daemon::instance()->addAllowedIp(ifname, prefix); +} + +bool IpcServer::delAllowedIp(const QString &ifname, const QString &prefix) +{ + return Daemon::instance() && Daemon::instance()->delAllowedIp(ifname, prefix); +} + +bool IpcServer::setTunnelResolvers(const QString &ifname, const QStringList &resolvers) +{ + return Daemon::instance() && Daemon::instance()->setTunnelResolvers(ifname, resolvers); +} + +bool IpcServer::restoreTunnelResolvers() +{ + return Daemon::instance() && Daemon::instance()->restoreTunnelResolvers(); +} + bool IpcServer::flushDns() { #ifdef MZ_DEBUG diff --git a/ipc/ipcserver.h b/ipc/ipcserver.h index e8607c5ad..8530e37cf 100644 --- a/ipc/ipcserver.h +++ b/ipc/ipcserver.h @@ -17,11 +17,18 @@ class IpcServer : public IpcInterfaceSource { public: explicit IpcServer(QObject *parent = nullptr); + virtual int createPrivilegedProcess() override; virtual int routeAddList(const QString &gw, const QStringList &ips) override; virtual bool clearSavedRoutes() override; virtual bool routeDeleteList(const QString &gw, const QStringList &ips) override; + virtual bool addExclusionRoute(const QString &addr) override; + virtual bool delExclusionRoute(const QString &addr) override; + virtual bool addAllowedIp(const QString &ifname, const QString &prefix) override; + virtual bool delAllowedIp(const QString &ifname, const QString &prefix) override; + virtual bool setTunnelResolvers(const QString &ifname, const QStringList &resolvers) override; + virtual bool restoreTunnelResolvers() override; virtual bool flushDns() override; virtual void resetIpStack() override; virtual bool checkAndInstallDriver() override; diff --git a/service/server/localserver.cpp b/service/server/localserver.cpp index 26706079a..774754aa5 100644 --- a/service/server/localserver.cpp +++ b/service/server/localserver.cpp @@ -56,14 +56,20 @@ LocalServer::LocalServer(QObject *parent) : QObject(parent), #ifdef Q_OS_LINUX // Signal handling for a proper shutdown. - QObject::connect(qApp, &QCoreApplication::aboutToQuit, - []() { LinuxDaemon::instance()->deactivate(); }); + QObject::connect(qApp, &QCoreApplication::aboutToQuit, [this]() { + LinuxDaemon::instance()->deactivate(); + m_ipcServer.restoreTunnelResolvers(); + m_ipcServer.disableKillSwitch(); + }); #endif #ifdef Q_OS_MAC // Signal handling for a proper shutdown. - QObject::connect(qApp, &QCoreApplication::aboutToQuit, - []() { MacOSDaemon::instance()->deactivate(); }); + QObject::connect(qApp, &QCoreApplication::aboutToQuit, [this]() { + MacOSDaemon::instance()->deactivate(); + m_ipcServer.restoreTunnelResolvers(); + m_ipcServer.disableKillSwitch(); + }); #endif #ifdef Q_OS_WIN