From 32374ad4cc5d8976f0c6e1b21f9b8d1f621c7abc Mon Sep 17 00:00:00 2001 From: Yaroslav Gurov Date: Tue, 3 Feb 2026 02:16:12 +0100 Subject: [PATCH] chore: rework xray routing * get rid of redundant delays * check if remote calls are successful --- client/protocols/xrayprotocol.cpp | 106 ++++++++++++++++++------------ client/protocols/xrayprotocol.h | 3 +- ipc/ipctun2socksprocess.cpp | 4 +- service/server/CMakeLists.txt | 4 +- service/server/router_win.cpp | 64 ++++++++++++++---- 5 files changed, 121 insertions(+), 60 deletions(-) diff --git a/client/protocols/xrayprotocol.cpp b/client/protocols/xrayprotocol.cpp index c70ac0b47..c25b14980 100755 --- a/client/protocols/xrayprotocol.cpp +++ b/client/protocols/xrayprotocol.cpp @@ -42,6 +42,66 @@ ErrorCode XrayProtocol::start() return startTun2Sock(); } +void XrayProtocol::tun2socksConnectionStateChanged(int vpnState) { + qDebug() << "PrivilegedProcess setConnectionState " << vpnState; + IpcClient::withInterface([&](QSharedPointer iface) { + if (vpnState == Vpn::ConnectionState::Connected) { + setConnectionState(Vpn::ConnectionState::Connecting); + QList dnsAddr; + + dnsAddr.push_back(QHostAddress(m_primaryDNS)); + // We don't use secondary DNS if primary DNS is AmneziaDNS + if (!m_primaryDNS.contains(amnezia::protocols::dns::amneziaDnsIp)) { + dnsAddr.push_back(QHostAddress(m_secondaryDNS)); + } + + QRemoteObjectPendingReply res; + +#ifdef AMNEZIA_DESKTOP +#ifdef Q_OS_MACOS + const QString tunName = "utun22"; +#else + const QString tunName = "tun2"; +#endif + res = iface->createTun(tunName, amnezia::protocols::xray::defaultLocalAddr); + if (!res.waitForFinished(10000)) { + qDebug() << "Failed to assign IP address for TUN"; + } + + res = iface->updateResolvers(tunName, dnsAddr); + if (!res.waitForFinished(1000)) { + qDebug() << "Failed to set DNS resolvers for TUN"; + } +#endif + + if (m_routeMode == Settings::RouteMode::VpnAllSites) { + iface->routeAddList(m_vpnGateway, QStringList() << "1.0.0.0/8" << "2.0.0.0/7" << "4.0.0.0/6" << "8.0.0.0/5" << "16.0.0.0/4" << "32.0.0.0/3" << "64.0.0.0/2" << "128.0.0.0/1"); + } + + res = iface->StopRoutingIpv6(); + if (!res.waitForFinished(1000)) { + qDebug() << "Failed to stop routing IPv6"; + } + +#ifdef Q_OS_WIN + res = iface->enablePeerTraffic(m_xrayConfig); + if (!res.waitForFinished()) { + qDebug() << "Failed to enable peer traffic"; + } +#endif + setConnectionState(Vpn::ConnectionState::Connected); + } +#if !defined(Q_OS_MACOS) + if (vpnState == Vpn::ConnectionState::Disconnected) { + setConnectionState(Vpn::ConnectionState::Disconnected); + iface->deleteTun("tun2"); + iface->StartRoutingIpv6(); + iface->clearSavedRoutes(); + } +#endif + }); +} + ErrorCode XrayProtocol::startTun2Sock() { m_t2sProcess->start(); @@ -50,48 +110,10 @@ ErrorCode XrayProtocol::startTun2Sock() [&](QProcess::ProcessState newState) { qDebug() << "PrivilegedProcess stateChanged" << newState; }); connect(m_t2sProcess.data(), &IpcProcessTun2SocksReplica::setConnectionState, this, [&](int vpnState) { - qDebug() << "PrivilegedProcess setConnectionState " << vpnState; - IpcClient::withInterface([&](QSharedPointer iface) { - if (vpnState == Vpn::ConnectionState::Connected) { - setConnectionState(Vpn::ConnectionState::Connecting); - QList dnsAddr; - - dnsAddr.push_back(QHostAddress(m_primaryDNS)); - // We don't use secondary DNS if primary DNS is AmneziaDNS - if (!m_primaryDNS.contains(amnezia::protocols::dns::amneziaDnsIp)) { - dnsAddr.push_back(QHostAddress(m_secondaryDNS)); - } - - #ifdef Q_OS_WIN - iface->createTun("tun2", amnezia::protocols::xray::defaultLocalAddr); - iface->updateResolvers("tun2", dnsAddr); - #endif - #ifdef Q_OS_MACOS - iface->createTun("utun22", amnezia::protocols::xray::defaultLocalAddr); - iface->updateResolvers("utun22", dnsAddr); - #endif - #ifdef Q_OS_LINUX - iface->createTun("tun2", amnezia::protocols::xray::defaultLocalAddr); - iface->updateResolvers("tun2", dnsAddr); - #endif - if (m_routeMode == Settings::RouteMode::VpnAllSites) { - iface->routeAddList(m_vpnGateway, QStringList() << "0.0.0.0/0"); - } - iface->StopRoutingIpv6(); - #ifdef Q_OS_WIN - iface->enablePeerTraffic(m_xrayConfig); - #endif - setConnectionState(Vpn::ConnectionState::Connected); - } - #if !defined(Q_OS_MACOS) - if (vpnState == Vpn::ConnectionState::Disconnected) { - setConnectionState(Vpn::ConnectionState::Disconnected); - iface->deleteTun("tun2"); - iface->StartRoutingIpv6(); - iface->clearSavedRoutes(); - } -#endif - }); + QMetaObject::invokeMethod(this, [this, vpnState]() { + QThread::msleep(5000); + tun2socksConnectionStateChanged(vpnState); + }, Qt::QueuedConnection); }); return ErrorCode::NoError; diff --git a/client/protocols/xrayprotocol.h b/client/protocols/xrayprotocol.h index 10f81fbc8..4b437571d 100644 --- a/client/protocols/xrayprotocol.h +++ b/client/protocols/xrayprotocol.h @@ -14,10 +14,11 @@ public: virtual ~XrayProtocol() override; ErrorCode start() override; - ErrorCode startTun2Sock(); void stop() override; private: + void tun2socksConnectionStateChanged(int vpnState); + ErrorCode startTun2Sock(); void readXrayConfiguration(const QJsonObject &configuration); QJsonObject m_xrayConfig; diff --git a/ipc/ipctun2socksprocess.cpp b/ipc/ipctun2socksprocess.cpp index 2f9bd7232..e255ebc68 100644 --- a/ipc/ipctun2socksprocess.cpp +++ b/ipc/ipctun2socksprocess.cpp @@ -29,7 +29,7 @@ void IpcProcessTun2Socks::start() QString XrayConStr = "socks5://127.0.0.1:10808"; #ifdef Q_OS_WIN - QStringList arguments({"-device", "tun://tun2?guid={081A8A84-8D12-4DF5-B8C4-396D5B0053E4}", "-proxy", XrayConStr}); + QStringList arguments({"-device", "tun://tun2?guid={081A8A84-8D12-4DF5-B8C4-396D5B0053E4}", "-proxy", XrayConStr }); #endif #ifdef Q_OS_LINUX QStringList arguments({"-device", "tun://tun2", "-proxy", XrayConStr}); @@ -45,8 +45,6 @@ void IpcProcessTun2Socks::start() Utils::killProcessByName(Utils::executable("tun2socks", false)); } - m_t2sProcess->start(); - connect(m_t2sProcess.data(), &QProcess::readyReadStandardOutput, this, [this]() { QString line = m_t2sProcess.data()->readAllStandardOutput(); if (line.contains("[STACK] tun://") && line.contains("<-> socks5://127.0.0.1")) { diff --git a/service/server/CMakeLists.txt b/service/server/CMakeLists.txt index 3330e9c56..7e180ec93 100644 --- a/service/server/CMakeLists.txt +++ b/service/server/CMakeLists.txt @@ -6,7 +6,7 @@ project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) -find_package(Qt6 REQUIRED COMPONENTS DBus Core Network Widgets RemoteObjects Core5Compat) +find_package(Qt6 REQUIRED COMPONENTS DBus Core Network Widgets RemoteObjects Core5Compat Concurrent) qt_standard_project_setup() @@ -353,7 +353,7 @@ include_directories( add_executable(${PROJECT} ${SOURCES} ${HEADERS} ${RESOURCES}) -target_link_libraries(${PROJECT} PRIVATE Qt6::Core Qt6::Widgets Qt6::Network Qt6::RemoteObjects Qt6::Core5Compat Qt6::DBus ${LIBS}) +target_link_libraries(${PROJECT} PRIVATE Qt6::Core Qt6::Widgets Qt6::Network Qt6::RemoteObjects Qt6::Core5Compat Qt6::DBus Qt6::Concurrent ${LIBS}) target_compile_definitions(${PROJECT} PRIVATE "MZ_$") if(CMAKE_BUILD_TYPE STREQUAL "Debug") diff --git a/service/server/router_win.cpp b/service/server/router_win.cpp index 2ea4f61dd..18effcd3e 100644 --- a/service/server/router_win.cpp +++ b/service/server/router_win.cpp @@ -5,6 +5,7 @@ #include #include +#include #include @@ -310,11 +311,37 @@ void RouterWin::resetIpStack() bool RouterWin::createTun(const QString &dev, const QString &subnet) { - const QString command = QString("netsh interface ip set address name=\"tun2\" static %1 255.255.255.255").arg(subnet); + NET_LUID luid; - QProcess p; - p.start(command); - return p.waitForFinished(); + DWORD result = ConvertInterfaceNameToLuidW(reinterpret_cast(dev.utf16()), &luid); + if (result != NO_ERROR) { + qDebug() << "Failed to get device LUID:" << result; + return false; + } + + MIB_UNICASTIPADDRESS_ROW row; + InitializeUnicastIpAddressEntry(&row); + + row.InterfaceLuid = luid; + row.Address.si_family = AF_INET; + + inet_pton(AF_INET, subnet.toStdString().c_str(), + &row.Address.Ipv4.sin_addr); + + row.OnLinkPrefixLength = 32; + row.PrefixOrigin = IpPrefixOriginManual; + row.SuffixOrigin = IpSuffixOriginManual; + row.ValidLifetime = 0xffffffff; + row.PreferredLifetime = 0xffffffff; + row.SkipAsSource = false; + + result = CreateUnicastIpAddressEntry(&row); + if (result != NO_ERROR && result != ERROR_OBJECT_ALREADY_EXISTS) { + qDebug() << "Failed to create IP address:" << result; + return false; + } + + return true; } void RouterWin::suspendWcmSvc(bool suspend) @@ -474,11 +501,19 @@ bool RouterWin::StopRoutingIpv6() qDebug() << "RouterWin::StopRoutingIpv6"; if (auto loopback = findLoopbackIface(); loopback.isValid()) { - for (auto subnet : kIpv6Subnets) { - QProcess{}.execute("netsh", { "interface", "ipv6", "add", "route", subnet, QString("interface=%1").arg(loopback.index()), "metric=0", "store=active" }); - } + QFuture res = QtConcurrent::mappedReduced(kIpv6Subnets, [loopback](const QString &subnet) -> bool { + int res = QProcess::execute("netsh", { "interface", "ipv6", "add", "route", subnet, QString("interface=%1").arg(loopback.index()), "metric=0", "store=active" }); + return res == 0; + }, + [](bool &result, bool success) { + result = result && success; + }, true); + + res.waitForFinished(); + return res.result(); } - return true; + + return false; } bool RouterWin::StartRoutingIpv6() @@ -486,9 +521,14 @@ bool RouterWin::StartRoutingIpv6() qDebug() << "RouterWin::StartRoutingIpv6"; if (auto loopback = findLoopbackIface(); loopback.isValid()) { - for (auto subnet : kIpv6Subnets) { - QProcess{}.execute("netsh", { "interface", "ipv6", "delete", "route", subnet, QString("interface=%1").arg(loopback.index()) }); - } + QFuture res = QtConcurrent::mappedReduced(kIpv6Subnets, [loopback](const QString &subnet) -> bool { + int res = QProcess::execute("netsh", { "interface", "ipv6", "delete", "route", subnet, QString("interface=%1").arg(loopback.index()) }); + return res == 0; + }, + [](bool &result, bool success) { + result = result && success; + }, true); } - return true; + + return false; }