diff --git a/db/ConfigBuilder.cpp b/db/ConfigBuilder.cpp index 9b023d2..5fe3a49 100644 --- a/db/ConfigBuilder.cpp +++ b/db/ConfigBuilder.cpp @@ -3,12 +3,12 @@ #include "fmt/includes.h" #include "fmt/Preset.hpp" #include "main/QJS.hpp" +#include "rpc/gRPC.h" #include #include #include -#define BOX_UNDERLYING_DNS dataStore->core_box_underlying_dns.isEmpty() ? "underlying://0.0.0.0" : dataStore->core_box_underlying_dns #define BOX_UNDERLYING_DNS_EXPORT dataStore->core_box_underlying_dns.isEmpty() ? (status->forExport ? "local" : "underlying://0.0.0.0") : dataStore->core_box_underlying_dns namespace NekoGui { @@ -151,35 +151,10 @@ namespace NekoGui { return chainTagOut; } -#define DOMAIN_USER_RULE \ - for (const auto &line: SplitLinesSkipSharp(dataStore->routing->proxy_domain)) { \ - if (dataStore->routing->dns_routing) status->domainListDNSRemote += line; \ - status->domainListRemote += line; \ - } \ - for (const auto &line: SplitLinesSkipSharp(dataStore->routing->direct_domain)) { \ - if (dataStore->routing->dns_routing) status->domainListDNSDirect += line; \ - status->domainListDirect += line; \ - } \ - for (const auto &line: SplitLinesSkipSharp(dataStore->routing->block_domain)) { \ - status->domainListBlock += line; \ - } - -#define IP_USER_RULE \ - for (const auto &line: SplitLinesSkipSharp(dataStore->routing->block_ip)) { \ - status->ipListBlock += line; \ - } \ - for (const auto &line: SplitLinesSkipSharp(dataStore->routing->proxy_ip)) { \ - status->ipListRemote += line; \ - } \ - for (const auto &line: SplitLinesSkipSharp(dataStore->routing->direct_ip)) { \ - status->ipListDirect += line; \ - } - QString BuildChainInternal(int chainId, const QList> &ents, const std::shared_ptr &status) { QString chainTag = "c-" + Int2String(chainId); QString chainTagOut; - bool muxApplied = false; QString pastTag; int pastExternalStat = 0; @@ -290,7 +265,6 @@ namespace NekoGui { // Outbound QJsonObject outbound; - auto stream = GetStreamSettings(ent->bean.get()); if (thisExternalStat > 0) { auto extR = ent->bean->BuildExternal(ext_mapping_port, ext_socks_port, thisExternalStat); @@ -309,71 +283,9 @@ namespace NekoGui { outbound["type"] = "socks"; outbound["server"] = "127.0.0.1"; outbound["server_port"] = ext_socks_port; - } else { - const auto coreR = ent->bean->BuildCoreObjSingBox(); - if (coreR.outbound.isEmpty()) { - status->result->error = "unsupported outbound"; - return {}; - } - if (!coreR.error.isEmpty()) { // rejected - status->result->error = coreR.error; - return {}; - } - outbound = coreR.outbound; } - // outbound misc - outbound["tag"] = tagOut; - ent->traffic_data->id = ent->id; - ent->traffic_data->tag = tagOut.toStdString(); - status->result->outboundStats += ent->traffic_data; - - // mux common - auto needMux = ent->type == "vmess" || ent->type == "trojan" || ent->type == "vless" || ent->type == "shadowsocks"; - needMux &= dataStore->mux_concurrency > 0; - - if (stream != nullptr) { - if (stream->network == "grpc" || stream->network == "quic" || (stream->network == "http" && stream->security == "tls")) { - needMux = false; - } - } - - auto mux_state = ent->bean->mux_state; - if (mux_state == 0) { - if (!dataStore->mux_default_on && !ent->bean->enable_brutal) needMux = false; - } else if (mux_state == 1) { - needMux = true; - } else if (mux_state == 2) { - needMux = false; - } - - if (ent->type == "vless" && outbound["flow"] != "") { - needMux = false; - } - - // common - // apply domain_strategy - outbound["domain_strategy"] = dataStore->routing->outbound_domain_strategy; - // apply mux - if (!muxApplied && needMux) { - auto muxObj = QJsonObject{ - {"enabled", true}, - {"protocol", dataStore->mux_protocol}, - {"padding", dataStore->mux_padding}, - {"max_streams", dataStore->mux_concurrency}, - }; - if (ent->bean->enable_brutal) { - auto brutalObj = QJsonObject{ - {"enabled", true}, - {"up_mbps", ent->bean->brutal_speed}, - {"down_mbps", ent->bean->brutal_speed}, - }; - muxObj["max_connections"] = 1; - muxObj["brutal"] = brutalObj; - } - outbound["multiplex"] = muxObj; - muxApplied = true; - } + BuildOutbound(ent, status, outbound, tagOut); // apply custom outbound settings MergeJson(QString2QJsonObject(ent->bean->custom_outbound), outbound); @@ -388,7 +300,7 @@ namespace NekoGui { } if (!IsIpAddress(serverAddress)) { - status->domainListDNSDirect += "full:" + serverAddress; + status->domainListDNSDirect += serverAddress; } status->outbounds += outbound; @@ -400,6 +312,72 @@ namespace NekoGui { return chainTagOut; } + void BuildOutbound(const std::shared_ptr &ent, const std::shared_ptr &status, QJsonObject& outbound, const QString& tag) { + const auto coreR = ent->bean->BuildCoreObjSingBox(); + if (coreR.outbound.isEmpty()) { + status->result->error = "unsupported outbound"; + return; + } + if (!coreR.error.isEmpty()) { // rejected + status->result->error = coreR.error; + return; + } + outbound = coreR.outbound; + + // outbound misc + outbound["tag"] = tag; + ent->traffic_data->id = ent->id; + ent->traffic_data->tag = tag.toStdString(); + status->result->outboundStats += ent->traffic_data; + + // mux common + auto needMux = ent->type == "vmess" || ent->type == "trojan" || ent->type == "vless" || ent->type == "shadowsocks"; + needMux &= dataStore->mux_concurrency > 0; + + auto stream = GetStreamSettings(ent->bean.get()); + if (stream != nullptr) { + if (stream->network == "grpc" || stream->network == "quic" || (stream->network == "http" && stream->security == "tls")) { + needMux = false; + } + } + + auto mux_state = ent->bean->mux_state; + if (mux_state == 0) { + if (!dataStore->mux_default_on && !ent->bean->enable_brutal) needMux = false; + } else if (mux_state == 1) { + needMux = true; + } else if (mux_state == 2) { + needMux = false; + } + + if (ent->type == "vless" && outbound["flow"] != "") { + needMux = false; + } + + // common + // apply domain_strategy + outbound["domain_strategy"] = dataStore->routing->outbound_domain_strategy; + // apply mux + if (needMux) { + auto muxObj = QJsonObject{ + {"enabled", true}, + {"protocol", dataStore->mux_protocol}, + {"padding", dataStore->mux_padding}, + {"max_streams", dataStore->mux_concurrency}, + }; + if (ent->bean->enable_brutal) { + auto brutalObj = QJsonObject{ + {"enabled", true}, + {"up_mbps", ent->bean->brutal_speed}, + {"down_mbps", ent->bean->brutal_speed}, + }; + muxObj["max_connections"] = 1; + muxObj["brutal"] = brutalObj; + } + outbound["multiplex"] = muxObj; + } + } + // SingBox void BuildConfigSingBox(const std::shared_ptr &status) { @@ -467,15 +445,11 @@ namespace NekoGui { auto tagProxy = BuildChain(0, status); if (!status->result->error.isEmpty()) return; - // direct & bypass & block + // direct & block & dns-out status->outbounds += QJsonObject{ {"type", "direct"}, {"tag", "direct"}, }; - status->outbounds += QJsonObject{ - {"type", "direct"}, - {"tag", "bypass"}, - }; status->outbounds += QJsonObject{ {"type", "block"}, {"tag", "block"}, @@ -493,60 +467,89 @@ namespace NekoGui { status->result->coreConfig.insert("inbounds", status->inbounds); status->result->coreConfig.insert("outbounds", status->outbounds); + // Routing + // geopath + auto geoip = FindCoreAsset("geoip.db"); + auto geosite = FindCoreAsset("geosite.db"); + if (geoip.isEmpty()) status->result->error = +"geoip.db not found"; + if (geosite.isEmpty()) status->result->error = +"geosite.db not found"; - // sing-box common rule object - auto make_rule = [&](const QStringList &list, bool isIP = false) { - QJsonObject rule; - // - QJsonArray ip_cidr; - QJsonArray geoip; - // - QJsonArray domain_keyword; - QJsonArray domain_subdomain; - QJsonArray domain_regexp; - QJsonArray domain_full; - QJsonArray geosite; - for (auto item: list) { - if (isIP) { - if (item.startsWith("geoip:")) { - geoip += item.replace("geoip:", ""); - } else { - ip_cidr += item; - } - } else { - // https://www.v2fly.org/config/dns.html#dnsobject - if (item.startsWith("geosite:")) { - geosite += item.replace("geosite:", ""); - } else if (item.startsWith("full:")) { - domain_full += item.replace("full:", "").toLower(); - } else if (item.startsWith("domain:")) { - domain_subdomain += item.replace("domain:", "").toLower(); - } else if (item.startsWith("regexp:")) { - domain_regexp += item.replace("regexp:", "").toLower(); - } else if (item.startsWith("keyword:")) { - domain_keyword += item.replace("keyword:", "").toLower(); - } else { - domain_full += item.toLower(); - } - } - } - if (isIP) { - 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_regexp.isEmpty() && domain_full.isEmpty() && geosite.isEmpty()) { - return rule; - } - rule["domain"] = domain_full; - rule["domain_suffix"] = domain_subdomain; // v2ray Subdomain => sing-box suffix - rule["domain_keyword"] = domain_keyword; - rule["domain_regex"] = domain_regexp; - rule["geosite"] = geosite; - } - return rule; - }; + // manage routing section + auto routeObj = QJsonObject{ + {"auto_detect_interface", true}, + { + "geoip", + QJsonObject{ + {"path", geoip}, + }, + }, + { + "geosite", + QJsonObject{ + {"path", geosite}, + }, + }}; + if (!status->forTest) routeObj["final"] = dataStore->routing->def_outbound; + if (status->forExport) { + routeObj.remove("geoip"); + routeObj.remove("geosite"); + } + auto routeChain = NekoGui::profileManager->GetRouteChain(NekoGui::dataStore->routing->current_route_id); + if (routeChain == nullptr) { + MessageBoxWarning("Corrupted Data", "Routing profile does not exist, try resetting the route profile in Routing Settings"); + return; + } + auto neededOutbounds = routeChain->get_used_outbounds(); + auto neededRuleSets = routeChain->get_used_rule_sets(); + std::map outboundMap; + outboundMap[-1] = "proxy"; + outboundMap[-2] = "direct"; + outboundMap[-3] = "block"; + outboundMap[-4] = "dns-out"; + int suffix = 0; + for (const auto &item: *neededOutbounds) { + if (item < 0) continue; + auto neededEnt = NekoGui::profileManager->GetProfile(item); + if (neededEnt == nullptr) { + MessageBoxWarning("Invalid Data", "The routing profile is referencing outbounds that no longer exists, consider revising your settings"); + return; + } + QJsonObject currOutbound; + QString tag = "rout-" + Int2String(suffix++); + BuildOutbound(neededEnt, status, currOutbound, tag); + status->outbounds += currOutbound; + outboundMap[item] = tag; + + // add to dns direct resolve + if (!IsIpAddress(neededEnt->bean->serverAddress)) { + status->domainListDNSDirect << neededEnt->bean->serverAddress; + } + } + routeObj["rules"] = routeChain->get_route_rules(false, outboundMap); + + auto ruleSetArray = QJsonArray(); + for (const auto &item: *neededRuleSets) { + ruleSetArray += QJsonObject{ + {"type", "local"}, + {"tag", item}, + {"format", "binary"}, + {"path", RULE_SETS_DIR + QString("/%1.srs").arg(item)}, + }; + if (QFile(QString(RULE_SETS_DIR + "/%1.srs").arg(item)).exists()) continue; + bool ok; + auto err = NekoGui_rpc::defaultClient->CompileGeoSet(&ok, item.contains("_IP") ? NekoGui_rpc::GeoRuleSetType::ip : NekoGui_rpc::GeoRuleSetType::site, item.toStdString()); + if (!ok) { + MW_show_log("Failed to generate rule set asset for " + item); + status->result->error = err; + return; + } + } + routeObj["rule_set"] = ruleSetArray; + + status->result->coreConfig["route"] = routeObj; + + // DNS settings // final add DNS QJsonObject dns; QJsonArray dnsServers; @@ -600,6 +603,16 @@ namespace NekoGui { }; } + // Direct dns domains + QJsonArray directDnsDomains; + for (const auto &item: status->domainListDNSDirect) { + directDnsDomains.append(item); + } + dnsRules += QJsonObject{ + {"domain", directDnsDomains}, + {"server", "dns-direct"}, + }; + // Underlying 100% Working DNS dnsServers += QJsonObject{ {"tag", "dns-local"}, @@ -607,28 +620,6 @@ namespace NekoGui { {"detour", "direct"}, }; - // sing-box dns rule object - 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; - }; - add_rule_dns(status->domainListDNSRemote, "dns-remote"); - add_rule_dns(status->domainListDNSDirect, "dns-direct"); - - // built-in rules - if (!status->forTest) { - dnsRules += QJsonObject{ - {"query_type", QJsonArray{32, 33}}, - {"server", "dns-block"}, - }; - dnsRules += QJsonObject{ - {"domain_suffix", ".lan"}, - {"server", "dns-block"}, - }; - } - // fakedns rule if (dataStore->fake_dns && dataStore->spmode_vpn && !status->forTest) { dnsRules += QJsonObject{ @@ -646,99 +637,6 @@ namespace NekoGui { } status->result->coreConfig.insert("dns", dns); - // Routing - - // dns hijack - if (!status->forTest) { - status->routingRules += QJsonObject{ - {"protocol", "dns"}, - {"outbound", "dns-out"}, - }; - } - - // sing-box routing rule object - 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; - status->routingRules += rule; - }; - - // final add user rule - add_rule_route(status->domainListBlock, false, "block"); - add_rule_route(status->domainListRemote, false, tagProxy); - add_rule_route(status->domainListDirect, false, "bypass"); - add_rule_route(status->ipListBlock, true, "block"); - add_rule_route(status->ipListRemote, true, tagProxy); - add_rule_route(status->ipListDirect, true, "bypass"); - - // built-in rules - status->routingRules += QJsonObject{ - {"ip_cidr", QJsonArray{"224.0.0.0/3", "ff00::/8"}}, - {"outbound", "block"}, - }; - status->routingRules += QJsonObject{ - {"source_ip_cidr", QJsonArray{"224.0.0.0/3", "ff00::/8"}}, - {"outbound", "block"}, - }; - - // tun user rule - if (dataStore->spmode_vpn && !status->forTest) { - auto match_out = dataStore->vpn_rule_white ? "proxy" : "bypass"; - - QString process_name_rule = dataStore->vpn_rule_process.trimmed(); - if (!process_name_rule.isEmpty()) { - auto arr = SplitLinesSkipSharp(process_name_rule); - QJsonObject rule{{"outbound", match_out}, - {"process_name", QList2QJsonArray(arr)}}; - status->routingRules += rule; - } - - QString cidr_rule = dataStore->vpn_rule_cidr.trimmed(); - if (!cidr_rule.isEmpty()) { - auto arr = SplitLinesSkipSharp(cidr_rule); - QJsonObject rule{{"outbound", match_out}, - {"ip_cidr", QList2QJsonArray(arr)}}; - status->routingRules += rule; - } - - auto autoBypassExternalProcessPaths = getAutoBypassExternalProcessPaths(status->result); - if (!autoBypassExternalProcessPaths.isEmpty()) { - QJsonObject rule{{"outbound", "bypass"}, - {"process_name", QList2QJsonArray(autoBypassExternalProcessPaths)}}; - status->routingRules += rule; - } - } - - // geopath - auto geoip = FindCoreAsset("geoip.db"); - auto geosite = FindCoreAsset("geosite.db"); - if (geoip.isEmpty()) status->result->error = +"geoip.db not found"; - if (geosite.isEmpty()) status->result->error = +"geosite.db not found"; - - // final add routing rule - auto routeObj = QJsonObject{ - {"auto_detect_interface", true}, - { - "geoip", - QJsonObject{ - {"path", geoip}, - }, - }, - { - "geosite", - QJsonObject{ - {"path", geosite}, - }, - }}; - if (!status->forTest) routeObj["final"] = dataStore->routing->def_outbound; - if (status->forExport) { - routeObj.remove("geoip"); - routeObj.remove("geosite"); - routeObj.remove("auto_detect_interface"); - } - status->result->coreConfig.insert("route", routeObj); - // experimental QJsonObject experimentalObj; diff --git a/db/ConfigBuilder.hpp b/db/ConfigBuilder.hpp index aec9d8e..ebcb518 100644 --- a/db/ConfigBuilder.hpp +++ b/db/ConfigBuilder.hpp @@ -28,14 +28,7 @@ namespace NekoGui { // xxList is V2Ray format string list - QStringList domainListDNSRemote; QStringList domainListDNSDirect; - QStringList domainListRemote; - QStringList domainListDirect; - QStringList ipListRemote; - QStringList ipListDirect; - QStringList domainListBlock; - QStringList ipListBlock; // config format @@ -52,4 +45,6 @@ namespace NekoGui { QString BuildChainInternal(int chainId, const QList> &ents, const std::shared_ptr &status); + + void BuildOutbound(const std::shared_ptr &ent, const std::shared_ptr &status, QJsonObject& outbound, const QString& tag); } // namespace NekoGui diff --git a/db/Database.cpp b/db/Database.cpp index 24a1ade..b2df5ef 100644 --- a/db/Database.cpp +++ b/db/Database.cpp @@ -40,7 +40,7 @@ namespace NekoGui { routes = {}; profilesIdOrder = filterIntJsonFile("profiles"); groupsIdOrder = filterIntJsonFile("groups"); - routesIdOrder = filterIntJsonFile("routes"); + routesIdOrder = filterIntJsonFile("route_profiles"); // Load Proxys QList delProfile; for (auto id: profilesIdOrder) { @@ -79,9 +79,9 @@ namespace NekoGui { } // Load Routing profiles for (auto id : routesIdOrder) { - auto route = LoadRouteChain(QString("routes/%1.json").arg(id)); + auto route = LoadRouteChain(QString("route_profiles/%1.json").arg(id)); if (route == nullptr) { - MW_show_log(QString("File routes/%1.json is corrupted, consider manually handling it").arg(id)); + MW_show_log(QString("File route_profiles/%1.json is corrupted, consider manually handling it").arg(id)); continue; } @@ -183,7 +183,7 @@ namespace NekoGui { } std::shared_ptr ProfileManager::LoadRouteChain(const QString &jsonPath) { - std::shared_ptr routingChain; + auto routingChain = std::make_shared(); routingChain->fn = jsonPath; if (!routingChain->Load()) { return nullptr; @@ -402,6 +402,34 @@ namespace NekoGui { return GetGroup(dataStore->current_group); } + RouteRule::RouteRule() { + _add(new configItem("name", &name, itemType::string)); + _add(new configItem("ip_version", &ip_version, itemType::string)); + _add(new configItem("protocol", &protocol, itemType::string)); + _add(new configItem("domain", &domain, itemType::stringList)); + _add(new configItem("domain_suffix", &domain_suffix, itemType::stringList)); + _add(new configItem("domain_keyword", &domain_keyword, itemType::stringList)); + _add(new configItem("domain_regex", &domain_regex, itemType::stringList)); + _add(new configItem("source_ip_cidr", &source_ip_cidr, itemType::stringList)); + _add(new configItem("source_ip_is_private", &source_ip_is_private, itemType::boolean)); + _add(new configItem("ip_cidr", &ip_cidr, itemType::stringList)); + _add(new configItem("ip_is_private", &ip_is_private, itemType::boolean)); + _add(new configItem("source_port", &source_port, itemType::stringList)); + _add(new configItem("source_port_range", &source_port_range, itemType::stringList)); + _add(new configItem("port", &port, itemType::stringList)); + _add(new configItem("port_range", &port_range, itemType::stringList)); + _add(new configItem("process_name", &process_name, itemType::stringList)); + _add(new configItem("process_path", &process_path, itemType::stringList)); + _add(new configItem("rule_set", &rule_set, itemType::stringList)); + _add(new configItem("invert", &invert, itemType::boolean)); + _add(new configItem("outboundID", &outboundID, itemType::integer)); + } + + RoutingChain::RoutingChain() { + _add(new configItem("id", &id, itemType::integer)); + _add(new configItem("name", &name, itemType::string)); + _add(new configItem("rules", &castedRules, itemType::jsonStoreList)); + } std::shared_ptr ProfileManager::NewRouteChain() { auto route = std::make_shared(); @@ -415,15 +443,15 @@ namespace NekoGui { return routesIdOrder.last() + 1; } - bool ProfileManager::AddRouteChain(const std::shared_ptr chain) { + bool ProfileManager::AddRouteChain(const std::shared_ptr& chain) { if (chain->id >= 0) { return false; } chain->id = NewRouteChainID(); + chain->fn = QString("route_profiles/%1.json").arg(chain->id); routes[chain->id] = chain; routesIdOrder.push_back(chain->id); - chain->fn = QString("routes/%1.json").arg(chain->id); chain->Save(); return true; @@ -433,6 +461,25 @@ namespace NekoGui { return routes.count(id) > 0 ? routes[id] : nullptr; } + void ProfileManager::UpdateRouteChains(const QList>& newChain) { + routes.clear(); + routesIdOrder.clear(); + + for (const auto &item: newChain) { + if (!AddRouteChain(item)) { + routes[item->id] = item; + item->Save(); + } + routesIdOrder << item->id; + } + auto currFiles = filterIntJsonFile("route_profiles"); + for (const auto &item: currFiles) { // clean up removed route profiles + if (!routes.count(item)) { + QFile(QString(ROUTES_PREFIX+"%1.json").arg(item)).remove(); + } + } + } + QList> Group::Profiles() const { QList> ret; for (const auto &[_, profile]: profileManager->profiles) { diff --git a/db/Database.hpp b/db/Database.hpp index 4e4e87f..a9fd345 100644 --- a/db/Database.hpp +++ b/db/Database.hpp @@ -48,10 +48,12 @@ namespace NekoGui { std::shared_ptr CurrentGroup(); - bool AddRouteChain(std::shared_ptr chain); + bool AddRouteChain(const std::shared_ptr& chain); std::shared_ptr GetRouteChain(int id); + void UpdateRouteChains(const QList>& newChain); + private: // sort by id QList profilesIdOrder; diff --git a/db/RouteEntity.cpp b/db/RouteEntity.cpp index 6c0b116..bf2a8ca 100644 --- a/db/RouteEntity.cpp +++ b/db/RouteEntity.cpp @@ -7,43 +7,57 @@ namespace NekoGui { QJsonArray get_as_array(const QList& str, bool castToNum = false) { QJsonArray res; for (const auto &item: str) { + if (item.trimmed().isEmpty()) continue; if (castToNum) res.append(item.toInt()); else res.append(item); } return res; } - QJsonObject RouteRule::get_rule_json(bool forView) const { + bool isValidStrArray(const QStringList& arr) { + for (const auto& item: arr) { + if (!item.trimmed().isEmpty()) return true; + } + return false; + } + + QJsonObject RouteRule::get_rule_json(bool forView, const QString& outboundTag) const { QJsonObject obj; if (ip_version != "") obj["ip_version"] = ip_version.toInt(); if (network != "") obj["network"] = network; if (protocol != "") obj["protocol"] = protocol; - if (!domain.empty()) obj["domain"] = get_as_array(domain); - if (!domain_suffix.empty()) obj["domain_suffix"] = get_as_array(domain_suffix); - if (!domain_keyword.empty()) obj["domain_keyword"] = get_as_array(domain_keyword); - if (!domain_regex.empty()) obj["domain_regex"] = get_as_array(domain_regex); - if (!source_ip_cidr.empty()) obj["source_ip_cidr"] = get_as_array(source_ip_cidr); - if (source_ip_is_private != nullptr) obj["source_ip_is_private"] = *source_ip_is_private; - if (!ip_cidr.empty()) obj["ip_cidr"] = get_as_array(ip_cidr); - if (ip_is_private != nullptr) obj["ip_is_private"] = *ip_is_private; - if (!source_port.empty()) obj["source_port"] = get_as_array(source_port, true); - if (!source_port_range.empty()) obj["source_port_range"] = get_as_array(source_port_range); - if (!port.empty()) obj["port"] = get_as_array(port, true); - if (!port_range.empty()) obj["port_range"] = get_as_array(port_range); - if (!process_name.empty()) obj["process_name"] = get_as_array(process_name); - if (!process_path.empty()) obj["process_path"] = get_as_array(process_path); - if (!rule_set.empty()) obj["rule_set"] = get_as_array(rule_set); + if (isValidStrArray(domain)) obj["domain"] = get_as_array(domain); + if (isValidStrArray(domain_suffix)) obj["domain_suffix"] = get_as_array(domain_suffix); + if (isValidStrArray(domain_keyword)) obj["domain_keyword"] = get_as_array(domain_keyword); + if (isValidStrArray(domain_regex)) obj["domain_regex"] = get_as_array(domain_regex); + if (isValidStrArray(source_ip_cidr)) obj["source_ip_cidr"] = get_as_array(source_ip_cidr); + if (source_ip_is_private) obj["source_ip_is_private"] = source_ip_is_private; + if (isValidStrArray(ip_cidr)) obj["ip_cidr"] = get_as_array(ip_cidr); + if (ip_is_private) obj["ip_is_private"] = ip_is_private; + if (isValidStrArray(source_port)) obj["source_port"] = get_as_array(source_port, true); + if (isValidStrArray(source_port_range)) obj["source_port_range"] = get_as_array(source_port_range); + if (isValidStrArray(port)) obj["port"] = get_as_array(port, true); + if (isValidStrArray(port_range)) obj["port_range"] = get_as_array(port_range); + if (isValidStrArray(process_name)) obj["process_name"] = get_as_array(process_name); + if (isValidStrArray(process_path)) obj["process_path"] = get_as_array(process_path); + if (isValidStrArray(rule_set)) obj["rule_set"] = get_as_array(rule_set); if (invert) obj["invert"] = invert; if (forView) { switch (outboundID) { // TODO use constants + case -1: + obj["outbound"] = "proxy"; + break; case -2: obj["outbound"] = "direct"; + break; case -3: obj["outbound"] = "block"; + break; case -4: obj["outbound"] = "dns_out"; + break; default: auto prof = NekoGui::profileManager->GetProfile(outboundID); if (prof == nullptr) { @@ -53,13 +67,13 @@ namespace NekoGui { obj["outbound"] = prof->bean->DisplayName(); } } else { - obj["outbound"] = outboundID; + if (!outboundTag.isEmpty()) obj["outbound"] = outboundTag; + else obj["outbound"] = outboundID; } return obj; } - // TODO use constant for field names QStringList RouteRule::get_attributes() { return { @@ -111,13 +125,13 @@ namespace NekoGui { } QStringList RouteRule::get_current_value_string(const QString& fieldName) { - if (fieldName == "ip_version" && ip_version != "") { + if (fieldName == "ip_version") { return {ip_version}; } - if (fieldName == "network" && network != "") { + if (fieldName == "network") { return {network}; } - if (fieldName == "protocol" && protocol != "") { + if (fieldName == "protocol") { return {protocol}; } if (fieldName == "domain") return domain; @@ -136,41 +150,20 @@ namespace NekoGui { return {}; } - bool* RouteRule::get_current_value_bool(const QString& fieldName) const { + QString RouteRule::get_current_value_bool(const QString& fieldName) const { if (fieldName == "source_ip_is_private") { - return source_ip_is_private; + return source_ip_is_private? "true":"false"; } if (fieldName == "ip_is_private") { - return ip_is_private; + return ip_is_private? "true":"false"; } if (fieldName == "invert") { - return reinterpret_cast(invert); + return invert? "true":"false"; } return nullptr; } void RouteRule::set_field_value(const QString& fieldName, const QStringList& value) { - /* - * "ip_version", -"network", -"protocol", -"domain", -"domain_suffix", -"domain_keyword", -"domain_regex", -"source_ip_cidr", -"source_ip_is_private", -"ip_cidr", -"ip_is_private", -"source_port", -"source_port_range", -"port", -"port_range", -"process_name", -"process_path", -"rule_set", -"invert", - */ if (fieldName == "ip_version") { ip_version = value[0]; } @@ -196,13 +189,13 @@ namespace NekoGui { source_ip_cidr = value; } if (fieldName == "source_ip_is_private") { - source_ip_is_private = reinterpret_cast((value[0] == "true")); + source_ip_is_private = value[0]=="true"; } if (fieldName == "ip_cidr") { ip_cidr = value; } if (fieldName == "ip_is_private") { - ip_is_private = reinterpret_cast((value[0] == "true")); + ip_is_private = value[0]=="true"; } if (fieldName == "source_port") { source_port = value; @@ -230,10 +223,16 @@ namespace NekoGui { } } - QJsonArray RoutingChain::get_route_rules(bool forView) { + bool RouteRule::isEmpty() const { + return get_rule_json().keys().length() == 1; + } + + QJsonArray RoutingChain::get_route_rules(bool forView, std::map outboundMap) { QJsonArray res; for (const auto &item: Rules) { - auto rule_json = item->get_rule_json(forView); + auto outboundTag = QString(); + if (outboundMap.count(item->outboundID)) outboundTag = outboundMap[item->outboundID]; + auto rule_json = item->get_rule_json(forView, outboundTag); if (rule_json.empty()) { MW_show_log("Aborted generating routing section, an error has occurred"); return {}; @@ -245,12 +244,63 @@ namespace NekoGui { } std::shared_ptr RoutingChain::GetDefaultChain() { - auto defaultChain = RoutingChain(); - defaultChain.name = "Default"; - auto defaultRule = RouteRule(); - defaultRule.protocol = {"dns"}; - defaultRule.outboundID = -4; - defaultChain.Rules << std::make_shared(defaultRule); - return std::make_shared(defaultChain); + auto defaultChain = std::make_shared(); + defaultChain->name = "Default"; + auto defaultRule = std::make_shared(); + defaultRule->protocol = "dns"; + defaultRule->outboundID = -4; + defaultChain->Rules << defaultRule; + return defaultChain; + } + + std::shared_ptr> RoutingChain::get_used_outbounds() { + auto res = std::make_shared>(); + for (const auto& item: Rules) { + res->push_back(item->outboundID); + } + return res; + } + + std::shared_ptr RoutingChain::get_used_rule_sets() { + auto res = std::make_shared(); + for (const auto& item: Rules) { + for (const auto& ruleItem: item->rule_set) { + res->push_back(ruleItem); + } + } + return res; + } + + bool RoutingChain::Save() { + castedRules.clear(); + for (const auto &item: Rules) { + castedRules.push_back(dynamic_cast(item.get())); + } + return JsonStore::Save(); + } + + void RoutingChain::FromJson(QJsonObject object) { + for (const auto &key: object.keys()) { + if (_map.count(key) == 0) { + continue; + } + + auto value = object[key]; + auto item = _map[key].get(); + + if (item == nullptr) continue; + if (item->type == itemType::jsonStoreList) { + // it is of rule type + if (!value.isArray()) continue; + Rules.clear(); + auto arr = value.toArray(); + for (auto obj : arr) { + auto rule = std::make_shared(); + rule->FromJson(obj.toObject()); + Rules << rule; + } + } + } + JsonStore::FromJson(object); } } \ No newline at end of file diff --git a/db/RouteEntity.h b/db/RouteEntity.h index 56096be..1ab12fe 100644 --- a/db/RouteEntity.h +++ b/db/RouteEntity.h @@ -7,6 +7,8 @@ namespace NekoGui { class RouteRule : public JsonStore { public: + RouteRule(); + QString name = ""; QString ip_version; QString network; @@ -16,9 +18,9 @@ namespace NekoGui { QList domain_keyword; QList domain_regex; QList source_ip_cidr; - bool* source_ip_is_private = nullptr; + bool source_ip_is_private = false; QList ip_cidr; - bool* ip_is_private = nullptr; + bool ip_is_private = false; QList source_port; QList source_port_range; QList port; @@ -27,15 +29,16 @@ namespace NekoGui { QList process_path; QList rule_set; bool invert = false; - int outboundID = -1; // -1 is invalid -2 is direct -3 is block -4 is dns_out + int outboundID = -2; // -1 is proxy -2 is direct -3 is block -4 is dns_out - [[nodiscard]] QJsonObject get_rule_json(bool forView = false) const; + [[nodiscard]] QJsonObject get_rule_json(bool forView = false, const QString& outboundTag = "") const; static QStringList get_attributes(); static inputType get_input_type(const QString& fieldName); static QStringList get_values_for_field(const QString& fieldName); QStringList get_current_value_string(const QString& fieldName); - [[nodiscard]] bool* get_current_value_bool(const QString& fieldName) const; + [[nodiscard]] QString get_current_value_bool(const QString& fieldName) const; void set_field_value(const QString& fieldName, const QStringList& value); + [[nodiscard]] bool isEmpty() const; }; class RoutingChain : public JsonStore { @@ -43,9 +46,20 @@ namespace NekoGui { int id = -1; QString name = ""; QList> Rules; + QList castedRules; - QJsonArray get_route_rules(bool forView = false); + RoutingChain(); + + bool Save() override; + + void FromJson(QJsonObject object); + + QJsonArray get_route_rules(bool forView = false, std::map outboundMap = {}); static std::shared_ptr GetDefaultChain(); + + std::shared_ptr> get_used_outbounds(); + + std::shared_ptr get_used_rule_sets(); }; } // namespace NekoGui \ No newline at end of file diff --git a/fmt/Bean2CoreObj_box.cpp b/fmt/Bean2CoreObj_box.cpp index b938001..c832c3d 100644 --- a/fmt/Bean2CoreObj_box.cpp +++ b/fmt/Bean2CoreObj_box.cpp @@ -25,7 +25,7 @@ namespace NekoGui_fmt { } } else if (network == "http") { if (!path.isEmpty()) transport["path"] = path; - if (!host.isEmpty()) transport["host"] = QList2QJsonArray(host.split(",")); + if (!host.isEmpty()) transport["host"] = QListStr2QJsonArray(host.split(",")); } else if (network == "grpc") { if (!path.isEmpty()) transport["service_name"] = path; } else if (network == "httpupgrade") { @@ -39,7 +39,7 @@ namespace NekoGui_fmt { {"type", "http"}, {"method", "GET"}, {"path", path}, - {"headers", QJsonObject{{"Host", QList2QJsonArray(host.split(","))}}}, + {"headers", QJsonObject{{"Host", QListStr2QJsonArray(host.split(","))}}}, }; outbound->insert("transport", transport); } @@ -53,7 +53,7 @@ namespace NekoGui_fmt { tls["certificate"] = certificate.trimmed(); } if (!alpn.trimmed().isEmpty()) { - tls["alpn"] = QList2QJsonArray(alpn.split(",")); + tls["alpn"] = QListStr2QJsonArray(alpn.split(",")); } QString fp = utlsFingerprint; if (!reality_pbk.trimmed().isEmpty()) { @@ -182,7 +182,7 @@ namespace NekoGui_fmt { {"certificate", caText.trimmed()}, {"server_name", sni}, }; - if (!alpn.trimmed().isEmpty()) coreTlsObj["alpn"] = QList2QJsonArray(alpn.split(",")); + if (!alpn.trimmed().isEmpty()) coreTlsObj["alpn"] = QListStr2QJsonArray(alpn.split(",")); if (proxy_type == proxy_Hysteria2) coreTlsObj["alpn"] = "h3"; QJsonObject outbound{ diff --git a/fmt/Bean2External.cpp b/fmt/Bean2External.cpp index 893fa44..d081090 100644 --- a/fmt/Bean2External.cpp +++ b/fmt/Bean2External.cpp @@ -141,7 +141,7 @@ namespace NekoGui_fmt { relay["zero_rtt_handshake"] = zeroRttHandshake; relay["disable_sni"] = disableSni; if (!heartbeat.trimmed().isEmpty()) relay["heartbeat"] = heartbeat; - if (!alpn.trimmed().isEmpty()) relay["alpn"] = QList2QJsonArray(alpn.split(",")); + if (!alpn.trimmed().isEmpty()) relay["alpn"] = QListStr2QJsonArray(alpn.split(",")); if (!caText.trimmed().isEmpty()) { WriteTempFile("tuic_" + GetRandomString(10) + ".crt", caText.toUtf8()); diff --git a/go/cmd/nekobox_core/go.mod b/go/cmd/nekobox_core/go.mod index 1374d8f..3da2298 100644 --- a/go/cmd/nekobox_core/go.mod +++ b/go/cmd/nekobox_core/go.mod @@ -3,13 +3,13 @@ module nekobox_core go 1.19 require ( - github.com/Mahdi-zarei/sing-box-extra v0.0.0-20240524210208-2f5446580565 + github.com/Mahdi-zarei/sing-box-extra v0.0.0-20240614112142-907408da0959 github.com/matsuridayo/libneko v0.0.0-20230913024055-5277a5bfc889 - github.com/sagernet/sing-box v1.9.0-rc.21 + github.com/sagernet/sing-box v1.9.3 grpc_server v1.0.0 ) -replace github.com/sagernet/sing-box => github.com/Mahdi-zarei/sing-box v1.3.5-0.20240524203629-e284713b657d +replace github.com/sagernet/sing-box => github.com/Mahdi-zarei/sing-box v1.3.5-0.20240613091205-668453f192a9 require ( berty.tech/go-libtor v1.0.385 // indirect @@ -55,16 +55,16 @@ require ( github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 // indirect github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f // indirect github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba // indirect - github.com/sagernet/quic-go v0.43.1-beta.1 // indirect + github.com/sagernet/quic-go v0.43.1-beta.2 // indirect github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect - github.com/sagernet/sing v0.4.0-beta.20 // indirect - github.com/sagernet/sing-dns v0.2.0-beta.18 // indirect + github.com/sagernet/sing v0.4.1 // indirect + github.com/sagernet/sing-dns v0.2.0 // indirect github.com/sagernet/sing-mux v0.2.0 // indirect github.com/sagernet/sing-quic v0.2.0-beta.5 // indirect github.com/sagernet/sing-shadowsocks v0.2.6 // indirect github.com/sagernet/sing-shadowsocks2 v0.2.0 // indirect github.com/sagernet/sing-shadowtls v0.1.4 // indirect - github.com/sagernet/sing-tun v0.3.0-beta.6 // indirect + github.com/sagernet/sing-tun v0.3.2 // indirect github.com/sagernet/sing-vmess v0.1.8 // indirect github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 // indirect github.com/sagernet/tfo-go v0.0.0-20231209031829-7b5343ac1dc6 // indirect @@ -84,7 +84,7 @@ require ( golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.25.0 // indirect golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.20.0 // indirect + golang.org/x/sys v0.21.0 // indirect golang.org/x/text v0.15.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.21.0 // indirect diff --git a/go/cmd/nekobox_core/go.sum b/go/cmd/nekobox_core/go.sum index d517419..e58d7f5 100644 --- a/go/cmd/nekobox_core/go.sum +++ b/go/cmd/nekobox_core/go.sum @@ -4,10 +4,10 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Mahdi-zarei/sing-box v1.3.5-0.20240524203629-e284713b657d h1:l+/67xmhHesa9urn+sdBfiwuvpt+r4Wj7hwrFnWSq+A= -github.com/Mahdi-zarei/sing-box v1.3.5-0.20240524203629-e284713b657d/go.mod h1:7OFR+knnBYl9AeUpGT+EGFkhzM8KPXSZbbMi/vFoCDY= -github.com/Mahdi-zarei/sing-box-extra v0.0.0-20240524210208-2f5446580565 h1:GrE6+ZvQWjmbDtcOQh3avtXKTynt72iLUytMWNWZUt0= -github.com/Mahdi-zarei/sing-box-extra v0.0.0-20240524210208-2f5446580565/go.mod h1:H3YA/kY0yKLmSP359MpY7oms4dQ9YW4s3dcobiarQS8= +github.com/Mahdi-zarei/sing-box v1.3.5-0.20240613091205-668453f192a9 h1:sB4V56lOFjDwefldsXLF0LldVFIr3K6eXXVTqb/PjQc= +github.com/Mahdi-zarei/sing-box v1.3.5-0.20240613091205-668453f192a9/go.mod h1:6Rx5nzbqIfN7HlUaHgO/IdkP7fDPPQ/U/TAC5asEjSM= +github.com/Mahdi-zarei/sing-box-extra v0.0.0-20240614112142-907408da0959 h1:1UClVl6qz9Su3gFUcY9ot21yXcvn4yPjbDfBMB1Bd+Q= +github.com/Mahdi-zarei/sing-box-extra v0.0.0-20240614112142-907408da0959/go.mod h1:umyw9HvgSA5opzUDaTL2w9fzY+6zIhmMY0pTiZfY7Lg= github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= @@ -134,15 +134,15 @@ github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f h1:NkhuupzH5ch7b/Y github.com/sagernet/gvisor v0.0.0-20240428053021-e691de28565f/go.mod h1:KXmw+ouSJNOsuRpg4wgwwCQuunrGz4yoAqQjsLjc6N0= github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba h1:EY5AS7CCtfmARNv2zXUOrsEMPFDGYxaw65JzA2p51Vk= github.com/sagernet/netlink v0.0.0-20240523065131-45e60152f9ba/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= -github.com/sagernet/quic-go v0.43.1-beta.1 h1:alizUjpvWYcz08dBCQsULOd+1xu0o7UtlyYf6SLbRNg= -github.com/sagernet/quic-go v0.43.1-beta.1/go.mod h1:BkrQYeop7Jx3hN3TW8/76CXcdhYiNPyYEBL/BVJ1ifc= +github.com/sagernet/quic-go v0.43.1-beta.2 h1:6YRCE9t1Q3UbNX1/dJGqpwFQbh6DXC6XBrQr2xp6hXY= +github.com/sagernet/quic-go v0.43.1-beta.2/go.mod h1:BkrQYeop7Jx3hN3TW8/76CXcdhYiNPyYEBL/BVJ1ifc= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc= github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= github.com/sagernet/sing v0.2.18/go.mod h1:OL6k2F0vHmEzXz2KW19qQzu172FDgSbUSODylighuVo= -github.com/sagernet/sing v0.4.0-beta.20 h1:8rEepj4LMcR0Wd389fJIziv/jr3MBtX5qXBHsfxJ+dY= -github.com/sagernet/sing v0.4.0-beta.20/go.mod h1:PFQKbElc2Pke7faBLv8oEba5ehtKO21Ho+TkYemTI3Y= -github.com/sagernet/sing-dns v0.2.0-beta.18 h1:6vzXZThRdA7YUzBOpSbUT48XRumtl/KIpIHFSOP0za8= -github.com/sagernet/sing-dns v0.2.0-beta.18/go.mod h1:k/dmFcQpg6+m08gC1yQBy+13+QkuLqpKr4bIreq4U24= +github.com/sagernet/sing v0.4.1 h1:zVlpE+7k7AFoC2pv6ReqLf0PIHjihL/jsBl5k05PQFk= +github.com/sagernet/sing v0.4.1/go.mod h1:ieZHA/+Y9YZfXs2I3WtuwgyCZ6GPsIR7HdKb1SdEnls= +github.com/sagernet/sing-dns v0.2.0 h1:dka3weRX6+CrYO3v+hrTy2z68rCOCZXNBiNXpLZ6JNs= +github.com/sagernet/sing-dns v0.2.0/go.mod h1:BJpJv6XLnrUbSyIntOT6DG9FW0f4fETmPAHvNjOprLg= github.com/sagernet/sing-mux v0.2.0 h1:4C+vd8HztJCWNYfufvgL49xaOoOHXty2+EAjnzN3IYo= github.com/sagernet/sing-mux v0.2.0/go.mod h1:khzr9AOPocLa+g53dBplwNDz4gdsyx/YM3swtAhlkHQ= github.com/sagernet/sing-quic v0.2.0-beta.5 h1:ceKFLd1iS5AtM+pScKmcDp5k7R6WgYIe8vl6nB0aVsE= @@ -153,8 +153,8 @@ github.com/sagernet/sing-shadowsocks2 v0.2.0 h1:wpZNs6wKnR7mh1wV9OHwOyUr21VkS3wK github.com/sagernet/sing-shadowsocks2 v0.2.0/go.mod h1:RnXS0lExcDAovvDeniJ4IKa2IuChrdipolPYWBv9hWQ= github.com/sagernet/sing-shadowtls v0.1.4 h1:aTgBSJEgnumzFenPvc+kbD9/W0PywzWevnVpEx6Tw3k= github.com/sagernet/sing-shadowtls v0.1.4/go.mod h1:F8NBgsY5YN2beQavdgdm1DPlhaKQlaL6lpDdcBglGK4= -github.com/sagernet/sing-tun v0.3.0-beta.6 h1:L11kMrM7UfUW0pzQiU66Fffh4o86KZc1SFGbkYi8Ma8= -github.com/sagernet/sing-tun v0.3.0-beta.6/go.mod h1:DxLIyhjWU/HwGYoX0vNGg2c5QgTQIakphU1MuERR5tQ= +github.com/sagernet/sing-tun v0.3.2 h1:z0bLUT/YXH9RrJS9DsIpB0Bb9afl2hVJOmHd0zA3HJY= +github.com/sagernet/sing-tun v0.3.2/go.mod h1:DxLIyhjWU/HwGYoX0vNGg2c5QgTQIakphU1MuERR5tQ= github.com/sagernet/sing-vmess v0.1.8 h1:XVWad1RpTy9b5tPxdm5MCU8cGfrTGdR8qCq6HV2aCNc= github.com/sagernet/sing-vmess v0.1.8/go.mod h1:vhx32UNzTDUkNwOyIjcZQohre1CaytquC5mPplId8uA= github.com/sagernet/smux v0.0.0-20231208180855-7041f6ea79e7 h1:DImB4lELfQhplLTxeq2z31Fpv8CQqqrUwTbrIRumZqQ= @@ -257,8 +257,8 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/go/cmd/nekobox_core/grpc_box.go b/go/cmd/nekobox_core/grpc_box.go index 61c87f0..73f6e39 100644 --- a/go/cmd/nekobox_core/grpc_box.go +++ b/go/cmd/nekobox_core/grpc_box.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "strings" "time" "grpc_server" @@ -150,3 +151,53 @@ func (s *server) ListConnections(ctx context.Context, in *gen.EmptyReq) (*gen.Li } return out, nil } + +func (s *server) GetGeoIPList(ctx context.Context, in *gen.EmptyReq) (*gen.GetGeoIPListResponse, error) { + resp, err := boxmain.ListGeoip() + if err != nil { + return nil, err + } + + res := make([]string, 0) + for _, r := range resp { + r += "_IP" + res = append(res, r) + } + + return &gen.GetGeoIPListResponse{Items: res}, nil +} + +func (s *server) GetGeoSiteList(ctx context.Context, in *gen.EmptyReq) (*gen.GetGeoSiteListResponse, error) { + resp, err := boxmain.GeositeList() + if err != nil { + return nil, err + } + + res := make([]string, 0) + for _, r := range resp { + r += "_SITE" + res = append(res, r) + } + + return &gen.GetGeoSiteListResponse{Items: res}, nil +} + +func (s *server) CompileGeoIPToSrs(ctx context.Context, in *gen.CompileGeoIPToSrsRequest) (*gen.EmptyResp, error) { + category := strings.TrimSuffix(in.Item, "_IP") + err := boxmain.CompileRuleSet(category, boxmain.IpRuleSet, "./rule_sets/"+in.Item+".srs") + if err != nil { + return nil, err + } + + return &gen.EmptyResp{}, nil +} + +func (s *server) CompileGeoSiteToSrs(ctx context.Context, in *gen.CompileGeoSiteToSrsRequest) (*gen.EmptyResp, error) { + category := strings.TrimSuffix(in.Item, "_SITE") + err := boxmain.CompileRuleSet(category, boxmain.SiteRuleSet, "./rule_sets/"+in.Item+".srs") + if err != nil { + return nil, err + } + + return &gen.EmptyResp{}, nil +} diff --git a/go/grpc_server/gen/libcore.pb.go b/go/grpc_server/gen/libcore.pb.go index 33f476f..600a7c3 100644 --- a/go/grpc_server/gen/libcore.pb.go +++ b/go/grpc_server/gen/libcore.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.33.0 +// protoc-gen-go v1.31.0 // protoc v5.26.1 // source: libcore.proto @@ -819,6 +819,194 @@ func (x *ListConnectionsResp) GetNekorayConnectionsJson() string { return "" } +type GetGeoIPListResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Items []string `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` +} + +func (x *GetGeoIPListResponse) Reset() { + *x = GetGeoIPListResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_libcore_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetGeoIPListResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetGeoIPListResponse) ProtoMessage() {} + +func (x *GetGeoIPListResponse) ProtoReflect() protoreflect.Message { + mi := &file_libcore_proto_msgTypes[11] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetGeoIPListResponse.ProtoReflect.Descriptor instead. +func (*GetGeoIPListResponse) Descriptor() ([]byte, []int) { + return file_libcore_proto_rawDescGZIP(), []int{11} +} + +func (x *GetGeoIPListResponse) GetItems() []string { + if x != nil { + return x.Items + } + return nil +} + +type GetGeoSiteListResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Items []string `protobuf:"bytes,2,rep,name=items,proto3" json:"items,omitempty"` +} + +func (x *GetGeoSiteListResponse) Reset() { + *x = GetGeoSiteListResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_libcore_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *GetGeoSiteListResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetGeoSiteListResponse) ProtoMessage() {} + +func (x *GetGeoSiteListResponse) ProtoReflect() protoreflect.Message { + mi := &file_libcore_proto_msgTypes[12] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetGeoSiteListResponse.ProtoReflect.Descriptor instead. +func (*GetGeoSiteListResponse) Descriptor() ([]byte, []int) { + return file_libcore_proto_rawDescGZIP(), []int{12} +} + +func (x *GetGeoSiteListResponse) GetItems() []string { + if x != nil { + return x.Items + } + return nil +} + +type CompileGeoIPToSrsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Item string `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` +} + +func (x *CompileGeoIPToSrsRequest) Reset() { + *x = CompileGeoIPToSrsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_libcore_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CompileGeoIPToSrsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CompileGeoIPToSrsRequest) ProtoMessage() {} + +func (x *CompileGeoIPToSrsRequest) ProtoReflect() protoreflect.Message { + mi := &file_libcore_proto_msgTypes[13] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CompileGeoIPToSrsRequest.ProtoReflect.Descriptor instead. +func (*CompileGeoIPToSrsRequest) Descriptor() ([]byte, []int) { + return file_libcore_proto_rawDescGZIP(), []int{13} +} + +func (x *CompileGeoIPToSrsRequest) GetItem() string { + if x != nil { + return x.Item + } + return "" +} + +type CompileGeoSiteToSrsRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Item string `protobuf:"bytes,1,opt,name=item,proto3" json:"item,omitempty"` +} + +func (x *CompileGeoSiteToSrsRequest) Reset() { + *x = CompileGeoSiteToSrsRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_libcore_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *CompileGeoSiteToSrsRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*CompileGeoSiteToSrsRequest) ProtoMessage() {} + +func (x *CompileGeoSiteToSrsRequest) ProtoReflect() protoreflect.Message { + mi := &file_libcore_proto_msgTypes[14] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use CompileGeoSiteToSrsRequest.ProtoReflect.Descriptor instead. +func (*CompileGeoSiteToSrsRequest) Descriptor() ([]byte, []int) { + return file_libcore_proto_rawDescGZIP(), []int{14} +} + +func (x *CompileGeoSiteToSrsRequest) GetItem() string { + if x != nil { + return x.Item + } + return "" +} + var File_libcore_proto protoreflect.FileDescriptor var file_libcore_proto_rawDesc = []byte{ @@ -905,13 +1093,25 @@ var file_libcore_proto_rawDesc = []byte{ 0x12, 0x38, 0x0a, 0x18, 0x6e, 0x65, 0x6b, 0x6f, 0x72, 0x61, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x6e, 0x65, 0x6b, 0x6f, 0x72, 0x61, 0x79, 0x43, 0x6f, 0x6e, 0x6e, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x4a, 0x73, 0x6f, 0x6e, 0x2a, 0x32, 0x0a, 0x08, 0x54, 0x65, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x4a, 0x73, 0x6f, 0x6e, 0x22, 0x2c, 0x0a, 0x14, 0x47, 0x65, + 0x74, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x2e, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x47, + 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x22, 0x2e, 0x0a, 0x18, 0x43, 0x6f, 0x6d, 0x70, + 0x69, 0x6c, 0x65, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x54, 0x6f, 0x53, 0x72, 0x73, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x22, 0x30, 0x0a, 0x1a, 0x43, 0x6f, 0x6d, 0x70, + 0x69, 0x6c, 0x65, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x54, 0x6f, 0x53, 0x72, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x69, 0x74, 0x65, 0x6d, 0x2a, 0x32, 0x0a, 0x08, 0x54, 0x65, 0x73, 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x54, 0x63, 0x70, 0x50, 0x69, 0x6e, 0x67, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x72, 0x6c, 0x54, 0x65, 0x73, 0x74, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x46, 0x75, 0x6c, 0x6c, 0x54, 0x65, 0x73, 0x74, 0x10, 0x02, 0x2a, 0x27, 0x0a, 0x0c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x09, 0x0a, 0x05, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x10, 0x00, 0x12, 0x0c, 0x0a, 0x08, 0x44, 0x6f, 0x77, - 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x10, 0x01, 0x32, 0x94, 0x03, 0x0a, 0x0e, 0x4c, 0x69, 0x62, 0x63, + 0x6e, 0x6c, 0x6f, 0x61, 0x64, 0x10, 0x01, 0x32, 0xb8, 0x05, 0x0a, 0x0e, 0x4c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x2f, 0x0a, 0x04, 0x45, 0x78, 0x69, 0x74, 0x12, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x12, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, @@ -936,9 +1136,27 @@ var file_libcore_proto_rawDesc = []byte{ 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x1c, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x6f, 0x6e, - 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x42, 0x11, - 0x5a, 0x0f, 0x67, 0x72, 0x70, 0x63, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2f, 0x67, 0x65, - 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x22, 0x00, 0x12, 0x40, + 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x11, + 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, + 0x71, 0x1a, 0x1d, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x47, + 0x65, 0x6f, 0x49, 0x50, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x44, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x4c, 0x69, + 0x73, 0x74, 0x12, 0x11, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, + 0x74, 0x79, 0x52, 0x65, 0x71, 0x1a, 0x1f, 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, + 0x47, 0x65, 0x74, 0x47, 0x65, 0x6f, 0x53, 0x69, 0x74, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4a, 0x0a, 0x11, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, + 0x65, 0x47, 0x65, 0x6f, 0x49, 0x50, 0x54, 0x6f, 0x53, 0x72, 0x73, 0x12, 0x21, 0x2e, 0x6c, 0x69, + 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x47, 0x65, 0x6f, + 0x49, 0x50, 0x54, 0x6f, 0x53, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, + 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, + 0x73, 0x70, 0x12, 0x4e, 0x0a, 0x13, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x47, 0x65, 0x6f, + 0x53, 0x69, 0x74, 0x65, 0x54, 0x6f, 0x53, 0x72, 0x73, 0x12, 0x23, 0x2e, 0x6c, 0x69, 0x62, 0x63, + 0x6f, 0x72, 0x65, 0x2e, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x47, 0x65, 0x6f, 0x53, 0x69, + 0x74, 0x65, 0x54, 0x6f, 0x53, 0x72, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, + 0x2e, 0x6c, 0x69, 0x62, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, + 0x73, 0x70, 0x42, 0x11, 0x5a, 0x0f, 0x67, 0x72, 0x70, 0x63, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, + 0x72, 0x2f, 0x67, 0x65, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -954,21 +1172,25 @@ func file_libcore_proto_rawDescGZIP() []byte { } var file_libcore_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_libcore_proto_msgTypes = make([]protoimpl.MessageInfo, 11) +var file_libcore_proto_msgTypes = make([]protoimpl.MessageInfo, 15) var file_libcore_proto_goTypes = []interface{}{ - (TestMode)(0), // 0: libcore.TestMode - (UpdateAction)(0), // 1: libcore.UpdateAction - (*EmptyReq)(nil), // 2: libcore.EmptyReq - (*EmptyResp)(nil), // 3: libcore.EmptyResp - (*ErrorResp)(nil), // 4: libcore.ErrorResp - (*LoadConfigReq)(nil), // 5: libcore.LoadConfigReq - (*TestReq)(nil), // 6: libcore.TestReq - (*TestResp)(nil), // 7: libcore.TestResp - (*QueryStatsReq)(nil), // 8: libcore.QueryStatsReq - (*QueryStatsResp)(nil), // 9: libcore.QueryStatsResp - (*UpdateReq)(nil), // 10: libcore.UpdateReq - (*UpdateResp)(nil), // 11: libcore.UpdateResp - (*ListConnectionsResp)(nil), // 12: libcore.ListConnectionsResp + (TestMode)(0), // 0: libcore.TestMode + (UpdateAction)(0), // 1: libcore.UpdateAction + (*EmptyReq)(nil), // 2: libcore.EmptyReq + (*EmptyResp)(nil), // 3: libcore.EmptyResp + (*ErrorResp)(nil), // 4: libcore.ErrorResp + (*LoadConfigReq)(nil), // 5: libcore.LoadConfigReq + (*TestReq)(nil), // 6: libcore.TestReq + (*TestResp)(nil), // 7: libcore.TestResp + (*QueryStatsReq)(nil), // 8: libcore.QueryStatsReq + (*QueryStatsResp)(nil), // 9: libcore.QueryStatsResp + (*UpdateReq)(nil), // 10: libcore.UpdateReq + (*UpdateResp)(nil), // 11: libcore.UpdateResp + (*ListConnectionsResp)(nil), // 12: libcore.ListConnectionsResp + (*GetGeoIPListResponse)(nil), // 13: libcore.GetGeoIPListResponse + (*GetGeoSiteListResponse)(nil), // 14: libcore.GetGeoSiteListResponse + (*CompileGeoIPToSrsRequest)(nil), // 15: libcore.CompileGeoIPToSrsRequest + (*CompileGeoSiteToSrsRequest)(nil), // 16: libcore.CompileGeoSiteToSrsRequest } var file_libcore_proto_depIdxs = []int32{ 0, // 0: libcore.TestReq.mode:type_name -> libcore.TestMode @@ -981,15 +1203,23 @@ var file_libcore_proto_depIdxs = []int32{ 6, // 7: libcore.LibcoreService.Test:input_type -> libcore.TestReq 8, // 8: libcore.LibcoreService.QueryStats:input_type -> libcore.QueryStatsReq 2, // 9: libcore.LibcoreService.ListConnections:input_type -> libcore.EmptyReq - 3, // 10: libcore.LibcoreService.Exit:output_type -> libcore.EmptyResp - 11, // 11: libcore.LibcoreService.Update:output_type -> libcore.UpdateResp - 4, // 12: libcore.LibcoreService.Start:output_type -> libcore.ErrorResp - 4, // 13: libcore.LibcoreService.Stop:output_type -> libcore.ErrorResp - 7, // 14: libcore.LibcoreService.Test:output_type -> libcore.TestResp - 9, // 15: libcore.LibcoreService.QueryStats:output_type -> libcore.QueryStatsResp - 12, // 16: libcore.LibcoreService.ListConnections:output_type -> libcore.ListConnectionsResp - 10, // [10:17] is the sub-list for method output_type - 3, // [3:10] is the sub-list for method input_type + 2, // 10: libcore.LibcoreService.GetGeoIPList:input_type -> libcore.EmptyReq + 2, // 11: libcore.LibcoreService.GetGeoSiteList:input_type -> libcore.EmptyReq + 15, // 12: libcore.LibcoreService.CompileGeoIPToSrs:input_type -> libcore.CompileGeoIPToSrsRequest + 16, // 13: libcore.LibcoreService.CompileGeoSiteToSrs:input_type -> libcore.CompileGeoSiteToSrsRequest + 3, // 14: libcore.LibcoreService.Exit:output_type -> libcore.EmptyResp + 11, // 15: libcore.LibcoreService.Update:output_type -> libcore.UpdateResp + 4, // 16: libcore.LibcoreService.Start:output_type -> libcore.ErrorResp + 4, // 17: libcore.LibcoreService.Stop:output_type -> libcore.ErrorResp + 7, // 18: libcore.LibcoreService.Test:output_type -> libcore.TestResp + 9, // 19: libcore.LibcoreService.QueryStats:output_type -> libcore.QueryStatsResp + 12, // 20: libcore.LibcoreService.ListConnections:output_type -> libcore.ListConnectionsResp + 13, // 21: libcore.LibcoreService.GetGeoIPList:output_type -> libcore.GetGeoIPListResponse + 14, // 22: libcore.LibcoreService.GetGeoSiteList:output_type -> libcore.GetGeoSiteListResponse + 3, // 23: libcore.LibcoreService.CompileGeoIPToSrs:output_type -> libcore.EmptyResp + 3, // 24: libcore.LibcoreService.CompileGeoSiteToSrs:output_type -> libcore.EmptyResp + 14, // [14:25] is the sub-list for method output_type + 3, // [3:14] is the sub-list for method input_type 3, // [3:3] is the sub-list for extension type_name 3, // [3:3] is the sub-list for extension extendee 0, // [0:3] is the sub-list for field type_name @@ -1133,6 +1363,54 @@ func file_libcore_proto_init() { return nil } } + file_libcore_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetGeoIPListResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_libcore_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*GetGeoSiteListResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_libcore_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CompileGeoIPToSrsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_libcore_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*CompileGeoSiteToSrsRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -1140,7 +1418,7 @@ func file_libcore_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_libcore_proto_rawDesc, NumEnums: 2, - NumMessages: 11, + NumMessages: 15, NumExtensions: 0, NumServices: 1, }, diff --git a/go/grpc_server/gen/libcore.proto b/go/grpc_server/gen/libcore.proto index fd9f3c7..d914a19 100644 --- a/go/grpc_server/gen/libcore.proto +++ b/go/grpc_server/gen/libcore.proto @@ -12,6 +12,11 @@ service LibcoreService { rpc Test(TestReq) returns (TestResp) {} rpc QueryStats(QueryStatsReq) returns (QueryStatsResp) {} rpc ListConnections(EmptyReq) returns (ListConnectionsResp) {} + // + rpc GetGeoIPList(EmptyReq) returns (GetGeoIPListResponse); + rpc GetGeoSiteList(EmptyReq) returns (GetGeoSiteListResponse); + rpc CompileGeoIPToSrs(CompileGeoIPToSrsRequest) returns (EmptyResp); + rpc CompileGeoSiteToSrs(CompileGeoSiteToSrsRequest) returns (EmptyResp); } message EmptyReq {} @@ -93,3 +98,19 @@ message UpdateResp { message ListConnectionsResp { string nekoray_connections_json = 1; } + +message GetGeoIPListResponse { + repeated string items = 1; +} + +message GetGeoSiteListResponse { + repeated string items = 2; +} + +message CompileGeoIPToSrsRequest { + string item = 1; +} + +message CompileGeoSiteToSrsRequest { + string item = 1; +} \ No newline at end of file diff --git a/go/grpc_server/gen/libcore_grpc.pb.go b/go/grpc_server/gen/libcore_grpc.pb.go index 6b570d0..834e861 100644 --- a/go/grpc_server/gen/libcore_grpc.pb.go +++ b/go/grpc_server/gen/libcore_grpc.pb.go @@ -19,13 +19,17 @@ import ( const _ = grpc.SupportPackageIsVersion7 const ( - LibcoreService_Exit_FullMethodName = "/libcore.LibcoreService/Exit" - LibcoreService_Update_FullMethodName = "/libcore.LibcoreService/Update" - LibcoreService_Start_FullMethodName = "/libcore.LibcoreService/Start" - LibcoreService_Stop_FullMethodName = "/libcore.LibcoreService/Stop" - LibcoreService_Test_FullMethodName = "/libcore.LibcoreService/Test" - LibcoreService_QueryStats_FullMethodName = "/libcore.LibcoreService/QueryStats" - LibcoreService_ListConnections_FullMethodName = "/libcore.LibcoreService/ListConnections" + LibcoreService_Exit_FullMethodName = "/libcore.LibcoreService/Exit" + LibcoreService_Update_FullMethodName = "/libcore.LibcoreService/Update" + LibcoreService_Start_FullMethodName = "/libcore.LibcoreService/Start" + LibcoreService_Stop_FullMethodName = "/libcore.LibcoreService/Stop" + LibcoreService_Test_FullMethodName = "/libcore.LibcoreService/Test" + LibcoreService_QueryStats_FullMethodName = "/libcore.LibcoreService/QueryStats" + LibcoreService_ListConnections_FullMethodName = "/libcore.LibcoreService/ListConnections" + LibcoreService_GetGeoIPList_FullMethodName = "/libcore.LibcoreService/GetGeoIPList" + LibcoreService_GetGeoSiteList_FullMethodName = "/libcore.LibcoreService/GetGeoSiteList" + LibcoreService_CompileGeoIPToSrs_FullMethodName = "/libcore.LibcoreService/CompileGeoIPToSrs" + LibcoreService_CompileGeoSiteToSrs_FullMethodName = "/libcore.LibcoreService/CompileGeoSiteToSrs" ) // LibcoreServiceClient is the client API for LibcoreService service. @@ -39,6 +43,10 @@ type LibcoreServiceClient interface { Test(ctx context.Context, in *TestReq, opts ...grpc.CallOption) (*TestResp, error) QueryStats(ctx context.Context, in *QueryStatsReq, opts ...grpc.CallOption) (*QueryStatsResp, error) ListConnections(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*ListConnectionsResp, error) + GetGeoIPList(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*GetGeoIPListResponse, error) + GetGeoSiteList(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*GetGeoSiteListResponse, error) + CompileGeoIPToSrs(ctx context.Context, in *CompileGeoIPToSrsRequest, opts ...grpc.CallOption) (*EmptyResp, error) + CompileGeoSiteToSrs(ctx context.Context, in *CompileGeoSiteToSrsRequest, opts ...grpc.CallOption) (*EmptyResp, error) } type libcoreServiceClient struct { @@ -112,6 +120,42 @@ func (c *libcoreServiceClient) ListConnections(ctx context.Context, in *EmptyReq return out, nil } +func (c *libcoreServiceClient) GetGeoIPList(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*GetGeoIPListResponse, error) { + out := new(GetGeoIPListResponse) + err := c.cc.Invoke(ctx, LibcoreService_GetGeoIPList_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *libcoreServiceClient) GetGeoSiteList(ctx context.Context, in *EmptyReq, opts ...grpc.CallOption) (*GetGeoSiteListResponse, error) { + out := new(GetGeoSiteListResponse) + err := c.cc.Invoke(ctx, LibcoreService_GetGeoSiteList_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *libcoreServiceClient) CompileGeoIPToSrs(ctx context.Context, in *CompileGeoIPToSrsRequest, opts ...grpc.CallOption) (*EmptyResp, error) { + out := new(EmptyResp) + err := c.cc.Invoke(ctx, LibcoreService_CompileGeoIPToSrs_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *libcoreServiceClient) CompileGeoSiteToSrs(ctx context.Context, in *CompileGeoSiteToSrsRequest, opts ...grpc.CallOption) (*EmptyResp, error) { + out := new(EmptyResp) + err := c.cc.Invoke(ctx, LibcoreService_CompileGeoSiteToSrs_FullMethodName, in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // LibcoreServiceServer is the server API for LibcoreService service. // All implementations must embed UnimplementedLibcoreServiceServer // for forward compatibility @@ -123,6 +167,10 @@ type LibcoreServiceServer interface { Test(context.Context, *TestReq) (*TestResp, error) QueryStats(context.Context, *QueryStatsReq) (*QueryStatsResp, error) ListConnections(context.Context, *EmptyReq) (*ListConnectionsResp, error) + GetGeoIPList(context.Context, *EmptyReq) (*GetGeoIPListResponse, error) + GetGeoSiteList(context.Context, *EmptyReq) (*GetGeoSiteListResponse, error) + CompileGeoIPToSrs(context.Context, *CompileGeoIPToSrsRequest) (*EmptyResp, error) + CompileGeoSiteToSrs(context.Context, *CompileGeoSiteToSrsRequest) (*EmptyResp, error) mustEmbedUnimplementedLibcoreServiceServer() } @@ -151,6 +199,18 @@ func (UnimplementedLibcoreServiceServer) QueryStats(context.Context, *QueryStats func (UnimplementedLibcoreServiceServer) ListConnections(context.Context, *EmptyReq) (*ListConnectionsResp, error) { return nil, status.Errorf(codes.Unimplemented, "method ListConnections not implemented") } +func (UnimplementedLibcoreServiceServer) GetGeoIPList(context.Context, *EmptyReq) (*GetGeoIPListResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetGeoIPList not implemented") +} +func (UnimplementedLibcoreServiceServer) GetGeoSiteList(context.Context, *EmptyReq) (*GetGeoSiteListResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GetGeoSiteList not implemented") +} +func (UnimplementedLibcoreServiceServer) CompileGeoIPToSrs(context.Context, *CompileGeoIPToSrsRequest) (*EmptyResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method CompileGeoIPToSrs not implemented") +} +func (UnimplementedLibcoreServiceServer) CompileGeoSiteToSrs(context.Context, *CompileGeoSiteToSrsRequest) (*EmptyResp, error) { + return nil, status.Errorf(codes.Unimplemented, "method CompileGeoSiteToSrs not implemented") +} func (UnimplementedLibcoreServiceServer) mustEmbedUnimplementedLibcoreServiceServer() {} // UnsafeLibcoreServiceServer may be embedded to opt out of forward compatibility for this service. @@ -290,6 +350,78 @@ func _LibcoreService_ListConnections_Handler(srv interface{}, ctx context.Contex return interceptor(ctx, in, info, handler) } +func _LibcoreService_GetGeoIPList_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(EmptyReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(LibcoreServiceServer).GetGeoIPList(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: LibcoreService_GetGeoIPList_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(LibcoreServiceServer).GetGeoIPList(ctx, req.(*EmptyReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _LibcoreService_GetGeoSiteList_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(EmptyReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(LibcoreServiceServer).GetGeoSiteList(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: LibcoreService_GetGeoSiteList_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(LibcoreServiceServer).GetGeoSiteList(ctx, req.(*EmptyReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _LibcoreService_CompileGeoIPToSrs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CompileGeoIPToSrsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(LibcoreServiceServer).CompileGeoIPToSrs(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: LibcoreService_CompileGeoIPToSrs_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(LibcoreServiceServer).CompileGeoIPToSrs(ctx, req.(*CompileGeoIPToSrsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _LibcoreService_CompileGeoSiteToSrs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CompileGeoSiteToSrsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(LibcoreServiceServer).CompileGeoSiteToSrs(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: LibcoreService_CompileGeoSiteToSrs_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(LibcoreServiceServer).CompileGeoSiteToSrs(ctx, req.(*CompileGeoSiteToSrsRequest)) + } + return interceptor(ctx, in, info, handler) +} + // LibcoreService_ServiceDesc is the grpc.ServiceDesc for LibcoreService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -325,6 +457,22 @@ var LibcoreService_ServiceDesc = grpc.ServiceDesc{ MethodName: "ListConnections", Handler: _LibcoreService_ListConnections_Handler, }, + { + MethodName: "GetGeoIPList", + Handler: _LibcoreService_GetGeoIPList_Handler, + }, + { + MethodName: "GetGeoSiteList", + Handler: _LibcoreService_GetGeoSiteList_Handler, + }, + { + MethodName: "CompileGeoIPToSrs", + Handler: _LibcoreService_CompileGeoIPToSrs_Handler, + }, + { + MethodName: "CompileGeoSiteToSrs", + Handler: _LibcoreService_CompileGeoSiteToSrs_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "libcore.proto", diff --git a/go/grpc_server/gen/update_proto.sh b/go/grpc_server/gen/update_proto.sh index 709ed22..ac894ad 100644 --- a/go/grpc_server/gen/update_proto.sh +++ b/go/grpc_server/gen/update_proto.sh @@ -1,3 +1 @@ -protoc -I . --go_out=. --go_opt paths=source_relative --go-grpc_out=. --go-grpc_opt paths=source_relative libcore.proto - -# protoc -I . --cpp_out=. --grpc_out=. --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` libcore.proto +protoc -I . --go_out=. --go_opt paths=source_relative --go-grpc_out=. --go-grpc_opt paths=source_relative libcore.proto \ No newline at end of file diff --git a/libs/build_go.sh b/libs/build_go.sh index 86171fe..3b0df74 100755 --- a/libs/build_go.sh +++ b/libs/build_go.sh @@ -25,5 +25,5 @@ popd #### Go: nekobox_core #### pushd go/cmd/nekobox_core -go build -v -o $DEST -trimpath -ldflags "-w -s -X $neko_common.Version_neko=$version_standalone" -tags "with_clash_api,with_gvisor,with_quic,with_wireguard,with_utls,with_ech" +go build -v -o $DEST -trimpath -ldflags "-w -s -X $neko_common.Version_neko=$version_standalone" -tags "with_clash_api,with_gvisor,with_quic,with_wireguard,with_utls,with_ech,with_dhcp" popd diff --git a/main/NekoGui.cpp b/main/NekoGui.cpp index b661300..922019c 100644 --- a/main/NekoGui.cpp +++ b/main/NekoGui.cpp @@ -86,16 +86,28 @@ namespace NekoGui_ConfigItem { case itemType::boolean: object.insert(item->name, *(bool *) item->ptr); break; - case itemType::stringList: - object.insert(item->name, QList2QJsonArray(*(QList *) item->ptr)); + case itemType::stringList: { + if (QListStr2QJsonArray(*(QList *) item->ptr).isEmpty()) continue; + object.insert(item->name, QListStr2QJsonArray(*(QList *) item->ptr)); break; - case itemType::integerList: - object.insert(item->name, QList2QJsonArray(*(QList *) item->ptr)); + } + case itemType::integerList: { + if (QListInt2QJsonArray(*(QList *) item->ptr).isEmpty()) continue; + object.insert(item->name, QListInt2QJsonArray(*(QList *) item->ptr)); break; + } case itemType::jsonStore: // _add 时应关联对应 JsonStore 的指针 object.insert(item->name, ((JsonStore *) item->ptr)->ToJson()); break; + case itemType::jsonStoreList: + QJsonArray jsonArray; + auto arr = *(QList *) item->ptr; + for ( JsonStore* obj : arr) { + jsonArray.push_back(obj->ToJson()); + } + object.insert(item->name, jsonArray); + break; } } return object; @@ -330,6 +342,7 @@ namespace NekoGui { if (!Preset::SingBox::DomainStrategy.contains(domain_strategy)) domain_strategy = ""; if (!Preset::SingBox::DomainStrategy.contains(outbound_domain_strategy)) outbound_domain_strategy = ""; _add(new configItem("current_route_id", &this->current_route_id, itemType::integer)); + _add(new configItem("default_outbound", &this->def_outbound, itemType::string)); // _add(new configItem("remote_dns", &this->remote_dns, itemType::string)); _add(new configItem("remote_dns_strategy", &this->remote_dns_strategy, itemType::string)); @@ -345,20 +358,7 @@ namespace NekoGui { } QStringList Routing::List() { - QDir dr(ROUTES_PREFIX); - return dr.entryList(QDir::Files); - } - - bool Routing::SetToActive(const QString &name) { - NekoGui::dataStore->routing = std::make_unique(); - NekoGui::dataStore->routing->load_control_must = true; - NekoGui::dataStore->routing->fn = ROUTES_PREFIX + name; - auto ok = NekoGui::dataStore->routing->Load(); - if (ok) { - NekoGui::dataStore->active_routing = name; - NekoGui::dataStore->Save(); - } - return ok; + return {"Default"}; } // NO default extra core diff --git a/main/NekoGui.hpp b/main/NekoGui.hpp index a4e349d..71a855e 100644 --- a/main/NekoGui.hpp +++ b/main/NekoGui.hpp @@ -18,5 +18,6 @@ namespace NekoGui { } // namespace NekoGui #define IS_NEKO_BOX (NekoGui::coreType == NekoGui::CoreType::SING_BOX) -#define ROUTES_PREFIX_NAME QString(IS_NEKO_BOX ? "routes_box" : "routes") +#define ROUTES_PREFIX_NAME QString("route_profiles") #define ROUTES_PREFIX QString(ROUTES_PREFIX_NAME + "/") +#define RULE_SETS_DIR QString("rule_sets") diff --git a/main/NekoGui_ConfigItem.hpp b/main/NekoGui_ConfigItem.hpp index 152ac3e..c8ead41 100644 --- a/main/NekoGui_ConfigItem.hpp +++ b/main/NekoGui_ConfigItem.hpp @@ -10,6 +10,7 @@ namespace NekoGui_ConfigItem { stringList, integerList, jsonStore, + jsonStoreList, }; class configItem { @@ -57,11 +58,11 @@ namespace NekoGui_ConfigItem { QByteArray ToJsonBytes(); - void FromJson(QJsonObject object); + virtual void FromJson(QJsonObject object); void FromJsonBytes(const QByteArray &data); - bool Save(); + virtual bool Save(); bool Load(); }; diff --git a/main/NekoGui_DataStore.hpp b/main/NekoGui_DataStore.hpp index 9065f2e..4c91102 100644 --- a/main/NekoGui_DataStore.hpp +++ b/main/NekoGui_DataStore.hpp @@ -25,8 +25,6 @@ namespace NekoGui { explicit Routing(int preset = 0); static QStringList List(); - - static bool SetToActive(const QString &name); }; class ExtraCore : public JsonStore { @@ -179,7 +177,7 @@ namespace NekoGui { void UpdateStartedId(int id); - QString GetUserAgent(bool isDefault = false) const; + [[nodiscard]] QString GetUserAgent(bool isDefault = false) const; }; extern DataStore *dataStore; diff --git a/main/NekoGui_Utils.cpp b/main/NekoGui_Utils.cpp index 92a0672..ef613a0 100644 --- a/main/NekoGui_Utils.cpp +++ b/main/NekoGui_Utils.cpp @@ -105,17 +105,26 @@ QString QJsonObject2QString(const QJsonObject &jsonObject, bool compact) { return QJsonDocument(jsonObject).toJson(compact ? QJsonDocument::Compact : QJsonDocument::Indented); } -template -QJsonArray QList2QJsonArray(const QList &list) { +QJsonArray QListStr2QJsonArray(const QList &list) { + QVariantList list2; + bool isEmpty = true; + for (auto &item: list) { + if (item.trimmed().isEmpty()) continue; + list2.append(item); + isEmpty = false; + } + + if (isEmpty) return {}; + else return QJsonArray::fromVariantList(list2); +} + +QJsonArray QListInt2QJsonArray(const QList &list) { QVariantList list2; for (auto &item: list) list2.append(item); return QJsonArray::fromVariantList(list2); } -template QJsonArray QList2QJsonArray(const QList &list); -template QJsonArray QList2QJsonArray(const QList &list); - QList QJsonArray2QListInt(const QJsonArray &arr) { QList list2; for (auto item: arr) diff --git a/main/NekoGui_Utils.hpp b/main/NekoGui_Utils.hpp index 034dc1f..ec69c58 100644 --- a/main/NekoGui_Utils.hpp +++ b/main/NekoGui_Utils.hpp @@ -83,8 +83,9 @@ QJsonObject QString2QJsonObject(const QString &jsonString); QString QJsonObject2QString(const QJsonObject &jsonObject, bool compact); -template -QJsonArray QList2QJsonArray(const QList &list); +QJsonArray QListInt2QJsonArray(const QList &list); + +QJsonArray QListStr2QJsonArray(const QList &list); QList QJsonArray2QListInt(const QJsonArray &arr); diff --git a/main/main.cpp b/main/main.cpp index c7c7e8b..8da4f2b 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -187,20 +187,16 @@ int main(int argc, char* argv[]) { if (!dir.exists(ROUTES_PREFIX_NAME)) { dir_success &= dir.mkdir(ROUTES_PREFIX_NAME); } + if (!dir.exists(RULE_SETS_DIR)) { + dir_success &= dir.mkdir(RULE_SETS_DIR); + } if (!dir_success) { QMessageBox::warning(nullptr, "Error", "No permission to write " + dir.absolutePath()); return 1; } // Load dataStore - switch (NekoGui::coreType) { - case NekoGui::CoreType::SING_BOX: - NekoGui::dataStore->fn = "groups/nekobox.json"; - break; - default: - MessageBoxWarning("Error", "Unknown coreType."); - return 0; - } + NekoGui::dataStore->fn = "groups/nekobox.json"; auto isLoaded = NekoGui::dataStore->Load(); if (!isLoaded) { NekoGui::dataStore->Save(); @@ -211,7 +207,7 @@ int main(int argc, char* argv[]) { // load routing NekoGui::dataStore->routing = std::make_unique(); - NekoGui::dataStore->routing->fn = ROUTES_PREFIX + NekoGui::dataStore->active_routing; + NekoGui::dataStore->routing->fn = ROUTES_PREFIX + "Default"; isLoaded = NekoGui::dataStore->routing->Load(); if (!isLoaded) { NekoGui::dataStore->routing->Save(); diff --git a/rpc/gRPC.cpp b/rpc/gRPC.cpp index d78d692..6a59414 100644 --- a/rpc/gRPC.cpp +++ b/rpc/gRPC.cpp @@ -3,8 +3,6 @@ #include #include -#ifndef NKR_NO_GRPC - #include "main/NekoGui.hpp" #include @@ -16,6 +14,8 @@ #include #include +#include + namespace QtGrpc { const char *GrpcAcceptEncodingHeader = "grpc-accept-encoding"; const char *AcceptEncodingHeader = "accept-encoding"; @@ -280,6 +280,78 @@ namespace NekoGui_rpc { return reply; } } -} // namespace NekoGui_rpc -#endif + QStringList Client::GetGeoList(bool *rpcOK, GeoRuleSetType mode) { + switch (mode) { + case GeoRuleSetType::ip: { + libcore::EmptyReq req; + libcore::GetGeoIPListResponse resp; + + auto status = default_grpc_channel->Call("GetGeoIPList", req, &resp); + if (status == QNetworkReply::NoError) { + QStringList res; + for (const auto & i : resp.items()) { + res.append(QString::fromStdString(i)); + } + *rpcOK = true; + return res; + } else { + NOT_OK + return {}; + } + } + case GeoRuleSetType::site: { + libcore::EmptyReq req; + libcore::GetGeoSiteListResponse resp; + + auto status = default_grpc_channel->Call("GetGeoSiteList", req, &resp); + if (status == QNetworkReply::NoError) { + QStringList res; + for (const auto & i : resp.items()) { + res.append(QString::fromStdString(i)); + } + *rpcOK = true; + return res; + } else { + NOT_OK + return {}; + } + } + } + return {}; + } + + QString Client::CompileGeoSet(bool *rpcOK, GeoRuleSetType mode, std::string category) { + switch (mode) { + case ip: { + libcore::CompileGeoIPToSrsRequest req; + libcore::EmptyResp resp; + req.set_item(category); + + auto status = default_grpc_channel->Call("CompileGeoIPToSrs", req, &resp); + if (status == QNetworkReply::NoError) { + *rpcOK = true; + return ""; + } else { + NOT_OK + return qt_error_string(status); + } + } + case site: { + libcore::CompileGeoSiteToSrsRequest req; + libcore::EmptyResp resp; + req.set_item(category); + + auto status = default_grpc_channel->Call("CompileGeoSiteToSrs", req, &resp); + if (status == QNetworkReply::NoError) { + *rpcOK = true; + return ""; + } else { + NOT_OK + return qt_error_string(status); + } + } + } + } + +} // namespace NekoGui_rpc diff --git a/rpc/gRPC.h b/rpc/gRPC.h index 03a5efa..b803879 100644 --- a/rpc/gRPC.h +++ b/rpc/gRPC.h @@ -10,6 +10,8 @@ namespace QtGrpc { } namespace NekoGui_rpc { + enum GeoRuleSetType {ip, site}; + class Client { public: explicit Client(std::function onError, const QString &target, const QString &token); @@ -28,6 +30,10 @@ namespace NekoGui_rpc { libcore::UpdateResp Update(bool *rpcOK, const libcore::UpdateReq &request); + QStringList GetGeoList(bool *rpcOK, GeoRuleSetType mode); + + QString CompileGeoSet(bool *rpcOK, GeoRuleSetType mode, std::string category); + private: std::function()> make_grpc_channel; std::unique_ptr default_grpc_channel; diff --git a/ui/dialog_manage_routes.cpp b/ui/dialog_manage_routes.cpp index f00e8b3..cebe6e9 100644 --- a/ui/dialog_manage_routes.cpp +++ b/ui/dialog_manage_routes.cpp @@ -1,7 +1,6 @@ #include "dialog_manage_routes.h" #include "ui_dialog_manage_routes.h" #include "db/Database.hpp" -//#include "ui_RouteItem.h" #include "3rdparty/qv2ray/v2/ui/widgets/editors/w_JsonEditor.hpp" #include "3rdparty/qv2ray/v3/components/GeositeReader/GeositeReader.hpp" @@ -11,53 +10,47 @@ #include #include -QList getRouteProfiles() { - auto routeProfiles = NekoGui::profileManager->routes; - QList res; - - for (const auto &item: routeProfiles) { - res << item.second->name; - } - return res; -} - -int getRouteID(const QString& name) { - auto routeProfiles = NekoGui::profileManager->routes; - - for (const auto &item: routeProfiles) { - if (item.second->name == name) return item.first; - } - - return -1; -} - -QString getRouteName(int id) { - return NekoGui::profileManager->routes.count(id) ? NekoGui::profileManager->routes[id]->name : ""; -} - -QList deleteItemFromList(const QList& base, const QString& target) { - QList res; - for (const auto &item: base) { - if (item == target) continue; - res << item; - } - return res; -} - void DialogManageRoutes::reloadProfileItems() { + QSignalBlocker blocker = QSignalBlocker(ui->route_prof); // apparently the currentIndexChanged will make us crash if we clear the QComboBox ui->route_prof->clear(); + blocker.unblock(); + ui->route_profiles->clear(); - ui->route_prof->addItems(currentRouteProfiles); - ui->route_profiles->addItems(currentRouteProfiles); + bool selectedChainGone = true; + int i=0; + for (const auto &item: chainList) { + ui->route_prof->addItem(item->name); + ui->route_profiles->addItem(item->name); + if (item->id == currentRouteProfileID) { + ui->route_prof->setCurrentIndex(i); + selectedChainGone=false; + } + i++; + } + if (selectedChainGone) { + currentRouteProfileID=0; + ui->route_prof->setCurrentIndex(0); + } } DialogManageRoutes::DialogManageRoutes(QWidget *parent) : QDialog(parent), ui(new Ui::DialogManageRoutes) { ui->setupUi(this); - currentRouteProfiles = getRouteProfiles(); + auto profiles = NekoGui::profileManager->routes; + for (const auto &item: profiles) { + chainList << item.second; + } + if (chainList.empty()) { + auto defaultChain = NekoGui::RoutingChain::GetDefaultChain(); + NekoGui::profileManager->AddRouteChain(defaultChain); + chainList.append(defaultChain); + } + currentRouteProfileID = NekoGui::dataStore->routing->current_route_id; + if (currentRouteProfileID < 0) currentRouteProfileID = chainList[0]->id; QStringList qsValue = {""}; QString dnsHelpDocumentUrl; + ui->default_out->setCurrentText(NekoGui::dataStore->routing->def_outbound); ui->outbound_domain_strategy->addItems(Preset::SingBox::DomainStrategy); ui->domainStrategyCombo->addItems(Preset::SingBox::DomainStrategy); qsValue += QString("prefer_ipv4 prefer_ipv6 ipv4_only ipv6_only").split(" "); @@ -96,17 +89,11 @@ DialogManageRoutes::DialogManageRoutes(QWidget *parent) : QDialog(parent), ui(ne ui->direct_dns_strategy->setCurrentText(NekoGui::dataStore->routing->direct_dns_strategy); ui->dns_final_out->setCurrentText(NekoGui::dataStore->routing->dns_final_out); reloadProfileItems(); - ui->route_prof->setCurrentText(getRouteName(NekoGui::dataStore->routing->current_route_id)); - - connect(ui->delete_route, &QPushButton::clicked, this, [=]{ - auto current = ui->route_profiles->currentItem()->text(); - currentRouteProfiles = deleteItemFromList(currentRouteProfiles, current); - reloadProfileItems(); + connect(ui->route_prof, &QComboBox::currentIndexChanged, this, [=](const int& idx) { + currentRouteProfileID = chainList[idx]->id; }); - - ADD_ASTERISK(this) } @@ -115,7 +102,7 @@ DialogManageRoutes::~DialogManageRoutes() { } void DialogManageRoutes::accept() { - if (currentRouteProfiles.empty()) { + if (chainList.empty()) { MessageBoxInfo("Invalid settings", "Routing profile cannot be empty"); return; } @@ -132,7 +119,55 @@ void DialogManageRoutes::accept() { NekoGui::dataStore->routing->direct_dns_strategy = ui->direct_dns_strategy->currentText(); NekoGui::dataStore->routing->dns_final_out = ui->dns_final_out->currentText(); - // TODO add mine + NekoGui::profileManager->UpdateRouteChains(chainList); + NekoGui::dataStore->routing->current_route_id = currentRouteProfileID; + NekoGui::dataStore->routing->def_outbound = ui->default_out->currentText(); + + + // + QStringList msg{"UpdateDataStore"}; + msg << "RouteChanged"; + MW_dialog_message("", msg.join(",")); QDialog::accept(); +} + +void DialogManageRoutes::on_new_route_clicked() { + routeChainWidget = new RouteItem(this, NekoGui::ProfileManager::NewRouteChain()); + routeChainWidget->setWindowModality(Qt::ApplicationModal); + routeChainWidget->show(); + connect(routeChainWidget, &RouteItem::settingsChanged, this, [=](const std::shared_ptr& chain) { + chainList << chain; + reloadProfileItems(); + }); +} + +void DialogManageRoutes::on_edit_route_clicked() { + auto idx = ui->route_profiles->currentRow(); + if (idx < 0) return; + + routeChainWidget = new RouteItem(this, chainList[idx]); + routeChainWidget->setWindowModality(Qt::ApplicationModal); + routeChainWidget->show(); + connect(routeChainWidget, &RouteItem::settingsChanged, this, [=](const std::shared_ptr& chain) { + chainList[idx] = chain; + reloadProfileItems(); + }); + +} + +void DialogManageRoutes::on_delete_route_clicked() { + auto idx = ui->route_profiles->currentRow(); + if (idx < 0) return; + if (chainList.size() == 1) { + MessageBoxWarning("Invalid operation", "Routing Profiles cannot be empty, try adding another profile or editing this one"); + return; + } + + auto profileToDel = chainList[idx]; + chainList.removeAt(idx); + if (profileToDel->id == currentRouteProfileID) { + currentRouteProfileID = chainList[0]->id; + } + reloadProfileItems(); } \ No newline at end of file diff --git a/ui/dialog_manage_routes.h b/ui/dialog_manage_routes.h index b4c376a..89ea705 100644 --- a/ui/dialog_manage_routes.h +++ b/ui/dialog_manage_routes.h @@ -5,6 +5,7 @@ #include "3rdparty/qv2ray/v2/ui/QvAutoCompleteTextEdit.hpp" #include "main/NekoGui.hpp" +#include "widget/RouteItem.h" QT_BEGIN_NAMESPACE namespace Ui { @@ -23,9 +24,13 @@ public: private: Ui::DialogManageRoutes *ui; + RouteItem* routeChainWidget; + void reloadProfileItems(); - QList currentRouteProfiles; + QList> chainList; + + int currentRouteProfileID = -1; public slots: void accept() override; diff --git a/ui/dialog_manage_routes.ui b/ui/dialog_manage_routes.ui index 6c07a8b..f046500 100644 --- a/ui/dialog_manage_routes.ui +++ b/ui/dialog_manage_routes.ui @@ -27,45 +27,6 @@ - - - - false - - - - - - - Server Address Strategy - - - - - - - For V2Ray, it sets routing.domainStrategy -For sing-box, it sets inbound.domain_strategy - - - Domain Strategy - - - - - - - Sniffing Mode - - - - - - - false - - - @@ -85,8 +46,26 @@ For sing-box, it sets inbound.domain_strategy - - + + + + Sniffing Mode + + + + + + + Server Address Strategy + + + + + + + false + + @@ -95,6 +74,48 @@ For sing-box, it sets inbound.domain_strategy + + + + + + + false + + + + + + + For V2Ray, it sets routing.domainStrategy +For sing-box, it sets inbound.domain_strategy + + + Domain Strategy + + + + + + + + proxy + + + + + direct + + + + + + + + Default Outbound + + + diff --git a/ui/mainwindow.cpp b/ui/mainwindow.cpp index 1739230..54cc529 100644 --- a/ui/mainwindow.cpp +++ b/ui/mainwindow.cpp @@ -92,17 +92,15 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi } // software_name - if (IS_NEKO_BOX) { - software_name = "NekoBox"; - software_core_name = "sing-box"; - // replace default values - if (NekoGui::dataStore->log_level == "warning") NekoGui::dataStore->log_level = "info"; - if (NekoGui::dataStore->mux_protocol.isEmpty()) NekoGui::dataStore->mux_protocol = "h2mux"; - // - if (QDir("dashboard").count() == 0) { - QDir().mkdir("dashboard"); - QFile::copy(":/neko/dashboard-notice.html", "dashboard/index.html"); - } + software_name = "NekoBox"; + software_core_name = "sing-box"; + // replace default values + if (NekoGui::dataStore->log_level == "warning") NekoGui::dataStore->log_level = "info"; + if (NekoGui::dataStore->mux_protocol.isEmpty()) NekoGui::dataStore->mux_protocol = "h2mux"; + // + if (QDir("dashboard").count() == 0) { + QDir().mkdir("dashboard"); + QFile::copy(":/neko/dashboard-notice.html", "dashboard/index.html"); } // top bar @@ -275,17 +273,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi ui->menuActive_Server->addAction(a); if (++active_server_item_count == 100) break; } - // active routing - for (const auto &old: ui->menuActive_Routing->actions()) { - ui->menuActive_Routing->removeAction(old); - old->deleteLater(); - } - for (const auto &name: NekoGui::Routing::List()) { - auto a = new QAction(name, this); - a->setCheckable(true); - a->setChecked(name == NekoGui::dataStore->active_routing); - ui->menuActive_Routing->addAction(a); - } }); connect(ui->menuActive_Server, &QMenu::triggered, this, [=](QAction *a) { bool ok; @@ -297,24 +284,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi neko_start(id); } }); - connect(ui->menuActive_Routing, &QMenu::triggered, this, [=](QAction *a) { - auto fn = a->text(); - if (!fn.isEmpty()) { - NekoGui::Routing r; - r.load_control_must = true; - r.fn = ROUTES_PREFIX + fn; - if (r.Load()) { - if (QMessageBox::question(GetMessageBoxParent(), software_name, tr("Load routing and apply: %1").arg(fn) + "\n" ) == QMessageBox::Yes) { - NekoGui::Routing::SetToActive(fn); - if (NekoGui::dataStore->started_id >= 0) { - neko_start(NekoGui::dataStore->started_id); - } else { - refresh_status(); - } - } - } - } - }); connect(ui->actionRemember_last_proxy, &QAction::triggered, this, [=](bool checked) { NekoGui::dataStore->remember_enable = checked; NekoGui::dataStore->Save(); @@ -531,6 +500,7 @@ void MainWindow::dialog_message_impl(const QString &sender, const QString &info) if (info.contains("UpdateDataStore")) { auto suggestRestartProxy = NekoGui::dataStore->Save(); if (info.contains("RouteChanged")) { + NekoGui::dataStore->routing->Save(); suggestRestartProxy = true; } if (info.contains("NeedRestart")) { diff --git a/ui/mainwindow.h b/ui/mainwindow.h index e863459..b059da7 100644 --- a/ui/mainwindow.h +++ b/ui/mainwindow.h @@ -179,7 +179,7 @@ private: void HotkeyEvent(const QString &key); - // grpc and ... + // grpc static void setup_grpc(); diff --git a/ui/mainwindow.ui b/ui/mainwindow.ui index 11a331c..36c71d3 100644 --- a/ui/mainwindow.ui +++ b/ui/mainwindow.ui @@ -408,12 +408,6 @@ - - - Active Routing - - - @@ -422,7 +416,6 @@ - diff --git a/ui/mainwindow_grpc.cpp b/ui/mainwindow_grpc.cpp index 503d885..48402e4 100644 --- a/ui/mainwindow_grpc.cpp +++ b/ui/mainwindow_grpc.cpp @@ -409,7 +409,6 @@ void MainWindow::neko_stop(bool crash, bool sem) { }, DS_cores); -#ifndef NKR_NO_GRPC NekoGui_traffic::trafficLooper->loop_enabled = false; NekoGui_traffic::trafficLooper->loop_mutex.lock(); if (NekoGui::dataStore->traffic_loop_interval != 0) { @@ -431,7 +430,6 @@ void MainWindow::neko_stop(bool crash, bool sem) { return false; } } -#endif NekoGui::dataStore->UpdateStartedId(-1919); NekoGui::dataStore->need_keep_vpn_off = false; @@ -475,7 +473,6 @@ void MainWindow::neko_stop(bool crash, bool sem) { void MainWindow::CheckUpdate() { // on new thread... -#ifndef NKR_NO_GRPC bool ok; libcore::UpdateReq request; request.set_action(libcore::UpdateAction::Check); @@ -536,5 +533,4 @@ void MainWindow::CheckUpdate() { QDesktopServices::openUrl(QUrl(response.release_url().c_str())); } }); -#endif -} +} \ No newline at end of file diff --git a/ui/widget/RouteItem.cpp b/ui/widget/RouteItem.cpp index 33dbc88..fd3eaba 100644 --- a/ui/widget/RouteItem.cpp +++ b/ui/widget/RouteItem.cpp @@ -2,7 +2,8 @@ #include "ui_RouteItem.h" #include "db/RouteEntity.h" #include "db/Database.hpp" - +#include +#include "rpc/gRPC.h" int RouteItem::getIndexOf(const QString& name) const { for (int i=0;iRules.size();i++) { @@ -13,7 +14,8 @@ int RouteItem::getIndexOf(const QString& name) const { } QString get_outbound_name(int id) { - // -2 is direct -3 is block -4 is dns_out + // -1 is proxy -2 is direct -3 is block -4 is dns_out + if (id == -1) return "proxy"; if (id == -2) return "direct"; if (id == -3) return "block"; if (id == -4) return "dns_out"; @@ -23,6 +25,7 @@ QString get_outbound_name(int id) { } int get_outbound_id(const QString& name) { + if (name == "proxy") return -1; if (name == "direct") return -2; if (name == "block") return -3; if (name == "dns_out") return -4; @@ -45,18 +48,42 @@ QStringList get_all_outbounds() { } RouteItem::RouteItem(QWidget *parent, const std::shared_ptr& routeChain) - : QGroupBox(parent), ui(new Ui::RouteItem) { + : QDialog(parent), ui(new Ui::RouteItem) { ui->setupUi(this); // make a copy chain = routeChain; + // add the default rule if empty + if (chain->Rules.empty()) { + auto routeItem = std::make_shared(); + routeItem->name = "dns-hijack"; + routeItem->protocol = "dns"; + routeItem->outboundID = -4; + chain->Rules << routeItem; + } + + // setup rule set helper + bool ok; // for now we discard this + auto geoIpList = NekoGui_rpc::defaultClient->GetGeoList(&ok, NekoGui_rpc::GeoRuleSetType::ip); + auto geoSiteList = NekoGui_rpc::defaultClient->GetGeoList(&ok, NekoGui_rpc::GeoRuleSetType::site); + geo_items << geoIpList << geoSiteList; + helperModel = new QStringListModel(geo_items, this); + ui->rule_set_helper->hide(); + ui->rule_set_helper->setEditTriggers(QAbstractItemView::NoEditTriggers); + ui->rule_set_helper->setSelectionMode(QAbstractItemView::SingleSelection); + ui->rule_set_helper->setSelectionRectVisible(false); + ui->rule_set_helper->setModel(helperModel); + connect(ui->rule_set_helper, &QListView::clicked, this, [=](const QModelIndex& index){ + applyRuleHelperSelect(index); + }); + std::map valueMap; for (auto &item: chain->Rules) { auto baseName = item->name; int randPart; if (baseName == "") { - randPart = GetRandomUint64()%1000; + randPart = int(GetRandomUint64()%1000); baseName = "rule_" + Int2String(randPart); lastNum = std::max(lastNum, randPart); } @@ -64,7 +91,7 @@ RouteItem::RouteItem(QWidget *parent, const std::shared_ptr 1) { valueMap[baseName]--; - randPart = GetRandomUint64()%1000; + randPart = int(GetRandomUint64()%1000); baseName = "rule_" + Int2String(randPart); lastNum = std::max(lastNum, randPart); continue; @@ -75,13 +102,17 @@ RouteItem::RouteItem(QWidget *parent, const std::shared_ptrroute_items->addItem(item->name); } - QStringList outboundOptions = {"direct", "block", "dns_out"}; + QStringList outboundOptions = {"proxy", "direct", "block", "dns_out"}; outboundOptions << get_all_outbounds(); + ui->route_name->setText(chain->name); ui->rule_attr->addItems(NekoGui::RouteRule::get_attributes()); ui->rule_out->addItems(outboundOptions); ui->rule_attr_text->hide(); ui->rule_attr_data->setTitle(""); + ui->rule_attr_box->setEnabled(false); + ui->rule_preview->setEnabled(false); + updateRuleSection(); connect(ui->route_name, &QLineEdit::textChanged, this, [=](const QString& text) { chain->name = text; @@ -93,7 +124,7 @@ RouteItem::RouteItem(QWidget *parent, const std::shared_ptrrule_name, &QLineEdit::textChanged, this, [=](const QString& text) { if (currentIndex == -1) return; chain->Rules[currentIndex]->name = text; + updateRouteItemsView(); }); connect(ui->rule_attr_selector, &QComboBox::currentTextChanged, this, [=](const QString& text){ if (currentIndex == -1) return; chain->Rules[currentIndex]->set_field_value(ui->rule_attr->currentText(), {text}); + updateRulePreview(); }); connect(ui->rule_attr_text, &QTextEdit::textChanged, this, [=] { if (currentIndex == -1) return; auto currentVal = ui->rule_attr_text->toPlainText().split('\n'); chain->Rules[currentIndex]->set_field_value(ui->rule_attr->currentText(), currentVal); + if (ui->rule_attr->currentText() == "rule_set") updateHelperItems(currentVal.last()); + updateRulePreview(); }); connect(ui->rule_out, &QComboBox::currentTextChanged, this, [=](const QString& text) { @@ -122,53 +157,56 @@ RouteItem::RouteItem(QWidget *parent, const std::shared_ptrRules[currentIndex]->outboundID = id; + updateRulePreview(); }); connect(ui->route_items, &QListWidget::itemClicked, this, [=](const QListWidgetItem *item) { auto idx = getIndexOf(item->text()); if (idx == -1) return; currentIndex = idx; - auto ruleItem = chain->Rules[idx]; - ui->rule_out->setCurrentText(get_outbound_name(ruleItem->outboundID)); - setDefaultRuleData(ruleItem->ip_version); + updateRuleSection(); }); connect(ui->rule_attr, &QComboBox::currentTextChanged, this, [=](const QString& text){ - if (currentIndex == -1) return; - ui->rule_attr_data->setTitle(text); - auto inputType = NekoGui::RouteRule::get_input_type(text); - switch (inputType) { - case NekoGui::trufalse: { - QStringList items = {"", "true", "false"}; - auto currentValPtr = chain->Rules[currentIndex]->get_current_value_bool(text); - QString currentVal = currentValPtr == nullptr ? "" : *currentValPtr ? "true" : "false"; - showSelectItem(items, currentVal); - break; - } - case NekoGui::select: { - auto items = NekoGui::RouteRule::get_values_for_field(text); - auto currentVal = chain->Rules[currentIndex]->get_current_value_string(text)[0]; - showSelectItem(items, currentVal); - break; - } - case NekoGui::text: { - auto currentItems = chain->Rules[currentIndex]->get_current_value_string(text); - showTextEnterItem(currentItems); - break; - } - } + updateRuleSection(); }); - connect(ui->new_route_item, &QPushButton::clicked, this, &RouteItem::on_new_route_item_clicked); - connect(ui->moveup_route_item, &QPushButton::clicked, this, &RouteItem::on_moveup_route_item_clicked); - connect(ui->movedown_route_item, &QPushButton::clicked, this, &RouteItem::on_movedown_route_item_clicked); - connect(ui->delete_route_item, &QPushButton::clicked, this, &RouteItem::on_delete_route_item_clicked); + connect(ui->buttonBox, &QDialogButtonBox::accepted, this, [=]{ + accept(); + }); + connect(ui->buttonBox, &QDialogButtonBox::rejected, this, [=]{ + QDialog::reject(); + }); } RouteItem::~RouteItem() { delete ui; } +void RouteItem::accept() { + if (chain->name == "") { + MessageBoxWarning("Invalid operation", "Cannot create Route Profile with empty name"); + return; + } + + int i=0; + for (const auto& item: chain->Rules) { + if (item->isEmpty()) { + chain->Rules.remove(i); + i--; + } + i++; + } + if (chain->Rules.empty()) { + MessageBoxInfo("Empty Route Profile", "No valid rules are in the profile"); + return; + } + + emit settingsChanged(chain); + + QDialog::accept(); +} + void RouteItem::updateRouteItemsView() { ui->route_items->clear(); if (chain->Rules.empty()) return; @@ -176,7 +214,45 @@ void RouteItem::updateRouteItemsView() { for (const auto& item: chain->Rules) { ui->route_items->addItem(item->name); } - ui->route_items->setCurrentRow(currentIndex); + if (currentIndex != -1) ui->route_items->setCurrentRow(currentIndex); +} + +void RouteItem::updateRuleSection() { + if (currentIndex == -1) return; + + auto ruleItem = chain->Rules[currentIndex]; + auto currentAttr = ui->rule_attr->currentText(); + switch (ruleItem->get_input_type(currentAttr)) { + case NekoGui::trufalse: { + QStringList items = {"false", "true"}; + QString currentVal = chain->Rules[currentIndex]->get_current_value_bool(currentAttr); + showSelectItem(items, currentVal); + break; + } + case NekoGui::select: { + auto items = NekoGui::RouteRule::get_values_for_field(currentAttr); + auto currentVal = chain->Rules[currentIndex]->get_current_value_string(currentAttr)[0]; + showSelectItem(items, currentVal); + break; + } + case NekoGui::text: { + auto currentItems = chain->Rules[currentIndex]->get_current_value_string(currentAttr); + showTextEnterItem(currentItems); + break; + } + } + ui->rule_name->setText(ruleItem->name); + ui->rule_attr_box->setEnabled(true); + if (currentAttr == "rule_set") ui->rule_set_helper->show(); + else ui->rule_set_helper->hide(); + + updateRulePreview(); +} + +void RouteItem::updateRulePreview() { + if (currentIndex == -1) return; + + ui->rule_preview->setText(QJsonObject2QString(chain->Rules[currentIndex]->get_rule_json(true), false)); } void RouteItem::setDefaultRuleData(const QString& currentData) { @@ -189,9 +265,7 @@ void RouteItem::showSelectItem(const QStringList& items, const QString& currentI ui->rule_attr_text->hide(); ui->rule_attr_selector->clear(); ui->rule_attr_selector->show(); - QStringList fullItems = {""}; - fullItems << items; - ui->rule_attr_selector->addItems(fullItems); + ui->rule_attr_selector->addItems(items); ui->rule_attr_selector->setCurrentText(currentItem); adjustSize(); } @@ -204,13 +278,34 @@ void RouteItem::showTextEnterItem(const QStringList& items) { adjustSize(); } +void RouteItem::updateHelperItems(const QString& base) { + ui->rule_set_helper->clearSelection(); + current_helper_items.clear(); + for (const auto& item: geo_items) { + if (item.contains(base)) current_helper_items << item; + } + helperModel->setStringList(current_helper_items); + ui->rule_set_helper->setModel(helperModel); +} + +void RouteItem::applyRuleHelperSelect(const QModelIndex& index) { + auto option = ui->rule_set_helper->model()->data(index, Qt::DisplayRole).toString(); + auto currentText = ui->rule_attr_text->toPlainText(); + auto parts = currentText.split('\n'); + parts[parts.size() - 1] = option; + ui->rule_attr_text->setText(parts.join('\n')); +} + void RouteItem::on_new_route_item_clicked() { - auto routeItem = std::make_shared(NekoGui::RouteRule()); + auto routeItem = std::make_shared(); routeItem->name = "rule_" + Int2String(++lastNum); chain->Rules << routeItem; currentIndex = chain->Rules.size() - 1; + ui->rule_name->setText(routeItem->name); + currentIndex = chain->Rules.size()-1; + updateRouteItemsView(); - setDefaultRuleData(""); + updateRuleSection(); } void RouteItem::on_moveup_route_item_clicked() { diff --git a/ui/widget/RouteItem.h b/ui/widget/RouteItem.h index bd74e56..a38415d 100644 --- a/ui/widget/RouteItem.h +++ b/ui/widget/RouteItem.h @@ -2,7 +2,8 @@ #include #include -#include +#include +#include #include "db/RouteEntity.h" @@ -12,7 +13,7 @@ namespace Ui { } QT_END_NAMESPACE -class RouteItem : public QGroupBox { +class RouteItem : public QDialog { Q_OBJECT public: @@ -21,13 +22,19 @@ public: std::shared_ptr chain; signals: - void settingsChanged(const std::shared_ptr routeChain); + void settingsChanged(std::shared_ptr routingChain); private: Ui::RouteItem *ui; int currentIndex = -1; - int lastNum; + int lastNum = 0; + + QStringList geo_items; + + QStringList current_helper_items; + + QStringListModel* helperModel; [[nodiscard]] int getIndexOf(const QString& name) const; @@ -37,11 +44,19 @@ private: void setDefaultRuleData(const QString& currentData); + void updateRuleSection(); + + void updateRulePreview(); + void updateRouteItemsView(); + void updateHelperItems(const QString& base); + + void applyRuleHelperSelect(const QModelIndex& index); + private slots: - void on_ok_button_clicked(); - void on_cancel_button_clicked(); + void accept() override; + void on_new_route_item_clicked(); void on_moveup_route_item_clicked(); void on_movedown_route_item_clicked(); diff --git a/ui/widget/RouteItem.ui b/ui/widget/RouteItem.ui index f433e0f..decaf12 100644 --- a/ui/widget/RouteItem.ui +++ b/ui/widget/RouteItem.ui @@ -1,7 +1,7 @@ RouteItem - + 0 @@ -11,7 +11,7 @@ - GroupBox + Route Profile @@ -81,7 +81,7 @@ - + Rule Attributes @@ -128,9 +128,22 @@ Name_Placeholder + + + + + 190 + 0 + + + + + + + @@ -140,29 +153,16 @@ - - - - - - - Ok - - - - - - - Cancel - - - - - - + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + +