diff --git a/client/protocols/xrayprotocol.cpp b/client/protocols/xrayprotocol.cpp index 575960b2d..1982971f4 100755 --- a/client/protocols/xrayprotocol.cpp +++ b/client/protocols/xrayprotocol.cpp @@ -42,6 +42,65 @@ ErrorCode XrayProtocol::start() return startTun2Sock(); } +ErrorCode XrayProtocol::setupRouting() { + return IpcClient::withInterface([this](QSharedPointer iface) -> ErrorCode { + 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 AMNEZIA_DESKTOP + #ifdef Q_OS_MACOS + const QString tunName = "utun22"; + #else + const QString tunName = "tun2"; + #endif + auto createTun = iface->createTun(tunName, amnezia::protocols::xray::defaultLocalAddr); + if (!createTun.waitForFinished(1000) || !createTun.returnValue()) { + qWarning() << "Failed to assign IP address for TUN"; + return ErrorCode::InternalError; + } + + auto updateResolvers = iface->updateResolvers(tunName, dnsAddr); + if (!updateResolvers.waitForFinished(1000) || !updateResolvers.returnValue()) { + qWarning() << "Failed to set DNS resolvers for TUN"; + return ErrorCode::InternalError; + } +#endif + + if (m_routeMode == Settings::RouteMode::VpnAllSites) { + static const QStringList subnets = { "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" }; + + auto routeAddList = iface->routeAddList(m_vpnGateway, subnets); + if (!routeAddList.waitForFinished(1000) || routeAddList.returnValue() != subnets.count()) { + qWarning() << "Failed to set routes for TUN"; + return ErrorCode::InternalError; + } + } + + auto StopRoutingIpv6 = iface->StopRoutingIpv6(); + if (!StopRoutingIpv6.waitForFinished(1000) || !StopRoutingIpv6.returnValue()) { + qWarning() << "Failed to disable IPv6 routing"; + return ErrorCode::InternalError; + } + +#ifdef Q_OS_WIN + auto enablePeerTraffic = iface->enablePeerTraffic(m_xrayConfig); + if (!enablePeerTraffic.waitForFinished(5000) || !enablePeerTraffic.returnValue()) { + qWarning() << "Failed to enable peer traffic"; + return ErrorCode::InternalError; + } +#endif + return ErrorCode::NoError; + }, + [] () { + return ErrorCode::AmneziaServiceConnectionFailed; + }); +} + ErrorCode XrayProtocol::startTun2Sock() { m_t2sProcess->start(); @@ -50,48 +109,23 @@ 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) { + QMetaObject::invokeMethod(this, [this, vpnState]() { + qDebug() << "PrivilegedProcess setConnectionState " << vpnState; + 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 - QThread::msleep(8000); - #endif - #ifdef Q_OS_MACOS - QThread::msleep(5000); - iface->createTun("utun22", amnezia::protocols::xray::defaultLocalAddr); - iface->updateResolvers("utun22", dnsAddr); - #endif - #ifdef Q_OS_LINUX - QThread::msleep(1000); - iface->createTun("tun2", amnezia::protocols::xray::defaultLocalAddr); - iface->updateResolvers("tun2", dnsAddr); - #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"); - } - iface->StopRoutingIpv6(); - #ifdef Q_OS_WIN - iface->updateResolvers("tun2", dnsAddr); - #endif - setConnectionState(Vpn::ConnectionState::Connected); + if (ErrorCode res = setupRouting(); res != ErrorCode::NoError) { + stop(); + setLastError(res); + } else + 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 - }); + + if (vpnState == Vpn::ConnectionState::Disconnected) + stop(); + + }, Qt::QueuedConnection); }); return ErrorCode::NoError; @@ -103,19 +137,19 @@ void XrayProtocol::stop() IpcClient::withInterface([](QSharedPointer iface) { #ifdef AMNEZIA_DESKTOP - QRemoteObjectPendingReply StartRoutingIpv6Resp = iface->StartRoutingIpv6(); - if (!StartRoutingIpv6Resp.waitForFinished(1000)) { + auto StartRoutingIpv6 = iface->StartRoutingIpv6(); + if (!StartRoutingIpv6.waitForFinished(1000) || !StartRoutingIpv6.returnValue()) { qWarning() << "XrayProtocol::stop(): Failed to start routing ipv6"; } - QRemoteObjectPendingReply restoreResolvers = iface->restoreResolvers(); - if (!restoreResolvers.waitForFinished(1000)) { + auto restoreResolvers = iface->restoreResolvers(); + if (!restoreResolvers.waitForFinished(1000) || !restoreResolvers.returnValue()) { qWarning() << "XrayProtocol::stop(): Failed to restore resolvers"; } #if !defined(Q_OS_MACOS) - QRemoteObjectPendingReply deleteTunResp = iface->deleteTun("tun2"); - if (!deleteTunResp.waitForFinished(1000)) { + auto deleteTun = iface->deleteTun("tun2"); + if (!deleteTun.waitForFinished(1000) || !deleteTun.returnValue()) { qWarning() << "XrayProtocol::stop(): Failed to delete tun"; } #endif diff --git a/client/protocols/xrayprotocol.h b/client/protocols/xrayprotocol.h index 10f81fbc8..f59856ab1 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: + ErrorCode setupRouting(); + ErrorCode startTun2Sock(); void readXrayConfiguration(const QJsonObject &configuration); QJsonObject m_xrayConfig; diff --git a/deploy/build_windows.bat b/deploy/build_windows.bat index 5d56dcb59..351da3944 100644 --- a/deploy/build_windows.bat +++ b/deploy/build_windows.bat @@ -31,6 +31,7 @@ set SCRIPT_DIR=%PROJECT_DIR:"=%\deploy set WORK_DIR=%SCRIPT_DIR:"=%\build_%BUILD_ARCH:"=% set APP_NAME=AmneziaVPN set APP_FILENAME=%APP_NAME:"=%.exe +set SERVICE_FILENAME=%APP_NAME:"=%-service.exe set APP_DOMAIN=org.amneziavpn.package set OUT_APP_DIR=%WORK_DIR:"=%\client\release set PREBILT_DEPLOY_DATA_DIR=%PROJECT_DIR:"=%\client\3rd-prebuilt\deploy-prebuilt\windows\x%BUILD_ARCH:"=% @@ -43,6 +44,7 @@ set STAGE_DIR=%WORK_DIR:"=%\stage echo "Environment:" echo "WORK_DIR: %WORK_DIR%" echo "APP_FILENAME: %APP_FILENAME%" +echo "SERVICE_FILENAME: %SERVICE_FILENAME%" echo "PROJECT_DIR: %PROJECT_DIR%" echo "SCRIPT_DIR: %SCRIPT_DIR%" echo "OUT_APP_DIR: %OUT_APP_DIR%" @@ -74,7 +76,7 @@ if %errorlevel% neq 0 exit /b %errorlevel% echo "Deploying..." mkdir "%OUT_APP_DIR%" -copy "%WORK_DIR%\service\server\release\%APP_NAME%-service.exe" "%OUT_APP_DIR%" +copy "%WORK_DIR%\service\server\release\%SERVICE_FILENAME%" "%OUT_APP_DIR%" rem copy "%WORK_DIR%\client\%APP_FILENAME%" "%OUT_APP_DIR%" copy /Y "%PROJECT_DIR%\client\images\app.ico" "%OUT_APP_DIR%\AmneziaVPN.ico" >nul @@ -84,6 +86,7 @@ cd %OUT_APP_DIR% signtool sign /v /n "Privacy Technologies OU" /fd sha256 /tr http://timestamp.comodoca.com/?td=sha256 /td sha256 *.exe "%QT_BIN_DIR:"=%\windeployqt" --release --qmldir "%PROJECT_DIR:"=%\client" --force --no-translations "%OUT_APP_DIR:"=%\%APP_FILENAME:"=%" +"%QT_BIN_DIR:"=%\windeployqt" --release "%OUT_APP_DIR:"=%\%SERVICE_FILENAME:"=%" signtool sign /v /n "Privacy Technologies OU" /fd sha256 /tr http://timestamp.comodoca.com/?td=sha256 /td sha256 *.dll diff --git a/ipc/ipctun2socksprocess.cpp b/ipc/ipctun2socksprocess.cpp index 65d801b03..e255ebc68 100644 --- a/ipc/ipctun2socksprocess.cpp +++ b/ipc/ipctun2socksprocess.cpp @@ -29,9 +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, "-tun-post-up", - QString("cmd /c netsh interface ip set address name=\"tun2\" static %1 255.255.255.255") - .arg(amnezia::protocols::xray::defaultLocalAddr)}); + 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}); @@ -47,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.cpp b/service/server/router.cpp index 24e82c0e5..8849d27b7 100644 --- a/service/server/router.cpp +++ b/service/server/router.cpp @@ -66,6 +66,9 @@ void Router::resetIpStack() bool Router::createTun(const QString &dev, const QString &subnet) { +#ifdef Q_OS_WIN + return RouterWin::Instance().createTun(dev, subnet); +#endif #ifdef Q_OS_LINUX return RouterLinux::Instance().createTun(dev, subnet); #endif diff --git a/service/server/router_win.cpp b/service/server/router_win.cpp index 7ad6505df..5d07cbd04 100644 --- a/service/server/router_win.cpp +++ b/service/server/router_win.cpp @@ -5,6 +5,7 @@ #include #include +#include #include @@ -308,6 +309,37 @@ void RouterWin::resetIpStack() } } +bool RouterWin::createTun(const QString &dev, const QString &subnet) +{ + NET_LUID luid; + DWORD res = ConvertInterfaceAliasToLuid(reinterpret_cast(dev.utf16()), &luid); + if (res != NO_ERROR) { + qCritical() << "Failed to convert luid: " << res; + 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.ValidLifetime = 0xffffffff; + row.PreferredLifetime = 0xffffffff; + row.DadState = IpDadStatePreferred; + + res = CreateUnicastIpAddressEntry(&row); + if (res != NO_ERROR && res != ERROR_OBJECT_ALREADY_EXISTS) { + qDebug() << "Failed to create IP address:" << res; + return false; + } + + return true; +} + void RouterWin::suspendWcmSvc(bool suspend) { if (suspend == m_suspended) return; @@ -465,11 +497,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() @@ -477,9 +517,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; } diff --git a/service/server/router_win.h b/service/server/router_win.h index c2412cc55..9258b7341 100644 --- a/service/server/router_win.h +++ b/service/server/router_win.h @@ -45,6 +45,7 @@ public: bool StartRoutingIpv6(); bool StopRoutingIpv6(); + bool createTun(const QString &dev, const QString &subnet); void suspendWcmSvc(bool suspend); bool updateResolvers(const QString& ifname, const QList& resolvers); bool restoreResolvers();