feat: key xray worker per tunnel via ifname-scoped IPC

This commit is contained in:
cd-amn
2026-06-03 13:27:24 +04:00
parent 99e6c18f15
commit d528a241d8
4 changed files with 101 additions and 79 deletions
+2 -2
View File
@@ -107,7 +107,7 @@ ErrorCode XrayProtocol::start()
return IpcClient::withInterface( return IpcClient::withInterface(
[&](QSharedPointer<IpcInterfaceReplica> iface) { [&](QSharedPointer<IpcInterfaceReplica> iface) {
auto xrayStart = iface->xrayStart(xrayConfigStr); auto xrayStart = iface->xrayStart(m_tunName, xrayConfigStr);
if (!xrayStart.waitForFinished() || !xrayStart.returnValue()) { if (!xrayStart.waitForFinished() || !xrayStart.returnValue()) {
qCritical() << "Failed to start xray"; qCritical() << "Failed to start xray";
return ErrorCode::XrayExecutableCrashed; return ErrorCode::XrayExecutableCrashed;
@@ -135,7 +135,7 @@ void XrayProtocol::stop()
if (!deleteTun.waitForFinished() || !deleteTun.returnValue()) if (!deleteTun.waitForFinished() || !deleteTun.returnValue())
qWarning() << "Failed to delete tun"; qWarning() << "Failed to delete tun";
auto xrayStop = iface->xrayStop(); auto xrayStop = iface->xrayStop(m_tunName);
if (!xrayStop.waitForFinished() || !xrayStop.returnValue()) if (!xrayStop.waitForFinished() || !xrayStop.returnValue())
qWarning() << "Failed to stop xray"; qWarning() << "Failed to stop xray";
}); });
+2 -2
View File
@@ -45,8 +45,8 @@ class IpcInterface
SLOT( bool updateResolvers(const QString& ifname, const QList<QHostAddress>& resolvers) ); SLOT( bool updateResolvers(const QString& ifname, const QList<QHostAddress>& resolvers) );
SLOT( bool restoreResolvers() ); SLOT( bool restoreResolvers() );
SLOT(bool xrayStart(const QString &config)); SLOT(bool xrayStart(const QString &ifname, const QString &config));
SLOT(bool xrayStop()); SLOT(bool xrayStop(const QString &ifname));
SLOT( bool startNetworkCheck(const QString& serverIpv4Gateway, const QString& deviceIpv4Address) ); SLOT( bool startNetworkCheck(const QString& serverIpv4Gateway, const QString& deviceIpv4Address) );
SLOT( bool stopNetworkCheck() ); SLOT( bool stopNetworkCheck() );
+85 -66
View File
@@ -353,139 +353,158 @@ bool IpcServer::refreshKillSwitch(bool enabled)
return KillSwitch::instance()->refresh(enabled); return KillSwitch::instance()->refresh(enabled);
} }
void IpcServer::onXrayWorkerLine(const QByteArray& line) void IpcServer::onXrayWorkerLine(const QString& ifname, const QByteArray& line)
{ {
const QJsonObject ev = QJsonDocument::fromJson(line).object(); const QJsonObject ev = QJsonDocument::fromJson(line).object();
const QString name = ev.value("ev").toString(); const QString name = ev.value("ev").toString();
if (name == "log") { if (name == "log") {
const QString prefix = QStringLiteral("[xray-worker:%1]").arg(ifname);
const QString level = ev.value("level").toString(); const QString level = ev.value("level").toString();
const QString msg = ev.value("msg").toString(); const QString msg = ev.value("msg").toString();
if (level == QLatin1String("warn")) { if (level == QLatin1String("warn")) {
qWarning().noquote() << "[xray-worker]" << msg; qWarning().noquote() << prefix << msg;
} else if (level == QLatin1String("error") || level == QLatin1String("fatal")) { } else if (level == QLatin1String("error") || level == QLatin1String("fatal")) {
qCritical().noquote() << "[xray-worker]" << msg; qCritical().noquote() << prefix << msg;
} else if (level == QLatin1String("info")) { } else if (level == QLatin1String("info")) {
qInfo().noquote() << "[xray-worker]" << msg; qInfo().noquote() << prefix << msg;
} else { } else {
qDebug().noquote() << "[xray-worker]" << msg; qDebug().noquote() << prefix << msg;
} }
} else if (name == "ready" || name == "failed") { } else if (name == "ready" || name == "failed") {
if (m_xrayStartLoop) { auto it = m_xrayWorkers.find(ifname);
m_xrayStartResult = (name == "ready"); if (it != m_xrayWorkers.end() && it->startLoop) {
m_xrayStartLoop->quit(); it->startResult = (name == "ready");
it->startLoop->quit();
} }
} }
} }
bool IpcServer::xrayStart(const QString& cfg) bool IpcServer::xrayStart(const QString& ifname, const QString& cfg)
{ {
#ifdef MZ_DEBUG #ifdef MZ_DEBUG
qDebug() << "IpcServer::xrayStart"; qDebug() << "IpcServer::xrayStart" << ifname;
#endif #endif
if (!m_xrayProcess || m_xrayProcess->state() == QProcess::NotRunning) { XrayWorker& w = m_xrayWorkers[ifname];
m_xrayProcess = QSharedPointer<QProcess>::create();
m_xrayStdoutBuf.clear();
QObject::connect(m_xrayProcess.data(), &QProcess::readyReadStandardOutput, this, [this]() { if (!w.process || w.process->state() == QProcess::NotRunning) {
m_xrayStdoutBuf.append(m_xrayProcess->readAllStandardOutput()); w.process = QSharedPointer<QProcess>::create();
w.stdoutBuf.clear();
QObject::connect(w.process.data(), &QProcess::readyReadStandardOutput, this, [this, ifname]() {
auto it = m_xrayWorkers.find(ifname);
if (it == m_xrayWorkers.end() || !it->process) return;
it->stdoutBuf.append(it->process->readAllStandardOutput());
int nl; int nl;
while ((nl = m_xrayStdoutBuf.indexOf('\n')) >= 0) { while ((nl = it->stdoutBuf.indexOf('\n')) >= 0) {
const QByteArray line = m_xrayStdoutBuf.left(nl); const QByteArray line = it->stdoutBuf.left(nl);
m_xrayStdoutBuf.remove(0, nl + 1); it->stdoutBuf.remove(0, nl + 1);
onXrayWorkerLine(line); onXrayWorkerLine(ifname, line);
} }
}); });
QObject::connect(m_xrayProcess.data(), &QProcess::errorOccurred, this, QObject::connect(w.process.data(), &QProcess::errorOccurred, this,
[this](QProcess::ProcessError err) { [this, ifname](QProcess::ProcessError err) {
qCritical().noquote().nospace() << "[xray-worker] process error: " << err; const QString prefix = QStringLiteral("[xray-worker:%1]").arg(ifname);
if (m_xrayStartLoop) { qCritical().noquote().nospace() << prefix << " process error: " << err;
m_xrayStartResult = false; auto it = m_xrayWorkers.find(ifname);
m_xrayStartLoop->quit(); if (it != m_xrayWorkers.end() && it->startLoop) {
it->startResult = false;
it->startLoop->quit();
} }
}); });
QObject::connect(m_xrayProcess.data(), QObject::connect(w.process.data(),
QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
this, [this](int code, QProcess::ExitStatus status) { this, [this, ifname](int code, QProcess::ExitStatus status) {
qDebug().noquote().nospace() << "[xray-worker] finished, code=" << code << " status=" << status; const QString prefix = QStringLiteral("[xray-worker:%1]").arg(ifname);
if (m_xrayStartLoop) { qDebug().noquote().nospace() << prefix << " finished, code=" << code << " status=" << status;
m_xrayStartResult = false; auto it = m_xrayWorkers.find(ifname);
m_xrayStartLoop->quit(); if (it != m_xrayWorkers.end() && it->startLoop) {
it->startResult = false;
it->startLoop->quit();
} }
}); });
m_xrayProcess->setProgram(QCoreApplication::applicationFilePath()); w.process->setProgram(QCoreApplication::applicationFilePath());
m_xrayProcess->setArguments({QStringLiteral("--xray-worker")}); w.process->setArguments({QStringLiteral("--xray-worker")});
m_xrayProcess->start(); w.process->start();
if (!m_xrayProcess->waitForStarted(5000)) { if (!w.process->waitForStarted(5000)) {
qCritical().noquote() << "[xray-worker] failed to start"; qCritical().noquote() << QStringLiteral("[xray-worker:%1] failed to start").arg(ifname);
m_xrayProcess.reset(); m_xrayWorkers.remove(ifname);
return false; return false;
} }
} }
#ifdef Q_OS_MAC #ifdef Q_OS_MAC
const auto gatewayAndIface = NetworkUtilities::getGatewayAndIface(); const auto gatewayAndIface = NetworkUtilities::getGatewayAndIface();
m_xrayUplinkGateway = gatewayAndIface.first; w.uplinkGateway = gatewayAndIface.first;
m_xrayUplinkIface = gatewayAndIface.second.name(); w.uplinkIface = gatewayAndIface.second.name();
if (!m_xrayUplinkIface.isEmpty() && !m_xrayUplinkGateway.isEmpty()) { if (!w.uplinkIface.isEmpty() && !w.uplinkGateway.isEmpty()) {
if (!RouterMac::Instance().routeAddXray(m_xrayUplinkIface, m_xrayUplinkGateway)) { if (!RouterMac::Instance().routeAddXray(w.uplinkIface, w.uplinkGateway)) {
qWarning() << "[xray] failed to install xray routes on" << m_xrayUplinkIface; qWarning() << "[xray] failed to install xray routes on" << w.uplinkIface;
} }
} }
#endif #endif
const QJsonObject startCmd{{QStringLiteral("op"), QStringLiteral("start")}, const QJsonObject startCmd{{QStringLiteral("op"), QStringLiteral("start")},
{QStringLiteral("config"), cfg}}; {QStringLiteral("config"), cfg}};
m_xrayProcess->write(QJsonDocument(startCmd).toJson(QJsonDocument::Compact) + '\n'); w.process->write(QJsonDocument(startCmd).toJson(QJsonDocument::Compact) + '\n');
QEventLoop loop; QEventLoop loop;
m_xrayStartLoop = &loop; w.startLoop = &loop;
m_xrayStartResult = false; w.startResult = false;
loop.exec(); loop.exec();
m_xrayStartLoop.clear();
if (!m_xrayStartResult) { // Re-fetch: the worker entry may have been removed during the loop (e.g. process finished).
auto it = m_xrayWorkers.find(ifname);
if (it == m_xrayWorkers.end()) {
return false;
}
it->startLoop.clear();
const bool ok = it->startResult;
if (!ok) {
#ifdef Q_OS_MAC #ifdef Q_OS_MAC
if (!m_xrayUplinkIface.isEmpty()) { if (!it->uplinkIface.isEmpty()) {
RouterMac::Instance().routeDeleteXray(m_xrayUplinkIface, m_xrayUplinkGateway); RouterMac::Instance().routeDeleteXray(it->uplinkIface, it->uplinkGateway);
m_xrayUplinkIface.clear();
m_xrayUplinkGateway.clear();
} }
#endif #endif
m_xrayWorkers.remove(ifname);
} }
return m_xrayStartResult; return ok;
} }
bool IpcServer::xrayStop() bool IpcServer::xrayStop(const QString& ifname)
{ {
#ifdef MZ_DEBUG #ifdef MZ_DEBUG
qDebug() << "IpcServer::xrayStop"; qDebug() << "IpcServer::xrayStop" << ifname;
#endif #endif
if (m_xrayProcess && m_xrayProcess->state() != QProcess::NotRunning) { auto it = m_xrayWorkers.find(ifname);
const QJsonObject stopCmd{{QStringLiteral("op"), QStringLiteral("stop")}}; if (it == m_xrayWorkers.end()) {
m_xrayProcess->write(QJsonDocument(stopCmd).toJson(QJsonDocument::Compact) + '\n'); return true;
}
if (!m_xrayProcess->waitForFinished(3000)) { if (it->process && it->process->state() != QProcess::NotRunning) {
qWarning().noquote() << "[xray-worker] did not exit after stop, killing"; const QJsonObject stopCmd{{QStringLiteral("op"), QStringLiteral("stop")}};
m_xrayProcess->kill(); it->process->write(QJsonDocument(stopCmd).toJson(QJsonDocument::Compact) + '\n');
m_xrayProcess->waitForFinished(1000);
if (!it->process->waitForFinished(3000)) {
qWarning().noquote() << QStringLiteral("[xray-worker:%1] did not exit after stop, killing").arg(ifname);
it->process->kill();
it->process->waitForFinished(1000);
} }
} }
m_xrayProcess.reset();
#ifdef Q_OS_MAC #ifdef Q_OS_MAC
if (!m_xrayUplinkIface.isEmpty()) { if (!it->uplinkIface.isEmpty()) {
RouterMac::Instance().routeDeleteXray(m_xrayUplinkIface, m_xrayUplinkGateway); RouterMac::Instance().routeDeleteXray(it->uplinkIface, it->uplinkGateway);
m_xrayUplinkIface.clear();
m_xrayUplinkGateway.clear();
} }
#endif #endif
m_xrayWorkers.remove(ifname);
return true; return true;
} }
+12 -9
View File
@@ -55,8 +55,8 @@ public:
virtual bool refreshKillSwitch( bool enabled ) override; virtual bool refreshKillSwitch( bool enabled ) override;
virtual bool updateResolvers(const QString& ifname, const QList<QHostAddress>& resolvers) override; virtual bool updateResolvers(const QString& ifname, const QList<QHostAddress>& resolvers) override;
virtual bool restoreResolvers() override; virtual bool restoreResolvers() override;
virtual bool xrayStart(const QString& cfg) override; virtual bool xrayStart(const QString& ifname, const QString& cfg) override;
virtual bool xrayStop() override; virtual bool xrayStop(const QString& ifname) override;
virtual bool startNetworkCheck(const QString& serverIpv4Gateway, const QString& deviceIpv4Address) override; virtual bool startNetworkCheck(const QString& serverIpv4Gateway, const QString& deviceIpv4Address) override;
virtual bool stopNetworkCheck() override; virtual bool stopNetworkCheck() override;
@@ -78,16 +78,19 @@ private:
QMap<int, ProcessDescriptor> m_processes; QMap<int, ProcessDescriptor> m_processes;
PingHelper m_pingHelper; PingHelper m_pingHelper;
QSharedPointer<QProcess> m_xrayProcess; struct XrayWorker {
QByteArray m_xrayStdoutBuf; QSharedPointer<QProcess> process;
QPointer<QEventLoop> m_xrayStartLoop; QByteArray stdoutBuf;
bool m_xrayStartResult = false; QPointer<QEventLoop> startLoop;
bool startResult = false;
#ifdef Q_OS_MAC #ifdef Q_OS_MAC
QString m_xrayUplinkIface; QString uplinkIface;
QString m_xrayUplinkGateway; QString uplinkGateway;
#endif #endif
};
QHash<QString, XrayWorker> m_xrayWorkers;
void onXrayWorkerLine(const QByteArray& line); void onXrayWorkerLine(const QString& ifname, const QByteArray& line);
}; };
#endif // IPCSERVER_H #endif // IPCSERVER_H