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