mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-23 02:00:20 +07:00
feat: key xray worker per tunnel via ifname-scoped IPC
This commit is contained in:
@@ -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";
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user