mirror of
https://github.com/Qv2ray/Qv2ray.git
synced 2025-05-22 03:40:22 +08:00
292 lines
13 KiB
C++
292 lines
13 KiB
C++
#include "KernelInstanceHandler.hpp"
|
|
|
|
#include "core/CoreUtils.hpp"
|
|
#include "core/connection/Generation.hpp"
|
|
namespace Qv2ray::core::handlers
|
|
{
|
|
#define isConnected (vCoreInstance->KernelStarted || !activeKernels.isEmpty())
|
|
KernelInstanceHandler::KernelInstanceHandler(QObject *parent) : QObject(parent)
|
|
{
|
|
KernelInstance = this;
|
|
vCoreInstance = new V2rayKernelInstance(this);
|
|
connect(vCoreInstance, &V2rayKernelInstance::OnNewStatsDataArrived, this, &KernelInstanceHandler::OnStatsDataArrived_p);
|
|
connect(vCoreInstance, &V2rayKernelInstance::OnProcessOutputReadyRead, this, &KernelInstanceHandler::OnKernelLogAvailable_p);
|
|
connect(vCoreInstance, &V2rayKernelInstance::OnProcessErrored, this, &KernelInstanceHandler::OnKernelCrashed_p);
|
|
//
|
|
auto kernelList = PluginHost->GetPluginKernels();
|
|
for (const auto &kernelInfo : kernelList.keys())
|
|
{
|
|
auto kernel = kernelList.value(kernelInfo).get();
|
|
kernels[kernelInfo] = kernel;
|
|
connect(kernel, &QvPluginKernel::OnKernelCrashed, this, &KernelInstanceHandler::OnKernelCrashed_p);
|
|
connect(kernel, &QvPluginKernel::OnKernelLogAvaliable, this, &KernelInstanceHandler::OnKernelLogAvailable_p);
|
|
}
|
|
}
|
|
|
|
KernelInstanceHandler::~KernelInstanceHandler()
|
|
{
|
|
}
|
|
|
|
std::optional<QString> KernelInstanceHandler::StartConnection(const ConnectionId &id, const CONFIGROOT &root)
|
|
{
|
|
if (isConnected)
|
|
{
|
|
StopConnection();
|
|
}
|
|
activeKernels.clear();
|
|
this->root = root;
|
|
bool isComplex = IsComplexConfig(root);
|
|
auto fullConfig = GenerateRuntimeConfig(root);
|
|
inboundPorts = GetConfigInboundPorts(fullConfig);
|
|
PluginHost->Send_ConnectivityEvent({ GetDisplayName(id), inboundPorts, Events::Connectivity::QvConnecticity_Connecting });
|
|
QList<std::tuple<QString, int, QString>> inboundInfo;
|
|
for (const auto &inbound_v : fullConfig["inbounds"].toArray())
|
|
{
|
|
const auto &inbound = inbound_v.toObject();
|
|
inboundInfo.push_back({ inbound["protocol"].toString(), inbound["port"].toInt(), inbound["tag"].toString() });
|
|
}
|
|
//
|
|
if (GlobalConfig.pluginConfig.v2rayIntegration)
|
|
{
|
|
if (isComplex)
|
|
{
|
|
LOG(MODULE_CONNECTION, "WARNING: Complex connection config support of this feature has not been tested.")
|
|
}
|
|
QList<std::tuple<QString, QString, QString>> pluginProcessedOutboundList;
|
|
//
|
|
// Process outbounds.
|
|
{
|
|
OUTBOUNDS new_outbounds;
|
|
auto pluginPort = GlobalConfig.pluginConfig.portAllocationStart;
|
|
//
|
|
/// Key = Original Outbound Tag, Value = QStringList containing new outbound lists.
|
|
for (const auto &outbound_v : fullConfig["outbounds"].toArray())
|
|
{
|
|
const auto &outbound = outbound_v.toObject();
|
|
const auto &outProtocol = outbound["protocol"].toString();
|
|
//
|
|
if (!kernels.contains(outProtocol))
|
|
{
|
|
// Normal outbound, or the one without a plugin supported.
|
|
new_outbounds.push_back(outbound);
|
|
continue;
|
|
}
|
|
LOG(MODULE_CONNECTION, "Get kernel plugin: " + outProtocol)
|
|
auto &kernel = kernels[outProtocol];
|
|
disconnect(kernel, &QvPluginKernel::OnKernelStatsAvailable, this, &KernelInstanceHandler::OnStatsDataArrived_p);
|
|
activeKernels.insert(outProtocol, kernel);
|
|
//
|
|
QMap<QString, int> pluginInboundPort;
|
|
const auto &originalOutboundTag = outbound["tag"].toString();
|
|
for (const auto &[inProtocol, inPort, inTag] : inboundInfo)
|
|
{
|
|
if (!QStringList{ "http", "socks" }.contains(inProtocol))
|
|
continue;
|
|
pluginInboundPort.insert(inProtocol, pluginPort);
|
|
LOG(MODULE_VCORE, "Plugin Integration: " + QSTRN(pluginPort) + " = " + inProtocol + "(" + inTag + ") --> " + outProtocol)
|
|
//
|
|
const auto &freedomTag = "plugin_" + inTag + "_" + inProtocol + "-" + QSTRN(inPort) + "_" + QSTRN(pluginPort);
|
|
const auto &pluginOutSettings = GenerateHTTPSOCKSOut("127.0.0.1", pluginPort, false, "", "");
|
|
const auto &direct = GenerateOutboundEntry(inProtocol, pluginOutSettings, {}, {}, "0.0.0.0", freedomTag);
|
|
//
|
|
// Add the integration outbound to the list.
|
|
new_outbounds.push_back(direct);
|
|
|
|
LOG(MODULE_CONNECTION, "Appended originalOutboundTag, inTag, freedomTag into processedOutboundList")
|
|
pluginProcessedOutboundList.append({ originalOutboundTag, inTag, freedomTag });
|
|
pluginPort++;
|
|
}
|
|
|
|
LOG(MODULE_CONNECTION, "Sending connection settings to kernel.")
|
|
kernel->SetConnectionSettings(GlobalConfig.inboundConfig.listenip, pluginInboundPort, outbound["settings"].toObject());
|
|
}
|
|
LOG(MODULE_CONNECTION, "Applying new outbound settings.")
|
|
fullConfig["outbounds"] = new_outbounds;
|
|
}
|
|
//
|
|
// Process routing entries
|
|
{
|
|
LOG(MODULE_CONNECTION, "Started processing route tables.")
|
|
QJsonArray newRules;
|
|
auto unprocessedOutbound = pluginProcessedOutboundList;
|
|
const auto rules = fullConfig["routing"].toObject()["rules"].toArray();
|
|
for (auto i = 0; i < rules.count(); i++)
|
|
{
|
|
const auto rule = rules.at(i).toObject();
|
|
//
|
|
bool ruleProcessed = false;
|
|
for (const auto &[originalTag, inboundTag, newOutboundTag] : pluginProcessedOutboundList)
|
|
{
|
|
// Check if a rule corresponds to the plugin outbound.
|
|
if (rule["outboundTag"] == originalTag)
|
|
{
|
|
LOG(MODULE_CONNECTION, "Replacing existed plugin outbound rule.")
|
|
auto newRule = rule;
|
|
newRule["outboundTag"] = newOutboundTag;
|
|
newRule["inboundTag"] = QJsonArray{ inboundTag };
|
|
newRules.push_back(newRule);
|
|
ruleProcessed = true;
|
|
unprocessedOutbound.removeOne({ originalTag, inboundTag, newOutboundTag });
|
|
}
|
|
}
|
|
if (!ruleProcessed)
|
|
{
|
|
newRules.append(rule);
|
|
}
|
|
}
|
|
|
|
for (const auto &[originalTag, inboundTag, newOutboundTag] : unprocessedOutbound)
|
|
{
|
|
LOG(MODULE_CONNECTION, "Adding new plugin outbound rule.")
|
|
QJsonObject integrationRule;
|
|
integrationRule["type"] = "field";
|
|
integrationRule["outboundTag"] = newOutboundTag;
|
|
integrationRule["inboundTag"] = QJsonArray{ inboundTag };
|
|
newRules.push_back(integrationRule);
|
|
}
|
|
auto routing = fullConfig["routing"].toObject();
|
|
routing["rules"] = newRules;
|
|
fullConfig["routing"] = routing;
|
|
}
|
|
// ================================================================================================
|
|
//
|
|
currentConnectionId = id;
|
|
lastConnectionId = id;
|
|
bool success = true;
|
|
for (auto &kernel : activeKernels.keys())
|
|
{
|
|
LOG(MODULE_CONNECTION, "Starting kernel: " + kernel)
|
|
bool status = activeKernels[kernel]->StartKernel();
|
|
success = success && status;
|
|
if (!status)
|
|
{
|
|
LOG(MODULE_CONNECTION, "Plugin Kernel: " + kernel + " failed to start.")
|
|
break;
|
|
}
|
|
}
|
|
if (!success)
|
|
{
|
|
StopConnection();
|
|
return tr("A plugin kernel failed to start. Please check the outbound settings.");
|
|
}
|
|
//
|
|
auto result = vCoreInstance->StartConnection(fullConfig);
|
|
//
|
|
if (!result.has_value())
|
|
{
|
|
emit OnConnected(currentConnectionId);
|
|
PluginHost->Send_ConnectivityEvent({ GetDisplayName(id), inboundPorts, Events::Connectivity::QvConnecticity_Connected });
|
|
}
|
|
else
|
|
{
|
|
PluginHost->Send_ConnectivityEvent({ GetDisplayName(id), {}, Events::Connectivity::QvConnecticity_Disconnected });
|
|
}
|
|
return result;
|
|
}
|
|
else
|
|
{
|
|
LOG(MODULE_CONNECTION, "Starting kernel without V2ray Integration")
|
|
auto firstOutbound = fullConfig["outbounds"].toArray().first().toObject();
|
|
const auto protocol = firstOutbound["protocol"].toString();
|
|
if (kernels.contains(protocol))
|
|
{
|
|
LOG(MODULE_CONNECTION, "Found existing kernel for: " + protocol)
|
|
auto &kernel = kernels[firstOutbound["protocol"].toString()];
|
|
activeKernels[protocol] = kernel;
|
|
QMap<QString, int> pluginInboundPort;
|
|
for (const auto &[_protocol, _port, _tag] : inboundInfo)
|
|
{
|
|
pluginInboundPort[_protocol] = _port;
|
|
}
|
|
connect(kernel, &QvPluginKernel::OnKernelStatsAvailable, this, &KernelInstanceHandler::OnStatsDataArrived_p);
|
|
currentConnectionId = id;
|
|
lastConnectionId = id;
|
|
kernel->SetConnectionSettings(GlobalConfig.inboundConfig.listenip, pluginInboundPort, firstOutbound["settings"].toObject());
|
|
bool result = kernel->StartKernel();
|
|
if (result)
|
|
{
|
|
emit OnConnected(currentConnectionId);
|
|
return {};
|
|
}
|
|
else
|
|
{
|
|
return tr("A plugin kernel failed to start. Please check the outbound settings.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LOG(MODULE_CONNECTION, "Starting V2ray without kernel")
|
|
currentConnectionId = id;
|
|
lastConnectionId = id;
|
|
auto result = vCoreInstance->StartConnection(fullConfig);
|
|
if (result.has_value())
|
|
{
|
|
PluginHost->Send_ConnectivityEvent({ GetDisplayName(id), {}, Events::Connectivity::QvConnecticity_Disconnected });
|
|
}
|
|
else
|
|
{
|
|
emit OnConnected(currentConnectionId);
|
|
PluginHost->Send_ConnectivityEvent({ GetDisplayName(id), inboundPorts, Events::Connectivity::QvConnecticity_Connected });
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
void KernelInstanceHandler::RestartConnection()
|
|
{
|
|
StopConnection();
|
|
StartConnection(lastConnectionId, root);
|
|
}
|
|
|
|
void KernelInstanceHandler::OnKernelCrashed_p(const QString &msg)
|
|
{
|
|
StopConnection();
|
|
emit OnCrashed(currentConnectionId, msg);
|
|
emit OnDisconnected(currentConnectionId);
|
|
lastConnectionId = currentConnectionId;
|
|
currentConnectionId = NullConnectionId;
|
|
}
|
|
|
|
void KernelInstanceHandler::OnKernelLogAvailable_p(const QString &log)
|
|
{
|
|
emit OnKernelLogAvailable(currentConnectionId, log);
|
|
}
|
|
|
|
void KernelInstanceHandler::StopConnection()
|
|
{
|
|
if (isConnected)
|
|
{
|
|
PluginHost->Send_ConnectivityEvent({ GetDisplayName(currentConnectionId), {}, Events::Connectivity::QvConnecticity_Disconnecting });
|
|
if (vCoreInstance->KernelStarted)
|
|
{
|
|
vCoreInstance->StopConnection();
|
|
}
|
|
//
|
|
for (const auto &kernel : activeKernels.keys())
|
|
{
|
|
LOG(MODULE_CONNECTION, "Stopping plugin kernel: " + kernel)
|
|
disconnect(activeKernels[kernel], &QvPluginKernel::OnKernelStatsAvailable, this, &KernelInstanceHandler::OnStatsDataArrived_p);
|
|
activeKernels[kernel]->StopKernel();
|
|
}
|
|
// Copy
|
|
ConnectionId id = currentConnectionId;
|
|
currentConnectionId = NullConnectionId;
|
|
emit OnDisconnected(id);
|
|
PluginHost->Send_ConnectivityEvent({ GetDisplayName(id), {}, Events::Connectivity::QvConnecticity_Disconnected });
|
|
}
|
|
else
|
|
{
|
|
LOG(MODULE_CORE_HANDLER, "Cannot disconnect when there's nothing connected.")
|
|
}
|
|
}
|
|
|
|
void KernelInstanceHandler::OnStatsDataArrived_p(const quint64 uploadSpeed, const quint64 downloadSpeed)
|
|
{
|
|
if (isConnected)
|
|
{
|
|
emit OnStatsDataAvailable(currentConnectionId, uploadSpeed, downloadSpeed);
|
|
}
|
|
}
|
|
} // namespace Qv2ray::core::handlers
|