mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-20 02:00:55 +07:00
feat: run xray-core in a forked worker process
This commit is contained in:
+130
-3
@@ -1,9 +1,12 @@
|
|||||||
#include "ipcserver.h"
|
#include "ipcserver.h"
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QEventLoop>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QHostAddress>
|
#include <QHostAddress>
|
||||||
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QLocalServer>
|
#include <QLocalServer>
|
||||||
#include <QLocalSocket>
|
#include <QLocalSocket>
|
||||||
@@ -16,10 +19,15 @@
|
|||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "router.h"
|
#include "router.h"
|
||||||
#include "killswitch.h"
|
#include "killswitch.h"
|
||||||
#include "xray.h"
|
|
||||||
|
|
||||||
#include "../client/daemon/daemon.h"
|
#include "../client/daemon/daemon.h"
|
||||||
|
|
||||||
|
#ifdef Q_OS_MAC
|
||||||
|
#include "router_mac.h"
|
||||||
|
#include "core/utils/networkUtilities.h"
|
||||||
|
#include <QNetworkInterface>
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
#include "tapcontroller_win.h"
|
#include "tapcontroller_win.h"
|
||||||
#endif
|
#endif
|
||||||
@@ -345,13 +353,112 @@ bool IpcServer::refreshKillSwitch(bool enabled)
|
|||||||
return KillSwitch::instance()->refresh(enabled);
|
return KillSwitch::instance()->refresh(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void IpcServer::onXrayWorkerLine(const QByteArray& line)
|
||||||
|
{
|
||||||
|
const QJsonObject ev = QJsonDocument::fromJson(line).object();
|
||||||
|
const QString name = ev.value("ev").toString();
|
||||||
|
if (name == "log") {
|
||||||
|
const QString level = ev.value("level").toString();
|
||||||
|
const QString msg = ev.value("msg").toString();
|
||||||
|
if (level == QLatin1String("warn")) {
|
||||||
|
qWarning().noquote() << "[xray-worker]" << msg;
|
||||||
|
} else if (level == QLatin1String("error") || level == QLatin1String("fatal")) {
|
||||||
|
qCritical().noquote() << "[xray-worker]" << msg;
|
||||||
|
} else if (level == QLatin1String("info")) {
|
||||||
|
qInfo().noquote() << "[xray-worker]" << msg;
|
||||||
|
} else {
|
||||||
|
qDebug().noquote() << "[xray-worker]" << msg;
|
||||||
|
}
|
||||||
|
} else if (name == "ready" || name == "failed") {
|
||||||
|
if (m_xrayStartLoop) {
|
||||||
|
m_xrayStartResult = (name == "ready");
|
||||||
|
m_xrayStartLoop->quit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool IpcServer::xrayStart(const QString& cfg)
|
bool IpcServer::xrayStart(const QString& cfg)
|
||||||
{
|
{
|
||||||
#ifdef MZ_DEBUG
|
#ifdef MZ_DEBUG
|
||||||
qDebug() << "IpcServer::xrayStart";
|
qDebug() << "IpcServer::xrayStart";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return Xray::getInstance().startXray(cfg);
|
if (!m_xrayProcess || m_xrayProcess->state() == QProcess::NotRunning) {
|
||||||
|
m_xrayProcess = QSharedPointer<QProcess>::create();
|
||||||
|
m_xrayStdoutBuf.clear();
|
||||||
|
|
||||||
|
QObject::connect(m_xrayProcess.data(), &QProcess::readyReadStandardOutput, this, [this]() {
|
||||||
|
m_xrayStdoutBuf.append(m_xrayProcess->readAllStandardOutput());
|
||||||
|
int nl;
|
||||||
|
while ((nl = m_xrayStdoutBuf.indexOf('\n')) >= 0) {
|
||||||
|
const QByteArray line = m_xrayStdoutBuf.left(nl);
|
||||||
|
m_xrayStdoutBuf.remove(0, nl + 1);
|
||||||
|
onXrayWorkerLine(line);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(m_xrayProcess.data(), &QProcess::errorOccurred, this,
|
||||||
|
[this](QProcess::ProcessError err) {
|
||||||
|
qCritical().noquote().nospace() << "[xray-worker] process error: " << err;
|
||||||
|
if (m_xrayStartLoop) {
|
||||||
|
m_xrayStartResult = false;
|
||||||
|
m_xrayStartLoop->quit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
QObject::connect(m_xrayProcess.data(),
|
||||||
|
QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
|
||||||
|
this, [this](int code, QProcess::ExitStatus status) {
|
||||||
|
qDebug().noquote().nospace() << "[xray-worker] finished, code=" << code << " status=" << status;
|
||||||
|
if (m_xrayStartLoop) {
|
||||||
|
m_xrayStartResult = false;
|
||||||
|
m_xrayStartLoop->quit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
m_xrayProcess->setProgram(QCoreApplication::applicationFilePath());
|
||||||
|
m_xrayProcess->setArguments({QStringLiteral("--xray-worker")});
|
||||||
|
m_xrayProcess->start();
|
||||||
|
|
||||||
|
if (!m_xrayProcess->waitForStarted(5000)) {
|
||||||
|
qCritical().noquote() << "[xray-worker] failed to start";
|
||||||
|
m_xrayProcess.reset();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef Q_OS_MAC
|
||||||
|
const auto gatewayAndIface = NetworkUtilities::getGatewayAndIface();
|
||||||
|
m_xrayUplinkGateway = gatewayAndIface.first;
|
||||||
|
m_xrayUplinkIface = gatewayAndIface.second.name();
|
||||||
|
if (!m_xrayUplinkIface.isEmpty() && !m_xrayUplinkGateway.isEmpty()) {
|
||||||
|
if (!RouterMac::Instance().routeAddXray(m_xrayUplinkIface, m_xrayUplinkGateway)) {
|
||||||
|
qWarning() << "[xray] failed to install xray routes on" << m_xrayUplinkIface;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const QJsonObject startCmd{{QStringLiteral("op"), QStringLiteral("start")},
|
||||||
|
{QStringLiteral("config"), cfg}};
|
||||||
|
m_xrayProcess->write(QJsonDocument(startCmd).toJson(QJsonDocument::Compact) + '\n');
|
||||||
|
|
||||||
|
QEventLoop loop;
|
||||||
|
m_xrayStartLoop = &loop;
|
||||||
|
m_xrayStartResult = false;
|
||||||
|
loop.exec();
|
||||||
|
m_xrayStartLoop.clear();
|
||||||
|
|
||||||
|
if (!m_xrayStartResult) {
|
||||||
|
#ifdef Q_OS_MAC
|
||||||
|
if (!m_xrayUplinkIface.isEmpty()) {
|
||||||
|
RouterMac::Instance().routeDeleteXray(m_xrayUplinkIface, m_xrayUplinkGateway);
|
||||||
|
m_xrayUplinkIface.clear();
|
||||||
|
m_xrayUplinkGateway.clear();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_xrayStartResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IpcServer::xrayStop()
|
bool IpcServer::xrayStop()
|
||||||
@@ -360,5 +467,25 @@ bool IpcServer::xrayStop()
|
|||||||
qDebug() << "IpcServer::xrayStop";
|
qDebug() << "IpcServer::xrayStop";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return Xray::getInstance().stopXray();
|
if (m_xrayProcess && m_xrayProcess->state() != QProcess::NotRunning) {
|
||||||
|
const QJsonObject stopCmd{{QStringLiteral("op"), QStringLiteral("stop")}};
|
||||||
|
m_xrayProcess->write(QJsonDocument(stopCmd).toJson(QJsonDocument::Compact) + '\n');
|
||||||
|
|
||||||
|
if (!m_xrayProcess->waitForFinished(3000)) {
|
||||||
|
qWarning().noquote() << "[xray-worker] did not exit after stop, killing";
|
||||||
|
m_xrayProcess->kill();
|
||||||
|
m_xrayProcess->waitForFinished(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_xrayProcess.reset();
|
||||||
|
|
||||||
|
#ifdef Q_OS_MAC
|
||||||
|
if (!m_xrayUplinkIface.isEmpty()) {
|
||||||
|
RouterMac::Instance().routeDeleteXray(m_xrayUplinkIface, m_xrayUplinkGateway);
|
||||||
|
m_xrayUplinkIface.clear();
|
||||||
|
m_xrayUplinkGateway.clear();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
#ifndef IPCSERVER_H
|
#ifndef IPCSERVER_H
|
||||||
#define IPCSERVER_H
|
#define IPCSERVER_H
|
||||||
|
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QEventLoop>
|
||||||
#include <QLocalServer>
|
#include <QLocalServer>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QPointer>
|
||||||
|
#include <QProcess>
|
||||||
#include <QRemoteObjectNode>
|
#include <QRemoteObjectNode>
|
||||||
|
#include <QSharedPointer>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include "../client/daemon/interfaceconfig.h"
|
#include "../client/daemon/interfaceconfig.h"
|
||||||
#include "../client/mozilla/pinghelper.h"
|
#include "../client/mozilla/pinghelper.h"
|
||||||
@@ -72,6 +77,17 @@ private:
|
|||||||
|
|
||||||
QMap<int, ProcessDescriptor> m_processes;
|
QMap<int, ProcessDescriptor> m_processes;
|
||||||
PingHelper m_pingHelper;
|
PingHelper m_pingHelper;
|
||||||
|
|
||||||
|
QSharedPointer<QProcess> m_xrayProcess;
|
||||||
|
QByteArray m_xrayStdoutBuf;
|
||||||
|
QPointer<QEventLoop> m_xrayStartLoop;
|
||||||
|
bool m_xrayStartResult = false;
|
||||||
|
#ifdef Q_OS_MAC
|
||||||
|
QString m_xrayUplinkIface;
|
||||||
|
QString m_xrayUplinkGateway;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void onXrayWorkerLine(const QByteArray& line);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // IPCSERVER_H
|
#endif // IPCSERVER_H
|
||||||
|
|||||||
@@ -1,21 +1,139 @@
|
|||||||
|
#include <QCoreApplication>
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
#include "localserver.h"
|
#include "localserver.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "systemservice.h"
|
#include "systemservice.h"
|
||||||
|
#include "xray.h"
|
||||||
#include "core/utils/utilities.h"
|
#include "core/utils/utilities.h"
|
||||||
|
|
||||||
#ifdef Q_OS_WIN
|
#ifdef Q_OS_WIN
|
||||||
#include "platforms/windows/daemon/windowsdaemontunnel.h"
|
#include "platforms/windows/daemon/windowsdaemontunnel.h"
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
int s_argc = 0;
|
int s_argc = 0;
|
||||||
char** s_argv = nullptr;
|
char** s_argv = nullptr;
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
#else
|
||||||
|
#include <unistd.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr const char* kXrayWorkerArg = "--xray-worker";
|
||||||
|
|
||||||
|
void writeWorkerEvent(const QJsonObject& obj)
|
||||||
|
{
|
||||||
|
const QByteArray bytes = QJsonDocument(obj).toJson(QJsonDocument::Compact) + '\n';
|
||||||
|
std::fwrite(bytes.constData(), 1, bytes.size(), stdout);
|
||||||
|
std::fflush(stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
void workerMessageHandler(QtMsgType type, const QMessageLogContext&, const QString& msg)
|
||||||
|
{
|
||||||
|
const char* level = "debug";
|
||||||
|
switch (type) {
|
||||||
|
case QtDebugMsg: level = "debug"; break;
|
||||||
|
case QtInfoMsg: level = "info"; break;
|
||||||
|
case QtWarningMsg: level = "warn"; break;
|
||||||
|
case QtCriticalMsg: level = "error"; break;
|
||||||
|
case QtFatalMsg: level = "fatal"; break;
|
||||||
|
}
|
||||||
|
writeWorkerEvent({{"ev", "log"}, {"level", QString::fromLatin1(level)}, {"msg", msg}});
|
||||||
|
}
|
||||||
|
|
||||||
|
int readStdinChunk(char* buf, int cap)
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
DWORD n = 0;
|
||||||
|
BOOL ok = ReadFile(GetStdHandle(STD_INPUT_HANDLE), buf, cap, &n, nullptr);
|
||||||
|
return ok ? static_cast<int>(n) : -1;
|
||||||
|
#else
|
||||||
|
return static_cast<int>(::read(STDIN_FILENO, buf, cap));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
int runXrayWorker(int argc, char** argv)
|
||||||
|
{
|
||||||
|
qInstallMessageHandler(workerMessageHandler);
|
||||||
|
QCoreApplication app(argc, argv);
|
||||||
|
|
||||||
|
auto* xray = new Xray;
|
||||||
|
auto* buf = new QByteArray;
|
||||||
|
|
||||||
|
auto exitWorker = [xray](int code) {
|
||||||
|
xray->stopXray();
|
||||||
|
std::fflush(stdout);
|
||||||
|
std::_Exit(code);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto handleLine = [xray, exitWorker](const QByteArray& line) {
|
||||||
|
const QJsonObject cmd = QJsonDocument::fromJson(line).object();
|
||||||
|
const QString op = cmd.value("op").toString();
|
||||||
|
if (op == "start") {
|
||||||
|
const QString cfg = cmd.value("config").toString();
|
||||||
|
const bool ok = xray->startXray(cfg);
|
||||||
|
writeWorkerEvent({{"ev", ok ? "ready" : "failed"}});
|
||||||
|
} else if (op == "stop") {
|
||||||
|
writeWorkerEvent({{"ev", "stopped"}});
|
||||||
|
exitWorker(0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto onChunk = [buf, handleLine](const QByteArray& data) {
|
||||||
|
buf->append(data);
|
||||||
|
int nl;
|
||||||
|
while ((nl = buf->indexOf('\n')) >= 0) {
|
||||||
|
const QByteArray line = buf->left(nl);
|
||||||
|
buf->remove(0, nl + 1);
|
||||||
|
handleLine(line);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Detached reader thread: stdin is an anonymous pipe (the parent's write end).
|
||||||
|
// QSocketNotifier doesn't work on anonymous pipes on Windows, so use a blocking
|
||||||
|
// read in a thread and dispatch events back to the main thread. On EOF the
|
||||||
|
// worker exits immediately via _Exit to avoid races with QCoreApplication
|
||||||
|
// teardown while this thread is still alive.
|
||||||
|
std::thread([onChunk, exitWorker]() {
|
||||||
|
char chunk[4096];
|
||||||
|
while (true) {
|
||||||
|
const int n = readStdinChunk(chunk, sizeof(chunk));
|
||||||
|
if (n <= 0) {
|
||||||
|
// Parent gone or pipe error: shut xray down and exit.
|
||||||
|
exitWorker(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QMetaObject::invokeMethod(qApp, [onChunk, data = QByteArray(chunk, n)]() {
|
||||||
|
onChunk(data);
|
||||||
|
}, Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
}).detach();
|
||||||
|
|
||||||
|
return app.exec();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasXrayWorkerArg(int argc, char** argv)
|
||||||
|
{
|
||||||
|
for (int i = 1; i < argc; ++i) {
|
||||||
|
if (std::strcmp(argv[i], kXrayWorkerArg) == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
int runApplication(int argc, char** argv)
|
int runApplication(int argc, char** argv)
|
||||||
{
|
{
|
||||||
QCoreApplication app(argc,argv);
|
QCoreApplication app(argc,argv);
|
||||||
@@ -44,6 +162,10 @@ int runApplication(int argc, char** argv)
|
|||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
|
if (hasXrayWorkerArg(argc, argv)) {
|
||||||
|
return runXrayWorker(argc, argv);
|
||||||
|
}
|
||||||
|
|
||||||
Utils::initializePath(Logger::systemLogDir());
|
Utils::initializePath(Logger::systemLogDir());
|
||||||
|
|
||||||
if (argc >= 2) {
|
if (argc >= 2) {
|
||||||
|
|||||||
+1
-25
@@ -1,8 +1,5 @@
|
|||||||
#include "xray.h"
|
#include "xray.h"
|
||||||
#include "core/utils/networkUtilities.h"
|
#include "core/utils/networkUtilities.h"
|
||||||
#ifdef Q_OS_MAC
|
|
||||||
#include "router_mac.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QNetworkInterface>
|
#include <QNetworkInterface>
|
||||||
@@ -34,9 +31,7 @@
|
|||||||
bool Xray::startXray(const QString &cfg)
|
bool Xray::startXray(const QString &cfg)
|
||||||
{
|
{
|
||||||
qDebug() << "Xray::startXray()";
|
qDebug() << "Xray::startXray()";
|
||||||
const auto gatewayAndIface = NetworkUtilities::getGatewayAndIface();
|
const QNetworkInterface defaultIface = NetworkUtilities::getGatewayAndIface().second;
|
||||||
const QString defaultGateway = gatewayAndIface.first;
|
|
||||||
const QNetworkInterface defaultIface = gatewayAndIface.second;
|
|
||||||
#ifdef Q_OS_LINUX
|
#ifdef Q_OS_LINUX
|
||||||
m_defaultIfaceName = defaultIface.name().toUtf8();
|
m_defaultIfaceName = defaultIface.name().toUtf8();
|
||||||
#else
|
#else
|
||||||
@@ -46,17 +41,6 @@ bool Xray::startXray(const QString &cfg)
|
|||||||
qDebug() << "[xray] using uplink interface:" << defaultIface.name() << "(" << defaultIface.index() << ")";
|
qDebug() << "[xray] using uplink interface:" << defaultIface.name() << "(" << defaultIface.index() << ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef Q_OS_MAC
|
|
||||||
m_uplinkIfaceName = defaultIface.name();
|
|
||||||
m_uplinkGateway = defaultGateway;
|
|
||||||
if (!m_uplinkIfaceName.isEmpty()) {
|
|
||||||
const bool installed = RouterMac::Instance().routeAddXray(m_uplinkIfaceName, m_uplinkGateway);
|
|
||||||
if (!installed) {
|
|
||||||
qWarning() << "[xray] failed to install xray routes on" << m_uplinkIfaceName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (auto err = amnezia_xray_setsockcallback(ctxSockCallback, this); err != nullptr) {
|
if (auto err = amnezia_xray_setsockcallback(ctxSockCallback, this); err != nullptr) {
|
||||||
qDebug() << "[xray] sockopt failed: " << err;
|
qDebug() << "[xray] sockopt failed: " << err;
|
||||||
amnezia_xray_free(err);
|
amnezia_xray_free(err);
|
||||||
@@ -91,14 +75,6 @@ bool Xray::stopXray()
|
|||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef Q_OS_MAC
|
|
||||||
if (!m_uplinkIfaceName.isEmpty()) {
|
|
||||||
RouterMac::Instance().routeDeleteXray(m_uplinkIfaceName, m_uplinkGateway);
|
|
||||||
}
|
|
||||||
m_uplinkIfaceName.clear();
|
|
||||||
m_uplinkGateway.clear();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,11 +31,6 @@ private:
|
|||||||
#else
|
#else
|
||||||
int m_defaultIfaceIdx;
|
int m_defaultIfaceIdx;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef Q_OS_MAC
|
|
||||||
QString m_uplinkIfaceName;
|
|
||||||
QString m_uplinkGateway;
|
|
||||||
#endif
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // XRAY_H
|
#endif // XRAY_H
|
||||||
|
|||||||
Reference in New Issue
Block a user