feat: route Xray through Tunnel for seamless server switch

This commit is contained in:
cd-amn
2026-06-08 11:15:12 +00:00
parent 7aebbe0f74
commit 5a886f3d90
4 changed files with 36 additions and 12 deletions
+6
View File
@@ -113,6 +113,12 @@ bool VpnProtocol::isWireGuardBased(amnezia::DockerContainer container)
|| container == amnezia::DockerContainer::WireGuard; || 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) VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject &configuration)
{ {
switch (container) { switch (container) {
+1
View File
@@ -74,6 +74,7 @@ public:
static VpnProtocol* factory(amnezia::DockerContainer container, const QJsonObject &configuration); static VpnProtocol* factory(amnezia::DockerContainer container, const QJsonObject &configuration);
static bool isWireGuardBased(amnezia::DockerContainer container); static bool isWireGuardBased(amnezia::DockerContainer container);
static bool isXrayBased(amnezia::DockerContainer container);
signals: signals:
void bytesChanged(quint64 receivedBytes, quint64 sentBytes); void bytesChanged(quint64 receivedBytes, quint64 sentBytes);
+11 -3
View File
@@ -28,11 +28,17 @@ XrayProtocol::XrayProtocol(const QJsonObject &configuration, QObject *parent) :
m_routeMode = static_cast<amnezia::RouteMode>(configuration.value(amnezia::configKey::splitTunnelType).toInt()); m_routeMode = static_cast<amnezia::RouteMode>(configuration.value(amnezia::configKey::splitTunnelType).toInt());
m_remoteAddress = NetworkUtilities::getIPAddress(m_rawConfig.value(amnezia::configKey::hostName).toString()); 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 #ifdef Q_OS_MACOS
m_tunName = configuration.value("tunName").toString("utun22"); m_tunName = QStringLiteral("utun22");
#else #else
m_tunName = configuration.value("tunName").toString("tun2"); m_tunName = QStringLiteral("tun2");
#endif #endif
}
const QString primaryDns = configuration.value(amnezia::configKey::dns1).toString(); const QString primaryDns = configuration.value(amnezia::configKey::dns1).toString();
m_dnsServers.push_back(QHostAddress(primaryDns)); m_dnsServers.push_back(QHostAddress(primaryDns));
if (primaryDns != amnezia::protocols::dns::amneziaDnsIp) { if (primaryDns != amnezia::protocols::dns::amneziaDnsIp) {
@@ -167,7 +173,9 @@ void XrayProtocol::stop()
void XrayProtocol::setPrimary(const QJsonObject &config) void XrayProtocol::setPrimary(const QJsonObject &config)
{ {
Q_UNUSED(config) Q_UNUSED(config)
emit primaryReady(); QMetaObject::invokeMethod(this, [this]() {
emit primaryReady();
}, Qt::QueuedConnection);
} }
ErrorCode XrayProtocol::startTun2Socks() ErrorCode XrayProtocol::startTun2Socks()
+18 -9
View File
@@ -167,12 +167,13 @@ void VpnConnection::connectToVpn(const QString &serverId, DockerContainer contai
#ifdef AMNEZIA_DESKTOP #ifdef AMNEZIA_DESKTOP
const bool isWg = VpnProtocol::isWireGuardBased(container); 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 if (m_active
&& m_connectionState == Vpn::ConnectionState::Connected && m_connectionState == Vpn::ConnectionState::Connected
&& isWg) { && useTunnelPath) {
if (!m_trafficGuard->allowEndpoint(resolvedRemote, preAllocatedIfname)) { if (!m_trafficGuard->allowEndpoint(resolvedRemote, preAllocatedIfname)) {
releaseIfname(preAllocatedIfname); releaseIfname(preAllocatedIfname);
setConnectionState(Vpn::ConnectionState::Error); setConnectionState(Vpn::ConnectionState::Error);
@@ -184,7 +185,7 @@ void VpnConnection::connectToVpn(const QString &serverId, DockerContainer contai
} }
if (!m_trafficGuard->allowEndpoint(resolvedRemote, preAllocatedIfname)) { if (!m_trafficGuard->allowEndpoint(resolvedRemote, preAllocatedIfname)) {
if (isWg) releaseIfname(preAllocatedIfname); if (useTunnelPath) releaseIfname(preAllocatedIfname);
setConnectionState(Vpn::ConnectionState::Error); setConnectionState(Vpn::ConnectionState::Error);
emit vpnProtocolError(ErrorCode::AmneziaServiceConnectionFailed); emit vpnProtocolError(ErrorCode::AmneziaServiceConnectionFailed);
return; return;
@@ -217,8 +218,11 @@ void VpnConnection::connectToVpn(const QString &serverId, DockerContainer contai
m_remoteAddress = resolvedRemote; m_remoteAddress = resolvedRemote;
#ifdef AMNEZIA_DESKTOP #ifdef AMNEZIA_DESKTOP
if (isWg) { if (useTunnelPath) {
config.insert("ifname", preAllocatedIfname); config.insert("ifname", preAllocatedIfname);
if (isXray) {
config.insert("tunName", preAllocatedIfname);
}
m_active = new Tunnel(preAllocatedIfname, container, config, resolvedRemote, this); m_active = new Tunnel(preAllocatedIfname, container, config, resolvedRemote, this);
wireTunnelSignals(m_active, /*isActive=*/true); wireTunnelSignals(m_active, /*isActive=*/true);
wireDaemonReconnectSignals(); wireDaemonReconnectSignals();
@@ -544,6 +548,9 @@ void VpnConnection::startTunnelSwitch(DockerContainer container,
{ {
QJsonObject config = vpnConfiguration; QJsonObject config = vpnConfiguration;
config.insert("ifname", stagingIfname); config.insert("ifname", stagingIfname);
if (VpnProtocol::isXrayBased(container)) {
config.insert("tunName", stagingIfname);
}
appendKillSwitchConfig(config); appendKillSwitchConfig(config);
appendSplitTunnelingConfig(config); appendSplitTunnelingConfig(config);
@@ -560,10 +567,8 @@ void VpnConnection::onTunnelPrepared()
if (!tunnel) return; if (!tunnel) return;
if (tunnel == m_staging && m_active) { if (tunnel == m_staging && m_active) {
const QString oldIfname = m_active->ifname(); Tunnel* oldTunnel = m_active;
m_trafficGuard->swap(m_active, m_staging); const QString oldIfname = oldTunnel->ifname();
delete m_active;
releaseIfname(oldIfname);
m_active = m_staging; m_active = m_staging;
m_staging = nullptr; m_staging = nullptr;
@@ -571,6 +576,10 @@ void VpnConnection::onTunnelPrepared()
m_vpnConfiguration = m_active->config(); m_vpnConfiguration = m_active->config();
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);
delete oldTunnel;
releaseIfname(oldIfname);
return; return;
} }