From bff3e228fced37c9e81d5894ff435d8c3a3a6d45 Mon Sep 17 00:00:00 2001 From: cd-amn Date: Mon, 8 Jun 2026 11:15:12 +0000 Subject: [PATCH] feat: route Xray through Tunnel for seamless server switch --- client/core/protocols/vpnProtocol.cpp | 6 ++++++ client/core/protocols/vpnProtocol.h | 1 + client/core/protocols/xrayProtocol.cpp | 14 ++++++++++--- client/vpnConnection.cpp | 27 +++++++++++++++++--------- 4 files changed, 36 insertions(+), 12 deletions(-) diff --git a/client/core/protocols/vpnProtocol.cpp b/client/core/protocols/vpnProtocol.cpp index 10cf9ba25..c1eb3b99b 100644 --- a/client/core/protocols/vpnProtocol.cpp +++ b/client/core/protocols/vpnProtocol.cpp @@ -113,6 +113,12 @@ bool VpnProtocol::isWireGuardBased(amnezia::DockerContainer container) || container == amnezia::DockerContainer::WireGuard; } +bool VpnProtocol::isXrayBased(amnezia::DockerContainer container) +{ + return container == amnezia::DockerContainer::Xray + || container == amnezia::DockerContainer::SSXray; +} + VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject &configuration) { switch (container) { diff --git a/client/core/protocols/vpnProtocol.h b/client/core/protocols/vpnProtocol.h index 83868d563..c053a5532 100644 --- a/client/core/protocols/vpnProtocol.h +++ b/client/core/protocols/vpnProtocol.h @@ -74,6 +74,7 @@ public: static VpnProtocol* factory(amnezia::DockerContainer container, const QJsonObject &configuration); static bool isWireGuardBased(amnezia::DockerContainer container); + static bool isXrayBased(amnezia::DockerContainer container); signals: void bytesChanged(quint64 receivedBytes, quint64 sentBytes); diff --git a/client/core/protocols/xrayProtocol.cpp b/client/core/protocols/xrayProtocol.cpp index 4009bc5ed..835b2f5fd 100644 --- a/client/core/protocols/xrayProtocol.cpp +++ b/client/core/protocols/xrayProtocol.cpp @@ -28,11 +28,17 @@ XrayProtocol::XrayProtocol(const QJsonObject &configuration, QObject *parent) : m_routeMode = static_cast(configuration.value(amnezia::configKey::splitTunnelType).toInt()); m_remoteAddress = NetworkUtilities::getIPAddress(m_rawConfig.value(amnezia::configKey::hostName).toString()); + m_tunName = configuration.value("tunName").toString(); + if (m_tunName.isEmpty()) { + m_tunName = configuration.value("ifname").toString(); + } + if (m_tunName.isEmpty()) { #ifdef Q_OS_MACOS - m_tunName = configuration.value("tunName").toString("utun22"); + m_tunName = QStringLiteral("utun22"); #else - m_tunName = configuration.value("tunName").toString("tun2"); + m_tunName = QStringLiteral("tun2"); #endif + } const QString primaryDns = configuration.value(amnezia::configKey::dns1).toString(); m_dnsServers.push_back(QHostAddress(primaryDns)); if (primaryDns != amnezia::protocols::dns::amneziaDnsIp) { @@ -167,7 +173,9 @@ void XrayProtocol::stop() void XrayProtocol::setPrimary(const QJsonObject &config) { Q_UNUSED(config) - emit primaryReady(); + QMetaObject::invokeMethod(this, [this]() { + emit primaryReady(); + }, Qt::QueuedConnection); } ErrorCode XrayProtocol::startTun2Socks() diff --git a/client/vpnConnection.cpp b/client/vpnConnection.cpp index 589c99b44..f8395250a 100644 --- a/client/vpnConnection.cpp +++ b/client/vpnConnection.cpp @@ -167,12 +167,13 @@ void VpnConnection::connectToVpn(const QString &serverId, DockerContainer contai #ifdef AMNEZIA_DESKTOP const bool isWg = VpnProtocol::isWireGuardBased(container); - const QString preAllocatedIfname = isWg ? allocateIfname() : QString(); + const bool isXray = VpnProtocol::isXrayBased(container); + const bool useTunnelPath = isWg || isXray; + const QString preAllocatedIfname = useTunnelPath ? allocateIfname() : QString(); - // Seamless WG -> WG switch path: already connected via Tunnel, new container is also WG. if (m_active && m_connectionState == Vpn::ConnectionState::Connected - && isWg) { + && useTunnelPath) { if (!m_trafficGuard->allowEndpoint(resolvedRemote, preAllocatedIfname)) { releaseIfname(preAllocatedIfname); setConnectionState(Vpn::ConnectionState::Error); @@ -184,7 +185,7 @@ void VpnConnection::connectToVpn(const QString &serverId, DockerContainer contai } if (!m_trafficGuard->allowEndpoint(resolvedRemote, preAllocatedIfname)) { - if (isWg) releaseIfname(preAllocatedIfname); + if (useTunnelPath) releaseIfname(preAllocatedIfname); setConnectionState(Vpn::ConnectionState::Error); emit vpnProtocolError(ErrorCode::AmneziaServiceConnectionFailed); return; @@ -217,8 +218,11 @@ void VpnConnection::connectToVpn(const QString &serverId, DockerContainer contai m_remoteAddress = resolvedRemote; #ifdef AMNEZIA_DESKTOP - if (isWg) { + if (useTunnelPath) { config.insert("ifname", preAllocatedIfname); + if (isXray) { + config.insert("tunName", preAllocatedIfname); + } m_active = new Tunnel(preAllocatedIfname, container, config, resolvedRemote, this); wireTunnelSignals(m_active, /*isActive=*/true); wireDaemonReconnectSignals(); @@ -544,6 +548,9 @@ void VpnConnection::startTunnelSwitch(DockerContainer container, { QJsonObject config = vpnConfiguration; config.insert("ifname", stagingIfname); + if (VpnProtocol::isXrayBased(container)) { + config.insert("tunName", stagingIfname); + } appendKillSwitchConfig(config); appendSplitTunnelingConfig(config); @@ -560,10 +567,8 @@ void VpnConnection::onTunnelPrepared() if (!tunnel) return; if (tunnel == m_staging && m_active) { - const QString oldIfname = m_active->ifname(); - m_trafficGuard->swap(m_active, m_staging); - delete m_active; - releaseIfname(oldIfname); + Tunnel* oldTunnel = m_active; + const QString oldIfname = oldTunnel->ifname(); m_active = m_staging; m_staging = nullptr; @@ -571,6 +576,10 @@ void VpnConnection::onTunnelPrepared() m_vpnConfiguration = m_active->config(); m_remoteAddress = m_active->remoteAddress(); m_trafficGuard->setConfig(m_vpnConfiguration); + + m_trafficGuard->swap(oldTunnel, m_active); + delete oldTunnel; + releaseIfname(oldIfname); return; }