mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-05-08 14:33:23 +00:00
* feat: initial conan support * feat: add awg-go and awg-apple recipes * feat: macos full feature conan build, except ss and cloak * feat: conan android initial support * fix: android libssh fixes * conan: android additional recipes and fixes * feat: openvpn add support android * fix: awg android connection establish * conan: apple full-featured support * chore: bump min macos version * chore: get rid of manual deploy recursive copying * conan: beautify makefile-based recipes * conan: add geosite.dat and geoip.dat * conan: use lib linking instead of QT_EXTRA_LIBS for OVPN * conan: address lack of SONAME of libck-ovpn-plugin.so correctly * conan: windows initial support * conan: make awg-windows and wintun be interpret as exes * conan: fix version for v2ray-rules-dat * feat: conan and platform bootstrap rework in cmake * feat: 16kb support for Android * chore(conan): recipes cleanup * feat: support of drivers for windows * feat: support full-featured cmake install * chore: exclude qtkeychain from the target build * fix: install for apple systems * fix: provide flags for cloak plugin for openvpn-pt-android * chore: bump android deps for 16kb support * feat(conan): patch cloak to properly provide env for golang * chore: remove redundant hint from conan find * feat: linux <-> conan features * feat: linux initial packaging support * feat: linux cpack support * feat: cpack windows full-featured build * feat: productbuild cpack support * feat: rework CI/CD for macos * feat: rework CI/CD for Linux * fix: libncap automake args * fix: CI/CD correct QT paths * fix: windows rework CI/CD * fix: windows artifact upload * chore: remove MacOS-old from build targets * feat: add conan to all mobile and NE builds * feat: support default amnezia conan remote * fix: use Release instead of release on Android * feat: get rid of 3rd-prebuilt * feat: conan CI/CD upload * fix: CI/CD change windows toolset versions * fix: remove MSVC version from CI/CD * feat: conan CI/CD add Release and Debug build types * feat: add multiple xcode versions for conan CI/CD * fix: correct conan CI/CD clang versions * feat: separate prebuilt baking, and add some for NE * feat: rework keychain on ios/macos even more * fix: add desktop Qt for iOS * feat: add QT_HOST_PATH to build.sh * fix: add deploy definition to cmake * fix: android adjustments for toolchains and CI/CD * fix: add needs for Android CI/CD * fix: Android CI/CD use android-28 * fix: modernize translations, and CI/CD fixes * fix: gradle min sdk compilation error * fix: CI/CD add installers to all jobs * fix: parse android platform more precisely * fix: adjust aab path in CI/CD * feat: CI/CD do not execute artifact build if there is nothing changed * fix: CI/CD use common jobs even if previous were failed * fix: Apple CI/CD use set-key-partition-list for keychains * fix: Apple CI/CD do not specify any keychain (use default) * fix: build aab as a different step in build script * chore: beautify build.sh script * feat: CI/CD build separate APKs per ABI * fix: Android CI/CD upload artifact in separate steps * chore: recipes cleanup * feat: add hints for conan on MacOS * fix: add main.cpp and tests back to CMakeLists.txt * chore: xrayProtocol codestyle changes * fix: openssl set proper X509 request version * fix: make openvpn protocol rely only on client while configuring * chore: get rid of old scripts * chore: readme update describing build process more precisely * feat: windows build script add multiprocessing capabilities * chore: bump Qt version in README * feat: add generator option and use Ninja by default in CI/CD for linux/macos --------- Co-authored-by: NickVs2015 <nv@amnezia.org>
288 lines
12 KiB
C++
Executable File
288 lines
12 KiB
C++
Executable File
#include "xrayProtocol.h"
|
|
|
|
#include "core/protocols/protocolUtils.h"
|
|
#include "core/utils/constants/configKeys.h"
|
|
#include "core/utils/ipcClient.h"
|
|
#include "core/utils/networkUtilities.h"
|
|
#include "core/utils/serialization/serialization.h"
|
|
#include "ipc.h"
|
|
|
|
#include <QCryptographicHash>
|
|
#include <QJsonDocument>
|
|
#include <QJsonObject>
|
|
#include <QNetworkInterface>
|
|
#include <QtCore/qlogging.h>
|
|
#include <QtCore/qobjectdefs.h>
|
|
#include <QtCore/qprocess.h>
|
|
|
|
#include <exception>
|
|
|
|
#ifdef Q_OS_MACOS
|
|
static const QString tunName = "utun22";
|
|
#else
|
|
static const QString tunName = "tun2";
|
|
#endif
|
|
|
|
XrayProtocol::XrayProtocol(const QJsonObject &configuration, QObject *parent) : VpnProtocol(configuration, parent)
|
|
{
|
|
m_vpnGateway = amnezia::protocols::xray::defaultLocalAddr;
|
|
m_vpnLocalAddress = amnezia::protocols::xray::defaultLocalAddr;
|
|
m_routeGateway = NetworkUtilities::getGatewayAndIface().first;
|
|
|
|
m_routeMode = static_cast<amnezia::RouteMode>(configuration.value(amnezia::configKey::splitTunnelType).toInt());
|
|
m_remoteAddress = NetworkUtilities::getIPAddress(m_rawConfig.value(amnezia::configKey::hostName).toString());
|
|
|
|
const QString primaryDns = configuration.value(amnezia::configKey::dns1).toString();
|
|
m_dnsServers.push_back(QHostAddress(primaryDns));
|
|
if (primaryDns != amnezia::protocols::dns::amneziaDnsIp) {
|
|
const QString secondaryDns = configuration.value(amnezia::configKey::dns2).toString();
|
|
m_dnsServers.push_back(QHostAddress(secondaryDns));
|
|
}
|
|
|
|
QJsonObject xrayConfiguration = configuration.value(ProtocolUtils::key_proto_config_data(Proto::Xray)).toObject();
|
|
if (xrayConfiguration.isEmpty()) {
|
|
xrayConfiguration = configuration.value(ProtocolUtils::key_proto_config_data(Proto::SSXray)).toObject();
|
|
}
|
|
|
|
if (xrayConfiguration.isEmpty()) {
|
|
qWarning() << "Xray config wrapper is empty";
|
|
m_xrayConfig = {};
|
|
}
|
|
|
|
m_xrayConfig = QJsonDocument::fromJson(xrayConfiguration.value(amnezia::configKey::config).toString().toUtf8()).object();
|
|
if (m_xrayConfig.isEmpty()) {
|
|
qWarning() << "Xray config string is not a valid JSON object";
|
|
m_xrayConfig = {};
|
|
}
|
|
}
|
|
|
|
XrayProtocol::~XrayProtocol()
|
|
{
|
|
qDebug() << "XrayProtocol::~XrayProtocol()";
|
|
XrayProtocol::stop();
|
|
}
|
|
|
|
ErrorCode XrayProtocol::start()
|
|
{
|
|
qDebug() << "XrayProtocol::start()";
|
|
|
|
// Inject SOCKS5 auth into the inbound before starting xray.
|
|
// Re-uses existing credentials if the config already has them (e.g. imported config).
|
|
amnezia::serialization::inbounds::InboundCredentials creds;
|
|
try {
|
|
creds = amnezia::serialization::inbounds::EnsureInboundAuth(m_xrayConfig);
|
|
} catch (const std::exception &e) {
|
|
qCritical() << "EnsureInboundAuth failed:" << e.what();
|
|
return ErrorCode::InternalError;
|
|
}
|
|
m_socksUser = creds.username;
|
|
m_socksPassword = creds.password;
|
|
m_socksPort = creds.port;
|
|
|
|
const QString xrayConfigStr = QJsonDocument(m_xrayConfig).toJson(QJsonDocument::Compact);
|
|
if (xrayConfigStr.isEmpty()) {
|
|
qCritical() << "Xray config is empty";
|
|
return ErrorCode::XrayExecutableCrashed;
|
|
}
|
|
|
|
return IpcClient::withInterface(
|
|
[&](QSharedPointer<IpcInterfaceReplica> iface) {
|
|
auto xrayStart = iface->xrayStart(xrayConfigStr);
|
|
if (!xrayStart.waitForFinished() || !xrayStart.returnValue()) {
|
|
qCritical() << "Failed to start xray";
|
|
return ErrorCode::XrayExecutableCrashed;
|
|
}
|
|
return startTun2Socks();
|
|
},
|
|
[]() { return ErrorCode::AmneziaServiceConnectionFailed; });
|
|
}
|
|
|
|
void XrayProtocol::stop()
|
|
{
|
|
qDebug() << "XrayProtocol::stop()";
|
|
|
|
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
|
|
auto disableKillSwitch = iface->disableKillSwitch();
|
|
if (!disableKillSwitch.waitForFinished() || !disableKillSwitch.returnValue())
|
|
qWarning() << "Failed to disable killswitch";
|
|
|
|
auto StartRoutingIpv6 = iface->StartRoutingIpv6();
|
|
if (!StartRoutingIpv6.waitForFinished() || !StartRoutingIpv6.returnValue())
|
|
qWarning() << "Failed to start routing ipv6";
|
|
|
|
auto restoreResolvers = iface->restoreResolvers();
|
|
if (!restoreResolvers.waitForFinished() || !restoreResolvers.returnValue())
|
|
qWarning() << "Failed to restore resolvers";
|
|
|
|
auto deleteTun = iface->deleteTun(tunName);
|
|
if (!deleteTun.waitForFinished() || !deleteTun.returnValue())
|
|
qWarning() << "Failed to delete tun";
|
|
|
|
auto xrayStop = iface->xrayStop();
|
|
if (!xrayStop.waitForFinished() || !xrayStop.returnValue())
|
|
qWarning() << "Failed to stop xray";
|
|
});
|
|
|
|
if (m_tun2socksProcess) {
|
|
m_tun2socksProcess->blockSignals(true);
|
|
|
|
#ifndef Q_OS_WIN
|
|
m_tun2socksProcess->terminate();
|
|
auto waitForFinished = m_tun2socksProcess->waitForFinished(1000);
|
|
if (!waitForFinished.waitForFinished() || !waitForFinished.returnValue()) {
|
|
qWarning() << "Failed to terminate tun2socks. Killing the process...";
|
|
m_tun2socksProcess->kill();
|
|
}
|
|
#else
|
|
// terminate does not do anything useful on Windows
|
|
// so just kill the process
|
|
m_tun2socksProcess->kill();
|
|
#endif
|
|
|
|
m_tun2socksProcess->close();
|
|
m_tun2socksProcess.reset();
|
|
}
|
|
|
|
setConnectionState(Vpn::ConnectionState::Disconnected);
|
|
}
|
|
|
|
ErrorCode XrayProtocol::startTun2Socks()
|
|
{
|
|
m_tun2socksProcess = IpcClient::CreatePrivilegedProcess();
|
|
if (!m_tun2socksProcess->waitForSource()) {
|
|
return ErrorCode::AmneziaServiceConnectionFailed;
|
|
}
|
|
|
|
const QString proxyUrl = QString("socks5://%1:%2@127.0.0.1:%3").arg(m_socksUser, m_socksPassword, QString::number(m_socksPort));
|
|
|
|
m_tun2socksProcess->setProgram(PermittedProcess::Tun2Socks);
|
|
m_tun2socksProcess->setArguments({ "-device", QString("tun://%1").arg(tunName), "-proxy", proxyUrl });
|
|
|
|
connect(
|
|
m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::readyReadStandardError, this,
|
|
[this]() {
|
|
auto readAllStandardError = m_tun2socksProcess->readAllStandardError();
|
|
if (!readAllStandardError.waitForFinished()) {
|
|
qWarning() << "Failed to read output from tun2socks";
|
|
return;
|
|
}
|
|
|
|
const QString line = readAllStandardError.returnValue();
|
|
|
|
if (!line.contains("[TCP]") && !line.contains("[UDP]"))
|
|
qDebug() << "[tun2socks]:" << line;
|
|
|
|
if (line.contains("[STACK] tun://") && line.contains("<-> socks5://")) {
|
|
disconnect(m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::readyReadStandardOutput, this, nullptr);
|
|
|
|
if (ErrorCode res = setupRouting(); res != ErrorCode::NoError) {
|
|
stop();
|
|
setLastError(res);
|
|
} else {
|
|
setConnectionState(Vpn::ConnectionState::Connected);
|
|
}
|
|
}
|
|
},
|
|
Qt::QueuedConnection);
|
|
|
|
connect(
|
|
m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::finished, this,
|
|
[this](int exitCode, QProcess::ExitStatus exitStatus) {
|
|
if (exitStatus == QProcess::ExitStatus::CrashExit) {
|
|
qCritical() << "Tun2socks process crashed!";
|
|
} else {
|
|
qCritical() << QString("Tun2socks process was closed with %1 exit code").arg(exitCode);
|
|
}
|
|
stop();
|
|
setLastError(ErrorCode::Tun2SockExecutableCrashed);
|
|
},
|
|
Qt::QueuedConnection);
|
|
|
|
m_tun2socksProcess->start();
|
|
return ErrorCode::NoError;
|
|
}
|
|
|
|
ErrorCode XrayProtocol::setupRouting()
|
|
{
|
|
return IpcClient::withInterface(
|
|
[this](QSharedPointer<IpcInterfaceReplica> iface) -> ErrorCode {
|
|
#ifdef Q_OS_WIN
|
|
const int inetAdapterIndex = NetworkUtilities::AdapterIndexTo(QHostAddress(m_remoteAddress));
|
|
#endif
|
|
auto createTun = iface->createTun(tunName, amnezia::protocols::xray::defaultLocalAddr);
|
|
if (!createTun.waitForFinished() || !createTun.returnValue()) {
|
|
qCritical() << "Failed to assign IP address for TUN";
|
|
return ErrorCode::InternalError;
|
|
}
|
|
|
|
auto updateResolvers = iface->updateResolvers(tunName, m_dnsServers);
|
|
if (!updateResolvers.waitForFinished() || !updateResolvers.returnValue()) {
|
|
qCritical() << "Failed to set DNS resolvers for TUN";
|
|
return ErrorCode::InternalError;
|
|
}
|
|
|
|
#ifdef Q_OS_WIN
|
|
int vpnAdapterIndex = -1;
|
|
QList<QNetworkInterface> netInterfaces = QNetworkInterface::allInterfaces();
|
|
for (auto &netInterface : netInterfaces) {
|
|
for (auto &address : netInterface.addressEntries()) {
|
|
if (m_vpnLocalAddress == address.ip().toString())
|
|
vpnAdapterIndex = netInterface.index();
|
|
}
|
|
}
|
|
#else
|
|
static const int vpnAdapterIndex = 0;
|
|
#endif
|
|
const bool killSwitchEnabled = QVariant(m_rawConfig.value(configKey::killSwitchOption).toString()).toBool();
|
|
if (killSwitchEnabled) {
|
|
if (vpnAdapterIndex != -1) {
|
|
QJsonObject config = m_rawConfig;
|
|
config.insert("vpnServer", m_remoteAddress);
|
|
|
|
auto enableKillSwitch = IpcClient::Interface()->enableKillSwitch(config, vpnAdapterIndex);
|
|
if (!enableKillSwitch.waitForFinished() || !enableKillSwitch.returnValue()) {
|
|
qCritical() << "Failed to enable killswitch";
|
|
return ErrorCode::InternalError;
|
|
}
|
|
} else
|
|
qWarning() << "Failed to get vpnAdapterIndex. Killswitch disabled";
|
|
}
|
|
|
|
if (m_routeMode == amnezia::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() || routeAddList.returnValue() != subnets.count()) {
|
|
qCritical() << "Failed to set routes for TUN";
|
|
return ErrorCode::InternalError;
|
|
}
|
|
}
|
|
|
|
auto StopRoutingIpv6 = iface->StopRoutingIpv6();
|
|
if (!StopRoutingIpv6.waitForFinished() || !StopRoutingIpv6.returnValue()) {
|
|
qCritical() << "Failed to disable IPv6 routing";
|
|
return ErrorCode::InternalError;
|
|
}
|
|
|
|
#ifdef Q_OS_WIN
|
|
if (inetAdapterIndex != -1 && vpnAdapterIndex != -1) {
|
|
QJsonObject config = m_rawConfig;
|
|
config.insert("inetAdapterIndex", inetAdapterIndex);
|
|
config.insert("vpnAdapterIndex", vpnAdapterIndex);
|
|
config.insert("vpnGateway", m_vpnGateway);
|
|
config.insert("vpnServer", m_remoteAddress);
|
|
|
|
auto enablePeerTraffic = iface->enablePeerTraffic(config);
|
|
if (!enablePeerTraffic.waitForFinished() || !enablePeerTraffic.returnValue()) {
|
|
qCritical() << "Failed to enable peer traffic";
|
|
return ErrorCode::InternalError;
|
|
}
|
|
} else
|
|
qWarning() << "Failed to get adapter indexes. Split-tunneling disabled";
|
|
#endif
|
|
return ErrorCode::NoError;
|
|
},
|
|
[]() { return ErrorCode::AmneziaServiceConnectionFailed; });
|
|
}
|