diff --git a/db/ConfigBuilder.cpp b/db/ConfigBuilder.cpp index 026c8fb..d4d7649 100644 --- a/db/ConfigBuilder.cpp +++ b/db/ConfigBuilder.cpp @@ -13,12 +13,19 @@ namespace NekoRay { // Common QSharedPointer BuildConfig(const QSharedPointer &ent, bool forTest, bool forExport) { - QSharedPointer result; + auto result = QSharedPointer(new BuildConfigResult); + auto status = QSharedPointer(new BuildConfigStatus); + status->ent = ent; + status->result = result; + status->forTest = forTest; + status->forExport = forExport; + if (IS_NEKO_BOX) { - result = BuildConfigSingBox(ent, forTest, forExport); + BuildConfigSingBox(status); } else { - result = BuildConfigV2Ray(ent, forTest, forExport); + BuildConfigV2Ray(status); } + // hook.js if (result->error.isEmpty()) { auto source = qjs::ReadHookJS(); @@ -47,11 +54,11 @@ namespace NekoRay { for (auto id: list) { ents += profileManager->GetProfile(id); if (ents.last() == nullptr) { - result->error = QString("chain missing ent: %1").arg(id); + status->result->error = QString("chain missing ent: %1").arg(id); return {}; } if (ents.last()->type == "chain") { - result->error = QString("chain in chain is not allowed: %1").arg(id); + status->result->error = QString("chain in chain is not allowed: %1").arg(id); return {}; } } @@ -125,15 +132,10 @@ namespace NekoRay { // V2Ray - QSharedPointer BuildConfigV2Ray(const QSharedPointer &ent, bool forTest, bool forExport) { - auto result = QSharedPointer(new BuildConfigResult); - auto status = QSharedPointer(new BuildConfigStatus); - status->ent = ent; - status->result = result; - + void BuildConfigV2Ray(const QSharedPointer &status) { // Log auto logObj = QJsonObject{{"loglevel", dataStore->log_level}}; - result->coreConfig.insert("log", logObj); + status->result->coreConfig.insert("log", logObj); // Inbounds QJsonObject sniffing{ @@ -145,7 +147,7 @@ namespace NekoRay { }; // socks-in - if (InRange(dataStore->inbound_socks_port, 0, 65535) && !forTest) { + if (InRange(dataStore->inbound_socks_port, 0, 65535) && !status->forTest) { QJsonObject socksInbound; socksInbound["tag"] = "socks-in"; socksInbound["protocol"] = "socks"; @@ -161,7 +163,7 @@ namespace NekoRay { status->inbounds += socksInbound; } // http-in - if (InRange(dataStore->inbound_http_port, 0, 65535) && !forTest) { + if (InRange(dataStore->inbound_http_port, 0, 65535) && !status->forTest) { QJsonObject socksInbound; socksInbound["tag"] = "http-in"; socksInbound["protocol"] = "http"; @@ -175,7 +177,7 @@ namespace NekoRay { // Outbounds auto tagProxy = BuildChain(0, status); - if (!result->error.isEmpty()) return result; + if (!status->result->error.isEmpty()) return; // direct & bypass & block status->outbounds += QJsonObject{ @@ -192,7 +194,7 @@ namespace NekoRay { }; // block for tun - if (!forTest) { + if (!status->forTest) { status->routingRules += QJsonObject{ {"type", "field"}, { @@ -212,7 +214,7 @@ namespace NekoRay { } // DNS Routing (tun2socks 用到,防污染) - if (dataStore->dns_routing && !forTest) { + if (dataStore->dns_routing && !status->forTest) { QJsonObject dnsOut; dnsOut["protocol"] = "dns"; dnsOut["tag"] = "dns-out"; @@ -240,13 +242,13 @@ namespace NekoRay { } // custom inbound - QJSONARRAY_ADD(status->inbounds, QString2QJsonObject(dataStore->custom_inbound)["inbounds"].toArray()) + if (!status->forTest) QJSONARRAY_ADD(status->inbounds, QString2QJsonObject(dataStore->custom_inbound)["inbounds"].toArray()) - result->coreConfig.insert("inbounds", status->inbounds); - result->coreConfig.insert("outbounds", status->outbounds); + status->result->coreConfig.insert("inbounds", status->inbounds); + status->result->coreConfig.insert("outbounds", status->outbounds); // user rule - if (!forTest) { + if (!status->forTest) { DOMAIN_USER_RULE IP_USER_RULE } @@ -258,8 +260,8 @@ namespace NekoRay { // Remote or FakeDNS QJsonObject dnsServerRemote; dnsServerRemote["address"] = dataStore->fake_dns ? "fakedns" : dataStore->remote_dns; - dnsServerRemote["domains"] = status->domainListDNSRemote; - if (!forTest) dnsServers += dnsServerRemote; + dnsServerRemote["domains"] = QList2QJsonArray(status->domainListDNSRemote); + if (!status->forTest) dnsServers += dnsServerRemote; // Direct auto directDnsAddress = dataStore->direct_dns; @@ -287,19 +289,20 @@ namespace NekoRay { } dnsServers += QJsonObject{ {"address", directDnsAddress}, - {"domains", status->domainListDNSDirect}, + {"domains", QList2QJsonArray(status->domainListDNSDirect)}, {"skipFallback", true}, }; dns["disableFallbackIfMatch"] = true; dns["servers"] = dnsServers; dns["tag"] = "dns"; - result->coreConfig.insert("dns", dns); + status->result->coreConfig.insert("dns", dns); // Routing QJsonObject routing; routing["domainStrategy"] = dataStore->domain_strategy; routing["domainMatcher"] = dataStore->domain_matcher == DomainMatcher::MPH ? "mph" : "linear"; + if (status->forTest) routing["domainStrategy"] = "AsIs"; // final add block route QJsonObject routingRule_tmp; @@ -307,12 +310,12 @@ namespace NekoRay { routingRule_tmp["outboundTag"] = "block"; if (!status->ipListBlock.isEmpty()) { auto tmp = routingRule_tmp; - tmp["ip"] = status->ipListBlock; + tmp["ip"] = QList2QJsonArray(status->ipListBlock); status->routingRules += tmp; } if (!status->domainListBlock.isEmpty()) { auto tmp = routingRule_tmp; - tmp["domain"] = status->domainListBlock; + tmp["domain"] = QList2QJsonArray(status->domainListBlock); status->routingRules += tmp; } @@ -320,12 +323,12 @@ namespace NekoRay { routingRule_tmp["outboundTag"] = "proxy"; if (!status->ipListRemote.isEmpty()) { auto tmp = routingRule_tmp; - tmp["ip"] = status->ipListRemote; + tmp["ip"] = QList2QJsonArray(status->ipListRemote); status->routingRules += tmp; } if (!status->domainListRemote.isEmpty()) { auto tmp = routingRule_tmp; - tmp["domain"] = status->domainListRemote; + tmp["domain"] = QList2QJsonArray(status->domainListRemote); status->routingRules += tmp; } @@ -333,22 +336,23 @@ namespace NekoRay { routingRule_tmp["outboundTag"] = "bypass"; if (!status->ipListDirect.isEmpty()) { auto tmp = routingRule_tmp; - tmp["ip"] = status->ipListDirect; + tmp["ip"] = QList2QJsonArray(status->ipListDirect); status->routingRules += tmp; } if (!status->domainListDirect.isEmpty()) { auto tmp = routingRule_tmp; - tmp["domain"] = status->domainListDirect; + tmp["domain"] = QList2QJsonArray(status->domainListDirect); status->routingRules += tmp; } // final add routing rule // custom routing rule auto routingRules = QString2QJsonObject(dataStore->routing->custom)["rules"].toArray(); - QJSONARRAY_ADD(routingRules, QString2QJsonObject(dataStore->custom_route_global)["rules"].toArray()) + if (status->forTest) routingRules = {}; + if (!status->forTest) QJSONARRAY_ADD(routingRules, QString2QJsonObject(dataStore->custom_route_global)["rules"].toArray()) QJSONARRAY_ADD(routingRules, status->routingRules) routing["rules"] = routingRules; - result->coreConfig.insert("routing", routing); + status->result->coreConfig.insert("routing", routing); // Policy & stats QJsonObject policy; @@ -362,10 +366,8 @@ namespace NekoRay { policySystem["statsOutboundDownlink"] = true; policySystem["statsOutboundUplink"] = true; policy["system"] = policySystem; - result->coreConfig.insert("policy", policy); - result->coreConfig.insert("stats", QJsonObject()); - - return result; + status->result->coreConfig.insert("policy", policy); + status->result->coreConfig.insert("stats", QJsonObject()); } QString BuildChainInternal(int chainId, const QList> &ents, @@ -555,9 +557,10 @@ namespace NekoRay { status->result->outboundStats += ent->traffic_data; if (IS_NEKO_BOX) { - // TODO no such field? auto ds = dataStore->outbound_domain_strategy; - if (ds == "UseIPv4") { + if (status->forTest) { + ds = ""; + } else if (ds == "UseIPv4") { ds = "ipv4_only"; } else if (ds == "UseIPv6") { ds = "ipv6_only"; @@ -571,7 +574,7 @@ namespace NekoRay { outbound["domain_strategy"] = ds; // TODO apply mux } else { - outbound["domainStrategy"] = dataStore->outbound_domain_strategy; + if (!status->forTest) outbound["domainStrategy"] = dataStore->outbound_domain_strategy; // apply mux if (dataStore->mux_cool > 0 && !muxApplied) { // TODO refactor mux settings @@ -616,19 +619,14 @@ namespace NekoRay { // SingBox - QSharedPointer BuildConfigSingBox(const QSharedPointer &ent, bool forTest, bool forExport) { - auto result = QSharedPointer(new BuildConfigResult); - auto status = QSharedPointer(new BuildConfigStatus); - status->ent = ent; - status->result = result; - + void BuildConfigSingBox(const QSharedPointer &status) { // Log - result->coreConfig["log"] = QJsonObject{{"level", dataStore->log_level}}; + status->result->coreConfig["log"] = QJsonObject{{"level", dataStore->log_level}}; // Inbounds // mixed-in - if (InRange(dataStore->inbound_socks_port, 0, 65535) && !forTest) { + if (InRange(dataStore->inbound_socks_port, 0, 65535) && !status->forTest) { QJsonObject socksInbound; socksInbound["tag"] = "mixed-in"; socksInbound["type"] = "mixed"; @@ -643,7 +641,7 @@ namespace NekoRay { // Outbounds auto tagProxy = BuildChain(0, status); - if (!result->error.isEmpty()) return result; + if (!status->result->error.isEmpty()) return; // direct & bypass & block status->outbounds += QJsonObject{ @@ -658,7 +656,7 @@ namespace NekoRay { {"type", "block"}, {"tag", "block"}, }; - if (!forTest) { + if (!status->forTest) { status->outbounds += QJsonObject{ {"type", "dns"}, {"tag", "dns-out"}, @@ -666,59 +664,60 @@ namespace NekoRay { } // custom inbound - QJSONARRAY_ADD(status->inbounds, QString2QJsonObject(dataStore->custom_inbound)["inbounds"].toArray()) + if (!status->forTest) QJSONARRAY_ADD(status->inbounds, QString2QJsonObject(dataStore->custom_inbound)["inbounds"].toArray()) - result->coreConfig.insert("inbounds", status->inbounds); - result->coreConfig.insert("outbounds", status->outbounds); + status->result->coreConfig.insert("inbounds", status->inbounds); + status->result->coreConfig.insert("outbounds", status->outbounds); // user rule - if (!forTest) { + if (!status->forTest) { DOMAIN_USER_RULE IP_USER_RULE } // sing-box common rule object - auto make_rule = [&](const QJsonArray &arr, bool isIP = false) { + auto make_rule = [&](const QStringList &list, bool isIP = false) { QJsonObject rule; - QJsonArray ips; - QJsonArray geoips; + // + QJsonArray ip_cidr; + QJsonArray geoip; + // QJsonArray domain_keyword; QJsonArray domain_subdomain; QJsonArray domain_full; QJsonArray domain_suffix; - QJsonArray geosites; - for (const auto &domain_: arr) { - auto domain = domain_.toString(); + QJsonArray geosite; + for (auto item: list) { if (isIP) { - if (domain.startsWith("geoip:")) { - geoips += domain.replace("geoip:", ""); + if (item.startsWith("geoip:")) { + geoip += item.replace("geoip:", ""); } else { - ips += domain; + ip_cidr += item; } } else { - if (domain.startsWith("geosite:")) { - geosites += domain.replace("geosite:", ""); - } else if (domain.startsWith("full:")) { - domain_full += domain.replace("full:", ""); - } else if (domain.startsWith("domain:")) { - domain_suffix += domain.replace("domain:", ""); + if (item.startsWith("geosite:")) { + geosite += item.replace("geosite:", ""); + } else if (item.startsWith("full:")) { + domain_full += item.replace("full:", ""); + } else if (item.startsWith("domain:")) { + domain_suffix += item.replace("domain:", ""); } else { - domain_keyword += domain; + domain_keyword += item; } } } if (isIP) { - if (ips.isEmpty() && geoips.isEmpty()) return rule; - rule["ip_cidr"] = ips; - rule["geoip"] = geoips; + if (ip_cidr.isEmpty() && geoip.isEmpty()) return rule; + rule["ip_cidr"] = ip_cidr; + rule["geoip"] = geoip; } else { - if (domain_keyword.isEmpty() && domain_subdomain.isEmpty() && domain_full.isEmpty() && geosites.isEmpty()) { + if (domain_keyword.isEmpty() && domain_subdomain.isEmpty() && domain_full.isEmpty() && geosite.isEmpty()) { return rule; } rule["domain"] = domain_full; rule["domain_suffix"] = domain_suffix; rule["domain_keyword"] = domain_keyword; - rule["geosite"] = geosites; + rule["geosite"] = geosite; } return rule; }; @@ -729,7 +728,7 @@ namespace NekoRay { QJsonArray dnsRules; // Remote - if (!forTest) + if (!status->forTest) dnsServers += QJsonObject{ {"tag", "dns-remote"}, {"address_resolver", "dns-underlying"}, @@ -738,12 +737,12 @@ namespace NekoRay { }; // neko only - auto underlyingStr = forExport ? "local" : "underlying://0.0.0.0"; + auto underlyingStr = status->forExport ? "local" : "underlying://0.0.0.0"; // Direct auto directDNSAddress = dataStore->direct_dns; if (directDNSAddress == "localhost") directDNSAddress = underlyingStr; - if (!forTest) + if (!status->forTest) dnsServers += QJsonObject{ {"tag", "dns-direct"}, {"address_resolver", "dns-underlying"}, @@ -759,8 +758,8 @@ namespace NekoRay { }; // sing-box dns rule object - auto add_rule_dns = [&](const QJsonArray &arr, const QString &server) { - auto rule = make_rule(arr, false); + auto add_rule_dns = [&](const QStringList &list, const QString &server) { + auto rule = make_rule(list, false); if (rule.isEmpty()) return; rule["server"] = server; dnsRules += rule; @@ -771,19 +770,20 @@ namespace NekoRay { dns["servers"] = dnsServers; dns["rules"] = dnsRules; - result->coreConfig.insert("dns", dns); + status->result->coreConfig.insert("dns", dns); // Routing // custom routing rule (top) auto routingRules = QString2QJsonObject(dataStore->routing->custom)["rules"].toArray(); + if (status->forTest) routingRules = {}; // dns hijack - if (!forTest) routingRules += QJsonObject{{"protocol", "dns"}, {"outbound", "dns-out"}}; + if (!status->forTest) routingRules += QJsonObject{{"protocol", "dns"}, {"outbound", "dns-out"}}; // sing-box routing rule object - auto add_rule_route = [&](const QJsonArray &arr, bool isIP, const QString &out) { - auto rule = make_rule(arr, isIP); + auto add_rule_route = [&](const QStringList &list, bool isIP, const QString &out) { + auto rule = make_rule(list, isIP); if (rule.isEmpty()) return; rule["outbound"] = out; routingRules += rule; @@ -800,11 +800,11 @@ namespace NekoRay { // geopath auto geoip = FindCoreAsset("geoip.db"); auto geosite = FindCoreAsset("geosite.db"); - if (geoip.isEmpty()) result->error = +"geoip.db not found"; - if (geosite.isEmpty()) result->error = +"geosite.db not found"; + if (geoip.isEmpty()) status->result->error = +"geoip.db not found"; + if (geosite.isEmpty()) status->result->error = +"geosite.db not found"; // final add routing rule - QJSONARRAY_ADD(routingRules, QString2QJsonObject(dataStore->custom_route_global)["rules"].toArray()) + if (!status->forTest) QJSONARRAY_ADD(routingRules, QString2QJsonObject(dataStore->custom_route_global)["rules"].toArray()) QJSONARRAY_ADD(routingRules, status->routingRules) auto routeObj = QJsonObject{ {"rules", routingRules}, @@ -821,26 +821,24 @@ namespace NekoRay { {"path", geosite}, }, }}; - if (forExport) { + if (status->forExport) { routeObj.remove("geoip"); routeObj.remove("geosite"); routeObj.remove("auto_detect_interface"); } - result->coreConfig.insert("route", routeObj); + status->result->coreConfig.insert("route", routeObj); // api - if (!forTest && !forExport && dataStore->traffic_loop_interval > 0) { - result->coreConfig.insert("experimental", QJsonObject{ - {"v2ray_api", QJsonObject{ - {"listen", "127.0.0.1:" + Int2String(dataStore->inbound_socks_port + 10)}, - {"stats", QJsonObject{ - {"enabled", true}, - {"outbounds", QJsonArray{tagProxy, "bypass"}}, - }}}}, - }); + if (!status->forTest && !status->forExport && dataStore->traffic_loop_interval > 0) { + status->result->coreConfig.insert("experimental", QJsonObject{ + {"v2ray_api", QJsonObject{ + {"listen", "127.0.0.1:" + Int2String(dataStore->inbound_socks_port + 10)}, + {"stats", QJsonObject{ + {"enabled", true}, + {"outbounds", QJsonArray{tagProxy, "bypass"}}, + }}}}, + }); } - - return result; } QString WriteVPNSingBoxConfig() { diff --git a/db/ConfigBuilder.hpp b/db/ConfigBuilder.hpp index d658337..e80b2b5 100644 --- a/db/ConfigBuilder.hpp +++ b/db/ConfigBuilder.hpp @@ -19,21 +19,24 @@ namespace NekoRay { class BuildConfigStatus { public: - QList globalProfiles; - QSharedPointer result; QSharedPointer ent; + bool forTest; + bool forExport; + + // priv + QList globalProfiles; // xxList is V2Ray format string list - QJsonArray domainListDNSRemote; - QJsonArray domainListDNSDirect; - QJsonArray domainListRemote; - QJsonArray domainListDirect; - QJsonArray ipListRemote; - QJsonArray ipListDirect; - QJsonArray domainListBlock; - QJsonArray ipListBlock; + QStringList domainListDNSRemote; + QStringList domainListDNSDirect; + QStringList domainListRemote; + QStringList domainListDirect; + QStringList ipListRemote; + QStringList ipListDirect; + QStringList domainListBlock; + QStringList ipListBlock; // config format @@ -42,14 +45,11 @@ namespace NekoRay { QJsonArray outbounds; }; - QSharedPointer BuildConfig(const QSharedPointer &ent, - bool forTest, bool forExport); + QSharedPointer BuildConfig(const QSharedPointer &ent, bool forTest, bool forExport); - QSharedPointer BuildConfigV2Ray(const QSharedPointer &ent, - bool forTest, bool forExport); + void BuildConfigV2Ray(const QSharedPointer &status); - QSharedPointer BuildConfigSingBox(const QSharedPointer &ent, - bool forTest, bool forExport); + void BuildConfigSingBox(const QSharedPointer &status); QString BuildChain(int chainId, const QSharedPointer &status); diff --git a/sub/GroupUpdater.cpp b/sub/GroupUpdater.cpp index 7872cad..106d17c 100644 --- a/sub/GroupUpdater.cpp +++ b/sub/GroupUpdater.cpp @@ -228,10 +228,26 @@ namespace NekoRay::sub { auto plugin_n = proxy["plugin"]; auto pluginOpts_n = proxy["plugin-opts"]; if (plugin_n.IsDefined() && pluginOpts_n.IsDefined()) { - if (Node2QString(plugin_n) == "obfs") { - bean->plugin = "obfs-local;obfs=" + Node2QString(pluginOpts_n["mode"]) + ";obfs-host=" + - Node2QString(pluginOpts_n["host"]); + QStringList ssPlugin; + auto plugin = Node2QString(plugin_n); + if (plugin == "obfs") { + ssPlugin << "obfs-local"; + ssPlugin << "obfs=" + Node2QString(pluginOpts_n["mode"]); + ssPlugin << "obfs-host=" + Node2QString(pluginOpts_n["host"]); + } else if (plugin == "v2ray-plugin") { + auto mode = Node2QString(pluginOpts_n["mode"]); + auto host = Node2QString(pluginOpts_n["host"]); + auto path = Node2QString(pluginOpts_n["path"]); + ssPlugin << "v2ray-plugin"; + if (!mode.isEmpty() && mode != "websocket") ssPlugin << "mode=" + mode; + if (Node2Bool(pluginOpts_n["tls"])) ssPlugin << "tls"; + if (!host.isEmpty()) ssPlugin << "host=" + host; + if (!path.isEmpty()) ssPlugin << "path=" + path; + // clash only: skip-cert-verify + // clash only: headers + // clash: mux=? } + bean->plugin = ssPlugin.join(";"); } auto protocol_n = proxy["protocol"]; if (protocol_n.IsDefined()) {