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 d528a241d8
commit bff3e228fc
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;
}
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) {
+1
View File
@@ -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);
+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_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()
+18 -9
View File
@@ -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;
}