Implement new config gen

This commit is contained in:
Nova 2025-11-04 04:04:30 +03:30
parent 3e9a5aa5c3
commit 56d6394e9c
25 changed files with 1393 additions and 71 deletions

View File

@ -261,8 +261,12 @@ set(PROJECT_SOURCES
src/configs/outbounds/vmess.cpp
src/configs/outbounds/wireguard.cpp
include/configs/generate.h
src/configs/generate.cpp
include/configs/common/utils.h
src/configs/common/utils.cpp
include/configs/outbounds/Custom.h
include/configs/outbounds/Chain.h
include/configs/outbounds/extracore.h
)
if (NOT APPLE AND Qt6_VERSION VERSION_GREATER_EQUAL 6.9.0)

View File

@ -1,14 +1,15 @@
#pragma once
#include <QString>
#include <include/global/Utils.hpp>
#include "generate.h"
#include "common/Outbound.h"
#include "include/global/ConfigItem.hpp"
#include <QJsonObject>
#include "include/global/Configs.hpp"
namespace Configs
{
struct BuildResult {
QJsonObject object;
QString error;
};
class baseConfig : public JsonStore
{
public:
@ -22,34 +23,4 @@ namespace Configs
virtual BuildResult Build();
};
class outbound : public baseConfig
{
public:
std::shared_ptr<OutboundCommons> commons = std::make_shared<OutboundCommons>();
void ResolveDomainToIP(const std::function<void()> &onFinished);
virtual QString DisplayAddress()
{
return ::DisplayAddress(commons->server, commons->server_port);
}
QString DisplayName()
{
if (commons->name.isEmpty()) {
return DisplayAddress();
}
return commons->name;
}
virtual QString DisplayType() { return {}; };
QString DisplayTypeAndName()
{
return QString("[%1] %2").arg(DisplayType(), DisplayName());
}
static bool IsEndpoint() { return false; };
};
}

View File

@ -15,7 +15,7 @@ namespace Configs
DialFields()
{
_add(new configItem("reuse_addr", &reuse_addr, itemType::boolean));
_add(new configItem("connect_timeout", &connect_timeout, itemType::string));
_add(new configItem("connect_timeout", &connect_timeout, string));
_add(new configItem("tcp_fast_open", &tcp_fast_open, itemType::boolean));
_add(new configItem("tcp_multi_path", &tcp_multi_path, itemType::boolean));
_add(new configItem("udp_fragment", &udp_fragment, itemType::boolean));

View File

@ -7,7 +7,6 @@ namespace Configs
class OutboundCommons : public baseConfig
{
public:
virtual ~OutboundCommons() = default;
QString name;
QString server;
int server_port = 0;
@ -28,4 +27,39 @@ namespace Configs
QJsonObject ExportToJson() override;
BuildResult Build() override;
};
class outbound : public baseConfig
{
public:
std::shared_ptr<OutboundCommons> commons = std::make_shared<OutboundCommons>();
void ResolveDomainToIP(const std::function<void()> &onFinished);
virtual QString GetAddress()
{
return commons->server;
}
virtual QString DisplayAddress()
{
return ::DisplayAddress(commons->server, commons->server_port);
}
virtual QString DisplayName()
{
if (commons->name.isEmpty()) {
return DisplayAddress();
}
return commons->name;
}
virtual QString DisplayType() { return {}; };
QString DisplayTypeAndName()
{
return QString("[%1] %2").arg(DisplayType(), DisplayName());
}
virtual bool IsEndpoint() { return false; };
};
}

View File

@ -14,7 +14,7 @@ namespace Configs
uTLS()
{
_add(new configItem("enabled", &enabled, boolean));
_add(new configItem("enabled", &enabled, itemType::boolean));
_add(new configItem("fingerprint", &fingerPrint, string));
}
@ -35,7 +35,7 @@ namespace Configs
ECH()
{
_add(new configItem("enabled", &enabled, boolean));
_add(new configItem("enabled", &enabled, itemType::boolean));
_add(new configItem("config", &config, stringList));
_add(new configItem("config_path", &config_path, string));
}
@ -57,7 +57,7 @@ namespace Configs
Reality()
{
_add(new configItem("enabled", &enabled, boolean));
_add(new configItem("enabled", &enabled, itemType::boolean));
_add(new configItem("public_key", &public_key, string));
_add(new configItem("short_id", &short_id, string));
}
@ -98,10 +98,10 @@ namespace Configs
TLS()
{
_add(new configItem("enabled", &enabled, boolean));
_add(new configItem("disable_sni", &disable_sni, boolean));
_add(new configItem("enabled", &enabled, itemType::boolean));
_add(new configItem("disable_sni", &disable_sni, itemType::boolean));
_add(new configItem("server_name", &server_name, string));
_add(new configItem("insecure", &insecure, boolean));
_add(new configItem("insecure", &insecure, itemType::boolean));
_add(new configItem("alpn", &alpn, stringList));
_add(new configItem("min_version", &min_version, string));
_add(new configItem("max_version", &max_version, string));
@ -114,9 +114,9 @@ namespace Configs
_add(new configItem("client_certificate_path", &client_certificate_path, string));
_add(new configItem("client_key", &client_key, stringList));
_add(new configItem("client_key_path", &client_key_path, string));
_add(new configItem("fragment", &fragment, boolean));
_add(new configItem("fragment", &fragment, itemType::boolean));
_add(new configItem("fragment_fallback_delay", &fragment_fallback_delay, string));
_add(new configItem("record_fragment", &record_fragment, boolean));
_add(new configItem("record_fragment", &record_fragment, itemType::boolean));
_add(new configItem("ech", dynamic_cast<JsonStore *>(ech.get()), jsonStore));
_add(new configItem("utls", dynamic_cast<JsonStore *>(utls.get()), jsonStore));
_add(new configItem("reality", dynamic_cast<JsonStore *>(reality.get()), jsonStore));

View File

@ -1,10 +1,165 @@
#pragma once
#include <QJsonArray>
#include <QJsonObject>
#include <include/dataStore/ProxyEntity.hpp>
#include <include/stats/traffic/TrafficData.hpp>
namespace Configs
{
struct BuildResult {
QJsonObject object;
QString error;
enum OSType
{
Unknown = 0,
Linux = 1,
Windows = 2,
Darwin = 3,
};
class ExtraCoreData
{
public:
QString path;
QString args;
QString config;
QString configDir;
bool noLog;
};
class DNSDeps
{
public:
bool needDirectDnsRules = false;
QJsonArray directDomains;
QJsonArray directRuleSets;
QJsonArray directSuffixes;
QJsonArray directKeywords;
QJsonArray directRegexes;
};
class HijackDeps
{
public:
QJsonArray hijackDomains;
QJsonArray hijackDomainSuffix;
QJsonArray hijackDomainRegex;
QJsonArray hijackGeoAssets;
};
class TunDeps
{
public:
QJsonArray directIPSets;
QJsonArray directIPCIDRs;
};
class RoutingDeps
{
public:
int defaultOutboundID;
QList<int> neededOutbounds;
QStringList neededRuleSets;
std::map<int, QString> outboundMap;
};
class BuildPrerequisities
{
public:
std::shared_ptr<ExtraCoreData> extraCoreData = std::make_shared<ExtraCoreData>();
std::shared_ptr<DNSDeps> dnsDeps = std::make_shared<DNSDeps>();
std::shared_ptr<HijackDeps> hijackDeps = std::make_shared<HijackDeps>();
std::shared_ptr<TunDeps> tunDeps = std::make_shared<TunDeps>();
std::shared_ptr<RoutingDeps> routingDeps = std::make_shared<RoutingDeps>();
};
class BuildConfigResult {
public:
QString error;
QJsonObject coreConfig;
std::shared_ptr<ExtraCoreData> extraCoreData;
QList<std::shared_ptr<Stats::TrafficData>> outboundStats;
};
class BuildSingBoxConfigContext
{
public:
bool forTest = false;
bool forExport = false;
bool tunEnabled = false;
bool isResolvedUsed = false;
std::shared_ptr<ProxyEntity> ent = std::make_shared<ProxyEntity>(nullptr, nullptr);
std::shared_ptr<BuildPrerequisities> buildPrerequisities = std::make_shared<BuildPrerequisities>();
OSType os;
QString error;
QStringList warnings;
QJsonArray outbounds;
QJsonArray endpoints;
std::shared_ptr<BuildConfigResult> buildConfigResult = std::make_shared<BuildConfigResult>();
};
inline QString get_jsdelivr_link(QString link)
{
if(dataStore->routing->ruleset_mirror == Mirrors::GITHUB)
return link;
if(auto url = QUrl(link); url.isValid() && url.host() == "raw.githubusercontent.com")
{
QStringList list = url.path().split('/');
QString result;
switch(dataStore->routing->ruleset_mirror) {
case Mirrors::GCORE: result = "https://gcore.jsdelivr.net/gh"; break;
case Mirrors::QUANTIL: result = "https://quantil.jsdelivr.net/gh"; break;
case Mirrors::FASTLY: result = "https://fastly.jsdelivr.net/gh"; break;
case Mirrors::CDN: result = "https://cdn.jsdelivr.net/gh"; break;
default: result = "https://testingcf.jsdelivr.net/gh";
}
int index = 0;
foreach(QString item, list)
{
if(!item.isEmpty())
{
if(index == 2)
result += "@" + item;
else
result += "/" + item;
index++;
}
}
return result;
}
return link;
}
void CalculatePrerequisities(std::shared_ptr<BuildSingBoxConfigContext> &ctx);
void buildLogSections(std::shared_ptr<BuildSingBoxConfigContext> &ctx);
void buildDNSSection(std::shared_ptr<BuildSingBoxConfigContext> &ctx, bool useDnsObj = true);
void buildNTPSection(std::shared_ptr<BuildSingBoxConfigContext> &ctx);
void buildCertificateSection(std::shared_ptr<BuildSingBoxConfigContext> &ctx);
void buildInboundSection(std::shared_ptr<BuildSingBoxConfigContext> &ctx);
void buildOutboundsSection(std::shared_ptr<BuildSingBoxConfigContext> &ctx);
void buildRouteSection(std::shared_ptr<BuildSingBoxConfigContext> &ctx);
void buildExperimentalSection(std::shared_ptr<BuildSingBoxConfigContext> &ctx);
std::shared_ptr<BuildConfigResult> BuildSingBoxConfig(const std::shared_ptr<ProxyEntity> &ent);
class BuildTestConfigResult {
public:
QString error;
QMap<int, QString> fullConfigs;
QMap<QString, int> tag2entID;
QJsonObject coreConfig;
QStringList outboundTags;
};
bool IsValid(const std::shared_ptr<ProxyEntity> &ent);
std::shared_ptr<BuildTestConfigResult> BuildTestConfig(const QList<std::shared_ptr<ProxyEntity>>& profiles);
}

View File

@ -1,5 +1,4 @@
#pragma once
#include "include/configs/baseConfig.h"
#include "include/configs/common/Outbound.h"
#include "include/configs/common/TLS.h"

View File

@ -0,0 +1,26 @@
#pragma once
#include "include/configs/common/Outbound.h"
namespace Configs
{
class chain : public outbound
{
public:
QList<int> list; // from in to out
chain()
{
_add(new configItem("commons", dynamic_cast<JsonStore *>(commons.get()), jsonStore));
_add(new configItem("list", &list, integerList));
}
QString DisplayType() override { return QObject::tr("Chain Proxy"); };
QString DisplayAddress() override { return ""; };
BuildResult Build() override
{
return {{}, "Cannot call Build on chain config"};
}
};
}

View File

@ -0,0 +1,54 @@
#pragma once
#include "include/configs/common/Outbound.h"
namespace Configs
{
class custom : public outbound
{
public:
QString config;
QString type; // one of outbound or fullconfig
custom()
{
_add(new configItem("commons", dynamic_cast<JsonStore *>(commons.get()), jsonStore));
_add(new configItem("config", &config, string));
_add(new configItem("type", &type, string));
}
QString GetAddress() override
{
if (type == "outbound") {
auto obj = QString2QJsonObject(config);
return obj["server"].toString();
}
return {};
}
QString DisplayAddress() override
{
if (type == "outbound") {
auto obj = QString2QJsonObject(config);
return ::DisplayAddress(obj["server"].toString(), obj["server_port"].toInt());
}
return {};
}
QString DisplayType() override
{
if (type == "outbound") {
auto outboundType = QString2QJsonObject(config)["type"].toString();
if (!outboundType.isEmpty()) outboundType[0] = outboundType[0].toUpper();
return outboundType.isEmpty() ? "Custom Outbound" : "Custom " + outboundType + " Outbound";
} else if (type == "fullconfig") {
return "Custom Config";
}
return type;
};
BuildResult Build() override
{
return {QString2QJsonObject(config), ""};
}
};
}

View File

@ -0,0 +1,36 @@
#pragma once
#include "include/configs/common/Outbound.h"
namespace Configs
{
class extracore : public outbound {
public:
QString socksAddress = "127.0.0.1";
int socksPort;
QString extraCorePath;
QString extraCoreArgs;
QString extraCoreConf;
bool noLogs = false;
extracore() {
_add(new configItem("commons", dynamic_cast<JsonStore *>(commons.get()), jsonStore));
_add(new configItem("socks_address", &socksAddress, itemType::string));
_add(new configItem("socks_port", &socksPort, itemType::integer));
_add(new configItem("extra_core_path", &extraCorePath, itemType::string));
_add(new configItem("extra_core_args", &extraCoreArgs, itemType::string));
_add(new configItem("extra_core_conf", &extraCoreConf, itemType::string));
_add(new configItem("no_logs", &noLogs, itemType::boolean));
};
QString DisplayType() override { return "ExtraCore"; };
BuildResult Build() override
{
QJsonObject socksOutbound;
socksOutbound["type"] = "socks";
socksOutbound["server"] = socksAddress;
socksOutbound["server_port"] = socksPort;
return {socksOutbound, ""};
}
};
}

View File

@ -1,6 +1,5 @@
#pragma once
#include "include/configs/baseConfig.h"
#include "include/configs/common/DialFields.h"
#include "include/configs/common/Outbound.h"
#include "include/configs/common/TLS.h"

View File

@ -1,5 +1,4 @@
#pragma once
#include "include/configs/baseConfig.h"
#include "include/configs/common/Outbound.h"
#include "include/configs/common/TLS.h"

View File

@ -1,5 +1,4 @@
#pragma once
#include "include/configs/baseConfig.h"
#include "include/configs/common/Outbound.h"
#include "include/configs/common/TLS.h"

View File

@ -1,14 +1,10 @@
#pragma once
#include <Wbemidl.h>
#include "include/configs/baseConfig.h"
#include "include/configs/common/DialFields.h"
#include "include/configs/common/multiplex.h"
#include "include/configs/common/Outbound.h"
namespace Configs
{
inline QStringList shadowsocksMethods = {"2022-blake3-aes-128-gcm", "2022-blake3-aes-256-gcm", "2022-blake3-chacha20-poly1305", "none", "aes-128-gcm", "aes-192-gcm", "aes-256-gcm", "chacha20-ietf-poly1305", "xchacha20-ietf-poly1305", "aes-128-ctr", "aes-192-ctr", "aes-256-ctr", "aes-128-cfb", "aes-192-cfb", "aes-256-cfb", "rc4-md5", "chacha20-ietf", "xchacha20"};
class shadowsocks : public outbound

View File

@ -1,6 +1,5 @@
#pragma once
#include "include/configs/baseConfig.h"
#include "include/configs/common/DialFields.h"
#include "include/configs/common/Outbound.h"
namespace Configs

View File

@ -1,5 +1,4 @@
#pragma once
#include "include/configs/baseConfig.h"
#include "include/configs/common/Outbound.h"
namespace Configs

View File

@ -1,5 +1,4 @@
#pragma once
#include "include/configs/baseConfig.h"
#include "include/configs/common/Outbound.h"
namespace Configs
@ -44,7 +43,7 @@ namespace Configs
QString DisplayAddress() override;
QString DisplayType() override;
static bool IsEndpoint();
bool IsEndpoint() override;
};
}

View File

@ -1,6 +1,4 @@
#pragma once
#include "include/configs/baseConfig.h"
#include "include/configs/common/DialFields.h"
#include "include/configs/common/multiplex.h"
#include "include/configs/common/Outbound.h"
#include "include/configs/common/TLS.h"

View File

@ -1,5 +1,4 @@
#pragma once
#include "include/configs/baseConfig.h"
#include "include/configs/common/Outbound.h"
#include "include/configs/common/TLS.h"

View File

@ -1,5 +1,4 @@
#pragma once
#include "include/configs/baseConfig.h"
#include "include/configs/common/multiplex.h"
#include "include/configs/common/Outbound.h"
#include "include/configs/common/TLS.h"

View File

@ -1,6 +1,4 @@
#pragma once
#include "include/configs/baseConfig.h"
#include "include/configs/common/DialFields.h"
#include "include/configs/common/multiplex.h"
#include "include/configs/common/Outbound.h"
#include "include/configs/common/TLS.h"

View File

@ -1,5 +1,4 @@
#pragma once
#include "include/configs/baseConfig.h"
#include "include/configs/common/Outbound.h"
namespace Configs
@ -88,7 +87,7 @@ namespace Configs
QString DisplayAddress() override;
QString DisplayType() override;
static bool IsEndpoint();
bool IsEndpoint() override;
};
}

View File

@ -1,5 +1,24 @@
#pragma once
#include <include/configs/outbounds/tailscale.h>
#include <include/configs/outbounds/wireguard.h>
#include "include/configs/common/Outbound.h"
#include "include/configs/outbounds/anyTLS.h"
#include "include/configs/outbounds/chain.h"
#include "include/configs/outbounds/custom.h"
#include "include/configs/outbounds/extracore.h"
#include "include/configs/outbounds/socks.h"
#include "include/configs/outbounds/http.h"
#include "include/configs/outbounds/hysteria.h"
#include "include/configs/outbounds/hysteria2.h"
#include "include/configs/outbounds/shadowsocks.h"
#include "include/configs/outbounds/ssh.h"
#include "include/configs/outbounds/trojan.h"
#include "include/configs/outbounds/tuic.h"
#include "include/configs/outbounds/vless.h"
#include "include/configs/outbounds/vmess.h"
#include "include/global/Configs.hpp"
#include "include/global/CountryHelper.hpp"
#include "include/stats/traffic/TrafficData.hpp"
@ -44,12 +63,15 @@ namespace Configs {
QString ul_speed;
QString test_country;
std::shared_ptr<Configs::AbstractBean> bean;
std::shared_ptr<Configs::outbound> outbound;
std::shared_ptr<Stats::TrafficData> traffic_data = std::make_shared<Stats::TrafficData>("");
QString full_test_report;
ProxyEntity(Configs::AbstractBean *bean, const QString &type_);
ProxyEntity(Configs::outbound *outbound, const QString &type_);
[[nodiscard]] QString DisplayTestResult() const;
[[nodiscard]] QColor DisplayLatencyColor() const;
@ -106,5 +128,70 @@ namespace Configs {
[[nodiscard]] Configs::ExtraCoreBean *ExtraCoreBean() const {
return (Configs::ExtraCoreBean *) bean.get();
};
// Outbound getter functions
[[nodiscard]] Configs::socks *Socks() const {
return dynamic_cast<Configs::socks *>(outbound.get());
};
[[nodiscard]] Configs::http *Http() const {
return dynamic_cast<Configs::http *>(outbound.get());
};
[[nodiscard]] Configs::shadowsocks *ShadowSocks() const {
return dynamic_cast<Configs::shadowsocks *>(outbound.get());
};
[[nodiscard]] Configs::vmess *VMess() const {
return dynamic_cast<Configs::vmess *>(outbound.get());
};
[[nodiscard]] Configs::Trojan *Trojan() const {
return dynamic_cast<Configs::Trojan *>(outbound.get());
};
[[nodiscard]] Configs::vless *VLESS() const {
return dynamic_cast<Configs::vless *>(outbound.get());
};
[[nodiscard]] Configs::anyTLS *AnyTLS() const {
return dynamic_cast<Configs::anyTLS *>(outbound.get());
};
[[nodiscard]] Configs::hysteria *Hysteria() const {
return dynamic_cast<Configs::hysteria *>(outbound.get());
};
[[nodiscard]] Configs::hysteria2 *Hysteria2() const {
return dynamic_cast<Configs::hysteria2 *>(outbound.get());
};
[[nodiscard]] Configs::ssh *SSH() const {
return dynamic_cast<Configs::ssh *>(outbound.get());
};
[[nodiscard]] Configs::tailscale *Tailscale() const {
return dynamic_cast<Configs::tailscale *>(outbound.get());
};
[[nodiscard]] Configs::tuic *TUIC() const {
return dynamic_cast<Configs::tuic *>(outbound.get());
};
[[nodiscard]] Configs::wireguard *Wireguard() const {
return dynamic_cast<Configs::wireguard *>(outbound.get());
};
[[nodiscard]] Configs::custom *Custom() const {
return dynamic_cast<Configs::custom *>(outbound.get());
};
[[nodiscard]] Configs::chain *Chain() const {
return dynamic_cast<Configs::chain *>(outbound.get());
};
[[nodiscard]] Configs::extracore *ExtraCore() const {
return dynamic_cast<Configs::extracore *>(outbound.get());
};
};
} // namespace Configs

953
src/configs/generate.cpp Normal file
View File

@ -0,0 +1,953 @@
#include "include/configs/generate.h"
#include "include/dataStore/Database.hpp"
#include "include/configs/proxy/includes.h"
#include "include/configs/proxy/Preset.hpp"
#include "include/api/RPC.h"
#include "include/global/Configs.hpp"
#include "include/global/Utils.hpp"
#include <QApplication>
#include <QFile>
#include <QFileInfo>
#include <include/configs/ConfigBuilder.hpp>
namespace Configs {
QString genTunName() {
auto tun_name = "throne-tun";
#ifdef Q_OS_MACOS
tun_name = "";
#endif
return tun_name;
}
void MergeJson(const QJsonObject &custom, QJsonObject &outbound) {
if (custom.isEmpty()) return;
for (const auto &key: custom.keys()) {
if (outbound.contains(key)) {
auto v = custom[key];
auto v_orig = outbound[key];
if (v.isObject() && v_orig.isObject()) {
auto vo = v.toObject();
QJsonObject vo_orig = v_orig.toObject();
MergeJson(vo, vo_orig);
outbound[key] = vo_orig;
} else {
outbound[key] = v;
}
} else {
outbound[key] = custom[key];
}
}
}
inline OSType getOS()
{
#ifdef Q_OS_MACOS
return Darwin;
#endif
#ifdef Q_OS_LINUX
return Linux;
#endif
#ifdef Q_OS_WIN
return Windows;
#endif
return Unknown;
}
QStringList getChainDomains (const std::shared_ptr<ProxyEntity>& ent, QString &error)
{
QStringList domains;
auto chain = ent->Chain();
if (!chain)
{
error = "Ent is Nullptr after cast to chain in getChainDomains, data is corrupted";
return domains;
}
auto entIDs = ent->Chain()->list;
for (int id : entIDs)
{
if (auto subEnt = profileManager->GetProfile(id); subEnt != nullptr)
{
if (auto addr = subEnt->outbound->GetAddress(); !addr.isEmpty() && !IsIpAddress(addr)) domains.append(addr);
}
}
return domains;
}
QStringList getEntDomains(const QList<int>& entIDs, QString &error)
{
QStringList domains;
for (const auto &id: entIDs)
{
if (auto ent = profileManager->GetProfile(id); ent != nullptr)
{
if (ent->type == "extracore") continue;
if (ent->type == "chain") domains << getChainDomains(ent, error);
else
{
if (auto addr = ent->outbound->GetAddress(); !addr.isEmpty() && !IsIpAddress(addr)) domains.append(addr);
}
}
}
return domains;
}
void CalculatePrerequisities(std::shared_ptr<BuildSingBoxConfigContext> &ctx) {
ctx->tunEnabled = dataStore->spmode_vpn;
ctx->os = getOS();
if (ctx->os == Linux)
{
ctx->isResolvedUsed = ReadFileText("/etc/resolv.conf").contains("systemd-resolved");
}
auto preReqs = ctx->buildPrerequisities;
// Get route chain
auto routeChain = profileManager->GetRouteChain(dataStore->routing->current_route_id);
if (routeChain == nullptr) {
ctx->error = "Routing profile does not exist, try resetting the route profile in Routing Settings";
return;
}
// Routing dependencies
auto neededOutbounds = routeChain->get_used_outbounds();
auto neededRuleSets = routeChain->get_used_rule_sets();
preReqs->routingDeps->defaultOutboundID = routeChain->defaultOutboundID;
preReqs->routingDeps->outboundMap[-1] = "proxy";
preReqs->routingDeps->outboundMap[-2] = "direct";
int suffix = 0;
for (const auto &item: *neededOutbounds) {
if (item < 0) continue;
auto neededEnt = profileManager->GetProfile(item);
if (neededEnt == nullptr) {
ctx->error = "The routing profile is referencing outbounds that no longer exists, consider revising your settings";
return;
}
if (neededEnt->type == "extracore" || neededEnt->type == "custom" || neededEnt->type == "chain")
{
ctx->error = "Outbounds used in routing profile cannot be of types extracore, custom or chain";
return;
}
if (auto entAddrs = getEntDomains({neededEnt->id}, ctx->error); !entAddrs.empty())
{
if (!ctx->error.isEmpty()) return;
for (const auto &addr: entAddrs)
{
preReqs->dnsDeps->directDomains << addr;
}
preReqs->dnsDeps->needDirectDnsRules = true;
}
preReqs->routingDeps->outboundMap[item] = "route-" + Int2String(suffix++);
preReqs->routingDeps->neededOutbounds << item;
}
for (const auto &item: *neededRuleSets) {
preReqs->routingDeps->neededRuleSets << item;
}
// Direct domains
auto sets = routeChain->get_direct_sites();
for (const auto &item: sets) {
if (item.startsWith("ruleset:")) {
preReqs->dnsDeps->directRuleSets << item.mid(8);
}
if (item.startsWith("domain:")) {
preReqs->dnsDeps->directDomains << item.mid(7);
}
if (item.startsWith("suffix:")) {
preReqs->dnsDeps->directSuffixes << item.mid(7);
}
if (item.startsWith("keyword:")) {
preReqs->dnsDeps->directKeywords << item.mid(8);
}
if (item.startsWith("regex:")) {
preReqs->dnsDeps->directRegexes << item.mid(6);
}
preReqs->dnsDeps->needDirectDnsRules = true;
}
if (auto entAddrs = getEntDomains({ctx->ent->id}, ctx->error); !entAddrs.isEmpty())
{
if (!ctx->error.isEmpty()) return;
for (const auto &addr: entAddrs) preReqs->dnsDeps->directDomains << addr;
preReqs->dnsDeps->needDirectDnsRules = true;
}
if (auto group = profileManager->GetGroup(ctx->ent->gid); group != nullptr)
{
QList<int> groupEnts;
if (auto frontEntID = group->front_proxy_id; frontEntID >= 0) groupEnts << frontEntID;
if (auto landingEntID = group->landing_proxy_id; landingEntID >= 0) groupEnts << landingEntID;
auto addrs = getEntDomains(groupEnts, ctx->error);
if (!ctx->error.isEmpty()) return;
for (const auto &addr: addrs)
{
preReqs->dnsDeps->directDomains << addr;
}
}
// Hijack
if (dataStore->enable_dns_server) {
for (const auto& rule : dataStore->dns_server_rules) {
if (rule.startsWith("ruleset:")) {
preReqs->hijackDeps->hijackGeoAssets << rule.mid(8);
}
if (rule.startsWith("domain:")) {
preReqs->hijackDeps->hijackDomains << rule.mid(7);
}
if (rule.startsWith("suffix:")) {
preReqs->hijackDeps->hijackDomainSuffix << rule.mid(7);
}
if (rule.startsWith("regex:")) {
preReqs->hijackDeps->hijackDomainRegex << rule.mid(6);
}
}
}
for (auto ruleSet : preReqs->hijackDeps->hijackGeoAssets) {
if (!preReqs->routingDeps->neededRuleSets.contains(ruleSet.toString())) preReqs->routingDeps->neededRuleSets.append(ruleSet.toString());
}
// Direct IPs
auto directIPraw = routeChain->get_direct_ips();
for (const auto &item: directIPraw) {
if (item.startsWith("ruleset:")) {
preReqs->tunDeps->directIPSets << item.mid(8);
}
if (item.startsWith("ip:")) {
preReqs->tunDeps->directIPCIDRs << item.mid(3);
}
}
// Extra core
if (ctx->ent->type == "extracore")
{
auto outbound = ctx->ent->ExtraCore();
if (outbound == nullptr)
{
MW_show_log("INVALID ENT TYPE, NEEDED EXTRACORE GOT NULLPTR");
ctx->error = "failed to cast to extracore, type is: " + ctx->ent->type;
return;
}
preReqs->extraCoreData->path = QFileInfo(outbound->extraCorePath).canonicalFilePath();
preReqs->extraCoreData->args = outbound->extraCoreArgs;
preReqs->extraCoreData->config = outbound->extraCoreConf;
preReqs->extraCoreData->configDir = GetBasePath();
preReqs->extraCoreData->noLog = outbound->noLogs;
}
}
void buildLogSections(std::shared_ptr<BuildSingBoxConfigContext> &ctx) {
ctx->buildConfigResult->coreConfig.insert("log", QJsonObject{{"level", dataStore->log_level}});
}
void buildNTPSection(std::shared_ptr<BuildSingBoxConfigContext> &ctx) {
if (dataStore->enable_ntp) {
QJsonObject ntpObj;
ntpObj["enabled"] = true;
ntpObj["server"] = dataStore->ntp_server_address;
ntpObj["server_port"] = dataStore->ntp_server_port;
ntpObj["interval"] = dataStore->ntp_interval;
ctx->buildConfigResult->coreConfig["ntp"] = ntpObj;
}
}
void buildCertificateSection(std::shared_ptr<BuildSingBoxConfigContext> &ctx) {
ctx->buildConfigResult->coreConfig.insert("certificate", QJsonObject{{"store", dataStore->use_mozilla_certs ? "mozilla" : "system"}});
}
QJsonObject buildDnsObj(QString address, std::shared_ptr<BuildSingBoxConfigContext> &ctx) {
if (address.startsWith("local")) {
if (ctx->tunEnabled && ctx->isResolvedUsed) {
return {{"type", "underlying"}};
}
if (ctx->tunEnabled && ctx->os == Darwin) {
return {
{"type", "udp"},
{"server", dataStore->core_box_underlying_dns}
};
}
return {{"type", "local"}};
}
if (address.startsWith("dhcp://")) {
auto ifcName = address.replace("dhcp://", "");
if (ifcName == "auto") ifcName = "";
return {
{"type", "dhcp"},
{"interface", ifcName},
};
}
QString addr = address;
int port = -1;
QString type = "udp";
QString path = "";
if (address.startsWith("tcp://")) {
type = "tcp";
addr = addr.replace("tcp://", "");
}
if (address.startsWith("tls://")) {
type = "tls";
addr = addr.replace("tls://", "");
}
if (address.startsWith("quic://")) {
type = "quic";
addr = addr.replace("quic://", "");
}
if (address.startsWith("https://")) {
type = "https";
addr = addr.replace("https://", "");
if (addr.contains("/")) {
path = addr.split("/").last();
addr = addr.left(addr.indexOf("/"));
}
}
if (address.startsWith("h3://")) {
type = "h3";
addr = addr.replace("h3://", "");
if (addr.contains("/")) {
path = addr.split("/").last();
addr = addr.left(addr.indexOf("/"));
}
}
if (addr.contains(":")) {
auto spl = addr.split(":");
addr = spl[0];
port = spl[1].toInt();
}
QJsonObject res = {
{"type", type},
{"server", addr},
};
if (port != -1) res["server_port"] = port;
if (!path.isEmpty()) res["path"] = path;
return res;
}
void buildDNSSection(std::shared_ptr<BuildSingBoxConfigContext> &ctx, bool useDnsObj) {
if (getOS() == Darwin && dataStore->core_box_underlying_dns.isEmpty() && dataStore->spmode_vpn)
{
ctx->error = QObject::tr("Local DNS and Tun mode do not work together, please set an IP to be used as the Local DNS server in the Routing Settings -> Local override");
return;
}
if (dataStore->routing->use_dns_object && useDnsObj) {
ctx->buildConfigResult->coreConfig["dns"] = QString2QJsonObject(dataStore->routing->dns_object);
return;
}
auto dnsDeps = ctx->buildPrerequisities->dnsDeps;
auto hijackDeps = ctx->buildPrerequisities->hijackDeps;
bool isTailscale = ctx->ent->type == "tailscale";
bool independentCache = false;
QJsonArray servers;
QJsonArray rules;
// remote
if (isTailscale)
{
auto tailscale = ctx->ent->Tailscale();
if (tailscale == nullptr)
{
ctx->error = "Corrupted state, needed tailscale been but could not cast";
return;
}
auto tailDns = QJsonObject{
{"type", "tailscale"},
{"tag", "dns-remote"},
{"endpoint", "proxy"},
{"accept_default_resolvers", tailscale->globalDNS},
};
servers += tailDns;
} else
{
auto remoteDnsObj = buildDnsObj(dataStore->routing->remote_dns, ctx);
remoteDnsObj["tag"] = "dns-remote";
remoteDnsObj["domain_resolver"] = "dns-local";
remoteDnsObj["detour"] = "proxy";
servers += remoteDnsObj;
}
// direct
auto directDnsObj = buildDnsObj(dataStore->routing->direct_dns, ctx);
directDnsObj["tag"] = "dns-direct";
directDnsObj["domain_resolver"] = "dns-local";
if (dataStore->routing->dns_final_out == "direct") {
servers.prepend(directDnsObj);
} else {
servers.append(directDnsObj);
}
// Handle localhost
rules += QJsonObject{
{"domain", "localhost"},
{"action", "predefined"},
{"query_type", "A"},
{"rcode", "NOERROR"},
{"answer", "localhost. IN A 127.0.0.1"},
};
rules += QJsonObject{
{"domain", "localhost"},
{"action", "predefined"},
{"query_type", "AAAA"},
{"rcode", "NOERROR"},
{"answer", "localhost. IN AAAA ::1"},
};
// HijackRules
if (dataStore->enable_dns_server && !ctx->forTest)
{
rules += QJsonObject{
{"rule_set", hijackDeps->hijackGeoAssets},
{"domain", hijackDeps->hijackDomains},
{"domain_suffix", hijackDeps->hijackDomainSuffix},
{"domain_regex", hijackDeps->hijackDomainRegex},
{"query_type", "A"},
{"action", "predefined"},
{"rcode", "NOERROR"},
{"answer", QString("* IN A %1").arg(dataStore->dns_v4_resp)},
};
if (!dataStore->dns_v6_resp.isEmpty())
{
rules += QJsonObject{
{"rule_set", hijackDeps->hijackGeoAssets},
{"domain", hijackDeps->hijackDomains},
{"domain_suffix", hijackDeps->hijackDomainSuffix},
{"domain_regex", hijackDeps->hijackDomainRegex},
{"query_type", "AAAA"},
{"action", "predefined"},
{"rcode", "NOERROR"},
{"answer", QString("* IN AAAA %1").arg(dataStore->dns_v6_resp)},
};
}
}
// FakeIP
if (dataStore->fake_dns) {
servers += QJsonObject{
{"tag", "dns-fake"},
{"type", "fakeip"},
{"inet4_range", "198.18.0.0/15"},
{"inet6_range", "fc00::/18"},
};
rules += QJsonObject{
{"query_type", QJsonArray{
"A",
"AAAA"
}},
{"action", "route"},
{"server", "dns-fake"}
};
independentCache = true;
}
if (dnsDeps->needDirectDnsRules) {
rules += QJsonObject{
{"rule_set", dnsDeps->directRuleSets},
{"domain", dnsDeps->directDomains},
{"domain_suffix", dnsDeps->directSuffixes},
{"domain_keyword", dnsDeps->directKeywords},
{"domain_regex", dnsDeps->directRegexes},
{"action", "route"},
{"server", "dns-direct"},
};
}
// Local
auto dnsLocalAddress = dataStore->core_box_underlying_dns.isEmpty() ? "local" : dataStore->core_box_underlying_dns;
auto dnsLocalObj = BuildDnsObject(dnsLocalAddress, dataStore->spmode_vpn);
dnsLocalObj["tag"] = "dns-local";
servers += dnsLocalObj;
auto dnsObj = QJsonObject{
{"servers", servers},
{"rules", rules}
};
if (independentCache) dnsObj["independent_cache"] = true;
ctx->buildConfigResult->coreConfig["dns"] = dnsObj;
}
void buildInboundSection(std::shared_ptr<BuildSingBoxConfigContext> &ctx) {
auto tunDeps = ctx->buildPrerequisities->tunDeps;
QJsonArray inbounds;
// mixed
if (!ctx->forTest) {
QJsonObject inboundObj;
inboundObj["tag"] = "mixed-in";
inboundObj["type"] = "mixed";
inboundObj["listen"] = dataStore->inbound_address;
inboundObj["listen_port"] = dataStore->inbound_socks_port;
inbounds += inboundObj;
}
// Tun
if (dataStore->spmode_vpn && !ctx->forTest) {
QJsonObject inboundObj;
inboundObj["tag"] = "tun-in";
inboundObj["type"] = "tun";
inboundObj["interface_name"] = genTunName();
inboundObj["auto_route"] = true;
inboundObj["mtu"] = dataStore->vpn_mtu;
inboundObj["stack"] = dataStore->vpn_implementation;
inboundObj["strict_route"] = dataStore->vpn_strict_route;
if (ctx->os == Linux) inboundObj["auto_redirect"] = true;
auto tunAddress = QJsonArray{"172.19.0.1/24"};
if (dataStore->vpn_ipv6) tunAddress += "fdfe:dcba:9876::1/96";
inboundObj["address"] = tunAddress;
if (ctx->buildPrerequisities->routingDeps->defaultOutboundID == proxyID && dataStore->enable_tun_routing)
{
QJsonArray routeExcludeAddrs = {"127.0.0.0/8"};
QJsonArray routeExcludeSets;
for (auto item: tunDeps->directIPCIDRs) routeExcludeAddrs << item;
for (auto item: tunDeps->directIPSets) routeExcludeSets << item;
inboundObj["route_exclude_address"] = routeExcludeAddrs;
if (!routeExcludeSets.isEmpty()) inboundObj["route_exclude_address_set"] = routeExcludeSets;
}
inbounds += inboundObj;
}
// Hijack
if (dataStore->enable_redirect && !ctx->forTest) {
inbounds.prepend(QJsonObject{
{"tag", "hijack"},
{"type", "direct"},
{"listen", dataStore->redirect_listen_address},
{"listen_port", dataStore->redirect_listen_port},
});
}
if (dataStore->enable_dns_server && !ctx->forTest) {
inbounds.prepend(QJsonObject{
{"tag", "dns-in"},
{"type", "direct"},
{"listen", dataStore->dns_server_listen_lan ? "0.0.0.0" : "127.1.1.1"},
{"listen_port", dataStore->dns_server_listen_port},
});
}
// custom
if (!ctx->forTest) QJSONARRAY_ADD(inbounds, QString2QJsonObject(dataStore->custom_inbound)["inbounds"].toArray())
ctx->buildConfigResult->coreConfig["inbounds"] = inbounds;
}
void unwrapChain(const QList<int>& entIDs, QList<std::shared_ptr<ProxyEntity>> &ents, QString& error)
{
bool hasExtracore = false;
bool hasCustom = false;
for (auto id : entIDs)
{
auto ent = profileManager->GetProfile(id);
if (ent == nullptr)
{
error = "Null proxy in chain, you may want to check your configs";
return;
}
if (ent->type == "chain")
{
error = "Chain in Chain is not allowed";
return;
}
if (ent->type == "extracore") hasExtracore = true;
if (ent->type == "custom") hasCustom = true;
ents.append(ent);
}
if (ents.size() > 1 && (hasExtracore || hasCustom))
{
error = "Cannot use Extracore or Custom configs in a chain";
}
}
void buildOutboundChain(std::shared_ptr<BuildSingBoxConfigContext> &ctx, const QList<int>& entIDs, const QString& prefix, bool includeProxy, bool link)
{
QList<std::shared_ptr<ProxyEntity>> ents;
unwrapChain(entIDs, ents, ctx->error);
for (int idx = 0; idx < ents.size(); idx++)
{
auto tag = prefix + "-" + Int2String(idx);
QString nextTag;
if (idx < ents.size() - 1) nextTag = prefix + "-" + Int2String(idx+1);
if (includeProxy && idx == 0) tag = "proxy";
const auto& ent = ents[idx];
auto [object, error] = ent->outbound->Build();
if (!error.isEmpty())
{
ctx->error += error;
return;
}
object["tag"] = tag;
if (!nextTag.isEmpty() && link) object["detour"] = nextTag;
if (ent->outbound->IsEndpoint())
{
ctx->endpoints.append(object);
} else
{
ctx->outbounds.append(object);
}
ent->traffic_data->id = ent->id;
ent->traffic_data->tag = tag.toStdString();
ctx->buildConfigResult->outboundStats += ent->traffic_data;
}
}
void buildOutboundsSection(std::shared_ptr<BuildSingBoxConfigContext> &ctx) {
// First, our own ent
QList<int> entIDs;
auto group = profileManager->GetGroup(ctx->ent->gid);
if (group == nullptr)
{
ctx->error = "No group found for ent, data is corrupted";
return;
}
if (group->landing_proxy_id >= 0) entIDs.append(group->landing_proxy_id);
if (ctx->ent->type == "chain")
{
auto chain = ctx->ent->Chain();
if (chain == nullptr)
{
ctx->error = "Ent is nullptr after cast to chain, data is corrupted";
return;
}
entIDs << chain->list;
} else
{
entIDs.append(ctx->ent->id);
}
if (group->front_proxy_id >= 0) entIDs.append(group->front_proxy_id);
buildOutboundChain(ctx, entIDs, "config", true, true);
// Now, build the outbounds needed by the route profile
buildOutboundChain(ctx, ctx->buildPrerequisities->routingDeps->neededOutbounds, "route", false, false);
// Add the direct outbound
ctx->outbounds.append(QJsonObject{
{"type", "direct"},
{"tag", "direct"}
});
ctx->buildConfigResult->coreConfig["endpoints"] = ctx->endpoints;
ctx->buildConfigResult->coreConfig["outbounds"] = ctx->outbounds;
}
void buildRouteSection(std::shared_ptr<BuildSingBoxConfigContext> &ctx) {
auto routeChain = profileManager->GetRouteChain(dataStore->routing->current_route_id);
if (routeChain == nullptr) {
ctx->error = "Routing profile does not exist, try resetting the route profile in Routing Settings";
return;
}
routeChain = std::make_shared<RoutingChain>(*routeChain);
auto routeDeps = ctx->buildPrerequisities->routingDeps;
// hijack
if (dataStore->enable_dns_server && !ctx->forTest)
{
auto sniffRule = std::make_shared<RouteRule>();
sniffRule->action = "sniff";
sniffRule->inbound = {"dns-in"};
auto redirRule = std::make_shared<RouteRule>();
redirRule->action = "hijack-dns";
redirRule->inbound = {"dns-in"};
routeChain->Rules.prepend(redirRule);
routeChain->Rules.prepend(sniffRule);
}
if (dataStore->enable_redirect && !ctx->forTest) {
auto sniffRule = std::make_shared<RouteRule>();
sniffRule->action = "sniff";
sniffRule->sniffOverrideDest = true;
sniffRule->inbound = {"hijack"};
routeChain->Rules.prepend(sniffRule);
}
// sniff and resolve
if (!dataStore->routing->domain_strategy.isEmpty())
{
auto resolveRule = std::make_shared<RouteRule>();
resolveRule->action = "resolve";
resolveRule->strategy = dataStore->routing->domain_strategy;
resolveRule->inbound = {"mixed-in", "tun-in"};
routeChain->Rules.prepend(resolveRule);
}
if (dataStore->routing->sniffing_mode != SniffingMode::DISABLE)
{
auto sniffRule = std::make_shared<RouteRule>();
sniffRule->action = "sniff";
sniffRule->inbound = {"mixed-in", "tun-in"};
routeChain->Rules.prepend(sniffRule);
}
// rules
auto routeRules = routeChain->get_route_rules(false, routeDeps->outboundMap);
// rulesets
auto ruleSetArray = QJsonArray();
for (const auto &item: routeDeps->neededRuleSets) {
if(auto url = QUrl(item); url.isValid() && url.fileName().contains(".srs")) {
ruleSetArray += QJsonObject{
{"type", "remote"},
{"tag", get_rule_set_name(item)},
{"format", "binary"},
{"url", item},
};
}
else
if(ruleSetMap.contains(item.toStdString())) {
ruleSetArray += QJsonObject{
{"type", "remote"},
{"tag", item},
{"format", "binary"},
{"url", get_jsdelivr_link(QString::fromStdString(ruleSetMap.at(item.toStdString())))},
};
}
}
// add block
if (dataStore->adblock_enable) {
ruleSetArray += QJsonObject{
{"type", "remote"},
{"tag", "throne-adblocksingbox"},
{"format", "binary"},
{"url", get_jsdelivr_link("https://raw.githubusercontent.com/217heidai/adblockfilters/main/rules/adblocksingbox.srs")},
};
}
// apply
QJsonObject route;
route["rules"] = routeRules;
route["rule_set"] = ruleSetArray;
route["final"] = outboundIDToString(routeChain->defaultOutboundID);
if (dataStore->spmode_vpn) route["auto_detect_interface"] = true;
ctx->buildConfigResult->coreConfig["route"] = route;
}
void buildExperimentalSection(std::shared_ptr<BuildSingBoxConfigContext> &ctx) {
if (ctx->forTest) return;
QJsonObject experimentalObj;
QJsonObject clash_api = {
{"default_mode", ""} // dummy to make sure it is created
};
if (dataStore->core_box_clash_api > 0){
clash_api = {
{"external_controller", dataStore->core_box_clash_listen_addr + ":" + Int2String(dataStore->core_box_clash_api)},
{"secret", dataStore->core_box_clash_api_secret},
{"external_ui", "dashboard"},
};
}
if (dataStore->core_box_clash_api > 0 || dataStore->enable_stats)
{
experimentalObj["clash_api"] = clash_api;
}
QJsonObject cache_file = {
{"enabled", true},
{"store_fakeip", true},
{"store_rdrc", true}
};
experimentalObj["cache_file"] = cache_file;
if (experimentalObj.isEmpty()) return;
// apply
ctx->buildConfigResult->coreConfig["experimental"] = experimentalObj;
}
std::shared_ptr<BuildConfigResult> BuildSingBoxConfig(const std::shared_ptr<ProxyEntity>& ent) {
if (ent->type == "custom")
{
auto res = std::make_shared<BuildConfigResult>();
auto custom = ent->Custom();
if (custom == nullptr)
{
res->error = "Corrupted data, needed custom ent, got nullptr";
return res;
}
if (custom->type == "fullconfig")
{
res->coreConfig = custom->Build().object;
return res;
}
}
auto ctx = std::make_shared<BuildSingBoxConfigContext>();
ctx->ent = ent;
// log
buildLogSections(ctx);
// ntp
buildNTPSection(ctx);
// DNS
buildDNSSection(ctx);
if (!ctx->error.isEmpty())
{
ctx->buildConfigResult->error = ctx->error;
return ctx->buildConfigResult;
}
// certificate
buildCertificateSection(ctx);
// Inbound
buildInboundSection(ctx);
if (!ctx->error.isEmpty())
{
ctx->buildConfigResult->error = ctx->error;
return ctx->buildConfigResult;
}
// outbound
buildOutboundsSection(ctx);
if (!ctx->error.isEmpty())
{
ctx->buildConfigResult->error = ctx->error;
return ctx->buildConfigResult;
}
// Route
buildRouteSection(ctx);
if (!ctx->error.isEmpty())
{
ctx->buildConfigResult->error = ctx->error;
return ctx->buildConfigResult;
}
// experimental
buildExperimentalSection(ctx);
if (!ctx->error.isEmpty())
{
ctx->buildConfigResult->error = ctx->error;
return ctx->buildConfigResult;
}
return ctx->buildConfigResult;
}
bool IsValid(const std::shared_ptr<ProxyEntity>& ent)
{
if (ent->type == "chain")
{
auto chain = ent->Chain();
if (chain == nullptr)
{
MW_show_log("Corrupted data, needed chain ent, got nullptr");
return false;
}
for (int eId : chain->list)
{
auto e = profileManager->GetProfile(eId);
if (e == nullptr)
{
MW_show_log("Null ent in validator");
return false;
}
if (!IsValid(e))
{
MW_show_log("Invalid ent in chain: ID=" + QString::number(eId));
return false;
}
}
return true;
}
QJsonObject conf;
bool fullConf = false;
if (ent->type == "custom")
{
auto custom = ent->Custom();
if (custom == nullptr)
{
MW_show_log("Corrupted data in isValid, needed custom ent, got nullptr");
return false;
}
if (custom->type == "fullconfig")
{
conf = QString2QJsonObject(custom->config);
fullConf = true;
}
}
if (!fullConf)
{
auto out = ent->outbound->Build();
auto outArr = QJsonArray{out.object};
auto key = ent->outbound->IsEndpoint() ? "endpoints" : "outbounds";
conf = {
{key, outArr},
};
}
bool ok;
auto resp = API::defaultClient->CheckConfig(&ok, QJsonObject2QString(conf, true));
if (!ok)
{
MW_show_log("Failed to Call the Core: " + resp);
return false;
}
if (resp.isEmpty()) return true;
// else
MW_show_log("Invalid ent " + ent->outbound->commons->name + ": " + resp);
return false;
}
std::shared_ptr<BuildTestConfigResult> BuildTestConfig(const QList<std::shared_ptr<ProxyEntity>>& profiles)
{
auto res = std::make_shared<BuildConfigResult>();
auto ctx = std::make_shared<BuildSingBoxConfigContext>();
QList<int> entIDs;
for (const auto& proxy : profiles) entIDs << proxy->id;
ctx->buildPrerequisities->dnsDeps->directDomains = QListStr2QJsonArray(getEntDomains(entIDs, ctx->error));
buildDNSSection(ctx);
if (!ctx->error.isEmpty())
{
res->error = ctx->error;
return res;
}
buildLogSections(ctx);
buildCertificateSection(ctx);
buildNTPSection(ctx);
int suffix = 1;
for (auto item : profiles)
{
if (item->type == "extracore")
{
MW_show_log("Skipping ExtraCore conf");
continue;
}
if (item->type == "tailscale")
{
MW_show_log("Skipping Tailscale conf");
continue;
}
if (!IsValid(item)) {
MW_show_log("Skipping invalid config: " + item->outbound->commons->name);
item->latency = -1;
continue;
}
if (item->type == "custom")
{
auto custom = item->Custom();
if (custom == nullptr)
{
MW_show_log("Corrupted data in build test config");
res->error = "Corrupted data in build test config";
return res;
}
if (custom->type == "fullconfig")
{
auto obj = QString2QJsonObject(custom->config);
obj["inbounds"] = QJsonArray();
res->fullConfigs[item->id] = QJsonObject2QString(obj, true);
continue;
}
}
buildOutboundChain(ctx, {item->id}, "proxy-" + Int2String(suffix++), false, true);
auto tag = "proxy-" + Int2String(suffix++) + "-0";
res->outboundTags << tag;
res->tag2entID.insert(tag, item->id);
}
ctx->outbounds << QJsonObject{{"type", "direct"}, {"tag", "direct"}};
ctx->buildConfigResult->coreConfig["outbounds"] = ctx->outbounds;
ctx->buildConfigResult->coreConfig["endpoints"] = ctx->endpoints;
ctx->buildConfigResult->coreConfig["route"] = QJsonObject{
{"auto_detect_interface", true},
{"default_domain_resolver", QJsonObject{
{"server", "dns-direct"},
{"strategy", dataStore->routing->outbound_domain_strategy},
}}
};
res->coreConfig = ctx->buildConfigResult->coreConfig;
return res;
}
}

View File

@ -21,6 +21,26 @@ namespace Configs
}
};
ProxyEntity::ProxyEntity(Configs::outbound *outbound, const QString &type_)
{
if (type_ != nullptr) this->type = type_;
_add(new configItem("type", &type, itemType::string));
_add(new configItem("id", &id, itemType::integer));
_add(new configItem("gid", &gid, itemType::integer));
_add(new configItem("yc", &latency, itemType::integer));
_add(new configItem("dl", &dl_speed, itemType::string));
_add(new configItem("ul", &ul_speed, itemType::string));
_add(new configItem("report", &full_test_report, itemType::string));
_add(new configItem("country", &test_country, itemType::string));
if (outbound != nullptr) {
this->outbound = std::shared_ptr<Configs::outbound>(outbound);
_add(new configItem("outbound", dynamic_cast<JsonStore *>(outbound), itemType::jsonStore));
_add(new configItem("traffic", dynamic_cast<JsonStore *>(traffic_data.get()), itemType::jsonStore));
}
}
QString ProxyEntity::DisplayTestResult() const {
QString result;
if (latency < 0) {